aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/stdlib
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/stdlib')
-rw-r--r--lib/stdlib/AUTHORS32
-rw-r--r--lib/stdlib/Makefile36
-rw-r--r--lib/stdlib/doc/html/.gitignore0
-rw-r--r--lib/stdlib/doc/man3/.gitignore0
-rw-r--r--lib/stdlib/doc/man6/.gitignore0
-rw-r--r--lib/stdlib/doc/pdf/.gitignore0
-rw-r--r--lib/stdlib/doc/src/Makefile176
-rw-r--r--lib/stdlib/doc/src/array.xml482
-rw-r--r--lib/stdlib/doc/src/base64.xml71
-rw-r--r--lib/stdlib/doc/src/beam_lib.xml507
-rw-r--r--lib/stdlib/doc/src/book.xml49
-rw-r--r--lib/stdlib/doc/src/c.xml320
-rw-r--r--lib/stdlib/doc/src/calendar.xml377
-rw-r--r--lib/stdlib/doc/src/dets.xml1255
-rw-r--r--lib/stdlib/doc/src/dict.xml347
-rw-r--r--lib/stdlib/doc/src/digraph.xml601
-rw-r--r--lib/stdlib/doc/src/digraph_utils.xml426
-rw-r--r--lib/stdlib/doc/src/epp.xml123
-rw-r--r--lib/stdlib/doc/src/erl_eval.xml246
-rw-r--r--lib/stdlib/doc/src/erl_expand_records.xml61
-rw-r--r--lib/stdlib/doc/src/erl_id_trans.xml76
-rw-r--r--lib/stdlib/doc/src/erl_internal.xml158
-rw-r--r--lib/stdlib/doc/src/erl_lint.xml159
-rw-r--r--lib/stdlib/doc/src/erl_parse.xml201
-rw-r--r--lib/stdlib/doc/src/erl_pp.xml178
-rw-r--r--lib/stdlib/doc/src/erl_scan.xml417
-rw-r--r--lib/stdlib/doc/src/erl_tar.xml434
-rw-r--r--lib/stdlib/doc/src/ets.xml1811
-rw-r--r--lib/stdlib/doc/src/fascicules.xml18
-rw-r--r--lib/stdlib/doc/src/file_sorter.xml390
-rw-r--r--lib/stdlib/doc/src/filelib.xml217
-rw-r--r--lib/stdlib/doc/src/filename.xml385
-rw-r--r--lib/stdlib/doc/src/gb_sets.xml487
-rw-r--r--lib/stdlib/doc/src/gb_trees.xml367
-rw-r--r--lib/stdlib/doc/src/gen_event.xml641
-rw-r--r--lib/stdlib/doc/src/gen_fsm.xml743
-rw-r--r--lib/stdlib/doc/src/gen_server.xml612
-rw-r--r--lib/stdlib/doc/src/io.xml1026
-rw-r--r--lib/stdlib/doc/src/io_lib.xml300
-rw-r--r--lib/stdlib/doc/src/io_protocol.xml861
-rw-r--r--lib/stdlib/doc/src/lib.xml114
-rw-r--r--lib/stdlib/doc/src/lists.xml1196
-rw-r--r--lib/stdlib/doc/src/log_mf_h.xml79
-rw-r--r--lib/stdlib/doc/src/make.dep40
-rw-r--r--lib/stdlib/doc/src/math.xml112
-rw-r--r--lib/stdlib/doc/src/ms_transform.xml651
-rw-r--r--lib/stdlib/doc/src/notes.xml2704
-rw-r--r--lib/stdlib/doc/src/notes_history.xml395
-rw-r--r--lib/stdlib/doc/src/orddict.xml354
-rw-r--r--lib/stdlib/doc/src/ordsets.xml254
-rw-r--r--lib/stdlib/doc/src/part.xml39
-rw-r--r--lib/stdlib/doc/src/part_notes.xml38
-rw-r--r--lib/stdlib/doc/src/part_notes_history.xml38
-rw-r--r--lib/stdlib/doc/src/pg.xml132
-rw-r--r--lib/stdlib/doc/src/pool.xml148
-rw-r--r--lib/stdlib/doc/src/proc_lib.xml322
-rw-r--r--lib/stdlib/doc/src/proplists.xml386
-rw-r--r--lib/stdlib/doc/src/qlc.xml1486
-rw-r--r--lib/stdlib/doc/src/queue.xml454
-rw-r--r--lib/stdlib/doc/src/random.xml151
-rw-r--r--lib/stdlib/doc/src/re.xml2957
-rw-r--r--lib/stdlib/doc/src/ref_man.xml97
-rw-r--r--lib/stdlib/doc/src/regexp.xml415
-rw-r--r--lib/stdlib/doc/src/sets.xml252
-rw-r--r--lib/stdlib/doc/src/shell.xml810
-rw-r--r--lib/stdlib/doc/src/shell_default.xml70
-rw-r--r--lib/stdlib/doc/src/slave.xml215
-rw-r--r--lib/stdlib/doc/src/sofs.xml1781
-rw-r--r--lib/stdlib/doc/src/stdlib_app.xml81
-rw-r--r--lib/stdlib/doc/src/string.xml415
-rw-r--r--lib/stdlib/doc/src/supervisor.xml495
-rw-r--r--lib/stdlib/doc/src/supervisor_bridge.xml162
-rw-r--r--lib/stdlib/doc/src/sys.xml429
-rw-r--r--lib/stdlib/doc/src/timer.xml300
-rw-r--r--lib/stdlib/doc/src/unicode.xml311
-rw-r--r--lib/stdlib/doc/src/unicode_usage.xml289
-rw-r--r--lib/stdlib/doc/src/user_guide.gifbin0 -> 1581 bytes
-rw-r--r--lib/stdlib/doc/src/ushell1.gifbin0 -> 13870 bytes
-rw-r--r--lib/stdlib/doc/src/ushell1.ps1196
-rw-r--r--lib/stdlib/doc/src/ushell2.gifbin0 -> 4384 bytes
-rw-r--r--lib/stdlib/doc/src/ushell2.ps404
-rw-r--r--lib/stdlib/doc/src/ushell3.gifbin0 -> 8239 bytes
-rw-r--r--lib/stdlib/doc/src/ushell3.ps662
-rw-r--r--lib/stdlib/doc/src/win32reg.xml276
-rw-r--r--lib/stdlib/doc/src/zip.xml463
-rw-r--r--lib/stdlib/ebin/.gitignore0
-rw-r--r--lib/stdlib/examples/Makefile52
-rw-r--r--lib/stdlib/examples/erl_id_trans.erl518
-rw-r--r--lib/stdlib/include/erl_bits.hrl48
-rw-r--r--lib/stdlib/include/erl_compile.hrl43
-rw-r--r--lib/stdlib/include/ms_transform.hrl19
-rw-r--r--lib/stdlib/include/qlc.hrl19
-rw-r--r--lib/stdlib/include/zip.hrl31
-rw-r--r--lib/stdlib/info2
-rw-r--r--lib/stdlib/src/Makefile227
-rw-r--r--lib/stdlib/src/array.erl1926
-rw-r--r--lib/stdlib/src/base64.erl304
-rw-r--r--lib/stdlib/src/beam_lib.erl1027
-rw-r--r--lib/stdlib/src/c.erl700
-rw-r--r--lib/stdlib/src/calendar.erl459
-rw-r--r--lib/stdlib/src/dets.erl2989
-rw-r--r--lib/stdlib/src/dets.hrl126
-rw-r--r--lib/stdlib/src/dets_server.erl402
-rw-r--r--lib/stdlib/src/dets_sup.erl31
-rw-r--r--lib/stdlib/src/dets_utils.erl1801
-rw-r--r--lib/stdlib/src/dets_v8.erl1591
-rw-r--r--lib/stdlib/src/dets_v9.erl2761
-rw-r--r--lib/stdlib/src/dict.erl547
-rw-r--r--lib/stdlib/src/digraph.erl570
-rw-r--r--lib/stdlib/src/digraph_utils.erl338
-rw-r--r--lib/stdlib/src/edlin.erl575
-rw-r--r--lib/stdlib/src/edlin_expand.erl168
-rw-r--r--lib/stdlib/src/epp.erl1146
-rw-r--r--lib/stdlib/src/erl_bits.erl186
-rw-r--r--lib/stdlib/src/erl_compile.erl233
-rw-r--r--lib/stdlib/src/erl_eval.erl1108
-rw-r--r--lib/stdlib/src/erl_expand_records.erl808
-rw-r--r--lib/stdlib/src/erl_internal.erl351
-rw-r--r--lib/stdlib/src/erl_lint.erl3489
-rw-r--r--lib/stdlib/src/erl_parse.yrl1028
-rw-r--r--lib/stdlib/src/erl_posix_msg.erl166
-rw-r--r--lib/stdlib/src/erl_pp.erl992
-rw-r--r--lib/stdlib/src/erl_scan.erl1307
-rw-r--r--lib/stdlib/src/erl_tar.erl959
-rw-r--r--lib/stdlib/src/error_logger_file_h.erl265
-rw-r--r--lib/stdlib/src/error_logger_tty_h.erl261
-rw-r--r--lib/stdlib/src/escript.erl694
-rw-r--r--lib/stdlib/src/ets.erl1269
-rw-r--r--lib/stdlib/src/eval_bits.erl348
-rw-r--r--lib/stdlib/src/file_sorter.erl1500
-rw-r--r--lib/stdlib/src/filelib.erl443
-rw-r--r--lib/stdlib/src/filename.erl787
-rw-r--r--lib/stdlib/src/gb_sets.erl812
-rw-r--r--lib/stdlib/src/gb_trees.erl515
-rw-r--r--lib/stdlib/src/gen.erl320
-rw-r--r--lib/stdlib/src/gen_event.erl721
-rw-r--r--lib/stdlib/src/gen_fsm.erl623
-rw-r--r--lib/stdlib/src/gen_server.erl853
-rw-r--r--lib/stdlib/src/io.erl578
-rw-r--r--lib/stdlib/src/io_lib.erl688
-rw-r--r--lib/stdlib/src/io_lib_format.erl678
-rw-r--r--lib/stdlib/src/io_lib_fread.erl466
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl646
-rw-r--r--lib/stdlib/src/lib.erl452
-rw-r--r--lib/stdlib/src/lists.erl2462
-rw-r--r--lib/stdlib/src/log_mf_h.erl202
-rw-r--r--lib/stdlib/src/math.erl25
-rw-r--r--lib/stdlib/src/ms_transform.erl992
-rw-r--r--lib/stdlib/src/orddict.erl173
-rw-r--r--lib/stdlib/src/ordsets.erl220
-rw-r--r--lib/stdlib/src/otp_internal.erl384
-rw-r--r--lib/stdlib/src/pg.erl172
-rw-r--r--lib/stdlib/src/pool.erl212
-rw-r--r--lib/stdlib/src/proc_lib.erl624
-rw-r--r--lib/stdlib/src/proplists.erl686
-rw-r--r--lib/stdlib/src/qlc.erl3540
-rw-r--r--lib/stdlib/src/qlc_pt.erl2746
-rw-r--r--lib/stdlib/src/queue.erl487
-rw-r--r--lib/stdlib/src/random.erl124
-rw-r--r--lib/stdlib/src/re.erl751
-rw-r--r--lib/stdlib/src/regexp.erl490
-rw-r--r--lib/stdlib/src/sets.erl417
-rw-r--r--lib/stdlib/src/shell.erl1440
-rw-r--r--lib/stdlib/src/shell_default.erl131
-rw-r--r--lib/stdlib/src/slave.erl332
-rw-r--r--lib/stdlib/src/sofs.erl2502
-rw-r--r--lib/stdlib/src/stdlib.app.src105
-rw-r--r--lib/stdlib/src/stdlib.appup.src1
-rw-r--r--lib/stdlib/src/string.erl394
-rw-r--r--lib/stdlib/src/supervisor.erl889
-rw-r--r--lib/stdlib/src/supervisor_bridge.erl116
-rw-r--r--lib/stdlib/src/sys.erl391
-rw-r--r--lib/stdlib/src/timer.erl364
-rw-r--r--lib/stdlib/src/unicode.erl677
-rw-r--r--lib/stdlib/src/win32reg.erl386
-rw-r--r--lib/stdlib/src/zip.erl1600
-rw-r--r--lib/stdlib/test/Makefile134
-rw-r--r--lib/stdlib/test/array_SUITE.erl816
-rw-r--r--lib/stdlib/test/base64_SUITE.erl257
-rw-r--r--lib/stdlib/test/beam_lib_SUITE.erl761
-rw-r--r--lib/stdlib/test/c_SUITE.erl116
-rw-r--r--lib/stdlib/test/c_SUITE_data/m.erl25
-rw-r--r--lib/stdlib/test/calendar_SUITE.erl251
-rw-r--r--lib/stdlib/test/dets_SUITE.erl4136
-rw-r--r--lib/stdlib/test/dets_SUITE_data/dets_test_v8b.detsbin0 -> 37396 bytes
-rw-r--r--lib/stdlib/test/dets_SUITE_data/dets_test_v8b_little_endian.detsbin0 -> 37396 bytes
-rw-r--r--lib/stdlib/test/dets_SUITE_data/version_9a.detsbin0 -> 5917 bytes
-rw-r--r--lib/stdlib/test/dets_SUITE_data/version_9b_phash.datbin0 -> 5981 bytes
-rw-r--r--lib/stdlib/test/dets_SUITE_data/version_r2d.detsbin0 -> 33885 bytes
-rw-r--r--lib/stdlib/test/dets_SUITE_data/version_r3b02.detsbin0 -> 34484 bytes
-rw-r--r--lib/stdlib/test/dict_SUITE.erl133
-rw-r--r--lib/stdlib/test/dict_test_lib.erl83
-rw-r--r--lib/stdlib/test/digraph_SUITE.erl520
-rw-r--r--lib/stdlib/test/digraph_utils_SUITE.erl316
-rw-r--r--lib/stdlib/test/dummy1_h.erl70
-rw-r--r--lib/stdlib/test/dummy_h.erl88
-rw-r--r--lib/stdlib/test/epp_SUITE.erl1148
-rw-r--r--lib/stdlib/test/epp_SUITE_data/mac.erl46
-rw-r--r--lib/stdlib/test/epp_SUITE_data/mac2.erl38
-rw-r--r--lib/stdlib/test/epp_SUITE_data/mac3.erl36
-rw-r--r--lib/stdlib/test/epp_SUITE_data/pmod.erl25
-rw-r--r--lib/stdlib/test/epp_SUITE_data/variable_1.erl24
-rw-r--r--lib/stdlib/test/epp_SUITE_data/variable_1_include.hrl19
-rw-r--r--lib/stdlib/test/epp_SUITE_data/variable_1_include_dir.hrl19
-rw-r--r--lib/stdlib/test/erl_eval_SUITE.erl1399
-rw-r--r--lib/stdlib/test/erl_eval_helper.erl28
-rw-r--r--lib/stdlib/test/erl_expand_records_SUITE.erl823
-rw-r--r--lib/stdlib/test/erl_internal_SUITE.erl69
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl2783
-rw-r--r--lib/stdlib/test/erl_lint_SUITE_data/format.erl55
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl1073
-rw-r--r--lib/stdlib/test/erl_scan_SUITE.erl1214
-rw-r--r--lib/stdlib/test/error_logger_forwarder.erl48
-rw-r--r--lib/stdlib/test/escript_SUITE.erl540
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/ebin/archive_script_dict.app12
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/priv/archive_script_dict.txt1
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict.erl125
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_app.erl29
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_sup.erl39
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/ebin/archive_script_dummy.app10
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy.erl29
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_app.erl29
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_sup.erl33
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main.erl61
-rw-r--r--lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main2.erl60
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/compile_error12
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/emulator_flags11
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/factorial11
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/factorial_compile12
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/factorial_compile_main13
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/factorial_epp17
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/factorial_warning13
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/filesize11
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/lint_error14
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/strange.name7
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/tail_rec25
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/test_script_name5
-rwxr-xr-xlib/stdlib/test/escript_SUITE_data/trap_exit6
-rw-r--r--lib/stdlib/test/ets_SUITE.erl5355
-rw-r--r--lib/stdlib/test/ets_SUITE_data/dummy.txt1
-rw-r--r--lib/stdlib/test/ets_tough_SUITE.erl1093
-rw-r--r--lib/stdlib/test/file_sorter_SUITE.erl1345
-rw-r--r--lib/stdlib/test/filelib_SUITE.erl241
-rw-r--r--lib/stdlib/test/filename_SUITE.erl459
-rw-r--r--lib/stdlib/test/fixtable_SUITE.erl414
-rw-r--r--lib/stdlib/test/format_SUITE.erl51
-rw-r--r--lib/stdlib/test/gen_event_SUITE.erl846
-rw-r--r--lib/stdlib/test/gen_fsm_SUITE.erl838
-rw-r--r--lib/stdlib/test/gen_server_SUITE.erl1049
-rw-r--r--lib/stdlib/test/id_transform_SUITE.erl398
-rw-r--r--lib/stdlib/test/id_transform_SUITE_data/External.hrl41
-rw-r--r--lib/stdlib/test/id_transform_SUITE_data/m.hrl30
-rw-r--r--lib/stdlib/test/id_transform_SUITE_data/m_i.hrl29
-rw-r--r--lib/stdlib/test/id_transform_SUITE_data/oe_ex.hrl29
-rw-r--r--lib/stdlib/test/io_SUITE.erl1900
-rw-r--r--lib/stdlib/test/io_proto_SUITE.erl1824
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/external_utf16_big_bom.datbin0 -> 22 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/external_utf16_little_bom.datbin0 -> 22 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/external_utf8_bom.dat1
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_latin1.dat2
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big.datbin0 -> 98 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big_bom.datbin0 -> 100 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little.datbin0 -> 98 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little_bom.datbin0 -> 100 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big.datbin0 -> 196 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big_bom.datbin0 -> 200 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little.datbin0 -> 196 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little_bom.datbin0 -> 200 bytes
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf8.dat1
-rw-r--r--lib/stdlib/test/io_proto_SUITE_data/testdata_utf8_bom.dat1
-rw-r--r--lib/stdlib/test/lists_SUITE.erl2657
-rw-r--r--lib/stdlib/test/log_mf_h_SUITE.erl92
-rw-r--r--lib/stdlib/test/ms_transform_SUITE.erl730
-rw-r--r--lib/stdlib/test/naughty_child.erl101
-rw-r--r--lib/stdlib/test/proc_lib_SUITE.erl344
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl8179
-rw-r--r--lib/stdlib/test/queue_SUITE.erl604
-rw-r--r--lib/stdlib/test/random_SUITE.erl110
-rw-r--r--lib/stdlib/test/random_iolist.erl195
-rw-r--r--lib/stdlib/test/random_unicode_list.erl270
-rw-r--r--lib/stdlib/test/re_SUITE.erl526
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput16608
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput10669
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput29388
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput3163
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput41074
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput51611
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput61682
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput77215
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput81287
-rw-r--r--lib/stdlib/test/re_SUITE_data/testoutput91643
-rw-r--r--lib/stdlib/test/re_testoutput1_replacement_test.erl18596
-rw-r--r--lib/stdlib/test/re_testoutput1_split_test.erl29418
-rw-r--r--lib/stdlib/test/run_pcre_tests.erl1201
-rw-r--r--lib/stdlib/test/select_SUITE.erl804
-rw-r--r--lib/stdlib/test/sets_SUITE.erl495
-rw-r--r--lib/stdlib/test/sets_test_lib.erl124
-rw-r--r--lib/stdlib/test/shell_SUITE.erl2822
-rw-r--r--lib/stdlib/test/slave_SUITE.erl261
-rw-r--r--lib/stdlib/test/sofs_SUITE.erl2374
-rw-r--r--lib/stdlib/test/stdlib.cover10
-rw-r--r--lib/stdlib/test/stdlib.spec4
-rw-r--r--lib/stdlib/test/stdlib.spec.vxworks8
-rw-r--r--lib/stdlib/test/stdlib_SUITE.erl64
-rw-r--r--lib/stdlib/test/string_SUITE.erl511
-rw-r--r--lib/stdlib/test/suite_release.exclude4
-rw-r--r--lib/stdlib/test/supervisor_1.erl79
-rw-r--r--lib/stdlib/test/supervisor_SUITE.erl1203
-rw-r--r--lib/stdlib/test/supervisor_bridge_SUITE.erl178
-rw-r--r--lib/stdlib/test/sys_SUITE.erl173
-rw-r--r--lib/stdlib/test/tar_SUITE.erl718
-rw-r--r--lib/stdlib/test/tar_SUITE_data/bad_checksum.tarbin0 -> 2560 bytes
-rw-r--r--lib/stdlib/test/tar_SUITE_data/bad_even_shorter.tarbin0 -> 567 bytes
-rw-r--r--lib/stdlib/test/tar_SUITE_data/bad_octal.tarbin0 -> 2560 bytes
-rw-r--r--lib/stdlib/test/tar_SUITE_data/bad_too_short.tarbin0 -> 1072 bytes
-rw-r--r--lib/stdlib/test/tar_SUITE_data/cooked_tar_problem.tar.gzbin0 -> 7099 bytes
-rw-r--r--lib/stdlib/test/tar_SUITE_data/long_names.tarbin0 -> 7168 bytes
-rwxr-xr-xlib/stdlib/test/tar_SUITE_data/make_tar21
-rw-r--r--lib/stdlib/test/tar_SUITE_data/no_fancy_stuff.tarbin0 -> 17408 bytes
-rw-r--r--lib/stdlib/test/timer_SUITE.erl391
-rw-r--r--lib/stdlib/test/timer_simple_SUITE.erl551
-rw-r--r--lib/stdlib/test/unicode_SUITE.erl1241
-rw-r--r--lib/stdlib/test/win32reg_SUITE.erl89
-rw-r--r--lib/stdlib/test/y2k_SUITE.erl185
-rw-r--r--lib/stdlib/test/zip_SUITE.erl759
-rw-r--r--lib/stdlib/test/zip_SUITE_data/META-INF/MANIFEST.MF3
-rw-r--r--lib/stdlib/test/zip_SUITE_data/abc.txt23
-rw-r--r--lib/stdlib/test/zip_SUITE_data/abc.zipbin0 -> 2358 bytes
-rw-r--r--lib/stdlib/test/zip_SUITE_data/bad_central_directory.zipbin0 -> 847 bytes
-rw-r--r--lib/stdlib/test/zip_SUITE_data/bad_crc.zipbin0 -> 847 bytes
-rw-r--r--lib/stdlib/test/zip_SUITE_data/bad_eocd.zipbin0 -> 847 bytes
-rw-r--r--lib/stdlib/test/zip_SUITE_data/bad_file_header.zipbin0 -> 847 bytes
-rw-r--r--lib/stdlib/test/zip_SUITE_data/emptyFile0
-rw-r--r--lib/stdlib/test/zip_SUITE_data/quotes/rain.txt1
-rw-r--r--lib/stdlib/test/zip_SUITE_data/test.jarbin0 -> 471 bytes
-rw-r--r--lib/stdlib/test/zip_SUITE_data/test.txt1
-rw-r--r--lib/stdlib/test/zip_SUITE_data/wikipedia.txt30
-rw-r--r--lib/stdlib/vsn.mk2
338 files changed, 242882 insertions, 0 deletions
diff --git a/lib/stdlib/AUTHORS b/lib/stdlib/AUTHORS
new file mode 100644
index 0000000000..417d7015d9
--- /dev/null
+++ b/lib/stdlib/AUTHORS
@@ -0,0 +1,32 @@
+Original Authors and Contributors:
+
+Joe Armstrong
+Robert Virding
+Mike Williams
+Claes Wikstr�m
+Tony Rogvall
+Magnus Fr�berg
+Martin Bj�rklund
+Bj�rn Gustavsson
+Patrik Nyblom
+Arndt Jonasson
+Kenneth Lundin
+Peter H�gfeldt
+Torbj�rn T�rnkvist
+Sebastian Strollo
+Carl Wilhelm Welin
+Lennart �hman
+Hans Bolinder
+Raimo Niskanen
+Rickard Green
+Gunilla Arendt
+Dan Gudmundsson
+Peter Andersson
+Ingela Anderton
+Jakob Cederlund
+
+and probably others...
+
+Open source contributors:
+Richard Carlsson and Sven-Olof Nystr�m: gb_trees and gb_sets
+Richard Carlsson (several modules)
diff --git a/lib/stdlib/Makefile b/lib/stdlib/Makefile
new file mode 100644
index 0000000000..01d299fd32
--- /dev/null
+++ b/lib/stdlib/Makefile
@@ -0,0 +1,36 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1996-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+#
+# Macros
+#
+
+SUB_DIRECTORIES = src doc/src examples
+
+include vsn.mk
+VSN = $(STDLIB_VSN)
+
+SPECIAL_TARGETS =
+
+#
+# Default Subdir Targets
+#
+include $(ERL_TOP)/make/otp_subdir.mk
diff --git a/lib/stdlib/doc/html/.gitignore b/lib/stdlib/doc/html/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/doc/html/.gitignore
diff --git a/lib/stdlib/doc/man3/.gitignore b/lib/stdlib/doc/man3/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/doc/man3/.gitignore
diff --git a/lib/stdlib/doc/man6/.gitignore b/lib/stdlib/doc/man6/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/doc/man6/.gitignore
diff --git a/lib/stdlib/doc/pdf/.gitignore b/lib/stdlib/doc/pdf/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/doc/pdf/.gitignore
diff --git a/lib/stdlib/doc/src/Makefile b/lib/stdlib/doc/src/Makefile
new file mode 100644
index 0000000000..13b9b2ff18
--- /dev/null
+++ b/lib/stdlib/doc/src/Makefile
@@ -0,0 +1,176 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../../vsn.mk
+VSN=$(STDLIB_VSN)
+APPLICATION=stdlib
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+XML_APPLICATION_FILES = ref_man.xml
+
+XML_REF3_FILES = \
+ array.xml \
+ base64.xml \
+ beam_lib.xml \
+ c.xml \
+ calendar.xml \
+ dets.xml \
+ dict.xml \
+ digraph.xml \
+ digraph_utils.xml \
+ epp.xml \
+ erl_eval.xml \
+ erl_expand_records.xml \
+ erl_id_trans.xml \
+ erl_internal.xml \
+ erl_lint.xml \
+ erl_parse.xml \
+ erl_pp.xml \
+ erl_scan.xml \
+ erl_tar.xml \
+ ets.xml \
+ file_sorter.xml \
+ filelib.xml \
+ filename.xml \
+ gb_sets.xml \
+ gb_trees.xml \
+ gen_event.xml \
+ gen_fsm.xml \
+ gen_server.xml \
+ io.xml \
+ io_lib.xml \
+ lib.xml \
+ lists.xml \
+ log_mf_h.xml \
+ math.xml \
+ ms_transform.xml \
+ orddict.xml \
+ ordsets.xml \
+ pg.xml \
+ pool.xml \
+ proc_lib.xml \
+ proplists.xml \
+ qlc.xml \
+ queue.xml \
+ random.xml \
+ re.xml \
+ regexp.xml \
+ sets.xml \
+ shell.xml \
+ shell_default.xml \
+ slave.xml \
+ sofs.xml \
+ string.xml \
+ supervisor.xml \
+ supervisor_bridge.xml \
+ sys.xml \
+ timer.xml \
+ unicode.xml \
+ win32reg.xml \
+ zip.xml
+
+XML_REF6_FILES = stdlib_app.xml
+
+XML_PART_FILES = part.xml part_notes.xml part_notes_history.xml
+XML_CHAPTER_FILES = io_protocol.xml unicode_usage.xml notes.xml notes_history.xml
+GIF_FILES = ushell1.gif ushell2.gif ushell3.gif
+
+BOOK_FILES = book.xml
+
+XML_FILES = \
+ $(BOOK_FILES) $(XML_CHAPTER_FILES) \
+ $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) $(XML_APPLICATION_FILES)
+
+# ----------------------------------------------------
+
+HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
+ $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) \
+ $(GIF_FILES:%.gif=$(HTMLDIR)/%.gif)
+
+INFO_FILE = ../../info
+
+MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
+MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
+
+HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
+
+TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+XML_FLAGS +=
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+$(HTMLDIR)/%.gif: %.gif
+ $(INSTALL_DATA) $< $@
+
+docs: pdf html man
+
+$(TOP_PDF_FILE): $(XML_FILES)
+
+pdf: $(TOP_PDF_FILE)
+
+html: gifs $(HTML_REF_MAN_FILE)
+
+man: $(MAN3_FILES) $(MAN6_FILES)
+
+gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
+
+debug opt:
+
+clean clean_docs:
+ rm -rf $(HTMLDIR)/*
+ rm -f $(MAN3DIR)/*
+ rm -f $(MAN6DIR)/*
+ rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
+ rm -f errs core *~
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_docs_spec: docs
+ $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf
+ $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf
+ $(INSTALL_DIR) $(RELSYSDIR)/doc/html
+ $(INSTALL_DATA) $(HTMLDIR)/* \
+ $(RELSYSDIR)/doc/html
+ $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR)
+ $(INSTALL_DIR) $(RELEASE_PATH)/man/man3
+ $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3
+ $(INSTALL_DIR) $(RELEASE_PATH)/man/man6
+ $(INSTALL_DATA) $(MAN6_FILES) $(RELEASE_PATH)/man/man6
+
+release_spec:
diff --git a/lib/stdlib/doc/src/array.xml b/lib/stdlib/doc/src/array.xml
new file mode 100644
index 0000000000..5c3ac6a2a9
--- /dev/null
+++ b/lib/stdlib/doc/src/array.xml
@@ -0,0 +1,482 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+<erlref>
+<header>
+ <copyright>
+ <year>2007</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+<title>array</title>
+<prepared></prepared>
+<responsible></responsible>
+<docno>1</docno>
+<approved></approved>
+<checked></checked>
+<date></date>
+<rev>A</rev>
+<file>array.xml</file></header>
+<module>array</module>
+<modulesummary>Functional, extendible arrays.</modulesummary>
+<description>
+<p>Functional, extendible arrays. Arrays can have fixed size, or
+can grow automatically as needed. A default value is used for entries
+that have not been explicitly set.</p>
+
+ <p>Arrays uses <em>zero</em> based indexing. This is a deliberate design
+choice and differs from other erlang datastructures, e.g. tuples.</p>
+
+ <p>Unless specified by the user when the array is created, the default
+ value is the atom <c>undefined</c>. There is no difference between an
+ unset entry and an entry which has been explicitly set to the same
+ value as the default one (cf. <seealso marker="#reset-2">reset/2</seealso>). If you need to
+differentiate between unset and set entries, you must make sure that
+the default value cannot be confused with the values of set entries.</p>
+
+ <p>The array never shrinks automatically; if an index <c>I</c> has been used
+ successfully to set an entry, all indices in the range [0,<c>I</c>] will
+ stay accessible unless the array size is explicitly changed by
+ calling <seealso marker="#resize-2">resize/2</seealso>.</p>
+
+ <p>Examples:
+ </p><pre> %% Create a fixed-size array with entries 0-9 set to 'undefined'
+ A0 = array:new(10).
+ 10 = array:size(A0).
+
+ %% Create an extendible array and set entry 17 to 'true',
+ %% causing the array to grow automatically
+ A1 = array:set(17, true, array:new()).
+ 18 = array:size(A1).
+
+ %% Read back a stored value
+ true = array:get(17, A1).
+
+ %% Accessing an unset entry returns the default value
+ undefined = array:get(3, A1).
+
+ %% Accessing an entry beyond the last set entry also returns the
+ %% default value, if the array does not have fixed size
+ undefined = array:get(18, A1).
+
+ %% "sparse" functions ignore default-valued entries
+ A2 = array:set(4, false, A1).
+ [{4, false}, {17, true}] = array:sparse_to_orddict(A2).
+
+ %% An extendible array can be made fixed-size later
+ A3 = array:fix(A2).
+
+ %% A fixed-size array does not grow automatically and does not
+ %% allow accesses beyond the last set entry
+ {'EXIT',{badarg,_}} = (catch array:set(18, true, A3)).
+ {'EXIT',{badarg,_}} = (catch array:get(18, A3)).</pre></description>
+<section><title>DATA TYPES</title><marker id="types"/>
+
+<taglist>
+<tag><c>array()</c></tag>
+<item><marker id="type-array"/>
+<p>A functional, extendible array. The representation is
+ not documented and is subject to change without notice. Note that
+ arrays cannot be directly compared for equality.</p>
+</item>
+</taglist></section>
+<funcs>
+<func>
+<name>default(Array::array()) -&gt; term()</name>
+<fsummary>Get the value used for uninitialized entries.</fsummary>
+
+<desc><marker id="default-1"/>
+
+<p>Get the value used for uninitialized entries.
+ </p>
+<p><em>See also:</em> <seealso marker="#new-2">new/2</seealso>.</p>
+</desc></func>
+<func>
+<name>fix(Array::array()) -&gt; array()</name>
+<fsummary>Fix the size of the array.</fsummary>
+
+<desc><marker id="fix-1"/>
+
+<p>Fix the size of the array. This prevents it from growing
+ automatically upon insertion; see also <seealso marker="#set-3">set/3</seealso>.</p>
+<p><em>See also:</em> <seealso marker="#relax-1">relax/1</seealso>.</p>
+</desc></func>
+<func>
+<name>foldl(Function, InitialAcc::term(), Array::array()) -&gt; term()</name>
+<fsummary>Fold the elements of the array using the given function and
+ initial accumulator value.</fsummary>
+<type>
+<v>Function = (Index::integer(), Value::term(), Acc::term()) -&gt; term()</v></type>
+<desc><marker id="foldl-3"/>
+
+<p>Fold the elements of the array using the given function and
+ initial accumulator value. The elements are visited in order from the
+ lowest index to the highest. If <c>Function</c> is not a function, the
+ call fails with reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#foldr-3">foldr/3</seealso>, <seealso marker="#map-2">map/2</seealso>, <seealso marker="#sparse_foldl-3">sparse_foldl/3</seealso>.</p>
+</desc></func>
+<func>
+<name>foldr(Function, InitialAcc::term(), Array::array()) -&gt; term()</name>
+<fsummary>Fold the elements of the array right-to-left using the given
+ function and initial accumulator value.</fsummary>
+<type>
+<v>Function = (Index::integer(), Value::term(), Acc::term()) -&gt; term()</v></type>
+<desc><marker id="foldr-3"/>
+
+<p>Fold the elements of the array right-to-left using the given
+ function and initial accumulator value. The elements are visited in
+ order from the highest index to the lowest. If <c>Function</c> is not a
+ function, the call fails with reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#foldl-3">foldl/3</seealso>, <seealso marker="#map-2">map/2</seealso>.</p>
+</desc></func>
+<func>
+<name>from_list(List::list()) -&gt; array()</name>
+<fsummary>Equivalent to from_list(List, undefined).
+</fsummary>
+
+<desc><marker id="from_list-1"/>
+<p>Equivalent to <seealso marker="#from_list-2">from_list(List, undefined)</seealso>.</p>
+</desc></func>
+<func>
+<name>from_list(List::list(), Default::term()) -&gt; array()</name>
+<fsummary>Convert a list to an extendible array.</fsummary>
+
+<desc><marker id="from_list-2"/>
+
+<p>Convert a list to an extendible array. <c>Default</c> is used as the value
+ for uninitialized entries of the array. If <c>List</c> is not a proper list,
+ the call fails with reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#new-2">new/2</seealso>, <seealso marker="#to_list-1">to_list/1</seealso>.</p>
+</desc></func>
+<func>
+<name>from_orddict(Orddict::list()) -&gt; array()</name>
+<fsummary>Equivalent to from_orddict(Orddict, undefined).
+</fsummary>
+
+<desc><marker id="from_orddict-1"/>
+<p>Equivalent to <seealso marker="#from_orddict-2">from_orddict(Orddict, undefined)</seealso>.</p>
+</desc></func>
+<func>
+<name>from_orddict(List::list(), Default::term()) -&gt; array()</name>
+<fsummary>Convert an ordered list of pairs {Index, Value} to a
+ corresponding extendible array.</fsummary>
+
+<desc><marker id="from_orddict-2"/>
+
+<p>Convert an ordered list of pairs <c>{Index, Value}</c> to a
+ corresponding extendible array. <c>Default</c> is used as the value for
+ uninitialized entries of the array. If <c>List</c> is not a proper,
+ ordered list of pairs whose first elements are nonnegative
+ integers, the call fails with reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#new-2">new/2</seealso>, <seealso marker="#to_orddict-1">to_orddict/1</seealso>.</p>
+</desc></func>
+<func>
+<name>get(I::integer(), Array::array()) -&gt; term()</name>
+<fsummary>Get the value of entry I.</fsummary>
+
+<desc><marker id="get-2"/>
+
+<p>Get the value of entry <c>I</c>. If <c>I</c> is not a nonnegative
+ integer, or if the array has fixed size and <c>I</c> is larger than the
+ maximum index, the call fails with reason <c>badarg</c>.</p>
+
+ <p>If the array does not have fixed size, this function will return the
+ default value for any index <c>I</c> greater than <c>size(Array)-1</c>.</p>
+<p><em>See also:</em> <seealso marker="#set-3">set/3</seealso>.</p>
+</desc></func>
+<func>
+<name>is_array(X::term()) -&gt; bool()</name>
+<fsummary>Returns true if X appears to be an array, otherwise false.</fsummary>
+
+<desc><marker id="is_array-1"/>
+
+<p>Returns <c>true</c> if <c>X</c> appears to be an array, otherwise <c>false</c>.
+ Note that the check is only shallow; there is no guarantee that <c>X</c>
+ is a well-formed array representation even if this function returns
+ <c>true</c>.</p>
+</desc></func>
+<func>
+<name>is_fix(Array::array()) -&gt; bool()</name>
+<fsummary>Check if the array has fixed size.</fsummary>
+
+<desc><marker id="is_fix-1"/>
+
+<p>Check if the array has fixed size.
+ Returns <c>true</c> if the array is fixed, otherwise <c>false</c>.</p>
+<p><em>See also:</em> <seealso marker="#fix-1">fix/1</seealso>.</p>
+</desc></func>
+<func>
+<name>map(Function, Array::array()) -&gt; array()</name>
+<fsummary>Map the given function onto each element of the array.</fsummary>
+<type>
+<v>Function = (Index::integer(), Value::term()) -&gt; term()</v></type>
+<desc><marker id="map-2"/>
+
+<p>Map the given function onto each element of the array. The
+ elements are visited in order from the lowest index to the highest.
+ If <c>Function</c> is not a function, the call fails with reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#foldl-3">foldl/3</seealso>, <seealso marker="#foldr-3">foldr/3</seealso>, <seealso marker="#sparse_map-2">sparse_map/2</seealso>.</p>
+</desc></func>
+<func>
+<name>new() -&gt; array()</name>
+<fsummary>Create a new, extendible array with initial size zero.</fsummary>
+
+<desc><marker id="new-0"/>
+
+<p>Create a new, extendible array with initial size zero.</p>
+<p><em>See also:</em> <seealso marker="#new-1">new/1</seealso>, <seealso marker="#new-2">new/2</seealso>.</p>
+</desc></func>
+<func>
+<name>new(Options::term()) -&gt; array()</name>
+<fsummary>Create a new array according to the given options.</fsummary>
+
+<desc><marker id="new-1"/>
+
+<p>Create a new array according to the given options. By default,
+the array is extendible and has initial size zero. Array indices
+start at 0.</p>
+
+ <p><c>Options</c> is a single term or a list of terms, selected from the
+ following:
+ </p><taglist>
+ <tag><c>N::integer()</c> or <c>{size, N::integer()}</c></tag>
+ <item><p>Specifies the initial size of the array; this also implies
+ <c>{fixed, true}</c>. If <c>N</c> is not a nonnegative integer, the call
+ fails with reason <c>badarg</c>.</p></item>
+ <tag><c>fixed</c> or <c>{fixed, true}</c></tag>
+ <item><p>Creates a fixed-size array; see also <seealso marker="#fix-1">fix/1</seealso>.</p></item>
+ <tag><c>{fixed, false}</c></tag>
+ <item><p>Creates an extendible (non fixed-size) array.</p></item>
+ <tag><c>{default, Value}</c></tag>
+ <item><p>Sets the default value for the array to <c>Value</c>.</p></item>
+ </taglist><p>
+Options are processed in the order they occur in the list, i.e.,
+later options have higher precedence.</p>
+
+ <p>The default value is used as the value of uninitialized entries, and
+cannot be changed once the array has been created.</p>
+
+ <p>Examples:
+ </p><pre> array:new(100)</pre><p> creates a fixed-size array of size 100.
+ </p><pre> array:new({default,0})</pre><p> creates an empty, extendible array
+ whose default value is 0.
+ </p><pre> array:new([{size,10},{fixed,false},{default,-1}])</pre><p> creates an
+ extendible array with initial size 10 whose default value is -1.
+ </p>
+<p><em>See also:</em> <seealso marker="#fix-1">fix/1</seealso>, <seealso marker="#from_list-2">from_list/2</seealso>, <seealso marker="#get-2">get/2</seealso>, <seealso marker="#new-0">new/0</seealso>, <seealso marker="#new-2">new/2</seealso>, <seealso marker="#set-3">set/3</seealso>.</p>
+</desc></func>
+<func>
+<name>new(Size::integer(), Options::term()) -&gt; array()</name>
+<fsummary>Create a new array according to the given size and options.</fsummary>
+
+<desc><marker id="new-2"/>
+
+<p>Create a new array according to the given size and options. If
+ <c>Size</c> is not a nonnegative integer, the call fails with reason
+ <c>badarg</c>. By default, the array has fixed size. Note that any size
+ specifications in <c>Options</c> will override the <c>Size</c> parameter.</p>
+
+ <p>If <c>Options</c> is a list, this is simply equivalent to <c>new([{size,
+ Size} | Options]</c>, otherwise it is equivalent to <c>new([{size, Size} |
+ [Options]]</c>. However, using this function directly is more efficient.</p>
+
+ <p>Example:
+ </p><pre> array:new(100, {default,0})</pre><p> creates a fixed-size array of size
+ 100, whose default value is 0.
+ </p>
+<p><em>See also:</em> <seealso marker="#new-1">new/1</seealso>.</p>
+</desc></func>
+<func>
+<name>relax(Array::array()) -&gt; array()</name>
+<fsummary>Make the array resizable.</fsummary>
+
+<desc><marker id="relax-1"/>
+
+<p>Make the array resizable. (Reverses the effects of <seealso marker="#fix-1">fix/1</seealso>.)</p>
+<p><em>See also:</em> <seealso marker="#fix-1">fix/1</seealso>.</p>
+</desc></func>
+<func>
+<name>reset(I::integer(), Array::array()) -&gt; array()</name>
+<fsummary>Reset entry I to the default value for the array.</fsummary>
+
+<desc><marker id="reset-2"/>
+
+<p>Reset entry <c>I</c> to the default value for the array.
+ If the value of entry <c>I</c> is the default value the array will be
+ returned unchanged. Reset will never change size of the array.
+ Shrinking can be done explicitly by calling <seealso marker="#resize-2">resize/2</seealso>.</p>
+
+ <p>If <c>I</c> is not a nonnegative integer, or if the array has fixed size
+ and <c>I</c> is larger than the maximum index, the call fails with reason
+ <c>badarg</c>; cf. <seealso marker="#set-3">set/3</seealso>
+ </p>
+<p><em>See also:</em> <seealso marker="#new-2">new/2</seealso>, <seealso marker="#set-3">set/3</seealso>.</p>
+</desc></func>
+<func>
+<name>resize(Array::array()) -&gt; array()</name>
+<fsummary>Change the size of the array to that reported by sparse_size/1.</fsummary>
+
+<desc><marker id="resize-1"/>
+
+<p>Change the size of the array to that reported by <seealso marker="#sparse_size-1">sparse_size/1</seealso>. If the given array has fixed size, the resulting
+ array will also have fixed size.</p>
+<p><em>See also:</em> <seealso marker="#resize-2">resize/2</seealso>, <seealso marker="#sparse_size-1">sparse_size/1</seealso>.</p>
+</desc></func>
+<func>
+<name>resize(Size::integer(), Array::array()) -&gt; array()</name>
+<fsummary>Change the size of the array.</fsummary>
+
+<desc><marker id="resize-2"/>
+
+<p>Change the size of the array. If <c>Size</c> is not a nonnegative
+ integer, the call fails with reason <c>badarg</c>. If the given array has
+ fixed size, the resulting array will also have fixed size.</p>
+</desc></func>
+<func>
+<name>set(I::integer(), Value::term(), Array::array()) -&gt; array()</name>
+<fsummary>Set entry I of the array to Value.</fsummary>
+
+<desc><marker id="set-3"/>
+
+<p>Set entry <c>I</c> of the array to <c>Value</c>. If <c>I</c> is not a
+ nonnegative integer, or if the array has fixed size and <c>I</c> is larger
+ than the maximum index, the call fails with reason <c>badarg</c>.</p>
+
+ <p>If the array does not have fixed size, and <c>I</c> is greater than
+ <c>size(Array)-1</c>, the array will grow to size <c>I+1</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#get-2">get/2</seealso>, <seealso marker="#reset-2">reset/2</seealso>.</p>
+</desc></func>
+<func>
+<name>size(Array::array()) -&gt; integer()</name>
+<fsummary>Get the number of entries in the array.</fsummary>
+
+<desc><marker id="size-1"/>
+
+<p>Get the number of entries in the array. Entries are numbered
+ from 0 to <c>size(Array)-1</c>; hence, this is also the index of the first
+ entry that is guaranteed to not have been previously set.</p>
+<p><em>See also:</em> <seealso marker="#set-3">set/3</seealso>, <seealso marker="#sparse_size-1">sparse_size/1</seealso>.</p>
+</desc></func>
+<func>
+<name>sparse_foldl(Function, InitialAcc::term(), Array::array()) -&gt; term()</name>
+<fsummary>Fold the elements of the array using the given function and
+ initial accumulator value, skipping default-valued entries.</fsummary>
+<type>
+<v>Function = (Index::integer(), Value::term(), Acc::term()) -&gt; term()</v></type>
+<desc><marker id="sparse_foldl-3"/>
+
+<p>Fold the elements of the array using the given function and
+ initial accumulator value, skipping default-valued entries. The
+ elements are visited in order from the lowest index to the highest.
+ If <c>Function</c> is not a function, the call fails with reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#foldl-3">foldl/3</seealso>, <seealso marker="#sparse_foldr-3">sparse_foldr/3</seealso>.</p>
+</desc></func>
+<func>
+<name>sparse_foldr(Function, InitialAcc::term(), Array::array()) -&gt; term()</name>
+<fsummary>Fold the elements of the array right-to-left using the given
+ function and initial accumulator value, skipping default-valued
+ entries.</fsummary>
+<type>
+<v>Function = (Index::integer(), Value::term(), Acc::term()) -&gt; term()</v></type>
+<desc><marker id="sparse_foldr-3"/>
+
+<p>Fold the elements of the array right-to-left using the given
+ function and initial accumulator value, skipping default-valued
+ entries. The elements are visited in order from the highest index to
+ the lowest. If <c>Function</c> is not a function, the call fails with
+ reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#foldr-3">foldr/3</seealso>, <seealso marker="#sparse_foldl-3">sparse_foldl/3</seealso>.</p>
+</desc></func>
+<func>
+<name>sparse_map(Function, Array::array()) -&gt; array()</name>
+<fsummary>Map the given function onto each element of the array, skipping
+ default-valued entries.</fsummary>
+<type>
+<v>Function = (Index::integer(), Value::term()) -&gt; term()</v></type>
+<desc><marker id="sparse_map-2"/>
+
+<p>Map the given function onto each element of the array, skipping
+ default-valued entries. The elements are visited in order from the
+ lowest index to the highest. If <c>Function</c> is not a function, the
+ call fails with reason <c>badarg</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#map-2">map/2</seealso>.</p>
+</desc></func>
+<func>
+<name>sparse_size(A::array()) -&gt; integer()</name>
+<fsummary>Get the number of entries in the array up until the last
+ non-default valued entry.</fsummary>
+
+<desc><marker id="sparse_size-1"/>
+
+<p>Get the number of entries in the array up until the last
+ non-default valued entry. In other words, returns <c>I+1</c> if <c>I</c> is the
+ last non-default valued entry in the array, or zero if no such entry
+ exists.</p>
+<p><em>See also:</em> <seealso marker="#resize-1">resize/1</seealso>, <seealso marker="#size-1">size/1</seealso>.</p>
+</desc></func>
+<func>
+<name>sparse_to_list(Array::array()) -&gt; list()</name>
+<fsummary>Converts the array to a list, skipping default-valued entries.</fsummary>
+
+<desc><marker id="sparse_to_list-1"/>
+
+<p>Converts the array to a list, skipping default-valued entries.
+ </p>
+<p><em>See also:</em> <seealso marker="#to_list-1">to_list/1</seealso>.</p>
+</desc></func>
+<func>
+<name>sparse_to_orddict(Array::array()) -&gt; [{Index::integer(), Value::term()}]</name>
+<fsummary>Convert the array to an ordered list of pairs {Index, Value},
+ skipping default-valued entries.</fsummary>
+
+<desc><marker id="sparse_to_orddict-1"/>
+
+<p>Convert the array to an ordered list of pairs <c>{Index, Value}</c>,
+ skipping default-valued entries.
+ </p>
+<p><em>See also:</em> <seealso marker="#to_orddict-1">to_orddict/1</seealso>.</p>
+</desc></func>
+<func>
+<name>to_list(Array::array()) -&gt; list()</name>
+<fsummary>Converts the array to a list.</fsummary>
+
+<desc><marker id="to_list-1"/>
+
+<p>Converts the array to a list.
+ </p>
+<p><em>See also:</em> <seealso marker="#from_list-2">from_list/2</seealso>, <seealso marker="#sparse_to_list-1">sparse_to_list/1</seealso>.</p>
+</desc></func>
+<func>
+<name>to_orddict(Array::array()) -&gt; [{Index::integer(), Value::term()}]</name>
+<fsummary>Convert the array to an ordered list of pairs {Index, Value}.</fsummary>
+
+<desc><marker id="to_orddict-1"/>
+
+<p>Convert the array to an ordered list of pairs <c>{Index, Value}</c>.
+ </p>
+<p><em>See also:</em> <seealso marker="#from_orddict-2">from_orddict/2</seealso>, <seealso marker="#sparse_to_orddict-1">sparse_to_orddict/1</seealso>.</p>
+</desc></func></funcs>
+
+</erlref>
+
diff --git a/lib/stdlib/doc/src/base64.xml b/lib/stdlib/doc/src/base64.xml
new file mode 100644
index 0000000000..d3fd7a843b
--- /dev/null
+++ b/lib/stdlib/doc/src/base64.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2007</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>base64</title>
+ <prepared>Ingela Anderton Andin</prepared>
+ <docno></docno>
+ <date>2007-02-22</date>
+ <rev></rev>
+ <file>base64.sgml</file>
+ </header>
+ <module>base64</module>
+ <modulesummary>Implements base 64 encode and decode, see RFC2045.</modulesummary>
+ <description>
+ <p>Implements base 64 encode and decode, see RFC2045. </p>
+ </description>
+ <funcs>
+ <func>
+ <name>encode(Data) -> Base64 </name>
+ <name>encode_to_string(Data) -> Base64String</name>
+ <fsummary>Encodes data into base64. </fsummary>
+ <type>
+ <v>Data = string() | binary()</v>
+ <v>Base64 = binary()</v>
+ <v>Base64String = string()</v>
+ </type>
+ <desc>
+ <p>Encodes a plain ASCII string into base64. The result will
+ be 33% larger than the data.</p>
+ </desc>
+ </func>
+ <func>
+ <name>decode(Base64) -> Data</name>
+ <name>decode_to_string(Base64) -> DataString</name>
+ <name>mime_decode(Base64) -> Data</name>
+ <name>mime_decode_to_string(Base64) -> DataString</name>
+ <fsummary>Decodes a base64 encoded string to data. </fsummary>
+ <type>
+ <v>Base64 = string() | binary()</v>
+ <v>Data = binary()</v>
+ <v>DataString = string()</v>
+ </type>
+ <desc>
+ <p>Decodes a base64 encoded string to plain ASCII. See RFC4648.
+ <c>mime_decode/1</c> and <c>mime_decode_to_string/1</c>
+ strips away illegal characters, while <c>decode/1</c> and
+ <c>decode_to_string/1</c> only strips away whitespace characters.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/beam_lib.xml b/lib/stdlib/doc/src/beam_lib.xml
new file mode 100644
index 0000000000..f2a9c2a671
--- /dev/null
+++ b/lib/stdlib/doc/src/beam_lib.xml
@@ -0,0 +1,507 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2000</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>beam_lib</title>
+ <prepared>Hans Bolinder</prepared>
+ <docno></docno>
+ <date>1999-10-30</date>
+ <rev>PA1</rev>
+ </header>
+ <module>beam_lib</module>
+ <modulesummary>An Interface To the BEAM File Format</modulesummary>
+ <description>
+ <p><c>beam_lib</c> provides an interface to files created by
+ the BEAM compiler ("BEAM files"). The format used, a variant of
+ "EA IFF 1985" Standard for Interchange Format Files, divides data
+ into chunks.</p>
+ <p>Chunk data can be returned as binaries or as compound terms.
+ Compound terms are returned when chunks are referenced by names
+ (atoms) rather than identifiers (strings). The names recognized
+ and the corresponding identifiers are:</p>
+ <list type="bulleted">
+ <item><c>abstract_code ("Abst")</c></item>
+ <item><c>attributes ("Attr")</c></item>
+ <item><c>compile_info ("CInf")</c></item>
+ <item><c>exports ("ExpT")</c></item>
+ <item><c>labeled_exports ("ExpT")</c></item>
+ <item><c>imports ("ImpT")</c></item>
+ <item><c>indexed_imports ("ImpT")</c></item>
+ <item><c>locals ("LocT")</c></item>
+ <item><c>labeled_locals ("LocT")</c></item>
+ <item><c>atoms ("Atom")</c></item>
+ </list>
+ </description>
+
+ <section>
+ <marker id="debug_info"></marker>
+ <title>Debug Information/Abstract Code</title>
+ <p>The option <c>debug_info</c> can be given to the compiler (see
+ <seealso marker="compiler:compile#debug_info">compile(3)</seealso>)
+ in order to have debug information in the form of abstract code
+ (see <seealso marker="erts:absform">The Abstract Format</seealso>
+ in ERTS User's Guide) stored in the <c>abstract_code</c> chunk.
+ Tools such as Debugger and Xref require the debug information to
+ be included.</p>
+ <warning>
+ <p>Source code can be reconstructed from the debug information.
+ Use encrypted debug information (see below) to prevent this.</p>
+ </warning>
+ <p>The debug information can also be removed from BEAM files
+ using <seealso marker="#strip/1">strip/1</seealso>,
+ <seealso marker="#strip_files/1">strip_files/1</seealso> and/or
+ <seealso marker="#strip_release/1">strip_release/1</seealso>.</p>
+ <p><em>Reconstructing source code</em></p>
+ <p>Here is an example of how to reconstruct source code from
+ the debug information in a BEAM file <c>Beam</c>:</p>
+ <code type="none">
+ {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Beam,[abstract_code]).
+ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
+ <p><em>Encrypted debug information</em></p>
+ <p>The debug information can be encrypted in order to keep
+ the source code secret, but still being able to use tools such as
+ Xref or Debugger. </p>
+ <p>To use encrypted debug information, a key must be provided to
+ the compiler and <c>beam_lib</c>. The key is given as a string and
+ it is recommended that it contains at least 32 characters and
+ that both upper and lower case letters as well as digits and
+ special characters are used.</p>
+ <p></p>
+ <p>The default type -- and currently the only type -- of crypto
+ algorithm is <c>des3_cbc</c>, three rounds of DES. The key string
+ will be scrambled using <c>erlang:md5/1</c> to generate
+ the actual keys used for <c>des3_cbc</c>.</p>
+ <note>
+ <p>As far as we know by the time of writing, it is
+ infeasible to break <c>des3_cbc</c> encryption without any
+ knowledge of the key. Therefore, as long as the key is kept
+ safe and is unguessable, the encrypted debug information
+ <em>should</em> be safe from intruders.</p>
+ </note>
+ <p>There are two ways to provide the key:</p>
+ <list type="ordered">
+ <item>
+ <p>Use the compiler option <c>{debug_info,Key}</c>, see
+ <seealso marker="compiler:compile#debug_info_key">compile(3)</seealso>,
+ and the function
+ <seealso marker="#crypto_key_fun/1">crypto_key_fun/1</seealso>
+ to register a fun which returns the key whenever
+ <c>beam_lib</c> needs to decrypt the debug information.</p>
+ <p>If no such fun is registered, <c>beam_lib</c> will instead
+ search for a <c>.erlang.crypt</c> file, see below.</p>
+ </item>
+ <item>
+ <p>Store the key in a text file named <c>.erlang.crypt</c>.</p>
+ <p>In this case, the compiler option <c>encrypt_debug_info</c>
+ can be used, see
+ <seealso marker="compiler:compile#encrypt_debug_info">compile(3)</seealso>.</p>
+ </item>
+ </list>
+ <p><em>.erlang.crypt</em></p>
+ <p><c>beam_lib</c> searches for <c>.erlang.crypt</c> in the current
+ directory and then the home directory for the current user. If
+ the file is found and contains a key, <c>beam_lib</c> will
+ implicitly create a crypto key fun and register it.</p>
+ <p>The <c>.erlang.crypt</c> file should contain a single list of
+ tuples:</p>
+ <code type="none">
+ {debug_info, Mode, Module, Key}</code>
+ <p><c>Mode</c> is the type of crypto algorithm; currently, the only
+ allowed value thus is <c>des3_cbc</c>. <c>Module</c> is either an
+ atom, in which case <c>Key</c> will only be used for the module
+ <c>Module</c>, or <c>[]</c>, in which case <c>Key</c> will be
+ used for all modules. <c>Key</c> is the non-empty key string.</p>
+ <p>The <c>Key</c> in the first tuple where both <c>Mode</c> and
+ <c>Module</c> matches will be used.</p>
+ <p>Here is an example of an <c>.erlang.crypt</c> file that returns
+ the same key for all modules:</p>
+ <code type="none"><![CDATA[
+[{debug_info, des3_cbc, [], "%>7}|pc/DM6Cga*68$Mw]L#&_Gejr]G^"}].]]></code>
+ <p>And here is a slightly more complicated example of an
+ <c>.erlang.crypt</c> which provides one key for the module
+ <c>t</c>, and another key for all other modules:</p>
+ <code type="none"><![CDATA[
+[{debug_info, des3_cbc, t, "My KEY"},
+ {debug_info, des3_cbc, [], "%>7}|pc/DM6Cga*68$Mw]L#&_Gejr]G^"}].]]></code>
+ <note>
+ <p>Do not use any of the keys in these examples. Use your own
+ keys.</p>
+ </note>
+ </section>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+beam() -> Module | Filename | binary()
+ Module = atom()
+ Filename = string() | atom()</code>
+ <p>Each of the functions described below accept either the module
+ name, the filename, or a binary containing the beam module.</p>
+ <code type="none">
+chunkdata() = {ChunkId, DataB} | {ChunkName, DataT}
+ ChunkId = chunkid()
+ DataB = binary()
+ {ChunkName, DataT} =
+ {abstract_code, AbstractCode}
+ | {attributes, [{Attribute, [AttributeValue]}]}
+ | {compile_info, [{InfoKey, [InfoValue]}]}
+ | {exports, [{Function, Arity}]}
+ | {labeled_exports, [{Function, Arity, Label}]}
+ | {imports, [{Module, Function, Arity}]}
+ | {indexed_imports, [{Index, Module, Function, Arity}]}
+ | {locals, [{Function, Arity}]}]}
+ | {labeled_locals, [{Function, Arity, Label}]}]}
+ | {atoms, [{integer(), atom()}]}
+ AbstractCode = {AbstVersion, Forms} | no_abstract_code
+ AbstVersion = atom()
+ Attribute = atom()
+ AttributeValue = term()
+ Module = Function = atom()
+ Arity = int()
+ Label = int()</code>
+ <p>It is not checked that the forms conform to the abstract format
+ indicated by <c>AbstVersion</c>. <c>no_abstract_code</c> means
+ that the <c>"Abst"</c> chunk is present, but empty.</p>
+ <p>The list of attributes is sorted on <c>Attribute</c>, and each
+ attribute name occurs once in the list. The attribute values
+ occur in the same order as in the file. The lists of functions
+ are also sorted.</p>
+ <code type="none">
+chunkid() = "Abst" | "Attr" | "CInf"
+ | "ExpT" | "ImpT" | "LocT"
+ | "Atom"
+
+chunkname() = abstract_code | attributes | compile_info
+ | exports | labeled_exports
+ | imports | indexed_imports
+ | locals | labeled_locals
+ | atoms
+
+chunkref() = chunkname() | chunkid()</code>
+ </section>
+ <funcs>
+ <func>
+ <name>chunks(Beam, [ChunkRef]) -> {ok, {Module, [ChunkData]}} | {error, beam_lib, Reason}</name>
+ <fsummary>Read selected chunks from a BEAM file or binary</fsummary>
+ <type>
+ <v>Beam = beam()</v>
+ <v>ChunkRef = chunkref()</v>
+ <v>Module = atom()</v>
+ <v>ChunkData = chunkdata()</v>
+ <v>Reason = {unknown_chunk, Filename, atom()}</v>
+ <v>&nbsp;&nbsp;| {key_missing_or_invalid, Filename, abstract_code}</v>
+ <v>&nbsp;&nbsp;| Reason1 -- see info/1</v>
+ <v>&nbsp;Filename = string()</v>
+ </type>
+ <desc>
+ <p>Reads chunk data for selected chunks refs. The order of
+ the returned list of chunk data is determined by the order
+ of the list of chunks references.</p>
+ </desc>
+ </func>
+ <func>
+ <name>chunks(Beam, [ChunkRef], [Option]) -> {ok, {Module, [ChunkResult]}} | {error, beam_lib, Reason}</name>
+ <fsummary>Read selected chunks from a BEAM file or binary</fsummary>
+ <type>
+ <v>Beam = beam()</v>
+ <v>ChunkRef = chunkref()</v>
+ <v>Module = atom()</v>
+ <v>Option = allow_missing_chunks</v>
+ <v>ChunkResult = {chunkref(), ChunkContents} | {chunkref(), missing_chunk}</v>
+ <v>Reason = {missing_chunk, Filename, atom()}</v>
+ <v>&nbsp;&nbsp;| {key_missing_or_invalid, Filename, abstract_code}</v>
+ <v>&nbsp;&nbsp;| Reason1 -- see info/1</v>
+ <v>&nbsp;Filename = string()</v>
+ </type>
+ <desc>
+ <p>Reads chunk data for selected chunks refs. The order of
+ the returned list of chunk data is determined by the order
+ of the list of chunks references.</p>
+ <p>By default, if any requested chunk is missing in <c>Beam</c>,
+ an <c>error</c> tuple is returned.
+ However, if the option <c>allow_missing_chunks</c> has been given,
+ a result will be returned even if chunks are missing.
+ In the result list, any missing chunks will be represented as
+ <c>{ChunkRef,missing_chunk}</c>.
+ Note, however, that if the <c>"Atom"</c> chunk if missing, that is
+ considered a fatal error and the return value will be an <c>error</c>
+ tuple.</p>
+ </desc>
+ </func>
+ <func>
+ <name>version(Beam) -> {ok, {Module, [Version]}} | {error, beam_lib, Reason}</name>
+ <fsummary>Read the BEAM file's module version</fsummary>
+ <type>
+ <v>Beam = beam()</v>
+ <v>Module = atom()</v>
+ <v>Version = term()</v>
+ <v>Reason -- see chunks/2</v>
+ </type>
+ <desc>
+ <p>Returns the module version(s). A version is defined by
+ the module attribute <c>-vsn(Vsn)</c>. If this attribute is
+ not specified, the version defaults to the checksum of
+ the module. Note that if the version <c>Vsn</c> is not a list,
+ it is made into one, that is <c>{ok,{Module,[Vsn]}}</c> is
+ returned. If there are several <c>-vsn</c> module attributes,
+ the result is the concatenated list of versions. Examples:</p>
+ <pre>
+1> <input>beam_lib:version(a).</input> % -vsn(1).
+{ok,{a,[1]}}
+2> <input>beam_lib:version(b).</input> % -vsn([1]).
+{ok,{b,[1]}}
+3> <input>beam_lib:version(c).</input> % -vsn([1]). -vsn(2).
+{ok,{c,[1,2]}}
+4> <input>beam_lib:version(d).</input> % no -vsn attribute
+{ok,{d,[275613208176997377698094100858909383631]}}</pre>
+ </desc>
+ </func>
+ <func>
+ <name>md5(Beam) -> {ok, {Module, MD5}} | {error, beam_lib, Reason}</name>
+ <fsummary>Read the BEAM file's module version</fsummary>
+ <type>
+ <v>Beam = beam()</v>
+ <v>Module = atom()</v>
+ <v>MD5 = binary()</v>
+ <v>Reason -- see chunks/2</v>
+ </type>
+ <desc>
+ <p>Calculates an MD5 redundancy check for the code of the module
+ (compilation date and other attributes are not included).</p>
+ </desc>
+ </func>
+ <func>
+ <name>info(Beam) -> [{Item, Info}] | {error, beam_lib, Reason1}</name>
+ <fsummary>Information about a BEAM file</fsummary>
+ <type>
+ <v>Beam = beam()</v>
+ <v>Item, Info -- see below</v>
+ <v>Reason1 = {chunk_too_big, Filename, ChunkId, ChunkSize, FileSize}</v>
+ <v>&nbsp;&nbsp;| {invalid_beam_file, Filename, Pos}</v>
+ <v>&nbsp;&nbsp;| {invalid_chunk, Filename, ChunkId}</v>
+ <v>&nbsp;&nbsp;| {missing_chunk, Filename, ChunkId}</v>
+ <v>&nbsp;&nbsp;| {not_a_beam_file, Filename}</v>
+ <v>&nbsp;&nbsp;| {file_error, Filename, Posix}</v>
+ <v>&nbsp;Filename = string()</v>
+ <v>&nbsp;ChunkId = chunkid()</v>
+ <v>&nbsp;ChunkSize = FileSize = int()</v>
+ <v>&nbsp;Pos = int()</v>
+ <v>&nbsp;Posix = posix() -- see file(3)</v>
+ </type>
+ <desc>
+ <p>Returns a list containing some information about a BEAM file
+ as tuples <c>{Item, Info}</c>:</p>
+ <taglist>
+ <tag><c>{file, Filename} | {binary, Binary}</c></tag>
+ <item>
+ <p>The name (string) of the BEAM file, or the binary from
+ which the information was extracted.</p>
+ </item>
+ <tag><c>{module, Module}</c></tag>
+ <item>
+ <p>The name (atom) of the module.</p>
+ </item>
+ <tag><c>{chunks, [{ChunkId, Pos, Size}]}</c></tag>
+ <item>
+ <p>For each chunk, the identifier (string) and the position
+ and size of the chunk data, in bytes.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>cmp(Beam1, Beam2) -> ok | {error, beam_lib, Reason}</name>
+ <fsummary>Compare two BEAM files</fsummary>
+ <type>
+ <v>Beam1 = Beam2 = beam()</v>
+ <v>Reason = {modules_different, Module1, Module2}</v>
+ <v>&nbsp;&nbsp;| {chunks_different, ChunkId}</v>
+ <v>&nbsp;&nbsp;| Reason1 -- see info/1</v>
+ <v>&nbsp;Module1 = Module2 = atom()</v>
+ <v>&nbsp;ChunkId = chunkid()</v>
+ </type>
+ <desc>
+ <p>Compares the contents of two BEAM files. If the module names
+ are the same, and the chunks with the identifiers
+ <c>"Code"</c>, <c>"ExpT"</c>, <c>"ImpT"</c>, <c>"StrT"</c>,
+ and <c>"Atom"</c> have the same contents in both files,
+ <c>ok</c> is returned. Otherwise an error message is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>cmp_dirs(Dir1, Dir2) -> {Only1, Only2, Different} | {error, beam_lib, Reason1}</name>
+ <fsummary>Compare the BEAM files in two directories</fsummary>
+ <type>
+ <v>Dir1 = Dir2 = string() | atom()</v>
+ <v>Different = [{Filename1, Filename2}]</v>
+ <v>Only1 = Only2 = [Filename]</v>
+ <v>Filename = Filename1 = Filename2 = string()</v>
+ <v>Reason1 = {not_a_directory, term()} | -- see info/1</v>
+ </type>
+ <desc>
+ <p>The <c>cmp_dirs/2</c> function compares the BEAM files in
+ two directories. Only files with extension <c>".beam"</c> are
+ compared. BEAM files that exist in directory <c>Dir1</c>
+ (<c>Dir2</c>) only are returned in <c>Only1</c>
+ (<c>Only2</c>). BEAM files that exist on both directories but
+ are considered different by <c>cmp/2</c> are returned as
+ pairs {<c>Filename1</c>, <c>Filename2</c>} where
+ <c>Filename1</c> (<c>Filename2</c>) exists in directory
+ <c>Dir1</c> (<c>Dir2</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>diff_dirs(Dir1, Dir2) -> ok | {error, beam_lib, Reason1}</name>
+ <fsummary>Compare the BEAM files in two directories</fsummary>
+ <type>
+ <v>Dir1 = Dir2 = string() | atom()</v>
+ <v>Reason1 = {not_a_directory, term()} | -- see info/1</v>
+ </type>
+ <desc>
+ <p>The <c>diff_dirs/2</c> function compares the BEAM files in
+ two directories the way <c>cmp_dirs/2</c> does, but names of
+ files that exist in only one directory or are different are
+ presented on standard output.</p>
+ </desc>
+ </func>
+ <func>
+ <name>strip(Beam1) -> {ok, {Module, Beam2}} | {error, beam_lib, Reason1}</name>
+ <fsummary>Removes chunks not needed by the loader from a BEAM file</fsummary>
+ <type>
+ <v>Beam1 = Beam2 = beam()</v>
+ <v>Module = atom()</v>
+ <v>Reason1 -- see info/1</v>
+ </type>
+ <desc>
+ <p>The <c>strip/1</c> function removes all chunks from a BEAM
+ file except those needed by the loader. In particular,
+ the debug information (<c>abstract_code</c> chunk) is removed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>strip_files(Files) -> {ok, [{Module, Beam2}]} | {error, beam_lib, Reason1}</name>
+ <fsummary>Removes chunks not needed by the loader from BEAM files</fsummary>
+ <type>
+ <v>Files = [Beam1]</v>
+ <v>&nbsp;Beam1 = beam()</v>
+ <v>Module = atom()</v>
+ <v>Beam2 = beam()</v>
+ <v>Reason1 -- see info/1</v>
+ </type>
+ <desc>
+ <p>The <c>strip_files/1</c> function removes all chunks except
+ those needed by the loader from BEAM files. In particular,
+ the debug information (<c>abstract_code</c> chunk) is removed.
+ The returned list contains one element for each given file
+ name, in the same order as in <c>Files</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>strip_release(Dir) -> {ok, [{Module, Filename]}} | {error, beam_lib, Reason1}</name>
+ <fsummary>Removes chunks not needed by the loader from all BEAM files of a release</fsummary>
+ <type>
+ <v>Dir = string() | atom()</v>
+ <v>Module = atom()</v>
+ <v>Filename = string()</v>
+ <v>Reason1 = {not_a_directory, term()} | -- see info/1</v>
+ </type>
+ <desc>
+ <p>The <c>strip_release/1</c> function removes all chunks
+ except those needed by the loader from the BEAM files of a
+ release. <c>Dir</c> should be the installation root
+ directory. For example, the current OTP release can be
+ stripped with the call
+ <c>beam_lib:strip_release(code:root_dir())</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(Reason) -> Chars</name>
+ <fsummary>Return an English description of a BEAM read error reply</fsummary>
+ <type>
+ <v>Reason -- see other functions</v>
+ <v>Chars = [char() | Chars]</v>
+ </type>
+ <desc>
+ <p>Given the error returned by any function in this module,
+ the function <c>format_error</c> returns a descriptive string
+ of the error in English. For file errors, the function
+ <c>file:format_error(Posix)</c> should be called.</p>
+ </desc>
+ </func>
+ <func>
+ <name>crypto_key_fun(CryptoKeyFun) -> ok | {error, Reason}</name>
+ <fsummary>Register a fun that provides a crypto key</fsummary>
+ <type>
+ <v>CryptoKeyFun = fun() -- see below</v>
+ <v>Reason = badfun | exists | term()</v>
+ </type>
+ <desc>
+ <p>The <c>crypto_key_fun/1</c> function registers a unary fun
+ that will be called if <c>beam_lib</c> needs to read an
+ <c>abstract_code</c> chunk that has been encrypted. The fun
+ is held in a process that is started by the function.</p>
+ <p>If there already is a fun registered when attempting to
+ register a fun, <c>{error, exists}</c> is returned.</p>
+ <p>The fun must handle the following arguments:</p>
+ <code type="none">
+ CryptoKeyFun(init) -> ok | {ok, NewCryptoKeyFun} | {error, Term}</code>
+ <p>Called when the fun is registered, in the process that holds
+ the fun. Here the crypto key fun can do any necessary
+ initializations. If <c>{ok, NewCryptoKeyFun}</c> is returned
+ then <c>NewCryptoKeyFun</c> will be registered instead of
+ <c>CryptoKeyFun</c>. If <c>{error, Term}</c> is returned,
+ the registration is aborted and <c>crypto_key_fun/1</c>
+ returns <c>{error, Term}</c> as well.</p>
+ <code type="none">
+ CryptoKeyFun({debug_info, Mode, Module, Filename}) -> Key</code>
+ <p>Called when the key is needed for the module <c>Module</c>
+ in the file named <c>Filename</c>. <c>Mode</c> is the type of
+ crypto algorithm; currently, the only possible value thus is
+ <c>des3_cbc</c>. The call should fail (raise an exception) if
+ there is no key available.</p>
+ <code type="none">
+ CryptoKeyFun(clear) -> term()</code>
+ <p>Called before the fun is unregistered. Here any cleaning up
+ can be done. The return value is not important, but is passed
+ back to the caller of <c>clear_crypto_key_fun/0</c> as part
+ of its return value.</p>
+ </desc>
+ </func>
+ <func>
+ <name>clear_crypto_key_fun() -> {ok, Result}</name>
+ <fsummary>Unregister the current crypto key fun</fsummary>
+ <type>
+ <v>Result = undefined | term()</v>
+ </type>
+ <desc>
+ <p>Unregisters the crypto key fun and terminates the process
+ holding it, started by <c>crypto_key_fun/1</c>.</p>
+ <p>The <c>clear_crypto_key_fun/1</c> either returns
+ <c>{ok, undefined}</c> if there was no crypto key fun
+ registered, or <c>{ok, Term}</c>, where <c>Term</c> is
+ the return value from <c>CryptoKeyFun(clear)</c>, see
+ <c>crypto_key_fun/1</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/book.xml b/lib/stdlib/doc/src/book.xml
new file mode 100644
index 0000000000..10ee6f3855
--- /dev/null
+++ b/lib/stdlib/doc/src/book.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE book SYSTEM "book.dtd">
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header titlestyle="normal">
+ <copyright>
+ <year>1997</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>STDLIB</title>
+ <prepared>OTP Team</prepared>
+ <docno></docno>
+ <date>1997-05-02</date>
+ <rev>1.3</rev>
+ <file>book.sgml</file>
+ </header>
+ <insidecover>
+ </insidecover>
+ <pagetext>STDLIB</pagetext>
+ <preamble>
+ <contents level="2"></contents>
+ </preamble>
+ <parts>
+ <xi:include href="part.xml"/>
+ </parts>
+ <applications>
+ <xi:include href="ref_man.xml"/>
+ </applications>
+ <releasenotes>
+ <xi:include href="notes.xml"/>
+ </releasenotes>
+ <listofterms></listofterms>
+ <index></index>
+</book>
+
diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml
new file mode 100644
index 0000000000..19e3ac1f08
--- /dev/null
+++ b/lib/stdlib/doc/src/c.xml
@@ -0,0 +1,320 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>c</title>
+ <prepared>Joe Armstrong</prepared>
+ <docno>1</docno>
+ <date>96-10-30</date>
+ <rev>B</rev>
+ </header>
+ <module>c</module>
+ <modulesummary>Command Interface Module</modulesummary>
+ <description>
+ <p>The <c>c</c> module enables users to enter the short form of
+ some commonly used commands.</p>
+ <note>
+ <p>These functions are are intended for interactive use in
+ the Erlang shell only. The module prefix may be omitted.</p>
+ </note>
+ </description>
+ <funcs>
+ <func>
+ <name>bt(Pid) -> void()</name>
+ <fsummary>Stack backtrace for a process</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Stack backtrace for a process. Equivalent to
+ <c>erlang:process_display(Pid, backtrace)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>c(File) -> {ok, Module} | error</name>
+ <name>c(File, Options) -> {ok, Module} | error</name>
+ <fsummary>Compile and load code in a file</fsummary>
+ <type>
+ <v>File = name() -- see filename(3)</v>
+ <v>Options = [Opt] -- see compile:file/2</v>
+ </type>
+ <desc>
+ <p><c>c/1,2</c> compiles and then purges and loads the code for
+ a file. <c>Options</c> defaults to []. Compilation is
+ equivalent to:</p>
+ <code type="none">
+compile:file(File, Options ++ [report_errors, report_warnings])</code>
+ <p>Note that purging the code means that any processes
+ lingering in old code for the module are killed without
+ warning. See <c>code/3</c> for more information.</p>
+ </desc>
+ </func>
+ <func>
+ <name>cd(Dir) -> void()</name>
+ <fsummary>Change working directory</fsummary>
+ <type>
+ <v>Dir = name() -- see filename(3)</v>
+ </type>
+ <desc>
+ <p>Changes working directory to <c>Dir</c>, which may be a
+ relative name, and then prints the name of the new working
+ directory.</p>
+ <pre>
+2> <input>cd("../erlang").</input>
+/home/ron/erlang</pre>
+ </desc>
+ </func>
+ <func>
+ <name>flush() -> void()</name>
+ <fsummary>Flush any messages sent to the shell</fsummary>
+ <desc>
+ <p>Flushes any messages sent to the shell.</p>
+ </desc>
+ </func>
+ <func>
+ <name>help() -> void()</name>
+ <fsummary>Help information</fsummary>
+ <desc>
+ <p>Displays help information: all valid shell internal commands,
+ and commands in this module.</p>
+ </desc>
+ </func>
+ <func>
+ <name>i() -> void()</name>
+ <name>ni() -> void()</name>
+ <fsummary>Information about the system</fsummary>
+ <desc>
+ <p><c>i/0</c> displays information about the system, listing
+ information about all processes. <c>ni/0</c> does the same,
+ but for all nodes the network.</p>
+ </desc>
+ </func>
+ <func>
+ <name>i(X, Y, Z) -> void()</name>
+ <fsummary>Information about pid &lt;X.Y.Z&gt;</fsummary>
+ <type>
+ <v>X = Y = Z = int()</v>
+ </type>
+ <desc>
+ <p>Displays information about a process, Equivalent to
+ <c>process_info(pid(X, Y, Z))</c>, but location transparent.</p>
+ </desc>
+ </func>
+ <func>
+ <name>l(Module) -> void()</name>
+ <fsummary>Load or reload module</fsummary>
+ <type>
+ <v>Module = atom()</v>
+ </type>
+ <desc>
+ <p>Purges and loads, or reloads, a module by calling
+ <c>code:purge(Module)</c> followed by
+ <c>code:load_file(Module)</c>.</p>
+ <p>Note that purging the code means that any processes
+ lingering in old code for the module are killed without
+ warning. See <c>code/3</c> for more information.</p>
+ </desc>
+ </func>
+ <func>
+ <name>lc(Files) -> ok</name>
+ <fsummary>Compile a list of files</fsummary>
+ <type>
+ <v>Files = [File]</v>
+ <v>&nbsp;File = name() -- see filename(3)</v>
+ </type>
+ <desc>
+ <p>Compiles a list of files by calling <c>compile:file(File, [report_errors, report_warnings])</c> for each <c>File</c>
+ in <c>Files</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>ls() -> void()</name>
+ <fsummary>List files in the current directory</fsummary>
+ <desc>
+ <p>Lists files in the current directory.</p>
+ </desc>
+ </func>
+ <func>
+ <name>ls(Dir) -> void()</name>
+ <fsummary>List files in a directory</fsummary>
+ <type>
+ <v>Dir = name() -- see filename(3)</v>
+ </type>
+ <desc>
+ <p>Lists files in directory <c>Dir</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>m() -> void()</name>
+ <fsummary>Which modules are loaded</fsummary>
+ <desc>
+ <p>Displays information about the loaded modules, including
+ the files from which they have been loaded.</p>
+ </desc>
+ </func>
+ <func>
+ <name>m(Module) -> void()</name>
+ <fsummary>Information about a module</fsummary>
+ <type>
+ <v>Module = atom()</v>
+ </type>
+ <desc>
+ <p>Displays information about <c>Module</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>memory() -> [{Type, Size}]</name>
+ <fsummary>Memory allocation information</fsummary>
+ <type>
+ <v>Type, Size -- see erlang:memory/0</v>
+ </type>
+ <desc>
+ <p>Memory allocation information. Equivalent to
+ <c>erlang:memory/0</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>memory(Type) -> Size</name>
+ <name>memory([Type]) -> [{Type, Size}]</name>
+ <fsummary>Memory allocation information</fsummary>
+ <type>
+ <v>Type, Size -- see erlang:memory/0</v>
+ </type>
+ <desc>
+ <p>Memory allocation information. Equivalent to
+ <c>erlang:memory/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>nc(File) -> {ok, Module} | error</name>
+ <name>nc(File, Options) -> {ok, Module} | error</name>
+ <fsummary>Compile and load code in a file on all nodes</fsummary>
+ <type>
+ <v>File = name() -- see filename(3)</v>
+ <v>Options = [Opt] -- see compile:file/2</v>
+ </type>
+ <desc>
+ <p>Compiles and then loads the code for a file on all nodes.
+ <c>Options</c> defaults to []. Compilation is equivalent to:</p>
+ <code type="none">
+compile:file(File, Opts ++ [report_errors, report_warnings])</code>
+ </desc>
+ </func>
+ <func>
+ <name>nl(Module) -> void()</name>
+ <fsummary>Load module on all nodes</fsummary>
+ <type>
+ <v>Module = atom()</v>
+ </type>
+ <desc>
+ <p>Loads <c>Module</c> on all nodes.</p>
+ </desc>
+ </func>
+ <func>
+ <name>pid(X, Y, Z) -> pid()</name>
+ <fsummary>Convert X,Y,Z to a pid</fsummary>
+ <type>
+ <v>X = Y = Z = int()</v>
+ </type>
+ <desc>
+ <p>Converts <c>X</c>, <c>Y</c>, <c>Z</c> to the pid
+ <c><![CDATA[<X.Y.Z>]]></c>. This function should only be used when
+ debugging.</p>
+ </desc>
+ </func>
+ <func>
+ <name>pwd() -> void()</name>
+ <fsummary>Print working directory</fsummary>
+ <desc>
+ <p>Prints the name of the working directory.</p>
+ </desc>
+ </func>
+ <func>
+ <name>q() -> void()</name>
+ <fsummary>Quit - shorthand for <c>init:stop()</c></fsummary>
+ <desc>
+ <p>This function is shorthand for <c>init:stop()</c>, that is,
+ it causes the node to stop in a controlled fashion.</p>
+ </desc>
+ </func>
+ <func>
+ <name>regs() -> void()</name>
+ <name>nregs() -> void()</name>
+ <fsummary>Information about registered processes</fsummary>
+ <desc>
+ <p><c>regs/0</c> displays information about all registered
+ processes. <c>nregs/0</c> does the same, but for all nodes
+ in the network.</p>
+ </desc>
+ </func>
+ <func>
+ <name>xm(ModSpec) -> void()</name>
+ <fsummary>Cross reference check a module</fsummary>
+ <type>
+ <v>ModSpec = Module | Filename</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Filename = string()</v>
+ </type>
+ <desc>
+ <p>This function finds undefined functions, unused functions,
+ and calls to deprecated functions in a module by calling
+ <c>xref:m/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>y(File) -> YeccRet</name>
+ <fsummary>Generate an LALR-1 parser</fsummary>
+ <type>
+ <v>File = name() -- see filename(3)</v>
+ <v>YeccRet = -- see yecc:file/2</v>
+ </type>
+ <desc>
+ <p>Generates an LALR-1 parser. Equivalent to:</p>
+ <code type="none">
+yecc:file(File)</code>
+ </desc>
+ </func>
+ <func>
+ <name>y(File, Options) -> YeccRet</name>
+ <fsummary>Generate an LALR-1 parser</fsummary>
+ <type>
+ <v>File = name() -- see filename(3)</v>
+ <v>Options, YeccRet = -- see yecc:file/2</v>
+ </type>
+ <desc>
+ <p>Generates an LALR-1 parser. Equivalent to:</p>
+ <code type="none">
+yecc:file(File, Options)</code>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="compiler:compile">compile(3)</seealso>,
+ <seealso marker="filename">filename(3)</seealso>,
+ <seealso marker="erts:erlang">erlang(3)</seealso>,
+ <seealso marker="parsetools:yecc">yecc(3)</seealso>,
+ <seealso marker="tools:xref">xref(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/calendar.xml b/lib/stdlib/doc/src/calendar.xml
new file mode 100644
index 0000000000..36f0c03162
--- /dev/null
+++ b/lib/stdlib/doc/src/calendar.xml
@@ -0,0 +1,377 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>calendar</title>
+ <prepared>Peter H&ouml;gfeldt</prepared>
+ <docno></docno>
+ <date>1996-11-05</date>
+ <rev>B</rev>
+ </header>
+ <module>calendar</module>
+ <modulesummary>Local and universal time, day-of-the-week, date and time conversions</modulesummary>
+ <description>
+ <p>This module provides computation of local and universal time,
+ day-of-the-week, and several time conversion functions.</p>
+ <p>Time is local when it is adjusted in accordance with the current
+ time zone and daylight saving. Time is universal when it reflects
+ the time at longitude zero, without any adjustment for daylight
+ saving. Universal Coordinated Time (UTC) time is also called
+ Greenwich Mean Time (GMT).</p>
+ <p>The time functions <c>local_time/0</c> and
+ <c>universal_time/0</c> provided in this module both return date
+ and time. The reason for this is that separate functions for date
+ and time may result in a date/time combination which is displaced
+ by 24 hours. This happens if one of the functions is called
+ before midnight, and the other after midnight. This problem also
+ applies to the Erlang BIFs <c>date/0</c> and <c>time/0</c>, and
+ their use is strongly discouraged if a reliable date/time stamp
+ is required.</p>
+ <p>All dates conform to the Gregorian calendar. This calendar was
+ introduced by Pope Gregory XIII in 1582 and was used in all
+ Catholic countries from this year. Protestant parts of Germany
+ and the Netherlands adopted it in 1698, England followed in 1752,
+ and Russia in 1918 (the October revolution of 1917 took place in
+ November according to the Gregorian calendar).</p>
+ <p>The Gregorian calendar in this module is extended back to year 0.
+ For a given date, the <em>gregorian days</em> is the number of
+ days up to and including the date specified. Similarly,
+ the <em>gregorian seconds</em> for a given date and time, is
+ the the number of seconds up to and including the specified date
+ and time.</p>
+ <p>For computing differences between epochs in time, use
+ the functions counting gregorian days or seconds. If epochs are
+ given as local time, they must be converted to universal time, in
+ order to get the correct value of the elapsed time between epochs.
+ Use of the function <c>time_difference/2</c> is discouraged.</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+date() = {Year, Month, Day}
+ Year = int()
+ Month = 1..12
+ Day = 1..31
+Year cannot be abbreviated. Example: 93 denotes year 93, not 1993.
+Valid range depends on the underlying OS.
+The date tuple must denote a valid date.
+
+time() = {Hour, Minute, Second}
+ Hour = 0..23
+ Minute = Second = 0..59</code>
+ </section>
+ <funcs>
+ <func>
+ <name>date_to_gregorian_days(Date) -> Days</name>
+ <name>date_to_gregorian_days(Year, Month, Day) -> Days</name>
+ <fsummary>Compute the number of days from year 0 up to the given date</fsummary>
+ <type>
+ <v>Date = date()</v>
+ <v>Days = int()</v>
+ </type>
+ <desc>
+ <p>This function computes the number of gregorian days starting
+ with year 0 and ending at the given date.</p>
+ </desc>
+ </func>
+ <func>
+ <name>datetime_to_gregorian_seconds({Date, Time}) -> Seconds</name>
+ <fsummary>Compute the number of seconds from year 0 up to the given date and time</fsummary>
+ <type>
+ <v>Date = date()</v>
+ <v>Time = time()</v>
+ <v>Seconds = int()</v>
+ </type>
+ <desc>
+ <p>This function computes the number of gregorian seconds
+ starting with year 0 and ending at the given date and time.</p>
+ </desc>
+ </func>
+ <func>
+ <name>day_of_the_week(Date) -> DayNumber</name>
+ <name>day_of_the_week(Year, Month, Day) -> DayNumber</name>
+ <fsummary>Compute the day of the week</fsummary>
+ <type>
+ <v>Date = date()</v>
+ <v>DayNumber = 1..7</v>
+ </type>
+ <desc>
+ <p>This function computes the day of the week given <c>Year</c>,
+ <c>Month</c> and <c>Day</c>. The return value denotes the day
+ of the week as <c>1</c>: Monday, <c>2</c>: Tuesday, and so on.</p>
+ </desc>
+ </func>
+ <func>
+ <name>gregorian_days_to_date(Days) -> Date</name>
+ <fsummary>Compute the date given the number of gregorian days</fsummary>
+ <type>
+ <v>Days = int()</v>
+ <v>Date = date()</v>
+ </type>
+ <desc>
+ <p>This function computes the date given the number of
+ gregorian days.</p>
+ </desc>
+ </func>
+ <func>
+ <name>gregorian_seconds_to_datetime(Seconds) -> {Date, Time}</name>
+ <fsummary>Compute the date given the number of gregorian days</fsummary>
+ <type>
+ <v>Seconds = int()</v>
+ <v>Date = date()</v>
+ <v>Time = time()</v>
+ </type>
+ <desc>
+ <p>This function computes the date and time from the given
+ number of gregorian seconds.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_leap_year(Year) -> bool()</name>
+ <fsummary>Check if a year is a leap year</fsummary>
+ <desc>
+ <p>This function checks if a year is a leap year.</p>
+ </desc>
+ </func>
+ <func>
+ <name>last_day_of_the_month(Year, Month) -> int()</name>
+ <fsummary>Compute the number of days in a month</fsummary>
+ <desc>
+ <p>This function computes the number of days in a month.</p>
+ </desc>
+ </func>
+ <func>
+ <name>local_time() -> {Date, Time}</name>
+ <fsummary>Compute local time</fsummary>
+ <type>
+ <v>Date = date()</v>
+ <v>Time = time()</v>
+ </type>
+ <desc>
+ <p>This function returns the local time reported by
+ the underlying operating system.</p>
+ </desc>
+ </func>
+ <func>
+ <name>local_time_to_universal_time({Date1, Time1}) -> {Date2, Time2}</name>
+ <fsummary>Convert from local time to universal time (deprecated)</fsummary>
+ <desc>
+ <p>This function converts from local time to Universal
+ Coordinated Time (UTC). <c>Date1</c> must refer to a local
+ date after Jan 1, 1970.</p>
+ <warning>
+ <p>This function is deprecated. Use
+ <c>local_time_to_universal_time_dst/1</c> instead, as it
+ gives a more correct and complete result. Especially for
+ the period that does not exist since it gets skipped during
+ the switch <em>to</em> daylight saving time, this function
+ still returns a result.</p>
+ </warning>
+ </desc>
+ </func>
+ <func>
+ <name>local_time_to_universal_time_dst({Date1, Time1}) -> [{Date, Time}]</name>
+ <fsummary>Convert from local time to universal time(s)</fsummary>
+ <type>
+ <v>Date1 = Date = date()</v>
+ <v>Time1 = Time = time()</v>
+ </type>
+ <desc>
+ <p>This function converts from local time to Universal
+ Coordinated Time (UTC). <c>Date1</c> must refer to a local
+ date after Jan 1, 1970.</p>
+ <p>The return value is a list of 0, 1 or 2 possible UTC times:</p>
+ <taglist>
+ <tag><c>[]</c></tag>
+ <item>
+ <p>For a local <c>{Date1, Time1}</c> during the period that
+ is skipped when switching <em>to</em> daylight saving
+ time, there is no corresponding UTC since the local time
+ is illegal - it has never happened.</p>
+ </item>
+ <tag><c>[DstDateTimeUTC, DateTimeUTC]</c></tag>
+ <item>
+ <p>For a local <c>{Date1, Time1}</c> during the period that
+ is repeated when switching <em>from</em> daylight saving
+ time, there are two corresponding UTCs. One for the first
+ instance of the period when daylight saving time is still
+ active, and one for the second instance.</p>
+ </item>
+ <tag><c>[DateTimeUTC]</c></tag>
+ <item>
+ <p>For all other local times there is only one
+ corresponding UTC.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>now_to_local_time(Now) -> {Date, Time}</name>
+ <fsummary>Convert now to local date and time</fsummary>
+ <type>
+ <v>Now -- see erlang:now/0</v>
+ <v>Date = date()</v>
+ <v>Time = time()</v>
+ </type>
+ <desc>
+ <p>This function returns local date and time converted from
+ the return value from <c>erlang:now()</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>now_to_universal_time(Now) -> {Date, Time}</name>
+ <name>now_to_datetime(Now) -> {Date, Time}</name>
+ <fsummary>Convert now to date and time</fsummary>
+ <type>
+ <v>Now -- see erlang:now/0</v>
+ <v>Date = date()</v>
+ <v>Time = time()</v>
+ </type>
+ <desc>
+ <p>This function returns Universal Coordinated Time (UTC)
+ converted from the return value from <c>erlang:now()</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>seconds_to_daystime(Seconds) -> {Days, Time}</name>
+ <fsummary>Compute days and time from seconds</fsummary>
+ <type>
+ <v>Seconds = Days = int()</v>
+ <v>Time = time()</v>
+ </type>
+ <desc>
+ <p>This function transforms a given number of seconds into days,
+ hours, minutes, and seconds. The <c>Time</c> part is always
+ non-negative, but <c>Days</c> is negative if the argument
+ <c>Seconds</c> is.</p>
+ </desc>
+ </func>
+ <func>
+ <name>seconds_to_time(Seconds) -> Time</name>
+ <fsummary>Compute time from seconds</fsummary>
+ <type>
+ <v>Seconds = int() &lt; 86400</v>
+ <v>Time = time()</v>
+ </type>
+ <desc>
+ <p>This function computes the time from the given number of
+ seconds. <c>Seconds</c> must be less than the number of
+ seconds per day (86400).</p>
+ </desc>
+ </func>
+ <func>
+ <name>time_difference(T1, T2) -> {Days, Time}</name>
+ <fsummary>Compute the difference between two times (deprecated)</fsummary>
+ <desc>
+ <p>This function returns the difference between two <c>{Date, Time}</c> tuples. <c>T2</c> should refer to an epoch later
+ than <c>T1</c>.</p>
+ <warning>
+ <p>This function is obsolete. Use the conversion functions for
+ gregorian days and seconds instead.</p>
+ </warning>
+ </desc>
+ </func>
+ <func>
+ <name>time_to_seconds(Time) -> Seconds</name>
+ <fsummary>Compute the number of seconds since midnight up to the given time</fsummary>
+ <type>
+ <v>Time = time()</v>
+ <v>Seconds = int()</v>
+ </type>
+ <desc>
+ <p>This function computes the number of seconds since midnight
+ up to the specified time.</p>
+ </desc>
+ </func>
+ <func>
+ <name>universal_time() -> {Date, Time}</name>
+ <fsummary>Compute universal time</fsummary>
+ <type>
+ <v>Date = date()</v>
+ <v>Time = time()</v>
+ </type>
+ <desc>
+ <p>This function returns the Universal Coordinated Time (UTC)
+ reported by the underlying operating system. Local time is
+ returned if universal time is not available.</p>
+ </desc>
+ </func>
+ <func>
+ <name>universal_time_to_local_time({Date1, Time1}) -> {Date2, Time2}</name>
+ <fsummary>Convert from universal time to local time</fsummary>
+ <type>
+ <v>Date1 = Date2 = date()</v>
+ <v>Time1 = Time2 = time()</v>
+ </type>
+ <desc>
+ <p>This function converts from Universal Coordinated Time (UTC)
+ to local time. <c>Date1</c> must refer to a date after Jan 1,
+ 1970.</p>
+ </desc>
+ </func>
+ <func>
+ <name>valid_date(Date) -> bool()</name>
+ <name>valid_date(Year, Month, Day) -> bool()</name>
+ <fsummary>Check if a date is valid</fsummary>
+ <type>
+ <v>Date = date()</v>
+ </type>
+ <desc>
+ <p>This function checks if a date is a valid.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Leap Years</title>
+ <p>The notion that every fourth year is a leap year is not
+ completely true. By the Gregorian rule, a year Y is a leap year if
+ either of the following rules is valid:</p>
+ <list type="bulleted">
+ <item>
+ <p>Y is divisible by 4, but not by 100; or</p>
+ </item>
+ <item>
+ <p>Y is divisible by 400.</p>
+ </item>
+ </list>
+ <p>Accordingly, 1996 is a leap year, 1900 is not, but 2000 is.</p>
+ </section>
+
+ <section>
+ <title>Date and Time Source</title>
+ <p>Local time is obtained from the Erlang BIF <c>localtime/0</c>.
+ Universal time is computed from the BIF <c>universaltime/0</c>.</p>
+ <p>The following facts apply:</p>
+ <list type="bulleted">
+ <item>there are 86400 seconds in a day</item>
+ <item>there are 365 days in an ordinary year</item>
+ <item>there are 366 days in a leap year</item>
+ <item>there are 1461 days in a 4 year period</item>
+ <item>there are 36524 days in a 100 year period</item>
+ <item>there are 146097 days in a 400 year period</item>
+ <item>there are 719528 days between Jan 1, 0 and Jan 1, 1970.</item>
+ </list>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/dets.xml b/lib/stdlib/doc/src/dets.xml
new file mode 100644
index 0000000000..8d1398d3b7
--- /dev/null
+++ b/lib/stdlib/doc/src/dets.xml
@@ -0,0 +1,1255 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>dets</title>
+ <prepared>Claes Wikstr&ouml;m</prepared>
+ <responsible>Claes Wikstr&ouml;m</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2001-06-06</date>
+ <rev>B</rev>
+ <file>dets.sgml</file>
+ </header>
+ <module>dets</module>
+ <modulesummary>A Disk Based Term Storage</modulesummary>
+ <description>
+ <p>The module <c>dets</c> provides a term storage on file. The
+ stored terms, in this module called <em>objects</em>, are tuples
+ such that one element is defined to be the key. A Dets
+ <em>table</em> is a collection of objects with the key at the same
+ position stored on a file.</p>
+ <p>Dets is used by the Mnesia application, and is provided as is
+ for users who are interested in an efficient storage of Erlang
+ terms on disk only. Many applications just need to store some
+ terms in a file. Mnesia adds transactions, queries, and
+ distribution. The size of Dets files cannot exceed 2 GB. If larger
+ tables are needed, Mnesia's table fragmentation can be used.</p>
+ <p>There are three types of Dets tables: set, bag and
+ duplicate_bag. A table of type <em>set</em> has at most one object
+ with a given key. If an object with a key already present in the
+ table is inserted, the existing object is overwritten by the new
+ object. A table of type <em>bag</em> has zero or more different
+ objects with a given key. A table of type <em>duplicate_bag</em>
+ has zero or more possibly matching objects with a given key.</p>
+ <p>Dets tables must be opened before they can be updated or read,
+ and when finished they must be properly closed. If a table has not
+ been properly closed, Dets will automatically repair the table.
+ This can take a substantial time if the table is large. A Dets
+ table is closed when the process which opened the table
+ terminates. If several Erlang processes (users) open the same Dets
+ table, they will share the table. The table is properly closed
+ when all users have either terminated or closed the table. Dets
+ tables are not properly closed if the Erlang runtime system is
+ terminated abnormally.</p>
+ <note>
+ <p>A ^C command abnormally terminates an Erlang runtime
+ system in a Unix environment with a break-handler.</p>
+ </note>
+ <p>Since all operations performed by Dets are disk operations, it
+ is important to realize that a single look-up operation involves a
+ series of disk seek and read operations. For this reason, the Dets
+ functions are much slower than the corresponding Ets functions,
+ although Dets exports a similar interface.</p>
+ <p>Dets organizes data as a linear hash list and the hash list
+ grows gracefully as more data is inserted into the table. Space
+ management on the file is performed by what is called a buddy
+ system. The current implementation keeps the entire buddy system
+ in RAM, which implies that if the table gets heavily fragmented,
+ quite some memory can be used up. The only way to defragment a
+ table is to close it and then open it again with the <c>repair</c>
+ option set to <c>force</c>.</p>
+ <p>It is worth noting that the ordered_set type present in Ets is
+ not yet implemented by Dets, neither is the limited support for
+ concurrent updates which makes a sequence of <c>first</c> and
+ <c>next</c> calls safe to use on fixed Ets tables. Both these
+ features will be implemented by Dets in a future release of
+ Erlang/OTP. Until then, the Mnesia application (or some user
+ implemented method for locking) has to be used to implement safe
+ concurrency. Currently, no library of Erlang/OTP has support for
+ ordered disk based term storage.</p>
+ <p>Two versions of the format used for storing objects on file are
+ supported by Dets. The first version, 8, is the format always used
+ for tables created by OTP R7 and earlier. The second version, 9,
+ is the default version of tables created by OTP R8 (and later OTP
+ releases). OTP R8 can create version 8 tables, and convert version
+ 8 tables to version 9, and vice versa, upon request.
+ </p>
+ <p>All Dets functions return <c>{error, Reason}</c> if an error
+ occurs (<c>first/1</c> and <c>next/2</c> are exceptions, they exit
+ the process with the error tuple). If given badly formed
+ arguments, all functions exit the process with a <c>badarg</c>
+ message.</p>
+ <p><em>Types</em></p>
+ <pre>
+access() = read | read_write
+auto_save() = infinity | int()
+bindings_cont() = tuple()
+bool() = true | false
+file() = string()
+int() = integer() >= 0
+keypos() = integer() >= 1
+name() = atom() | ref()
+no_slots() = integer() >= 0 | default
+object() = tuple()
+object_cont() = tuple()
+select_cont() = tuple()
+type() = bag | duplicate_bag | set
+version() = 8 | 9 | default </pre>
+ </description>
+ <funcs>
+ <func>
+ <name>all() -> [Name]</name>
+ <fsummary>Return a list of the names of all open Dets tables on this node.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ </type>
+ <desc>
+ <p>Returns a list of the names of all open tables on this
+ node.</p>
+ </desc>
+ </func>
+ <func>
+ <name>bchunk(Name, Continuation) -> {Continuation2, Data} | '$end_of_table' | {error, Reason}</name>
+ <fsummary>Return a chunk of objects stored in a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Continuation = start | cont()</v>
+ <v>Continuation2 = cont()</v>
+ <v>Data = binary() | tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a list of objects stored in a table. The exact
+ representation of the returned objects is not public. The
+ lists of data can be used for initializing a table by giving
+ the value <c>bchunk</c> to the <c>format</c> option of the
+ <c>init_table/3</c> function. The Mnesia application uses this
+ function for copying open tables.</p>
+ <p>Unless the table is protected using <c>safe_fixtable/2</c>,
+ calls to <c>bchunk/2</c> may not work as expected if
+ concurrent updates are made to the table.</p>
+ <p>The first time <c>bchunk/2</c> is called, an initial
+ continuation, the atom <c>start</c>, must be provided.</p>
+ <p>The <c>bchunk/2</c> function returns a tuple
+ <c>{Continuation2, Data}</c>, where <c>Data</c> is a list of
+ objects. <c>Continuation2</c> is another continuation which is
+ to be passed on to a subsequent call to <c>bchunk/2</c>. With
+ a series of calls to <c>bchunk/2</c> it is possible to extract
+ all objects of the table.
+ </p>
+ <p><c>bchunk/2</c> returns <c>'$end_of_table'</c> when all
+ objects have been returned, or <c>{error, Reason}</c> if an
+ error occurs.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>close(Name) -> ok | {error, Reason} </name>
+ <fsummary>Close a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ </type>
+ <desc>
+ <p>Closes a table. Only processes that have opened a table are
+ allowed to close it.
+ </p>
+ <p>All open tables must be closed before the system is
+ stopped. If an attempt is made to open a table which has not
+ been properly closed, Dets automatically tries to repair the
+ table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete(Name, Key) -> ok | {error, Reason}</name>
+ <fsummary>Delete all objects with a given key from a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ </type>
+ <desc>
+ <p>Deletes all objects with the key <c>Key</c> from the table
+ <c>Name</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_all_objects(Name) -> ok | {error, Reason}</name>
+ <fsummary>Delete all objects from a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ </type>
+ <desc>
+ <p>Deletes all objects from a table in almost constant time.
+ However, if the table if fixed, <c>delete_all_objects(T)</c>
+ is equivalent to <c>match_delete(T, '_')</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_object(Name, Object) -> ok | {error, Reason}</name>
+ <fsummary>Delete a given object from a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Object = object()</v>
+ </type>
+ <desc>
+ <p>Deletes all instances of a given object from a table. If a
+ table is of type <c>bag</c> or <c>duplicate_bag</c>, the
+ <c>delete/2</c> function cannot be used to delete only some of
+ the objects with a given key. This function makes this
+ possible.</p>
+ </desc>
+ </func>
+ <func>
+ <name>first(Name) -> Key | '$end_of_table'</name>
+ <fsummary>Return the first key stored in a Dets table.</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Name = name()</v>
+ </type>
+ <desc>
+ <p>Returns the first key stored in the table <c>Name</c>
+ according to the table's internal order, or
+ <c>'$end_of_table'</c> if the table is empty.</p>
+ <p>Unless the table is protected using <c>safe_fixtable/2</c>,
+ subsequent calls to <c>next/2</c> may not work as expected if
+ concurrent updates are made to the table.</p>
+ <p>Should an error occur, the process is exited with an error
+ tuple <c>{error, Reason}</c>. The reason for not returning the
+ error tuple is that it cannot be distinguished from a key.</p>
+ <p>There are two reasons why <c>first/1</c> and <c>next/2</c>
+ should not be used: they are not very efficient, and they
+ prevent the use of the key <c>'$end_of_table'</c> since this
+ atom is used to indicate the end of the table. If possible,
+ the <c>match</c>, <c>match_object</c>, and <c>select</c>
+ functions should be used for traversing tables.</p>
+ </desc>
+ </func>
+ <func>
+ <name>foldl(Function, Acc0, Name) -> Acc1 | {error, Reason}</name>
+ <fsummary>Fold a function over a Dets table.</fsummary>
+ <type>
+ <v>Function = fun(Object, AccIn) -> AccOut</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>Name = name()</v>
+ <v>Object = object()</v>
+ </type>
+ <desc>
+ <p>Calls <c>Function</c> on successive elements of the table
+ <c>Name</c> together with an extra argument <c>AccIn</c>. The
+ order in which the elements of the table are traversed is
+ unspecified. <c>Function</c> must return a new accumulator
+ which is passed to the next call. <c>Acc0</c> is returned if
+ the table is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>foldr(Function, Acc0, Name) -> Acc1 | {error, Reason}</name>
+ <fsummary>Fold a function over a Dets table.</fsummary>
+ <type>
+ <v>Function = fun(Object, AccIn) -> AccOut</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>Name = name()</v>
+ <v>Object = object()</v>
+ </type>
+ <desc>
+ <p>Calls <c>Function</c> on successive elements of the table
+ <c>Name</c> together with an extra argument <c>AccIn</c>. The
+ order in which the elements of the table are traversed is
+ unspecified. <c>Function</c> must return a new accumulator
+ which is passed to the next call. <c>Acc0</c> is returned if
+ the table is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_ets(Name, EtsTab) -> ok | {error, Reason}</name>
+ <fsummary>Replace the objects of a Dets table with the objects of an Ets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>EtsTab = -&nbsp;see ets(3)&nbsp;-</v>
+ </type>
+ <desc>
+ <p>Deletes all objects of the table <c>Name</c> and then
+ inserts all the objects of the Ets table <c>EtsTab</c>. The
+ order in which the objects are inserted is not specified.
+ Since <c>ets:safe_fixtable/2</c> is called the Ets table must
+ be public or owned by the calling process.</p>
+ </desc>
+ </func>
+ <func>
+ <name>info(Name) -> InfoList | undefined</name>
+ <fsummary>Return information about a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>InfoList = [{Item, Value}]</v>
+ </type>
+ <desc>
+ <p>Returns information about the table <c>Name</c> as a list of
+ <c>{Item, Value}</c> tuples:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>{file_size, int()}</c>, the size of the file in
+ bytes.</p>
+ </item>
+ <item>
+ <p><c>{filename, file()}</c>, the name of the file
+ where objects are stored.</p>
+ </item>
+ <item>
+ <p><c>{keypos, keypos()}</c>, the position of the
+ key.</p>
+ </item>
+ <item>
+ <p><c>{size, int()}</c>, the number of objects stored
+ in the table.</p>
+ </item>
+ <item>
+ <p><c>{type, type()}</c>, the type of the table.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>info(Name, Item) -> Value | undefined</name>
+ <fsummary>Return the information associated with a given item for a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ </type>
+ <desc>
+ <p>Returns the information associated with <c>Item</c> for the
+ table <c>Name</c>. In addition to the <c>{Item, Value}</c>
+ pairs defined for <c>info/1</c>, the following items are
+ allowed:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>{access, access()}</c>, the access mode.</p>
+ </item>
+ <item>
+ <p><c>{auto_save, auto_save()}</c>, the auto save
+ interval.</p>
+ </item>
+ <item>
+ <p><c>{bchunk_format, binary()}</c>, an opaque binary
+ describing the format of the objects returned by
+ <c>bchunk/2</c>. The binary can be used as argument to
+ <c>is_compatible_chunk_format/2</c>. Only available for
+ version 9 tables.</p>
+ </item>
+ <item>
+ <p><c>{hash,</c> Hash<c>}</c>. Describes which BIF is
+ used to calculate the hash values of the objects stored in
+ the Dets table. Possible values of Hash are <c>hash</c>,
+ which implies that the <c>erlang:hash/2</c> BIF is used,
+ <c>phash</c>, which implies that the <c>erlang:phash/2</c>
+ BIF is used, and <c>phash2</c>, which implies that the
+ <c>erlang:phash2/1</c> BIF is used.</p>
+ </item>
+ <item>
+ <p><c>{memory, int()}</c>, the size of the file in
+ bytes. The same value is associated with the item
+ <c>file_size</c>.</p>
+ </item>
+ <item>
+ <p><c>{no_keys, int()}</c>, the number of different
+ keys stored in the table. Only available for version 9
+ tables.</p>
+ </item>
+ <item>
+ <p><c>{no_objects, int()}</c>, the number of objects
+ stored in the table.</p>
+ </item>
+ <item>
+ <p><c>{no_slots, {Min, Used, Max}}</c>, the number of
+ slots of the table. <c>Min</c> is the minimum number of
+ slots, <c>Used</c> is the number of currently used slots,
+ and <c>Max</c> is the maximum number of slots. Only
+ available for version 9 tables.</p>
+ </item>
+ <item>
+ <p><c>{owner, pid()}</c>, the pid of the process that
+ handles requests to the Dets table.</p>
+ </item>
+ <item>
+ <p><c>{ram_file, bool()}</c>, whether the table is
+ kept in RAM.</p>
+ </item>
+ <item>
+ <p><c>{safe_fixed,</c> SafeFixed<c>}</c>. If the table
+ is fixed, SafeFixed is a tuple <c>{FixedAtTime, [{Pid,RefCount}]}</c>. <c>FixedAtTime</c> is the time when
+ the table was first fixed, and <c>Pid</c> is the pid of
+ the process that fixes the table <c>RefCount</c> times.
+ There may be any number of processes in the list. If the
+ table is not fixed, SafeFixed is the atom <c>false</c>.</p>
+ </item>
+ <item>
+ <p><c>{version, int()}</c>, the version of the format
+ of the table.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>init_table(Name, InitFun [, Options]) -> ok | {error, Reason}</name>
+ <fsummary>Replace all objects of a Dets table.</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ <v>InitFun = fun(Arg) -> Res</v>
+ <v>Arg = read | close</v>
+ <v>Res = end_of_input | {[object()], InitFun} | {Data, InitFun} | term()</v>
+ <v>Data = binary() | tuple()</v>
+ </type>
+ <desc>
+ <p>Replaces the existing objects of the table <c>Name</c> with
+ objects created by calling the input function <c>InitFun</c>,
+ see below. The reason for using this function rather than
+ calling <c>insert/2</c> is that of efficiency. It should be
+ noted that the input functions are called by the process that
+ handles requests to the Dets table, not by the calling
+ process.</p>
+ <p>When called with the argument <c>read</c> the function
+ <c>InitFun</c> is assumed to return <c>end_of_input</c> when
+ there is no more input, or <c>{Objects, Fun}</c>, where
+ <c>Objects</c> is a list of objects and <c>Fun</c> is a new
+ input function. Any other value Value is returned as an error
+ <c>{error, {init_fun, Value}}</c>. Each input function will be
+ called exactly once, and should an error occur, the last
+ function is called with the argument <c>close</c>, the reply
+ of which is ignored.</p>
+ <p>If the type of the table is <c>set</c> and there is more
+ than one object with a given key, one of the objects is
+ chosen. This is not necessarily the last object with the given
+ key in the sequence of objects returned by the input
+ functions. Duplicate keys should be avoided, or the file
+ will be unnecessarily fragmented. This holds also for duplicated
+ objects stored in tables of type <c>bag</c>.</p>
+ <p>It is important that the table has a sufficient number of
+ slots for the objects. If not, the hash list will start to
+ grow when <c>init_table/2</c> returns which will significantly
+ slow down access to the table for a period of time. The
+ minimum number of slots is set by the <c>open_file/2</c>
+ option <c>min_no_slots</c> and returned by the <c>info/2</c>
+ item <c>no_slots</c>. See also the <c>min_no_slots</c> option
+ below.
+ </p>
+ <p>The <c>Options</c> argument is a list of <c>{Key, Val}</c>
+ tuples where the following values are allowed:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>{min_no_slots, no_slots()}</c>. Specifies the
+ estimated number of different keys that will be stored
+ in the table. The <c>open_file</c> option with the same
+ name is ignored unless the table is created, and in that
+ case performance can be enhanced by supplying an
+ estimate when initializing the table.</p>
+ </item>
+ <item>
+ <p><c>{format, Format}</c>. Specifies the format of the
+ objects returned by the function <c>InitFun</c>. If
+ <c>Format</c> is <c>term</c> (the default),
+ <c>InitFun</c> is assumed to return a list of tuples. If
+ <c>Format</c> is <c>bchunk</c>, <c>InitFun</c> is
+ assumed to return <c>Data</c> as returned by
+ <c>bchunk/2</c>. This option overrides the
+ <c>min_no_slots</c> option.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>insert(Name, Objects) -> ok | {error, Reason}</name>
+ <fsummary>Insert one or more objects into a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Objects = object() | [object()]</v>
+ </type>
+ <desc>
+ <p>Inserts one or more objects into the table <c>Name</c>. If
+ there already exists an object with a key matching the key of
+ some of the given objects and the table type is <c>set</c>,
+ the old object will be replaced.</p>
+ </desc>
+ </func>
+ <func>
+ <name>insert_new(Name, Objects) -> Bool</name>
+ <fsummary>Insert one or more objects into a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Objects = object() | [object()]</v>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Inserts one or more objects into the table <c>Name</c>. If
+ there already exists some object with a key matching the key
+ of any of the given objects the table is not updated and
+ <c>false</c> is returned, otherwise the objects are inserted
+ and <c>true</c> returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_compatible_bchunk_format(Name, BchunkFormat) -> Bool</name>
+ <fsummary>Test compatibility of a table's chunk data.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>BchunkFormat = binary()</v>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if it would be possible to initialize
+ the table <c>Name</c>, using <c>init_table/3</c> with the
+ option <c>{format,&nbsp;bchunk}</c>, with objects read with
+ <c>bchunk/2</c> from some table <c>T</c> such that calling
+ <c>info(T,&nbsp;bchunk_format)</c> returns
+ <c>BchunkFormat</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_dets_file(FileName) -> Bool | {error, Reason}</name>
+ <fsummary>Test for a Dets table.</fsummary>
+ <type>
+ <v>FileName = file()</v>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if the file <c>FileName</c> is a Dets
+ table, <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>lookup(Name, Key) -> [Object] | {error, Reason}</name>
+ <fsummary>Return all objects with a given key stored in a Dets table.</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Name = name()</v>
+ <v>Object = object()</v>
+ </type>
+ <desc>
+ <p>Returns a list of all objects with the key <c>Key</c>
+ stored in the table <c>Name</c>. For example:</p>
+ <pre>
+2> <input>dets:open_file(abc, [{type, bag}]).</input>
+{ok,abc}
+3> <input>dets:insert(abc, {1,2,3}).</input>
+ok
+4> <input>dets:insert(abc, {1,3,4}).</input>
+ok
+5> <input>dets:lookup(abc, 1).</input>
+[{1,2,3},{1,3,4}] </pre>
+ <p>If the table is of type <c>set</c>, the function returns
+ either the empty list or a list with one object, as there
+ cannot be more than one object with a given key. If the table
+ is of type <c>bag</c> or <c>duplicate_bag</c>, the function
+ returns a list of arbitrary length.</p>
+ <p>Note that the order of objects returned is unspecified. In
+ particular, the order in which objects were inserted is not
+ reflected.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match(Continuation) -> {[Match], Continuation2} | '$end_of_table' | {error, Reason}</name>
+ <fsummary>Match a chunk of objects stored in a Dets table and return a list of variable bindings.</fsummary>
+ <type>
+ <v>Continuation = Continuation2 = bindings_cont()</v>
+ <v>Match = [term()]</v>
+ </type>
+ <desc>
+ <p>Matches some objects stored in a table and returns a
+ non-empty list of the bindings that match a given pattern in
+ some unspecified order. The table, the pattern, and the number
+ of objects that are matched are all defined by
+ <c>Continuation</c>, which has been returned by a prior call
+ to <c>match/1</c> or <c>match/3</c>.</p>
+ <p>When all objects of the table have been matched,
+ <c>'$end_of_table'</c> is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match(Name, Pattern) -> [Match] | {error, Reason}</name>
+ <fsummary>Match the objects stored in a Dets table and return a list of variable bindings.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Pattern = tuple()</v>
+ <v>Match = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns for each object of the table <c>Name</c> that
+ matches <c>Pattern</c> a list of bindings in some unspecified
+ order. See <seealso marker="ets">ets(3)</seealso> for a
+ description of patterns. If the keypos'th element of
+ <c>Pattern</c> is unbound, all objects of the table are
+ matched. If the keypos'th element is bound, only the
+ objects with the right key are matched.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match(Name, Pattern, N) -> {[Match], Continuation} | '$end_of_table' | {error, Reason}</name>
+ <fsummary>Match the first chunk of objects stored in a Dets table and return a list of variable bindings.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Pattern = tuple()</v>
+ <v>N = default | int()</v>
+ <v>Match = [term()]</v>
+ <v>Continuation = bindings_cont()</v>
+ </type>
+ <desc>
+ <p>Matches some or all objects of the table <c>Name</c> and
+ returns a non-empty list of the bindings that match
+ <c>Pattern</c> in some unspecified order. See <seealso marker="ets">ets(3)</seealso> for a description of
+ patterns.</p>
+ <p>A tuple of the bindings and a continuation is returned,
+ unless the table is empty, in which case
+ <c>'$end_of_table'</c> is returned. The continuation is to be
+ used when matching further objects by calling
+ <c>match/1</c>.</p>
+ <p>If the keypos'th element of <c>Pattern</c> is bound, all
+ objects of the table are matched. If the keypos'th element is
+ unbound, all objects of the table are matched, <c>N</c>
+ objects at a time, until at least one object matches or the
+ end of the table has been reached. The default, indicated by
+ giving <c>N</c> the value <c>default</c>, is to let the number
+ of objects vary depending on the sizes of the objects. If
+ <c>Name</c> is a version 9 table, all objects with the same
+ key are always matched at the same time which implies that
+ more than N objects may sometimes be matched.
+ </p>
+ <p>The table should always be protected using
+ <c>safe_fixtable/2</c> before calling <c>match/3</c>, or
+ errors may occur when calling <c>match/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_delete(Name, Pattern) -> ok | {error, Reason}</name>
+ <fsummary>Delete all objects that match a given pattern from a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Pattern = tuple()</v>
+ </type>
+ <desc>
+ <p>Deletes all objects that match <c>Pattern</c> from the
+ table <c>Name</c>.
+ See <seealso marker="ets#match/2">ets:match/2</seealso> for a
+ description of patterns.</p>
+ <p>If the keypos'th element of <c>Pattern</c> is bound,
+ only the objects with the right key are matched.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_object(Continuation) -> {[Object], Continuation2} | '$end_of_table' | {error, Reason}</name>
+ <fsummary>Match a chunk of objects stored in a Dets table and return a list of objects.</fsummary>
+ <type>
+ <v>Continuation = Continuation2 = object_cont()</v>
+ <v>Object = object()</v>
+ </type>
+ <desc>
+ <p>Returns a non-empty list of some objects stored in a table
+ that match a given pattern in some unspecified order. The
+ table, the pattern, and the number of objects that are matched
+ are all defined by <c>Continuation</c>, which has been
+ returned by a prior call to <c>match_object/1</c> or
+ <c>match_object/3</c>.</p>
+ <p>When all objects of the table have been matched,
+ <c>'$end_of_table'</c> is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_object(Name, Pattern) -> [Object] | {error, Reason}</name>
+ <fsummary>Match the objects stored in a Dets table and return a list of objects.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Pattern = tuple()</v>
+ <v>Object = object()</v>
+ </type>
+ <desc>
+ <p>Returns a list of all objects of the table <c>Name</c> that
+ match <c>Pattern</c> in some unspecified order. See <seealso marker="ets">ets(3)</seealso> for a description of patterns.
+ </p>
+ <p>If the keypos'th element of <c>Pattern</c> is
+ unbound, all objects of the table are matched. If the
+ keypos'th element of <c>Pattern</c> is bound, only the
+ objects with the right key are matched.</p>
+ <p>Using the <c>match_object</c> functions for traversing all
+ objects of a table is more efficient than calling
+ <c>first/1</c> and <c>next/2</c> or <c>slot/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_object(Name, Pattern, N) -> {[Object], Continuation} | '$end_of_table' | {error, Reason}</name>
+ <fsummary>Match the first chunk of objects stored in a Dets table and return a list of objects.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Pattern = tuple()</v>
+ <v>N = default | int()</v>
+ <v>Object = object()</v>
+ <v>Continuation = object_cont()</v>
+ </type>
+ <desc>
+ <p>Matches some or all objects stored in the table <c>Name</c>
+ and returns a non-empty list of the objects that match
+ <c>Pattern</c> in some unspecified order. See <seealso marker="ets">ets(3)</seealso> for a description of
+ patterns.</p>
+ <p>A list of objects and a continuation is returned, unless
+ the table is empty, in which case <c>'$end_of_table'</c>
+ is returned. The continuation is to be used when matching
+ further objects by calling <c>match_object/1</c>.</p>
+ <p>If the keypos'th element of <c>Pattern</c> is bound, all
+ objects of the table are matched. If the keypos'th element is
+ unbound, all objects of the table are matched, <c>N</c>
+ objects at a time, until at least one object matches or the
+ end of the table has been reached. The default, indicated by
+ giving <c>N</c> the value <c>default</c>, is to let the number
+ of objects vary depending on the sizes of the objects. If
+ <c>Name</c> is a version 9 table, all matching objects with
+ the same key are always returned in the same reply which
+ implies that more than N objects may sometimes be returned.
+ </p>
+ <p>The table should always be protected using
+ <c>safe_fixtable/2</c> before calling <c>match_object/3</c>,
+ or errors may occur when calling <c>match_object/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>member(Name, Key) -> Bool | {error, Reason}</name>
+ <fsummary>Test for occurrence of a key in a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Key = term()</v>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Works like <c>lookup/2</c>, but does not return the
+ objects. The function returns <c>true</c> if one or more
+ elements of the table has the key <c>Key</c>, <c>false</c>
+ otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>next(Name, Key1) -> Key2 | '$end_of_table'</name>
+ <fsummary>Return the next key in a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Key1 = Key2 = term()</v>
+ </type>
+ <desc>
+ <p>Returns the key following <c>Key1</c> in the table
+ <c>Name</c> according to the table's internal order, or
+ <c>'$end_of_table'</c> if there is no next key.</p>
+ <p>Should an error occur, the process is exited with an error
+ tuple <c>{error, Reason}</c>.</p>
+ <p>Use <c>first/1</c> to find the first key in the table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>open_file(Filename) -> {ok, Reference} | {error, Reason}</name>
+ <fsummary>Open an existing Dets table.</fsummary>
+ <type>
+ <v>FileName = file()</v>
+ <v>Reference = ref()</v>
+ </type>
+ <desc>
+ <p>Opens an existing table. If the table has not been properly
+ closed, it will be repaired. The returned reference is to be
+ used as the name of the table. This function is most useful
+ for debugging purposes.</p>
+ </desc>
+ </func>
+ <func>
+ <name>open_file(Name, Args) -> {ok, Name} | {error, Reason}</name>
+ <fsummary>Open a Dets table.</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ </type>
+ <desc>
+ <p>Opens a table. An empty Dets table is created if no file
+ exists.</p>
+ <p>The atom <c>Name</c> is the name of the table. The table
+ name must be provided in all subsequent operations on the
+ table. The name can be used by other processes as well, and
+ several process can share one table.
+ </p>
+ <p>If two processes open the same table by giving the same
+ name and arguments, then the table will have two users. If one
+ user closes the table, it still remains open until the second
+ user closes the table.</p>
+ <p>The <c>Args</c> argument is a list of <c>{Key, Val}</c>
+ tuples where the following values are allowed:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>{access, access()}</c>. It is possible to open
+ existing tables in read-only mode. A table which is opened
+ in read-only mode is not subjected to the automatic file
+ reparation algorithm if it is later opened after a crash.
+ The default value is <c>read_write</c>.</p>
+ </item>
+ <item>
+ <p><c>{auto_save, auto_save()}</c>, the auto save
+ interval. If the interval is an integer <c>Time</c>, the
+ table is flushed to disk whenever it is not accessed for
+ <c>Time</c> milliseconds. A table that has been flushed
+ will require no reparation when reopened after an
+ uncontrolled emulator halt. If the interval is the atom
+ <c>infinity</c>, auto save is disabled. The default value
+ is 180000 (3 minutes).</p>
+ </item>
+ <item>
+ <p><c>{estimated_no_objects, int()}</c>. Equivalent to the
+ <c>min_no_slots</c> option.</p>
+ </item>
+ <item>
+ <p><c>{file, file()}</c>, the name of the file to be
+ opened. The default value is the name of the table.</p>
+ </item>
+ <item>
+ <p><c>{max_no_slots, no_slots()}</c>, the maximum number
+ of slots that will be used. The default value is 2 M, and
+ the maximal value is 32 M. Note that a higher value may
+ increase the fragmentation of the table, and conversely,
+ that a smaller value may decrease the fragmentation, at
+ the expense of execution time. Only available for version
+ 9 tables.</p>
+ </item>
+ <item>
+ <p><c>{min_no_slots, no_slots()}</c>. Application
+ performance can be enhanced with this flag by specifying,
+ when the table is created, the estimated number of
+ different keys that will be stored in the table. The
+ default value as well as the minimum value is 256.</p>
+ </item>
+ <item>
+ <p><c>{keypos, keypos()}</c>, the position of the
+ element of each object to be used as key. The default
+ value is 1. The ability to explicitly state the key
+ position is most convenient when we want to store Erlang
+ records in which the first position of the record is the
+ name of the record type.</p>
+ </item>
+ <item>
+ <p><c>{ram_file, bool()}</c>, whether the table is to
+ be kept in RAM. Keeping the table in RAM may sound like an
+ anomaly, but can enhance the performance of applications
+ which open a table, insert a set of objects, and then
+ close the table. When the table is closed, its contents
+ are written to the disk file. The default value is
+ <c>false</c>.</p>
+ </item>
+ <item>
+ <p><c>{repair, Value}</c>. <c>Value</c> can be either
+ a <c>bool()</c> or the atom <c>force</c>. The flag
+ specifies whether the Dets server should invoke the
+ automatic file reparation algorithm. The default is
+ <c>true</c>. If <c>false</c> is specified, there is no
+ attempt to repair the file and <c>{error, {needs_repair,
+ FileName}}</c> is returned if the table needs to be
+ repaired.</p>
+ <p>The value <c>force</c> means that a reparation will
+ take place even if the table has been properly closed.
+ This is how to convert tables created by older versions of
+ STDLIB. An example is tables hashed with the deprecated
+ <c>erlang:hash/2</c> BIF. Tables created with Dets from a
+ STDLIB version of 1.8.2 and later use the
+ <c>erlang:phash/2</c> function or the
+ <c>erlang:phash2/1</c> function, which is preferred.</p>
+ <p>The <c>repair</c> option is ignored if the table is
+ already open.</p>
+ </item>
+ <item>
+ <p><c>{type, type()}</c>, the type of the table. The
+ default value is <c>set</c>.</p>
+ </item>
+ <item>
+ <p><c>{version, version()}</c>, the version of the format
+ used for the table. The default value is <c>9</c>. Tables
+ on the format used before OTP R8 can be created by giving
+ the value <c>8</c>. A version 8 table can be converted to
+ a version 9 table by giving the options <c>{version,9}</c>
+ and <c>{repair,force}</c>.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>pid2name(Pid) -> {ok, Name} | undefined</name>
+ <fsummary>Return the name of the Dets table handled by a pid.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Returns the name of the table given the pid of a process
+ that handles requests to a table, or <c>undefined</c> if
+ there is no such table.</p>
+ <p>This function is meant to be used for debugging only.</p>
+ </desc>
+ </func>
+ <func>
+ <name>repair_continuation(Continuation, MatchSpec) -> Continuation2</name>
+ <fsummary>Repair a continuation from select/1 or select/3.</fsummary>
+ <type>
+ <v>Continuation = Continuation2 = select_cont()</v>
+ <v>MatchSpec = match_spec()</v>
+ </type>
+ <desc>
+ <p>This function can be used to restore an opaque continuation
+ returned by <c>select/3</c> or <c>select/1</c> if the
+ continuation has passed through external term format (been
+ sent between nodes or stored on disk).</p>
+ <p>The reason for this function is that continuation terms
+ contain compiled match specifications and therefore will be
+ invalidated if converted to external term format. Given that
+ the original match specification is kept intact, the
+ continuation can be restored, meaning it can once again be
+ used in subsequent <c>select/1</c> calls even though it has
+ been stored on disk or on another node.</p>
+ <p>See also <c>ets(3)</c> for further explanations and
+ examples.
+ </p>
+ <note>
+ <p>This function is very rarely needed in application code. It
+ is used by Mnesia to implement distributed <c>select/3</c>
+ and <c>select/1</c> sequences. A normal application would
+ either use Mnesia or keep the continuation from being
+ converted to external format.</p>
+ <p>The reason for not having an external representation of
+ compiled match specifications is performance. It may be
+ subject to change in future releases, while this interface
+ will remain for backward compatibility.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>safe_fixtable(Name, Fix)</name>
+ <fsummary>Fix a Dets table for safe traversal.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Fix = bool()</v>
+ </type>
+ <desc>
+ <p>If <c>Fix</c> is <c>true</c>, the table <c>Name</c> is
+ fixed (once more) by the calling process, otherwise the table
+ is released. The table is also released when a fixing process
+ terminates.
+ </p>
+ <p>If several processes fix a table, the table will remain
+ fixed until all processes have released it or terminated. A
+ reference counter is kept on a per process basis, and N
+ consecutive fixes require N releases to release the table.</p>
+ <p>It is not guaranteed that calls to <c>first/1</c>,
+ <c>next/2</c>, select and match functions work as expected
+ even if the table has been fixed; the limited support for
+ concurrency implemented in Ets has not yet been implemented
+ in Dets. Fixing a table currently only disables resizing of
+ the hash list of the table.</p>
+ <p>If objects have been added while the table was fixed, the
+ hash list will start to grow when the table is released which
+ will significantly slow down access to the table for a period
+ of time.</p>
+ </desc>
+ </func>
+ <func>
+ <name>select(Continuation) -> {Selection, Continuation2} | '$end_of_table' | {error, Reason}</name>
+ <fsummary>Apply a match specification to some objects stored in a Dets table.</fsummary>
+ <type>
+ <v>Continuation = Continuation2 = select_cont()</v>
+ <v>Selection = [term()]</v>
+ </type>
+ <desc>
+ <p>Applies a match specification to some objects stored in a
+ table and returns a non-empty list of the results. The
+ table, the match specification, and the number of objects
+ that are matched are all defined by <c>Continuation</c>,
+ which has been returned by a prior call to <c>select/1</c>
+ or <c>select/3</c>.</p>
+ <p>When all objects of the table have been matched,
+ <c>'$end_of_table'</c> is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>select(Name, MatchSpec) -> Selection | {error, Reason}</name>
+ <fsummary>Apply a match specification to all objects stored in a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>Selection = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the results of applying the match specification
+ <c>MatchSpec</c> to all or some objects stored in the table
+ <c>Name</c>. The order of the objects is not specified. See
+ the ERTS User's Guide for a description of match
+ specifications.</p>
+ <p>If the keypos'th element of <c>MatchSpec</c> is
+ unbound, the match specification is applied to all objects of
+ the table. If the keypos'th element is bound, the match
+ specification is applied to the objects with the right key(s)
+ only.</p>
+ <p>Using the <c>select</c> functions for traversing all
+ objects of a table is more efficient than calling
+ <c>first/1</c> and <c>next/2</c> or <c>slot/2</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>select(Name, MatchSpec, N) -> {Selection, Continuation} | '$end_of_table' | {error, Reason}</name>
+ <fsummary>Apply a match specification to the first chunk of objects stored in a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>N = default | int()</v>
+ <v>Selection = [term()]</v>
+ <v>Continuation = select_cont()</v>
+ </type>
+ <desc>
+ <p>Returns the results of applying the match specification
+ <c>MatchSpec</c> to some or all objects stored in the table
+ <c>Name</c>. The order of the objects is not specified. See
+ the ERTS User's Guide for a description of match
+ specifications.</p>
+ <p>A tuple of the results of applying the match specification
+ and a continuation is returned, unless the table is empty,
+ in which case <c>'$end_of_table'</c> is returned. The
+ continuation is to be used when matching further objects by
+ calling <c>select/1</c>.</p>
+ <p>If the keypos'th element of <c>MatchSpec</c> is bound, the
+ match specification is applied to all objects of the table
+ with the right key(s). If the keypos'th element of
+ <c>MatchSpec</c> is unbound, the match specification is
+ applied to all objects of the table, <c>N</c> objects at a
+ time, until at least one object matches or the end of the
+ table has been reached. The default, indicated by giving
+ <c>N</c> the value <c>default</c>, is to let the number of
+ objects vary depending on the sizes of the objects. If
+ <c>Name</c> is a version 9 table, all objects with the same
+ key are always handled at the same time which implies that the
+ match specification may be applied to more than N objects.
+ </p>
+ <p>The table should always be protected using
+ <c>safe_fixtable/2</c> before calling <c>select/3</c>, or
+ errors may occur when calling <c>select/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>select_delete(Name, MatchSpec) -> N | {error, Reason}</name>
+ <fsummary>Delete all objects that match a given pattern from a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>N = int()</v>
+ </type>
+ <desc>
+ <p>Deletes each object from the table <c>Name</c> such that
+ applying the match specification <c>MatchSpec</c> to the
+ object returns the value <c>true</c>. See the ERTS
+ User's Guide for a description of match
+ specifications. Returns the number of deleted objects.</p>
+ <p>If the keypos'th element of <c>MatchSpec</c> is
+ bound, the match specification is applied to the objects
+ with the right key(s) only.</p>
+ </desc>
+ </func>
+ <func>
+ <name>slot(Name, I) -> '$end_of_table' | [Object] | {error, Reason}</name>
+ <fsummary>Return the list of objects associated with a slot of a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>I = int()</v>
+ <v>Object = object()</v>
+ </type>
+ <desc>
+ <p>The objects of a table are distributed among slots,
+ starting with slot <c>0</c> and ending with slot n. This
+ function returns the list of objects associated with slot
+ <c>I</c>. If <c>I</c> is greater than n <c>'$end_of_table'</c>
+ is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sync(Name) -> ok | {error, Reason}</name>
+ <fsummary>Ensure that all updates made to a Dets table are written to disk.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ </type>
+ <desc>
+ <p>Ensures that all updates made to the table <c>Name</c> are
+ written to disk. This also applies to tables which have been
+ opened with the <c>ram_file</c> flag set to <c>true</c>. In
+ this case, the contents of the RAM file are flushed to
+ disk.</p>
+ <p>Note that the space management data structures kept in RAM,
+ the buddy system, is also written to the disk. This may take
+ some time if the table is fragmented.</p>
+ </desc>
+ </func>
+ <func>
+ <name>table(Name [, Options]) -> QueryHandle</name>
+ <fsummary>Return a QLC query handle.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>QueryHandle = -&nbsp;a query handle, see qlc(3)&nbsp;-</v>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {n_objects, Limit} | {traverse, TraverseMethod}</v>
+ <v>Limit = default | integer() >= 1</v>
+ <v>TraverseMethod = first_next | select | {select, MatchSpec}</v>
+ <v>MatchSpec = match_spec()</v>
+ </type>
+ <desc>
+ <p> <marker id="qlc_table"></marker>
+Returns a QLC (Query List
+ Comprehension) query handle. The module <c>qlc</c>
+ implements a query language aimed mainly at Mnesia but Ets
+ tables, Dets tables, and lists are also recognized by <c>qlc</c>
+ as sources of data. Calling <c>dets:table/1,2</c> is the
+ means to make the Dets table <c>Name</c> usable to <c>qlc</c>.</p>
+ <p>When there are only simple restrictions on the key position
+ <c>qlc</c> uses <c>dets:lookup/2</c> to look up the keys, but when
+ that is not possible the whole table is traversed. The
+ option <c>traverse</c> determines how this is done:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>first_next</c>. The table is traversed one key at
+ a time by calling <c>dets:first/1</c> and
+ <c>dets:next/2</c>.</p>
+ </item>
+ <item>
+ <p><c>select</c>. The table is traversed by calling
+ <c>dets:select/3</c> and <c>dets:select/1</c>. The option
+ <c>n_objects</c> determines the number of objects
+ returned (the third argument of <c>select/3</c>). The
+ match specification (the second argument of
+ <c>select/3</c>) is assembled by <c>qlc</c>: simple filters are
+ translated into equivalent match specifications while
+ more complicated filters have to be applied to all
+ objects returned by <c>select/3</c> given a match
+ specification that matches all objects.</p>
+ </item>
+ <item>
+ <p><c>{select, MatchSpec}</c>. As for <c>select</c>
+ the table is traversed by calling <c>dets:select/3</c>
+ and <c>dets:select/1</c>. The difference is that the
+ match specification is explicitly given. This is how to
+ state match specifications that cannot easily be
+ expressed within the syntax provided by <c>qlc</c>.</p>
+ </item>
+ </list>
+ <p>The following example uses an explicit match specification
+ to traverse the table:</p>
+ <pre>
+1> <input>dets:open_file(t, []),</input>
+<input>ok = dets:insert(t, [{1,a},{2,b},{3,c},{4,d}]),</input>
+<input>MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X &lt; 5) -> {Y} end),</input>
+<input>QH1 = dets:table(t, [{traverse, {select, MS}}]).</input> </pre>
+ <p>An example with implicit match specification:</p>
+ <pre>
+2> <input>QH2 = qlc:q([{Y} || {X,Y} &lt;- dets:table(t), (X > 1) or (X &lt; 5)]).</input> </pre>
+ <p>The latter example is in fact equivalent to the former which
+ can be verified using the function <c>qlc:info/1</c>:</p>
+ <pre>
+3> <input>qlc:info(QH1) =:= qlc:info(QH2).</input>
+true </pre>
+ <p><c>qlc:info/1</c> returns information about a query handle,
+ and in this case identical information is returned for the
+ two query handles.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_ets(Name, EtsTab) -> EtsTab | {error, Reason}</name>
+ <fsummary>Insert all objects of a Dets table into an Ets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>EtsTab = -&nbsp;see ets(3)&nbsp;-</v>
+ </type>
+ <desc>
+ <p>Inserts the objects of the Dets table <c>Name</c> into the
+ Ets table <c>EtsTab</c>. The order in which the objects are
+ inserted is not specified. The existing objects of the Ets
+ table are kept unless overwritten.</p>
+ </desc>
+ </func>
+ <func>
+ <name>traverse(Name, Fun) -> Return | {error, Reason}</name>
+ <fsummary>Apply a function to all or some objects stored in a Dets table.</fsummary>
+ <type>
+ <v>Fun = fun(Object) -> FunReturn</v>
+ <v>FunReturn = continue | {continue, Val} | {done, Value}</v>
+ <v>Val = Value = term()</v>
+ <v>Name = name()</v>
+ <v>Object = object()</v>
+ <v>Return = [term()]</v>
+ </type>
+ <desc>
+ <p>Applies <c>Fun</c> to each object stored in the table
+ <c>Name</c> in some unspecified order. Different actions are
+ taken depending on the return value of <c>Fun</c>. The
+ following <c>Fun</c> return values are allowed:</p>
+ <taglist>
+ <tag><c>continue</c></tag>
+ <item>
+ <p>Continue to perform the traversal. For example, the
+ following function can be used to print out the contents
+ of a table:</p>
+ <pre>
+fun(X) -> io:format("~p~n", [X]), continue end. </pre>
+ </item>
+ <tag><c>{continue, Val}</c></tag>
+ <item>
+ <p>Continue the traversal and accumulate <c>Val</c>. The
+ following function is supplied in order to collect all
+ objects of a table in a list: </p>
+ <pre>
+fun(X) -> {continue, X} end. </pre>
+ </item>
+ <tag><c>{done, Value}</c></tag>
+ <item>
+ <p>Terminate the traversal and return <c>[Value | Acc]</c>.</p>
+ </item>
+ </taglist>
+ <p>Any other value returned by <c>Fun</c> terminates the
+ traversal and is immediately returned.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>update_counter(Name, Key, Increment) -> Result</name>
+ <fsummary>Update a counter object stored in a Dets table.</fsummary>
+ <type>
+ <v>Name = name()</v>
+ <v>Key = term()</v>
+ <v>Increment = {Pos, Incr} | Incr</v>
+ <v>Pos = Incr = Result = integer()</v>
+ </type>
+ <desc>
+ <p>Updates the object with key <c>Key</c> stored in the table
+ <c>Name</c> of type <c>set</c> by adding <c>Incr</c> to the
+ element at the <c>Pos</c>:th position. The new counter value
+ is returned. If no position is specified, the element directly
+ following the key is updated.</p>
+ <p>This functions provides a way of updating a counter,
+ without having to look up an object, update the object by
+ incrementing an element and insert the resulting object into
+ the table again.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="ets">ets(3)</seealso>,
+ mnesia(3),
+ <seealso marker="qlc">qlc(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/dict.xml b/lib/stdlib/doc/src/dict.xml
new file mode 100644
index 0000000000..ebcd2eed09
--- /dev/null
+++ b/lib/stdlib/doc/src/dict.xml
@@ -0,0 +1,347 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>dict</title>
+ <prepared>Robert Virding</prepared>
+ <docno></docno>
+ <date>1997-01-15</date>
+ <rev>B</rev>
+ </header>
+ <module>dict</module>
+ <modulesummary>Key-Value Dictionary</modulesummary>
+ <description>
+ <p><c>Dict</c> implements a <c>Key</c> - <c>Value</c> dictionary.
+ The representation of a dictionary is not defined.</p>
+ <p>This module provides exactly the same interface as the module
+ <c>orddict</c>. One difference is that while this module
+ considers two keys as different if they do not match (<c>=:=</c>),
+ <c>orddict</c> considers two keys as different if and only if
+ they do not compare equal (<c>==</c>).</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+dictionary()
+ as returned by new/0</code>
+ </section>
+ <funcs>
+ <func>
+ <name>append(Key, Value, Dict1) -> Dict2</name>
+ <fsummary>Append a value to keys in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function appends a new <c>Value</c> to the current list
+ of values associated with <c>Key</c>. An exception is
+ generated if the initial value associated with <c>Key</c> is
+ not a list of values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>append_list(Key, ValList, Dict1) -> Dict2</name>
+ <fsummary>Append new values to keys in a dictionary</fsummary>
+ <type>
+ <v>ValList = [Value]</v>
+ <v>Key = Value = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function appends a list of values <c>ValList</c> to
+ the current list of values associated with <c>Key</c>. An
+ exception is generated if the initial value associated with
+ <c>Key</c> is not a list of values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>erase(Key, Dict1) -> Dict2</name>
+ <fsummary>Erase a key from a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function erases all items with a given key from a
+ dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fetch(Key, Dict) -> Value</name>
+ <fsummary>Look-up values in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Dict = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function returns the value associated with <c>Key</c>
+ in the dictionary <c>Dict</c>. <c>fetch</c> assumes that
+ the <c>Key</c> is present in the dictionary and an exception
+ is generated if <c>Key</c> is not in the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fetch_keys(Dict) -> Keys</name>
+ <fsummary>Return all keys in a dictionary</fsummary>
+ <type>
+ <v>Dict = dictionary()</v>
+ <v>Keys = [term()]</v>
+ </type>
+ <desc>
+ <p>This function returns a list of all keys in the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>filter(Pred, Dict1) -> Dict2</name>
+ <fsummary>Choose elements which satisfy a predicate</fsummary>
+ <type>
+ <v>Pred = fun(Key, Value) -> bool()</v>
+ <v>&nbsp;Key = Value = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p><c>Dict2</c> is a dictionary of all keys and values in
+ <c>Dict1</c> for which <c>Pred(Key, Value)</c> is <c>true</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>find(Key, Dict) -> {ok, Value} | error</name>
+ <fsummary>Search for a key in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Dict = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function searches for a key in a dictionary. Returns
+ <c>{ok, Value}</c> where <c>Value</c> is the value associated
+ with <c>Key</c>, or <c>error</c> if the key is not present in
+ the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fold(Fun, Acc0, Dict) -> Acc1</name>
+ <fsummary>Fold a function over a dictionary</fsummary>
+ <type>
+ <v>Fun = fun(Key, Value, AccIn) -> AccOut</v>
+ <v>Key = Value = term()</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>Dict = dictionary()</v>
+ </type>
+ <desc>
+ <p>Calls <c>Fun</c> on successive keys and values of
+ <c>Dict</c> together with an extra argument <c>Acc</c>
+ (short for accumulator). <c>Fun</c> must return a new
+ accumulator which is passed to the next call. <c>Acc0</c> is
+ returned if the list is empty. The evaluation order is
+ undefined.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_list(List) -> Dict</name>
+ <fsummary>Convert a list of pairs to a dictionary</fsummary>
+ <type>
+ <v>List = [{Key, Value}]</v>
+ <v>Dict = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function converts the key/value list <c>List</c> to a
+ dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_key(Key, Dict) -> bool()</name>
+ <fsummary>Test if a key is in a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Dict = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function tests if <c>Key</c> is contained in
+ the dictionary <c>Dict</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>map(Fun, Dict1) -> Dict2</name>
+ <fsummary>Map a function over a dictionary</fsummary>
+ <type>
+ <v>Fun = fun(Key, Value1) -> Value2</v>
+ <v>&nbsp;Key = Value1 = Value2 = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p><c>map</c> calls <c>Func</c> on successive keys and values
+ of <c>Dict</c> to return a new value for each key.
+ The evaluation order is undefined.</p>
+ </desc>
+ </func>
+ <func>
+ <name>merge(Fun, Dict1, Dict2) -> Dict3</name>
+ <fsummary>Merge two dictionaries</fsummary>
+ <type>
+ <v>Fun = fun(Key, Value1, Value2) -> Value</v>
+ <v>&nbsp;Key = Value1 = Value2 = Value3 = term()</v>
+ <v>Dict1 = Dict2 = Dict3 = dictionary()</v>
+ </type>
+ <desc>
+ <p><c>merge</c> merges two dictionaries, <c>Dict1</c> and
+ <c>Dict2</c>, to create a new dictionary. All the <c>Key</c>
+ - <c>Value</c> pairs from both dictionaries are included in
+ the new dictionary. If a key occurs in both dictionaries then
+ <c>Fun</c> is called with the key and both values to return a
+ new value. <c>merge</c> could be defined as:</p>
+ <code type="none">
+merge(Fun, D1, D2) ->
+ fold(fun (K, V1, D) ->
+ update(K, fun (V2) -> Fun(K, V1, V2) end, V1, D)
+ end, D2, D1).</code>
+ <p>but is faster.</p>
+ </desc>
+ </func>
+ <func>
+ <name>new() -> dictionary()</name>
+ <fsummary>Create a dictionary</fsummary>
+ <desc>
+ <p>This function creates a new dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>size(Dict) -> int()</name>
+ <fsummary>Return the number of elements in a dictionary</fsummary>
+ <type>
+ <v>Dict = dictionary()</v>
+ </type>
+ <desc>
+ <p>Returns the number of elements in a <c>Dict</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>store(Key, Value, Dict1) -> Dict2</name>
+ <fsummary>Store a value in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p>This function stores a <c>Key</c> - <c>Value</c> pair in a
+ dictionary. If the <c>Key</c> already exists in <c>Dict1</c>,
+ the associated value is replaced by <c>Value</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_list(Dict) -> List</name>
+ <fsummary>Convert a dictionary to a list of pairs</fsummary>
+ <type>
+ <v>Dict = dictionary()</v>
+ <v>List = [{Key, Value}]</v>
+ </type>
+ <desc>
+ <p>This function converts the dictionary to a list
+ representation.</p>
+ </desc>
+ </func>
+ <func>
+ <name>update(Key, Fun, Dict1) -> Dict2</name>
+ <fsummary>Update a value in a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Fun = fun(Value1) -> Value2</v>
+ <v>&nbsp;Value1 = Value2 = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p>Update the a value in a dictionary by calling <c>Fun</c> on
+ the value to get a new value. An exception is generated if
+ <c>Key</c> is not present in the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>update(Key, Fun, Initial, Dict1) -> Dict2</name>
+ <fsummary>Update a value in a dictionary</fsummary>
+ <type>
+ <v>Key = Initial = term()</v>
+ <v>Fun = fun(Value1) -> Value2</v>
+ <v>&nbsp;Value1 = Value2 = term()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p>Update the a value in a dictionary by calling <c>Fun</c> on
+ the value to get a new value. If <c>Key</c> is not present
+ in the dictionary then <c>Initial</c> will be stored as
+ the first value. For example <c>append/3</c> could be defined
+ as:</p>
+ <code type="none">
+append(Key, Val, D) ->
+ update(Key, fun (Old) -> Old ++ [Val] end, [Val], D).</code>
+ </desc>
+ </func>
+ <func>
+ <name>update_counter(Key, Increment, Dict1) -> Dict2</name>
+ <fsummary>Increment a value in a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Increment = number()</v>
+ <v>Dict1 = Dict2 = dictionary()</v>
+ </type>
+ <desc>
+ <p>Add <c>Increment</c> to the value associated with <c>Key</c>
+ and store this value. If <c>Key</c> is not present in
+ the dictionary then <c>Increment</c> will be stored as
+ the first value.</p>
+ <p>This could be defined as:</p>
+ <code type="none">
+update_counter(Key, Incr, D) ->
+ update(Key, fun (Old) -> Old + Incr end, Incr, D).</code>
+ <p>but is faster.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Notes</title>
+ <p>The functions <c>append</c> and <c>append_list</c> are included
+ so we can store keyed values in a list <em>accumulator</em>. For
+ example:</p>
+ <pre>
+> D0 = dict:new(),
+ D1 = dict:store(files, [], D0),
+ D2 = dict:append(files, f1, D1),
+ D3 = dict:append(files, f2, D2),
+ D4 = dict:append(files, f3, D3),
+ dict:fetch(files, D4).
+[f1,f2,f3] </pre>
+ <p>This saves the trouble of first fetching a keyed value,
+ appending a new value to the list of stored values, and storing
+ the result.
+ </p>
+ <p>The function <c>fetch</c> should be used if the key is known to
+ be in the dictionary, otherwise <c>find</c>.</p>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="gb_trees">gb_trees(3)</seealso>,
+ <seealso marker="orddict">orddict(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/digraph.xml b/lib/stdlib/doc/src/digraph.xml
new file mode 100644
index 0000000000..ad256e671f
--- /dev/null
+++ b/lib/stdlib/doc/src/digraph.xml
@@ -0,0 +1,601 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>digraph</title>
+ <prepared>Tony</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>2001-08-27</date>
+ <rev>C</rev>
+ <file>digraph.sgml</file>
+ </header>
+ <module>digraph</module>
+ <modulesummary>Directed Graphs</modulesummary>
+ <description>
+ <p>The <c>digraph</c> module implements a version of labeled
+ directed graphs. What makes the graphs implemented here
+ non-proper directed graphs is that multiple edges between
+ vertices are allowed. However, the customary definition of
+ directed graphs will be used in the text that follows.
+ </p>
+ <p>A <marker id="digraph"></marker><em>directed graph</em> (or just
+ "digraph") is a pair (V,&nbsp;E) of a finite set V of
+ <marker id="vertex"></marker><em>vertices</em> and a finite set E of
+ <marker id="edge"></marker><em>directed edges</em> (or just "edges").
+ The set of
+ edges E is a subset of V&nbsp;&times;&nbsp;V (the Cartesian
+ product of V with itself). In this module, V is allowed to be
+ empty; the so obtained unique digraph is called the
+ <marker id="empty_digraph"></marker><em>empty digraph</em>.
+ Both vertices and edges are represented by unique Erlang terms.
+ </p>
+ <p>Digraphs can be annotated with additional information. Such
+ information may be attached to the vertices and to the edges of
+ the digraph. A digraph which has been annotated is called a
+ <em>labeled digraph</em>, and the information attached to a
+ vertex or an edge is called a <marker id="label"></marker>
+ <em>label</em>. Labels are Erlang terms.
+ </p>
+ <p>An edge e&nbsp;=&nbsp;(v,&nbsp;w) is said to
+ <marker id="emanate"></marker><em>emanate</em> from vertex v and
+ to be <marker id="incident"></marker><em>incident</em> on vertex w.
+ The <marker id="out_degree"></marker><em>out-degree</em> of a vertex
+ is the number of edges emanating from that vertex.
+ The <marker id="in_degree"></marker><em>in-degree</em> of a vertex
+ is the number of edges incident on that vertex.
+ If there is an edge emanating from v and incident on w, then w is
+ said to be an <marker id="out_neighbour"></marker>
+ <em>out-neighbour</em> of v, and v is said to be an
+ <marker id="in_neighbour"></marker><em>in-neighbour</em> of w.
+ A <marker id="path"></marker><em>path</em> P from v[1] to v[k]
+ in a digraph (V,&nbsp;E) is a non-empty sequence
+ v[1],&nbsp;v[2],&nbsp;...,&nbsp;v[k] of vertices in V such that
+ there is an edge (v[i],v[i+1]) in E for
+ 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;&nbsp;k.
+ The <marker id="length"></marker><em>length</em> of the path P is k-1.
+ P is <marker id="simple_path"></marker><em>simple</em> if all
+ vertices are distinct, except that the first and the last vertices
+ may be the same.
+ P is a <marker id="cycle"></marker><em>cycle</em> if the length
+ of P is not zero and v[1] = v[k].
+ A <marker id="loop"></marker><em>loop</em> is a cycle of length one.
+ A <marker id="simple_cycle"></marker><em>simple cycle</em> is a path
+ that is both a cycle and simple.
+ An <marker id="acyclic_digraph"></marker><em>acyclic digraph</em>
+ is a digraph that has no cycles.
+ </p>
+ </description>
+ <funcs>
+ <func>
+ <name>add_edge(G, E, V1, V2, Label) -> edge() | {error, Reason}</name>
+ <name>add_edge(G, V1, V2, Label) -> edge() | {error, Reason}</name>
+ <name>add_edge(G, V1, V2) -> edge() | {error, Reason}</name>
+ <fsummary>Add an edge to a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>E = edge()</v>
+ <v>V1 = V2 = vertex()</v>
+ <v>Label = label()</v>
+ <v>Reason = {bad_edge, Path} | {bad_vertex, V}</v>
+ <v>Path = [vertex()]</v>
+ </type>
+ <desc>
+ <p><c>add_edge/5</c> creates (or modifies) the edge <c>E</c>
+ of the digraph <c>G</c>, using <c>Label</c> as the (new)
+ <seealso marker="#label">label</seealso> of the edge. The
+ edge is <seealso marker="#emanate">emanating</seealso> from
+ <c>V1</c> and <seealso marker="#incident">incident</seealso>
+ on <c>V2</c>. Returns <c>E</c>.
+ </p>
+ <p><c>add_edge(G,&nbsp;V1,&nbsp;V2,&nbsp;Label)</c> is
+ equivalent to
+ <c>add_edge(G,&nbsp;E,&nbsp;V1,&nbsp;V2,&nbsp;Label)</c>,
+ where <c>E</c> is a created edge. The created edge is
+ represented by the term <c>['$e'&nbsp;|&nbsp;N]</c>, where N
+ is an integer&nbsp;&gt;=&nbsp;0.
+ </p>
+ <p><c>add_edge(G,&nbsp;V1,&nbsp;V2)</c> is equivalent to
+ <c>add_edge(G,&nbsp;V1,&nbsp;V2,&nbsp;[])</c>.
+ </p>
+ <p>If the edge would create a cycle in
+ an <seealso marker="#acyclic_digraph">acyclic digraph</seealso>,
+ then <c>{error,&nbsp;{bad_edge,&nbsp;Path}}</c> is returned. If
+ either of <c>V1</c> or <c>V2</c> is not a vertex of the
+ digraph <c>G</c>, then
+ <c>{error,&nbsp;{bad_vertex,&nbsp;</c>V<c>}}</c> is
+ returned, V&nbsp;=&nbsp;<c>V1</c> or
+ V&nbsp;=&nbsp;<c>V2</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>add_vertex(G, V, Label) -> vertex()</name>
+ <name>add_vertex(G, V) -> vertex()</name>
+ <name>add_vertex(G) -> vertex()</name>
+ <fsummary>Add or modify a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ <v>Label = label()</v>
+ </type>
+ <desc>
+ <p><c>add_vertex/3</c> creates (or modifies) the vertex <c>V</c>
+ of the digraph <c>G</c>, using <c>Label</c> as the (new)
+ <seealso marker="#label">label</seealso> of the
+ vertex. Returns <c>V</c>.
+ </p>
+ <p><c>add_vertex(G,&nbsp;V)</c> is equivalent to
+ <c>add_vertex(G,&nbsp;V,&nbsp;[])</c>.
+ </p>
+ <p><c>add_vertex/1</c> creates a vertex using the empty list
+ as label, and returns the created vertex. The created vertex
+ is represented by the term <c>['$v'&nbsp;|&nbsp;N]</c>,
+ where N is an integer&nbsp;&gt;=&nbsp;0.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>del_edge(G, E) -> true</name>
+ <fsummary>Delete an edge from a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>E = edge()</v>
+ </type>
+ <desc>
+ <p>Deletes the edge <c>E</c> from the digraph <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>del_edges(G, Edges) -> true</name>
+ <fsummary>Delete edges from a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>Edges = [edge()]</v>
+ </type>
+ <desc>
+ <p>Deletes the edges in the list <c>Edges</c> from the digraph
+ <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>del_path(G, V1, V2) -> true</name>
+ <fsummary>Delete paths from a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V1 = V2 = vertex()</v>
+ </type>
+ <desc>
+ <p>Deletes edges from the digraph <c>G</c> until there are no
+ <seealso marker="#path">paths</seealso> from the vertex
+ <c>V1</c> to the vertex <c>V2</c>.
+ </p>
+ <p>A sketch of the procedure employed: Find an arbitrary
+ <seealso marker="#simple_path">simple path</seealso>
+ v[1],&nbsp;v[2],&nbsp;...,&nbsp;v[k] from <c>V1</c> to
+ <c>V2</c> in <c>G</c>. Remove all edges of
+ <c>G</c> <seealso marker="#emanate">emanating</seealso> from v[i]
+ and <seealso marker="#incident">incident</seealso> to v[i+1] for
+ 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;&nbsp;k (including multiple
+ edges). Repeat until there is no path between <c>V1</c> and
+ <c>V2</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>del_vertex(G, V) -> true</name>
+ <fsummary>Delete a vertex from a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ </type>
+ <desc>
+ <p>Deletes the vertex <c>V</c> from the digraph <c>G</c>. Any
+ edges <seealso marker="#emanate">emanating</seealso> from
+ <c>V</c> or <seealso marker="#incident">incident</seealso>
+ on <c>V</c> are also deleted.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>del_vertices(G, Vertices) -> true</name>
+ <fsummary>Delete vertices from a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Deletes the vertices in the list <c>Vertices</c> from the
+ digraph <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>delete(G) -> true</name>
+ <fsummary>Delete a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ </type>
+ <desc>
+ <p>Deletes the digraph <c>G</c>. This call is important
+ because digraphs are implemented with <c>Ets</c>. There is
+ no garbage collection of <c>Ets</c> tables. The digraph
+ will, however, be deleted if the process that created the
+ digraph terminates.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>edge(G, E) -> {E, V1, V2, Label} | false</name>
+ <fsummary>Return the vertices and the label of an edge of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>E = edge()</v>
+ <v>V1 = V2 = vertex()</v>
+ <v>Label = label()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{E,&nbsp;V1,&nbsp;V2,&nbsp;Label}</c> where
+ <c>Label</c> is the <seealso marker="#label">label</seealso>
+ of the edge
+ <c>E</c> <seealso marker="#emanate">emanating</seealso> from
+ <c>V1</c> and <seealso marker="#incident">incident</seealso> on
+ <c>V2</c> of the digraph <c>G</c>.
+ If there is no edge <c>E</c> of the
+ digraph <c>G</c>, then <c>false</c> is returned.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>edges(G) -> Edges</name>
+ <fsummary>Return all edges of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>Edges = [edge()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of all edges of the digraph <c>G</c>, in
+ some unspecified order.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>edges(G, V) -> Edges</name>
+ <fsummary>Return the edges emanating from or incident on a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ <v>Edges = [edge()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of all
+ edges <seealso marker="#emanate">emanating</seealso> from
+ or <seealso marker="#incident">incident</seealso> on <c>V</c>
+ of the digraph <c>G</c>, in some unspecified order.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_cycle(G, V) -> Vertices | false</name>
+ <fsummary>Find one cycle in a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V1 = V2 = vertex()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>If there is
+ a <seealso marker="#simple_cycle">simple cycle</seealso> of
+ length two or more through the vertex
+ <c>V</c>, then the cycle is returned as a list
+ <c>[V,&nbsp;...,&nbsp;V]</c> of vertices, otherwise if there
+ is a <seealso marker="#loop">loop</seealso> through
+ <c>V</c>, then the loop is returned as a list <c>[V]</c>. If
+ there are no cycles through <c>V</c>, then <c>false</c> is
+ returned.
+ </p>
+ <p><c>get_path/3</c> is used for finding a simple cycle
+ through <c>V</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>get_path(G, V1, V2) -> Vertices | false</name>
+ <fsummary>Find one path in a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V1 = V2 = vertex()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Tries to find
+ a <seealso marker="#simple_path">simple path</seealso> from
+ the vertex <c>V1</c> to the vertex
+ <c>V2</c> of the digraph <c>G</c>. Returns the path as a
+ list <c>[V1,&nbsp;...,&nbsp;V2]</c> of vertices, or
+ <c>false</c> if no simple path from <c>V1</c> to <c>V2</c>
+ of length one or more exists.
+ </p>
+ <p>The digraph <c>G</c> is traversed in a depth-first manner,
+ and the first path found is returned.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>get_short_cycle(G, V) -> Vertices | false</name>
+ <fsummary>Find one short cycle in a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V1 = V2 = vertex()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Tries to find an as short as
+ possible <seealso marker="#simple_cycle">simple cycle</seealso> through
+ the vertex <c>V</c> of the digraph <c>G</c>. Returns the cycle
+ as a list <c>[V,&nbsp;...,&nbsp;V]</c> of vertices, or
+ <c>false</c> if no simple cycle through <c>V</c> exists.
+ Note that a <seealso marker="#loop">loop</seealso> through
+ <c>V</c> is returned as the list <c>[V,&nbsp;V]</c>.
+ </p>
+ <p><c>get_short_path/3</c> is used for finding a simple cycle
+ through <c>V</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>get_short_path(G, V1, V2) -> Vertices | false</name>
+ <fsummary>Find one short path in a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V1 = V2 = vertex()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Tries to find an as short as
+ possible <seealso marker="#simple_path">simple path</seealso> from
+ the vertex <c>V1</c> to the vertex <c>V2</c> of the digraph <c>G</c>.
+ Returns the path as a list <c>[V1,&nbsp;...,&nbsp;V2]</c> of
+ vertices, or <c>false</c> if no simple path from <c>V1</c>
+ to <c>V2</c> of length one or more exists.
+ </p>
+ <p>The digraph <c>G</c> is traversed in a breadth-first
+ manner, and the first path found is returned.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>in_degree(G, V) -> integer()</name>
+ <fsummary>Return the in-degree of a vertex of a digraph.</fsummary>
+ <type>
+ <v>G= digraph()</v>
+ <v>V = vertex()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#in_degree">in-degree</seealso> of the vertex
+ <c>V</c> of the digraph <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>in_edges(G, V) -> Edges</name>
+ <fsummary>Return all edges incident on a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ <v>Edges = [edge()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of all
+ edges <seealso marker="#incident">incident</seealso> on
+ <c>V</c> of the digraph <c>G</c>, in some unspecified order.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>in_neighbours(G, V) -> Vertices</name>
+ <fsummary>Return all in-neighbours of a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of
+ all <seealso marker="#in_neighbour">in-neighbours</seealso> of
+ <c>V</c> of the digraph <c>G</c>, in some unspecified order.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>info(G) -> InfoList</name>
+ <fsummary>Return information about a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>InfoList = [{cyclicity, Cyclicity}, {memory, NoWords}, {protection, Protection}]</v>
+ <v>Cyclicity = cyclic | acyclic</v>
+ <v>Protection = protected | private</v>
+ <v>NoWords = integer() >= 0</v>
+ </type>
+ <desc>
+ <p>Returns a list of <c>{Tag, Value}</c> pairs describing the
+ digraph <c>G</c>. The following pairs are returned:
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>{cyclicity, Cyclicity}</c>, where <c>Cyclicity</c>
+ is <c>cyclic</c> or <c>acyclic</c>, according to the
+ options given to <c>new</c>.</p>
+ </item>
+ <item>
+ <p><c>{memory, NoWords}</c>, where <c>NoWords</c> is
+ the number of words allocated to the <c>ets</c> tables.</p>
+ </item>
+ <item>
+ <p><c>{protection, Protection}</c>, where <c>Protection</c>
+ is <c>protected</c> or <c>private</c>, according
+ to the options given to <c>new</c>.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>new() -> digraph()</name>
+ <fsummary>Return a protected empty digraph, where cycles are allowed.</fsummary>
+ <desc>
+ <p>Equivalent to <c>new([])</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>new(Type) -> digraph()</name>
+ <fsummary>Create a new empty digraph.</fsummary>
+ <type>
+ <v>Type = [cyclic | acyclic | private | protected]</v>
+ </type>
+ <desc>
+ <p>Returns
+ an <seealso marker="#empty_digraph">empty digraph</seealso> with
+ properties according to the options in <c>Type</c>:</p>
+ <taglist>
+ <tag><c>cyclic</c></tag>
+ <item>Allow <seealso marker="#cycle">cycles</seealso> in the
+ digraph (default).</item>
+ <tag><c>acyclic</c></tag>
+ <item>The digraph is to be kept <seealso marker="#acyclic_digraph">acyclic</seealso>.</item>
+ <tag><c>protected</c></tag>
+ <item>Other processes can read the digraph (default).</item>
+ <tag><c>private</c></tag>
+ <item>The digraph can be read and modified by the creating
+ process only.</item>
+ </taglist>
+ <p>If an unrecognized type option <c>T</c> is given or <c>Type</c>
+ is not a proper list, there will be a <c>badarg</c> exception.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>no_edges(G) -> integer() >= 0</name>
+ <fsummary>Return the number of edges of the a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ </type>
+ <desc>
+ <p>Returns the number of edges of the digraph <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>no_vertices(G) -> integer() >= 0</name>
+ <fsummary>Return the number of vertices of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ </type>
+ <desc>
+ <p>Returns the number of vertices of the digraph <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>out_degree(G, V) -> integer()</name>
+ <fsummary>Return the out-degree of a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#out_degree">out-degree</seealso> of the vertex
+ <c>V</c> of the digraph <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>out_edges(G, V) -> Edges</name>
+ <fsummary>Return all edges emanating from a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ <v>Edges = [edge()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of all
+ edges <seealso marker="#emanate">emanating</seealso> from
+ <c>V</c> of the digraph <c>G</c>, in some unspecified order.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>out_neighbours(G, V) -> Vertices</name>
+ <fsummary>Return all out-neighbours of a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of
+ all <seealso marker="#out_neighbour">out-neighbours</seealso> of
+ <c>V</c> of the digraph <c>G</c>, in some unspecified order.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>vertex(G, V) -> {V, Label} | false</name>
+ <fsummary>Return the label of a vertex of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>V = vertex()</v>
+ <v>Label = label()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{V,&nbsp;Label}</c> where <c>Label</c> is the
+ <seealso marker="#label">label</seealso> of the vertex
+ <c>V</c> of the digraph <c>G</c>, or <c>false</c> if there
+ is no vertex <c>V</c> of the digraph <c>G</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>vertices(G) -> Vertices</name>
+ <fsummary>Return all vertices of a digraph.</fsummary>
+ <type>
+ <v>G = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of all vertices of the digraph <c>G</c>, in
+ some unspecified order.
+ </p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="digraph_utils">digraph_utils(3)</seealso>,
+ <seealso marker="ets">ets(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/digraph_utils.xml b/lib/stdlib/doc/src/digraph_utils.xml
new file mode 100644
index 0000000000..4b137456b3
--- /dev/null
+++ b/lib/stdlib/doc/src/digraph_utils.xml
@@ -0,0 +1,426 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2000</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>digraph_utils</title>
+ <prepared>Hans Bolinder</prepared>
+ <responsible>nobody</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2001-08-27</date>
+ <rev>PA1</rev>
+ <file>digraph_utils.sgml</file>
+ </header>
+ <module>digraph_utils</module>
+ <modulesummary>Algorithms for Directed Graphs</modulesummary>
+ <description>
+ <p>The <c>digraph_utils</c> module implements some algorithms
+ based on depth-first traversal of directed graphs. See the
+ <c>digraph</c> module for basic functions on directed graphs.
+ </p>
+ <p>A <marker id="digraph"></marker><em>directed graph</em> (or
+ just "digraph") is a pair (V,&nbsp;E) of a finite set V of
+ <marker id="vertex"></marker><em>vertices</em> and a finite set E
+ of <marker id="edge"></marker><em>directed edges</em> (or just
+ "edges"). The set of edges E is a subset of V&nbsp;&times;&nbsp;V
+ (the Cartesian product of V with itself).
+ </p>
+ <p>Digraphs can be annotated with additional information. Such
+ information may be attached to the vertices and to the edges of
+ the digraph. A digraph which has been annotated is called a
+ <em>labeled digraph</em>, and the information attached to a
+ vertex or an edge is called a <marker id="label"></marker>
+ <em>label</em>.</p>
+ <p>An edge e&nbsp;=&nbsp;(v,&nbsp;w) is said
+ to <marker id="emanate"></marker><em>emanate</em> from vertex v and
+ to be <marker id="incident"></marker><em>incident</em> on vertex w.
+ If there is an edge emanating from v and incident on w, then w is
+ said to be
+ an <marker id="out_neighbour"></marker><em>out-neighbour</em> of v,
+ and v is said to be
+ an <marker id="in_neighbour"></marker><em>in-neighbour</em> of w.
+ A <marker id="path"></marker><em>path</em> P from v[1] to v[k] in a
+ digraph (V,&nbsp;E) is a non-empty sequence
+ v[1],&nbsp;v[2],&nbsp;...,&nbsp;v[k] of vertices in V such that
+ there is an edge (v[i],v[i+1]) in E for
+ 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;&nbsp;k.
+ The <marker id="length"></marker><em>length</em> of the path P is k-1.
+ P is a <marker id="cycle"></marker><em>cycle</em> if the length of P
+ is not zero and v[1] = v[k].
+ A <marker id="loop"></marker><em>loop</em> is a cycle of length one.
+ An <marker id="acyclic_digraph"></marker><em>acyclic digraph</em> is
+ a digraph that has no cycles.
+ </p>
+
+ <p>A <marker id="depth_first_traversal"></marker> <em>depth-first
+ traversal</em> of a directed digraph can be viewed as a process
+ that visits all vertices of the digraph. Initially, all vertices
+ are marked as unvisited. The traversal starts with an
+ arbitrarily chosen vertex, which is marked as visited, and
+ follows an edge to an unmarked vertex, marking that vertex. The
+ search then proceeds from that vertex in the same fashion, until
+ there is no edge leading to an unvisited vertex. At that point
+ the process backtracks, and the traversal continues as long as
+ there are unexamined edges. If there remain unvisited vertices
+ when all edges from the first vertex have been examined, some
+ hitherto unvisited vertex is chosen, and the process is
+ repeated.
+ </p>
+ <p>A <marker id="partial_ordering"></marker><em>partial ordering</em> of
+ a set S is a transitive, antisymmetric and reflexive relation
+ between the objects of S. The problem
+ of <marker id="topsort"></marker><em>topological sorting</em> is to
+ find a total
+ ordering of S that is a superset of the partial ordering. A
+ digraph G&nbsp;=&nbsp;(V,&nbsp;E) is equivalent to a relation E
+ on V (we neglect the fact that the version of directed graphs
+ implemented in the <c>digraph</c> module allows multiple edges
+ between vertices). If the digraph has no cycles of length two or
+ more, then the reflexive and transitive closure of E is a
+ partial ordering.
+ </p>
+ <p>A <marker id="subgraph"></marker><em>subgraph</em> G' of G is a
+ digraph whose vertices and edges form subsets of the vertices
+ and edges of G. G' is <em>maximal</em> with respect to a
+ property P if all other subgraphs that include the vertices of
+ G' do not have the property P. A <marker
+ id="strong_components"></marker> <em>strongly connected
+ component</em> is a maximal subgraph such that there is a path
+ between each pair of vertices. A <marker
+ id="components"></marker><em>connected component</em> is a
+ maximal subgraph such that there is a path between each pair of
+ vertices, considering all edges undirected. An <marker
+ id="arborescence"></marker><em>arborescence</em> is an acyclic
+ digraph with a vertex V, the <marker
+ id="root"></marker><em>root</em>, such that there is a unique
+ path from V to every other vertex of G. A <marker
+ id="tree"></marker><em>tree</em> is an acyclic non-empty digraph
+ such that there is a unique path between every pair of vertices,
+ considering all edges undirected.</p>
+ </description>
+
+ <funcs>
+ <func>
+ <name>arborescence_root(Digraph) -> no | {yes, Root}</name>
+ <fsummary>Check if a digraph is an arborescence.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Root = vertex()</v>
+ </type>
+ <desc>
+
+ <p>Returns <c>{yes, Root}</c> if <c>Root</c> is
+ the <seealso marker="#root">root</seealso> of the arborescence
+ <c>Digraph</c>, <c>no</c> otherwise.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>components(Digraph) -> [Component]</name>
+ <fsummary>Return the components of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Component = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a list
+ of <seealso marker="#components">connected components</seealso>.
+ Each component is represented by its
+ vertices. The order of the vertices and the order of the
+ components are arbitrary. Each vertex of the digraph
+ <c>Digraph</c> occurs in exactly one component.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>condensation(Digraph) -> CondensedDigraph</name>
+ <fsummary>Return a condensed graph of a digraph.</fsummary>
+ <type>
+ <v>Digraph = CondensedDigraph = digraph()</v>
+ </type>
+ <desc>
+ <p>Creates a digraph where the vertices are
+ the <seealso marker="#strong_components">strongly connected
+ components</seealso> of <c>Digraph</c> as returned by
+ <c>strong_components/1</c>. If X and Y are strongly
+ connected components, and there exist vertices x and y in X
+ and Y respectively such that there is an
+ edge <seealso marker="#emanate">emanating</seealso> from x
+ and <seealso marker="#incident">incident</seealso> on y, then
+ an edge emanating from X and incident on Y is created.
+ </p>
+ <p>The created digraph has the same type as <c>Digraph</c>.
+ All vertices and edges have the
+ default <seealso marker="#label">label</seealso> <c>[]</c>.
+ </p>
+ <p>Each and every <seealso marker="#cycle">cycle</seealso> is
+ included in some strongly connected component, which implies
+ that there always exists
+ a <seealso marker="#topsort">topological ordering</seealso> of the
+ created digraph.</p>
+ </desc>
+ </func>
+ <func>
+ <name>cyclic_strong_components(Digraph) -> [StrongComponent]</name>
+ <fsummary>Return the cyclic strong components of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>StrongComponent = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of <seealso marker="#strong_components">strongly
+ connected components</seealso>.
+ Each strongly component is represented
+ by its vertices. The order of the vertices and the order of
+ the components are arbitrary. Only vertices that are
+ included in some <seealso marker="#cycle">cycle</seealso> in
+ <c>Digraph</c> are returned, otherwise the returned list is
+ equal to that returned by <c>strong_components/1</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>is_acyclic(Digraph) -> bool()</name>
+ <fsummary>Check if a digraph is acyclic.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if and only if the digraph
+ <c>Digraph</c> is <seealso marker="#acyclic_digraph">acyclic</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_arborescence(Digraph) -> bool()</name>
+ <fsummary>Check if a digraph is an arborescence.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if and only if the digraph
+ <c>Digraph</c> is
+ an <seealso marker="#arborescence">arborescence</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_tree(Digraph) -> bool()</name>
+ <fsummary>Check if a digraph is a tree.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if and only if the digraph
+ <c>Digraph</c> is
+ a <seealso marker="#tree">tree</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>loop_vertices(Digraph) -> Vertices</name>
+ <fsummary>Return the vertices of a digraph included in some loop.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of all vertices of <c>Digraph</c> that are
+ included in some <seealso marker="#loop">loop</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>postorder(Digraph) -> Vertices</name>
+ <fsummary>Return the vertices of a digraph in post-order.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns all vertices of the digraph <c>Digraph</c>. The
+ order is given by
+ a <seealso marker="#depth_first_traversal">depth-first
+ traversal</seealso> of the digraph, collecting visited
+ vertices in postorder. More precisely, the vertices visited
+ while searching from an arbitrarily chosen vertex are
+ collected in postorder, and all those collected vertices are
+ placed before the subsequently visited vertices.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>preorder(Digraph) -> Vertices</name>
+ <fsummary>Return the vertices of a digraph in pre-order.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns all vertices of the digraph <c>Digraph</c>. The
+ order is given by
+ a <seealso marker="#depth_first_traversal">depth-first
+ traversal</seealso> of the digraph, collecting visited
+ vertices in pre-order.</p>
+ </desc>
+ </func>
+ <func>
+ <name>reachable(Vertices, Digraph) -> Vertices</name>
+ <fsummary>Return the vertices reachable from some vertices of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns an unsorted list of digraph vertices such that for
+ each vertex in the list, there is
+ a <seealso marker="#path">path</seealso> in <c>Digraph</c> from some
+ vertex of <c>Vertices</c> to the vertex. In particular,
+ since paths may have length zero, the vertices of
+ <c>Vertices</c> are included in the returned list.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>reachable_neighbours(Vertices, Digraph) -> Vertices</name>
+ <fsummary>Return the neighbours reachable from some vertices of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns an unsorted list of digraph vertices such that for
+ each vertex in the list, there is
+ a <seealso marker="#path">path</seealso> in <c>Digraph</c> of length
+ one or more from some vertex of <c>Vertices</c> to the
+ vertex. As a consequence, only those vertices
+ of <c>Vertices</c> that are included in
+ some <seealso marker="#cycle">cycle</seealso> are returned.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>reaching(Vertices, Digraph) -> Vertices</name>
+ <fsummary>Return the vertices that reach some vertices of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns an unsorted list of digraph vertices such that for
+ each vertex in the list, there is
+ a <seealso marker="#path">path</seealso> from the vertex to some
+ vertex of <c>Vertices</c>. In particular, since paths may have
+ length zero, the vertices of <c>Vertices</c> are included in
+ the returned list.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>reaching_neighbours(Vertices, Digraph) -> Vertices</name>
+ <fsummary>Return the neighbours that reach some vertices of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns an unsorted list of digraph vertices such that for
+ each vertex in the list, there is
+ a <seealso marker="#path">path</seealso> of length one or more
+ from the vertex to some vertex of <c>Vertices</c>. As a consequence,
+ only those vertices of <c>Vertices</c> that are included in
+ some <seealso marker="#cycle">cycle</seealso> are returned.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>strong_components(Digraph) -> [StrongComponent]</name>
+ <fsummary>Return the strong components of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>StrongComponent = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of <seealso marker="#strong_components">strongly
+ connected components</seealso>.
+ Each strongly component is represented
+ by its vertices. The order of the vertices and the order of
+ the components are arbitrary. Each vertex of the digraph
+ <c>Digraph</c> occurs in exactly one strong component.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>subgraph(Digraph, Vertices [, Options]) -> Subgraph</name>
+ <fsummary>Return a subgraph of a digraph.</fsummary>
+ <type>
+ <v>Digraph = Subgraph = digraph()</v>
+ <v>Options = [{type, SubgraphType}, {keep_labels, bool()}]</v>
+ <v>SubgraphType = inherit | type()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Creates a maximal <seealso marker="#subgraph">subgraph</seealso> of <c>Digraph</c> having
+ as vertices those vertices of <c>Digraph</c> that are
+ mentioned in <c>Vertices</c>.
+ </p>
+ <p>If the value of the option <c>type</c> is <c>inherit</c>,
+ which is the default, then the type of <c>Digraph</c> is used
+ for the subgraph as well. Otherwise the option value of <c>type</c>
+ is used as argument to <c>digraph:new/1</c>.
+ </p>
+ <p>If the value of the option <c>keep_labels</c> is <c>true</c>,
+ which is the default, then
+ the <seealso marker="#label">labels</seealso> of vertices and edges
+ of <c>Digraph</c> are used for the subgraph as well. If the value
+ is <c>false</c>, then the default label, <c>[]</c>, is used
+ for the subgraph's vertices and edges.
+ </p>
+ <p><c>subgraph(Digraph, Vertices)</c> is equivalent to
+ <c>subgraph(Digraph, Vertices, [])</c>.
+ </p>
+ <p>There will be a <c>badarg</c> exception if any of the arguments
+ are invalid.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>topsort(Digraph) -> Vertices | false</name>
+ <fsummary>Return a topological sorting of the vertices of a digraph.</fsummary>
+ <type>
+ <v>Digraph = digraph()</v>
+ <v>Vertices = [vertex()]</v>
+ </type>
+ <desc>
+ <p>Returns a <seealso marker="#topsort">topological
+ ordering</seealso> of the vertices of the digraph
+ <c>Digraph</c> if such an ordering exists, <c>false</c>
+ otherwise. For each vertex in the returned list, there are
+ no <seealso marker="#out_neighbour">out-neighbours</seealso>
+ that occur earlier in the list.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="digraph">digraph(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/epp.xml b/lib/stdlib/doc/src/epp.xml
new file mode 100644
index 0000000000..455d9dc124
--- /dev/null
+++ b/lib/stdlib/doc/src/epp.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>epp</title>
+ <prepared>Kenneth Lundin</prepared>
+ <responsible>Kenneth Lundin</responsible>
+ <docno>1</docno>
+ <approved>Kenneth Lundin</approved>
+ <checked></checked>
+ <date>97-01-31</date>
+ <rev>B</rev>
+ <file>epp.sgml</file>
+ </header>
+ <module>epp</module>
+ <modulesummary>An Erlang Code Preprocessor</modulesummary>
+ <description>
+ <p>The Erlang code preprocessor includes functions which are used
+ by <c>compile</c> to preprocess macros and include files before
+ the actual parsing takes place.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>open(FileName, IncludePath) -> {ok,Epp} | {error, ErrorDescriptor}</name>
+ <name>open(FileName, IncludePath, PredefMacros) -> {ok,Epp} | {error, ErrorDescriptor}</name>
+ <fsummary>Open a file for preprocessing</fsummary>
+ <type>
+ <v>FileName = atom() | string()</v>
+ <v>IncludePath = [DirectoryName]</v>
+ <v>DirectoryName = atom() | string()</v>
+ <v>PredefMacros = [{atom(),term()}]</v>
+ <v>Epp = pid() -- handle to the epp server</v>
+ <v>ErrorDescriptor = term()</v>
+ </type>
+ <desc>
+ <p>Opens a file for preprocessing.</p>
+ </desc>
+ </func>
+ <func>
+ <name>close(Epp) -> ok</name>
+ <fsummary>Close the preprocessing of the file associated with <c>Epp</c></fsummary>
+ <type>
+ <v>Epp = pid() -- handle to the epp server</v>
+ </type>
+ <desc>
+ <p>Closes the preprocessing of a file.</p>
+ </desc>
+ </func>
+ <func>
+ <name>parse_erl_form(Epp) -> {ok, AbsForm} | {eof, Line} | {error, ErrorInfo}</name>
+ <fsummary>Return the next Erlang form from the opened Erlang source file</fsummary>
+ <type>
+ <v>Epp = pid()</v>
+ <v>AbsForm = term()</v>
+ <v>Line = integer()</v>
+ <v>ErrorInfo = see separate description below.</v>
+ </type>
+ <desc>
+ <p>Returns the next Erlang form from the opened Erlang source file.
+ The tuple <c>{eof, Line}</c> is returned at end-of-file. The first
+ form corresponds to an implicit attribute <c>-file(File,1).</c>, where
+ <c>File</c> is the name of the file.</p>
+ </desc>
+ </func>
+ <func>
+ <name>parse_file(FileName,IncludePath,PredefMacro) -> {ok,[Form]} | {error,OpenError}</name>
+ <fsummary>Preprocesse and parse an Erlang source file</fsummary>
+ <type>
+ <v>FileName = atom() | string()</v>
+ <v>IncludePath = [DirectoryName]</v>
+ <v>DirectoryName = atom() | string()</v>
+ <v>PredefMacros = [{atom(),term()}]</v>
+ <v>Form = term() -- same as returned by erl_parse:parse_form</v>
+ </type>
+ <desc>
+ <p>Preprocesses and parses an Erlang source file.
+ Note that the tuple <c>{eof, Line}</c> returned at end-of-file is
+ included as a "form".</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Error Information</title>
+ <p>The <c>ErrorInfo</c> mentioned above is the standard
+ <c>ErrorInfo</c> structure which is returned from all IO
+ modules. It has the following format:
+ </p>
+ <code type="none">
+ {ErrorLine, Module, ErrorDescriptor} </code>
+ <p>A string which describes the error is obtained with the following call:
+ </p>
+ <code type="none">
+apply(Module, format_error, ErrorDescriptor) </code>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="erl_parse">erl_parse(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_eval.xml b/lib/stdlib/doc/src/erl_eval.xml
new file mode 100644
index 0000000000..62af23c5eb
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_eval.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>erl_eval</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-21</date>
+ <rev>B</rev>
+ <file>erl_eval.sgml</file>
+ </header>
+ <module>erl_eval</module>
+ <modulesummary>The Erlang Meta Interpreter</modulesummary>
+ <description>
+ <p>This module provides an interpreter for Erlang expressions. The
+ expressions are in the abstract syntax as returned by
+ <c>erl_parse</c>, the Erlang parser, or a call to
+ <c>io:parse_erl_exprs/2</c>.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>exprs(Expressions, Bindings) -> {value, Value, NewBindings}</name>
+ <name>exprs(Expressions, Bindings, LocalFunctionHandler) -> {value, Value, NewBindings}</name>
+ <name>exprs(Expressions, Bindings, LocalFunctionHandler, NonlocalFunctionHandler) -> {value, Value, NewBindings}</name>
+ <fsummary>Evaluate expressions</fsummary>
+ <type>
+ <v>Expressions = as returned by erl_parse or io:parse_erl_exprs/2</v>
+ <v>Bindings = as returned by bindings/1</v>
+ <v>LocalFunctionHandler = {value, Func} | {eval, Func} | none</v>
+ <v>NonlocalFunctionHandler = {value, Func} | none</v>
+ </type>
+ <desc>
+ <p>Evaluates <c>Expressions</c> with the set of bindings
+ <c>Bindings</c>, where <c>Expressions</c> is a sequence of
+ expressions (in abstract syntax) of a type which may be
+ returned by <c>io:parse_erl_exprs/2</c>. See below for an
+ explanation of how and when to use the arguments
+ <c>LocalFunctionHandler</c> and <c>NonlocalFunctionHandler</c>.
+ </p>
+ <p>Returns <c>{value, Value, NewBindings}</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>expr(Expression, Bindings) -> { value, Value, NewBindings }</name>
+ <name>expr(Expression, Bindings, LocalFunctionHandler) -> { value, Value, NewBindings }</name>
+ <name>expr(Expression, Bindings, LocalFunctionHandler, NonlocalFunctionHandler) -> { value, Value, NewBindings }</name>
+ <name>expr(Expression, Bindings, LocalFunctionHandler, NonlocalFunctionHandler, ReturnFormat) -> { value, Value, NewBindings } | Value</name>
+ <fsummary>Evaluate expression</fsummary>
+ <type>
+ <v>Expression = as returned by io:parse_erl_form/2, for example</v>
+ <v>Bindings = as returned by bindings/1</v>
+ <v>LocalFunctionHandler = {value, Func} | {eval, Func} | none</v>
+ <v>NonlocalFunctionHandler = {value, Func} | none</v>
+ <v>ReturnFormat = value | none</v>
+ </type>
+ <desc>
+ <p>Evaluates <c>Expression</c> with the set of bindings
+ <c>Bindings</c>. <c>Expression</c> is an expression (in
+ abstract syntax) of a type which may be returned by
+ <c>io:parse_erl_form/2</c>. See below for an explanation of
+ how and when to use the arguments
+ <c>LocalFunctionHandler</c> and
+ <c>NonlocalFunctionHandler</c>.
+ </p>
+ <p>Returns <c>{value, Value, NewBindings}</c> by default. But
+ if the <c>ReturnFormat</c> is <c>value</c> only the <c>Value</c>
+ is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>expr_list(ExpressionList, Bindings) -> {ValueList, NewBindings}</name>
+ <name>expr_list(ExpressionList, Bindings, LocalFunctionHandler) -> {ValueList, NewBindings}</name>
+ <name>expr_list(ExpressionList, Bindings, LocalFunctionHandler, NonlocalFunctionHandler) -> {ValueList, NewBindings}</name>
+ <fsummary>Evaluate a list of expressions</fsummary>
+ <desc>
+ <p>Evaluates a list of expressions in parallel, using the same
+ initial bindings for each expression. Attempts are made to
+ merge the bindings returned from each evaluation. This
+ function is useful in the <c>LocalFunctionHandler</c>. See below.
+ </p>
+ <p>Returns <c>{ValueList, NewBindings}</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>new_bindings() -> BindingStruct</name>
+ <fsummary>Return a bindings structure</fsummary>
+ <desc>
+ <p>Returns an empty binding structure.</p>
+ </desc>
+ </func>
+ <func>
+ <name>bindings(BindingStruct) -> Bindings</name>
+ <fsummary>Return bindings</fsummary>
+ <desc>
+ <p>Returns the list of bindings contained in the binding
+ structure.</p>
+ </desc>
+ </func>
+ <func>
+ <name>binding(Name, BindingStruct) -> Binding</name>
+ <fsummary>Return bindings</fsummary>
+ <desc>
+ <p>Returns the binding of <c>Name</c> in <c>BindingStruct</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>add_binding(Name, Value, Bindings) -> BindingStruct</name>
+ <fsummary>Add a binding</fsummary>
+ <desc>
+ <p>Adds the binding <c>Name = Value</c> to <c>Bindings</c>.
+ Returns an updated binding structure.</p>
+ </desc>
+ </func>
+ <func>
+ <name>del_binding(Name, Bindings) -> BindingStruct</name>
+ <fsummary>Delete a binding</fsummary>
+ <desc>
+ <p>Removes the binding of <c>Name</c> in <c>Bindings</c>.
+ Returns an updated binding structure.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Local Function Handler</title>
+ <p>During evaluation of a function, no calls can be made to local
+ functions. An undefined function error would be
+ generated. However, the optional argument
+ <c>LocalFunctionHandler</c> may be used to define a function
+ which is called when there is a call to a local function. The
+ argument can have the following formats:</p>
+ <taglist>
+ <tag><c>{value,Func}</c></tag>
+ <item>
+ <p>This defines a local function handler which is called with:</p>
+ <code type="none">
+Func(Name, Arguments) </code>
+ <p><c>Name</c> is the name of the local function (an atom) and
+ <c>Arguments</c> is a list of the <em>evaluated</em>
+ arguments. The function handler returns the value of the
+ local function. In this case, it is not possible to access
+ the current bindings. To signal an error, the function
+ handler just calls <c>exit/1</c> with a suitable exit value.</p>
+ </item>
+ <tag><c>{eval,Func}</c></tag>
+ <item>
+ <p>This defines a local function handler which is called with:</p>
+ <code type="none">
+Func(Name, Arguments, Bindings) </code>
+ <p><c>Name</c> is the name of the local function (an atom),
+ <c>Arguments</c> is a list of the <em>unevaluated</em>
+ arguments, and <c>Bindings</c> are the current variable
+ bindings. The function handler returns:</p>
+ <code type="none">
+{value,Value,NewBindings} </code>
+ <p><c>Value</c> is the value of the local function and
+ <c>NewBindings</c> are the updated variable bindings. In
+ this case, the function handler must itself evaluate all the
+ function arguments and manage the bindings. To signal an
+ error, the function handler just calls <c>exit/1</c> with a
+ suitable exit value.</p>
+ </item>
+ <tag><c>none</c></tag>
+ <item>
+ <p>There is no local function handler.</p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>Non-local Function Handler</title>
+ <p>The optional argument <c>NonlocalFunctionHandler</c> may be
+ used to define a function which is called in the following
+ cases: a functional object (fun) is called; a built-in function
+ is called; a function is called using the M:F syntax, where M
+ and F are atoms or expressions; an operator Op/A is called
+ (this is handled as a call to the function <c>erlang:Op/A</c>).
+ Exceptions are calls to <c>erlang:apply/2,3</c>; neither of the
+ function handlers will be called for such calls.
+ The argument can have the following formats:</p>
+ <taglist>
+ <tag><c>{value,Func}</c></tag>
+ <item>
+ <p>This defines an nonlocal function handler which is called with:</p>
+ <code type="none">
+Func(FuncSpec, Arguments) </code>
+ <p><c>FuncSpec</c> is the name of the function on the form
+ <c>{Module,Function}</c> or a fun, and <c>Arguments</c> is a
+ list of the <em>evaluated</em> arguments. The function
+ handler returns the value of the function. To
+ signal an error, the function handler just calls
+ <c>exit/1</c> with a suitable exit value.</p>
+ </item>
+ <tag><c>none</c></tag>
+ <item>
+ <p>There is no nonlocal function handler.</p>
+ </item>
+ </taglist>
+ <note>
+ <p>For calls such as <c>erlang:apply(Fun, Args)</c> or
+ <c>erlang:apply(Module, Function, Args)</c> the call of the
+ non-local function handler corresponding to the call to
+ <c>erlang:apply/2,3</c> itself--<c>Func({erlang, apply}, [Fun, Args])</c> or <c>Func({erlang, apply}, [Module, Function, Args])</c>--will never take place. The non-local function
+ handler <em>will</em> however be called with the evaluated
+ arguments of the call to <c>erlang:apply/2,3</c>: <c>Func(Fun, Args)</c> or <c>Func({Module, Function}, Args)</c> (assuming
+ that <c>{Module, Function}</c> is not <c>{erlang, apply}</c>).</p>
+ <p>Calls to functions defined by evaluating fun expressions
+ <c>"fun ... end"</c> are also hidden from non-local function
+ handlers.</p> </note>
+ <p>The nonlocal function handler argument is probably not used as
+ frequently as the local function handler argument. A possible
+ use is to call <c>exit/1</c> on calls to functions that for some
+ reason are not allowed to be called.</p>
+ </section>
+
+ <section>
+ <title>Bugs</title>
+ <p>The evaluator is not complete. <c>receive</c> cannot be
+ handled properly.
+ </p>
+ <p>Any undocumented functions in <c>erl_eval</c> should not be used.</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_expand_records.xml b/lib/stdlib/doc/src/erl_expand_records.xml
new file mode 100644
index 0000000000..7fb03e7c50
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_expand_records.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2005</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>erl_expand_records</title>
+ <prepared>Hans Bolinder</prepared>
+ <responsible>nobody</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2005-12-23</date>
+ <rev>PA1</rev>
+ <file>erl_expand_records.sgml</file>
+ </header>
+ <module>erl_expand_records</module>
+ <modulesummary>Expands Records in a Module</modulesummary>
+ <description>
+ </description>
+ <funcs>
+ <func>
+ <name>module(AbsForms, CompileOptions) -> AbsForms</name>
+ <fsummary>Expand all records in a module</fsummary>
+ <type>
+ <v>AbsForms = [term()]</v>
+ <v>CompileOptions = [term()]</v>
+ </type>
+ <desc>
+ <p>Expands all records in a module. The returned module has no
+ references to records, neither attributes nor code.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p>The <seealso marker="erts:absform">abstract format</seealso>
+ documentation in ERTS User's Guide</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml
new file mode 100644
index 0000000000..7c821d2efc
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_id_trans.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>erl_id_trans</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-21</date>
+ <rev>B</rev>
+ <file>erl_id_trans.sgml</file>
+ </header>
+ <module>erl_id_trans</module>
+ <modulesummary>An Identity Parse Transform</modulesummary>
+ <description>
+ <p>This module performs an identity parse transformation of Erlang code.
+ It is included as an example for users who may wish to write their own
+ parse transformers. If the option <c>{parse_transform,Module}</c> is passed
+ to the compiler, a user written function <c>parse_transform/2</c>
+ is called by the compiler before the code is checked for
+ errors.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>parse_transform(Forms, Options) -> Forms</name>
+ <fsummary>Transform Erlang forms</fsummary>
+ <type>
+ <v>Forms = [erlang_form()]</v>
+ <v>Options = [compiler_options()]</v>
+ </type>
+ <desc>
+ <p>Performs an identity transformation on Erlang forms, as an example.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Parse Transformations</title>
+ <p>Parse transformations are used if a programmer wants to use
+ Erlang syntax, but with different semantics. The original Erlang
+ code is then transformed into other Erlang code.
+ </p>
+ <note>
+ <p>Programmers are strongly advised not to engage in parse transformations and no support is offered for problems encountered.</p>
+ </note>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="erl_parse">erl_parse(3)</seealso>, compile(3).</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_internal.xml b/lib/stdlib/doc/src/erl_internal.xml
new file mode 100644
index 0000000000..906b95deb7
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_internal.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>erl_internal</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-21</date>
+ <rev>B</rev>
+ <file>erl_internal.sgml</file>
+ </header>
+ <module>erl_internal</module>
+ <modulesummary>Internal Erlang Definitions</modulesummary>
+ <description>
+ <p>This module defines Erlang BIFs, guard tests and operators.
+ This module is only of interest to programmers who
+ manipulate Erlang code.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>bif(Name, Arity) -> bool()</name>
+ <fsummary>Test for an Erlang BIF</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Name/Arity</c> is an Erlang BIF
+ which is automatically recognized by the compiler, otherwise
+ <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>guard_bif(Name, Arity) -> bool()</name>
+ <fsummary>Test for an Erlang BIF allowed in guards</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Name/Arity</c> is an Erlang BIF
+ which is allowed in guards, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>type_test(Name, Arity) -> bool()</name>
+ <fsummary>Test for a valid type test</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Name/Arity</c> is a valid Erlang
+ type test, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>arith_op(OpName, Arity) -> bool()</name>
+ <fsummary>Test for an arithmetic operator</fsummary>
+ <type>
+ <v>OpName = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>OpName/Arity</c> is an arithmetic
+ operator, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>bool_op(OpName, Arity) -> bool()</name>
+ <fsummary>Test for a Boolean operator</fsummary>
+ <type>
+ <v>OpName = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>OpName/Arity</c> is a Boolean
+ operator, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>comp_op(OpName, Arity) -> bool()</name>
+ <fsummary>Test for a comparison operator</fsummary>
+ <type>
+ <v>OpName = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>OpName/Arity</c> is a comparison
+ operator, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>list_op(OpName, Arity) -> bool()</name>
+ <fsummary>Test for a list operator</fsummary>
+ <type>
+ <v>OpName = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>OpName/Arity</c> is a list
+ operator, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send_op(OpName, Arity) -> bool()</name>
+ <fsummary>Test for a send operator</fsummary>
+ <type>
+ <v>OpName = atom()</v>
+ <v>Arity = integer()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>OpName/Arity</c> is a send
+ operator, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>op_type(OpName, Arity) -> Type</name>
+ <fsummary>Return operator type</fsummary>
+ <type>
+ <v>OpName = atom()</v>
+ <v>Arity = integer()</v>
+ <v>Type = arith | bool | comp | list | send</v>
+ </type>
+ <desc>
+ <p>Returns the <c>Type</c> of operator that <c>OpName/Arity</c>
+ belongs to,
+ or generates a <c>function_clause</c> error if it is not an
+ operator at all.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_lint.xml b/lib/stdlib/doc/src/erl_lint.xml
new file mode 100644
index 0000000000..e339f484cc
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_lint.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>erl_lint</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-27</date>
+ <rev>B</rev>
+ <file>erl_lint.sgml</file>
+ </header>
+ <module>erl_lint</module>
+ <modulesummary>The Erlang Code Linter</modulesummary>
+ <description>
+ <p>This module is used to check Erlang code for illegal syntax and
+ other bugs. It also warns against coding practices which are
+ not recommended. </p>
+ <p>The errors detected include:</p>
+ <list type="bulleted">
+ <item>redefined and undefined functions</item>
+ <item>unbound and unsafe variables</item>
+ <item>illegal record usage.</item>
+ </list>
+ <p>Warnings include:</p>
+ <list type="bulleted">
+ <item>unused functions and imports</item>
+ <item>unused variables</item>
+ <item>variables imported into matches</item>
+ <item>variables exported from
+ <c>if</c>/<c>case</c>/<c>receive</c></item>
+ <item>variables shadowed in lambdas and list
+ comprehensions.</item>
+ </list>
+ <p>Some of the warnings are optional, and can be turned on by
+ giving the appropriate option, described below.</p>
+ <p>The functions in this module are invoked automatically by the
+ Erlang compiler and there is no reason to invoke these
+ functions separately unless you have written your own Erlang
+ compiler.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>module(AbsForms) -> {ok,Warnings} | {error,Errors,Warnings}</name>
+ <name>module(AbsForms, FileName) -> {ok,Warnings} | {error,Errors,Warnings}</name>
+ <name>module(AbsForms, FileName, CompileOptions) -> {ok,Warnings} | {error,Errors,Warnings}</name>
+ <fsummary>Check a module for errors</fsummary>
+ <type>
+ <v>AbsForms = [term()]</v>
+ <v>FileName = FileName2 = atom() | string()</v>
+ <v>Warnings = Errors = [{Filename2,[ErrorInfo]}]</v>
+ <v>ErrorInfo = see separate description below.</v>
+ <v>CompileOptions = [term()]</v>
+ </type>
+ <desc>
+ <p>This function checks all the forms in a module for errors.
+ It returns:
+ </p>
+ <taglist>
+ <tag><c>{ok,Warnings}</c></tag>
+ <item>
+ <p>There were no errors in the module.</p>
+ </item>
+ <tag><c>{error,Errors,Warnings}</c></tag>
+ <item>
+ <p>There were errors in the module.</p>
+ </item>
+ </taglist>
+ <p>Since this module is of interest only to the maintainers of
+ the compiler, and to avoid having the same description in
+ two places to avoid the usual maintenance nightmare, the
+ elements of <c>Options</c> that control the warnings are
+ only described in <seealso marker="compiler:compile#erl_lint_options">compile(3)</seealso>.
+ </p>
+ <p>The <c>AbsForms</c> of a module which comes from a file
+ that is read through <c>epp</c>, the Erlang pre-processor,
+ can come from many files. This means that any references to
+ errors must include the file name (see <seealso marker="epp">epp(3)</seealso>, or parser <seealso marker="erl_parse">erl_parse(3)</seealso> The warnings and
+ errors returned have the following format:
+ </p>
+ <code type="none">
+ [{FileName2,[ErrorInfo]}] </code>
+ <p>The errors and warnings are listed in the order in which
+ they are encountered in the forms. This means that the
+ errors from one file may be split into different entries in
+ the list of errors.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_guard_test(Expr) -> bool()</name>
+ <fsummary>Test for a guard test</fsummary>
+ <type>
+ <v>Expr = term()</v>
+ </type>
+ <desc>
+ <p>This function tests if <c>Expr</c> is a legal guard test.
+ <c>Expr</c> is an Erlang term representing the abstract form
+ for the expression. <c>erl_parse:parse_exprs(Tokens)</c> can
+ be used to generate a list of <c>Expr</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(ErrorDescriptor) -> Chars</name>
+ <fsummary>Format an error descriptor</fsummary>
+ <type>
+ <v>ErrorDescriptor = errordesc()</v>
+ <v>Chars = [char() | Chars]</v>
+ </type>
+ <desc>
+ <p>Takes an <c>ErrorDescriptor</c> and returns a string which
+ describes the error or warning. This function is usually
+ called implicitly when processing an <c>ErrorInfo</c>
+ structure (see below).</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Error Information</title>
+ <p>The <c>ErrorInfo</c> mentioned above is the standard
+ <c>ErrorInfo</c> structure which is returned from all IO
+ modules. It has the following format:
+ </p>
+ <code type="none">
+ {ErrorLine, Module, ErrorDescriptor} </code>
+ <p>A string which describes the error is obtained with the following call:
+ </p>
+ <code type="none">
+apply(Module, format_error, ErrorDescriptor) </code>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="erl_parse">erl_parse(3)</seealso>,
+ <seealso marker="epp">epp(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml
new file mode 100644
index 0000000000..739fde7a40
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_parse.xml
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>erl_parse</title>
+ <prepared>Robert</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-24</date>
+ <rev>B</rev>
+ <file>erl_parse.sgml</file>
+ </header>
+ <module>erl_parse</module>
+ <modulesummary>The Erlang Parser</modulesummary>
+ <description>
+ <p>This module is the basic Erlang parser which converts tokens into
+ the abstract form of either forms (i.e., top-level constructs),
+ expressions, or terms. The Abstract Format is described in the ERTS
+ User's Guide.
+ Note that a token list must end with the <em>dot</em> token in order
+ to be acceptable to the parse functions (see erl_scan).</p>
+ </description>
+ <funcs>
+ <func>
+ <name>parse_form(Tokens) -> {ok, AbsForm} | {error, ErrorInfo}</name>
+ <fsummary>Parse an Erlang form</fsummary>
+ <type>
+ <v>Tokens = [Token]</v>
+ <v>Token = {Tag,Line} | {Tag,Line,term()}</v>
+ <v>Tag = atom()</v>
+ <v>AbsForm = term()</v>
+ <v>ErrorInfo = see section Error Information below.</v>
+ </type>
+ <desc>
+ <p>This function parses <c>Tokens</c> as if it were a form. It returns:</p>
+ <taglist>
+ <tag><c>{ok, AbsForm}</c></tag>
+ <item>
+ <p>The parsing was successful. <c>AbsForm</c> is the
+ abstract form of the parsed form.</p>
+ </item>
+ <tag><c>{error, ErrorInfo}</c></tag>
+ <item>
+ <p>An error occurred.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>parse_exprs(Tokens) -> {ok, Expr_list} | {error, ErrorInfo}</name>
+ <fsummary>Parse Erlang expressions</fsummary>
+ <type>
+ <v>Tokens = [Token]</v>
+ <v>Token = {Tag,Line} | {Tag,Line,term()}</v>
+ <v>Tag = atom()</v>
+ <v>Expr_list = [AbsExpr]</v>
+ <v>AbsExpr = term()</v>
+ <v>ErrorInfo = see section Error Information below.</v>
+ </type>
+ <desc>
+ <p>This function parses <c>Tokens</c> as if it were a list of expressions. It returns:</p>
+ <taglist>
+ <tag><c>{ok, Expr_list}</c></tag>
+ <item>
+ <p>The parsing was successful. <c>Expr_list</c> is a
+ list of the abstract forms of the parsed expressions.</p>
+ </item>
+ <tag><c>{error, ErrorInfo}</c></tag>
+ <item>
+ <p>An error occurred.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>parse_term(Tokens) -> {ok, Term} | {error, ErrorInfo}</name>
+ <fsummary>Parse an Erlang term</fsummary>
+ <type>
+ <v>Tokens = [Token]</v>
+ <v>Token = {Tag,Line} | {Tag,Line,term()}</v>
+ <v>Tag = atom()</v>
+ <v>Term = term()</v>
+ <v>ErrorInfo = see section Error Information below.</v>
+ </type>
+ <desc>
+ <p>This function parses <c>Tokens</c> as if it were a term. It returns:</p>
+ <taglist>
+ <tag><c>{ok, Term}</c></tag>
+ <item>
+ <p>The parsing was successful. <c>Term</c> is
+ the Erlang term corresponding to the token list.</p>
+ </item>
+ <tag><c>{error, ErrorInfo}</c></tag>
+ <item>
+ <p>An error occurred.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(ErrorDescriptor) -> Chars</name>
+ <fsummary>Format an error descriptor</fsummary>
+ <type>
+ <v>ErrorDescriptor = errordesc()</v>
+ <v>Chars = [char() | Chars]</v>
+ </type>
+ <desc>
+ <p>Uses an <c>ErrorDescriptor</c> and returns a string
+ which describes the error. This function is usually called
+ implicitly when an <c>ErrorInfo</c> structure is processed
+ (see below).</p>
+ </desc>
+ </func>
+ <func>
+ <name>tokens(AbsTerm) -> Tokens</name>
+ <name>tokens(AbsTerm, MoreTokens) -> Tokens</name>
+ <fsummary>Generate a list of tokens for an expression</fsummary>
+ <type>
+ <v>Tokens = MoreTokens = [Token]</v>
+ <v>Token = {Tag,Line} | {Tag,Line,term()}</v>
+ <v>Tag = atom()</v>
+ <v>AbsTerm = term()</v>
+ <v>ErrorInfo = see section Error Information below.</v>
+ </type>
+ <desc>
+ <p>This function generates a list of tokens representing the abstract
+ form <c>AbsTerm</c> of an expression. Optionally, it appends
+ <c>Moretokens</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>normalise(AbsTerm) -> Data</name>
+ <fsummary>Convert abstract form to an Erlang term</fsummary>
+ <type>
+ <v>AbsTerm = Data = term()</v>
+ </type>
+ <desc>
+ <p>Converts the abstract form <c>AbsTerm</c> of a term into a
+ conventional Erlang data structure (i.e., the term itself).
+ This is the inverse of <c>abstract/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>abstract(Data) -> AbsTerm</name>
+ <fsummary>Convert an Erlang term into an abstract form</fsummary>
+ <type>
+ <v>Data = AbsTerm = term()</v>
+ </type>
+ <desc>
+ <p>Converts the Erlang data structure <c>Data</c> into an
+ abstract form of type <c>AbsTerm</c>. This is the inverse of
+ <c>normalise/1</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Error Information</title>
+ <p>The <c>ErrorInfo</c> mentioned above is the standard
+ <c>ErrorInfo</c> structure which is returned from all IO
+ modules. It has the format:
+ </p>
+ <code type="none">
+ {ErrorLine, Module, ErrorDescriptor} </code>
+ <p>A string which describes the error is obtained with the following call:
+ </p>
+ <code type="none">
+apply(Module, format_error, ErrorDescriptor) </code>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="io">io(3)</seealso>,
+ <seealso marker="erl_scan">erl_scan(3)</seealso>,
+ ERTS User's Guide</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_pp.xml b/lib/stdlib/doc/src/erl_pp.xml
new file mode 100644
index 0000000000..6b15c5afd3
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_pp.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>erl_pp</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-24</date>
+ <rev>B</rev>
+ <file>erl_pp.sgml</file>
+ </header>
+ <module>erl_pp</module>
+ <modulesummary>The Erlang Pretty Printer</modulesummary>
+ <description>
+ <p>The functions in this module are used to generate
+ aesthetically attractive representations of abstract
+ forms, which are suitable for printing. All functions return (possibly deep)
+ lists of characters and generate an error if the form is wrong.</p>
+ <p>All functions can have an optional argument which specifies a hook
+ that is called if an attempt is made to print an unknown form.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>form(Form) -> DeepCharList</name>
+ <name>form(Form, HookFunction) -> DeepCharList</name>
+ <fsummary>Pretty print a form</fsummary>
+ <type>
+ <v>Form = term()</v>
+ <v>HookFunction = see separate description below.</v>
+ <v>DeepCharList = [char()|DeepCharList]</v>
+ </type>
+ <desc>
+ <p>Pretty prints a
+ <c>Form</c> which is an abstract form of a type which is
+ returned by <c>erl_parse:parse_form</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>attribute(Attribute) -> DeepCharList</name>
+ <name>attribute(Attribute, HookFunction) -> DeepCharList</name>
+ <fsummary>Pretty print an attribute</fsummary>
+ <type>
+ <v>Attribute = term()</v>
+ <v>HookFunction = see separate description below.</v>
+ <v>DeepCharList = [char()|DeepCharList]</v>
+ </type>
+ <desc>
+ <p>The same as <c>form</c>, but only for the attribute
+ <c>Attribute</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>function(Function) -> DeepCharList</name>
+ <name>function(Function, HookFunction) -> DeepCharList</name>
+ <fsummary>Pretty print a function</fsummary>
+ <type>
+ <v>Function = term()</v>
+ <v>HookFunction = see separate description below.</v>
+ <v>DeepCharList = [char()|DeepCharList]</v>
+ </type>
+ <desc>
+ <p>The same as <c>form</c>, but only for the function
+ <c>Function</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>guard(Guard) -> DeepCharList</name>
+ <name>guard(Guard, HookFunction) -> DeepCharList</name>
+ <fsummary>Pretty print a guard</fsummary>
+ <type>
+ <v>Form = term()</v>
+ <v>HookFunction = see separate description below.</v>
+ <v>DeepCharList = [char()|DeepCharList]</v>
+ </type>
+ <desc>
+ <p>The same as <c>form</c>, but only for the guard test
+ <c>Guard</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>exprs(Expressions) -> DeepCharList</name>
+ <name>exprs(Expressions, HookFunction) -> DeepCharList</name>
+ <name>exprs(Expressions, Indent, HookFunction) -> DeepCharList</name>
+ <fsummary>Pretty print <c>Expressions</c></fsummary>
+ <type>
+ <v>Expressions = term()</v>
+ <v>HookFunction = see separate description below.</v>
+ <v>Indent = integer()</v>
+ <v>DeepCharList = [char()|DeepCharList]</v>
+ </type>
+ <desc>
+ <p>The same as <c>form</c>, but only for the sequence of
+ expressions in <c>Expressions</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>expr(Expression) -> DeepCharList</name>
+ <name>expr(Expression, HookFunction) -> DeepCharList</name>
+ <name>expr(Expression, Indent, HookFunction) -> DeepCharList</name>
+ <name>expr(Expression, Indent, Precedence, HookFunction) ->-> DeepCharList</name>
+ <fsummary>Pretty print one <c>Expression</c></fsummary>
+ <type>
+ <v>Expression = term()</v>
+ <v>HookFunction = see separate description below.</v>
+ <v>Indent = integer()</v>
+ <v>Precedence = </v>
+ <v>DeepCharList = [char()|DeepCharList]</v>
+ </type>
+ <desc>
+ <p>This function prints one expression. It is useful for implementing hooks (see below).</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Unknown Expression Hooks</title>
+ <p>The optional argument <c>HookFunction</c>, shown in the functions described above, defines a function which is called when an unknown form occurs where there
+ should be a valid expression. It can have the following formats:</p>
+ <taglist>
+ <tag><c>Function</c></tag>
+ <item>
+ <p>The hook function is called by:</p>
+ <code type="none">
+Function(Expr,
+ CurrentIndentation,
+ CurrentPrecedence,
+ HookFunction) </code>
+ </item>
+ <tag><c>none</c></tag>
+ <item>
+ <p>There is no hook function</p>
+ </item>
+ </taglist>
+ <p>The called hook function should return a (possibly deep) list
+ of characters. <c>expr/4</c> is useful in a hook.
+ </p>
+ <p>If <c>CurrentIndentation</c> is negative, there will be no line
+ breaks and only a space is used as a separator.</p>
+ </section>
+
+ <section>
+ <title>Bugs</title>
+ <p>It should be possible to have hook functions for unknown forms
+ at places other than expressions.</p>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="io">io(3)</seealso>,
+ <seealso marker="erl_parse">erl_parse(3)</seealso>,
+ <seealso marker="erl_eval">erl_eval(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/erl_scan.xml b/lib/stdlib/doc/src/erl_scan.xml
new file mode 100644
index 0000000000..4175146c3c
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_scan.xml
@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>erl_scan</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-24</date>
+ <rev>B</rev>
+ <file>erl_scan.sgml</file>
+ </header>
+ <module>erl_scan</module>
+ <modulesummary>The Erlang Token Scanner</modulesummary>
+ <description>
+ <p>This module contains functions for tokenizing characters into
+ Erlang tokens.</p>
+ </description>
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+category() = atom()
+column() = integer() > 0
+line() = integer()
+location() = line() | {line(), column()}
+reserved_word_fun() -> fun(atom()) -> bool()
+set_attribute_fun() -> fun(term()) -> term()
+symbol() = atom() | float() | integer() | string()
+token() = {category(), attributes()} | {category(), attributes(), symbol()}
+attributes() = line() | list() | tuple()</code>
+ </section>
+ <funcs>
+ <func>
+ <name>string(String) -> Return</name>
+ <name>string(String, StartLocation) -> Return</name>
+ <name>string(String, StartLocation, Options) -> Return</name>
+ <fsummary>Scan a string and return the Erlang tokens</fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>Return = {ok, Tokens, EndLocation} | Error</v>
+ <v>Tokens = [token()]</v>
+ <v>Error = {error, ErrorInfo, EndLocation}</v>
+ <v>StartLocation = EndLocation = location()</v>
+ <v>Options = Option | [Option]</v>
+ <v>Option = {reserved_word_fun,reserved_word_fun()}
+ | return_comments | return_white_spaces | return
+ | text</v>
+ </type>
+ <desc>
+ <p>Takes the list of characters <c>String</c> and tries to
+ scan (tokenize) them. Returns <c>{ok, Tokens, EndLocation}</c>,
+ where <c>Tokens</c> are the Erlang tokens from
+ <c>String</c>. <c>EndLocation</c> is the first location
+ after the last token.</p>
+ <p><c>{error, ErrorInfo, EndLocation}</c> is returned if an
+ error occurs. <c>EndLocation</c> is the first location after
+ the erroneous token.</p>
+ <p><c>string(String)</c> is equivalent to
+ <c>string(String, 1)</c>, and <c>string(String,
+ StartLocation)</c> is equivalent to <c>string(String,
+ StartLocation, [])</c>.</p>
+ <p><c>StartLocation</c> indicates the initial location when
+ scanning starts. If <c>StartLocation</c> is a line
+ <c>attributes()</c> as well as <c>EndLocation</c> and
+ <c>ErrorLocation</c> will be lines. If
+ <c>StartLocation</c> is a pair of a line and a column
+ <c>attributes()</c> takes the form of an opaque compound
+ data type, and <c>EndLocation</c> and <c>ErrorLocation</c>
+ will be pairs of a line and a column. The <em>token
+ attributes</em> contain information about the column and the
+ line where the token begins, as well as the text of the
+ token (if the <c>text</c> option is given), all of which can
+ be accessed by calling <seealso
+ marker="#token_info/1">token_info/1,2</seealso> or <seealso
+ marker="#attributes_info/1">attributes_info/1,2</seealso>.</p>
+ <p>A <em>token</em> is a tuple containing information about
+ syntactic category, the token attributes, and the actual
+ terminal symbol. For punctuation characters (e.g. <c>;</c>,
+ <c>|</c>) and reserved words, the category and the symbol
+ coincide, and the token is represented by a two-tuple.
+ Three-tuples have one of the following forms: <c>{atom,
+ Info, atom()}</c>,
+ <c>{char, Info, integer()}</c>, <c>{comment, Info,
+ string()}</c>, <c>{float, Info, float()}</c>, <c>{integer,
+ Info, integer()}</c>, <c>{var, Info, atom()}</c>,
+ and <c>{white_space, Info, string()}</c>.</p>
+ <p>The valid options are:</p>
+ <taglist>
+ <tag><c>{reserved_word_fun, reserved_word_fun()}</c></tag>
+ <item><p>A callback function that is called when the scanner
+ has found an unquoted atom. If the function returns
+ <c>true</c>, the unquoted atom itself will be the category
+ of the token; if the function returns <c>false</c>,
+ <c>atom</c> will be the category of the unquoted atom.</p>
+ </item>
+ <tag><c>return_comments</c></tag>
+ <item><p>Return comment tokens.</p>
+ </item>
+ <tag><c>return_white_spaces</c></tag>
+ <item><p>Return white space tokens. By convention, if there is
+ a newline character, it is always the first character of the
+ text (there cannot be more than one newline in a white space
+ token).</p>
+ </item>
+ <tag><c>return</c></tag>
+ <item><p>Short for <c>[return_comments, return_white_spaces]</c>.</p>
+ </item>
+ <tag><c>text</c></tag>
+ <item><p>Include the token's text in the token attributes. The
+ text is the part of the input corresponding to the token.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>tokens(Continuation, CharSpec, StartLocation) -> Return</name>
+ <name>tokens(Continuation, CharSpec, StartLocation, Options) -> Return</name>
+ <fsummary>Re-entrant scanner</fsummary>
+ <type>
+ <v>Continuation = [] | Continuation1</v>
+ <v>Return = {done, Result, LeftOverChars} | {more, Continuation1}</v>
+ <v>LeftOverChars = CharSpec</v>
+ <v>CharSpec = string() | eof</v>
+ <v>Continuation1 = tuple()</v>
+ <v>Result = {ok, Tokens, EndLocation} | {eof, EndLocation} | Error</v>
+ <v>Tokens = [token()]</v>
+ <v>Error = {error, ErrorInfo, EndLocation}</v>
+ <v>StartLocation = EndLocation = location()</v>
+ <v>Options = Option | [Option]</v>
+ <v>Option = {reserved_word_fun,reserved_word_fun()}
+ | return_comments | return_white_spaces | return</v>
+ </type>
+ <desc>
+ <p>This is the re-entrant scanner which scans characters until
+ a <em>dot</em> ('.' followed by a white space) or
+ <c>eof</c> has been reached. It returns:</p>
+ <taglist>
+ <tag><c>{done, Result, LeftOverChars}</c></tag>
+ <item>
+ <p>This return indicates that there is sufficient input
+ data to get a result. <c>Result</c> is:</p>
+ <taglist>
+ <tag><c>{ok, Tokens, EndLocation}</c></tag>
+ <item>
+ <p>The scanning was successful. <c>Tokens</c> is the
+ list of tokens including <em>dot</em>.</p>
+ </item>
+ <tag><c>{eof, EndLocation}</c></tag>
+ <item>
+ <p>End of file was encountered before any more tokens.</p>
+ </item>
+ <tag><c>{error, ErrorInfo, EndLocation}</c></tag>
+ <item>
+ <p>An error occurred. <c>LeftOverChars</c> is the remaining
+ characters of the input data,
+ starting from <c>EndLocation</c>.</p>
+ </item>
+ </taglist>
+ </item>
+ <tag><c>{more, Continuation1}</c></tag>
+ <item>
+ <p>More data is required for building a term.
+ <c>Continuation1</c> must be passed in a new call to
+ <c>tokens/3,4</c> when more data is available.</p>
+ </item>
+ </taglist>
+ <p>The <c>CharSpec</c> <c>eof</c> signals end of file.
+ <c>LeftOverChars</c> will then take the value <c>eof</c> as
+ well.</p>
+ <p><c>tokens(Continuation, CharSpec, StartLocation)</c> is
+ equivalent to <c>tokens(Continuation, CharSpec,
+ StartLocation, [])</c>.</p>
+ <p>See <seealso marker="#string/3">string/3</seealso> for a
+ description of the various options.</p>
+ </desc>
+ </func>
+ <func>
+ <name>reserved_word(Atom) -> bool()</name>
+ <fsummary>Test for a reserved word</fsummary>
+ <type>
+ <v>Atom = atom()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Atom</c> is an Erlang reserved
+ word, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>token_info(Token) -> TokenInfo</name>
+ <fsummary>Return information about a token</fsummary>
+ <type>
+ <v>Token = token()</v>
+ <v>TokenInfo = [TokenInfoTuple]</v>
+ <v>TokenInfoTuple = {TokenItem, Info}</v>
+ <v>TokenItem = atom()</v>
+ <v>Info = term()</v>
+ </type>
+ <desc>
+ <p>Returns a list containing information about the token
+ <c>Token</c>. The order of the <c>TokenInfoTuple</c>s is not
+ defined. The following <c>TokenItem</c>s are returned:
+ <c>category</c>, <c>column</c>, <c>length</c>,
+ <c>line</c>, <c>symbol</c>, and <c>text</c>. See <seealso
+ marker="#token_info/2">token_info/2</seealso> for
+ information about specific
+ <c>TokenInfoTuple</c>s.</p>
+ <p>Note that if <c>token_info(Token, TokenItem)</c> returns
+ <c>undefined</c> for some <c>TokenItem</c> in the list above, the
+ item is not included in <c>TokenInfo</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>token_info(Token, TokenItemSpec) -> TokenInfo</name>
+ <fsummary>Return information about a token</fsummary>
+ <type>
+ <v>Token = token()</v>
+ <v>TokenItemSpec = TokenItem | [TokenItem]</v>
+ <v>TokenInfo = TokenInfoTuple | undefined | [TokenInfoTuple]</v>
+ <v>TokenInfoTuple = {TokenItem, Info}</v>
+ <v>TokenItem = atom()</v>
+ <v>Info = term()</v>
+ </type>
+ <desc>
+ <p>Returns a list containing information about the token
+ <c>Token</c>. If <c>TokenItemSpec</c> is a single
+ <c>TokenItem</c>, the returned value is the corresponding
+ <c>TokenInfoTuple</c>, or <c>undefined</c> if the
+ <c>TokenItem</c> has no value. If <c>TokenItemSpec</c> is a
+ list of
+ <c>TokenItem</c>, the result is a list of
+ <c>TokenInfoTuple</c>. The <c>TokenInfoTuple</c>s will
+ appear with the corresponding
+ <c>TokenItem</c>s in the same order as the <c>TokenItem</c>s
+ appeared in the list of <c>TokenItem</c>s. <c>TokenItem</c>s
+ with no value are not included in the list of
+ <c>TokenInfoTuple</c>.</p>
+ <p>The following <c>TokenInfoTuple</c>s with corresponding
+ <c>TokenItem</c>s are valid:</p>
+ <taglist>
+ <tag><c>{category, category()}</c></tag>
+ <item><p>The category of the token.</p>
+ </item>
+ <tag><c>{column, column()}</c></tag>
+ <item><p>The column where the token begins.</p>
+ </item>
+ <tag><c>{length, integer() > 0}</c></tag>
+ <item><p>The length of the token's text.</p>
+ </item>
+ <tag><c>{line, line()}</c></tag>
+ <item><p>The line where the token begins.</p>
+ </item>
+ <tag><c>{location, location()}</c></tag>
+ <item><p>The line and column where the token begins, or
+ just the line if the column unknown.</p>
+ </item>
+ <tag><c>{symbol, symbol()}</c></tag>
+ <item><p>The token's symbol.</p>
+ </item>
+ <tag><c>{text, string()}</c></tag>
+ <item><p>The token's text..</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>attributes_info(Attributes) -> AttributesInfo</name>
+ <fsummary>Return information about token attributes</fsummary>
+ <type>
+ <v>Attributes = attributes()</v>
+ <v>AttributesInfo = [AttributeInfoTuple]</v>
+ <v>AttributeInfoTuple = {AttributeItem, Info}</v>
+ <v>AttributeItem = atom()</v>
+ <v>Info = term()</v>
+ </type>
+ <desc>
+ <p>Returns a list containing information about the token
+ attributes <c>Attributes</c>. The order of the
+ <c>AttributeInfoTuple</c>s is not defined. The following
+ <c>AttributeItem</c>s are returned:
+ <c>column</c>, <c>length</c>, <c>line</c>, and <c>text</c>.
+ See <seealso
+ marker="#attributes_info/2">attributes_info/2</seealso> for
+ information about specific
+ <c>AttributeInfoTuple</c>s.</p>
+ <p>Note that if <c>attributes_info(Token, AttributeItem)</c>
+ returns <c>undefined</c> for some <c>AttributeItem</c> in
+ the list above, the item is not included in
+ <c>AttributesInfo</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>attributes_info(Attributes, AttributeItemSpec) -> AttributesInfo</name>
+ <fsummary>Return information about a token attributes</fsummary>
+ <type>
+ <v>Attributes = attributes()</v>
+ <v>AttributeItemSpec = AttributeItem | [AttributeItem]</v>
+ <v>AttributesInfo = AttributeInfoTuple | undefined
+ | [AttributeInfoTuple]</v>
+ <v>AttributeInfoTuple = {AttributeItem, Info}</v>
+ <v>AttributeItem = atom()</v>
+ <v>Info = term()</v>
+ </type>
+ <desc>
+ <p>Returns a list containing information about the token
+ attributes <c>Attributes</c>. If <c>AttributeItemSpec</c> is
+ a single <c>AttributeItem</c>, the returned value is the
+ corresponding <c>AttributeInfoTuple</c>, or <c>undefined</c>
+ if the <c>AttributeItem</c> has no value. If
+ <c>AttributeItemSpec</c> is a list of
+ <c>AttributeItem</c>, the result is a list of
+ <c>AttributeInfoTuple</c>. The <c>AttributeInfoTuple</c>s
+ will appear with the corresponding <c>AttributeItem</c>s in
+ the same order as the <c>AttributeItem</c>s appeared in the
+ list of <c>AttributeItem</c>s. <c>AttributeItem</c>s with no
+ value are not included in the list of
+ <c>AttributeInfoTuple</c>.</p>
+ <p>The following <c>AttributeInfoTuple</c>s with corresponding
+ <c>AttributeItem</c>s are valid:</p>
+ <taglist>
+ <tag><c>{column, column()}</c></tag>
+ <item><p>The column where the token begins.</p>
+ </item>
+ <tag><c>{length, integer() > 0}</c></tag>
+ <item><p>The length of the token's text.</p>
+ </item>
+ <tag><c>{line, line()}</c></tag>
+ <item><p>The line where the token begins.</p>
+ </item>
+ <tag><c>{location, location()}</c></tag>
+ <item><p>The line and column where the token begins, or
+ just the line if the column unknown.</p>
+ </item>
+ <tag><c>{text, string()}</c></tag>
+ <item><p>The token's text..</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>set_attribute(AttributeItem, Attributes, SetAttributeFun) -> AttributesInfo</name>
+ <fsummary>Set a token attribute value</fsummary>
+ <type>
+ <v>AttributeItem = line</v>
+ <v>Attributes = attributes()</v>
+ <v>SetAttributeFun = set_attribute_fun()</v>
+ </type>
+ <desc>
+ <p>Sets the value of the <c>line</c> attribute of the token
+ attributes <c>Attributes</c>.</p>
+ <p>The <c>SetAttributeFun</c> is called with the value of
+ the <c>line</c> attribute, and is to return the new value of
+ the <c>line</c> attribute.</p>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(ErrorDescriptor) -> string()</name>
+ <fsummary>Format an error descriptor</fsummary>
+ <type>
+ <v>ErrorDescriptor = errordesc()</v>
+ </type>
+ <desc>
+ <p>Takes an <c>ErrorDescriptor</c> and returns a string which
+ describes the error or warning. This function is usually
+ called implicitly when processing an <c>ErrorInfo</c>
+ structure (see below).</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Error Information</title>
+ <p>The <c>ErrorInfo</c> mentioned above is the standard
+ <c>ErrorInfo</c> structure which is returned from all IO
+ modules. It has the following format:</p>
+ <code type="none">
+{ErrorLocation, Module, ErrorDescriptor}</code>
+ <p>A string which describes the error is obtained with the
+ following call:</p>
+ <code type="none">
+Module:format_error(ErrorDescriptor)</code>
+ </section>
+
+ <section>
+ <title>Notes</title>
+ <p>The continuation of the first call to the re-entrant input
+ functions must be <c>[]</c>. Refer to Armstrong, Virding and
+ Williams, 'Concurrent Programming in Erlang', Chapter 13, for a
+ complete description of how the re-entrant input scheme works.</p>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="io">io(3)</seealso>,
+ <seealso marker="erl_parse">erl_parse(3)</seealso></p>
+ </section>
+</erlref>
diff --git a/lib/stdlib/doc/src/erl_tar.xml b/lib/stdlib/doc/src/erl_tar.xml
new file mode 100644
index 0000000000..929620bb88
--- /dev/null
+++ b/lib/stdlib/doc/src/erl_tar.xml
@@ -0,0 +1,434 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2003</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>erl_tar</title>
+ <prepared>Bjorn Gustavsson</prepared>
+ <responsible>Bjorn Gustavsson</responsible>
+ <docno>1</docno>
+ <approved>Kenneth Lundin</approved>
+ <checked></checked>
+ <date>03-01-21</date>
+ <rev>A</rev>
+ <file>erl_tar.sgml</file>
+ </header>
+ <module>erl_tar</module>
+ <modulesummary>Unix 'tar' utility for reading and writing tar archives</modulesummary>
+ <description>
+ <p>The <c>erl_tar</c> module archives and extract files to and from
+ a tar file. The tar file format is the POSIX extended tar file format
+ specified in IEEE Std 1003.1 and ISO/IEC&nbsp;9945-1. That is the same
+ format as used by <c>tar</c> program on Solaris, but is not the same
+ as used by the GNU tar program.</p>
+ <p>By convention, the name of a tar file should end in "<c>.tar</c>".
+ To abide to the convention, you'll need to add "<c>.tar</c>" yourself
+ to the name.</p>
+ <p>Tar files can be created in one operation using the
+ <seealso marker="#create_2">create/2</seealso> or
+ <seealso marker="#create_3">create/3</seealso> function.</p>
+ <p>Alternatively, for more control, the
+ <seealso marker="#open">open</seealso>,
+ <seealso marker="#add">add/3,4</seealso>, and
+ <seealso marker="#close">close/1</seealso> functions can be used.</p>
+ <p>To extract all files from a tar file, use the
+ <seealso marker="#extract_1">extract/1</seealso> function.
+ To extract only some files or to be able to specify some more options,
+ use the <seealso marker="#extract_2">extract/2</seealso> function.</p>
+ <p>To return a list of the files in a tar file,
+ use either the <seealso marker="#table_1">table/1</seealso> or
+ <seealso marker="#table_2">table/2</seealso> function.
+ To print a list of files to the Erlang shell,
+ use either the <seealso marker="#t_1">t/1</seealso> or
+ <seealso marker="#tt_1">tt/1</seealso> function.</p>
+ <p>To convert an error term returned from one of the functions
+ above to a readable message, use the
+ <seealso marker="#format_error_1">format_error/1</seealso> function.</p>
+ </description>
+
+ <section>
+ <title>LIMITATIONS</title>
+ <p>For maximum compatibility, it is safe to archive files with names
+ up to 100 characters in length. Such tar files can generally be
+ extracted by any <c>tar</c> program.</p>
+ <p>If filenames exceed 100 characters in length, the resulting tar
+ file can only be correctly extracted by a POSIX-compatible <c>tar</c>
+ program (such as Solaris <c>tar</c>), not by GNU tar.</p>
+ <p>File have longer names than 256 bytes cannot be stored at all.</p>
+ <p>The filename of the file a symbolic link points is always limited
+ to 100 characters.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>add(TarDescriptor, Filename, Options) -> RetValue</name>
+ <fsummary>Add a file to an open tar file</fsummary>
+ <type>
+ <v>TarDescriptor = term()</v>
+ <v>Filename = filename()</v>
+ <v>Options = [Option]</v>
+ <v>Option = dereference|verbose</v>
+ <v>RetValue = ok|{error,{Filename,Reason}}</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="add"></marker>
+<c>add/3</c> function adds a file to a tar file
+ that has been opened for writing by
+ <seealso marker="#open">open/1</seealso>.</p>
+ <taglist>
+ <tag><c>dereference</c></tag>
+ <item>
+ <p>By default, symbolic links will be stored as symbolic links
+ in the tar file. Use the <c>dereference</c> option to override the
+ default and store the file that the symbolic link points to into
+ the tar file.</p>
+ </item>
+ <tag><c>verbose</c></tag>
+ <item>
+ <p>Print an informational message about the file being added.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>add(TarDescriptor, FilenameOrBin, NameInArchive, Options) -> RetValue </name>
+ <fsummary>Add a file to an open tar file</fsummary>
+ <type>
+ <v>TarDescriptor = term()</v>
+ <v>FilenameOrBin = Filename()|binary()</v>
+ <v>Filename = filename()()</v>
+ <v>NameInArchive = filename()</v>
+ <v>Options = [Option]</v>
+ <v>Option = dereference|verbose</v>
+ <v>RetValue = ok|{error,{Filename,Reason}}</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <c>add/4</c> function adds a file to a tar file
+ that has been opened for writing by
+ <seealso marker="#open">open/1</seealso>. It accepts the same
+ options as <seealso marker="#add">add/3</seealso>.</p>
+ <p><c>NameInArchive</c> is the name under which the file will
+ be stored in the tar file. That is the name that the file will
+ get when it will be extracted from the tar file.</p>
+ </desc>
+ </func>
+ <func>
+ <name>close(TarDescriptor)</name>
+ <fsummary>Close an open tar file</fsummary>
+ <type>
+ <v>TarDescriptor = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="close"></marker>
+<c>close/1</c> function closes a tar file
+ opened by <seealso marker="#open">open/1</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>create(Name, FileList) ->RetValue </name>
+ <fsummary>Create a tar archive</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ <v>FileList = [Filename|{NameInArchive, binary()},{NameInArchive, Filename}]</v>
+ <v>Filename = filename()</v>
+ <v>NameInArchive = filename()</v>
+ <v>RetValue = ok|{error,{Name,Reason}} &lt;V>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="create_2"></marker>
+<c>create/2</c> function creates a tar file and
+ archives the files whose names are given in <c>FileList</c> into it.
+ The files may either be read from disk or given as
+ binaries.</p>
+ </desc>
+ </func>
+ <func>
+ <name>create(Name, FileList, OptionList)</name>
+ <fsummary>Create a tar archive with options</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ <v>FileList = [Filename|{NameInArchive, binary()},{NameInArchive, Filename}]</v>
+ <v>Filename = filename()</v>
+ <v>NameInArchive = filename()</v>
+ <v>OptionList = [Option]</v>
+ <v>Option = compressed|cooked|dereference|verbose</v>
+ <v>RetValue = ok|{error,{Name,Reason}} &lt;V>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="create_3"></marker>
+<c>create/3</c> function
+ creates a tar file and archives the files whose names are given
+ in <c>FileList</c> into it. The files may either be read from
+ disk or given as binaries.</p>
+ <p>The options in <c>OptionList</c> modify the defaults as follows.
+ </p>
+ <taglist>
+ <tag><c>compressed</c></tag>
+ <item>
+ <p>The entire tar file will be compressed, as if it has
+ been run through the <c>gzip</c> program. To abide to the
+ convention that a compressed tar file should end in "<c>.tar.gz</c>" or
+ "<c>.tgz</c>", you'll need to add the appropriate extension yourself.</p>
+ </item>
+ <tag><c>cooked</c></tag>
+ <item>
+ <p>By default, the <c>open/2</c> function will open the tar file
+ in <c>raw</c> mode, which is faster but does not allow a remote (erlang)
+ file server to be used. Adding <c>cooked</c> to the mode list will
+ override the default and open the tar file without the <c>raw</c>
+ option.</p>
+ </item>
+ <tag><c>dereference</c></tag>
+ <item>
+ <p>By default, symbolic links will be stored as symbolic links
+ in the tar file. Use the <c>dereference</c> option to override the
+ default and store the file that the symbolic link points to into
+ the tar file.</p>
+ </item>
+ <tag><c>verbose</c></tag>
+ <item>
+ <p>Print an informational message about each file being added.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>extract(Name) -> RetValue</name>
+ <fsummary>Extract all files from a tar file</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ <v>RetValue = ok|{error,{Name,Reason}}</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="extract_1"></marker>
+<c>extract/1</c> function extracts
+ all files from a tar archive.</p>
+ <p>If the <c>Name</c> argument is given as "<c>{binary,Binary}</c>",
+ the contents of the binary is assumed to be a tar archive.
+ </p>
+ <p>If the <c>Name</c> argument is given as "<c>{file,Fd}</c>",
+ <c>Fd</c> is assumed to be a file descriptor returned from
+ the <c>file:open/2</c> function.
+ </p>
+ <p>Otherwise, <c>Name</c> should be a filename.</p>
+ </desc>
+ </func>
+ <func>
+ <name>extract(Name, OptionList)</name>
+ <fsummary>Extract files from a tar file</fsummary>
+ <type>
+ <v>Name = filename() | {binary,Binary} | {file,Fd} </v>
+ <v>Binary = binary()</v>
+ <v>Fd = file_descriptor()</v>
+ <v>OptionList = [Option]</v>
+ <v>Option = {cwd,Cwd}|{files,FileList}|keep_old_files|verbose|memory</v>
+ <v>Cwd = [dirname()]</v>
+ <v>FileList = [filename()]</v>
+ <v>RetValue = ok|MemoryRetValue|{error,{Name,Reason}}</v>
+ <v>MemoryRetValue = {ok, [{NameInArchive,binary()}]}</v>
+ <v>NameInArchive = filename()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="extract_2"></marker>
+<c>extract/2</c> function extracts
+ files from a tar archive.</p>
+ <p>If the <c>Name</c> argument is given as "<c>{binary,Binary}</c>",
+ the contents of the binary is assumed to be a tar archive.
+ </p>
+ <p>If the <c>Name</c> argument is given as "<c>{file,Fd}</c>",
+ <c>Fd</c> is assumed to be a file descriptor returned from
+ the <c>file:open/2</c> function.
+ </p>
+ <p>Otherwise, <c>Name</c> should be a filename.
+ </p>
+ <p>The following options modify the defaults for the extraction as
+ follows.</p>
+ <taglist>
+ <tag><c>{cwd,Cwd}</c></tag>
+ <item>
+ <p>Files with relative filenames will by default be extracted
+ to the current working directory.
+ Given the <c>{cwd,Cwd}</c> option, the <c>extract/2</c> function
+ will extract into the directory <c>Cwd</c> instead of to the current
+ working directory.</p>
+ </item>
+ <tag><c>{files,FileList}</c></tag>
+ <item>
+ <p>By default, all files will be extracted from the tar file.
+ Given the <c>{files,Files}</c> option, the <c>extract/2</c> function
+ will only extract the files whose names are included in <c>FileList</c>.</p>
+ </item>
+ <tag><c>compressed</c></tag>
+ <item>
+ <p>Given the <c>compressed</c> option, the <c>extract/2</c>
+ function will uncompress the file while extracting
+ If the tar file is not actually compressed, the <c>compressed</c>
+ will effectively be ignored.</p>
+ </item>
+ <tag><c>cooked</c></tag>
+ <item>
+ <p>By default, the <c>open/2</c> function will open the tar file
+ in <c>raw</c> mode, which is faster but does not allow a remote (erlang)
+ file server to be used. Adding <c>cooked</c> to the mode list will
+ override the default and open the tar file without the <c>raw</c>
+ option.</p>
+ </item>
+ <tag><c>memory</c></tag>
+ <item>
+ <p>Instead of extracting to a directory, the memory option will
+ give the result as a list of tuples {Filename, Binary}, where
+ Binary is a binary containing the extracted data of the file named
+ Filename in the tar file.</p>
+ </item>
+ <tag><c>keep_old_files</c></tag>
+ <item>
+ <p>By default, all existing files with the same name as file in
+ the tar file will be overwritten
+ Given the <c>keep_old_files</c> option, the <c>extract/2</c> function
+ will not overwrite any existing files.</p>
+ </item>
+ <tag><c>verbose</c></tag>
+ <item>
+ <p>Print an informational message as each file is being extracted.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(Reason) -> string()</name>
+ <fsummary>Convert error term to a readable string</fsummary>
+ <type>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="format_error_1"></marker>
+<c>format_error/1</c> converts
+ an error reason term to a human-readable error message string.</p>
+ </desc>
+ </func>
+ <func>
+ <name>open(Name, OpenModeList) -> RetValue</name>
+ <fsummary>Open a tar file for writing.</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ <v>OpenModeList = [OpenMode]</v>
+ <v>Mode = write|compressed|cooked</v>
+ <v>RetValue = {ok,TarDescriptor}|{error,{Name,Reason}}</v>
+ <v>TarDescriptor = term()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="open"></marker>
+<c>open/2</c> function creates a tar file for writing.
+ (Any existing file with the same name will be truncated.)</p>
+ <p>By convention, the name of a tar file should end in "<c>.tar</c>".
+ To abide to the convention, you'll need to add "<c>.tar</c>" yourself
+ to the name.</p>
+ <p>Except for the <c>write</c> atom the following atoms
+ may be added to <c>OpenModeList</c>:</p>
+ <taglist>
+ <tag><c>compressed</c></tag>
+ <item>
+ <p>The entire tar file will be compressed, as if it has
+ been run through the <c>gzip</c> program. To abide to the
+ convention that a compressed tar file should end in "<c>.tar.gz</c>" or
+ "<c>.tgz</c>", you'll need to add the appropriate extension yourself.</p>
+ </item>
+ <tag><c>cooked</c></tag>
+ <item>
+ <p>By default, the <c>open/2</c> function will open the tar file
+ in <c>raw</c> mode, which is faster but does not allow a remote (erlang)
+ file server to be used. Adding <c>cooked</c> to the mode list will
+ override the default and open the tar file without the <c>raw</c>
+ option.</p>
+ </item>
+ </taglist>
+ <p>Use the <seealso marker="#add">add/3,4</seealso> functions
+ to add one file at the time into an opened tar file. When you are
+ finished adding files, use the <seealso marker="#close">close</seealso>
+ function to close the tar file.</p>
+ <warning>
+ <p>The <c>TarDescriptor</c> term is not a file descriptor.
+ You should not rely on the specific contents of the <c>TarDescriptor</c>
+ term, as it may change in future versions as more features are added
+ to the <c>erl_tar</c> module.</p>
+ <p></p>
+ </warning>
+ </desc>
+ </func>
+ <func>
+ <name>table(Name) -> RetValue</name>
+ <fsummary>Retrieve the name of all files in a tar file</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ <v>RetValue = {ok,[string()]}|{error,{Name,Reason}}</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="table_1"></marker>
+<c>table/1</c> function retrieves
+ the names of all files in the tar file <c>Name</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>table(Name, Options)</name>
+ <fsummary>Retrieve name and information of all files in a tar file</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ </type>
+ <desc>
+ <p>The <marker id="table_2"></marker>
+<c>table/2</c> function retrieves
+ the names of all files in the tar file <c>Name</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>t(Name)</name>
+ <fsummary>Print the name of each file in a tar file</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ </type>
+ <desc>
+ <p>The <marker id="t_1"></marker>
+<c>t/1</c> function prints the names
+ of all files in the tar file <c>Name</c> to the Erlang shell.
+ (Similar to "<c>tar&nbsp;t</c>".)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tt(Name)</name>
+ <fsummary>Print name and information for each file in a tar file</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ </type>
+ <desc>
+ <p>The <marker id="tt_1"></marker>
+<c>tt/1</c> function prints names and
+ information about all files in the tar file <c>Name</c> to
+ the Erlang shell. (Similar to "<c>tar&nbsp;tv</c>".)</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
new file mode 100644
index 0000000000..7b9f0e7772
--- /dev/null
+++ b/lib/stdlib/doc/src/ets.xml
@@ -0,0 +1,1811 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>ets</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>ets</module>
+ <modulesummary>Built-In Term Storage</modulesummary>
+ <description>
+ <p>This module is an interface to the Erlang built-in term storage
+ BIFs. These provide the ability to store very large quantities of
+ data in an Erlang runtime system, and to have constant access
+ time to the data. (In the case of <c>ordered_set</c>, see below,
+ access time is proportional to the logarithm of the number of
+ objects stored).</p>
+ <p>Data is organized as a set of dynamic tables, which can store
+ tuples. Each table is created by a process. When the process
+ terminates, the table is automatically destroyed. Every table has
+ access rights set at creation.</p>
+ <p>Tables are divided into four different types, <c>set</c>,
+ <c>ordered_set</c>, <c>bag</c> and <c>duplicate_bag</c>.
+ A <c>set</c> or <c>ordered_set</c> table can only have one object
+ associated with each key. A <c>bag</c> or <c>duplicate_bag</c> can
+ have many objects associated with each key.</p>
+ <p>The number of tables stored at one Erlang node is limited.
+ The current default limit is approximately 1400 tables. The upper
+ limit can be increased by setting the environment variable
+ <c>ERL_MAX_ETS_TABLES</c> before starting the Erlang runtime
+ system (i.e. with the <c>-env</c> option to
+ <c>erl</c>/<c>werl</c>). The actual limit may be slightly higher
+ than the one specified, but never lower.</p>
+ <p>Note that there is no automatic garbage collection for tables.
+ Even if there are no references to a table from any process, it
+ will not automatically be destroyed unless the owner process
+ terminates. It can be destroyed explicitly by using
+ <c>delete/1</c>.</p>
+ <p>Since R13B01, table ownership can be transferred at process termination
+ by using the <seealso marker="#heir">heir</seealso> option or explicitly
+ by calling <seealso marker="#give_away/3">give_away/3</seealso>.</p>
+ <p>Some implementation details:</p>
+ <list type="bulleted">
+ <item>In the current implementation, every object insert and
+ look-up operation results in a copy of the object.</item>
+ <item><c>'$end_of_table'</c> should not be used as a key since
+ this atom is used to mark the end of the table when using
+ <c>first</c>/<c>next</c>.</item>
+ </list>
+ <p>Also worth noting is the subtle difference between
+ <em>matching</em> and <em>comparing equal</em>, which is
+ demonstrated by the different table types <c>set</c> and
+ <c>ordered_set</c>. Two Erlang terms <c>match</c> if they are of
+ the same type and have the same value, so that <c>1</c> matches
+ <c>1</c>, but not <c>1.0</c> (as <c>1.0</c> is a <c>float()</c>
+ and not an <c>integer()</c>). Two Erlang terms <em>compare equal</em> if they either are of the same type and value, or if
+ both are numeric types and extend to the same value, so that
+ <c>1</c> compares equal to both <c>1</c> and <c>1.0</c>. The
+ <c>ordered_set</c> works on the <em>Erlang term order</em> and
+ there is no defined order between an <c>integer()</c> and a
+ <c>float()</c> that extends to the same value, hence the key
+ <c>1</c> and the key <c>1.0</c> are regarded as equal in an
+ <c>ordered_set</c> table.</p>
+ <p>In general, the functions below will exit with reason
+ <c>badarg</c> if any argument is of the wrong format, or if the
+ table identifier is invalid.</p>
+ </description>
+
+ <section><marker id="concurrency"></marker>
+ <title>Concurrency</title>
+ <p>This module provides some limited support for concurrent access.
+ All updates to single objects are guaranteed to be both <em>atomic</em>
+ and <em>isolated</em>. This means that an updating operation towards
+ a single object will either succeed or fail completely without any
+ effect at all (atomicy).
+ Nor can any intermediate results of the update be seen by other
+ processes (isolation). Some functions that update several objects
+ state that they even guarantee atomicy and isolation for the entire
+ operation. In database terms the isolation level can be seen as
+ "serializable", as if all isolated operations were carried out serially,
+ one after the other in a strict order.</p>
+ <p>No other support is available within ETS that would guarantee
+ consistency between objects. However, the <c>safe_fixtable/2</c>
+ function can be used to guarantee that a sequence of
+ <c>first/1</c> and <c>next/2</c> calls will traverse the table
+ without errors and that each existing object in the table is visited
+ exactly once, even if another process (or the same process)
+ simultaneously deletes or inserts objects into the table.
+ Nothing more is guaranteed; in particular objects that are inserted
+ or deleted during such a traversal may be visited once or not at all.
+ Functions that internally traverse over a table, like <c>select</c>
+ and <c>match</c>, will give the same guarantee as <c>safe_fixtable</c>.</p>
+ </section>
+ <section>
+ <marker id="match_spec"></marker>
+ <title>Match Specifications</title>
+ <p>Some of the functions uses a <em>match specification</em>,
+ match_spec. A brief explanation is given in
+ <seealso marker="#select/2">select/2</seealso>. For a detailed
+ description, see the chapter "Match specifications in Erlang" in
+ <em>ERTS User's Guide</em>.</p>
+ </section>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+match_spec()
+ a match specification, see above
+
+tid()
+ a table identifier, as returned by new/2</code>
+ </section>
+ <funcs>
+ <func>
+ <name>all() -> [Tab]</name>
+ <fsummary>Return a list of all ETS tables.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ </type>
+ <desc>
+ <p>Returns a list of all tables at the node. Named tables are
+ given by their names, unnamed tables are given by their
+ table identifiers.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete(Tab) -> true</name>
+ <fsummary>Delete an entire ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ </type>
+ <desc>
+ <p>Deletes the entire table <c>Tab</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete(Tab, Key) -> true</name>
+ <fsummary>Delete all objects with a given key from an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = term()</v>
+ </type>
+ <desc>
+ <p>Deletes all objects with the key <c>Key</c> from the table
+ <c>Tab</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_all_objects(Tab) -> true</name>
+ <fsummary>Delete all objects in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ </type>
+ <desc>
+ <p>Delete all objects in the ETS table <c>Tab</c>.
+ The operation is guaranteed to be
+ <seealso marker="#concurrency">atomic and isolated</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_object(Tab,Object) -> true</name>
+ <fsummary>Deletes a specific from an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Object = tuple()</v>
+ </type>
+ <desc>
+ <p>Delete the exact object <c>Object</c> from the ETS table,
+ leaving objects with the same key but other differences
+ (useful for type <c>bag</c>). In a <c>duplicate_bag</c>, all
+ instances of the object will be deleted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>file2tab(Filename) -> {ok,Tab} | {error,Reason}</name>
+ <fsummary>Read an ETS table from a file.</fsummary>
+ <type>
+ <v>Filename = string() | atom()</v>
+ <v>Tab = tid() | atom()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Reads a file produced by <seealso
+ marker="#tab2file/2">tab2file/2</seealso> or
+ <seealso marker="#tab2file/3">tab2file/3</seealso> and creates the
+ corresponding table <c>Tab</c>.</p>
+ <p>Equivalent to <c>file2tab(Filename,[])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>file2tab(Filename,Options) -> {ok,Tab} | {error,Reason}</name>
+ <fsummary>Read an ETS table from a file.</fsummary>
+ <type>
+ <v>Filename = string() | atom()</v>
+ <v>Tab = tid() | atom()</v>
+ <v>Options = [Option]</v>
+ <v>Option = {verify, bool()}</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Reads a file produced by <seealso
+ marker="#tab2file/2">tab2file/2</seealso> or
+ <seealso marker="#tab2file/3">tab2file/3</seealso> and creates the
+ corresponding table <c>Tab</c>.</p>
+ <p>The currently only supported option is <c>{verify,bool()}</c>. If
+ verification is turned on (by means of specifying
+ <c>{verify,true}</c>), the function utilizes whatever
+ information is present in the file to assert that the
+ information is not damaged. How this is done depends on which
+ <c>extended_info</c> was written using
+ <seealso marker="#tab2file/3">tab2file/3</seealso>.</p>
+ <p>If no <c>extended_info</c> is present in the file and
+ <c>{verify,true}</c> is specified, the number of objects
+ written is compared to the size of the original table when the
+ dump was started. This might make verification fail if the
+ table was
+ <c>public</c> and objects were added or removed while the
+ table was dumped to file. To avoid this type of problems,
+ either do not verify files dumped while updated simultaneously
+ or use the <c>{extended_info, [object_count]}</c> option to
+ <seealso marker="#tab2file/3">tab2file/3</seealso>, which
+ extends the information in the file with the number of objects
+ actually written.</p>
+ <p>If verification is turned on and the file was written with
+ the option <c>{extended_info, [md5sum]}</c>, reading the file
+ is slower and consumes radically more CPU time than
+ otherwise.</p>
+ <p><c>{verify,false}</c> is the default.</p>
+ </desc>
+ </func>
+ <func>
+ <name>first(Tab) -> Key | '$end_of_table'</name>
+ <fsummary>Return the first key in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = term()</v>
+ </type>
+ <desc>
+ <p>Returns the first key <c>Key</c> in the table <c>Tab</c>.
+ If the table is of the <c>ordered_set</c> type, the first key
+ in Erlang term order will be returned. If the table is of any
+ other type, the first key according to the table's internal
+ order will be returned. If the table is empty,
+ <c>'$end_of_table'</c> will be returned.</p>
+ <p>Use <c>next/2</c> to find subsequent keys in the table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>foldl(Function, Acc0, Tab) -> Acc1</name>
+ <fsummary>Fold a function over an ETS table</fsummary>
+ <type>
+ <v>Function = fun(A, AccIn) -> AccOut</v>
+ <v>Tab = tid() | atom()</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ </type>
+ <desc>
+ <p><c>Acc0</c> is returned if the table is empty.
+ This function is similar to <c>lists:foldl/3</c>. The order in
+ which the elements of the table are traversed is unspecified,
+ except for tables of type <c>ordered_set</c>, for which they
+ are traversed first to last.</p>
+
+ <p>If <c>Function</c> inserts objects into the table, or another
+ process inserts objects into the table, those objects <em>may</em>
+ (depending on key ordering) be included in the traversal.</p>
+ </desc>
+ </func>
+ <func>
+ <name>foldr(Function, Acc0, Tab) -> Acc1</name>
+ <fsummary>Fold a function over an ETS table</fsummary>
+ <type>
+ <v>Function = fun(A, AccIn) -> AccOut</v>
+ <v>Tab = tid() | atom()</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ </type>
+ <desc>
+ <p><c>Acc0</c> is returned if the table is empty.
+ This function is similar to <c>lists:foldr/3</c>. The order in
+ which the elements of the table are traversed is unspecified,
+ except for tables of type <c>ordered_set</c>, for which they
+ are traversed last to first.</p>
+
+ <p>If <c>Function</c> inserts objects into the table, or another
+ process inserts objects into the table, those objects <em>may</em>
+ (depending on key ordering) be included in the traversal.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_dets(Tab, DetsTab) -> true</name>
+ <fsummary>Fill an ETS table with objects from a Dets table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>DetsTab = atom()</v>
+ </type>
+ <desc>
+ <p>Fills an already created ETS table with the objects in the
+ already opened Dets table named <c>DetsTab</c>. The existing
+ objects of the ETS table are kept unless overwritten.</p>
+ <p>Throws a badarg error if any of the tables does not exist or the
+ dets table is not open.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fun2ms(LiteralFun) -> MatchSpec</name>
+ <fsummary>Pseudo function that transforms fun syntax to a match_spec.</fsummary>
+ <type>
+ <v>LiteralFun -- see below</v>
+ <v>MatchSpec = match_spec()</v>
+ </type>
+ <desc>
+ <p>Pseudo function that by means of a <c>parse_transform</c>
+ translates <c>LiteralFun</c> typed as parameter in the
+ function call to a
+ <seealso marker="#match_spec">match_spec</seealso>. With
+ "literal" is meant that the fun needs to textually be written
+ as the parameter of the function, it cannot be held in a
+ variable which in turn is passed to the function).</p>
+ <p>The parse transform is implemented in the module
+ <c>ms_transform</c> and the source <em>must</em> include the
+ file <c>ms_transform.hrl</c> in <c>stdlib</c> for this
+ pseudo function to work. Failing to include the hrl file in
+ the source will result in a runtime error, not a compile
+ time ditto. The include file is easiest included by adding
+ the line
+ <c>-include_lib("stdlib/include/ms_transform.hrl").</c> to
+ the source file.</p>
+ <p>The fun is very restricted, it can take only a single
+ parameter (the object to match): a sole variable or a
+ tuple. It needs to use the <c>is_</c>XXX guard tests.
+ Language constructs that have no representation
+ in a match_spec (like <c>if</c>, <c>case</c>, <c>receive</c>
+ etc) are not allowed.</p>
+ <p>The return value is the resulting match_spec.</p>
+ <p>Example:</p>
+ <pre>
+1> <input>ets:fun2ms(fun({M,N}) when N > 3 -> M end).</input>
+[{{'$1','$2'},[{'>','$2',3}],['$1']}]</pre>
+ <p>Variables from the environment can be imported, so that this
+ works:</p>
+ <pre>
+2> <input>X=3.</input>
+3
+3> <input>ets:fun2ms(fun({M,N}) when N > X -> M end).</input>
+[{{'$1','$2'},[{'>','$2',{const,3}}],['$1']}]</pre>
+ <p>The imported variables will be replaced by match_spec
+ <c>const</c> expressions, which is consistent with the
+ static scoping for Erlang funs. Local or global function
+ calls can not be in the guard or body of the fun however.
+ Calls to builtin match_spec functions of course is allowed:</p>
+ <pre>
+4> <input>ets:fun2ms(fun({M,N}) when N > X, is_atomm(M) -> M end).</input>
+Error: fun containing local Erlang function calls
+('is_atomm' called in guard) cannot be translated into match_spec
+{error,transform_error}
+5> <input>ets:fun2ms(fun({M,N}) when N > X, is_atom(M) -> M end).</input>
+[{{'$1','$2'},[{'>','$2',{const,3}},{is_atom,'$1'}],['$1']}]</pre>
+ <p>As can be seen by the example, the function can be called
+ from the shell too. The fun needs to be literally in the call
+ when used from the shell as well. Other means than the
+ parse_transform are used in the shell case, but more or less
+ the same restrictions apply (the exception being records,
+ as they are not handled by the shell).</p>
+ <warning>
+ <p>If the parse_transform is not applied to a module which
+ calls this pseudo function, the call will fail in runtime
+ (with a <c>badarg</c>). The module <c>ets</c> actually
+ exports a function with this name, but it should never
+ really be called except for when using the function in the
+ shell. If the <c>parse_transform</c> is properly applied by
+ including the <c>ms_transform.hrl</c> header file, compiled
+ code will never call the function, but the function call is
+ replaced by a literal match_spec.</p>
+ </warning>
+ <p>For more information, see
+ <seealso marker="ms_transform#top">ms_transform(3)</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>give_away(Tab, Pid, GiftData) -> true</name>
+ <fsummary>Change owner of a table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Pid = pid()</v>
+ <v>GiftData = term()</v>
+ </type>
+ <desc>
+ <p>Make process <c>Pid</c> the new owner of table <c>Tab</c>.
+ If successful, the message
+ <c>{'ETS-TRANSFER',Tab,FromPid,GiftData}</c> will be sent
+ to the new owner.</p>
+ <p>The process <c>Pid</c> must be alive, local and not already the
+ owner of the table. The calling process must be the table owner.</p>
+ <p>Note that <c>give_away</c> does not at all affect the
+ <seealso marker="#heir">heir</seealso> option of the table. A table
+ owner can for example set the <c>heir</c> to itself, give the table
+ away and then get it back in case the receiver terminates.</p>
+ </desc>
+ </func>
+ <func>
+ <name>i() -> ok</name>
+ <fsummary>Display information about all ETS tables on tty.</fsummary>
+ <desc>
+ <p>Displays information about all ETS tables on tty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>i(Tab) -> ok</name>
+ <fsummary>Browse an ETS table on tty.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ </type>
+ <desc>
+ <p>Browses the table <c>Tab</c> on tty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>info(Tab) -> [{Item, Value}] | undefined</name>
+ <fsummary>Return information about an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Item = atom(), see below</v>
+ <v>Value = term(), see below</v>
+ </type>
+ <desc>
+ <p>Returns information about the table <c>Tab</c> as a list of
+ <c>{Item, Value}</c> tuples. If <c>Tab</c> has the correct type
+ for a table identifier, but does not refer to an existing ETS
+ table, <c>undefined</c> is returned. If <c>Tab</c> is not of the
+ correct type, this function fails with reason <c>badarg</c>.</p>
+
+ <list type="bulleted">
+ <item><c>Item=memory, Value=int()</c> <br></br>
+
+ The number of words allocated to the table.</item>
+ <item><c>Item=owner, Value=pid()</c> <br></br>
+
+ The pid of the owner of the table.</item>
+ <item><c>Item=heir, Value=pid()|none</c> <br></br>
+
+ The pid of the heir of the table, or <c>none</c> if no heir is set.</item>
+ <item><c>Item=name, Value=atom()</c> <br></br>
+
+ The name of the table.</item>
+ <item><c>Item=size, Value=int()</c> <br></br>
+
+ The number of objects inserted in the table.</item>
+ <item><c>Item=node, Value=atom()</c> <br></br>
+
+ The node where the table is stored. This field is no longer
+ meaningful as tables cannot be accessed from other nodes.</item>
+ <item><c>Item=named_table, Value=true|false</c> <br></br>
+
+ Indicates if the table is named or not.</item>
+ <item><c>Item=type, Value=set|ordered_set|bag|duplicate_bag</c> <br></br>
+
+ The table type.</item>
+ <item><c>Item=keypos, Value=int()</c> <br></br>
+
+ The key position.</item>
+ <item><c>Item=protection, Value=public|protected|private</c> <br></br>
+
+ The table access rights.</item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>info(Tab, Item) -> Value | undefined</name>
+ <fsummary>Return the information associated with given item for an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Item, Value - see below</v>
+ </type>
+ <desc>
+ <p>Returns the information associated with <c>Item</c> for
+ the table <c>Tab</c>, or returns <c>undefined</c> if <c>Tab</c>
+ does not refer an existing ETS table.
+ If <c>Tab</c> is not of the correct type, or if <c>Item</c> is not
+ one of the allowed values, this function fails with reason <c>badarg</c>.</p>
+
+ <warning><p>In R11B and earlier, this function would not fail but return
+ <c>undefined</c> for invalid values for <c>Item</c>.</p>
+ </warning>
+
+ <p>In addition to the <c>{Item,Value}</c>
+ pairs defined for <c>info/1</c>, the following items are
+ allowed:</p>
+ <list type="bulleted">
+ <item><c>Item=fixed, Value=true|false</c> <br></br>
+
+ Indicates if the table is fixed by any process or not.</item>
+ <item>
+ <p><c>Item=safe_fixed, Value={FirstFixed,Info}|false</c> <br></br>
+</p>
+ <p>If the table has been fixed using <c>safe_fixtable/2</c>,
+ the call returns a tuple where <c>FirstFixed</c> is the
+ time when the table was first fixed by a process, which
+ may or may not be one of the processes it is fixed by
+ right now.</p>
+ <p><c>Info</c> is a possibly empty lists of tuples
+ <c>{Pid,RefCount}</c>, one tuple for every process the
+ table is fixed by right now. <c>RefCount</c> is the value
+ of the reference counter, keeping track of how many times
+ the table has been fixed by the process.</p>
+ <p>If the table never has been fixed, the call returns
+ <c>false</c>.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>init_table(Name, InitFun) -> true</name>
+ <fsummary>Replace all objects of an ETS table.</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ <v>InitFun = fun(Arg) -> Res</v>
+ <v>Arg = read | close</v>
+ <v>Res = end_of_input | {[object()], InitFun} | term()</v>
+ </type>
+ <desc>
+ <p>Replaces the existing objects of the table <c>Tab</c> with
+ objects created by calling the input function <c>InitFun</c>,
+ see below. This function is provided for compatibility with
+ the <c>dets</c> module, it is not more efficient than filling
+ a table by using <c>ets:insert/2</c>.
+ </p>
+ <p>When called with the argument <c>read</c> the function
+ <c>InitFun</c> is assumed to return <c>end_of_input</c> when
+ there is no more input, or <c>{Objects, Fun}</c>, where
+ <c>Objects</c> is a list of objects and <c>Fun</c> is a new
+ input function. Any other value Value is returned as an error
+ <c>{error, {init_fun, Value}}</c>. Each input function will be
+ called exactly once, and should an error occur, the last
+ function is called with the argument <c>close</c>, the reply
+ of which is ignored.</p>
+ <p>If the type of the table is <c>set</c> and there is more
+ than one object with a given key, one of the objects is
+ chosen. This is not necessarily the last object with the given
+ key in the sequence of objects returned by the input
+ functions. This holds also for duplicated
+ objects stored in tables of type <c>bag</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>insert(Tab, ObjectOrObjects) -> true</name>
+ <fsummary>Insert an object into an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>ObjectOrObjects = tuple() | [tuple()]</v>
+ </type>
+ <desc>
+ <p>Inserts the object or all of the objects in the list
+ <c>ObjectOrObjects</c> into the table <c>Tab</c>.
+ If the table is a <c>set</c> and the key of the inserted
+ objects <em>matches</em> the key of any object in the table,
+ the old object will be replaced. If the table is an
+ <c>ordered_set</c> and the key of the inserted object
+ <em>compares equal</em> to the key of any object in the
+ table, the old object is also replaced. If the list contains
+ more than one object with <em>matching</em> keys and the table is a
+ <c>set</c>, one will be inserted, which one is
+ not defined. The same thing holds for <c>ordered_set</c>, but
+ will also happen if the keys <em>compare equal</em>.</p>
+ <p>The entire operation is guaranteed to be
+ <seealso marker="#concurrency">atomic and isolated</seealso>,
+ even when a list of objects is inserted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>insert_new(Tab, ObjectOrObjects) -> bool()</name>
+ <fsummary>Insert an object into an ETS table if the key is not already present.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>ObjectOrObjects = tuple() | [tuple()]</v>
+ </type>
+ <desc>
+ <p>This function works exactly like <c>insert/2</c>, with the
+ exception that instead of overwriting objects with the same
+ key (in the case of <c>set</c> or <c>ordered_set</c>) or
+ adding more objects with keys already existing in the table
+ (in the case of <c>bag</c> and <c>duplicate_bag</c>), it
+ simply returns <c>false</c>. If <c>ObjectOrObjects</c> is a
+ list, the function checks <em>every</em> key prior to
+ inserting anything. Nothing will be inserted if not
+ <em>all</em> keys present in the list are absent from the
+ table. Like <c>insert/2</c>, the entire operation is guaranteed to be
+ <seealso marker="#concurrency">atomic and isolated</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_compiled_ms(Term) -> bool()</name>
+ <fsummary>Checks if an Erlang term is the result of ets:match_spec_compile</fsummary>
+ <type>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>This function is used to check if a term is a valid
+ compiled <seealso marker="#match_spec">match_spec</seealso>.
+ The compiled match_spec is an opaque datatype which can
+ <em>not</em> be sent between Erlang nodes nor be stored on
+ disk. Any attempt to create an external representation of a
+ compiled match_spec will result in an empty binary
+ (<c><![CDATA[<<>>]]></c>). As an example, the following
+ expression:</p>
+ <code type="none">
+ets:is_compiled_ms(ets:match_spec_compile([{'_',[],[true]}])).</code>
+ <p>will yield <c>true</c>, while the following expressions:</p>
+ <code type="none">
+MS = ets:match_spec_compile([{'_',[],[true]}]),
+Broken = binary_to_term(term_to_binary(MS)),
+ets:is_compiled_ms(Broken).</code>
+ <p>will yield false, as the variable <c>Broken</c> will contain
+ a compiled match_spec that has passed through external
+ representation.</p>
+ <note>
+ <p>The fact that compiled match_specs has no external
+ representation is for performance reasons. It may be subject
+ to change in future releases, while this interface will
+ still remain for backward compatibility reasons.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>last(Tab) -> Key | '$end_of_table'</name>
+ <fsummary>Return the last key in an ETS table of type<c>ordered_set</c>.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = term()</v>
+ </type>
+ <desc>
+ <p>Returns the last key <c>Key</c> according to Erlang term
+ order in the table <c>Tab</c> of the <c>ordered_set</c> type.
+ If the table is of any other type, the function is synonymous
+ to <c>first/2</c>. If the table is empty,
+ <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <c>prev/2</c> to find preceding keys in the table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>lookup(Tab, Key) -> [Object]</name>
+ <fsummary>Return all objects with a given key in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = term()</v>
+ <v>Object = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a list of all objects with the key <c>Key</c> in
+ the table <c>Tab</c>.</p>
+ <p>In the case of <c>set, bag and duplicate_bag</c>, an object
+ is returned only if the given key <em>matches</em> the key
+ of the object in the table. If the table is an
+ <c>ordered_set</c> however, an object is returned if the key
+ given <em>compares equal</em> to the key of an object in the
+ table. The difference being the same as between <c>=:=</c>
+ and <c>==</c>. As an example, one might insert an object
+ with the
+ <c>integer()</c><c>1</c> as a key in an <c>ordered_set</c>
+ and get the object returned as a result of doing a
+ <c>lookup/2</c> with the <c>float()</c><c>1.0</c> as the
+ key to search for.</p>
+ <p>If the table is of type <c>set</c> or <c>ordered_set</c>,
+ the function returns either the empty list or a list with one
+ element, as there cannot be more than one object with the same
+ key. If the table is of type <c>bag</c> or
+ <c>duplicate_bag</c>, the function returns a list of
+ arbitrary length.</p>
+ <p>Note that the time order of object insertions is preserved;
+ The first object inserted with the given key will be first
+ in the resulting list, and so on.</p>
+ <p>Insert and look-up times in tables of type <c>set</c>,
+ <c>bag</c> and <c>duplicate_bag</c> are constant, regardless
+ of the size of the table. For the <c>ordered_set</c>
+ data-type, time is proportional to the (binary) logarithm of
+ the number of objects.</p>
+ </desc>
+ </func>
+ <func>
+ <name>lookup_element(Tab, Key, Pos) -> Elem</name>
+ <fsummary>Return the <c>Pos</c>:th element of all objects with a given key in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = term()</v>
+ <v>Pos = int()</v>
+ <v>Elem = term() | [term()]</v>
+ </type>
+ <desc>
+ <p>If the table <c>Tab</c> is of type <c>set</c> or
+ <c>ordered_set</c>, the function returns the <c>Pos</c>:th
+ element of the object with the key <c>Key</c>.</p>
+ <p>If the table is of type <c>bag</c> or <c>duplicate_bag</c>,
+ the functions returns a list with the <c>Pos</c>:th element of
+ every object with the key <c>Key</c>.</p>
+ <p>If no object with the key <c>Key</c> exists, the function
+ will exit with reason <c>badarg</c>.</p>
+ <p>The difference between <c>set</c>, <c>bag</c> and
+ <c>duplicate_bag</c> on one hand, and <c>ordered_set</c> on
+ the other, regarding the fact that <c>ordered_set</c>'s
+ view keys as equal when they <em>compare equal</em>
+ whereas the other table types only regard them equal when
+ they <em>match</em>, naturally holds for
+ <c>lookup_element</c> as well as for <c>lookup</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match(Tab, Pattern) -> [Match]</name>
+ <fsummary>Match the objects in an ETS table against a pattern.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Pattern = tuple()</v>
+ <v>Match = [term()]</v>
+ </type>
+ <desc>
+ <p>Matches the objects in the table <c>Tab</c> against the
+ pattern <c>Pattern</c>.</p>
+ <p>A pattern is a term that may contain:</p>
+ <list type="bulleted">
+ <item>bound parts (Erlang terms),</item>
+ <item><c>'_'</c> which matches any Erlang term, and</item>
+ <item>pattern variables: <c>'$N'</c> where
+ <c>N</c>=0,1,...</item>
+ </list>
+ <p>The function returns a list with one element for each
+ matching object, where each element is an ordered list of
+ pattern variable bindings. An example:</p>
+ <pre>
+6> <input>ets:match(T, '$1').</input> % Matches every object in the table
+[[{rufsen,dog,7}],[{brunte,horse,5}],[{ludde,dog,5}]]
+7> <input>ets:match(T, {'_',dog,'$1'}).</input>
+[[7],[5]]
+8> <input>ets:match(T, {'_',cow,'$1'}).</input>
+[]</pre>
+ <p>If the key is specified in the pattern, the match is very
+ efficient. If the key is not specified, i.e. if it is a
+ variable or an underscore, the entire table must be searched.
+ The search time can be substantial if the table is very large.</p>
+ <p>On tables of the <c>ordered_set</c> type, the result is in
+ the same order as in a <c>first/next</c> traversal.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match(Tab, Pattern, Limit) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Match the objects in an ETS table against a pattern and returns part of the answers.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Pattern = tuple()</v>
+ <v>Match = [term()]</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+ <p>Works like <c>ets:match/2</c> but only returns a limited
+ (<c>Limit</c>) number of matching objects. The
+ <c>Continuation</c> term can then be used in subsequent calls
+ to <c>ets:match/1</c> to get the next chunk of matching
+ objects. This is a space efficient way to work on objects in a
+ table which is still faster than traversing the table object
+ by object using <c>ets:first/1</c> and <c>ets:next/1</c>.</p>
+ <p><c>'$end_of_table'</c> is returned if the table is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match(Continuation) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Continues matching objects in an ETS table.</fsummary>
+ <type>
+ <v>Match = [term()]</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+ <p>Continues a match started with <c>ets:match/3</c>. The next
+ chunk of the size given in the initial <c>ets:match/3</c>
+ call is returned together with a new <c>Continuation</c>
+ that can be used in subsequent calls to this function.</p>
+ <p><c>'$end_of_table'</c> is returned when there are no more
+ objects in the table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_delete(Tab, Pattern) -> true</name>
+ <fsummary>Delete all objects which match a given pattern from an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Pattern = tuple()</v>
+ </type>
+ <desc>
+ <p>Deletes all objects which match the pattern <c>Pattern</c>
+ from the table <c>Tab</c>. See <c>match/2</c> for a
+ description of patterns.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_object(Tab, Pattern) -> [Object]</name>
+ <fsummary>Match the objects in an ETS table against a pattern.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Pattern = Object = tuple()</v>
+ </type>
+ <desc>
+ <p>Matches the objects in the table <c>Tab</c> against the
+ pattern <c>Pattern</c>. See <c>match/2</c> for a description
+ of patterns. The function returns a list of all objects which
+ match the pattern.</p>
+ <p>If the key is specified in the pattern, the match is very
+ efficient. If the key is not specified, i.e. if it is a
+ variable or an underscore, the entire table must be searched.
+ The search time can be substantial if the table is very large.</p>
+ <p>On tables of the <c>ordered_set</c> type, the result is in
+ the same order as in a <c>first/next</c> traversal.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_object(Tab, Pattern, Limit) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Match the objects in an ETS table against a pattern and returns part of the answers.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Pattern = tuple()</v>
+ <v>Match = [term()]</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+ <p>Works like <c>ets:match_object/2</c> but only returns a
+ limited (<c>Limit</c>) number of matching objects. The
+ <c>Continuation</c> term can then be used in subsequent calls
+ to <c>ets:match_object/1</c> to get the next chunk of matching
+ objects. This is a space efficient way to work on objects in a
+ table which is still faster than traversing the table object
+ by object using <c>ets:first/1</c> and <c>ets:next/1</c>.</p>
+ <p><c>'$end_of_table'</c> is returned if the table is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_object(Continuation) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Continues matching objects in an ETS table.</fsummary>
+ <type>
+ <v>Match = [term()]</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+ <p>Continues a match started with <c>ets:match_object/3</c>.
+ The next chunk of the size given in the initial
+ <c>ets:match_object/3</c> call is returned together with a
+ new <c>Continuation</c> that can be used in subsequent calls
+ to this function.</p>
+ <p><c>'$end_of_table'</c> is returned when there are no more
+ objects in the table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match_spec_compile(MatchSpec) -> CompiledMatchSpec</name>
+ <fsummary>Compiles a match specification into its internal representation</fsummary>
+ <type>
+ <v>MatchSpec = match_spec()</v>
+ <v>CompiledMatchSpec = comp_match_spec()</v>
+ </type>
+ <desc>
+ <p>This function transforms a
+ <seealso marker="#match_spec">match_spec</seealso> into an
+ internal representation that can be used in subsequent calls
+ to <c>ets:match_spec_run/2</c>. The internal representation is
+ opaque and can not be converted to external term format and
+ then back again without losing its properties (meaning it can
+ not be sent to a process on another node and still remain a
+ valid compiled match_spec, nor can it be stored on disk).
+ The validity of a compiled match_spec can be checked using
+ <c>ets:is_compiled_ms/1</c>.</p>
+ <p>If the term <c>MatchSpec</c> can not be compiled (does not
+ represent a valid match_spec), a <c>badarg</c> fault is
+ thrown.</p>
+ <note>
+ <p>This function has limited use in normal code, it is used by
+ Dets to perform the <c>dets:select</c> operations.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>match_spec_run(List,CompiledMatchSpec) -> list()</name>
+ <fsummary>Performs matching, using a compiled match_spec, on a list of tuples</fsummary>
+ <type>
+ <v>List = [ tuple() ]</v>
+ <v>CompiledMatchSpec = comp_match_spec()</v>
+ </type>
+ <desc>
+ <p>This function executes the matching specified in a
+ compiled <seealso marker="#match_spec">match_spec</seealso> on
+ a list of tuples. The <c>CompiledMatchSpec</c> term should be
+ the result of a call to <c>ets:match_spec_compile/1</c> and
+ is hence the internal representation of the match_spec one
+ wants to use.</p>
+ <p>The matching will be executed on each element in <c>List</c>
+ and the function returns a list containing all results. If an
+ element in <c>List</c> does not match, nothing is returned
+ for that element. The length of the result list is therefore
+ equal or less than the the length of the parameter
+ <c>List</c>. The two calls in the following example will give
+ the same result (but certainly not the same execution
+ time...):</p>
+ <code type="none">
+Table = ets:new...
+MatchSpec = ....
+% The following call...
+ets:match_spec_run(ets:tab2list(Table),
+ets:match_spec_compile(MatchSpec)),
+% ...will give the same result as the more common (and more efficient)
+ets:select(Table,MatchSpec),</code>
+ <note>
+ <p>This function has limited use in normal code, it is used by
+ Dets to perform the <c>dets:select</c> operations and by
+ Mnesia during transactions.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>member(Tab, Key) -> true | false</name>
+ <fsummary>Tests for occurrence of a key in an ETS table</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = term()</v>
+ </type>
+ <desc>
+ <p>Works like <c>lookup/2</c>, but does not return the objects.
+ The function returns <c>true</c> if one or more elements in
+ the table has the key <c>Key</c>, <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>new(Name, Options) -> tid() | atom()</name>
+ <fsummary>Create a new ETS table.</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = Type | Access | named_table | {keypos,Pos} | {heir,pid(),HeirData} | {heir,none} | {write_concurrency,bool()}</v>
+ <v>&nbsp;&nbsp;Type = set | ordered_set | bag | duplicate_bag</v>
+ <v>&nbsp;&nbsp;Access = public | protected | private</v>
+ <v>&nbsp;&nbsp;Pos = int()</v>
+ <v>&nbsp;&nbsp;HeirData = term()</v>
+ </type>
+ <desc>
+ <p>Creates a new table and returns a table identifier which can
+ be used in subsequent operations. The table identifier can be
+ sent to other processes so that a table can be shared between
+ different processes within a node.</p>
+ <p>The parameter <c>Options</c> is a list of atoms which
+ specifies table type, access rights, key position and if the
+ table is named or not. If one or more options are left out,
+ the default values are used. This means that not specifying
+ any options (<c>[]</c>) is the same as specifying
+ <c>[set,protected,{keypos,1},{heir,none},{write_concurrency,false}]</c>.</p>
+ <list type="bulleted">
+ <item>
+ <p><c>set</c>
+ The table is a <c>set</c> table - one key, one object,
+ no order among objects. This is the default table type.</p>
+ </item>
+ <item>
+ <p><c>ordered_set</c>
+ The table is a <c>ordered_set</c> table - one key, one
+ object, ordered in Erlang term order, which is the order
+ implied by the &lt; and &gt; operators. Tables of this type
+ have a somewhat different behavior in some situations
+ than tables of the other types. Most notably the
+ <c>ordered_set</c> tables regard keys as equal when they
+ <em>compare equal</em>, not only when they match. This
+ means that to an <c>ordered_set</c>, the
+ <c>integer()</c><c>1</c> and the <c>float()</c><c>1.0</c> are regarded as equal. This also means that the
+ key used to lookup an element not necessarily
+ <em>matches</em> the key in the elements returned, if
+ <c>float()</c>'s and <c>integer()</c>'s are mixed in
+ keys of a table.</p>
+ </item>
+ <item>
+ <p><c>bag</c>
+ The table is a <c>bag</c> table which can have many
+ objects, but only one instance of each object, per key.</p>
+ </item>
+ <item>
+ <p><c>duplicate_bag</c>
+ The table is a <c>duplicate_bag</c> table which can have
+ many objects, including multiple copies of the same
+ object, per key.</p>
+ </item>
+ <item>
+ <p><c>public</c>
+ Any process may read or write to the table.</p>
+ </item>
+ <item>
+ <p><c>protected</c>
+ The owner process can read and write to the table. Other
+ processes can only read the table. This is the default
+ setting for the access rights.</p>
+ </item>
+ <item>
+ <p><c>private</c>
+ Only the owner process can read or write to the table.</p>
+ </item>
+ <item>
+ <p><c>named_table</c>
+ If this option is present, the name <c>Name</c> is
+ associated with the table identifier. The name can then
+ be used instead of the table identifier in subsequent
+ operations.</p>
+ </item>
+ <item>
+ <p><c>{keypos,Pos}</c>
+ Specfies which element in the stored tuples should be
+ used as key. By default, it is the first element, i.e.
+ <c>Pos=1</c>. However, this is not always appropriate. In
+ particular, we do not want the first element to be the
+ key if we want to store Erlang records in a table.</p>
+ <p>Note that any tuple stored in the table must have at
+ least <c>Pos</c> number of elements.</p>
+ </item>
+ <item>
+ <marker id="heir"></marker>
+ <p><c>{heir,Pid,HeirData} | {heir,none}</c><br></br>
+ Set a process as heir. The heir will inherit the table if
+ the owner terminates. The message
+ <c>{'ETS-TRANSFER',tid(),FromPid,HeirData}</c> will be sent to
+ the heir when that happens. The heir must be a local process.
+ Default heir is <c>none</c>, which will destroy the table when
+ the owner terminates.</p>
+ </item>
+ <item>
+ <p><c>{write_concurrency,bool()}</c>
+ Performance tuning. Default is <c>false</c>, which means that the table
+ is optimized towards concurrent read access. An operation that
+ mutates (writes to) the table will obtain exclusive access,
+ blocking any concurrent access of the same table until finished.
+ If set to <c>true</c>, the table is optimized towards concurrent
+ write access. Different objects of the same table can be mutated
+ (and read) by concurrent processes. This is achieved to some degree
+ at the expense of single access and concurrent reader performance.
+ Note that this option does not change any guarantees about
+ <seealso marker="#concurrency">atomicy and isolation</seealso>.
+ Functions that makes such promises over several objects (like
+ <c>insert/2</c>) will gain less (or nothing) from this option.</p>
+ <p>Table type <c>ordered_set</c> is not affected by this option in current
+ implementation.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>next(Tab, Key1) -> Key2 | '$end_of_table'</name>
+ <fsummary>Return the next key in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key1 = Key2 = term()</v>
+ </type>
+ <desc>
+ <p>Returns the next key <c>Key2</c>, following the key
+ <c>Key1</c> in the table <c>Tab</c>. If the table is of the
+ <c>ordered_set</c> type, the next key in Erlang term order is
+ returned. If the table is of any other type, the next key
+ according to the table's internal order is returned. If there
+ is no next key, <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <c>first/1</c> to find the first key in the table.</p>
+ <p>Unless a table of type <c>set</c>, <c>bag</c> or
+ <c>duplicate_bag</c> is protected using
+ <c>safe_fixtable/2</c>, see below, a traversal may fail if
+ concurrent updates are made to the table. If the table is of
+ type <c>ordered_set</c>, the function returns the next key in
+ order, even if the object does no longer exist.</p>
+ </desc>
+ </func>
+ <func>
+ <name>prev(Tab, Key1) -> Key2 | '$end_of_table'</name>
+ <fsummary>Return the previous key in an ETS table of type<c>ordered_set</c>.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key1 = Key2 = term()</v>
+ </type>
+ <desc>
+ <p>Returns the previous key <c>Key2</c>, preceding the key
+ <c>Key1</c> according the Erlang term order in the table
+ <c>Tab</c> of the <c>ordered_set</c> type. If the table is of
+ any other type, the function is synonymous to <c>next/2</c>.
+ If there is no previous key, <c>'$end_of_table'</c> is
+ returned.</p>
+ <p>Use <c>last/1</c> to find the last key in the table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>rename(Tab, Name) -> Name</name>
+ <fsummary>Rename a named ETS table.</fsummary>
+ <type>
+ <v>Tab = Name = atom()</v>
+ </type>
+ <desc>
+ <p>Renames the named table <c>Tab</c> to the new name
+ <c>Name</c>. Afterwards, the old name can not be used to
+ access the table. Renaming an unnamed table has no effect.</p>
+ </desc>
+ </func>
+ <func>
+ <name>repair_continuation(Continuation, MatchSpec) -> Continuation</name>
+ <fsummary>Repair a continuation from ets:select/1 or ets:select/3 that has passed through external representation</fsummary>
+ <type>
+ <v>Continuation = term()</v>
+ <v>MatchSpec = match_spec()</v>
+ </type>
+ <desc>
+ <p>This function can be used to restore an opaque continuation
+ returned by <c>ets:select/3</c> or <c>ets:select/1</c> if the
+ continuation has passed through external term format (been
+ sent between nodes or stored on disk).</p>
+ <p>The reason for this function is that continuation terms
+ contain compiled match_specs and therefore will be
+ invalidated if converted to external term format. Given that
+ the original match_spec is kept intact, the continuation can
+ be restored, meaning it can once again be used in subsequent
+ <c>ets:select/1</c> calls even though it has been stored on
+ disk or on another node.</p>
+ <p>As an example, the following sequence of calls will fail:</p>
+ <code type="none">
+T=ets:new(x,[]),
+...
+{_,C} = ets:select(T,ets:fun2ms(fun({N,_}=A)
+when (N rem 10) =:= 0 ->
+A
+end),10),
+Broken = binary_to_term(term_to_binary(C)),
+ets:select(Broken).</code>
+ <p>...while the following sequence will work:</p>
+ <code type="none">
+T=ets:new(x,[]),
+...
+MS = ets:fun2ms(fun({N,_}=A)
+when (N rem 10) =:= 0 ->
+A
+end),
+{_,C} = ets:select(T,MS,10),
+Broken = binary_to_term(term_to_binary(C)),
+ets:select(ets:repair_continuation(Broken,MS)).</code>
+ <p>...as the call to <c>ets:repair_continuation/2</c> will
+ reestablish the (deliberately) invalidated continuation
+ <c>Broken</c>.</p>
+ <note>
+ <p>This function is very rarely needed in application code. It
+ is used by Mnesia to implement distributed <c>select/3</c>
+ and <c>select/1</c> sequences. A normal application would
+ either use Mnesia or keep the continuation from being
+ converted to external format.</p>
+ <p>The reason for not having an external representation of a
+ compiled match_spec is performance. It may be subject to
+ change in future releases, while this interface will remain
+ for backward compatibility.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>safe_fixtable(Tab, true|false) -> true</name>
+ <fsummary>Fix an ETS table for safe traversal.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ </type>
+ <desc>
+ <p>Fixes a table of the <c>set</c>, <c>bag</c> or
+ <c>duplicate_bag</c> table type for safe traversal.</p>
+ <p>A process fixes a table by calling
+ <c>safe_fixtable(Tab,true)</c>. The table remains fixed until
+ the process releases it by calling
+ <c>safe_fixtable(Tab,false)</c>, or until the process
+ terminates.</p>
+ <p>If several processes fix a table, the table will remain fixed
+ until all processes have released it (or terminated).
+ A reference counter is kept on a per process basis, and N
+ consecutive fixes requires N releases to actually release
+ the table.</p>
+ <p>When a table is fixed, a sequence of <c>first/1</c> and
+ <c>next/2</c> calls are guaranteed to succeed and each object in
+ the table will only be returned once, even if objects
+ are removed or inserted during the traversal.
+ The keys for new objects inserted during the traversal <em>may</em>
+ be returned by <seealso marker="#next/2">next/2</seealso>
+ (it depends on the internal ordering of the keys). An example:</p>
+ <code type="none">
+clean_all_with_value(Tab,X) ->
+ safe_fixtable(Tab,true),
+ clean_all_with_value(Tab,X,ets:first(Tab)),
+ safe_fixtable(Tab,false).
+
+clean_all_with_value(Tab,X,'$end_of_table') ->
+ true;
+clean_all_with_value(Tab,X,Key) ->
+ case ets:lookup(Tab,Key) of
+ [{Key,X}] ->
+ ets:delete(Tab,Key);
+ _ ->
+ true
+ end,
+ clean_all_with_value(Tab,X,ets:next(Tab,Key)).</code>
+ <p>Note that no deleted objects are actually removed from a
+ fixed table until it has been released. If a process fixes a
+ table but never releases it, the memory used by the deleted
+ objects will never be freed. The performance of operations on
+ the table will also degrade significantly.</p>
+ <p>Use <c>info/2</c> to retrieve information about which
+ processes have fixed which tables. A system with a lot of
+ processes fixing tables may need a monitor which sends alarms
+ when tables have been fixed for too long.</p>
+ <p>Note that for tables of the <c>ordered_set</c> type,
+ <c>safe_fixtable/2</c> is not necessary as calls to
+ <c>first/1</c> and <c>next/2</c> will always succeed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>select(Tab, MatchSpec) -> [Match]</name>
+ <fsummary>Match the objects in an ETS table against a match_spec.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Match = term()</v>
+ <v>MatchSpec = match_spec()</v>
+ </type>
+ <desc>
+ <p>Matches the objects in the table <c>Tab</c> using a
+ <seealso marker="#match_spec">match_spec</seealso>. This is a
+ more general call than the <c>ets:match/2</c> and
+ <c>ets:match_object/2</c> calls. In its simplest forms the
+ match_specs look like this:</p>
+ <list type="bulleted">
+ <item>MatchSpec = [MatchFunction]</item>
+ <item>MatchFunction = {MatchHead, [Guard], [Result]}</item>
+ <item>MatchHead = "Pattern as in ets:match"</item>
+ <item>Guard = {"Guardtest name", ...}</item>
+ <item>Result = "Term construct"</item>
+ </list>
+ <p>This means that the match_spec is always a list of one or
+ more tuples (of arity 3). The tuples first element should be
+ a pattern as described in the documentation of
+ <c>ets:match/2</c>. The second element of the tuple should
+ be a list of 0 or more guard tests (described below). The
+ third element of the tuple should be a list containing a
+ description of the value to actually return. In almost all
+ normal cases the list contains exactly one term which fully
+ describes the value to return for each object.</p>
+ <p>The return value is constructed using the "match variables"
+ bound in the MatchHead or using the special match variables
+ <c>'$_'</c> (the whole matching object) and <c>'$$'</c> (all
+ match variables in a list), so that the following
+ <c>ets:match/2</c> expression:</p>
+ <code type="none">
+ets:match(Tab,{'$1','$2','$3'})</code>
+ <p>is exactly equivalent to:</p>
+ <code type="none">
+ets:select(Tab,[{{'$1','$2','$3'},[],['$$']}])</code>
+ <p>- and the following <c>ets:match_object/2</c> call:</p>
+ <code type="none">
+ets:match_object(Tab,{'$1','$2','$1'})</code>
+ <p>is exactly equivalent to</p>
+ <code type="none">
+ets:select(Tab,[{{'$1','$2','$1'},[],['$_']}])</code>
+ <p>Composite terms can be constructed in the <c>Result</c> part
+ either by simply writing a list, so that this code:</p>
+ <code type="none">
+ets:select(Tab,[{{'$1','$2','$3'},[],['$$']}])</code>
+ <p>gives the same output as:</p>
+ <code type="none">
+ets:select(Tab,[{{'$1','$2','$3'},[],[['$1','$2','$3']]}])</code>
+ <p>i.e. all the bound variables in the match head as a list. If
+ tuples are to be constructed, one has to write a tuple of
+ arity 1 with the single element in the tuple being the tuple
+ one wants to construct (as an ordinary tuple could be mistaken
+ for a <c>Guard</c>). Therefore the following call:</p>
+ <code type="none">
+ets:select(Tab,[{{'$1','$2','$1'},[],['$_']}])</code>
+ <p>gives the same output as:</p>
+ <code type="none">
+ets:select(Tab,[{{'$1','$2','$1'},[],[{{'$1','$2','$3'}}]}])</code>
+ <p>- this syntax is equivalent to the syntax used in the trace
+ patterns (see
+ <seealso marker="runtime_tools:dbg">dbg(3)</seealso>).</p>
+ <p>The <c>Guard</c>s are constructed as tuples where the first
+ element is the name of the test and the rest of the elements
+ are the parameters of the test. To check for a specific type
+ (say a list) of the element bound to the match variable
+ <c>'$1'</c>, one would write the test as
+ <c>{is_list, '$1'}</c>. If the test fails, the object in the
+ table will not match and the next <c>MatchFunction</c> (if
+ any) will be tried. Most guard tests present in Erlang can be
+ used, but only the new versions prefixed <c>is_</c> are
+ allowed (like <c>is_float</c>, <c>is_atom</c> etc).</p>
+ <p>The <c>Guard</c> section can also contain logic and
+ arithmetic operations, which are written with the same syntax
+ as the guard tests (prefix notation), so that a guard test
+ written in Erlang looking like this:</p>
+ <code type="none"><![CDATA[
+is_integer(X), is_integer(Y), X + Y < 4711]]></code>
+ <p>is expressed like this (X replaced with '$1' and Y with
+ '$2'):</p>
+ <code type="none"><![CDATA[
+[{is_integer, '$1'}, {is_integer, '$2'}, {'<', {'+', '$1', '$2'}, 4711}]]]></code>
+ <p>On tables of the <c>ordered_set</c> type, objects are visited
+ in the same order as in a <c>first/next</c>
+ traversal. This means that the match specification will be
+ executed against objects with keys in the <c>first/next</c>
+ order and the corresponding result list will be in the order of that
+ execution.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>select(Tab, MatchSpec, Limit) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Match the objects in an ETS table against a match_spec and returns part of the answers.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Match = term()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+ <p>Works like <c>ets:select/2</c> but only returns a limited
+ (<c>Limit</c>) number of matching objects. The
+ <c>Continuation</c> term can then be used in subsequent calls
+ to <c>ets:select/1</c> to get the next chunk of matching
+ objects. This is a space efficient way to work on objects in a
+ table which is still faster than traversing the table object
+ by object using <c>ets:first/1</c> and <c>ets:next/1</c>.</p>
+ <p><c>'$end_of_table'</c> is returned if the table is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>select(Continuation) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Continue matching objects in an ETS table.</fsummary>
+ <type>
+ <v>Match = term()</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+ <p>Continues a match started with
+ <c>ets:select/3</c>. The next
+ chunk of the size given in the initial <c>ets:select/3</c>
+ call is returned together with a new <c>Continuation</c>
+ that can be used in subsequent calls to this function.</p>
+ <p><c>'$end_of_table'</c> is returned when there are no more
+ objects in the table.</p>
+ </desc>
+ </func>
+ <func>
+ <name>select_delete(Tab, MatchSpec) -> NumDeleted</name>
+ <fsummary>Match the objects in an ETS table against a match_spec and deletes objects where the match_spec returns 'true'</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Object = tuple()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>NumDeleted = integer()</v>
+ </type>
+ <desc>
+ <p>Matches the objects in the table <c>Tab</c> using a
+ <seealso marker="#match_spec">match_spec</seealso>. If the
+ match_spec returns <c>true</c> for an object, that object is
+ removed from the table. For any other result from the
+ match_spec the object is retained. This is a more general
+ call than the <c>ets:match_delete/2</c> call.</p>
+ <p>The function returns the number of objects actually
+ deleted from the table.</p>
+ <note>
+ <p>The <c>match_spec</c> has to return the atom <c>true</c> if
+ the object is to be deleted. No other return value will get the
+ object deleted, why one can not use the same match specification for
+ looking up elements as for deleting them.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>select_count(Tab, MatchSpec) -> NumMatched</name>
+ <fsummary>Match the objects in an ETS table against a match_spec and returns the number of objects for which the match_spec returned 'true'</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Object = tuple()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>NumMatched = integer()</v>
+ </type>
+ <desc>
+ <p>Matches the objects in the table <c>Tab</c> using a
+ <seealso marker="#match_spec">match_spec</seealso>. If the
+ match_spec returns <c>true</c> for an object, that object
+ considered a match and is counted. For any other result from
+ the match_spec the object is not considered a match and is
+ therefore not counted.</p>
+ <p>The function could be described as a <c>match_delete/2</c>
+ that does not actually delete any elements, but only counts
+ them.</p>
+ <p>The function returns the number of objects matched.</p>
+ </desc>
+ </func>
+ <func>
+ <name>setopts(Tab, Opts) -> true</name>
+ <fsummary>Set table options.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Opts = Opt | [Opt]</v>
+ <v>Opt = {heir,pid(),HeirData} | {heir,none}</v>
+ <v>HeirData = term()</v>
+ </type>
+ <desc>
+ <p>Set table options. The only option that currently is allowed to be
+ set after the table has been created is
+ <seealso marker="#heir">heir</seealso>. The calling process must be
+ the table owner.</p>
+ </desc>
+ </func>
+ <func>
+ <name>slot(Tab, I) -> [Object] | '$end_of_table'</name>
+ <fsummary>Return all objects in a given slot of an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>I = int()</v>
+ <v>Object = tuple()</v>
+ </type>
+ <desc>
+ <p>This function is mostly for debugging purposes, Normally
+ one should use <c>first/next</c> or <c>last/prev</c> instead.</p>
+ <p>Returns all objects in the <c>I</c>:th slot of the table
+ <c>Tab</c>. A table can be traversed by repeatedly calling
+ the function, starting with the first slot <c>I=0</c> and
+ ending when <c>'$end_of_table'</c> is returned.
+ The function will fail with reason <c>badarg</c> if the
+ <c>I</c> argument is out of range.</p>
+ <p>Unless a table of type <c>set</c>, <c>bag</c> or
+ <c>duplicate_bag</c> is protected using
+ <c>safe_fixtable/2</c>, see above, a traversal may fail if
+ concurrent updates are made to the table. If the table is of
+ type <c>ordered_set</c>, the function returns a list
+ containing the <c>I</c>:th object in Erlang term order.</p>
+ </desc>
+ </func>
+ <func>
+ <name>tab2file(Tab, Filename) -> ok | {error,Reason}</name>
+ <fsummary>Dump an ETS table to a file.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Filename = string() | atom()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Dumps the table <c>Tab</c> to the file <c>Filename</c>.</p>
+ <p>Equivalent to <c>tab2file(Tab, Filename,[])</c></p>
+
+ </desc>
+ </func>
+ <func>
+ <name>tab2file(Tab, Filename, Options) -> ok | {error,Reason}</name>
+ <fsummary>Dump an ETS table to a file.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Filename = string() | atom()</v>
+ <v>Options = [Option]</v>
+ <v>Option = {extended_info, [ExtInfo]}</v>
+ <v>ExtInfo = object_count | md5sum</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Dumps the table <c>Tab</c> to the file <c>Filename</c>.</p>
+ <p>When dumping the table, certain information about the table
+ is dumped to a header at the beginning of the dump. This
+ information contains data about the table type,
+ name, protection, size, version and if it's a named table. It
+ also contains notes about what extended information is added
+ to the file, which can be a count of the objects in the file
+ or a MD5 sum of the header and records in the file.</p>
+ <p>The size field in the header might not correspond to the
+ actual number of records in the file if the table is public
+ and records are added or removed from the table during
+ dumping. Public tables updated during dump, and that one wants
+ to verify when reading, needs at least one field of extended
+ information for the read verification process to be reliable
+ later.</p>
+ <p>The <c>extended_info</c> option specifies what extra
+ information is written to the table dump:</p>
+ <taglist>
+ <tag><c>object_count</c></tag>
+ <item><p>The number of objects actually written to the file is
+ noted in the file footer, why verification of file truncation
+ is possible even if the file was updated during
+ dump.</p></item>
+ <tag><c>md5sum</c></tag>
+ <item><p>The header and objects in the file are checksummed using
+ the built in MD5 functions. The MD5 sum of all objects is
+ written in the file footer, so that verification while reading
+ will detect the slightest bitflip in the file data. Using this
+ costs a fair amount of CPU time.</p></item>
+ </taglist>
+ <p>Whenever the <c>extended_info</c> option is used, it
+ results in a file not readable by versions of ets prior to
+ that in stdlib-1.15.1</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>tab2list(Tab) -> [Object]</name>
+ <fsummary>Return a list of all objects in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Object = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a list of all objects in the table <c>Tab</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>tabfile_info(Filename) -> {ok, TableInfo} | {error, Reason}</name>
+ <fsummary>Return a list of all objects in an ETS table.</fsummary>
+ <type>
+ <v>Filename = string() | atom()</v>
+ <v>TableInfo = [InfoItem]</v>
+ <v>InfoItem = {InfoTag, term()}</v>
+ <v>InfoTag = name | type | protection | named_table | keypos | size | extended_info | version</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Returns information about the table dumped to file by
+ <seealso marker="#tab2file/2">tab2file/2</seealso> or
+ <seealso marker="#tab2file/3">tab2file/3</seealso></p>
+ <p>The following items are returned:</p>
+ <taglist>
+ <tag>name</tag>
+ <item><p>The name of the dumped table. If the table was a
+ named table, a table with the same name cannot exist when the
+ table is loaded from file with
+ <seealso marker="#file2tab/2">file2tab/2</seealso>. If the table is
+ not saved as a named table, this field has no significance
+ at all when loading the table from file.</p></item>
+ <tag>type</tag>
+ <item>The ets type of the dumped table (i.e. <c>set</c>, <c>bag</c>,
+ <c>duplicate_bag</c> or <c>ordered_set</c>). This type will be used
+ when loading the table again.</item>
+ <tag>protection</tag>
+ <item>The protection of the dumped table (i.e. <c>private</c>,
+ <c>protected</c> or <c>public</c>). A table loaded from the file
+ will get the same protection.</item>
+ <tag>named_table</tag>
+ <item><c>true</c> if the table was a named table when dumped
+ to file, otherwise <c>false</c>. Note that when a named table
+ is loaded from a file, there cannot exist a table in the
+ system with the same name.</item>
+ <tag>keypos</tag>
+ <item>The <c>keypos</c> of the table dumped to file, which
+ will be used when loading the table again.</item>
+ <tag>size</tag>
+ <item>The number of objects in the table when the table dump
+ to file started, which in case of a <c>public</c> table need
+ not correspond to the number of objects actually saved to the
+ file, as objects might have been added or deleted by another
+ process during table dump.</item>
+ <tag>extended_info</tag>
+ <item>The extended information written in the file footer to
+ allow stronger verification during table loading from file, as
+ specified to <seealso
+ marker="#tab2file/3">tab2file/3</seealso>. Note that this
+ function only tells <em>which</em> information is present, not
+ the values in the file footer. The value is a list containing
+ one or more of the atoms <c>object_count</c> and
+ <c>md5sum</c>.</item>
+ <tag>version</tag>
+ <item>A tuple <c>{Major,Minor}</c> containing the major and
+ minor version of the file format for ets table dumps. This
+ version field was added beginning with stdlib-1.5.1, files
+ dumped with older versions will return <c>{0,0}</c> in this
+ field.</item>
+ </taglist>
+ <p>An error is returned if the file is inaccessible,
+ badly damaged or not an file produced with <seealso
+ marker="#tab2file/2">tab2file/2</seealso> or <seealso
+ marker="#tab2file/3">tab2file/3</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>table(Tab [, Options]) -> QueryHandle</name>
+ <fsummary>Return a QLC query handle.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>QueryHandle = -&nbsp;a query handle, see qlc(3)&nbsp;-</v>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {n_objects, NObjects} | {traverse, TraverseMethod}</v>
+ <v>NObjects = default | integer() > 0</v>
+ <v>TraverseMethod = first_next | last_prev | select | {select, MatchSpec}</v>
+ <v>MatchSpec = match_spec()</v>
+ </type>
+ <desc>
+ <p> <marker id="qlc_table"></marker>
+Returns a QLC (Query List
+ Comprehension) query handle. The module <c>qlc</c> implements
+ a query language aimed mainly at Mnesia but ETS tables, Dets
+ tables, and lists are also recognized by QLC as sources of
+ data. Calling <c>ets:table/1,2</c> is the means to make the
+ ETS table <c>Tab</c> usable to QLC.</p>
+ <p>When there are only simple restrictions on the key position
+ QLC uses <c>ets:lookup/2</c> to look up the keys, but when
+ that is not possible the whole table is traversed. The
+ option <c>traverse</c> determines how this is done:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>first_next</c>. The table is traversed one key at
+ a time by calling <c>ets:first/1</c> and
+ <c>ets:next/2</c>.</p>
+ </item>
+ <item>
+ <p><c>last_prev</c>. The table is traversed one key at
+ a time by calling <c>ets:last/1</c> and
+ <c>ets:prev/2</c>.</p>
+ </item>
+ <item>
+ <p><c>select</c>. The table is traversed by calling
+ <c>ets:select/3</c> and <c>ets:select/1</c>. The option
+ <c>n_objects</c> determines the number of objects
+ returned (the third argument of <c>select/3</c>); the
+ default is to return <c>100</c> objects at a time. The
+ <seealso marker="#match_spec">match_spec</seealso> (the
+ second argument of <c>select/3</c>) is assembled by QLC:
+ simple filters are translated into equivalent match_specs
+ while more complicated filters have to be applied to all
+ objects returned by <c>select/3</c> given a match_spec
+ that matches all objects.</p>
+ </item>
+ <item>
+ <p><c>{select, MatchSpec}</c>. As for <c>select</c>
+ the table is traversed by calling <c>ets:select/3</c> and
+ <c>ets:select/1</c>. The difference is that the
+ match_spec is explicitly given. This is how to state
+ match_specs that cannot easily be expressed within the
+ syntax provided by QLC.</p>
+ </item>
+ </list>
+ <p>The following example uses an explicit match_spec to
+ traverse the table:</p>
+ <pre>
+9> <input>true = ets:insert(Tab = ets:new(t, []), [{1,a},{2,b},{3,c},{4,d}]),</input>
+<input>MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X &lt; 5) -> {Y} end),</input>
+<input>QH1 = ets:table(Tab, [{traverse, {select, MS}}]).</input></pre>
+ <p>An example with implicit match_spec:</p>
+ <pre>
+10> <input>QH2 = qlc:q([{Y} || {X,Y} &lt;- ets:table(Tab), (X > 1) or (X &lt; 5)]).</input></pre>
+ <p>The latter example is in fact equivalent to the former which
+ can be verified using the function <c>qlc:info/1</c>:</p>
+ <pre>
+11> <input>qlc:info(QH1) =:= qlc:info(QH2).</input>
+true</pre>
+ <p><c>qlc:info/1</c> returns information about a query handle,
+ and in this case identical information is returned for the
+ two query handles.</p>
+ </desc>
+ </func>
+ <func>
+ <name>test_ms(Tuple, MatchSpec) -> {ok, Result} | {error, Errors}</name>
+ <fsummary>Test a match_spec for use in ets:select/2.</fsummary>
+ <type>
+ <v>Tuple = tuple()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>Result = term()</v>
+ <v>Errors = [{warning|error, string()}]</v>
+ </type>
+ <desc>
+ <p>This function is a utility to test a
+ <seealso marker="#match_spec">match_spec</seealso> used in
+ calls to <c>ets:select/2</c>. The function both tests
+ <c>MatchSpec</c> for "syntactic" correctness and runs the
+ match_spec against the object <c>Tuple</c>. If the match_spec
+ contains errors, the tuple <c>{error, Errors}</c> is returned
+ where <c>Errors</c> is a list of natural language
+ descriptions of what was wrong with the match_spec. If the
+ match_spec is syntactically OK, the function returns
+ <c>{ok,Term}</c> where <c>Term</c> is what would have been
+ the result in a real <c>ets:select/2</c> call or <c>false</c>
+ if the match_spec does not match the object <c>Tuple</c>.</p>
+ <p>This is a useful debugging and test tool, especially when
+ writing complicated <c>ets:select/2</c> calls.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_dets(Tab, DetsTab) -> Tab</name>
+ <fsummary>Fill a Dets table with objects from an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>DetsTab = atom()</v>
+ </type>
+ <desc>
+ <p>Fills an already created/opened Dets table with the objects
+ in the already opened ETS table named <c>Tab</c>. The Dets
+ table is emptied before the objects are inserted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>update_counter(Tab, Key, UpdateOp) -> Result</name>
+ <name>update_counter(Tab, Key, [UpdateOp]) -> [Result]</name>
+ <name>update_counter(Tab, Key, Incr) -> Result</name>
+ <fsummary>Update a counter object in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = term()</v>
+ <v>UpdateOp = {Pos,Incr} | {Pos,Incr,Threshold,SetValue}</v>
+ <v>Pos = Incr = Threshold = SetValue = Result = int()</v>
+ </type>
+ <desc>
+ <p>This function provides an efficient way to update one or more
+ counters, without the hassle of having to look up an object, update
+ the object by incrementing an element and insert the resulting object
+ into the table again. (The update is done atomically; i.e. no process
+ can access the ets table in the middle of the operation.)
+ </p>
+ <p>It will destructively update the object with key <c>Key</c>
+ in the table <c>Tab</c> by adding <c>Incr</c> to the element
+ at the <c>Pos</c>:th position. The new counter value is
+ returned. If no position is specified, the element directly
+ following the key (<c><![CDATA[<keypos>+1]]></c>) is updated.</p>
+ <p>If a <c>Threshold</c> is specified, the counter will be
+ reset to the value <c>SetValue</c> if the following
+ conditions occur:</p>
+ <list type="bulleted">
+ <item>The <c>Incr</c> is not negative (<c>>= 0</c>) and the
+ result would be greater than (<c>></c>) <c>Threshold</c></item>
+ <item>The <c>Incr</c> is negative (<c><![CDATA[< 0]]></c>) and the
+ result would be less than (<c><![CDATA[<]]></c>)
+ <c>Threshold</c></item>
+ </list>
+ <p>A list of <c>UpdateOp</c> can be supplied to do several update
+ operations within the object. The operations are carried out in the
+ order specified in the list. If the same counter position occurs
+ more than one time in the list, the corresponding counter will thus
+ be updated several times, each time based on the previous result.
+ The return value is a list of the new counter values from each
+ update operation in the same order as in the operation list. If an
+ empty list is specified, nothing is updated and an empty list is
+ returned. If the function should fail, no updates will be done at
+ all.
+ </p>
+ <p>The given Key is used to identify the object by either
+ <em>matching</em> the key of an object in a <c>set</c> table,
+ or <em>compare equal</em> to the key of an object in an
+ <c>ordered_set</c> table (see
+ <seealso marker="#lookup/2">lookup/2</seealso> and
+ <seealso marker="#new/2">new/2</seealso>
+ for details on the difference).</p>
+ <p>The function will fail with reason <c>badarg</c> if:</p>
+ <list type="bulleted">
+ <item>the table is not of type <c>set</c> or
+ <c>ordered_set</c>,</item>
+ <item>no object with the right key exists,</item>
+ <item>the object has the wrong arity,</item>
+ <item>the element to update is not an integer,</item>
+ <item>the element to update is also the key, or,</item>
+ <item>any of <c>Pos</c>, <c>Incr</c>, <c>Threshold</c> or
+ <c>SetValue</c> is not an integer</item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>update_element(Tab, Key, {Pos,Value}) -> true | false</name>
+ <name>update_element(Tab, Key, [{Pos,Value}]) -> true | false</name>
+ <fsummary>Updates the <c>Pos</c>:th element of the object with a given key in an ETS table.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Key = Value = term()</v>
+ <v>Pos = int()</v>
+ </type>
+ <desc>
+ <p>This function provides an efficient way to update one or more
+ elements within an object, without the hassle of having to look up,
+ update and write back the entire object.
+ </p>
+ <p>It will destructively update the object with key <c>Key</c>
+ in the table <c>Tab</c>. The element at the <c>Pos</c>:th position
+ will be given the value <c>Value</c>. </p>
+ <p>A list of <c>{Pos,Value}</c> can be supplied to update several
+ elements within the same object. If the same position occurs more
+ than one in the list, the last value in the list will be written. If
+ the list is empty or the function fails, no updates will be done at
+ all. The function is also atomic in the sense that other processes
+ can never see any intermediate results.
+ </p>
+ <p>The function returns <c>true</c> if an object with the key
+ <c>Key</c> was found, <c>false</c> otherwise.
+ </p>
+ <p>The given Key is used to identify the object by either
+ <em>matching</em> the key of an object in a <c>set</c> table,
+ or <em>compare equal</em> to the key of an object in an
+ <c>ordered_set</c> table (see
+ <seealso marker="#lookup/2">lookup/2</seealso> and
+ <seealso marker="#new/2">new/2</seealso>
+ for details on the difference).</p>
+ <p>The function will fail with reason <c>badarg</c> if:</p>
+ <list type="bulleted">
+ <item>the table is not of type <c>set</c> or
+ <c>ordered_set</c>,</item>
+ <item><c>Pos</c> is less than 1 or greater than the object
+ arity, or,</item>
+ <item>the element to update is also the key</item>
+ </list>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/fascicules.xml b/lib/stdlib/doc/src/fascicules.xml
new file mode 100644
index 0000000000..b30d34186e
--- /dev/null
+++ b/lib/stdlib/doc/src/fascicules.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE fascicules SYSTEM "fascicules.dtd">
+
+<fascicules>
+ <fascicule file="part" href="part_frame.html" entry="no">
+ STDLIB User's Guide
+ </fascicule>
+ <fascicule file="ref_man" href="ref_man_frame.html" entry="yes">
+ Reference Manual
+ </fascicule>
+ <fascicule file="part_notes" href="part_notes_frame.html" entry="no">
+ Release Notes
+ </fascicule>
+ <fascicule file="" href="../../../../doc/print.html" entry="no">
+ Off-Print
+ </fascicule>
+</fascicules>
+
diff --git a/lib/stdlib/doc/src/file_sorter.xml b/lib/stdlib/doc/src/file_sorter.xml
new file mode 100644
index 0000000000..b3f4da294c
--- /dev/null
+++ b/lib/stdlib/doc/src/file_sorter.xml
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2001</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>file_sorter</title>
+ <prepared>Hans Bolinder</prepared>
+ <responsible>nobody</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2001-03-13</date>
+ <rev>PA1</rev>
+ <file>file_sorter.sgml</file>
+ </header>
+ <module>file_sorter</module>
+ <modulesummary>File Sorter</modulesummary>
+ <description>
+ <p>The functions of this module sort terms on files, merge already
+ sorted files, and check files for sortedness. Chunks containing
+ binary terms are read from a sequence of files, sorted
+ internally in memory and written on temporary files, which are
+ merged producing one sorted file as output. Merging is provided
+ as an optimization; it is faster when the files are already
+ sorted, but it always works to sort instead of merge.
+ </p>
+ <p>On a file, a term is represented by a header and a binary. Two
+ options define the format of terms on files:
+ </p>
+ <list type="bulleted">
+ <item><c>{header, HeaderLength}</c>. HeaderLength determines the
+ number of bytes preceding each binary and containing the
+ length of the binary in bytes. Default is 4. The order of the
+ header bytes is defined as follows: if <c>B</c> is a binary
+ containing a header only, the size <c>Size</c> of the binary
+ is calculated as
+ <c><![CDATA[<<Size:HeaderLength/unit:8>> = B]]></c>.
+ </item>
+ <item><c>{format, Format}</c>. The format determines the
+ function that is applied to binaries in order to create the
+ terms that will be sorted. The default value is
+ <c>binary_term</c>, which is equivalent to
+ <c>fun&nbsp;binary_to_term/1</c>. The value <c>binary</c> is
+ equivalent to <c>fun(X) -> X end</c>, which means that the
+ binaries will be sorted as they are. This is the fastest
+ format. If <c>Format</c> is <c>term</c>, <c>io:read/2</c> is
+ called to read terms. In that case only the default value of
+ the <c>header</c> option is allowed. The <c>format</c> option
+ also determines what is written to the sorted output file: if
+ <c>Format</c> is <c>term</c> then <c>io:format/3</c> is called
+ to write each term, otherwise the binary prefixed by a header
+ is written. Note that the binary written is the same binary
+ that was read; the results of applying the <c>Format</c>
+ function are thrown away as soon as the terms have been
+ sorted. Reading and writing terms using the <c>io</c> module
+ is very much slower than reading and writing binaries.
+ </item>
+ </list>
+ <p>Other options are:
+ </p>
+ <list type="bulleted">
+ <item><c>{order, Order}</c>. The default is to sort terms in
+ ascending order, but that can be changed by the value
+ <c>descending</c> or by giving an ordering function <c>Fun</c>.
+ An ordering function is antisymmetric, transitive and total.
+ <c>Fun(A,&nbsp;B)</c> should return <c>true</c> if <c>A</c>
+ comes before <c>B</c> in the ordering, <c>false</c> otherwise.
+ Using an ordering function will slow down the sort
+ considerably. The <c>keysort</c>, <c>keymerge</c> and
+ <c>keycheck</c> functions do not accept ordering functions.
+ </item>
+ <item><c>{unique, bool()}</c>. When sorting or merging files,
+ only the first of a sequence of terms that compare equal is
+ output if this option is set to <c>true</c>. The default
+ value is <c>false</c> which implies that all terms that
+ compare equal are output. When checking files for
+ sortedness, a check that no pair of consecutive terms
+ compares equal is done if this option is set to <c>true</c>.
+ </item>
+ <item><c>{tmpdir, TempDirectory}</c>. The directory where
+ temporary files are put can be chosen explicitly. The
+ default, implied by the value <c>""</c>, is to put temporary
+ files on the same directory as the sorted output file. If
+ output is a function (see below), the directory returned by
+ <c>file:get_cwd()</c> is used instead. The names of
+ temporary files are derived from the Erlang nodename
+ (<c>node()</c>), the process identifier of the current Erlang
+ emulator (<c>os:getpid()</c>), and a timestamp
+ (<c>erlang:now()</c>); a typical name would be
+ <c>fs_mynode@myhost_1763_1043_337000_266005.17</c>, where
+ <c>17</c> is a sequence number. Existing files will be
+ overwritten. Temporary files are deleted unless some
+ uncaught EXIT signal occurs.
+ </item>
+ <item><c>{compressed, bool()}</c>. Temporary files and the
+ output file may be compressed. The default value
+ <c>false</c> implies that written files are not
+ compressed. Regardless of the value of the <c>compressed</c>
+ option, compressed files can always be read. Note that
+ reading and writing compressed files is significantly slower
+ than reading and writing uncompressed files.
+ </item>
+ <item><c>{size, Size}</c>. By default approximately 512*1024
+ bytes read from files are sorted internally. This option
+ should rarely be needed.
+ </item>
+ <item><c>{no_files, NoFiles}</c>. By default 16 files are
+ merged at a time. This option should rarely be needed.
+ </item>
+ </list>
+ <p>To summarize, here is the syntax of the options:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>Options = [Option] | Option</c></p>
+ </item>
+ <item>
+ <p><c>Option = {header, HeaderLength} | {format, Format} | {order, Order} | {unique, bool()} | {tmpdir, TempDirectory} | {compressed, bool()} | {size, Size} | {no_files, NoFiles}</c></p>
+ </item>
+ <item>
+ <p><c>HeaderLength = int() > 0</c></p>
+ </item>
+ <item>
+ <p><c>Format = binary_term | term | binary | FormatFun</c></p>
+ </item>
+ <item>
+ <p><c>FormatFun = fun(Binary) -> Term</c></p>
+ </item>
+ <item>
+ <p><c>Order = ascending | descending | OrderFun</c></p>
+ </item>
+ <item>
+ <p><c>OrderFun = fun(Term, Term) -> bool()</c></p>
+ </item>
+ <item>
+ <p><c>TempDirectory = "" | file_name()</c></p>
+ </item>
+ <item>
+ <p><c>Size = int() >= 0</c></p>
+ </item>
+ <item>
+ <p><c>NoFiles = int() > 1</c></p>
+ </item>
+ </list>
+ <p>As an alternative to sorting files, a function of one argument
+ can be given as input. When called with the argument <c>read</c>
+ the function is assumed to return <c>end_of_input</c> or
+ <c>{end_of_input, Value}}</c> when there is no more input
+ (<c>Value</c> is explained below), or <c>{Objects, Fun}</c>,
+ where <c>Objects</c> is a list of binaries or terms depending on
+ the format and <c>Fun</c> is a new input function. Any other
+ value is immediately returned as value of the current call to
+ <c>sort</c> or <c>keysort</c>. Each input function will be
+ called exactly once, and should an error occur, the last
+ function is called with the argument <c>close</c>, the reply of
+ which is ignored.
+ </p>
+ <p>A function of one argument can be given as output. The results
+ of sorting or merging the input is collected in a non-empty
+ sequence of variable length lists of binaries or terms depending
+ on the format. The output function is called with one list at a
+ time, and is assumed to return a new output function. Any other
+ return value is immediately returned as value of the current
+ call to the sort or merge function. Each output function is
+ called exactly once. When some output function has been applied
+ to all of the results or an error occurs, the last function is
+ called with the argument <c>close</c>, and the reply is returned
+ as value of the current call to the sort or merge function. If a
+ function is given as input and the last input function returns
+ <c>{end_of_input, Value}</c>, the function given as output will
+ be called with the argument <c>{value, Value}</c>. This makes it
+ easy to initiate the sequence of output functions with a value
+ calculated by the input functions.
+ </p>
+ <p>As an example, consider sorting the terms on a disk log file.
+ A function that reads chunks from the disk log and returns a
+ list of binaries is used as input. The results are collected in
+ a list of terms.</p>
+ <pre>
+sort(Log) ->
+ {ok, _} = disk_log:open([{name,Log}, {mode,read_only}]),
+ Input = input(Log, start),
+ Output = output([]),
+ Reply = file_sorter:sort(Input, Output, {format,term}),
+ ok = disk_log:close(Log),
+ Reply.
+
+input(Log, Cont) ->
+ fun(close) ->
+ ok;
+ (read) ->
+ case disk_log:chunk(Log, Cont) of
+ {error, Reason} ->
+ {error, Reason};
+ {Cont2, Terms} ->
+ {Terms, input(Log, Cont2)};
+ {Cont2, Terms, _Badbytes} ->
+ {Terms, input(Log, Cont2)};
+ eof ->
+ end_of_input
+ end
+ end.
+
+output(L) ->
+ fun(close) ->
+ lists:append(lists:reverse(L));
+ (Terms) ->
+ output([Terms | L])
+ end. </pre>
+ <p>Further examples of functions as input and output can be found
+ at the end of the <c>file_sorter</c> module; the <c>term</c>
+ format is implemented with functions.
+ </p>
+ <p>The possible values of <c>Reason</c> returned when an error
+ occurs are:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>bad_object</c>, <c>{bad_object, FileName}</c>.
+ Applying the format function failed for some binary,
+ or the key(s) could not be extracted from some term.</p>
+ </item>
+ <item>
+ <p><c>{bad_term, FileName}</c>. <c>io:read/2</c> failed
+ to read some term.</p>
+ </item>
+ <item>
+ <p><c>{file_error, FileName, Reason2}</c>. See
+ <c>file(3)</c> for an explanation of <c>Reason2</c>.</p>
+ </item>
+ <item>
+ <p><c>{premature_eof, FileName}</c>. End-of-file was
+ encountered inside some binary term.</p>
+ </item>
+ </list>
+ <p><em>Types</em></p>
+ <pre>
+Binary = binary()
+FileName = file_name()
+FileNames = [FileName]
+ICommand = read | close
+IReply = end_of_input | {end_of_input, Value} | {[Object], Infun} | InputReply
+Infun = fun(ICommand) -> IReply
+Input = FileNames | Infun
+InputReply = Term
+KeyPos = int() > 0 | [int() > 0]
+OCommand = {value, Value} | [Object] | close
+OReply = Outfun | OutputReply
+Object = Term | Binary
+Outfun = fun(OCommand) -> OReply
+Output = FileName | Outfun
+OutputReply = Term
+Term = term()
+Value = Term</pre>
+ </description>
+ <funcs>
+ <func>
+ <name>sort(FileName) -> Reply</name>
+ <name>sort(Input, Output) -> Reply</name>
+ <name>sort(Input, Output, Options) -> Reply</name>
+ <fsummary>Sort terms on files.</fsummary>
+ <type>
+ <v>Reply = ok | {error, Reason} | InputReply | OutputReply</v>
+ </type>
+ <desc>
+ <p>Sorts terms on files.
+ </p>
+ <p><c>sort(FileName)</c> is equivalent to
+ <c>sort([FileName], FileName)</c>.
+ </p>
+ <p><c>sort(Input, Output)</c> is equivalent to
+ <c>sort(Input, Output, [])</c>.
+ </p>
+ <p></p>
+ </desc>
+ </func>
+ <func>
+ <name>keysort(KeyPos, FileName) -> Reply</name>
+ <name>keysort(KeyPos, Input, Output) -> Reply</name>
+ <name>keysort(KeyPos, Input, Output, Options) -> Reply</name>
+ <fsummary>Sort terms on files by key.</fsummary>
+ <type>
+ <v>Reply = ok | {error, Reason} | InputReply | OutputReply</v>
+ </type>
+ <desc>
+ <p>Sorts tuples on files. The sort is performed on the
+ element(s) mentioned in <c>KeyPos</c>. If two tuples
+ compare equal on one element, next element according to
+ <c>KeyPos</c> is compared. The sort is stable.
+ </p>
+ <p><c>keysort(N, FileName)</c> is equivalent to
+ <c>keysort(N, [FileName], FileName)</c>.
+ </p>
+ <p><c>keysort(N, Input, Output)</c> is equivalent to
+ <c>keysort(N, Input, Output, [])</c>.
+ </p>
+ <p></p>
+ </desc>
+ </func>
+ <func>
+ <name>merge(FileNames, Output) -> Reply</name>
+ <name>merge(FileNames, Output, Options) -> Reply</name>
+ <fsummary>Merge terms on files.</fsummary>
+ <type>
+ <v>Reply = ok | {error, Reason} | OutputReply</v>
+ </type>
+ <desc>
+ <p>Merges terms on files. Each input file is assumed to be
+ sorted.
+ </p>
+ <p><c>merge(FileNames, Output)</c> is equivalent to
+ <c>merge(FileNames, Output, [])</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>keymerge(KeyPos, FileNames, Output) -> Reply</name>
+ <name>keymerge(KeyPos, FileNames, Output, Options) -> Reply</name>
+ <fsummary>Merge terms on files by key.</fsummary>
+ <type>
+ <v>Reply = ok | {error, Reason} | OutputReply</v>
+ </type>
+ <desc>
+ <p>Merges tuples on files. Each input file is assumed to be
+ sorted on key(s).
+ </p>
+ <p><c>keymerge(KeyPos, FileNames, Output)</c> is equivalent
+ to <c>keymerge(KeyPos, FileNames, Output, [])</c>.
+ </p>
+ <p></p>
+ </desc>
+ </func>
+ <func>
+ <name>check(FileName) -> Reply</name>
+ <name>check(FileNames, Options) -> Reply</name>
+ <fsummary>Check whether terms on files are sorted.</fsummary>
+ <type>
+ <v>Reply = {ok, [Result]} | {error, Reason}</v>
+ <v>Result = {FileName, TermPosition, Term}</v>
+ <v>TermPosition = int() > 1</v>
+ </type>
+ <desc>
+ <p>Checks files for sortedness. If a file is not sorted, the
+ first out-of-order element is returned. The first term on a
+ file has position 1.
+ </p>
+ <p><c>check(FileName)</c> is equivalent to
+ <c>check([FileName], [])</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>keycheck(KeyPos, FileName) -> CheckReply</name>
+ <name>keycheck(KeyPos, FileNames, Options) -> Reply</name>
+ <fsummary>Check whether terms on files are sorted by key.</fsummary>
+ <type>
+ <v>Reply = {ok, [Result]} | {error, Reason}</v>
+ <v>Result = {FileName, TermPosition, Term}</v>
+ <v>TermPosition = int() > 1</v>
+ </type>
+ <desc>
+ <p>Checks files for sortedness. If a file is not sorted, the
+ first out-of-order element is returned. The first term on a
+ file has position 1.
+ </p>
+ <p><c>keycheck(KeyPos, FileName)</c> is equivalent
+ to <c>keycheck(KeyPos, [FileName], [])</c>.
+ </p>
+ <p></p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/filelib.xml b/lib/stdlib/doc/src/filelib.xml
new file mode 100644
index 0000000000..c1c4ca9350
--- /dev/null
+++ b/lib/stdlib/doc/src/filelib.xml
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2003</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>filelib</title>
+ <prepared>Bjorn Gustavsson</prepared>
+ <responsible>Bjorn Gustavsson</responsible>
+ <docno>1</docno>
+ <approved>Kenneth Lundin</approved>
+ <checked></checked>
+ <date>03-01-21</date>
+ <rev>A</rev>
+ <file>filelib.sgml</file>
+ </header>
+ <module>filelib</module>
+ <modulesummary>File utilities, such as wildcard matching of filenames</modulesummary>
+ <description>
+ <p>This module contains utilities on a higher level than the <c>file</c>
+ module.</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+filename() = string() | atom() | DeepList
+dirname() = filename()
+DeepList = [char() | atom() | DeepList]</code>
+ </section>
+
+ <funcs>
+ <func>
+ <name>ensure_dir(Name) -> ok | {error, Reason}</name>
+ <fsummary>Ensure that all parent directories for a file or directory exist.</fsummary>
+ <type>
+ <v>Name = filename() | dirname()</v>
+ <v>Reason = posix() -- see file(3)</v>
+ </type>
+ <desc>
+ <p>The <c>ensure_dir/1</c> function ensures that all parent
+ directories for the given file or directory name <c>Name</c>
+ exist, trying to create them if necessary.</p>
+ <p>Returns <c>ok</c> if all parent directories already exist
+ or could be created, or <c>{error, Reason}</c> if some parent
+ directory does not exist and could not be created for some
+ reason.</p>
+ </desc>
+ </func>
+ <func>
+ <name>file_size(Filename) -> integer()</name>
+ <fsummary>Return the size in bytes of the file.</fsummary>
+ <desc>
+ <p>The <c>file_size</c> function returns the size of the given file.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fold_files(Dir, RegExp, Recursive, Fun, AccIn) -> AccOut </name>
+ <fsummary>Fold over all files matching a regular expression.</fsummary>
+ <type>
+ <v>Dir = dirname()</v>
+ <v>RegExp = regular_expression_string()</v>
+ <v>Recursive = true|false</v>
+ <v>Fun = fun(F, AccIn) -> AccOut</v>
+ <v>AccIn = AccOut = term()</v>
+ </type>
+ <desc>
+ <p>The <c>fold_files/5</c> function folds the function
+ <c>Fun</c> over all (regular) files <c>F</c> in the
+ directory <c>Dir</c> that match the regular expression <c>RegExp</c>
+ (see the <seealso marker="re">re</seealso> module for a description
+ of the allowed regular expressions).
+ If <c>Recursive</c> is true all sub-directories to <c>Dir</c>
+ are processed. The regular expression matching is done on just
+ the filename without the directory part.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_dir(Name) -> true | false</name>
+ <fsummary>Test whether Name refer to a directory or not</fsummary>
+ <type>
+ <v>Name = filename() | dirname()</v>
+ </type>
+ <desc>
+ <p>The <c>is_dir/1</c> function returns <c>true</c> if <c>Name</c>
+ refers to a directory, and <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_file(Name) -> true | false</name>
+ <fsummary>Test whether Name refer to a file or directory.</fsummary>
+ <type>
+ <v>Name = filename() | dirname()</v>
+ </type>
+ <desc>
+ <p>The <c>is_file/1</c> function returns <c>true</c> if <c>Name</c>
+ refers to a file or a directory, and <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_regular(Name) -> true | false</name>
+ <fsummary>Test whether Name refer to a (regular) file.</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ </type>
+ <desc>
+ <p>The <c>is_regular/1</c> function returns <c>true</c> if <c>Name</c>
+ refers to a file (regular file), and <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>last_modified(Name) -> {{Year,Month,Day},{Hour,Min,Sec}} | 0</name>
+ <fsummary>Return the local date and time when a file was last modified.</fsummary>
+ <type>
+ <v>Name = filename() | dirname()</v>
+ </type>
+ <desc>
+ <p>The <c>last_modified/1</c> function returns the date and time the
+ given file or directory was last modified, or 0 if the file
+ does not exist.</p>
+ </desc>
+ </func>
+ <func>
+ <name>wildcard(Wildcard) -> list()</name>
+ <fsummary>Match filenames using Unix-style wildcards.</fsummary>
+ <type>
+ <v>Wildcard = filename() | dirname()</v>
+ </type>
+ <desc>
+ <p>The <c>wildcard/1</c> function returns a list of all files
+ that match Unix-style wildcard-string <c>Wildcard</c>.</p>
+ <p>The wildcard string looks like an ordinary filename, except
+ that certain "wildcard characters" are interpreted in a special
+ way. The following characters are special:
+ </p>
+ <taglist>
+ <tag>?</tag>
+ <item>
+ <p>Matches one character.</p>
+ </item>
+ <tag>*</tag>
+ <item>
+ <p>Matches any number of characters up to the end of
+ the filename, the next dot, or the next slash.</p>
+ </item>
+ <tag>{Item,...}</tag>
+ <item>
+ <p>Alternation. Matches one of the alternatives.</p>
+ </item>
+ </taglist>
+ <p>Other characters represent themselves. Only filenames that
+ have exactly the same character in the same position will match.
+ (Matching is case-sensitive; i.e. "a" will not match "A").
+ </p>
+ <p>Note that multiple "*" characters are allowed
+ (as in Unix wildcards, but opposed to Windows/DOS wildcards).
+ </p>
+ <p>Examples:</p>
+ <p>The following examples assume that the current directory is the
+ top of an Erlang/OTP installation.
+ </p>
+ <p>To find all <c>.beam</c> files in all applications, the following
+ line can be used:</p>
+ <code type="none">
+ filelib:wildcard("lib/*/ebin/*.beam"). </code>
+ <p>To find either <c>.erl</c> or <c>.hrl</c> in all applications
+ <c>src</c> directories, the following</p>
+ <code type="none">
+ filelib:wildcard("lib/*/src/*.?rl") </code>
+ <p>or the following line</p>
+ <code type="none">
+ filelib:wildcard("lib/*/src/*.{erl,hrl}") </code>
+ <p>can be used.</p>
+ <p>To find all <c>.hrl</c> files in either <c>src</c> or <c>include</c>
+ directories, use:</p>
+ <code type="none">
+ filelib:wildcard("lib/*/{src,include}/*.hrl"). </code>
+ <p>To find all <c>.erl</c> or <c>.hrl</c> files in either
+ <c>src</c> or <c>include</c> directories, use:</p>
+ <code type="none">
+ filelib:wildcard("lib/*/{src,include}/*.{erl,hrl}") </code>
+ </desc>
+ </func>
+ <func>
+ <name>wildcard(Wildcard, Cwd) -> list()</name>
+ <fsummary>Match filenames using Unix-style wildcards starting at a specified directory.</fsummary>
+ <type>
+ <v>Wildcard = filename() | dirname()</v>
+ <v>Cwd = dirname()</v>
+ </type>
+ <desc>
+ <p>The <c>wildcard/2</c> function works like <c>wildcard/1</c>,
+ except that instead of the actual working directory, <c>Cwd</c>
+ will be used.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
+
diff --git a/lib/stdlib/doc/src/filename.xml b/lib/stdlib/doc/src/filename.xml
new file mode 100644
index 0000000000..3a6c5e0b60
--- /dev/null
+++ b/lib/stdlib/doc/src/filename.xml
@@ -0,0 +1,385 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1997</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>filename</title>
+ <prepared>Kenneth Lundin</prepared>
+ <docno>1</docno>
+ <date>97-11-13</date>
+ <rev>B</rev>
+ </header>
+ <module>filename</module>
+ <modulesummary>Filename Manipulation Functions</modulesummary>
+ <description>
+ <p>The module <c>filename</c> provides a number of useful functions
+ for analyzing and manipulating file names. These functions are
+ designed so that the Erlang code can work on many different
+ platforms with different formats for file names. With file name
+ is meant all strings that can be used to denote a file. They can
+ be short relative names like <c>foo.erl</c>, very long absolute
+ name which include a drive designator and directory names like
+ <c>D:\\usr/local\\bin\\erl/lib\\tools\\foo.erl</c>, or any variations
+ in between.</p>
+ <p>In Windows, all functions return file names with forward slashes
+ only, even if the arguments contain back slashes. Use
+ <c>join/1</c> to normalize a file name by removing redundant
+ directory separators.</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+name() = string() | atom() | DeepList
+ DeepList = [char() | atom() | DeepList]</code>
+ </section>
+ <funcs>
+ <func>
+ <name>absname(Filename) -> string()</name>
+ <fsummary>Convert a filename to an absolute name, relative the working directory</fsummary>
+ <type>
+ <v>Filename = name()</v>
+ </type>
+ <desc>
+ <p>Converts a relative <c>Filename</c> and returns an absolute
+ name. No attempt is made to create the shortest absolute name,
+ because this can give incorrect results on file systems which
+ allow links.</p>
+ <p>Unix examples:</p>
+ <pre>
+1> <input>pwd().</input>
+"/usr/local"
+2> <input>filename:absname("foo").</input>
+"/usr/local/foo"
+3> <input>filename:absname("../x").</input>
+"/usr/local/../x"
+4> <input>filename:absname("/").</input>
+"/"</pre>
+ <p>Windows examples:</p>
+ <pre>
+1> <input>pwd().</input>
+"D:/usr/local"
+2> <input>filename:absname("foo").</input>
+"D:/usr/local/foo"
+3> <input>filename:absname("../x").</input>
+"D:/usr/local/../x"
+4> <input>filename:absname("/").</input>
+"D:/"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>absname(Filename, Dir) -> string()</name>
+ <fsummary>Convert a filename to an absolute name, relative a specified directory</fsummary>
+ <type>
+ <v>Filename = name()</v>
+ <v>Dir = string()</v>
+ </type>
+ <desc>
+ <p>This function works like <c>absname/1</c>, except that
+ the directory to which the file name should be made relative
+ is given explicitly in the <c>Dir</c> argument.</p>
+ </desc>
+ </func>
+ <func>
+ <name>absname_join(Dir, Filename) -> string()</name>
+ <fsummary>Join an absolute directory with a relative filename</fsummary>
+ <type>
+ <v>Dir = string()</v>
+ <v>Filename = name()</v>
+ </type>
+ <desc>
+ <p>Joins an absolute directory with a relative filename.
+ Similar to <c>join/2</c>, but on platforms with tight
+ restrictions on raw filename length and no support for
+ symbolic links (read: VxWorks), leading parent directory
+ components in <c>Filename</c> are matched against trailing
+ directory components in <c>Dir</c> so they can be removed
+ from the result - minimizing its length.</p>
+ </desc>
+ </func>
+ <func>
+ <name>basename(Filename) -> string()</name>
+ <fsummary>Return the last component of a filename</fsummary>
+ <type>
+ <v>Filename = name()</v>
+ </type>
+ <desc>
+ <p>Returns the last component of <c>Filename</c>, or
+ <c>Filename</c> itself if it does not contain any directory
+ separators.</p>
+ <pre>
+5> <input>filename:basename("foo").</input>
+"foo"
+6> <input>filename:basename("/usr/foo").</input>
+"foo"
+7> <input>filename:basename("/").</input>
+[]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>basename(Filename, Ext) -> string()</name>
+ <fsummary>Return the last component of a filename, stripped of the specified extension</fsummary>
+ <type>
+ <v>Filename = Ext = name()</v>
+ </type>
+ <desc>
+ <p>Returns the last component of <c>Filename</c> with the
+ extension <c>Ext</c> stripped. This function should be used
+ to remove a specific extension which might, or might not, be
+ there. Use <c>rootname(basename(Filename))</c> to remove an
+ extension that exists, but you are not sure which one it is.</p>
+ <pre>
+8> <input>filename:basename("~/src/kalle.erl", ".erl").</input>
+"kalle"
+9> <input>filename:basename("~/src/kalle.beam", ".erl").</input>
+"kalle.beam"
+10> <input>filename:basename("~/src/kalle.old.erl", ".erl").</input>
+"kalle.old"
+11> <input>filename:rootname(filename:basename("~/src/kalle.erl")).</input>
+"kalle"
+12> <input>filename:rootname(filename:basename("~/src/kalle.beam")).</input>
+"kalle"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>dirname(Filename) -> string()</name>
+ <fsummary>Return the directory part of a path name</fsummary>
+ <type>
+ <v>Filename = name()</v>
+ </type>
+ <desc>
+ <p>Returns the directory part of <c>Filename</c>.</p>
+ <pre>
+13> <input>filename:dirname("/usr/src/kalle.erl").</input>
+"/usr/src"
+14> <input>filename:dirname("kalle.erl").</input>
+"."
+
+5> <input>filename:dirname("\\\\usr\\\\src/kalle.erl").</input> % Windows
+"/usr/src"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>extension(Filename) -> string()</name>
+ <fsummary>Return the file extension</fsummary>
+ <type>
+ <v>Filename = name()</v>
+ </type>
+ <desc>
+ <p>Returns the file extension of <c>Filename</c>, including
+ the period. Returns an empty string if there is no extension.</p>
+ <pre>
+15> <input>filename:extension("foo.erl").</input>
+".erl"
+16> <input>filename:extension("beam.src/kalle").</input>
+[]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>flatten(Filename) -> string()</name>
+ <fsummary>Convert a filename to a flat string</fsummary>
+ <type>
+ <v>Filename = name()</v>
+ </type>
+ <desc>
+ <p>Converts a possibly deep list filename consisting of
+ characters and atoms into the corresponding flat string
+ filename.</p>
+ </desc>
+ </func>
+ <func>
+ <name>join(Components) -> string()</name>
+ <fsummary>Join a list of filename components with directory separators</fsummary>
+ <type>
+ <v>Components = [string()]</v>
+ </type>
+ <desc>
+ <p>Joins a list of file name <c>Components</c> with directory
+ separators. If one of the elements of <c>Components</c>
+ includes an absolute path, for example <c>"/xxx"</c>,
+ the preceding elements, if any, are removed from the result.</p>
+ <p>The result is "normalized":</p>
+ <list type="bulleted">
+ <item>Redundant directory separators are removed.</item>
+ <item>In Windows, all directory separators are forward
+ slashes and the drive letter is in lower case.</item>
+ </list>
+ <pre>
+17> <input>filename:join(["/usr", "local", "bin"]).</input>
+"/usr/local/bin"
+18> <input>filename:join(["a/b///c/"]).</input>
+"a/b/c"
+
+6> <input>filename:join(["B:a\\\\b///c/"]).</input> % Windows
+"b:a/b/c"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>join(Name1, Name2) -> string()</name>
+ <fsummary>Join two filename components with directory separators</fsummary>
+ <type>
+ <v>Name1 = Name2 = string()</v>
+ </type>
+ <desc>
+ <p>Joins two file name components with directory separators.
+ Equivalent to <c>join([Name1, Name2])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>nativename(Path) -> string()</name>
+ <fsummary>Return the native form of a file path</fsummary>
+ <type>
+ <v>Path = string()</v>
+ </type>
+ <desc>
+ <p>Converts <c>Path</c> to a form accepted by the command shell
+ and native applications on the current platform. On Windows,
+ forward slashes is converted to backward slashes. On all
+ platforms, the name is normalized as done by <c>join/1</c>.</p>
+ <pre>
+19> <input>filename:nativename("/usr/local/bin/").</input> % Unix
+"/usr/local/bin"
+
+7> <input>filename:nativename("/usr/local/bin/").</input> % Windows
+"\\\\usr\\\\local\\\\bin"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>pathtype(Path) -> absolute | relative | volumerelative</name>
+ <fsummary>Return the type of a path</fsummary>
+ <desc>
+ <p>Returns the type of path, one of <c>absolute</c>,
+ <c>relative</c>, or <c>volumerelative</c>.</p>
+ <taglist>
+ <tag><c>absolute</c></tag>
+ <item>
+ <p>The path name refers to a specific file on a specific
+ volume.</p>
+ <p>Unix example: <c>/usr/local/bin</c></p>
+ <p>Windows example: <c>D:/usr/local/bin</c></p>
+ </item>
+ <tag><c>relative</c></tag>
+ <item>
+ <p>The path name is relative to the current working
+ directory on the current volume.</p>
+ <p>Example: <c>foo/bar, ../src</c></p>
+ </item>
+ <tag><c>volumerelative</c></tag>
+ <item>
+ <p>The path name is relative to the current working
+ directory on a specified volume, or it is a specific file
+ on the current working volume.</p>
+ <p>Windows example: <c>D:bar.erl, /bar/foo.erl</c></p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>rootname(Filename) -> string()</name>
+ <name>rootname(Filename, Ext) -> string()</name>
+ <fsummary>Remove a filename extension</fsummary>
+ <type>
+ <v>Filename = Ext = name()</v>
+ </type>
+ <desc>
+ <p>Remove a filename extension. <c>rootname/2</c> works as
+ <c>rootname/1</c>, except that the extension is removed only
+ if it is <c>Ext</c>.</p>
+ <pre>
+20> <input>filename:rootname("/beam.src/kalle").</input>
+/beam.src/kalle"
+21> <input>filename:rootname("/beam.src/foo.erl").</input>
+"/beam.src/foo"
+22> <input>filename:rootname("/beam.src/foo.erl", ".erl").</input>
+"/beam.src/foo"
+23> <input>filename:rootname("/beam.src/foo.beam", ".erl").</input>
+"/beam.src/foo.beam"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>split(Filename) -> Components</name>
+ <fsummary>Split a filename into its path components</fsummary>
+ <type>
+ <v>Filename = name()</v>
+ <v>Components = [string()]</v>
+ </type>
+ <desc>
+ <p>Returns a list whose elements are the path components of
+ <c>Filename</c>.</p>
+ <pre>
+24> <input>filename:split("/usr/local/bin").</input>
+["/","usr","local","bin"]
+25> <input>filename:split("foo/bar").</input>
+["foo","bar"]
+26> <input>filename:split("a:\\\\msdev\\\\include").</input>
+["a:/","msdev","include"]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>find_src(Beam) -> {SourceFile, Options} | {error,{ErrorReason,Module}}</name>
+ <name>find_src(Beam, Rules) -> {SourceFile, Options} | {error,{ErrorReason,Module}}</name>
+ <fsummary>Find the filename and compiler options for a module</fsummary>
+ <type>
+ <v>Beam = Module | Filename</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Filename = string() | atom()</v>
+ <v>SourceFile = string()</v>
+ <v>Options = [Opt]</v>
+ <v>&nbsp;Opt = {i, string()} | {outdir, string()} | {d, atom()}</v>
+ <v>ErrorReason = non_existing | preloaded | interpreted</v>
+ </type>
+ <desc>
+ <p>Finds the source filename and compiler options for a module.
+ The result can be fed to <c>compile:file/2</c> in order to
+ compile the file again.</p>
+ <p>The <c>Beam</c> argument, which can be a string or an atom,
+ specifies either the module name or the path to the source
+ code, with or without the <c>".erl"</c> extension. In either
+ case, the module must be known by the code server, i.e.
+ <c>code:which(Module)</c> must succeed.</p>
+ <p><c>Rules</c> describes how the source directory can be found,
+ when the object code directory is known. It is a list of
+ tuples <c>{BinSuffix, SourceSuffix}</c> and is interpreted
+ as follows: If the end of the directory name where the object
+ is located matches <c>BinSuffix</c>, then the source code
+ directory has the same name, but with <c>BinSuffix</c>
+ replaced by <c>SourceSuffix</c>. <c>Rules</c> defaults to:</p>
+ <code type="none">
+[{"", ""}, {"ebin", "src"}, {"ebin", "esrc"}]</code>
+ <p>If the source file is found in the resulting directory, then
+ the function returns that location together with
+ <c>Options</c>. Otherwise, the next rule is tried, and so on.</p>
+
+ <p>The function returns <c>{SourceFile, Options}</c> if it succeeds.
+ <c>SourceFile</c> is the absolute path to the source file
+ without the <c>".erl"</c> extension. <c>Options</c> include
+ the options which are necessary to recompile the file with
+ <c>compile:file/2</c>, but excludes options such as
+ <c>report</c> or <c>verbose</c> which do not change the way
+ code is generated. The paths in the <c>{outdir, Path}</c>
+ and <c>{i, Path}</c> options are guaranteed to be
+ absolute.</p>
+
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
new file mode 100644
index 0000000000..accec623b9
--- /dev/null
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -0,0 +1,487 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2001</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>gb_sets</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>gb_sets</module>
+ <modulesummary>General Balanced Trees</modulesummary>
+ <description>
+ <p>An implementation of ordered sets using Prof. Arne Andersson's
+ General Balanced Trees. This can be much more efficient than
+ using ordered lists, for larger sets, but depends on the
+ application.</p>
+ </description>
+
+ <section>
+ <title>Complexity note</title>
+ <p>The complexity on set operations is bounded by either O(|S|) or
+ O(|T| * log(|S|)), where S is the largest given set, depending
+ on which is fastest for any particular function call. For
+ operating on sets of almost equal size, this implementation is
+ about 3 times slower than using ordered-list sets directly. For
+ sets of very different sizes, however, this solution can be
+ arbitrarily much faster; in practical cases, often between 10
+ and 100 times. This implementation is particularly suited for
+ accumulating elements a few at a time, building up a large set
+ (more than 100-200 elements), and repeatedly testing for
+ membership in the current set.</p>
+ <p>As with normal tree structures, lookup (membership testing),
+ insertion and deletion have logarithmic complexity.</p>
+ </section>
+
+ <section>
+ <title>Compatibility</title>
+ <p>All of the following functions in this module also exist
+ and do the same thing in the <c>sets</c> and <c>ordsets</c>
+ modules. That is, by only changing the module name for each call,
+ you can try out different set representations.</p>
+ <p></p>
+ <list type="bulleted">
+ <item>
+ <p><c>add_element/2</c></p>
+ </item>
+ <item>
+ <p><c>del_element/2</c></p>
+ </item>
+ <item>
+ <p><c>filter/2</c></p>
+ </item>
+ <item>
+ <p><c>fold/3</c></p>
+ </item>
+ <item>
+ <p><c>from_list/1</c></p>
+ </item>
+ <item>
+ <p><c>intersection/1</c></p>
+ </item>
+ <item>
+ <p><c>intersection/2</c></p>
+ </item>
+ <item>
+ <p><c>is_element/2</c></p>
+ </item>
+ <item>
+ <p><c>is_set/1</c></p>
+ </item>
+ <item>
+ <p><c>is_subset/2</c></p>
+ </item>
+ <item>
+ <p><c>new/0</c></p>
+ </item>
+ <item>
+ <p><c>size/1</c></p>
+ </item>
+ <item>
+ <p><c>subtract/2</c></p>
+ </item>
+ <item>
+ <p><c>to_list/1</c></p>
+ </item>
+ <item>
+ <p><c>union/1</c></p>
+ </item>
+ <item>
+ <p><c>union/2</c></p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+gb_set() = a GB set</code>
+ </section>
+ <funcs>
+ <func>
+ <name>add(Element, Set1) -> Set2</name>
+ <name>add_element(Element, Set1) -> Set2</name>
+ <fsummary>Add a (possibly existing) element to a gb_set</fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns a new gb_set formed from <c>Set1</c> with
+ <c>Element</c> inserted. If <c>Element</c> is already an
+ element in <c>Set1</c>, nothing is changed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>balance(Set1) -> Set2</name>
+ <fsummary>Rebalance tree representation of a gb_set</fsummary>
+ <type>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Rebalances the tree representation of <c>Set1</c>. Note that
+ this is rarely necessary, but may be motivated when a large
+ number of elements have been deleted from the tree without
+ further insertions. Rebalancing could then be forced in order
+ to minimise lookup times, since deletion only does not
+ rebalance the tree.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete(Element, Set1) -> Set2</name>
+ <fsummary>Remove an element from a gb_set</fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns a new gb_set formed from <c>Set1</c> with
+ <c>Element</c> removed. Assumes that <c>Element</c> is present
+ in <c>Set1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_any(Element, Set1) -> Set2</name>
+ <name>del_element(Element, Set1) -> Set2</name>
+ <fsummary>Remove a (possibly non-existing) element from a gb_set</fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns a new gb_set formed from <c>Set1</c> with
+ <c>Element</c> removed. If <c>Element</c> is not an element
+ in <c>Set1</c>, nothing is changed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>difference(Set1, Set2) -> Set3</name>
+ <name>subtract(Set1, Set2) -> Set3</name>
+ <fsummary>Return the difference of two gb_sets</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns only the elements of <c>Set1</c> which are not also
+ elements of <c>Set2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>empty() -> Set</name>
+ <name>new() -> Set</name>
+ <fsummary>Return an empty gb_set</fsummary>
+ <type>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns a new empty gb_set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>filter(Pred, Set1) -> Set2</name>
+ <fsummary>Filter gb_set elements</fsummary>
+ <type>
+ <v>Pred = fun (E) -> bool()</v>
+ <v>&nbsp;E = term()</v>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Filters elements in <c>Set1</c> using predicate function
+ <c>Pred</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fold(Function, Acc0, Set) -> Acc1</name>
+ <fsummary>Fold over gb_set elements</fsummary>
+ <type>
+ <v>Function = fun (E, AccIn) -> AccOut</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>&nbsp;E = term()</v>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Folds <c>Function</c> over every element in <c>Set</c>
+ returning the final value of the accumulator.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_list(List) -> Set</name>
+ <fsummary>Convert a list into a gb_set</fsummary>
+ <type>
+ <v>List = [term()]</v>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns a gb_set of the elements in <c>List</c>, where
+ <c>List</c> may be unordered and contain duplicates.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_ordset(List) -> Set</name>
+ <fsummary>Make a gb_set from an ordset list</fsummary>
+ <type>
+ <v>List = [term()]</v>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Turns an ordered-set list <c>List</c> into a gb_set. The list
+ must not contain duplicates.</p>
+ </desc>
+ </func>
+ <func>
+ <name>insert(Element, Set1) -> Set2</name>
+ <fsummary>Add a new element to a gb_set</fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns a new gb_set formed from <c>Set1</c> with
+ <c>Element</c> inserted. Assumes that <c>Element</c> is not
+ present in <c>Set1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(Set1, Set2) -> Set3</name>
+ <fsummary>Return the intersection of two gb_sets</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns the intersection of <c>Set1</c> and <c>Set2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(SetList) -> Set</name>
+ <fsummary>Return the intersection of a list of gb_sets</fsummary>
+ <type>
+ <v>SetList = [gb_set()]</v>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns the intersection of the non-empty list of gb_sets.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_disjoint(Set1, Set2) -> bool()</name>
+ <fsummary>Check whether two gb_sets are disjoint</fsummary>
+ <type>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Set1</c> and
+ <c>Set2</c> are disjoint (have no elements in common),
+ and <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_empty(Set) -> bool()</name>
+ <fsummary>Test for empty gb_set</fsummary>
+ <type>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Set</c> is an empty set, and
+ <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_member(Element, Set) -> bool()</name>
+ <name>is_element(Element, Set) -> bool()</name>
+ <fsummary>Test for membership of a gb_set</fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Element</c> is an element of
+ <c>Set</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_set(Term) -> bool()</name>
+ <fsummary>Test for a gb_set</fsummary>
+ <type>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Set</c> appears to be a gb_set,
+ otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_subset(Set1, Set2) -> bool()</name>
+ <fsummary>Test for subset</fsummary>
+ <type>
+ <v>Set1 = Set2 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> when every element of <c>Set1</c> is
+ also a member of <c>Set2</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>iterator(Set) -> Iter</name>
+ <fsummary>Return an iterator for a gb_set</fsummary>
+ <type>
+ <v>Set = gb_set()</v>
+ <v>Iter = term()</v>
+ </type>
+ <desc>
+ <p>Returns an iterator that can be used for traversing the
+ entries of <c>Set</c>; see <c>next/1</c>. The implementation
+ of this is very efficient; traversing the whole set using
+ <c>next/1</c> is only slightly slower than getting the list
+ of all elements using <c>to_list/1</c> and traversing that.
+ The main advantage of the iterator approach is that it does
+ not require the complete list of all elements to be built in
+ memory at one time.</p>
+ </desc>
+ </func>
+ <func>
+ <name>largest(Set) -> term()</name>
+ <fsummary>Return largest element</fsummary>
+ <type>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns the largest element in <c>Set</c>. Assumes that
+ <c>Set</c> is nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>next(Iter1) -> {Element, Iter2} | none</name>
+ <fsummary>Traverse a gb_set with an iterator</fsummary>
+ <type>
+ <v>Iter1 = Iter2 = Element = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Element, Iter2}</c> where <c>Element</c> is the
+ smallest element referred to by the iterator <c>Iter1</c>,
+ and <c>Iter2</c> is the new iterator to be used for
+ traversing the remaining elements, or the atom <c>none</c> if
+ no elements remain.</p>
+ </desc>
+ </func>
+ <func>
+ <name>singleton(Element) -> gb_set()</name>
+ <fsummary>Return a gb_set with one element</fsummary>
+ <type>
+ <v>Element = term()</v>
+ </type>
+ <desc>
+ <p>Returns a gb_set containing only the element <c>Element</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>size(Set) -> int()</name>
+ <fsummary>Return the number of elements in a gb_set</fsummary>
+ <type>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns the number of elements in <c>Set</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>smallest(Set) -> term()</name>
+ <fsummary>Return smallest element</fsummary>
+ <type>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns the smallest element in <c>Set</c>. Assumes that
+ <c>Set</c> is nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>take_largest(Set1) -> {Element, Set2}</name>
+ <fsummary>Extract largest element</fsummary>
+ <type>
+ <v>Set1 = Set2 = gb_set()</v>
+ <v>Element = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Element, Set2}</c>, where <c>Element</c> is the
+ largest element in <c>Set1</c>, and <c>Set2</c> is this set
+ with <c>Element</c> deleted. Assumes that <c>Set1</c> is
+ nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>take_smallest(Set1) -> {Element, Set2}</name>
+ <fsummary>Extract smallest element</fsummary>
+ <type>
+ <v>Set1 = Set2 = gb_set()</v>
+ <v>Element = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Element, Set2}</c>, where <c>Element</c> is the
+ smallest element in <c>Set1</c>, and <c>Set2</c> is this set
+ with <c>Element</c> deleted. Assumes that <c>Set1</c> is
+ nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_list(Set) -> List</name>
+ <fsummary>Convert a gb_set into a list</fsummary>
+ <type>
+ <v>Set = gb_set()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the elements of <c>Set</c> as a list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(Set1, Set2) -> Set3</name>
+ <fsummary>Return the union of two gb_sets</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns the merged (union) gb_set of <c>Set1</c> and
+ <c>Set2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(SetList) -> Set</name>
+ <fsummary>Return the union of a list of gb_sets</fsummary>
+ <type>
+ <v>SetList = [gb_set()]</v>
+ <v>Set = gb_set()</v>
+ </type>
+ <desc>
+ <p>Returns the merged (union) gb_set of the list of gb_sets.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="gb_trees">gb_trees(3)</seealso>,
+ <seealso marker="ordsets">ordsets(3)</seealso>,
+ <seealso marker="sets">sets(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/gb_trees.xml b/lib/stdlib/doc/src/gb_trees.xml
new file mode 100644
index 0000000000..2bf18138c0
--- /dev/null
+++ b/lib/stdlib/doc/src/gb_trees.xml
@@ -0,0 +1,367 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2001</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>gb_trees</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>gb_trees</module>
+ <modulesummary>General Balanced Trees</modulesummary>
+ <description>
+ <p>An efficient implementation of Prof. Arne Andersson's General
+ Balanced Trees. These have no storage overhead compared to
+ unbalanced binary trees, and their performance is in general
+ better than AVL trees.</p>
+ </description>
+
+ <section>
+ <title>Data structure</title>
+ <p>Data structure:</p>
+ <code type="none">
+
+- {Size, Tree}, where `Tree' is composed of nodes of the form:
+ - {Key, Value, Smaller, Bigger}, and the "empty tree" node:
+ - nil.</code>
+ <p>There is no attempt to balance trees after deletions. Since
+ deletions do not increase the height of a tree, this should be OK.</p>
+ <p>Original balance condition <em>h(T) &lt;= ceil(c * log(|T|))</em>
+ has been changed to the similar (but not quite equivalent)
+ condition <em>2 ^ h(T) &lt;= |T| ^ c</em>. This should also be OK.</p>
+ <p>Performance is comparable to the AVL trees in the Erlang book
+ (and faster in general due to less overhead); the difference is
+ that deletion works for these trees, but not for the book's
+ trees. Behaviour is logarithmic (as it should be).</p>
+ </section>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+gb_tree() = a GB tree</code>
+ </section>
+ <funcs>
+ <func>
+ <name>balance(Tree1) -> Tree2</name>
+ <fsummary>Rebalance a tree</fsummary>
+ <type>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Rebalances <c>Tree1</c>. Note that this is rarely necessary,
+ but may be motivated when a large number of nodes have been
+ deleted from the tree without further insertions. Rebalancing
+ could then be forced in order to minimise lookup times, since
+ deletion only does not rebalance the tree.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete(Key, Tree1) -> Tree2</name>
+ <fsummary>Remove a node from a tree</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Removes the node with key <c>Key</c> from <c>Tree1</c>;
+ returns new tree. Assumes that the key is present in the tree,
+ crashes otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_any(Key, Tree1) -> Tree2</name>
+ <fsummary>Remove a (possibly non-existing) node from a tree</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Removes the node with key <c>Key</c> from <c>Tree1</c> if
+ the key is present in the tree, otherwise does nothing;
+ returns new tree.</p>
+ </desc>
+ </func>
+ <func>
+ <name>empty() -> Tree</name>
+ <fsummary>Return an empty tree</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Returns a new empty tree</p>
+ </desc>
+ </func>
+ <func>
+ <name>enter(Key, Val, Tree1) -> Tree2</name>
+ <fsummary>Insert or update key with value in a tree</fsummary>
+ <type>
+ <v>Key = Val = term()</v>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Inserts <c>Key</c> with value <c>Val</c> into <c>Tree1</c> if
+ the key is not present in the tree, otherwise updates
+ <c>Key</c> to value <c>Val</c> in <c>Tree1</c>. Returns the
+ new tree.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_orddict(List) -> Tree</name>
+ <fsummary>Make a tree from an orddict</fsummary>
+ <type>
+ <v>List = [{Key, Val}]</v>
+ <v>&nbsp;Key = Val = term()</v>
+ <v>Tree = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Turns an ordered list <c>List</c> of key-value tuples into a
+ tree. The list must not contain duplicate keys.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get(Key, Tree) -> Val</name>
+ <fsummary>Look up a key in a tree, if present</fsummary>
+ <type>
+ <v>Key = Val = term()</v>
+ <v>Tree = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Retrieves the value stored with <c>Key</c> in <c>Tree</c>.
+ Assumes that the key is present in the tree, crashes
+ otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>lookup(Key, Tree) -> {value, Val} | none</name>
+ <fsummary>Look up a key in a tree</fsummary>
+ <type>
+ <v>Key = Val = term()</v>
+ <v>Tree = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Looks up <c>Key</c> in <c>Tree</c>; returns
+ <c>{value, Val}</c>, or <c>none</c> if <c>Key</c> is not
+ present.</p>
+ </desc>
+ </func>
+ <func>
+ <name>insert(Key, Val, Tree1) -> Tree2</name>
+ <fsummary>Insert a new key and value in a tree</fsummary>
+ <type>
+ <v>Key = Val = term()</v>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Inserts <c>Key</c> with value <c>Val</c> into <c>Tree1</c>;
+ returns the new tree. Assumes that the key is not present in
+ the tree, crashes otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_defined(Key, Tree) -> bool()</name>
+ <fsummary>Test for membership of a tree</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Key</c> is present in <c>Tree</c>,
+ otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_empty(Tree) -> bool()</name>
+ <fsummary>Test for empty tree</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Tree</c> is an empty tree, and
+ <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>iterator(Tree) -> Iter</name>
+ <fsummary>Return an iterator for a tree</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ <v>Iter = term()</v>
+ </type>
+ <desc>
+ <p>Returns an iterator that can be used for traversing the
+ entries of <c>Tree</c>; see <c>next/1</c>. The implementation
+ of this is very efficient; traversing the whole tree using
+ <c>next/1</c> is only slightly slower than getting the list
+ of all elements using <c>to_list/1</c> and traversing that.
+ The main advantage of the iterator approach is that it does
+ not require the complete list of all elements to be built in
+ memory at one time.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keys(Tree) -> [Key]</name>
+ <fsummary>Return a list of the keys in a tree</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ <v>Key = term()</v>
+ </type>
+ <desc>
+ <p>Returns the keys in <c>Tree</c> as an ordered list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>largest(Tree) -> {Key, Val}</name>
+ <fsummary>Return largest key and value</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ <v>Key = Val = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Key, Val}</c>, where <c>Key</c> is the largest
+ key in <c>Tree</c>, and <c>Val</c> is the value associated
+ with this key. Assumes that the tree is nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>map(Function, Tree1) -> Tree2</name>
+ <fsummary>Return largest key and value</fsummary>
+ <type>
+ <v>Function = fun(K, V1) -> V2</v>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ </type>
+ <desc><p>maps the function F(K, V1) -> V2 to all key-value pairs
+ of the tree Tree1 and returns a new tree Tree2 with the same set of keys
+ as Tree1 and the new set of values V2.</p>
+ </desc>
+ </func>
+ <func>
+ <name>next(Iter1) -> {Key, Val, Iter2} | none</name>
+ <fsummary>Traverse a tree with an iterator</fsummary>
+ <type>
+ <v>Iter1 = Iter2 = Key = Val = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Key, Val, Iter2}</c> where <c>Key</c> is the
+ smallest key referred to by the iterator <c>Iter1</c>, and
+ <c>Iter2</c> is the new iterator to be used for
+ traversing the remaining nodes, or the atom <c>none</c> if no
+ nodes remain.</p>
+ </desc>
+ </func>
+ <func>
+ <name>size(Tree) -> int()</name>
+ <fsummary>Return the number of nodes in a tree</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Returns the number of nodes in <c>Tree</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>smallest(Tree) -> {Key, Val}</name>
+ <fsummary>Return smallest key and value</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ <v>Key = Val = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Key, Val}</c>, where <c>Key</c> is the smallest
+ key in <c>Tree</c>, and <c>Val</c> is the value associated
+ with this key. Assumes that the tree is nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>take_largest(Tree1) -> {Key, Val, Tree2}</name>
+ <fsummary>Extract largest key and value</fsummary>
+ <type>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ <v>Key = Val = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Key, Val, Tree2}</c>, where <c>Key</c> is the
+ largest key in <c>Tree1</c>, <c>Val</c> is the value
+ associated with this key, and <c>Tree2</c> is this tree with
+ the corresponding node deleted. Assumes that the tree is
+ nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>take_smallest(Tree1) -> {Key, Val, Tree2}</name>
+ <fsummary>Extract smallest key and value</fsummary>
+ <type>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ <v>Key = Val = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>{Key, Val, Tree2}</c>, where <c>Key</c> is the
+ smallest key in <c>Tree1</c>, <c>Val</c> is the value
+ associated with this key, and <c>Tree2</c> is this tree with
+ the corresponding node deleted. Assumes that the tree is
+ nonempty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_list(Tree) -> [{Key, Val}]</name>
+ <fsummary>Convert a tree into a list</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ <v>Key = Val = term()</v>
+ </type>
+ <desc>
+ <p>Converts a tree into an ordered list of key-value tuples.</p>
+ </desc>
+ </func>
+ <func>
+ <name>update(Key, Val, Tree1) -> Tree2</name>
+ <fsummary>Update a key to new value in a tree</fsummary>
+ <type>
+ <v>Key = Val = term()</v>
+ <v>Tree1 = Tree2 = gb_tree()</v>
+ </type>
+ <desc>
+ <p>Updates <c>Key</c> to value <c>Val</c> in <c>Tree1</c>;
+ returns the new tree. Assumes that the key is present in the
+ tree.</p>
+ </desc>
+ </func>
+ <func>
+ <name>values(Tree) -> [Val]</name>
+ <fsummary>Return a list of the values in a tree</fsummary>
+ <type>
+ <v>Tree = gb_tree()</v>
+ <v>Val = term()</v>
+ </type>
+ <desc>
+ <p>Returns the values in <c>Tree</c> as an ordered list, sorted
+ by their corresponding keys. Duplicates are not removed.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="gb_sets">gb_sets(3)</seealso>,
+ <seealso marker="dict">dict(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml
new file mode 100644
index 0000000000..df09294de6
--- /dev/null
+++ b/lib/stdlib/doc/src/gen_event.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>gen_event</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>gen_event</module>
+ <modulesummary>Generic Event Handling Behaviour</modulesummary>
+ <description>
+ <p>A behaviour module for implementing event handling functionality.
+ The OTP event handling model consists of a generic event manager
+ process with an arbitrary number of event handlers which are added and
+ deleted dynamically.</p>
+ <p>An event manager implemented using this module will have a standard
+ set of interface functions and include functionality for tracing and
+ error reporting. It will also fit into an OTP supervision tree.
+ Refer to <em>OTP Design Principles</em> for more information.</p>
+ <p>Each event handler is implemented as a callback module exporting
+ a pre-defined set of functions. The relationship between the behaviour
+ functions and the callback functions can be illustrated as follows:</p>
+ <pre>
+gen_event module Callback module
+---------------- ---------------
+gen_event:start_link -----> -
+
+gen_event:add_handler
+gen_event:add_sup_handler -----> Module:init/1
+
+gen_event:notify
+gen_event:sync_notify -----> Module:handle_event/2
+
+gen_event:call -----> Module:handle_call/2
+
+- -----> Module:handle_info/2
+
+gen_event:delete_handler -----> Module:terminate/2
+
+gen_event:swap_handler
+gen_event:swap_sup_handler -----> Module1:terminate/2
+ Module2:init/1
+
+gen_event:which_handlers -----> -
+
+gen_event:stop -----> Module:terminate/2
+
+- -----> Module:code_change/3</pre>
+ <p>Since each event handler is one callback module, an event manager
+ will have several callback modules which are added and deleted
+ dynamically. Therefore <c>gen_event</c> is more tolerant of callback
+ module errors than the other behaviours. If a callback function for
+ an installed event handler fails with <c>Reason</c>, or returns a
+ bad value <c>Term</c>, the event manager will not fail. It will delete
+ the event handler by calling the callback function
+ <c>Module:terminate/2</c> (see below), giving as argument
+ <c>{error,{'EXIT',Reason}}</c> or <c>{error,Term}</c>, respectively.
+ No other event handler will be affected.</p>
+ <p>A gen_event process handles system messages as documented in
+ <seealso marker="sys">sys(3)</seealso>. The <c>sys</c> module
+ can be used for debugging an event manager.</p>
+ <p>Note that an event manager <em>does</em> trap exit signals
+ automatically.</p>
+ <p>The gen_event process can go into hibernation
+ (see <seealso marker="erts:erlang#erlang:hibernate/3">erlang(3)</seealso>) if a callback
+ function in a handler module specifies <c>'hibernate'</c> in its return value.
+ This might be useful if the server is expected to be idle for a long
+ time. However this feature should be used with care as hibernation
+ implies at least two garbage collections (when hibernating and
+ shortly after waking up) and is not something you'd want to do
+ between each event handled by a busy event manager.</p>
+
+ <p>It's also worth noting that when multiple event handlers are
+ invoked, it's sufficient that one single event handler returns a
+ <c>'hibernate'</c> request for the whole event manager to go into
+ hibernation.</p>
+
+ <p>Unless otherwise stated, all functions in this module fail if
+ the specified event manager does not exist or if bad arguments are
+ given.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>start_link() -> Result</name>
+ <name>start_link(EventMgrName) -> Result</name>
+ <fsummary>Create a generic event manager process in a supervision tree.</fsummary>
+ <type>
+ <v>EventMgrName = {local,Name} | {global,Name}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>Result = {ok,Pid} | {error,{already_started,Pid}}</v>
+ <v>&nbsp;Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Creates an event manager process as part of a supervision
+ tree. The function should be called, directly or indirectly,
+ by the supervisor. It will, among other things, ensure that
+ the event manager is linked to the supervisor.</p>
+ <p>If <c>EventMgrName={local,Name}</c>, the event manager is
+ registered locally as <c>Name</c> using <c>register/2</c>.
+ If <c>EventMgrName={global,Name}</c>, the event manager is
+ registered globally as <c>Name</c> using
+ <c>global:register_name/2</c>. If no name is provided,
+ the event manager is not registered.</p>
+ <p>If the event manager is successfully created the function
+ returns <c>{ok,Pid}</c>, where <c>Pid</c> is the pid of
+ the event manager. If there already exists a process with
+ the specified <c>EventMgrName</c> the function returns
+ <c>{error,{already_started,Pid}}</c>, where <c>Pid</c> is
+ the pid of that process.</p>
+ </desc>
+ </func>
+ <func>
+ <name>start() -> Result</name>
+ <name>start(EventMgrName) -> Result</name>
+ <fsummary>Create a stand-alone event manager process.</fsummary>
+ <type>
+ <v>EventMgrName = {local,Name} | {global,Name}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>Result = {ok,Pid} | {error,{already_started,Pid}}</v>
+ <v>&nbsp;Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Creates a stand-alone event manager process, i.e. an event
+ manager which is not part of a supervision tree and thus has
+ no supervisor.</p>
+ <p>See <c>start_link/0,1</c> for a description of arguments and
+ return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>add_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <fsummary>Add an event handler to a generic event manager.</fsummary>
+ <type>
+ <v>EventMgr = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Handler = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ <v>Args = term()</v>
+ <v>Result = ok | {'EXIT',Reason} | term()</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Adds a new event handler to the event manager <c>EventMgrRef</c>.
+ The event manager will call <c>Module:init/1</c> to initiate
+ the event handler and its internal state.</p>
+ <p><c>EventMgrRef</c> can be:</p>
+ <list type="bulleted">
+ <item>the pid,</item>
+ <item><c>Name</c>, if the event manager is locally registered,</item>
+ <item><c>{Name,Node}</c>, if the event manager is locally
+ registered at another node, or</item>
+ <item><c>{global,Name}</c>, if the event manager is globally
+ registered.</item>
+ </list>
+ <p><c>Handler</c> is the name of the callback module <c>Module</c> or
+ a tuple <c>{Module,Id}</c>, where <c>Id</c> is any term.
+ The <c>{Module,Id}</c> representation makes it possible to
+ identify a specific event handler when there are several event
+ handlers using the same callback module.</p>
+ <p><c>Args</c> is an arbitrary term which is passed as the argument
+ to <c>Module:init/1</c>.</p>
+ <p>If <c>Module:init/1</c> returns a correct value, the event
+ manager adds the event handler and this function returns
+ <c>ok</c>. If <c>Module:init/1</c> fails with <c>Reason</c> or
+ returns an unexpected value <c>Term</c>, the event handler is
+ ignored and this function returns <c>{'EXIT',Reason}</c> or
+ <c>Term</c>, respectively.</p>
+ </desc>
+ </func>
+ <func>
+ <name>add_sup_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <fsummary>Add a supervised event handler to a generic event manager.</fsummary>
+ <type>
+ <v>EventMgr = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Handler = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ <v>Args = term()</v>
+ <v>Result = ok | {'EXIT',Reason} | term()</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Adds a new event handler in the same way as <c>add_handler/3</c>
+ but will also supervise the connection between the event handler
+ and the calling process.</p>
+ <list type="bulleted">
+ <item>If the calling process later terminates with <c>Reason</c>,
+ the event manager will delete the event handler by calling
+ <c>Module:terminate/2</c> with <c>{stop,Reason}</c> as argument.</item>
+ <item>
+ <p>If the event handler later is deleted, the event manager
+ sends a message<c>{gen_event_EXIT,Handler,Reason}</c> to
+ the calling process. <c>Reason</c> is one of the following:</p>
+ <list type="bulleted">
+ <item><c>normal</c>, if the event handler has been removed due to a
+ call to <c>delete_handler/3</c>, or <c>remove_handler</c>
+ has been returned by a callback function (see below).</item>
+ <item><c>shutdown</c>, if the event handler has been removed
+ because the event manager is terminating.</item>
+ <item><c>{swapped,NewHandler,Pid}</c>, if the process <c>Pid</c>
+ has replaced the event handler with another event handler
+ <c>NewHandler</c> using a call to <c>swap_handler/3</c> or
+ <c>swap_sup_handler/3</c>.</item>
+ <item>a term, if the event handler is removed due to an error.
+ Which term depends on the error.</item>
+ </list>
+ </item>
+ </list>
+ <p>See <c>add_handler/3</c> for a description of the arguments
+ and return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>notify(EventMgrRef, Event) -> ok</name>
+ <name>sync_notify(EventMgrRef, Event) -> ok</name>
+ <fsummary>Notify an event manager about an event.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Event = term()</v>
+ </type>
+ <desc>
+ <p>Sends an event notification to the event manager
+ <c>EventMgrRef</c>. The event manager will call
+ <c>Module:handle_event/2</c> for each installed event handler to
+ handle the event.</p>
+ <p><c>notify</c> is asynchronous and will return immediately after
+ the event notification has been sent. <c>sync_notify</c> is
+ synchronous in the sense that it will return <c>ok</c> after
+ the event has been handled by all event handlers.</p>
+ <p>See <c>add_handler/3</c> for a description of <c>EventMgrRef</c>.</p>
+ <p><c>Event</c> is an arbitrary term which is passed as one of
+ the arguments to <c>Module:handle_event/2</c>.</p>
+ <p><c>notify</c> will not fail even if the specified event manager
+ does not exist, unless it is specified as <c>Name</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>call(EventMgrRef, Handler, Request) -> Result</name>
+ <name>call(EventMgrRef, Handler, Request, Timeout) -> Result</name>
+ <fsummary>Make a synchronous call to a generic event manager.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Handler = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ <v>Request = term()</v>
+ <v>Timeout = int()>0 | infinity</v>
+ <v>Result = Reply | {error,Error}</v>
+ <v>&nbsp;Reply = term()</v>
+ <v>&nbsp;Error = bad_module | {'EXIT',Reason} | term()</v>
+ <v>&nbsp;&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Makes a synchronous call to the event handler <c>Handler</c>
+ installed in the event manager <c>EventMgrRef</c> by sending a
+ request and waiting until a reply arrives or a timeout occurs.
+ The event manager will call <c>Module:handle_call/2</c> to handle
+ the request.</p>
+ <p>See <c>add_handler/3</c> for a description of <c>EventMgrRef</c>
+ and <c>Handler</c>.</p>
+ <p><c>Request</c> is an arbitrary term which is passed as one of
+ the arguments to <c>Module:handle_call/2</c>.</p>
+ <p><c>Timeout</c> is an integer greater than zero which specifies
+ how many milliseconds to wait for a reply, or the atom
+ <c>infinity</c> to wait indefinitely. Default value is 5000.
+ If no reply is received within the specified time, the function
+ call fails.</p>
+ <p>The return value <c>Reply</c> is defined in the return value of
+ <c>Module:handle_call/2</c>. If the specified event handler is not
+ installed, the function returns <c>{error,bad_module}</c>. If
+ the callback function fails with <c>Reason</c> or returns an
+ unexpected value <c>Term</c>, this function returns
+ <c>{error,{'EXIT',Reason}}</c> or <c>{error,Term}</c>,
+ respectively.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <fsummary>Delete an event handler from a generic event manager.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Handler = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ <v>Args = term()</v>
+ <v>Result = term() | {error,module_not_found} | {'EXIT',Reason}</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Deletes an event handler from the event manager
+ <c>EventMgrRef</c>. The event manager will call
+ <c>Module:terminate/2</c> to terminate the event handler.</p>
+ <p>See <c>add_handler/3</c> for a description of <c>EventMgrRef</c>
+ and <c>Handler</c>.</p>
+ <p><c>Args</c> is an arbitrary term which is passed as one of
+ the arguments to <c>Module:terminate/2</c>.</p>
+ <p>The return value is the return value of <c>Module:terminate/2</c>.
+ If the specified event handler is not installed, the function
+ returns <c>{error,module_not_found}</c>. If the callback function
+ fails with <c>Reason</c>, the function returns
+ <c>{'EXIT',Reason}</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
+ <fsummary>Replace an event handler in a generic event manager.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Handler1 = Handler2 = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ <v>Args1 = Args2 = term()</v>
+ <v>Result = ok | {error,Error}</v>
+ <v>&nbsp;Error = {'EXIT',Reason} | term()</v>
+ <v>&nbsp;&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Replaces an old event handler with a new event handler in
+ the event manager <c>EventMgrRef</c>.</p>
+ <p>See <c>add_handler/3</c> for a description of the arguments.</p>
+ <p>First the old event handler <c>Handler1</c> is deleted.
+ The event manager calls <c>Module1:terminate(Args1, ...)</c>,
+ where <c>Module1</c> is the callback module of <c>Handler1</c>,
+ and collects the return value.</p>
+ <p>Then the new event handler <c>Handler2</c> is added and initiated
+ by calling <c>Module2:init({Args2,Term})</c>, where <c>Module2</c>
+ is the callback module of <c>Handler2</c> and <c>Term</c>
+ the return value of <c>Module1:terminate/2</c>. This makes it
+ possible to transfer information from <c>Handler1</c> to
+ <c>Handler2</c>.</p>
+ <p>The new handler will be added even if the the specified old event
+ handler is not installed in which case <c>Term=error</c>, or if
+ <c>Module1:terminate/2</c> fails with <c>Reason</c> in which case
+ <c>Term={'EXIT',Reason}</c>.
+ The old handler will be deleted even if <c>Module2:init/1</c>
+ fails.</p>
+ <p>If there was a supervised connection between <c>Handler1</c> and
+ a process <c>Pid</c>, there will be a supervised connection
+ between <c>Handler2</c> and <c>Pid</c> instead.</p>
+ <p>If <c>Module2:init/1</c> returns a correct value, this function
+ returns <c>ok</c>. If <c>Module2:init/1</c> fails with
+ <c>Reason</c> or returns an unexpected value <c>Term</c>, this
+ this function returns <c>{error,{'EXIT',Reason}}</c> or
+ <c>{error,Term}</c>, respectively.</p>
+ </desc>
+ </func>
+ <func>
+ <name>swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
+ <fsummary>Replace an event handler in a generic event manager.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Handler1 = Handler 2 = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ <v>Args1 = Args2 = term()</v>
+ <v>Result = ok | {error,Error}</v>
+ <v>&nbsp;Error = {'EXIT',Reason} | term()</v>
+ <v>&nbsp;&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Replaces an event handler in the event manager <c>EventMgrRef</c>
+ in the same way as <c>swap_handler/3</c> but will also supervise
+ the connection between <c>Handler2</c> and the calling process.</p>
+ <p>See <c>swap_handler/3</c> for a description of the arguments
+ and return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>which_handlers(EventMgrRef) -> [Handler]</name>
+ <fsummary>Return all event handlers installed in a generic event manager.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Handler = Module | {Module,Id}</v>
+ <v>&nbsp;Module = atom()</v>
+ <v>&nbsp;Id = term()</v>
+ </type>
+ <desc>
+ <p>Returns a list of all event handlers installed in the event
+ manager <c>EventMgrRef</c>.</p>
+ <p>See <c>add_handler/3</c> for a description of <c>EventMgrRef</c>
+ and <c>Handler</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>stop(EventMgrRef) -> ok</name>
+ <fsummary>Terminate a generic event manager.</fsummary>
+ <type>
+ <v>EventMgrRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>Name = Node = atom()</v>
+ </type>
+ <desc>
+ <p>Terminates the event manager <c>EventMgrRef</c>. Before
+ terminating, the event manager will call
+ <c>Module:terminate(stop,...)</c> for each installed event
+ handler.</p>
+ <p>See <c>add_handler/3</c> for a description of the argument.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>The following functions should be exported from a <c>gen_event</c>
+ callback module.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate}</name>
+ <fsummary>Initialize an event handler.</fsummary>
+ <type>
+ <v>InitArgs = Args | {Args,Term}</v>
+ <v>&nbsp;Args = Term = term()</v>
+ <v>State = term()</v>
+ </type>
+ <desc>
+ <p>Whenever a new event handler is added to an event manager,
+ this function is called to initialize the event handler.</p>
+ <p>If the event handler is added due to a call to
+ <c>gen_event:add_handler/3</c> or
+ <c>gen_event:add_sup_handler/3</c>, <c>InitArgs</c> is
+ the <c>Args</c> argument of these functions.</p>
+ <p>If the event handler is replacing another event handler due to
+ a call to <c>gen_event:swap_handler/3</c> or
+ <c>gen_event:swap_sup_handler/3</c>, or due to a <c>swap</c>
+ return tuple from one of the other callback functions,
+ <c>InitArgs</c> is a tuple <c>{Args,Term}</c> where <c>Args</c> is
+ the argument provided in the function call/return tuple and
+ <c>Term</c> is the result of terminating the old event handler,
+ see <c>gen_event:swap_handler/3</c>.</p>
+ <p>The function should return <c>{ok,State}</c> or <c>{ok,State, hibernate}</c>
+ where <c>State</c> is the initial internal state of the event handler.</p>
+ <p>If <c>{ok,State,hibernate}</c> is returned, the event
+ manager will go into hibernation (by calling <seealso
+ marker="proc_lib#hibernate/3">proc_lib:hibernate/3</seealso>),
+ waiting for the next event to occur.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_event(Event, State) -> Result</name>
+ <fsummary>Handle an event.</fsummary>
+ <type>
+ <v>Event = term()</v>
+ <v>State = term()</v>
+ <v>Result = {ok,NewState} | {ok,NewState,hibernate} </v>
+ <v>&nbsp;&nbsp;| {swap_handler,Args1,NewState,Handler2,Args2} | remove_handler</v>
+ <v>&nbsp;NewState = term()</v>
+ <v>&nbsp;Args1 = Args2 = term()</v>
+ <v>&nbsp;Handler2 = Module2 | {Module2,Id}</v>
+ <v>&nbsp;&nbsp;Module2 = atom()</v>
+ <v>&nbsp;&nbsp;Id = term()</v>
+ </type>
+ <desc>
+ <p>Whenever an event manager receives an event sent using
+ <c>gen_event:notify/2</c> or <c>gen_event:sync_notify/2</c>, this
+ function is called for each installed event handler to handle
+ the event.</p>
+ <p><c>Event</c> is the <c>Event</c> argument of
+ <c>notify</c>/<c>sync_notify</c>.</p>
+ <p><c>State</c> is the internal state of the event handler.</p>
+ <p>If the function returns <c>{ok,NewState}</c> or <c>{ok,NewState,hibernate}</c>
+ the event handler
+ will remain in the event manager with the possible updated
+ internal state <c>NewState</c>.</p>
+ <p>If <c>{ok,NewState,hibernate}</c> is returned, the event
+ manager will also go into hibernation (by calling <seealso
+ marker="proc_lib#hibernate/3">proc_lib:hibernate/3</seealso>),
+ waiting for the next event to occur. It is sufficient that one of the event
+ handlers return <c>{ok,NewState,hibernate}</c> for the whole event manager
+ process to hibernate.</p>
+ <p>If the function returns
+ <c>{swap_handler,Args1,NewState,Handler2,Args2}</c> the event
+ handler will be replaced by <c>Handler2</c> by first calling
+ <c>Module:terminate(Args1,NewState)</c> and then
+ <c>Module2:init({Args2,Term})</c> where <c>Term</c> is the return
+ value of <c>Module:terminate/2</c>.
+ See <c>gen_event:swap_handler/3</c> for more information.</p>
+ <p>If the function returns <c>remove_handler</c> the event handler
+ will be deleted by calling
+ <c>Module:terminate(remove_handler,State)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_call(Request, State) -> Result</name>
+ <fsummary>Handle a synchronous request.</fsummary>
+ <type>
+ <v>Request = term()</v>
+ <v>State = term()</v>
+ <v>Result = {ok,Reply,NewState} | {ok,Reply,NewState,hibernate}</v>
+ <v>&nbsp;| {swap_handler,Reply,Args1,NewState,Handler2,Args2}</v>
+ <v>&nbsp;| {remove_handler, Reply}</v>
+ <v>&nbsp;Reply = term()</v>
+ <v>&nbsp;NewState = term()</v>
+ <v>&nbsp;Args1 = Args2 = term()</v>
+ <v>&nbsp;Handler2 = Module2 | {Module2,Id}</v>
+ <v>&nbsp;&nbsp;Module2 = atom()</v>
+ <v>&nbsp;&nbsp;Id = term()</v>
+ </type>
+ <desc>
+ <p>Whenever an event manager receives a request sent using
+ <c>gen_event:call/3,4</c>, this function is called for
+ the specified event handler to handle the request.</p>
+ <p><c>Request</c> is the <c>Request</c> argument of <c>call</c>.</p>
+ <p><c>State</c> is the internal state of the event handler.</p>
+ <p>The return values are the same as for <c>handle_event/2</c>
+ except they also contain a term <c>Reply</c> which is the reply
+ given back to the client as the return value of <c>call</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_info(Info, State) -> Result</name>
+ <fsummary>Handle an incoming message.</fsummary>
+ <type>
+ <v>Info = term()</v>
+ <v>State = term()</v>
+ <v>Result = {ok,NewState} | {ok,NewState,hibernate}</v>
+ <v>&nbsp;| {swap_handler,Args1,NewState,Handler2,Args2} | remove_handler</v>
+ <v>&nbsp;NewState = term()</v>
+ <v>&nbsp;Args1 = Args2 = term()</v>
+ <v>&nbsp;Handler2 = Module2 | {Module2,Id}</v>
+ <v>&nbsp;&nbsp;Module2 = atom()</v>
+ <v>&nbsp;&nbsp;Id = term()</v>
+ </type>
+ <desc>
+ <p>This function is called for each installed event handler when
+ an event manager receives any other message than an event or
+ a synchronous request (or a system message).</p>
+ <p><c>Info</c> is the received message.</p>
+ <p>See <c>Module:handle_event/2</c> for a description of State
+ and possible return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:terminate(Arg, State) -> term()</name>
+ <fsummary>Clean up before deletion.</fsummary>
+ <type>
+ <v>Arg = Args | {stop,Reason} | stop | remove_handler</v>
+ <v>&nbsp;| {error,{'EXIT',Reason}} | {error,Term}</v>
+ <v>&nbsp;Args = Reason = Term = term()</v>
+ </type>
+ <desc>
+ <p>Whenever an event handler is deleted from an event manager,
+ this function is called. It should be the opposite of
+ <c>Module:init/1</c> and do any necessary cleaning up.</p>
+ <p>If the event handler is deleted due to a call to
+ <c>gen_event:delete_handler</c>, <c>gen_event:swap_handler/3</c>
+ or <c>gen_event:swap_sup_handler/3</c>, <c>Arg</c> is
+ the <c>Args</c> argument of this function call.</p>
+ <p><c>Arg={stop,Reason}</c> if the event handler has a supervised
+ connection to a process which has terminated with reason
+ <c>Reason</c>.</p>
+ <p><c>Arg=stop</c> if the event handler is deleted because
+ the event manager is terminating.</p>
+ <p>The event manager will terminate if it is part of a supervision
+ tree and it is ordered by its supervisor to terminate.
+ Even if it is <em>not</em> part of a supervision tree, it will
+ terminate if it receives an <c>'EXIT'</c> message from
+ its parent.</p>
+ <p><c>Arg=remove_handler</c> if the event handler is deleted because
+ another callback function has returned <c>remove_handler</c> or
+ <c>{remove_handler,Reply}</c>.</p>
+ <p><c>Arg={error,Term}</c> if the event handler is deleted because
+ a callback function returned an unexpected value <c>Term</c>,
+ or <c>Arg={error,{'EXIT',Reason}}</c> if a callback function
+ failed.</p>
+ <p><c>State</c> is the internal state of the event handler.</p>
+ <p>The function may return any term. If the event handler is
+ deleted due to a call to <c>gen_event:delete_handler</c>,
+ the return value of that function will be the return value of this
+ function. If the event handler is to be replaced with another event
+ handler due to a swap, the return value will be passed to
+ the <c>init</c> function of the new event handler. Otherwise
+ the return value is ignored.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name>
+ <fsummary>Update the internal state during upgrade/downgrade.</fsummary>
+ <type>
+ <v>OldVsn = Vsn | {down, Vsn}</v>
+ <v>&nbsp;&nbsp;Vsn = term()</v>
+ <v>State = NewState = term()</v>
+ <v>Extra = term()</v>
+ </type>
+ <desc>
+ <p>This function is called for an installed event handler which
+ should update its internal state during a release
+ upgrade/downgrade, i.e. when the instruction
+ <c>{update,Module,Change,...}</c> where
+ <c>Change={advanced,Extra}</c> is given in the <c>.appup</c>
+ file. See <em>OTP Design Principles</em> for more
+ information.</p>
+ <p>In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
+ in the case of a downgrade, <c>OldVsn</c> is
+ <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c>
+ attribute(s) of the old version of the callback module
+ <c>Module</c>. If no such attribute is defined, the version
+ is the checksum of the BEAM file.</p>
+ <p><c>State</c> is the internal state of the event handler.</p>
+ <p><c>Extra</c> is passed as-is from the <c>{advanced,Extra}</c>
+ part of the update instruction.</p>
+ <p>The function should return the updated internal state.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="supervisor">supervisor(3)</seealso>,
+ <seealso marker="sys">sys(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml
new file mode 100644
index 0000000000..f5d8b9bb48
--- /dev/null
+++ b/lib/stdlib/doc/src/gen_fsm.xml
@@ -0,0 +1,743 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>gen_fsm</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>gen_fsm</module>
+ <modulesummary>Generic Finite State Machine Behaviour</modulesummary>
+ <description>
+ <p>A behaviour module for implementing a finite state machine.
+ A generic finite state machine process (gen_fsm) implemented
+ using this module will have a standard set of interface functions
+ and include functionality for tracing and error reporting. It will
+ also fit into an OTP supervision tree. Refer to
+ <seealso marker="doc/design_principles:fsm">OTP Design Principles</seealso> for more information.</p>
+ <p>A gen_fsm assumes all specific parts to be located in a callback
+ module exporting a pre-defined set of functions. The relationship
+ between the behaviour functions and the callback functions can be
+ illustrated as follows:</p>
+ <pre>
+gen_fsm module Callback module
+-------------- ---------------
+gen_fsm:start_link -----> Module:init/1
+
+gen_fsm:send_event -----> Module:StateName/2
+
+gen_fsm:send_all_state_event -----> Module:handle_event/3
+
+gen_fsm:sync_send_event -----> Module:StateName/3
+
+gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4
+
+- -----> Module:handle_info/3
+
+- -----> Module:terminate/3
+
+- -----> Module:code_change/4</pre>
+ <p>If a callback function fails or returns a bad value, the gen_fsm
+ will terminate.</p>
+ <p>A gen_fsm handles system messages as documented in
+ <seealso marker="sys">sys(3)</seealso>. The <c>sys</c> module
+ can be used for debugging a gen_fsm.</p>
+ <p>Note that a gen_fsm does not trap exit signals automatically,
+ this must be explicitly initiated in the callback module.</p>
+ <p>Unless otherwise stated, all functions in this module fail if
+ the specified gen_fsm does not exist or if bad arguments are
+ given.</p>
+ <p>The gen_fsm process can go into hibernation
+ (see <seealso marker="erts:erlang#erlang:hibernate/3">erlang(3)</seealso>) if a callback
+ function specifies <c>'hibernate'</c> instead of a timeout value. This
+ might be useful if the server is expected to be idle for a long
+ time. However this feature should be used with care as hibernation
+ implies at least two garbage collections (when hibernating and
+ shortly after waking up) and is not something you'd want to do
+ between each call to a busy state machine.</p>
+
+ </description>
+ <funcs>
+ <func>
+ <name>start_link(Module, Args, Options) -> Result</name>
+ <name>start_link(FsmName, Module, Args, Options) -> Result</name>
+ <fsummary>Create a gen_fsm process in a supervision tree.</fsummary>
+ <type>
+ <v>FsmName = {local,Name} | {global,GlobalName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Module = atom()</v>
+ <v>Args = term()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;| {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>&nbsp;&nbsp;SOpts = [SOpt]</v>
+ <v>&nbsp;&nbsp;&nbsp;SOpt - see erlang:spawn_opt/2,3,4,5</v>
+ <v>Result = {ok,Pid} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;Error = {already_started,Pid} | term()</v>
+ </type>
+ <desc>
+ <p>Creates a gen_fsm process as part of a supervision tree.
+ The function should be called, directly or indirectly, by
+ the supervisor. It will, among other things, ensure that
+ the gen_fsm is linked to the supervisor.</p>
+ <p>The gen_fsm process calls <c>Module:init/1</c> to
+ initialize. To ensure a synchronized start-up procedure,
+ <c>start_link/3,4</c> does not return until
+ <c>Module:init/1</c> has returned.</p>
+ <p>If <c>FsmName={local,Name}</c>, the gen_fsm is registered
+ locally as <c>Name</c> using <c>register/2</c>.
+ If <c>FsmName={global,GlobalName}</c>, the gen_fsm is
+ registered globally as <c>GlobalName</c> using
+ <c>global:register_name/2</c>. If no name is provided,
+ the gen_fsm is not registered.</p>
+ <p><c>Module</c> is the name of the callback module.</p>
+ <p><c>Args</c> is an arbitrary term which is passed as
+ the argument to <c>Module:init/1</c>.</p>
+ <p>If the option <c>{timeout,Time}</c> is present, the gen_fsm
+ is allowed to spend <c>Time</c> milliseconds initializing
+ or it will be terminated and the start function will return
+ <c>{error,timeout}</c>.</p>
+ <p>If the option <c>{debug,Dbgs}</c> is present,
+ the corresponding <c>sys</c> function will be called for each
+ item in <c>Dbgs</c>. See
+ <seealso marker="sys">sys(3)</seealso>.</p>
+ <p>If the option <c>{spawn_opt,SOpts}</c> is present,
+ <c>SOpts</c> will be passed as option list to
+ the <c>spawn_opt</c> BIF which is used to spawn the gen_fsm
+ process. See
+ <seealso marker="erts:erlang#spawn_opt/2">erlang(3)</seealso>.</p>
+ <note>
+ <p>Using the spawn option <c>monitor</c> is currently not
+ allowed, but will cause the function to fail with reason
+ <c>badarg</c>.</p>
+ </note>
+ <p>If the gen_fsm is successfully created and initialized
+ the function returns <c>{ok,Pid}</c>, where <c>Pid</c> is
+ the pid of the gen_fsm. If there already exists a process with
+ the specified <c>FsmName</c>, the function returns
+ <c>{error,{already_started,Pid}}</c> where <c>Pid</c> is
+ the pid of that process.</p>
+ <p>If <c>Module:init/1</c> fails with <c>Reason</c>,
+ the function returns <c>{error,Reason}</c>. If
+ <c>Module:init/1</c> returns <c>{stop,Reason}</c> or
+ <c>ignore</c>, the process is terminated and the function
+ returns <c>{error,Reason}</c> or <c>ignore</c>, respectively.</p>
+ </desc>
+ </func>
+ <func>
+ <name>start(Module, Args, Options) -> Result</name>
+ <name>start(FsmName, Module, Args, Options) -> Result</name>
+ <fsummary>Create a stand-alone gen_fsm process.</fsummary>
+ <type>
+ <v>FsmName = {local,Name} | {global,GlobalName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Module = atom()</v>
+ <v>Args = term()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;| {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>&nbsp;&nbsp;SOpts = [term()]</v>
+ <v>Result = {ok,Pid} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;Error = {already_started,Pid} | term()</v>
+ </type>
+ <desc>
+ <p>Creates a stand-alone gen_fsm process, i.e. a gen_fsm which
+ is not part of a supervision tree and thus has no supervisor.</p>
+ <p>See <seealso marker="#start_link/3">start_link/3,4</seealso>
+ for a description of arguments and return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send_event(FsmRef, Event) -> ok</name>
+ <fsummary>Send an event asynchronously to a generic FSM.</fsummary>
+ <type>
+ <v>FsmRef = Name | {Name,Node} | {global,GlobalName} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Event = term()</v>
+ </type>
+ <desc>
+ <p>Sends an event asynchronously to the gen_fsm <c>FsmRef</c>
+ and returns <c>ok</c> immediately. The gen_fsm will call
+ <c>Module:StateName/2</c> to handle the event, where
+ <c>StateName</c> is the name of the current state of
+ the gen_fsm.</p>
+ <p><c>FsmRef</c> can be:</p>
+ <list type="bulleted">
+ <item>the pid,</item>
+ <item><c>Name</c>, if the gen_fsm is locally registered,</item>
+ <item><c>{Name,Node}</c>, if the gen_fsm is locally
+ registered at another node, or</item>
+ <item><c>{global,GlobalName}</c>, if the gen_fsm is globally
+ registered.</item>
+ </list>
+ <p><c>Event</c> is an arbitrary term which is passed as one of
+ the arguments to <c>Module:StateName/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send_all_state_event(FsmRef, Event) -> ok</name>
+ <fsummary>Send an event asynchronously to a generic FSM.</fsummary>
+ <type>
+ <v>FsmRef = Name | {Name,Node} | {global,GlobalName} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Event = term()</v>
+ </type>
+ <desc>
+ <p>Sends an event asynchronously to the gen_fsm <c>FsmRef</c>
+ and returns <c>ok</c> immediately. The gen_fsm will call
+ <c>Module:handle_event/3</c> to handle the event.</p>
+ <p>See <seealso marker="#send_event/2">send_event/2</seealso>
+ for a description of the arguments.</p>
+ <p>The difference between <c>send_event</c> and
+ <c>send_all_state_event</c> is which callback function is
+ used to handle the event. This function is useful when
+ sending events that are handled the same way in every state,
+ as only one <c>handle_event</c> clause is needed to handle
+ the event instead of one clause in each state name function.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sync_send_event(FsmRef, Event) -> Reply</name>
+ <name>sync_send_event(FsmRef, Event, Timeout) -> Reply</name>
+ <fsummary>Send an event synchronously to a generic FSM.</fsummary>
+ <type>
+ <v>FsmRef = Name | {Name,Node} | {global,GlobalName} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Event = term()</v>
+ <v>Timeout = int()>0 | infinity</v>
+ <v>Reply = term()</v>
+ </type>
+ <desc>
+ <p>Sends an event to the gen_fsm <c>FsmRef</c> and waits until a
+ reply arrives or a timeout occurs. The gen_fsm will call
+ <c>Module:StateName/3</c> to handle the event, where
+ <c>StateName</c> is the name of the current state of
+ the gen_fsm.</p>
+ <p>See <seealso marker="#send_event/2">send_event/2</seealso>
+ for a description of <c>FsmRef</c> and <c>Event</c>.</p>
+ <p><c>Timeout</c> is an integer greater than zero which
+ specifies how many milliseconds to wait for a reply, or
+ the atom <c>infinity</c> to wait indefinitely. Default value
+ is 5000. If no reply is received within the specified time,
+ the function call fails.</p>
+ <p>The return value <c>Reply</c> is defined in the return value
+ of <c>Module:StateName/3</c>.</p>
+ <p>The ancient behaviour of sometimes consuming the server
+ exit message if the server died during the call while
+ linked to the client has been removed in OTP R12B/Erlang 5.6.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sync_send_all_state_event(FsmRef, Event) -> Reply</name>
+ <name>sync_send_all_state_event(FsmRef, Event, Timeout) -> Reply</name>
+ <fsummary>Send an event synchronously to a generic FSM.</fsummary>
+ <type>
+ <v>FsmRef = Name | {Name,Node} | {global,GlobalName} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Event = term()</v>
+ <v>Timeout = int()>0 | infinity</v>
+ <v>Reply = term()</v>
+ </type>
+ <desc>
+ <p>Sends an event to the gen_fsm <c>FsmRef</c> and waits until a
+ reply arrives or a timeout occurs. The gen_fsm will call
+ <c>Module:handle_sync_event/4</c> to handle the event.</p>
+ <p>See <seealso marker="#send_event/2">send_event/2</seealso>
+ for a description of <c>FsmRef</c> and <c>Event</c>. See
+ <seealso marker="#sync_send_event/3">sync_send_event/3</seealso>
+ for a description of <c>Timeout</c> and <c>Reply</c>.</p>
+ <p>See
+ <seealso marker="#send_all_state_event/2">send_all_state_event/2</seealso>
+ for a discussion about the difference between
+ <c>sync_send_event</c> and <c>sync_send_all_state_event</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>reply(Caller, Reply) -> true</name>
+ <fsummary>Send a reply to a caller.</fsummary>
+ <type>
+ <v>Caller - see below</v>
+ <v>Reply = term()</v>
+ </type>
+ <desc>
+ <p>This function can be used by a gen_fsm to explicitly send a
+ reply to a client process that called
+ <seealso marker="#sync_send_event/2">sync_send_event/2,3</seealso>
+ or
+ <seealso marker="#sync_send_all_state_event/2">sync_send_all_state_event/2,3</seealso>,
+ when the reply cannot be defined in the return value of
+ <c>Module:State/3</c> or <c>Module:handle_sync_event/4</c>.</p>
+ <p><c>Caller</c> must be the <c>From</c> argument provided to
+ the callback function. <c>Reply</c> is an arbitrary term,
+ which will be given back to the client as the return value of
+ <c>sync_send_event/2,3</c> or
+ <c>sync_send_all_state_event/2,3</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send_event_after(Time, Event) -> Ref</name>
+ <fsummary>Send a delayed event internally in a generic FSM.</fsummary>
+ <type>
+ <v>Time = integer()</v>
+ <v>Event = term()</v>
+ <v>Ref = reference()</v>
+ </type>
+ <desc>
+ <p>Sends a delayed event internally in the gen_fsm that calls
+ this function after <c>Time</c> ms. Returns immediately a
+ reference that can be used to cancel the delayed send using
+ <seealso marker="#cancel_timer/1">cancel_timer/1</seealso>.</p>
+ <p>The gen_fsm will call <c>Module:StateName/2</c> to handle
+ the event, where <c>StateName</c> is the name of the current
+ state of the gen_fsm at the time the delayed event is
+ delivered.</p>
+ <p><c>Event</c> is an arbitrary term which is passed as one of
+ the arguments to <c>Module:StateName/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>start_timer(Time, Msg) -> Ref</name>
+ <fsummary>Send a timeout event internally in a generic FSM.</fsummary>
+ <type>
+ <v>Time = integer()</v>
+ <v>Msg = term()</v>
+ <v>Ref = reference()</v>
+ </type>
+ <desc>
+ <p>Sends a timeout event internally in the gen_fsm that calls
+ this function after <c>Time</c> ms. Returns immediately a
+ reference that can be used to cancel the timer using
+ <seealso marker="#cancel_timer/1">cancel_timer/1</seealso>.</p>
+ <p>The gen_fsm will call <c>Module:StateName/2</c> to handle
+ the event, where <c>StateName</c> is the name of the current
+ state of the gen_fsm at the time the timeout message is
+ delivered.</p>
+ <p><c>Msg</c> is an arbitrary term which is passed in the
+ timeout message, <c>{timeout, Ref, Msg}</c>, as one of
+ the arguments to <c>Module:StateName/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>cancel_timer(Ref) -> RemainingTime | false</name>
+ <fsummary>Cancel an internal timer in a generic FSM.</fsummary>
+ <type>
+ <v>Ref = reference()</v>
+ <v>RemainingTime = integer()</v>
+ </type>
+ <desc>
+ <p>Cancels an internal timer referred by <c>Ref</c> in the
+ gen_fsm that calls this function.</p>
+ <p><c>Ref</c> is a reference returned from
+ <seealso marker="#send_event_after/2">send_event_after/2</seealso>
+ or
+ <seealso marker="#start_timer/2">start_timer/2</seealso>.</p>
+ <p>If the timer has already timed out, but the event not yet
+ been delivered, it is cancelled as if it had <em>not</em>
+ timed out, so there will be no false timer event after
+ returning from this function.</p>
+ <p>Returns the remaining time in ms until the timer would
+ have expired if <c>Ref</c> referred to an active timer,
+ <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>enter_loop(Module, Options, StateName, StateData)</name>
+ <name>enter_loop(Module, Options, StateName, StateData, FsmName)</name>
+ <name>enter_loop(Module, Options, StateName, StateData, Timeout)</name>
+ <name>enter_loop(Module, Options, StateName, StateData, FsmName, Timeout)</name>
+ <fsummary>Enter the gen_fsm receive loop</fsummary>
+ <type>
+ <v>Module = atom()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;| {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>StateName = atom()</v>
+ <v>StateData = term()</v>
+ <v>FsmName = {local,Name} | {global,GlobalName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Timeout = int() | infinity</v>
+ </type>
+ <desc>
+ <p>Makes an existing process into a gen_fsm. Does not return,
+ instead the calling process will enter the gen_fsm receive
+ loop and become a gen_fsm process. The process <em>must</em>
+ have been started using one of the start functions in
+ <c>proc_lib</c>, see
+ <seealso marker="proc_lib">proc_lib(3)</seealso>. The user is
+ responsible for any initialization of the process, including
+ registering a name for it.</p>
+ <p>This function is useful when a more complex initialization
+ procedure is needed than the gen_fsm behaviour provides.</p>
+ <p><c>Module</c>, <c>Options</c> and <c>FsmName</c> have
+ the same meanings as when calling
+ <seealso marker="#start_link/3">start[_link]/3,4</seealso>.
+ However, if <c>FsmName</c> is specified, the process must have
+ been registered accordingly <em>before</em> this function is
+ called.</p>
+ <p><c>StateName</c>, <c>StateData</c> and <c>Timeout</c> have
+ the same meanings as in the return value of
+ <seealso marker="#Moduleinit">Module:init/1</seealso>.
+ Also, the callback module <c>Module</c> does not need to
+ export an <c>init/1</c> function.</p>
+ <p>Failure: If the calling process was not started by a
+ <c>proc_lib</c> start function, or if it is not registered
+ according to <c>FsmName</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>The following functions should be exported from a <c>gen_fsm</c>
+ callback module.</p>
+ <p>In the description, the expression <em>state name</em> is used to
+ denote a state of the state machine. <em>state data</em> is used
+ to denote the internal state of the Erlang process which
+ implements the state machine.</p>
+ <p></p>
+ </section>
+ <funcs>
+ <func>
+ <name>Module:init(Args) -> Result</name>
+ <fsummary>Initialize process and internal state name and state data.</fsummary>
+ <type>
+ <v>Args = term()</v>
+ <v>Return = {ok,StateName,StateData} | {ok,StateName,StateData,Timeout}</v>
+ <v>&nbsp;&nbsp;| {ok,StateName,StateData,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason} | ignore</v>
+ <v>&nbsp;StateName = atom()</v>
+ <v>&nbsp;StateData = term()</v>
+ <v>&nbsp;Timeout = int()>0 | infinity</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <marker id="Moduleinit"></marker>
+ <p>Whenever a gen_fsm is started using
+ <seealso marker="#start/3">gen_fsm:start/3,4</seealso> or
+ <seealso marker="#start_link/3">gen_fsm:start_link/3,4</seealso>,
+ this function is called by the new process to initialize.</p>
+ <p><c>Args</c> is the <c>Args</c> argument provided to the start
+ function.</p>
+ <p>If initialization is successful, the function should return
+ <c>{ok,StateName,StateData}</c>,
+ <c>{ok,StateName,StateData,Timeout}</c> or <c>{ok,StateName,StateData,hibernate}</c>,
+ where <c>StateName</c>
+ is the initial state name and <c>StateData</c> the initial
+ state data of the gen_fsm.</p>
+ <p>If an integer timeout value is provided, a timeout will occur
+ unless an event or a message is received within <c>Timeout</c>
+ milliseconds. A timeout is represented by the atom
+ <c>timeout</c> and should be handled by
+ the <c>Module:StateName/2</c> callback functions. The atom
+ <c>infinity</c> can be used to wait indefinitely, this is
+ the default value.</p>
+ <p>If <c>hibernate</c> is specified instead of a timeout value, the process will go
+ into hibernation when waiting for the next message to arrive (by calling
+ <seealso marker="proc_lib#hibernate/3">proc_lib:hibernate/3</seealso>).</p>
+ <p>If something goes wrong during the initialization
+ the function should return <c>{stop,Reason}</c>, where
+ <c>Reason</c> is any term, or <c>ignore</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:StateName(Event, StateData) -> Result</name>
+ <fsummary>Handle an asynchronous event.</fsummary>
+ <type>
+ <v>Event = timeout | term()</v>
+ <v>StateData = term()</v>
+ <v>Result = {next_state,NextStateName,NewStateData} </v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,Timeout}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason,NewStateData}</v>
+ <v>&nbsp;NextStateName = atom()</v>
+ <v>&nbsp;NewStateData = term()</v>
+ <v>&nbsp;Timeout = int()>0 | infinity</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>There should be one instance of this function for each
+ possible state name. Whenever a gen_fsm receives an event
+ sent using
+ <seealso marker="#send_event/2">gen_fsm:send_event/2</seealso>,
+ the instance of this function with the same name as
+ the current state name <c>StateName</c> is called to handle
+ the event. It is also called if a timeout occurs.</p>
+ <p><c>Event</c> is either the atom <c>timeout</c>, if a timeout
+ has occurred, or the <c>Event</c> argument provided to
+ <c>send_event/2</c>.</p>
+ <p><c>StateData</c> is the state data of the gen_fsm.</p>
+ <p>If the function returns
+ <c>{next_state,NextStateName,NewStateData}</c>,
+ <c>{next_state,NextStateName,NewStateData,Timeout}</c> or
+ <c>{next_state,NextStateName,NewStateData,hibernate}</c>,
+ the gen_fsm will continue executing with the current state
+ name set to <c>NextStateName</c> and with the possibly
+ updated state data <c>NewStateData</c>. See
+ <c>Module:init/1</c> for a description of <c>Timeout</c> and <c>hibernate</c>.</p>
+ <p>If the function returns <c>{stop,Reason,NewStateData}</c>,
+ the gen_fsm will call
+ <c>Module:terminate(Reason,NewStateData)</c> and terminate.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_event(Event, StateName, StateData) -> Result</name>
+ <fsummary>Handle an asynchronous event.</fsummary>
+ <type>
+ <v>Event = term()</v>
+ <v>StateName = atom()</v>
+ <v>StateData = term()</v>
+ <v>Result = {next_state,NextStateName,NewStateData} </v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,Timeout}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason,NewStateData}</v>
+ <v>&nbsp;NextStateName = atom()</v>
+ <v>&nbsp;NewStateData = term()</v>
+ <v>&nbsp;Timeout = int()>0 | infinity</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Whenever a gen_fsm receives an event sent using
+ <seealso marker="#send_all_state_event/2">gen_fsm:send_all_state_event/2</seealso>,
+ this function is called to handle the event.</p>
+ <p><c>StateName</c> is the current state name of the gen_fsm.</p>
+ <p>See <c>Module:StateName/2</c> for a description of the other
+ arguments and possible return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:StateName(Event, From, StateData) -> Result</name>
+ <fsummary>Handle a synchronous event.</fsummary>
+ <type>
+ <v>Event = term()</v>
+ <v>From = {pid(),Tag}</v>
+ <v>StateData = term()</v>
+ <v>Result = {reply,Reply,NextStateName,NewStateData}</v>
+ <v>&nbsp;&nbsp;| {reply,Reply,NextStateName,NewStateData,Timeout}</v>
+ <v>&nbsp;&nbsp;| {reply,Reply,NextStateName,NewStateData,hibernate}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,Timeout}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason,Reply,NewStateData} | {stop,Reason,NewStateData}</v>
+ <v>&nbsp;Reply = term()</v>
+ <v>&nbsp;NextStateName = atom()</v>
+ <v>&nbsp;NewStateData = term()</v>
+ <v>&nbsp;Timeout = int()>0 | infinity</v>
+ <v>&nbsp;Reason = normal | term()</v>
+ </type>
+ <desc>
+ <p>There should be one instance of this function for each
+ possible state name. Whenever a gen_fsm receives an event
+ sent using
+ <seealso marker="#sync_send_event/2">gen_fsm:sync_send_event/2,3</seealso>,
+ the instance of this function with the same name as
+ the current state name <c>StateName</c> is called to handle
+ the event.</p>
+ <p><c>Event</c> is the <c>Event</c> argument provided to
+ <c>sync_send_event</c>.</p>
+ <p><c>From</c> is a tuple <c>{Pid,Tag}</c> where <c>Pid</c> is
+ the pid of the process which called <c>sync_send_event/2,3</c>
+ and <c>Tag</c> is a unique tag.</p>
+ <p><c>StateData</c> is the state data of the gen_fsm.</p>
+ <p>If the function returns
+ <c>{reply,Reply,NextStateName,NewStateData}</c>,
+ <c>{reply,Reply,NextStateName,NewStateData,Timeout}</c> or
+ <c>{reply,Reply,NextStateName,NewStateData,hibernate}</c>,
+ <c>Reply</c> will be given back to <c>From</c> as the return
+ value of <c>sync_send_event/2,3</c>. The gen_fsm then
+ continues executing with the current state name set to
+ <c>NextStateName</c> and with the possibly updated state data
+ <c>NewStateData</c>. See <c>Module:init/1</c> for a
+ description of <c>Timeout</c> and <c>hibernate</c>.</p>
+ <p>If the function returns
+ <c>{next_state,NextStateName,NewStateData}</c>,
+ <c>{next_state,NextStateName,NewStateData,Timeout}</c> or
+ <c>{next_state,NextStateName,NewStateData,hibernate}</c>,
+ the gen_fsm will continue executing in <c>NextStateName</c>
+ with <c>NewStateData</c>. Any reply to <c>From</c> must be
+ given explicitly using
+ <seealso marker="#reply/2">gen_fsm:reply/2</seealso>.</p>
+ <p>If the function returns
+ <c>{stop,Reason,Reply,NewStateData}</c>, <c>Reply</c> will be
+ given back to <c>From</c>. If the function returns
+ <c>{stop,Reason,NewStateData}</c>, any reply to <c>From</c>
+ must be given explicitly using <c>gen_fsm:reply/2</c>.
+ The gen_fsm will then call
+ <c>Module:terminate(Reason,NewStateData)</c> and terminate.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_sync_event(Event, From, StateName, StateData) -> Result</name>
+ <fsummary>Handle a synchronous event.</fsummary>
+ <type>
+ <v>Event = term()</v>
+ <v>From = {pid(),Tag}</v>
+ <v>StateName = atom()</v>
+ <v>StateData = term()</v>
+ <v>Result = {reply,Reply,NextStateName,NewStateData}</v>
+ <v>&nbsp;&nbsp;| {reply,Reply,NextStateName,NewStateData,Timeout}</v>
+ <v>&nbsp;&nbsp;| {reply,Reply,NextStateName,NewStateData,hibernate}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,Timeout}</v>
+ <v>&nbsp;&nbsp;| {next_state,NextStateName,NewStateData,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason,Reply,NewStateData} | {stop,Reason,NewStateData}</v>
+ <v>&nbsp;Reply = term()</v>
+ <v>&nbsp;NextStateName = atom()</v>
+ <v>&nbsp;NewStateData = term()</v>
+ <v>&nbsp;Timeout = int()>0 | infinity</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Whenever a gen_fsm receives an event sent using
+ <seealso marker="#sync_send_all_state_event/2">gen_fsm:sync_send_all_state_event/2,3</seealso>,
+ this function is called to handle the event.</p>
+ <p><c>StateName</c> is the current state name of the gen_fsm.</p>
+ <p>See <c>Module:StateName/3</c> for a description of the other
+ arguments and possible return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_info(Info, StateName, StateData) -> Result</name>
+ <fsummary>Handle an incoming message.</fsummary>
+ <type>
+ <v>Info = term()</v>
+ <v>StateName = atom()</v>
+ <v>StateData = term()</v>
+ <v>Result = {next_state,NextStateName,NewStateData}</v>
+ <v>&nbsp;>&nbsp;| {next_state,NextStateName,NewStateData,Timeout}</v>
+ <v>&nbsp;>&nbsp;| {next_state,NextStateName,NewStateData,hibernate}</v>
+ <v>&nbsp;>&nbsp;| {stop,Reason,NewStateData}</v>
+ <v>&nbsp;NextStateName = atom()</v>
+ <v>&nbsp;NewStateData = term()</v>
+ <v>&nbsp;Timeout = int()>0 | infinity</v>
+ <v>&nbsp;Reason = normal | term()</v>
+ </type>
+ <desc>
+ <p>This function is called by a gen_fsm when it receives any
+ other message than a synchronous or asynchronous event (or a
+ system message).</p>
+ <p><c>Info</c> is the received message.</p>
+ <p>See <c>Module:StateName/2</c> for a description of the other
+ arguments and possible return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:terminate(Reason, StateName, StateData)</name>
+ <fsummary>Clean up before termination.</fsummary>
+ <type>
+ <v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
+ <v>StateName = atom()</v>
+ <v>StateData = term()</v>
+ </type>
+ <desc>
+ <p>This function is called by a gen_fsm when it is about to
+ terminate. It should be the opposite of <c>Module:init/1</c>
+ and do any necessary cleaning up. When it returns, the gen_fsm
+ terminates with <c>Reason</c>. The return value is ignored.</p>
+ <p><c>Reason</c> is a term denoting the stop reason,
+ <c>StateName</c> is the current state name, and
+ <c>StateData</c> is the state data of the gen_fsm.</p>
+ <p><c>Reason</c> depends on why the gen_fsm is terminating. If
+ it is because another callback function has returned a stop
+ tuple <c>{stop,..}</c>, <c>Reason</c> will have the value
+ specified in that tuple. If it is due to a failure,
+ <c>Reason</c> is the error reason.</p>
+ <p>If the gen_fsm is part of a supervision tree and is ordered
+ by its supervisor to terminate, this function will be called
+ with <c>Reason=shutdown</c> if the following conditions apply:</p>
+ <list type="bulleted">
+ <item>the gen_fsm has been set to trap exit signals, and</item>
+ <item>the shutdown strategy as defined in the supervisor's
+ child specification is an integer timeout value, not
+ <c>brutal_kill</c>.</item>
+ </list>
+ <p>Even if the gen_fsm is <em>not</em> part of a supervision tree,
+ this function will be called if it receives an <c>'EXIT'</c>
+ message from its parent. <c>Reason</c> will be the same as in
+ the <c>'EXIT'</c> message.</p>
+ <p>Otherwise, the gen_fsm will be immediately terminated.</p>
+ <p>Note that for any other reason than <c>normal</c>,
+ <c>shutdown</c>, or <c>{shutdown,Term}</c> the gen_fsm is
+ assumed to terminate due to an error and
+ an error report is issued using
+ <seealso marker="kernel:error_logger#format/2">error_logger:format/2</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:code_change(OldVsn, StateName, StateData, Extra) -> {ok, NextStateName, NewStateData}</name>
+ <fsummary>Update the internal state data during upgrade/downgrade.</fsummary>
+ <type>
+ <v>OldVsn = Vsn | {down, Vsn}</v>
+ <v>&nbsp;&nbsp;Vsn = term()</v>
+ <v>StateName = NextStateName = atom()</v>
+ <v>StateData = NewStateData = term()</v>
+ <v>Extra = term()</v>
+ </type>
+ <desc>
+ <p>This function is called by a gen_fsm when it should update
+ its internal state data during a release upgrade/downgrade,
+ i.e. when the instruction <c>{update,Module,Change,...}</c>
+ where <c>Change={advanced,Extra}</c> is given in
+ the <c>appup</c> file. See
+ <seealso marker="doc/design_principles:release_handling#instr">OTP Design Principles</seealso>.</p>
+ <p>In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
+ in the case of a downgrade, <c>OldVsn</c> is
+ <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c>
+ attribute(s) of the old version of the callback module
+ <c>Module</c>. If no such attribute is defined, the version is
+ the checksum of the BEAM file.</p>
+ <p><c>StateName</c> is the current state name and
+ <c>StateData</c> the internal state data of the gen_fsm.</p>
+ <p><c>Extra</c> is passed as-is from the <c>{advanced,Extra}</c>
+ part of the update instruction.</p>
+ <p>The function should return the new current state name and
+ updated internal data.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="gen_event">gen_event(3)</seealso>,
+ <seealso marker="gen_server">gen_server(3)</seealso>,
+ <seealso marker="supervisor">supervisor(3)</seealso>,
+ <seealso marker="proc_lib">proc_lib(3)</seealso>,
+ <seealso marker="sys">sys(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml
new file mode 100644
index 0000000000..8496802259
--- /dev/null
+++ b/lib/stdlib/doc/src/gen_server.xml
@@ -0,0 +1,612 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>gen_server</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>gen_server</module>
+ <modulesummary>Generic Server Behaviour</modulesummary>
+ <description>
+ <p>A behaviour module for implementing the server of a client-server
+ relation. A generic server process (gen_server) implemented using
+ this module will have a standard set of interface functions and
+ include functionality for tracing and error reporting. It will
+ also fit into an OTP supervision tree. Refer to
+ <seealso marker="doc/design_principles:gen_server_concepts">OTP Design Principles</seealso> for more information.</p>
+ <p>A gen_server assumes all specific parts to be located in a
+ callback module exporting a pre-defined set of functions.
+ The relationship between the behaviour functions and the callback
+ functions can be illustrated as follows:</p>
+ <pre>
+gen_server module Callback module
+----------------- ---------------
+gen_server:start_link -----> Module:init/1
+
+gen_server:call
+gen_server:multi_call -----> Module:handle_call/3
+
+gen_server:cast
+gen_server:abcast -----> Module:handle_cast/2
+
+- -----> Module:handle_info/2
+
+- -----> Module:terminate/2
+
+- -----> Module:code_change/3 </pre>
+ <p>If a callback function fails or returns a bad value,
+ the gen_server will terminate.</p>
+ <p>A gen_server handles system messages as documented in
+ <seealso marker="sys">sys(3)</seealso>. The <c>sys</c> module
+ can be used for debugging a gen_server.</p>
+ <p>Note that a gen_server does not trap exit signals automatically,
+ this must be explicitly initiated in the callback module.</p>
+ <p>Unless otherwise stated, all functions in this module fail if
+ the specified gen_server does not exist or if bad arguments are
+ given.</p>
+
+ <p>The gen_server process can go into hibernation
+ (see <seealso marker="erts:erlang#erlang:hibernate/3">erlang(3)</seealso>) if a callback
+ function specifies <c>'hibernate'</c> instead of a timeout value. This
+ might be useful if the server is expected to be idle for a long
+ time. However this feature should be used with care as hibernation
+ implies at least two garbage collections (when hibernating and
+ shortly after waking up) and is not something you'd want to do
+ between each call to a busy server.</p>
+
+ </description>
+ <funcs>
+ <func>
+ <name>start_link(Module, Args, Options) -> Result</name>
+ <name>start_link(ServerName, Module, Args, Options) -> Result</name>
+ <fsummary>Create a gen_server process in a supervision tree.</fsummary>
+ <type>
+ <v>ServerName = {local,Name} | {global,GlobalName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Module = atom()</v>
+ <v>Args = term()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics | {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>&nbsp;&nbsp;SOpts = [term()]</v>
+ <v>Result = {ok,Pid} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;Error = {already_started,Pid} | term()</v>
+ </type>
+ <desc>
+ <p>Creates a gen_server process as part of a supervision tree.
+ The function should be called, directly or indirectly, by
+ the supervisor. It will, among other things, ensure that
+ the gen_server is linked to the supervisor.</p>
+ <p>The gen_server process calls <c>Module:init/1</c> to
+ initialize. To ensure a synchronized start-up procedure,
+ <c>start_link/3,4</c> does not return until
+ <c>Module:init/1</c> has returned.</p>
+ <p>If <c>ServerName={local,Name}</c> the gen_server is
+ registered locally as <c>Name</c> using <c>register/2</c>.
+ If <c>ServerName={global,GlobalName}</c> the gen_server is
+ registered globally as <c>GlobalName</c> using
+ <c>global:register_name/2</c>. If no name is provided,
+ the gen_server is not registered.</p>
+ <p><c>Module</c> is the name of the callback module.</p>
+ <p><c>Args</c> is an arbitrary term which is passed as
+ the argument to <c>Module:init/1</c>.</p>
+ <p>If the option <c>{timeout,Time}</c> is present,
+ the gen_server is allowed to spend <c>Time</c> milliseconds
+ initializing or it will be terminated and the start function
+ will return <c>{error,timeout}</c>.</p>
+ <p>If the option <c>{debug,Dbgs}</c> is present,
+ the corresponding <c>sys</c> function will be called for each
+ item in <c>Dbgs</c>. See
+ <seealso marker="sys">sys(3)</seealso>.</p>
+ <p>If the option <c>{spawn_opt,SOpts}</c> is present,
+ <c>SOpts</c> will be passed as option list to
+ the <c>spawn_opt</c> BIF which is used to spawn
+ the gen_server. See
+ <seealso marker="erts:erlang#spawn_opt/2">erlang(3)</seealso>.</p>
+ <note>
+ <p>Using the spawn option <c>monitor</c> is currently not
+ allowed, but will cause the function to fail with reason
+ <c>badarg</c>.</p>
+ </note>
+ <p>If the gen_server is successfully created and initialized
+ the function returns <c>{ok,Pid}</c>, where <c>Pid</c> is
+ the pid of the gen_server. If there already exists a process
+ with the specified <c>ServerName</c> the function returns
+ <c>{error,{already_started,Pid}}</c>, where <c>Pid</c> is
+ the pid of that process.</p>
+ <p>If <c>Module:init/1</c> fails with <c>Reason</c>,
+ the function returns <c>{error,Reason}</c>. If
+ <c>Module:init/1</c> returns <c>{stop,Reason}</c> or
+ <c>ignore</c>, the process is terminated and the function
+ returns <c>{error,Reason}</c> or <c>ignore</c>, respectively.</p>
+ </desc>
+ </func>
+ <func>
+ <name>start(Module, Args, Options) -> Result</name>
+ <name>start(ServerName, Module, Args, Options) -> Result</name>
+ <fsummary>Create a stand-alone gen_server process.</fsummary>
+ <type>
+ <v>ServerName = {local,Name} | {global,GlobalName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Module = atom()</v>
+ <v>Args = term()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs} | {timeout,Time} | {spawn_opt,SOpts}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics | {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>&nbsp;&nbsp;SOpts = [term()]</v>
+ <v>Result = {ok,Pid} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;Error = {already_started,Pid} | term()</v>
+ </type>
+ <desc>
+ <p>Creates a stand-alone gen_server process, i.e. a gen_server
+ which is not part of a supervision tree and thus has no
+ supervisor.</p>
+ <p>See <seealso marker="#start_link/3">start_link/3,4</seealso>
+ for a description of arguments and return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>call(ServerRef, Request) -> Reply</name>
+ <name>call(ServerRef, Request, Timeout) -> Reply</name>
+ <fsummary>Make a synchronous call to a generic server.</fsummary>
+ <type>
+ <v>ServerRef = Name | {Name,Node} | {global,GlobalName} | pid()</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Request = term()</v>
+ <v>Timeout = int()>0 | infinity</v>
+ <v>Reply = term()</v>
+ </type>
+ <desc>
+ <p>Makes a synchronous call to the gen_server <c>ServerRef</c>
+ by sending a request and waiting until a reply arrives or a
+ timeout occurs. The gen_server will call
+ <c>Module:handle_call/3</c> to handle the request.</p>
+ <p><c>ServerRef</c> can be:</p>
+ <list type="bulleted">
+ <item>the pid,</item>
+ <item><c>Name</c>, if the gen_server is locally registered,</item>
+ <item><c>{Name,Node}</c>, if the gen_server is locally
+ registered at another node, or</item>
+ <item><c>{global,GlobalName}</c>, if the gen_server is
+ globally registered.</item>
+ </list>
+ <p><c>Request</c> is an arbitrary term which is passed as one of
+ the arguments to <c>Module:handle_call/3</c>.</p>
+ <p><c>Timeout</c> is an integer greater than zero which
+ specifies how many milliseconds to wait for a reply, or
+ the atom <c>infinity</c> to wait indefinitely. Default value
+ is 5000. If no reply is received within the specified time,
+ the function call fails. If the caller catches the failure
+ and continues running, and the server is just late with the reply,
+ it may arrive at any time later into the caller's message queue.
+ The caller must in this case be prepared for this
+ and discard any such garbage messages that are two element
+ tuples with a reference as the first element.</p>
+ <p>The return value <c>Reply</c> is defined in the return value
+ of <c>Module:handle_call/3</c>.</p>
+ <p>The call may fail for several reasons, including timeout and
+ the called gen_server dying before or during the call.</p>
+ <p>The ancient behaviour of sometimes consuming the server
+ exit message if the server died during the call while
+ linked to the client has been removed in OTP R12B/Erlang 5.6.</p>
+ </desc>
+ </func>
+ <func>
+ <name>multi_call(Name, Request) -> Result</name>
+ <name>multi_call(Nodes, Name, Request) -> Result</name>
+ <name>multi_call(Nodes, Name, Request, Timeout) -> Result</name>
+ <fsummary>Make a synchronous call to several generic servers.</fsummary>
+ <type>
+ <v>Nodes = [Node]</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>Name = atom()</v>
+ <v>Request = term()</v>
+ <v>Timeout = int()>=0 | infinity</v>
+ <v>Result = {Replies,BadNodes}</v>
+ <v>&nbsp;Replies = [{Node,Reply}]</v>
+ <v>&nbsp;&nbsp;Reply = term()</v>
+ <v>BadNodes = [Node]</v>
+ </type>
+ <desc>
+ <p>Makes a synchronous call to all gen_servers locally
+ registered as <c>Name</c> at the specified nodes by first
+ sending a request to every node and then waiting for
+ the replies. The gen_servers will call
+ <c>Module:handle_call/3</c> to handle the request.</p>
+ <p>The function returns a tuple <c>{Replies,BadNodes}</c> where
+ <c>Replies</c> is a list of <c>{Node,Reply}</c> and
+ <c>BadNodes</c> is a list of node that either did not exist,
+ or where the gen_server <c>Name</c> did not exist or did not
+ reply.</p>
+ <p><c>Nodes</c> is a list of node names to which the request
+ should be sent. Default value is the list of all known nodes
+ <c>[node()|nodes()]</c>.</p>
+ <p><c>Name</c> is the locally registered name of each
+ gen_server.</p>
+ <p><c>Request</c> is an arbitrary term which is passed as one of
+ the arguments to <c>Module:handle_call/3</c>.</p>
+ <p><c>Timeout</c> is an integer greater than zero which
+ specifies how many milliseconds to wait for each reply, or
+ the atom <c>infinity</c> to wait indefinitely. Default value
+ is <c>infinity</c>. If no reply is received from a node within
+ the specified time, the node is added to <c>BadNodes</c>.</p>
+ <p>When a reply <c>Reply</c> is received from the gen_server at
+ a node <c>Node</c>, <c>{Node,Reply}</c> is added to
+ <c>Replies</c>. <c>Reply</c> is defined in the return value of
+ <c>Module:handle_call/3</c>.</p>
+ <warning>
+ <p>If one of the nodes is not capable of process monitors,
+ for example C or Java nodes, and the gen_server is not started
+ when the requests are sent, but starts within 2 seconds,
+ this function waits the whole <c>Timeout</c>,
+ which may be infinity.</p>
+ <p>This problem does not exist if all nodes are Erlang nodes.</p>
+ </warning>
+ <p>To avoid that late answers (after the timeout) pollutes
+ the caller's message queue, a middleman process is used to
+ do the actual calls. Late answers will then be discarded
+ when they arrive to a terminated process.</p>
+ </desc>
+ </func>
+ <func>
+ <name>cast(ServerRef, Request) -> ok</name>
+ <fsummary>Send an asynchronous request to a generic server.</fsummary>
+ <type>
+ <v>ServerRef = Name | {Name,Node} | {global,GlobalName} | pid()</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Request = term()</v>
+ </type>
+ <desc>
+ <p>Sends an asynchronous request to the gen_server
+ <c>ServerRef</c> and returns <c>ok</c> immediately, ignoring
+ if the destination node or gen_server does not exist.
+ The gen_server will call <c>Module:handle_cast/2</c> to
+ handle the request.</p>
+ <p>See <seealso marker="#call/2">call/2,3</seealso> for a
+ description of <c>ServerRef</c>.</p>
+ <p><c>Request</c> is an arbitrary term which is passed as one
+ of the arguments to <c>Module:handle_cast/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>abcast(Name, Request) -> abcast</name>
+ <name>abcast(Nodes, Name, Request) -> abcast</name>
+ <fsummary>Send an asynchronous request to several generic servers.</fsummary>
+ <type>
+ <v>Nodes = [Node]</v>
+ <v>&nbsp;Node = atom()</v>
+ <v>Name = atom()</v>
+ <v>Request = term()</v>
+ </type>
+ <desc>
+ <p>Sends an asynchronous request to the gen_servers locally
+ registered as <c>Name</c> at the specified nodes. The function
+ returns immediately and ignores nodes that do not exist, or
+ where the gen_server <c>Name</c> does not exist.
+ The gen_servers will call <c>Module:handle_cast/2</c> to
+ handle the request.</p>
+ <p>See
+ <seealso marker="#multi_call/2">multi_call/2,3,4</seealso>
+ for a description of the arguments.</p>
+ </desc>
+ </func>
+ <func>
+ <name>reply(Client, Reply) -> Result</name>
+ <fsummary>Send a reply to a client.</fsummary>
+ <type>
+ <v>Client - see below</v>
+ <v>Reply = term()</v>
+ <v>Result = term()</v>
+ </type>
+ <desc>
+ <p>This function can be used by a gen_server to explicitly send
+ a reply to a client that called <c>call/2,3</c> or
+ <c>multi_call/2,3,4</c>, when the reply cannot be defined in
+ the return value of <c>Module:handle_call/3</c>.</p>
+ <p><c>Client</c> must be the <c>From</c> argument provided to
+ the callback function. <c>Reply</c> is an arbitrary term,
+ which will be given back to the client as the return value of
+ <c>call/2,3</c> or <c>multi_call/2,3,4</c>.</p>
+ <p>The return value <c>Result</c> is not further defined, and
+ should always be ignored.</p>
+ </desc>
+ </func>
+ <func>
+ <name>enter_loop(Module, Options, State)</name>
+ <name>enter_loop(Module, Options, State, ServerName)</name>
+ <name>enter_loop(Module, Options, State, Timeout)</name>
+ <name>enter_loop(Module, Options, State, ServerName, Timeout)</name>
+ <fsummary>Enter the gen_server receive loop</fsummary>
+ <type>
+ <v>Module = atom()</v>
+ <v>Options = [Option]</v>
+ <v>&nbsp;Option = {debug,Dbgs}</v>
+ <v>&nbsp;&nbsp;Dbgs = [Dbg]</v>
+ <v>&nbsp;&nbsp;&nbsp;Dbg = trace | log | statistics</v>
+ <v>&nbsp;&nbsp;&nbsp;&nbsp;| {log_to_file,FileName} | {install,{Func,FuncState}}</v>
+ <v>State = term()</v>
+ <v>ServerName = {local,Name} | {global,GlobalName}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>&nbsp;GlobalName = term()</v>
+ <v>Timeout = int() | infinity</v>
+ </type>
+ <desc>
+ <p>Makes an existing process into a gen_server. Does not return,
+ instead the calling process will enter the gen_server receive
+ loop and become a gen_server process. The process
+ <em>must</em> have been started using one of the start
+ functions in <c>proc_lib</c>, see
+ <seealso marker="proc_lib">proc_lib(3)</seealso>. The user is
+ responsible for any initialization of the process, including
+ registering a name for it.</p>
+ <p>This function is useful when a more complex initialization
+ procedure is needed than the gen_server behaviour provides.</p>
+ <p><c>Module</c>, <c>Options</c> and <c>ServerName</c> have
+ the same meanings as when calling
+ <seealso marker="#start_link/3">gen_server:start[_link]/3,4</seealso>.
+ However, if <c>ServerName</c> is specified, the process must
+ have been registered accordingly <em>before</em> this function
+ is called.</p>
+ <p><c>State</c> and <c>Timeout</c> have the same meanings as in
+ the return value of
+ <seealso marker="#Moduleinit">Module:init/1</seealso>.
+ Also, the callback module <c>Module</c> does not need to
+ export an <c>init/1</c> function. </p>
+ <p>Failure: If the calling process was not started by a
+ <c>proc_lib</c> start function, or if it is not registered
+ according to <c>ServerName</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>The following functions
+ should be exported from a <c>gen_server</c> callback module.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>Module:init(Args) -> Result</name>
+ <fsummary>Initialize process and internal state.</fsummary>
+ <type>
+ <v>Args = term()</v>
+ <v>Result = {ok,State} | {ok,State,Timeout} | {ok,State,hibernate}</v>
+ <v>&nbsp;| {stop,Reason} | ignore</v>
+ <v>&nbsp;State = term()</v>
+ <v>&nbsp;Timeout = int()>=0 | infinity</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <marker id="Moduleinit"></marker>
+ <p>Whenever a gen_server is started using
+ <seealso marker="#start/3">gen_server:start/3,4</seealso> or
+ <seealso marker="#start_link/3">gen_server:start_link/3,4</seealso>,
+ this function is called by the new process to initialize.</p>
+ <p><c>Args</c> is the <c>Args</c> argument provided to the start
+ function.</p>
+ <p>If the initialization is successful, the function should
+ return <c>{ok,State}</c>, <c>{ok,State,Timeout}</c> or <c>{ok,State,hibernate}</c>, where
+ <c>State</c> is the internal state of the gen_server.</p>
+ <p>If an integer timeout value is provided, a timeout will occur
+ unless a request or a message is received within
+ <c>Timeout</c> milliseconds. A timeout is represented by
+ the atom <c>timeout</c> which should be handled by
+ the <c>handle_info/2</c> callback function. The atom
+ <c>infinity</c> can be used to wait indefinitely, this is
+ the default value.</p>
+ <p>If <c>hibernate</c> is specified instead of a timeout value, the process will go
+ into hibernation when waiting for the next message to arrive (by calling
+ <seealso marker="proc_lib#hibernate/3">proc_lib:hibernate/3</seealso>).</p>
+ <p>If something goes wrong during the initialization
+ the function should return <c>{stop,Reason}</c> where
+ <c>Reason</c> is any term, or <c>ignore</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_call(Request, From, State) -> Result</name>
+ <fsummary>Handle a synchronous request.</fsummary>
+ <type>
+ <v>Request = term()</v>
+ <v>From = {pid(),Tag}</v>
+ <v>State = term()</v>
+ <v>Result = {reply,Reply,NewState} | {reply,Reply,NewState,Timeout}</v>
+ <v>&nbsp;&nbsp;| {reply,Reply,NewState,hibernate}</v>
+ <v>&nbsp;&nbsp;| {noreply,NewState} | {noreply,NewState,Timeout}</v>
+ <v>&nbsp;&nbsp;| {noreply,NewState,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason,Reply,NewState} | {stop,Reason,NewState}</v>
+ <v>&nbsp;Reply = term()</v>
+ <v>&nbsp;NewState = term()</v>
+ <v>&nbsp;Timeout = int()>=0 | infinity</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Whenever a gen_server receives a request sent using
+ <seealso marker="#call/2">gen_server:call/2,3</seealso> or
+ <seealso marker="#multi_call/2">gen_server:multi_call/2,3,4</seealso>,
+ this function is called to handle the request.</p>
+ <p><c>Request</c> is the <c>Request</c> argument provided
+ to <c>call</c> or <c>multi_call</c>.</p>
+ <p><c>From</c> is a tuple <c>{Pid,Tag}</c> where <c>Pid</c> is
+ the pid of the client and <c>Tag</c> is a unique tag.</p>
+ <p><c>State</c> is the internal state of the gen_server.</p>
+ <p>If the function returns <c>{reply,Reply,NewState}</c>,
+ <c>{reply,Reply,NewState,Timeout}</c> or
+ <c>{reply,Reply,NewState,hibernate}</c>, <c>Reply</c> will be
+ given back to <c>From</c> as the return value of
+ <c>call/2,3</c> or included in the return value of
+ <c>multi_call/2,3,4</c>. The gen_server then continues
+ executing with the possibly updated internal state
+ <c>NewState</c>. See <c>Module:init/1</c> for a description
+ of <c>Timeout</c> and <c>hibernate</c>.</p>
+ <p>If the functions returns <c>{noreply,NewState}</c>,
+ <c>{noreply,NewState,Timeout}</c> or <c>{noreply,NewState,hibernate}</c>,
+ the gen_server will
+ continue executing with <c>NewState</c>. Any reply to
+ <c>From</c> must be given explicitly using
+ <seealso marker="#reply/2">gen_server:reply/2</seealso>.</p>
+ <p>If the function returns <c>{stop,Reason,Reply,NewState}</c>,
+ <c>Reply</c> will be given back to <c>From</c>. If
+ the function returns <c>{stop,Reason,NewState}</c>, any reply
+ to <c>From</c> must be given explicitly using
+ <c>gen_server:reply/2</c>. The gen_server will then call
+ <c>Module:terminate(Reason,NewState)</c> and terminate.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_cast(Request, State) -> Result</name>
+ <fsummary>Handle an asynchronous request.</fsummary>
+ <type>
+ <v>Request = term()</v>
+ <v>State = term()</v>
+ <v>Result = {noreply,NewState} | {noreply,NewState,Timeout}</v>
+ <v>&nbsp;&nbsp;| {noreply,NewState,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason,NewState}</v>
+ <v>&nbsp;NewState = term()</v>
+ <v>&nbsp;Timeout = int()>=0 | infinity</v>
+ <v>&nbsp;Reason = term()</v>
+ </type>
+ <desc>
+ <p>Whenever a gen_server receives a request sent using
+ <seealso marker="#cast/2">gen_server:cast/2</seealso> or
+ <seealso marker="#abcast/2">gen_server:abcast/2,3</seealso>,
+ this function is called to handle the request.</p>
+ <p>See <c>Module:handle_call/3</c> for a description of
+ the arguments and possible return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:handle_info(Info, State) -> Result</name>
+ <fsummary>Handle an incoming message.</fsummary>
+ <type>
+ <v>Info = timeout | term()</v>
+ <v>State = term()</v>
+ <v>Result = {noreply,NewState} | {noreply,NewState,Timeout} </v>
+ <v>&nbsp;&nbsp;| {noreply,NewState,hibernate}</v>
+ <v>&nbsp;&nbsp;| {stop,Reason,NewState}</v>
+ <v>&nbsp;NewState = term()</v>
+ <v>&nbsp;Timeout = int()>=0 | infinity</v>
+ <v>&nbsp;Reason = normal | term()</v>
+ </type>
+ <desc>
+ <p>This function is called by a gen_server when a timeout
+ occurs or when it receives any other message than a
+ synchronous or asynchronous request (or a system message).</p>
+ <p><c>Info</c> is either the atom <c>timeout</c>, if a timeout
+ has occurred, or the received message.</p>
+ <p>See <c>Module:handle_call/3</c> for a description of
+ the other arguments and possible return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:terminate(Reason, State)</name>
+ <fsummary>Clean up before termination.</fsummary>
+ <type>
+ <v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
+ <v>State = term()</v>
+ </type>
+ <desc>
+ <p>This function is called by a gen_server when it is about to
+ terminate. It should be the opposite of <c>Module:init/1</c>
+ and do any necessary cleaning up. When it returns,
+ the gen_server terminates with <c>Reason</c>. The return
+ value is ignored.</p>
+ <p><c>Reason</c> is a term denoting the stop reason and
+ <c>State</c> is the internal state of the gen_server.</p>
+ <p><c>Reason</c> depends on why the gen_server is terminating.
+ If it is because another callback function has returned a
+ stop tuple <c>{stop,..}</c>, <c>Reason</c> will have
+ the value specified in that tuple. If it is due to a failure,
+ <c>Reason</c> is the error reason.</p>
+ <p>If the gen_server is part of a supervision tree and is
+ ordered by its supervisor to terminate, this function will be
+ called with <c>Reason=shutdown</c> if the following
+ conditions apply:</p>
+ <list type="bulleted">
+ <item>the gen_server has been set to trap exit signals, and</item>
+ <item>the shutdown strategy as defined in the supervisor's
+ child specification is an integer timeout value, not
+ <c>brutal_kill</c>.</item>
+ </list>
+ <p>Even if the gen_server is <em>not</em> part of a supervision tree,
+ this function will be called if it receives an <c>'EXIT'</c>
+ message from its parent. <c>Reason</c> will be the same as in
+ the <c>'EXIT'</c> message.</p>
+ <p>Otherwise, the gen_server will be immediately terminated.</p>
+ <p>Note that for any other reason than <c>normal</c>,
+ <c>shutdown</c>, or <c>{shutdown,Term}</c> the gen_server is
+ assumed to terminate due to an error and
+ an error report is issued using
+ <seealso marker="kernel:error_logger#format/2">error_logger:format/2</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name>
+ <fsummary>Update the internal state during upgrade/downgrade.</fsummary>
+ <type>
+ <v>OldVsn = Vsn | {down, Vsn}</v>
+ <v>&nbsp;&nbsp;Vsn = term()</v>
+ <v>State = NewState = term()</v>
+ <v>Extra = term()</v>
+ </type>
+ <desc>
+ <p>This function is called by a gen_server when it should
+ update its internal state during a release upgrade/downgrade,
+ i.e. when the instruction <c>{update,Module,Change,...}</c>
+ where <c>Change={advanced,Extra}</c> is given in
+ the <c>appup</c> file. See
+ <seealso marker="doc/design_principles:release_handling#instr">OTP Design Principles</seealso>
+ for more information.</p>
+ <p>In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
+ in the case of a downgrade, <c>OldVsn</c> is
+ <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c>
+ attribute(s) of the old version of the callback module
+ <c>Module</c>. If no such attribute is defined, the version
+ is the checksum of the BEAM file.</p>
+ <p><c>State</c> is the internal state of the gen_server.</p>
+ <p><c>Extra</c> is passed as-is from the <c>{advanced,Extra}</c>
+ part of the update instruction.</p>
+ <p>The function should return the updated internal state.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="gen_event">gen_event(3)</seealso>,
+ <seealso marker="gen_fsm">gen_fsm(3)</seealso>,
+ <seealso marker="supervisor">supervisor(3)</seealso>,
+ <seealso marker="proc_lib">proc_lib(3)</seealso>,
+ <seealso marker="sys">sys(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml
new file mode 100644
index 0000000000..c075f11792
--- /dev/null
+++ b/lib/stdlib/doc/src/io.xml
@@ -0,0 +1,1026 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>io</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>io</module>
+ <modulesummary>Standard IO Server Interface Functions</modulesummary>
+ <description>
+ <p>This module provides an interface to standard Erlang IO servers.
+ The output functions all return <c>ok</c> if they are successful,
+ or exit if they are not.</p>
+ <p>In the following description, all functions have an optional
+ parameter <c>IoDevice</c>. If included, it must be the pid of a
+ process which handles the IO protocols. Normally, it is the
+ <c>IoDevice</c> returned by
+ <seealso marker="kernel:file#open/2">file:open/2</seealso>.</p>
+ <p>For a description of the IO protocols refer to the STDLIB Users Guide.</p>
+ <warning>
+
+ <p>As of R13A, data supplied to the <seealso
+ marker="#put_chars/2">put_chars</seealso> function should be in the
+ <c>chardata()</c> format described below. This means that programs
+ supplying binaries to this function need to convert them to UTF-8
+ before trying to output the data on an
+ <c>io_device()</c>.</p>
+
+ <p>If an io_device() is set in binary mode, the functions <seealso
+ marker="#get_chars/3">get_chars</seealso> and <seealso
+ marker="#get_line/2">get_line</seealso> may return binaries
+ instead of lists. The binaries will, as of R13A, be encoded in
+ UTF-8.</p>
+
+ <p>To work with binaries in ISO-latin-1 encoding, use the <seealso
+ marker="kernel:file">file</seealso> module instead.</p>
+
+ <p>For conversion functions between character encodings, see the <seealso
+ marker="stdlib:unicode">unicode</seealso> module.</p>
+
+ </warning>
+
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+io_device()
+ as returned by file:open/2, a process handling IO protocols</code>
+
+ <code type="none">
+unicode_binary() = binary() with characters encoded in UTF-8 coding standard
+unicode_char() = integer() representing valid unicode codepoint
+
+chardata() = charlist() | unicode_binary()
+
+charlist() = [unicode_char() | unicode_binary() | charlist()]
+ a unicode_binary is allowed as the tail of the list</code>
+ </section>
+ <funcs>
+ <func>
+ <name>columns([IoDevice]) -> {ok,int()} | {error, enotsup}</name>
+ <fsummary>Get the number of columns of a device</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ </type>
+ <desc>
+ <p>Retrieves the number of columns of the
+ <c>IoDevice</c> (i.e. the width of a terminal). The function
+ only succeeds for terminal devices, for all other devices
+ the function returns <c>{error, enotsup}</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>put_chars([IoDevice,] IoData) -> ok</name>
+ <fsummary>Write a list of characters</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>IoData = chardata()</v>
+ </type>
+ <desc>
+ <p>Writes the characters of <c>IoData</c> to the io_server()
+ (<c>IoDevice</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>nl([IoDevice]) -> ok</name>
+ <fsummary>Write a newline</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ </type>
+ <desc>
+ <p>Writes new line to the standard output (<c>IoDevice</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_chars([IoDevice,] Prompt, Count) -> Data | eof</name>
+ <fsummary>Read a specified number of characters</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>Count = int()</v>
+ <v>Data = [ unicode_char() ] | unicode_binary()</v>
+ </type>
+ <desc>
+ <p>Reads <c>Count</c> characters from standard input
+ (<c>IoDevice</c>), prompting it with <c>Prompt</c>. It
+ returns:</p>
+ <taglist>
+ <tag><c>Data</c></tag>
+ <item>
+ <p>The input characters. If the device supports Unicode,
+ the data may represent codepoints larger than 255 (the
+ latin1 range). If the io_server() is set to deliver
+ binaries, they will be encoded in UTF-8 (regardless of if
+ the device actually supports Unicode or not).</p>
+ </item>
+ <tag><c>eof</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error,Reason}</c></tag>
+ <item>
+ <p>Other (rare) error condition, for instance <c>{error,estale}</c>
+ if reading from an NFS file system.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>get_line([IoDevice,] Prompt) -> Data | eof | {error,Reason}</name>
+ <fsummary>Read a line</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>Data = [ unicode_char() ] | unicode_binary()</v>
+ </type>
+ <desc>
+ <p>Reads a line from the standard input (<c>IoDevice</c>),
+ prompting it with <c>Prompt</c>. It returns:</p>
+ <taglist>
+ <tag><c>Data</c></tag>
+ <item>
+ <p>The characters in the line terminated by a LF (or end of
+ file). If the device supports Unicode,
+ the data may represent codepoints larger than 255 (the
+ latin1 range). If the io_server() is set to deliver
+ binaries, they will be encoded in UTF-8 (regardless of if
+ the device actually supports Unicode or not).</p>
+ </item>
+ <tag><c>eof</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error,Reason}</c></tag>
+ <item>
+ <p>Other (rare) error condition, for instance <c>{error,estale}</c>
+ if reading from an NFS file system.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>getopts([IoDevice]) -> Opts</name>
+ <fsummary>Get the supported options and values from an I/O-server</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Opts = [Opt]</v>
+ <v>&nbsp;&nbsp;Opt = {atom(),Value}</v>
+ <v>&nbsp;&nbsp;Value = term()</v>
+ </type>
+ <desc>
+ <p>This function requests all available options and their current values for a specific io_device(). Example:</p>
+<pre>
+1> <input>{ok,F} = file:open("/dev/null",[read]).</input>
+{ok,&lt;0.42.0&gt;}
+2> <input>io:getopts(F).</input>
+[{binary,false},{encoding,latin1}]</pre>
+ <p>Here the file I/O-server returns all available options for a file,
+ which are the expected ones, <c>encoding</c> and <c>binary</c>. The standard shell however has some more options:</p>
+<pre>
+3> io:getopts().
+[{expand_fun,#Fun&lt;group.0.120017273&gt;},
+ {echo,true},
+ {binary,false},
+ {encoding,unicode}]</pre>
+ <p>This example is, as can be seen, run in an environment where the terminal supports Unicode input and output.</p>
+ </desc>
+ </func>
+ <func>
+ <name>setopts([IoDevice,] Opts) -> ok | {error, Reason}</name>
+ <fsummary>Set options</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Opts = [Opt]</v>
+ <v>&nbsp;&nbsp;Opt = atom() | {atom(),Value}</v>
+ <v>&nbsp;&nbsp;Value = term()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Set options for the io_device() (<c>IoDevice</c>).</p>
+
+ <p>Possible options and values vary depending on the actual
+ io_device(). For a list of supported options and their current values
+ on a specific device, use the <seealso
+ marker="#getopts/1">getopts/1</seealso> function.</p>
+
+ <p>The options and values supported by the current OTP io_devices are:</p>
+ <taglist>
+ <tag><c>binary, list or {binary, bool()}</c></tag>
+ <item>
+ <p>If set in binary mode (binary or {binary,true}), the io_server() sends binary data (encoded in UTF-8) as answers to the get_line, get_chars and, if possible, get_until requests (see the I/O protocol description in STDLIB User's Guide for details). The immediate effect is that <c>get_chars/2,3</c> and <c>get_line/1,2</c> return UTF-8 binaries instead of lists of chars for the affected device.</p>
+ <p>By default, all io_devices in OTP are set in list mode, but the io functions can handle any of these modes and so should other, user written, modules behaving as clients to I/O-servers.</p>
+ <p>This option is supported by the standard shell (group.erl), the 'oldshell' (user.erl) and the file I/O servers.</p>
+ </item>
+ <tag><c>{echo, bool()}</c></tag>
+ <item>
+ <p>Denotes if the terminal should echo input. Only supported for the standard shell I/O-server (group.erl)</p>
+ </item>
+ <tag><c>{expand_fun, fun()}</c></tag>
+ <item>
+ <p>Provide a function for tab-completion (expansion)
+ like the erlang shell. This function is called
+ when the user presses the Tab key. The expansion is
+ active when calling line-reading functions such as
+ <c>get_line/1,2</c>.</p>
+ <p>The function is called with the current line, upto
+ the cursor, as a reversed string. It should return a
+ three-tuple: <c>{yes|no, string(), [string(), ...]}</c>. The
+ first element gives a beep if <c>no</c>, otherwise the
+ expansion is silent, the second is a string that will be
+ entered at the cursor position, and the third is a list of
+ possible expansions. If this list is non-empty, the list
+ will be printed and the current input line will be written
+ once again.</p>
+ <p>Trivial example (beep on anything except empty line, which
+ is expanded to "quit"):</p>
+ <code type="none">
+ fun("") -> {yes, "quit", []};
+ (_) -> {no, "", ["quit"]} end</code>
+ <p>This option is supported by the standard shell only (group.erl).</p>
+ </item>
+ <tag><c>{encoding, latin1 | unicode}</c></tag>
+ <item>
+ <p>Specifies how characters are input or output from or to the actual device, implying that i.e. a terminal is set to handle Unicode input and output or a file is set to handle UTF-8 data encoding.</p>
+ <p>The option <em>does not</em> affect how data is returned from the io-functions or how it is sent in the I/O-protocol, it only affects how the io_device() is to handle Unicode characters towards the &quot;physical&quot; device.</p>
+ <p>The standard shell will be set for either unicode or latin1 encoding when the system is started. The actual encoding is set with the help of the "LANG" or "LC_CTYPE" environment variables on Unix-like system or by other means on other systems. The bottom line is that the user can input Unicode characters and the device will be in {encoding, unicode} mode if the device supports it. The mode can be changed, if the assumption of the runtime system is wrong, by setting this option.</p>
+ <p>The io_device() used when Erlang is started with the "-oldshell" or "-noshell" flags is by default set to latin1 encoding, meaning that any characters beyond codepoint 255 will be escaped and that input is expected to be plain 8-bit ISO-latin-1. If the encoding is changed to Unicode, input and output from the standard file descriptors will be in UTF-8 (regardless of operating system).</p>
+ <p>Files can also be set in {encoding, unicode}, meaning that data is written and read as UTF-8. More encodings are possible for files, see below.</p>
+ <p>{encoding, unicode | latin1} is supported by both the standard shell (group.erl including werl on windows), the 'oldshell' (user.erl) and the file I/O servers.</p>
+ </item>
+ <tag><c>{encoding, utf8 | utf16 | utf32 | {utf16,big} | {utf16,little} | {utf32,big} | {utf32,little}}</c></tag>
+ <item>
+ <p>For disk files, the encoding can be set to various UTF variants. This will have the effect that data is expected to be read as the specified encoding from the file and the data will be written in the specified encoding to the disk file.</p>
+ <p>{encoding, utf8} will have the same effect as {encoding,unicode} on files.</p>
+ <p>The extended encodings are only supported on disk files (opened by the <seealso marker="kernel:file#open/2">file:open/2</seealso> function)</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>write([IoDevice,] Term) -> ok</name>
+ <fsummary>Write a term</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Writes the term <c>Term</c> to the standard output
+ (<c>IoDevice</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>read([IoDevice,] Prompt) -> Result</name>
+ <fsummary>Read a term</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>Result = {ok, Term} | eof | {error, ErrorInfo}</v>
+ <v>&nbsp;Term = term()</v>
+ <v>&nbsp;ErrorInfo -- see section Error Information below</v>
+ </type>
+ <desc>
+ <p>Reads a term <c>Term</c> from the standard input
+ (<c>IoDevice</c>), prompting it with <c>Prompt</c>. It
+ returns:</p>
+ <taglist>
+ <tag><c>{ok, Term}</c></tag>
+ <item>
+ <p>The parsing was successful.</p>
+ </item>
+ <tag><c>eof</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error, ErrorInfo}</c></tag>
+ <item>
+ <p>The parsing failed.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>read(IoDevice, Prompt, StartLine) -> Result</name>
+ <fsummary>Read a term</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>StartLine = int()</v>
+ <v>Result = {ok, Term, EndLine} | {eof, EndLine} | {error, ErrorInfo, EndLine}</v>
+ <v>&nbsp;Term = term()</v>
+ <v>&nbsp;EndLine = int()</v>
+ <v>&nbsp;ErrorInfo -- see section Error Information below</v>
+ </type>
+ <desc>
+ <p>Reads a term <c>Term</c> from <c>IoDevice</c>, prompting it
+ with <c>Prompt</c>. Reading starts at line number
+ <c>StartLine</c>. It returns:</p>
+ <taglist>
+ <tag><c>{ok, Term, EndLine}</c></tag>
+ <item>
+ <p>The parsing was successful.</p>
+ </item>
+ <tag><c>{eof, EndLine}</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error, ErrorInfo, EndLine}</c></tag>
+ <item>
+ <p>The parsing failed.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>fwrite(Format) -></name>
+ <name>fwrite([IoDevice,] Format, Data) -> ok</name>
+ <name>format(Format) -></name>
+ <name>format([IoDevice,] Format, Data) -> ok</name>
+ <fsummary>Write formatted output</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Format = atom() | string() | binary()</v>
+ <v>Data = [term()]</v>
+ </type>
+ <desc>
+ <p>Writes the items in <c>Data</c> (<c>[]</c>) on the standard
+ output (<c>IoDevice</c>) in accordance with <c>Format</c>.
+ <c>Format</c> contains plain characters which are copied to
+ the output device, and control sequences for formatting, see
+ below. If <c>Format</c> is an atom or a binary, it is first
+ converted to a list with the aid of <c>atom_to_list/1</c>
+ or <c>binary_to_list/1</c>.</p>
+ <pre>
+1> <input>io:fwrite("Hello world!~n", []).</input>
+Hello world!
+ok</pre>
+ <p>The general format of a control sequence is <c>~F.P.PadModC</c>.
+ The character <c>C</c> determines the type of control sequence
+ to be used, <c>F</c> and <c>P</c> are optional numeric
+ arguments. If <c>F</c>, <c>P</c>, or <c>Pad</c> is <c>*</c>,
+ the next argument in <c>Data</c> is used as the numeric value
+ of <c>F</c> or <c>P</c>.</p>
+ <p><c>F</c> is the <c>field width</c> of the printed argument. A
+ negative value means that the argument will be left justified
+ within the field, otherwise it will be right justified. If no
+ field width is specified, the required print width will be
+ used. If the field width specified is too small, then the
+ whole field will be filled with <c>*</c> characters.</p>
+ <p><c>P</c> is the <c>precision</c> of the printed argument. A
+ default value is used if no precision is specified. The
+ interpretation of precision depends on the control sequences.
+ Unless otherwise specified, the argument <c>within</c> is used
+ to determine print width.</p>
+ <p><c>Pad</c> is the padding character. This is the character
+ used to pad the printed representation of the argument so that
+ it conforms to the specified field width and precision. Only
+ one padding character can be specified and, whenever
+ applicable, it is used for both the field width and precision.
+ The default padding character is <c>' '</c> (space).</p>
+ <p><c>Mod</c> is the control sequence modifier. It is either a
+ single character (currently only 't', for unicode translation,
+ is supported) that changes the interpretation of Data.</p>
+
+ <p>The following control sequences are available:</p>
+ <taglist>
+ <tag><c>~</c></tag>
+ <item>
+ <p>The character <c>~</c> is written.</p>
+ </item>
+ <tag><c>c</c></tag>
+ <item>
+ <p>The argument is a number that will be interpreted as an
+ ASCII code. The precision is the number of times the
+ character is printed and it defaults to the field width,
+ which in turn defaults to 1. The following example
+ illustrates:</p>
+ <pre>
+2> <input>io:fwrite("|~10.5c|~-10.5c|~5c|~n", [$a, $b, $c]).</input>
+| aaaaa|bbbbb |ccccc|
+ok</pre>
+ <p>If the Unicode translation modifier ('t') is in effect,
+ the integer argument can be any number representing a
+ valid unicode codepoint, otherwise it should be an integer
+ less than or equal to 255, otherwise it is masked with 16#FF:</p>
+<pre>
+1> <input>io:fwrite("~tc~n",[1024]).</input>
+\x{400}
+ok
+2> <input>io:fwrite("~c~n",[1024]).</input>
+^@
+ok</pre>
+
+ </item>
+ <tag><c>f</c></tag>
+ <item>
+ <p>The argument is a float which is written as
+ <c>[-]ddd.ddd</c>, where the precision is the number of
+ digits after the decimal point. The default precision is 6
+ and it cannot be less than 1.</p>
+ </item>
+ <tag><c>e</c></tag>
+ <item>
+ <p>The argument is a float which is written as
+ <c>[-]d.ddde+-ddd</c>, where the precision is the number
+ of digits written. The default precision is 6 and it
+ cannot be less than 2.</p>
+ </item>
+ <tag><c>g</c></tag>
+ <item>
+ <p>The argument is a float which is written as <c>f</c>, if
+ it is &gt;= 0.1 and &lt; 10000.0. Otherwise, it is written
+ in the <c>e</c> format. The precision is the number of
+ significant digits. It defaults to 6 and should not be
+ less than 2. If the absolute value of the float does not
+ allow it to be written in the <c>f</c> format with the
+ desired number of significant digits, it is also written
+ in the <c>e</c> format.</p>
+ </item>
+ <tag><c>s</c></tag>
+ <item>
+ <p>Prints the argument with the <c>string</c> syntax. The
+ argument is, if no Unicode translation modifier is present, an
+ <seealso marker="erts:erlang#iolist_definition">I/O list</seealso>, a binary, or an atom. If the Unicode translation modifier ('t') is in effect, the argument is chardata(), meaning that binaries are in UTF-8. The characters
+ are printed without quotes. In this format, the printed
+ argument is truncated to the given precision and field
+ width.</p>
+ <p>This format can be used for printing any object and
+ truncating the output so it fits a specified field:</p>
+ <pre>
+3> <input>io:fwrite("|~10w|~n", [{hey, hey, hey}]).</input>
+|**********|
+ok
+4> <input>io:fwrite("|~10s|~n", [io_lib:write({hey, hey, hey})]).</input>
+|{hey,hey,h|
+ok</pre>
+ <p>A list with integers larger than 255 is considered an error if the Unicode translation modifier is not given:</p>
+<pre>
+1> <input>io:fwrite("~ts~n",[[1024]]).</input>
+\x{400}
+ok
+2> io:fwrite("~s~n",[[1024]]).
+** exception exit: {badarg,[{io,format,[&lt;0.26.0&gt;,"~s~n",[[1024]]]},
+ ...</pre>
+ </item>
+ <tag><c>w</c></tag>
+ <item>
+ <p>Writes data with the standard syntax. This is used to
+ output Erlang terms. Atoms are printed within quotes if
+ they contain embedded non-printable characters, and
+ floats are printed accurately as the shortest, correctly
+ rounded string.</p>
+ </item>
+ <tag><c>p</c></tag>
+ <item>
+ <p>Writes the data with standard syntax in the same way as
+ <c>~w</c>, but breaks terms whose printed representation
+ is longer than one line into many lines and indents each
+ line sensibly. It also tries to detect lists of printable
+ characters and to output these as strings. For example:</p>
+ <pre>
+5> <input>T = [{attributes,[[{id,age,1.50000},{mode,explicit},</input>
+<input>{typename,"INTEGER"}], [{id,cho},{mode,explicit},{typename,'Cho'}]]},</input>
+<input>{typename,'Person'},{tag,{'PRIVATE',3}},{mode,implicit}].</input>
+...
+6> <input>io:fwrite("~w~n", [T]).</input>
+[{attributes,[[{id,age,1.5},{mode,explicit},{typename,
+[73,78,84,69,71,69,82]}],[{id,cho},{mode,explicit},{typena
+me,'Cho'}]]},{typename,'Person'},{tag,{'PRIVATE',3}},{mode
+,implicit}]
+ok
+7> <input>io:fwrite("~62p~n", [T]).</input>
+[{attributes,[[{id,age,1.5},
+ {mode,explicit},
+ {typename,"INTEGER"}],
+ [{id,cho},{mode,explicit},{typename,'Cho'}]]},
+ {typename,'Person'},
+ {tag,{'PRIVATE',3}},
+ {mode,implicit}]
+ok</pre>
+ <p>The field width specifies the maximum line length. It
+ defaults to 80. The precision specifies the initial
+ indentation of the term. It defaults to the number of
+ characters printed on this line in the <c>same</c> call to
+ <c>io:fwrite</c> or <c>io:format</c>. For example, using
+ <c>T</c> above:</p>
+ <pre>
+8> <input>io:fwrite("Here T = ~62p~n", [T]).</input>
+Here T = [{attributes,[[{id,age,1.5},
+ {mode,explicit},
+ {typename,"INTEGER"}],
+ [{id,cho},
+ {mode,explicit},
+ {typename,'Cho'}]]},
+ {typename,'Person'},
+ {tag,{'PRIVATE',3}},
+ {mode,implicit}]
+ok</pre>
+ </item>
+ <tag><c>W</c></tag>
+ <item>
+ <p>Writes data in the same way as <c>~w</c>, but takes an
+ extra argument which is the maximum depth to which terms
+ are printed. Anything below this depth is replaced with
+ <c>...</c>. For example, using <c>T</c> above:</p>
+ <pre>
+9> <input>io:fwrite("~W~n", [T,9]).</input>
+[{attributes,[[{id,age,1.5},{mode,explicit},{typename,...}],
+[{id,cho},{mode,...},{...}]]},{typename,'Person'},
+{tag,{'PRIVATE',3}},{mode,implicit}]
+ok</pre>
+ <p>If the maximum depth has been reached, then it is
+ impossible to read in the resultant output. Also, the
+ <c>,...</c> form in a tuple denotes that there are more
+ elements in the tuple but these are below the print depth.</p>
+ </item>
+ <tag><c>P</c></tag>
+ <item>
+ <p>Writes data in the same way as <c>~p</c>, but takes an
+ extra argument which is the maximum depth to which terms
+ are printed. Anything below this depth is replaced with
+ <c>...</c>. For example:</p>
+ <pre>
+10> <input>io:fwrite("~62P~n", [T,9]).</input>
+[{attributes,[[{id,age,1.5},{mode,explicit},{typename,...}],
+ [{id,cho},{mode,...},{...}]]},
+ {typename,'Person'},
+ {tag,{'PRIVATE',3}},
+ {mode,implicit}]
+ok</pre>
+ </item>
+ <tag><c>B</c></tag>
+ <item>
+ <p>Writes an integer in base 2..36, the default base is
+ 10. A leading dash is printed for negative integers.</p>
+ <p>The precision field selects base. For example:</p>
+ <pre>
+11> <input>io:fwrite("~.16B~n", [31]).</input>
+1F
+ok
+12> <input>io:fwrite("~.2B~n", [-19]).</input>
+-10011
+ok
+13> <input>io:fwrite("~.36B~n", [5*36+35]).</input>
+5Z
+ok</pre>
+ </item>
+ <tag><c>X</c></tag>
+ <item>
+ <p>Like <c>B</c>, but takes an extra argument that is a
+ prefix to insert before the number, but after the leading
+ dash, if any.</p>
+ <p>The prefix can be a possibly deep list of characters or
+ an atom.</p>
+ <pre>
+14> <input>io:fwrite("~X~n", [31,"10#"]).</input>
+10#31
+ok
+15> <input>io:fwrite("~.16X~n", [-31,"0x"]).</input>
+-0x1F
+ok</pre>
+ </item>
+ <tag><c>#</c></tag>
+ <item>
+ <p>Like <c>B</c>, but prints the number with an Erlang style
+ '#'-separated base prefix.</p>
+ <pre>
+16> <input>io:fwrite("~.10#~n", [31]).</input>
+10#31
+ok
+17> <input>io:fwrite("~.16#~n", [-31]).</input>
+-16#1F
+ok</pre>
+ </item>
+ <tag><c>b</c></tag>
+ <item>
+ <p>Like <c>B</c>, but prints lowercase letters.</p>
+ </item>
+ <tag><c>x</c></tag>
+ <item>
+ <p>Like <c>X</c>, but prints lowercase letters.</p>
+ </item>
+ <tag><c>+</c></tag>
+ <item>
+ <p>Like <c>#</c>, but prints lowercase letters.</p>
+ </item>
+ <tag><c>n</c></tag>
+ <item>
+ <p>Writes a new line.</p>
+ </item>
+ <tag><c>i</c></tag>
+ <item>
+ <p>Ignores the next term.</p>
+ </item>
+ </taglist>
+ <p>Returns:</p>
+ <taglist>
+ <tag><c>ok</c></tag>
+ <item>
+ <p>The formatting succeeded.</p>
+ </item>
+ </taglist>
+ <p>If an error occurs, there is no output. For example:</p>
+ <pre>
+18> <input>io:fwrite("~s ~w ~i ~w ~c ~n",['abc def', 'abc def', {foo, 1},{foo, 1}, 65]).</input>
+abc def 'abc def' {foo,1} A
+ok
+19> <input>io:fwrite("~s", [65]).</input>
+** exception exit: {badarg,[{io,format,[&lt;0.22.0>,"~s","A"]},
+ {erl_eval,do_apply,5},
+ {shell,exprs,6},
+ {shell,eval_exprs,6},
+ {shell,eval_loop,3}]}
+ in function io:o_request/2</pre>
+ <p>In this example, an attempt was made to output the single
+ character '65' with the aid of the string formatting directive
+ "~s".</p>
+ </desc>
+ </func>
+ <func>
+ <name>fread([IoDevice,] Prompt, Format) -> Result</name>
+ <fsummary>Read formatted input</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>Format = string()</v>
+ <v>Result = {ok, Terms} | eof | {error, What}</v>
+ <v>&nbsp;Terms = [term()]</v>
+ <v>&nbsp;What = term()</v>
+ </type>
+ <desc>
+ <p>Reads characters from the standard input (<c>IoDevice</c>),
+ prompting it with <c>Prompt</c>. Interprets the characters in
+ accordance with <c>Format</c>. <c>Format</c> contains control
+ sequences which directs the interpretation of the input.</p>
+ <p><c>Format</c> may contain:</p>
+ <list type="bulleted">
+ <item>
+ <p>White space characters (SPACE, TAB and NEWLINE) which
+ cause input to be read to the next non-white space
+ character.</p>
+ </item>
+ <item>
+ <p>Ordinary characters which must match the next input
+ character.</p>
+ </item>
+ <item>
+
+ <p>Control sequences, which have the general format
+ <c>~*FMC</c>. The character <c>*</c> is an optional
+ return suppression character. It provides a method to
+ specify a field which is to be omitted. <c>F</c> is the
+ <c>field width</c> of the input field, <c>M</c> is an optional
+ translation modifier (of which 't' is the only currently
+ supported, meaning Unicode translation) and <c>C</c>
+ determines the type of control sequence.</p>
+
+ <p>Unless otherwise specified, leading white-space is
+ ignored for all control sequences. An input field cannot
+ be more than one line wide. The following control
+ sequences are available:</p>
+ <taglist>
+ <tag><c>~</c></tag>
+ <item>
+ <p>A single <c>~</c> is expected in the input.</p>
+ </item>
+ <tag><c>d</c></tag>
+ <item>
+ <p>A decimal integer is expected.</p>
+ </item>
+ <tag><c>u</c></tag>
+ <item>
+ <p>An unsigned integer in base 2..36 is expected. The
+ field width parameter is used to specify base. Leading
+ white-space characters are not skipped.</p>
+ </item>
+ <tag><c>-</c></tag>
+ <item>
+ <p>An optional sign character is expected. A sign
+ character '-' gives the return value <c>-1</c>. Sign
+ character '+' or none gives <c>1</c>. The field width
+ parameter is ignored. Leading white-space characters
+ are not skipped.</p>
+ </item>
+ <tag><c>#</c></tag>
+ <item>
+ <p>An integer in base 2..36 with Erlang-style base
+ prefix (for example <c>"16#ffff"</c>) is expected.</p>
+ </item>
+ <tag><c>f</c></tag>
+ <item>
+ <p>A floating point number is expected. It must follow
+ the Erlang floating point number syntax.</p>
+ </item>
+ <tag><c>s</c></tag>
+ <item>
+ <p>A string of non-white-space characters is read. If a
+ field width has been specified, this number of
+ characters are read and all trailing white-space
+ characters are stripped. An Erlang string (list of
+ characters) is returned.</p>
+
+ <p>If Unicode translation is in effect (~ts),
+ characters larger than 255 are accepted, otherwise
+ not. With the translation modifier, the list
+ returned may as a consequence also contain
+ integers larger than 255:</p>
+
+<pre>
+1> <input>io:fread("Prompt> ","~s").</input>
+Prompt> <input>&lt;Characters beyond latin1 range not printable in this medium&gt;</input>
+{error,{fread,string}}
+2> <input>io:fread("Prompt> ","~ts").</input>
+Prompt> <input>&lt;Characters beyond latin1 range not printable in this medium&gt;</input>
+{ok,[[1091,1085,1080,1094,1086,1076,1077]]}</pre>
+
+ </item>
+ <tag><c>a</c></tag>
+ <item>
+ <p>Similar to <c>s</c>, but the resulting string is
+ converted into an atom.</p>
+ <p>The Unicode translation modifier is not allowed (atoms can not contain characters beyond the latin1 range).</p>
+ </item>
+ <tag><c>c</c></tag>
+ <item>
+ <p>The number of characters equal to the field width are
+ read (default is 1) and returned as an Erlang string.
+ However, leading and trailing white-space characters
+ are not omitted as they are with <c>s</c>. All
+ characters are returned.</p>
+ <p>The Unicode translation modifier works as with <c>s</c>:</p>
+<pre>
+1> <input>io:fread("Prompt> ","~c").</input>
+Prompt> <input>&lt;Character beyond latin1 range not printable in this medium&gt;</input>
+{error,{fread,string}}
+2> <input>io:fread("Prompt> ","~tc").</input>
+Prompt> <input>&lt;Character beyond latin1 range not printable in this medium&gt;</input>
+{ok,[[1091]]}</pre>
+
+ </item>
+ <tag><c>l</c></tag>
+ <item>
+ <p>Returns the number of characters which have been
+ scanned up to that point, including white-space
+ characters.</p>
+ </item>
+ </taglist>
+ <p>It returns:</p>
+ <taglist>
+ <tag><c>{ok, Terms}</c></tag>
+ <item>
+ <p>The read was successful and <c>Terms</c> is the list
+ of successfully matched and read items.</p>
+ </item>
+ <tag><c>eof</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error, What}</c></tag>
+ <item>
+ <p>The read operation failed and the parameter
+ <c>What</c> gives a hint about the error.</p>
+ </item>
+ </taglist>
+ </item>
+ </list>
+ <p>Examples:</p>
+ <pre>
+20> <input>io:fread('enter>', "~f~f~f").</input>
+enter><input>1.9 35.5e3 15.0</input>
+{ok,[1.9,3.55e4,15.0]}
+21> <input>io:fread('enter>', "~10f~d").</input>
+enter> <input>5.67899</input>
+{ok,[5.678,99]}
+22> <input>io:fread('enter>', ":~10s:~10c:").</input>
+enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</input> <input>:</input>
+{ok, ["alan", " joe "]}</pre>
+ </desc>
+ </func>
+ <func>
+ <name>rows([IoDevice]) -> {ok,int()} | {error, enotsup}</name>
+ <fsummary>Get the number of rows of a device</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ </type>
+ <desc>
+ <p>Retrieves the number of rows of the
+ <c>IoDevice</c> (i.e. the height of a terminal). The function
+ only succeeds for terminal devices, for all other devices
+ the function returns <c>{error, enotsup}</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>scan_erl_exprs(Prompt) -></name>
+ <name>scan_erl_exprs([IoDevice,] Prompt, StartLine) -> Result</name>
+ <fsummary>Read and tokenize Erlang expressions</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>StartLine = int()</v>
+ <v>Result = {ok, Tokens, EndLine} | {eof, EndLine} | {error, ErrorInfo, EndLine}</v>
+ <v>&nbsp;Tokens -- see erl_scan(3)</v>
+ <v>&nbsp;EndLine = int()</v>
+ <v>&nbsp;ErrorInfo -- see section Error Information below</v>
+ </type>
+ <desc>
+ <p>Reads data from the standard input (<c>IoDevice</c>),
+ prompting it with <c>Prompt</c>. Reading starts at line number
+ <c>StartLine</c> (1). The data is tokenized as if it were a
+ sequence of Erlang expressions until a final <c>'.'</c> is
+ reached. This token is also returned. It returns:</p>
+ <taglist>
+ <tag><c>{ok, Tokens, EndLine}</c></tag>
+ <item>
+ <p>The tokenization succeeded.</p>
+ </item>
+ <tag><c>{eof, EndLine}</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error, ErrorInfo, EndLine}</c></tag>
+ <item>
+ <p>An error occurred.</p>
+ </item>
+ </taglist>
+ <p>Example:</p>
+ <pre>
+23> <input>io:scan_erl_exprs('enter>').</input>
+enter><input>abc(), "hey".</input>
+{ok,[{atom,1,abc},{'(',1},{')',1},{',',1},{string,1,"hey"},{dot,1}],2}
+24> <input>io:scan_erl_exprs('enter>').</input>
+enter><input>1.0er.</input>
+{error,{1,erl_scan,{illegal,float}},2}</pre>
+ </desc>
+ </func>
+ <func>
+ <name>scan_erl_form(Prompt) -></name>
+ <name>scan_erl_form([IoDevice,] Prompt, StartLine) -> Result</name>
+ <fsummary>Read and tokenize an Erlang form</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>StartLine = int()</v>
+ <v>Result = {ok, Tokens, EndLine} | {eof, EndLine} | {error, ErrorInfo, EndLine}</v>
+ <v>&nbsp;Tokens -- see erl_scan(3)</v>
+ <v>&nbsp;EndLine = int()</v>
+ <v>&nbsp;ErrorInfo -- see section Error Information below</v>
+ </type>
+ <desc>
+ <p>Reads data from the standard input (<c>IoDevice</c>),
+ prompting it with <c>Prompt</c>. Starts reading at line number
+ <c>StartLine</c> (1). The data is tokenized as if it were an
+ Erlang form - one of the valid Erlang expressions in an
+ Erlang source file - until a final <c>'.'</c> is reached.
+ This last token is also returned. The return values are the
+ same as for <c>scan_erl_exprs/1,2,3</c> above.</p>
+ </desc>
+ </func>
+ <func>
+ <name>parse_erl_exprs(Prompt) -></name>
+ <name>parse_erl_exprs([IoDevice,] Prompt, StartLine) -> Result</name>
+ <fsummary>Read, tokenize and parse Erlang expressions</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>StartLine = int()</v>
+ <v>Result = {ok, Expr_list, EndLine} | {eof, EndLine} | {error, ErrorInfo, EndLine}</v>
+ <v>&nbsp;Expr_list -- see erl_parse(3)</v>
+ <v>&nbsp;EndLine = int()</v>
+ <v>&nbsp;ErrorInfo -- see section Error Information below</v>
+ </type>
+ <desc>
+ <p>Reads data from the standard input (<c>IoDevice</c>),
+ prompting it with <c>Prompt</c>. Starts reading at line number
+ <c>StartLine</c> (1). The data is tokenized and parsed as if
+ it were a sequence of Erlang expressions until a final '.' is
+ reached. It returns:</p>
+ <taglist>
+ <tag><c>{ok, Expr_list, EndLine}</c></tag>
+ <item>
+ <p>The parsing was successful.</p>
+ </item>
+ <tag><c>{eof, EndLine}</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error, ErrorInfo, EndLine}</c></tag>
+ <item>
+ <p>An error occurred.</p>
+ </item>
+ </taglist>
+ <p>Example:</p>
+ <pre>
+25> <input>io:parse_erl_exprs('enter>').</input>
+enter><input>abc(), "hey".</input>
+{ok, [{call,1,{atom,1,abc},[]},{string,1,"hey"}],2}
+26> <input>io:parse_erl_exprs ('enter>').</input>
+enter><input>abc("hey".</input>
+{error,{1,erl_parse,["syntax error before: ",["'.'"]]},2}</pre>
+ </desc>
+ </func>
+ <func>
+ <name>parse_erl_form(Prompt) -></name>
+ <name>parse_erl_form([IoDevice,] Prompt, StartLine) -> Result</name>
+ <fsummary>Read, tokenize and parse an Erlang form</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Prompt = atom() | string()</v>
+ <v>StartLine = int()</v>
+ <v>Result = {ok, AbsForm, EndLine} | {eof, EndLine} | {error, ErrorInfo, EndLine}</v>
+ <v>&nbsp;AbsForm -- see erl_parse(3)</v>
+ <v>&nbsp;EndLine = int()</v>
+ <v>&nbsp;ErrorInfo -- see section Error Information below</v>
+ </type>
+ <desc>
+ <p>Reads data from the standard input (<c>IoDevice</c>),
+ prompting it with <c>Prompt</c>. Starts reading at line number
+ <c>StartLine</c> (1). The data is tokenized and parsed as if
+ it were an Erlang form - one of the valid Erlang expressions
+ in an Erlang source file - until a final '.' is reached. It
+ returns:</p>
+ <taglist>
+ <tag><c>{ok, AbsForm, EndLine}</c></tag>
+ <item>
+ <p>The parsing was successful.</p>
+ </item>
+ <tag><c>{eof, EndLine}</c></tag>
+ <item>
+ <p>End of file was encountered.</p>
+ </item>
+ <tag><c>{error, ErrorInfo, EndLine}</c></tag>
+ <item>
+ <p>An error occurred.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Standard Input/Output</title>
+ <p>All Erlang processes have a default standard IO device. This
+ device is used when no <c>IoDevice</c> argument is specified in
+ the above function calls. However, it is sometimes desirable to
+ use an explicit <c>IoDevice</c> argument which refers to the
+ default IO device. This is the case with functions that can
+ access either a file or the default IO device. The atom
+ <c>standard_io</c> has this special meaning. The following example
+ illustrates this:</p>
+ <pre>
+27> <input>io:read('enter>').</input>
+enter><input>foo.</input>
+{ok,foo}
+28> <input>io:read(standard_io, 'enter>').</input>
+enter><input>bar.</input>
+{ok,bar}</pre>
+ <p>There is always a process registered under the name of
+ <c>user</c>. This can be used for sending output to the user.</p>
+ </section>
+ <section>
+ <title>Standard Error</title>
+ <p>In certain situations, especially when the standard output is redirected, access to an io_server() specific for error messages might be convenient. The io_device 'standard_error' can be used to direct output to whatever the current operating system considers a suitable device for error output. Example on a Unix-like operating system:</p>
+<pre>
+$ <input>erl -noshell -noinput -eval 'io:format(standard_error,"Error: ~s~n",["error 11"]),init:stop().' > /dev/null</input>
+Error: error 11</pre>
+
+
+
+ </section>
+
+ <section>
+ <title>Error Information</title>
+ <p>The <c>ErrorInfo</c> mentioned above is the standard
+ <c>ErrorInfo</c> structure which is returned from all IO modules.
+ It has the format:</p>
+ <code type="none">
+{ErrorLine, Module, ErrorDescriptor}</code>
+ <p>A string which describes the error is obtained with the following
+ call:</p>
+ <code type="none">
+apply(Module, format_error, ErrorDescriptor)</code>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/io_lib.xml b/lib/stdlib/doc/src/io_lib.xml
new file mode 100644
index 0000000000..399f968c5f
--- /dev/null
+++ b/lib/stdlib/doc/src/io_lib.xml
@@ -0,0 +1,300 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>io_lib</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>io_lib</module>
+ <modulesummary>IO Library Functions</modulesummary>
+ <description>
+ <p>This module contains functions for converting to and from
+ strings (lists of characters). They are used for implementing the
+ functions in the <c>io</c> module. There is no guarantee that the
+ character lists returned from some of the functions are flat,
+ they can be deep lists. <c>lists:flatten/1</c> can be used for
+ flattening deep lists.</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+chars() = [char() | chars()]</code>
+ </section>
+ <funcs>
+ <func>
+ <name>nl() -> chars()</name>
+ <fsummary>Write a newline</fsummary>
+ <desc>
+ <p>Returns a character list which represents a new line
+ character.</p>
+ </desc>
+ </func>
+ <func>
+ <name>write(Term) -></name>
+ <name>write(Term, Depth) -> chars()</name>
+ <fsummary>Write a term</fsummary>
+ <type>
+ <v>Term = term()</v>
+ <v>Depth = int()</v>
+ </type>
+ <desc>
+ <p>Returns a character list which represents <c>Term</c>. The
+ <c>Depth</c> (-1) argument controls the depth of the
+ structures written. When the specified depth is reached,
+ everything below this level is replaced by "...". For
+ example:</p>
+ <pre>
+1> <input>lists:flatten(io_lib:write({1,[2],[3],[4,5],6,7,8,9})).</input>
+"{1,[2],[3],[4,5],6,7,8,9}"
+2> <input>lists:flatten(io_lib:write({1,[2],[3],[4,5],6,7,8,9}, 5)).</input>
+"{1,[2],[3],[...],...}"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>print(Term) -></name>
+ <name>print(Term, Column, LineLength, Depth) -> chars()</name>
+ <fsummary>Pretty print a term</fsummary>
+ <type>
+ <v>Term = term()</v>
+ <v>Column = LineLenght = Depth = int()</v>
+ </type>
+ <desc>
+ <p>Also returns a list of characters which represents
+ <c>Term</c>, but breaks representations which are longer than
+ one line into many lines and indents each line sensibly. It
+ also tries to detect and output lists of printable characters
+ as strings. <c>Column</c> is the starting column (1),
+ <c>LineLength</c> the maximum line length (80), and
+ <c>Depth</c> (-1) the maximum print depth.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fwrite(Format, Data) -></name>
+ <name>format(Format, Data) -> chars() | UnicodeList</name>
+ <fsummary>Write formatted output</fsummary>
+ <type>
+ <v>Format = atom() | string() | binary()</v>
+ <v>Data = [term()]</v>
+ <v>UnicodeList = [Unicode]</v>
+ <v>Unicode = int() representing valid unicode codepoint</v>
+ </type>
+ <desc>
+ <p>Returns a character list which represents <c>Data</c>
+ formatted in accordance with <c>Format</c>. See
+ <seealso marker="io#fwrite/1">io:fwrite/1,2,3</seealso> for a detailed
+ description of the available formatting options. A fault is
+ generated if there is an error in the format string or
+ argument list.</p>
+
+ <p>If (and only if) the Unicode translation modifier is used
+ in the format string (i.e. ~ts or ~tc), the resulting list
+ may contain characters beyond the ISO-latin-1 character
+ range (in other words, numbers larger than 255). If so, the
+ result is not an ordinary Erlang string(), but can well be
+ used in any context where Unicode data is allowed.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>fread(Format, String) -> Result</name>
+ <fsummary>Read formatted input</fsummary>
+ <type>
+ <v>Format = String = string()</v>
+ <v>Result = {ok, InputList, LeftOverChars} | {more, RestFormat, Nchars, InputStack} | {error, What}</v>
+ <v>&nbsp;InputList = chars()</v>
+ <v>&nbsp;LeftOverChars = string()</v>
+ <v>&nbsp;RestFormat = string()</v>
+ <v>&nbsp;Nchars = int()</v>
+ <v>&nbsp;InputStack = chars()</v>
+ <v>&nbsp;What = term()</v>
+ </type>
+ <desc>
+ <p>Tries to read <c>String</c> in accordance with the control
+ sequences in <c>Format</c>. See
+ <seealso marker="io#fread/3">io:fread/3</seealso> for a detailed
+ description of the available formatting options. It is
+ assumed that <c>String</c> contains whole lines. It returns:</p>
+ <taglist>
+ <tag><c>{ok, InputList, LeftOverChars}</c></tag>
+ <item>
+ <p>The string was read. <c>InputList</c> is the list of
+ successfully matched and read items, and
+ <c>LeftOverChars</c> are the input characters not used.</p>
+ </item>
+ <tag><c>{more, RestFormat, Nchars, InputStack}</c></tag>
+ <item>
+ <p>The string was read, but more input is needed in order
+ to complete the original format string. <c>RestFormat</c>
+ is the remaining format string, <c>NChars</c> the number
+ of characters scanned, and <c>InputStack</c> is the
+ reversed list of inputs matched up to that point.</p>
+ </item>
+ <tag><c>{error, What}</c></tag>
+ <item>
+ <p>The read operation failed and the parameter <c>What</c>
+ gives a hint about the error.</p>
+ </item>
+ </taglist>
+ <p>Example:</p>
+ <pre>
+3> <input>io_lib:fread("~f~f~f", "15.6 17.3e-6 24.5").</input>
+{ok,[15.6,1.73e-5,24.5],[]}</pre>
+ </desc>
+ </func>
+ <func>
+ <name>fread(Continuation, String, Format) -> Return</name>
+ <fsummary>Re-entrant formatted reader</fsummary>
+ <type>
+ <v>Continuation = see below</v>
+ <v>String = Format = string()</v>
+ <v>Return = {done, Result, LeftOverChars} | {more, Continuation}</v>
+ <v>&nbsp;Result = {ok, InputList} | eof | {error, What}</v>
+ <v>&nbsp;&nbsp;InputList = chars()</v>
+ <v>&nbsp;&nbsp;What = term()()</v>
+ <v>&nbsp;LeftOverChars = string()</v>
+ </type>
+ <desc>
+ <p>This is the re-entrant formatted reader. The continuation of
+ the first call to the functions must be <c>[]</c>. Refer to
+ Armstrong, Virding, Williams, 'Concurrent Programming in
+ Erlang', Chapter 13 for a complete description of how the
+ re-entrant input scheme works.</p>
+ <p>The function returns:</p>
+ <taglist>
+ <tag><c>{done, Result, LeftOverChars}</c></tag>
+ <item>
+ <p>The input is complete. The result is one of the
+ following:</p>
+ <taglist>
+ <tag><c>{ok, InputList}</c></tag>
+ <item>
+ <p>The string was read. <c>InputList</c> is the list of
+ successfully matched and read items, and
+ <c>LeftOverChars</c> are the remaining characters.</p>
+ </item>
+ <tag><c>eof</c></tag>
+ <item>
+ <p>End of file has been encountered.
+ <c>LeftOverChars</c> are the input characters not
+ used.</p>
+ </item>
+ <tag><c>{error, What}</c></tag>
+ <item>
+ <p>An error occurred and the parameter <c>What</c> gives
+ a hint about the error.</p>
+ </item>
+ </taglist>
+ </item>
+ <tag><c>{more, Continuation}</c></tag>
+ <item>
+ <p>More data is required to build a term.
+ <c>Continuation</c> must be passed to <c>fread/3</c>,
+ when more data becomes available.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>write_atom(Atom) -> chars()</name>
+ <fsummary>Write an atom</fsummary>
+ <type>
+ <v>Atom = atom()</v>
+ </type>
+ <desc>
+ <p>Returns the list of characters needed to print the atom
+ <c>Atom</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>write_string(String) -> chars()</name>
+ <fsummary>Write a string</fsummary>
+ <type>
+ <v>String = string()</v>
+ </type>
+ <desc>
+ <p>Returns the list of characters needed to print <c>String</c>
+ as a string.</p>
+ </desc>
+ </func>
+ <func>
+ <name>write_char(Integer) -> chars()</name>
+ <fsummary>Write a character</fsummary>
+ <type>
+ <v>Integer = int()</v>
+ </type>
+ <desc>
+ <p>Returns the list of characters needed to print a character
+ constant in the ISO-latin-1 character set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>indentation(String, StartIndent) -> int()</name>
+ <fsummary>Indentation after printing string</fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>StartIndent = int()</v>
+ </type>
+ <desc>
+ <p>Returns the indentation if <c>String</c> has been printed,
+ starting at <c>StartIndent</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>char_list(Term) -> bool()</name>
+ <fsummary>Test for a list of characters</fsummary>
+ <type>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Term</c> is a flat list of
+ characters in the ISO-latin-1 range, otherwise it returns <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>deep_char_list(Term) -> bool()</name>
+ <fsummary>Test for a deep list of characters</fsummary>
+ <type>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Term</c> is a, possibly deep, list
+ of characters in the ISO-latin-1 range, otherwise it returns <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>printable_list(Term) -> bool()</name>
+ <fsummary>Test for a list of printable ISO-latin-1 characters</fsummary>
+ <type>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Term</c> is a flat list of
+ printable ISO-latin-1 characters, otherwise it returns <c>false</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/io_protocol.xml b/lib/stdlib/doc/src/io_protocol.xml
new file mode 100644
index 0000000000..1b75114031
--- /dev/null
+++ b/lib/stdlib/doc/src/io_protocol.xml
@@ -0,0 +1,861 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>1999</year>
+ <year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>The Erlang I/O-protocol</title>
+ <prepared>Patrik Nyblom</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2009-02-25</date>
+ <rev>PA1</rev>
+ <file>io_protocol.xml</file>
+ </header>
+
+
+<p>The I/O-protocol in Erlang specifies a way for a client to communicate
+with an io_server and vice versa. The io_server is a process handling the
+requests and that performs the requested task on i.e. a device. The
+client is any Erlang process wishing to read or write data from/to the
+device.</p>
+
+<p>The common I/O-protocol has been present in OTP since the
+beginning, but has been fairly undocumented and has also somewhat
+evolved over the years. In an addendum to Robert Virdings rationale
+the original I/O-protocol is described. This document describes the
+current I/O-protocol.</p>
+
+<p>The original I/O-protocol was simple and flexible. Demands for spacial
+and execution time efficiency has triggered extensions to the protocol
+over the years, making the protocol larger and somewhat less easy to
+implement than the original. It can certainly be argumented that the
+current protocol is to complex, but this text describes how it looks
+today, not how it should have looked.</p>
+
+<p>The basic ideas from the original protocol still holds. The io_server
+and client communicate with one single, rather simplistic protocol and
+no server state is ever present in the client. Any io_server can be
+used together with any client code and client code need not be aware
+of the actual device the io_server communicates with.</p>
+
+<section>
+<title>Protocol basics</title>
+
+<p>As described in Roberts paper, servers and clients communicate using
+io_request/io_reply tuples as follows:</p>
+
+<p><em>{io_request, From, ReplyAs, Request}</em><br/>
+<em>{io_reply, ReplyAs, Reply}</em></p>
+
+<p>The client sends an io_request to the io_server and the server
+eventually sends a corresponding reply.</p>
+
+<list type="bulleted">
+<item>From is the pid() of the client, the process which the io_server
+sends the reply to.</item>
+
+<item>ReplyAs can be any datum and is simply returned in the corresponding
+io_reply. The io-module in the Erlang standard library simply uses the pid()
+of the io_server as the ReplyAs datum, but a more complicated client
+could have several outstanding io-requests to the same server and
+would then use i.e. a ref() or something else to differentiate among
+the incoming io_reply's. The ReplyAs element should be considered
+opaque by the io_server. Note that the pid() of the server is not
+explicitly present in the io_reply. The reply can be sent from any
+process, not necessarily the actual io_server. The ReplyAs element is
+the only thing that connects one io_request with an io_reply.</item>
+
+<item>Request and Reply are described below.</item>
+</list>
+
+<p>When an io_server receives an io_request, it acts upon the actual
+Request part and eventually sends an io_reply with the corresponding
+Reply part.</p>
+</section>
+<section>
+<title>Output requests</title>
+
+<p>To output characters on a device, the following Requests exist:</p>
+
+<p>
+<em>{put_chars, Encoding, Characters}</em><br/>
+<em>{put_chars, Encoding, Module, Function, Args}</em>
+</p>
+<list type="bulleted">
+<item>Encoding is either 'latin1' or 'unicode', meaning that the
+ characters are (in case of binaries) encoded as either UTF-8 or
+ iso-latin-1 (pure bytes). A well behaved io_server should also
+ return error if list elements contain integers > 255 when the
+ Encoding is set to latin1. Note that this does not in any way tell
+ how characters should be put on the actual device or how the
+ io_server should handle them. Different io_servers may handle the
+ characters however they want, this simply tells the io_server which
+ format the data is expected to have. In the Module/Function/argument
+ case, the Encoding tells which format the designated function
+ produces. Note that byte-oriented data is simplest sent using latin1
+ Encoding</item>
+
+<item>Characters are the data to be put on the device. If encoding is
+ latin1, this is an iolist(). If encoding is unicode, this is an
+ Erlang standard mixed unicode list (one integer in a list per
+ character, characters in binaries represented as UTF-8).</item>
+
+<item>Module, Function, Args denotes a function which will be called to
+ produce the data (like io_lib:format), Args is a list of arguments
+ to the function. The function should produce data in the given
+ Encoding. The io_server should call the function as apply(Mod, Func,
+ Args) and will put the returned data on the device as if it was sent
+ in a {put_chars, Encoding, Characters} request. If the function
+ returns anything else than a binary or list or throws an exception,
+ an error should be sent back to the client.</item>
+</list>
+
+<p>The server replies to the client with an io_reply where the Reply
+element is one of:</p>
+<p>
+<em>ok</em><br/>
+<em>{error, Error}</em>
+</p>
+
+<list type="bulleted">
+<item>Error describes the error to the client, which may do whatever it
+ wants with it. The Erlang io-module typically returns it as is.</item>
+</list>
+
+<p>For backward compatibility the following Requests should also be
+handled by an io_server (these messages should not be present after
+R15B of OTP):</p>
+<p>
+<em>{put_chars, Characters}</em><br/>
+<em>{put_chars, Module, Function, Args}</em>
+</p>
+
+<p>These should behave as {put_chars, latin1, Characters} and {put_chars,
+latin1, Module, Function, Args} respectively. </p>
+</section>
+<section>
+<title>Input Requests</title>
+
+<p>To read characters from a device, the following Requests exist:</p>
+
+<p><em>{get_until, Encoding, Prompt, Module, Function, ExtraArgs}</em></p>
+
+<list type="bulleted">
+<item>Encoding denotes how data is to be sent back to the client and
+ what data is sent to the function denoted by
+ Module/Function/Arity. If the function supplied returns data as a
+ list, the data is converted to this encoding. If however the
+ function supplied returns data in some other format, no conversion
+ can be done and it's up to the client supplied function to return
+ data in a proper way. If Encoding is latin1, lists of integers
+ 0..255 or binaries containing plain bytes are sent back to the
+ client when possible, if Encoding is unicode, lists with integers in
+ the whole unicode range or binaries encoded in UTF-8 are sent to the
+ client. The user supplied function will always see lists of integers, never
+ binaries, but the list may contain numbers > 255 if the Encoding is
+ 'unicode'.</item>
+
+<item>Prompt is a list of characters (not mixed, no binaries) or an atom()
+ to be output as a prompt for input on the device. The Prompt is
+ often ignored by the io_server and a Prompt set to '' should always
+ be ignored (and result in nothing being written to the device). </item>
+
+<item><p>Module, Function, ExtraArgs denotes a function and arguments to
+ determine when enough data is written. The function should take two
+ additional arguments, the last state, and a list of characters. The
+ function should return one of:</p>
+<p>
+<em>{done, Result, RestChars}</em><br/>
+<em>{more, Continuation}</em>
+</p>
+ <p>The Result can be any Erlang term, but if it is a list(), the
+ io_server may convert it to a binary() of appropriate format before
+ returning it to the client, if the server is set in binary mode (see
+ below).</p>
+
+ <p>The function will be called with the data the io_server finds on
+ it's device, returning {done, Result, RestChars} when enough data is
+ read (in which case Result is sent to the client and RestChars are
+ kept in the io_server as a buffer for subsequent input) or {more,
+ Continuation}, indicating that more characters are needed to
+ complete the request. The Continuation will be sent as the state in
+ subsequent calls to the function when more characters are
+ available. When no more characters are available, the function
+ shall return {done,eof,Rest}.
+ The initial state is the empty list and the data when an
+ end of file is reached on the device is the atom 'eof'. An emulation
+ of the get_line request could be (inefficiently) implemented using
+ the following functions:</p>
+<code>
+-module(demo).
+-export([until_newline/3, get_line/1]).
+
+until_newline(_ThisFar,eof,_MyStopCharacter) -&gt;
+ {done,eof,[]};
+until_newline(ThisFar,CharList,MyStopCharacter) -&gt;
+ case lists:splitwith(fun(X) -&gt; X =/= MyStopCharacter end, CharList) of
+ {L,[]} -&gt;
+ {more,ThisFar++L};
+ {L2,[MyStopCharacter|Rest]} -&gt;
+ {done,ThisFar++L2++[MyStopCharacter],Rest}
+ end.
+
+get_line(IoServer) -&gt;
+ IoServer ! {io_request, self(), IoServer, {get_until, unicode, '',
+ ?MODULE, until_newline, [$\n]}},
+ receive
+ {io_reply, IoServer, Data} -&gt;
+ Data
+ end.
+</code>
+ <p>Note especially that the last element in the Request tuple ([$\n])
+ is appended to the argument list when the function is called. The
+ function should be called like
+ apply(Module, Function, [ State, Data | ExtraArgs ]) by the io_server</p>
+</item>
+</list>
+
+<p>A defined number of characters is requested using this Request:</p>
+<p>
+<em>{get_chars, Encoding, Prompt, N}</em>
+</p>
+
+<list type="bulleted">
+<item>Encoding and Prompt as for get_until.</item>
+
+<item>N is the number of characters to be read from the device.</item>
+</list>
+
+<p>A single line (like in the example above) is requested with this Request:</p>
+<p>
+<em>{get_line, Encoding, Prompt}</em>
+</p>
+
+<list type="bulleted">
+<item>Encoding and prompt as above.</item>
+</list>
+
+<p>Obviously, get_chars and get_line could be implemented with the
+get_until request (and indeed was originally), but demands for
+efficiency has made these additions necessary.</p>
+
+<p>The server replies to the client with an io_reply where the Reply
+element is one of:</p>
+<p>
+<em>Data</em><br/>
+<em>eof</em><br/>
+<em>{error, Error}</em>
+</p>
+
+<list type="bulleted">
+<item>Data is the characters read, in either list or binary form
+ (depending on the io_server mode, see below).</item>
+<item>Error describes the error to the client, which may do whatever it
+ wants with it. The Erlang io-module typically returns it as is.</item>
+<item>eof is returned when input end is reached and no more data is
+available to the client process.</item>
+</list>
+
+<p>For backward compatibility the following Requests should also be
+handled by an io_server (these messages should not be present after
+R15B of OTP):</p>
+
+<p>
+<em>{get_until, Prompt, Module, Function, ExtraArgs}</em><br/>
+<em>{get_chars, Prompt, N}</em><br/>
+<em>{get_line, Prompt}</em><br/>
+</p>
+
+<p>These should behave as {get_until, latin1, Prompt, Module, Function,
+ExtraArgs}, {get_chars, latin1, Prompt, N} and {get_line, latin1,
+Prompt} respectively.</p>
+</section>
+<section>
+<title>I/O-server modes</title>
+
+<p>Demands for efficiency when reading data from an io_server has not
+only lead to the addition of the get_line and get_chars requests, but
+has also added the concept of io_server options. No options are
+mandatory to implement, but all io_servers in the Erlang standard
+libraries honor the 'binary' option, which allows the Data in the
+io_reply to be binary instead of in list form <em>when possible</em>.
+If the data is sent as a binary, Unicode data will be sent in the
+standard Erlang unicode
+format, i.e. UTF-8 (note that the function in get_until still gets
+list data regardless of the io_server mode).</p>
+
+<p>Note that i.e. the <c>get_until</c> request allows for a function with the data specified as always being a list. Also the return value data from such a function can be of any type (as is indeed the case when an io:fread request is sent to an io_server). The client has to be prepared for data received as answers to those requests to be in a variety of forms, but the server should convert the results to binaries whenever possible (i.e. when the function supplied to get_until actually returns a list). The example shown later in this text does just that.</p>
+
+<p>An I/O-server in binary mode will affect the data sent to the client,
+so that it has to be able to handle binary data. For convenience, it
+is possible to set and retrieve the modes of an io_server using the
+following I/O-requests:</p>
+
+<p>
+<em>{setopts, Opts}</em>
+</p>
+
+
+<list type="bulleted">
+<item>Opts is a list of options in the format recognized by proplists (and
+ of course by the io_server itself).</item>
+</list>
+<p>As an example, the io_server for the interactive shell (in group.erl)
+understands the following options:</p>
+<p>
+<em>{binary, bool()} (or 'binary'/'list')</em><br/>
+<em>{echo, bool()}</em><br/>
+<em>{expand_fun, fun()}</em><br/>
+<em>{encoding, 'unicode'/'latin1'} (or 'unicode'/'latin1')</em>
+</p>
+
+<p>- of which the 'binary' and 'encoding' options are common for all
+io_servers in OTP, while 'echo' and 'expand' is valid only for this
+io_server. It's worth noting that the 'unicode' option notifies how
+characters are actually put on the physical device, i.e. if the
+terminal per se is unicode aware, it does not affect how characters
+are sent in the I/O-protocol, where each request contains encoding
+information for the provided or returned data.</p>
+
+<p>The server should send one of the following as Reply:</p>
+<p>
+<em>ok</em><br/>
+<em>{error, Error}</em>
+</p>
+
+<p>An error (preferably enotsup) is to be expected if the option is
+not supported by the io_server (like if an 'echo' option is sent in a
+setopt Request to a plain file).</p>
+
+<p>To retrieve options, this message is used:</p>
+<p>
+<em>getopts</em>
+</p>
+
+<p>The 'getopts' message requests a complete list of all options
+supported by the io_server as well as their current values.</p>
+
+<p>The server replies:</p>
+<p>
+<em>OptList</em><br/>
+<em>{error,Error}</em>
+</p>
+
+<list type="bulleted">
+<item>OptList is a list of tuples {Option, Value} where Option is always
+ an atom.</item>
+</list>
+</section>
+<section>
+<title>Multiple I/O requests</title>
+
+<p>The Request element can in itself contain several Requests by using
+the following format:</p>
+<p>
+<em>{requests, Requests}</em>
+</p>
+<list type="bulleted">
+<item>Requests is a list of valid Request tuples for the protocol, they
+ shall be executed in the order in which they appear in the list and
+ the execution should continue until one of the requests result in an
+ error or the list is consumed. The result of the last request is
+ sent back to the client.</item>
+</list>
+
+<p>The server can for a list of requests send any of the valid results in
+the reply:</p>
+
+<p>
+<em>ok</em><br/>
+<em>{ok, Data}</em><br/>
+<em>{ok, Options}</em><br/>
+<em>{error, Error}</em>
+</p>
+<p>- depending on the actual requests in the list.</p>
+</section>
+<section>
+<title>Optional I/O-requests</title>
+
+<p>The following I/O request is optional to implement and a client
+should be prepared for an error return:</p>
+<p>
+<em>{get_geometry, Geometry}</em>
+</p>
+<list type="bulleted">
+<item>Geometry is either the atom 'rows' or the atom 'columns'.</item>
+</list>
+<p>The server should send the Reply as:</p>
+<p>
+<em>{ok, N}</em><br/>
+<em>{error, Error}</em>
+</p>
+
+<list type="bulleted">
+<item>N is the number of character rows or columns the device has, if
+ applicable to the device the io_server handles, otherwise {error,
+ enotsup} is a good answer.</item>
+</list>
+</section>
+<section>
+<title>Unimplemented request types:</title>
+
+<p>If an io_server encounters a request it does not recognize (i.e. the
+io_request tuple is in the expected format, but the actual Request is
+unknown), the server should send a valid reply with the error tuple:</p>
+<p>
+<em>{error, request}</em>
+</p>
+
+<p>This makes it possible to extend the protocol with optional messages
+and for the clients to be somewhat backwards compatible.</p>
+</section>
+<section>
+<title>An annotated and working example io_server:</title>
+
+<p>An io_server is any process capable of handling the protocol. There is
+no generic io_server behavior, but could well be. The framework is
+simple enough, a process handling incoming requests, usually both
+io_requests and other device-specific requests (for i.e. positioning ,
+closing etc.).</p>
+
+<p>Our example io_server stores characters in an ets table, making up a
+fairly crude ram-file (it is probably not useful, but working).</p>
+
+<p>The module begins with the usual directives, a function to start the
+server and a main loop handling the requests:</p>
+
+<code>
+-module(ets_io_server).
+
+-export([start_link/0, init/0, loop/1, until_newline/3, until_enough/3]).
+
+-define(CHARS_PER_REC, 10).
+
+-record(state, {
+ table,
+ position, % absolute
+ mode % binary | list
+ }).
+
+start_link() -&gt;
+ spawn_link(?MODULE,init,[]).
+
+init() -&gt;
+ Table = ets:new(noname,[ordered_set]),
+ ?MODULE:loop(#state{table = Table, position = 0, mode=list}).
+
+loop(State) -&gt;
+ receive
+ {io_request, From, ReplyAs, Request} -&gt;
+ case request(Request,State) of
+ {Tag, Reply, NewState} when Tag =:= ok; Tag =:= error -&gt;
+ reply(From, ReplyAs, Reply),
+ ?MODULE:loop(NewState);
+ {stop, Reply, _NewState} -&gt;
+ reply(From, ReplyAs, Reply),
+ exit(Reply)
+ end;
+ %% Private message
+ {From, rewind} -&gt;
+ From ! {self(), ok},
+ ?MODULE:loop(State#state{position = 0});
+ _Unknown -&gt;
+ ?MODULE:loop(State)
+ end.
+</code>
+
+<p>The main loop receives messages from the client (which might be using
+the io-module to send requests). For each request the function
+request/2 is called and a reply is eventually sent using the reply/3
+function.</p>
+
+<p>The &quot;private&quot; message {From, rewind} results in the
+current position in the pseudo-file to be reset to 0 (the beginning of
+the &quot;file&quot;). This is a typical example of device-specific
+messages not being part of the I/O-protocol. It is usually a bad idea
+to embed such private messages in io_request tuples, as that might be
+confusing to the reader.</p>
+
+<p>Let's look at the reply function first...</p>
+
+<code>
+
+reply(From, ReplyAs, Reply) -&gt;
+ From ! {io_reply, ReplyAs, Reply}.
+
+</code>
+
+<p>Simple enough, it sends the io_reply tuple back to the client,
+providing the ReplyAs element received in the request along with the
+result of the request, as described above.</p>
+
+<p>Now look at the different requests we need to handle. First the
+requests for writing characters:</p>
+
+<code>
+request({put_chars, Encoding, Chars}, State) -&gt;
+ put_chars(unicode:characters_to_list(Chars,Encoding),State);
+request({put_chars, Encoding, Module, Function, Args}, State) -&gt;
+ try
+ request({put_chars, Encoding, apply(Module, Function, Args)}, State)
+ catch
+ _:_ -&gt;
+ {error, {error,Function}, State}
+ end;
+</code>
+
+<p>The Encoding tells us how the characters in the message are
+represented. We want to store the characters as lists in the
+ets-table, so we convert them to lists using the
+unicode:characters_to_list/2 function. The conversion function
+conveniently accepts the encoding types unicode or latin1, so we can
+use the Encoding parameter directly.</p>
+
+<p>When Module, Function and Arguments are provided, we simply apply it
+and do the same thing with the result as if the data was provided
+directly.</p>
+
+<p>Let's handle the requests for retrieving data too:</p>
+
+<code>
+request({get_until, Encoding, _Prompt, M, F, As}, State) -&gt;
+ get_until(Encoding, M, F, As, State);
+request({get_chars, Encoding, _Prompt, N}, State) -&gt;
+ %% To simplify the code, get_chars is implemented using get_until
+ get_until(Encoding, ?MODULE, until_enough, [N], State);
+request({get_line, Encoding, _Prompt}, State) -&gt;
+ %% To simplify the code, get_line is implemented using get_until
+ get_until(Encoding, ?MODULE, until_newline, [$\\n], State);
+</code>
+
+<p>Here we have cheated a little by more or less only implementing
+get_until and using internal helpers to implement get__chars and
+get_line. In production code, this might be to inefficient, but that
+of course depends on the frequency of the different requests. Before
+we start actually implementing the functions put_chars/2 and
+get_until/5, lets look into the few remaining requests:</p>
+
+<code>
+request({get_geometry,_}, State) -&gt;
+ {error, {error,enotsup}, State};
+request({setopts, Opts}, State) -&gt;
+ setopts(Opts, State);
+request(getopts, State) -&gt;
+ getopts(State);
+request({requests, Reqs}, State) -&gt;
+ multi_request(Reqs, {ok, ok, State});
+</code>
+
+<p>The get_geometry request has no meaning for this io_server, so the
+reply will be {error, enotsup}. The only option we handle is the
+binary/list option, which is done in separate functions.</p>
+
+<p>The multi-request tag (requests) is handled in a separate loop
+function applying the requests in the list one after another,
+returning the last result.</p>
+
+<p>What's left is to handle backward compatibility and the file-module
+(which uses the old requests until backward compatibility with pre-R13
+nodes is no longer needed). Note that the io_server will not work with
+a simple file:write if these are not added:</p>
+
+<code>
+request({put_chars,Chars}, State) -&gt;
+ request({put_chars,latin1,Chars}, State);
+request({put_chars,M,F,As}, State) -&gt;
+ request({put_chars,latin1,M,F,As}, State);
+request({get_chars,Prompt,N}, State) -&gt;
+ request({get_chars,latin1,Prompt,N}, State);
+request({get_line,Prompt}, State) -&gt;
+ request({get_line,latin1,Prompt}, State);
+request({get_until, Prompt,M,F,As}, State) -&gt;
+ request({get_until,latin1,Prompt,M,F,As}, State);
+</code>
+
+<p>Ok, what's left now is to return {error, request} if the request is
+not recognized:</p>
+
+<code>
+request(_Other, State) -&gt;
+ {error, {error, request}, State}.
+</code>
+
+<p>Let's move further and actually handle the different requests, first
+the fairly generic multi-request type:</p>
+
+<code>
+multi_request([R|Rs], {ok, _Res, State}) -&gt;
+ multi_request(Rs, request(R, State));
+multi_request([_|_], Error) -&gt;
+ Error;
+multi_request([], Result) -&gt;
+ Result.
+</code>
+
+<p>We loop through the requests one at the time, stopping when we either
+encounter an error or the list is exhausted. The last return value is
+sent back to the client (it's first returned to the main loop and then
+sent back by the function io_reply).</p>
+
+<p>The getopt and setopt requests is also simple to handle, we just
+change or read our state record:</p>
+
+<code>
+setopts(Opts0,State) -&gt;
+ Opts = proplists:unfold(
+ proplists:substitute_negations(
+ [{list,binary}],
+ Opts0)),
+ case check_valid_opts(Opts) of
+ true -&gt;
+ case proplists:get_value(binary, Opts) of
+ true -&gt;
+ {ok,ok,State#state{mode=binary}};
+ false -&gt;
+ {ok,ok,State#state{mode=binary}};
+ _ -&gt;
+ {ok,ok,State}
+ end;
+ false -&gt;
+ {error,{error,enotsup},State}
+ end.
+check_valid_opts([]) -&gt;
+ true;
+check_valid_opts([{binary,Bool}|T]) when is_boolean(Bool) -&gt;
+ check_valid_opts(T);
+check_valid_opts(_) -&gt;
+ false.
+
+getopts(#state{mode=M} = S) -&gt;
+ {ok,[{binary, case M of
+ binary -&gt;
+ true;
+ _ -&gt;
+ false
+ end}],S}.
+</code>
+
+<p>As a convention, all io_servers handle both {setopts, [binary]},
+{setopts, [list]} and {setopts,[{binary, bool()}]}, hence the trick
+with proplists:substitute_negations/2 and proplists:unfold/1. If
+invalid options are sent to us, we send {error,enotsup} back to the
+client.</p>
+
+<p>The getopts request should return a list of {Option, Value} tuples,
+which has the twofold function of providing both the current values
+and the available options of this io_server. We have only one option,
+and hence return that.</p>
+
+<p>So far our io_server has been fairly generic (except for the rewind
+request handled in the main loop and the creation of an ets table).
+Most io_servers contain code similar to what's above.</p>
+
+<p>To make the example runnable, we now start implementing the actual
+reading and writing of the data to/from the ets-table. First the
+put_chars function:</p>
+
+<code>
+put_chars(Chars, #state{table = T, position = P} = State) -&gt;
+ R = P div ?CHARS_PER_REC,
+ C = P rem ?CHARS_PER_REC,
+ [ apply_update(T,U) || U &lt;- split_data(Chars, R, C) ],
+ {ok, ok, State#state{position = (P + length(Chars))}}.
+</code>
+
+<p>We already have the data as (Unicode) lists and therefore just split
+the list in runs of a predefined size and put each run in the
+table at the current position (and forward). The functions
+split_data/3 and apply_update/2 are implemented below.</p>
+
+<p>Now we want to read data from the table. The get_until function reads
+data and applies the function until it says it's done. The result is
+sent back to the client:</p>
+
+<code>
+get_until(Encoding, Mod, Func, As,
+ #state{position = P, mode = M, table = T} = State) -&gt;
+ case get_loop(Mod,Func,As,T,P,[]) of
+ {done,Data,_,NewP} when is_binary(Data); is_list(Data) -&gt;
+ if
+ M =:= binary -&gt;
+ {ok,
+ unicode:characters_to_binary(Data,unicode,Encoding),
+ State#state{position = NewP}};
+ true -&gt;
+ case check(Encoding,
+ unicode:characters_to_list(Data, unicode)) of
+ {error, _} = E -&gt;
+ {error, E, State};
+ List -&gt;
+ {ok, List,
+ State#state{position = NewP}}
+ end
+ end;
+ {done,Data,_,NewP} -&gt;
+ {ok, Data, State#state{position = NewP}};
+ Error -&gt;
+ {error, Error, State}
+ end.
+
+get_loop(M,F,A,T,P,C) -&gt;
+ {NewP,L} = get(P,T),
+ case catch apply(M,F,[C,L|A]) of
+ {done, List, Rest} -&gt;
+ {done, List, [], NewP - length(Rest)};
+ {more, NewC} -&gt;
+ get_loop(M,F,A,T,NewP,NewC);
+ _ -&gt;
+ {error,F}
+ end.
+</code>
+
+<p>Here we also handle the mode (binary or list) that can be set by
+the setopts request. By default, all OTP io_servers send data back to
+the client as lists, but switching mode to binary might increase
+efficiency if the server handles it in an appropriate way. The
+implementation of get_until is hard to get efficient as the supplied
+function is defined to take lists as arguments, but get_chars and
+get_line can be optimized for binary mode. This example does not
+optimize anything however. It is important though that the returned
+data is of the right type depending on the options set, so we convert
+the lists to binaries in the correct encoding <em>if possible</em>
+before returning. The function supplied in the get_until request may,
+as it's final result return anything, so only functions actually
+returning lists can get them converted to binaries. If the request
+contained the encoding tag unicode, the lists can contain all unicode
+codepoints and the binaries should be in UTF-8, if the encoding tag
+was latin1, the client should only get characters in the range
+0..255. The function check/2 takes care of not returning arbitrary
+unicode codepoints in lists if the encoding was given as latin1. If
+the function did not return a list, the check cannot be performed and
+the result will be that of the supplied function untouched.</p>
+
+<p>Now we are more or less done. We implement the utility functions below
+to actually manipulate the table:</p>
+
+<code>
+check(unicode, List) -&gt;
+ List;
+check(latin1, List) -&gt;
+ try
+ [ throw(not_unicode) || X &lt;- List,
+ X > 255 ],
+ List
+ catch
+ throw:_ -&gt;
+ {error,{cannot_convert, unicode, latin1}}
+ end.
+</code>
+
+<p>The function check takes care of providing an error tuple if unicode
+codepoints above 255 is to be returned if the client requested
+latin1.</p>
+
+<p>The two functions until_newline/3 and until_enough/3 are helpers used
+together with the get_until function to implement get_chars and
+get_line (inefficiently):</p>
+
+<code>
+until_newline([],eof,_MyStopCharacter) -&gt;
+ {done,eof,[]};
+until_newline(ThisFar,eof,_MyStopCharacter) -&gt;
+ {done,ThisFar,[]};
+until_newline(ThisFar,CharList,MyStopCharacter) -&gt;
+ case lists:splitwith(fun(X) -&gt; X =/= MyStopCharacter end, CharList) of
+ {L,[]} -&gt;
+ {more,ThisFar++L};
+ {L2,[MyStopCharacter|Rest]} -&gt;
+ {done,ThisFar++L2++[MyStopCharacter],Rest}
+ end.
+
+until_enough([],eof,_N) -&gt;
+ {done,eof,[]};
+until_enough(ThisFar,eof,_N) -&gt;
+ {done,ThisFar,[]};
+until_enough(ThisFar,CharList,N)
+ when length(ThisFar) + length(CharList) >= N -&gt;
+ {Res,Rest} = my_split(N,ThisFar ++ CharList, []),
+ {done,Res,Rest};
+until_enough(ThisFar,CharList,_N) -&gt;
+ {more,ThisFar++CharList}.
+</code>
+
+<p>As can be seen, the functions above are just the type of functions
+that should be provided in get_until requests.</p>
+
+<p>Now we only need to read and write the table in an appropriate way to
+complete the server:</p>
+
+<code>
+get(P,Tab) -&gt;
+ R = P div ?CHARS_PER_REC,
+ C = P rem ?CHARS_PER_REC,
+ case ets:lookup(Tab,R) of
+ [] -&gt;
+ {P,eof};
+ [{R,List}] -&gt;
+ case my_split(C,List,[]) of
+ {_,[]} -&gt;
+ {P+length(List),eof};
+ {_,Data} -&gt;
+ {P+length(Data),Data}
+ end
+ end.
+
+my_split(0,Left,Acc) -&gt;
+ {lists:reverse(Acc),Left};
+my_split(_,[],Acc) -&gt;
+ {lists:reverse(Acc),[]};
+my_split(N,[H|T],Acc) -&gt;
+ my_split(N-1,T,[H|Acc]).
+
+split_data([],_,_) -&gt;
+ [];
+split_data(Chars, Row, Col) -&gt;
+ {This,Left} = my_split(?CHARS_PER_REC - Col, Chars, []),
+ [ {Row, Col, This} | split_data(Left, Row + 1, 0) ].
+
+apply_update(Table, {Row, Col, List}) -&gt;
+ case ets:lookup(Table,Row) of
+ [] -&gt;
+ ets:insert(Table,{Row, lists:duplicate(Col,0) ++ List});
+ [{Row, OldData}] -&gt;
+ {Part1,_} = my_split(Col,OldData,[]),
+ {_,Part2} = my_split(Col+length(List),OldData,[]),
+ ets:insert(Table,{Row, Part1 ++ List ++ Part2})
+ end.
+</code>
+
+<p>The table is read or written in chunks of ?CHARS_PER_REC, overwriting
+when necessary. The implementation is obviously not efficient, it is
+just working.</p>
+
+<p>This concludes the example. It is fully runnable and you can read or
+write to the io_server by using i.e. the io_module or even the file
+module. It's as simple as that to implement a fully fledged io_server
+in Erlang.</p>
+</section>
+</chapter>
+
+
diff --git a/lib/stdlib/doc/src/lib.xml b/lib/stdlib/doc/src/lib.xml
new file mode 100644
index 0000000000..046add48e8
--- /dev/null
+++ b/lib/stdlib/doc/src/lib.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>lib</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>lib</module>
+ <modulesummary>A number of useful library functions</modulesummary>
+ <description>
+ <warning>
+ <p>This module is retained for compatibility. It may disappear
+ without warning in a future release.</p>
+ </warning>
+ </description>
+ <funcs>
+ <func>
+ <name>flush_receive() -> void()</name>
+ <fsummary>Flush messages</fsummary>
+ <desc>
+ <p>Flushes the message buffer of the current process.</p>
+ </desc>
+ </func>
+ <func>
+ <name>error_message(Format, Args) -> ok</name>
+ <fsummary>Print error message</fsummary>
+ <type>
+ <v>Format = atom() | string() | binary()</v>
+ <v>Args = [term()]</v>
+ </type>
+ <desc>
+ <p>Prints error message <c>Args</c> in accordance with
+ <c>Format</c>. Similar to <c>io:format/2</c>, see
+ <seealso marker="io#fwrite/1">io(3)</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>progname() -> atom()</name>
+ <fsummary>Return name of Erlang start script</fsummary>
+ <desc>
+ <p>Returns the name of the script that started the current
+ Erlang session.</p>
+ </desc>
+ </func>
+ <func>
+ <name>nonl(String1) -> String2</name>
+ <fsummary>Remove last newline</fsummary>
+ <type>
+ <v>String1 = String2 = string()</v>
+ </type>
+ <desc>
+ <p>Removes the last newline character, if any, in
+ <c>String1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send(To, Msg)</name>
+ <fsummary>Send a message</fsummary>
+ <type>
+ <v>To = pid() | Name | {Name,Node}</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Msg = term()</v>
+ </type>
+ <desc>
+ <p>This function to makes it possible to send a message using
+ the <c>apply/3</c> BIF.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sendw(To, Msg)</name>
+ <fsummary>Send a message and wait for an answer</fsummary>
+ <type>
+ <v>To = pid() | Name | {Name,Node}</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Msg = term()</v>
+ </type>
+ <desc>
+ <p>As <c>send/2</c>, but waits for an answer. It is implemented
+ as follows:</p>
+ <code type="none">
+sendw(To, Msg) ->
+ To ! {self(),Msg},
+ receive
+ Reply -> Reply
+ end.</code>
+ <p>The message returned is not necessarily a reply to the
+ message sent.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
new file mode 100644
index 0000000000..39fc05420d
--- /dev/null
+++ b/lib/stdlib/doc/src/lists.xml
@@ -0,0 +1,1196 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>lists</title>
+ <prepared>Robert Virding</prepared>
+ <docno>1</docno>
+ <date>96-09-28</date>
+ <rev>A</rev>
+ </header>
+ <module>lists</module>
+ <modulesummary>List Processing Functions</modulesummary>
+ <description>
+ <p>This module contains functions for list processing. The functions
+ are organized in two groups: those in the first group perform a
+ particular operation on one or more lists, whereas those in the
+ second group are higher-order functions, using a fun as argument
+ to perform an operation on one list.</p>
+ <p>Unless otherwise stated, all functions assume that position
+ numbering starts at 1. That is, the first element of a list is at
+ position 1.</p>
+
+ <p>Whenever an <marker
+ id="ordering_function"></marker><em>ordering function</em>
+ <c>F</c> is expected as argument, it is assumed that the
+ following properties hold of <c>F</c> for all x, y and z:</p>
+ <list type="bulleted">
+ <item><p>if x <c>F</c> y and y <c>F</c> x then x = y (<c>F</c>
+ is antisymmetric);</p>
+ </item>
+ <item><p>if x <c>F</c> y and and y <c>F</c> z then x <c>F</c> z
+ (<c>F</c> is transitive);</p>
+ </item>
+ <item><p>x <c>F</c> y or y <c>F</c> x (<c>F</c> is total).</p>
+ </item>
+ </list>
+ <p>An example of a typical ordering function is less than or equal
+ to, <c>=&lt;/2</c>.</p>
+
+ </description>
+ <funcs>
+ <func>
+ <name>all(Pred, List) -> bool()</name>
+ <fsummary>Return true if all elements in the list satisfy<c>Pred</c></fsummary>
+ <type>
+ <v>Pred = fun(Elem) -> bool()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Pred(Elem)</c> returns
+ <c>true</c> for all elements <c>Elem</c> in <c>List</c>,
+ otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>any(Pred, List) -> bool()</name>
+ <fsummary>Return true if any of the elements in the list satisfies<c>Pred</c></fsummary>
+ <type>
+ <v>Pred = fun(Elem) -> bool()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Pred(Elem)</c> returns
+ <c>true</c> for at least one element <c>Elem</c> in
+ <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>append(ListOfLists) -> List1</name>
+ <fsummary>Append a list of lists</fsummary>
+ <type>
+ <v>ListOfLists = [List]</v>
+ <v>List = List1 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list in which all the sub-lists of
+ <c>ListOfLists</c> have been appended. For example:</p>
+ <pre>
+> <input>lists:append([[1, 2, 3], [a, b], [4, 5, 6]]).</input>
+[1,2,3,a,b,4,5,6]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>append(List1, List2) -> List3</name>
+ <fsummary>Append two lists</fsummary>
+ <type>
+ <v>List1 = List2 = List3 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a new list <c>List3</c> which is made from
+ the elements of <c>List1</c> followed by the elements of
+ <c>List2</c>. For example:</p>
+ <pre>
+> <input>lists:append("abc", "def").</input>
+"abcdef"</pre>
+ <p><c>lists:append(A, B)</c> is equivalent to <c>A ++ B</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>concat(Things) -> string()</name>
+ <fsummary>Concatenate a list of atoms</fsummary>
+ <type>
+ <v>Things = [Thing]</v>
+ <v>&nbsp;Thing = atom() | integer() | float() | string()</v>
+ </type>
+ <desc>
+ <p>Concatenates the text representation of the elements
+ of <c>Things</c>. The elements of <c>Things</c> can be atoms,
+ integers, floats or strings.</p>
+ <pre>
+> <input>lists:concat([doc, '/', file, '.', 3]).</input>
+"doc/file.3"</pre>
+ </desc>
+ </func>
+ <func>
+ <name>delete(Elem, List1) -> List2</name>
+ <fsummary>Delete an element from a list</fsummary>
+ <type>
+ <v>Elem = term()</v>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a copy of <c>List1</c> where the first element
+ matching <c>Elem</c> is deleted, if there is such an
+ element.</p>
+ </desc>
+ </func>
+ <func>
+ <name>dropwhile(Pred, List1) -> List2</name>
+ <fsummary>Drop elements from a list while a predicate is true</fsummary>
+ <type>
+ <v>Pred = fun(Elem) -> bool()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Drops elements <c>Elem</c> from <c>List1</c> while
+ <c>Pred(Elem)</c> returns <c>true</c> and returns
+ the remaining list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>duplicate(N, Elem) -> List</name>
+ <fsummary>Make N copies of element</fsummary>
+ <type>
+ <v>N = int()</v>
+ <v>Elem = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list which contains N copies of the term
+ <c>Elem</c>. For example:</p>
+ <pre>
+> <input>lists:duplicate(5, xx).</input>
+[xx,xx,xx,xx,xx]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>filter(Pred, List1) -> List2</name>
+ <fsummary>Choose elements which satisfy a predicate</fsummary>
+ <type>
+ <v>Pred = fun(Elem) -> bool()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p><c>List2</c> is a list of all elements <c>Elem</c> in
+ <c>List1</c> for which <c>Pred(Elem)</c> returns
+ <c>true</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>flatlength(DeepList) -> int()</name>
+ <fsummary>Length of flattened deep list</fsummary>
+ <type>
+ <v>DeepList = [term() | DeepList]</v>
+ </type>
+ <desc>
+ <p>Equivalent to <c>length(flatten(DeepList))</c>, but more
+ efficient.</p>
+ </desc>
+ </func>
+ <func>
+ <name>flatmap(Fun, List1) -> List2</name>
+ <fsummary>Map and flatten in one pass</fsummary>
+ <type>
+ <v>Fun = fun(A) -> [B]</v>
+ <v>List1 = [A]</v>
+ <v>List2 = [B]</v>
+ <v>&nbsp;A = B = term()</v>
+ </type>
+ <desc>
+ <p>Takes a function from <c>A</c>s to lists of <c>B</c>s, and a
+ list of <c>A</c>s (<c>List1</c>) and produces a list of
+ <c>B</c>s by applying the function to every element in
+ <c>List1</c> and appending the resulting lists.</p>
+ <p>That is, <c>flatmap</c> behaves as if it had been defined as
+ follows:</p>
+ <code type="none">
+flatmap(Fun, List1) ->
+ append(map(Fun, List1))</code>
+ <p>Example:</p>
+ <pre>
+> <input>lists:flatmap(fun(X)->[X,X] end, [a,b,c]).</input>
+[a,a,b,b,c,c]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>flatten(DeepList) -> List</name>
+ <fsummary>Flatten a deep list</fsummary>
+ <type>
+ <v>DeepList = [term() | DeepList]</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a flattened version of <c>DeepList</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>flatten(DeepList, Tail) -> List</name>
+ <fsummary>Flatten a deep list</fsummary>
+ <type>
+ <v>DeepList = [term() | DeepList]</v>
+ <v>Tail = List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a flattened version of <c>DeepList</c> with the tail
+ <c>Tail</c> appended.</p>
+ </desc>
+ </func>
+ <func>
+ <name>foldl(Fun, Acc0, List) -> Acc1</name>
+ <fsummary>Fold a function over a list</fsummary>
+ <type>
+ <v>Fun = fun(Elem, AccIn) -> AccOut</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Calls <c>Fun(Elem, AccIn)</c> on successive elements <c>A</c>
+ of <c>List</c>, starting with <c>AccIn == Acc0</c>.
+ <c>Fun/2</c> must return a new accumulator which is passed to
+ the next call. The function returns the final value of
+ the accumulator. <c>Acc0</c> is returned if the list is empty.
+ For example:</p>
+ <pre>
+> <input>lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]).</input>
+15
+> <input>lists:foldl(fun(X, Prod) -> X * Prod end, 1, [1,2,3,4,5]).</input>
+120</pre>
+ </desc>
+ </func>
+ <func>
+ <name>foldr(Fun, Acc0, List) -> Acc1</name>
+ <fsummary>Fold a function over a list</fsummary>
+ <type>
+ <v>Fun = fun(Elem, AccIn) -> AccOut</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Like <c>foldl/3</c>, but the list is traversed from right to
+ left. For example:</p>
+ <pre>
+> <input>P = fun(A, AccIn) -> io:format("~p ", [A]), AccIn end.</input>
+#Fun&lt;erl_eval.12.2225172&gt;
+> <input>lists:foldl(P, void, [1,2,3]).</input>
+1 2 3 void
+> <input>lists:foldr(P, void, [1,2,3]).</input>
+3 2 1 void</pre>
+ <p><c>foldl/3</c> is tail recursive and would usually be
+ preferred to <c>foldr/3</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>foreach(Fun, List) -> void()</name>
+ <fsummary>Apply a function to each element of a list</fsummary>
+ <type>
+ <v>Fun = fun(Elem) -> void()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Calls <c>Fun(Elem)</c> for each element <c>Elem</c> in
+ <c>List</c>. This function is used for its side effects and
+ the evaluation order is defined to be the same as the order
+ of the elements in the list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keydelete(Key, N, TupleList1) -> TupleList2</name>
+ <fsummary>Delete an element from a list of tuples</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = [Tuple]</v>
+ <v>&nbsp;Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a copy of <c>TupleList1</c> where the first
+ occurrence of a tuple whose <c>N</c>th element compares equal to
+ <c>Key</c> is deleted, if there is such a tuple.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keyfind(Key, N, TupleList) -> Tuple | false</name>
+ <fsummary>Search for an element in a list of tuples</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList = [Tuple]</v>
+ <v>Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Searches the list of tuples <c>TupleList</c> for a
+ tuple whose <c>N</c>th element compares equal to <c>Key</c>.
+ Returns <c>Tuple</c> if such a tuple is found,
+ otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keymap(Fun, N, TupleList1) -> TupleList2</name>
+ <fsummary>Map a function over a list of tuples</fsummary>
+ <type>
+ <v>Fun = fun(Term1) -> Term2</v>
+ <v>&nbsp;Term1 = Term2 = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = [tuple()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of tuples where, for each tuple in
+ <c>TupleList1</c>, the <c>N</c>th element <c>Term1</c> of the tuple
+ has been replaced with the result of calling
+ <c>Fun(Term1)</c>.</p>
+ <p>Examples:</p>
+ <pre>
+> <input>Fun = fun(Atom) -> atom_to_list(Atom) end.</input>
+#Fun&lt;erl_eval.6.10732646&gt;
+2> <input>lists:keymap(Fun, 2, [{name,jane,22},{name,lizzie,20},{name,lydia,15}]).</input>
+[{name,"jane",22},{name,"lizzie",20},{name,"lydia",15}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>keymember(Key, N, TupleList) -> bool()</name>
+ <fsummary>Test for membership of a list of tuples</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList = [Tuple]</v>
+ <v>&nbsp;Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if there is a tuple in <c>TupleList</c>
+ whose <c>N</c>th element compares equal to <c>Key</c>, otherwise
+ <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keymerge(N, TupleList1, TupleList2) -> TupleList3</name>
+ <fsummary>Merge two key-sorted lists of tuples</fsummary>
+ <type>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = TupleList3 = [Tuple]</v>
+ <v>&nbsp;Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>TupleList1</c>
+ and <c>TupleList2</c>. The merge is performed on
+ the <c>N</c>th element of each tuple. Both <c>TupleList1</c> and
+ <c>TupleList2</c> must be key-sorted prior to evaluating this
+ function. When two tuples compare equal, the tuple from
+ <c>TupleList1</c> is picked before the tuple from
+ <c>TupleList2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keyreplace(Key, N, TupleList1, NewTuple) -> TupleList2</name>
+ <fsummary>Replace an element in a list of tuples</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = [Tuple]</v>
+ <v>NewTuple = Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a copy of <c>TupleList1</c> where the first
+ occurrence of a <c>T</c> tuple whose <c>N</c>th element
+ compares equal to <c>Key</c> is replaced with
+ <c>NewTuple</c>, if there is such a tuple <c>T</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keysearch(Key, N, TupleList) -> {value, Tuple} | false</name>
+ <fsummary>Search for an element in a list of tuples</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList = [Tuple]</v>
+ <v>Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Searches the list of tuples <c>TupleList</c> for a
+ tuple whose <c>N</c>th element compares equal to <c>Key</c>.
+ Returns <c>{value, Tuple}</c> if such a tuple is found,
+ otherwise <c>false</c>.</p>
+ <note><p>This function is retained for backward compatibility.
+ The function <c>lists:keyfind/3</c> (introduced in R13A)
+ is in most cases more convenient.</p></note>
+ </desc>
+ </func>
+ <func>
+ <name>keysort(N, TupleList1) -> TupleList2</name>
+ <fsummary>Sort a list of tuples</fsummary>
+ <type>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = [Tuple]</v>
+ <v>&nbsp;Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a list containing the sorted elements of the list
+ <c>TupleList1</c>. Sorting is performed on the <c>N</c>th
+ element of the tuples.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keystore(Key, N, TupleList1, NewTuple) -> TupleList2</name>
+ <fsummary>Store an element in a list of tuples</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = [Tuple]</v>
+ <v>NewTuple = Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a copy of <c>TupleList1</c> where the first
+ occurrence of a tuple <c>T</c> whose <c>N</c>th element
+ compares equal to <c>Key</c> is replaced with
+ <c>NewTuple</c>, if there is such a tuple <c>T</c>. If there
+ is no such tuple <c>T</c> a copy of <c>TupleList1</c> where
+ [<c>NewTuple</c>] has been appended to the end is
+ returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>keytake(Key, N, TupleList1) -> {value, Tuple, TupleList2}
+ | false</name>
+ <fsummary>Extract an element from a list of tuples</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = [Tuple]</v>
+ <v>Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Searches the list of tuples <c>TupleList1</c> for a tuple
+ whose <c>N</c>th element compares equal to <c>Key</c>.
+ Returns <c>{value, Tuple, TupleList2}</c> if such a tuple is
+ found, otherwise <c>false</c>. <c>TupleList2</c> is a copy
+ of <c>TupleList1</c> where the first occurrence of
+ <c>Tuple</c> has been removed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>last(List) -> Last</name>
+ <fsummary>Return last element in a list</fsummary>
+ <type>
+ <v>List = [term()], length(List) > 0</v>
+ <v>Last = term()</v>
+ </type>
+ <desc>
+ <p>Returns the last element in <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>map(Fun, List1) -> List2</name>
+ <fsummary>Map a function over a list</fsummary>
+ <type>
+ <v>Fun = fun(A) -> B</v>
+ <v>List1 = [A]</v>
+ <v>List2 = [B]</v>
+ <v>&nbsp;A = B = term()</v>
+ </type>
+ <desc>
+ <p>Takes a function from <c>A</c>s to <c>B</c>s, and a list of
+ <c>A</c>s and produces a list of <c>B</c>s by applying
+ the function to every element in the list. This function is
+ used to obtain the return values. The evaluation order is
+ implementation dependent.</p>
+ </desc>
+ </func>
+ <func>
+ <name>mapfoldl(Fun, Acc0, List1) -> {List2, Acc1}</name>
+ <fsummary>Map and fold in one pass</fsummary>
+ <type>
+ <v>Fun = fun(A, AccIn) -> {B, AccOut}</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>List1 = [A]</v>
+ <v>List2 = [B]</v>
+ <v>&nbsp;A = B = term()</v>
+ </type>
+ <desc>
+ <p><c>mapfold</c> combines the operations of <c>map/2</c> and
+ <c>foldl/3</c> into one pass. An example, summing
+ the elements in a list and double them at the same time:</p>
+ <pre>
+> <input>lists:mapfoldl(fun(X, Sum) -> {2*X, X+Sum} end,</input>
+<input>0, [1,2,3,4,5]).</input>
+{[2,4,6,8,10],15}</pre>
+ </desc>
+ </func>
+ <func>
+ <name>mapfoldr(Fun, Acc0, List1) -> {List2, Acc1}</name>
+ <fsummary>Map and fold in one pass</fsummary>
+ <type>
+ <v>Fun = fun(A, AccIn) -> {B, AccOut}</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>List1 = [A]</v>
+ <v>List2 = [B]</v>
+ <v>&nbsp;A = B = term()</v>
+ </type>
+ <desc>
+ <p><c>mapfold</c> combines the operations of <c>map/2</c> and
+ <c>foldr/3</c> into one pass.</p>
+ </desc>
+ </func>
+ <func>
+ <name>max(List) -> Max</name>
+ <fsummary>Return maximum element of a list</fsummary>
+ <type>
+ <v>List = [term()], length(List) > 0</v>
+ <v>Max = term()</v>
+ </type>
+ <desc>
+ <p>Returns the first element of <c>List</c> that compares
+ greater than or equal to all other elements of
+ <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>member(Elem, List) -> bool()</name>
+ <fsummary>Test for membership of a list</fsummary>
+ <type>
+ <v>Elem = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Elem</c> matches some element of
+ <c>List</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>merge(ListOfLists) -> List1</name>
+ <fsummary>Merge a list of sorted lists</fsummary>
+ <type>
+ <v>ListOfLists = [List]</v>
+ <v>List = List1 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging all the sub-lists
+ of <c>ListOfLists</c>. All sub-lists must be sorted prior to
+ evaluating this function. When two elements compare equal,
+ the element from the sub-list with the lowest position in
+ <c>ListOfLists</c> is picked before the other element.</p>
+ </desc>
+ </func>
+ <func>
+ <name>merge(List1, List2) -> List3</name>
+ <fsummary>Merge two sorted lists</fsummary>
+ <type>
+ <v>List1 = List2 = List3 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>List1</c> and
+ <c>List2</c>. Both <c>List1</c> and <c>List2</c> must be
+ sorted prior to evaluating this function. When two elements
+ compare equal, the element from <c>List1</c> is picked
+ before the element from <c>List2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>merge(Fun, List1, List2) -> List3</name>
+ <fsummary>Merge two sorted list</fsummary>
+ <type>
+ <v>Fun = fun(A, B) -> bool()</v>
+ <v>List1 = [A]</v>
+ <v>List2 = [B]</v>
+ <v>List3 = [A | B]</v>
+ <v>&nbsp;A = B = term()</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>List1</c> and
+ <c>List2</c>. Both <c>List1</c> and <c>List2</c> must be
+ sorted according to the <seealso
+ marker="#ordering_function">ordering function</seealso>
+ <c>Fun</c> prior to evaluating this function. <c>Fun(A,
+ B)</c> should return <c>true</c> if <c>A</c> compares less
+ than or equal to <c>B</c> in the ordering, <c>false</c>
+ otherwise. When two elements compare equal, the element from
+ <c>List1</c> is picked before the element from
+ <c>List2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>merge3(List1, List2, List3) -> List4</name>
+ <fsummary>Merge three sorted lists</fsummary>
+ <type>
+ <v>List1 = List2 = List3 = List4 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>List1</c>,
+ <c>List2</c> and <c>List3</c>. All of <c>List1</c>,
+ <c>List2</c> and <c>List3</c> must be sorted prior to
+ evaluating this function. When two elements compare equal,
+ the element from <c>List1</c>, if there is such an element,
+ is picked before the other element, otherwise the element
+ from <c>List2</c> is picked before the element from
+ <c>List3</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>min(List) -> Min</name>
+ <fsummary>Return minimum element of a list</fsummary>
+ <type>
+ <v>List = [term()], length(List) > 0</v>
+ <v>Min = term()</v>
+ </type>
+ <desc>
+ <p>Returns the first element of <c>List</c> that compares
+ less than or equal to all other elements of
+ <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>nth(N, List) -> Elem</name>
+ <fsummary>Return the Nth element of a list</fsummary>
+ <type>
+ <v>N = 1..length(List)</v>
+ <v>List = [term()]</v>
+ <v>Elem = term()</v>
+ </type>
+ <desc>
+ <p>Returns the <c>N</c>th element of <c>List</c>. For example:</p>
+ <pre>
+> <input>lists:nth(3, [a, b, c, d, e]).</input>
+c</pre>
+ </desc>
+ </func>
+ <func>
+ <name>nthtail(N, List1) -> Tail</name>
+ <fsummary>Return the Nth tail of a list</fsummary>
+ <type>
+ <v>N = 0..length(List1)</v>
+ <v>List1 = Tail = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the <c>N</c>th tail of <c>List</c>, that is, the sublist of
+ <c>List</c> starting at <c>N+1</c> and continuing up to
+ the end of the list. For example:</p>
+ <pre>
+> <input>lists:nthtail(3, [a, b, c, d, e]).</input>
+[d,e]
+> <input>tl(tl(tl([a, b, c, d, e]))).</input>
+[d,e]
+> <input>lists:nthtail(0, [a, b, c, d, e]).</input>
+[a,b,c,d,e]
+> <input>lists:nthtail(5, [a, b, c, d, e]).</input>
+[]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>partition(Pred, List) -> {Satisfying, NonSatisfying}</name>
+ <fsummary>Partition a list into two lists based on a predicate</fsummary>
+ <type>
+ <v>Pred = fun(Elem) -> bool()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List = Satisfying = NonSatisfying = [term()]</v>
+ </type>
+ <desc>
+ <p>Partitions <c>List</c> into two lists, where the first list
+ contains all elements for which <c>Pred(Elem)</c> returns
+ <c>true</c>, and the second list contains all elements for
+ which <c>Pred(Elem)</c> returns <c>false</c>.</p>
+ <p>Examples:</p>
+ <pre>
+> <input>lists:partition(fun(A) -> A rem 2 == 1 end, [1,2,3,4,5,6,7]).</input>
+{[1,3,5,7],[2,4,6]}
+> <input>lists:partition(fun(A) -> is_atom(A) end, [a,b,1,c,d,2,3,4,e]).</input>
+{[a,b,c,d,e],[1,2,3,4]}</pre>
+ <p>See also <c>splitwith/2</c> for a different way to partition
+ a list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>prefix(List1, List2) -> bool()</name>
+ <fsummary>Test for list prefix</fsummary>
+ <type>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>List1</c> is a prefix of
+ <c>List2</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>reverse(List1) -> List2</name>
+ <fsummary>Reverse a list</fsummary>
+ <type>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list with the top level elements in <c>List1</c>
+ in reverse order.</p>
+ </desc>
+ </func>
+ <func>
+ <name>reverse(List1, Tail) -> List2</name>
+ <fsummary>Reverse a list appending a tail</fsummary>
+ <type>
+ <v>List1 = Tail = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list with the top level elements in <c>List1</c>
+ in reverse order, with the tail <c>Tail</c> appended. For
+ example:</p>
+ <pre>
+> <input>lists:reverse([1, 2, 3, 4], [a, b, c]).</input>
+[4,3,2,1,a,b,c]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>seq(From, To) -> Seq</name>
+ <name>seq(From, To, Incr) -> Seq</name>
+ <fsummary>Generate a sequence of integers</fsummary>
+ <type>
+ <v>From = To = Incr = int()</v>
+ <v>Seq = [int()]</v>
+ </type>
+ <desc>
+ <p>Returns a sequence of integers which starts with <c>From</c>
+ and contains the successive results of adding <c>Incr</c> to
+ the previous element, until <c>To</c> has been reached or
+ passed (in the latter case, <c>To</c> is not an element of
+ the sequence). <c>Incr</c> defaults to 1.</p>
+ <p>Failure: If <c><![CDATA[To<From-Incr]]></c> and <c>Incr</c>
+ is positive, or if <c>To>From-Incr</c> and <c>Incr</c> is
+ negative, or if <c>Incr==0</c> and <c>From/=To</c>.</p>
+ <p>The following equalities hold for all sequences:</p>
+ <code type="none">
+length(lists:seq(From, To)) == To-From+1
+length(lists:seq(From, To, Incr)) == (To-From+Incr) div Incr</code>
+ <p>Examples:</p>
+ <pre>
+> <input>lists:seq(1, 10).</input>
+[1,2,3,4,5,6,7,8,9,10]
+> <input>lists:seq(1, 20, 3).</input>
+[1,4,7,10,13,16,19]
+> <input>lists:seq(1, 0, 1).</input>
+[]
+> <input>lists:seq(10, 6, 4).</input>
+[]
+> <input>lists:seq(1, 1, 0).</input>
+[1]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>sort(List1) -> List2</name>
+ <fsummary>Sort a list</fsummary>
+ <type>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list containing the sorted elements of
+ <c>List1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sort(Fun, List1) -> List2</name>
+ <fsummary>Sort a list</fsummary>
+ <type>
+ <v>Fun = fun(Elem1, Elem2) -> bool()</v>
+ <v>&nbsp;Elem1 = Elem2 = term()</v>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list containing the sorted elements of
+ <c>List1</c>, according to the <seealso
+ marker="#ordering_function">ordering function</seealso>
+ <c>Fun</c>. <c>Fun(A, B)</c> should return <c>true</c> if
+ <c>A</c> compares less than or equal to <c>B</c> in the
+ ordering, <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>split(N, List1) -> {List2, List3}</name>
+ <fsummary>Split a list into two lists</fsummary>
+ <type>
+ <v>N = 0..length(List1)</v>
+ <v>List1 = List2 = List3 = [term()]</v>
+ </type>
+ <desc>
+ <p>Splits <c>List1</c> into <c>List2</c> and <c>List3</c>.
+ <c>List2</c> contains the first <c>N</c> elements and
+ <c>List3</c> the rest of the elements (the <c>N</c>th tail).</p>
+ </desc>
+ </func>
+ <func>
+ <name>splitwith(Pred, List) -> {List1, List2}</name>
+ <fsummary>Split a list into two lists based on a predicate</fsummary>
+ <type>
+ <v>Pred = fun(Elem) -> bool()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List = List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Partitions <c>List</c> into two lists according to
+ <c>Pred</c>. <c>splitwith/2</c> behaves as if it is defined
+ as follows:</p>
+ <code type="none">
+splitwith(Pred, List) ->
+ {takewhile(Pred, List), dropwhile(Pred, List)}.</code>
+ <p>Examples:</p>
+ <pre>
+> <input>lists:splitwith(fun(A) -> A rem 2 == 1 end, [1,2,3,4,5,6,7]).</input>
+{[1],[2,3,4,5,6,7]}
+> <input>lists:splitwith(fun(A) -> is_atom(A) end, [a,b,1,c,d,2,3,4,e]).</input>
+{[a,b],[1,c,d,2,3,4,e]}</pre>
+ <p>See also <c>partition/2</c> for a different way to partition
+ a list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sublist(List1, Len) -> List2</name>
+ <fsummary>Return a sub-list of a certain length, starting at the first position</fsummary>
+ <type>
+ <v>List1 = List2 = [term()]</v>
+ <v>Len = int()</v>
+ </type>
+ <desc>
+ <p>Returns the sub-list of <c>List1</c> starting at position 1
+ and with (max) <c>Len</c> elements. It is not an error for
+ <c>Len</c> to exceed the length of the list -- in that case
+ the whole list is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sublist(List1, Start, Len) -> List2</name>
+ <fsummary>Return a sub-list starting at a given position and with a given number of elements</fsummary>
+ <type>
+ <v>List1 = List2 = [term()]</v>
+ <v>Start = 1..(length(List1)+1)</v>
+ <v>Len = int()</v>
+ </type>
+ <desc>
+ <p>Returns the sub-list of <c>List1</c> starting at <c>Start</c>
+ and with (max) <c>Len</c> elements. It is not an error for
+ <c>Start+Len</c> to exceed the length of the list.</p>
+ <pre>
+> <input>lists:sublist([1,2,3,4], 2, 2).</input>
+[2,3]
+> <input>lists:sublist([1,2,3,4], 2, 5).</input>
+[2,3,4]
+> <input>lists:sublist([1,2,3,4], 5, 2).</input>
+[]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>subtract(List1, List2) -> List3</name>
+ <fsummary>Subtract the element in one list from another list</fsummary>
+ <type>
+ <v>List1 = List2 = List3 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a new list <c>List3</c> which is a copy of
+ <c>List1</c>, subjected to the following procedure: for each
+ element in <c>List2</c>, its first occurrence in <c>List1</c>
+ is deleted. For example:</p>
+ <pre>
+> <input>lists:subtract("123212", "212").</input>
+"312".</pre>
+ <p><c>lists:subtract(A, B)</c> is equivalent to <c>A -- B</c>.</p>
+ <warning><p>The complexity of <c>lists:subtract(A, B)</c> is proportional
+ to <c>length(A)*length(B)</c>, meaning that it will be very slow if
+ both <c>A</c> and <c>B</c> are long lists.
+ (Using ordered lists and
+ <seealso marker="ordsets#subtract/2">ordsets:subtract/2</seealso>
+ is a much better choice if both lists are long.)</p></warning>
+ </desc>
+ </func>
+ <func>
+ <name>suffix(List1, List2) -> bool()</name>
+ <fsummary>Test for list suffix</fsummary>
+ <desc>
+ <p>Returns <c>true</c> if <c>List1</c> is a suffix of
+ <c>List2</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sum(List) -> number()</name>
+ <fsummary>Return sum of elements in a list</fsummary>
+ <type>
+ <v>List = [number()]</v>
+ </type>
+ <desc>
+ <p>Returns the sum of the elements in <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>takewhile(Pred, List1) -> List2</name>
+ <fsummary>Take elements from a list while a predicate is true</fsummary>
+ <type>
+ <v>Pred = fun(Elem) -> bool()</v>
+ <v>&nbsp;Elem = term()</v>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Takes elements <c>Elem</c> from <c>List1</c> while
+ <c>Pred(Elem)</c> returns <c>true</c>, that is,
+ the function returns the longest prefix of the list for which
+ all elements satisfy the predicate.</p>
+ </desc>
+ </func>
+ <func>
+ <name>ukeymerge(N, TupleList1, TupleList2) -> TupleList3</name>
+ <fsummary>Merge two key-sorted lists of tuples, removing duplicates</fsummary>
+ <type>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = TupleList3 = [Tuple]</v>
+ <v>&nbsp;Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>TupleList1</c>
+ and <c>TupleList2</c>. The merge is performed on the
+ <c>N</c>th element of each tuple. Both <c>TupleList1</c> and
+ <c>TupleList2</c> must be key-sorted without duplicates
+ prior to evaluating this function. When two tuples compare
+ equal, the tuple from <c>TupleList1</c> is picked and the
+ one from <c>TupleList2</c> deleted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>ukeysort(N, TupleList1) -> TupleList2</name>
+ <fsummary>Sort a list of tuples, removing duplicates</fsummary>
+ <type>
+ <v>N = 1..tuple_size(Tuple)</v>
+ <v>TupleList1 = TupleList2 = [Tuple]</v>
+ <v>&nbsp;Tuple = tuple()</v>
+ </type>
+ <desc>
+ <p>Returns a list containing the sorted elements of the list
+ <c>TupleList1</c> where all but the first tuple of the
+ tuples comparing equal have been deleted. Sorting is
+ performed on the <c>N</c>th element of the tuples.</p>
+ </desc>
+ </func>
+ <func>
+ <name>umerge(ListOfLists) -> List1</name>
+ <fsummary>Merge a list of sorted lists, removing duplicates</fsummary>
+ <type>
+ <v>ListOfLists = [List]</v>
+ <v>List = List1 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging all the sub-lists
+ of <c>ListOfLists</c>. All sub-lists must be sorted and
+ contain no duplicates prior to evaluating this function.
+ When two elements compare equal, the element from the
+ sub-list with the lowest position in <c>ListOfLists</c> is
+ picked and the other one deleted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>umerge(List1, List2) -> List3</name>
+ <fsummary>Merge two sorted lists, removing duplicates</fsummary>
+ <type>
+ <v>List1 = List2 = List3 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>List1</c> and
+ <c>List2</c>. Both <c>List1</c> and <c>List2</c> must be
+ sorted and contain no duplicates prior to evaluating this
+ function. When two elements compare equal, the element from
+ <c>List1</c> is picked and the one from <c>List2</c>
+ deleted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>umerge(Fun, List1, List2) -> List3</name>
+ <fsummary>Merge two sorted lists, removing duplicates</fsummary>
+ <type>
+ <v>Fun = fun(A, B) -> bool()</v>
+ <v>List1 = [A]</v>
+ <v>List2 = [B]</v>
+ <v>List3 = [A | B]</v>
+ <v>&nbsp;A = B = term()</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>List1</c> and
+ <c>List2</c>. Both <c>List1</c> and <c>List2</c> must be
+ sorted according to the <seealso
+ marker="#ordering_function">ordering function</seealso>
+ <c>Fun</c> and contain no duplicates prior to evaluating
+ this function. <c>Fun(A, B)</c> should return <c>true</c> if
+ <c>A</c> compares less than or equal to <c>B</c> in the
+ ordering, <c>false</c> otherwise. When two elements compare
+ equal, the element from
+ <c>List1</c> is picked and the one from <c>List2</c>
+ deleted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>umerge3(List1, List2, List3) -> List4</name>
+ <fsummary>Merge three sorted lists, removing duplicates</fsummary>
+ <type>
+ <v>List1 = List2 = List3 = List4 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the sorted list formed by merging <c>List1</c>,
+ <c>List2</c> and <c>List3</c>. All of <c>List1</c>,
+ <c>List2</c> and <c>List3</c> must be sorted and contain no
+ duplicates prior to evaluating this function. When two
+ elements compare equal, the element from <c>List1</c> is
+ picked if there is such an element, otherwise the element
+ from <c>List2</c> is picked, and the other one deleted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>unzip(List1) -> {List2, List3}</name>
+ <fsummary>Unzip a list of two-tuples into two lists</fsummary>
+ <type>
+ <v>List1 = [{X, Y}]</v>
+ <v>List2 = [X]</v>
+ <v>List3 = [Y]</v>
+ <v>&nbsp;X = Y = term()</v>
+ </type>
+ <desc>
+ <p>"Unzips" a list of two-tuples into two lists, where the first
+ list contains the first element of each tuple, and the second
+ list contains the second element of each tuple.</p>
+ </desc>
+ </func>
+ <func>
+ <name>unzip3(List1) -> {List2, List3, List4}</name>
+ <fsummary>Unzip a list of three-tuples into three lists</fsummary>
+ <type>
+ <v>List1 = [{X, Y, Z}]</v>
+ <v>List2 = [X]</v>
+ <v>List3 = [Y]</v>
+ <v>List4 = [Z]</v>
+ <v>&nbsp;X = Y = Z = term()</v>
+ </type>
+ <desc>
+ <p>"Unzips" a list of three-tuples into three lists, where
+ the first list contains the first element of each tuple,
+ the second list contains the second element of each tuple, and
+ the third list contains the third element of each tuple.</p>
+ </desc>
+ </func>
+ <func>
+ <name>usort(List1) -> List2</name>
+ <fsummary>Sort a list, removing duplicates</fsummary>
+ <type>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list containing the sorted elements of
+ <c>List1</c> where all but the first element of the elements
+ comparing equal have been deleted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>usort(Fun, List1) -> List2</name>
+ <fsummary>Sort a list, removing duplicates</fsummary>
+ <type>
+ <v>Fun = fun(Elem1, Elem2) -> bool()</v>
+ <v>&nbsp;Elem1 = Elem2 = term()</v>
+ <v>List1 = List2 = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns a list which contains the sorted elements of
+ <c>List1</c> where all but the first element of the elements
+ comparing equal according to the <seealso
+ marker="#ordering_function">ordering function</seealso>
+ <c>Fun</c> have been deleted. <c>Fun(A, B)</c> should return
+ <c>true</c> if <c>A</c> compares less than or equal to
+ <c>B</c> in the ordering, <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>zip(List1, List2) -> List3</name>
+ <fsummary>Zip two lists into a list of two-tuples</fsummary>
+ <type>
+ <v>List1 = [X]</v>
+ <v>List2 = [Y]</v>
+ <v>List3 = [{X, Y}]</v>
+ <v>&nbsp;X = Y = term()</v>
+ </type>
+ <desc>
+ <p>"Zips" two lists of equal length into one list of two-tuples,
+ where the first element of each tuple is taken from the first
+ list and the second element is taken from corresponding
+ element in the second list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>zip3(List1, List2, List3) -> List4</name>
+ <fsummary>Zip three lists into a list of three-tuples</fsummary>
+ <type>
+ <v>List1 = [X]</v>
+ <v>List2 = [Y]</v>
+ <v>List3 = [Z]</v>
+ <v>List3 = [{X, Y, Z}]</v>
+ <v>&nbsp;X = Y = Z = term()</v>
+ </type>
+ <desc>
+ <p>"Zips" three lists of equal length into one list of
+ three-tuples, where the first element of each tuple is taken
+ from the first list, the second element is taken from
+ corresponding element in the second list, and the third
+ element is taken from the corresponding element in the third
+ list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>zipwith(Combine, List1, List2) -> List3</name>
+ <fsummary>Zip two lists into one list according to a fun</fsummary>
+ <type>
+ <v>Combine = fun(X, Y) -> T</v>
+ <v>List1 = [X]</v>
+ <v>List2 = [Y]</v>
+ <v>List3 = [T]</v>
+ <v>&nbsp;X = Y = T = term()</v>
+ </type>
+ <desc>
+ <p>Combine the elements of two lists of equal length into one
+ list. For each pair <c>X, Y</c> of list elements from the two
+ lists, the element in the result list will be
+ <c>Combine(X, Y)</c>.</p>
+ <p><c>zipwith(fun(X, Y) -> {X,Y} end, List1, List2)</c> is
+ equivalent to <c>zip(List1, List2)</c>.</p>
+ <p>Example:</p>
+ <pre>
+> <input>lists:zipwith(fun(X, Y) -> X+Y end, [1,2,3], [4,5,6]).</input>
+[5,7,9]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>zipwith3(Combine, List1, List2, List3) -> List4</name>
+ <fsummary>Zip three lists into one list according to a fun</fsummary>
+ <type>
+ <v>Combine = fun(X, Y, Z) -> T</v>
+ <v>List1 = [X]</v>
+ <v>List2 = [Y]</v>
+ <v>List3 = [Z]</v>
+ <v>List4 = [T]</v>
+ <v>&nbsp;X = Y = Z = T = term()</v>
+ </type>
+ <desc>
+ <p>Combine the elements of three lists of equal length into one
+ list. For each triple <c>X, Y, Z</c> of list elements from
+ the three lists, the element in the result list will be
+ <c>Combine(X, Y, Z)</c>.</p>
+ <p><c>zipwith3(fun(X, Y, Z) -> {X,Y,Z} end, List1, List2, List3)</c> is equivalent to <c>zip3(List1, List2, List3)</c>.</p>
+ <p>Examples:</p>
+ <pre>
+> <input>lists:zipwith3(fun(X, Y, Z) -> X+Y+Z end, [1,2,3], [4,5,6], [7,8,9]).</input>
+[12,15,18]
+> <input>lists:zipwith3(fun(X, Y, Z) -> [X,Y,Z] end, [a,b,c], [x,y,z], [1,2,3]).</input>
+[[a,x,1],[b,y,2],[c,z,3]]</pre>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/log_mf_h.xml b/lib/stdlib/doc/src/log_mf_h.xml
new file mode 100644
index 0000000000..198a55a63b
--- /dev/null
+++ b/lib/stdlib/doc/src/log_mf_h.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>log_mf_h</title>
+ <prepared>Martin Bj&ouml;rklund</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno></docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked>Martin Bj&ouml;rklund</checked>
+ <date>1996-10-31</date>
+ <rev>A</rev>
+ <file>log_mf_h.sgml</file>
+ </header>
+ <module>log_mf_h</module>
+ <modulesummary>An Event Handler which Logs Events to Disk</modulesummary>
+ <description>
+ <p>The <c>log_mf_h</c> is a <c>gen_event</c> handler module which
+ can be installed in any <c>gen_event</c> process. It logs onto disk all events
+ which are sent to an event manager. Each event is written as a
+ binary which makes the logging very fast. However, a tool such as the <c>Report Browser</c> (<c>rb</c>) must be used in order to read the files. The events are written to multiple files. When all files have been used, the first one is re-used and overwritten. The directory location, the number of files, and the size of each file are configurable. The directory will include one file called <c>index</c>, and
+ report files <c>1, 2, ....</c>.
+ </p>
+ </description>
+ <funcs>
+ <func>
+ <name>init(Dir, MaxBytes, MaxFiles)</name>
+ <name>init(Dir, MaxBytes, MaxFiles, Pred) -> Args</name>
+ <fsummary>Initiate the event handler</fsummary>
+ <type>
+ <v>Dir = string()</v>
+ <v>MaxBytes = integer()</v>
+ <v>MaxFiles = 0 &lt; integer() &lt; 256</v>
+ <v>Pred = fun(Event) -> boolean()</v>
+ <v>Event = term()</v>
+ <v>Args = args()</v>
+ </type>
+ <desc>
+ <p>Initiates the event handler. This function returns
+ <c>Args</c>, which should be used in a call to
+ <c>gen_event:add_handler(EventMgr, log_mf_h, Args)</c>.
+ </p>
+ <p><c>Dir</c> specifies which directory to use for the log
+ files. <c>MaxBytes</c> specifies the size of each individual
+ file. <c>MaxFiles</c> specifies how many files are
+ used. <c>Pred</c> is a predicate function used to filter the
+ events. If no predicate function is specified, all events are
+ logged.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="gen_event">gen_event(3)</seealso>, rb(3) </p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/make.dep b/lib/stdlib/doc/src/make.dep
new file mode 100644
index 0000000000..48ee6209ef
--- /dev/null
+++ b/lib/stdlib/doc/src/make.dep
@@ -0,0 +1,40 @@
+# ----------------------------------------------------
+# >>>> Do not edit this file <<<<
+# This file was automaticly generated by
+# /home/otp/bin/docdepend
+# ----------------------------------------------------
+
+
+# ----------------------------------------------------
+# TeX files that the DVI file depend on
+# ----------------------------------------------------
+
+book.dvi: array.tex base64.tex beam_lib.tex book.tex \
+ c.tex calendar.tex dets.tex dict.tex digraph.tex \
+ digraph_utils.tex epp.tex erl_eval.tex erl_expand_records.tex \
+ erl_id_trans.tex erl_internal.tex erl_lint.tex \
+ erl_parse.tex erl_pp.tex erl_scan.tex erl_tar.tex \
+ ets.tex file_sorter.tex filelib.tex filename.tex \
+ gb_sets.tex gb_trees.tex gen_event.tex gen_fsm.tex \
+ gen_server.tex io.tex io_lib.tex io_protocol.tex \
+ lib.tex lists.tex log_mf_h.tex math.tex ms_transform.tex \
+ orddict.tex ordsets.tex part.tex pg.tex pool.tex \
+ proc_lib.tex proplists.tex qlc.tex queue.tex \
+ random.tex re.tex ref_man.tex regexp.tex sets.tex \
+ shell.tex shell_default.tex slave.tex sofs.tex \
+ stdlib_app.tex string.tex supervisor.tex supervisor_bridge.tex \
+ sys.tex timer.tex unicode.tex unicode_usage.tex \
+ win32reg.tex zip.tex
+
+# ----------------------------------------------------
+# Source inlined when transforming from source to LaTeX
+# ----------------------------------------------------
+
+book.tex: ref_man.xml
+
+# ----------------------------------------------------
+# Pictures that the DVI file depend on
+# ----------------------------------------------------
+
+book.dvi: ushell1.ps
+
diff --git a/lib/stdlib/doc/src/math.xml b/lib/stdlib/doc/src/math.xml
new file mode 100644
index 0000000000..990a6b4024
--- /dev/null
+++ b/lib/stdlib/doc/src/math.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>math</title>
+ <prepared>Joe Armstrong</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-15</date>
+ <rev>B</rev>
+ <file>math.sgml</file>
+ </header>
+ <module>math</module>
+ <modulesummary>Mathematical Functions</modulesummary>
+ <description>
+ <p>This module provides an interface to a number of mathematical
+ functions.</p>
+ <note>
+ <p>Not all functions are implemented on all platforms. In particular,
+ the <c>erf/1</c> and <c>erfc/1</c> functions are not implemented on Windows.</p>
+ </note>
+ </description>
+ <funcs>
+ <func>
+ <name>pi() -> float()</name>
+ <fsummary>A useful number</fsummary>
+ <desc>
+ <p>A useful number.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sin(X)</name>
+ <name>cos(X)</name>
+ <name>tan(X)</name>
+ <name>asin(X)</name>
+ <name>acos(X)</name>
+ <name>atan(X)</name>
+ <name>atan2(Y, X)</name>
+ <name>sinh(X)</name>
+ <name>cosh(X)</name>
+ <name>tanh(X)</name>
+ <name>asinh(X)</name>
+ <name>acosh(X)</name>
+ <name>atanh(X)</name>
+ <name>exp(X)</name>
+ <name>log(X)</name>
+ <name>log10(X)</name>
+ <name>pow(X, Y)</name>
+ <name>sqrt(X)</name>
+ <fsummary>Diverse math functions</fsummary>
+ <type>
+ <v>X = Y = number()</v>
+ </type>
+ <desc>
+ <p>A collection of math functions which return floats. Arguments
+ are numbers. </p>
+ </desc>
+ </func>
+ <func>
+ <name>erf(X) -> float()</name>
+ <fsummary>Error function.</fsummary>
+ <type>
+ <v>X = number()</v>
+ </type>
+ <desc>
+ <p>Returns the error function of <c>X</c>, where</p>
+ <pre>
+erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt. </pre>
+ </desc>
+ </func>
+ <func>
+ <name>erfc(X) -> float()</name>
+ <fsummary>Another error function</fsummary>
+ <type>
+ <v>X = number()</v>
+ </type>
+ <desc>
+ <p><c>erfc(X)</c> returns <c>1.0 - erf(X)</c>, computed by
+ methods that avoid cancellation for large <c>X</c>. </p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Bugs</title>
+ <p>As these are the C library, the bugs are the same.</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/ms_transform.xml b/lib/stdlib/doc/src/ms_transform.xml
new file mode 100644
index 0000000000..9f178b426c
--- /dev/null
+++ b/lib/stdlib/doc/src/ms_transform.xml
@@ -0,0 +1,651 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2002</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>ms_transform</title>
+ <prepared>Patrik Nyblom</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>99-02-09</date>
+ <rev>C</rev>
+ <file>ms_transform.sgml</file>
+ </header>
+ <module>ms_transform</module>
+ <modulesummary>Parse_transform that translates fun syntax into match specifications. </modulesummary>
+ <description>
+ <marker id="top"></marker>
+ <p>This module implements the parse_transform that makes calls to
+ <c>ets</c> and <c>dbg</c>:<c>fun2ms/1</c> translate into literal
+ match specifications. It also implements the back end for the same
+ functions when called from the Erlang shell.</p>
+ <p>The translations from fun's to match_specs
+ is accessed through the two "pseudo
+ functions" <c>ets:fun2ms/1</c> and <c>dbg:fun2ms/1</c>.</p>
+ <p>Actually this introduction is more or less an introduction to the
+ whole concept of match specifications. Since everyone trying to use
+ <c>ets:select</c> or <c>dbg</c> seems to end up reading
+ this page, it seems in good place to explain a little more than
+ just what this module does.</p>
+ <p>There are some caveats one should be aware of, please read through
+ the whole manual page if it's the first time you're using the
+ transformations. </p>
+ <p>Match specifications are used more or less as filters.
+ They resemble usual Erlang matching in a list comprehension or in
+ a <c>fun</c> used in conjunction with <c>lists:foldl</c> etc. The
+ syntax of pure match specifications is somewhat awkward though, as
+ they are made up purely by Erlang terms and there is no syntax in the
+ language to make the match specifications more readable.</p>
+ <p>As the match specifications execution and structure is quite like
+ that of a fun, it would for most programmers be more straight forward
+ to simply write it using the familiar fun syntax and having that
+ translated into a match specification automatically. Of course a real
+ fun is more powerful than the match specifications allow, but bearing
+ the match specifications in mind, and what they can do, it's still
+ more convenient to write it all as a fun. This module contains the
+ code that simply translates the fun syntax into match_spec terms.</p>
+ <p>Let's start with an ets example. Using <c>ets:select</c> and
+ a match specification, one can filter out rows of a table and construct
+ a list of tuples containing relevant parts of the data in these
+ rows. Of course one could use <c>ets:foldl</c> instead, but the
+ select call is far more efficient. Without the translation, one has to
+ struggle with writing match specifications terms to accommodate this,
+ or one has to resort to the less powerful
+ <c>ets:match(_object)</c> calls, or simply give up and use
+ the more inefficient method of <c>ets:foldl</c>. Using the
+ <c>ets:fun2ms</c> transformation, a <c>ets:select</c> call
+ is at least as easy to write as any of the alternatives.</p>
+ <p>As an example, consider a simple table of employees:</p>
+ <code type="none">
+-record(emp, {empno, %Employee number as a string, the key
+ surname, %Surname of the employee
+ givenname, %Given name of employee
+ dept, %Department one of {dev,sales,prod,adm}
+ empyear}). %Year the employee was employed </code>
+ <p>We create the table using:</p>
+ <code type="none">
+ets:new(emp_tab,[{keypos,#emp.empno},named_table,ordered_set]). </code>
+ <p>Let's also fill it with some randomly chosen data for the examples:</p>
+ <code type="none">
+[{emp,"011103","Black","Alfred",sales,2000},
+ {emp,"041231","Doe","John",prod,2001},
+ {emp,"052341","Smith","John",dev,1997},
+ {emp,"076324","Smith","Ella",sales,1995},
+ {emp,"122334","Weston","Anna",prod,2002},
+ {emp,"535216","Chalker","Samuel",adm,1998},
+ {emp,"789789","Harrysson","Joe",adm,1996},
+ {emp,"963721","Scott","Juliana",dev,2003},
+ {emp,"989891","Brown","Gabriel",prod,1999}] </code>
+ <p>Now, the amount of data in the table is of course to small to justify
+ complicated ets searches, but on real tables, using <c>select</c> to get
+ exactly the data you want will increase efficiency remarkably.</p>
+ <p>Lets say for example that we'd want the employee numbers of
+ everyone in the sales department. One might use <c>ets:match</c>
+ in such a situation:</p>
+ <pre>
+1> <input>ets:match(emp_tab, {'_', '$1', '_', '_', sales, '_'}).</input>
+[["011103"],["076324"]] </pre>
+ <p>Even though <c>ets:match</c> does not require a full match
+ specification, but a simpler type, it's still somewhat unreadable, and
+ one has little control over the returned result, it's always a list of
+ lists. OK, one might use <c>ets:foldl</c> or
+ <c>ets:foldr</c> instead:</p>
+ <code type="none">
+ets:foldr(fun(#emp{empno = E, dept = sales},Acc) -> [E | Acc];
+ (_,Acc) -> Acc
+ end,
+ [],
+ emp_tab). </code>
+ <p>Running that would result in <c>["011103","076324"]</c>
+ , which at least gets rid of the extra lists. The fun is also quite
+ straightforward, so the only problem is that all the data from the
+ table has to be transferred from the table to the calling process for
+ filtering. That's inefficient compared to the <c>ets:match</c>
+ call where the filtering can be done "inside" the emulator and only
+ the result is transferred to the process. Remember that ets tables are
+ all about efficiency, if it wasn't for efficiency all of ets could be
+ implemented in Erlang, as a process receiving requests and sending
+ answers back. One uses ets because one wants performance, and
+ therefore one wouldn't want all of the table transferred to the
+ process for filtering. OK, let's look at a pure
+ <c>ets:select</c> call that does what the <c>ets:foldr</c>
+ does:</p>
+ <code type="none">
+ets:select(emp_tab,[{#emp{empno = '$1', dept = sales, _='_'},[],['$1']}]). </code>
+ <p>Even though the record syntax is used, it's still somewhat hard to
+ read and even harder to write. The first element of the tuple,
+ <c>#emp{empno = '$1', dept = sales, _='_'}</c> tells what to
+ match, elements not matching this will not be returned at all, as in
+ the <c>ets:match</c> example. The second element, the empty list
+ is a list of guard expressions, which we need none, and the third
+ element is the list of expressions constructing the return value (in
+ ets this almost always is a list containing one single term). In our
+ case <c>'$1'</c> is bound to the employee number in the head
+ (first element of tuple), and hence it is the employee number that is
+ returned. The result is <c>["011103","076324"]</c>, just as in
+ the <c>ets:foldr</c> example, but the result is retrieved much
+ more efficiently in terms of execution speed and memory consumption.</p>
+ <p>We have one efficient but hardly readable way of doing it and one
+ inefficient but fairly readable (at least to the skilled Erlang
+ programmer) way of doing it. With the use of <c>ets:fun2ms</c>,
+ one could have something that is as efficient as possible but still is
+ written as a filter using the fun syntax:</p>
+ <code type="none">
+-include_lib("stdlib/include/ms_transform.hrl").
+
+% ...
+
+ets:select(emp_tab, ets:fun2ms(
+ fun(#emp{empno = E, dept = sales}) ->
+ E
+ end)). </code>
+ <p>This may not be the shortest of the expressions, but it requires no
+ special knowledge of match specifications to read. The fun's head
+ should simply match what you want to filter out and the body returns
+ what you want returned. As long as the fun can be kept within the
+ limits of the match specifications, there is no need to transfer all
+ data of the table to the process for filtering as in the
+ <c>ets:foldr</c> example. In fact it's even easier to read then
+ the <c>ets:foldr</c> example, as the select call in itself
+ discards anything that doesn't match, while the fun of the
+ <c>foldr</c> call needs to handle both the elements matching and
+ the ones not matching.</p>
+ <p>It's worth noting in the above <c>ets:fun2ms</c> example that one
+ needs to include <c>ms_transform.hrl</c> in the source code, as this is
+ what triggers the parse transformation of the <c>ets:fun2ms</c> call
+ to a valid match specification. This also implies that the
+ transformation is done at compile time (except when called from the
+ shell of course) and therefore will take no resources at all in
+ runtime. So although you use the more intuitive fun syntax, it gets as
+ efficient in runtime as writing match specifications by hand.</p>
+ <p>Let's look at some more <c>ets</c> examples. Let's say one
+ wants to get all the employee numbers of any employee hired before the
+ year 2000. Using <c>ets:match</c> isn't an alternative here as
+ relational operators cannot be expressed there. Once again, an
+ <c>ets:foldr</c> could do it (slowly, but correct):</p>
+ <code type="none"><![CDATA[
+ets:foldr(fun(#emp{empno = E, empyear = Y},Acc) when Y < 2000 -> [E | Acc];
+ (_,Acc) -> Acc
+ end,
+ [],
+ emp_tab). ]]></code>
+ <p>The result will be
+ <c>["052341","076324","535216","789789","989891"]</c>, as
+ expected. Now the equivalent expression using a handwritten match
+ specification would look something like this:</p>
+ <code type="none"><![CDATA[
+ets:select(emp_tab,[{#emp{empno = '$1', empyear = '$2', _='_'},
+ [{'<', '$2', 2000}],
+ ['$1']}]). ]]></code>
+ <p>This gives the same result, the <c><![CDATA[[{'<', '$2', 2000}]]]></c> is in
+ the guard part and therefore discards anything that does not have a
+ empyear (bound to '$2' in the head) less than 2000, just as the guard
+ in the <c>foldl</c> example. Lets jump on to writing it using
+ <c>ets:fun2ms</c></p>
+ <code type="none"><![CDATA[
+-include_lib("stdlib/include/ms_transform.hrl").
+
+% ...
+
+ets:select(emp_tab, ets:fun2ms(
+ fun(#emp{empno = E, empyear = Y}) when Y < 2000 ->
+ E
+ end)). ]]></code>
+ <p>Obviously readability is gained by using the parse transformation.</p>
+ <p>I'll show some more examples without the tiresome
+ comparing-to-alternatives stuff. Let's say we'd want the whole object
+ matching instead of only one element. We could of course assign a
+ variable to every part of the record and build it up once again in the
+ body of the <c>fun</c>, but it's easier to do like this:</p>
+ <code type="none"><![CDATA[
+ets:select(emp_tab, ets:fun2ms(
+ fun(Obj = #emp{empno = E, empyear = Y})
+ when Y < 2000 ->
+ Obj
+ end)). ]]></code>
+ <p>Just as in ordinary Erlang matching, you can bind a variable to the
+ whole matched object using a "match in then match", i.e. a
+ <c>=</c>. Unfortunately this is not general in <c>fun's</c> translated
+ to match specifications, only on the "top level", i.e. matching the
+ <em>whole</em> object arriving to be matched into a separate variable,
+ is it allowed. For the one's used to writing match specifications by
+ hand, I'll have to mention that the variable A will simply be
+ translated into '$_'. It's not general, but it has very common usage,
+ why it is handled as a special, but useful, case. If this bothers you,
+ the pseudo function <c>object</c> also returns the whole matched
+ object, see the part about caveats and limitations below.</p>
+ <p>Let's do something in the <c>fun</c>'s body too: Let's say
+ that someone realizes that there are a few people having an employee
+ number beginning with a zero (<c>0</c>), which shouldn't be
+ allowed. All those should have their numbers changed to begin with a
+ one (<c>1</c>) instead and one wants the
+ list <c><![CDATA[[{<Old empno>,<New empno>}]]]></c> created:</p>
+ <code type="none">
+ets:select(emp_tab, ets:fun2ms(
+ fun(#emp{empno = [$0 | Rest] }) ->
+ {[$0|Rest],[$1|Rest]}
+ end)). </code>
+ <p>As a matter of fact, this query hit's the feature of partially bound
+ keys in the table type <c>ordered_set</c>, so that not the whole
+ table need be searched, only the part of the table containing keys
+ beginning with <c>0</c> is in fact looked into. </p>
+ <p>The fun of course can have several clauses, so that if one could do
+ the following: For each employee, if he or she is hired prior to 1997,
+ return the tuple <c><![CDATA[{inventory, <employee number>}]]></c>, for each hired 1997
+ or later, but before 2001, return <c><![CDATA[{rookie, <employee number>}]]></c>, for all others return <c><![CDATA[{newbie, <employee number>}]]></c>. All except for the ones named <c>Smith</c> as
+ they would be affronted by anything other than the tag
+ <c>guru</c> and that is also what's returned for their numbers;
+ <c><![CDATA[{guru, <employee number>}]]></c>:</p>
+ <code type="none"><![CDATA[
+ets:select(emp_tab, ets:fun2ms(
+ fun(#emp{empno = E, surname = "Smith" }) ->
+ {guru,E};
+ (#emp{empno = E, empyear = Y}) when Y < 1997 ->
+ {inventory, E};
+ (#emp{empno = E, empyear = Y}) when Y > 2001 ->
+ {newbie, E};
+ (#emp{empno = E, empyear = Y}) -> % 1997 -- 2001
+ {rookie, E}
+ end)). ]]></code>
+ <p>The result will be:</p>
+ <code type="none">
+[{rookie,"011103"},
+ {rookie,"041231"},
+ {guru,"052341"},
+ {guru,"076324"},
+ {newbie,"122334"},
+ {rookie,"535216"},
+ {inventory,"789789"},
+ {newbie,"963721"},
+ {rookie,"989891"}] </code>
+ <p>and so the Smith's will be happy...</p>
+ <p>So, what more can you do? Well, the simple answer would be; look
+ in the documentation of match specifications in ERTS users
+ guide. However let's briefly go through the most useful "built in
+ functions" that you can use when the <c>fun</c> is to be
+ translated into a match specification by <c>ets:fun2ms</c> (it's
+ worth mentioning, although it might be obvious to some, that calling
+ other functions than the one's allowed in match specifications cannot
+ be done. No "usual" Erlang code can be executed by the <c>fun</c> being
+ translated by <c>fun2ms</c>, the <c>fun</c> is after all limited
+ exactly to the power of the match specifications, which is
+ unfortunate, but the price one has to pay for the execution speed of
+ an <c>ets:select</c> compared to <c>ets:foldl/foldr</c>).</p>
+ <p>The head of the <c>fun</c> is obviously a head matching (or mismatching)
+ <em>one</em> parameter, one object of the table we <c>select</c>
+ from. The object is always a single variable (can be <c>_</c>) or
+ a tuple, as that's what's in <c>ets, dets</c> and
+ <c>mnesia</c> tables (the match specification returned by
+ <c>ets:fun2ms</c> can of course be used with
+ <c>dets:select</c> and <c>mnesia:select</c> as well as
+ with <c>ets:select</c>). The use of <c>=</c> in the head
+ is allowed (and encouraged) on the top level.</p>
+ <p>The guard section can contain any guard expression of Erlang.
+ Even the "old" type test are allowed on the toplevel of the guard
+ (<c>integer(X)</c> instead of <c>is_integer(X)</c>). As the new type tests (the
+ <c>is_</c> tests) are in practice just guard bif's they can also
+ be called from within the body of the fun, but so they can in ordinary
+ Erlang code. Also arithmetics is allowed, as well as ordinary guard
+ bif's. Here's a list of bif's and expressions:</p>
+ <list type="bulleted">
+ <item>The type tests: is_atom, is_constant, is_float, is_integer,
+ is_list, is_number, is_pid, is_port, is_reference, is_tuple,
+ is_binary, is_function, is_record</item>
+ <item>The boolean operators: not, and, or, andalso, orelse </item>
+ <item>The relational operators: >, >=, &lt;, =&lt;, =:=, ==, =/=, /=</item>
+ <item>Arithmetics: +, -, *, div, rem</item>
+ <item>Bitwise operators: band, bor, bxor, bnot, bsl, bsr</item>
+ <item>The guard bif's: abs, element, hd, length, node, round, size, tl,
+ trunc, self</item>
+ <item>The obsolete type test (only in guards):
+ atom, constant, float, integer,
+ list, number, pid, port, reference, tuple,
+ binary, function, record</item>
+ </list>
+ <p>Contrary to the fact with "handwritten" match specifications, the
+ <c>is_record</c> guard works as in ordinary Erlang code.</p>
+ <p>Semicolons (<c>;</c>) in guards are allowed, the result will be (as
+ expected) one "match_spec-clause" for each semicolon-separated
+ part of the guard. The semantics being identical to the Erlang
+ semantics.</p>
+ <p>The body of the <c>fun</c> is used to construct the
+ resulting value. When selecting from tables one usually just construct
+ a suiting term here, using ordinary Erlang term construction, like
+ tuple parentheses, list brackets and variables matched out in the
+ head, possibly in conjunction with the occasional constant. Whatever
+ expressions are allowed in guards are also allowed here, but there are
+ no special functions except <c>object</c> and
+ <c>bindings</c> (see further down), which returns the whole
+ matched object and all known variable bindings respectively.</p>
+ <p>The <c>dbg</c> variants of match specifications have an
+ imperative approach to the match specification body, the ets dialect
+ hasn't. The fun body for <c>ets:fun2ms</c> returns the result
+ without side effects, and as matching (<c>=</c>) in the body of
+ the match specifications is not allowed (for performance reasons) the
+ only thing left, more or less, is term construction...</p>
+ <p>Let's move on to the <c>dbg</c> dialect, the slightly
+ different match specifications translated by <c>dbg:fun2ms</c>. </p>
+ <p>The same reasons for using the parse transformation applies to
+ <c>dbg</c>, maybe even more so as filtering using Erlang code is
+ simply not a good idea when tracing (except afterwards, if you trace
+ to file). The concept is similar to that of <c>ets:fun2ms</c>
+ except that you usually use it directly from the shell (which can also
+ be done with <c>ets:fun2ms</c>). </p>
+ <p>Let's manufacture a toy module to trace on </p>
+ <code type="none">
+-module(toy).
+
+-export([start/1, store/2, retrieve/1]).
+
+start(Args) ->
+ toy_table = ets:new(toy_table,Args).
+
+store(Key, Value) ->
+ ets:insert(toy_table,{Key,Value}).
+
+retrieve(Key) ->
+ [{Key, Value}] = ets:lookup(toy_table,Key),
+ Value. </code>
+ <p>During model testing, the first test bails out with a
+ <c>{badmatch,16}</c> in <c>{toy,start,1}</c>, why?</p>
+ <p>We suspect the ets call, as we match hard on the return value, but
+ want only the particular <c>new</c> call with
+ <c>toy_table</c> as first parameter.
+ So we start a default tracer on the node:</p>
+ <pre>
+1> <input>dbg:tracer().</input>
+{ok,&lt;0.88.0>}</pre>
+ <p>And so we turn on call tracing for all processes, we are going to
+ make a pretty restrictive trace pattern, so there's no need to call
+ trace only a few processes (it usually isn't):</p>
+ <pre>
+2> <input>dbg:p(all,call).</input>
+{ok,[{matched,nonode@nohost,25}]} </pre>
+ <p>It's time to specify the filter. We want to view calls that resemble
+ <c><![CDATA[ets:new(toy_table,<something>)]]></c>:</p>
+ <pre>
+3> <input>dbg:tp(ets,new,dbg:fun2ms(fun([toy_table,_]) -> true end)).</input>
+{ok,[{matched,nonode@nohost,1},{saved,1}]} </pre>
+ <p>As can be seen, the <c>fun</c>'s used with
+ <c>dbg:fun2ms</c> takes a single list as parameter instead of a
+ single tuple. The list matches a list of the parameters to the traced
+ function. A single variable may also be used of course. The body
+ of the fun expresses in a more imperative way actions to be taken if
+ the fun head (and the guards) matches. I return <c>true</c> here, but it's
+ only because the body of a fun cannot be empty, the return value will
+ be discarded. </p>
+ <p>When we run the test of our module now, we get the following trace
+ output:</p>
+ <code type="none"><![CDATA[
+(<0.86.0>) call ets:new(toy_table,[ordered_set]) ]]></code>
+ <p>Let's play we haven't spotted the problem yet, and want to see what
+ <c>ets:new</c> returns. We do a slightly different trace
+ pattern:</p>
+ <pre>
+4> <input>dbg:tp(ets,new,dbg:fun2ms(fun([toy_table,_]) -> return_trace() end)).</input></pre>
+ <p>Resulting in the following trace output when we run the test:</p>
+ <code type="none"><![CDATA[
+(<0.86.0>) call ets:new(toy_table,[ordered_set])
+(<0.86.0>) returned from ets:new/2 -> 24 ]]></code>
+ <p>The call to <c>return_trace</c>, makes a trace message appear
+ when the function returns. It applies only to the specific function call
+ triggering the match specification (and matching the head/guards of
+ the match specification). This is the by far the most common call in the
+ body of a <c>dbg</c> match specification.</p>
+ <p>As the test now fails with <c>{badmatch,24}</c>, it's obvious
+ that the badmatch is because the atom <c>toy_table</c> does not
+ match the number returned for an unnamed table. So we spotted the
+ problem, the table should be named and the arguments supplied by our
+ test program does not include <c>named_table</c>. We rewrite the
+ start function to:</p>
+ <code type="none">
+start(Args) ->
+ toy_table = ets:new(toy_table,[named_table |Args]). </code>
+ <p>And with the same tracing turned on, we get the following trace
+ output:</p>
+ <code type="none"><![CDATA[
+(<0.86.0>) call ets:new(toy_table,[named_table,ordered_set])
+(<0.86.0>) returned from ets:new/2 -> toy_table ]]></code>
+ <p>Very well. Let's say the module now passes all testing and goes into
+ the system. After a while someone realizes that the table
+ <c>toy_table</c> grows while the system is running and that for some
+ reason there are a lot of elements with atom's as keys. You had
+ expected only integer keys and so does the rest of the system. Well,
+ obviously not all of the system. You turn on call tracing and try to
+ see calls to your module with an atom as the key:</p>
+ <pre>
+1> <input>dbg:tracer().</input>
+{ok,&lt;0.88.0>}
+2> <input>dbg:p(all,call).</input>
+{ok,[{matched,nonode@nohost,25}]}
+3> <input>dbg:tpl(toy,store,dbg:fun2ms(fun([A,_]) when is_atom(A) -> true end)).</input>
+{ok,[{matched,nonode@nohost,1},{saved,1}]}</pre>
+ <p>We use <c>dbg:tpl</c> here to make sure to catch local calls
+ (let's say the module has grown since the smaller version and we're
+ not sure this inserting of atoms is not done locally...). When in
+ doubt always use local call tracing.</p>
+ <p>Let's say nothing happens when we trace in this way. Our function
+ is never called with these parameters. We make the conclusion that
+ someone else (some other module) is doing it and we realize that we
+ must trace on ets:insert and want to see the calling function. The
+ calling function may be retrieved using the match specification
+ function <c>caller</c> and to get it into the trace message, one
+ has to use the match spec function <c>message</c>. The filter
+ call looks like this (looking for calls to <c>ets:insert</c>):</p>
+ <pre>
+4> <input>dbg:tpl(ets,insert,dbg:fun2ms(fun([toy_table,{A,_}]) when is_atom(A) -> </input>
+<input> message(caller()) </input>
+<input> end)). </input>
+{ok,[{matched,nonode@nohost,1},{saved,2}]} </pre>
+ <p>The caller will now appear in the "additional message" part of the
+ trace output, and so after a while, the following output comes:</p>
+ <code type="none"><![CDATA[
+(<0.86.0>) call ets:insert(toy_table,{garbage,can}) ({evil_mod,evil_fun,2}) ]]></code>
+ <p>You have found out that the function <c>evil_fun</c> of the
+ module <c>evil_mod</c>, with arity <c>2</c>, is the one
+ causing all this trouble.</p>
+ <p>This was just a toy example, but it illustrated the most used
+ calls in match specifications for <c>dbg</c> The other, more
+ esotheric calls are listed and explained in the <em>Users guide of the ERTS application</em>, they really are beyond the scope of this
+ document.</p>
+ <p>To end this chatty introduction with something more precise, here
+ follows some parts about caveats and restrictions concerning the fun's
+ used in conjunction with <c>ets:fun2ms</c> and
+ <c>dbg:fun2ms</c>:</p>
+ <warning>
+ <p>To use the pseudo functions triggering the translation, one
+ <em>has to</em> include the header file <c>ms_transform.hrl</c>
+ in the source code. Failure to do so will possibly result in
+ runtime errors rather than compile time, as the expression may
+ be valid as a plain Erlang program without translation.</p>
+ </warning>
+ <warning>
+ <p>The <c>fun</c> has to be literally constructed inside the
+ parameter list to the pseudo functions. The <c>fun</c> cannot
+ be bound to a variable first and then passed to
+ <c>ets:fun2ms</c> or <c>dbg:fun2ms</c>, i.e this
+ will work: <c>ets:fun2ms(fun(A) -> A end)</c> but not this:
+ <c>F = fun(A) -> A end, ets:fun2ms(F)</c>. The later will result
+ in a compile time error if the header is included, otherwise a
+ runtime error. Even if the later construction would ever
+ appear to work, it really doesn't, so don't ever use it.</p>
+ </warning>
+ <p>Several restrictions apply to the fun that is being translated
+ into a match_spec. To put it simple you cannot use anything in
+ the fun that you cannot use in a match_spec. This means that,
+ among others, the following restrictions apply to the fun itself:</p>
+ <list type="bulleted">
+ <item>Functions written in Erlang cannot be called, neither
+ local functions, global functions or real fun's</item>
+ <item>Everything that is written as a function call will be
+ translated into a match_spec call to a builtin function, so that
+ the call <c>is_list(X)</c> will be translated to <c>{'is_list', '$1'}</c> (<c>'$1'</c> is just an example, the numbering may
+ vary). If one tries to call a function that is not a match_spec
+ builtin, it will cause an error.</item>
+ <item>Variables occurring in the head of the <c>fun</c> will be
+ replaced by match_spec variables in the order of occurrence, so
+ that the fragment <c>fun({A,B,C})</c> will be replaced by
+ <c>{'$1', '$2', '$3'}</c> etc. Every occurrence of such a
+ variable later in the match_spec will be replaced by a
+ match_spec variable in the same way, so that the fun
+ <c>fun({A,B}) when is_atom(A) -> B end</c> will be translated into
+ <c>[{{'$1','$2'},[{is_atom,'$1'}],['$2']}]</c>.</item>
+ <item>
+ <p>Variables that are not appearing in the head are imported
+ from the environment and made into
+ match_spec <c>const</c> expressions. Example from the shell:</p>
+ <pre>
+1> <input>X = 25.</input>
+25
+2> <input>ets:fun2ms(fun({A,B}) when A > X -> B end).</input>
+[{{'$1','$2'},[{'>','$1',{const,25}}],['$2']}]</pre>
+ </item>
+ <item>
+ <p>Matching with <c>=</c> cannot be used in the body. It can only
+ be used on the top level in the head of the fun.
+ Example from the shell again:</p>
+ <pre>
+1> <input>ets:fun2ms(fun({A,[B|C]} = D) when A > B -> D end).</input>
+[{{'$1',['$2'|'$3']},[{'>','$1','$2'}],['$_']}]
+2> <input>ets:fun2ms(fun({A,[B|C]=D}) when A > B -> D end).</input>
+Error: fun with head matching ('=' in head) cannot be translated into
+match_spec
+{error,transform_error}
+3> <input>ets:fun2ms(fun({A,[B|C]}) when A > B -> D = [B|C], D end).</input>
+Error: fun with body matching ('=' in body) is illegal as match_spec
+{error,transform_error} </pre>
+ <p>All variables are bound in the head of a match_spec, so the
+ translator can not allow multiple bindings. The special case
+ when matching is done on the top level makes the variable bind
+ to <c>'$_'</c> in the resulting match_spec, it is to allow a more
+ natural access to the whole matched object. The pseudo
+ function <c>object()</c> could be used instead, see below.
+ The following expressions are translated equally: </p>
+ <code type="none">
+ets:fun2ms(fun({a,_} = A) -> A end).
+ets:fun2ms(fun({a,_}) -> object() end).</code>
+ </item>
+ <item>
+ <p>The special match_spec variables <c>'$_'</c> and <c>'$*'</c>
+ can be accessed through the pseudo functions <c>object()</c>
+ (for <c>'$_'</c>) and <c>bindings()</c> (for <c>'$*'</c>).
+ as an example, one could translate the following
+ <c>ets:match_object/2</c> call to a <c>ets:select</c> call:</p>
+ <code type="none">
+ets:match_object(Table, {'$1',test,'$2'}). </code>
+ <p>...is the same as...</p>
+ <code type="none">
+ets:select(Table, ets:fun2ms(fun({A,test,B}) -> object() end)).</code>
+ <p>(This was just an example, in this simple case the former
+ expression is probably preferable in terms of readability).
+ The <c>ets:select/2</c> call will conceptually look like this
+ in the resulting code:</p>
+ <code type="none">
+ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
+ <p>Matching on the top level of the fun head might feel like a
+ more natural way to access <c>'$_'</c>, see above.</p>
+ </item>
+ <item>Term constructions/literals are translated as much as is
+ needed to get them into valid match_specs, so that tuples are
+ made into match_spec tuple constructions (a one element tuple
+ containing the tuple) and constant expressions are used when
+ importing variables from the environment. Records are also
+ translated into plain tuple constructions, calls to element
+ etc. The guard test <c>is_record/2</c> is translated into
+ match_spec code using the three parameter version that's built
+ into match_specs, so that <c>is_record(A,t)</c> is translated
+ into <c>{is_record,'$1',t,5}</c> given that the record size of
+ record type <c>t</c> is 5.</item>
+ <item>Language constructions like <c>case</c>, <c>if</c>,
+ <c>catch</c> etc that are not present in match_specs are not
+ allowed.</item>
+ <item>If the header file <c>ms_transform.hrl</c> is not included,
+ the fun won't be translated, which may result in a
+ <em>runtime error</em> (depending on if the fun is valid in a
+ pure Erlang context). Be absolutely sure that the header is
+ included when using <c>ets</c> and <c>dbg:fun2ms/1</c> in
+ compiled code.</item>
+ <item>If the pseudo function triggering the translation is
+ <c>ets:fun2ms/1</c>, the fun's head must contain a single
+ variable or a single tuple. If the pseudo function is
+ <c>dbg:fun2ms/1</c> the fun's head must contain a single
+ variable or a single list.</item>
+ </list>
+ <p>The translation from fun's to match_specs is done at compile
+ time, so runtime performance is not affected by using these pseudo
+ functions. The compile time might be somewhat longer though. </p>
+ <p>For more information about match_specs, please read about them
+ in <em>ERTS users guide</em>.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>parse_transform(Forms,_Options) -> Forms</name>
+ <fsummary>Transforms Erlang abstract format containing calls to ets/dbg:fun2ms into literal match specifications.</fsummary>
+ <type>
+ <v>Forms = Erlang abstract code format, see the erl_parse module description </v>
+ <v>_Options = Option list, required but not used</v>
+ </type>
+ <desc>
+ <p>Implements the actual transformation at compile time. This
+ function is called by the compiler to do the source code
+ transformation if and when the <c>ms_transform.hrl</c> header
+ file is included in your source code. See the <c>ets</c> and
+ <c>dbg</c>:<c>fun2ms/1</c> function manual pages for
+ documentation on how to use this parse_transform, see the
+ <c>match_spec</c> chapter in <c>ERTS</c> users guide for a
+ description of match specifications. </p>
+ </desc>
+ </func>
+ <func>
+ <name>transform_from_shell(Dialect,Clauses,BoundEnvironment) -> term()</name>
+ <fsummary>Used when transforming fun's created in the shell into match_specifications.</fsummary>
+ <type>
+ <v>Dialect = ets | dbg</v>
+ <v>Clauses = Erlang abstract form for a single fun</v>
+ <v>BoundEnvironment = [{atom(), term()}, ...], list of variable bindings in the shell environment</v>
+ </type>
+ <desc>
+ <p>Implements the actual transformation when the <c>fun2ms</c>
+ functions are called from the shell. In this case the abstract
+ form is for one single fun (parsed by the Erlang shell), and
+ all imported variables should be in the key-value list passed
+ as <c>BoundEnvironment</c>. The result is a term, normalized,
+ i.e. not in abstract format.</p>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(Errcode) -> ErrMessage</name>
+ <fsummary>Error formatting function as required by the parse_transform interface.</fsummary>
+ <type>
+ <v>Errcode = term()</v>
+ <v>ErrMessage = string()</v>
+ </type>
+ <desc>
+ <p>Takes an error code returned by one of the other functions
+ in the module and creates a textual description of the
+ error. Fairly uninteresting function actually.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
new file mode 100644
index 0000000000..f7128b641d
--- /dev/null
+++ b/lib/stdlib/doc/src/notes.xml
@@ -0,0 +1,2704 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2004</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>STDLIB Release Notes</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>notes.xml</file>
+ </header>
+ <p>This document describes the changes made to the STDLIB application.</p>
+
+<section><title>STDLIB 1.16.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The documentation is now built with open source tools
+ (xsltproc and fop) that exists on most platforms. One
+ visible change is that the frames are removed.</p>
+ <p>
+ Own Id: OTP-8201</p>
+ </item>
+ <item>
+ <p>
+ [escript] The restriction that the first line in escripts
+ must begin with <c>#!</c> has been removed.</p>
+ <p>
+ [escript] Some command line options to the escript
+ executable has now been documented. For example you can
+ run an escript in the debugger by just adding a command
+ line option.</p>
+ <p>
+ [escript] The documentation of the escript header syntax
+ has been clarified. For example the header is optional.
+ This means that it is possible to directly "execute"
+ <c>.erl</c>, <c>.beam</c> and<c>.zip</c> files.</p>
+ <p>
+ Own Id: OTP-8215</p>
+ </item>
+ <item>
+ <p>Optimized array:from_orddict/1, it is now faster and
+ uses less memory if the orddict was sparse.</p>
+ <p>Changed array:reset/2, it will now never expand the
+ array which it could before for non fixed arrays. See the
+ documentation.</p>
+ <p>
+ Own Id: OTP-8216</p>
+ </item>
+ <item>
+ <p>The Erlang Pretty Printer (<c>erl_pp</c>) now puts the
+ leading <c>[</c> of list comprehensions as well as the
+ leading <c>&lt;&lt;</c> of bit string comprehensions on a
+ separate line in order to expose the Cover counter of the
+ template.</p>
+ <p>
+ Own Id: OTP-8227</p>
+ </item>
+ <item>
+ <p>The extension ".xrl" used for Leex input files is now
+ recognized by the compiler.</p>
+ <p>
+ Own Id: OTP-8232</p>
+ </item>
+ <item>
+ <p>
+ Some clarifications have been made in the documentation
+ regarding <c>gen_server</c>, <c>gen_fsm</c>, and
+ <c>gen_event</c> behavior when handling <c>'EXIT'</c>
+ messages from the parent process. For more information
+ see the <seealso
+ marker="gen_server">gen_server(3)</seealso>, <seealso
+ marker="gen_fsm">gen_fsm(3)</seealso>, and <seealso
+ marker="gen_event">gen_event(3)</seealso> documentation.</p>
+ <p>
+ Own Id: OTP-8255 Aux Id: seq11419 </p>
+ </item>
+ <item>
+ <p>The -on_load() directive can be used to run a function
+ when a module is loaded. It is documented in the section
+ about code loading in the Reference Manual.</p>
+ <p>
+ Own Id: OTP-8295</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.16.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ An erroneous type spec for <c>gen:start/6</c> caused
+ dialyzer to erroneously issue warnings when
+ <c>{spawn_opt, SpawnOptionList}</c> was passed in the
+ option list to the <c>gen_server</c> and <c>gen_fsm</c>
+ start functions.</p>
+ <p>
+ Own Id: OTP-8068 Aux Id: seq11323, seq11314 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.16.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The linter used to crash on invalid <c>-opaque</c>
+ declarations.</p>
+ <p>
+ Own Id: OTP-8051</p>
+ </item>
+ <item>
+ <p>Bugs in <c>digraph:add_edge/5</c> and
+ <c>digraph:del_path/3</c> have been fixed. (Thanks to
+ Crystal Din.)</p>
+ <p>
+ Own Id: OTP-8066</p>
+ </item>
+ <item>
+ <p>When trying to insert objects with
+ <c>dets:insert_new()</c> into a Dets table of type
+ <c>duplicate_bag</c>, already existing objects would
+ sometimes be duplicated. This bug has been fixed. (Thanks
+ to Crystal Din.)</p>
+ <p>
+ Own Id: OTP-8070</p>
+ </item>
+ <item>
+ <p>
+ Running erlc in a very deep directory (with a path length
+ of more 256 or more characters) would cause the emulator
+ to crash in a call to <c>list_to_atom/1</c>. (Thanks to
+ Chris Newcombe.)</p>
+ <p>
+ Own Id: OTP-8124</p>
+ </item>
+ <item>
+ <p>A few minor bugs have been fixed in the Erlang Code
+ Preprocessor (<c>epp</c>).</p>
+ <p>
+ Own Id: OTP-8130</p>
+ </item>
+ <item>
+ <p>A bug in The Erlang Meta Interpreter (<c>erl_eval</c>)
+ has been fixed: exceptions generated in the template of
+ bit string comprehensions were not handled properly.
+ (Thanks to Ulf Wiger.)</p>
+ <p>
+ Own Id: OTP-8133</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Option <c>{capture,none}</c> was missing in documentation
+ for <c>re:run/3</c>.</p>
+ <p>
+ Own Id: OTP-8113</p>
+ </item>
+ <item>
+ <p>When <c>erl_scan:tokens()</c> returns an error tuple
+ <c>{error, ErrorInfo, EndLocation</c>}, the list
+ <c>LeftOverChars</c> is the remaining characters of the
+ input data, starting from <c>EndLocation</c>. It used to
+ be the empty list.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8129</p>
+ </item>
+ <item>
+ <p>The Erlang Meta Interpreter (<c>erl_eval</c>) has been
+ somewhat optimized when it comes to interpreting
+ <c>receive</c>-expressions. (Thanks to Richard
+ Carlsson.)</p>
+ <p>
+ Own Id: OTP-8139</p>
+ </item>
+ <item>
+ <p>The Erlang Pretty Printer (<c>erl_pp</c>) has been
+ modified as to handle types.</p>
+ <p>
+ Own Id: OTP-8150</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.16.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The text of tokens returned by the Erlang scanner
+ (<c>erl_scan</c>) was sometimes empty when the
+ <c>text</c> option was given and <c>StartLocation</c> was
+ a line. This bug has been fixed.</p>
+ <p>
+ Own Id: OTP-7965</p>
+ </item>
+ <item>
+ <p>The documentation for <c>base64:decode/1</c> has been
+ updated to point out that it strips whitespace.</p>
+ <p><c>base64:decode/1</c> and <c>base64:mime_decode/1</c>
+ would sometimes fail instead of stripping away non-base64
+ characters.</p>
+ <p>
+ Own Id: OTP-7984</p>
+ </item>
+ <item>
+ <p>
+ Two types in the <c>gen</c> module were corrected.</p>
+ <p>
+ Own Id: OTP-8029 Aux Id: seq11296 </p>
+ </item>
+ <item>
+ <p>
+ <c>array:from_orddict([])</c> and
+ <c>array:from_list([])</c> would construct fixed arrays
+ instead of extendible arrays.</p>
+ <p>
+ Own Id: OTP-8033</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Interpreted escripts are now tail recursive.</p>
+ <p>
+ The function erl_eval:expr/5 has been introduced.</p>
+ <p>
+ Own Id: OTP-7933</p>
+ </item>
+ <item>
+ <p>
+ <c>gen_server:call/2,3</c> will be somewhat faster if the
+ calling process has a many messages in its message queue.</p>
+ <p>
+ Own Id: OTP-7979</p>
+ </item>
+ <item>
+ <p>
+ Random now supports seed with arity one,
+ <c>random:seed/1</c>, which takes a three-tuple.</p>
+ <p>
+ Own Id: OTP-8019</p>
+ </item>
+ <item>
+ <p>The <c>regexp</c> module now recognizes the escape
+ sequences <c>\xXY</c> and <c>\x{X...}</c>.</p>
+ <p>
+ Own Id: OTP-8024</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.16.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The documentation of <c>dets:open_file/1</c> now
+ states that the file is repaired if it has not been
+ properly closed. (Thanks to Ulf Wiger.)</p>
+ <p>
+ Own Id: OTP-7895</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The Erlang scanner no longer returns the text of
+ tokens when the start location is a pair of a line and
+ column unless the new option <c>text</c> is supplied
+ (incompatibility with R13A).</p> <p>There are new
+ functions to access the attributes of tokens:
+ <c>attributes_info/1,2</c> and
+ <c>set_attribute/3</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7892 Aux Id: OTP-7810 </p>
+ </item>
+ <item>
+ <p>
+ Several glitches and performance issues in the Unicode
+ and I/O-system implementation of R13A have been
+ corrected.</p>
+ <p>
+ Own Id: OTP-7896 Aux Id: OTP-7648 OTP-7887 </p>
+ </item>
+ <item>
+ <p>
+ The type spec of filelib:wildcard/2 has been corrected.</p>
+ <p>
+ Own Id: OTP-7915</p>
+ </item>
+ <item>
+ <p>New functions: <c>gb_sets:is_disjoint/2</c>,
+ <c>ordsets:is_disjoint/2</c>, and
+ <c>gb_sets:is_disjoint/2</c>.</p>
+ <p>
+ Own Id: OTP-7947</p>
+ </item>
+ <item>
+ <p>The function <c>gb_trees:map/2</c> which was added in
+ R13A is now documented.</p>
+ <p>
+ Own Id: OTP-7948</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.16</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a minor race conditions in
+ <c>gen_server:start*</c>: if one of these functions
+ returned <c>{error,Reason}</c> or <c>ignore</c>, the name
+ could still be registered (either locally or in
+ <c>global</c>).</p>
+ <p>A process started by <c>proc_lib</c> in some cases
+ depended on its process dictionary not to be erased, and
+ would crash when terminating abnormally and not generate
+ a proper crash report. This has been corrected (but the
+ initial call will not be shown in the error report if the
+ process dictionary has been erased). NOTE: There is no
+ longer any need to erase the process dictionary for
+ memory conservation reasons, since the actual call
+ arguments are no longer saved in the process
+ dictionary.</p>
+ <p>
+ Own Id: OTP-7669</p>
+ </item>
+ <item>
+ <p>The Erlang preprocessor used wrong line number when
+ stringifying macro arguments. (Thanks to John
+ Hughes.)</p>
+ <p>
+ Own Id: OTP-7702</p>
+ </item>
+ <item>
+ <p>A bug in the <c>qlc</c> module has been fixed: merge
+ join sometimes failed to return all answers. (Thanks to
+ Bernard Duggan.)</p>
+ <p>
+ Own Id: OTP-7714</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>A new option, <c>key_equality</c>, has been added to
+ <c>qlc:table/2</c>. This option makes it possible for
+ <c>qlc</c> to better handle tables that use <c>==/2</c>
+ when comparing keys for equality (examples of such tables
+ are ordered ETS tables and gb_table in qlc(3)).</p>
+ <p>
+ Own Id: OTP-6674</p>
+ </item>
+ <item>
+ <p>The functions <c>lists:seq/1,2</c> return the empty
+ list in a few cases when they used to generate an
+ exception, for example <c>lists:seq(1, 0)</c>. See
+ lists(3) for details. (Thanks to Richard O'Keefe.)</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7230</p>
+ </item>
+ <item>
+ <p>
+ The order of objects visited in select for ordered_set is
+ now documented.</p>
+ <p>
+ Own Id: OTP-7339</p>
+ </item>
+ <item>
+ <p>
+ It is now possible to debug code in escripts and
+ archives.</p>
+ <p>
+ Own Id: OTP-7626</p>
+ </item>
+ <item>
+ <p>Support for Unicode is implemented as described in
+ EEP10. Formatting and reading of unicode data both from
+ terminals and files is supported by the io and io_lib
+ modules. Files can be opened in modes with automatic
+ translation to and from different unicode formats. The
+ module 'unicode' contains functions for conversion
+ between external and internal unicode formats and the re
+ module has support for unicode data. There is also
+ language syntax for specifying string and character data
+ beyond the ISO-latin-1 range.</p>
+ <p>The interactive shell will support input and output of
+ unicode characters when the terminal and operating system
+ supports it.</p>
+ <p>Please see the EEP and the io/io_lib manual pages as
+ well as the stdlib users guide for details.</p>
+ <p><em>I/O-protocol incompatibilities:</em></p>
+ <p>The io_protocol between io_Server and client is
+ updated to handle protocol data in unicode formats. The
+ updated protocol is now documented. The specification
+ resides in the stdlib <em>users manual</em>, which is a
+ new part of the manual.</p>
+ <p><em>io module incompatibilities:</em></p>
+ <p>The io:put_chars, io:get_chars and io:get_line all
+ handle and return unicode data. In the case where
+ binaries can be provided (as to io:put_chars), they shall
+ be encoded in UTF-8. When binaries are returned (as by
+ io:get_line/get_chars when the io_server is set in
+ <em>binary mode</em>) the returned data is also
+ <em>always</em> encoded as UTF-8. The file module however
+ still returns byte-oriented data, why file:read can be
+ used instead of io:get_chars to read binary data in
+ ISO-latin-1.</p>
+ <p><em>io_lib module incompatibilities:</em></p>
+ <p>io_lib:format can, given new format directives (i.e
+ "~ts" and "~tc"), return lists containing integers larger
+ than 255. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7648 Aux Id: OTP-7580 OTP-7514 OTP-7494
+ OTP-7443 OTP-7181 EEP10 EEP11 </p>
+ </item>
+ <item>
+ <p>
+ The function <c>pool:attach/1</c> now returns
+ <c>already_attached</c> if the node is already attached,
+ rather than <c>allready_attached</c> (sic!). (Thanks to
+ Edwin Fine.)</p>
+ <p>
+ Own Id: OTP-7653 Aux Id: OTP-7603 </p>
+ </item>
+ <item>
+ <p>
+ Preprocessor directives are now allowed in escripts. This
+ means that for example macros may be used in escripts.</p>
+ <p>
+ Own Id: OTP-7662</p>
+ </item>
+ <item>
+ <p>When a process started with <c>proc_lib</c>,
+ <c>gen_server</c>, or <c>gen_fsm</c> exits with reason
+ <c>{shutdown,Term}</c>, a crash report will no longer be
+ generated (to allow a clean shutdown, but still provide
+ additional information to process that are linked to the
+ terminating process).</p>
+ <p>
+ Own Id: OTP-7740 Aux Id: seq10847 </p>
+ </item>
+ <item>
+ <p>
+ A new BIF, <c>lists:keyfind/3</c>, has been added. It
+ works like <c>lists:keysearch/3</c> except that it does
+ not wrap the returned tuple in a <c>value</c> tuple in
+ case of success. (Thanks to James Hague for suggesting
+ this function.)</p>
+ <p>
+ Own Id: OTP-7752</p>
+ </item>
+ <item>
+ <p><c>lists:suffix(Suffix, List)</c> used to have a a
+ complexity of <c>length(Suffix)*length(List)</c> (which
+ could become quite slow for some inputs). It has now been
+ re-implemented so that its complexity is
+ <c>length(Suffix)+length(List)</c>. (Thanks to Richard
+ O'Keefe for the new implementation.)</p>
+ <p>
+ Own Id: OTP-7797</p>
+ </item>
+ <item>
+ <p>The Erlang scanner has been augmented as to return
+ white spaces, comments, and exact location of tokens. The
+ functions <c>string/3</c>, <c>tokens/4</c>, and
+ <c>token_info/1,2</c> are new. See erl_scan(3) for
+ details.</p>
+ <p><c>tokens/3,4</c> have been modified as to return a
+ list of tokens instead of an error when <c>eof</c> is
+ encountered before the dot.</p>
+ <p>
+ Own Id: OTP-7810</p>
+ </item>
+ <item>
+ <p>
+ <c>filelib:fold_files/5</c> now uses the <c>re</c> module
+ instead of the <c>regexp</c> module for regular
+ expression matching. In practice, this change will not be
+ a problem for most regular expressions used for
+ <c>filelib:fold_files/5</c>. (The major difference in
+ regular expression is that parenthesis and curly brackets
+ is treated as literal characters by <c>regexp</c> but as
+ special characters by <c>re</c>; fortunately, those
+ characters are rarely used in filenames.)</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7819</p>
+ </item>
+ <item>
+ <p>
+ <c>digraph:new(Type)</c> will now cause a <c>badarg</c>
+ exception if <c>Type</c> is not a valid type. Similarly,
+ <c>digraph_utils:subgraph/2,3</c> will now cause a
+ <c>badarg</c> if the arguments are invalid. (Those
+ functions used to return error tuples if something was
+ wrong.)</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7824</p>
+ </item>
+ <item>
+ <p>The argument passed to <c>random:uniform/1</c> must
+ now be an integer (as stated in the documentation). In
+ previous releases, a floating point number was also
+ allowed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7827</p>
+ </item>
+ <item>
+ <p>The copyright notices have been updated.</p>
+ <p>
+ Own Id: OTP-7851</p>
+ </item>
+ <item>
+ <p>A few missing match spec functions was added to
+ dbg:fun2ms; exception_trace/0 and trace/2,3.</p>
+ <p>There is a new function queue:member/2.</p>
+ <p>A bug in io_lib:fread that made it accidentally
+ concatenate fields separated by newline has been
+ corrected. Reported and analyzed by Matthew Palmer to
+ erlang-patches.</p>
+ <p>
+ Own Id: OTP-7865</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+
+<section><title>STDLIB 1.15.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>A bug in the <c>qlc</c> module has been fixed: when
+ merge joining two query handles the temporary file used
+ for equivalence classes was not truncated properly which
+ could result in poor performance.</p>
+ <p>
+ Own Id: OTP-7552</p>
+ </item>
+ <item>
+ <p>
+ The characters 16#C0 and 16#E0 ("A" and "a" with grave
+ accent), were not properly converted by the
+ <c>string:to_lower/1</c> and <c>string:to_upper/1</c>
+ functions. (Thanks to Richard O'Keefe.)</p>
+ <p>
+ Own Id: OTP-7589</p>
+ </item>
+ <item>
+ <p>
+ The function <c>pool:attach/1</c> now returns
+ <c>already_attached</c> if the node is already attached,
+ rather than <c>allready_attached</c> (sic!). (Thanks to
+ Edwin Fine.)</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7603</p>
+ </item>
+ <item>
+ <p>The documentation for <c>io:get_line/1,2</c> now
+ mentions that the return value can also be
+ <c>{error,Reason}</c>.</p>
+ <p>
+ Own Id: OTP-7604 Aux Id: seq11063 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The split function is now added to the re library.
+ Exceptions and errors from both run, replace and split
+ are made more consistent.</p>
+ <p>
+ Own Id: OTP-7514 Aux Id: OTP-7494 </p>
+ </item>
+ <item>
+ <p>Processes spawned using <c>proc_lib</c> (including
+ <c>gen_server</c> and other library modules that use
+ <c>proc_lib</c>) no longer keep the entire argument list
+ for the initial call, but only the arity.</p>
+ <p>Also, if <c>proc_lib:spawn/1</c> is used to spawn a
+ fun, the actual fun is not kept, but only module,
+ function name, and arity of the function that implements
+ the fun.</p>
+ <p>The reason for the change is that keeping the initial
+ fun (or a fun in an argument list), would prevent
+ upgrading the code for the module. A secondary reason is
+ that keeping the fun and function arguments could waste a
+ significant amount of memory.</p>
+ <p>The drawback with the change is that the crash reports
+ will provide less precise information about the initial
+ call (only <c>Module:Function/Arity</c> instead of
+ <c>Module:Function(Arguments)</c>). The function
+ <c>proc_lib:initial_call/1</c> still returns a list, but
+ each argument has been replaced with a dummy atom.</p>
+ <p>
+ Own Id: OTP-7531 Aux Id: seq11036 </p>
+ </item>
+ <item>
+ <p>
+ There is now experimental support for loading of code
+ from archive files. See the documentation of <c>code</c>,
+ <c>init</c>, <c>erl_prim_loader </c> and <c>escript</c>
+ for more info.</p>
+ <p>
+ The error handling of <c>escripts</c> has been improved.</p>
+ <p>
+ An <c>escript</c> may now set explicit arguments to the
+ emulator, such as <c>-smp enabled</c>.</p>
+ <p>
+ An <c>escript</c> may now contain a precompiled beam
+ file.</p>
+ <p>
+ An <c>escript</c> may now contain an archive file
+ containing one or more applications (experimental).</p>
+ <p>
+ The internal module <c>code_aux</c> has been removed.</p>
+ <p>
+ Own Id: OTP-7548 Aux Id: otp-6622 </p>
+ </item>
+ <item>
+ <p>
+ Enabled explicit control of which types of files that
+ should be compressed in a ZIP archive.</p>
+ <p>
+ Own Id: OTP-7549 Aux Id: otp-6622 </p>
+ </item>
+ <item>
+ <p>
+ In the job control mode, the "s" and "r" commands now
+ take an optional argument to specify which shell to
+ start. (Thanks to Robert Virding.)</p>
+ <p>
+ Own Id: OTP-7617</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.15.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A bug in the calendar module could cause
+ calendar:local_time_to_universal_time_dst/1 to return
+ duplicate identical values for local times in timezones
+ without DST. Multiple values should only be returned when
+ a local time is within the hour occurring twice due to
+ shift from DST to non-DST, and certainly only in
+ timezones with DST. The correct behaviour is now
+ implemented.</p>
+ <p>
+ Own Id: OTP-7344 Aux Id: seq10960 </p>
+ </item>
+ <item>
+ <p>The documentation of <c>(d)ets:init_table()</c> has
+ been corrected. (Thanks to Paul Mineiro.)</p>
+ <p>
+ Own Id: OTP-7413</p>
+ </item>
+ <item>
+ <p>The soft upper limit of 60 on the number of non-white
+ characters on a line, which was introduced in R12B-0 for
+ the control sequences <c>p</c> and <c>P</c> of the
+ functions <c>io:fwrite/2,3</c> and
+ <c>io_lib:fwrite/2</c>, has been removed. This means that
+ terms whose printed representation fits on a line will
+ have no NEWLINEs. The Erlang shell still uses the 60
+ character limit, though.</p>
+ <p>
+ Own Id: OTP-7421 Aux Id: OTP-6708 </p>
+ </item>
+ <item>
+ <p>Some debug code has been removed from Dets.</p>
+ <p>
+ Own Id: OTP-7424</p>
+ </item>
+ <item>
+ <p>The documentation of <c>dets:match_delete/2</c> has
+ been corrected. (Thanks to Paul Mineiro.)</p>
+ <p>
+ Own Id: OTP-7445</p>
+ </item>
+ <item>
+ <p>Corrections of digraph(3). (Thanks to Vlad
+ Dumitrescu.)</p>
+ <p>
+ Own Id: OTP-7492</p>
+ </item>
+ <item>
+ <p>
+ For the process that an escript runs in, the
+ <c>trap_exit</c> process flag is now <c>false</c> instead
+ of <c>true</c> (as in previous releases). Scripts that
+ depend on the previous (counter-intuitive) behaviour
+ might not work. (Thanks to Bengt Kleberg.)</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7517</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The documentation of <c>lists:(u)sort/2</c> now states
+ what is expected of an ordering function.</p>
+ <p>
+ Own Id: OTP-7489</p>
+ </item>
+ <item>
+ <p>
+ The re module is extended with repetitive matches (global
+ option) and replacement function.</p>
+ <p>
+ Own Id: OTP-7494 Aux Id: OTP-7181 </p>
+ </item>
+ <item>
+ <p>The Erlang shell now displays a nicer error message
+ when evaluating an undefined command. (Thanks to Richard
+ Carlsson.)</p>
+ <p>
+ Own Id: OTP-7495</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+
+<section><title>STDLIB 1.15.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ zip:unzip to/from binary with empty directories did not
+ work. (Thanks to Martin Dvorak.)</p>
+ <p>
+ Own Id: OTP-7248</p>
+ </item>
+ <item>
+ <p>The documentation of the control sequence <c>w</c> of
+ the <c>io_lib</c> module now states that floating point
+ numbers are printed accurately.</p>
+ <p>
+ Own Id: OTP-7324 Aux Id: OTP-7084 </p>
+ </item>
+ <item>
+ <p>
+ zip:unzip was not supporting a flavour of the zip format
+ found in jar-files.</p>
+ <p>
+ Own Id: OTP-7382 Aux Id: seq10970 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ An experimental module "re" is added to the emulator
+ which interfaces a publicly available regular expression
+ library for Perl-like regular expressions (PCRE). The
+ interface is purely experimental and *will* be subject to
+ change.</p>
+ <p>
+ The implementation is for reference and testing in
+ connection to the relevant EEP.</p>
+ <p>
+ Own Id: OTP-7181</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.15.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> When inserting many small objects, Dets sometimes
+ crashed when reaching the maximum number of slots.
+ (Thanks to Daniel Goertzen.) </p>
+ <p>
+ Own Id: OTP-7146</p>
+ </item>
+ <item>
+ <p>Processes linked to the Erlang shell did not get an
+ exit signal when the evaluator process was killed. This
+ bug, introduced in R12B-0, has been fixed.</p>
+ <p>
+ Own Id: OTP-7184 Aux Id: OTP-6554 </p>
+ </item>
+ <item>
+ <p>
+ Invalid arguments to <c>ets:update_counter/3</c> were not
+ handled correctly. A tuple position (<c>Pos</c>) less
+ than 1 caused the element directly following the key to
+ be updated (as if no position at all had been specified).
+ All invalid values for <c>Pos</c> will now fail with
+ <c>badarg</c>.</p>
+ <p>
+ Own Id: OTP-7226</p>
+ </item>
+ <item>
+ <p>
+ For certain terminals, io:columns/0 could return 0
+ instead of enotsup. That is now corrected.</p>
+ <p>
+ Own Id: OTP-7229 Aux Id: seq10886 </p>
+ </item>
+ <item>
+ <p><c>qlc:info()</c> can now handle port identifiers,
+ pids, references, and funs. (Thanks to Wojciech Kaczmare
+ for reporting this bug.)</p> <p>When evaluating the
+ <c>parent_fun</c> messages sent to the process calling
+ <c>qlc:cursor()</c> were sometimes erroneously consumed.
+ This bug has been fixed.</p>
+ <p>
+ Own Id: OTP-7232</p>
+ </item>
+ <item>
+ <p><c>erl_parse:abstract()</c> can now handle bit
+ strings.</p>
+ <p>
+ Own Id: OTP-7234</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The <c>queue</c> module has been rewritten to make it
+ easier to use. Suggestions and discussion from and with
+ among others Lev Walkin, Anders Ramsell and Rober Virding
+ in december 2007 on [email protected]. It was
+ also discussed to change the internal representation to
+ contain length information which would speed up
+ <c>len/1</c> but that change has been postponed. Anyone
+ interested may write an EEP and try to reach an
+ acceptable compromise for queue overhead and thereby the
+ speed of all other operations than <c>len/1</c>. The
+ <c>queue</c> module is now optimized for fast and minimal
+ garbage <c>in/2</c> and <c>out/1</c> and such. See the
+ documentation.</p>
+ <p>New functions: <c>is_queue/1</c>, <c>get/1</c>,
+ <c>get_r/1</c>, <c>peek/1</c>, <c>peek_r/1</c>,
+ <c>drop/1</c>, <c>drop_r/1</c> and <c>liat/1</c>.
+ <c>is_queue/1</c> is a new predicate, <c>liat/1</c> is a
+ correction of an old misspelling, and the others
+ (<c>get</c>*, <c>peek</c>* and <c>drop</c>*) are new
+ interface functions.</p>
+ <p>
+ Own Id: OTP-7064</p>
+ </item>
+ <item>
+ <p>The functions <c>io_lib:write/1,2</c> and
+ <c>io_lib:print/1,4</c> have been changed when it comes
+ to writing floating point numbers. This change affects
+ the control sequences <c>p</c>, <c>P</c>, <c>w</c>, and
+ <c>W</c> of the <c>io_lib</c> module. (Thanks to Bob
+ Ippolito for code contribution.) </p>
+ <p>
+ Own Id: OTP-7084</p>
+ </item>
+ <item>
+ <p>
+ Updated the documentation for
+ <c>erlang:function_exported/3</c> and <c>io:format/2</c>
+ functions to no longer state that those functions are
+ kept mainly for backwards compatibility.</p>
+ <p>
+ Own Id: OTP-7186</p>
+ </item>
+ <item>
+ <p>
+ A new BIF ets:update_element/3. To update individual
+ elements within an ets-tuple, without having to read,
+ update and write back the entire tuple.</p>
+ <p>
+ Own Id: OTP-7200</p>
+ </item>
+ <item>
+ <p><c>string:join/2</c> now accepts an empty list as
+ first argument.</p>
+ <p>
+ Own Id: OTP-7231 Aux Id: OTP-6671 </p>
+ </item>
+ <item>
+ <p><c>qlc:info/1,2</c> accepts a new option,
+ <c>depth</c>. The type <c>SelectedObjects</c> used in the
+ description of <c>qlc:table/2</c> has been augmented.</p>
+ <p>
+ Own Id: OTP-7238</p>
+ </item>
+ <item>
+ <p><c>tuple_size/1</c> and <c>byte_size/1</c> have been
+ substituted for <c>size/1</c> in the documentation.</p>
+ <p>
+ Own Id: OTP-7244</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.15.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Ets:select/3 in combination with
+ ets:repair_continuation/2 and ordered_set data tables
+ could result in function_clause although used as
+ intended. This is now corrected. Thanks to Paul Mineiro
+ for finding and isolating the bug!</p>
+ <p>
+ Own Id: OTP-7025</p>
+ </item>
+ <item>
+ <p>The compiler warning for the deprecated function
+ <c>ftp:close/1</c> now mentions the correct replacement
+ function.</p>
+ <p>The warning for the removed functions in the
+ <c>httpd_util</c> module have been changed to say they
+ have been removed, not merely deprecated. (Thanks to
+ Fredrik Thulin.)</p>
+ <p>
+ Own Id: OTP-7034 Aux Id: seq10825 </p>
+ </item>
+ <item>
+ <p>In <c>(Expr)#r{}</c> (no fields are updated),
+ <c>Expr</c> is no longer evaluated more than once. There
+ is also a test that <c>Expr</c> is of the correct record
+ type. (Thanks to Dominic Williams.)</p>
+ <p>
+ Own Id: OTP-7078 Aux Id: OTP-4962 </p>
+ </item>
+ <item>
+ <p>Documentation bugfixes and clarifications.</p> (Thanks
+ to Joern ([email protected]), Matthias Lang, and Richard
+ Carlsson.)
+ <p>
+ Own Id: OTP-7079</p>
+ </item>
+ <item>
+ <p>Duplicated objects were sometimes not deleted from the
+ list of answers when a QLC table was traversed using a
+ match specification. (Thanks to Dmitri Girenko.)</p>
+ <p>
+ Own Id: OTP-7114</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The documentation has been updated so as to reflect
+ the last updates of the Erlang shell as well as the minor
+ modifications of the control sequence <c>p</c> of the
+ <c>io_lib</c> module.</p> <p>Superfluous empty lines have
+ been removed from code examples and from Erlang shell
+ examples.</p>
+ <p>
+ Own Id: OTP-6944 Aux Id: OTP-6554, OTP-6911 </p>
+ </item>
+ <item>
+ <p><c>tuple_size/1</c> and <c>byte_size/1</c> have been
+ substituted for <c>size/1</c>.</p>
+ <p>
+ Own Id: OTP-7009</p>
+ </item>
+ <item>
+ <p>
+ It is now possible to hibernate a
+ gen_server/gen_event/gen_fsm. In gen_server and gen_fsm,
+ hibernation is triggered by returning the atom
+ 'hibernate'�instead of a timeout value. In the gen_event
+ case hibernation is triggered by a event handler
+ returning a tuple with an extra element containing the
+ atom 'hibernate'.</p>
+ <p>
+ Own Id: OTP-7026 Aux Id: seq10817 </p>
+ </item>
+ <item>
+ <p>Some undocumented debug functionality has been added
+ to Dets.</p>
+ <p>
+ Own Id: OTP-7066</p>
+ </item>
+ <item>
+ <p>The functions <c>digraph_utils:is_tree/1</c>,
+ <c>digraph_utils:is_arborescence/1</c>, and
+ <c>digraph_utils:arborescence_root/1</c> are new.</p>
+ <p>
+ Own Id: OTP-7081</p>
+ </item>
+ <item>
+ <p>
+ The compiler could generate suboptimal code for record
+ updates if the record update code consisted of multiple
+ source code lines.</p>
+ <p>
+ Own Id: OTP-7101</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.15</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Bugs have been fixed in <c>qlc</c>:</p> <list
+ type="bulleted"> <item>Setting the <c>lookup_fun</c>
+ option of <c>qlc:table/2</c> to <c>undefined</c> could
+ cause a crash.</item> <item>If a QLC restricted some
+ column of a table in such a way that a traversal using a
+ match specification was possible and the QLC also
+ compared the key column or some indexed column of the the
+ table with a column of some other table, <c>qlc</c>
+ always chose to traverse the table first, never
+ considering lookup join. This has been changed so that
+ lookup join is always preferred; if an initial traversal
+ using the match specification is desired, the query needs
+ to be rewritten introducing an extra QLC with the
+ filter(s) restricting the column.</item> <item>When
+ trying to find candidates for match specifications and
+ lookup, filters using variables from one generator only
+ are ignored unless they are placed immediately after the
+ generator and possibly other filters using variables from
+ the same generator. In particular, filters joining two
+ tables should not be placed between the generator and the
+ filters using the generator only.</item> <item>The
+ call-back function <c>TraverseFun</c> used for
+ implementing QLC tables is allowed to return a term other
+ than a list since STDLIB 1.14 (OTP-5195). However, when
+ the returned term was a fun <c>qlc</c> often tried to
+ call the fun instead of returning it.</item> </list> <p>A
+ few minor optimizations have been implemented as
+ well.</p>
+ <p>
+ Own Id: OTP-6673</p>
+ </item>
+ <item>
+ <p>A bug concerning the use of parameterized modules from
+ the shell has been fixed.</p>
+ <p>
+ Own Id: OTP-6785</p>
+ </item>
+ <item>
+ <p>A bug regarding the size expression of the bit syntax
+ has been fixed in the <c>erl_eval</c> module.</p>
+ <p>
+ Own Id: OTP-6787</p>
+ </item>
+ <item>
+ <p>
+ The log_mf_h event handler didn't close the index file
+ when it was done reading it causing a file descriptor
+ leak.</p>
+ <p>
+ Own Id: OTP-6800</p>
+ </item>
+ <item>
+ <p>
+ Definitions for the <c>filename()</c> and
+ <c>dirname()</c> types have been added to the
+ documentation for the <c>filelib</c> module.</p>
+ <p>
+ Own Id: OTP-6870</p>
+ </item>
+ <item>
+ <p>file:write_file/3, file:write/2 and file:read/2 could
+ crash (contrary to documentation) for odd enough file
+ system problems, e.g write to full file system. This bug
+ has now been corrected.</p> <p>In this process the file
+ module has been rewritten to produce better error codes.
+ Posix error codes now originate from the OS file system
+ calls or are generated only for very similar causes (for
+ example 'enomem' is generated if a memory allocation
+ fails, and 'einval' is generated if the file handle in
+ Erlang is a file handle but currently invalid).</p>
+ <p>More Erlang-ish error codes are now generated. For
+ example <c>{error,badarg}</c> is now returned from
+ <c>file:close/1</c> if the argument is not of a file
+ handle type. See file(3).</p> <p>The possibility to write
+ a single byte using <c>file:write/2</c> instead of a list
+ or binary of one byte, contradictory to the
+ documentation, has been removed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-6967 Aux Id: OTP-6597 OTP-6291 </p>
+ </item>
+ <item>
+ <p>A bug concerning the evaluation of the <c>++/2</c>
+ operator has been fixed in <c>erl_eval</c>. (Thanks to
+ Matthew Dempsky.)</p>
+ <p>
+ Own Id: OTP-6977</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The behaviour of the internal functions gen:call/3,4
+ has been changed slightly in the rare case that when the
+ caller was linked to the called server, and the server
+ crashed during the call; its exit signal was consumed by
+ the gen:call/3,4 code and converted to an exit exception.
+ This exit signal is no longer consumed.</p>
+ <p>To even notice this change, 1) the calling process has
+ to be linked to the called server.</p>
+ <p>
+ 2) the call must not be remote by name that is it must be
+ local or remote by pid, local by name or global by name.</p>
+ <p>
+ 3) the calling process has to have set
+ <c>process_flag(trap_exit, true)</c>.</p>
+ <p>
+ 4) the server has to crash during the call.</p>
+ <p>
+ 5) the calling process has to be sensitive to getting
+ previously consumed <c>{'EXIT',Pid,Reason}</c> messages
+ in its message queue.</p>
+ <p>The old behaviour was once the only way for a client
+ to notice if the server died, but has since
+ <c>erlang:monitor(process, {Name,Node})</c> was
+ introduced and used in gen:call been regarded as an
+ undesired behaviour if not a bug.</p>
+ <p>The affected user APIs are:
+ <c>gen_server:call/2,3</c>,
+ <c>gen_fsm:sync_send_event/2,3</c>,
+ <c>gen_fsm:sync_send_all_state_event/2,3</c>,
+ <c>gen_event:_</c>, <c>sys:_</c> and maybe a few others
+ that hardly will be noticed.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-3954 Aux Id: Seq 4538 </p>
+ </item>
+ <item>
+ <p>When an exception occurs the Erlang shell now displays
+ the class, the reason, and the stacktrace in a clearer
+ way (rather than dumping the raw EXIT tuples as before).
+ <c>proc_lib:format/1</c> displays the exception of crash
+ reports in the same clearer way.</p> <p>The new shell
+ command <c>catch_exception</c> and the new application
+ configuration parameter <c>shell_catch_exception</c> can
+ be used for catching exceptions that would normally exit
+ the Erlang shell.</p>
+ <p>
+ Own Id: OTP-6554 Aux Id: OTP-6289 </p>
+ </item>
+ <item>
+ <p>The function <c>string:join/2</c> joins strings in a
+ list with a separator. Example: '<c>string:join(["a",
+ "b", "c"], ", ") gives "a, b, c"</c>'</p>
+ <p>
+ Own Id: OTP-6671</p>
+ </item>
+ <item>
+ <p>The control sequence <c>P</c> of the <c>Format</c>
+ argument of the functions <c>io:fwrite/2,3</c> and
+ <c>io_lib:fwrite/2</c> now inserts fewer line breaks when
+ printing tuples and lists. A soft upper limit of 60 on
+ the number of non-white characters on a line has been
+ introduced.</p>
+ <p>
+ Own Id: OTP-6708</p>
+ </item>
+ <item>
+ <p>
+ The new module <c>array</c> provides a fast functional
+ array implementation.</p>
+ <p>
+ Own Id: OTP-6733</p>
+ </item>
+ <item>
+ <p>Functions that have long been deprecated have now been
+ removed from the following modules: <c>dict</c>,
+ <c>erl_eval</c>, <c>erl_pp</c>, <c>io</c>, <c>io_lib</c>,
+ <c>lists</c>, <c>orddict</c>, <c>ordsets</c>,
+ <c>sets</c>, and <c>string</c>.</p>
+ <p>The undocumented function <c>lists:zf/3</c> has also
+ been removed (use a list comprehension or
+ <c>lists:zf/2</c> instead).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-6845</p>
+ </item>
+ <item>
+ <p>
+ Minor documentation corrections for file:pread/2 and
+ file:pread/3.</p>
+ <p>
+ Own Id: OTP-6853</p>
+ </item>
+ <item>
+ <p>
+ Contract directives for modules in Kernel and STDLIB.</p>
+ <p>
+ Own Id: OTP-6895</p>
+ </item>
+ <item>
+ <p>The <c>ets:fixtable/2</c> function, which has been
+ deprecated for several releases, has been removed.</p>
+ <p>The <c>ets:info/1</c> function has been reimplemented
+ as a BIF, which guarantees that information returned is
+ consistent.</p>
+ <p>The <c>ets:info/2</c> function now fails with reason
+ <c>badarg</c> if the second argument is invalid.
+ (Dialyzer can be used to find buggy code where the second
+ argument is misspelled.)</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-6906</p>
+ </item>
+ <item>
+ <p>The Erlang pretty printer <c>erl_pp</c> now inserts
+ more newlines in order to facilitate line coverage
+ analysis by <c>Cover</c>. (Thanks to Thomas Arts.)</p>
+ <p>
+ Own Id: OTP-6911</p>
+ </item>
+ <item>
+ <p>
+ The documentation for ets:safe_fixtable/2, ets:foldl/3,
+ and ets:foldr/3 is now clearer about what will happen if
+ objects are inserted during table traversals.</p>
+ <p>
+ Own Id: OTP-6928 Aux Id: seq10779 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to extract files in tar files directly
+ into binaries. It is also possible to add files to tar
+ files directly from binaries.</p>
+ <p>
+ Own Id: OTP-6943</p>
+ </item>
+ <item>
+ <p>The functions <c>keystore/4</c> and <c>keytake/3</c>
+ are new in the <c>lists</c> module.</p>
+ <p>
+ Own Id: OTP-6953</p>
+ </item>
+ <item>
+ <p>The new <c>qlc</c> option <c>tmpdir_usage</c> can be
+ used for outputting messages onto the error logger when a
+ temporary file is about to be created, or to prohibit the
+ usage of temporary files altogether.</p>
+ <p>
+ Own Id: OTP-6964</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.14.5.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The allowed syntax for -type() and -spec() was updated.</p>
+ <p>
+ Own Id: OTP-6861 Aux Id: OTP-6834 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 1.14.5.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The compiler will for forward compatibility ignore the
+ -type() and -spec() attributes that will be introduced in
+ the R12B release.</p>
+ <p>
+ Own Id: OTP-6834</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+<section><title>STDLIB 1.14.5.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The log_mf_h event handler didn't close the index file
+ when it was done reading it causing a file descriptor
+ leak.</p>
+ <p>
+ Own Id: OTP-6800</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The dict:size/1 and orddict:size/1 functions have been
+ documented.</p>
+ <p>
+ Own Id: OTP-6818</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+ <section>
+ <title>STDLIB 1.14.5</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Bugs have been fixed in Dets concerning comparison
+ (==) and matching (=:=).</p>
+ <p>The STDLIB manual pages
+ have been updated as to more carefully state when terms
+ are matched and when they are compared.</p>
+ <p>Own Id: OTP-4738 Aux Id: OTP-4685 </p>
+ </item>
+ <item>
+ <p>The shell has been updated to fix the following flaws:
+ Shell process exit left you with an unresponsive initial
+ shell if not using oldshell. Starting a restricted shell
+ with a nonexisting callback module resulted in a shell
+ where no commands could be used, not even init:stop/0.
+ Fun's could not be used as parameters to local shell
+ functions (in shell_default or user_default) when
+ restricted_shell was active.</p>
+ <p>Own Id: OTP-6537</p>
+ </item>
+ <item>
+ <p>A bug in QLC's parse transform has been fixed.</p>
+ <p>Own Id: OTP-6590</p>
+ </item>
+ <item>
+ <p>A bug concerning <c>lists:sort/1</c> and
+ <c>lists:keysort/2</c> and a mix of floating point
+ numbers and integers has been fixed.</p>
+ <p>Own Id: OTP-6606</p>
+ </item>
+ <item>
+ <p>When calling <c>erlang:garbage_collect/0</c> in the
+ Erlang shell not only the evaluator process (the one
+ returned by calling <c>self()</c> in the Erlang shell) is
+ garbage collected, but also the process holding the
+ history list.</p>
+ <p>Own Id: OTP-6659</p>
+ </item>
+ <item>
+ <p>Functions of the <c>beam_lib</c> module that used to
+ catch exceptions and return a tuple
+ <c>{'EXIT',Reason}</c> now exit with the reason
+ <c>Reason</c>.</p>
+ <p>Own Id: OTP-6711</p>
+ </item>
+ <item>
+ <p>The <c>erl_eval</c> module now calls the non-local
+ function handler whenever an operator is evaluated
+ (exceptions are <c>andalso</c>, <c>orelse</c>, and
+ <c>catch</c>). The non-local function handler is now also
+ called when the function or operator occurs in a guard
+ test (such calls used to be ignored).</p>
+ <p>These changes affect the Erlang shell when running in
+ restricted mode: the callback function
+ <c>non_local_allowed/3</c> is now called for operators
+ such as <c>'!'/2</c>. This means that
+ <c>non_local_allowed/3</c> may need to be changed as to
+ let operators through. Note that <c>erlang:'!'/2</c> as
+ well as <c>erlang:send/2,3</c> have to be restricted in
+ order to stop message passing in the shell.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-6714 Aux Id: seq10374 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The new compiler option <c>warn_obsolete_guard</c> can
+ be used for turning on warnings for calls to old type
+ testing BIFs.</p>
+ <p>Own Id: OTP-6585</p>
+ </item>
+ <item>
+ <p>For scripts written using <c>escript</c>, there is a new
+ function <c>escript:script_name/0</c>, which can be used
+ to retrieve the pathame of the script. The documentation
+ has been clarified regarding pre-defined macros such as
+ ?MODULE and the module name.</p>
+ <p>Own Id: OTP-6593</p>
+ </item>
+ <item>
+ <p>Minor Makefile changes.</p>
+ <p>Own Id: OTP-6689 Aux Id: OTP-6742 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14.4</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The MD5 calculation of a BEAM file done by
+ <c>code:module_md5/1</c>, <c>beam_lib:md5/1</c>, and by
+ the compiler for the default value of the <c>vsn</c>
+ attribute have all been changed so that its result will
+ be the same on all platforms; modules containing funs
+ could get different MD5s on different platforms.</p>
+ <p>Own Id: OTP-6459</p>
+ </item>
+ <item>
+ <p>When sorting terms using the <c>file_sorter</c> module
+ (the option <c>Format</c> set to <c>term</c>), file
+ errors were not always properly handled. This bug has
+ been fixed.</p>
+ <p>The directory supplied with the
+ <c>tmpdir</c> option is no longer checked unless it is
+ actually used. The error reason <c>not_a_directory</c>
+ can no longer be returned; instead a <c>file_error</c>
+ tuple is returned</p>
+ <p>Own Id: OTP-6526</p>
+ </item>
+ <item>
+ <p>Bugs regarding <c>try</c>/<c>catch</c> have been fixed
+ in the <c>erl_eval</c> module.</p>
+ <p>Own Id: OTP-6539</p>
+ </item>
+ <item>
+ <p>When sorting the operands of a join operation, QLC
+ called <c>file:open/3</c> with bad arguments. This bug
+ has been fixed.</p>
+ <p>Own Id: OTP-6562 Aux Id: seq10606 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The functions <c>beam_lib:cmp/1</c> and
+ <c>beam_lib:strip/1</c> (and similar functions) have been
+ updated to handle optional chunks (such as "FunT") in
+ more general way in order to be future compatible.</p>
+ <p>The function <c>beam_lib:chunks/3</c> has been
+ added.</p>
+ <p>The function <c>beam_lib:md5/1</c> has been added.</p>
+ <p>Own Id: OTP-6443</p>
+ </item>
+ <item>
+ <p>Added base64 as a module to stdlib, encoding and decoding</p>
+ <p>Own Id: OTP-6470</p>
+ </item>
+ <item>
+ <p>Added the functions to_upper/1 and to_lower/1 to the
+ string module. These provide case conversion for ISO/IEC
+ 8859-1 characters (Latin1) and strings.</p>
+ <p>Own Id: OTP-6472</p>
+ </item>
+ <item>
+ <p>The callback function <c>non_local_allowed/3</c> used
+ by the restricted shell can now return the value
+ <c>{{restricted,NewFuncSpec,NewArgList},NewState}</c>
+ which can be used for letting the shell call some other
+ function than the one specified.</p>
+ <p>Own Id: OTP-6497 Aux Id: seq10555 </p>
+ </item>
+ <item>
+ <p>There is a new <c>escript</c> program that can be used
+ for writing scripts in Erlang. Erlang scripts don't need
+ to be compiled and any arguments can be passed to them
+ without risk that they are interpreted by the Erlang
+ system.</p>
+ <p>Own Id: OTP-6505</p>
+ </item>
+ <item>
+ <p>The <c>Format</c> argument of the functions
+ <c>io:fwrite/2,3</c> and <c>io_lib:fwrite/2</c> is now
+ allowed to be a binary.</p>
+ <p>Own Id: OTP-6517</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14.3.1</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The control sequences <c>p</c> and <c>P</c> of the
+ <c>Format</c> argument of the functions
+ <c>io:fwrite/2,3</c> and <c>io_lib:fwrite/2</c> could
+ cause a <c>badarg</c> failure when applied to binaries.
+ This bug was introduced in STDLIB 1.14.3. (Thanks to
+ Denis Bilenko.)</p>
+ <p>Own Id: OTP-6495</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>Added the option {cwd, Dir} to make zip-archives with
+ relative pathnames without having to do (a global)
+ file:set_cwd.</p>
+ <p>Own Id: OTP-6491 Aux Id: seq10551 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14.3</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The <c>spawn_opt/2,3,4,5</c> option <c>monitor</c> --
+ introduced in Kernel 2.11.2 -- is currently not possible
+ to use when starting a process using <c>proc_lib</c>,
+ that is, also when starting a gen_server, gen_fsm etc. </p>
+ <p>This limitation has now been properly documented and the
+ behavior of the <c>gen_fsm</c>, <c>gen_server</c>, and
+ <c>proc_lib</c><c>start</c> and <c>start_link</c>
+ functions when providing this option has been changed
+ from hanging indefinitely to failing with reason
+ <c>badarg</c>.</p>
+ <p>(Thanks to Fredrik Linder)</p>
+ <p>Own Id: OTP-6345</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The control sequence <c>P</c> of the <c>Format</c>
+ argument of the functions <c>io:fwrite/2,3</c> and
+ <c>io_lib:fwrite/2</c> now replaces the tail of binary
+ strings with <c>...</c> when the maximum depth has been
+ reached. For instance, <c><![CDATA[io:fwrite("~P", [<<"a binary string">>, 3]).]]></c> prints <c><![CDATA[<<"a binary"...>>]]></c>.</p>
+ <p>The indentation takes more care not to exceed the
+ right margin, if possible.</p>
+ <p>If the maximum depth is
+ reached while printing a tuple, <c>,...</c> is printed
+ instead of <c>|...</c> (this change applies to the
+ control sequence <c>W</c> as well).</p>
+ <p>Own Id: OTP-6354</p>
+ </item>
+ <item>
+ <p>The Erlang shell command <c>h/0</c> that prints the
+ history list now avoids printing (huge) terms referred to
+ by <c>v/1</c> but instead just prints the call to
+ <c>v/1</c>.</p>
+ <p>Own Id: OTP-6390</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14.2.2</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The functions <c>dets:select/1,3</c>,
+ <c>dets:match/1,3</c>, and <c>dets:match_object/1,3</c>
+ have been changed as to never return
+ <c>{[],Continuation}</c>. This change affects the
+ corresponding functions in Mnesia.</p>
+ <p>Bugs have been
+ fixed in QLC: <c>qlc:info()</c> could crash if the
+ <c>tmpdir</c> option did not designate a valid directory;
+ the results of looking up keys are kept in RAM, which
+ should improve performance.</p>
+ <p>Own Id: OTP-6359</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14.2.1</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>A bug in <c>erl_pp:exprs()</c> has been fixed.</p>
+ <p>Own Id: OTP-6321 Aux Id: seq10497 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14.2</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The control sequences <c>p</c> and <c>P</c> of the
+ <c>Format</c> argument of the functions
+ <c>io:format/2,3</c> and <c>io_lib:format/2</c> did not
+ handle binaries very well. This bug, introduced in
+ stdlib-1.14, has been fixed.</p>
+ <p>Own Id: OTP-6230</p>
+ </item>
+ <item>
+ <p><c>filelib:wildcard(Wc, PathWithRedundantSlashes)</c>,
+ where <c>PathWithRedundantSlashes</c> is a directory path
+ containing redundant slashes, such as <c>/tmp/</c> or
+ <c>//tmp</c>, could return incorrect results. (Thanks to
+ Martin Bjorklund.)</p>
+ <p>Own Id: OTP-6271</p>
+ </item>
+ <item>
+ <p>The Erlang code preprocessor crashed if the predefined
+ macros ?MODULE or ?MODULE_STRING were used before the
+ module declaration. This bug has been fixed.</p>
+ <p>Own Id: OTP-6277</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>Support for faster join of two tables has been added
+ to the <c>qlc</c> module. There are two kinds of fast
+ joins: lookup join that uses existing indices, and merge
+ join that takes two sorted inputs. There is a new
+ <c>join</c> option that can be used to force QLC to use a
+ particular kind of join in some QLC expression.</p>
+ <p>Several other changes have also been included:</p>
+ <p></p>
+ <list type="bulleted">
+ <item>
+ <p>The new <c>tmpdir</c> option of <c>cursor/2</c>,
+ <c>eval/2</c>, <c>fold/4</c>, and <c>info/2</c> can be
+ used to set the directory that join uses for temporary
+ files. The option also overrides the <c>tmpdir</c> option
+ of <c>keysort/3</c> and <c>sort/2</c>.</p>
+ </item>
+ <item>
+ <p>The new <c>lookup</c> option can be used to
+ assert that constants are looked up when evaluating some
+ QLC expression.</p>
+ </item>
+ <item>
+ <p>The <c>cache</c> and <c>cache_all</c> options
+ accept new tags: <c>ets</c>, <c>list</c>, and <c>no</c>.
+ The tag <c>list</c> caches answers in a list using a
+ temporary file if the answers cannot be held in RAM.
+ Combining <c>{cache,list}</c> and <c>{unique, true}</c>
+ is equivalent to calling <c>sort/2</c> with the option
+ <c>unique</c> set to <c>true</c>. The old tags
+ <c>true</c> (equivalent to <c>ets</c>) and <c>false</c>
+ (equivalent to <c>no</c>) are recognized for backward
+ compatibility.</p>
+ </item>
+ <item>
+ <p>The new option <c>max_list_size</c> can be used
+ to set the limit where merge join starts to use temporary
+ files for large equivalence classes and when answers
+ cached in lists are put on temporary files.</p>
+ </item>
+ <item>
+ <p>There is a new callback <c>is_sorted_key</c> to
+ be supplied as an option to <c>table/2</c>.</p>
+ </item>
+ <item>
+ <p>QLC analyzes each and every QLC expression when
+ trying to find constants for the lookup function.
+ Hitherto only QLC expressions with exactly one generator
+ were analyzed.</p>
+ <p>Note that only filters with guard
+ syntax placed immediately after the generator are
+ analyzed. The restriction to guard filters is an
+ incompatible change. See <c>qlc(3)</c> for further
+ details.</p>
+ </item>
+ <item>
+ <p>In a similar way several match specifications
+ for traversal of QLC tables can be utilized for different
+ generators of one single QLC expression.</p>
+ </item>
+ <item>
+ <p>A bug has been fixed: when caching answers to a
+ sufficiently complex query it could happen that some
+ answers were not returned.</p>
+ </item>
+ </list>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-6038</p>
+ </item>
+ <item>
+ <p>The Erlang pretty printer (<c>erl_pp</c>) is now much
+ faster when the code is deeply nested. A few minor bugs
+ have been fixed as well.</p>
+ <p>Own Id: OTP-6227 Aux Id: OTP-5924 </p>
+ </item>
+ <item>
+ <p>The Erlang shell now tries to garbage collect large
+ binaries. Under certain circumstances such binaries could
+ otherwise linger on for an indefinite amount of time.</p>
+ <p>Own Id: OTP-6239</p>
+ </item>
+ <item>
+ <p>To help Dialyzer find more bugs, many functions in the
+ Kernel and STDLIB applications now only accept arguments
+ of the type that is documented.</p>
+ <p>For instance, the functions <c>lists:prefix/2</c> and
+ <c>lists:suffix/2</c> are documented to only accept lists
+ as their arguments, but they actually accepted anything
+ and returned <c>false</c>. That has been changed so that
+ the functions cause an exception if one or both arguments
+ are not lists.</p>
+ <p>Also, the <c>string:strip/3</c> function is documented
+ to take a character argument that is a character to strip
+ from one or both ends of the string. Given a list instead
+ of a character, it used to do nothing, but will now cause
+ an exception.</p>
+ <p>Dialyzer will find most cases where those functions
+ are passed arguments of the wrong type.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-6295</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14.1</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The functions <c>c:y/1,2</c> which call
+ <c>yecc:file/1,2</c> are now listed by
+ <c>c:help/0</c>.</p>
+ <p>Documentation of <c>c:y/1,2</c> has been added to
+ <c>c(3)</c>.</p>
+ <p>The fact that the control sequence character <c>s</c>
+ recognizes binaries and deep character lists has been
+ documented in <c>io(3)</c>. This feature was added in
+ R11B-0 (OTP-5403).</p>
+ <p>Own Id: OTP-6140</p>
+ </item>
+ <item>
+ <p>The shell command rr() sometimes failed to read record
+ definitions from file(s). This problem has been fixed.</p>
+ <p>Own Id: OTP-6166 Aux Id: OTP-5878 </p>
+ </item>
+ <item>
+ <p>The nonlocal function handler in <c>erl_eval</c>, which
+ is used for implementing the restricted mode of the
+ Erlang shell, did not handle calls to
+ <c>erlang:apply/3</c> correctly. This bug has been fixed.</p>
+ <p>Own Id: OTP-6169 Aux Id: seq10374 </p>
+ </item>
+ <item>
+ <p>ets:rename/1 could deadlock, or crash the SMP emulator
+ when the table wasn't a named table.</p>
+ <p>ets:next/2, and ets:prev/2 could return erroneous results
+ on the SMP emulator.</p>
+ <p>Own Id: OTP-6198 Aux Id: seq10392, seq10415 </p>
+ </item>
+ <item>
+ <p>When closing a Dets table the space management data was
+ sometimes saved in such a way that opening the table
+ could not be done without repairing the file. This bug
+ has been fixed.</p>
+ <p>Own Id: OTP-6206</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.14</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>A bugfix in QLC: two of the call-back functions used
+ for implementing QLC tables, <c>TraverseFun</c> and
+ <c>LookupFun</c>, are now allowed to return a term other
+ than a list. Such a term is immediately returned as the
+ results of the current query, and is useful mostly for
+ returning error tuples.</p>
+ <p>Several other minor bugs have been also been fixed.</p>
+ <p>Own Id: OTP-5195</p>
+ </item>
+ <item>
+ <p>The STDLIB modules <c>error_logger_file_h</c> and
+ <c>error_logger_tty_h</c> now read the environment
+ variable <c>utc_log</c> from the SASL application.</p>
+ <p>Own Id: OTP-5535</p>
+ </item>
+ <item>
+ <p><c>ets:info/1</c> has been corrected to behave according
+ to the documentation and return a list of tuples, not a
+ tuple with tuples.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-5639</p>
+ </item>
+ <item>
+ <p>Referencing a so far undeclared record from the default
+ value of some record declaration is from now on considered
+ an error by the linter. It is also an error if the default
+ value of a record declaration uses or binds a variable.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-5878</p>
+ </item>
+ <item>
+ <p>When a file <c>.hrl</c> file is included using
+ <c>-include_lib</c>, the include path is temporarily
+ updated to include the directory the <c>.hrl</c> file was
+ found in, which will allow that <c>.hrl</c> file to itself
+ include files from the same directory as itself using
+ <c>-include</c>. (Thanks to Richard Carlsson.)</p>
+ <p>Own Id: OTP-5944</p>
+ </item>
+ <item>
+ <p>Corrected <c>filelib:ensure_dir/1</c> which sometimes
+ returned <c>true</c> and sometimes <c>ok</c> to always
+ return <c>ok</c> when successful. This goes against the
+ documentation which said <c>true</c>, but <c>ok</c> was
+ judged to be a more logical return value.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-5960 Aux Id: seq10240 </p>
+ </item>
+ <item>
+ <p>The shell now handles records better when used in calls
+ on the form <c>{Module, Function}(ArgList)</c>.</p>
+ <p>Own Id: OTP-5990 Aux Id: OTP-5876 </p>
+ </item>
+ <item>
+ <p>The functions <c>lists:ukeysort/2</c> and
+ <c>lists:ukeymerge/3</c> have been changed in such a way
+ that two tuples are considered equal if their keys
+ match.</p>
+ <p>For the sake of consistency, <c>lists:usort/2</c> and
+ <c>lists:umerge/3</c> have been modified too: two elements
+ are considered equal if they compare equal.</p>
+ <p>The <c>file_sorter</c> module has been modified in a
+ similar way: the <c>unique</c> option now applies to the
+ key (<c>keysort()</c> and <c>keymerge()</c>) and the
+ ordering function (the option <c>{order, Order} </c>).</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-6019</p>
+ </item>
+ <item>
+ <p>Correction in documentation for
+ <c>ets:update_counter/3</c>; failure with <c>badarg</c>
+ also if the counter to be updated is the key.</p>
+ <p>Own Id: OTP-6072</p>
+ </item>
+ <item>
+ <p>When sorting terms using the <c>file_sorter</c> module
+ and an ordering fun, the sort was not always stable. This
+ bug has been fixed.</p>
+ <p>Own Id: OTP-6088</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>Improvements of the linter:</p>
+ <list type="bulleted">
+ <item>
+ <p>The <c>compile</c> attribute is recognized after
+ function definitions.</p>
+ </item>
+ <item>
+ <p>The new compiler option
+ <c>nowarn_deprecated_function</c> can be used for
+ turning off warnings for calls to deprecated functions.</p>
+ </item>
+ <item>
+ <p>The new compiler option
+ <c>{nowarn_unused_function,[{Name,Arity}]}</c> turns off
+ warnings for unused local functions for the mentioned
+ functions. The new options
+ <c>{nowarn_deprecated_function,[{Module,Name,Arity}]}</c>
+ and <c>{nowarn_bif_clash,[{Name,Arity}]}</c> work
+ similarly.</p>
+ </item>
+ </list>
+ <p>The Erlang code preprocessor <c>epp</c> now recognizes
+ the <c>file</c> attribute. This attribute is meant to be
+ used by tools such as Yecc that generate source code
+ files.</p>
+ <p>Own Id: OTP-5362</p>
+ </item>
+ <item>
+ <p>The formatting option <c>~s</c> of <c>io:fwrite</c> and
+ <c>io_lib:fwrite</c> has been extended to handle arguments
+ that are binaries or I/O lists.</p>
+ <p>Own Id: OTP-5403</p>
+ </item>
+ <item>
+ <p>The control sequences <c>p</c> and <c>P</c> of the
+ <c>Format</c> argument of the functions
+ <c>io:format/2,3</c> and <c>io_lib:format/2</c> have been
+ changed as to display the contents of binaries containing
+ printable characters as strings.</p>
+ <p>Own Id: OTP-5485</p>
+ </item>
+ <item>
+ <p>The linter emits warnings for functions exported more
+ than once in <c>export</c> attributes.</p>
+ <p>Own Id: OTP-5494</p>
+ </item>
+ <item>
+ <p>A manual for STDLIB has been added, <c>stdlib(6)</c>. It
+ mentions the configuration parameters for the Erlang
+ shell.</p>
+ <p>Own Id: OTP-5530</p>
+ </item>
+ <item>
+ <p>Added the <c>zip</c> module with functions for reading
+ and creating zip archives. See <c>zip(3)</c>.</p>
+ <p>Own Id: OTP-5786</p>
+ </item>
+ <item>
+ <p>Simple-one-for-one supervisors now store the pids of
+ child processes using <c>dict</c> instead of a list. This
+ significantly improves performance when there are many
+ dynamic supervised child processes. (Thanks to Micka&euml;l
+ R&eacute;mond et al.)</p>
+ <p>Own Id: OTP-5898</p>
+ </item>
+ <item>
+ <p>When given the new option '<c>strict_record_tests</c>',
+ the compiler will generate code that verifies the record
+ type for '<c>R#record.field</c>' operations in guards. Code
+ that verifies record types in bodies has already been
+ generated since R10B, but in this release there will be a
+ '<c>{badrecord,RecordTag}</c>' instead of a
+ '<c>badmatch</c>' if the record verification test fails.
+ See the documentation for the <c>compile</c> module for
+ more information.</p>
+ <p>The Erlang shell always applies strict record tests.</p>
+ <p>Own Id: OTP-5915 Aux Id: OTP-5714 </p>
+ </item>
+ <item>
+ <p>The Erlang pretty printer (<c>erl_pp</c>) now tries to
+ insert line breaks at appropriate places.</p>
+ <p>Own Id: OTP-5924</p>
+ </item>
+ <item>
+ <p>The <c>public</c> option has been removed from
+ <c>digraph:new/1</c>. The reason is that several
+ functions in the <c>digraph</c> module are implemented
+ using multiple ETS accesses, which is not thread safe.
+ (Thanks to Ulf Wiger.)</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-5985</p>
+ </item>
+ <item>
+ <p>The function <c>lists:keyreplace/4</c> checks that the
+ fourth argument (<c>NewTuple</c>) is a tuple.</p>
+ <p>Own Id: OTP-6023</p>
+ </item>
+ <item>
+ <p>Added an example of how to reconstruct source code from
+ debug info (abstract code) to <c>beam_lib(3)</c>. (Thanks
+ to Mats Cronqvist who wrote the example.)</p>
+ <p>Own Id: OTP-6073</p>
+ </item>
+ <item>
+ <p>The new compiler option <c>warn_unused_record</c> is used
+ for finding unused locally defined record types.</p>
+ <p>Own Id: OTP-6105</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.12</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p><c>shell_default:xm/1</c> has been added. It calls
+ <c>xref:m/1</c>.</p>
+ <p>Own Id: OTP-5405 Aux Id: OTP-4101 </p>
+ </item>
+ <item>
+ <p>Warnings are output whenever so far undeclared records are
+ referenced from some default value of a record
+ declaration. In STDLIB 1.14 (R11B) such forward references
+ will cause a compilation error.</p>
+ <p>Own Id: OTP-5878</p>
+ </item>
+ <item>
+ <p>The linter's check of the <c>deprecated</c> attribute did
+ not take the compile option <c>export_all</c> into
+ account. This bug has been fixed.</p>
+ <p>Own Id: OTP-5917</p>
+ </item>
+ <item>
+ <p>The Erlang pretty printer did not handle <c>try/catch</c>
+ correctly. This bug has been fixed.</p>
+ <p>Own Id: OTP-5926</p>
+ </item>
+ <item>
+ <p>Corrected documentation for <c>lists:nthtail/3</c>.</p>
+ <p>Added documentation for <c>lists:keymap/3</c>.</p>
+ <p>Tried to clarify some other type declarations and
+ function descriptions in <c>lists(3)</c>.</p>
+ <p>Corrected documentation for <c>timer:now_diff/2</c>.</p>
+ <p>Fixed broken links in <c>gen_fsm(3)</c>,
+ <c>gen_server(3)</c>, <c>io_lib(3)</c> and <c>lib(3)</c>.</p>
+ <p>Own Id: OTP-5931</p>
+ </item>
+ <item>
+ <p>Type checks have been added to functions in
+ <c>lists.erl</c>.</p>
+ <p>Own Id: OTP-5939</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The new STDLIB module <c>erl_expand_records</c> expands
+ records in abstract code. It is used by the Erlang shell,
+ which means that Compiler is no longer used by the shell.</p>
+ <p>Own Id: OTP-5876 Aux Id: OTP-5435 </p>
+ </item>
+ <item>
+ <p>The compiler will now warn that the
+ <c>megaco:format_versions/1</c> function is deprecated.</p>
+ <p>Own Id: OTP-5976</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.11</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>When calling <c>gen_server:enter_loop</c> with a
+ registered server name, it was only checked that the
+ registered name existed, not that it actually was the
+ name of the calling process.</p>
+ <p>Own Id: OTP-5854</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>More detail on <c>beam_lib:version/1</c> in
+ documentation.</p>
+ <p>Own Id: OTP-5789</p>
+ </item>
+ <item>
+ <p>The new function <c>io:read/3</c> works like
+ <c>io:read/1,2</c> but takes a third argument,
+ <c>StartLine</c>.</p>
+ <p>Own Id: OTP-5813</p>
+ </item>
+ <item>
+ <p>The new function <c>gen_fsm:enter_loop/4,5,6</c>, similar
+ to <c>gen_server:enter_loop/3,4,5</c>, has been added.</p>
+ <p>Own Id: OTP-5846 Aux Id: seq10163 </p>
+ </item>
+ <item>
+ <p>The function <c>c:i/1</c> is now exported.</p>
+ <p>Own Id: OTP-5848 Aux Id: seq10164 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.10</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>A couple of type errors have been fixed in <c>sofs</c>.</p>
+ <p>Own Id: OTP-5739</p>
+ </item>
+ <item>
+ <p>The pre-processor used to complain that the macro
+ definition '<c>-define(S(S), ??S).</c>' was circular,
+ which it isn't. (Thanks to Richard Carlsson.)</p>
+ <p>Own Id: OTP-5777</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.9</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The linter, QLC and the module <c>erl_pp</c> did not
+ handle the new '<c>fun M:F/A</c>' construct in all
+ situations. This problem has been fixed.</p>
+ <p>Own Id: OTP-5644</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The manual pages for most of the Kernel and some of
+ the STDLIB modules have been updated, in particular
+ regarding type definitions.</p>
+ <p>The documentation of the return value for
+ <c>erts:info/1</c> has been corrected.</p>
+ <p>The documentation for <c>erlang:statistics/1</c> now
+ lists all possible arguments.</p>
+ <p>Own Id: OTP-5360</p>
+ </item>
+ <item>
+ <p>Replaced some tuple funs with the new <c>fun M:F/A</c>
+ construct.</p>
+ <p>The high-order functions in the lists module no longer
+ accept bad funs under any circumstances.
+ '<c>lists:map(bad_fun, [])</c>' used to return
+ '<c>[]</c>' but now causes an exception.</p>
+ <p>Unused, broken compatibility code in the <c>ets</c>
+ module was removed. (Thanks to Dialyzer.)</p>
+ <p>Eliminated 5 discrepancies found by Dialyzer in the
+ Appmon application.</p>
+ <p>Own Id: OTP-5633</p>
+ </item>
+ <item>
+ <p>The <c>c:i/0</c> function will now run in a paged mode if
+ there are more than 100 processes in the system. (Thanks
+ to Ulf Wiger.)</p>
+ <p><c>erlang:system_info(process_count)</c> has been
+ optimized and does now return exactly the same value as
+ <c>length(processes())</c>. Previously
+ <c>erlang:system_info(process_count)</c> did not include
+ exiting processes which are included in
+ <c>length(processes())</c>.</p>
+ <p>The <c>+P</c> flag for <c>erl</c>, which sets the maximum
+ number of processes allowed to exist at the same, no longer
+ accepts values higher than 134217727. (You will still
+ probably run out of memory before you'll be able to reach
+ that limit.)</p>
+ <p>Own Id: OTP-5645 Aux Id: seq9984 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.8</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Very minor corrections in <c>beam_lib</c> and its
+ documentation.</p>
+ <p>Own Id: OTP-5589</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The <c>erlang:port_info/1</c> BIF is now documented.
+ Minor corrections of the documentation for
+ <c>erlang:port_info/2</c>.</p>
+ <p>Added a note to the documentation of the <c>math</c>
+ module that all functions are not available on all
+ platforms.</p>
+ <p>Added more information about the '<c>+c</c>' option in
+ the <c>erl</c> man page in the ERTS documentation.</p>
+ <p>Own Id: OTP-5555</p>
+ </item>
+ <item>
+ <p>The new <c>fun M:F/A</c> construct creates a fun that
+ refers to the latest version of <c>M:F/A</c>. This syntax is
+ meant to replace tuple funs <c>{M,F}</c> which have many
+ problems.</p>
+ <p>The new type test <c>is_function(Fun,A)</c> (which may be
+ used in guards) test whether <c>Fun</c> is a fun that can be
+ applied with <c>A</c> arguments. (Currently, <c>Fun</c> can
+ also be a tuple fun.)</p>
+ <p>Own Id: OTP-5584</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.7</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p><c>filelib:wildcard/2</c> was broken (it ignored its
+ second argument).</p>
+ <p>Also, <c>filelib:wildcard("Filename")</c> (where the
+ argument does not contain any meta-characters) would
+ always return <c>["Filename"]</c>. Corrected so that an
+ empty list will be returned if <c>"Filename"</c> does not
+ actually exist. (Same correction in
+ <c>filelib:wildcard/2</c>.) (This change is a slight
+ incompatibility.)</p>
+ <p><c>filelib:wildcard/1,2</c> will generate a different
+ exception when given bad patterns such as <c>"{a,"</c>. The
+ exception used to be caused by
+ '<c>exit(missing_delimiter)</c>' but is now
+ '<c>erlang:error({badpattern,missing_delimiter})</c>'.</p>
+ <p>Own Id: OTP-5523 Aux Id: seq9824 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>Further improvements of encrypted debug info: New option
+ <c>encrypt_debug_info</c> for compiler.</p>
+ <p>Own Id: OTP-5541 Aux Id: seq9837 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.6</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>When opening a Dets table read only an attempt was
+ sometimes made to re-hash the table resulting in an error
+ message. This problem has been fixed.</p>
+ <p>Own Id: OTP-5487 Aux Id: OTP-4989 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>It is now possible to encrypt the debug information in
+ Beam files, to help keep the source code secret. See the
+ documentation for <c>compile</c> on how to provide the key
+ for encrypting, and the documentation for <c>beam_lib</c>
+ on how to provide the key for decryption so that tools such
+ as the Debugger, <c>xref</c>, or <c>cover</c> can be used.</p>
+ <p>The <c>beam_lib:chunks/2</c> functions now accepts an
+ additional chunk type <c>compile_info</c> to retrieve
+ the compilation information directly as a term. (Thanks
+ to Tobias Lindahl.)</p>
+ <p>Own Id: OTP-5460 Aux Id: seq9787 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.5</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Closing a Dets table kept in RAM would cause a crash if
+ the file could not be written. This problem has been
+ fixed by returning an error tuple.</p>
+ <p>Own Id: OTP-5402</p>
+ </item>
+ <item>
+ <p><c>erl_pp</c> now correctly pretty-prints <c>fun F/A</c>.</p>
+ <p>Own Id: OTP-5412</p>
+ </item>
+ <item>
+ <p>The Erlang shell failed if the compiler was not in the
+ code path. This problem has been fixed, but in order to
+ evaluate records the compiler is still needed.</p>
+ <p>Own Id: OTP-5435</p>
+ </item>
+ <item>
+ <p>Corrected the example in the documentation for
+ <c>ets:match/2</c>. Also clarified that
+ <c>ets:update_counter/3</c> updates the counter atomically.
+ (Thanks to Anders Svensson.)</p>
+ <p>Own Id: OTP-5452 Aux Id: seq9770, seq9789 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The possibility to start the Erlang shell in parallel
+ with the rest of the system was reintroduced for
+ backwards compatibility in STDLIB 1.13.1. The flag to be
+ used for this is now called <c>async_shell_start</c> and has
+ been documented. New shells started from the JCL menu are
+ not synchronized with <c>init</c> anymore. This makes it
+ possible to start a new shell (e.g. for debugging purposes)
+ even if the initial shell has not come up.</p>
+ <p>Own Id: OTP-5406 Aux Id: OTP-5218 </p>
+ </item>
+ <item>
+ <p>The compiler will now produce warnings when using the
+ deprecated functions in the <c>snmp</c> module.</p>
+ <p>Own Id: OTP-5425</p>
+ </item>
+ <item>
+ <p>The function <c>c:zi/0</c> has been removed. Use
+ <c>c:i/0</c> instead.</p>
+ <p>Own Id: OTP-5432</p>
+ </item>
+ <item>
+ <p>Corrected two minor bugs found by the Dialyzer:
+ Calling a parameterized module from a restricted shell
+ (i.e. if <c>shell:start_restricted/1</c> has been used)
+ would crash the shell evaluator. A debug printout in
+ <c>gen_fsm</c> had a clause that would never match; causing
+ less information to be printed.</p>
+ <p>And a somewhat more serious one also found by
+ Dialyzer: <c>rpc:yield/1</c> would crash unless the call
+ started by <c>rpc:async_call/4</c> had already finished;
+ <c>rpc:nb_yield(Key,infinity)</c> would also crash.</p>
+ <p>Cleaned up and removed redundant code found by
+ Dialyzer in <c>erlang:dmonitor_p/2</c>.</p>
+ <p>Own Id: OTP-5462</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.4</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Bugs in the Erlang shell have been fixed.</p>
+ <p>Own Id: OTP-5327</p>
+ </item>
+ <item>
+ <p>Some dead code reported by Dialyzer was eliminated.</p>
+ <p>A bug in <c>dbg</c> when tracing to wrap trace files has
+ been corrected. It failed to delete any already existing
+ wrap trace files with the same names when starting a new
+ wrap trace.</p>
+ <p>Own Id: OTP-5329</p>
+ </item>
+ <item>
+ <p>The linter could output invalid warnings about bit
+ patterns in record initializations. This problem has been
+ fixed.</p>
+ <p>Own Id: OTP-5338</p>
+ </item>
+ <item>
+ <p><c>ordsets:is_set(NoList)</c>, where <c>NoList</c> is any
+ term except a list, would crash. For consistency with
+ <c>sets:is_set/1</c> and <c>gb_sets:is_set/1</c>, it now
+ returns <c>false</c>.</p>
+ <p>Own Id: OTP-5341</p>
+ </item>
+ <item>
+ <p>A BIF <c>erlang:raise/3</c> has been added. See the manual
+ for details. It is intended for internal system programming
+ only, advanced error handling.</p>
+ <p>Own Id: OTP-5376 Aux Id: OTP-5257 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The <c>deprecated</c> attribute is now checked by the
+ linter. See <c>xref(3)</c> for a description of the
+ <c>deprecated</c> attribute.</p>
+ <p>Own Id: OTP-5276</p>
+ </item>
+ <item>
+ <p>The restricted shell will now indicate if the return
+ value from a user predicate is on an incorrect form.</p>
+ <p>Own Id: OTP-5335</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.3</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>Bugs concerning unused and shadowed variables have been
+ fixed in the linter.</p>
+ <p>Own Id: OTP-5091</p>
+ </item>
+ <item>
+ <p>A bug in the evaluator that caused the shell to choke on
+ bit syntax expressions has been fixed.</p>
+ <p>Own Id: OTP-5237</p>
+ </item>
+ <item>
+ <p><c>io:format/2</c> et.al no longer crashes for some
+ combinations of precision and value for format character
+ "g". Previously it crashed if the precision P was 4 or lower
+ and the absolute value of the float to print was lower
+ than 10^4 but 10^(P-1) or higher. Now it will not crash
+ depending on the value of the float.</p>
+ <p>Own Id: OTP-5263</p>
+ </item>
+ <item>
+ <p>Bugs in the handling of the bit syntax have been fixed in
+ the Erlang shell.</p>
+ <p>Own Id: OTP-5269</p>
+ </item>
+ <item>
+ <p><c>gb_sets:del_element/2</c> was changed to do the
+ same as <c>gb_sets:delete_any/2</c> which was the
+ original intention, not as <c>gb_sets:delete/2</c>. Code
+ that relies on <c>gb_sets:del_element/2</c> causing an
+ error if the element does not exist must be changed to
+ call <c>gb_sets:delete/2</c> instead.</p>
+ <p>The documentation was also updated to explicitly
+ document functions that were only referred to as
+ 'aliases' of a documented function. Also, a list of all
+ functions common to the <c>gb_sets</c>, <c>sets</c>, and
+ <c>ordsets</c> was added.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-5277</p>
+ </item>
+ <item>
+ <p>Debug messages have been removed from the QLC module.</p>
+ <p>Own Id: OTP-5283</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The size of continuations returned from
+ <c>dets:match/1,3</c>, <c>dets:match_object/1,3</c>, and
+ <c>dets:select/1,3</c> has been reduced. This affects the
+ amount of data Mnesia sends between nodes while
+ evaluating QLC queries.</p>
+ <p>Own Id: OTP-5232</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.2</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The <c>-rsh</c> switch for starting a remote shell
+ (introduced with OTP-5210) clashed with an already existing
+ switch used by <c>slave</c>. Therefore the switch for
+ the remote shell is now instead named <c>-remsh</c>.</p>
+ <p>Own Id: OTP-5248 Aux Id: OTP-5210 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.13.1</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The Pman 'trace shell' functionality was broken as has
+ now been fixed. Furthermore, Pman could not correctly
+ find the pid of the active shell if more than one shell
+ process was running on the node. This has also been
+ corrected.</p>
+ <p>Own Id: OTP-5191</p>
+ </item>
+ <item>
+ <p>When the undocumented feature "parameterized modules" was
+ used, the ?MODULE macro did not work correctly.</p>
+ <p>Own Id: OTP-5224</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>You can now start Erlang with the <c>-rsh</c> flag which
+ gives you a remote initial shell instead of a local one.
+ Example:</p>
+ <pre>
+ erl -sname this_node -rsh other_node@other_host
+ </pre>
+ <p>Own Id: OTP-5210</p>
+ </item>
+ <item>
+ <p>The man page for the <c>lists</c> module has been updated
+ with description of the new <c>zip</c>, <c>unzip</c>,
+ and <c>partition/2</c> functions.</p>
+ <p>Own Id: OTP-5213</p>
+ </item>
+ <item>
+ <p>The top level group leader used to be listed as job #1 in
+ the job list in JCL mode. Since there is no shell
+ associated with this process that can be connected to, it
+ will no longer be listed.</p>
+ <p>Own Id: OTP-5214</p>
+ </item>
+ <item>
+ <p>The possibility to start the Erlang shell in parallel
+ with the rest of the system has been reintroduced for
+ backwards compatibility. Note that this old behaviour is
+ error prone and should not be used unless for some reason
+ necessary.</p>
+ <p>Own Id: OTP-5218 Aux Id: seq9534 </p>
+ </item>
+ <item>
+ <p>The <c>shell</c> commands <c>rr/1,2,3</c> now accepts
+ wildcards when reading record definitions from BEAM
+ files.</p>
+ <p>Own Id: OTP-5226</p>
+ </item>
+ </list>
+ </section>
+ </section>
+</chapter>
+
diff --git a/lib/stdlib/doc/src/notes_history.xml b/lib/stdlib/doc/src/notes_history.xml
new file mode 100644
index 0000000000..85997f1717
--- /dev/null
+++ b/lib/stdlib/doc/src/notes_history.xml
@@ -0,0 +1,395 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2006</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>STDLIB Release Notes</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+
+ <section>
+ <title>STDLIB 1.13</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>For the bit syntax, when matching with a size field that
+ is bound during the same matching, a warning for unused
+ variable was emitted. This bug has been fixed.</p>
+ <p>Own Id: OTP-4858</p>
+ </item>
+ <item>
+ <p>When parsing a badly formed file <c>epp</c> hangs. This
+ problem has been fixed.</p>
+ <p>Own Id: OTP-4871 Aux Id: OTP-4870 </p>
+ </item>
+ <item>
+ <p>Bugs concerning guards have been fixed in the Erlang
+ interpreter.</p>
+ <p>Own Id: OTP-4885</p>
+ </item>
+ <item>
+ <p>The linter now checks the record name when calling the
+ function <c>erlang:is_record/2</c>.</p>
+ <p>Own Id: OTP-4886</p>
+ </item>
+ <item>
+ <p>Guards of mach specifications are corrected to resemble
+ the semantics of guards in real code more closely. The
+ implementation now corresponds to the documentation in
+ ERTS User's Guide. The following things are corrected:</p>
+ <list type="bulleted">
+ <item>Guard semantics was wrong when it came to logical
+ operators and exceptions.
+ <c>{'or', {'is_integer','$1'}, {'or', '$1', '$1'}}</c>
+ evaluated to <c>true</c> with <c>'$1'</c> bound to an
+ integer.</item>
+ <item>Unary + and - was not implemented</item>
+ <item>Calling operators as Bif's was not supported by
+ <c>ets/dbg:fun2ms</c> (<c>erlang:'or'(A,B)</c> etc)</item>
+ <item>Old typetests (like <c>integer(X)</c> instead of
+ <c>is_integer(X)</c>) was not supported by
+ <c>ets/dbg:fun2ms</c></item>
+ <item>Semicolon (;) in guards was not supported by
+ <c>ets/dbg:fun2ms</c></item>
+ </list>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-4927</p>
+ </item>
+ <item>
+ <p>A bug that could cause a crash has been fixed in the
+ <c>file_sorter</c> module. The crash could only occur
+ when sorting or merging to a file and the input function
+ returned <c>{end_of_input, Value}</c>.</p>
+ <p>Own Id: OTP-5009</p>
+ </item>
+ <item>
+ <p>A function clause exit in <c>filelib:fold_files/5</c> has
+ been fixed.</p>
+ <p>Own Id: OTP-5137</p>
+ </item>
+ <item>
+ <p><c>filelib:fold_files/5</c> has been fixed to not include
+ directories that match the regular expression in the
+ result. In this process, <c>filelib:is_regular/1</c> has
+ been added to the module.</p>
+ <p>WARNING the behaviour of <c>filelib:fold_files/5</c> has
+ also been changed so the regexp match is tried not on
+ the full name, but locally in each traversed directory.</p>
+ <p>Own Id: OTP-5171 Aux Id: OTP-5128 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The initial shell process started in parallel with the
+ <c>init</c> boot process. This made it possible to pipe
+ commands to the shell that got evaluated before the system
+ had booted properly. Also the shell was normally accessible
+ before <c>.erlang</c> had been evaluated and this caused
+ problems especially related to code loading. This has now
+ been fixed so that the initial shell is not accessible
+ until after the init boot routine has finished and
+ <c>.erlang</c> has been evaluated. Note that the shell
+ starts before any <c>-s</c> arguments are executed.</p>
+ <p>Own Id: OTP-4877</p>
+ </item>
+ <item>
+ <p>It is now possible to compile files with <c>erlc</c> without
+ getting a lot of (for compilation) unnecessary code
+ loaded and executed (like distribution, inet config,
+ etc). <c>erlc</c> now also calls <c>erl</c> with <c>-boot start_clean</c> (so that SASL is not started even if
+ <c>start_sasl</c> is default boot script).</p>
+ <p>Own Id: OTP-4878</p>
+ </item>
+ <item>
+ <p>Dets tables can now be opened or closed in parallel. In
+ particular, if some table is being repaired, other tables
+ can still be opened or closed.</p>
+ <p>Own Id: OTP-4908</p>
+ </item>
+ <item>
+ <p>The new STDLIB module <c>qlc</c> implements a query
+ language with a list comprehension syntax completely
+ embedded in Erlang. There is support for reading data
+ from ETS, Dets, and Mnesia tables as well as for defining
+ other sources of data. For easy testing queries can be
+ stated in the Erlang shell. The <c>qlc</c> module aims at
+ replacing Mnemosyne. See <c>qlc(3)</c> for details and
+ examples.</p>
+ <p>Own Id: OTP-5043</p>
+ </item>
+ <item>
+ <p>Some support for records has been added to the Erlang
+ shell. There are commands for reading record definitions
+ from files and for manipulating record definitions in the
+ shell. The record syntax can be used in the shell and
+ return values are printed as records when possible using
+ the records definitions known to the shell. New commands
+ in the shell are <c>rd/2</c>, <c>rf/0,1</c>,
+ <c>rl/0,1</c>, <c>rp/1</c>, and <c>rr/1,2,3</c>. Existing
+ functions in <c>user_default.erl</c> with any of these
+ names need to be renamed. See <c>shell(3)</c> for further
+ details.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-5063</p>
+ </item>
+ <item>
+ <p>A new function, <c>string:to_integer/1,</c> has been added.</p>
+ <p>Own Id: OTP-5081 Aux Id: OTP-5136 </p>
+ </item>
+ <item>
+ <p>The new function <c>dets:repair_continuation/2</c> can be
+ used for restoring an opaque continuation returned by
+ <c>dets:select/1</c> or <c>dets:select/3</c> if the
+ continuation has passed through the external term format
+ (been sent between nodes or stored on disk).</p>
+ <p>Own Id: OTP-5126</p>
+ </item>
+ <item>
+ <p>A new function, <c>string:to_float/1,</c> has been added.</p>
+ <p>Own Id: OTP-5136 Aux Id: OTP-5081 </p>
+ </item>
+ <item>
+ <p>Test cases have been added for <c>string</c>.</p>
+ <p>Own Id: OTP-5138</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.12.8</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The functions <c>ets:insert_new/2</c> and
+ <c>dets:insert_new/2</c> are added. Please consult
+ the manual pages for details.</p>
+ <p>Own Id: OTP-5075</p>
+ </item>
+ <item>
+ <p>New function: <c>proc_lib:hibernate/3.</c> Processes spawned
+ using <c>proc_lib</c> (also indirectly, such as
+ <c>gen_server</c> process), should use this function
+ instead of the BIF <c>erlang:hibernate/3</c> directly to
+ ensure that the exception handler for the process continues
+ to work when the process is awaken.</p>
+ <p>Own Id: OTP-5077</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.12.6</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>If many files or sockets were open (more than 256),
+ <c>beam_lib</c> operations could fail. Corrected.</p>
+ <p>Own Id: OTP-5046 Aux Id: OTP-4997, seq8590 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.12.5</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>When opening a Dets table read only an attempt was made
+ to re-hash the table resulting in an error message. This
+ problem has been fixed.</p>
+ <p>Own Id: OTP-4989 Aux Id: seq8536 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>If a <c>gen_server</c>, <c>gen_event</c> or <c>gen_fsm</c>
+ process exits with <c>{undef,[{M,F,A}|...]}</c>, the error
+ report will now state if the function call failed because
+ the module could not be loaded or because the function was
+ not exported.</p>
+ <p>Own Id: OTP-4952 Aux Id: OTP-4925 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.12.4</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The shell function <c>c:regs()</c> did crash if there was
+ a registered port among the registered names. This is now
+ corrected.</p>
+ <p>Own Id: OTP-4890</p>
+ </item>
+ <item>
+ <p>A bug has been fixed in <c>dets</c>: not all objects were
+ always visible in tables with more than 131072 keys.</p>
+ <p>Own Id: OTP-4906</p>
+ </item>
+ <item>
+ <p>A bug has been fixed in <c>dets</c>: when trying to
+ repair a version 9 table, a <c>not_a_dets_file</c> error
+ message was returned unnecessarily often.</p>
+ <p>Own Id: OTP-4907</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>Starting Erlang with the <c>+Bi</c> flag (to ignore ^C), now
+ also disables the quit ('q') option in the JCL menu.</p>
+ <p>Own Id: OTP-4897</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.12.3</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>A bug in <c>erl_scan</c> caused the compiler to hang when
+ end-of-file in a comment was encountered. This is now
+ corrected.</p>
+ <p>Own Id: OTP-4787</p>
+ </item>
+ <item>
+ <p>The linter now reports errors for unsafe variables when
+ the option <c>nowarn_unused_vars</c> is given.</p>
+ <p>Own Id: OTP-4831 Aux Id: seq8202 </p>
+ </item>
+ <item>
+ <p><c>gen_server:cast/2</c> now crashes for invalid
+ arguments.</p>
+ <p><c>gen_server:abcast/2-3</c> now crashes for invalid
+ arguments. The behaviour when casting to the name
+ <c>global</c> has been corrected. Previously,
+ <c>gen_server:abcast(global,[node@host])</c> tried to
+ cast to the globally registered name <c>node@host</c>,
+ which is more of a bug than less. Now such a call will
+ cast to the registered name <c>global</c> at the node
+ <c>node@host</c> as (probably) expected.</p>
+ <p>An <c>rpc:call/5</c> and <c>rpc:block_call/5</c> having a
+ timeout argument has been added.</p>
+ <p><c>rpc:abcast/2-3</c> has been improved not to get stuck
+ waiting for connection setup if the remote node is not
+ connected. In this case the send is spawned off to a
+ temporary process.</p>
+ <p>Two typos in the docs for module <c>calendar</c> has
+ been corrected thanks to Bengt Kleberg.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-4844 Aux Id: seq8226 </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>The <c>f/1</c> shell command did not work correctly with
+ restricted shell.</p>
+ <p>Own Id: OTP-4818</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.12.2</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p><c>gen_server</c>: the internal function <c>get_parent,</c>
+ called by <c>gen_server:enter_loop/[4,5],</c> now returns
+ the pid of parents that are registered processes, instead of
+ returning their name. The reason for this is that
+ <c>gen_server</c> relies on that the parent is represented
+ as a pid. This error in <c>get_parent/0</c> had the effect
+ that the terminate function of the child was not run when
+ it was shutdown.</p>
+ <p>Own Id: OTP-4820 Aux Id: seq8170 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
+ <title>STDLIB 1.12.1</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list type="bulleted">
+ <item>
+ <p>The code server could hang if invoked early in the startup.
+ For example if the emulator was started with
+ "<c>-s file eval Filename</c> and <c>Filename</c> contained
+ a call to <c>code:add_patha/1</c> the code server
+ accidentally tried to execute code in an unloaded module
+ from inside the code that loaded a module - hence hangup.
+ This bug has now been fixed.</p>
+ <p>Note! Starting Erlang through code loading from a remote
+ Erlang boot server will not work after this patch. It will
+ be fixed in a later patch. Rumours has it that remote boot
+ server code loading did not work before this patch either.
+ It is not a commonly used feature.</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-4802 Aux Id: seq8314 </p>
+ </item>
+ </list>
+ </section>
+ </section>
+</chapter>
+
diff --git a/lib/stdlib/doc/src/orddict.xml b/lib/stdlib/doc/src/orddict.xml
new file mode 100644
index 0000000000..08c808f822
--- /dev/null
+++ b/lib/stdlib/doc/src/orddict.xml
@@ -0,0 +1,354 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2000</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>orddict</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>nobody</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2007-04-16</date>
+ <rev>B</rev>
+ <file>orddict.sgml</file>
+ </header>
+ <module>orddict</module>
+ <modulesummary>Key-Value Dictionary as Ordered List</modulesummary>
+ <description>
+ <p><c>Orddict</c> implements a <c>Key</c> - <c>Value</c> dictionary.
+ An <c>orddict</c> is a representation of a dictionary, where a
+ list of pairs is used to store the keys and values. The list is
+ ordered after the keys.</p>
+ <p>This module provides exactly the same interface as the module
+ <c>dict</c> but with a defined representation. One difference is
+ that while <c>dict</c> considers two keys as different if they
+ do not match (<c>=:=</c>), this module considers two keys as
+ different if and only if they do not compare equal
+ (<c>==</c>).</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+ordered_dictionary()
+ as returned by new/0</code>
+ </section>
+ <funcs>
+ <func>
+ <name>append(Key, Value, Orddict1) -> Orddict2</name>
+ <fsummary>Append a value to keys in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function appends a new <c>Value</c> to the current list
+ of values associated with <c>Key</c>. An exception is
+ generated if the initial value associated with <c>Key</c> is
+ not a list of values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>append_list(Key, ValList, Orddict1) -> Orddict2</name>
+ <fsummary>Append new values to keys in a dictionary</fsummary>
+ <type>
+ <v>ValList = [Value]</v>
+ <v>Key = Value = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function appends a list of values <c>ValList</c> to
+ the current list of values associated with <c>Key</c>. An
+ exception is generated if the initial value associated with
+ <c>Key</c> is not a list of values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>erase(Key, Orddict1) -> Orddict2</name>
+ <fsummary>Erase a key from a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function erases all items with a given key from a
+ dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fetch(Key, Orddict) -> Value</name>
+ <fsummary>Look-up values in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Orddict = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function returns the value associated with <c>Key</c>
+ in the dictionary <c>Orddict</c>. <c>fetch</c> assumes that
+ the <c>Key</c> is present in the dictionary and an exception
+ is generated if <c>Key</c> is not in the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fetch_keys(Orddict) -> Keys</name>
+ <fsummary>Return all keys in a dictionary</fsummary>
+ <type>
+ <v>Orddict = ordered_dictionary()</v>
+ <v>Keys = [term()]</v>
+ </type>
+ <desc>
+ <p>This function returns a list of all keys in the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>filter(Pred, Orddict1) -> Orddict2</name>
+ <fsummary>Choose elements which satisfy a predicate</fsummary>
+ <type>
+ <v>Pred = fun(Key, Value) -> bool()</v>
+ <v>&nbsp;Key = Value = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p><c>Orddict2</c> is a dictionary of all keys and values in
+ <c>Orddict1</c> for which <c>Pred(Key, Value)</c> is <c>true</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>find(Key, Orddict) -> {ok, Value} | error</name>
+ <fsummary>Search for a key in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Orddict = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function searches for a key in a dictionary. Returns
+ <c>{ok, Value}</c> where <c>Value</c> is the value associated
+ with <c>Key</c>, or <c>error</c> if the key is not present in
+ the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fold(Fun, Acc0, Orddict) -> Acc1</name>
+ <fsummary>Fold a function over a dictionary</fsummary>
+ <type>
+ <v>Fun = fun(Key, Value, AccIn) -> AccOut</v>
+ <v>Key = Value = term()</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>Orddict = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>Calls <c>Fun</c> on successive keys and values of
+ <c>Orddict</c> together with an extra argument <c>Acc</c>
+ (short for accumulator). <c>Fun</c> must return a new
+ accumulator which is passed to the next call. <c>Acc0</c> is
+ returned if the list is empty. The evaluation order is
+ undefined.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_list(List) -> Orddict</name>
+ <fsummary>Convert a list of pairs to a dictionary</fsummary>
+ <type>
+ <v>List = [{Key, Value}]</v>
+ <v>Orddict = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function converts the key/value list <c>List</c> to a
+ dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_key(Key, Orddict) -> bool()</name>
+ <fsummary>Test if a key is in a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Orddict = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function tests if <c>Key</c> is contained in
+ the dictionary <c>Orddict</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>map(Fun, Orddict1) -> Orddict2</name>
+ <fsummary>Map a function over a dictionary</fsummary>
+ <type>
+ <v>Fun = fun(Key, Value1) -> Value2</v>
+ <v>&nbsp;Key = Value1 = Value2 = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p><c>map</c> calls <c>Func</c> on successive keys and values
+ of <c>Orddict</c> to return a new value for each key.
+ The evaluation order is undefined.</p>
+ </desc>
+ </func>
+ <func>
+ <name>merge(Fun, Orddict1, Orddict2) -> Orddict3</name>
+ <fsummary>Merge two dictionaries</fsummary>
+ <type>
+ <v>Fun = fun(Key, Value1, Value2) -> Value</v>
+ <v>&nbsp;Key = Value1 = Value2 = Value3 = term()</v>
+ <v>Orddict1 = Orddict2 = Orddict3 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p><c>merge</c> merges two dictionaries, <c>Orddict1</c> and
+ <c>Orddict2</c>, to create a new dictionary. All the <c>Key</c>
+ - <c>Value</c> pairs from both dictionaries are included in
+ the new dictionary. If a key occurs in both dictionaries then
+ <c>Fun</c> is called with the key and both values to return a
+ new value. <c>merge</c> could be defined as:</p>
+ <code type="none">
+merge(Fun, D1, D2) ->
+ fold(fun (K, V1, D) ->
+ update(K, fun (V2) -> Fun(K, V1, V2) end, V1, D)
+ end, D2, D1).</code>
+ <p>but is faster.</p>
+ </desc>
+ </func>
+ <func>
+ <name>new() -> ordered_dictionary()</name>
+ <fsummary>Create a dictionary</fsummary>
+ <desc>
+ <p>This function creates a new dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>size(Orddict) -> int()</name>
+ <fsummary>Return the number of elements in an ordered dictionary</fsummary>
+ <type>
+ <v>Orddict = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>Returns the number of elements in an <c>Orddict</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>store(Key, Value, Orddict1) -> Orddict2</name>
+ <fsummary>Store a value in a dictionary</fsummary>
+ <type>
+ <v>Key = Value = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>This function stores a <c>Key</c> - <c>Value</c> pair in a
+ dictionary. If the <c>Key</c> already exists in <c>Orddict1</c>,
+ the associated value is replaced by <c>Value</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_list(Orddict) -> List</name>
+ <fsummary>Convert a dictionary to a list of pairs</fsummary>
+ <type>
+ <v>Orddict = ordered_dictionary()</v>
+ <v>List = [{Key, Value}]</v>
+ </type>
+ <desc>
+ <p>This function converts the dictionary to a list
+ representation.</p>
+ </desc>
+ </func>
+ <func>
+ <name>update(Key, Fun, Orddict1) -> Orddict2</name>
+ <fsummary>Update a value in a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Fun = fun(Value1) -> Value2</v>
+ <v>&nbsp;Value1 = Value2 = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>Update the a value in a dictionary by calling <c>Fun</c> on
+ the value to get a new value. An exception is generated if
+ <c>Key</c> is not present in the dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>update(Key, Fun, Initial, Orddict1) -> Orddict2</name>
+ <fsummary>Update a value in a dictionary</fsummary>
+ <type>
+ <v>Key = Initial = term()</v>
+ <v>Fun = fun(Value1) -> Value2</v>
+ <v>&nbsp;Value1 = Value2 = term()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>Update the a value in a dictionary by calling <c>Fun</c> on
+ the value to get a new value. If <c>Key</c> is not present
+ in the dictionary then <c>Initial</c> will be stored as
+ the first value. For example <c>append/3</c> could be defined
+ as:</p>
+ <code type="none">
+append(Key, Val, D) ->
+ update(Key, fun (Old) -> Old ++ [Val] end, [Val], D).</code>
+ </desc>
+ </func>
+ <func>
+ <name>update_counter(Key, Increment, Orddict1) -> Orddict2</name>
+ <fsummary>Increment a value in a dictionary</fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Increment = number()</v>
+ <v>Orddict1 = Orddict2 = ordered_dictionary()</v>
+ </type>
+ <desc>
+ <p>Add <c>Increment</c> to the value associated with <c>Key</c>
+ and store this value. If <c>Key</c> is not present in
+ the dictionary then <c>Increment</c> will be stored as
+ the first value.</p>
+ <p>This could be defined as:</p>
+ <code type="none">
+update_counter(Key, Incr, D) ->
+ update(Key, fun (Old) -> Old + Incr end, Incr, D).</code>
+ <p>but is faster.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Notes</title>
+ <p>The functions <c>append</c> and <c>append_list</c> are included
+ so we can store keyed values in a list <em>accumulator</em>. For
+ example:</p>
+ <pre>
+> D0 = orddict:new(),
+ D1 = orddict:store(files, [], D0),
+ D2 = orddict:append(files, f1, D1),
+ D3 = orddict:append(files, f2, D2),
+ D4 = orddict:append(files, f3, D3),
+ orddict:fetch(files, D4).
+[f1,f2,f3] </pre>
+ <p>This saves the trouble of first fetching a keyed value,
+ appending a new value to the list of stored values, and storing
+ the result.
+ </p>
+ <p>The function <c>fetch</c> should be used if the key is known to
+ be in the dictionary, otherwise <c>find</c>.</p>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="dict">dict(3)</seealso>,
+ <seealso marker="gb_trees">gb_trees(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
new file mode 100644
index 0000000000..a20ab2d879
--- /dev/null
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>ordsets</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>99-07-27</date>
+ <rev>A</rev>
+ <file>ordsets.sgml</file>
+ </header>
+ <module>ordsets</module>
+ <modulesummary>Functions for Manipulating Sets as Ordered Lists</modulesummary>
+ <description>
+ <p>Sets are collections of elements with no duplicate elements.
+ An <c>ordset</c> is a representation of a set, where an ordered
+ list is used to store the elements of the set. An ordered list
+ is more efficient than an unordered list.</p>
+ <p>This module provides exactly the same interface as the module
+ <c>sets</c> but with a defined representation. One difference is
+ that while <c>sets</c> considers two elements as different if they
+ do not match (<c>=:=</c>), this module considers two elements as
+ different if and only if they do not compare equal (<c>==</c>).</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+ordered_set()
+ as returned by new/0</code>
+ </section>
+ <funcs>
+ <func>
+ <name>new() -> Ordset</name>
+ <fsummary>Return an empty set</fsummary>
+ <type>
+ <v>Ordset = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns a new empty ordered set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_set(Ordset) -> bool()</name>
+ <fsummary>Test for an <c>Ordset</c></fsummary>
+ <type>
+ <v>Ordset = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Ordset</c> is an ordered set of
+ elements, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>size(Ordset) -> int()</name>
+ <fsummary>Return the number of elements in a set</fsummary>
+ <type>
+ <v>Ordset = term()</v>
+ </type>
+ <desc>
+ <p>Returns the number of elements in <c>Ordset</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_list(Ordset) -> List</name>
+ <fsummary>Convert an <c>Ordset</c>into a list</fsummary>
+ <type>
+ <v>Ordset = ordered_set()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the elements of <c>Ordset</c> as a list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_list(List) -> Ordset</name>
+ <fsummary>Convert a list into an <c>Ordset</c></fsummary>
+ <type>
+ <v>List = [term()]</v>
+ <v>Ordset = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns an ordered set of the elements in <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_element(Element, Ordset) -> bool()</name>
+ <fsummary>Test for membership of an <c>Ordset</c></fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Ordset = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Element</c> is an element of
+ <c>Ordset</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>add_element(Element, Ordset1) -> Ordset2</name>
+ <fsummary>Add an element to an <c>Ordset</c></fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Ordset1 = Ordset2 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns a new ordered set formed from <c>Ordset1</c> with
+ <c>Element</c> inserted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>del_element(Element, Ordset1) -> Ordset2</name>
+ <fsummary>Remove an element from an <c>Ordset</c></fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Ordset1 = Ordset2 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>Ordset1</c>, but with <c>Element</c> removed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(Ordset1, Ordset2) -> Ordset3</name>
+ <fsummary>Return the union of two <c>Ordsets</c></fsummary>
+ <type>
+ <v>Ordset1 = Ordset2 = Ordset3 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns the merged (union) set of <c>Ordset1</c> and
+ <c>Ordset2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(OrdsetList) -> Ordset</name>
+ <fsummary>Return the union of a list of <c>Ordsets</c></fsummary>
+ <type>
+ <v>OrdsetList = [ordered_set()]</v>
+ <v>Ordset = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns the merged (union) set of the list of sets.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(Ordset1, Ordset2) -> Ordset3</name>
+ <fsummary>Return the intersection of two <c>Ordsets</c></fsummary>
+ <type>
+ <v>Ordset1 = Ordset2 = Ordset3 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns the intersection of <c>Ordset1</c> and
+ <c>Ordset2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(OrdsetList) -> Ordset</name>
+ <fsummary>Return the intersection of a list of <c>Ordsets</c></fsummary>
+ <type>
+ <v>OrdsetList = [ordered_set()]</v>
+ <v>Ordset = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns the intersection of the non-empty list of sets.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_disjoint(Ordset1, Ordset2) -> bool()</name>
+ <fsummary>Check whether two <c>Ordsets</c> are disjoint</fsummary>
+ <type>
+ <v>Ordset1 = Ordset2 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Ordset1</c> and
+ <c>Ordset2</c> are disjoint (have no elements in common),
+ and <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>subtract(Ordset1, Ordset2) -> Ordset3</name>
+ <fsummary>Return the difference of two <c>Ordsets</c></fsummary>
+ <type>
+ <v>Ordset1 = Ordset2 = Ordset3 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns only the elements of <c>Ordset1</c> which are not
+ also elements of <c>Ordset2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_subset(Ordset1, Ordset2) -> bool()</name>
+ <fsummary>Test for subset</fsummary>
+ <type>
+ <v>Ordset1 = Ordset2 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> when every element of <c>Ordset</c>1 is
+ also a member of <c>Ordset2</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fold(Function, Acc0, Ordset) -> Acc1</name>
+ <fsummary>Fold over set elements</fsummary>
+ <type>
+ <v>Function = fun (E, AccIn) -> AccOut</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>Ordset = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Fold <c>Function</c> over every element in <c>Ordset</c>
+ returning the final value of the accumulator.</p>
+ </desc>
+ </func>
+ <func>
+ <name>filter(Pred, Ordset1) -> Set2</name>
+ <fsummary>Filter set elements</fsummary>
+ <type>
+ <v>Pred = fun (E) -> bool()</v>
+ <v>Set1 = Set2 = ordered_set()</v>
+ </type>
+ <desc>
+ <p>Filter elements in <c>Set1</c> with boolean function
+ <c>Fun</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="gb_sets">gb_sets(3)</seealso>,
+ <seealso marker="sets">sets(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/part.xml b/lib/stdlib/doc/src/part.xml
new file mode 100644
index 0000000000..25ca56ad86
--- /dev/null
+++ b/lib/stdlib/doc/src/part.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>STDLIB User's Guide</title>
+ <prepared>Patrik Nyblom</prepared>
+ <docno></docno>
+ <date>2008-02-25</date>
+ <rev>1.0</rev>
+ <file>part.xml</file>
+ </header>
+ <description>
+ <p>The Erlang standard library <em>STDLIB</em>.</p>
+ </description>
+ <xi:include href="io_protocol.xml"/>
+ <xi:include href="unicode_usage.xml"/>
+</part>
+
diff --git a/lib/stdlib/doc/src/part_notes.xml b/lib/stdlib/doc/src/part_notes.xml
new file mode 100644
index 0000000000..66ad294753
--- /dev/null
+++ b/lib/stdlib/doc/src/part_notes.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>2004</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>STDLIB Release Notes</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <description>
+ <p>The Standard Erlang Libraries application, <em>STDLIB</em>,
+ contains modules for manipulating lists, strings and files etc.</p>
+ <p>For information about older versions, see
+ <url href="part_notes_history_frame.html">Release Notes History</url>.</p>
+ </description>
+ <xi:include href="notes.xml"/>
+</part>
+
diff --git a/lib/stdlib/doc/src/part_notes_history.xml b/lib/stdlib/doc/src/part_notes_history.xml
new file mode 100644
index 0000000000..744b009583
--- /dev/null
+++ b/lib/stdlib/doc/src/part_notes_history.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part>
+ <header>
+ <copyright>
+ <year>2006</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>STDLIB Release Notes History</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <description>
+ <p>The Standard Erlang Libraries application, <em>STDLIB</em>,
+ contains modules for manipulating lists, strings and files etc.</p>
+ </description>
+ <include file="notes_history"></include>
+</part>
+
diff --git a/lib/stdlib/doc/src/pg.xml b/lib/stdlib/doc/src/pg.xml
new file mode 100644
index 0000000000..66b9702ae0
--- /dev/null
+++ b/lib/stdlib/doc/src/pg.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>pg</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>pg</module>
+ <modulesummary>Distributed, Named Process Groups</modulesummary>
+ <description>
+ <p>This (experimental) module implements process groups. A process
+ group is a group of processes that can be accessed by a common
+ name. For example, a group named <c>foobar</c> can include a set
+ of processes as members of this group and they can be located on
+ different nodes.</p>
+ <p>When messages are sent to the named group, all members of
+ the group receive the message. The messages are serialized. If
+ the process <c>P1</c> sends the message <c>M1</c> to the group,
+ and process <c>P2</c> simultaneously sends message <c>M2</c>, then
+ all members of the group receive the two messages in the same
+ order. If members of a group terminate, they are automatically
+ removed from the group.</p>
+ <p>This module is not complete. The module is inspired by the ISIS
+ system and the causal order protocol of the ISIS system should
+ also be implemented. At the moment, all messages are serialized
+ by sending them through a group master process.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>create(PgName) -> ok | {error, Reason}</name>
+ <fsummary>Create an empty group</fsummary>
+ <type>
+ <v>PgName = term()</v>
+ <v>Reason = already_created | term()</v>
+ </type>
+ <desc>
+ <p>Creates an empty group named <c>PgName</c> on the current
+ node.</p>
+ </desc>
+ </func>
+ <func>
+ <name>create(PgName, Node) -> ok | {error, Reason}</name>
+ <fsummary>Create an empty group on another node</fsummary>
+ <type>
+ <v>PgName = term()</v>
+ <v>Node = node()</v>
+ <v>Reason = already_created | term()</v>
+ </type>
+ <desc>
+ <p>Creates an empty group named <c>PgName</c> on the node
+ <c>Node</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>join(PgName, Pid) -> Members</name>
+ <fsummary>Join a pid to a process group</fsummary>
+ <type>
+ <v>PgName = term()</v>
+ <v>Pid = pid()</v>
+ <v>Members = [pid()]</v>
+ </type>
+ <desc>
+ <p>Joins the pid <c>Pid</c> to the process group <c>PgName</c>.
+ Returns a list of all old members of the group.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send(PgName, Msg) -> void()</name>
+ <fsummary>Send a message to all members of a process group</fsummary>
+ <type>
+ <v>PgName = Msg = term()</v>
+ </type>
+ <desc>
+ <p>Sends the tuple <c>{pg_message, From, PgName, Msg}</c> to
+ all members of the process group <c>PgName</c>.</p>
+ <p>Failure: <c>{badarg, {PgName, Msg}}</c> if <c>PgName</c> is
+ not a process group (a globally registered name).</p>
+ </desc>
+ </func>
+ <func>
+ <name>esend(PgName, Msg) -> void()</name>
+ <fsummary>Send a message to all members of a process group, except ourselves</fsummary>
+ <type>
+ <v>PgName = Msg = term()</v>
+ </type>
+ <desc>
+ <p>Sends the tuple <c>{pg_message, From, PgName, Msg}</c> to
+ all members of the process group <c>PgName</c>, except
+ ourselves.</p>
+ <p>Failure: <c>{badarg, {PgName, Msg}}</c> if <c>PgName</c> is
+ not a process group (a globally registered name).</p>
+ </desc>
+ </func>
+ <func>
+ <name>members(PgName) -> Members</name>
+ <fsummary>Return a list of all members of a process group</fsummary>
+ <type>
+ <v>PgName = term()</v>
+ <v>Members = [pid()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of all members of the process group
+ <c>PgName</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/pool.xml b/lib/stdlib/doc/src/pool.xml
new file mode 100644
index 0000000000..2b890352eb
--- /dev/null
+++ b/lib/stdlib/doc/src/pool.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>pool</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>pool</module>
+ <modulesummary>Load Distribution Facility</modulesummary>
+ <description>
+ <p><c>pool</c> can be used to run a set of Erlang nodes as a pool
+ of computational processors. It is organized as a master and a
+ set of slave nodes and includes the following features:</p>
+ <list type="bulleted">
+ <item>The slave nodes send regular reports to the master about
+ their current load.</item>
+ <item>Queries can be sent to the master to determine which node
+ will have the least load.</item>
+ </list>
+ <p>The BIF <c>statistics(run_queue)</c> is used for estimating
+ future loads. It returns the length of the queue of ready to run
+ processes in the Erlang runtime system.</p>
+ <p>The slave nodes are started with the <c>slave</c> module. This
+ effects, tty IO, file IO, and code loading.</p>
+ <p>If the master node fails, the entire pool will exit.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>start(Name) -></name>
+ <name>start(Name, Args) -> Nodes</name>
+ <fsummary>>Start a new pool</fsummary>
+ <type>
+ <v>Name = atom()</v>
+ <v>Args = string()</v>
+ <v>Nodes = [node()]</v>
+ </type>
+ <desc>
+ <p>Starts a new pool. The file <c>.hosts.erlang</c> is read to
+ find host names where the pool nodes can be started. See
+ section <seealso marker="#files">Files</seealso> below. The
+ start-up procedure fails if the file is not found.</p>
+ <p>The slave nodes are started with <c>slave:start/2,3</c>,
+ passing along <c>Name</c> and, if provided, <c>Args</c>.
+ <c>Name</c> is used as the first part of the node names,
+ <c>Args</c> is used to specify command line arguments. See
+ <seealso marker="slave#start/2">slave(3)</seealso>.</p>
+ <p>Access rights must be set so that all nodes in the pool have
+ the authority to access each other.</p>
+ <p>The function is synchronous and all the nodes, as well as
+ all the system servers, are running when it returns a value.</p>
+ </desc>
+ </func>
+ <func>
+ <name>attach(Node) -> already_attached | attached</name>
+ <fsummary>Ensure that a pool master is running</fsummary>
+ <type>
+ <v>Node = node()</v>
+ </type>
+ <desc>
+ <p>This function ensures that a pool master is running and
+ includes <c>Node</c> in the pool master's pool of nodes.</p>
+ </desc>
+ </func>
+ <func>
+ <name>stop() -> stopped</name>
+ <fsummary>Stop the pool and kill all the slave nodes</fsummary>
+ <desc>
+ <p>Stops the pool and kills all the slave nodes.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_nodes() -> Nodes</name>
+ <fsummary>Return a list of the current member nodes of the pool</fsummary>
+ <type>
+ <v>Nodes = [node()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of the current member nodes of the pool.</p>
+ </desc>
+ </func>
+ <func>
+ <name>pspawn(Mod, Fun, Args) -> pid()</name>
+ <fsummary>Spawn a process on the pool node with expected lowest future load</fsummary>
+ <type>
+ <v>Mod = Fun = atom()</v>
+ <v>Args = [term()]</v>
+ </type>
+ <desc>
+ <p>Spawns a process on the pool node which is expected to have
+ the lowest future load.</p>
+ </desc>
+ </func>
+ <func>
+ <name>pspawn_link(Mod, Fun, Args) -> pid()</name>
+ <fsummary>Spawn and link to a process on the pool node with expected lowest future load</fsummary>
+ <type>
+ <v>Mod = Fun = atom()</v>
+ <v>Args = [term()]</v>
+ </type>
+ <desc>
+ <p>Spawn links a process on the pool node which is expected to
+ have the lowest future load.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_node() -> node()</name>
+ <fsummary>Return the node with the expected lowest future load</fsummary>
+ <desc>
+ <p>Returns the node with the expected lowest future load.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <marker id="files"></marker>
+ <title>Files</title>
+ <p><c>.hosts.erlang</c> is used to pick hosts where nodes can
+ be started. See
+ <seealso marker="kernel:net_adm#host_file/0">net_adm(3)</seealso>
+ for information about format and location of this file.</p>
+ <p><c>$HOME/.erlang.slave.out.HOST</c> is used for all additional IO
+ that may come from the slave nodes on standard IO. If the start-up
+ procedure does not work, this file may indicate the reason.</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml
new file mode 100644
index 0000000000..791001cb52
--- /dev/null
+++ b/lib/stdlib/doc/src/proc_lib.xml
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>proc_lib</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>proc_lib</module>
+ <modulesummary>Functions for asynchronous and synchronous start of processes adhering to the OTP design principles.</modulesummary>
+ <description>
+ <p>This module is used to start processes adhering to
+ the <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso>. Specifically, the functions in this
+ module are used by the OTP standard behaviors (<c>gen_server</c>,
+ <c>gen_fsm</c>, ...) when starting new processes. The functions
+ can also be used to start <em>special processes</em>, user
+ defined processes which comply to the OTP design principles. See
+ <seealso marker="doc/design_principles:spec_proc">Sys and Proc_Lib</seealso> in OTP Design Principles for an example.</p>
+ <p>Some useful information is initialized when a process starts.
+ The registered names, or the process identifiers, of the parent
+ process, and the parent ancestors, are stored together with
+ information about the function initially called in the process.</p>
+ <p>While in "plain Erlang" a process is said to terminate normally
+ only for the exit reason <c>normal</c>, a process started
+ using <c>proc_lib</c> is also said to terminate normally if it
+ exits with reason <c>shutdown</c> or <c>{shutdown,Term}</c>.
+ <c>shutdown</c> is the reason used when
+ an application (supervision tree) is stopped.</p>
+ <p>When a process started using <c>proc_lib</c> terminates
+ abnormally -- that is, with another exit reason than <c>normal</c>,
+ <c>shutdown</c>, or <c>{shutdown,Term}</c> -- a <em>crash report</em>
+ is generated, which is written to terminal by the default SASL
+ event handler. That is, the crash report is normally only visible
+ if the SASL application is started. See
+ <seealso marker="sasl:sasl_app">sasl(6)</seealso> and
+ <seealso marker="sasl:error_logging">SASL User's Guide</seealso>.</p>
+ <p>The crash report contains the previously stored information such
+ as ancestors and initial function, the termination reason, and
+ information regarding other processes which terminate as a result
+ of this process terminating.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>spawn(Fun) -> pid()</name>
+ <name>spawn(Node, Fun) -> pid()</name>
+ <name>spawn(Module, Function, Args) -> pid()</name>
+ <name>spawn(Node, Module, Function, Args) -> pid()</name>
+ <fsummary>Spawn a new process.</fsummary>
+ <type>
+ <v>Node = node()</v>
+ <v>Fun = fun() -> void()</v>
+ <v>Module = Function = atom()</v>
+ <v>Args = [term()]</v>
+ </type>
+ <desc>
+ <p>Spawns a new process and initializes it as described above.
+ The process is spawned using the
+ <seealso marker="erts:erlang#spawn/1">spawn</seealso> BIFs.</p>
+ </desc>
+ </func>
+ <func>
+ <name>spawn_link(Fun) -> pid()</name>
+ <name>spawn_link(Node, Fun) -> pid()</name>
+ <name>spawn_link(Module, Function, Args) -> pid()</name>
+ <name>spawn_link(Node, Module, Function, Args) -> pid()</name>
+ <fsummary>Spawn and link to a new process.</fsummary>
+ <type>
+ <v>Node = node()</v>
+ <v>Fun = fun() -> void()</v>
+ <v>Module = Function = atom()</v>
+ <v>Args = [term()]</v>
+ </type>
+ <desc>
+ <p>Spawns a new process and initializes it as described above.
+ The process is spawned using the
+ <seealso marker="erts:erlang#spawn_link/1">spawn_link</seealso>
+ BIFs.</p>
+ </desc>
+ </func>
+ <func>
+ <name>spawn_opt(Fun, SpawnOpts) -> pid()</name>
+ <name>spawn_opt(Node, Fun, SpawnOpts) -> pid()</name>
+ <name>spawn_opt(Module, Function, Args, SpawnOpts) -> pid()</name>
+ <name>spawn_opt(Node, Module, Func, Args, SpawnOpts) -> pid()</name>
+ <fsummary>Spawn a new process with given options.</fsummary>
+ <type>
+ <v>Node = node()</v>
+ <v>Fun = fun() -> void()</v>
+ <v>Module = Function = atom()</v>
+ <v>Args = [term()]</v>
+ <v>SpawnOpts -- see erlang:spawn_opt/2,3,4,5</v>
+ </type>
+ <desc>
+ <p>Spawns a new process and initializes it as described above.
+ The process is spawned using the
+ <seealso marker="erts:erlang#spawn_opt/2">spawn_opt</seealso>
+ BIFs.</p>
+ <note>
+ <p>Using the spawn option <c>monitor</c> is currently not
+ allowed, but will cause the function to fail with reason
+ <c>badarg</c>.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>start(Module, Function, Args) -> Ret</name>
+ <name>start(Module, Function, Args, Time) -> Ret</name>
+ <name>start(Module, Function, Args, Time, SpawnOpts) -> Ret</name>
+ <name>start_link(Module, Function, Args) -> Ret</name>
+ <name>start_link(Module, Function, Args, Time) -> Ret</name>
+ <name>start_link(Module, Function, Args, Time, SpawnOpts) -> Ret</name>
+ <fsummary>Start a new process synchronously.</fsummary>
+ <type>
+ <v>Module = Function = atom()</v>
+ <v>Args = [term()]</v>
+ <v>Time = int() >= 0 | infinity</v>
+ <v>SpawnOpts -- see erlang:spawn_opt/2,3,4,5</v>
+ <v>Ret = term() | {error, Reason}</v>
+ </type>
+ <desc>
+ <p>Starts a new process synchronously. Spawns the process and
+ waits for it to start. When the process has started, it
+ <em>must</em> call
+ <seealso marker="#init_ack/2">init_ack(Parent,Ret)</seealso>
+ or <seealso marker="#init_ack/1">init_ack(Ret)</seealso>,
+ where <c>Parent</c> is the process that evaluates this
+ function. At this time, <c>Ret</c> is returned.</p>
+ <p>If the <c>start_link/3,4,5</c> function is used and
+ the process crashes before it has called <c>init_ack/1,2</c>,
+ <c>{error, Reason}</c> is returned if the calling process
+ traps exits.</p>
+ <p>If <c>Time</c> is specified as an integer, this function
+ waits for <c>Time</c> milliseconds for the new process to call
+ <c>init_ack</c>, or <c>{error, timeout}</c> is returned, and
+ the process is killed.</p>
+ <p>The <c>SpawnOpts</c> argument, if given, will be passed
+ as the last argument to the <c>spawn_opt/2,3,4,5</c> BIF.</p>
+ <note>
+ <p>Using the spawn option <c>monitor</c> is currently not
+ allowed, but will cause the function to fail with reason
+ <c>badarg</c>.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>init_ack(Parent, Ret) -> void()</name>
+ <name>init_ack(Ret) -> void()</name>
+ <fsummary>Used by a process when it has started.</fsummary>
+ <type>
+ <v>Parent = pid()</v>
+ <v>Ret = term()</v>
+ </type>
+ <desc>
+ <p>This function must used by a process that has been started by
+ a <seealso marker="#start/3">start[_link]/3,4,5</seealso>
+ function. It tells <c>Parent</c> that the process has
+ initialized itself, has started, or has failed to initialize
+ itself.</p>
+ <p>The <c>init_ack/1</c> function uses the parent value
+ previously stored by the start function used.</p>
+ <p>If this function is not called, the start function will
+ return an error tuple (if a link and/or a timeout is used) or
+ hang otherwise.</p>
+ <p>The following example illustrates how this function and
+ <c>proc_lib:start_link/3</c> are used.</p>
+ <code type="none">
+-module(my_proc).
+-export([start_link/0]).
+-export([init/1]).
+
+start_link() ->
+ proc_lib:start_link(my_proc, init, [self()]).
+
+init(Parent) ->
+ case do_initialization() of
+ ok ->
+ proc_lib:init_ack(Parent, {ok, self()});
+ {error, Reason} ->
+ exit(Reason)
+ end,
+ loop().
+
+...</code>
+ </desc>
+ </func>
+ <func>
+ <name>format(CrashReport) -> string()</name>
+ <fsummary>Format a crash report.</fsummary>
+ <type>
+ <v>CrashReport = term()</v>
+ </type>
+ <desc>
+ <p>This function can be used by a user defined event handler to
+ format a crash report. The crash report is sent using
+ <c>error_logger:error_report(crash_report, CrashReport)</c>.
+ That is, the event to be handled is of the format
+ <c>{error_report, GL, {Pid, crash_report, CrashReport}}</c>
+ where <c>GL</c> is the group leader pid of the process
+ <c>Pid</c> which sent the crash report.</p>
+ </desc>
+ </func>
+ <func>
+ <name>initial_call(Process) -> {Module,Function,Args} | false</name>
+ <fsummary>Extract the initial call of a <c>proc_lib</c>spawned process.</fsummary>
+ <type>
+ <v>Process = pid() | {X,Y,Z} | ProcInfo</v>
+ <v>&nbsp;X = Y = Z = int()</v>
+ <v>&nbsp;ProcInfo = term()</v>
+ <v>Module = Function = atom()</v>
+ <v>Args = [atom()]</v>
+ </type>
+ <desc>
+ <p>Extracts the initial call of a process that was started
+ using one of the spawn or start functions described above.
+ <c>Process</c> can either be a pid, an integer tuple (from
+ which a pid can be created), or the process information of a
+ process <c>Pid</c> fetched through an
+ <c>erlang:process_info(Pid)</c> function call.</p>
+
+ <note><p>The list <c>Args</c> no longer contains the actual arguments,
+ but the same number of atoms as the number of arguments; the first atom
+ is always <c>'Argument__1'</c>, the second <c>'Argument__2'</c>, and
+ so on. The reason is that the argument list could waste a significant
+ amount of memory, and if the argument list contained funs, it could
+ be impossible to upgrade the code for the module.</p>
+
+ <p>If the process was spawned using a fun, <c>initial_call/1</c> no
+ longer returns the actual fun, but the module, function for the local
+ function implementing the fun, and the arity, for instance
+ <c>{some_module,-work/3-fun-0-,0}</c> (meaning that the fun was
+ created in the function <c>some_module:work/3</c>).
+ The reason is that keeping the fun would prevent code upgrade for the
+ module, and that a significant amount of memory could be wasted.</p>
+ </note>
+ </desc>
+ </func>
+ <func>
+ <name>translate_initial_call(Process) -> {Module,Function,Arity} | Fun</name>
+ <fsummary>Extract and translate the initial call of a <c>proc_lib</c>spawned process.</fsummary>
+ <type>
+ <v>Process = pid() | {X,Y,Z} | ProcInfo</v>
+ <v>&nbsp;X = Y = Z = int()</v>
+ <v>&nbsp;ProcInfo = term()</v>
+ <v>Module = Function = atom()</v>
+ <v>Arity = int()</v>
+ <v>Fun = fun() -> void()</v>
+ </type>
+ <desc>
+ <p>This function is used by the <c>c:i/0</c> and
+ <c>c:regs/0</c> functions in order to present process
+ information.</p>
+ <p>Extracts the initial call of a process that was started
+ using one of the spawn or start functions described above,
+ and translates it to more useful information. <c>Process</c>
+ can either be a pid, an integer tuple (from which a pid can
+ be created), or the process information of a process
+ <c>Pid</c> fetched through an <c>erlang:process_info(Pid)</c>
+ function call.</p>
+ <p>If the initial call is to one of the system defined behaviors
+ such as <c>gen_server</c> or <c>gen_event</c>, it is
+ translated to more useful information. If a <c>gen_server</c>
+ is spawned, the returned <c>Module</c> is the name of
+ the callback module and <c>Function</c> is <c>init</c>
+ (the function that initiates the new server).</p>
+ <p>A <c>supervisor</c> and a <c>supervisor_bridge</c> are also
+ <c>gen_server</c> processes. In order to return information
+ that this process is a supervisor and the name of the
+ call-back module, <c>Module</c> is <c>supervisor</c> and
+ <c>Function</c> is the name of the supervisor callback
+ module. <c>Arity</c> is <c>1</c> since the <c>init/1</c>
+ function is called initially in the callback module.</p>
+ <p>By default, <c>{proc_lib,init_p,5}</c> is returned if no
+ information about the initial call can be found. It is
+ assumed that the caller knows that the process has been
+ spawned with the <c>proc_lib</c> module.</p>
+ </desc>
+ </func>
+ <func>
+ <name>hibernate(Module, Function, Args)</name>
+ <fsummary>Hibernate a process until a message is sent to it</fsummary>
+ <type>
+ <v>Module = Function = atom()</v>
+ <v>Args = [term()]</v>
+ </type>
+ <desc>
+ <p>This function does the same as (and does call) the BIF
+ <seealso marker="erts:erlang#erlang:hibernate/3">hibernate/3</seealso>,
+ but ensures that exception handling and logging continues to
+ work as expected when the process wakes up. Always use this
+ function instead of the BIF for processes started using
+ <c>proc_lib</c> functions.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="kernel:error_logger">error_logger(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/proplists.xml b/lib/stdlib/doc/src/proplists.xml
new file mode 100644
index 0000000000..a218dcf1fe
--- /dev/null
+++ b/lib/stdlib/doc/src/proplists.xml
@@ -0,0 +1,386 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2002</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>proplists</title>
+ <prepared>Patrik Nyblom</prepared>
+ <responsible>Kenneth Lundin</responsible>
+ <docno>1</docno>
+ <approved>Kenneth Lundin</approved>
+ <checked></checked>
+ <date>2002-09-28</date>
+ <rev>A</rev>
+ <file>proplists.sgml</file>
+ </header>
+ <module>proplists</module>
+ <modulesummary>Support functions for property lists</modulesummary>
+ <description>
+ <p>Property lists are ordinary lists containing entries in the form
+ of either tuples, whose first elements are keys used for lookup and
+ insertion, or atoms, which work as shorthand for tuples <c>{Atom, true}</c>. (Other terms are allowed in the lists, but are ignored
+ by this module.) If there is more than one entry in a list for a
+ certain key, the first occurrence normally overrides any later
+ (irrespective of the arity of the tuples).</p>
+ <p>Property lists are useful for representing inherited properties,
+ such as options passed to a function where a user may specify options
+ overriding the default settings, object properties, annotations,
+ etc.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>append_values(Key, List) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Similar to <c>get_all_values/2</c>, but each value is
+ wrapped in a list unless it is already itself a list, and the
+ resulting list of lists is concatenated. This is often useful for
+ "incremental" options; e.g., <c>append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</c> will return the list
+ <c>[1,2,3,4]</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>compact(List) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Minimizes the representation of all entries in the list. This is
+ equivalent to <c><![CDATA[[property(P) || P <- List]]]></c>.</p>
+ <p>See also: <c>property/1</c>, <c>unfold/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete(Key, List) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Deletes all entries associated with <c>Key</c> from
+ <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>expand(Expansions, List) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Expansions = [{Property,[term()]}]</v>
+ <v>Property = atom() | tuple()</v>
+ </type>
+ <desc>
+ <p>Expands particular properties to corresponding sets of
+ properties (or other terms). For each pair <c>{Property, Expansion}</c> in <c>Expansions</c>, if <c>E</c> is
+ the first entry in <c>List</c> with the same key as
+ <c>Property</c>, and <c>E</c> and <c>Property</c>
+ have equivalent normal forms, then <c>E</c> is replaced with
+ the terms in <c>Expansion</c>, and any following entries with
+ the same key are deleted from <c>List</c>.</p>
+ <p>For example, the following expressions all return <c>[fie, bar, baz, fum]</c>:</p>
+ <code type="none">
+ expand([{foo, [bar, baz]}],
+ [fie, foo, fum])
+ expand([{{foo, true}, [bar, baz]}],
+ [fie, foo, fum])
+ expand([{{foo, false}, [bar, baz]}],
+ [fie, {foo, false}, fum])</code>
+ <p>However, no expansion is done in the following call:</p>
+ <code type="none">
+ expand([{{foo, true}, [bar, baz]}],
+ [{foo, false}, fie, foo, fum])</code>
+ <p>because <c>{foo, false}</c> shadows <c>foo</c>.</p>
+ <p>Note that if the original property term is to be preserved in the
+ result when expanded, it must be included in the expansion list. The
+ inserted terms are not expanded recursively. If
+ <c>Expansions</c> contains more than one property with the same
+ key, only the first occurrence is used.</p>
+ <p>See also: <c>normalize/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_all_values(Key, List) -> [term()]</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Similar to <c>get_value/2</c>, but returns the list of
+ values for <em>all</em> entries <c>{Key, Value}</c> in
+ <c>List</c>. If no such entry exists, the result is the empty
+ list.</p>
+ <p>See also: <c>get_value/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_bool(Key, List) -> bool()</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the value of a boolean key/value option. If
+ <c>lookup(Key, List)</c> would yield <c>{Key, true}</c>,
+ this function returns <c>true</c>; otherwise <c>false</c>
+ is returned.</p>
+ <p>See also: <c>get_value/2</c>, <c>lookup/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_keys(List) -> [term()]</name>
+ <fsummary></fsummary>
+ <type>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns an unordered list of the keys used in <c>List</c>,
+ not containing duplicates.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_value(Key, List) -> term()</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Equivalent to <c>get_value(Key, List, undefined)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_value(Key, List, Default) -> term()</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Default = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the value of a simple key/value property in
+ <c>List</c>. If <c>lookup(Key, List)</c> would yield
+ <c>{Key, Value}</c>, this function returns the corresponding
+ <c>Value</c>, otherwise <c>Default</c> is returned.</p>
+ <p>See also: <c>get_all_values/2</c>, <c>get_bool/2</c>,
+ <c>get_value/2</c>, <c>lookup/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_defined(Key, List) -> bool()</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>List</c> contains at least
+ one entry associated with <c>Key</c>, otherwise
+ <c>false</c> is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>lookup(Key, List) -> none | tuple()</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the first entry associated with <c>Key</c> in
+ <c>List</c>, if one exists, otherwise returns
+ <c>none</c>. For an atom <c>A</c> in the list, the tuple
+ <c>{A, true}</c> is the entry associated with <c>A</c>.</p>
+ <p>See also: <c>get_bool/2</c>, <c>get_value/2</c>,
+ <c>lookup_all/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>lookup_all(Key, List) -> [tuple()]</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the list of all entries associated with <c>Key</c>
+ in <c>List</c>. If no such entry exists, the result is the
+ empty list.</p>
+ <p>See also: <c>lookup/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>normalize(List, Stages) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>List = [term()]</v>
+ <v>Stages = [Operation]</v>
+ <v>Operation = {aliases, Aliases} | {negations, Negations} | {expand, Expansions}</v>
+ <v>Aliases = [{Key, Key}]</v>
+ <v>Negations = [{Key, Key}]</v>
+ <v>Key = term()</v>
+ <v>Expansions = [{Property, [term()]}]</v>
+ <v>Property = atom() | tuple()</v>
+ </type>
+ <desc>
+ <p>Passes <c>List</c> through a sequence of
+ substitution/expansion stages. For an <c>aliases</c> operation,
+ the function <c>substitute_aliases/2</c> is applied using the
+ given list of aliases; for a <c>negations</c> operation,
+ <c>substitute_negations/2</c> is applied using the given
+ negation list; for an <c>expand</c> operation, the function
+ <c>expand/2</c> is applied using the given list of expansions.
+ The final result is automatically compacted (cf.
+ <c>compact/1</c>).</p>
+ <p>Typically you want to substitute negations first, then aliases,
+ then perform one or more expansions (sometimes you want to pre-expand
+ particular entries before doing the main expansion). You might want
+ to substitute negations and/or aliases repeatedly, to allow such
+ forms in the right-hand side of aliases and expansion lists.</p>
+ <p>See also: <c>compact/1</c>, <c>expand/2</c>,
+ <c>substitute_aliases/2</c>, <c>substitute_negations/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>property(Property) -> Property</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Property = atom() | tuple()</v>
+ </type>
+ <desc>
+ <p>Creates a normal form (minimal) representation of a property. If
+ <c>Property</c> is <c>{Key, true}</c> where <c>Key</c> is
+ an atom, this returns <c>Key</c>, otherwise the whole term
+ <c>Property</c> is returned.</p>
+ <p>See also: <c>property/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>property(Key, Value) -> Property</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Key = term()</v>
+ <v>Value = term()</v>
+ <v>Property = atom() | tuple()</v>
+ </type>
+ <desc>
+ <p>Creates a normal form (minimal) representation of a simple
+ key/value property. Returns <c>Key</c> if <c>Value</c> is
+ <c>true</c> and <c>Key</c> is an atom, otherwise a tuple
+ <c>{Key, Value}</c> is returned.</p>
+ <p>See also: <c>property/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>split(List, Keys) -> {Lists, Rest}</name>
+ <fsummary></fsummary>
+ <type>
+ <v>List = [term()]</v>
+ <v>Keys = [term()]</v>
+ <v>Lists = [[term()]]</v>
+ <v>Rest = [term()]</v>
+ </type>
+ <desc>
+ <p>Partitions <c>List</c> into a list of sublists and a
+ remainder. <c>Lists</c> contains one sublist for each key in
+ <c>Keys</c>, in the corresponding order. The relative order of
+ the elements in each sublist is preserved from the original
+ <c>List</c>. <c>Rest</c> contains the elements in
+ <c>List</c> that are not associated with any of the given keys,
+ also with their original relative order preserved.</p>
+ <p>Example:
+ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</p>
+ <p>returns</p>
+ <p>{[[a], [{b, 5}, b],[{c, 2}, {c, 3, 4}]], [{e, 1}, d]}</p>
+ </desc>
+ </func>
+ <func>
+ <name>substitute_aliases(Aliases, List) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Aliases = [{Key, Key}]</v>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Substitutes keys of properties. For each entry in
+ <c>List</c>, if it is associated with some key <c>K1</c>
+ such that <c>{K1, K2}</c> occurs in <c>Aliases</c>, the
+ key of the entry is changed to <c>Key2</c>. If the same
+ <c>K1</c> occurs more than once in <c>Aliases</c>, only
+ the first occurrence is used.</p>
+ <p>Example: <c>substitute_aliases([{color, colour}], L)</c>
+ will replace all tuples <c>{color, ...}</c> in <c>L</c>
+ with <c>{colour, ...}</c>, and all atoms <c>color</c>
+ with <c>colour</c>.</p>
+ <p>See also: <c>normalize/2</c>, <c>substitute_negations/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>substitute_negations(Negations, List) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>Negations = [{Key, Key}]</v>
+ <v>Key = term()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Substitutes keys of boolean-valued properties and
+ simultaneously negates their values. For each entry in
+ <c>List</c>, if it is associated with some key <c>K1</c>
+ such that <c>{K1, K2}</c> occurs in <c>Negations</c>, then
+ if the entry was <c>{K1, true}</c> it will be replaced with
+ <c>{K2, false}</c>, otherwise it will be replaced with
+ <c>{K2, true}</c>, thus changing the name of the option and
+ simultaneously negating the value given by
+ <c>get_bool(List)</c>. If the same <c>K1</c> occurs more
+ than once in <c>Negations</c>, only the first occurrence is
+ used.</p>
+ <p>Example: <c>substitute_negations([{no_foo, foo}], L)</c>
+ will replace any atom <c>no_foo</c> or tuple
+ <c>{no_foo, true}</c> in <c>L</c> with <c>{foo, false}</c>,
+ and any other tuple <c>{no_foo, ...}</c> with
+ <c>{foo, true}</c>.</p>
+ <p>See also: <c>get_bool/2</c>, <c>normalize/2</c>,
+ <c>substitute_aliases/2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>unfold(List) -> List</name>
+ <fsummary></fsummary>
+ <type>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Unfolds all occurrences of atoms in <c>List</c> to tuples
+ <c>{Atom, true}</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/qlc.xml b/lib/stdlib/doc/src/qlc.xml
new file mode 100644
index 0000000000..da24ee9914
--- /dev/null
+++ b/lib/stdlib/doc/src/qlc.xml
@@ -0,0 +1,1486 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2004</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>qlc</title>
+ <prepared>Hans Bolinder</prepared>
+ <responsible>nobody</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2004-08-25</date>
+ <rev>PA1</rev>
+ <file>qlc.sgml</file>
+ </header>
+ <module>qlc</module>
+ <modulesummary>Query Interface to Mnesia, ETS, Dets, etc</modulesummary>
+ <description>
+ <p>The <c>qlc</c> module provides a query interface to Mnesia, ETS,
+ Dets and other data structures that implement an iterator style
+ traversal of objects. </p>
+ </description>
+
+ <section><title>Overview</title>
+
+ <p>The <c>qlc</c> module implements a query interface to <em>QLC
+ tables</em>. Typical QLC tables are ETS, Dets, and Mnesia
+ tables. There is also support for user defined tables, see the
+ <seealso marker="#implementing_a_qlc_table">Implementing a QLC
+ table</seealso> section. A <em>query</em> is stated using
+ <em>Query List Comprehensions</em> (QLCs). The answers to a
+ query are determined by data in QLC tables that fulfill the
+ constraints expressed by the QLCs of the query. QLCs are similar
+ to ordinary list comprehensions as described in the Erlang
+ Reference Manual and Programming Examples except that variables
+ introduced in patterns cannot be used in list expressions. In
+ fact, in the absence of optimizations and options such as
+ <c>cache</c> and <c>unique</c> (see below), every QLC free of
+ QLC tables evaluates to the same list of answers as the
+ identical ordinary list comprehension. </p>
+
+ <p>While ordinary list comprehensions evaluate to lists, calling
+ <seealso marker="#q">qlc:q/1,2</seealso> returns a <em>Query
+ Handle</em>. To obtain all the answers to a query, <seealso
+ marker="#eval">qlc:eval/1,2</seealso> should be called with the
+ query handle as first argument. Query handles are essentially
+ functional objects ("funs") created in the module calling <c>q/1,2</c>.
+ As the funs refer to the module's code, one should
+ be careful not to keep query handles too long if the module's
+ code is to be replaced.
+ Code replacement is described in the <seealso
+ marker="doc/reference_manual:code_loading">Erlang Reference
+ Manual</seealso>. The list of answers can also be traversed in
+ chunks by use of a <em>Query Cursor</em>. Query cursors are
+ created by calling <seealso
+ marker="#cursor">qlc:cursor/1,2</seealso> with a query handle as
+ first argument. Query cursors are essentially Erlang processes.
+ One answer at a time is sent from the query cursor process to
+ the process that created the cursor.</p>
+
+ </section>
+
+ <section><title>Syntax</title>
+
+ <p>Syntactically QLCs have the same parts as ordinary list
+ comprehensions:</p>
+
+ <code type="none">[Expression || Qualifier1, Qualifier2, ...]</code>
+
+ <p><c>Expression</c> (the <em>template</em>) is an arbitrary
+ Erlang expression. Qualifiers are either <em>filters</em> or
+ <em>generators</em>. Filters are Erlang expressions returning
+ <c>bool()</c>. Generators have the form
+ <c><![CDATA[Pattern <- ListExpression]]></c>, where
+ <c>ListExpression</c> is an expression evaluating to a query
+ handle or a list. Query handles are returned from
+ <c>qlc:table/2</c>, <c>qlc:append/1,2</c>, <c>qlc:sort/1,2</c>,
+ <c>qlc:keysort/2,3</c>, <c>qlc:q/1,2</c>, and
+ <c>qlc:string_to_handle/1,2,3</c>.</p>
+
+ </section>
+
+ <section><title>Evaluation</title>
+
+ <p>The evaluation of a query handle begins by the inspection of
+ options and the collection of information about tables. As a
+ result qualifiers are modified during the optimization phase.
+ Next all list expressions are evaluated. If a cursor has been
+ created evaluation takes place in the cursor process. For those
+ list expressions that are QLCs, the list expressions of the
+ QLCs' generators are evaluated as well. One has to be careful if
+ list expressions have side effects since the order in which list
+ expressions are evaluated is unspecified. Finally the answers
+ are found by evaluating the qualifiers from left to right,
+ backtracking when some filter returns <c>false</c>, or
+ collecting the template when all filters return <c>true</c>.</p>
+
+ <p>Filters that do not return <c>bool()</c> but fail are handled
+ differently depending on their syntax: if the filter is a guard
+ it returns <c>false</c>, otherwise the query evaluation fails.
+ This behavior makes it possible for the <c>qlc</c> module to do
+ some optimizations without affecting the meaning of a query. For
+ example, when testing some position of a table and one or more
+ constants for equality, only
+ the objects with equal values are candidates for further
+ evaluation. The other objects are guaranteed to make the filter
+ return <c>false</c>, but never fail. The (small) set of
+ candidate objects can often be found by looking up some key
+ values of the table or by traversing the table using a match
+ specification. It is necessary to place the guard filters
+ immediately after the table's generator, otherwise the candidate
+ objects will not be restricted to a small set. The reason is
+ that objects that could make the query evaluation fail must not
+ be excluded by looking up a key or running a match
+ specification.</p>
+
+ </section>
+
+ <section><title>Join</title>
+
+ <p>The <c>qlc</c> module supports fast join of two query handles.
+ Fast join is possible if some position <c>P1</c> of one query
+ handler and some position <c>P2</c> of another query handler are
+ tested for equality. Two fast join methods have been
+ implemented:</p>
+
+ <list type="bulleted">
+ <item>Lookup join traverses all objects of one query handle and
+ finds objects of the other handle (a QLC table) such that the
+ values at <c>P1</c> and <c>P2</c> match or compare equal.
+ The <c>qlc</c> module does not create
+ any indices but looks up values using the key position and
+ the indexed positions of the QLC table.
+ </item>
+ <item>Merge join sorts the objects of each query handle if
+ necessary and filters out objects where the values at
+ <c>P1</c> and <c>P2</c> do not compare equal. If there are
+ many objects with the same value of <c>P2</c> a temporary
+ file will be used for the equivalence classes.
+ </item>
+ </list>
+
+ <p>The <c>qlc</c> module warns at compile time if a QLC
+ combines query handles in such a way that more than one join is
+ possible. In other words, there is no query planner that can
+ choose a good order between possible join operations. It is up
+ to the user to order the joins by introducing query handles.</p>
+
+ <p>The join is to be expressed as a guard filter. The filter must
+ be placed immediately after the two joined generators, possibly
+ after guard filters that use variables from no other generators
+ but the two joined generators. The <c>qlc</c> module inspects
+ the operands of
+ <c>=:=/2</c>, <c>==/2</c>, <c>is_record/2</c>, <c>element/2</c>,
+ and logical operators (<c>and/2</c>, <c>or/2</c>,
+ <c>andalso/2</c>, <c>orelse/2</c>, <c>xor/2</c>) when
+ determining which joins to consider.</p>
+
+ </section>
+
+ <section><title>Common options</title>
+
+ <p>The following options are accepted by <c>cursor/2</c>,
+ <c>eval/2</c>, <c>fold/4</c>, and <c>info/2</c>:</p>
+
+ <list type="bulleted">
+ <item><c>{cache_all, Cache}</c> where <c>Cache</c> is
+ equal to <c>ets</c> or <c>list</c> adds a
+ <c>{cache,&nbsp;Cache}</c> option to every list expression
+ of the query except tables and lists. Default is
+ <c>{cache_all,&nbsp;no}</c>. The option <c>cache_all</c> is
+ equivalent to <c>{cache_all,&nbsp;ets}</c>.
+ </item>
+ <item><c>{max_list_size, MaxListSize}</c> <marker
+ id="max_list_size"></marker> where <c>MaxListSize</c> is the
+ size in bytes of terms on the external format. If the
+ accumulated size of collected objects exceeds
+ <c>MaxListSize</c> the objects are written onto a temporary
+ file. This option is used by the <c>{cache,&nbsp;list}</c>
+ option as well as by the merge join method. Default is
+ 512*1024 bytes.
+ </item>
+ <item><c>{tmpdir_usage, TmpFileUsage}</c> determines the
+ action taken when <c>qlc</c> is about to create temporary
+ files on the directory set by the <c>tmpdir</c> option. If the
+ value is <c>not_allowed</c> an error tuple is returned,
+ otherwise temporary files are created as needed. Default is
+ <c>allowed</c> which means that no further action is taken.
+ The values <c>info_msg</c>, <c>warning_msg</c>, and
+ <c>error_msg</c> mean that the function with the corresponding
+ name in the module <c>error_logger</c> is called for printing
+ some information (currently the stacktrace).
+ </item>
+ <item><c>{tmpdir, TempDirectory}</c> sets the directory used by
+ merge join for temporary files and by the
+ <c>{cache,&nbsp;list}</c> option. The option also overrides
+ the <c>tmpdir</c> option of <c>keysort/3</c> and
+ <c>sort/2</c>. The default value is <c>""</c> which means that
+ the directory returned by <c>file:get_cwd()</c> is used.
+ </item>
+ <item><c>{unique_all, true}</c> adds a
+ <c>{unique,&nbsp;true}</c> option to every list expression of
+ the query. Default is <c>{unique_all,&nbsp;false}</c>. The
+ option <c>unique_all</c> is equivalent to
+ <c>{unique_all,&nbsp;true}</c>.
+ </item>
+ </list>
+
+ </section>
+
+ <section><title>Common data types</title>
+
+ <list type="bulleted">
+ <item><p><c>QueryCursor = {qlc_cursor, term()}</c></p>
+ </item>
+ <item><p><c>QueryHandle = {qlc_handle, term()}</c></p>
+ </item>
+ <item><p><c>QueryHandleOrList = QueryHandle | list()</c></p>
+ </item>
+ <item><p><c>Answers = [Answer]</c></p>
+ </item>
+ <item><p><c>Answer = term()</c></p>
+ </item>
+ <item><p><c>AbstractExpression =&nbsp;</c> -&nbsp;parse trees
+ for Erlang expressions, see the <seealso
+ marker="erts:absform">abstract format</seealso>
+ documentation in the ERTS User's Guide&nbsp;-</p>
+ </item>
+ <item><p><c>MatchExpression =&nbsp;</c>
+ -&nbsp;match&nbsp;specifications, see the <seealso
+ marker="erts:match_spec">match specification</seealso>
+ documentation in the ERTS User's Guide and <seealso
+ marker="ms_transform">ms_transform(3)</seealso>&nbsp;-</p>
+ </item>
+ <item><p><c>SpawnOptions = default | spawn_options()</c></p>
+ </item>
+ <item><p><c>SortOptions = [SortOption] | SortOption</c></p>
+ </item>
+ <item><p><c>SortOption = {compressed, bool()}
+ | {no_files, NoFiles}
+ | {order, Order}
+ | {size, Size}
+ | {tmpdir, TempDirectory}
+ | {unique, bool()}&nbsp;</c>
+ -&nbsp;see <seealso
+ marker="file_sorter">file_sorter(3)</seealso>&nbsp;-</p>
+ </item>
+ <item><p><c>Order = ascending | descending | OrderFun</c></p>
+ </item>
+ <item><p><c>OrderFun = fun(term(), term()) -> bool()</c></p>
+ </item>
+ <item><p><c>TempDirectory = "" | filename()</c></p>
+ </item>
+ <item><p><c>Size = int() > 0</c></p>
+ </item>
+ <item><p><c>NoFiles = int() > 1</c></p>
+ </item>
+ <item><p><c>KeyPos = int() > 0 | [int() > 0]</c></p>
+ </item>
+ <item><p><c>MaxListSize = int() >= 0</c></p>
+ </item>
+ <item><p><c>bool() = true | false</c></p>
+ </item>
+ <item><p><c>Cache = ets | list | no</c></p>
+ </item>
+ <item><p><c>TmpFileUsage = allowed | not_allowed | info_msg
+ | warning_msg | error_msg</c></p>
+ </item>
+ <item><p><c>filename() =&nbsp;</c> -&nbsp;see <seealso
+ marker="filename">filename(3)</seealso>&nbsp;-</p>
+ </item>
+ <item><p><c>spawn_options() =&nbsp;</c> -&nbsp;see <seealso
+ marker="erts:erlang">erlang(3)</seealso>&nbsp;-</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section><title>Getting started</title>
+
+ <p><marker id="getting_started"></marker> As already mentioned
+ queries are stated in the list comprehension syntax as described
+ in the <seealso marker="doc/reference_manual:expressions">Erlang
+ Reference Manual</seealso>. In the following some familiarity
+ with list comprehensions is assumed. There are examples in
+ <seealso
+ marker="doc/programming_examples:list_comprehensions">Programming
+ Examples</seealso> that can get you started. It should be
+ stressed that list comprehensions do not add any computational
+ power to the language; anything that can be done with list
+ comprehensions can also be done without them. But they add a
+ syntax for expressing simple search problems which is compact
+ and clear once you get used to it.</p>
+
+ <p>Many list comprehension expressions can be evaluated by the
+ <c>qlc</c> module. Exceptions are expressions such that
+ variables introduced in patterns (or filters) are used in some
+ generator later in the list comprehension. As an example
+ consider an implementation of lists:append(L):
+ <c><![CDATA[[X ||Y <- L, X <- Y]]]></c>.
+ Y is introduced in the first generator and used in the second.
+ The ordinary list comprehension is normally to be preferred when
+ there is a choice as to which to use. One difference is that
+ <c>qlc:eval/1,2</c> collects answers in a list which is finally
+ reversed, while list comprehensions collect answers on the stack
+ which is finally unwound.</p>
+
+ <p>What the <c>qlc</c> module primarily adds to list
+ comprehensions is that data can be read from QLC tables in small
+ chunks. A QLC table is created by calling <c>qlc:table/2</c>.
+ Usually <c>qlc:table/2</c> is not called directly from the query
+ but via an interface function of some data structure. There are
+ a few examples of such functions in Erlang/OTP:
+ <c>mnesia:table/1,2</c>, <c>ets:table/1,2</c>, and
+ <c>dets:table/1,2</c>. For a given data structure there can be
+ several functions that create QLC tables, but common for all
+ these functions is that they return a query handle created by
+ <c>qlc:table/2</c>. Using the QLC tables provided by OTP is
+ probably sufficient in most cases, but for the more advanced
+ user the section <seealso
+ marker="#implementing_a_qlc_table">Implementing a QLC
+ table</seealso> describes the implementation of a function
+ calling <c>qlc:table/2</c>.</p>
+
+ <p>Besides <c>qlc:table/2</c> there are other functions that
+ return query handles. They might not be used as often as tables,
+ but are useful from time to time. <c>qlc:append</c> traverses
+ objects from several tables or lists after each other. If, for
+ instance, you want to traverse all answers to a query QH and
+ then finish off by a term <c>{finished}</c>, you can do that by
+ calling <c>qlc:append(QH, [{finished}])</c>. <c>append</c> first
+ returns all objects of QH, then <c>{finished}</c>. If there is
+ one tuple <c>{finished}</c> among the answers to QH it will be
+ returned twice from <c>append</c>.</p>
+
+ <p>As another example, consider concatenating the answers to two
+ queries QH1 and QH2 while removing all duplicates. The means to
+ accomplish this is to use the <c>unique</c> option:</p>
+
+ <code type="none"><![CDATA[
+qlc:q([X || X <- qlc:append(QH1, QH2)], {unique, true})]]></code>
+
+ <p>The cost is substantial: every returned answer will be stored
+ in an ETS table. Before returning an answer it is looked up in
+ the ETS table to check if it has already been returned. Without
+ the <c>unique</c> options all answers to QH1 would be returned
+ followed by all answers to QH2. The <c>unique</c> options keeps
+ the order between the remaining answers.</p>
+
+ <p>If the order of the answers is not important there is the
+ alternative to sort the answers uniquely:</p>
+
+ <code type="none"><![CDATA[
+qlc:sort(qlc:q([X || X <- qlc:append(QH1, QH2)], {unique, true})).]]></code>
+
+ <p>This query also removes duplicates but the answers will be
+ sorted. If there are many answers temporary files will be used.
+ Note that in order to get the first unique answer all answers
+ have to be found and sorted. Both alternatives find duplicates
+ by comparing answers, that is, if A1 and A2 are answers found in
+ that order, then A2 is a removed if A1 == A2.</p>
+
+ <p>To return just a few answers cursors can be used. The following
+ code returns no more than five answers using an ETS table for
+ storing the unique answers:</p>
+
+ <code type="none"><![CDATA[
+C = qlc:cursor(qlc:q([X || X <- qlc:append(QH1, QH2)],{unique,true})),
+R = qlc:next_answers(C, 5),
+ok = qlc:delete_cursor(C),
+R.]]></code>
+
+ <p>Query list comprehensions are convenient for stating
+ constraints on data from two or more tables. An example that
+ does a natural join on two query handles on position 2:</p>
+
+ <code type="none"><![CDATA[
+qlc:q([{X1,X2,X3,Y1} ||
+ {X1,X2,X3} <- QH1,
+ {Y1,Y2} <- QH2,
+ X2 =:= Y2])]]></code>
+
+ <p>The <c>qlc</c> module will evaluate this differently depending on
+ the query
+ handles <c>QH1</c> and <c>QH2</c>. If, for example, <c>X2</c> is
+ matched against the key of a QLC table the lookup join method
+ will traverse the objects of <c>QH2</c> while looking up key
+ values in the table. On the other hand, if neither <c>X2</c> nor
+ <c>Y2</c> is matched against the key or an indexed position of a
+ QLC table, the merge join method will make sure that <c>QH1</c>
+ and <c>QH2</c> are both sorted on position 2 and next do the
+ join by traversing the objects one by one.</p>
+
+ <p>The <c>join</c> option can be used to force the <c>qlc</c> module
+ to use a
+ certain join method. For the rest of this section it is assumed
+ that the excessively slow join method called "nested loop" has
+ been chosen:</p>
+
+ <code type="none"><![CDATA[
+qlc:q([{X1,X2,X3,Y1} ||
+ {X1,X2,X3} <- QH1,
+ {Y1,Y2} <- QH2,
+ X2 =:= Y2],
+ {join, nested_loop})]]></code>
+
+ <p>In this case the filter will be applied to every possible pair
+ of answers to QH1 and QH2, one at a time. If there are M answers
+ to QH1 and N answers to QH2 the filter will be run M*N
+ times.</p>
+
+ <p>If QH2 is a call to the function for <c>gb_trees</c> as defined
+ in the <seealso marker="#implementing_a_qlc_table">Implementing
+ a QLC table</seealso> section, <c>gb_table:table/1</c>, the
+ iterator for the gb-tree will be initiated for each answer to
+ QH1 after which the objects of the gb-tree will be returned one
+ by one. This is probably the most efficient way of traversing
+ the table in that case since it takes minimal computational
+ power to get the following object. But if QH2 is not a table but
+ a more complicated QLC, it can be more efficient use some RAM
+ memory for collecting the answers in a cache, particularly if
+ there are only a few answers. It must then be assumed that
+ evaluating QH2 has no side effects so that the meaning of the
+ query does not change if QH2 is evaluated only once. One way of
+ caching the answers is to evaluate QH2 first of all and
+ substitute the list of answers for QH2 in the query. Another way
+ is to use the <c>cache</c> option. It is stated like this:</p>
+
+ <code type="none"><![CDATA[
+QH2' = qlc:q([X || X <- QH2], {cache, ets})]]></code>
+
+ <p>or just</p>
+
+ <code type="none"><![CDATA[
+QH2' = qlc:q([X || X <- QH2], cache)]]></code>
+
+ <p>The effect of the <c>cache</c> option is that when the
+ generator QH2' is run the first time every answer is stored in
+ an ETS table. When next answer of QH1 is tried, answers to QH2'
+ are copied from the ETS table which is very fast. As for the
+ <c>unique</c> option the cost is a possibly substantial amount
+ of RAM memory. The <c>{cache,&nbsp;list}</c> option offers the
+ possibility to store the answers in a list on the process heap.
+ While this has the potential of being faster than ETS tables
+ since there is no need to copy answers from the table it can
+ often result in slower evaluation due to more garbage
+ collections of the process' heap as well as increased RAM memory
+ consumption due to larger heaps. Another drawback with cache
+ lists is that if the size of the list exceeds a limit a
+ temporary file will be used. Reading the answers from a file is
+ very much slower than copying them from an ETS table. But if the
+ available RAM memory is scarce setting the <seealso
+ marker="#max_list_size">limit</seealso> to some low value is an
+ alternative.</p>
+
+ <p>There is an option <c>cache_all</c> that can be set to
+ <c>ets</c> or <c>list</c> when evaluating a query. It adds a
+ <c>cache</c> or <c>{cache,&nbsp;list}</c> option to every list
+ expression except QLC tables and lists on all levels of the
+ query. This can be used for testing if caching would improve
+ efficiency at all. If the answer is yes further testing is
+ needed to pinpoint the generators that should be cached.</p>
+
+ </section>
+
+ <section><title>Implementing a QLC table</title>
+
+ <p><marker id="implementing_a_qlc_table"></marker>As an example of
+ how to use the <seealso marker="#q">qlc:table/2</seealso>
+ function the implementation of a QLC table for the <seealso
+ marker="gb_trees">gb_trees</seealso> module is given:</p>
+
+ <code type="none"><![CDATA[
+-module(gb_table).
+
+-export([table/1]).
+
+table(T) ->
+ TF = fun() -> qlc_next(gb_trees:next(gb_trees:iterator(T))) end,
+ InfoFun = fun(num_of_objects) -> gb_trees:size(T);
+ (keypos) -> 1;
+ (is_sorted_key) -> true;
+ (is_unique_objects) -> true;
+ (_) -> undefined
+ end,
+ LookupFun =
+ fun(1, Ks) ->
+ lists:flatmap(fun(K) ->
+ case gb_trees:lookup(K, T) of
+ {value, V} -> [{K,V}];
+ none -> []
+ end
+ end, Ks)
+ end,
+ FormatFun =
+ fun({all, NElements, ElementFun}) ->
+ ValsS = io_lib:format("gb_trees:from_orddict(~w)",
+ [gb_nodes(T, NElements, ElementFun)]),
+ io_lib:format("gb_table:table(~s)", [ValsS]);
+ ({lookup, 1, KeyValues, _NElements, ElementFun}) ->
+ ValsS = io_lib:format("gb_trees:from_orddict(~w)",
+ [gb_nodes(T, infinity, ElementFun)]),
+ io_lib:format("lists:flatmap(fun(K) -> "
+ "case gb_trees:lookup(K, ~s) of "
+ "{value, V} -> [{K,V}];none -> [] end "
+ "end, ~w)",
+ [ValsS, [ElementFun(KV) || KV <- KeyValues]])
+ end,
+ qlc:table(TF, [{info_fun, InfoFun}, {format_fun, FormatFun},
+ {lookup_fun, LookupFun},{key_equality,'=='}]).
+
+qlc_next({X, V, S}) ->
+ [{X,V} | fun() -> qlc_next(gb_trees:next(S)) end];
+qlc_next(none) ->
+ [].
+
+gb_nodes(T, infinity, ElementFun) ->
+ gb_nodes(T, -1, ElementFun);
+gb_nodes(T, NElements, ElementFun) ->
+ gb_iter(gb_trees:iterator(T), NElements, ElementFun).
+
+gb_iter(_I, 0, _EFun) ->
+ '...';
+gb_iter(I0, N, EFun) ->
+ case gb_trees:next(I0) of
+ {X, V, I} ->
+ [EFun({X,V}) | gb_iter(I, N-1, EFun)];
+ none ->
+ []
+ end.]]></code>
+
+ <p><c>TF</c> is the traversal function. The <c>qlc</c> module
+ requires that there is a way of traversing all objects of the
+ data structure; in <c>gb_trees</c> there is an iterator function
+ suitable for that purpose. Note that for each object returned a
+ new fun is created. As long as the list is not terminated by
+ <c>[]</c> it is assumed that the tail of the list is a nullary
+ function and that calling the function returns further objects
+ (and functions).</p>
+
+ <p>The lookup function is optional. It is assumed that the lookup
+ function always finds values much faster than it would take to
+ traverse the table. The first argument is the position of the
+ key. Since <c>qlc_next</c> returns the objects as
+ {Key,&nbsp;Value} pairs the position is 1. Note that the lookup
+ function should return {Key,&nbsp;Value} pairs, just as the
+ traversal function does.</p>
+
+ <p>The format function is also optional. It is called by
+ <c>qlc:info</c> to give feedback at runtime of how the query
+ will be evaluated. One should try to give as good feedback as
+ possible without showing too much details. In the example at
+ most 7 objects of the table are shown. The format function
+ handles two cases: <c>all</c> means that all objects of the
+ table will be traversed; <c>{lookup,&nbsp;1,&nbsp;KeyValues}</c>
+ means that the lookup function will be used for looking up key
+ values.</p>
+
+ <p>Whether the whole table will be traversed or just some keys
+ looked up depends on how the query is stated. If the query has
+ the form</p>
+
+ <code type="none"><![CDATA[
+qlc:q([T || P <- LE, F])]]></code>
+
+ <p>and P is a tuple, the <c>qlc</c> module analyzes P and F in
+ compile time to find positions of the tuple P that are tested
+ for equality to constants. If such a position at runtime turns
+ out to be the key position, the lookup function can be used,
+ otherwise all objects of the table have to be traversed. It is
+ the info function <c>InfoFun</c> that returns the key position.
+ There can be indexed positions as well, also returned by the
+ info function. An index is an extra table that makes lookup on
+ some position fast. Mnesia maintains indices upon request,
+ thereby introducing so called secondary keys. The <c>qlc</c>
+ module prefers to look up objects using the key before secondary
+ keys regardless of the number of constants to look up.</p>
+
+ </section>
+
+ <section><title>Key equality</title>
+
+ <p>In Erlang there are two operators for testing term equality,
+ namely <c>==/2</c> and <c>=:=/2</c>. The difference between them
+ is all about the integers that can be represented by floats. For
+ instance, <c>2 == 2.0</c> evaluates to
+ <c>true</c> while <c>2 =:= 2.0</c> evaluates to <c>false</c>.
+ Normally this is a minor issue, but the <c>qlc</c> module cannot
+ ignore the difference, which affects the user's choice of
+ operators in QLCs.</p>
+
+ <p>If the <c>qlc</c> module can find out at compile time that some
+ constant is free of integers, it does not matter which one of
+ <c>==/2</c> or <c>=:=/2</c> is used:</p>
+
+ <pre>
+1> <input>E1 = ets:new(t, [set]), % uses =:=/2 for key equality</input>
+<input>Q1 = qlc:q([K ||</input>
+<input>{K} &lt;- ets:table(E1),</input>
+<input>K == 2.71 orelse K == a]),</input>
+<input>io:format("~s~n", [qlc:info(Q1)]).</input>
+ets:match_spec_run(lists:flatmap(fun(V) ->
+ ets:lookup(20493, V)
+ end,
+ [a,2.71]),
+ ets:match_spec_compile([{{'$1'},[],['$1']}]))</pre>
+
+ <p>In the example the <c>==/2</c> operator has been handled
+ exactly as <c>=:=/2</c> would have been handled. On the other
+ hand, if it cannot be determined at compile time that some
+ constant is free of integers and the table uses <c>=:=/2</c>
+ when comparing keys for equality (see the option <seealso
+ marker="#key_equality">key_equality</seealso>), the
+ <c>qlc</c> module will not try to look up the constant. The
+ reason is that there is in the general case no upper limit on
+ the number of key values that can compare equal to such a
+ constant; every combination of integers and floats has to be
+ looked up:</p>
+
+ <pre>
+2> <input>E2 = ets:new(t, [set]),</input>
+<input>true = ets:insert(E2, [{{2,2},a},{{2,2.0},b},{{2.0,2},c}]),</input>
+<input>F2 = fun(I) -></input>
+<input>qlc:q([V || {K,V} &lt;- ets:table(E2), K == I])</input>
+<input>end,</input>
+<input>Q2 = F2({2,2}),</input>
+<input>io:format("~s~n", [qlc:info(Q2)]).</input>
+ets:table(53264,
+ [{traverse,
+ {select,[{{'$1','$2'},[{'==','$1',{const,{2,2}}}],['$2']}]}}])
+3> <input>lists:sort(qlc:e(Q2)).</input>
+[a,b,c]</pre>
+
+ <p>Looking up just <c>{2,2}</c> would not return <c>b</c> and
+ <c>c</c>.</p>
+
+ <p>If the table uses <c>==/2</c> when comparing keys for equality,
+ the <c>qlc</c> module will look up the constant regardless of
+ which operator is used in the QLC. However, <c>==/2</c> is to
+ be preferred:</p>
+
+ <pre>
+4> <input>E3 = ets:new(t, [ordered_set]), % uses ==/2 for key equality</input>
+<input>true = ets:insert(E3, [{{2,2.0},b}]),</input>
+<input>F3 = fun(I) -></input>
+<input>qlc:q([V || {K,V} &lt;- ets:table(E3), K == I])</input>
+<input>end,</input>
+<input>Q3 = F3({2,2}),</input>
+<input>io:format("~s~n", [qlc:info(Q3)]).</input>
+ets:match_spec_run(ets:lookup(86033, {2,2}),
+ ets:match_spec_compile([{{'$1','$2'},[],['$2']}]))
+5> <input>qlc:e(Q3).</input>
+[b]</pre>
+
+ <p>Lookup join is handled analogously to lookup of constants in a
+ table: if the join operator is <c>==/2</c> and the table where
+ constants are to be looked up uses <c>=:=/2</c> when testing
+ keys for equality, the <c>qlc</c> module will not consider
+ lookup join for that table.</p>
+
+ </section>
+
+ <funcs>
+
+ <func>
+ <name>append(QHL) -> QH</name>
+ <fsummary>Return a query handle.</fsummary>
+ <type>
+ <v>QHL = [QueryHandleOrList]</v>
+ <v>QH = QueryHandle</v>
+ </type>
+ <desc>
+ <p>Returns a query handle. When evaluating the query handle
+ <c>QH</c> all answers to the first query handle in
+ <c>QHL</c> is returned followed by all answers to the rest
+ of the query handles in <c>QHL</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>append(QH1, QH2) -> QH3</name>
+ <fsummary>Return a query handle.</fsummary>
+ <type>
+ <v>QH1 = QH2 = QueryHandleOrList</v>
+ <v>QH3 = QueryHandle</v>
+ </type>
+ <desc>
+ <p>Returns a query handle. When evaluating the query handle
+ <c>QH3</c> all answers to <c>QH1</c> are returned followed
+ by all answers to <c>QH2</c>.</p>
+
+ <p><c>append(QH1,&nbsp;QH2)</c> is equivalent to
+ <c>append([QH1,&nbsp;QH2])</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>cursor(QueryHandleOrList [, Options]) -> QueryCursor</name>
+ <fsummary>Create a query cursor.</fsummary>
+ <type>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {cache_all, Cache} | cache_all
+ | {max_list_size, MaxListSize}
+ | {spawn_options, SpawnOptions}
+ | {tmpdir_usage, TmpFileUsage}
+ | {tmpdir, TempDirectory}
+ | {unique_all, bool()} | unique_all</v>
+ </type>
+ <desc>
+ <p><marker id="cursor"></marker>Creates a query cursor and
+ makes the calling process the owner of the cursor. The
+ cursor is to be used as argument to <c>next_answers/1,2</c>
+ and (eventually) <c>delete_cursor/1</c>. Calls
+ <c>erlang:spawn_opt</c> to spawn and link a process which
+ will evaluate the query handle. The value of the option
+ <c>spawn_options</c> is used as last argument when calling
+ <c>spawn_opt</c>. The default value is <c>[link]</c>.</p>
+
+ <pre>
+1> <input>QH = qlc:q([{X,Y} || X &lt;- [a,b], Y &lt;- [1,2]]),</input>
+<input>QC = qlc:cursor(QH),</input>
+<input>qlc:next_answers(QC, 1).</input>
+[{a,1}]
+2> <input>qlc:next_answers(QC, 1).</input>
+[{a,2}]
+3> <input>qlc:next_answers(QC, all_remaining).</input>
+[{b,1},{b,2}]
+4> <input>qlc:delete_cursor(QC).</input>
+ok</pre>
+ </desc>
+ </func>
+
+ <func>
+ <name>delete_cursor(QueryCursor) -> ok</name>
+ <fsummary>Delete a query cursor.</fsummary>
+ <desc>
+ <p>Deletes a query cursor. Only the owner of the cursor can
+ delete the cursor.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>eval(QueryHandleOrList [, Options]) -> Answers | Error</name>
+ <name>e(QueryHandleOrList [, Options]) -> Answers</name>
+ <fsummary>Return all answers to a query.</fsummary>
+ <type>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {cache_all, Cache} | cache_all
+ | {max_list_size, MaxListSize}
+ | {tmpdir_usage, TmpFileUsage}
+ | {tmpdir, TempDirectory}
+ | {unique_all, bool()} | unique_all</v>
+ <v>Error = {error, module(), Reason}</v>
+ <v>Reason =&nbsp;-&nbsp;as returned by file_sorter(3)&nbsp;-</v>
+ </type>
+ <desc>
+ <p><marker id="eval"></marker>Evaluates a query handle in the
+ calling process and collects all answers in a list.</p>
+
+ <pre>
+1> <input>QH = qlc:q([{X,Y} || X &lt;- [a,b], Y &lt;- [1,2]]),</input>
+<input>qlc:eval(QH).</input>
+[{a,1},{a,2},{b,1},{b,2}]</pre>
+ </desc>
+ </func>
+
+ <func>
+ <name>fold(Function, Acc0, QueryHandleOrList [, Options]) ->
+ Acc1 | Error</name>
+ <fsummary>Fold a function over the answers to a query.</fsummary>
+ <type>
+ <v>Function = fun(Answer, AccIn) -> AccOut</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {cache_all, Cache} | cache_all
+ | {max_list_size, MaxListSize}
+ | {tmpdir_usage, TmpFileUsage}
+ | {tmpdir, TempDirectory}
+ | {unique_all, bool()} | unique_all</v>
+ <v>Error = {error, module(), Reason}</v>
+ <v>Reason =&nbsp;-&nbsp;as returned by file_sorter(3)&nbsp;-</v>
+ </type>
+ <desc>
+ <p>Calls <c>Function</c> on successive answers to the query
+ handle together with an extra argument <c>AccIn</c>. The
+ query handle and the function are evaluated in the calling
+ process. <c>Function</c> must return a new accumulator which
+ is passed to the next call. <c>Acc0</c> is returned if there
+ are no answers to the query handle.</p>
+
+ <pre>
+1> <input>QH = [1,2,3,4,5,6],</input>
+<input>qlc:fold(fun(X, Sum) -> X + Sum end, 0, QH).</input>
+21</pre>
+ </desc>
+ </func>
+
+ <func>
+ <name>format_error(Error) -> Chars</name>
+ <fsummary>Return an English description of a an error tuple.</fsummary>
+ <type>
+ <v>Error = {error, module(), term()}</v>
+ <v>Chars = [char() | Chars]</v>
+ </type>
+ <desc>
+ <p>Returns a descriptive string in English of an error tuple
+ returned by some of the functions of the <c>qlc</c> module
+ or the parse transform. This function is mainly used by the
+ compiler invoking the parse transform.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>info(QueryHandleOrList [, Options]) -> Info</name>
+ <fsummary>Return code describing a query handle.</fsummary>
+ <type>
+ <v>Options = [Option] | Option</v>
+ <v>Option = EvalOption | ReturnOption</v>
+ <v>EvalOption = {cache_all, Cache} | cache_all
+ | {max_list_size, MaxListSize}
+ | {tmpdir_usage, TmpFileUsage}
+ | {tmpdir, TempDirectory}
+ | {unique_all, bool()} | unique_all</v>
+ <v>ReturnOption = {depth, Depth}
+ | {flat, bool()}
+ | {format, Format}
+ | {n_elements, NElements}</v>
+ <v>Depth = infinity | int() >= 0</v>
+ <v>Format = abstract_code | string</v>
+ <v>NElements = infinity | int() > 0</v>
+ <v>Info = AbstractExpression | string()</v>
+ </type>
+ <desc>
+ <p><marker id="info"></marker>Returns information about a
+ query handle. The information describes the simplifications
+ and optimizations that are the results of preparing the
+ query for evaluation. This function is probably useful
+ mostly during debugging.</p>
+
+ <p>The information has the form of an Erlang expression where
+ QLCs most likely occur. Depending on the format functions of
+ mentioned QLC tables it may not be absolutely accurate.</p>
+
+ <p>The default is to return a sequence of QLCs in a block, but
+ if the option <c>{flat,&nbsp;false}</c> is given, one single
+ QLC is returned. The default is to return a string, but if
+ the option <c>{format,&nbsp;abstract_code}</c> is given,
+ abstract code is returned instead. In the abstract code
+ port identifiers, references, and pids are represented by
+ strings. The default is to return
+ all elements in lists, but if the
+ <c>{n_elements,&nbsp;NElements}</c> option is given, only a
+ limited number of elements are returned. The default is to
+ show all of objects and match specifications, but if the
+ <c>{depth,&nbsp;Depth}</c> option is given, parts of terms
+ below a certain depth are replaced by <c>'...'</c>.</p>
+
+ <pre>
+1> <input>QH = qlc:q([{X,Y} || X &lt;- [x,y], Y &lt;- [a,b]]),</input>
+<input>io:format("~s~n", [qlc:info(QH, unique_all)]).</input>
+begin
+ V1 =
+ qlc:q([
+ SQV ||
+ SQV &lt;- [x,y]
+ ],
+ [{unique,true}]),
+ V2 =
+ qlc:q([
+ SQV ||
+ SQV &lt;- [a,b]
+ ],
+ [{unique,true}]),
+ qlc:q([
+ {X,Y} ||
+ X &lt;- V1,
+ Y &lt;- V2
+ ],
+ [{unique,true}])
+end</pre>
+
+ <p>In this example two simple QLCs have been inserted just to
+ hold the <c>{unique,&nbsp;true}</c> option.</p>
+
+ <pre>
+1> <input>E1 = ets:new(e1, []),</input>
+<input>E2 = ets:new(e2, []),</input>
+<input>true = ets:insert(E1, [{1,a},{2,b}]),</input>
+<input>true = ets:insert(E2, [{a,1},{b,2}]),</input>
+<input>Q = qlc:q([{X,Z,W} ||</input>
+<input>{X, Z} &lt;- ets:table(E1),</input>
+<input>{W, Y} &lt;- ets:table(E2),</input>
+<input>X =:= Y]),</input>
+<input>io:format("~s~n", [qlc:info(Q)]).</input>
+begin
+ V1 =
+ qlc:q([
+ P0 ||
+ P0 = {W,Y} &lt;- ets:table(17)
+ ]),
+ V2 =
+ qlc:q([
+ [G1|G2] ||
+ G2 &lt;- V1,
+ G1 &lt;- ets:table(16),
+ element(2, G1) =:= element(1, G2)
+ ],
+ [{join,lookup}]),
+ qlc:q([
+ {X,Z,W} ||
+ [{X,Z}|{W,Y}] &lt;- V2
+ ])
+end</pre>
+
+ <p>In this example the query list comprehension <c>V2</c> has
+ been inserted to show the joined generators and the join
+ method chosen. A convention is used for lookup join: the
+ first generator (<c>G2</c>) is the one traversed, the second
+ one (<c>G1</c>) is the table where constants are looked up.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>keysort(KeyPos, QH1 [, SortOptions]) -> QH2</name>
+ <fsummary>Return a query handle.</fsummary>
+ <type>
+ <v>QH1 = QueryHandleOrList</v>
+ <v>QH2 = QueryHandle</v>
+ </type>
+ <desc>
+ <p>Returns a query handle. When evaluating the query handle
+ <c>QH2</c> the answers to the query handle <c>QH1</c> are
+ sorted by <seealso
+ marker="file_sorter">file_sorter:keysort/4</seealso>
+ according to the options.</p>
+
+ <p>The sorter will use temporary files only if <c>QH1</c> does
+ not evaluate to a list and the size of the binary
+ representation of the answers exceeds <c>Size</c> bytes,
+ where <c>Size</c> is the value of the <c>size</c> option.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>next_answers(QueryCursor [, NumberOfAnswers]) ->
+ Answers | Error</name>
+ <fsummary>Return some or all answers to a query.</fsummary>
+ <type>
+ <v>NumberOfAnswers = all_remaining | int() > 0</v>
+ <v>Error = {error, module(), Reason}</v>
+ <v>Reason =&nbsp;-&nbsp;as returned by file_sorter(3)&nbsp;-</v>
+ </type>
+ <desc>
+ <p>Returns some or all of the remaining answers to a query
+ cursor. Only the owner of <c>Cursor</c> can retrieve
+ answers.</p>
+
+ <p>The optional argument <c>NumberOfAnswers</c>determines the
+ maximum number of answers returned. The default value is
+ <c>10</c>. If less than the requested number of answers is
+ returned, subsequent calls to <c>next_answers</c> will
+ return <c>[]</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>q(QueryListComprehension [, Options]) -> QueryHandle</name>
+ <fsummary>Return a handle for a query list comprehension.</fsummary>
+ <type>
+ <v>QueryListComprehension =&nbsp;
+ -&nbsp;literal query listcomprehension&nbsp;-</v>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {max_lookup, MaxLookup}
+ | {cache, Cache} | cache
+ | {join, Join}
+ | {lookup, Lookup}
+ | {unique, bool()} | unique</v>
+ <v>MaxLookup = int() >= 0 | infinity</v>
+ <v>Join = any | lookup | merge | nested_loop</v>
+ <v>Lookup = bool() | any</v>
+ </type>
+ <desc>
+ <p><marker id="q"></marker>Returns a query handle for a query
+ list comprehension. The query list comprehension must be the
+ first argument to <c>qlc:q/1,2</c> or it will be evaluated
+ as an ordinary list comprehension. It is also necessary to
+ add the line</p>
+
+ <code type="none">
+-include_lib("stdlib/include/qlc.hrl").</code>
+
+ <p>to the source file. This causes a parse transform to
+ substitute a fun for the query list comprehension. The
+ (compiled) fun will be called when the query handle is
+ evaluated.</p>
+
+ <p>When calling <c>qlc:q/1,2</c> from the Erlang shell the
+ parse transform is automatically called. When this happens
+ the fun substituted for the query list comprehension is not
+ compiled but will be evaluated by <c>erl_eval(3)</c>. This
+ is also true when expressions are evaluated by means of
+ <c>file:eval/1,2</c> or in the debugger.</p>
+
+ <p>To be very explicit, this will not work:</p>
+
+ <pre>
+...
+A = [X || {X} &lt;- [{1},{2}]],
+QH = qlc:q(A),
+...</pre>
+
+ <p>The variable <c>A</c> will be bound to the evaluated value
+ of the list comprehension (<c>[1,2]</c>). The compiler
+ complains with an error message ("argument is not a query
+ list comprehension"); the shell process stops with a
+ <c>badarg</c> reason.</p>
+
+ <p>The <c>{cache,&nbsp;ets}</c> option can be used to cache
+ the answers to a query list comprehension. The answers are
+ stored in one ETS table for each cached query list
+ comprehension. When a cached query list comprehension is
+ evaluated again, answers are fetched from the table without
+ any further computations. As a consequence, when all answers
+ to a cached query list comprehension have been found, the
+ ETS tables used for caching answers to the query list
+ comprehension's qualifiers can be emptied. The option
+ <c>cache</c> is equivalent to <c>{cache,&nbsp;ets}</c>.</p>
+
+ <p>The <c>{cache,&nbsp;list}</c> option can be used to cache
+ the answers to a query list comprehension just like
+ <c>{cache,&nbsp;ets}</c>. The difference is that the answers
+ are kept in a list (on the process heap). If the answers
+ would occupy more than a certain amount of RAM memory a
+ temporary file is used for storing the answers. The option
+ <c>max_list_size</c> sets the limit in bytes and the temporary
+ file is put on the directory set by the <c>tmpdir</c> option.</p>
+
+ <p>The <c>cache</c> option has no effect if it is known that
+ the query list comprehension will be evaluated at most once.
+ This is always true for the top-most query list
+ comprehension and also for the list expression of the first
+ generator in a list of qualifiers. Note that in the presence
+ of side effects in filters or callback functions the answers
+ to query list comprehensions can be affected by the
+ <c>cache</c> option.</p>
+
+ <p>The <c>{unique,&nbsp;true}</c> option can be used to remove
+ duplicate answers to a query list comprehension. The unique
+ answers are stored in one ETS table for each query list
+ comprehension. The table is emptied every time it is known
+ that there are no more answers to the query list
+ comprehension. The option <c>unique</c> is equivalent to
+ <c>{unique,&nbsp;true}</c>. If the <c>unique</c> option is
+ combined with the <c>{cache,&nbsp;ets}</c> option, two ETS
+ tables are used, but the full answers are stored in one
+ table only. If the <c>unique</c> option is combined with the
+ <c>{cache,&nbsp;list}</c> option the answers are sorted
+ twice using <c>keysort/3</c>; once to remove duplicates, and
+ once to restore the order.</p>
+
+ <p>The <c>cache</c> and <c>unique</c> options apply not only
+ to the query list comprehension itself but also to the
+ results of looking up constants, running match
+ specifications, and joining handles. </p>
+
+ <pre>
+1> <input>Q = qlc:q([{A,X,Z,W} ||</input>
+<input>A &lt;- [a,b,c],</input>
+<input>{X,Z} &lt;- [{a,1},{b,4},{c,6}],</input>
+<input>{W,Y} &lt;- [{2,a},{3,b},{4,c}],</input>
+<input>X =:= Y],</input>
+<input>{cache, list}),</input>
+<input>io:format("~s~n", [qlc:info(Q)]).</input>
+begin
+ V1 =
+ qlc:q([
+ P0 ||
+ P0 = {X,Z} &lt;-
+ qlc:keysort(1, [{a,1},{b,4},{c,6}], [])
+ ]),
+ V2 =
+ qlc:q([
+ P0 ||
+ P0 = {W,Y} &lt;-
+ qlc:keysort(2, [{2,a},{3,b},{4,c}], [])
+ ]),
+ V3 =
+ qlc:q([
+ [G1|G2] ||
+ G1 &lt;- V1,
+ G2 &lt;- V2,
+ element(1, G1) == element(2, G2)
+ ],
+ [{join,merge},{cache,list}]),
+ qlc:q([
+ {A,X,Z,W} ||
+ A &lt;- [a,b,c],
+ [{X,Z}|{W,Y}] &lt;- V3,
+ X =:= Y
+ ])
+end</pre>
+
+ <p>In this example the cached results of the merge join are
+ traversed for each value of <c>A</c>. Note that without the
+ <c>cache</c> option the join would have been carried out
+ three times, once for each value of <c>A</c></p>
+
+ <p><c>sort/1,2</c> and <c>keysort/2,3</c> can also be used for
+ caching answers and for removing duplicates. When sorting
+ answers are cached in a list, possibly stored on a temporary
+ file, and no ETS tables are used.</p>
+
+ <p>Sometimes (see <seealso
+ marker="#lookup_fun">qlc:table/2</seealso> below) traversal
+ of tables can be done by looking up key values, which is
+ assumed to be fast. Under certain (rare) circumstances it
+ could happen that there are too many key values to look up.
+ <marker id="max_lookup"></marker> The
+ <c>{max_lookup,&nbsp;MaxLookup}</c> option can then be used
+ to limit the number of lookups: if more than
+ <c>MaxLookup</c> lookups would be required no lookups are
+ done but the table traversed instead. The default value is
+ <c>infinity</c> which means that there is no limit on the
+ number of keys to look up.</p>
+ <pre>
+1> <input>T = gb_trees:empty(),</input>
+<input>QH = qlc:q([X || {{X,Y},_} &lt;- gb_table:table(T),</input>
+<input>((X == 1) or (X == 2)) andalso</input>
+<input>((Y == a) or (Y == b) or (Y == c))]),</input>
+<input>io:format("~s~n", [qlc:info(QH)]).</input>
+ets:match_spec_run(
+ lists:flatmap(fun(K) ->
+ case
+ gb_trees:lookup(K,
+ gb_trees:from_orddict([]))
+ of
+ {value,V} ->
+ [{K,V}];
+ none ->
+ []
+ end
+ end,
+ [{1,a},{1,b},{1,c},{2,a},{2,b},{2,c}]),
+ ets:match_spec_compile([{{{'$1','$2'},'_'},[],['$1']}]))</pre>
+
+ <p>In this example using the <c>gb_table</c> module from the
+ <seealso marker="#implementing_a_qlc_table">Implementing a
+ QLC table</seealso> section there are six keys to look up:
+ <c>{1,a}</c>, <c>{1,b}</c>, <c>{1,c}</c>, <c>{2,a}</c>,
+ <c>{2,b}</c>, and <c>{2,c}</c>. The reason is that the two
+ elements of the key {X,&nbsp;Y} are compared separately.</p>
+
+ <p>The <c>{lookup,&nbsp;true}</c> option can be used to ensure
+ that the <c>qlc</c> module will look up constants in some
+ QLC table. If there
+ are more than one QLC table among the generators' list
+ expressions, constants have to be looked up in at least one
+ of the tables. The evaluation of the query fails if there
+ are no constants to look up. This option is useful in
+ situations when it would be unacceptable to traverse all
+ objects in some table. Setting the <c>lookup</c> option to
+ <c>false</c> ensures that no constants will be looked up
+ (<c>{max_lookup,&nbsp;0}</c> has the same effect). The
+ default value is <c>any</c> which means that constants will
+ be looked up whenever possible.</p>
+
+ <p>The <c>{join,&nbsp;Join}</c> option can be used to ensure
+ that a certain join method will be used:
+ <c>{join,&nbsp;lookup}</c> invokes the lookup join method;
+ <c>{join,&nbsp;merge}</c> invokes the merge join method; and
+ <c>{join,&nbsp;nested_loop}</c> invokes the method of
+ matching every pair of objects from two handles. The last
+ method is mostly very slow. The evaluation of the query
+ fails if the <c>qlc</c> module cannot carry out the chosen
+ join method. The
+ default value is <c>any</c> which means that some fast join
+ method will be used if possible.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>sort(QH1 [, SortOptions]) -> QH2</name>
+ <fsummary>Return a query handle.</fsummary>
+ <type>
+ <v>QH1 = QueryHandleOrList</v>
+ <v>QH2 = QueryHandle</v>
+ </type>
+ <desc>
+ <p>Returns a query handle. When evaluating the query handle
+ <c>QH2</c> the answers to the query handle <c>QH1</c> are
+ sorted by <seealso
+ marker="file_sorter">file_sorter:sort/3</seealso> according
+ to the options.</p>
+
+ <p>The sorter will use temporary files only if <c>QH1</c> does
+ not evaluate to a list and the size of the binary
+ representation of the answers exceeds <c>Size</c> bytes,
+ where <c>Size</c> is the value of the <c>size</c> option.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>string_to_handle(QueryString [, Options [, Bindings]]) ->
+ QueryHandle | Error</name>
+ <fsummary>Return a handle for a query list comprehension.</fsummary>
+ <type>
+ <v>QueryString = string()</v>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {max_lookup, MaxLookup}
+ | {cache, Cache} | cache
+ | {join, Join}
+ | {lookup, Lookup}
+ | {unique, bool()} | unique</v>
+ <v>MaxLookup = int() >= 0 | infinity</v>
+ <v>Join = any | lookup | merge | nested_loop</v>
+ <v>Lookup = bool() | any</v>
+ <v>Bindings =&nbsp;-&nbsp;as returned by
+ erl_eval:bindings/1&nbsp;-</v>
+ <v>Error = {error, module(), Reason}</v>
+ <v>Reason = &nbsp;-&nbsp;ErrorInfo as returned by
+ erl_scan:string/1 or erl_parse:parse_exprs/1&nbsp;-</v>
+ </type>
+ <desc>
+ <p>A string version of <c>qlc:q/1,2</c>. When the query handle
+ is evaluated the fun created by the parse transform is
+ interpreted by <c>erl_eval(3)</c>. The query string is to be
+ one single query list comprehension terminated by a
+ period.</p>
+
+ <pre>
+1> <input>L = [1,2,3],</input>
+<input>Bs = erl_eval:add_binding('L', L, erl_eval:new_bindings()),</input>
+<input>QH = qlc:string_to_handle("[X+1 || X &lt;- L].", [], Bs),</input>
+<input>qlc:eval(QH).</input>
+[2,3,4]</pre>
+
+ <p>This function is probably useful mostly when called from
+ outside of Erlang, for instance from a driver written in C.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>table(TraverseFun, Options) -> QueryHandle</name>
+ <fsummary>Return a query handle for a table.</fsummary>
+ <type>
+ <v>TraverseFun = TraverseFun0 | TraverseFun1</v>
+ <v>TraverseFun0 = fun() -> TraverseResult</v>
+ <v>TraverseFun1 = fun(MatchExpression) -> TraverseResult</v>
+ <v>TraverseResult = Objects | term()</v>
+ <v>Objects = [] | [term() | ObjectList]</v>
+ <v>ObjectList = TraverseFun0 | Objects</v>
+ <v>Options = [Option] | Option</v>
+ <v>Option = {format_fun, FormatFun}
+ | {info_fun, InfoFun}
+ | {lookup_fun, LookupFun}
+ | {parent_fun, ParentFun}
+ | {post_fun, PostFun}
+ | {pre_fun, PreFun}
+ | {key_equality, KeyComparison}</v>
+ <v>FormatFun = undefined | fun(SelectedObjects) -> FormatedTable</v>
+ <v>SelectedObjects = all
+ | {all, NElements, DepthFun}
+ | {match_spec, MatchExpression}
+ | {lookup, Position, Keys}
+ | {lookup, Position, Keys, NElements, DepthFun}</v>
+ <v>NElements = infinity | int() > 0</v>
+ <v>DepthFun = fun(term()) -> term()</v>
+ <v>FormatedTable = {Mod, Fun, Args}
+ | AbstractExpression
+ | character_list()</v>
+ <v>InfoFun = undefined | fun(InfoTag) -> InfoValue</v>
+ <v>InfoTag = indices | is_unique_objects | keypos | num_of_objects</v>
+ <v>InfoValue = undefined | term()</v>
+ <v>LookupFun = undefined | fun(Position, Keys) -> LookupResult</v>
+ <v>LookupResult = [term()] | term()</v>
+ <v>ParentFun = undefined | fun() -> ParentFunValue</v>
+ <v>PostFun = undefined | fun() -> void()</v>
+ <v>PreFun = undefined | fun([PreArg]) -> void()</v>
+ <v>PreArg = {parent_value, ParentFunValue} | {stop_fun, StopFun}</v>
+ <v>ParentFunValue = undefined | term()</v>
+ <v>StopFun = undefined | fun() -> void()</v>
+ <v>KeyComparison = '=:=' | '=='</v>
+ <v>Position = int() > 0</v>
+ <v>Keys = [term()]</v>
+ <v>Mod = Fun = atom()</v>
+ <v>Args = [term()]</v>
+ </type>
+ <desc>
+ <p><marker id="table"></marker>Returns a query handle for a
+ QLC table. In Erlang/OTP there is support for ETS, Dets and
+ Mnesia tables, but it is also possible to turn many other
+ data structures into QLC tables. The way to accomplish this
+ is to let function(s) in the module implementing the data
+ structure create a query handle by calling
+ <c>qlc:table/2</c>. The different ways to traverse the table
+ as well as properties of the table are handled by callback
+ functions provided as options to <c>qlc:table/2</c>.</p>
+
+ <p>The callback function <c>TraverseFun</c> is used for
+ traversing the table. It is to return a list of objects
+ terminated by either <c>[]</c> or a nullary fun to be used
+ for traversing the not yet traversed objects of the table.
+ Any other return value is immediately returned as value of
+ the query evaluation. Unary <c>TraverseFun</c>s are to
+ accept a match specification as argument. The match
+ specification is created by the parse transform by analyzing
+ the pattern of the generator calling <c>qlc:table/2</c> and
+ filters using variables introduced in the pattern. If the
+ parse transform cannot find a match specification equivalent
+ to the pattern and filters, <c>TraverseFun</c> will be
+ called with a match specification returning every object.
+ Modules that can utilize match specifications for optimized
+ traversal of tables should call <c>qlc:table/2</c> with a
+ unary <c>TraverseFun</c> while other modules can provide a
+ nullary <c>TraverseFun</c>. <c>ets:table/2</c> is an example
+ of the former; <c>gb_table:table/1</c> in the <seealso
+ marker="#implementing_a_qlc_table">Implementing a QLC
+ table</seealso> section is an example of the latter.</p>
+
+ <p><c>PreFun</c> is a unary callback function that is called
+ once before the table is read for the first time. If the
+ call fails, the query evaluation fails. Similarly, the
+ nullary callback function <c>PostFun</c> is called once
+ after the table was last read. The return value, which is
+ caught, is ignored. If <c>PreFun</c> has been called for a
+ table, <c>PostFun</c> is guaranteed to be called for that
+ table, even if the evaluation of the query fails for some
+ reason. The order in which pre (post) functions for
+ different tables are evaluated is not specified. Other table
+ access than reading, such as calling <c>InfoFun</c>, is
+ assumed to be OK at any time. The argument <c>PreArgs</c> is
+ a list of tagged values. Currently there are two tags,
+ <c>parent_value</c> and <c>stop_fun</c>, used by Mnesia for
+ managing transactions. The value of <c>parent_value</c> is
+ the value returned by <c>ParentFun</c>, or <c>undefined</c>
+ if there is no <c>ParentFun</c>. <c>ParentFun</c> is called
+ once just before the call of <c>PreFun</c> in the context of
+ the process calling <c>eval</c>, <c>fold</c>, or
+ <c>cursor</c>. The value of <c>stop_fun</c> is a nullary fun
+ that deletes the cursor if called from the parent, or
+ <c>undefined</c> if there is no cursor.</p>
+
+ <p><marker id="lookup_fun"></marker>The binary callback
+ function <c>LookupFun</c> is used for looking up objects in
+ the table. The first argument <c>Position</c> is the key
+ position or an indexed position and the second argument
+ <c>Keys</c> is a sorted list of unique values. The return
+ value is to be a list of all objects (tuples) such that the
+ element at <c>Position</c> is a member of <c>Keys</c>. Any
+ other return value is immediately returned as value of the
+ query evaluation. <c>LookupFun</c> is called instead of
+ traversing the table if the parse transform at compile time
+ can find out that the filters match and compare the element
+ at <c>Position</c> in such a way that only <c>Keys</c> need
+ to be looked up in order to find all potential answers. The
+ key position is obtained by calling <c>InfoFun(keypos)</c>
+ and the indexed positions by calling
+ <c>InfoFun(indices)</c>. If the key position can be used for
+ lookup it is always chosen, otherwise the indexed position
+ requiring the least number of lookups is chosen. If there is
+ a tie between two indexed positions the one occurring first
+ in the list returned by <c>InfoFun</c> is chosen. Positions
+ requiring more than <seealso
+ marker="#max_lookup">max_lookup</seealso> lookups are
+ ignored.</p>
+
+ <p>The unary callback function <c>InfoFun</c> is to return
+ information about the table. <c>undefined</c> should be
+ returned if the value of some tag is unknown:</p>
+
+ <list type="bulleted">
+ <item><c>indices</c>. Returns a list of indexed
+ positions, a list of positive integers.
+ </item>
+ <item><c>is_unique_objects</c>. Returns <c>true</c> if
+ the objects returned by <c>TraverseFun</c> are unique.
+ </item>
+ <item><c>keypos</c>. Returns the position of the table's
+ key, a positive integer.
+ </item>
+ <item><c>is_sorted_key</c>. Returns <c>true</c> if
+ the objects returned by <c>TraverseFun</c> are sorted
+ on the key.
+ </item>
+ <item><c>num_of_objects</c>. Returns the number of
+ objects in the table, a non-negative integer.
+ </item>
+ </list>
+
+ <p>The unary callback function <c>FormatFun</c> is used by
+ <seealso marker="#info">qlc:info/1,2</seealso> for
+ displaying the call that created the table's query handle.
+ The default value, <c>undefined</c>, means that
+ <c>info/1,2</c> displays a call to <c>'$MOD':'$FUN'/0</c>.
+ It is up to <c>FormatFun</c> to present the selected objects
+ of the table in a suitable way. However, if a character list
+ is chosen for presentation it must be an Erlang expression
+ that can be scanned and parsed (a trailing dot will be added
+ by <c>qlc:info</c> though). <c>FormatFun</c> is called with
+ an argument that describes the selected objects based on
+ optimizations done as a result of analyzing the filters of
+ the QLC where the call to <c>qlc:table/2</c> occurs. The
+ possible values of the argument are:</p>
+
+ <list type="bulleted">
+ <item><c>{lookup, Position, Keys, NElements, DepthFun}</c>.
+ <c>LookupFun</c> is used for looking up objects in the
+ table.
+ </item>
+ <item><c>{match_spec, MatchExpression}</c>. No way of
+ finding all possible answers by looking up keys was
+ found, but the filters could be transformed into a
+ match specification. All answers are found by calling
+ <c>TraverseFun(MatchExpression)</c>.
+ </item>
+ <item><c>{all, NElements, DepthFun}</c>. No optimization was
+ found. A match specification matching all objects will be
+ used if <c>TraverseFun</c> is unary.
+ </item>
+ </list>
+
+ <p><c>NElements</c> is the value of the <c>info/1,2</c> option
+ <c>n_elements</c>, and <c>DepthFun</c> is a function that
+ can be used for limiting the size of terms; calling
+ <c>DepthFun(Term)</c> substitutes <c>'...'</c> for parts of
+ <c>Term</c> below the depth specified by the <c>info/1,2</c>
+ option <c>depth</c>. If calling <c>FormatFun</c> with an
+ argument including <c>NElements</c> and <c>DepthFun</c>
+ fails, <c>FormatFun</c> is called once again with an
+ argument excluding <c>NElements</c> and <c>DepthFun</c>
+ (<c>{lookup,&nbsp;Position,&nbsp;Keys}</c> or
+ <c>all</c>).</p>
+
+ <p><marker id="key_equality"></marker>The value of
+ <c>key_equality</c> is to be <c>'=:='</c> if the table
+ considers two keys equal if they match, and to be
+ <c>'=='</c> if two keys are equal if they compare equal. The
+ default is <c>'=:='</c>.</p>
+
+ <p>See <seealso marker="ets#qlc_table">ets(3)</seealso>,
+ <seealso marker="dets#qlc_table">dets(3)</seealso> and
+ <seealso marker="mnesia:mnesia#qlc_table">mnesia(3)</seealso>
+ for the various options recognized by <c>table/1,2</c> in
+ respective module.</p>
+ </desc>
+ </func>
+
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="dets">dets(3)</seealso>,
+ <seealso marker="doc/reference_manual:users_guide">
+ Erlang Reference Manual</seealso>,
+ <seealso marker="erl_eval">erl_eval(3)</seealso>,
+ <seealso marker="erts:erlang">erlang(3)</seealso>,
+ <seealso marker="ets">ets(3)</seealso>,
+ <seealso marker="kernel:file">file(3)</seealso>,
+ <seealso marker="error_logger:file">error_logger(3)</seealso>,
+ <seealso marker="file_sorter">file_sorter(3)</seealso>,
+ <seealso marker="mnesia:mnesia">mnesia(3)</seealso>,
+ <seealso marker="doc/programming_examples:users_guide">
+ Programming Examples</seealso>,
+ <seealso marker="shell">shell(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/queue.xml b/lib/stdlib/doc/src/queue.xml
new file mode 100644
index 0000000000..5ada1c2c57
--- /dev/null
+++ b/lib/stdlib/doc/src/queue.xml
@@ -0,0 +1,454 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>queue</title>
+ <prepared>Joe</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-15</date>
+ <rev>B</rev>
+ <file>queue.sgml</file>
+ </header>
+ <module>queue</module>
+ <modulesummary>Abstract Data Type for FIFO Queues</modulesummary>
+ <description>
+ <p>This module implements (double ended) FIFO queues
+ in an efficient manner.</p>
+ <p>All functions fail with reason <c>badarg</c> if arguments
+ are of wrong type, for example queue arguments are not
+ queues, indexes are not integers, list arguments are
+ not lists. Improper lists cause internal crashes.
+ An index out of range for a queue also causes
+ a failure with reason <c>badarg</c>.</p>
+ <p>Some functions, where noted, fail with reason <c>empty</c>
+ for an empty queue.</p>
+ <p>The data representing a queue as used by this module
+ should be regarded as opaque by other modules. Any code
+ assuming knowledge of the format is running on thin ice.</p>
+ <p>All operations has an amortized O(1) running time, except
+ <c>len/1</c>, <c>join/2</c>, <c>split/2</c>, <c>filter/2</c>
+ and <c>member/2</c> that have O(n).
+ To minimize the size of a queue minimizing
+ the amount of garbage built by queue operations, the queues
+ do not contain explicit length information, and that is
+ why <c>len/1</c> is O(n). If better performance for this
+ particular operation is essential, it is easy for
+ the caller to keep track of the length.</p>
+ <p>Queues are double ended. The mental picture of
+ a queue is a line of people (items) waiting for
+ their turn. The queue front is the end with the item
+ that has waited the longest. The queue rear is the end
+ an item enters when it starts to wait. If instead using
+ the mental picture of a list, the front is called head
+ and the rear is called tail.</p>
+ <p>Entering at the front and exiting at the rear
+ are reverse operations on the queue.</p>
+ <p>The module has several sets of interface functions. The
+ "Original API", the "Extended API" and the "Okasaki API".</p>
+ <p>The "Original API" and the "Extended API" both use the
+ mental picture of a waiting line of items. Both also
+ have reverse operations suffixed "_r".</p>
+ <p>The "Original API" item removal functions return compound
+ terms with both the removed item and the resulting queue.
+ The "Extended API" contain alternative functions that build
+ less garbage as well as functions for just inspecting the
+ queue ends. Also the "Okasaki API" functions build less garbage.</p>
+ <p>The "Okasaki API" is inspired by "Purely Functional Data structures"
+ by Chris Okasaki. It regards queues as lists.
+ The API is by many regarded as strange and avoidable.
+ For example many reverse operations have lexically reversed names,
+ some with more readable but perhaps less understandable aliases.</p>
+ </description>
+
+
+
+ <section>
+ <title>Original API</title>
+ </section>
+
+ <funcs>
+ <func>
+ <name>new() -> Q</name>
+ <fsummary>Create an empty queue</fsummary>
+ <type>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns an empty queue.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_queue(Term) -> true | false</name>
+ <fsummary>Test if a term is a queue</fsummary>
+ <type>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Tests if <c>Q</c> is a queue and returns <c>true</c> if so and
+ <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_empty(Q) -> true | false</name>
+ <fsummary>Test if a queue is empty</fsummary>
+ <type>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Tests if <c>Q</c> is empty and returns <c>true</c> if so and
+ <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>len(Q) -> N</name>
+ <fsummary>Get the length of a queue</fsummary>
+ <type>
+ <v>Q = queue()</v>
+ <v>N = integer()</v>
+ </type>
+ <desc>
+ <p>Calculates and returns the length of queue <c>Q</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>in(Item, Q1) -> Q2</name>
+ <fsummary>Insert an item at the rear of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Inserts <c>Item</c> at the rear of queue <c>Q1</c>.
+ Returns the resulting queue <c>Q2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>in_r(Item, Q1) -> Q2</name>
+ <fsummary>Insert an item at the front of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Inserts <c>Item</c> at the front of queue <c>Q1</c>.
+ Returns the resulting queue <c>Q2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>out(Q1) -> Result</name>
+ <fsummary>Remove the front item from a queue</fsummary>
+ <type>
+ <v>Result = {{value, Item}, Q2} | {empty, Q1}</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Removes the item at the front of queue <c>Q1</c>. Returns the
+ tuple <c>{{value, Item}, Q2}</c>, where <c>Item</c> is the
+ item removed and <c>Q2</c> is the resulting queue. If <c>Q1</c> is
+ empty, the tuple <c>{empty, Q1}</c> is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>out_r(Q1) -> Result</name>
+ <fsummary>Remove the rear item from a queue</fsummary>
+ <type>
+ <v>Result = {{value, Item}, Q2} | {empty, Q1}</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Removes the item at the rear of the queue <c>Q1</c>. Returns the
+ tuple <c>{{value, Item}, Q2}</c>, where <c>Item</c> is the
+ item removed and <c>Q2</c> is the new queue. If <c>Q1</c> is
+ empty, the tuple <c>{empty, Q1}</c> is returned. </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>from_list(L) -> queue()</name>
+ <fsummary>Convert a list to a queue</fsummary>
+ <type>
+ <v>L = list()</v>
+ </type>
+ <desc>
+ <p>Returns a queue containing the items in <c>L</c> in the
+ same order; the head item of the list will become the front
+ item of the queue.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_list(Q) -> list()</name>
+ <fsummary>Convert a queue to a list</fsummary>
+ <type>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a list of the items in the queue in the same order;
+ the front item of the queue will become the head of the list.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>reverse(Q1) -> Q2</name>
+ <fsummary>Reverse a queue</fsummary>
+ <type>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a queue <c>Q2</c> that contains the items of
+ <c>Q1</c> in the reverse order.</p>
+ </desc>
+ </func>
+ <func>
+ <name>split(N, Q1) -> {Q2,Q3}</name>
+ <fsummary>Split a queue in two</fsummary>
+ <type>
+ <v>N = integer()</v>
+ <v>Q1 = Q2 = Q3 = queue()</v>
+ </type>
+ <desc>
+ <p>Splits <c>Q1</c> in two. The <c>N</c> front items
+ are put in <c>Q2</c> and the rest in <c>Q3</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>join(Q1, Q2) -> Q3</name>
+ <fsummary>Join two queues</fsummary>
+ <type>
+ <v>Q1 = Q2 = Q3 = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a queue <c>Q3</c> that is the result of joining
+ <c>Q1</c> and <c>Q2</c> with <c>Q1</c> in front of
+ <c>Q2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>filter(Fun, Q1) -> Q2</name>
+ <fsummary>Filter a queue</fsummary>
+ <type>
+ <v>Fun = fun(Item) -> bool() | list()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a queue <c>Q2</c> that is the result of calling
+ <c>Fun(Item)</c> on all items in <c>Q1</c>,
+ in order from front to rear.</p>
+ <p>If <c>Fun(Item)</c> returns <c>true</c>, <c>Item</c>
+ is copied to the result queue. If it returns <c>false</c>,
+ <c>Item</c> is not copied. If it returns a list
+ the list elements are inserted instead of <c>Item</c> in the
+ result queue.</p>
+ <p>So, <c>Fun(Item)</c> returning <c>[Item]</c> is thereby
+ semantically equivalent to returning <c>true</c>, just
+ as returning <c>[]</c> is semantically equivalent to
+ returning <c>false</c>. But returning a list builds
+ more garbage than returning an atom.</p>
+ </desc>
+ </func>
+ <func>
+ <name>member(Item, Q) -> bool()</name>
+ <fsummary>Test if an item is in a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Item</c> matches some element
+ in <c>Q</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+
+
+ <section>
+ <title>Extended API</title>
+ </section>
+
+ <funcs>
+ <func>
+ <name>get(Q) -> Item</name>
+ <fsummary>Return the front item of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns <c>Item</c> at the front of queue <c>Q</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_r(Q) -> Item</name>
+ <fsummary>Return the rear item of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns <c>Item</c> at the rear of queue <c>Q</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>drop(Q1) -> Q2</name>
+ <fsummary>Remove the front item from a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a queue <c>Q2</c> that is the result of removing
+ the front item from <c>Q1</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q1</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>drop_r(Q1) -> Q2</name>
+ <fsummary>Remove the rear item from a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a queue <c>Q2</c> that is the result of removing
+ the rear item from <c>Q1</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q1</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>peek(Q) -> {value,Item} | empty</name>
+ <fsummary>Return the front item of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns the tuple <c>{value, Item}</c> where <c>Item</c> is the
+ front item of <c>Q</c>, or <c>empty</c> if <c>Q1</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>peek_r(Q) -> {value,Item} | empty</name>
+ <fsummary>Return the rear item of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns the tuple <c>{value, Item}</c> where <c>Item</c> is the
+ rear item of <c>Q</c>, or <c>empty</c> if <c>Q1</c> is empty.</p>
+ </desc>
+ </func>
+ </funcs>
+
+
+ <section>
+ <title>Okasaki API</title>
+ </section>
+
+ <funcs>
+ <func>
+ <name>cons(Item, Q1) -> Q2</name>
+ <fsummary>Insert an item at the head of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Inserts <c>Item</c> at the head of queue <c>Q1</c>. Returns
+ the new queue <c>Q2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>head(Q) -> Item</name>
+ <fsummary>Return the item at the head of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns <c>Item</c> from the head of queue <c>Q</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>tail(Q1) -> Q2</name>
+ <fsummary>Remove the head item from a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a queue <c>Q2</c> that is the result of removing
+ the head item from <c>Q1</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q1</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>snoc(Q1, Item) -> Q2</name>
+ <fsummary>Insert an item at the tail of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Inserts <c>Item</c> as the tail item of queue <c>Q1</c>. Returns
+ the new queue <c>Q2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>daeh(Q) -> Item</name>
+ <name>last(Q) -> Item</name>
+ <fsummary>Return the tail item of a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q = queue()</v>
+ </type>
+ <desc>
+ <p>Returns the tail item of queue <c>Q</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q</c> is empty.</p>
+ </desc>
+ </func>
+ <func>
+ <name>liat(Q1) -> Q2</name>
+ <name>init(Q1) -> Q2</name>
+ <name>lait(Q1) -> Q2</name>
+ <fsummary>Remove the tail item from a queue</fsummary>
+ <type>
+ <v>Item = term()</v>
+ <v>Q1 = Q2 = queue()</v>
+ </type>
+ <desc>
+ <p>Returns a queue <c>Q2</c> that is the result of removing
+ the tail item from <c>Q1</c>.</p>
+ <p>Fails with reason <c>empty</c> if <c>Q1</c> is empty.</p>
+ <p>The name <c>lait/1</c> is a misspelling - do not use it anymore.</p>
+ </desc>
+ </func>
+ </funcs>
+
+</erlref>
diff --git a/lib/stdlib/doc/src/random.xml b/lib/stdlib/doc/src/random.xml
new file mode 100644
index 0000000000..dcc6d756e1
--- /dev/null
+++ b/lib/stdlib/doc/src/random.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>random</title>
+ <prepared>Joe Armstrong</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>96-09-09</date>
+ <rev>A</rev>
+ <file>random.sgml</file>
+ </header>
+ <module>random</module>
+ <modulesummary>Pseudo random number generation</modulesummary>
+ <description>
+ <p>Random number generator. The method is attributed to
+ B.A. Wichmann and I.D.Hill, in 'An efficient and portable
+ pseudo-random number generator', Journal of Applied
+ Statistics. AS183. 1982. Also Byte March 1987. </p>
+ <p>The current algorithm is a modification of the version attributed
+ to Richard A O'Keefe in the standard Prolog library.</p>
+ <p>Every time a random number is requested, a state is used to calculate
+ it, and a new state produced. The state can either be implicit (kept
+ in the process dictionary) or be an explicit argument and return value.
+ In this implementation, the state (the type <c>ran()</c>) consists of a
+ tuple of three integers.</p>
+ <p>It should be noted that this random number generator is not cryptographically
+ strong. If a strong cryptographic random number generator is needed for
+ example <c>crypto:rand_bytes/1</c> could be used instead.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>seed() -> ran()</name>
+ <fsummary>Seeds random number generation with default values</fsummary>
+ <desc>
+ <p>Seeds random number generation with default (fixed) values
+ in the process dictionary, and returns the old state.</p>
+ </desc>
+ </func>
+ <func>
+ <name>seed(A1, A2, A3) -> undefined | ran()</name>
+ <fsummary>Seeds random number generator</fsummary>
+ <type>
+ <v>A1 = A2 = A3 = integer()</v>
+ </type>
+ <desc>
+ <p>Seeds random number generation with integer values in the process
+ dictionary, and returns the old state.</p>
+ <p>One way of obtaining a seed is to use the BIF <c>now/0</c>:</p>
+ <code type="none">
+ ...
+ {A1,A2,A3} = now(),
+ random:seed(A1, A2, A3),
+ ...</code>
+ </desc>
+ </func>
+ <func>
+ <name>seed({A1, A2, A3}) -> undefined | ran()</name>
+ <fsummary>Seeds random number generator</fsummary>
+ <type>
+ <v>A1 = A2 = A3 = integer()</v>
+ </type>
+ <desc>
+ <p>
+ <c>seed({A1, A2, A3})</c> is equivalent to <c>seed(A1, A2, A3)</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>seed0() -> ran()</name>
+ <fsummary>Return default state for random number generation</fsummary>
+ <desc>
+ <p>Returns the default state.</p>
+ </desc>
+ </func>
+ <func>
+ <name>uniform()-> float()</name>
+ <fsummary>Return a random float</fsummary>
+ <desc>
+ <p>Returns a random float uniformly distributed between <c>0.0</c>
+ and <c>1.0</c>, updating the state in the process dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>uniform(N) -> integer()</name>
+ <fsummary>Return a random integer</fsummary>
+ <type>
+ <v>N = integer()</v>
+ </type>
+ <desc>
+ <p>Given an integer <c>N >= 1</c>, <c>uniform/1</c> returns a
+ random integer uniformly distributed between <c>1</c> and
+ <c>N</c>, updating the state in the process dictionary.</p>
+ </desc>
+ </func>
+ <func>
+ <name>uniform_s(State0) -> {float(), State1}</name>
+ <fsummary>Return a random float</fsummary>
+ <type>
+ <v>State0 = State1 = ran()</v>
+ </type>
+ <desc>
+ <p>Given a state, <c>uniform_s/1</c>returns a random float uniformly
+ distributed between <c>0.0</c> and <c>1.0</c>, and a new state.</p>
+ </desc>
+ </func>
+ <func>
+ <name>uniform_s(N, State0) -> {integer(), State1}</name>
+ <fsummary>Return a random integer</fsummary>
+ <type>
+ <v>N = integer()</v>
+ <v>State0 = State1 = ran()</v>
+ </type>
+ <desc>
+ <p>Given an integer <c>N >= 1</c> and a state, <c>uniform_s/2</c>
+ returns a random integer uniformly distributed between <c>1</c> and
+ <c>N</c>, and a new state.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Note</title>
+ <p>Some of the functions use the process dictionary variable
+ <c>random_seed</c> to remember the current seed.</p>
+ <p>If a process calls <c>uniform/0</c> or <c>uniform/1</c> without
+ setting a seed first, <c>seed/0</c> is called automatically.</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
new file mode 100644
index 0000000000..41dce7f2a7
--- /dev/null
+++ b/lib/stdlib/doc/src/re.xml
@@ -0,0 +1,2957 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2007</year>
+ <year>2008</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved on line at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>re</title>
+ <prepared>Patrik Nyblom</prepared>
+ <responsible>Kenneth Lundin</responsible>
+ <docno>1</docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2008-05-27</date>
+ <rev>A</rev>
+ <file>re.xml</file>
+ </header>
+ <module>re</module>
+ <modulesummary>Perl like regular expressions for Erlang</modulesummary>
+ <description>
+
+ <p>This module contains functions for regular expression
+ matching for strings and binaries.</p>
+
+ <p>The regular expression syntax and semantics resemble that of
+ Perl. This library in many ways replaces the old regexp library
+ written purely in Erlang, as it has a richer syntax as well as
+ many more options. The library is also faster than the
+ older regexp implementation.</p>
+
+ <p>Although the library's matching algorithms are currently based
+ on the PCRE library, it is not to be viewed as an Erlang to PCRE
+ mapping. Only parts of the PCRE library is interfaced and the re
+ library in some ways extend PCRE. The PCRE documentation contains
+ many parts of no interest to the Erlang programmer, why only the
+ relevant part of the documentation is included here. There should
+ bee no need to go directly to the PCRE library documentation.</p>
+
+ <note>
+ <p>The Erlang literal syntax for strings give special
+ meaning to the &quot;\\&quot; (backslash) character. To literally write
+ a regular expression or a replacement string containing a
+ backslash in your code or in the shell, two backslashes have to be written:
+ &quot;\\\\&quot;.</p>
+ </note>
+
+
+ </description>
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+ iodata() = iolist() | binary()
+ iolist() = [char() | binary() | iolist()]
+ - a binary is allowed as the tail of the list</code>
+ <code type="none">
+ unicode_binary() = binary() with characters encoded in UTF-8 coding standard
+ unicode_char() = integer() representing valid unicode codepoint
+
+ chardata() = charlist() | unicode_binary()
+
+ charlist() = [unicode_char() | unicode_binary() | charlist()]
+ - a unicode_binary is allowed as the tail of the list</code>
+
+ <code type="none">
+ mp() = Opaque datatype containing a compiled regular expression.</code>
+ </section>
+ <funcs>
+ <func>
+ <name>compile(Regexp) -> {ok, MP} | {error, ErrSpec}</name>
+ <fsummary>Compile a regular expression into a match program</fsummary>
+ <type>
+ <v>Regexp = iodata()</v>
+ </type>
+ <desc>
+ <p>The same as <c>compile(Regexp,[])</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>compile(Regexp,Options) -> {ok, MP} | {error, ErrSpec}</name>
+ <fsummary>Compile a regular expression into a match program</fsummary>
+ <type>
+ <v>Regexp = iodata() | charlist()</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = unicode | anchored | caseless | dollar_endonly | dotall | extended | firstline | multiline | no_auto_capture | dupnames | ungreedy | {newline, NLSpec}| bsr_anycrlf | bsr_unicode</v>
+ <v>NLSpec = cr | crlf | lf | anycrlf | any </v>
+ <v>MP = mp()</v>
+ <v>ErrSpec = {ErrString, Position}</v>
+ <v>ErrString = string()</v>
+ <v>Position = int()</v>
+ </type>
+ <desc>
+ <p>This function compiles a regular expression with the syntax
+ described below into an internal format to be used later as a
+ parameter to the run/2,3 functions.</p>
+ <p>Compiling the regular expression before matching is useful if
+ the same expression is to be used in matching against multiple
+ subjects during the program's lifetime. Compiling once and
+ executing many times is far more efficient than compiling each
+ time one wants to match.</p>
+ <p>When the unicode option is given, the regular expression should be given as a valid unicode <c>charlist()</c>, otherwise as any valid <c>iodata()</c>.</p>
+
+ <p>The options have the following meanings:</p>
+ <taglist>
+ <tag><c>unicode</c></tag>
+ <item>The regular expression is given as a unicode <c>charlist()</c> and the resulting regular expression code is to be run against a valid unicode <c>charlist()</c> subject.</item>
+ <tag><c>anchored</c></tag>
+ <item>The pattern is forced to be "anchored", that is, it is constrained to match only at the first matching point in the string that is being searched (the "subject string"). This effect can also be achieved by appropriate constructs in the pattern itself.</item>
+ <tag><c>caseless</c></tag>
+ <item>Letters in the pattern match both upper and lower case letters. It is equivalent to Perl's /i option, and it can be changed within a pattern by a (?i) option setting. Uppercase and lowercase letters are defined as in the ISO-8859-1 character set.</item>
+ <tag><c>dollar_endonly</c></tag>
+ <item>A dollar metacharacter in the pattern matches only at the end of the subject string. Without this option, a dollar also matches immediately before a newline at the end of the string (but not before any other newlines). The <c>dollar_endonly</c> option is ignored if <c>multiline</c> is given. There is no equivalent option in Perl, and no way to set it within a pattern.</item>
+ <tag><c>dotall</c></tag>
+ <item>A dot maturate in the pattern matches all characters, including those that indicate newline. Without it, a dot does not match when the current position is at a newline. This option is equivalent to Perl's /s option, and it can be changed within a pattern by a (?s) option setting. A negative class such as [^a] always matches newline characters, independent of the setting of this option.</item>
+ <tag><c>extended</c></tag>
+ <item>Whitespace data characters in the pattern are ignored except when escaped or inside a character class. Whitespace does not include the VT character (ASCII 11). In addition, characters between an unescaped # outside a character class and the next newline, inclusive, are also ignored. This is equivalent to Perl's /x option, and it can be changed within a pattern by a (?x) option setting.
+
+This option makes it possible to include comments inside complicated patterns. Note, however, that this applies only to data characters. Whitespace characters may never appear within special character sequences in a pattern, for example within the sequence <c>(?(</c> which introduces a conditional subpattern.</item>
+ <tag><c>firstline</c></tag>
+ <item>An unanchored pattern is required to match before or at the first newline in the subject string, though the matched text may continue over the newline.</item>
+ <tag><c>multiline</c></tag>
+ <item><p>By default, PCRE treats the subject string as consisting of a single line of characters (even if it actually contains newlines). The "start of line" metacharacter (^) matches only at the start of the string, while the "end of line" metacharacter ($) matches only at the end of the string, or before a terminating newline (unless <c>dollar_endonly</c> is given). This is the same as Perl.</p>
+
+<p>When <c>multiline</c> it is given, the "start of line" and "end of line" constructs match immediately following or immediately before internal newlines in the subject string, respectively, as well as at the very start and end. This is equivalent to Perl's /m option, and it can be changed within a pattern by a (?m) option setting. If there are no newlines in a subject string, or no occurrences of ^ or $ in a pattern, setting <c>multiline</c> has no effect.</p> </item>
+ <tag><c>no_auto_capture</c></tag>
+ <item>Disables the use of numbered capturing parentheses in the pattern. Any opening parenthesis that is not followed by ? behaves as if it were followed by ?: but named parentheses can still be used for capturing (and they acquire numbers in the usual way). There is no equivalent of this option in Perl.
+</item>
+ <tag><c>dupnames</c></tag>
+ <item>Names used to identify capturing subpatterns need not be unique. This can be helpful for certain types of pattern when it is known that only one instance of the named subpattern can ever be matched. There are more details of named subpatterns below</item>
+ <tag><c>ungreedy</c></tag>
+ <item>This option inverts the "greediness" of the quantifiers so that they are not greedy by default, but become greedy if followed by "?". It is not compatible with Perl. It can also be set by a (?U) option setting within the pattern.</item>
+ <tag><c>{newline, NLSpec}</c></tag>
+ <item>
+ <p>Override the default definition of a newline in the subject string, which is LF (ASCII 10) in Erlang.</p>
+ <taglist>
+ <tag><c>cr</c></tag>
+ <item>Newline is indicated by a single character CR (ASCII 13)</item>
+ <tag><c>lf</c></tag>
+ <item>Newline is indicated by a single character LF (ASCII 10), the default</item>
+ <tag><c>crlf</c></tag>
+ <item>Newline is indicated by the two-character CRLF (ASCII 13 followed by ASCII 10) sequence.</item>
+ <tag><c>anycrlf</c></tag>
+ <item>Any of the three preceding sequences should be recognized.</item>
+ <tag><c>any</c></tag>
+ <item>Any of the newline sequences above, plus the Unicode sequences VT (vertical tab, U+000B), FF (formfeed, U+000C), NEL (next line, U+0085), LS (line separator, U+2028), and PS (paragraph separator, U+2029). </item>
+ </taglist>
+ </item>
+ <tag><c>bsr_anycrlf</c></tag>
+ <item>Specifies specifically that \\R is to match only the cr, lf or crlf sequences, not the Unicode specific newline characters.</item>
+ <tag><c>bsr_unicode</c></tag>
+ <item>Specifies specifically that \\R is to match all the Unicode newline characters (including crlf etc, the default).</item>
+ </taglist>
+ </desc>
+ </func>
+
+ <func>
+ <name>run(Subject,RE) -> {match, Captured} | nomatch</name>
+ <fsummary>Match a subject against regular expression and capture subpatterns</fsummary>
+ <type>
+ <v>Subject = iodata() | charlist()</v>
+ <v>RE = mp() | iodata() | charlist()</v>
+ <v>Captured = [ CaptureData ]</v>
+ <v>CaptureData = {int(),int()}</v>
+ </type>
+ <desc>
+ <p>The same as <c>run(Subject,RE,[])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>run(Subject,RE,Options) -> {match, Captured} | match | nomatch</name>
+ <fsummary>Match a subject against regular expression and capture subpatterns</fsummary>
+ <type>
+ <v>Subject = iodata() | charlist()</v>
+ <v>RE = mp() | iodata() | charlist()</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = anchored | global | notbol | noteol | notempty | {offset, int()} | {newline, NLSpec} | bsr_anycrlf | bsr_unicode | {capture, ValueSpec} | {capture, ValueSpec, Type} | CompileOpt</v>
+ <v>Type = index | list | binary</v>
+ <v>ValueSpec = all | all_but_first | first | none | ValueList</v>
+ <v>ValueList = [ ValueID ]</v>
+ <v>ValueID = int() | string() | atom()</v>
+ <v>CompileOpt = see compile/2 above</v>
+ <v>NLSpec = cr | crlf | lf | anycrlf | any</v>
+ <v>Captured = [ CaptureData ] | [ [ CaptureData ] ... ]</v>
+ <v>CaptureData = {int(),int()} | ListConversionData | binary()</v>
+ <v>ListConversionData = string() | {error, string(), binary()} | {incomplete, string(), binary()}</v>
+ </type>
+ <desc>
+
+ <p>Executes a regexp matching, returning <c>match/{match,
+ Captured}</c> or <c>nomatch</c>. The regular expression can be
+ given either as <c>iodata()</c> in which case it is
+ automatically compiled (as by <c>re:compile/2</c>) and executed,
+ or as a pre compiled <c>mp()</c> in which case it is executed
+ against the subject directly.</p>
+
+ <p>When compilation is involved, the exception <c>badarg</c> is thrown if
+ a compilation error occurs. To locate the error in the regular
+ expression, use the function <c>re:compile/2</c> to get more information.</p>
+
+ <p>If the regular expression is previously compiled, the option
+ list can only contain the options <c>anchored</c>,
+ <c>global</c>, <c>notbol</c>, <c>noteol</c>,
+ <c>notempty</c>, <c>{offset, int()}</c>, <c>{newline,
+ NLSpec}</c> and <c>{capture, ValueSpec}/{capture, ValueSpec,
+ Type}</c>. Otherwise all options valid for the
+ <c>re:compile/2</c> function are allowed as well. Options
+ allowed both for compilation and execution of a match, namely
+ <c>anchored</c> and <c>{newline, NLSpec}</c>, will affect both
+ the compilation and execution if present together with a non
+ pre-compiled regular expression.</p>
+
+ <p>If the regular expression was previously compiled with the
+ option <c>unicode</c>, the <c>Subject</c> should be provided as
+ a valid Unicode <c>charlist()</c>, otherwise any <c>iodata()</c>
+ will do. If compilation is involved and the option
+ <c>unicode</c> is given, both the <c>Subject</c> and the regular
+ expression should be given as valid Unicode
+ <c>charlists()</c>.</p>
+
+ <p>The <c>{capture, ValueSpec}/{capture, ValueSpec, Type}</c>
+ defines what to return from the function upon successful
+ matching. The <c>capture</c> tuple may contain both a
+ value specification telling which of the captured
+ substrings are to be returned, and a type specification, telling
+ how captured substrings are to be returned (as index tuples,
+ lists or binaries). The <c>capture</c> option makes the function
+ quite flexible and powerful. The different options are described
+ in detail below</p>
+
+ <p>If the capture options describe that no substring capturing
+ at all is to be done (<c>{capture, none}</c>), the function will
+ return the single atom <c>match</c> upon successful matching,
+ otherwise the tuple
+ <c>{match, ValueList}</c> is returned. Disabling capturing can
+ be done either by specifying <c>none</c> or an empty list as
+ <c>ValueSpec</c>.</p>
+
+ <p>A description of all the options relevant for execution follows:</p>
+
+ <taglist>
+ <tag><c>anchored</c></tag>
+
+ <item>Limits <c>re:run/3</c> to matching at the first matching
+ position. If a pattern was compiled with <c>anchored</c>, or
+ turned out to be anchored by virtue of its contents, it cannot
+ be made unanchored at matching time, hence there is no
+ <c>unanchored</c> option.</item>
+
+ <tag><c>global</c></tag>
+ <item>
+
+ <p>Implements global (repetitive) search as the <c>g</c> flag in
+ i.e. Perl. Each match found is returned as a separate
+ <c>list()</c> containing the specific match as well as any
+ matching subexpressions (or as specified by the <c>capture
+ option</c>). The <c>Captured</c> part of the return value will
+ hence be a <c>list()</c> of <c>list()</c>'s when this
+ option is given.</p>
+
+ <p>When the regular expression matches an empty string, the
+ behaviour might seem non-intuitive, why the behaviour requites
+ some clarifying. With the global option, <c>re:run/3</c>
+ handles empty matches in the same way as Perl, meaning that a
+ match at any point giving an empty string (with length 0) will
+ be retried with the options
+ <c>[anchored, notempty]</c> as well. If that
+ search gives a result of length &gt; 0, the result is included.
+ An example:</p>
+
+<code> re:run("cat","(|at)",[global]).</code>
+
+ <p>The matching will be performed as following:</p>
+ <taglist>
+ <tag>At offset <c>0</c></tag>
+ <item>The regexp <c>(|at)</c> will first match at the initial
+ position of the string <c>cat</c>, giving the result set
+ <c>[{0,0},{0,0}]</c> (the second <c>{0,0}</c> is due to the
+ subexpression marked by the parentheses). As the length of the
+ match is 0, we don't advance to the next position yet.</item>
+ <tag>At offset <c>0</c> with <c>[anchored, notempty]</c></tag>
+ <item> The search is retried
+ with the options <c>[anchored, notempty]</c> at the same
+ position, which does not give any interesting result of longer
+ length, why the search position is now advanced to the next
+ character (<c>a</c>).</item>
+ <tag>At offset <c>1</c></tag>
+ <item>Now the search results in
+ <c>[{1,0},{1,0}]</c> meaning this search will also be repeated
+ with the extra options.</item>
+ <tag>At offset <c>1</c> with <c>[anchored, notempty]</c></tag>
+ <item>Now the <c>ab</c> alternative
+ is found and the result will be [{1,2},{1,2}]. The result is
+ added to the list of results and the position in the
+ search string is advanced two steps.</item>
+ <tag>At offset <c>3</c></tag>
+ <item>The search now once again
+ matches the empty string, giving <c>[{3,0},{3,0}]</c>.</item>
+ <tag>At offset <c>1</c> with <c>[anchored, notempty]</c></tag>
+ <item>This will give no result of length &gt; 0 and we are at
+ the last position, so the global search is complete.</item>
+ </taglist>
+ <p>The result of the call is:</p>
+
+<code> {match,[[{0,0},{0,0}],[{1,0},{1,0}],[{1,2},{1,2}],[{3,0},{3,0}]]}</code>
+</item>
+
+ <tag><c>notempty</c></tag>
+ <item>
+ <p>An empty string is not considered to be a valid match if this
+ option is given. If there are alternatives in the pattern, they
+ are tried. If all the alternatives match the empty string, the
+ entire match fails. For example, if the pattern</p>
+<code> a?b?</code>
+ <p>is applied to a string not beginning with "a" or "b", it
+ matches the empty string at the start of the subject. With
+ <c>notempty</c> given, this match is not valid, so re:run/3 searches
+ further into the string for occurrences of "a" or "b".</p>
+
+ <p>Perl has no direct equivalent of <c>notempty</c>, but it does
+ make a special case of a pattern match of the empty string
+ within its split() function, and when using the /g modifier. It
+ is possible to emulate Perl's behavior after matching a null
+ string by first trying the match again at the same offset with
+ <c>notempty</c> and <c>anchored</c>, and then if that fails by
+ advancing the starting offset (see below) and trying an ordinary
+ match again.</p>
+ </item>
+ <tag><c>notbol</c></tag>
+
+ <item>This option specifies that the first character of the subject
+ string is not the beginning of a line, so the circumflex
+ metacharacter should not match before it. Setting this without
+ <c>multiline</c> (at compile time) causes circumflex never to
+ match. This option affects only the behavior of the circumflex
+ metacharacter. It does not affect \A.</item>
+
+ <tag><c>noteol</c></tag>
+
+ <item>This option specifies that the end of the subject string
+ is not the end of a line, so the dollar metacharacter should not
+ match it nor (except in multiline mode) a newline immediately
+ before it. Setting this without <c>multiline</c> (at compile time)
+ causes dollar never to match. This option affects only the
+ behavior of the dollar metacharacter. It does not affect \Z or
+ \z.</item>
+
+ <tag><c>{offset, int()}</c></tag>
+
+ <item>Start matching at the offset (position) given in the
+ subject string. The offset is zero-based, so that the default is
+ <c>{offset,0}</c> (all of the subject string).</item>
+
+ <tag><c>{newline, NLSpec}</c></tag>
+ <item>
+ <p>Override the default definition of a newline in the subject string, which is LF (ASCII 10) in Erlang.</p>
+ <taglist>
+ <tag><c>cr</c></tag>
+ <item>Newline is indicated by a single character CR (ASCII 13)</item>
+ <tag><c>lf</c></tag>
+ <item>Newline is indicated by a single character LF (ASCII 10), the default</item>
+ <tag><c>crlf</c></tag>
+ <item>Newline is indicated by the two-character CRLF (ASCII 13 followed by ASCII 10) sequence.</item>
+ <tag><c>anycrlf</c></tag>
+ <item>Any of the three preceding sequences should be recognized.</item>
+ <tag><c>any</c></tag>
+ <item>Any of the newline sequences above, plus the Unicode sequences VT (vertical tab, U+000B), FF (formfeed, U+000C), NEL (next line, U+0085), LS (line separator, U+2028), and PS (paragraph separator, U+2029). </item>
+ </taglist>
+ </item>
+ <tag><c>bsr_anycrlf</c></tag>
+ <item>Specifies specifically that \\R is to match only the cr, lf or crlf sequences, not the Unicode specific newline characters.(overrides compilation option)</item>
+ <tag><c>bsr_unicode</c></tag>
+ <item>Specifies specifically that \\R is to match all the Unicode newline characters (including crlf etc, the default).(overrides compilation option)</item>
+
+ <tag><c>{capture, ValueSpec}</c>/<c>{capture, ValueSpec, Type}</c></tag>
+ <item>
+
+ <p>Specifies which captured substrings are returned and in what
+ format. By default,
+ <c>re:run/3</c> captures all of the matching part of the
+ substring as well as all capturing subpatterns (all of the
+ pattern is automatically captured). The default return type is
+ (zero-based) indexes of the captured parts of the string, given as
+ <c>{Offset,Length}</c> pairs (the <c>index</c> <c>Type</c> of
+ capturing).</p>
+
+ <p>As an example of the default behavior, the following call:</p>
+
+ <code> re:run("ABCabcdABC","abcd",[]).</code>
+
+ <p>returns, as first and only captured string the matching part of the subject ("abcd" in the middle) as a index pair <c>{3,4}</c>, where character positions are zero based, just as in offsets. The return value of the call above would then be:</p>
+ <code> {match,[{3,4}]}</code>
+ <p>Another (and quite common) case is where the regular expression matches all of the subject, as in:</p>
+ <code> re:run("ABCabcdABC",".*abcd.*",[]).</code>
+ <p>where the return value correspondingly will point out all of the string, beginning at index 0 and being 10 characters long:</p>
+ <code> {match,[{0,10}]}</code>
+
+ <p>If the regular expression contains capturing subpatterns,
+ like in the following case:</p>
+
+ <code> re:run("ABCabcdABC",".*(abcd).*",[]).</code>
+
+ <p>all of the matched subject is captured, as
+ well as the captured substrings:</p>
+
+ <code> {match,[{0,10},{3,4}]}</code>
+
+ <p>the complete matching pattern always giving the first return value in the
+ list and the rest of the subpatterns being added in the
+ order they occurred in the regular expression.</p>
+
+ <p>The capture tuple is built up as follows:</p>
+ <taglist>
+ <tag><c>ValueSpec</c></tag>
+ <item><p>Specifies which captured (sub)patterns are to be returned. The ValueSpec can either be an atom describing a predefined set of return values, or a list containing either the indexes or the names of specific subpatterns to return.</p>
+ <p>The predefined sets of subpatterns are:</p>
+ <taglist>
+ <tag><c>all</c></tag>
+ <item>All captured subpatterns including the complete matching string. This is the default.</item>
+ <tag><c>first</c></tag>
+ <item>Only the first captured subpattern, which is always the complete matching part of the subject. All explicitly captured subpatterns are discarded.</item>
+ <tag><c>all_but_first</c></tag>
+ <item>All but the first matching subpattern, i.e. all explicitly captured subpatterns, but not the complete matching part of the subject string. This is useful if the regular expression as a whole matches a large part of the subject, but the part you're interested in is in an explicitly captured subpattern. If the return type is <c>list</c> or <c>binary</c>, not returning subpatterns you're not interested in is a good way to optimize.</item>
+ <tag><c>none</c></tag>
+ <item>Do not return matching subpatterns at all, yielding the single atom <c>match</c> as the return value of the function when matching successfully instead of the <c>{match, list()}</c> return. Specifying an empty list gives the same behavior.</item>
+ </taglist>
+ <p>The value list is a list of indexes for the subpatterns to return, where index 0 is for all of the pattern, and 1 is for the first explicit capturing subpattern in the regular expression, and so forth. When using named captured subpatterns (see below) in the regular expression, one can use <c>atom()</c>'s or <c>string()</c>'s to specify the subpatterns to be returned. This deserves an example, consider the following regular expression:</p>
+ <code> ".*(abcd).*"</code>
+ <p>matched against the string ""ABCabcdABC", capturing only the "abcd" part (the first explicit subpattern):</p>
+ <code> re:run("ABCabcdABC",".*(abcd).*",[{capture,[1]}]).</code>
+ <p>The call will yield the following result:</p>
+ <code> {match,[{3,4}]}</code>
+ <p>as the first explicitly captured subpattern is "(abcd)", matching "abcd" in the subject, at (zero-based) position 3, of length 4.</p>
+ <p>Now consider the same regular expression, but with the subpattern explicitly named 'FOO':</p>
+ <code> ".*(?&lt;FOO&gt;abcd).*"</code>
+ <p>With this expression, we could still give the index of the subpattern with the following call:</p>
+ <code> re:run("ABCabcdABC",".*(?&lt;FOO&gt;abcd).*",[{capture,[1]}]).</code>
+ <p>giving the same result as before. But as the subpattern is named, we can also give its name in the value list:</p>
+ <code> re:run("ABCabcdABC",".*(?&lt;FOO&gt;abcd).*",[{capture,['FOO']}]).</code>
+ <p>which would yield the same result as the earlier examples, namely:</p>
+ <code> {match,[{3,4}]}</code>
+
+ <p>The values list might specify indexes or names not present in
+ the regular expression, in which case the return values vary
+ depending on the type. If the type is <c>index</c>, the tuple
+ <c>{-1,0}</c> is returned for values having no corresponding
+ subpattern in the regexp, but for the other types
+ (<c>binary</c> and <c>list</c>), the values are the empty binary
+ or list respectively.</p>
+
+ </item>
+ <tag><c>Type</c></tag>
+ <item><p>Optionally specifies how captured substrings are to be returned. If omitted, the default of <c>index</c> is used. The <c>Type</c> can be one of the following:</p>
+ <taglist>
+ <tag><c>index</c></tag>
+ <item>Return captured substrings as pairs of byte indexes into the subject string and length of the matching string in the subject (as if the subject string was flattened with <c>iolist_to_binary/1</c> or <c>unicode:characters_to_binary/2</c> prior to matching). Note that the <c>unicode</c> option results in <em>byte-oriented</em> indexes in a (possibly imagined) <em>UTF-8 encoded</em> binary. A byte index tuple <c>{0,2}</c> might therefore represent one or two characters when <c>unicode</c> is in effect. This might seem contra-intuitive, but has been deemed the most effective and useful way to way to do it. To return lists instead might result in simpler code if that is desired. This return type is the default.</item>
+ <tag><c>list</c></tag>
+ <item>Return matching substrings as lists of characters (Erlang <c>string()</c>'s). It the <c>unicode</c> option is used in combination with the \\C sequence in the regular expression, a captured subpattern can contain bytes that has is not valid UTF-8 (\\C matches bytes regardless of character encoding). In that case the <c>list</c> capturing may result in the same types of tuples that <c>unicode:characters_to_list/2</c> can return, namely three-tuples with the tag <c>incomplete</c> or <c>error</c>, the successfully converted characters and the invalid UTF-8 tail of the conversion as a binary. The best strategy is to avoid using the\\C sequence when capturing lists.</item>
+ <tag><c>binary</c></tag>
+ <item>Return matching substrings as binaries. If the <c>unicode</c> option is used, these binaries is in UTF-8. If the \\C sequence is used together with <c>unicode</c> the binaries may be invalid UTF-8.</item>
+ </taglist>
+ </item>
+ </taglist>
+ <p>In general, subpatterns that got assigned no value in the match are returned as the tuple <c>{-1,0}</c> when <c>type</c> is <c>index</c>. Unassigned subpatterns are returned as the empty binary or list respectively for other return types. Consider the regular expression:</p>
+<code> ".*((?&lt;FOO&gt;abdd)|a(..d)).*"</code>
+ <p>There are three explicitly capturing subpatterns, where the opening parenthesis position determines the order in the result, hence <c>((?&lt;FOO&gt;abdd)|a(..d))</c> is subpattern index 1, <c>(?&lt;FOO&gt;abdd)</c> is subpattern index 2 and <c>(..d)</c> is subpattern index 3. When matched against the following string:</p>
+<code> "ABCabcdABC"</code>
+ <p>the subpattern at index 2 won't match, as "abdd" is not present in the string, but the complete pattern matches (due to the alternative <c>a(..d)</c>. The subpattern at index 2 is therefore unassigned and the default return value will be:</p>
+<code> {match,[{0,10},{3,4},{-1,0},{4,3}]}</code>
+ <p>Setting the capture <c>Type</c> to <c>binary</c> would give the following:</p>
+<code> {match,[&lt;&lt;"ABCabcdABC"&gt;&gt;,&lt;&lt;"abcd"&gt;&gt;,&lt;&lt;&gt;&gt;,&lt;&lt;"bcd"&gt;&gt;]}</code>
+ <p>where the empty binary (<c>&lt;&lt;&gt;&gt;</c>) represents the unassigned subpattern. In the <c>binary</c> case, some information about the matching is therefore lost, the <c>&lt;&lt;&gt;&gt;</c> might just as well be an empty string captured.</p>
+ <p>If differentiation between empty matches and non existing subpatterns is necessary, use the <c>type</c> <c>index</c>
+ and do the conversion to the final type in Erlang code.</p>
+
+ <p>When the option <c>global</c> is given, the <c>capture</c>
+ specification affects each match separately, so that:</p>
+
+ <code> re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code>
+
+ <p>gives the result:</p>
+
+ <code> {match,[["a"],["b"]]}</code>
+
+ </item>
+ </taglist>
+ <p>The options solely affecting the compilation step are described in the <c>re:compile/2</c> function.</p>
+ </desc>
+ </func>
+ <func>
+ <name>replace(Subject,RE,Replacement) -> iodata() | charlist()</name>
+ <fsummary>Match a subject against regular expression and replace matching elements with Replacement</fsummary>
+ <type>
+ <v>Subject = iodata() | charlist()</v>
+ <v>RE = mp() | iodata()</v>
+ <v>Replacement = iodata() | charlist()</v>
+ </type>
+ <desc>
+ <p>The same as <c>replace(Subject,RE,Replacement,[])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>replace(Subject,RE,Replacement,Options) -> iodata() | charlist() | binary() | list()</name>
+ <fsummary>Match a subject against regular expression and replace matching elements with Replacement</fsummary>
+ <type>
+ <v>Subject = iodata() | charlist()</v>
+ <v>RE = mp() | iodata() | charlist()</v>
+ <v>Replacement = iodata() | charlist()</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = anchored | global | notbol | noteol | notempty | {offset, int()} | {newline, NLSpec} | bsr_anycrlf | bsr_unicode | {return, ReturnType} | CompileOpt</v>
+ <v>ReturnType = iodata | list | binary</v>
+ <v>CompileOpt = see compile/2 above</v>
+ <v>NLSpec = cr | crlf | lf | anycrlf | any </v>
+ </type>
+ <desc>
+ <p>Replaces the matched part of the <c>Subject</c> string with the content of <c>Replacement</c>.</p>
+ <p>Options are given as to the <c>re:run/3</c> function except that the <c>capture</c> option of <c>re:run/3</c> is not allowed.
+ Instead a <c>{return, ReturnType}</c> is present. The default return type is <c>iodata</c>, constructed in a
+ way to minimize copying. The <c>iodata</c> result can be used directly in many i/o-operations. If a flat <c>list()</c> is
+ desired, specify <c>{return, list}</c> and if a binary is preferred, specify <c>{return, binary}</c>.</p>
+
+ <p>As in the <c>re:run/3</c> function, an <c>mp()</c> compiled
+ with the <c>unicode</c> option requires the <c>Subject</c> to be
+ a Unicode <c>charlist()</c>. If compilation is done implicitly
+ and the <c>unicode</c> compilation option is given to this
+ function, both the regular expression and the <c>Subject</c>
+ should be given as valid Unicode <c>charlist()</c>'s.</p>
+
+ <p>The replacement string can contain the special character
+ <c>&amp;</c>, which inserts the whole matching expression in the
+ result, and the special sequence <c>\\</c>N (where N is an
+ integer &gt; 0), resulting in the subexpression number N will be
+ inserted in the result. If no subexpression with that number is
+ generated by the regular expression, nothing is inserted.</p>
+ <p>To insert an <c>&amp;</c> or <c>\\</c> in the result, precede it
+ with a <c>\\</c>. Note that Erlang already gives a special
+ meaning to <c>\\</c> in literal strings, why a single <c>\\</c>
+ has to be written as <c>"\\\\"</c> and therefore a double <c>\\</c>
+ as <c>"\\\\\\\\"</c>. Example:</p>
+ <code> re:replace("abcd","c","[&amp;]",[{return,list}]).</code>
+ <p>gives</p>
+ <code> "ab[c]d"</code>
+ <p>while</p>
+ <code> re:replace("abcd","c","[\\\&amp;]",[{return,list}]).</code>
+ <p>gives</p>
+ <code> "ab[&amp;]d"</code>
+ <p>As with <c>re:run/3</c>, compilation errors raise the <c>badarg</c>
+ exception, <c>re:compile/2</c> can be used to get more information
+ about the error.</p>
+ </desc>
+ </func>
+ <func>
+ <name>split(Subject,RE) -> SplitList</name>
+ <fsummary>Split a string by tokens specified as a regular expression</fsummary>
+ <type>
+ <v>Subject = iodata() | charlist()</v>
+ <v>RE = mp() | iodata()</v>
+ <v>SplitList = [ iodata() | charlist() ]</v>
+ </type>
+ <desc>
+ <p>The same as <c>split(Subject,RE,[])</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>split(Subject,RE,Options) -> SplitList</name>
+ <fsummary>Split a string by tokens specified as a regular expression</fsummary>
+ <type>
+ <v>Subject = iodata() | charlist()</v>
+ <v>RE = mp() | iodata() | charlist()</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = anchored | global | notbol | noteol | notempty | {offset, int()} | {newline, NLSpec} | bsr_anycrlf | bsr_unicode | {return, ReturnType} | {parts, NumParts} | group | trim | CompileOpt</v>
+ <v>NumParts = int() | infinity</v>
+ <v>ReturnType = iodata | list | binary</v>
+ <v>CompileOpt = see compile/2 above</v>
+ <v>NLSpec = cr | crlf | lf | anycrlf | any </v>
+ <v>SplitList = [ RetData ] | [ GroupedRetData ]</v>
+ <v>GroupedRetData = [ RetData ]</v>
+ <v>RetData = iodata() charlist() | binary() | list()</v>
+ </type>
+ <desc>
+ <p>This function splits the input into parts by finding tokens
+ according to the regular expression supplied.</p>
+
+ <p>The splitting is done basically by running a global regexp match and
+ dividing the initial string wherever a match occurs. The matching part
+ of the string is removed from the output.</p>
+
+ <p>As in the <c>re:run/3</c> function, an <c>mp()</c> compiled
+ with the <c>unicode</c> option requires the <c>Subject</c> to be
+ a Unicode <c>charlist()</c>. If compilation is done implicitly
+ and the <c>unicode</c> compilation option is given to this
+ function, both the regular expression and the <c>Subject</c>
+ should be given as valid Unicode <c>charlist()</c>'s.</p>
+
+ <p>The result is given as a list of &quot;strings&quot;, the
+ preferred datatype given in the <c>return</c> option (default iodata).</p>
+ <p>If subexpressions are given in the regular expression, the
+ matching subexpressions are returned in the resulting list as
+ well. An example:</p>
+
+<code> re:split("Erlang","[ln]",[{return,list}]).</code>
+
+ <p>will yield the result:</p>
+
+<code> ["Er","a","g"]</code>
+
+ <p>while</p>
+
+<code> re:split("Erlang","([ln])",[{return,list}]).</code>
+
+ <p>will yield</p>
+
+<code> ["Er","l","a","n","g"]</code>
+
+ <p>The text matching the subexpression (marked by the parentheses
+ in the regexp) is
+ inserted in the result list where it was found. In effect this means
+ that concatenating the result of a split where the whole regexp is a
+ single subexpression (as in the example above) will always result in
+ the original string.</p>
+
+ <p>As there is no matching subexpression for the last part in
+ the example (the &quot;g&quot;), there is nothing inserted after
+ that. To make the group of strings and the parts matching the
+ subexpressions more obvious, one might use the <c>group</c>
+ option, which groups together the part of the subject string with the
+ parts matching the subexpressions when the string was split:</p>
+
+<code> re:split("Erlang","([ln])",[{return,list},group]).</code>
+
+ <p>gives:</p>
+
+<code> [["Er","l"],["a","n"],["g"]]</code>
+
+ <p>Here the regular expression matched first the &quot;l&quot;,
+ causing &quot;Er&quot; to be the first part in the result. When
+ the regular expression matched, the (only) subexpression was
+ bound to the &quot;l&quot;, why the &quot;l&quot; is inserted
+ in the group together with &quot;Er&quot;. The next match is of
+ the &quot;n&quot;, making &quot;a&quot; the next part to be
+ returned. As the subexpression is bound to the substring
+ &quot;n&quot; in this case, the &quot;n&quot; is inserted into
+ this group. The last group consists of the rest of the string,
+ as no more matches are found.</p>
+
+
+ <p>By default, all parts of the string, including the empty
+ strings are returned from the function. As an example:</p>
+
+<code> re:split("Erlang","[lg]",[{return,list}]).</code>
+
+ <p>The result will be:</p>
+
+<code> ["Er","an",[]]</code>
+
+ <p>as the matching of the &quot;g&quot; in the end of the string
+ leaves an empty rest which is also returned. This behaviour
+ differs from the default behaviour of the split function in
+ Perl, where empty strings at the end are by default removed. To
+ get the
+ &quot;trimming&quot; default behavior of Perl, specify
+ <c>trim</c> as an option:</p>
+
+<code> re:split("Erlang","[lg]",[{return,list},trim]).</code>
+
+ <p>The result will be:</p>
+
+<code> ["Er","an"]</code>
+
+ <p>The &quot;trim&quot; option in effect says; &quot;give me as
+ many parts as possible except the empty ones&quot;, which might
+ be useful in some circumstances. You can also specify how many
+ parts you want, by specifying <c>{parts,</c>N<c>}</c>:</p>
+
+<code> re:split("Erlang","[lg]",[{return,list},{parts,2}]).</code>
+
+ <p>This will give:</p>
+
+<code> ["Er","ang"]</code>
+
+ <p>Note that the last part is &quot;ang&quot;, not
+ &quot;an&quot;, as we only specified splitting into two parts,
+ and the splitting stops when enough parts are given, why the
+ result differs from that of <c>trim</c>.</p>
+
+ <p>More than three parts are not possible with this indata, why</p>
+
+<code> re:split("Erlang","[lg]",[{return,list},{parts,4}]).</code>
+
+ <p>will give the same result as the default, which is to be
+ viewed as &quot;an infinite number of parts&quot;.</p>
+
+ <p>Specifying <c>0</c> as the number of parts gives the same
+ effect as the option <c>trim</c>. If subexpressions are
+ captured, empty subexpression matches at the end are also
+ stripped from the result if <c>trim</c> or <c>{parts,0}</c> is
+ specified.</p>
+
+ <p>If you are familiar with Perl, the <c>trim</c>
+ behaviour corresponds exactly to the Perl default, the
+ <c>{parts,N}</c> where N is a positive integer corresponds
+ exactly to the Perl behaviour with a positive numerical third
+ parameter and the default behaviour of <c>re:split/3</c> corresponds
+ to that when the Perl routine is given a negative integer as the
+ third parameter.</p>
+
+ <p>Summary of options not previously described for the <c>re:run/3</c> function:</p>
+ <taglist>
+ <tag>{return,ReturnType}</tag>
+ <item><p>Specifies how the parts of the original string are presented in the result list. The possible types are:</p>
+ <taglist>
+ <tag>iodata</tag>
+ <item>The variant of <c>iodata()</c> that gives the least copying of data with the current implementation (often a binary, but don't depend on it).</item>
+ <tag>binary</tag>
+ <item>All parts returned as binaries.</item>
+ <tag>list</tag>
+ <item>All parts returned as lists of characters (&quot;strings&quot;).</item>
+ </taglist>
+ </item>
+ <tag>group</tag>
+ <item>
+
+ <p>Groups together the part of the string with
+ the parts of the string matching the subexpressions of the
+ regexp.</p>
+ <p>The return value from the function will in this case be a
+ <c>list()</c> of <c>list()</c>'s. Each sublist begins with the
+ string picked out of the subject string, followed by the parts
+ matching each of the subexpressions in order of occurrence in the
+ regular expression.</p>
+
+ </item>
+ <tag>{parts,N}</tag>
+ <item>
+
+ <p>Specifies the number of parts the subject string is to be
+ split into.</p>
+
+ <p>The number of parts should be a positive integer for a specific maximum on the
+ number of parts and <c>infinity</c> for the maximum number of
+ parts possible (the default). Specifying <c>{parts,0}</c> gives as many parts as
+ possible disregarding empty parts at the end, the same as
+ specifying <c>trim</c></p>
+ </item>
+ <tag>trim</tag>
+ <item>
+
+ <p>Specifies that empty parts at the end of the result list are
+ to be disregarded. The same as specifying <c>{parts,0}</c>. This
+ corresponds to the default behaviour of the <c>split</c>
+ built in function in Perl.</p>
+ </item>
+ </taglist>
+
+ </desc>
+ </func>
+ </funcs>
+
+
+ <section>
+ <title>PERL LIKE REGULAR EXPRESSIONS SYNTAX</title>
+ <p>The following sections contain reference material for the
+ regular expressions used by this module. The regular expression
+ reference is taken from the PCRE documentation, but converted as
+ needed.</p>
+ <p>The documentation is altered where appropriate and where the re
+ module behaves differently than the PCRE library.</p>
+ </section>
+
+<section><title>PCRE regular expression details</title>
+
+<p>The syntax and semantics of the regular expressions that are supported by PCRE
+are described in detail below. Perl's regular expressions are described in its own documentation, and
+regular expressions in general are covered in a number of books, some of which
+have copious examples. Jeffrey Friedl's "Mastering Regular Expressions",
+published by O'Reilly, covers regular expressions in great detail. This
+description of PCRE's regular expressions is intended as reference material.</p>
+<p>The reference material is divided into the following sections:</p>
+<list>
+<item><seealso marker="#sect1">Newline conventions</seealso></item>
+<item><seealso marker="#sect2">Characters and metacharacters</seealso></item>
+<item><seealso marker="#sect3">Backslash</seealso></item>
+<item><seealso marker="#sect4">Circumflex and dollar</seealso></item>
+<item><seealso marker="#sect5">Full stop (period, dot)</seealso></item>
+<item><seealso marker="#sect6">Matching a single byte</seealso></item>
+<item><seealso marker="#sect7">Square brackets and character classes</seealso></item>
+<item><seealso marker="#sect8">POSIX character classes</seealso></item>
+<item><seealso marker="#sect9">Vertical bar</seealso></item>
+<item><seealso marker="#sect10">Internal option setting</seealso></item>
+<item><seealso marker="#sect11">Subpatterns</seealso></item>
+<item><seealso marker="#sect12">Duplicate subpattern numbers</seealso></item>
+<item><seealso marker="#sect13">Named subpatterns</seealso></item>
+<item><seealso marker="#sect14">Repetition</seealso></item>
+<item><seealso marker="#sect15">Atomic grouping and possessive quantifiers</seealso></item>
+<item><seealso marker="#sect16">Back references</seealso></item>
+<item><seealso marker="#sect17">Assertions</seealso></item>
+<item><seealso marker="#sect18">Conditional subpatterns</seealso></item>
+<item><seealso marker="#sect19">Comments</seealso></item>
+<item><seealso marker="#sect20">Recursive patterns</seealso></item>
+<item><seealso marker="#sect21">Subpatterns as subroutines</seealso></item>
+<!-- XXX C Interface
+<item><seealso marker="#sect22">Callouts</seealso></item>
+-->
+<item><seealso marker="#sect23">Backtracking control</seealso></item>
+</list>
+
+</section>
+
+
+<section><marker id="sect1"></marker><title>Newline conventions</title>
+
+<p>PCRE supports
+five
+different conventions for indicating line breaks in
+strings: a single CR (carriage return) character, a single LF (linefeed)
+character, the two-character sequence CRLF
+, any of the three preceding, or any
+Unicode newline sequence.</p>
+
+<p>It is also possible to specify a newline convention by starting a pattern
+string with one of the following five sequences:</p>
+
+<taglist>
+ <tag>(*CR)</tag> <item>carriage return</item>
+ <tag>(*LF)</tag> <item>linefeed</item>
+ <tag>(*CRLF)</tag> <item>carriage return, followed by linefeed</item>
+ <tag>(*ANYCRLF)</tag> <item>any of the three above</item>
+ <tag>(*ANY)</tag> <item>all Unicode newline sequences</item>
+</taglist>
+
+<p>These override the default and the options given to <c>re:compile/2</c>. For
+example, the pattern:</p>
+
+<quote>
+<p> (*CR)a.b</p>
+</quote>
+
+<p>changes the convention to CR. That pattern matches "a\\nb" because LF is no
+longer a newline. Note that these special settings, which are not
+Perl-compatible, are recognized only at the very start of a pattern, and that
+they must be in upper case. If more than one of them is present, the last one
+is used.</p>
+
+<p>The newline convention does not affect what the \\R escape sequence matches. By
+default, this is any Unicode newline sequence, for Perl compatibility. However,
+this can be changed; see the description of \\R in the section entitled
+
+"Newline sequences"
+
+below. A change of \\R setting can be combined with a change of newline
+convention.</p>
+
+</section>
+
+
+<section><marker id="sect2"></marker><title>Characters and metacharacters</title>
+<!-- .rs -->
+
+<p>A regular expression is a pattern that is matched against a subject
+string from left to right. Most characters stand for themselves in a
+pattern, and match the corresponding characters in the subject. As a
+trivial example, the pattern</p>
+
+<quote>
+<p> The quick brown fox</p>
+</quote>
+
+<p>matches a portion of a subject string that is identical to
+itself. When caseless matching is specified (the <c>caseless</c>
+option), letters are matched independently of case.</p>
+
+<p>The power of regular expressions comes from the ability to include
+alternatives and repetitions in the pattern. These are encoded in the
+pattern by the use of <em>metacharacters</em>, which do not stand for
+themselves but instead are interpreted in some special way.</p>
+
+<p>There are two different sets of metacharacters: those that are recognized
+anywhere in the pattern except within square brackets, and those that are
+recognized within square brackets. Outside square brackets, the metacharacters
+are as follows:</p>
+
+<taglist>
+ <tag>\\</tag> <item>general escape character with several uses</item>
+ <tag>^</tag> <item>assert start of string (or line, in multiline mode)</item>
+ <tag>$</tag> <item>assert end of string (or line, in multiline mode)</item>
+ <tag>.</tag> <item>match any character except newline (by default)</item>
+ <tag>[</tag> <item>start character class definition</item>
+ <tag>|</tag> <item>start of alternative branch</item>
+ <tag>(</tag> <item>start subpattern</item>
+ <tag>)</tag> <item>end subpattern</item>
+ <tag>?</tag> <item>extends the meaning of (,
+ also 0 or 1 quantifier,
+ also quantifier minimizer</item>
+ <tag>*</tag> <item>0 or more quantifier</item>
+ <tag>+</tag> <item>1 or more quantifier,
+ also "possessive quantifier"</item>
+ <tag>{</tag> <item>start min/max quantifier</item>
+</taglist>
+
+<p>Part of a pattern that is in square brackets is called a "character class". In
+a character class the only metacharacters are:</p>
+
+<taglist>
+ <tag>\\</tag> <item>general escape character</item>
+ <tag>^</tag> <item>negate the class, but only if the first character</item>
+ <tag>-</tag> <item>indicates character range</item>
+ <tag>[</tag> <item>POSIX character class (only if followed by POSIX
+ syntax)</item>
+ <tag>]</tag> <item>terminates the character class</item>
+</taglist>
+
+<p>The following sections describe the use of each of the metacharacters.</p>
+
+
+</section>
+
+<section><marker id="sect3"></marker><title>Backslash</title>
+
+
+<p>The backslash character has several uses. Firstly, if it is followed by a
+non-alphanumeric character, it takes away any special meaning that character
+may have. This use of backslash as an escape character applies both inside and
+outside character classes.</p>
+
+<p>For example, if you want to match a * character, you write \\* in the pattern.
+This escaping action applies whether or not the following character would
+otherwise be interpreted as a metacharacter, so it is always safe to precede a
+non-alphanumeric with backslash to specify that it stands for itself. In
+particular, if you want to match a backslash, you write \\\\.</p>
+
+<p>If a pattern is compiled with the <c>extended</c> option, whitespace in the
+pattern (other than in a character class) and characters between a # outside
+a character class and the next newline are ignored. An escaping backslash can
+be used to include a whitespace or # character as part of the pattern.</p>
+
+<p>If you want to remove the special meaning from a sequence of characters, you
+can do so by putting them between \\Q and \\E. This is different from Perl in
+that $ and @ are handled as literals in \\Q...\\E sequences in PCRE, whereas in
+Perl, $ and @ cause variable interpolation. Note the following examples:</p>
+<code type="none">
+ Pattern PCRE matches Perl matches
+
+ \\Qabc$xyz\\E abc$xyz abc followed by the contents of $xyz
+ \\Qabc\\$xyz\\E abc\\$xyz abc\\$xyz
+ \\Qabc\\E\\$\\Qxyz\\E abc$xyz abc$xyz</code>
+
+
+<p>The \\Q...\\E sequence is recognized both inside and outside character classes.</p>
+
+
+<p><em>Non-printing characters</em></p>
+
+<p>A second use of backslash provides a way of encoding non-printing characters
+in patterns in a visible manner. There is no restriction on the appearance of
+non-printing characters, apart from the binary zero that terminates a pattern,
+but when a pattern is being prepared by text editing, it is usually easier to
+use one of the following escape sequences than the binary character it
+represents:</p>
+
+<taglist>
+ <tag>\\a</tag> <item>alarm, that is, the BEL character (hex 07)</item>
+ <tag>\\cx</tag> <item>"control-x", where x is any character</item>
+ <tag>\\e </tag> <item>escape (hex 1B)</item>
+ <tag>\\f</tag> <item>formfeed (hex 0C)</item>
+ <tag>\\n</tag> <item>linefeed (hex 0A)</item>
+ <tag>\\r</tag> <item>carriage return (hex 0D)</item>
+ <tag>\\t </tag> <item>tab (hex 09)</item>
+ <tag>\\ddd</tag> <item>character with octal code ddd, or backreference</item>
+ <tag>\\xhh </tag> <item>character with hex code hh</item>
+ <tag>\\x{hhh..}</tag> <item>character with hex code hhh..</item>
+</taglist>
+
+<p>The precise effect of \\cx is as follows: if x is a lower case letter, it
+is converted to upper case. Then bit 6 of the character (hex 40) is inverted.
+Thus \\cz becomes hex 1A, but \\c{ becomes hex 3B, while \\c; becomes hex
+7B.</p>
+
+<p>After \\x, from zero to two hexadecimal digits are read (letters can be in
+upper or lower case). Any number of hexadecimal digits may appear between \\x{
+and }, but the value of the character code must be less than 256 in non-UTF-8
+mode, and less than 2**31 in UTF-8 mode. That is, the maximum value in
+hexadecimal is 7FFFFFFF. Note that this is bigger than the largest Unicode code
+point, which is 10FFFF.</p>
+
+<p>If characters other than hexadecimal digits appear between \\x{ and }, or if
+there is no terminating }, this form of escape is not recognized. Instead, the
+initial \\x will be interpreted as a basic hexadecimal escape, with no
+following digits, giving a character whose value is zero.</p>
+
+<p>Characters whose value is less than 256 can be defined by either of the two
+syntaxes for \\x. There is no difference in the way they are handled. For
+example, \\xdc is exactly the same as \\x{dc}.</p>
+
+<p>After \\0 up to two further octal digits are read. If there are fewer than two
+digits, just those that are present are used. Thus the sequence \\0\\x\\07
+specifies two binary zeros followed by a BEL character (code value 7). Make
+sure you supply two digits after the initial zero if the pattern character that
+follows is itself an octal digit.</p>
+
+<p>The handling of a backslash followed by a digit other than 0 is complicated.
+Outside a character class, PCRE reads it and any following digits as a decimal
+number. If the number is less than 10, or if there have been at least that many
+previous capturing left parentheses in the expression, the entire sequence is
+taken as a <em>back reference</em>. A description of how this works is given
+later, following the discussion of parenthesized subpatterns.</p>
+
+
+<p>Inside a character class, or if the decimal number is greater than 9 and there
+have not been that many capturing subpatterns, PCRE re-reads up to three octal
+digits following the backslash, and uses them to generate a data character. Any
+subsequent digits stand for themselves.
+The value of a
+character specified in octal must be less than \\400.
+In non-UTF-8 mode, the value of a
+character specified in octal must be less than \\400. In UTF-8 mode, values up
+to \\777 are permitted.
+
+For example:</p>
+
+<taglist>
+ <tag>\\040</tag> <item>is another way of writing a space</item>
+
+ <tag>\\40</tag> <item>is the same, provided there are fewer than 40
+ previous capturing subpatterns</item>
+ <tag>\\7</tag> <item>is always a back reference</item>
+
+ <tag>\\11</tag> <item> might be a back reference, or another way of
+ writing a tab</item>
+ <tag>\\011</tag> <item>is always a tab</item>
+ <tag>\\0113</tag> <item>is a tab followed by the character "3"</item>
+
+ <tag>\\113</tag> <item>might be a back reference, otherwise the
+ character with octal code 113</item>
+
+ <tag>\\377</tag> <item>might be a back reference, otherwise
+ the byte consisting entirely of 1 bits</item>
+
+ <tag>\\81</tag> <item>is either a back reference, or a binary zero
+ followed by the two characters "8" and "1"</item>
+</taglist>
+
+<p>Note that octal values of 100 or greater must not be introduced by
+a leading zero, because no more than three octal digits are ever
+read.</p>
+
+<p>All the sequences that define a single character value can be used
+both inside and outside character classes. In addition, inside a
+character class, the sequence \\b is interpreted as the backspace
+character (hex 08), and the sequences \\R and \\X are interpreted as
+the characters "R" and "X", respectively. Outside a character class,
+these sequences have different meanings (see below).</p>
+
+<p><em>Absolute and relative back references</em></p>
+
+<p>The sequence \\g followed by an unsigned or a negative number,
+optionally enclosed in braces, is an absolute or relative back
+reference. A named back reference can be coded as \\g{name}. Back
+references are discussed later, following the discussion of
+parenthesized subpatterns.</p>
+
+<p><em>Generic character types</em></p>
+
+<p>Another use of backslash is for specifying generic character types. The
+following are always recognized:</p>
+
+<taglist>
+ <tag>\\d</tag> <item>any decimal digit</item>
+ <tag>\\D</tag> <item>any character that is not a decimal digit</item>
+ <tag>\\h</tag> <item>any horizontal whitespace character</item>
+ <tag>\\H</tag> <item>any character that is not a horizontal whitespace character</item>
+ <tag>\\s</tag> <item>any whitespace character</item>
+ <tag>\\S</tag> <item>any character that is not a whitespace character</item>
+ <tag>\\v</tag> <item>any vertical whitespace character</item>
+ <tag>\\V</tag> <item>any character that is not a vertical whitespace character</item>
+ <tag>\\w</tag> <item>any "word" character</item>
+ <tag>\\W</tag> <item>any "non-word" character</item>
+</taglist>
+
+<p>Each pair of escape sequences partitions the complete set of characters into
+two disjoint sets. Any given character matches one, and only one, of each pair.</p>
+
+<p>These character type sequences can appear both inside and outside character
+classes. They each match one character of the appropriate type. If the current
+matching point is at the end of the subject string, all of them fail, since
+there is no character to match.</p>
+
+<p>For compatibility with Perl, \\s does not match the VT character (code 11).
+This makes it different from the POSIX "space" class. The \\s characters
+are HT (9), LF (10), FF (12), CR (13), and space (32). If "use locale;" is
+included in a Perl script, \\s may match the VT character. In PCRE, it never
+does.</p>
+
+<p>In UTF-8 mode, characters with values greater than 128 never match \\d, \\s, or
+\\w, and always match \\D, \\S, and \\W. This is true even when Unicode
+character property support is available. These sequences retain their original
+meanings from before UTF-8 support was available, mainly for efficiency
+reasons.</p>
+
+<p>The sequences \\h, \\H, \\v, and \\V are Perl 5.10 features. In contrast to the
+other sequences, these do match certain high-valued codepoints in UTF-8 mode.
+The horizontal space characters are:</p>
+
+<taglist>
+ <tag>U+0009</tag> <item>Horizontal tab</item>
+ <tag>U+0020</tag> <item>Space</item>
+ <tag>U+00A0</tag> <item>Non-break space</item>
+ <tag>U+1680</tag> <item>Ogham space mark</item>
+ <tag>U+180E</tag> <item>Mongolian vowel separator</item>
+ <tag>U+2000</tag> <item>En quad</item>
+ <tag>U+2001</tag> <item>Em quad</item>
+ <tag>U+2002</tag> <item>En space</item>
+ <tag>U+2003</tag> <item>Em space</item>
+ <tag>U+2004</tag> <item>Three-per-em space</item>
+ <tag>U+2005</tag> <item>Four-per-em space</item>
+ <tag>U+2006</tag> <item>Six-per-em space</item>
+ <tag>U+2007</tag> <item>Figure space</item>
+ <tag>U+2008</tag> <item>Punctuation space</item>
+ <tag>U+2009</tag> <item>Thin space</item>
+ <tag>U+200A</tag> <item>Hair space</item>
+ <tag>U+202F</tag> <item>Narrow no-break space</item>
+ <tag>U+205F</tag> <item>Medium mathematical space</item>
+ <tag>U+3000</tag> <item>Ideographic space</item>
+</taglist>
+
+<p>The vertical space characters are:</p>
+
+<taglist>
+ <tag>U+000A</tag> <item>Linefeed</item>
+ <tag>U+000B</tag> <item>Vertical tab</item>
+ <tag>U+000C</tag> <item>Formfeed</item>
+ <tag>U+000D</tag> <item>Carriage return</item>
+ <tag>U+0085</tag> <item>Next line</item>
+ <tag>U+2028</tag> <item>Line separator</item>
+ <tag>U+2029</tag> <item>Paragraph separator</item>
+</taglist>
+
+<p>A "word" character is an underscore or any character less than 256 that is a
+letter or digit. The definition of letters and digits is controlled by PCRE's
+low-valued character tables, which are always ISO-8859-1.</p>
+
+<p><em>Newline sequences</em></p>
+
+<p>Outside a character class, by default, the escape sequence \\R matches any
+Unicode newline sequence. This is a Perl 5.10 feature. In non-UTF-8 mode \\R is
+equivalent to the following:</p>
+
+<quote><p> (?&gt;\\r\\n|\\n|\\x0b|\\f|\\r|\\x85)</p></quote>
+
+<p>This is an example of an "atomic group", details of which are given below.</p>
+
+<p>This particular group matches either the two-character sequence CR followed by
+LF, or one of the single characters LF (linefeed, U+000A), VT (vertical tab,
+U+000B), FF (formfeed, U+000C), CR (carriage return, U+000D), or NEL (next
+line, U+0085). The two-character sequence is treated as a single unit that
+cannot be split.</p>
+
+<p>In UTF-8 mode, two additional characters whose codepoints are greater than 255
+are added: LS (line separator, U+2028) and PS (paragraph separator, U+2029).
+Unicode character property support is not needed for these characters to be
+recognized.</p>
+
+
+<p>It is possible to restrict \\R to match only CR, LF, or CRLF (instead of the
+complete set of Unicode line endings) by setting the option <c>bsr_anycrlf</c>
+either at compile time or when the pattern is matched. (BSR is an abbreviation
+for "backslash R".) This can be made the default when PCRE is built; if this is
+the case, the other behaviour can be requested via the <c>bsr_unicode</c> option.
+It is also possible to specify these settings by starting a pattern string with
+one of the following sequences:</p>
+
+<p> (*BSR_ANYCRLF) CR, LF, or CRLF only
+ (*BSR_UNICODE) any Unicode newline sequence</p>
+
+<p>These override the default and the options given to <c>re:compile/2</c>, but
+they can be overridden by options given to <c>re:run/3</c>. Note that these
+special settings, which are not Perl-compatible, are recognized only at the
+very start of a pattern, and that they must be in upper case. If more than one
+of them is present, the last one is used. They can be combined with a change of
+newline convention, for example, a pattern can start with:</p>
+
+<p> (*ANY)(*BSR_ANYCRLF)</p>
+
+<p>Inside a character class, \\R matches the letter "R".</p>
+
+
+<p><em>Unicode character properties</em></p>
+
+<p>When PCRE is built with Unicode character property support, three additional
+escape sequences that match characters with specific properties are available.
+When not in UTF-8 mode, these sequences are of course limited to testing
+characters whose codepoints are less than 256, but they do work in this mode.
+The extra escape sequences are:</p>
+
+<p> \\p{<em>xx</em>} a character with the <em>xx</em> property
+ \\P{<em>xx</em>} a character without the <em>xx</em> property
+ \\X an extended Unicode sequence</p>
+
+<p>The property names represented by <em>xx</em> above are limited to the Unicode
+script names, the general category properties, and "Any", which matches any
+character (including newline). Other properties such as "InMusicalSymbols" are
+not currently supported by PCRE. Note that \\P{Any} does not match any
+characters, so always causes a match failure.</p>
+
+<p>Sets of Unicode characters are defined as belonging to certain scripts. A
+character from one of these sets can be matched using a script name. For
+example:</p>
+
+<p> \\p{Greek}
+ \\P{Han}</p>
+
+<p>Those that are not part of an identified script are lumped together as
+"Common". The current list of scripts is:</p>
+
+<list>
+<item>Arabic</item>
+<item>Armenian</item>
+<item>Balinese</item>
+<item>Bengali</item>
+<item>Bopomofo</item>
+<item>Braille</item>
+<item>Buginese</item>
+<item>Buhid</item>
+<item>Canadian_Aboriginal</item>
+<item>Cherokee</item>
+<item>Common</item>
+<item>Coptic</item>
+<item>Cuneiform</item>
+<item>Cypriot</item>
+<item>Cyrillic</item>
+<item>Deseret</item>
+<item>Devanagari</item>
+<item>Ethiopic</item>
+<item>Georgian</item>
+<item>Glagolitic</item>
+<item>Gothic</item>
+<item>Greek</item>
+<item>Gujarati</item>
+<item>Gurmukhi</item>
+<item>Han</item>
+<item>Hangul</item>
+<item>Hanunoo</item>
+<item>Hebrew</item>
+<item>Hiragana</item>
+<item>Inherited</item>
+<item>Kannada</item>
+<item>Katakana</item>
+<item>Kharoshthi</item>
+<item>Khmer</item>
+<item>Lao</item>
+<item>Latin</item>
+<item>Limbu</item>
+<item>Linear_B</item>
+<item>Malayalam</item>
+<item>Mongolian</item>
+<item>Myanmar</item>
+<item>New_Tai_Lue</item>
+<item>Nko</item>
+<item>Ogham</item>
+<item>Old_Italic</item>
+<item>Old_Persian</item>
+<item>Oriya</item>
+<item>Osmanya</item>
+<item>Phags_Pa</item>
+<item>Phoenician</item>
+<item>Runic</item>
+<item>Shavian</item>
+<item>Sinhala</item>
+<item>Syloti_Nagri</item>
+<item>Syriac</item>
+<item>Tagalog</item>
+<item>Tagbanwa</item>
+<item>Tai_Le</item>
+<item>Tamil</item>
+<item>Telugu</item>
+<item>Thaana</item>
+<item>Thai</item>
+<item>Tibetan</item>
+<item>Tifinagh</item>
+<item>Ugaritic</item>
+<item>Yi</item>
+</list>
+
+<p>Each character has exactly one general category property, specified by a
+two-letter abbreviation. For compatibility with Perl, negation can be specified
+by including a circumflex between the opening brace and the property name. For
+example, \\p{^Lu} is the same as \\P{Lu}.</p>
+
+<p>If only one letter is specified with \\p or \\P, it includes all the general
+category properties that start with that letter. In this case, in the absence
+of negation, the curly brackets in the escape sequence are optional; these two
+examples have the same effect:</p>
+
+<list><item>\\p{L}</item>
+ <item>\\pL</item></list>
+
+<p>The following general category property codes are supported:</p>
+
+<taglist>
+ <tag>C</tag> <item>Other</item>
+ <tag>Cc</tag> <item>Control</item>
+ <tag>Cf</tag> <item>Format</item>
+ <tag>Cn</tag> <item>Unassigned</item>
+ <tag>Co</tag> <item>Private use</item>
+ <tag>Cs</tag> <item>Surrogate</item>
+</taglist>
+
+<taglist>
+ <tag>L</tag> <item>Letter</item>
+ <tag>Ll</tag> <item>Lower case letter</item>
+ <tag>Lm</tag> <item>Modifier letter</item>
+ <tag>Lo</tag> <item>Other letter</item>
+ <tag>Lt</tag> <item>Title case letter</item>
+ <tag>Lu</tag> <item>Upper case letter</item>
+</taglist>
+
+
+<taglist>
+ <tag>M</tag> <item>Mark</item>
+ <tag>Mc</tag> <item>Spacing mark</item>
+ <tag>Me</tag> <item>Enclosing mark</item>
+ <tag>Mn</tag> <item>Non-spacing mark</item>
+</taglist>
+
+<taglist>
+ <tag>N</tag> <item>Number</item>
+ <tag>Nd</tag> <item>Decimal number</item>
+ <tag>Nl</tag> <item>Letter number</item>
+ <tag>No</tag> <item>Other number</item>
+</taglist>
+
+<taglist>
+ <tag>P</tag> <item>Punctuation</item>
+ <tag>Pc</tag> <item>Connector punctuation</item>
+ <tag>Pd</tag> <item>Dash punctuation</item>
+ <tag>Pe</tag> <item>Close punctuation</item>
+ <tag>Pf</tag> <item>Final punctuation</item>
+ <tag>Pi</tag> <item>Initial punctuation</item>
+ <tag>Po</tag> <item>Other punctuation</item>
+ <tag>Ps</tag> <item>Open punctuation</item>
+</taglist>
+
+<taglist>
+ <tag>S</tag> <item>Symbol</item>
+ <tag>Sc</tag> <item>Currency symbol</item>
+ <tag>Sk</tag> <item>Modifier symbol</item>
+ <tag>Sm</tag> <item>Mathematical symbol</item>
+ <tag>So</tag> <item>Other symbol</item>
+</taglist>
+
+<taglist>
+ <tag>Z</tag> <item>Separator</item>
+ <tag>Zl</tag> <item>Line separator</item>
+ <tag>Zp</tag> <item>Paragraph separator</item>
+ <tag>Zs</tag> <item>Space separator</item>
+</taglist>
+
+<p>The special property L&amp; is also supported: it matches a character that has
+the Lu, Ll, or Lt property, in other words, a letter that is not classified as
+a modifier or "other".</p>
+
+<p>The Cs (Surrogate) property applies only to characters in the range U+D800 to
+U+DFFF. Such characters are not valid in UTF-8 strings (see RFC 3629) and so
+cannot be tested by PCRE, unless UTF-8 validity checking has been turned off
+(see the discussion of <c>no_utf8_check</c> in the
+<em>pcreapi</em>
+page).</p>
+
+<p>The long synonyms for these properties that Perl supports (such as \\p{Letter})
+are not supported by PCRE, nor is it permitted to prefix any of these
+properties with "Is".</p>
+
+<p>No character that is in the Unicode table has the Cn (unassigned) property.
+Instead, this property is assumed for any code point that is not in the
+Unicode table.</p>
+
+<p>Specifying caseless matching does not affect these escape sequences. For
+example, \\p{Lu} always matches only upper case letters.</p>
+
+<p>The \\X escape matches any number of Unicode characters that form an extended
+Unicode sequence. \\X is equivalent to</p>
+
+<quote><p> (?&gt;\\PM\\pM*)</p></quote>
+
+<p>That is, it matches a character without the "mark" property, followed by zero
+or more characters with the "mark" property, and treats the sequence as an
+atomic group
+(see below).
+Characters with the "mark" property are typically accents that affect the
+preceding character. None of them have codepoints less than 256, so in
+non-UTF-8 mode \\X matches any one character.</p>
+
+<p>Matching characters by Unicode property is not fast, because PCRE has to search
+a structure that contains data for over fifteen thousand characters. That is
+why the traditional escape sequences such as \\d and \\w do not use Unicode
+properties in PCRE.</p>
+
+<p><em>Resetting the match start</em></p>
+
+<p>The escape sequence \\K, which is a Perl 5.10 feature, causes any previously
+matched characters not to be included in the final matched sequence. For
+example, the pattern:</p>
+
+<quote><p> foo\\Kbar</p></quote>
+
+<p>matches "foobar", but reports that it has matched "bar". This feature is
+similar to a lookbehind assertion
+<!-- HTML &lt;a href="#lookbehind"&gt; -->
+<!-- &lt;/a&gt; -->
+(described below).
+
+However, in this case, the part of the subject before the real match does not
+have to be of fixed length, as lookbehind assertions do. The use of \\K does
+not interfere with the setting of
+captured substrings.
+For example, when the pattern</p>
+
+<quote><p> (foo)\\Kbar</p></quote>
+
+<p>matches "foobar", the first substring is still set to "foo".</p>
+
+<p><em>Simple assertions</em></p>
+
+<p>The final use of backslash is for certain simple assertions. An
+assertion specifies a condition that has to be met at a particular
+point in a match, without consuming any characters from the subject
+string. The use of subpatterns for more complicated assertions is
+described below. The backslashed assertions are:</p>
+
+<taglist>
+ <tag>\\b</tag> <item>matches at a word boundary</item>
+ <tag>\\B</tag> <item>matches when not at a word boundary</item>
+ <tag>\\A</tag> <item>matches at the start of the subject</item>
+ <tag>\\Z</tag> <item>matches at the end of the subject
+ also matches before a newline at the end of
+ the subject</item>
+ <tag>\\z</tag> <item>matches only at the end of the subject</item>
+ <tag>\\G</tag> <item>matches at the first matching position in the
+ subject</item>
+</taglist>
+
+<p>These assertions may not appear in character classes (but note that \\b has a
+different meaning, namely the backspace character, inside a character class).</p>
+
+<p>A word boundary is a position in the subject string where the current character
+and the previous character do not both match \\w or \\W (i.e. one matches
+\\w and the other matches \\W), or the start or end of the string if the
+first or last character matches \\w, respectively.</p>
+
+<p>The \\A, \\Z, and \\z assertions differ from the traditional circumflex and
+dollar (described in the next section) in that they only ever match at the very
+start and end of the subject string, whatever options are set. Thus, they are
+independent of multiline mode. These three assertions are not affected by the
+<c>notbol</c> or <c>noteol</c> options, which affect only the behaviour of the
+circumflex and dollar metacharacters. However, if the <em>startoffset</em>
+argument of <c>re:run/3</c> is non-zero, indicating that matching is to start
+at a point other than the beginning of the subject, \\A can never match. The
+difference between \\Z and \\z is that \\Z matches before a newline at the end
+of the string as well as at the very end, whereas \\z matches only at the end.</p>
+
+<p>The \\G assertion is true only when the current matching position is at the
+start point of the match, as specified by the <em>startoffset</em> argument of
+<c>re:run/3</c>. It differs from \\A when the value of <em>startoffset</em> is
+non-zero. By calling <c>re:run/3</c> multiple times with appropriate
+arguments, you can mimic Perl's /g option, and it is in this kind of
+implementation where \\G can be useful.</p>
+
+<p>Note, however, that PCRE's interpretation of \\G, as the start of the current
+match, is subtly different from Perl's, which defines it as the end of the
+previous match. In Perl, these can be different when the previously matched
+string was empty. Because PCRE does just one match at a time, it cannot
+reproduce this behaviour.</p>
+
+<p>If all the alternatives of a pattern begin with \\G, the expression is anchored
+to the starting match position, and the "anchored" flag is set in the compiled
+regular expression.</p>
+
+</section>
+
+<section><marker id="sect4"></marker><title>Circumflex and dollar</title>
+
+<p>Outside a character class, in the default matching mode, the circumflex
+character is an assertion that is true only if the current matching point is
+at the start of the subject string. If the <em>startoffset</em> argument of
+<c>re:run/3</c> is non-zero, circumflex can never match if the <c>multiline</c>
+option is unset. Inside a character class, circumflex has an entirely different
+meaning (see below).</p>
+
+<p>Circumflex need not be the first character of the pattern if a number of
+alternatives are involved, but it should be the first thing in each alternative
+in which it appears if the pattern is ever to match that branch. If all
+possible alternatives start with a circumflex, that is, if the pattern is
+constrained to match only at the start of the subject, it is said to be an
+"anchored" pattern. (There are also other constructs that can cause a pattern
+to be anchored.)</p>
+
+<p>A dollar character is an assertion that is true only if the current matching
+point is at the end of the subject string, or immediately before a newline
+at the end of the string (by default). Dollar need not be the last character of
+the pattern if a number of alternatives are involved, but it should be the last
+item in any branch in which it appears. Dollar has no special meaning in a
+character class.</p>
+
+<p>The meaning of dollar can be changed so that it matches only at the
+very end of the string, by setting the <c>dollar_endonly</c> option at
+compile time. This does not affect the \\Z assertion.</p>
+
+<p>The meanings of the circumflex and dollar characters are changed if the
+<c>multiline</c> option is set. When this is the case, a circumflex matches
+immediately after internal newlines as well as at the start of the subject
+string. It does not match after a newline that ends the string. A dollar
+matches before any newlines in the string, as well as at the very end, when
+<c>multiline</c> is set. When newline is specified as the two-character
+sequence CRLF, isolated CR and LF characters do not indicate newlines.</p>
+
+<p>For example, the pattern /^abc$/ matches the subject string
+"def\\nabc" (where \\n represents a newline) in multiline mode, but
+not otherwise. Consequently, patterns that are anchored in single line
+mode because all branches start with ^ are not anchored in multiline
+mode, and a match for circumflex is possible when the
+<em>startoffset</em> argument of <c>re:run/3</c> is non-zero. The
+<c>dollar_endonly</c> option is ignored if <c>multiline</c> is set.</p>
+
+<p>Note that the sequences \\A, \\Z, and \\z can be used to match the start and
+end of the subject in both modes, and if all branches of a pattern start with
+\\A it is always anchored, whether or not <c>multiline</c> is set.</p>
+
+
+</section>
+
+<section><marker id="sect5"></marker><title>Full stop (period, dot)</title>
+
+<p>Outside a character class, a dot in the pattern matches any one character in
+the subject string except (by default) a character that signifies the end of a
+line.
+ In UTF-8 mode, the matched character may be more than one byte long.
+</p>
+
+<p>When a line ending is defined as a single character, dot never matches that
+character; when the two-character sequence CRLF is used, dot does not match CR
+if it is immediately followed by LF, but otherwise it matches all characters
+(including isolated CRs and LFs).
+When any Unicode line endings are being
+recognized, dot does not match CR or LF or any of the other line ending
+characters.
+</p>
+
+<p>The behaviour of dot with regard to newlines can be changed. If
+the <c>dotall</c> option is set, a dot matches any one character,
+without exception. If the two-character sequence CRLF is present in
+the subject string, it takes two dots to match it.</p>
+
+<p>The handling of dot is entirely independent of the handling of
+circumflex and dollar, the only relationship being that they both
+involve newlines. Dot has no special meaning in a character class.</p>
+
+</section>
+
+<section><marker id="sect6"></marker><title>Matching a single byte</title>
+
+<p>Outside a character class, the escape sequence \\C matches any one byte, both
+in and out of UTF-8 mode. Unlike a dot, it always matches any line-ending
+characters. The feature is provided in Perl in order to match individual bytes
+in UTF-8 mode. Because it breaks up UTF-8 characters into individual bytes,
+what remains in the string may be a malformed UTF-8 string. For this reason,
+the \\C escape sequence is best avoided.</p>
+
+<p>PCRE does not allow \\C to appear in lookbehind assertions (described below),
+because in UTF-8 mode this would make it impossible to calculate the length of
+the lookbehind.</p>
+
+</section>
+
+<section><marker id="sect7"></marker><title>Square brackets and character classes</title>
+
+<p>An opening square bracket introduces a character class, terminated
+by a closing square bracket. A closing square bracket on its own is
+not special. If a closing square bracket is required as a member of
+the class, it should be the first data character in the class (after
+an initial circumflex, if present) or escaped with a backslash.</p>
+
+<p>A character class matches a single character in the subject.
+In
+UTF-8 mode, the character may occupy more than one byte.
+A matched
+character must be in the set of characters defined by the class,
+unless the first character in the class definition is a circumflex, in
+which case the subject character must not be in the set defined by the
+class. If a circumflex is actually required as a member of the class,
+ensure it is not the first character, or escape it with a
+backslash.</p>
+
+<p>For example, the character class [aeiou] matches any lower case vowel, while
+[^aeiou] matches any character that is not a lower case vowel. Note that a
+circumflex is just a convenient notation for specifying the characters that
+are in the class by enumerating those that are not. A class that starts with a
+circumflex is not an assertion: it still consumes a character from the subject
+string, and therefore it fails if the current pointer is at the end of the
+string.</p>
+
+<p>In UTF-8 mode, characters with values greater than 255 can be included in a
+class as a literal string of bytes, or by using the \\x{ escaping mechanism.</p>
+
+<p>When caseless matching is set, any letters in a class represent both their
+upper case and lower case versions, so for example, a caseless [aeiou] matches
+"A" as well as "a", and a caseless [^aeiou] does not match "A", whereas a
+caseful version would.
+In UTF-8 mode, PCRE always understands the concept of
+case for characters whose values are less than 128, so caseless matching is
+always possible. For characters with higher values, the concept of case is
+supported if PCRE is compiled with Unicode property support, but not otherwise.
+If you want to use caseless matching for characters 128 and above, you must
+ensure that PCRE is compiled with Unicode property support as well as with
+UTF-8 support.
+</p>
+
+<p>Characters that might indicate line breaks are never treated in any
+special way when matching character classes, whatever line-ending
+sequence is in use, and whatever setting of the <c>dotall</c> and
+<c>multiline</c> options is used. A class such as [^a] always matches
+one of these characters.</p>
+
+<p>The minus (hyphen) character can be used to specify a range of
+characters in a character class. For example, [d-m] matches any letter
+between d and m, inclusive. If a minus character is required in a
+class, it must be escaped with a backslash or appear in a position
+where it cannot be interpreted as indicating a range, typically as the
+first or last character in the class.</p>
+
+<p>It is not possible to have the literal character "]" as the end
+character of a range. A pattern such as [W-]46] is interpreted as a
+class of two characters ("W" and "-") followed by a literal string
+"46]", so it would match "W46]" or "-46]". However, if the "]" is
+escaped with a backslash it is interpreted as the end of range, so
+[W-\\]46] is interpreted as a class containing a range followed by two
+other characters. The octal or hexadecimal representation of "]" can
+also be used to end a range.</p>
+
+<p>Ranges operate in the collating sequence of character values. They can also be
+used for characters specified numerically, for example [\\000-\\037].
+In UTF-8
+mode, ranges can include characters whose values are greater than 255, for
+example [\\x{100}-\\x{2ff}].
+</p>
+
+<p>If a range that includes letters is used when caseless matching is set, it
+matches the letters in either case. For example, [W-c] is equivalent to
+[][\\\\^_`wxyzabc], matched caselessly
+, and in non-UTF-8 mode, if character
+tables for a French locale are in use, [\\xc8-\\xcb] matches accented E
+characters in both cases. In UTF-8 mode, PCRE supports the concept of case for
+characters with values greater than 128 only when it is compiled with Unicode
+property support.</p>
+
+<p>The character types \\d, \\D, \\p, \\P, \\s, \\S, \\w, and \\W may
+also appear in a character class, and add the characters that they
+match to the class. For example, [\\dABCDEF] matches any hexadecimal
+digit. A circumflex can conveniently be used with the upper case
+character types to specify a more restricted set of characters than
+the matching lower case type. For example, the class [^\\W_] matches
+any letter or digit, but not underscore.</p>
+
+<p>The only metacharacters that are recognized in character classes
+are backslash, hyphen (only where it can be interpreted as specifying
+a range), circumflex (only at the start), opening square bracket (only
+when it can be interpreted as introducing a POSIX class name - see the
+next section), and the terminating closing square bracket. However,
+escaping other non-alphanumeric characters does no harm.</p>
+</section>
+
+
+<section><marker id="sect8"></marker><title>POSIX character classes</title>
+
+<p>Perl supports the POSIX notation for character classes. This uses names
+enclosed by [: and :] within the enclosing square brackets. PCRE also supports
+this notation. For example,</p>
+
+<quote><p> [01[:alpha:]%]</p></quote>
+
+<p>matches "0", "1", any alphabetic character, or "%". The supported class names
+are</p>
+
+<taglist>
+ <tag>alnum</tag> <item>letters and digits</item>
+ <tag>alpha</tag> <item>letters</item>
+ <tag>ascii</tag> <item>character codes 0 - 127</item>
+ <tag>blank</tag> <item>space or tab only</item>
+ <tag>cntrl</tag> <item>control characters</item>
+ <tag>digit</tag> <item>decimal digits (same as \\d)</item>
+ <tag>graph</tag> <item>printing characters, excluding space</item>
+ <tag>lower</tag> <item>lower case letters</item>
+ <tag>print</tag> <item>printing characters, including space</item>
+ <tag>punct</tag> <item>printing characters, excluding letters and digits</item>
+ <tag>space</tag> <item>whitespace (not quite the same as \\s)</item>
+ <tag>upper</tag> <item>upper case letters</item>
+ <tag>word</tag> <item>"word" characters (same as \\w)</item>
+ <tag>xdigit</tag> <item>hexadecimal digits</item>
+</taglist>
+
+<p>The "space" characters are HT (9), LF (10), VT (11), FF (12), CR (13), and
+space (32). Notice that this list includes the VT character (code 11). This
+makes "space" different to \\s, which does not include VT (for Perl
+compatibility).</p>
+
+<p>The name "word" is a Perl extension, and "blank" is a GNU extension
+from Perl 5.8. Another Perl extension is negation, which is indicated
+by a ^ character after the colon. For example,</p>
+
+<quote><p> [12[:^digit:]]</p></quote>
+
+<p>matches "1", "2", or any non-digit. PCRE (and Perl) also recognize the POSIX
+syntax [.ch.] and [=ch=] where "ch" is a "collating element", but these are not
+supported, and an error is given if they are encountered.</p>
+
+<p>In UTF-8 mode, characters with values greater than 128 do not match any of
+the POSIX character classes.</p>
+
+</section>
+
+
+<section><marker id="sect9"></marker><title>Vertical bar</title>
+
+<p>Vertical bar characters are used to separate alternative
+patterns. For example, the pattern</p>
+
+<quote><p> gilbert|sullivan</p></quote>
+
+<p>matches either "gilbert" or "sullivan". Any number of alternatives
+may appear, and an empty alternative is permitted (matching the empty
+string). The matching process tries each alternative in turn, from
+left to right, and the first one that succeeds is used. If the
+alternatives are within a subpattern (defined below), "succeeds" means
+matching the rest of the main pattern as well as the alternative in
+the subpattern.</p>
+
+</section>
+
+<section><marker id="sect10"></marker><title>Internal option setting</title>
+
+<p>The settings of the <c>caseless</c>, <c>multiline</c>, <c>dotall</c>, and
+<c>extended</c> options (which are Perl-compatible) can be changed from within
+the pattern by a sequence of Perl option letters enclosed between "(?" and ")".
+The option letters are</p>
+
+<taglist>
+ <tag>i</tag> <item>for <c>caseless</c></item>
+ <tag>m</tag> <item>for <c>multiline</c></item>
+ <tag>s</tag> <item>for <c>dotall</c></item>
+ <tag>x</tag> <item>for <c>extended</c></item>
+</taglist>
+
+<p>For example, (?im) sets caseless, multiline matching. It is also possible to
+unset these options by preceding the letter with a hyphen, and a combined
+setting and unsetting such as (?im-sx), which sets <c>caseless</c> and
+<c>multiline</c> while unsetting <c>dotall</c> and <c>extended</c>, is also
+permitted. If a letter appears both before and after the hyphen, the option is
+unset.</p>
+
+<p>The PCRE-specific options <c>dupnames</c>, <c>ungreedy</c>, and
+<c>extra</c> can be changed in the same way as the Perl-compatible
+options by using the characters J, U and X respectively.</p>
+
+<p>When an option change occurs at top level (that is, not inside subpattern
+parentheses), the change applies to the remainder of the pattern that follows.
+If the change is placed right at the start of a pattern, PCRE extracts it into
+the global options
+<!-- XXX C Interface
+(and it will therefore show up in data extracted by the
+<c>pcre_fullinfo()</c> function).
+-->
+</p>
+
+<p>An option change within a subpattern (see below for a description
+of subpatterns) affects only that part of the current pattern that
+follows it, so</p>
+
+<quote><p> (a(?i)b)c</p></quote>
+
+<p>matches abc and aBc and no other strings (assuming <c>caseless</c>
+is not used). By this means, options can be made to have different
+settings in different parts of the pattern. Any changes made in one
+alternative do carry on into subsequent branches within the same
+subpattern. For example,</p>
+
+<quote><p> (a(?i)b|c)</p></quote>
+
+<p>matches "ab", "aB", "c", and "C", even though when matching "C" the first
+branch is abandoned before the option setting. This is because the effects of
+option settings happen at compile time. There would be some very weird
+behaviour otherwise.</p>
+
+<p><em>Note:</em> There are other PCRE-specific options that can be set by the
+application when the compile or match functions are called. In some cases the
+pattern can contain special leading sequences to override what the application
+has set or what has been defaulted. Details are given in the section entitled
+"Newline sequences" above.</p>
+
+
+</section>
+
+<section><marker id="sect11"></marker><title>Subpatterns</title>
+
+<p>Subpatterns are delimited by parentheses (round brackets), which
+can be nested. Turning part of a pattern into a subpattern does two
+things:</p>
+
+<p>1. It localizes a set of alternatives. For example, the pattern</p>
+
+<quote><p> cat(aract|erpillar|)</p></quote>
+
+<p>matches one of the words "cat", "cataract", or "caterpillar". Without the
+parentheses, it would match "cataract", "erpillar" or an empty string.</p>
+
+<p>2. It sets up the subpattern as a capturing subpattern. This means that, when
+the complete pattern matches, that portion of the subject string that matched the
+subpattern is passed back to the caller via the return value of
+<c>re:run/3</c>. Opening parentheses are counted from left to right (starting
+from 1) to obtain numbers for the capturing subpatterns.</p>
+
+<p>For example, if the string "the red king" is matched against the pattern</p>
+
+<quote><p> the ((red|white) (king|queen))</p></quote>
+
+<p>the captured substrings are "red king", "red", and "king", and are numbered 1,
+2, and 3, respectively.</p>
+
+<p>The fact that plain parentheses fulfil two functions is not always helpful.
+There are often times when a grouping subpattern is required without a
+capturing requirement. If an opening parenthesis is followed by a question mark
+and a colon, the subpattern does not do any capturing, and is not counted when
+computing the number of any subsequent capturing subpatterns. For example, if
+the string "the white queen" is matched against the pattern</p>
+
+<quote><p> the ((?:red|white) (king|queen))</p></quote>
+
+<p>the captured substrings are "white queen" and "queen", and are numbered 1 and
+2. The maximum number of capturing subpatterns is 65535.</p>
+
+<p>As a convenient shorthand, if any option settings are required at the start of
+a non-capturing subpattern, the option letters may appear between the "?" and
+the ":". Thus the two patterns</p>
+
+<list>
+<item>(?i:saturday|sunday)</item>
+<item>(?:(?i)saturday|sunday)</item>
+</list>
+
+<p>match exactly the same set of strings. Because alternative branches are tried
+from left to right, and options are not reset until the end of the subpattern
+is reached, an option setting in one branch does affect subsequent branches, so
+the above patterns match "SUNDAY" as well as "Saturday".</p>
+
+
+</section>
+
+<section><marker id="sect12"></marker><title>Duplicate subpattern numbers</title>
+
+<p>Perl 5.10 introduced a feature whereby each alternative in a subpattern uses
+the same numbers for its capturing parentheses. Such a subpattern starts with
+(?| and is itself a non-capturing subpattern. For example, consider this
+pattern:</p>
+
+<quote><p> (?|(Sat)ur|(Sun))day</p></quote>
+
+<p>Because the two alternatives are inside a (?| group, both sets of capturing
+parentheses are numbered one. Thus, when the pattern matches, you can look
+at captured substring number one, whichever alternative matched. This construct
+is useful when you want to capture part, but not all, of one of a number of
+alternatives. Inside a (?| group, parentheses are numbered as usual, but the
+number is reset at the start of each branch. The numbers of any capturing
+buffers that follow the subpattern start after the highest number used in any
+branch. The following example is taken from the Perl documentation.
+The numbers underneath show in which buffer the captured content will be
+stored.</p>
+
+<code type="none">
+ # before ---------------branch-reset----------- after
+ / ( a ) (?| x ( y ) z | (p (q) r) | (t) u (v) ) ( z ) /x
+ # 1 2 2 3 2 3 4</code>
+
+<p>A backreference or a recursive call to a numbered subpattern always
+refers to the first one in the pattern with the given number.</p>
+
+<p>An alternative approach to using this "branch reset" feature is to use
+duplicate named subpatterns, as described in the next section.</p>
+
+</section>
+
+<section><marker id="sect13"></marker><title>Named subpatterns</title>
+
+<p>Identifying capturing parentheses by number is simple, but it can be very hard
+to keep track of the numbers in complicated regular expressions. Furthermore,
+if an expression is modified, the numbers may change. To help with this
+difficulty, PCRE supports the naming of subpatterns. This feature was not
+added to Perl until release 5.10. Python had the feature earlier, and PCRE
+introduced it at release 4.0, using the Python syntax. PCRE now supports both
+the Perl and the Python syntax.</p>
+
+<p>In PCRE, a subpattern can be named in one of three ways:
+(?&lt;name&gt;...) or (?'name'...) as in Perl, or (?P&lt;name&gt;...)
+as in Python. References to capturing parentheses from other parts of
+the pattern, such as backreferences, recursion, and conditions, can be
+made by name as well as by number.</p>
+
+<p>Names consist of up to 32 alphanumeric characters and underscores. Named
+capturing parentheses are still allocated numbers as well as names, exactly as
+if the names were not present.
+<!-- XXX C Interface
+The PCRE API provides function calls for
+extracting the name-to-number translation table from a compiled pattern. There
+is also a convenience function for extracting a captured substring by name.
+-->
+The <c>capture</c> specification to <c>re:run/3</c> can use named values if they are present in the regular expression.
+</p>
+
+<p>By default, a name must be unique within a pattern, but it is possible to relax
+this constraint by setting the <c>dupnames</c> option at compile time. This can
+be useful for patterns where only one instance of the named parentheses can
+match. Suppose you want to match the name of a weekday, either as a 3-letter
+abbreviation or as the full name, and in both cases you want to extract the
+abbreviation. This pattern (ignoring the line breaks) does the job:</p>
+
+<code type="none">
+ (?&lt;DN&gt;Mon|Fri|Sun)(?:day)?|
+ (?&lt;DN&gt;Tue)(?:sday)?|
+ (?&lt;DN&gt;Wed)(?:nesday)?|
+ (?&lt;DN&gt;Thu)(?:rsday)?|
+ (?&lt;DN&gt;Sat)(?:urday)?</code>
+
+<p>There are five capturing substrings, but only one is ever set after a match.
+(An alternative way of solving this problem is to use a "branch reset"
+subpattern, as described in the previous section.)</p>
+
+<!-- XXX C Interface
+
+<p>The convenience function for extracting the data by name returns the substring
+for the first (and in this example, the only) subpattern of that name that
+matched. This saves searching to find which numbered subpattern it was. If you
+make a reference to a non-unique named subpattern from elsewhere in the
+pattern, the one that corresponds to the lowest number is used. For further
+details of the interfaces for handling named subpatterns, see the
+<em>pcreapi</em>
+
+documentation.</p>
+-->
+
+<p>In case of capturing named subpatterns which are not unique, the first occurrence is returned from <c>re:exec/3</c>, if the name is specified int the <c>values</c> part of the <c>capture</c> statement.</p>
+
+</section>
+
+<section><marker id="sect14"></marker><title>Repetition</title>
+
+<p>Repetition is specified by quantifiers, which can follow any of the
+following items:</p>
+
+<list>
+ <item>a literal data character</item>
+ <item>the dot metacharacter</item>
+ <item>the \\C escape sequence</item>
+ <item>the \\X escape sequence
+(in UTF-8 mode with Unicode properties)
+ </item>
+ <item>the \\R escape sequence</item>
+ <item>an escape such as \\d that matches a single character</item>
+ <item>a character class</item>
+ <item>a back reference (see next section)</item>
+ <item>a parenthesized subpattern (unless it is an assertion)</item>
+</list>
+
+<p>The general repetition quantifier specifies a minimum and maximum number of
+permitted matches, by giving the two numbers in curly brackets (braces),
+separated by a comma. The numbers must be less than 65536, and the first must
+be less than or equal to the second. For example:</p>
+
+<quote><p> z{2,4}</p></quote>
+
+<p>matches "zz", "zzz", or "zzzz". A closing brace on its own is not a special
+character. If the second number is omitted, but the comma is present, there is
+no upper limit; if the second number and the comma are both omitted, the
+quantifier specifies an exact number of required matches. Thus</p>
+
+<quote><p> [aeiou]{3,}</p></quote>
+
+<p>matches at least 3 successive vowels, but may match many more, while</p>
+
+<quote><p> \\d{8}</p></quote>
+
+<p>matches exactly 8 digits. An opening curly bracket that appears in a position
+where a quantifier is not allowed, or one that does not match the syntax of a
+quantifier, is taken as a literal character. For example, {,6} is not a
+quantifier, but a literal string of four characters.</p>
+
+<p>In UTF-8 mode, quantifiers apply to UTF-8 characters rather than to individual
+bytes. Thus, for example, \\x{100}{2} matches two UTF-8 characters, each of
+which is represented by a two-byte sequence. Similarly, when Unicode property
+support is available, \\X{3} matches three Unicode extended sequences, each of
+which may be several bytes long (and they may be of different lengths).</p>
+
+<p>The quantifier {0} is permitted, causing the expression to behave as if the
+previous item and the quantifier were not present.</p>
+
+<p>For convenience, the three most common quantifiers have single-character
+abbreviations:</p>
+
+<taglist>
+ <tag>*</tag> <item>is equivalent to {0,}</item>
+ <tag>+</tag> <item>is equivalent to {1,}</item>
+ <tag>?</tag> <item>is equivalent to {0,1}</item>
+</taglist>
+
+<p>It is possible to construct infinite loops by following a
+subpattern that can match no characters with a quantifier that has no
+upper limit, for example:</p>
+
+<quote><p> (a?)*</p></quote>
+
+<p>Earlier versions of Perl and PCRE used to give an error at compile time for
+such patterns. However, because there are cases where this can be useful, such
+patterns are now accepted, but if any repetition of the subpattern does in fact
+match no characters, the loop is forcibly broken.</p>
+
+<p>By default, the quantifiers are "greedy", that is, they match as much as
+possible (up to the maximum number of permitted times), without causing the
+rest of the pattern to fail. The classic example of where this gives problems
+is in trying to match comments in C programs. These appear between /* and */
+and within the comment, individual * and / characters may appear. An attempt to
+match C comments by applying the pattern</p>
+
+<quote><p> /\\*.*\\*/</p></quote>
+
+<p>to the string</p>
+
+<quote><p> /* first comment */ not comment /* second comment */</p></quote>
+
+<p>fails, because it matches the entire string owing to the greediness of the .*
+item.</p>
+
+<p>However, if a quantifier is followed by a question mark, it ceases to be
+greedy, and instead matches the minimum number of times possible, so the
+pattern</p>
+
+<quote><p> /\\*.*?\\*/</p></quote>
+
+<p>does the right thing with the C comments. The meaning of the various
+quantifiers is not otherwise changed, just the preferred number of matches.
+Do not confuse this use of question mark with its use as a quantifier in its
+own right. Because it has two uses, it can sometimes appear doubled, as in</p>
+
+<quote><p> \\d??\\d</p></quote>
+
+<p>which matches one digit by preference, but can match two if that is the only
+way the rest of the pattern matches.</p>
+
+<p>If the <c>ungreedy</c> option is set (an option that is not available in Perl),
+the quantifiers are not greedy by default, but individual ones can be made
+greedy by following them with a question mark. In other words, it inverts the
+default behaviour.</p>
+
+<p>When a parenthesized subpattern is quantified with a minimum repeat count that
+is greater than 1 or with a limited maximum, more memory is required for the
+compiled pattern, in proportion to the size of the minimum or maximum.</p>
+
+<p>If a pattern starts with .* or .{0,} and the <c>dotall</c> option (equivalent
+to Perl's /s) is set, thus allowing the dot to match newlines, the pattern is
+implicitly anchored, because whatever follows will be tried against every
+character position in the subject string, so there is no point in retrying the
+overall match at any position after the first. PCRE normally treats such a
+pattern as though it were preceded by \\A.</p>
+
+<p>In cases where it is known that the subject string contains no newlines, it is
+worth setting <c>dotall</c> in order to obtain this optimization, or
+alternatively using ^ to indicate anchoring explicitly.</p>
+
+<p>However, there is one situation where the optimization cannot be used. When .*
+is inside capturing parentheses that are the subject of a backreference
+elsewhere in the pattern, a match at the start may fail where a later one
+succeeds. Consider, for example:</p>
+
+<quote><p> (.*)abc\\1</p></quote>
+
+<p>If the subject is "xyz123abc123" the match point is the fourth character. For
+this reason, such a pattern is not implicitly anchored.</p>
+
+<p>When a capturing subpattern is repeated, the value captured is the substring
+that matched the final iteration. For example, after</p>
+
+<quote><p> (tweedle[dume]{3}\\s*)+</p></quote>
+
+<p>has matched "tweedledum tweedledee" the value of the captured substring is
+"tweedledee". However, if there are nested capturing subpatterns, the
+corresponding captured values may have been set in previous iterations. For
+example, after</p>
+
+<quote><p> /(a|(b))+/</p></quote>
+
+<p>matches "aba" the value of the second captured substring is "b".</p>
+
+
+</section>
+
+<section><marker id="sect15"></marker><title>Atomic grouping and possessive quantifiers</title>
+
+<p>With both maximizing ("greedy") and minimizing ("ungreedy" or "lazy")
+repetition, failure of what follows normally causes the repeated item to be
+re-evaluated to see if a different number of repeats allows the rest of the
+pattern to match. Sometimes it is useful to prevent this, either to change the
+nature of the match, or to cause it fail earlier than it otherwise might, when
+the author of the pattern knows there is no point in carrying on.</p>
+
+<p>Consider, for example, the pattern \\d+foo when applied to the subject line</p>
+
+<quote><p> 123456bar</p></quote>
+
+<p>After matching all 6 digits and then failing to match "foo", the normal
+action of the matcher is to try again with only 5 digits matching the \\d+
+item, and then with 4, and so on, before ultimately failing. "Atomic grouping"
+(a term taken from Jeffrey Friedl's book) provides the means for specifying
+that once a subpattern has matched, it is not to be re-evaluated in this way.</p>
+
+<p>If we use atomic grouping for the previous example, the matcher gives up
+immediately on failing to match "foo" the first time. The notation is a kind of
+special parenthesis, starting with (?&gt; as in this example:</p>
+
+<quote><p> (?&gt;\\d+)foo</p></quote>
+
+<p>This kind of parenthesis "locks up" the part of the pattern it contains once
+it has matched, and a failure further into the pattern is prevented from
+backtracking into it. Backtracking past it to previous items, however, works as
+normal.</p>
+
+<p>An alternative description is that a subpattern of this type matches the string
+of characters that an identical standalone pattern would match, if anchored at
+the current point in the subject string.</p>
+
+<p>Atomic grouping subpatterns are not capturing subpatterns. Simple cases such as
+the above example can be thought of as a maximizing repeat that must swallow
+everything it can. So, while both \\d+ and \\d+? are prepared to adjust the
+number of digits they match in order to make the rest of the pattern match,
+(?&gt;\\d+) can only match an entire sequence of digits.</p>
+
+<p>Atomic groups in general can of course contain arbitrarily complicated
+subpatterns, and can be nested. However, when the subpattern for an atomic
+group is just a single repeated item, as in the example above, a simpler
+notation, called a "possessive quantifier" can be used. This consists of an
+additional + character following a quantifier. Using this notation, the
+previous example can be rewritten as</p>
+
+<quote><p> \\d++foo</p></quote>
+
+<p>Note that a possessive quantifier can be used with an entire group, for
+example:</p>
+
+<quote><p> (abc|xyz){2,3}+</p></quote>
+
+<p>Possessive quantifiers are always greedy; the setting of the <c>ungreedy</c>
+option is ignored. They are a convenient notation for the simpler forms of
+atomic group. However, there is no difference in the meaning of a possessive
+quantifier and the equivalent atomic group, though there may be a performance
+difference; possessive quantifiers should be slightly faster.</p>
+
+<p>The possessive quantifier syntax is an extension to the Perl 5.8 syntax.
+Jeffrey Friedl originated the idea (and the name) in the first edition of his
+book. Mike McCloskey liked it, so implemented it when he built Sun's Java
+package, and PCRE copied it from there. It ultimately found its way into Perl
+at release 5.10.</p>
+
+<p>PCRE has an optimization that automatically "possessifies" certain simple
+pattern constructs. For example, the sequence A+B is treated as A++B because
+there is no point in backtracking into a sequence of A's when B must follow.</p>
+
+<p>When a pattern contains an unlimited repeat inside a subpattern that can itself
+be repeated an unlimited number of times, the use of an atomic group is the
+only way to avoid some failing matches taking a very long time indeed. The
+pattern</p>
+
+<quote><p> (\\D+|&lt;\\d+&gt;)*[!?]</p></quote>
+
+<p>matches an unlimited number of substrings that either consist of non-digits, or
+digits enclosed in &lt;&gt;, followed by either ! or ?. When it matches, it runs
+quickly. However, if it is applied to</p>
+
+<quote><p> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</p></quote>
+
+<p>it takes a long time before reporting failure. This is because the string can
+be divided between the internal \\D+ repeat and the external * repeat in a
+large number of ways, and all have to be tried. (The example uses [!?] rather
+than a single character at the end, because both PCRE and Perl have an
+optimization that allows for fast failure when a single character is used. They
+remember the last single character that is required for a match, and fail early
+if it is not present in the string.) If the pattern is changed so that it uses
+an atomic group, like this:</p>
+
+<quote><p> ((?&gt;\\D+)|&lt;\\d+&gt;)*[!?]</p></quote>
+
+<p>sequences of non-digits cannot be broken, and failure happens quickly.</p>
+
+</section>
+
+<section><marker id="sect16"></marker><title>Back references</title>
+
+<p>Outside a character class, a backslash followed by a digit greater than 0 (and
+possibly further digits) is a back reference to a capturing subpattern earlier
+(that is, to its left) in the pattern, provided there have been that many
+previous capturing left parentheses.</p>
+
+<p>However, if the decimal number following the backslash is less than 10, it is
+always taken as a back reference, and causes an error only if there are not
+that many capturing left parentheses in the entire pattern. In other words, the
+parentheses that are referenced need not be to the left of the reference for
+numbers less than 10. A "forward back reference" of this type can make sense
+when a repetition is involved and the subpattern to the right has participated
+in an earlier iteration.</p>
+
+<p>It is not possible to have a numerical "forward back reference" to
+a subpattern whose number is 10 or more using this syntax because a
+sequence such as \\50 is interpreted as a character defined in
+octal. See the subsection entitled "Non-printing characters" above for
+further details of the handling of digits following a backslash. There
+is no such problem when named parentheses are used. A back reference
+to any subpattern is possible using named parentheses (see below).</p>
+
+<p>Another way of avoiding the ambiguity inherent in the use of digits
+following a backslash is to use the \\g escape sequence, which is a
+feature introduced in Perl 5.10. This escape must be followed by an
+unsigned number or a negative number, optionally enclosed in
+braces. These examples are all identical:</p>
+
+<list>
+ <item>(ring), \\1</item>
+ <item>(ring), \\g1</item>
+ <item>(ring), \\g{1}</item>
+</list>
+
+<p>An unsigned number specifies an absolute reference without the
+ambiguity that is present in the older syntax. It is also useful when
+literal digits follow the reference. A negative number is a relative
+reference. Consider this example:</p>
+
+<quote><p> (abc(def)ghi)\\g{-1}</p></quote>
+
+<p>The sequence \\g{-1} is a reference to the most recently started capturing
+subpattern before \\g, that is, is it equivalent to \\2. Similarly, \\g{-2}
+would be equivalent to \\1. The use of relative references can be helpful in
+long patterns, and also in patterns that are created by joining together
+fragments that contain references within themselves.</p>
+
+<p>A back reference matches whatever actually matched the capturing
+subpattern in the current subject string, rather than anything
+matching the subpattern itself (see "Subpatterns as subroutines" below
+for a way of doing that). So the pattern</p>
+
+<quote><p> (sens|respons)e and \\1ibility</p></quote>
+
+<p>matches "sense and sensibility" and "response and responsibility", but not
+"sense and responsibility". If caseful matching is in force at the time of the
+back reference, the case of letters is relevant. For example,</p>
+
+<quote><p> ((?i)rah)\\s+\\1</p></quote>
+
+<p>matches "rah rah" and "RAH RAH", but not "RAH rah", even though the original
+capturing subpattern is matched caselessly.</p>
+
+<p>There are several different ways of writing back references to named
+subpatterns. The .NET syntax \\k{name} and the Perl syntax \\k&lt;name&gt; or
+\\k'name' are supported, as is the Python syntax (?P=name). Perl 5.10's unified
+back reference syntax, in which \\g can be used for both numeric and named
+references, is also supported. We could rewrite the above example in any of
+the following ways:</p>
+
+<list>
+ <item>(?&lt;p1&gt;(?i)rah)\\s+\\k&lt;p1&gt;</item>
+ <item>(?'p1'(?i)rah)\\s+\\k{p1}</item>
+ <item>(?P&lt;p1&gt;(?i)rah)\\s+(?P=p1)</item>
+ <item>(?&lt;p1&gt;(?i)rah)\\s+\\g{p1}</item>
+</list>
+
+<p>A subpattern that is referenced by name may appear in the pattern before or
+after the reference.</p>
+
+<p>There may be more than one back reference to the same subpattern. If a
+subpattern has not actually been used in a particular match, any back
+references to it always fail. For example, the pattern</p>
+
+<quote><p> (a|(bc))\\2</p></quote>
+
+<p>always fails if it starts to match "a" rather than "bc". Because
+there may be many capturing parentheses in a pattern, all digits
+following the backslash are taken as part of a potential back
+reference number. If the pattern continues with a digit character,
+some delimiter must be used to terminate the back reference. If the
+<c>extended</c> option is set, this can be whitespace. Otherwise an
+empty comment (see "Comments" below) can be used.</p>
+
+<p>A back reference that occurs inside the parentheses to which it refers fails
+when the subpattern is first used, so, for example, (a\\1) never matches.
+However, such references can be useful inside repeated subpatterns. For
+example, the pattern</p>
+
+<quote><p> (a|b\\1)+</p></quote>
+
+<p>matches any number of "a"s and also "aba", "ababbaa" etc. At each iteration of
+the subpattern, the back reference matches the character string corresponding
+to the previous iteration. In order for this to work, the pattern must be such
+that the first iteration does not need to match the back reference. This can be
+done using alternation, as in the example above, or by a quantifier with a
+minimum of zero.</p>
+
+</section>
+
+<section><marker id="sect17"></marker><title>Assertions</title>
+
+<p>An assertion is a test on the characters following or preceding the current
+matching point that does not actually consume any characters. The simple
+assertions coded as \\b, \\B, \\A, \\G, \\Z, \\z, ^ and $ are described
+above.</p>
+
+
+<p>More complicated assertions are coded as subpatterns. There are two kinds:
+those that look ahead of the current position in the subject string, and those
+that look behind it. An assertion subpattern is matched in the normal way,
+except that it does not cause the current matching position to be changed.</p>
+
+<p>Assertion subpatterns are not capturing subpatterns, and may not be repeated,
+because it makes no sense to assert the same thing several times. If any kind
+of assertion contains capturing subpatterns within it, these are counted for
+the purposes of numbering the capturing subpatterns in the whole pattern.
+However, substring capturing is carried out only for positive assertions,
+because it does not make sense for negative assertions.</p>
+
+<p><em>Lookahead assertions</em></p>
+
+<p>Lookahead assertions start with (?= for positive assertions and (?! for
+negative assertions. For example,</p>
+
+<quote><p> \\w+(?=;)</p></quote>
+
+<p>matches a word followed by a semicolon, but does not include the semicolon in
+the match, and</p>
+
+<quote><p> foo(?!bar)</p></quote>
+
+<p>matches any occurrence of "foo" that is not followed by "bar". Note that the
+apparently similar pattern</p>
+
+<quote><p> (?!foo)bar</p></quote>
+
+<p>does not find an occurrence of "bar" that is preceded by something other than
+"foo"; it finds any occurrence of "bar" whatsoever, because the assertion
+(?!foo) is always true when the next three characters are "bar". A
+lookbehind assertion is needed to achieve the other effect.</p>
+
+<p>If you want to force a matching failure at some point in a pattern, the most
+convenient way to do it is with (?!) because an empty string always matches, so
+an assertion that requires there not to be an empty string must always fail.</p>
+
+
+<p><em>Lookbehind assertions</em></p>
+
+<p>Lookbehind assertions start with (?&lt;= for positive assertions and (?&lt;! for
+negative assertions. For example,</p>
+
+<quote><p> (?&lt;!foo)bar</p></quote>
+
+<p>does find an occurrence of "bar" that is not preceded by "foo". The contents of
+a lookbehind assertion are restricted such that all the strings it matches must
+have a fixed length. However, if there are several top-level alternatives, they
+do not all have to have the same fixed length. Thus</p>
+
+<quote><p> (?&lt;=bullock|donkey)</p></quote>
+
+<p>is permitted, but</p>
+
+<quote><p> (?&lt;!dogs?|cats?)</p></quote>
+
+<p>causes an error at compile time. Branches that match different length strings
+are permitted only at the top level of a lookbehind assertion. This is an
+extension compared with Perl (at least for 5.8), which requires all branches to
+match the same length of string. An assertion such as</p>
+
+<quote><p> (?&lt;=ab(c|de))</p></quote>
+
+<p>is not permitted, because its single top-level branch can match two different
+lengths, but it is acceptable if rewritten to use two top-level branches:</p>
+
+<quote><p> (?&lt;=abc|abde)</p></quote>
+
+<p>In some cases, the Perl 5.10 escape sequence \\K (see above) can be
+used instead of a lookbehind assertion; this is not restricted to a
+fixed-length.</p>
+
+<p>The implementation of lookbehind assertions is, for each alternative, to
+temporarily move the current position back by the fixed length and then try to
+match. If there are insufficient characters before the current position, the
+assertion fails.</p>
+
+<p>PCRE does not allow the \\C escape (which matches a single byte in UTF-8 mode)
+to appear in lookbehind assertions, because it makes it impossible to calculate
+the length of the lookbehind. The \\X and \\R escapes, which can match
+different numbers of bytes, are also not permitted.</p>
+
+<p>Possessive quantifiers can be used in conjunction with lookbehind assertions to
+specify efficient matching at the end of the subject string. Consider a simple
+pattern such as</p>
+
+<quote><p> abcd$</p></quote>
+
+<p>when applied to a long string that does not match. Because matching proceeds
+from left to right, PCRE will look for each "a" in the subject and then see if
+what follows matches the rest of the pattern. If the pattern is specified as</p>
+
+<quote><p> ^.*abcd$</p></quote>
+
+<p>the initial .* matches the entire string at first, but when this fails (because
+there is no following "a"), it backtracks to match all but the last character,
+then all but the last two characters, and so on. Once again the search for "a"
+covers the entire string, from right to left, so we are no better off. However,
+if the pattern is written as</p>
+
+<quote><p> ^.*+(?&lt;=abcd)</p></quote>
+
+<p>there can be no backtracking for the .*+ item; it can match only the entire
+string. The subsequent lookbehind assertion does a single test on the last four
+characters. If it fails, the match fails immediately. For long strings, this
+approach makes a significant difference to the processing time.</p>
+
+<p><em>Using multiple assertions</em></p>
+
+<p>Several assertions (of any sort) may occur in succession. For example,</p>
+
+<quote><p> (?&lt;=\\d{3})(?&lt;!999)foo</p></quote>
+
+<p>matches "foo" preceded by three digits that are not "999". Notice
+that each of the assertions is applied independently at the same point
+in the subject string. First there is a check that the previous three
+characters are all digits, and then there is a check that the same
+three characters are not "999". This pattern does <em>not</em> match
+"foo" preceded by six characters, the first of which are digits and
+the last three of which are not "999". For example, it doesn't match
+"123abcfoo". A pattern to do that is</p>
+
+<quote><p> (?&lt;=\\d{3}...)(?&lt;!999)foo</p></quote>
+
+<p>This time the first assertion looks at the preceding six
+characters, checking that the first three are digits, and then the
+second assertion checks that the preceding three characters are not
+"999".</p>
+
+<p>Assertions can be nested in any combination. For example,</p>
+
+<quote><p> (?&lt;=(?&lt;!foo)bar)baz</p></quote>
+
+<p>matches an occurrence of "baz" that is preceded by "bar" which in
+turn is not preceded by "foo", while</p>
+
+<quote><p> (?&lt;=\\d{3}(?!999)...)foo</p></quote>
+
+<p>is another pattern that matches "foo" preceded by three digits and any three
+characters that are not "999".</p>
+
+</section>
+
+<section><marker id="sect18"></marker><title>Conditional subpatterns</title>
+
+<p>It is possible to cause the matching process to obey a subpattern
+conditionally or to choose between two alternative subpatterns, depending on
+the result of an assertion, or whether a previous capturing subpattern matched
+or not. The two possible forms of conditional subpattern are</p>
+
+<list>
+<item>(?(condition)yes-pattern)</item>
+<item>(?(condition)yes-pattern|no-pattern)</item>
+</list>
+
+<p>If the condition is satisfied, the yes-pattern is used; otherwise the
+no-pattern (if present) is used. If there are more than two alternatives in the
+subpattern, a compile-time error occurs.</p>
+
+<p>There are four kinds of condition: references to subpatterns, references to
+recursion, a pseudo-condition called DEFINE, and assertions.</p>
+
+
+<p><em>Checking for a used subpattern by number</em></p>
+
+<p>If the text between the parentheses consists of a sequence of
+digits, the condition is true if the capturing subpattern of that
+number has previously matched. An alternative notation is to precede
+the digits with a plus or minus sign. In this case, the subpattern
+number is relative rather than absolute. The most recently opened
+parentheses can be referenced by (?(-1), the next most recent by
+(?(-2), and so on. In looping constructs it can also make sense to
+refer to subsequent groups with constructs such as (?(+2).</p>
+
+<p>Consider the following pattern, which contains non-significant
+whitespace to make it more readable (assume the <c>extended</c>
+option) and to divide it into three parts for ease of discussion:</p>
+
+<quote><p> ( \\( )? [^()]+ (?(1) \\) )</p></quote>
+
+<p>The first part matches an optional opening parenthesis, and if that
+character is present, sets it as the first captured substring. The second part
+matches one or more characters that are not parentheses. The third part is a
+conditional subpattern that tests whether the first set of parentheses matched
+or not. If they did, that is, if subject started with an opening parenthesis,
+the condition is true, and so the yes-pattern is executed and a closing
+parenthesis is required. Otherwise, since no-pattern is not present, the
+subpattern matches nothing. In other words, this pattern matches a sequence of
+non-parentheses, optionally enclosed in parentheses.</p>
+
+<p>If you were embedding this pattern in a larger one, you could use a relative
+reference:</p>
+
+<quote><p> ...other stuff... ( \\( )? [^()]+ (?(-1) \\) ) ...</p></quote>
+
+<p>This makes the fragment independent of the parentheses in the larger pattern.</p>
+
+<p><em>Checking for a used subpattern by name</em></p>
+
+<p>Perl uses the syntax (?(&lt;name&gt;)...) or (?('name')...) to test
+for a used subpattern by name. For compatibility with earlier versions
+of PCRE, which had this facility before Perl, the syntax (?(name)...)
+is also recognized. However, there is a possible ambiguity with this
+syntax, because subpattern names may consist entirely of digits. PCRE
+looks first for a named subpattern; if it cannot find one and the name
+consists entirely of digits, PCRE looks for a subpattern of that
+number, which must be greater than zero. Using subpattern names that
+consist entirely of digits is not recommended.</p>
+
+<p>Rewriting the above example to use a named subpattern gives this:</p>
+
+<quote><p> (?&lt;OPEN&gt; \\( )? [^()]+ (?(&lt;OPEN&gt;) \\) )</p></quote>
+
+<p><em>Checking for pattern recursion</em></p>
+
+<p>If the condition is the string (R), and there is no subpattern with
+the name R, the condition is true if a recursive call to the whole
+pattern or any subpattern has been made. If digits or a name preceded
+by ampersand follow the letter R, for example:</p>
+
+<quote><p> (?(R3)...) or (?(R&amp;name)...)</p></quote>
+
+<p>the condition is true if the most recent recursion is into the
+subpattern whose number or name is given. This condition does not
+check the entire recursion stack.</p>
+
+<p>At "top level", all these recursion test conditions are false. Recursive
+patterns are described below.</p>
+
+<p><em>Defining subpatterns for use by reference only</em></p>
+
+<p>If the condition is the string (DEFINE), and there is no subpattern with the
+name DEFINE, the condition is always false. In this case, there may be only one
+alternative in the subpattern. It is always skipped if control reaches this
+point in the pattern; the idea of DEFINE is that it can be used to define
+"subroutines" that can be referenced from elsewhere. (The use of "subroutines"
+is described below.) For example, a pattern to match an IPv4 address could be
+written like this (ignore whitespace and line breaks):</p>
+
+<quote><p> (?(DEFINE) (?&lt;byte&gt; 2[0-4]\\d | 25[0-5] | 1\\d\\d | [1-9]?\\d) )
+ \\b (?&amp;byte) (\\.(?&amp;byte)){3} \\b</p></quote>
+
+<p>The first part of the pattern is a DEFINE group inside which a another group
+named "byte" is defined. This matches an individual component of an IPv4
+address (a number less than 256). When matching takes place, this part of the
+pattern is skipped because DEFINE acts like a false condition.</p>
+
+<p>The rest of the pattern uses references to the named group to match the four
+dot-separated components of an IPv4 address, insisting on a word boundary at
+each end.</p>
+
+<p><em>Assertion conditions</em></p>
+
+<p>If the condition is not in any of the above formats, it must be an
+assertion. This may be a positive or negative lookahead or lookbehind
+assertion. Consider this pattern, again containing non-significant
+whitespace, and with the two alternatives on the second line:</p>
+
+<code type="none">
+ (?(?=[^a-z]*[a-z])
+ \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} )</code>
+
+<p>The condition is a positive lookahead assertion that matches an optional
+sequence of non-letters followed by a letter. In other words, it tests for the
+presence of at least one letter in the subject. If a letter is found, the
+subject is matched against the first alternative; otherwise it is matched
+against the second. This pattern matches strings in one of the two forms
+dd-aaa-dd or dd-dd-dd, where aaa are letters and dd are digits.</p>
+
+
+</section>
+
+<section><marker id="sect19"></marker><title>Comments</title>
+
+<p>The sequence (?# marks the start of a comment that continues up to the next
+closing parenthesis. Nested parentheses are not permitted. The characters
+that make up a comment play no part in the pattern matching at all.</p>
+
+<p>If the <c>extended</c> option is set, an unescaped # character outside a
+character class introduces a comment that continues to immediately after the
+next newline in the pattern.</p>
+
+
+</section>
+
+<section><marker id="sect20"></marker><title>Recursive patterns</title>
+
+<p>Consider the problem of matching a string in parentheses, allowing for
+unlimited nested parentheses. Without the use of recursion, the best that can
+be done is to use a pattern that matches up to some fixed depth of nesting. It
+is not possible to handle an arbitrary nesting depth.</p>
+
+<p>For some time, Perl has provided a facility that allows regular
+expressions to recurse (amongst other things). It does this by
+interpolating Perl code in the expression at run time, and the code
+can refer to the expression itself. A Perl pattern using code
+interpolation to solve the parentheses problem can be created like
+this:</p>
+
+<quote><p> $re = qr{\\( (?: (?&gt;[^()]+) | (?p{$re}) )* \\)}x;</p></quote>
+
+<p>The (?p{...}) item interpolates Perl code at run time, and in this
+case refers recursively to the pattern in which it appears.</p>
+
+<p>Obviously, PCRE cannot support the interpolation of Perl code. Instead, it
+supports special syntax for recursion of the entire pattern, and also for
+individual subpattern recursion. After its introduction in PCRE and Python,
+this kind of recursion was introduced into Perl at release 5.10.</p>
+
+<p>A special item that consists of (? followed by a number greater
+than zero and a closing parenthesis is a recursive call of the
+subpattern of the given number, provided that it occurs inside that
+subpattern. (If not, it is a "subroutine" call, which is described in
+the next section.) The special item (?R) or (?0) is a recursive call
+of the entire regular expression.</p>
+
+<p>In PCRE (like Python, but unlike Perl), a recursive subpattern call
+is always treated as an atomic group. That is, once it has matched
+some of the subject string, it is never re-entered, even if it
+contains untried alternatives and there is a subsequent matching
+failure.</p>
+
+<p>This PCRE pattern solves the nested parentheses problem (assume the
+<c>extended</c> option is set so that whitespace is ignored):</p>
+
+<quote><p> \\( ( (?&gt;[^()]+) | (?R) )* \\)</p></quote>
+
+<p>First it matches an opening parenthesis. Then it matches any number
+of substrings which can either be a sequence of non-parentheses, or a
+recursive match of the pattern itself (that is, a correctly
+parenthesized substring). Finally there is a closing parenthesis.</p>
+
+<p>If this were part of a larger pattern, you would not want to
+recurse the entire pattern, so instead you could use this:</p>
+
+<quote><p> ( \\( ( (?&gt;[^()]+) | (?1) )* \\) )</p></quote>
+
+<p>We have put the pattern into parentheses, and caused the recursion
+to refer to them instead of the whole pattern.</p>
+
+<p>In a larger pattern, keeping track of parenthesis numbers can be
+tricky. This is made easier by the use of relative references. (A Perl
+5.10 feature.) Instead of (?1) in the pattern above you can write
+(?-2) to refer to the second most recently opened parentheses
+preceding the recursion. In other words, a negative number counts
+capturing parentheses leftwards from the point at which it is
+encountered.</p>
+
+<p>It is also possible to refer to subsequently opened parentheses, by
+writing references such as (?+2). However, these cannot be recursive
+because the reference is not inside the parentheses that are
+referenced. They are always "subroutine" calls, as described in the
+next section.</p>
+
+<p>An alternative approach is to use named parentheses instead. The
+Perl syntax for this is (?&amp;name); PCRE's earlier syntax
+(?P&gt;name) is also supported. We could rewrite the above example as
+follows:</p>
+
+<quote><p> (?&lt;pn&gt; \\( ( (?&gt;[^()]+) | (?&amp;pn) )* \\) )</p></quote>
+
+<p>If there is more than one subpattern with the same name, the earliest one is
+used.</p>
+
+<p>This particular example pattern that we have been looking at contains nested
+unlimited repeats, and so the use of atomic grouping for matching strings of
+non-parentheses is important when applying the pattern to strings that do not
+match. For example, when this pattern is applied to</p>
+
+<quote><p> (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa()</p></quote>
+
+<p>it yields "no match" quickly. However, if atomic grouping is not used,
+the match runs for a very long time indeed because there are so many different
+ways the + and * repeats can carve up the subject, and all have to be tested
+before failure can be reported.</p>
+
+<p>At the end of a match, the values set for any capturing subpatterns are those
+from the outermost level of the recursion at which the subpattern value is set.
+
+<!-- XXX C Interface
+If you want to obtain intermediate values, a callout function can be used (see
+below and the
+
+<em>pcrecallout</em>
+
+documentation).
+-->
+If the pattern above is matched against</p>
+
+<quote><p> (ab(cd)ef)</p></quote>
+
+<p>the value for the capturing parentheses is "ef", which is the last value taken
+on at the top level. If additional parentheses are added, giving</p>
+
+<code type="none">
+ \\( ( ( (?&gt;[^()]+) | (?R) )* ) \\)
+ ^ ^
+ ^ ^</code>
+
+<p>the string they capture is "ab(cd)ef", the contents of the top level
+parentheses.
+<!-- XXX C interface
+If there are more than 15 capturing parentheses in a pattern, PCRE
+has to obtain extra memory to store data during a recursion, which it does by
+using <em>pcre_malloc</em>, freeing it via <em>pcre_free</em> afterwards. If no
+memory can be obtained, the match fails with the <c>error_nomemory</c> error.</p>
+-->
+</p>
+
+<p>Do not confuse the (?R) item with the condition (R), which tests
+for recursion. Consider this pattern, which matches text in angle
+brackets, allowing for arbitrary nesting. Only digits are allowed in
+nested brackets (that is, when recursing), whereas any characters are
+permitted at the outer level.</p>
+
+<quote><p> &lt; (?: (?(R) \\d++ | [^&lt;&gt;]*+) | (?R)) * &gt;</p></quote>
+
+<p>In this pattern, (?(R) is the start of a conditional subpattern,
+with two different alternatives for the recursive and non-recursive
+cases. The (?R) item is the actual recursive call.</p>
+
+</section>
+
+<section><marker id="sect21"></marker><title>Subpatterns as subroutines</title>
+
+<p>If the syntax for a recursive subpattern reference (either by number or by
+name) is used outside the parentheses to which it refers, it operates like a
+subroutine in a programming language. The "called" subpattern may be defined
+before or after the reference. A numbered reference can be absolute or
+relative, as in these examples:</p>
+
+<list>
+ <item>(...(absolute)...)...(?2)...</item>
+ <item>(...(relative)...)...(?-1)...</item>
+ <item>(...(?+1)...(relative)...</item>
+</list>
+
+<p>An earlier example pointed out that the pattern</p>
+
+<quote><p> (sens|respons)e and \\1ibility</p></quote>
+
+<p>matches "sense and sensibility" and "response and responsibility", but not
+"sense and responsibility". If instead the pattern</p>
+
+<quote><p> (sens|respons)e and (?1)ibility</p></quote>
+
+<p>is used, it does match "sense and responsibility" as well as the other two
+strings. Another example is given in the discussion of DEFINE above.</p>
+
+<p>Like recursive subpatterns, a "subroutine" call is always treated
+as an atomic group. That is, once it has matched some of the subject
+string, it is never re-entered, even if it contains untried
+alternatives and there is a subsequent matching failure.</p>
+
+<p>When a subpattern is used as a subroutine, processing options such as
+case-independence are fixed when the subpattern is defined. They cannot be
+changed for different calls. For example, consider this pattern:</p>
+
+<quote><p> (abc)(?i:(?-1))</p></quote>
+
+<p>It matches "abcabc". It does not match "abcABC" because the change of
+processing option does not affect the called subpattern.</p>
+
+
+</section>
+
+<!-- XXX C interface
+
+<section> <marker id="sect22"><title>Callouts</title></marker>
+
+<p>Perl has a feature whereby using the sequence (?{...}) causes arbitrary Perl
+code to be obeyed in the middle of matching a regular expression. This makes it
+possible, amongst other things, to extract different substrings that match the
+same pair of parentheses when there is a repetition.</p>
+
+<p>PCRE provides a similar feature, but of course it cannot obey arbitrary Perl
+code. The feature is called "callout". The caller of PCRE provides an external
+function by putting its entry point in the global variable <em>pcre_callout</em>.
+By default, this variable contains NULL, which disables all calling out.</p>
+
+<p>Within a regular expression, (?C) indicates the points at which the external
+function is to be called. If you want to identify different callout points, you
+can put a number less than 256 after the letter C. The default value is zero.
+For example, this pattern has two callout points:</p>
+
+<quote><p> (?C1)abc(?C2)def</p></quote>
+
+
+<p>If the <c>AUTO_CALLOUT</c> flag is passed to <c>re:compile/2</c>, callouts are
+automatically installed before each item in the pattern. They are all numbered
+255.</p>
+
+<p>During matching, when PCRE reaches a callout point (and <em>pcre_callout</em> is
+set), the external function is called. It is provided with the number of the
+callout, the position in the pattern, and, optionally, one item of data
+originally supplied by the caller of <c>re:run/3</c>. The callout function
+may cause matching to proceed, to backtrack, or to fail altogether. A complete
+description of the interface to the callout function is given in the
+<em>pcrecallout</em>
+documentation.</p>
+
+
+</section>
+-->
+
+<section><marker id="sect23"></marker><title>Backtracking control</title>
+
+<p>Perl 5.10 introduced a number of "Special Backtracking Control Verbs", which
+are described in the Perl documentation as "experimental and subject to change
+or removal in a future version of Perl". It goes on to say: "Their usage in
+production code should be noted to avoid problems during upgrades." The same
+remarks apply to the PCRE features described in this section.</p>
+
+<!-- XXX C interface
+<p>Since these verbs are specifically related to backtracking, they can be used
+only when the pattern is to be matched using <c>re:run/3</c>, which uses a
+backtracking algorithm. They cause an error if encountered by
+<c>pcre_dfa_exec()</c>.</p>
+-->
+
+<p>The new verbs make use of what was previously invalid syntax: an opening
+parenthesis followed by an asterisk. In Perl, they are generally of the form
+(*VERB:ARG) but PCRE does not support the use of arguments, so its general
+form is just (*VERB). Any number of these verbs may occur in a pattern. There
+are two kinds:</p>
+
+
+<p><em>Verbs that act immediately</em></p>
+
+<p>The following verbs act as soon as they are encountered:</p>
+
+<quote><p> (*ACCEPT)</p></quote>
+
+<p>This verb causes the match to end successfully, skipping the remainder of the
+pattern. When inside a recursion, only the innermost pattern is ended
+immediately. PCRE differs from Perl in what happens if the (*ACCEPT) is inside
+capturing parentheses. In Perl, the data so far is captured: in PCRE no data is
+captured. For example:</p>
+
+<quote><p> A(A|B(*ACCEPT)|C)D</p></quote>
+
+<p>This matches "AB", "AAD", or "ACD", but when it matches "AB", no data is
+captured.</p>
+
+<quote><p> (*FAIL) or (*F)</p></quote>
+
+<p>This verb causes the match to fail, forcing backtracking to occur. It is
+equivalent to (?!) but easier to read. The Perl documentation notes that it is
+probably useful only when combined with (?{}) or (??{}). Those are, of course,
+Perl features that are not present in PCRE. The nearest equivalent is the
+callout feature, as for example in this pattern:</p>
+
+<quote><p> a+(?C)(*FAIL)</p></quote>
+
+<p>A match with the string "aaaa" always fails, but the callout is taken before
+each backtrack happens (in this example, 10 times).</p>
+
+
+
+<p><em>Verbs that act after backtracking</em></p>
+
+<p>The following verbs do nothing when they are encountered. Matching continues
+with what follows, but if there is no subsequent match, a failure is forced.
+The verbs differ in exactly what kind of failure occurs.</p>
+
+<quote><p> (*COMMIT)</p></quote>
+
+<p>This verb causes the whole match to fail outright if the rest of the pattern
+does not match. Even if the pattern is unanchored, no further attempts to find
+a match by advancing the start point take place. Once (*COMMIT) has been
+passed, <c>re:run/3</c> is committed to finding a match at the current
+starting point, or not at all. For example:</p>
+
+<quote><p> a+(*COMMIT)b</p></quote>
+
+<p>This matches "xxaab" but not "aacaab". It can be thought of as a kind of
+dynamic anchor, or "I've started, so I must finish."</p>
+
+<quote><p> (*PRUNE)</p></quote>
+
+<p>This verb causes the match to fail at the current position if the rest of the
+pattern does not match. If the pattern is unanchored, the normal "bumpalong"
+advance to the next starting character then happens. Backtracking can occur as
+usual to the left of (*PRUNE), or when matching to the right of (*PRUNE), but
+if there is no match to the right, backtracking cannot cross (*PRUNE).
+In simple cases, the use of (*PRUNE) is just an alternative to an atomic
+group or possessive quantifier, but there are some uses of (*PRUNE) that cannot
+be expressed in any other way.</p>
+
+<quote><p> (*SKIP)</p></quote>
+
+<p>This verb is like (*PRUNE), except that if the pattern is unanchored, the
+"bumpalong" advance is not to the next character, but to the position in the
+subject where (*SKIP) was encountered. (*SKIP) signifies that whatever text
+was matched leading up to it cannot be part of a successful match. Consider:</p>
+
+<quote><p> a+(*SKIP)b</p></quote>
+
+<p>If the subject is "aaaac...", after the first match attempt fails (starting at
+the first character in the string), the starting point skips on to start the
+next attempt at "c". Note that a possessive quantifier does not have the same
+effect in this example; although it would suppress backtracking during the
+first match attempt, the second attempt would start at the second character
+instead of skipping on to "c".</p>
+
+<quote><p> (*THEN)</p></quote>
+
+<p>This verb causes a skip to the next alternation if the rest of the pattern does
+not match. That is, it cancels pending backtracking, but only within the
+current alternation. Its name comes from the observation that it can be used
+for a pattern-based if-then-else block:</p>
+
+<quote><p> ( COND1 (*THEN) FOO | COND2 (*THEN) BAR | COND3 (*THEN) BAZ ) ...</p></quote>
+
+<p>If the COND1 pattern matches, FOO is tried (and possibly further items after
+the end of the group if FOO succeeds); on failure the matcher skips to the
+second alternative and tries COND2, without backtracking into COND1. If (*THEN)
+is used outside of any alternation, it acts exactly like (*PRUNE).</p>
+
+</section>
+
+</erlref>
+
diff --git a/lib/stdlib/doc/src/ref_man.xml b/lib/stdlib/doc/src/ref_man.xml
new file mode 100644
index 0000000000..f6ae368e92
--- /dev/null
+++ b/lib/stdlib/doc/src/ref_man.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE application SYSTEM "application.dtd">
+
+<application xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>STDLIB Reference Manual</title>
+ <prepared>OTP Team</prepared>
+ <docno></docno>
+ <date>1997-06-04</date>
+ <rev>1.3.1</rev>
+ <file>application.xml</file>
+ </header>
+ <description>
+ <p>The Standard Erlang Libraries application, <em>STDLIB</em>,
+ contains modules for manipulating lists, strings and files etc.</p>
+ <br></br>
+ </description>
+ <xi:include href="stdlib_app.xml"/>
+ <xi:include href="array.xml"/>
+ <xi:include href="base64.xml"/>
+ <xi:include href="beam_lib.xml"/>
+ <xi:include href="c.xml"/>
+ <xi:include href="calendar.xml"/>
+ <xi:include href="dets.xml"/>
+ <xi:include href="dict.xml"/>
+ <xi:include href="digraph.xml"/>
+ <xi:include href="digraph_utils.xml"/>
+ <xi:include href="epp.xml"/>
+ <xi:include href="erl_eval.xml"/>
+ <xi:include href="erl_expand_records.xml"/>
+ <xi:include href="erl_id_trans.xml"/>
+ <xi:include href="erl_internal.xml"/>
+ <xi:include href="erl_lint.xml"/>
+ <xi:include href="erl_parse.xml"/>
+ <xi:include href="erl_pp.xml"/>
+ <xi:include href="erl_scan.xml"/>
+ <xi:include href="erl_tar.xml"/>
+ <xi:include href="ets.xml"/>
+ <xi:include href="file_sorter.xml"/>
+ <xi:include href="filelib.xml"/>
+ <xi:include href="filename.xml"/>
+ <xi:include href="gb_sets.xml"/>
+ <xi:include href="gb_trees.xml"/>
+ <xi:include href="gen_event.xml"/>
+ <xi:include href="gen_fsm.xml"/>
+ <xi:include href="gen_server.xml"/>
+ <xi:include href="io.xml"/>
+ <xi:include href="io_lib.xml"/>
+ <xi:include href="lib.xml"/>
+ <xi:include href="lists.xml"/>
+ <xi:include href="log_mf_h.xml"/>
+ <xi:include href="math.xml"/>
+ <xi:include href="ms_transform.xml"/>
+ <xi:include href="orddict.xml"/>
+ <xi:include href="ordsets.xml"/>
+ <xi:include href="pg.xml"/>
+ <xi:include href="pool.xml"/>
+ <xi:include href="proc_lib.xml"/>
+ <xi:include href="proplists.xml"/>
+ <xi:include href="qlc.xml"/>
+ <xi:include href="queue.xml"/>
+ <xi:include href="random.xml"/>
+ <xi:include href="re.xml"/>
+ <xi:include href="regexp.xml"/>
+ <xi:include href="sets.xml"/>
+ <xi:include href="shell.xml"/>
+ <xi:include href="shell_default.xml"/>
+ <xi:include href="slave.xml"/>
+ <xi:include href="sofs.xml"/>
+ <xi:include href="string.xml"/>
+ <xi:include href="supervisor.xml"/>
+ <xi:include href="supervisor_bridge.xml"/>
+ <xi:include href="sys.xml"/>
+ <xi:include href="timer.xml"/>
+ <xi:include href="unicode.xml"/>
+ <xi:include href="win32reg.xml"/>
+ <xi:include href="zip.xml"/>
+</application>
+
diff --git a/lib/stdlib/doc/src/regexp.xml b/lib/stdlib/doc/src/regexp.xml
new file mode 100644
index 0000000000..8da636e4ad
--- /dev/null
+++ b/lib/stdlib/doc/src/regexp.xml
@@ -0,0 +1,415 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>regexp</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>96-09-28</date>
+ <rev>A</rev>
+ <file>regexp.sgml</file>
+ </header>
+ <module>regexp</module>
+ <modulesummary>Regular Expression Functions for Strings</modulesummary>
+ <description>
+ <note><p>This module has been obsoleted by the
+ <seealso marker="re">re</seealso> module and will be removed in a future
+ release.</p></note>
+ <p>This module contains functions for regular expression
+ matching and substitution.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>match(String, RegExp) -> MatchRes</name>
+ <fsummary>Match a regular expression</fsummary>
+ <type>
+ <v>String = RegExp = string()</v>
+ <v>MatchRes = {match,Start,Length} | nomatch | {error,errordesc()}</v>
+ <v>Start = Length = integer()</v>
+ </type>
+ <desc>
+ <p>Finds the first, longest match of the regular expression <c>RegExp</c> in <c>String</c>. This function searches for the longest possible match and returns the first one found if there are several expressions of the same length. It returns as follows:</p>
+ <taglist>
+ <tag><c>{match,Start,Length}</c></tag>
+ <item>
+ <p>if the match succeeded. <c>Start</c> is the starting
+ position of the match, and <c>Length</c> is the length of
+ the matching string.</p>
+ </item>
+ <tag><c>nomatch</c></tag>
+ <item>
+ <p>if there were no matching characters.</p>
+ </item>
+ <tag><c>{error,Error}</c></tag>
+ <item>
+ <p>if there was an error in <c>RegExp</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>first_match(String, RegExp) -> MatchRes</name>
+ <fsummary>Match a regular expression</fsummary>
+ <type>
+ <v>String = RegExp = string()</v>
+ <v>MatchRes = {match,Start,Length} | nomatch | {error,errordesc()}</v>
+ <v>Start = Length = integer()</v>
+ </type>
+ <desc>
+ <p>Finds the first match of the regular expression <c>RegExp</c> in <c>String</c>. This call is
+ usually faster than <c>match</c> and it is also a useful way to ascertain that a match exists. It returns as follows:</p>
+ <taglist>
+ <tag><c>{match,Start,Length}</c></tag>
+ <item>
+ <p>if the match succeeded. <c>Start</c> is the starting
+ position of the match and <c>Length</c> is the length of
+ the matching string.</p>
+ </item>
+ <tag><c>nomatch</c></tag>
+ <item>
+ <p>if there were no matching characters.</p>
+ </item>
+ <tag><c>{error,Error}</c></tag>
+ <item>
+ <p>if there was an error in <c>RegExp</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>matches(String, RegExp) -> MatchRes</name>
+ <fsummary>Match a regular expression</fsummary>
+ <type>
+ <v>String = RegExp = string()</v>
+ <v>MatchRes = {match, Matches} | {error, errordesc()}</v>
+ <v>Matches = list()</v>
+ </type>
+ <desc>
+ <p>Finds all non-overlapping matches of the
+ expression <c>RegExp</c> in <c>String</c>.
+ It returns as follows:</p>
+ <taglist>
+ <tag><c>{match, Matches}</c></tag>
+ <item>
+ <p>if the regular expression was correct.
+ The list will be empty if there was no match. Each element in the list looks like <c>{Start, Length}</c>, where <c>Start</c> is the starting position of the match, and <c>Length</c> is the length of the matching string.</p>
+ </item>
+ <tag><c>{error,Error}</c></tag>
+ <item>
+ <p>if there was an error in <c>RegExp</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>sub(String, RegExp, New) -> SubRes</name>
+ <fsummary>Substitute the first occurrence of a regular expression</fsummary>
+ <type>
+ <v>String = RegExp = New = string()</v>
+ <v>SubRes = {ok,NewString,RepCount} | {error,errordesc()}</v>
+ <v>RepCount = integer()</v>
+ </type>
+ <desc>
+ <p>Substitutes the first occurrence of a substring matching <c>RegExp</c> in <c>String</c> with the string <c>New</c>. A <c><![CDATA[&]]></c> in the string <c>New</c> is replaced by the matched substring of <c>String</c>. <c><![CDATA[\\&]]></c> puts a literal <c><![CDATA[&]]></c> into the replacement string. It returns as follows:</p>
+ <taglist>
+ <tag><c>{ok,NewString,RepCount}</c></tag>
+ <item>
+ <p>if <c>RegExp</c> is correct. <c>RepCount</c> is the number of replacements which have been made
+ (this will be either 0 or 1).</p>
+ </item>
+ <tag><c>{error, Error}</c></tag>
+ <item>
+ <p>if there is an error in <c>RegExp</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>gsub(String, RegExp, New) -> SubRes</name>
+ <fsummary>Substitute all occurrences of a regular expression</fsummary>
+ <type>
+ <v>String = RegExp = New = string()</v>
+ <v>SubRes = {ok,NewString,RepCount} | {error,errordesc()}</v>
+ <v>RepCount = integer()</v>
+ </type>
+ <desc>
+ <p>The same as <c>sub</c>, except that all non-overlapping
+ occurrences of a substring matching
+ <c>RegExp</c> in <c>String</c> are replaced by the string <c>New</c>. It returns:</p>
+ <taglist>
+ <tag><c>{ok,NewString,RepCount}</c></tag>
+ <item>
+ <p>if <c>RegExp</c> is correct. <c>RepCount</c> is the number of replacements which have been made.</p>
+ </item>
+ <tag><c>{error, Error}</c></tag>
+ <item>
+ <p>if there is an error in <c>RegExp</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>split(String, RegExp) -> SplitRes</name>
+ <fsummary>Split a string into fields</fsummary>
+ <type>
+ <v>String = RegExp = string()</v>
+ <v>SubRes = {ok,FieldList} | {error,errordesc()}</v>
+ <v>Fieldlist = [string()]</v>
+ </type>
+ <desc>
+ <p><c>String</c> is split into fields (sub-strings) by the
+ regular expression <c>RegExp</c>.</p>
+ <p>If the separator expression is <c>" "</c> (a single space),
+ then the fields are separated by blanks and/or tabs and
+ leading and trailing blanks and tabs are discarded. For all
+ other values of the separator, leading and trailing blanks
+ and tabs are not discarded. It returns:</p>
+ <taglist>
+ <tag><c>{ok, FieldList}</c></tag>
+ <item>
+ <p>to indicate that the string has been split up into the fields of
+ <c>FieldList</c>.</p>
+ </item>
+ <tag><c>{error, Error}</c></tag>
+ <item>
+ <p>if there is an error in <c>RegExp</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>sh_to_awk(ShRegExp) -> AwkRegExp</name>
+ <fsummary>Convert an <c>sh</c>regular expression into an <c>AWK</c>one</fsummary>
+ <type>
+ <v>ShRegExp AwkRegExp = string()</v>
+ <v>SubRes = {ok,NewString,RepCount} | {error,errordesc()}</v>
+ <v>RepCount = integer()</v>
+ </type>
+ <desc>
+ <p>Converts the <c>sh</c> type regular expression
+ <c>ShRegExp</c> into a full <c>AWK</c> regular
+ expression. Returns the converted regular expression
+ string. <c>sh</c> expressions are used in the shell for
+ matching file names and have the following special
+ characters:</p>
+ <taglist>
+ <tag><c>*</c></tag>
+ <item>
+ <p>matches any string including the null string.</p>
+ </item>
+ <tag><c>?</c></tag>
+ <item>
+ <p>matches any single character.</p>
+ </item>
+ <tag><c>[...]</c></tag>
+ <item>
+ <p>matches any of the enclosed characters. Character
+ ranges are specified by a pair of characters separated
+ by a <c>-</c>. If the first character after <c>[</c> is a
+ <c>!</c>, then any character not enclosed is matched.</p>
+ </item>
+ </taglist>
+ <p>It may sometimes be more practical to use <c>sh</c> type
+ expansions as they are simpler and easier to use, even though they are not as powerful.</p>
+ </desc>
+ </func>
+ <func>
+ <name>parse(RegExp) -> ParseRes</name>
+ <fsummary>Parse a regular expression</fsummary>
+ <type>
+ <v>RegExp = string()</v>
+ <v>ParseRes = {ok,RE} | {error,errordesc()}</v>
+ </type>
+ <desc>
+ <p>Parses the regular expression <c>RegExp</c> and builds the
+ internal representation used in the other regular expression
+ functions. Such representations can be used in all of the
+ other functions instead of a regular expression string. This
+ is more efficient when the same regular expression is used
+ in many strings. It returns:</p>
+ <taglist>
+ <tag><c>{ok, RE}</c>if <c>RegExp</c>is correct and <c>RE</c>is the internal representation.</tag>
+ <item>
+ <p></p>
+ </item>
+ <tag><c>{error, Error}</c>if there is an error in <c>RegExpString</c>.</tag>
+ <item>
+ <p></p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(ErrorDescriptor) -> Chars</name>
+ <fsummary>Format an error descriptor</fsummary>
+ <type>
+ <v>ErrorDescriptor = errordesc()</v>
+ <v>Chars = [char() | Chars]</v>
+ </type>
+ <desc>
+ <p>Returns a string which describes the error <c>ErrorDescriptor</c>
+ returned when there is an error in a regular expression.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Regular Expressions</title>
+ <p>The regular expressions allowed here is a subset of the set found
+ in <c>egrep</c> and in the <c>AWK</c> programming language, as
+ defined in the book, <c>The AWK Programming Language, by A. V. Aho, B. W. Kernighan, P. J. Weinberger</c>. They are
+ composed of the following characters:</p>
+ <taglist>
+ <tag>c</tag>
+ <item>
+ <p>matches the non-metacharacter <c>c</c>.</p>
+ </item>
+ <tag>\\c</tag>
+ <item>
+ <p>matches the escape sequence or literal character <c>c</c>.</p>
+ </item>
+ <tag>.</tag>
+ <item>
+ <p>matches any character.</p>
+ </item>
+ <tag>^</tag>
+ <item>
+ <p>matches the beginning of a string.</p>
+ </item>
+ <tag>$</tag>
+ <item>
+ <p>matches the end of a string.</p>
+ </item>
+ <tag>[abc...]</tag>
+ <item>
+ <p>character class, which matches any of the characters
+ <c>abc...</c> Character ranges are specified by a pair of
+ characters separated by a <c>-</c>.</p>
+ </item>
+ <tag>[^abc...]</tag>
+ <item>
+ <p>negated character class, which matches any character except
+ <c>abc...</c>.</p>
+ </item>
+ <tag>r1 | r2</tag>
+ <item>
+ <p>alternation. It matches either <c>r1</c> or <c>r2</c>.</p>
+ </item>
+ <tag>r1r2</tag>
+ <item>
+ <p>concatenation. It matches <c>r1</c> and then <c>r2</c>.</p>
+ </item>
+ <tag>r+</tag>
+ <item>
+ <p>matches one or more <c>r</c>s.</p>
+ </item>
+ <tag>r*</tag>
+ <item>
+ <p>matches zero or more <c>r</c>s.</p>
+ </item>
+ <tag>r?</tag>
+ <item>
+ <p>matches zero or one <c>r</c>s.</p>
+ </item>
+ <tag>(r)</tag>
+ <item>
+ <p>grouping. It matches <c>r</c>.</p>
+ </item>
+ </taglist>
+ <p>The escape sequences allowed are the same as for Erlang
+ strings:</p>
+ <taglist>
+ <tag><c>\\b</c></tag>
+ <item>
+ <p>backspace</p>
+ </item>
+ <tag><c>\\f</c></tag>
+ <item>
+ <p>form feed </p>
+ </item>
+ <tag><c>\</c></tag>
+ <item>
+ <p>newline (line feed) </p>
+ </item>
+ <tag><c>\\r</c></tag>
+ <item>
+ <p>carriage return </p>
+ </item>
+ <tag><c>\\t</c></tag>
+ <item>
+ <p>tab </p>
+ </item>
+ <tag><c>\\e</c></tag>
+ <item>
+ <p>escape </p>
+ </item>
+ <tag><c>\\v</c></tag>
+ <item>
+ <p>vertical tab </p>
+ </item>
+ <tag><c>\\s</c></tag>
+ <item>
+ <p>space </p>
+ </item>
+ <tag><c>\\d</c></tag>
+ <item>
+ <p>delete </p>
+ </item>
+ <tag><c>\\ddd</c></tag>
+ <item>
+ <p>the octal value ddd </p>
+ </item>
+ <tag><c>\\xhh</c></tag>
+ <item>
+ <p>The hexadecimal value <c>hh</c>.</p>
+ </item>
+ <tag><c>\\x{h...}</c></tag>
+ <item>
+ <p>The hexadecimal value <c>h...</c>.</p>
+ </item>
+ <tag><c>\\c</c></tag>
+ <item>
+ <p>any other character literally, for example <c>\\\\</c> for backslash,
+ <c>\\"</c> for ")</p>
+ </item>
+ </taglist>
+ <p>To make these functions easier to use, in combination with the
+ function <c>io:get_line</c> which terminates the input line with
+ a new line, the <c>$</c> characters also matches a string ending
+ with <c>"...\ "</c>. The following examples
+ define Erlang data types:</p>
+ <pre>
+Atoms [a-z][0-9a-zA-Z_]*
+
+Variables [A-Z_][0-9a-zA-Z_]*
+
+Floats (\\+|-)?[0-9]+\\.[0-9]+((E|e)(\\+|-)?[0-9]+)?</pre>
+ <p>Regular expressions are written as Erlang strings when used with the functions in this module. This means that any <c>\\</c> or <c>"</c> characters in a regular expression
+ string must be written with <c>\\</c> as they are also escape characters for the string. For example, the regular expression string for Erlang floats is:
+ <c>"(\\\\+|-)?[0-9]+\\\\.[0-9]+((E|e)(\\\\+|-)?[0-9]+)?"</c>.</p>
+ <p>It is not really necessary to have the escape sequences as part of the regular expression syntax as they can always be generated directly in the string. They are included for completeness and can they can also be useful when generating regular expressions, or when they are entered other than with Erlang strings.</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
new file mode 100644
index 0000000000..3610bb0184
--- /dev/null
+++ b/lib/stdlib/doc/src/sets.xml
@@ -0,0 +1,252 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2000</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>sets</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>99-07-27</date>
+ <rev>A</rev>
+ <file>sets.sgml</file>
+ </header>
+ <module>sets</module>
+ <modulesummary>Functions for Set Manipulation</modulesummary>
+ <description>
+ <p>Sets are collections of elements with no duplicate elements.
+ The representation of a set is not defined.</p>
+ <p>This module provides exactly the same interface as the module
+ <c>ordsets</c> but with a defined representation. One difference is
+ that while this module considers two elements as different if they
+ do not match (<c>=:=</c>), <c>ordsets</c> considers two elements as
+ different if and only if they do not compare equal (<c>==</c>).</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+set()
+ as returned by new/0</code>
+ </section>
+ <funcs>
+ <func>
+ <name>new() -> Set</name>
+ <fsummary>Return an empty set</fsummary>
+ <type>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns a new empty set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_set(Set) -> bool()</name>
+ <fsummary>Test for an <c>Set</c></fsummary>
+ <type>
+ <v>Set = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Set</c> is a set of
+ elements, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>size(Set) -> int()</name>
+ <fsummary>Return the number of elements in a set</fsummary>
+ <type>
+ <v>Set = term()</v>
+ </type>
+ <desc>
+ <p>Returns the number of elements in <c>Set</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_list(Set) -> List</name>
+ <fsummary>Convert an <c>Set</c>into a list</fsummary>
+ <type>
+ <v>Set = set()</v>
+ <v>List = [term()]</v>
+ </type>
+ <desc>
+ <p>Returns the elements of <c>Set</c> as a list.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_list(List) -> Set</name>
+ <fsummary>Convert a list into an <c>Set</c></fsummary>
+ <type>
+ <v>List = [term()]</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns an set of the elements in <c>List</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_element(Element, Set) -> bool()</name>
+ <fsummary>Test for membership of an <c>Set</c></fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Element</c> is an element of
+ <c>Set</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>add_element(Element, Set1) -> Set2</name>
+ <fsummary>Add an element to an <c>Set</c></fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns a new set formed from <c>Set1</c> with
+ <c>Element</c> inserted.</p>
+ </desc>
+ </func>
+ <func>
+ <name>del_element(Element, Set1) -> Set2</name>
+ <fsummary>Remove an element from an <c>Set</c></fsummary>
+ <type>
+ <v>Element = term()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>Set1</c>, but with <c>Element</c> removed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(Set1, Set2) -> Set3</name>
+ <fsummary>Return the union of two <c>Sets</c></fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the merged (union) set of <c>Set1</c> and
+ <c>Set2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(SetList) -> Set</name>
+ <fsummary>Return the union of a list of <c>Sets</c></fsummary>
+ <type>
+ <v>SetList = [set()]</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the merged (union) set of the list of sets.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(Set1, Set2) -> Set3</name>
+ <fsummary>Return the intersection of two <c>Sets</c></fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the intersection of <c>Set1</c> and
+ <c>Set2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(SetList) -> Set</name>
+ <fsummary>Return the intersection of a list of <c>Sets</c></fsummary>
+ <type>
+ <v>SetList = [set()]</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the intersection of the non-empty list of sets.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_disjoint(Set1, Set2) -> bool()</name>
+ <fsummary>Check whether two <c>Sets</c> are disjoint</fsummary>
+ <type>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if <c>Set1</c> and
+ <c>Set2</c> are disjoint (have no elements in common),
+ and <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>subtract(Set1, Set2) -> Set3</name>
+ <fsummary>Return the difference of two <c>Sets</c></fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns only the elements of <c>Set1</c> which are not
+ also elements of <c>Set2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_subset(Set1, Set2) -> bool()</name>
+ <fsummary>Test for subset</fsummary>
+ <type>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> when every element of <c>Set</c>1 is
+ also a member of <c>Set2</c>, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>fold(Function, Acc0, Set) -> Acc1</name>
+ <fsummary>Fold over set elements</fsummary>
+ <type>
+ <v>Function = fun (E, AccIn) -> AccOut</v>
+ <v>Acc0 = Acc1 = AccIn = AccOut = term()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Fold <c>Function</c> over every element in <c>Set</c>
+ returning the final value of the accumulator.</p>
+ </desc>
+ </func>
+ <func>
+ <name>filter(Pred, Set1) -> Set2</name>
+ <fsummary>Filter set elements</fsummary>
+ <type>
+ <v>Pred = fun (E) -> bool()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Filter elements in <c>Set1</c> with boolean function
+ <c>Fun</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="ordsets">ordsets(3)</seealso>,
+ <seealso marker="gb_sets">gb_sets(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
new file mode 100644
index 0000000000..24b845fee9
--- /dev/null
+++ b/lib/stdlib/doc/src/shell.xml
@@ -0,0 +1,810 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>shell</title>
+ <prepared>Bjorn Gustavsson</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>97-01-24</date>
+ <rev>A</rev>
+ <file>shell.sgml</file>
+ </header>
+ <module>shell</module>
+ <modulesummary>The Erlang Shell</modulesummary>
+ <description>
+ <p>The module <c>shell</c> implements an Erlang shell.
+ </p>
+ <p>The shell is a user interface program
+ for entering expression sequences. The expressions are
+ evaluated and a value is returned.
+ A history mechanism saves previous commands and their
+ values, which can then be incorporated in later commands.
+ How many commands and results to save can be determined by the user,
+ either interactively, by calling <c>shell:history/1</c> and
+ <c>shell:results/1</c>, or by setting the application configuration
+ parameters <c>shell_history_length</c> and
+ <c>shell_saved_results</c> for the application STDLIB.
+ </p>
+ <p>The shell uses a helper process for evaluating commands in
+ order to protect the history mechanism from exceptions. By
+ default the evaluator process is killed when an exception
+ occurs, but by calling <c>shell:catch_exception/1</c> or by
+ setting the application configuration parameter
+ <c>shell_catch_exception</c> for the application STDLIB
+ this behavior can be changed. See also the example below.
+ </p>
+ <p>Variable bindings, and local process dictionary changes
+ which are generated in user expressions are preserved, and the variables
+ can be used in later commands to access their values. The
+ bindings can also be forgotten so the variables can be re-used.
+ </p>
+ <p>The special shell commands all have the syntax of (local)
+ function calls. They are evaluated as
+ normal function calls and many commands can be used in one
+ expression sequence.
+ </p>
+ <p>If a command (local function call) is not recognized by the
+ shell, an attempt is first made to find the function in the
+ module <c>user_default</c>, where customized local commands
+ can be placed. If found, then the function is evaluated.
+ Otherwise, an attempt is made to evaluate the function in the
+ module <c>shell_default</c>. The module
+ <c>user_default</c> must be explicitly loaded.
+ </p>
+ <p>The shell also permits the user to start multiple concurrent
+ jobs. A job can be regarded as a set of processes which can
+ communicate with the shell.
+ </p>
+ <p>There is some support for reading and printing records in
+ the shell. During compilation record expressions are translated
+ to tuple expressions. In runtime it is not known whether a tuple
+ actually represents a record. Nor are the record definitions
+ used by compiler available at runtime. So in order to read the
+ record syntax and print tuples as records when possible, record
+ definitions have to be maintained by the shell itself. The shell
+ commands for reading, defining, forgetting, listing, and
+ printing records are described below. Note that each job has its
+ own set of record definitions. To facilitate matters record
+ definitions in the modules <c>shell_default</c> and
+ <c>user_default</c> (if loaded) are read each time a new job is
+ started. For instance, adding the line</p>
+ <code type="none">
+ -include_lib("kernel/include/file.hrl").</code>
+ <p>to <c>user_default</c> makes the definition of <c>file_info</c>
+ readily available in the shell.
+ </p>
+ <p>The shell runs in two modes: </p>
+ <list type="bulleted">
+ <item><c>Normal (possibly restricted)</c> mode, in which
+ commands can be edited and expressions evaluated.
+ </item>
+ <item>Job Control Mode <c>JCL</c>, in which jobs can be
+ started, killed, detached and connected.
+ </item>
+ </list>
+ <p>Only the currently connected job can 'talk' to the shell.</p>
+ </description>
+
+ <section>
+ <title>Shell Commands</title>
+ <taglist>
+ <tag><c>b()</c></tag>
+ <item>
+ <p>Prints the current variable bindings.</p>
+ </item>
+ <tag><c>f()</c></tag>
+ <item>
+ <p>Removes all variable bindings.
+ </p>
+ </item>
+ <tag><c>f(X)</c></tag>
+ <item>
+ <p>Removes the binding of variable <c>X</c>.
+ </p>
+ </item>
+ <tag><c>h()</c></tag>
+ <item>
+ <p>Prints the history list.
+ </p>
+ </item>
+ <tag><c>history(N)</c></tag>
+ <item>
+ <p>Sets the number of previous commands to keep in the
+ history list to <c>N</c>. The previous number is returned.
+ The default number is 20.
+ </p>
+ </item>
+ <tag><c>results(N)</c></tag>
+ <item>
+ <p>Sets the number of results from previous commands to keep in
+ the history list to <c>N</c>. The previous number is returned.
+ The default number is 20.
+ </p>
+ </item>
+ <tag><c>e(N)</c></tag>
+ <item>
+ <p>Repeats the command <c>N</c>, if <c>N</c> is positive. If
+ it is negative, the <c>N</c>th previous command is repeated
+ (i.e., <c>e(-1)</c> repeats the previous command).
+ </p>
+ </item>
+ <tag><c>v(N)</c></tag>
+ <item>
+ <p>Uses the return value of the command <c>N</c> in the
+ current command, if <c>N</c> is positive. If it is negative,
+ the return value of the <c>N</c>th previous command is used
+ (i.e., <c>v(-1)</c> uses the value of the previous command).
+ </p>
+ </item>
+ <tag><c>help()</c></tag>
+ <item>
+ <p>Evaluates <c>shell_default:help()</c>.
+ </p>
+ </item>
+ <tag><c>c(File)</c></tag>
+ <item>
+ <p>Evaluates <c>shell_default:c(File)</c>. This compiles
+ and loads code in <c>File</c> and purges old versions of
+ code, if necessary. Assumes that the file and module names
+ are the same.
+ </p>
+ </item>
+ <tag><c>catch_exception(Bool)</c></tag>
+ <item>
+ <p>Sets the exception handling of the evaluator process. The
+ previous exception handling is returned. The default
+ (<c>false</c>) is to kill the evaluator process when an
+ exception occurs, which causes the shell to create a new
+ evaluator process. When the exception handling is set to
+ <c>true</c> the evaluator process lives on which means that
+ for instance ports and ETS tables as well as processes
+ linked to the evaluator process survive the exception.
+ </p>
+ </item>
+ <tag><c>rd(RecordName, RecordDefinition)</c></tag>
+ <item>
+ <p>Defines a record in the shell. <c>RecordName</c> is
+ an atom and <c>RecordDefinition</c> lists the field names
+ and the default values. Usually record definitions are made
+ known to the shell by use of the <c>rr</c> commands
+ described below, but sometimes it is handy to define records
+ on the fly.
+ </p>
+ </item>
+ <tag><c>rf()</c></tag>
+ <item>
+ <p>Removes all record definitions, then reads record
+ definitions from the modules <c>shell_default</c> and
+ <c>user_default</c> (if loaded). Returns the names of the
+ records defined.
+ </p>
+ </item>
+ <tag><c>rf(RecordNames)</c></tag>
+ <item>
+ <p>Removes selected record definitions.
+ <c>RecordNames</c> is a record name or a list of record names.
+ Use <c>'_'</c> to remove all record definitions.
+ </p>
+ </item>
+ <tag><c>rl()</c></tag>
+ <item>
+ <p>Prints all record definitions.
+ </p>
+ </item>
+ <tag><c>rl(RecordNames)</c></tag>
+ <item>
+ <p>Prints selected record definitions.
+ <c>RecordNames</c> is a record name or a list of record names.
+ </p>
+ </item>
+ <tag><c>rp(Term)</c></tag>
+ <item>
+ <p>Prints a term using the record definitions known to the
+ shell. All of <c>Term</c> is printed; the depth is not
+ limited as is the case when a return value is printed.
+ </p>
+ </item>
+ <tag><c>rr(Module)</c></tag>
+ <item>
+ <p>Reads record definitions from a module's BEAM file. If
+ there are no record definitions in the BEAM file, the
+ source file is located and read instead. Returns the names
+ of the record definitions read. <c>Module</c> is an atom.
+ </p>
+ </item>
+ <tag><c>rr(Wildcard)</c></tag>
+ <item>
+ <p>Reads record definitions from files. Existing
+ definitions of any of the record names read are replaced.
+ <c>Wildcard</c> is a wildcard string as defined in
+ <c>filelib(3)</c> but not an atom.
+ </p>
+ </item>
+ <tag><c>rr(WildcardOrModule, RecordNames)</c></tag>
+ <item>
+ <p>Reads record definitions from files but
+ discards record names not mentioned in <c>RecordNames</c> (a
+ record name or a list of record names).
+ </p>
+ </item>
+ <tag><c>rr(WildcardOrModule, RecordNames, Options)</c></tag>
+ <item>
+ <p>Reads record definitions from files. The compiler
+ options <c>{i,&nbsp;Dir}</c>, <c>{d,&nbsp;Macro}</c>, and
+ <c>{d,&nbsp;Macro,&nbsp;Value}</c> are recognized and used
+ for setting up the include path and macro definitions. Use
+ <c>'_'</c> as value of <c>RecordNames</c> to read all record
+ definitions.
+ </p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>Example</title>
+ <p>The following example is a long dialogue with the shell. Commands
+ starting with <c>></c> are inputs to the shell. All other lines
+ are output from the shell. All commands in this example are explained at the end of the dialogue.
+ .</p>
+ <pre>
+strider 1> <input>erl</input>
+Erlang (BEAM) emulator version 5.3 [hipe] [threads:0]
+
+Eshell V5.3 (abort with ^G)
+1><input>Str = "abcd".</input>
+"abcd"
+2> <input>L = length(Str).</input>
+4
+3> <input>Descriptor = {L, list_to_atom(Str)}.</input>
+{4,abcd}
+4> <input>L.</input>
+4
+5> <input>b().</input>
+Descriptor = {4,abcd}
+L = 4
+Str = "abcd"
+ok
+6> <input>f(L).</input>
+ok
+7> <input>b().</input>
+Descriptor = {4,abcd}
+Str = "abcd"
+ok
+8> <input>f(L).</input>
+ok
+9> <input>{L, _} = Descriptor.</input>
+{4,abcd}
+10> <input>L.</input>
+4
+11> <input>{P, Q, R} = Descriptor.</input>
+** exception error: no match of right hand side value {4,abcd}
+12> <input>P.</input>
+* 1: variable 'P' is unbound **
+13> <input>Descriptor.</input>
+{4,abcd}
+14><input>{P, Q} = Descriptor.</input>
+{4,abcd}
+15> <input>P.</input>
+4
+16> <input>f().</input>
+ok
+17> <input>put(aa, hello).</input>
+undefined
+18> <input>get(aa).</input>
+hello
+19> <input>Y = test1:demo(1).</input>
+11
+20> <input>get().</input>
+[{aa,worked}]
+21> <input>put(aa, hello).</input>
+worked
+22> <input>Z = test1:demo(2).</input>
+** exception error: no match of right hand side value 1
+ in function test1:demo/1
+23> <input>Z.</input>
+* 1: variable 'Z' is unbound **
+24> <input>get(aa).</input>
+hello
+25> <input>erase(), put(aa, hello).</input>
+undefined
+26> <input>spawn(test1, demo, [1]).</input>
+&lt;0.57.0>
+27> <input>get(aa).</input>
+hello
+28> <input>io:format("hello hello\ ").</input>
+hello hello ok
+29> <input>e(28).</input>
+hello hello ok
+30> <input>v(28).</input>
+ok
+31> <input>c(ex).</input>
+{ok,ex}
+32> <input>rr(ex).</input>
+[rec]
+33> <input>rl(rec).</input>
+-record(rec,{a,b = val()}).
+ok
+34> <input>#rec{}.</input>
+** exception error: undefined shell command val/0
+35> <input>#rec{b = 3}.</input>
+#rec{a = undefined,b = 3}
+36> <input>rp(v(-1)).</input>
+#rec{a = undefined,b = 3}
+ok
+37> <input>rd(rec, {f = orddict:new()}).</input>
+rec
+38> <input>#rec{}.</input>
+#rec{f = []}
+ok
+39> <input>rd(rec, {c}), A.</input>
+* 1: variable 'A' is unbound **
+40> <input>#rec{}.</input>
+#rec{c = undefined}
+ok
+41> <input>test1:loop(0).</input>
+Hello Number: 0
+Hello Number: 1
+Hello Number: 2
+Hello Number: 3
+
+User switch command
+ --> i
+ --> c
+.
+.
+.
+Hello Number: 3374
+Hello Number: 3375
+Hello Number: 3376
+Hello Number: 3377
+Hello Number: 3378
+** exception exit: killed
+42> <input>E = ets:new(t, []).</input>
+17
+43> <input>ets:insert({d,1,2}).</input>
+** exception error: undefined function ets:insert/1
+44> <input>ets:insert(E, {d,1,2}).</input>
+** exception error: argument is of wrong type
+ in function ets:insert/2
+ called as ets:insert(16,{d,1,2})
+45> <input>f(E).</input>
+ok
+46> <input>catch_exception(true).</input>
+false
+47> <input>E = ets:new(t, []).</input>
+18
+48> <input>ets:insert({d,1,2}).</input>
+* exception error: undefined function ets:insert/1
+49> <input>ets:insert(E, {d,1,2}).</input>
+true
+50> <input>halt().</input>
+strider 2></pre>
+ </section>
+
+ <section>
+ <title>Comments</title>
+ <p>Command 1 sets the variable <c>Str</c> to the string
+ <c>"abcd"</c>.
+ </p>
+ <p>Command 2 sets <c>L</c> to the length of the string evaluating
+ the BIF <c>atom_to_list</c>.
+ </p>
+ <p>Command 3 builds the tuple <c>Descriptor</c>.
+ </p>
+ <p>Command 4 prints the value of the variable <c>L</c>.
+ </p>
+ <p>Command 5 evaluates the internal shell command <c>b()</c>, which
+ is an abbreviation of "bindings". This prints
+ the current shell variables and their bindings. The <c>ok</c> at
+ the end is the return value of the <c>b()</c> function.
+ </p>
+ <p>Command 6 <c>f(L)</c> evaluates the internal shell command
+ <c>f(L)</c> (abbreviation of "forget"). The value of the variable
+ <c>L</c> is removed.
+ </p>
+ <p>Command 7 prints the new bindings.
+ </p>
+ <p>Command 8 has no effect since <c>L</c> has no value.</p>
+ <p>Command 9 performs a pattern matching operation on
+ <c>Descriptor</c>, binding a new value to <c>L</c>.
+ </p>
+ <p>Command 10 prints the current value of <c>L</c>.
+ </p>
+ <p>Command 11 tries to match <c>{P, Q, R}</c> against
+ <c>Descriptor</c> which is <c>{4, abc}</c>. The match fails and
+ none of the new variables become bound. The printout starting
+ with "<c>** exception error:</c>" is not the value of the
+ expression (the expression had no value because its evaluation
+ failed), but rather a warning printed by the system to inform
+ the user that an error has occurred. The values of the other
+ variables (<c>L</c>, <c>Str</c>, etc.) are unchanged.
+ </p>
+ <p>Commands 12 and 13 show that <c>P</c> is unbound because the
+ previous command failed, and that <c>Descriptor</c> has not
+ changed.
+ </p>
+ <p>Commands 14 and 15 show a correct match where <c>P</c> and
+ <c>Q</c> are bound.
+ </p>
+ <p>Command 16 clears all bindings.
+ </p>
+ <p>The next few commands assume that <c>test1:demo(X)</c> is
+ defined in the following way:</p>
+ <pre>
+demo(X) ->
+ put(aa, worked),
+ X = 1,
+ X + 10. </pre>
+ <p>Commands 17 and 18 set and inspect the value of the item
+ <c>aa</c> in the process dictionary.
+ </p>
+ <p>Command 19 evaluates <c>test1:demo(1)</c>. The evaluation
+ succeeds and the changes made in the process dictionary become
+ visible to the shell. The new value of the dictionary item
+ <c>aa</c> can be seen in command 20.
+ </p>
+ <p>Commands 21 and 22 change the value of the dictionary item
+ <c>aa</c> to <c>hello</c> and call <c>test1:demo(2)</c>. Evaluation
+ fails and the changes made to the dictionary in
+ <c>test1:demo(2)</c>, before the error occurred, are discarded.
+ </p>
+ <p>Commands 23 and 24 show that <c>Z</c> was not bound and that the
+ dictionary item <c>aa</c> has retained its original value.
+ </p>
+ <p>Commands 25, 26 and 27 show the effect of evaluating
+ <c>test1:demo(1)</c> in the background. In this case, the
+ expression is evaluated in a newly spawned process. Any
+ changes made in the process dictionary are local to the newly
+ spawned process and therefore not visible to the shell.
+ </p>
+ <p>Commands 28, 29 and 30 use the history facilities of the shell.
+ </p>
+ <p>Command 29 is <c>e(28)</c>. This re-evaluates command
+ 28. Command 30 is <c>v(28)</c>. This uses the value (result) of
+ command 28. In the cases of a pure function (a function
+ with no side effects), the result is the same. For a function
+ with side effects, the result can be different.
+ </p>
+ <p>The next few commands show some record manipulation. It is
+ assumed that <c>ex.erl</c> defines a record like this:</p>
+ <pre>
+-record(rec, {a, b = val()}).
+
+val() ->
+ 3. </pre>
+ <p>Commands 31 and 32 compiles the file <c>ex.erl</c> and reads
+ the record definitions in <c>ex.beam</c>. If the compiler did not
+ output any record definitions on the BEAM file, <c>rr(ex)</c>
+ tries to read record definitions from the source file instead.
+ </p>
+ <p>Command 33 prints the definition of the record named
+ <c>rec</c>.
+ </p>
+ <p>Command 34 tries to create a <c>rec</c> record, but fails
+ since the function <c>val/0</c> is undefined. Command 35 shows
+ the workaround: explicitly assign values to record fields that
+ cannot otherwise be initialized.
+ </p>
+ <p>Command 36 prints the newly created record using record
+ definitions maintained by the shell.
+ </p>
+ <p>Command 37 defines a record directly in the shell. The
+ definition replaces the one read from the file <c>ex.beam</c>.
+ </p>
+ <p>Command 38 creates a record using the new definition, and
+ prints the result.
+ </p>
+ <p>Command 39 and 40 show that record definitions are updated
+ as side effects. The evaluation of the command fails but
+ the definition of <c>rec</c> has been carried out.
+ </p>
+ <p>For the next command, it is assumed that <c>test1:loop(N)</c> is
+ defined in the following way:</p>
+ <pre>
+loop(N) ->
+ io:format("Hello Number: ~w~n", [N]),
+ loop(N+1).</pre>
+ <p>Command 41 evaluates <c>test1:loop(0)</c>, which puts the
+ system into an infinite loop. At this point the user types
+ <c>Control G</c>, which suspends output from the current process,
+ which is stuck in a loop, and activates <c>JCL</c> mode. In <c>JCL</c>
+ mode the user can start and stop jobs.
+ </p>
+ <p>In this particular case, the <c>i</c> command ("interrupt") is
+ used to terminate the looping program, and the <c>c</c> command
+ is used to connect to the shell again. Since the process was
+ running in the background before we killed it, there will be
+ more printouts before the "<c>** exception exit: killed</c>"
+ message is shown.
+ </p>
+ <p>Command 42 creates an ETS table.</p>
+ <p>Command 43 tries to insert a tuple into the ETS table but the
+ first argument (the table) is missing. The exception kills the
+ evaluator process.</p>
+ <p>Command 44 corrects the mistake, but the ETS table has been
+ destroyed since it was owned by the killed evaluator process.</p>
+ <p>Command 46 sets the exception handling of the evaluator process
+ to <c>true</c>. The exception handling can also be set when
+ starting Erlang, like this: <c>erl -stdlib shell_catch_exception
+ true</c>.</p>
+ <p>Command 48 makes the same mistake as in command 43, but this time
+ the evaluator process lives on. The single star at the beginning
+ of the printout signals that the exception has been caught.</p>
+ <p>Command 49 successfully inserts the tuple into the ETS table.</p>
+ <p>The <c>halt()</c> command exits the Erlang runtime system.
+ </p>
+ </section>
+
+ <section>
+ <title>JCL Mode</title>
+ <p>When the shell starts, it starts a single evaluator
+ process. This process, together with any local processes which
+ it spawns, is referred to as a <c>job</c>. Only the current job,
+ which is said to be <c>connected</c>, can perform operations
+ with standard IO. All other jobs, which are said to be <c>detached</c>, are
+ <c>blocked</c> if they attempt to use standard IO.
+ </p>
+ <p>All jobs which do not use standard IO run in the normal way.
+ </p>
+ <p>The shell escape key <em><c>^G</c></em> (Control G) detaches the current job
+ and activates <c>JCL</c> mode. The <c>JCL</c> mode prompt is <c>"-->"</c>. If <c>"?"</c> is entered at the prompt, the following help message is
+ displayed:</p>
+ <pre>
+ --> ?
+ c [nn] - connect to job
+ i [nn] - interrupt job
+ k [nn] - kill job
+ j - list all jobs
+ s [shell] - start local shell
+ r [node [shell]] - start remote shell
+ q - quit erlang
+ ? | h - this message </pre>
+ <p>The <c>JCL</c> commands have the following meaning:</p>
+ <taglist>
+ <tag><c>c [nn]</c></tag>
+ <item>
+ <p>Connects to job number <c><![CDATA[<nn>]]></c> or the current
+ job. The standard shell is resumed. Operations which use
+ standard IO by the current job will be interleaved with
+ user inputs to the shell.
+ </p>
+ </item>
+ <tag><c>i [nn]</c></tag>
+ <item>
+ <p>Stops the current evaluator process for job number
+ <c>nn</c> or the current job, but does not kill the shell
+ process. Accordingly, any variable bindings and the process dictionary
+ will be preserved and the job can be connected again.
+ This command can be used to interrupt an endless loop.
+ </p>
+ </item>
+ <tag><c>k [nn]</c></tag>
+ <item>
+ <p>Kills job number <c>nn</c> or the current
+ job. All spawned processes in the job are
+ killed, provided they have not evaluated the
+ <c>group_leader/1</c> BIF and are located on
+ the local machine. Processes spawned on remote nodes will
+ not be killed.
+ </p>
+ </item>
+ <tag><c>j</c></tag>
+ <item>
+ <p>Lists all jobs. A list of all known jobs is
+ printed. The current job name is prefixed with '*'.
+ </p>
+ </item>
+ <tag><c>s</c></tag>
+ <item>
+ <p>Starts a new job. This will be assigned the new index
+ <c>[nn]</c> which can be used in references.
+ </p>
+ </item>
+ <tag><c>s [shell]</c></tag>
+ <item>
+ <p>Starts a new job. This will be assigned the new index
+ <c>[nn]</c> which can be used in references.
+ If the optional argument <c>shell</c> is given, it is assumed
+ to be a module that implements an alternative shell.
+ </p>
+ </item>
+ <tag><c>r [node]</c></tag>
+ <item>
+ <p>Starts a remote job on <c>node</c>. This is used in
+ distributed Erlang to allow a shell running on one node to
+ control a number of applications running on a network of
+ nodes.
+ If the optional argument <c>shell</c> is given, it is assumed
+ to be a module that implements an alternative shell.
+ </p>
+ </item>
+ <tag><c>q</c></tag>
+ <item>
+ <p>Quits Erlang. Note that this option is disabled if
+ Erlang is started with the ignore break, <c>+Bi</c>,
+ system flag (which may be useful e.g. when running
+ a restricted shell, see below).
+ </p>
+ </item>
+ <tag><c>?</c></tag>
+ <item>
+ <p>Displays this message.</p>
+ </item>
+ </taglist>
+ <p>It is possible to alter the behavior of shell escape by means
+ of the STDLIB application variable <c>shell_esc</c>. The value of
+ the variable can be either <c>jcl</c> (<c>erl -stdlib shell_esc jcl</c>)
+ or <c>abort</c> (<c>erl -stdlib shell_esc abort</c>). The
+ first option sets ^G to activate <c>JCL</c> mode (which is also
+ default behavior). The latter sets ^G to terminate the current
+ shell and start a new one. <c>JCL</c> mode cannot be invoked when
+ <c>shell_esc</c> is set to <c>abort</c>. </p>
+ <p>If you want an Erlang node to have a remote job active from the start
+ (rather than the default local job), you start Erlang with the
+ <c>-remsh</c> flag. Example: <c>erl -sname this_node -remsh other_node@other_host</c></p>
+ </section>
+
+ <section>
+ <title>Restricted Shell</title>
+ <p>The shell may be started in a
+ restricted mode. In this mode, the shell evaluates a function call
+ only if allowed. This feature makes it possible to, for example,
+ prevent a user from accidentally calling a function from the
+ prompt that could harm a running system (useful in combination
+ with the the system flag <em><c>+Bi</c></em>).</p>
+ <p>When the restricted shell evaluates an expression and
+ encounters a function call or an operator application,
+ it calls a callback function (with
+ information about the function call in question). This callback
+ function returns <c>true</c> to let the shell go ahead with the
+ evaluation, or <c>false</c> to abort it. There are two possible
+ callback functions for the user to implement:</p>
+ <p><em><c>local_allowed(Func, ArgList, State) -> {true,NewState} | {false,NewState}</c></em></p>
+ <p>to determine if the call to the local function <c>Func</c>
+ with arguments <c>ArgList</c> should be allowed.</p>
+ <p><em><c>non_local_allowed(FuncSpec, ArgList, State) -> {true,NewState} | {false,NewState} | {{redirect,NewFuncSpec,NewArgList},NewState}</c></em></p>
+ <p>to determine if the call to non-local function
+ <c>FuncSpec</c> (<c>{Module,Func}</c> or a fun) with arguments
+ <c>ArgList</c> should be allowed. The return value
+ <c>{redirect,NewFuncSpec,NewArgList}</c> can be used to let
+ the shell evaluate some other function than the one specified by
+ <c>FuncSpec</c> and <c>ArgList</c>.</p>
+ <p>These callback functions are in fact called from local and
+ non-local evaluation function handlers, described in the
+ <seealso marker="erl_eval">erl_eval</seealso>
+ manual page. (Arguments in <c>ArgList</c> are evaluated before the
+ callback functions are called.)</p>
+ <p>The <c>State</c> argument is a tuple
+ <c>{ShellState,ExprState}</c>. The return value <c>NewState</c>
+ has the same form. This may be used to carry a state between calls
+ to the callback functions. Data saved in <c>ShellState</c> lives
+ through an entire shell session. Data saved in <c>ExprState</c>
+ lives only through the evaluation of the current expression.</p>
+ <p>There are two ways to start a restricted shell session:</p>
+ <list type="bulleted">
+ <item>Use the STDLIB application variable <c>restricted_shell</c>
+ and specify, as its value, the name of the callback
+ module. Example (with callback functions implemented in
+ callback_mod.erl): <c>$ erl -stdlib restricted_shell callback_mod</c></item>
+ <item>From a normal shell session, call function
+ <c>shell:start_restricted/1</c>. This exits the current evaluator
+ and starts a new one in restricted mode.</item>
+ </list>
+ <p><em>Notes:</em></p>
+ <list type="bulleted">
+ <item>When restricted shell mode is activated or
+ deactivated, new jobs started on the node will run in restricted
+ or normal mode respectively.</item>
+ <item>If restricted mode has been enabled on a
+ particular node, remote shells connecting to this node will also
+ run in restricted mode.</item>
+ <item>The callback functions cannot be used to allow or disallow
+ execution of functions called from compiled code (only functions
+ called from expressions entered at the shell prompt).</item>
+ </list>
+ <p>Errors when loading the callback module is handled in different
+ ways depending on how the restricted shell is activated:</p>
+ <list type="bulleted">
+ <item>If the restricted shell is activated by setting the kernel
+ variable during emulator startup and the callback module cannot be
+ loaded, a default restricted shell allowing only the commands
+ <c>q()</c> and <c>init:stop()</c> is used as fallback.</item>
+ <item>If the restricted shell is activated using
+ <c>shell:start_restricted/1</c> and the callback module cannot be
+ loaded, an error report is sent to the error logger and the call
+ returns <c>{error,Reason}</c>.</item>
+ </list>
+ </section>
+ <funcs>
+ <func>
+ <name>history(N) -> integer()</name>
+ <fsummary>Sets the number of previous commands to keep</fsummary>
+ <type>
+ <v>N = integer()</v>
+ </type>
+ <desc>
+ <p>Sets the number of previous commands to keep in the
+ history list to <c>N</c>. The previous number is returned.
+ The default number is 20.</p>
+ </desc>
+ </func>
+ <func>
+ <name>results(N) -> integer()</name>
+ <fsummary>Sets the number of previous results to keep</fsummary>
+ <type>
+ <v>N = integer()</v>
+ </type>
+ <desc>
+ <p>Sets the number of results from previous commands to keep in
+ the history list to <c>N</c>. The previous number is returned.
+ The default number is 20.</p>
+ </desc>
+ </func>
+ <func>
+ <name>catch_exception(Bool) -> Bool</name>
+ <fsummary>Sets the exception handling of the shell</fsummary>
+ <type>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Sets the exception handling of the evaluator process. The
+ previous exception handling is returned. The default
+ (<c>false</c>) is to kill the evaluator process when an
+ exception occurs, which causes the shell to create a new
+ evaluator process. When the exception handling is set to
+ <c>true</c> the evaluator process lives on which means that
+ for instance ports and ETS tables as well as processes
+ linked to the evaluator process survive the exception.</p>
+ </desc>
+ </func>
+ <func>
+ <name>start_restricted(Module) -> ok | {error, Reason}</name>
+ <fsummary>Exits a normal shell and starts a restricted shell.</fsummary>
+ <type>
+ <v>Module = atom()</v>
+ <v>Reason = atom()</v>
+ </type>
+ <desc>
+ <p>Exits a normal shell and starts a restricted
+ shell. <c>Module</c> specifies the callback module for the
+ functions <c>local_allowed/3</c> and <c>non_local_allowed/3</c>.
+ The function is meant to be called from the shell.</p>
+ <p>If the callback module cannot be loaded, an error tuple is
+ returned. The <c>Reason</c> in the error tuple is the one
+ returned by the code loader when trying to load the code of the callback
+ module.</p>
+ </desc>
+ </func>
+ <func>
+ <name>stop_restricted() -> ok</name>
+ <fsummary>Exits a restricted shell and starts a normal shell.</fsummary>
+ <desc>
+ <p>Exits a restricted shell and starts a normal shell. The function
+ is meant to be called from the shell.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/shell_default.xml b/lib/stdlib/doc/src/shell_default.xml
new file mode 100644
index 0000000000..4f8cc6c5bb
--- /dev/null
+++ b/lib/stdlib/doc/src/shell_default.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>shell_default</title>
+ <prepared>Joe Armstrong</prepared>
+ <responsible>Joe Armstrong</responsible>
+ <docno></docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked>Joe Armstrong</checked>
+ <date>1996-09-09</date>
+ <rev>A</rev>
+ <file>shell_default.sgml</file>
+ </header>
+ <module>shell_default</module>
+ <modulesummary>Customizing the Erlang Environment</modulesummary>
+ <description>
+ <p>The functions in <c>shell_default</c> are called when no module
+ name is given in a shell command.
+ </p>
+ <p>Consider the following shell dialogue:</p>
+ <pre>
+1 > <input>lists:reverse("abc").</input>
+"cba"
+2 > <input>c(foo).</input>
+{ok, foo} </pre>
+ <p>In command one, the module <c>lists</c> is called. In command
+ two, no module name is specified. The shell searches the modules
+ <c>user_default</c> followed by <c>shell_default</c> for the
+ function <c>foo/1</c>.
+ </p>
+ <p><c>shell_default</c> is intended for "system wide"
+ customizations to the shell. <c>user_default</c> is intended for
+ "local" or individual user customizations.</p>
+ </description>
+
+ <section>
+ <title>Hint</title>
+ <p>To add your own commands to the shell, create a module called
+ <c>user_default</c> and add the commands you want. Then add the
+ following line as the <em>first</em> line in your <c>.erlang</c> file in your
+ home directory. </p>
+ <pre>
+code:load_abs("$PATH/user_default"). </pre>
+ <p><c>$PATH</c> is the directory where your
+ <c>user_default</c> module can be found.</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/slave.xml b/lib/stdlib/doc/src/slave.xml
new file mode 100644
index 0000000000..168d83f301
--- /dev/null
+++ b/lib/stdlib/doc/src/slave.xml
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>slave</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>slave</module>
+ <modulesummary>Functions to Starting and Controlling Slave Nodes</modulesummary>
+ <description>
+ <p>This module provides functions for starting Erlang slave nodes.
+ All slave nodes which are started by a master will terminate
+ automatically when the master terminates. All TTY output produced
+ at the slave will be sent back to the master node. File I/O is
+ done via the master.</p>
+ <p>Slave nodes on other hosts than the current one are started with
+ the program <c>rsh</c>. The user must be allowed to <c>rsh</c> to
+ the remote hosts without being prompted for a password. This can
+ be arranged in a number of ways (refer to the <c>rsh</c>
+ documentation for details). A slave node started on the same host
+ as the master inherits certain environment values from the master,
+ such as the current directory and the environment variables. For
+ what can be assumed about the environment when a slave is started
+ on another host, read the documentation for the <c>rsh</c>
+ program.</p>
+ <p>An alternative to the <c>rsh</c> program can be specified on
+ the command line to <c>erl</c> as follows: <c>-rsh Program</c>.</p>
+ <p>The slave node should use the same file system at the master. At
+ least, Erlang/OTP should be installed in the same place on both
+ computers and the same version of Erlang should be used.</p>
+ <p>Currently, a node running on Windows NT can only start slave
+ nodes on the host on which it is running.</p>
+ <p>The master node must be alive.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>start(Host) -></name>
+ <name>start(Host, Name) -></name>
+ <name>start(Host, Name, Args) -> {ok, Node} | {error, Reason}</name>
+ <fsummary>Start a slave node on a host</fsummary>
+ <type>
+ <v>Host = Name = atom()</v>
+ <v>Args = string()</v>
+ <v>Node = node()</v>
+ <v>Reason = timeout | no_rsh | {already_running, Node}</v>
+ </type>
+ <desc>
+ <p>Starts a slave node on the host <c>Host</c>. Host names need
+ not necessarily be specified as fully qualified names; short
+ names can also be used. This is the same condition that
+ applies to names of distributed Erlang nodes.</p>
+ <p>The name of the started node will be <c>Name@Host</c>. If no
+ name is provided, the name will be the same as the node which
+ executes the call (with the exception of the host name part of
+ the node name).</p>
+ <p>The slave node resets its <c>user</c> process so that all
+ terminal I/O which is produced at the slave is automatically
+ relayed to the master. Also, the file process will be relayed
+ to the master.</p>
+ <p>The <c>Args</c> argument is used to set <c>erl</c> command
+ line arguments. If provided, it is passed to the new node and
+ can be used for a variety of purposes. See
+ <seealso marker="erts:erl#erl">erl(1)</seealso></p>
+ <p>As an example, suppose that we want to start a slave node at
+ host <c>H</c> with the node name <c>Name@H</c>, and we also
+ want the slave node to have the following properties:</p>
+ <list type="bulleted">
+ <item>
+ <p>directory <c>Dir</c> should be added to the code path;</p>
+ </item>
+ <item>
+ <p>the Mnesia directory should be set to <c>M</c>;</p>
+ </item>
+ <item>
+ <p>the unix <c>DISPLAY</c> environment variable should be
+ set to the display of the master node.</p>
+ </item>
+ </list>
+ <p>The following code is executed to achieve this:</p>
+ <code type="none">
+E = " -env DISPLAY " ++ net_adm:localhost() ++ ":0 ",
+Arg = "-mnesia_dir " ++ M ++ " -pa " ++ Dir ++ E,
+slave:start(H, Name, Arg).</code>
+ <p>If successful, the function returns <c>{ok, Node}</c>,
+ where <c>Node</c> is the name of the new node. Otherwise it
+ returns <c>{error, Reason}</c>, where <c>Reason</c> can be
+ one of:</p>
+ <taglist>
+ <tag><c>timeout</c></tag>
+ <item>
+ <p>The master node failed to get in contact with the slave
+ node. This can happen in a number of circumstances:</p>
+ <list type="bulleted">
+ <item>Erlang/OTP is not installed on the remote host</item>
+ <item>the file system on the other host has a different
+ structure to the the master</item>
+ <item>the Erlang nodes have different cookies.</item>
+ </list>
+ </item>
+ <tag><c>no_rsh</c></tag>
+ <item>
+ <p>There is no <c>rsh</c> program on the computer.</p>
+ </item>
+ <tag><c>{already_running, Node}</c></tag>
+ <item>
+ <p>A node with the name <c>Name@Host</c> already exists.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>start_link(Host) -></name>
+ <name>start_link(Host, Name) -></name>
+ <name>start_link(Host, Name, Args) -> {ok, Node} | {error, Reason}</name>
+ <fsummary>Start and link to a slave node on a host</fsummary>
+ <type>
+ <v>Host = Name = atom()</v>
+ <v>Args = string()</v>
+ <v>Node = node()</v>
+ <v>Reason = timeout | no_rsh | {already_running, Node}</v>
+ </type>
+ <desc>
+ <p>Starts a slave node in the same way as <c>start/1,2,3</c>,
+ except that the slave node is linked to the currently
+ executing process. If that process terminates, the slave node
+ also terminates.</p>
+ <p>See <c>start/1,2,3</c> for a description of arguments and
+ return values.</p>
+ </desc>
+ </func>
+ <func>
+ <name>stop(Node) -> ok</name>
+ <fsummary>Stop (kill) a node</fsummary>
+ <type>
+ <v>Node = node()</v>
+ </type>
+ <desc>
+ <p>Stops (kills) a node.</p>
+ </desc>
+ </func>
+ <func>
+ <name>pseudo([Master | ServerList]) -> ok</name>
+ <fsummary>Start a number of pseudo servers</fsummary>
+ <type>
+ <v>Master = node()</v>
+ <v>ServerList = [atom()]</v>
+ </type>
+ <desc>
+ <p>Calls <c>pseudo(Master, ServerList)</c>. If we want to start
+ a node from the command line and set up a number of pseudo
+ servers, an Erlang runtime system can be started as
+ follows:</p>
+ <pre>
+% erl -name abc -s slave pseudo klacke@super x --</pre>
+ </desc>
+ </func>
+ <func>
+ <name>pseudo(Master, ServerList) -> ok</name>
+ <fsummary>Start a number of pseudo servers</fsummary>
+ <type>
+ <v>Master = node()</v>
+ <v>ServerList = [atom()]</v>
+ </type>
+ <desc>
+ <p>Starts a number of pseudo servers. A pseudo server is a
+ server with a registered name which does absolutely nothing
+ but pass on all message to the real server which executes at a
+ master node. A pseudo server is an intermediary which only has
+ the same registered name as the real server.</p>
+ <p>For example, if we have started a slave node <c>N</c> and
+ want to execute <c>pxw</c> graphics code on this node, we can
+ start the server <c>pxw_server</c> as a pseudo server at
+ the slave node. The following code illustrates:</p>
+ <code type="none">
+rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code>
+ </desc>
+ </func>
+ <func>
+ <name>relay(Pid)</name>
+ <fsummary>Run a pseudo server</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Runs a pseudo server. This function never returns any value
+ and the process which executes the function will receive
+ messages. All messages received will simply be passed on to
+ <c>Pid</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/sofs.xml b/lib/stdlib/doc/src/sofs.xml
new file mode 100644
index 0000000000..ac434ec5b7
--- /dev/null
+++ b/lib/stdlib/doc/src/sofs.xml
@@ -0,0 +1,1781 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2001</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>sofs</title>
+ <prepared>Hans Bolinder</prepared>
+ <responsible>nobody</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2001-08-25</date>
+ <rev>PA1</rev>
+ <file>sofs.sgml</file>
+ </header>
+ <module>sofs</module>
+ <modulesummary>Functions for Manipulating Sets of Sets</modulesummary>
+ <description>
+ <p>The <c>sofs</c> module implements operations on finite sets and
+ relations represented as sets. Intuitively, a set is a
+ collection of elements; every element belongs to the set, and
+ the set contains every element.</p>
+ <p>Given a set A and a sentence S(x), where x is a free variable,
+ a new set B whose elements are exactly those elements of A for
+ which S(x) holds can be formed, this is denoted B&nbsp;=
+ {x&nbsp;in&nbsp;A&nbsp;: S(x)}. Sentences are expressed using
+ the logical operators "for some" (or "there exists"), "for all",
+ "and", "or", "not". If the existence of a set containing all the
+ specified elements is known (as will always be the case in this
+ module), we write B&nbsp;= {x&nbsp;: S(x)}. </p>
+ <p>The <em>unordered set</em> containing the elements a, b and c
+ is denoted {a,&nbsp;b,&nbsp;c}. This notation is not to be
+ confused with tuples. The <em>ordered pair</em> of a and b, with
+ first <em>coordinate</em> a and second coordinate b, is denoted
+ (a,&nbsp;b). An ordered pair is an <em>ordered set</em> of two
+ elements. In this module ordered sets can contain one, two or
+ more elements, and parentheses are used to enclose the elements.
+ Unordered sets and ordered sets are orthogonal, again in this
+ module; there is no unordered set equal to any ordered set.</p>
+ <p>The set that contains no elements is called the <em>empty set</em>.
+ If two sets A and B contain the same elements, then A
+ is <marker id="equal"></marker><em>equal</em> to B, denoted
+ A&nbsp;=&nbsp;B. Two ordered sets are equal if they contain the
+ same number of elements and have equal elements at each
+ coordinate. If a set A contains all elements that B contains,
+ then B is a <marker id="subset"></marker><em>subset</em> of A.
+ The <marker id="union"></marker><em>union</em> of two sets A and B is
+ the smallest set that contains all elements of A and all elements of
+ B. The <marker id="intersection"></marker><em>intersection</em> of two
+ sets A and B is the set that contains all elements of A that
+ belong to B.
+ Two sets are <marker id="disjoint"></marker><em>disjoint</em> if their
+ intersection is the empty set.
+ The <marker id="difference"></marker><em>difference</em> of
+ two sets A and B is the set that contains all elements of A that
+ do not belong to B.
+ The <marker id="symmetric_difference"></marker><em>symmetric
+ difference</em> of
+ two sets is the set that contains those element that belong to
+ either of the two sets, but not both.
+ The <marker id="union_n"></marker><em>union</em> of a collection
+ of sets is the smallest set that contains all the elements that
+ belong to at least one set of the collection.
+ The <marker id="intersection_n"></marker><em>intersection</em> of
+ a non-empty collection of sets is the set that contains all elements
+ that belong to every set of the collection.</p>
+ <p>The <marker id="Cartesian_product"></marker><em>Cartesian
+ product</em> of
+ two sets X and Y, denoted X&nbsp;&times;&nbsp;Y, is the set
+ {a&nbsp;: a&nbsp;= (x,&nbsp;y) for some x&nbsp;in&nbsp;X and for
+ some y&nbsp;in&nbsp;Y}.
+ A <marker id="relation"></marker><em>relation</em> is a subset of
+ X&nbsp;&times;&nbsp;Y. Let R be a relation. The fact that
+ (x,&nbsp;y) belongs to R is written as x&nbsp;R&nbsp;y. Since
+ relations are sets, the definitions of the last paragraph
+ (subset, union, and so on) apply to relations as well.
+ The <marker id="domain"></marker><em>domain</em> of R is the
+ set {x&nbsp;: x&nbsp;R&nbsp;y for some y&nbsp;in&nbsp;Y}.
+ The <marker id="range"></marker><em>range</em> of R is the
+ set {y&nbsp;: x&nbsp;R&nbsp;y for some x&nbsp;in&nbsp;X}.
+ The <marker id="converse"></marker><em>converse</em> of R is the
+ set {a&nbsp;: a&nbsp;= (y,&nbsp;x) for some
+ (x,&nbsp;y)&nbsp;in&nbsp;R}. If A is a subset of X, then
+ the <marker id="image"></marker><em>image</em> of
+ A under R is the set {y&nbsp;: x&nbsp;R&nbsp;y for some
+ x&nbsp;in&nbsp;A}, and if B is a subset of Y, then
+ the <marker id="inverse_image"></marker><em>inverse image</em> of B is
+ the set {x&nbsp;: x&nbsp;R&nbsp;y for some y&nbsp;in&nbsp;B}. If R is a
+ relation from X to Y and S is a relation from Y to Z, then
+ the <marker id="relative_product"></marker><em>relative product</em> of
+ R and S is the relation T from X to Z defined so that x&nbsp;T&nbsp;z
+ if and only if there exists an element y in Y such that
+ x&nbsp;R&nbsp;y and y&nbsp;S&nbsp;z.
+ The <marker id="restriction"></marker><em>restriction</em> of R to A is
+ the set S defined so that x&nbsp;S&nbsp;y if and only if there exists an
+ element x in A such that x&nbsp;R&nbsp;y. If S is a restriction
+ of R to A, then R is
+ an <marker id="extension"></marker><em>extension</em> of S to X.
+ If X&nbsp;=&nbsp;Y then we call R a relation <em>in</em> X.
+ The <marker id="field"></marker><em>field</em> of a relation R in X
+ is the union of the domain of R and the range of R.
+ If R is a relation in X, and
+ if S is defined so that x&nbsp;S&nbsp;y if x&nbsp;R&nbsp;y and
+ not x&nbsp;=&nbsp;y, then S is
+ the <marker id="strict_relation"></marker><em>strict</em> relation
+ corresponding to
+ R, and vice versa, if S is a relation in X, and if R is defined
+ so that x&nbsp;R&nbsp;y if x&nbsp;S&nbsp;y or x&nbsp;=&nbsp;y,
+ then R is the <marker id="weak_relation"></marker><em>weak</em> relation
+ corresponding to S. A relation R in X is <em>reflexive</em> if
+ x&nbsp;R&nbsp;x for every element x of X; it is
+ <em>symmetric</em> if x&nbsp;R&nbsp;y implies that
+ y&nbsp;R&nbsp;x; and it is <em>transitive</em> if
+ x&nbsp;R&nbsp;y and y&nbsp;R&nbsp;z imply that x&nbsp;R&nbsp;z.</p>
+ <p>A <marker id="function"></marker><em>function</em> F is a relation, a
+ subset of X&nbsp;&times;&nbsp;Y, such that the domain of F is
+ equal to X and such that for every x in X there is a unique
+ element y in Y with (x,&nbsp;y) in F. The latter condition can
+ be formulated as follows: if x&nbsp;F&nbsp;y and x&nbsp;F&nbsp;z
+ then y&nbsp;=&nbsp;z. In this module, it will not be required
+ that the domain of F be equal to X for a relation to be
+ considered a function. Instead of writing
+ (x,&nbsp;y)&nbsp;in&nbsp;F or x&nbsp;F&nbsp;y, we write
+ F(x)&nbsp;=&nbsp;y when F is a function, and say that F maps x
+ onto y, or that the value of F at x is y. Since functions are
+ relations, the definitions of the last paragraph (domain, range,
+ and so on) apply to functions as well. If the converse of a
+ function F is a function F', then F' is called
+ the <marker id="inverse"></marker><em>inverse</em> of F.
+ The relative product of two functions F1 and F2 is called
+ the <marker id="composite"></marker><em>composite</em> of F1 and F2
+ if the range of F1 is a subset of the domain of F2. </p>
+ <p>Sometimes, when the range of a function is more important than
+ the function itself, the function is called a <em>family</em>.
+ The domain of a family is called the <em>index set</em>, and the
+ range is called the <em>indexed set</em>. If x is a family from
+ I to X, then x[i] denotes the value of the function at index i.
+ The notation "a family in X" is used for such a family. When the
+ indexed set is a set of subsets of a set X, then we call x
+ a <marker id="family"></marker><em>family of subsets</em> of X. If x
+ is a family of subsets of X, then the union of the range of x is
+ called the <em>union of the family</em> x. If x is non-empty
+ (the index set is non-empty),
+ the <em>intersection of the family</em> x is the intersection of
+ the range of x. In this
+ module, the only families that will be considered are families
+ of subsets of some set X; in the following the word "family"
+ will be used for such families of subsets.</p>
+ <p>A <marker id="partition"></marker><em>partition</em> of a set X is a
+ collection S of non-empty subsets of X whose union is X and
+ whose elements are pairwise disjoint. A relation in a set is an
+ <em>equivalence relation</em> if it is reflexive, symmetric and
+ transitive. If R is an equivalence relation in X, and x is an
+ element of X,
+ the <marker id="equivalence_class"></marker><em>equivalence
+ class</em> of x with respect to R is the set of all those
+ elements y of X for which x&nbsp;R&nbsp;y holds. The equivalence
+ classes constitute a partitioning of X. Conversely, if C is a
+ partition of X, then the relation that holds for any two
+ elements of X if they belong to the same equivalence class, is
+ an equivalence relation induced by the partition C. If R is an
+ equivalence relation in X, then
+ the <marker id="canonical_map"></marker><em>canonical map</em> is
+ the function that maps every element of X onto its equivalence class.
+ </p>
+ <p>Relations as defined above (as sets of ordered pairs) will from
+ now on be referred to as <em>binary relations</em>. We call a
+ set of ordered sets (x[1],&nbsp;...,&nbsp;x[n])
+ an <em>(n-ary) relation</em>, and say that the relation is a subset of
+ the <marker id="Cartesian_product_tuple"></marker>Cartesian product
+ X[1]&nbsp;&times;&nbsp;...&nbsp;&times;&nbsp;X[n] where x[i] is
+ an element of X[i], 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;=&nbsp;n.
+ The <marker id="projection"></marker><em>projection</em> of an n-ary
+ relation R onto coordinate i is the set {x[i]&nbsp;:
+ (x[1],&nbsp;...,&nbsp;x[i],&nbsp;...,&nbsp;x[n]) in R for some
+ x[j]&nbsp;in&nbsp;X[j], 1&nbsp;&lt;=&nbsp;j&nbsp;&lt;=&nbsp;n
+ and not i&nbsp;=&nbsp;j}. The projections of a binary relation R
+ onto the first and second coordinates are the domain and the
+ range of R respectively. The relative product of binary
+ relations can be generalized to n-ary relations as follows. Let
+ TR be an ordered set (R[1],&nbsp;...,&nbsp;R[n]) of binary
+ relations from X to Y[i] and S a binary relation from
+ (Y[1]&nbsp;&times;&nbsp;...&nbsp;&times;&nbsp;Y[n]) to Z.
+ The <marker id="tuple_relative_product"></marker><em>relative
+ product</em> of
+ TR and S is the binary relation T from X to Z defined so that
+ x&nbsp;T&nbsp;z if and only if there exists an element y[i] in
+ Y[i] for each 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;=&nbsp;n such that
+ x&nbsp;R[i]&nbsp;y[i] and
+ (y[1],&nbsp;...,&nbsp;y[n])&nbsp;S&nbsp;z. Now let TR be a an
+ ordered set (R[1],&nbsp;...,&nbsp;R[n]) of binary relations from
+ X[i] to Y[i] and S a subset of
+ X[1]&nbsp;&times;&nbsp;...&nbsp;&times;&nbsp;X[n].
+ The <marker id="multiple_relative_product"></marker><em>multiple
+ relative product</em> of TR and and S is defined to be the
+ set {z&nbsp;: z&nbsp;= ((x[1],&nbsp;...,&nbsp;x[n]), (y[1],...,y[n]))
+ for some (x[1],&nbsp;...,&nbsp;x[n])&nbsp;in&nbsp;S and for some
+ (x[i],&nbsp;y[i]) in R[i],
+ 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;=&nbsp;n}.
+ The <marker id="natural_join"></marker><em>natural join</em> of
+ an n-ary relation R
+ and an m-ary relation S on coordinate i and j is defined to be
+ the set {z&nbsp;: z&nbsp;= (x[1],&nbsp;...,&nbsp;x[n],&nbsp;
+ y[1],&nbsp;...,&nbsp;y[j-1],&nbsp;y[j+1],&nbsp;...,&nbsp;y[m])
+ for some (x[1],&nbsp;...,&nbsp;x[n])&nbsp;in&nbsp;R and for some
+ (y[1],&nbsp;...,&nbsp;y[m])&nbsp;in&nbsp;S such that
+ x[i]&nbsp;=&nbsp;y[j]}.</p>
+ <p><marker id="sets_definition"></marker>The sets recognized by this
+ module will be represented by elements of the relation Sets, defined as
+ the smallest set such that:</p>
+ <list type="bulleted">
+ <item>for every atom T except '_' and for every term X,
+ (T,&nbsp;X) belongs to Sets (<em>atomic sets</em>);
+ </item>
+ <item>(['_'],&nbsp;[]) belongs to Sets (the <em>untyped empty set</em>);
+ </item>
+ <item>for every tuple T&nbsp;= {T[1],&nbsp;...,&nbsp;T[n]} and
+ for every tuple X&nbsp;= {X[1],&nbsp;...,&nbsp;X[n]}, if
+ (T[i],&nbsp;X[i]) belongs to Sets for every
+ 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;=&nbsp;n then (T,&nbsp;X) belongs
+ to Sets (<em>ordered sets</em>);
+ </item>
+ <item>for every term T, if X is the empty list or a non-empty
+ sorted list [X[1],&nbsp;...,&nbsp;X[n]] without duplicates
+ such that (T,&nbsp;X[i]) belongs to Sets for every
+ 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;=&nbsp;n, then ([T],&nbsp;X)
+ belongs to Sets (<em>typed unordered sets</em>).</item>
+ </list>
+ <p>An <marker id="external_set"></marker><em>external set</em> is an
+ element of the range of Sets.
+ A <marker id="type"></marker><em>type</em>
+ is an element of the domain of Sets. If S is an element
+ (T,&nbsp;X) of Sets, then T is
+ a <marker id="valid_type"></marker><em>valid type</em> of X,
+ T is the type of S, and X is the external set
+ of S. <seealso marker="#from_term">from_term/2</seealso> creates a
+ set from a type and an Erlang term turned into an external set.</p>
+ <p>The actual sets represented by Sets are the elements of the
+ range of the function Set from Sets to Erlang terms and sets of
+ Erlang terms:</p>
+ <list type="bulleted">
+ <item>Set(T,Term)&nbsp;= Term, where T is an atom;</item>
+ <item>Set({T[1],&nbsp;...,&nbsp;T[n]},&nbsp;{X[1],&nbsp;...,&nbsp;X[n]})
+ &nbsp;= (Set(T[1],&nbsp;X[1]),&nbsp;...,&nbsp;Set(T[n],&nbsp;X[n]));</item>
+ <item>Set([T],&nbsp;[X[1],&nbsp;...,&nbsp;X[n]])
+ &nbsp;= {Set(T,&nbsp;X[1]),&nbsp;...,&nbsp;Set(T,&nbsp;X[n])};</item>
+ <item>Set([T],&nbsp;[])&nbsp;= {}.</item>
+ </list>
+ <p>When there is no risk of confusion, elements of Sets will be
+ identified with the sets they represent. For instance, if U is
+ the result of calling <c>union/2</c> with S1 and S2 as
+ arguments, then U is said to be the union of S1 and S2. A more
+ precise formulation would be that Set(U) is the union of Set(S1)
+ and Set(S2).</p>
+ <p>The types are used to implement the various conditions that
+ sets need to fulfill. As an example, consider the relative
+ product of two sets R and S, and recall that the relative
+ product of R and S is defined if R is a binary relation to Y and
+ S is a binary relation from Y. The function that implements the relative
+ product, <seealso marker="#relprod_impl">relative_product/2</seealso>, checks
+ that the arguments represent binary relations by matching [{A,B}]
+ against the type of the first argument (Arg1 say), and [{C,D}]
+ against the type of the second argument (Arg2 say). The fact
+ that [{A,B}] matches the type of Arg1 is to be interpreted as
+ Arg1 representing a binary relation from X to Y, where X is
+ defined as all sets Set(x) for some element x in Sets the type
+ of which is A, and similarly for Y. In the same way Arg2 is
+ interpreted as representing a binary relation from W to Z.
+ Finally it is checked that B matches C, which is sufficient to
+ ensure that W is equal to Y. The untyped empty set is handled
+ separately: its type, ['_'], matches the type of any unordered
+ set.</p>
+ <p>A few functions of this module (<c>drestriction/3</c>,
+ <c>family_projection/2</c>, <c>partition/2</c>,
+ <c>partition_family/2</c>, <c>projection/2</c>,
+ <c>restriction/3</c>, <c>substitution/2</c>) accept an Erlang
+ function as a means to modify each element of a given unordered
+ set. Such a function, called SetFun in the following, can be
+ specified as a functional object (fun), a tuple
+ <c>{external,&nbsp;Fun}</c>, or an integer. If SetFun is
+ specified as a fun, the fun is applied to each element of the
+ given set and the return value is assumed to be a set. If SetFun
+ is specified as a tuple <c>{external, Fun}</c>, Fun is applied
+ to the external set of each element of the given set and the
+ return value is assumed to be an external set. Selecting the
+ elements of an unordered set as external sets and assembling a
+ new unordered set from a list of external sets is in the present
+ implementation more efficient than modifying each element as a
+ set. However, this optimization can only be utilized when the
+ elements of the unordered set are atomic or ordered sets. It
+ must also be the case that the type of the elements matches some
+ clause of Fun (the type of the created set is the result of
+ applying Fun to the type of the given set), and that Fun does
+ nothing but selecting, duplicating or rearranging parts of the
+ elements. Specifying a SetFun as an integer I is equivalent to
+ specifying <c>{external, fun(X)&nbsp;-> element(I,&nbsp;X)}</c>,
+ but is to be preferred since it makes it possible to handle this
+ case even more efficiently. Examples of SetFuns:</p>
+ <pre>
+{sofs, union}
+fun(S) -> sofs:partition(1, S) end
+{external, fun(A) -> A end}
+{external, fun({A,_,C}) -> {C,A} end}
+{external, fun({_,{_,C}}) -> C end}
+{external, fun({_,{_,{_,E}=C}}) -> {E,{E,C}} end}
+2</pre>
+ <p>The order in which a SetFun is applied to the elements of an
+ unordered set is not specified, and may change in future
+ versions of sofs.</p>
+ <p>The execution time of the functions of this module is dominated
+ by the time it takes to sort lists. When no sorting is needed,
+ the execution time is in the worst case proportional to the sum
+ of the sizes of the input arguments and the returned value. A
+ few functions execute in constant time: <c>from_external</c>,
+ <c>is_empty_set</c>, <c>is_set</c>, <c>is_sofs_set</c>,
+ <c>to_external</c>, <c>type</c>.</p>
+ <p>The functions of this module exit the process with a
+ <c>badarg</c>, <c>bad_function</c>, or <c>type_mismatch</c>
+ message when given badly formed arguments or sets the types of
+ which are not compatible.</p>
+ <p><em>Types</em></p>
+ <pre>
+anyset() = -&nbsp;an unordered, ordered or atomic set&nbsp;-
+binary_relation() = -&nbsp;a binary relation&nbsp;-
+bool() = true | false
+external_set() = -&nbsp;an external set&nbsp;-
+family() = -&nbsp;a family (of subsets)&nbsp;-
+function() = -&nbsp;a function&nbsp;-
+ordset() = -&nbsp;an ordered set&nbsp;-
+relation() = -&nbsp;an n-ary relation&nbsp;-
+set() = -&nbsp;an unordered set&nbsp;-
+set_of_sets() = -&nbsp;an unordered set of set()&nbsp;-
+set_fun() = integer() >= 1
+ | {external, fun(external_set()) -> external_set()}
+ | fun(anyset()) -> anyset()
+spec_fun() = {external, fun(external_set()) -> bool()}
+ | fun(anyset()) -> bool()
+type() = -&nbsp;a type&nbsp;- </pre>
+ </description>
+ <funcs>
+ <func>
+ <name>a_function(Tuples [, Type]) -> Function</name>
+ <fsummary>Create a function.</fsummary>
+ <type>
+ <v>Function = function()</v>
+ <v>Tuples = [tuple()]</v>
+ <v>Type = type()</v>
+ </type>
+ <desc>
+ <p>Creates a <seealso marker="#function">function</seealso>.
+ <c>a_function(F,&nbsp;T)</c> is equivalent to
+ <c>from_term(F,&nbsp;T)</c>, if the result is a function. If
+ no <seealso marker="#type">type</seealso> is explicitly
+ given, <c>[{atom,&nbsp;atom}]</c> is used as type of the
+ function.</p>
+ </desc>
+ </func>
+ <func>
+ <name>canonical_relation(SetOfSets) -> BinRel</name>
+ <fsummary>Return the canonical map.</fsummary>
+ <type>
+ <v>BinRel = binary_relation()</v>
+ <v>SetOfSets = set_of_sets()</v>
+ </type>
+ <desc>
+ <p>Returns the binary relation containing the elements
+ (E,&nbsp;Set) such that Set belongs to SetOfSets and E
+ belongs to Set. If SetOfSets is
+ a <seealso marker="#partition">partition</seealso> of a set X and
+ R is the equivalence relation in X induced by SetOfSets, then the
+ returned relation is
+ the <seealso marker="#canonical_map">canonical map</seealso> from
+ X onto the equivalence classes with respect to R.</p>
+ <pre>
+1> <input>Ss = sofs:from_term([[a,b],[b,c]]),</input>
+<input>CR = sofs:canonical_relation(Ss),</input>
+<input>sofs:to_external(CR).</input>
+[{a,[a,b]},{b,[a,b]},{b,[b,c]},{c,[b,c]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>composite(Function1, Function2) -> Function3</name>
+ <fsummary>Return the composite of two functions.</fsummary>
+ <type>
+ <v>Function1 = Function2 = Function3 = function()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#composite">composite</seealso> of
+ the functions Function1 and Function2.</p>
+ <pre>
+1> <input>F1 = sofs:a_function([{a,1},{b,2},{c,2}]),</input>
+<input>F2 = sofs:a_function([{1,x},{2,y},{3,z}]),</input>
+<input>F = sofs:composite(F1, F2),</input>
+<input>sofs:to_external(F).</input>
+[{a,x},{b,y},{c,y}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>constant_function(Set, AnySet) -> Function</name>
+ <fsummary>Create the function that maps each element of a
+ set onto another set.</fsummary>
+ <type>
+ <v>AnySet = anyset()</v>
+ <v>Function = function()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Creates the <seealso marker="#function">function</seealso>
+ that maps each element of the set Set onto AnySet.</p>
+ <pre>
+1> <input>S = sofs:set([a,b]),</input>
+<input>E = sofs:from_term(1),</input>
+<input>R = sofs:constant_function(S, E),</input>
+<input>sofs:to_external(R).</input>
+[{a,1},{b,1}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>converse(BinRel1) -> BinRel2</name>
+ <fsummary>Return the converse of a binary relation.</fsummary>
+ <type>
+ <v>BinRel1 = BinRel2 = binary_relation()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#converse">converse</seealso>
+ of the binary relation BinRel1.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,a},{2,b},{3,a}]),</input>
+<input>R2 = sofs:converse(R1),</input>
+<input>sofs:to_external(R2).</input>
+[{a,1},{a,3},{b,2}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>difference(Set1, Set2) -> Set3</name>
+ <fsummary>Return the difference of two sets.</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#difference">difference</seealso> of
+ the sets Set1 and Set2.</p>
+ </desc>
+ </func>
+ <func>
+ <name>digraph_to_family(Graph [, Type]) -> Family</name>
+ <fsummary>Create a family from a directed graph.</fsummary>
+ <type>
+ <v>Graph = digraph() -&nbsp;see digraph(3)&nbsp;-</v>
+ <v>Family = family()</v>
+ <v>Type = type()</v>
+ </type>
+ <desc>
+ <p>Creates a <seealso marker="#family">family</seealso> from
+ the directed graph Graph. Each vertex a of Graph is
+ represented by a pair (a,&nbsp;{b[1],&nbsp;...,&nbsp;b[n]})
+ where the b[i]'s are the out-neighbours of a. If no type is
+ explicitly given, [{atom,&nbsp;[atom]}] is used as type of
+ the family. It is assumed that Type is
+ a <seealso marker="#valid_type">valid type</seealso> of the
+ external set of the family.</p>
+ <p>If G is a directed graph, it holds that the vertices and
+ edges of G are the same as the vertices and edges of
+ <c>family_to_digraph(digraph_to_family(G))</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>domain(BinRel) -> Set</name>
+ <fsummary>Return the domain of a binary relation.</fsummary>
+ <type>
+ <v>BinRel = binary_relation()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#domain">domain</seealso> of
+ the binary relation BinRel.</p>
+ <pre>
+1> <input>R = sofs:relation([{1,a},{1,b},{2,b},{2,c}]),</input>
+<input>S = sofs:domain(R),</input>
+<input>sofs:to_external(S).</input>
+[1,2]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>drestriction(BinRel1, Set) -> BinRel2</name>
+ <fsummary>Return a restriction of a binary relation.</fsummary>
+ <type>
+ <v>BinRel1 = BinRel2 = binary_relation()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the difference between the binary relation BinRel1
+ and the <seealso marker="#restriction">restriction</seealso>
+ of BinRel1 to Set.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,a},{2,b},{3,c}]),</input>
+<input>S = sofs:set([2,4,6]),</input>
+<input>R2 = sofs:drestriction(R1, S),</input>
+<input>sofs:to_external(R2).</input>
+[{1,a},{3,c}]</pre>
+ <p><c>drestriction(R,&nbsp;S)</c> is equivalent to
+ <c>difference(R,&nbsp;restriction(R,&nbsp;S))</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>drestriction(SetFun, Set1, Set2) -> Set3</name>
+ <fsummary>Return a restriction of a relation.</fsummary>
+ <type>
+ <v>SetFun = set_fun()</v>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns a subset of Set1 containing those elements that do
+ not yield an element in Set2 as the result of applying
+ SetFun.</p>
+ <pre>
+1> <input>SetFun = {external, fun({_A,B,C}) -> {B,C} end},</input>
+<input>R1 = sofs:relation([{a,aa,1},{b,bb,2},{c,cc,3}]),</input>
+<input>R2 = sofs:relation([{bb,2},{cc,3},{dd,4}]),</input>
+<input>R3 = sofs:drestriction(SetFun, R1, R2),</input>
+<input>sofs:to_external(R3).</input>
+[{a,aa,1}]</pre>
+ <p><c>drestriction(F,&nbsp;S1,&nbsp;S2)</c> is equivalent to
+ <c>difference(S1,&nbsp;restriction(F,&nbsp;S1,&nbsp;S2))</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>empty_set() -> Set</name>
+ <fsummary>Return the untyped empty set.</fsummary>
+ <type>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#sets_definition">untyped empty
+ set</seealso>. <c>empty_set()</c> is equivalent to
+ <c>from_term([],&nbsp;['_'])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>extension(BinRel1, Set, AnySet) -> BinRel2</name>
+ <fsummary>Extend the domain of a binary relation.</fsummary>
+ <type>
+ <v>AnySet = anyset()</v>
+ <v>BinRel1 = BinRel2 = binary_relation()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#extension">extension</seealso> of
+ BinRel1 such that
+ for each element E in Set that does not belong to the
+ <seealso marker="#domain">domain</seealso> of BinRel1,
+ BinRel2 contains the pair (E,&nbsp;AnySet).</p>
+ <pre>
+1> <input>S = sofs:set([b,c]),</input>
+<input>A = sofs:empty_set(),</input>
+<input>R = sofs:family([{a,[1,2]},{b,[3]}]),</input>
+<input>X = sofs:extension(R, S, A),</input>
+<input>sofs:to_external(X).</input>
+[{a,[1,2]},{b,[3]},{c,[]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family(Tuples [, Type]) -> Family</name>
+ <fsummary>Create a family of subsets.</fsummary>
+ <type>
+ <v>Family = family()</v>
+ <v>Tuples = [tuple()]</v>
+ <v>Type = type()</v>
+ </type>
+ <desc>
+ <p>Creates a <seealso marker="#family">family of subsets</seealso>.
+ <c>family(F,&nbsp;T)</c> is equivalent to
+ <c>from_term(F,&nbsp;T)</c>, if the result is a family. If
+ no <seealso marker="#type">type</seealso> is explicitly
+ given, <c>[{atom,&nbsp;[atom]}]</c> is used as type of the
+ family.</p>
+ </desc>
+ </func>
+ <func>
+ <name>family_difference(Family1, Family2) -> Family3</name>
+ <fsummary>Return the difference of two families.</fsummary>
+ <type>
+ <v>Family1 = Family2 = Family3 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 and Family2
+ are <seealso marker="#family">families</seealso>, then
+ Family3 is the family
+ such that the index set is equal to the index set of
+ Family1, and Family3[i] is the difference between Family1[i]
+ and Family2[i] if Family2 maps i, Family1[i] otherwise.</p>
+ <pre>
+1> <input>F1 = sofs:family([{a,[1,2]},{b,[3,4]}]),</input>
+<input>F2 = sofs:family([{b,[4,5]},{c,[6,7]}]),</input>
+<input>F3 = sofs:family_difference(F1, F2),</input>
+<input>sofs:to_external(F3).</input>
+[{a,[1,2]},{b,[3]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_domain(Family1) -> Family2</name>
+ <fsummary>Return a family of domains.</fsummary>
+ <type>
+ <v>Family1 = Family2 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 is a <seealso marker="#family">family</seealso>
+ and Family1[i] is a binary relation for every i in the index
+ set of Family1, then Family2 is the family with the same
+ index set as Family1 such that Family2[i] is
+ the <seealso marker="#domain">domain</seealso> of Family1[i].</p>
+ <pre>
+1> <input>FR = sofs:from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),</input>
+<input>F = sofs:family_domain(FR),</input>
+<input>sofs:to_external(F).</input>
+[{a,[1,2,3]},{b,[]},{c,[4,5]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_field(Family1) -> Family2</name>
+ <fsummary>Return a family of fields.</fsummary>
+ <type>
+ <v>Family1 = Family2 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 is a <seealso marker="#family">family</seealso>
+ and Family1[i] is a binary relation for every i in the index
+ set of Family1, then Family2 is the family with the same
+ index set as Family1 such that Family2[i] is
+ the <seealso marker="#field">field</seealso> of Family1[i].</p>
+ <pre>
+1> <input>FR = sofs:from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),</input>
+<input>F = sofs:family_field(FR),</input>
+<input>sofs:to_external(F).</input>
+[{a,[1,2,3,a,b,c]},{b,[]},{c,[4,5,d,e]}]</pre>
+ <p><c>family_field(Family1)</c> is equivalent to
+ <c>family_union(family_domain(Family1), family_range(Family1))</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>family_intersection(Family1) -> Family2</name>
+ <fsummary>Return the intersection of a family
+ of sets of sets.</fsummary>
+ <type>
+ <v>Family1 = Family2 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 is a <seealso marker="#family">family</seealso>
+ and Family1[i] is a set of sets for every i in the index set
+ of Family1, then Family2 is the family with the same index
+ set as Family1 such that Family2[i] is
+ the <seealso marker="#intersection_n">intersection</seealso> of
+ Family1[i].</p>
+ <p>If Family1[i] is an empty set for some i, then the process
+ exits with a <c>badarg</c> message.</p>
+ <pre>
+1> <input>F1 = sofs:from_term([{a,[[1,2,3],[2,3,4]]},{b,[[x,y,z],[x,y]]}]),</input>
+<input>F2 = sofs:family_intersection(F1),</input>
+<input>sofs:to_external(F2).</input>
+[{a,[2,3]},{b,[x,y]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_intersection(Family1, Family2) -> Family3</name>
+ <fsummary>Return the intersection of two families.</fsummary>
+ <type>
+ <v>Family1 = Family2 = Family3 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 and Family2
+ are <seealso marker="#family">families</seealso>, then Family3
+ is the family such that the index set is the intersection of
+ Family1's and Family2's index sets, and Family3[i] is the
+ intersection of Family1[i] and Family2[i].</p>
+ <pre>
+1> <input>F1 = sofs:family([{a,[1,2]},{b,[3,4]},{c,[5,6]}]),</input>
+<input>F2 = sofs:family([{b,[4,5]},{c,[7,8]},{d,[9,10]}]),</input>
+<input>F3 = sofs:family_intersection(F1, F2),</input>
+<input>sofs:to_external(F3).</input>
+[{b,[4]},{c,[]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_projection(SetFun, Family1) -> Family2</name>
+ <fsummary>Return a family of modified subsets.</fsummary>
+ <type>
+ <v>SetFun = set_fun()</v>
+ <v>Family1 = Family2 = family()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>If Family1 is a <seealso marker="#family">family</seealso>
+ then Family2 is the family with the same index set as
+ Family1 such that Family2[i] is the result of calling SetFun
+ with Family1[i] as argument.</p>
+ <pre>
+1> <input>F1 = sofs:from_term([{a,[[1,2],[2,3]]},{b,[[]]}]),</input>
+<input>F2 = sofs:family_projection({sofs, union}, F1),</input>
+<input>sofs:to_external(F2).</input>
+[{a,[1,2,3]},{b,[]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_range(Family1) -> Family2</name>
+ <fsummary>Return a family of ranges.</fsummary>
+ <type>
+ <v>Family1 = Family2 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 is a <seealso marker="#family">family</seealso>
+ and Family1[i] is a binary relation for every i in the index
+ set of Family1, then Family2 is the family with the same
+ index set as Family1 such that Family2[i] is
+ the <seealso marker="#range">range</seealso> of Family1[i].</p>
+ <pre>
+1> <input>FR = sofs:from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),</input>
+<input>F = sofs:family_range(FR),</input>
+<input>sofs:to_external(F).</input>
+[{a,[a,b,c]},{b,[]},{c,[d,e]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_specification(Fun, Family1) -> Family2</name>
+ <fsummary>Select a subset of a family using a predicate.</fsummary>
+ <type>
+ <v>Fun = spec_fun()</v>
+ <v>Family1 = Family2 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 is a <seealso marker="#family">family</seealso>,
+ then Family2 is
+ the <seealso marker="#restriction">restriction</seealso> of
+ Family1 to those elements i of the
+ index set for which Fun applied to Family1[i] returns
+ <c>true</c>. If Fun is a tuple <c>{external,&nbsp;Fun2}</c>,
+ Fun2 is applied to
+ the <seealso marker="#external_set">external set</seealso> of
+ Family1[i], otherwise Fun is applied to Family1[i].</p>
+ <pre>
+1> <input>F1 = sofs:family([{a,[1,2,3]},{b,[1,2]},{c,[1]}]),</input>
+<input>SpecFun = fun(S) -> sofs:no_elements(S) =:= 2 end,</input>
+<input>F2 = sofs:family_specification(SpecFun, F1),</input>
+<input>sofs:to_external(F2).</input>
+[{b,[1,2]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_to_digraph(Family [, GraphType]) -> Graph</name>
+ <fsummary>Create a directed graph from a family.</fsummary>
+ <type>
+ <v>Graph = digraph()</v>
+ <v>Family = family()</v>
+ <v>GraphType = -&nbsp;see digraph(3)&nbsp;-</v>
+ </type>
+ <desc>
+ <p>Creates a directed graph from
+ the <seealso marker="#family">family</seealso> Family. For each
+ pair (a,&nbsp;{b[1],&nbsp;...,&nbsp;b[n]}) of Family, the vertex
+ a as well the edges (a,&nbsp;b[i]) for
+ 1&nbsp;&lt;=&nbsp;i&nbsp;&lt;=&nbsp;n are added to a newly
+ created directed graph.</p>
+ <p>If no graph type is given, <c>digraph:new/1</c> is used for
+ creating the directed graph, otherwise the GraphType
+ argument is passed on as second argument to
+ <c>digraph:new/2</c>.</p>
+ <p>It F is a family, it holds that F is a subset of
+ <c>digraph_to_family(family_to_digraph(F),&nbsp;type(F))</c>.
+ Equality holds if <c>union_of_family(F)</c> is a subset of
+ <c>domain(F)</c>.</p>
+ <p>Creating a cycle in an acyclic graph exits the process with
+ a <c>cyclic</c> message.</p>
+ </desc>
+ </func>
+ <func>
+ <name>family_to_relation(Family) -> BinRel</name>
+ <fsummary>Create a binary relation from a family.</fsummary>
+ <type>
+ <v>Family = family()</v>
+ <v>BinRel = binary_relation()</v>
+ </type>
+ <desc>
+ <p>If Family is a <seealso marker="#family">family</seealso>,
+ then BinRel is the binary relation containing all pairs
+ (i,&nbsp;x) such that i belongs to the index set of Family
+ and x belongs to Family[i].</p>
+ <pre>
+1> <input>F = sofs:family([{a,[]}, {b,[1]}, {c,[2,3]}]),</input>
+<input>R = sofs:family_to_relation(F),</input>
+<input>sofs:to_external(R).</input>
+[{b,1},{c,2},{c,3}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>family_union(Family1) -> Family2</name>
+ <fsummary>Return the union of a family of sets of sets.</fsummary>
+ <type>
+ <v>Family1 = Family2 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 is a <seealso marker="#family">family</seealso>
+ and Family1[i] is a set of sets for each i in the index set
+ of Family1, then Family2 is the family with the same index
+ set as Family1 such that Family2[i] is
+ the <seealso marker="#union_n">union</seealso> of Family1[i].</p>
+ <pre>
+1> <input>F1 = sofs:from_term([{a,[[1,2],[2,3]]},{b,[[]]}]),</input>
+<input>F2 = sofs:family_union(F1),</input>
+<input>sofs:to_external(F2).</input>
+[{a,[1,2,3]},{b,[]}]</pre>
+ <p><c>family_union(F)</c> is equivalent to
+ <c>family_projection({sofs,union},&nbsp;F)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>family_union(Family1, Family2) -> Family3</name>
+ <fsummary>Return the union of two families.</fsummary>
+ <type>
+ <v>Family1 = Family2 = Family3 = family()</v>
+ </type>
+ <desc>
+ <p>If Family1 and Family2
+ are <seealso marker="#family">families</seealso>, then Family3
+ is the family such that the index set is the union of Family1's
+ and Family2's index sets, and Family3[i] is the union of
+ Family1[i] and Family2[i] if both maps i, Family1[i] or
+ Family2[i] otherwise.</p>
+ <pre>
+1> <input>F1 = sofs:family([{a,[1,2]},{b,[3,4]},{c,[5,6]}]),</input>
+<input>F2 = sofs:family([{b,[4,5]},{c,[7,8]},{d,[9,10]}]),</input>
+<input>F3 = sofs:family_union(F1, F2),</input>
+<input>sofs:to_external(F3).</input>
+[{a,[1,2]},{b,[3,4,5]},{c,[5,6,7,8]},{d,[9,10]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>field(BinRel) -> Set</name>
+ <fsummary>Return the field of a binary relation.</fsummary>
+ <type>
+ <v>BinRel = binary_relation()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#field">field</seealso> of the
+ binary relation BinRel.</p>
+ <pre>
+1> <input>R = sofs:relation([{1,a},{1,b},{2,b},{2,c}]),</input>
+<input>S = sofs:field(R),</input>
+<input>sofs:to_external(S).</input>
+[1,2,a,b,c]</pre>
+ <p><c>field(R)</c> is equivalent
+ to <c>union(domain(R), range(R))</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_external(ExternalSet, Type) -> AnySet</name>
+ <fsummary>Create a set.</fsummary>
+ <type>
+ <v>ExternalSet = external_set()</v>
+ <v>AnySet = anyset()</v>
+ <v>Type = type()</v>
+ </type>
+ <desc>
+ <p>Creates a set from the <seealso marker="#external_set">external
+ set</seealso> ExternalSet
+ and the <seealso marker="#type">type</seealso> Type. It is
+ assumed that Type is a <seealso marker="#valid_type">valid
+ type</seealso> of ExternalSet.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_sets(ListOfSets) -> Set</name>
+ <fsummary>Create a set out of a list of sets.</fsummary>
+ <type>
+ <v>Set = set()</v>
+ <v>ListOfSets = [anyset()]</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#sets_definition">unordered
+ set</seealso> containing the sets of the list ListOfSets.</p>
+ <pre>
+1> <input>S1 = sofs:relation([{a,1},{b,2}]),</input>
+<input>S2 = sofs:relation([{x,3},{y,4}]),</input>
+<input>S = sofs:from_sets([S1,S2]),</input>
+<input>sofs:to_external(S).</input>
+[[{a,1},{b,2}],[{x,3},{y,4}]]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>from_sets(TupleOfSets) -> Ordset</name>
+ <fsummary>Create an ordered set out of a tuple of sets.</fsummary>
+ <type>
+ <v>Ordset = ordset()</v>
+ <v>TupleOfSets = tuple-of(anyset())</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#sets_definition">ordered
+ set</seealso> containing the sets of the non-empty tuple
+ TupleOfSets.</p>
+ </desc>
+ </func>
+ <func>
+ <name>from_term(Term [, Type]) -> AnySet</name>
+ <fsummary>Create a set.</fsummary>
+ <type>
+ <v>AnySet = anyset()</v>
+ <v>Term = term()</v>
+ <v>Type = type()</v>
+ </type>
+ <desc>
+ <p><marker id="from_term"></marker>Creates an element
+ of <seealso marker="#sets_definition">Sets</seealso> by
+ traversing the term Term, sorting lists, removing duplicates and
+ deriving or verifying a <seealso marker="#valid_type">valid
+ type</seealso> for the so obtained external set. An
+ explicitly given <seealso marker="#type">type</seealso> Type
+ can be used to limit the depth of the traversal; an atomic
+ type stops the traversal, as demonstrated by this example
+ where "foo" and {"foo"} are left unmodified:</p>
+ <pre>
+1> <input>S = sofs:from_term([{{"foo"},[1,1]},{"foo",[2,2]}], [{atom,[atom]}]),</input>
+<input>sofs:to_external(S).</input>
+[{{"foo"},[1]},{"foo",[2]}]</pre>
+ <p><c>from_term</c> can be used for creating atomic or ordered
+ sets. The only purpose of such a set is that of later
+ building unordered sets since all functions in this module
+ that <em>do</em> anything operate on unordered sets.
+ Creating unordered sets from a collection of ordered sets
+ may be the way to go if the ordered sets are big and one
+ does not want to waste heap by rebuilding the elements of
+ the unordered set. An example showing that a set can be
+ built "layer by layer":</p>
+ <pre>
+1> <input>A = sofs:from_term(a),</input>
+<input>S = sofs:set([1,2,3]),</input>
+<input>P1 = sofs:from_sets({A,S}),</input>
+<input>P2 = sofs:from_term({b,[6,5,4]}),</input>
+<input>Ss = sofs:from_sets([P1,P2]),</input>
+<input>sofs:to_external(Ss).</input>
+[{a,[1,2,3]},{b,[4,5,6]}]</pre>
+ <p>Other functions that create sets are <c>from_external/2</c>
+ and <c>from_sets/1</c>. Special cases of <c>from_term/2</c>
+ are <c>a_function/1,2</c>, <c>empty_set/0</c>,
+ <c>family/1,2</c>, <c>relation/1,2</c>, and <c>set/1,2</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>image(BinRel, Set1) -> Set2</name>
+ <fsummary>Return the image of a set under a binary relation.</fsummary>
+ <type>
+ <v>BinRel = binary_relation()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#image">image</seealso> of the
+ set Set1 under the binary relation BinRel.</p>
+ <pre>
+1> <input>R = sofs:relation([{1,a},{2,b},{2,c},{3,d}]),</input>
+<input>S1 = sofs:set([1,2]),</input>
+<input>S2 = sofs:image(R, S1),</input>
+<input>sofs:to_external(S2).</input>
+[a,b,c]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(SetOfSets) -> Set</name>
+ <fsummary>Return the intersection of a set of sets.</fsummary>
+ <type>
+ <v>Set = set()</v>
+ <v>SetOfSets = set_of_sets()</v>
+ </type>
+ <desc>
+ <p>Returns
+ the <seealso marker="#intersection_n">intersection</seealso> of
+ the set of sets SetOfSets.</p>
+ <p>Intersecting an empty set of sets exits the process with a
+ <c>badarg</c> message.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection(Set1, Set2) -> Set3</name>
+ <fsummary>Return the intersection of two sets.</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns
+ the <seealso marker="#intersection">intersection</seealso> of
+ Set1 and Set2.</p>
+ </desc>
+ </func>
+ <func>
+ <name>intersection_of_family(Family) -> Set</name>
+ <fsummary>Return the intersection of a family.</fsummary>
+ <type>
+ <v>Family = family()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the intersection of
+ the <seealso marker="#family">family</seealso> Family.</p>
+ <p>Intersecting an empty family exits the process with a
+ <c>badarg</c> message.</p>
+ <pre>
+1> <input>F = sofs:family([{a,[0,2,4]},{b,[0,1,2]},{c,[2,3]}]),</input>
+<input>S = sofs:intersection_of_family(F),</input>
+<input>sofs:to_external(S).</input>
+[2]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>inverse(Function1) -> Function2</name>
+ <fsummary>Return the inverse of a function.</fsummary>
+ <type>
+ <v>Function1 = Function2 = function()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#inverse">inverse</seealso>
+ of the function Function1.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,a},{2,b},{3,c}]),</input>
+<input>R2 = sofs:inverse(R1),</input>
+<input>sofs:to_external(R2).</input>
+[{a,1},{b,2},{c,3}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>inverse_image(BinRel, Set1) -> Set2</name>
+ <fsummary>Return the inverse image of a set under
+ a binary relation.</fsummary>
+ <type>
+ <v>BinRel = binary_relation()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#inverse_image">inverse
+ image</seealso> of Set1 under the binary relation BinRel.</p>
+ <pre>
+1> <input>R = sofs:relation([{1,a},{2,b},{2,c},{3,d}]),</input>
+<input>S1 = sofs:set([c,d,e]),</input>
+<input>S2 = sofs:inverse_image(R, S1),</input>
+<input>sofs:to_external(S2).</input>
+[2,3]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>is_a_function(BinRel) -> Bool</name>
+ <fsummary>Test for a function.</fsummary>
+ <type>
+ <v>Bool = bool()</v>
+ <v>BinRel = binary_relation()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if the binary relation BinRel is a
+ <seealso marker="#function">function</seealso> or the
+ untyped empty set, <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_disjoint(Set1, Set2) -> Bool</name>
+ <fsummary>Test for disjoint sets.</fsummary>
+ <type>
+ <v>Bool = bool()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if Set1 and Set2
+ are <seealso marker="#disjoint">disjoint</seealso>, <c>false</c>
+ otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_empty_set(AnySet) -> Bool</name>
+ <fsummary>Test for an empty set.</fsummary>
+ <type>
+ <v>AnySet = anyset()</v>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if Set is an empty unordered set,
+ <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_equal(AnySet1, AnySet2) -> Bool</name>
+ <fsummary>Test two sets for equality.</fsummary>
+ <type>
+ <v>AnySet1 = AnySet2 = anyset()</v>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if the AnySet1 and AnySet2
+ are <seealso marker="#equal">equal</seealso>, <c>false</c>
+ otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_set(AnySet) -> Bool</name>
+ <fsummary>Test for an unordered set.</fsummary>
+ <type>
+ <v>AnySet = anyset()</v>
+ <v>Bool = bool()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if AnySet is
+ an <seealso marker="#sets_definition">unordered set</seealso>, and
+ <c>false</c> if AnySet is an ordered set or an atomic set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_sofs_set(Term) -> Bool</name>
+ <fsummary>Test for an unordered set.</fsummary>
+ <type>
+ <v>Bool = bool()</v>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if Term is
+ an <seealso marker="#sets_definition">unordered set</seealso>, an
+ ordered set or an atomic set, <c>false</c> otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_subset(Set1, Set2) -> Bool</name>
+ <fsummary>Test two sets for subset.</fsummary>
+ <type>
+ <v>Bool = bool()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if Set1 is
+ a <seealso marker="#subset">subset</seealso> of Set2, <c>false</c>
+ otherwise.</p>
+ </desc>
+ </func>
+ <func>
+ <name>is_type(Term) -> Bool</name>
+ <fsummary>Test for a type.</fsummary>
+ <type>
+ <v>Bool = bool()</v>
+ <v>Term = term()</v>
+ </type>
+ <desc>
+ <p>Returns <c>true</c> if the term Term is
+ a <seealso marker="#type">type</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>join(Relation1, I, Relation2, J) -> Relation3</name>
+ <fsummary>Return the join of two relations.</fsummary>
+ <type>
+ <v>Relation1 = Relation2 = Relation3 = relation()</v>
+ <v>I = J = integer() > 0</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#natural_join">natural
+ join</seealso> of the relations Relation1 and Relation2 on
+ coordinates I and J.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{a,x,1},{b,y,2}]),</input>
+<input>R2 = sofs:relation([{1,f,g},{1,h,i},{2,3,4}]),</input>
+<input>J = sofs:join(R1, 3, R2, 1),</input>
+<input>sofs:to_external(J).</input>
+[{a,x,1,f,g},{a,x,1,h,i},{b,y,2,3,4}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>multiple_relative_product(TupleOfBinRels, BinRel1) -> BinRel2</name>
+ <fsummary>Return the multiple relative product of a tuple of binary
+ relations and a relation.</fsummary>
+ <type>
+ <v>TupleOfBinRels = tuple-of(BinRel)</v>
+ <v>BinRel = BinRel1 = BinRel2 = binary_relation()</v>
+ </type>
+ <desc>
+ <p>If TupleOfBinRels is a non-empty tuple
+ {R[1],&nbsp;...,&nbsp;R[n]} of binary relations and BinRel1
+ is a binary relation, then BinRel2 is
+ the <seealso marker="#multiple_relative_product">multiple relative
+ product</seealso> of the ordered set
+ (R[i],&nbsp;...,&nbsp;R[n]) and BinRel1.</p>
+ <pre>
+1> <input>Ri = sofs:relation([{a,1},{b,2},{c,3}]),</input>
+<input>R = sofs:relation([{a,b},{b,c},{c,a}]),</input>
+<input>MP = sofs:multiple_relative_product({Ri, Ri}, R),</input>
+<input>sofs:to_external(sofs:range(MP)).</input>
+[{1,2},{2,3},{3,1}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>no_elements(ASet) -> NoElements</name>
+ <fsummary>Return the number of elements of a set.</fsummary>
+ <type>
+ <v>ASet = set() | ordset()</v>
+ <v>NoElements = integer() >= 0 </v>
+ </type>
+ <desc>
+ <p>Returns the number of elements of the ordered or unordered
+ set ASet.</p>
+ </desc>
+ </func>
+ <func>
+ <name>partition(SetOfSets) -> Partition</name>
+ <fsummary>Return the coarsest partition given a set of sets.</fsummary>
+ <type>
+ <v>SetOfSets = set_of_sets()</v>
+ <v>Partition = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#partition">partition</seealso> of
+ the union of the set of sets SetOfSets such that two
+ elements are considered equal if they belong to the same
+ elements of SetOfSets.</p>
+ <pre>
+1> <input>Sets1 = sofs:from_term([[a,b,c],[d,e,f],[g,h,i]]),</input>
+<input>Sets2 = sofs:from_term([[b,c,d],[e,f,g],[h,i,j]]),</input>
+<input>P = sofs:partition(sofs:union(Sets1, Sets2)),</input>
+<input>sofs:to_external(P).</input>
+[[a],[b,c],[d],[e,f],[g],[h,i],[j]]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>partition(SetFun, Set) -> Partition</name>
+ <fsummary>Return a partition of a set.</fsummary>
+ <type>
+ <v>SetFun = set_fun()</v>
+ <v>Partition = set()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#partition">partition</seealso> of
+ Set such that two elements are considered equal if the
+ results of applying SetFun are equal.</p>
+ <pre>
+1> <input>Ss = sofs:from_term([[a],[b],[c,d],[e,f]]),</input>
+<input>SetFun = fun(S) -> sofs:from_term(sofs:no_elements(S)) end,</input>
+<input>P = sofs:partition(SetFun, Ss),</input>
+<input>sofs:to_external(P).</input>
+[[[a],[b]],[[c,d],[e,f]]]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>partition(SetFun, Set1, Set2) -> {Set3, Set4}</name>
+ <fsummary>Return a partition of a set.</fsummary>
+ <type>
+ <v>SetFun = set_fun()</v>
+ <v>Set1 = Set2 = Set3 = Set4 = set()</v>
+ </type>
+ <desc>
+ <p>Returns a pair of sets that, regarded as constituting a
+ set, forms a <seealso marker="#partition">partition</seealso> of
+ Set1. If the
+ result of applying SetFun to an element of Set1 yields an
+ element in Set2, the element belongs to Set3, otherwise the
+ element belongs to Set4.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,a},{2,b},{3,c}]),</input>
+<input>S = sofs:set([2,4,6]),</input>
+<input>{R2,R3} = sofs:partition(1, R1, S),</input>
+<input>{sofs:to_external(R2),sofs:to_external(R3)}.</input>
+{[{2,b}],[{1,a},{3,c}]}</pre>
+ <p><c>partition(F,&nbsp;S1,&nbsp;S2)</c> is equivalent to
+ <c>{restriction(F,&nbsp;S1,&nbsp;S2),
+ drestriction(F,&nbsp;S1,&nbsp;S2)}</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>partition_family(SetFun, Set) -> Family</name>
+ <fsummary>Return a family indexing a partition.</fsummary>
+ <type>
+ <v>Family = family()</v>
+ <v>SetFun = set_fun()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#family">family</seealso>
+ Family where the indexed set is
+ a <seealso marker="#partition">partition</seealso> of Set
+ such that two elements are considered equal if the results
+ of applying SetFun are the same value i. This i is the index
+ that Family maps onto
+ the <seealso marker="#equivalence_class">equivalence
+ class</seealso>.</p>
+ <pre>
+1> <input>S = sofs:relation([{a,a,a,a},{a,a,b,b},{a,b,b,b}]),</input>
+<input>SetFun = {external, fun({A,_,C,_}) -> {A,C} end},</input>
+<input>F = sofs:partition_family(SetFun, S),</input>
+<input>sofs:to_external(F).</input>
+[{{a,a},[{a,a,a,a}]},{{a,b},[{a,a,b,b},{a,b,b,b}]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>product(TupleOfSets) -> Relation</name>
+ <fsummary>Return the Cartesian product of a tuple of sets.</fsummary>
+ <type>
+ <v>Relation = relation()</v>
+ <v>TupleOfSets = tuple-of(set())</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#Cartesian_product_tuple">Cartesian
+ product</seealso> of the non-empty tuple of sets
+ TupleOfSets. If (x[1],&nbsp;...,&nbsp;x[n]) is an element of
+ the n-ary relation Relation, then x[i] is drawn from element
+ i of TupleOfSets.</p>
+ <pre>
+1> <input>S1 = sofs:set([a,b]),</input>
+<input>S2 = sofs:set([1,2]),</input>
+<input>S3 = sofs:set([x,y]),</input>
+<input>P3 = sofs:product({S1,S2,S3}),</input>
+<input>sofs:to_external(P3).</input>
+[{a,1,x},{a,1,y},{a,2,x},{a,2,y},{b,1,x},{b,1,y},{b,2,x},{b,2,y}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>product(Set1, Set2) -> BinRel</name>
+ <fsummary>Return the Cartesian product of two sets.</fsummary>
+ <type>
+ <v>BinRel = binary_relation()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#Cartesian_product">Cartesian
+ product</seealso> of Set1 and Set2.</p>
+ <pre>
+1> <input>S1 = sofs:set([1,2]),</input>
+<input>S2 = sofs:set([a,b]),</input>
+<input>R = sofs:product(S1, S2),</input>
+<input>sofs:to_external(R).</input>
+[{1,a},{1,b},{2,a},{2,b}]</pre>
+ <p><c>product(S1,&nbsp;S2)</c> is equivalent to
+ <c>product({S1,&nbsp;S2})</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>projection(SetFun, Set1) -> Set2</name>
+ <fsummary>Return a set of substituted elements.</fsummary>
+ <type>
+ <v>SetFun = set_fun()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the set created by substituting each element of
+ Set1 by the result of applying SetFun to the element.</p>
+ <p>If SetFun is a number i&nbsp;&gt;=&nbsp;1 and Set1 is a
+ relation, then the returned set is
+ the <seealso marker="#projection">projection</seealso> of Set1
+ onto coordinate i.</p>
+ <pre>
+1> <input>S1 = sofs:from_term([{1,a},{2,b},{3,a}]),</input>
+<input>S2 = sofs:projection(2, S1),</input>
+<input>sofs:to_external(S2).</input>
+[a,b]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>range(BinRel) -> Set</name>
+ <fsummary>Return the range of a binary relation.</fsummary>
+ <type>
+ <v>BinRel = binary_relation()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#range">range</seealso> of the
+ binary relation BinRel.</p>
+ <pre>
+1> <input>R = sofs:relation([{1,a},{1,b},{2,b},{2,c}]),</input>
+<input>S = sofs:range(R),</input>
+<input>sofs:to_external(S).</input>
+[a,b,c]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>relation(Tuples [, Type]) -> Relation</name>
+ <fsummary>Create a relation.</fsummary>
+ <type>
+ <v>N = integer()</v>
+ <v>Type = N | type()</v>
+ <v>Relation = relation()</v>
+ <v>Tuples = [tuple()]</v>
+ </type>
+ <desc>
+ <p>Creates a <seealso marker="#relation">relation</seealso>.
+ <c>relation(R,&nbsp;T)</c> is equivalent to
+ <c>from_term(R,&nbsp;T)</c>, if T is
+ a <seealso marker="#type">type</seealso> and the result is a
+ relation. If Type is an integer N, then
+ <c>[{atom,&nbsp;...,&nbsp;atom}])</c>, where the size of the
+ tuple is N, is used as type of the relation. If no type is
+ explicitly given, the size of the first tuple of Tuples is
+ used if there is such a tuple. <c>relation([])</c> is
+ equivalent to <c>relation([],&nbsp;2)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>relation_to_family(BinRel) -> Family</name>
+ <fsummary>Create a family from a binary relation.</fsummary>
+ <type>
+ <v>Family = family()</v>
+ <v>BinRel = binary_relation()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#family">family</seealso>
+ Family such that the index set is equal to
+ the <seealso marker="#domain">domain</seealso> of the binary
+ relation BinRel, and Family[i] is
+ the <seealso marker="#image">image</seealso> of the set of i
+ under BinRel.</p>
+ <pre>
+1> <input>R = sofs:relation([{b,1},{c,2},{c,3}]),</input>
+<input>F = sofs:relation_to_family(R),</input>
+<input>sofs:to_external(F).</input>
+[{b,[1]},{c,[2,3]}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>relative_product(TupleOfBinRels [, BinRel1]) -> BinRel2</name>
+ <fsummary>Return the relative product of a tuple of binary relations
+ and a binary relation.</fsummary>
+ <type>
+ <v>TupleOfBinRels = tuple-of(BinRel)</v>
+ <v>BinRel = BinRel1 = BinRel2 = binary_relation()</v>
+ </type>
+ <desc>
+ <p>If TupleOfBinRels is a non-empty tuple
+ {R[1],&nbsp;...,&nbsp;R[n]} of binary relations and BinRel1
+ is a binary relation, then BinRel2 is
+ the <seealso marker="#tuple_relative_product">relative
+ product</seealso> of the ordered set (R[i],&nbsp;...,&nbsp;R[n])
+ and BinRel1.</p>
+ <p>If BinRel1 is omitted, the relation of equality between the
+ elements of
+ the <seealso marker="#Cartesian_product_tuple">Cartesian
+ product</seealso> of the ranges of R[i],
+ range&nbsp;R[1]&nbsp;&times;&nbsp;...&nbsp;&times;&nbsp;range&nbsp;R[n],
+ is used instead (intuitively, nothing is "lost").</p>
+ <pre>
+1> <input>TR = sofs:relation([{1,a},{1,aa},{2,b}]),</input>
+<input>R1 = sofs:relation([{1,u},{2,v},{3,c}]),</input>
+<input>R2 = sofs:relative_product({TR, R1}),</input>
+<input>sofs:to_external(R2).</input>
+[{1,{a,u}},{1,{aa,u}},{2,{b,v}}]</pre>
+ <p>Note that <c>relative_product({R1},&nbsp;R2)</c> is
+ different from <c>relative_product(R1,&nbsp;R2)</c>; the
+ tuple of one element is not identified with the element
+ itself.</p>
+ </desc>
+ </func>
+ <func>
+ <name>relative_product(BinRel1, BinRel2) -> BinRel3</name>
+ <fsummary>Return the relative product of
+ two binary relations.</fsummary>
+ <type>
+ <v>BinRel1 = BinRel2 = BinRel3 = binary_relation()</v>
+ </type>
+ <desc>
+ <p><marker id="relprod_impl"></marker>Returns
+ the <seealso marker="#relative_product">relative
+ product</seealso> of the binary relations BinRel1 and BinRel2.</p>
+ </desc>
+ </func>
+ <func>
+ <name>relative_product1(BinRel1, BinRel2) -> BinRel3</name>
+ <fsummary>Return the relative_product of
+ two binary relations.</fsummary>
+ <type>
+ <v>BinRel1 = BinRel2 = BinRel3 = binary_relation()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#relative_product">relative
+ product</seealso> of
+ the <seealso marker="#converse">converse</seealso> of the
+ binary relation BinRel1 and the binary relation BinRel2.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,a},{1,aa},{2,b}]),</input>
+<input>R2 = sofs:relation([{1,u},{2,v},{3,c}]),</input>
+<input>R3 = sofs:relative_product1(R1, R2),</input>
+<input>sofs:to_external(R3).</input>
+[{a,u},{aa,u},{b,v}]</pre>
+ <p><c>relative_product1(R1,&nbsp;R2)</c> is equivalent to
+ <c>relative_product(converse(R1),&nbsp;R2)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>restriction(BinRel1, Set) -> BinRel2</name>
+ <fsummary>Return a restriction of a binary relation.</fsummary>
+ <type>
+ <v>BinRel1 = BinRel2 = binary_relation()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#restriction">restriction</seealso> of
+ the binary relation BinRel1 to Set.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,a},{2,b},{3,c}]),</input>
+<input>S = sofs:set([1,2,4]),</input>
+<input>R2 = sofs:restriction(R1, S),</input>
+<input>sofs:to_external(R2).</input>
+[{1,a},{2,b}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>restriction(SetFun, Set1, Set2) -> Set3</name>
+ <fsummary>Return a restriction of a set.</fsummary>
+ <type>
+ <v>SetFun = set_fun()</v>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns a subset of Set1 containing those elements that
+ yield an element in Set2 as the result of applying SetFun.</p>
+ <pre>
+1> <input>S1 = sofs:relation([{1,a},{2,b},{3,c}]),</input>
+<input>S2 = sofs:set([b,c,d]),</input>
+<input>S3 = sofs:restriction(2, S1, S2),</input>
+<input>sofs:to_external(S3).</input>
+[{2,b},{3,c}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>set(Terms [, Type]) -> Set</name>
+ <fsummary>Create a set of atoms or any type of sets.</fsummary>
+ <type>
+ <v>Set = set()</v>
+ <v>Terms = [term()]</v>
+ <v>Type = type()</v>
+ </type>
+ <desc>
+ <p>Creates an <seealso marker="#sets_definition">unordered
+ set</seealso>. <c>set(L,&nbsp;T)</c> is equivalent to
+ <c>from_term(L,&nbsp;T)</c>, if the result is an unordered
+ set. If no <seealso marker="#type">type</seealso> is
+ explicitly given, <c>[atom]</c> is used as type of the set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>specification(Fun, Set1) -> Set2</name>
+ <fsummary>Select a subset using a predicate.</fsummary>
+ <type>
+ <v>Fun = spec_fun()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the set containing every element of Set1 for which
+ Fun returns <c>true</c>. If Fun is a tuple
+ <c>{external,&nbsp;Fun2}</c>, Fun2 is applied to the
+ <seealso marker="#external_set">external set</seealso> of
+ each element, otherwise Fun is applied to each element.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{a,1},{b,2}]),</input>
+<input>R2 = sofs:relation([{x,1},{x,2},{y,3}]),</input>
+<input>S1 = sofs:from_sets([R1,R2]),</input>
+<input>S2 = sofs:specification({sofs,is_a_function}, S1),</input>
+<input>sofs:to_external(S2).</input>
+[[{a,1},{b,2}]]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>strict_relation(BinRel1) -> BinRel2</name>
+ <fsummary>Return the strict relation corresponding to
+ a given relation.</fsummary>
+ <type>
+ <v>BinRel1 = BinRel2 = binary_relation()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#strict_relation">strict
+ relation</seealso> corresponding to the binary relation BinRel1.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,1},{1,2},{2,1},{2,2}]),</input>
+<input>R2 = sofs:strict_relation(R1),</input>
+<input>sofs:to_external(R2).</input>
+[{1,2},{2,1}]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>substitution(SetFun, Set1) -> Set2</name>
+ <fsummary>Return a function with a given set as domain.</fsummary>
+ <type>
+ <v>SetFun = set_fun()</v>
+ <v>Set1 = Set2 = set()</v>
+ </type>
+ <desc>
+ <p>Returns a function, the domain of which is Set1. The value
+ of an element of the domain is the result of applying SetFun
+ to the element.</p>
+ <pre>
+1> <input>L = [{a,1},{b,2}].</input>
+[{a,1},{b,2}]
+2> <input>sofs:to_external(sofs:projection(1,sofs:relation(L))).</input>
+[a,b]
+3> <input>sofs:to_external(sofs:substitution(1,sofs:relation(L))).</input>
+[{{a,1},a},{{b,2},b}]
+4> <input>SetFun = {external, fun({A,_}=E) -> {E,A} end},</input>
+<input>sofs:to_external(sofs:projection(SetFun,sofs:relation(L))).</input>
+[{{a,1},a},{{b,2},b}]</pre>
+ <p>The relation of equality between the elements of {a,b,c}:</p>
+ <pre>
+1> <input>I = sofs:substitution(fun(A) -> A end, sofs:set([a,b,c])),</input>
+<input>sofs:to_external(I).</input>
+[{a,a},{b,b},{c,c}]</pre>
+ <p>Let SetOfSets be a set of sets and BinRel a binary
+ relation. The function that maps each element Set of
+ SetOfSets onto the <seealso marker="#image">image</seealso>
+ of Set under BinRel is returned by this function:</p>
+ <pre>
+images(SetOfSets, BinRel) ->
+ Fun = fun(Set) -> sofs:image(BinRel, Set) end,
+ sofs:substitution(Fun, SetOfSets).</pre>
+ <p>Here might be the place to reveal something that was more
+ or less stated before, namely that external unordered sets
+ are represented as sorted lists. As a consequence, creating
+ the image of a set under a relation R may traverse all
+ elements of R (to that comes the sorting of results, the
+ image). In <c>images/2</c>, BinRel will be traversed once
+ for each element of SetOfSets, which may take too long. The
+ following efficient function could be used instead under the
+ assumption that the image of each element of SetOfSets under
+ BinRel is non-empty:</p>
+ <pre>
+images2(SetOfSets, BinRel) ->
+ CR = sofs:canonical_relation(SetOfSets),
+ R = sofs:relative_product1(CR, BinRel),
+ sofs:relation_to_family(R).</pre>
+ </desc>
+ </func>
+ <func>
+ <name>symdiff(Set1, Set2) -> Set3</name>
+ <fsummary>Return the symmetric difference of two sets.</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#symmetric_difference">symmetric
+ difference</seealso> (or the Boolean sum) of Set1 and Set2.</p>
+ <pre>
+1> <input>S1 = sofs:set([1,2,3]),</input>
+<input>S2 = sofs:set([2,3,4]),</input>
+<input>P = sofs:symdiff(S1, S2),</input>
+<input>sofs:to_external(P).</input>
+[1,4]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>symmetric_partition(Set1, Set2) -> {Set3, Set4, Set5}</name>
+ <fsummary>Return a partition of two sets.</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = Set4 = Set5 = set()</v>
+ </type>
+ <desc>
+ <p>Returns a triple of sets: Set3 contains the elements
+ of Set1 that do not belong to Set2; Set4 contains the
+ elements of Set1 that belong to Set2; Set5 contains the
+ elements of Set2 that do not belong to Set1.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_external(AnySet) -> ExternalSet</name>
+ <fsummary>Return the elements of a set.</fsummary>
+ <type>
+ <v>ExternalSet = external_set()</v>
+ <v>AnySet = anyset()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#external_set">external
+ set</seealso> of an atomic, ordered or unordered set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>to_sets(ASet) -> Sets</name>
+ <fsummary>Return a list or a tuple of the elements of set.</fsummary>
+ <type>
+ <v>ASet = set() | ordset()</v>
+ <v>Sets = tuple_of(AnySet) | [AnySet]</v>
+ </type>
+ <desc>
+ <p>Returns the elements of the ordered set ASet as a tuple of
+ sets, and the elements of the unordered set ASet as a sorted
+ list of sets without duplicates.</p>
+ </desc>
+ </func>
+ <func>
+ <name>type(AnySet) -> Type</name>
+ <fsummary>Return the type of a set.</fsummary>
+ <type>
+ <v>AnySet = anyset()</v>
+ <v>Type = type()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#type">type</seealso> of an
+ atomic, ordered or unordered set.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(SetOfSets) -> Set</name>
+ <fsummary>Return the union of a set of sets.</fsummary>
+ <type>
+ <v>Set = set()</v>
+ <v>SetOfSets = set_of_sets()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#union_n">union</seealso> of the
+ set of sets SetOfSets.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union(Set1, Set2) -> Set3</name>
+ <fsummary>Return the union of two sets.</fsummary>
+ <type>
+ <v>Set1 = Set2 = Set3 = set()</v>
+ </type>
+ <desc>
+ <p>Returns the <seealso marker="#union">union</seealso> of
+ Set1 and Set2.</p>
+ </desc>
+ </func>
+ <func>
+ <name>union_of_family(Family) -> Set</name>
+ <fsummary>Return the union of a family.</fsummary>
+ <type>
+ <v>Family = family()</v>
+ <v>Set = set()</v>
+ </type>
+ <desc>
+ <p>Returns the union of
+ the <seealso marker="#family">family</seealso> Family.</p>
+ <pre>
+1> <input>F = sofs:family([{a,[0,2,4]},{b,[0,1,2]},{c,[2,3]}]),</input>
+<input>S = sofs:union_of_family(F),</input>
+<input>sofs:to_external(S).</input>
+[0,1,2,3,4]</pre>
+ </desc>
+ </func>
+ <func>
+ <name>weak_relation(BinRel1) -> BinRel2</name>
+ <fsummary>Return the weak relation corresponding to
+ a given relation.</fsummary>
+ <type>
+ <v>BinRel1 = BinRel2 = binary_relation()</v>
+ </type>
+ <desc>
+ <p>Returns a subset S of the <seealso marker="#weak_relation">weak
+ relation</seealso> W
+ corresponding to the binary relation BinRel1. Let F be the
+ <seealso marker="#field">field</seealso> of BinRel1. The
+ subset S is defined so that x S y if x W y for some x in F
+ and for some y in F.</p>
+ <pre>
+1> <input>R1 = sofs:relation([{1,1},{1,2},{3,1}]),</input>
+<input>R2 = sofs:weak_relation(R1),</input>
+<input>sofs:to_external(R2).</input>
+[{1,1},{1,2},{2,2},{3,1},{3,3}]</pre>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="dict">dict(3)</seealso>,
+ <seealso marker="digraph">digraph(3)</seealso>,
+ <seealso marker="orddict">orddict(3)</seealso>,
+ <seealso marker="ordsets">ordsets(3)</seealso>,
+ <seealso marker="sets">sets(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/stdlib_app.xml b/lib/stdlib/doc/src/stdlib_app.xml
new file mode 100644
index 0000000000..da046b8a8d
--- /dev/null
+++ b/lib/stdlib/doc/src/stdlib_app.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE appref SYSTEM "appref.dtd">
+
+<appref>
+ <header>
+ <copyright>
+ <year>2005</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>STDLIB</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <app>STDLIB</app>
+ <appsummary>The STDLIB Application</appsummary>
+ <description>
+ <p>The STDLIB is mandatory in the sense that the minimal system
+ based on Erlang/OTP consists of Kernel and STDLIB. The STDLIB
+ application contains no services.</p>
+ </description>
+
+ <section>
+ <title>Configuration</title>
+ <p>The following configuration parameters are defined for the STDLIB
+ application. See <c>app(4)</c> for more information about
+ configuration parameters.</p>
+ <taglist>
+ <tag><c>shell_esc = icl | abort</c></tag>
+ <item>
+ <p>This parameter can be used to alter the behaviour of
+ the Erlang shell when ^G is pressed.</p>
+ </item>
+ <tag><c>restricted_shell = module()</c></tag>
+ <item>
+ <p>This parameter can be used to run the Erlang shell
+ in restricted mode.</p>
+ </item>
+ <tag><c>shell_catch_exception = bool()</c></tag>
+ <item>
+ <p>This parameter can be used to set the exception handling
+ of the Erlang shell's evaluator process.</p>
+ </item>
+ <tag><c>shell_history_length = integer() >= 0</c></tag>
+ <item>
+ <p>This parameter can be used to determine how many
+ commands are saved by the Erlang shell.</p>
+ </item>
+ <tag><c>shell_saved_results = integer() >= 0</c></tag>
+ <item>
+ <p>This parameter can be used to determine how many
+ results are saved by the Erlang shell.</p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>See Also</title>
+ <p><seealso marker="kernel:app">app(4)</seealso>,
+ <seealso marker="kernel:application">application(3)</seealso>,
+ <seealso marker="shell">shell(3)</seealso>, </p>
+ </section>
+</appref>
+
diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml
new file mode 100644
index 0000000000..7ee38e496d
--- /dev/null
+++ b/lib/stdlib/doc/src/string.xml
@@ -0,0 +1,415 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>string</title>
+ <prepared>Robert Virding</prepared>
+ <responsible>Bjarne Dacker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>96-09-28</date>
+ <rev>A</rev>
+ <file>string.sgml</file>
+ </header>
+ <module>string</module>
+ <modulesummary>String Processing Functions</modulesummary>
+ <description>
+ <p>This module contains functions for string processing.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>len(String) -> Length</name>
+ <fsummary>Return the length of a string</fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>Length = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the number of characters in the string.</p>
+ </desc>
+ </func>
+ <func>
+ <name>equal(String1, String2) -> bool()</name>
+ <fsummary>Test string equality</fsummary>
+ <type>
+ <v>String1 = String2 = string()</v>
+ </type>
+ <desc>
+ <p>Tests whether two strings are equal. Returns <c>true</c> if
+ they are, otherwise <c>false</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>concat(String1, String2) -> String3</name>
+ <fsummary>Concatenate two strings</fsummary>
+ <type>
+ <v>String1 = String2 = String3 = string()</v>
+ </type>
+ <desc>
+ <p>Concatenates two strings to form a new string. Returns the
+ new string.</p>
+ </desc>
+ </func>
+ <func>
+ <name>chr(String, Character) -> Index</name>
+ <name>rchr(String, Character) -> Index</name>
+ <fsummary>Return the index of the first/last occurrence of<c>Character</c>in <c>String</c></fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>Character = char()</v>
+ <v>Index = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the index of the first/last occurrence of
+ <c>Character</c> in <c>String</c>. <c>0</c> is returned if <c>Character</c> does not
+ occur.</p>
+ </desc>
+ </func>
+ <func>
+ <name>str(String, SubString) -> Index</name>
+ <name>rstr(String, SubString) -> Index</name>
+ <fsummary>Find the index of a substring</fsummary>
+ <type>
+ <v>String = SubString = string()</v>
+ <v>Index = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the position where the first/last occurrence of
+ <c>SubString</c> begins in <c>String</c>. <c>0</c> is returned if <c>SubString</c>
+ does not exist in <c>String</c>.
+ For example:</p>
+ <code type="none">
+> string:str(" Hello Hello World World ", "Hello World").
+8 </code>
+ </desc>
+ </func>
+ <func>
+ <name>span(String, Chars) -> Length </name>
+ <name>cspan(String, Chars) -> Length</name>
+ <fsummary>Span characters at start of string</fsummary>
+ <type>
+ <v>String = Chars = string()</v>
+ <v>Length = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the length of the maximum initial segment of
+ String, which consists entirely of characters from (not
+ from) Chars.</p>
+ <p>For example:</p>
+ <code type="none">
+> string:span("\\t abcdef", " \\t").
+5
+> string:cspan("\\t abcdef", " \\t").
+0 </code>
+ </desc>
+ </func>
+ <func>
+ <name>substr(String, Start) -> SubString</name>
+ <name>substr(String, Start, Length) -> Substring</name>
+ <fsummary>Return a substring of <c>String</c></fsummary>
+ <type>
+ <v>String = SubString = string()</v>
+ <v>Start = Length = integer()</v>
+ </type>
+ <desc>
+ <p>Returns a substring of <c>String</c>, starting at the
+ position <c>Start</c>, and ending at the end of the string or
+ at length <c>Length</c>.</p>
+ <p>For example:</p>
+ <code type="none">
+> substr("Hello World", 4, 5).
+"lo Wo" </code>
+ </desc>
+ </func>
+ <func>
+ <name>tokens(String, SeparatorList) -> Tokens</name>
+ <fsummary>Split string into tokens</fsummary>
+ <type>
+ <v>String = SeparatorList = string()</v>
+ <v>Tokens = [string()]</v>
+ </type>
+ <desc>
+ <p>Returns a list of tokens in <c>String</c>, separated by the
+ characters in <c>SeparatorList</c>.</p>
+ <p>For example:</p>
+ <code type="none">
+> tokens("abc defxxghix jkl", "x ").
+["abc", "def", "ghi", "jkl"] </code>
+ </desc>
+ </func>
+ <func>
+ <name>join(StringList, Separator) -> String</name>
+ <fsummary>Join a list of strings with separator</fsummary>
+ <type>
+ <v>StringList = [string()]</v>
+ <v>Separator = string()</v>
+ </type>
+ <desc>
+ <p>Returns a string with the elements of <c>StringList</c>
+ separated by the string in <c>Seperator</c>.</p>
+ <p>For example:</p>
+ <code type="none">
+> join(["one", "two", "three"], ", ").
+"one, two, three" </code>
+ </desc>
+ </func>
+ <func>
+ <name>chars(Character, Number) -> String</name>
+ <name>chars(Character, Number, Tail) -> String</name>
+ <fsummary>Returns a string consisting of numbers of characters</fsummary>
+ <type>
+ <v>Character = char()</v>
+ <v>Number = integer()</v>
+ <v>String = string()</v>
+ </type>
+ <desc>
+ <p>Returns a string consisting of <c>Number</c> of characters
+ <c>Character</c>. Optionally, the string can end with the
+ string <c>Tail</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>copies(String, Number) -> Copies</name>
+ <fsummary>Copy a string</fsummary>
+ <type>
+ <v>String = Copies = string()</v>
+ <v>Number = integer()</v>
+ </type>
+ <desc>
+ <p>Returns a string containing <c>String</c> repeated
+ <c>Number</c> times.</p>
+ </desc>
+ </func>
+ <func>
+ <name>words(String) -> Count</name>
+ <name>words(String, Character) -> Count</name>
+ <fsummary>Count blank separated words</fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>Character = char()</v>
+ <v>Count = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the number of words in <c>String</c>, separated by
+ blanks or <c>Character</c>.</p>
+ <p>For example:</p>
+ <code type="none">
+> words(" Hello old boy!", $o).
+4 </code>
+ </desc>
+ </func>
+ <func>
+ <name>sub_word(String, Number) -> Word</name>
+ <name>sub_word(String, Number, Character) -> Word</name>
+ <fsummary>Extract subword</fsummary>
+ <type>
+ <v>String = Word = string()</v>
+ <v>Character = char()</v>
+ <v>Number = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the word in position <c>Number</c> of <c>String</c>.
+ Words are separated by blanks or <c>Character</c>s.</p>
+ <p>For example:</p>
+ <code type="none">
+> string:sub_word(" Hello old boy !",3,$o).
+"ld b" </code>
+ </desc>
+ </func>
+ <func>
+ <name>strip(String) -> Stripped</name>
+ <name>strip(String, Direction) -> Stripped</name>
+ <name>strip(String, Direction, Character) -> Stripped</name>
+ <fsummary>Strip leading or trailing characters</fsummary>
+ <type>
+ <v>String = Stripped = string()</v>
+ <v>Direction = left | right | both</v>
+ <v>Character = char()</v>
+ </type>
+ <desc>
+ <p>Returns a string, where leading and/or trailing blanks or a
+ number of <c>Character</c> have been removed.
+ <c>Direction</c> can be <c>left</c>, <c>right</c>, or
+ <c>both</c> and indicates from which direction blanks are to be
+ removed. The function <c>strip/1</c> is equivalent to
+ <c>strip(String, both)</c>.</p>
+ <p>For example:</p>
+ <code type="none">
+> string:strip("...Hello.....", both, $.).
+"Hello" </code>
+ </desc>
+ </func>
+ <func>
+ <name>left(String, Number) -> Left</name>
+ <name>left(String, Number, Character) -> Left</name>
+ <fsummary>Adjust left end of string</fsummary>
+ <type>
+ <v>String = Left = string()</v>
+ <v>Character = char</v>
+ <v>Number = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the <c>String</c> with the length adjusted in
+ accordance with <c>Number</c>. The left margin is
+ fixed. If the <c>length(String)</c> &lt; <c>Number</c>,
+ <c>String</c> is padded with blanks or <c>Character</c>s.</p>
+ <p>For example:</p>
+ <code type="none">
+> string:left("Hello",10,$.).
+"Hello....." </code>
+ </desc>
+ </func>
+ <func>
+ <name>right(String, Number) -> Right</name>
+ <name>right(String, Number, Character) -> Right</name>
+ <fsummary>Adjust right end of string</fsummary>
+ <type>
+ <v>String = Right = string()</v>
+ <v>Character = char</v>
+ <v>Number = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the <c>String</c> with the length adjusted in
+ accordance with <c>Number</c>. The right margin is
+ fixed. If the length of <c>(String)</c> &lt; <c>Number</c>,
+ <c>String</c> is padded with blanks or <c>Character</c>s.</p>
+ <p>For example:</p>
+ <code type="none">
+> string:right("Hello", 10, $.).
+".....Hello" </code>
+ </desc>
+ </func>
+ <func>
+ <name>centre(String, Number) -> Centered</name>
+ <name>centre(String, Number, Character) -> Centered</name>
+ <fsummary>Center a string</fsummary>
+ <type>
+ <v>String = Centered = string()</v>
+ <v>Character = char</v>
+ <v>Number = integer()</v>
+ </type>
+ <desc>
+ <p>Returns a string, where <c>String</c> is centred in the
+ string and surrounded by blanks or characters. The resulting
+ string will have the length <c>Number</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sub_string(String, Start) -> SubString</name>
+ <name>sub_string(String, Start, Stop) -> SubString</name>
+ <fsummary>Extract a substring</fsummary>
+ <type>
+ <v>String = SubString = string()</v>
+ <v>Start = Stop = integer()</v>
+ </type>
+ <desc>
+ <p>Returns a substring of <c>String</c>, starting at the
+ position <c>Start</c> to the end of the string, or to and
+ including the <c>Stop</c> position.</p>
+ <p>For example:</p>
+ <code type="none">
+sub_string("Hello World", 4, 8).
+"lo Wo" </code>
+ </desc>
+ </func>
+ <func>
+ <name>to_float(String) -> {Float,Rest} | {error,Reason} </name>
+ <fsummary>Returns a float whose text representation is the integers (ASCII values) in String.</fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>Float = float()</v>
+ <v>Rest = string()</v>
+ <v>Reason = no_float | not_a_list</v>
+ </type>
+ <desc>
+ <p>Argument <c>String</c> is expected to start with a valid text
+ represented float (the digits being ASCII values). Remaining characters
+ in the string after the float are returned in <c>Rest</c>.</p>
+ <p>Example:</p>
+ <code type="none">
+ > {F1,Fs} = string:to_float("1.0-1.0e-1"),
+ > {F2,[]} = string:to_float(Fs),
+ > F1+F2.
+ 0.9
+ > string:to_float("3/2=1.5").
+ {error,no_float}
+ > string:to_float("-1.5eX").
+ {-1.5,"eX"}</code>
+ </desc>
+ </func>
+ <func>
+ <name>to_integer(String) -> {Int,Rest} | {error,Reason} </name>
+ <fsummary>Returns an integer whose text representation is the integers (ASCII values) in String.</fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>Int = integer()</v>
+ <v>Rest = string()</v>
+ <v>Reason = no_integer | not_a_list</v>
+ </type>
+ <desc>
+ <p>Argument <c>String</c> is expected to start with a valid text
+ represented integer (the digits being ASCII values). Remaining characters
+ in the string after the integer are returned in <c>Rest</c>.</p>
+ <p>Example:</p>
+ <code type="none">
+ > {I1,Is} = string:to_integer("33+22"),
+ > {I2,[]} = string:to_integer(Is),
+ > I1-I2.
+ 11
+ > string:to_integer("0.5").
+ {0,".5"}
+ > string:to_integer("x=2").
+ {error,no_integer}</code>
+ </desc>
+ </func>
+ <func>
+ <name>to_lower(String) -> Result</name>
+ <name>to_lower(Char) -> CharResult</name>
+ <name>to_upper(String) -> Result</name>
+ <name>to_upper(Char) -> CharResult</name>
+ <fsummary>Convert case of string (ISO/IEC 8859-1)</fsummary>
+ <type>
+ <v>String = Result = string()</v>
+ <v>Char = CharResult = integer()</v>
+ </type>
+ <desc>
+ <p>The given string or character is case-converted. Note that
+ the supported character set is ISO/IEC 8859-1 (a.k.a. Latin 1),
+ all values outside this set is unchanged</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Notes</title>
+ <p>Some of the general string functions may seem to overlap each
+ other. The reason for this is that this string package is the
+ combination of two earlier packages and all the functions of
+ both packages have been retained.
+ </p>
+ <note>
+ <p>Any undocumented functions in <c>string</c> should not be used.</p>
+ </note>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml
new file mode 100644
index 0000000000..adf9d24eae
--- /dev/null
+++ b/lib/stdlib/doc/src/supervisor.xml
@@ -0,0 +1,495 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>supervisor</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>supervisor</module>
+ <modulesummary>Generic Supervisor Behaviour</modulesummary>
+ <description>
+ <p>A behaviour module for implementing a supervisor, a process which
+ supervises other processes called child processes. A child
+ process can either be another supervisor or a worker process.
+ Worker processes are normally implemented using one of
+ the <c>gen_event</c>, <c>gen_fsm</c>, or <c>gen_server</c>
+ behaviours. A supervisor implemented using this module will have
+ a standard set of interface functions and include functionality
+ for tracing and error reporting. Supervisors are used to build an
+ hierarchical process structure called a supervision tree, a
+ nice way to structure a fault tolerant application. Refer to
+ <em>OTP Design Principles</em> for more information.</p>
+ <p>A supervisor assumes the definition of which child processes to
+ supervise to be located in a callback module exporting a
+ pre-defined set of functions.</p>
+ <p>Unless otherwise stated, all functions in this module will fail
+ if the specified supervisor does not exist or if bad arguments
+ are given.</p>
+ </description>
+
+ <section>
+ <title>Supervision Principles</title>
+ <p>The supervisor is responsible for starting, stopping and
+ monitoring its child processes. The basic idea of a supervisor is
+ that it should keep its child processes alive by restarting them
+ when necessary.</p>
+ <p>The children of a supervisor is defined as a list of <em>child specifications</em>. When the supervisor is started, the child
+ processes are started in order from left to right according to
+ this list. When the supervisor terminates, it first terminates
+ its child processes in reversed start order, from right to left.</p>
+ <p></p>
+ <p>A supervisor can have one of the following <em>restart strategies</em>:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>one_for_one</c> - if one child process terminates and
+ should be restarted, only that child process is affected.</p>
+ </item>
+ <item>
+ <p><c>one_for_all</c> - if one child process terminates and
+ should be restarted, all other child processes are terminated
+ and then all child processes are restarted.</p>
+ </item>
+ <item>
+ <p><c>rest_for_one</c> - if one child process terminates and
+ should be restarted, the 'rest' of the child processes --
+ i.e. the child processes after the terminated child process
+ in the start order -- are terminated. Then the terminated
+ child process and all child processes after it are restarted.</p>
+ </item>
+ <item>
+ <p><c>simple_one_for_one</c> - a simplified <c>one_for_one</c>
+ supervisor, where all child processes are dynamically added
+ instances of the same process type, i.e. running the same
+ code.</p>
+ <p>The functions <c>terminate_child/2</c>, <c>delete_child/2</c>
+ and <c>restart_child/2</c> are invalid for
+ <c>simple_one_for_one</c> supervisors and will return
+ <c>{error,simple_one_for_one}</c> if the specified supervisor
+ uses this restart strategy.</p>
+ </item>
+ </list>
+ <p>To prevent a supervisor from getting into an infinite loop of
+ child process terminations and restarts, a <em>maximum restart frequency</em> is defined using two integer values <c>MaxR</c>
+ and <c>MaxT</c>. If more than <c>MaxR</c> restarts occur within
+ <c>MaxT</c> seconds, the supervisor terminates all child
+ processes and then itself.
+ </p>
+ <marker id="child_spec"/>
+ <p>This is the type definition of a child specification:</p>
+ <pre>
+child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
+ Id = term()
+ StartFunc = {M,F,A}
+ M = F = atom()
+ A = [term()]
+ Restart = permanent | transient | temporary
+ Shutdown = brutal_kill | int()>=0 | infinity
+ Type = worker | supervisor
+ Modules = [Module] | dynamic
+ Module = atom()</pre>
+ <list type="bulleted">
+ <item>
+ <p><c>Id</c> is a name that is used to identify the child
+ specification internally by the supervisor.</p>
+ </item>
+ <item>
+ <p><c>StartFunc</c> defines the function call used to start
+ the child process. It should be a module-function-arguments
+ tuple <c>{M,F,A}</c> used as <c>apply(M,F,A)</c>.</p>
+ <p> <br></br>
+</p>
+ <p>The start function <em>must create and link to</em> the child
+ process, and should return <c>{ok,Child}</c> or
+ <c>{ok,Child,Info}</c> where <c>Child</c> is the pid of
+ the child process and <c>Info</c> an arbitrary term which is
+ ignored by the supervisor.</p>
+ <p> <br></br>
+</p>
+ <p>The start function can also return <c>ignore</c> if the child
+ process for some reason cannot be started, in which case
+ the child specification will be kept by the supervisor but
+ the non-existing child process will be ignored.</p>
+ <p> <br></br>
+</p>
+ <p>If something goes wrong, the function may also return an
+ error tuple <c>{error,Error}</c>.</p>
+ <p> <br></br>
+</p>
+ <p>Note that the <c>start_link</c> functions of the different
+ behaviour modules fulfill the above requirements.</p>
+ </item>
+ <item>
+ <p><c>Restart</c> defines when a terminated child process
+ should be restarted. A <c>permanent</c> child process should
+ always be restarted, a <c>temporary</c> child process should
+ never be restarted and a <c>transient</c> child process
+ should be restarted only if it terminates abnormally, i.e.
+ with another exit reason than <c>normal</c>.</p>
+ </item>
+ <item>
+ <p><c>Shutdown</c> defines how a child process should be
+ terminated. <c>brutal_kill</c> means the child process will
+ be unconditionally terminated using <c>exit(Child,kill)</c>.
+ An integer timeout value means that the supervisor will tell
+ the child process to terminate by calling
+ <c>exit(Child,shutdown)</c> and then wait for an exit signal
+ with reason <c>shutdown</c> back from the child process. If
+ no exit signal is received within the specified time,
+ the child process is unconditionally terminated using
+ <c>exit(Child,kill)</c>.</p>
+ <p>If the child process is another supervisor, <c>Shutdown</c>
+ should be set to <c>infinity</c> to give the subtree ample
+ time to shutdown.</p>
+ <p><em>Important note on simple-one-for-one supervisors:</em>
+ The dynamically created child processes of a
+ simple-one-for-one supervisor are not explicitly killed,
+ regardless of shutdown strategy, but are expected to terminate
+ when the supervisor does (that is, when an exit signal from
+ the parent process is received).</p>
+ <p>Note that all child processes implemented using the standard
+ OTP behavior modules automatically adhere to the shutdown
+ protocol.</p>
+ </item>
+ <item>
+ <p><c>Type</c> specifies if the child process is a supervisor or
+ a worker.</p>
+ </item>
+ <item>
+ <p><c>Modules</c> is used by the release handler during code
+ replacement to determine which processes are using a certain
+ module. As a rule of thumb <c>Modules</c> should be a list
+ with one element <c>[Module]</c>, where <c>Module</c> is
+ the callback module, if the child process is a supervisor,
+ gen_server or gen_fsm. If the child process is an event
+ manager (gen_event) with a dynamic set of callback modules,
+ <c>Modules</c> should be <c>dynamic</c>. See <em>OTP Design Principles</em> for more information about release handling.</p>
+ </item>
+ <item>
+ <p>Internally, the supervisor also keeps track of the pid
+ <c>Child</c> of the child process, or <c>undefined</c> if no
+ pid exists.</p>
+ </item>
+ </list>
+ </section>
+ <funcs>
+ <func>
+ <name>start_link(Module, Args) -> Result</name>
+ <name>start_link(SupName, Module, Args) -> Result</name>
+ <fsummary>Create a supervisor process.</fsummary>
+ <type>
+ <v>SupName = {local,Name} | {global,Name}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>Module = atom()</v>
+ <v>Args = term()</v>
+ <v>Result = {ok,Pid} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;Error = {already_started,Pid}} | shutdown | term()</v>
+ </type>
+ <desc>
+ <p>Creates a supervisor process as part of a supervision tree.
+ The function will, among other things, ensure that
+ the supervisor is linked to the calling process (its
+ supervisor).</p>
+ <p>The created supervisor process calls <c>Module:init/1</c> to
+ find out about restart strategy, maximum restart frequency
+ and child processes. To ensure a synchronized start-up
+ procedure, <c>start_link/2,3</c> does not return until
+ <c>Module:init/1</c> has returned and all child processes
+ have been started.</p>
+ <p>If <c>SupName={local,Name}</c> the supervisor is registered
+ locally as <c>Name</c> using <c>register/2</c>. If
+ <c>SupName={global,Name}</c> the supervisor is registered
+ globally as <c>Name</c> using <c>global:register_name/2</c>.
+ If no name is provided, the supervisor is not registered.</p>
+ <p><c>Module</c> is the name of the callback module.</p>
+ <p><c>Args</c> is an arbitrary term which is passed as
+ the argument to <c>Module:init/1</c>.</p>
+ <p>If the supervisor and its child processes are successfully
+ created (i.e. if all child process start functions return
+ <c>{ok,Child}</c>, <c>{ok,Child,Info}</c>, or <c>ignore</c>)
+ the function returns <c>{ok,Pid}</c>, where <c>Pid</c> is
+ the pid of the supervisor. If there already exists a process
+ with the specified <c>SupName</c> the function returns
+ <c>{error,{already_started,Pid}}</c>, where <c>Pid</c> is
+ the pid of that process.</p>
+ <p>If <c>Module:init/1</c> returns <c>ignore</c>, this function
+ returns <c>ignore</c> as well and the supervisor terminates
+ with reason <c>normal</c>.
+ If <c>Module:init/1</c> fails or returns an incorrect value,
+ this function returns <c>{error,Term}</c> where <c>Term</c>
+ is a term with information about the error, and the supervisor
+ terminates with reason <c>Term</c>.</p>
+ <p>If any child process start function fails or returns an error
+ tuple or an erroneous value, the function returns
+ <c>{error,shutdown}</c> and the supervisor terminates all
+ started child processes and then itself with reason
+ <c>shutdown</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>start_child(SupRef, ChildSpec) -> Result</name>
+ <fsummary>Dynamically add a child process to a supervisor.</fsummary>
+ <type>
+ <v>SupRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>ChildSpec = child_spec() | [term()]</v>
+ <v>Result = {ok,Child} | {ok,Child,Info} | {error,Error}</v>
+ <v>&nbsp;Child = pid() | undefined</v>
+ <v>&nbsp;Info = term()</v>
+ <v>&nbsp;Error = already_present | {already_started,Child} | term()</v>
+ </type>
+ <desc>
+ <p>Dynamically adds a child specification to the supervisor
+ <c>SupRef</c> which starts the corresponding child process.</p>
+ <p><c>SupRef</c> can be:</p>
+ <list type="bulleted">
+ <item>the pid,</item>
+ <item><c>Name</c>, if the supervisor is locally registered,</item>
+ <item><c>{Name,Node}</c>, if the supervisor is locally
+ registered at another node, or</item>
+ <item><c>{global,Name}</c>, if the supervisor is globally
+ registered.</item>
+ </list>
+ <p><c>ChildSpec</c> should be a valid child specification
+ (unless the supervisor is a <c>simple_one_for_one</c>
+ supervisor, see below). The child process will be started by
+ using the start function as defined in the child
+ specification.</p>
+ <p>If the case of a <c>simple_one_for_one</c> supervisor,
+ the child specification defined in <c>Module:init/1</c> will
+ be used and <c>ChildSpec</c> should instead be an arbitrary
+ list of terms <c>List</c>. The child process will then be
+ started by appending <c>List</c> to the existing start
+ function arguments, i.e. by calling
+ <c>apply(M, F, A++List)</c> where <c>{M,F,A}</c> is the start
+ function defined in the child specification.</p>
+ <p>If there already exists a child specification with
+ the specified <c>Id</c>, <c>ChildSpec</c> is discarded and
+ the function returns <c>{error,already_present}</c> or
+ <c>{error,{already_started,Child}}</c>, depending on if
+ the corresponding child process is running or not.</p>
+ <p>If the child process start function returns <c>{ok,Child}</c>
+ or <c>{ok,Child,Info}</c>, the child specification and pid is
+ added to the supervisor and the function returns the same
+ value.</p>
+ <p>If the child process start function returns <c>ignore</c>,
+ the child specification is added to the supervisor, the pid
+ is set to <c>undefined</c> and the function returns
+ <c>{ok,undefined}</c>.</p>
+ <p>If the child process start function returns an error tuple or
+ an erroneous value, or if it fails, the child specification is
+ discarded and the function returns <c>{error,Error}</c> where
+ <c>Error</c> is a term containing information about the error
+ and child specification.</p>
+ </desc>
+ </func>
+ <func>
+ <name>terminate_child(SupRef, Id) -> Result</name>
+ <fsummary>Terminate a child process belonging to a supervisor.</fsummary>
+ <type>
+ <v>SupRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Id = term()</v>
+ <v>Result = ok | {error,Error}</v>
+ <v>&nbsp;Error = not_found | simple_one_for_one</v>
+ </type>
+ <desc>
+ <p>Tells the supervisor <c>SupRef</c> to terminate the child
+ process corresponding to the child specification identified
+ by <c>Id</c>. The process, if there is one, is terminated but
+ the child specification is kept by the supervisor. This means
+ that the child process may be later be restarted by
+ the supervisor. The child process can also be restarted
+ explicitly by calling <c>restart_child/2</c>. Use
+ <c>delete_child/2</c> to remove the child specification.</p>
+ <p>See <c>start_child/2</c> for a description of
+ <c>SupRef</c>.</p>
+ <p>If successful, the function returns <c>ok</c>. If there is
+ no child specification with the specified <c>Id</c>,
+ the function returns <c>{error,not_found}</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_child(SupRef, Id) -> Result</name>
+ <fsummary>Delete a child specification from a supervisor.</fsummary>
+ <type>
+ <v>SupRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Id = term()</v>
+ <v>Result = ok | {error,Error}</v>
+ <v>&nbsp;Error = running | not_found | simple_one_for_one</v>
+ </type>
+ <desc>
+ <p>Tells the supervisor <c>SupRef</c> to delete the child
+ specification identified by <c>Id</c>. The corresponding child
+ process must not be running, use <c>terminate_child/2</c> to
+ terminate it.</p>
+ <p>See <c>start_child/2</c> for a description of <c>SupRef</c>.</p>
+ <p>If successful, the function returns <c>ok</c>. If the child
+ specification identified by <c>Id</c> exists but
+ the corresponding child process is running, the function
+ returns <c>{error,running}</c>. If the child specification
+ identified by <c>Id</c> does not exist, the function returns
+ <c>{error,not_found}</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>restart_child(SupRef, Id) -> Result</name>
+ <fsummary>Restart a terminated child process belonging to a supervisor.</fsummary>
+ <type>
+ <v>SupRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Id = term()</v>
+ <v>Result = {ok,Child} | {ok,Child,Info} | {error,Error}</v>
+ <v>&nbsp;Child = pid() | undefined</v>
+ <v>&nbsp;Error = running | not_found | simple_one_for_one | term()</v>
+ </type>
+ <desc>
+ <p>Tells the supervisor <c>SupRef</c> to restart a child process
+ corresponding to the child specification identified by
+ <c>Id</c>. The child specification must exist and
+ the corresponding child process must not be running.</p>
+ <p>See <c>start_child/2</c> for a description of <c>SupRef</c>.</p>
+ <p>If the child specification identified by <c>Id</c> does not
+ exist, the function returns <c>{error,not_found}</c>. If
+ the child specification exists but the corresponding process
+ is already running, the function returns
+ <c>{error,running}</c>.</p>
+ <p>If the child process start function returns <c>{ok,Child}</c>
+ or <c>{ok,Child,Info}</c>, the pid is added to the supervisor
+ and the function returns the same value.</p>
+ <p>If the child process start function returns <c>ignore</c>,
+ the pid remains set to <c>undefined</c> and the function
+ returns <c>{ok,undefined}</c>.</p>
+ <p>If the child process start function returns an error tuple or
+ an erroneous value, or if it fails, the function returns
+ <c>{error,Error}</c> where <c>Error</c> is a term containing
+ information about the error.</p>
+ </desc>
+ </func>
+ <func>
+ <name>which_children(SupRef) -> [{Id,Child,Type,Modules}]</name>
+ <fsummary>Return information about all children specifications and child processes belonging to a supervisor.</fsummary>
+ <type>
+ <v>SupRef = Name | {Name,Node} | {global,Name} | pid()</v>
+ <v>&nbsp;Name = Node = atom()</v>
+ <v>Id = term() | undefined</v>
+ <v>Child = pid() | undefined</v>
+ <v>Type = worker | supervisor</v>
+ <v>Modules = [Module] | dynamic</v>
+ <v>&nbsp;Module = atom()</v>
+ </type>
+ <desc>
+ <p>Returns a list with information about all child
+ specifications and child processes belonging to
+ the supervisor <c>SupRef</c>.</p>
+ <p>See <c>start_child/2</c> for a description of <c>SupRef</c>.</p>
+ <p>The information given for each child specification/process
+ is:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>Id</c> - as defined in the child specification or
+ <c>undefined</c> in the case of a
+ <c>simple_one_for_one</c> supervisor.</p>
+ </item>
+ <item>
+ <p><c>Child</c> - the pid of the corresponding child
+ process, or <c>undefined</c> if there is no such process.</p>
+ </item>
+ <item>
+ <p><c>Type</c> - as defined in the child specification.</p>
+ </item>
+ <item>
+ <p><c>Modules</c> - as defined in the child specification.</p>
+ </item>
+ </list>
+ </desc>
+ </func>
+ <func>
+ <name>check_childspecs([ChildSpec]) -> Result</name>
+ <fsummary>Check if children specifications are syntactically correct.</fsummary>
+ <type>
+ <v>ChildSpec = child_spec()</v>
+ <v>Result = ok | {error,Error}</v>
+ <v>&nbsp;Error = term()</v>
+ </type>
+ <desc>
+ <p>This function takes a list of child specification as argument
+ and returns <c>ok</c> if all of them are syntactically
+ correct, or <c>{error,Error}</c> otherwise.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>The following functions should be exported from a
+ <c>supervisor</c> callback module.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>Module:init(Args) -> Result</name>
+ <fsummary>Return a supervisor specification.</fsummary>
+ <type>
+ <v>Args = term()</v>
+ <v>Result = {ok,{{RestartStrategy,MaxR,MaxT},[ChildSpec]}} | ignore</v>
+ <v>&nbsp;RestartStrategy = one_for_all | one_for_one | rest_for_one | simple_one_for_one</v>
+ <v>&nbsp;MaxR = MaxT = int()>=0</v>
+ <v>&nbsp;ChildSpec = child_spec()</v>
+ </type>
+ <desc>
+ <p>Whenever a supervisor is started using
+ <c>supervisor:start_link/2,3</c>, this function is called by
+ the new process to find out about restart strategy, maximum
+ restart frequency and child specifications.</p>
+ <p><c>Args</c> is the <c>Args</c> argument provided to the start
+ function.</p>
+ <p><c>RestartStrategy</c> is the restart strategy and
+ <c>MaxR</c> and <c>MaxT</c> defines the maximum restart
+ frequency of the supervisor. <c>[ChildSpec]</c> is a list of
+ valid child specifications defining which child processes
+ the supervisor should start and monitor. See the discussion
+ about Supervision Principles above.</p>
+ <p>Note that when the restart strategy is
+ <c>simple_one_for_one</c>, the list of child specifications
+ must be a list with one child specification only.
+ (The <c>Id</c> is ignored). No child process is then started
+ during the initialization phase, but all children are assumed
+ to be started dynamically using
+ <c>supervisor:start_child/2</c>.</p>
+ <p>The function may also return <c>ignore</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="gen_event">gen_event(3)</seealso>,
+ <seealso marker="gen_fsm">gen_fsm(3)</seealso>,
+ <seealso marker="gen_server">gen_server(3)</seealso>,
+ <seealso marker="sys">sys(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/supervisor_bridge.xml b/lib/stdlib/doc/src/supervisor_bridge.xml
new file mode 100644
index 0000000000..b334f57caf
--- /dev/null
+++ b/lib/stdlib/doc/src/supervisor_bridge.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>supervisor_bridge</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>supervisor_bridge</module>
+ <modulesummary>Generic Supervisor Bridge Behaviour.</modulesummary>
+ <description>
+ <p>A behaviour module for implementing a supervisor_bridge, a process
+ which connects a subsystem not designed according to the OTP design
+ principles to a supervision tree. The supervisor_bridge sits between
+ a supervisor and the subsystem. It behaves like a real supervisor to
+ its own supervisor, but has a different interface than a real
+ supervisor to the subsystem. Refer to <em>OTP Design Principles</em>
+ for more information.</p>
+ <p>A supervisor_bridge assumes the functions for starting and stopping
+ the subsystem to be located in a callback module exporting a
+ pre-defined set of functions.</p>
+ <p>The <c>sys</c> module can be used for debugging a
+ supervisor_bridge.</p>
+ <p>Unless otherwise stated, all functions in this module will fail if
+ the specified supervisor_bridge does not exist or if bad arguments are
+ given.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>start_link(Module, Args) -> Result</name>
+ <name>start_link(SupBridgeName, Module, Args) -> Result</name>
+ <fsummary>Create a supervisor bridge process.</fsummary>
+ <type>
+ <v>SupBridgeName = {local,Name} | {global,Name}</v>
+ <v>&nbsp;Name = atom()</v>
+ <v>Module = atom()</v>
+ <v>Args = term()</v>
+ <v>Result = {ok,Pid} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;Error = {already_started,Pid} | term()</v>
+ </type>
+ <desc>
+ <p>Creates a supervisor_bridge process, linked to the calling
+ process, which calls <c>Module:init/1</c> to start the subsystem.
+ To ensure a synchronized start-up procedure, this function does
+ not return until <c>Module:init/1</c> has returned.</p>
+ <p>If <c>SupBridgeName={local,Name}</c> the supervisor_bridge is
+ registered locally as <c>Name</c> using <c>register/2</c>.
+ If <c>SupBridgeName={global,Name}</c> the supervisor_bridge is
+ registered globally as <c>Name</c> using
+ <c>global:register_name/2</c>.
+ If no name is provided, the supervisor_bridge is not registered.
+ If there already exists a process with the specified
+ <c>SupBridgeName</c> the function returns
+ <c>{error,{already_started,Pid}}</c>, where <c>Pid</c> is the pid
+ of that process.</p>
+ <p><c>Module</c> is the name of the callback module.</p>
+ <p><c>Args</c> is an arbitrary term which is passed as the argument
+ to <c>Module:init/1</c>.</p>
+ <p>If the supervisor_bridge and the subsystem are successfully
+ started the function returns <c>{ok,Pid}</c>, where <c>Pid</c> is
+ is the pid of the supervisor_bridge.</p>
+ <p>If <c>Module:init/1</c> returns <c>ignore</c>, this function
+ returns <c>ignore</c> as well and the supervisor_bridge terminates
+ with reason <c>normal</c>.
+ If <c>Module:init/1</c> fails or returns an error tuple or an
+ incorrect value, this function returns <c>{error,Term}</c> where
+ <c>Term</c> is a term with information about the error, and
+ the supervisor_bridge terminates with reason <c>Term</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>CALLBACK FUNCTIONS</title>
+ <p>The following functions should be exported from a
+ <c>supervisor_bridge</c> callback module.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>Module:init(Args) -> Result</name>
+ <fsummary>Initialize process and start subsystem.</fsummary>
+ <type>
+ <v>Args = term()</v>
+ <v>Result = {ok,Pid,State} | ignore | {error,Error}</v>
+ <v>&nbsp;Pid = pid()</v>
+ <v>&nbsp;State = term()</v>
+ <v>&nbsp;Error = term()</v>
+ </type>
+ <desc>
+ <p>Whenever a supervisor_bridge is started using
+ <c>supervisor_bridge:start_link/2,3</c>, this function is called
+ by the new process to start the subsystem and initialize.</p>
+ <p><c>Args</c> is the <c>Args</c> argument provided to the start
+ function.</p>
+ <p>The function should return <c>{ok,Pid,State}</c> where <c>Pid</c>
+ is the pid of the main process in the subsystem and <c>State</c>
+ is any term.</p>
+ <p>If later <c>Pid</c> terminates with a reason <c>Reason</c>,
+ the supervisor bridge will terminate with reason <c>Reason</c> as
+ well.
+ If later the supervisor_bridge is stopped by its supervisor with
+ reason <c>Reason</c>, it will call
+ <c>Module:terminate(Reason,State)</c> to terminate.</p>
+ <p>If something goes wrong during the initialization the function
+ should return <c>{error,Error}</c> where <c>Error</c> is any
+ term, or <c>ignore</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Module:terminate(Reason, State)</name>
+ <fsummary>Clean up and stop subsystem.</fsummary>
+ <type>
+ <v>Reason = shutdown | term()</v>
+ <v>State = term()</v>
+ </type>
+ <desc>
+ <p>This function is called by the supervisor_bridge when it is about
+ to terminate. It should be the opposite of <c>Module:init/1</c>
+ and stop the subsystem and do any necessary cleaning up.
+ The return value is ignored.</p>
+ <p><c>Reason</c> is <c>shutdown</c> if the supervisor_bridge is
+ terminated by its supervisor. If the supervisor_bridge terminates
+ because a a linked process (apart from the main process of
+ the subsystem) has terminated with reason <c>Term</c>,
+ <c>Reason</c> will be <c>Term</c>.</p>
+ <p><c>State</c> is taken from the return value of
+ <c>Module:init/1</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="supervisor">supervisor(3)</seealso>,
+ <seealso marker="sys">sys(3)</seealso></p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml
new file mode 100644
index 0000000000..a395a8a415
--- /dev/null
+++ b/lib/stdlib/doc/src/sys.xml
@@ -0,0 +1,429 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2007</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>sys</title>
+ <prepared>Martin Bj&ouml;rklund</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno></docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>1996-06-06</date>
+ <rev></rev>
+ <file>sys.sgml</file>
+ </header>
+ <module>sys</module>
+ <modulesummary>A Functional Interface to System Messages</modulesummary>
+ <description>
+ <p>This module contains functions for sending system messages used by programs, and messaged used for debugging purposes.
+ </p>
+ <p>Functions used for implementation of processes
+ should also understand system messages such as debugging
+ messages and code change. These functions must be used to implement the use of system messages for a process; either directly, or through standard behaviours, such as <c>gen_server</c>.</p>
+ <p>The following types are used in the functions defined below:</p>
+ <list type="bulleted">
+ <item>
+ <p><c>Name = pid() | atom() | {global, atom()}</c></p>
+ </item>
+ <item>
+ <p><c>Timeout = int() >= 0 | infinity</c></p>
+ </item>
+ <item>
+ <p><c>system_event() = {in, Msg} | {in, Msg, From} | {out, Msg, To} | term()</c></p>
+ </item>
+ </list>
+ <p>The default timeout is 5000 ms, unless otherwise specified. The
+ <c>timeout</c> defines the time period to wait for the process to
+ respond to a request. If the process does not respond, the
+ function evaluates <c>exit({timeout, {M, F, A}})</c>.
+ </p>
+ <p>The functions make reference to a debug structure.
+ The debug structure is a list of <c>dbg_opt()</c>.
+ <c>dbg_opt()</c> is an internal data type used by the
+ <c>handle_system_msg/6</c> function. No debugging is performed if it is an empty list.
+ </p>
+ </description>
+
+ <section>
+ <title>System Messages</title>
+ <p>Processes which are not implemented as one of the standard
+ behaviours must still understand system
+ messages. There are three different messages which must be
+ understood:
+ </p>
+ <list type="bulleted">
+ <item>
+ <p>Plain system messages. These are received as
+ <c>{system, From, Msg}</c>. The content and meaning of
+ this message are not interpreted by the
+ receiving process module. When a system message has been
+ received, the function <c>sys:handle_system_msg/6</c>
+ is called in order to handle the request.
+ </p>
+ </item>
+ <item>
+ <p>Shutdown messages. If the process traps exits, it must
+ be able to handle an shut-down request from its parent, the
+ supervisor. The message <c>{'EXIT', Parent, Reason}</c>
+ from the parent is an order to terminate. The process must terminate when this message is received, normally with the
+ same <c>Reason</c> as <c>Parent</c>.
+ </p>
+ </item>
+ <item>
+ <p>There is one more message which the process must understand if the modules used to implement the process change dynamically during runtime. An example of such a process is the <c>gen_event</c> processes. This message is <c>{get_modules, From}</c>. The reply to this message is <c>From ! {modules, Modules}</c>,
+ where <c>Modules</c> is a list of the currently active modules in the process.
+ </p>
+ <p>This message is used by the release handler to find which
+ processes execute a certain module. The process may at a
+ later time be suspended and ordered to perform a code change
+ for one of its modules.
+ </p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>System Events</title>
+ <p>When debugging a process with the functions of this
+ module, the process generates <em>system_events</em> which are
+ then treated in the debug function. For example, <c>trace</c>
+ formats the system events to the tty.
+ </p>
+ <p>There are three predefined system events which are used when a
+ process receives or sends a message. The process can also define its
+ own system events. It is always up to the process itself
+ to format these events.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>log(Name,Flag)</name>
+ <name>log(Name,Flag,Timeout) -> ok | {ok, [system_event()]}</name>
+ <fsummary>Log system events in memory</fsummary>
+ <type>
+ <v>Flag = true | {true, N} | false | get | print</v>
+ <v>N = integer() > 0</v>
+ </type>
+ <desc>
+ <p>Turns the logging of system events On or Off. If On, a
+ maximum of <c>N</c> events are kept in the
+ debug structure (the default is 10). If <c>Flag</c> is <c>get</c>, a list of all
+ logged events is returned. If <c>Flag</c> is <c>print</c>, the
+ logged events are printed to <c>standard_io</c>. The events are
+ formatted with a function that is defined by the process that
+ generated the event (with a call to
+ <c>sys:handle_debug/4</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>log_to_file(Name,Flag)</name>
+ <name>log_to_file(Name,Flag,Timeout) -> ok | {error, open_file}</name>
+ <fsummary>Log system events to the specified file</fsummary>
+ <type>
+ <v>Flag = FileName | false</v>
+ <v>FileName = string()</v>
+ </type>
+ <desc>
+ <p>Enables or disables the logging of all system events in textual
+ format to the file. The events are formatted with a function that is
+ defined by the process that generated the event (with a call
+ to <c>sys:handle_debug/4</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>statistics(Name,Flag)</name>
+ <name>statistics(Name,Flag,Timeout) -> ok | {ok, Statistics} </name>
+ <fsummary>Enable or disable the collections of statistics</fsummary>
+ <type>
+ <v>Flag = true | false | get</v>
+ <v>Statistics = [{start_time, {Date1, Time1}}, {current_time, {Date, Time2}}, {reductions, integer()}, {messages_in, integer()}, {messages_out, integer()}]</v>
+ <v>Date1 = Date2 = {Year, Month, Day}</v>
+ <v>Time1 = Time2 = {Hour, Min, Sec}</v>
+ </type>
+ <desc>
+ <p>Enables or disables the collection of statistics. If <c>Flag</c> is
+ <c>get</c>, the statistical collection is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>trace(Name,Flag)</name>
+ <name>trace(Name,Flag,Timeout) -> void()</name>
+ <fsummary>Print all system events on <c>standard_io</c></fsummary>
+ <type>
+ <v>Flag = boolean()</v>
+ </type>
+ <desc>
+ <p>Prints all system events on <c>standard_io</c>. The events are
+ formatted with a function that is defined by the process that
+ generated the event (with a call to
+ <c>sys:handle_debug/4</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>no_debug(Name)</name>
+ <name>no_debug(Name,Timeout) -> void()</name>
+ <fsummary>Turn off debugging</fsummary>
+ <desc>
+ <p>Turns off all debugging for the process. This includes
+ functions that have been installed explicitly with the
+ <c>install</c> function, for example triggers.</p>
+ </desc>
+ </func>
+ <func>
+ <name>suspend(Name)</name>
+ <name>suspend(Name,Timeout) -> void()</name>
+ <fsummary>Suspend the process</fsummary>
+ <desc>
+ <p>Suspends the process. When the process is suspended, it
+ will only respond to other system messages, but not other
+ messages.</p>
+ </desc>
+ </func>
+ <func>
+ <name>resume(Name)</name>
+ <name>resume(Name,Timeout) -> void()</name>
+ <fsummary>Resume a suspended process</fsummary>
+ <desc>
+ <p>Resumes a suspended process.</p>
+ </desc>
+ </func>
+ <func>
+ <name>change_code(Name, Module, OldVsn, Extra)</name>
+ <name>change_code(Name, Module, OldVsn, Extra, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Send the code change system message to the process</fsummary>
+ <type>
+ <v>OldVsn = undefined | term()</v>
+ <v>Module = atom()</v>
+ <v>Extra = term()</v>
+ </type>
+ <desc>
+ <p>Tells the process to change code. The process must be
+ suspended to handle this message. The <c>Extra</c> argument is
+ reserved for each process to use as its own. The function
+ <c>Mod:system_code_change/4</c> is called. <c>OldVsn</c> is
+ the old version of the <c>Module</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_status(Name)</name>
+ <name>get_status(Name,Timeout) -> {status, Pid, {module, Mod}, [PDict, SysState, Parent, Dbg, Misc]}</name>
+ <fsummary>Get the status of the process</fsummary>
+ <type>
+ <v>PDict = [{Key, Value}]</v>
+ <v>SysState = running | suspended</v>
+ <v>Parent = pid()</v>
+ <v>Dbg = [dbg_opt()]</v>
+ <v>Misc = term()</v>
+ </type>
+ <desc>
+ <p>Gets the status of the process.</p>
+ </desc>
+ </func>
+ <func>
+ <name>install(Name,{Func,FuncState})</name>
+ <name>install(Name,{Func,FuncState},Timeout)</name>
+ <fsummary>Install a debug function in the process</fsummary>
+ <type>
+ <v>Func = dbg_fun()</v>
+ <v>dbg_fun() = fun(FuncState, Event, ProcState) -> done | NewFuncState</v>
+ <v>FuncState = term()</v>
+ <v>Event = system_event()</v>
+ <v>ProcState = term()</v>
+ <v>NewFuncState = term()</v>
+ </type>
+ <desc>
+ <p>This function makes it possible to install other debug
+ functions than the ones defined above. An example of such a
+ function is a trigger, a function that waits for some
+ special event and performs some action when the event is
+ generated. This could, for example, be turning on low level tracing.
+ </p>
+ <p><c>Func</c> is called whenever a system event is
+ generated. This function should return <c>done</c>, or a new
+ func state. In the first case, the function is removed. It is removed
+ if the function fails.</p>
+ </desc>
+ </func>
+ <func>
+ <name>remove(Name,Func)</name>
+ <name>remove(Name,Func,Timeout) -> void()</name>
+ <fsummary>Remove a debug function from the process</fsummary>
+ <type>
+ <v>Func = dbg_fun()</v>
+ </type>
+ <desc>
+ <p>Removes a previously installed debug function from the
+ process. <c>Func</c> must be the same as previously
+ installed.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Process Implementation Functions</title>
+ <p>The following functions are used when implementing a
+ special process. This is an ordinary process which does not use a
+ standard behaviour, but a process which understands the standard system messages.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>debug_options(Options) -> [dbg_opt()]</name>
+ <fsummary>Convert a list of options to a debug structure</fsummary>
+ <type>
+ <v>Options = [Opt]</v>
+ <v>Opt = trace | log | statistics | {log_to_file, FileName} | {install, {Func, FuncState}}</v>
+ <v>Func = dbg_fun()</v>
+ <v>FuncState = term()</v>
+ </type>
+ <desc>
+ <p>This function can be used by a process that initiates a debug
+ structure from a list of options. The values of the
+ <c>Opt</c> argument are the same as the corresponding
+ functions.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_debug(Item,Debug,Default) -> term()</name>
+ <fsummary>Get the data associated with a debug option</fsummary>
+ <type>
+ <v>Item = log | statistics</v>
+ <v>Debug = [dbg_opt()]</v>
+ <v>Default = term()</v>
+ </type>
+ <desc>
+ <p>This function gets the data associated with a debug option. <c>Default</c> is returned if the
+ <c>Item</c> is not found. Can be
+ used by the process to retrieve debug data for printing
+ before it terminates.</p>
+ </desc>
+ </func>
+ <func>
+ <name>handle_debug([dbg_opt()],FormFunc,Extra,Event) -> [dbg_opt()]</name>
+ <fsummary>Generate a system event</fsummary>
+ <type>
+ <v>FormFunc = dbg_fun()</v>
+ <v>Extra = term()</v>
+ <v>Event = system_event()</v>
+ </type>
+ <desc>
+ <p>This function is called by a process when it generates a system event. <c>FormFunc</c> is a formatting function which is called as <c>FormFunc(Device, Event, Extra)</c> in order to print the events, which is necessary if tracing is activated. <c>Extra</c> is any
+ extra information which the process needs in the format function, for example the name of the process.</p>
+ </desc>
+ </func>
+ <func>
+ <name>handle_system_msg(Msg,From,Parent,Module,Debug,Misc)</name>
+ <fsummary>Take care of system messages</fsummary>
+ <type>
+ <v>Msg = term()</v>
+ <v>From = pid()</v>
+ <v>Parent = pid()</v>
+ <v>Module = atom()</v>
+ <v>Debug = [dbg_opt()]</v>
+ <v>Misc = term()</v>
+ </type>
+ <desc>
+ <p>This function is used by a process module that wishes to take care of system
+ messages. The process receives a <c>{system, From, Msg}</c>
+ message and passes the <c>Msg</c> and <c>From</c> to this
+ function.
+ </p>
+ <p>This function <em>never</em> returns. It calls the function
+ <c>Module:system_continue(Parent, NDebug, Misc)</c> where the
+ process continues the execution, or
+ <c>Module:system_terminate(Reason, Parent, Debug, Misc)</c> if
+ the process should terminate. The <c>Module</c> must export
+ <c>system_continue/3</c>, <c>system_terminate/4</c>, and
+ <c>system_code_change/4</c> (see below).
+ </p>
+ <p>The <c>Misc</c> argument can be used to save internal data
+ in a process, for example its state. It is sent to
+ <c>Module:system_continue/3</c> or
+ <c>Module:system_terminate/4</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>print_log(Debug) -> void()</name>
+ <fsummary>Print the logged events in the debug structure</fsummary>
+ <type>
+ <v>Debug = [dbg_opt()]</v>
+ </type>
+ <desc>
+ <p>Prints the logged system events in the debug structure
+ using <c>FormFunc</c> as defined when the event was
+ generated by a call to <c>handle_debug/4</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Mod:system_continue(Parent, Debug, Misc)</name>
+ <fsummary>Called when the process should continue its execution</fsummary>
+ <type>
+ <v>Parent = pid()</v>
+ <v>Debug = [dbg_opt()]</v>
+ <v>Misc = term()</v>
+ </type>
+ <desc>
+ <p>This function is called from <c>sys:handle_system_msg/6</c> when the process
+ should continue its execution (for example after it has been
+ suspended). This function never returns.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Mod:system_terminate(Reason, Parent, Debug, Misc)</name>
+ <fsummary>Called when the process should terminate</fsummary>
+ <type>
+ <v>Reason = term()</v>
+ <v>Parent = pid()</v>
+ <v>Debug = [dbg_opt()]</v>
+ <v>Misc = term()</v>
+ </type>
+ <desc>
+ <p>This function is called from <c>sys:handle_system_msg/6</c> when the process
+ should terminate. For example, this function is called when
+ the process is suspended and its parent orders shut-down.
+ It gives the process a chance to do a clean-up. This function never
+ returns.</p>
+ </desc>
+ </func>
+ <func>
+ <name>Mod:system_code_change(Misc, Module, OldVsn, Extra) -> {ok, NMisc}</name>
+ <fsummary>Called when the process should perform a code change</fsummary>
+ <type>
+ <v>Misc = term()</v>
+ <v>OldVsn = undefined | term()</v>
+ <v>Module = atom()</v>
+ <v>Extra = term()</v>
+ <v>NMisc = term()</v>
+ </type>
+ <desc>
+ <p>Called from <c>sys:handle_system_msg/6</c> when the process
+ should perform a code change. The code change is used when the
+ internal data structure has changed. This function
+ converts the <c>Misc</c> argument to the new data
+ structure. <c>OldVsn</c> is the <em>vsn</em> attribute of the
+ old version of the <c>Module</c>. If no such attribute was
+ defined, the atom <c>undefined</c> is sent.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml
new file mode 100644
index 0000000000..0b6807dd6c
--- /dev/null
+++ b/lib/stdlib/doc/src/timer.xml
@@ -0,0 +1,300 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>timer</title>
+ <prepared>Sebastian Strollo</prepared>
+ <responsible>Bjarne D&auml;cker</responsible>
+ <docno>1</docno>
+ <approved>Bjarne D&auml;cker</approved>
+ <checked></checked>
+ <date>1998-09-09</date>
+ <rev>D</rev>
+ <file>timer.sgml</file>
+ </header>
+ <module>timer</module>
+ <modulesummary>Timer Functions</modulesummary>
+ <description>
+ <p>This module provides useful functions related to time. Unless otherwise
+ stated, time is always measured in <c>milliseconds</c>. All
+ timer functions return immediately, regardless of work carried
+ out by another process.
+ </p>
+ <p>Successful evaluations of the timer functions yield return values
+ containing a timer reference, denoted <c>TRef</c> below. By using
+ <c>cancel/1</c>, the returned reference can be used to cancel any
+ requested action. A <c>TRef</c> is an Erlang term, the contents
+ of which must not be altered.
+ </p>
+ <p>The timeouts are not exact, but should be <c>at least</c> as long
+ as requested.
+ </p>
+ </description>
+ <funcs>
+ <func>
+ <name>start() -> ok</name>
+ <fsummary>Start a global timer server (named <c>timer_server</c>).</fsummary>
+ <desc>
+ <p>Starts the timer server. Normally, the server does not need
+ to be started explicitly. It is started dynamically if it
+ is needed. This is useful during development, but in a
+ target system the server should be started explicitly. Use
+ configuration parameters for <c>kernel</c> for this.</p>
+ </desc>
+ </func>
+ <func>
+ <name>apply_after(Time, Module, Function, Arguments) -> {ok, Tref} | {error, Reason}</name>
+ <fsummary>Apply <c>Module:Function(Arguments)</c>after a specified <c>Time</c>.</fsummary>
+ <type>
+ <v>Time = integer() in Milliseconds</v>
+ <v>Module = Function = atom()</v>
+ <v>Arguments = [term()]</v>
+ </type>
+ <desc>
+ <p>Evaluates <c>apply(M, F, A)</c> after <c>Time</c> amount of time
+ has elapsed. Returns <c>{ok, TRef}</c>, or <c>{error, Reason}</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send_after(Time, Pid, Message) -> {ok, TRef} | {error,Reason}</name>
+ <name>send_after(Time, Message) -> {ok, TRef} | {error,Reason}</name>
+ <fsummary>Send <c>Message</c>to <c>Pid</c>after a specified <c>Time</c>.</fsummary>
+ <type>
+ <v>Time = integer() in Milliseconds</v>
+ <v>Pid = pid() | atom()</v>
+ <v>Message = term()</v>
+ <v>Result = {ok, TRef} | {error, Reason}</v>
+ </type>
+ <desc>
+ <p></p>
+ <taglist>
+ <tag><c>send_after/3</c></tag>
+ <item>
+ <p>Evaluates <c>Pid ! Message</c> after <c>Time</c> amount
+ of time has elapsed. (<c>Pid</c> can also be an atom of a
+ registered name.) Returns <c>{ok, TRef}</c>, or
+ <c>{error, Reason}</c>.</p>
+ </item>
+ <tag><c>send_after/2</c></tag>
+ <item>
+ <p>Same as <c>send_after(Time, self(), Message)</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>exit_after(Time, Pid, Reason1) -> {ok, TRef} | {error,Reason2}</name>
+ <name>exit_after(Time, Reason1) -> {ok, TRef} | {error,Reason2}</name>
+ <name>kill_after(Time, Pid)-> {ok, TRef} | {error,Reason2}</name>
+ <name>kill_after(Time) -> {ok, TRef} | {error,Reason2}</name>
+ <fsummary>Send an exit signal with <c>Reason</c>after a specified <c>Time</c>.</fsummary>
+ <type>
+ <v>Time = integer() in milliseconds</v>
+ <v>Pid = pid() | atom()</v>
+ <v>Reason1 = Reason2 = term()</v>
+ </type>
+ <desc>
+ <p></p>
+ <taglist>
+ <tag><c>exit_after/3</c></tag>
+ <item>
+ <p>Send an exit signal with reason <c>Reason1</c> to Pid
+ <c>Pid</c>. Returns <c>{ok, TRef}</c>, or
+ <c>{error, Reason2}</c>.</p>
+ </item>
+ <tag><c>exit_after/2</c></tag>
+ <item>
+ <p>Same as <c>exit_after(Time, self(), Reason1)</c>. </p>
+ </item>
+ <tag><c>kill_after/2</c></tag>
+ <item>
+ <p>Same as <c>exit_after(Time, Pid, kill)</c>. </p>
+ </item>
+ <tag><c>kill_after/1</c></tag>
+ <item>
+ <p>Same as <c>exit_after(Time, self(), kill)</c>. </p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>apply_interval(Time, Module, Function, Arguments) -> {ok, TRef} | {error, Reason}</name>
+ <fsummary>Evaluate <c>Module:Function(Arguments)</c>repeatedly at intervals of <c>Time</c>.</fsummary>
+ <type>
+ <v>Time = integer() in milliseconds</v>
+ <v>Module = Function = atom()</v>
+ <v>Arguments = [term()]</v>
+ </type>
+ <desc>
+ <p>Evaluates <c>apply(Module, Function, Arguments)</c> repeatedly at
+ intervals of <c>Time</c>. Returns <c>{ok, TRef}</c>, or
+ <c>{error, Reason}</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send_interval(Time, Pid, Message) -> {ok, TRef} | {error, Reason}</name>
+ <name>send_interval(Time, Message) -> {ok, TRef} | {error, Reason}</name>
+ <fsummary>Send <c>Message</c>repeatedly at intervals of <c>Time</c>.</fsummary>
+ <type>
+ <v>Time = integer() in milliseconds</v>
+ <v>Pid = pid() | atom()</v>
+ <v>Message = term()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p></p>
+ <taglist>
+ <tag><c>send_interval/3</c></tag>
+ <item>
+ <p>Evaluates <c>Pid ! Message</c> repeatedly after <c>Time</c>
+ amount of time has elapsed. (<c>Pid</c> can also be an atom of
+ a registered name.) Returns <c>{ok, TRef}</c> or
+ <c>{error, Reason}</c>.</p>
+ </item>
+ <tag><c>send_interval/2</c></tag>
+ <item>
+ <p>Same as <c>send_interval(Time, self(), Message)</c>.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>cancel(TRef) -> {ok, cancel} | {error, Reason}</name>
+ <fsummary>Cancel a previously requested timeout identified by <c>TRef</c>.</fsummary>
+ <desc>
+ <p>Cancels a previously requested timeout. <c>TRef</c> is a unique
+ timer reference returned by the timer function in question. Returns
+ <c>{ok, cancel}</c>, or <c>{error, Reason}</c> when <c>TRef</c>
+ is not a timer reference.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sleep(Time) -> ok</name>
+ <fsummary>Suspend the calling process for <c>Time</c>amount of milliseconds.</fsummary>
+ <type>
+ <v>Time = integer() in milliseconds or the atom infinity</v>
+ </type>
+ <desc>
+ <p>Suspends the process calling this function for <c>Time</c> amount
+ of milliseconds and then returns <c>ok</c>, or suspend the process
+ forever if <c>Time</c> is the atom <c>infinity</c>. Naturally, this
+ function does <em>not</em> return immediately.</p>
+ </desc>
+ </func>
+ <func>
+ <name>tc(Module, Function, Arguments) -> {Time, Value}</name>
+ <fsummary>Measure the real time it takes to evaluate <c>apply(Module, Function, Arguments)</c></fsummary>
+ <type>
+ <v>Module = Function = atom()</v>
+ <v>Arguments = [term()]</v>
+ <v>Time = integer() in microseconds</v>
+ <v>Value = term()</v>
+ </type>
+ <desc>
+ <p>Evaluates <c>apply(Module, Function, Arguments)</c> and measures
+ the elapsed real time. Returns <c>{Time, Value}</c>, where
+ <c>Time</c> is the elapsed real time in <em>microseconds</em>,
+ and <c>Value</c> is what is returned from the apply.</p>
+ </desc>
+ </func>
+ <func>
+ <name>now_diff(T2, T1) -> Tdiff</name>
+ <fsummary>Calculate time difference between <c>now/0</c>timestamps</fsummary>
+ <type>
+ <v>T1 = T2 = {MegaSecs, Secs, MicroSecs}</v>
+ <v>Tdiff = MegaSecs = Secs = MicroSecs = integer()</v>
+ </type>
+ <desc>
+ <p>Calculates the time difference <c>Tdiff = T2 - T1</c> in
+ <em>microseconds</em>, where <c>T1</c> and <c>T2</c> probably
+ are timestamp tuples returned from <c>erlang:now/0</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>seconds(Seconds) -> Milliseconds</name>
+ <fsummary>Convert <c>Seconds</c>to <c>Milliseconds</c>.</fsummary>
+ <desc>
+ <p>Returns the number of milliseconds in <c>Seconds</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>minutes(Minutes) -> Milliseconds</name>
+ <fsummary>Converts <c>Minutes</c>to <c>Milliseconds</c>.</fsummary>
+ <desc>
+ <p>Return the number of milliseconds in <c>Minutes</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>hours(Hours) -> Milliseconds</name>
+ <fsummary>Convert <c>Hours</c>to <c>Milliseconds</c>.</fsummary>
+ <desc>
+ <p>Returns the number of milliseconds in <c>Hours</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>hms(Hours, Minutes, Seconds) -> Milliseconds</name>
+ <fsummary>Convert <c>Hours</c>+<c>Minutes</c>+<c>Seconds</c>to <c>Milliseconds</c>.</fsummary>
+ <desc>
+ <p>Returns the number of milliseconds in <c>Hours + Minutes + Seconds</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>Examples</title>
+ <p>This example illustrates how to print out "Hello World!" in 5 seconds:</p>
+ <p></p>
+ <pre>
+ 1> <input>timer:apply_after(5000, io, format, ["~nHello World!~n", []]).</input>
+ {ok,TRef}
+ Hello World!</pre>
+ <p>The following coding example illustrates a process which performs a
+ certain action and if this action is not completed within a certain
+ limit, then the process is killed.</p>
+ <code type="none">
+ Pid = spawn(mod, fun, [foo, bar]),
+ %% If pid is not finished in 10 seconds, kill him
+ {ok, R} = timer:kill_after(timer:seconds(10), Pid),
+ ...
+ %% We change our mind...
+ timer:cancel(R),
+ ...</code>
+ </section>
+
+ <section>
+ <title>WARNING</title>
+ <p>A timer can always be removed by calling <c>cancel/1</c>.
+ </p>
+ <p>An interval timer, i.e. a timer created by evaluating any of the
+ functions <c>apply_interval/4</c>, <c>send_interval/3</c>, and
+ <c>send_interval/2</c>, is linked to the process towards which
+ the timer performs its task.
+ </p>
+ <p>A one-shot timer, i.e. a timer created by evaluating any of the
+ functions <c>apply_after/4</c>, <c>send_after/3</c>,
+ <c>send_after/2</c>, <c>exit_after/3</c>, <c>exit_after/2</c>,
+ <c>kill_after/2</c>, and <c>kill_after/1</c> is not linked to any
+ process. Hence, such a timer is removed only when it reaches its
+ timeout, or if it is explicitly removed by a call to <c>cancel/1</c>.</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml
new file mode 100644
index 0000000000..b3aad51591
--- /dev/null
+++ b/lib/stdlib/doc/src/unicode.xml
@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1996</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>unicode</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ </header>
+ <module>unicode</module>
+ <modulesummary>Functions for converting Unicode characters</modulesummary>
+ <description>
+ <p>This module contains functions for converting between different character representations. Basically it converts between iso-latin-1 characters and Unicode ditto, but it can also convert between different Unicode encodings (like UTF-8, UTF-16 and UTF-32).</p>
+ <p>The default Unicode encoding in Erlang is in binaries UTF-8, which is also the format in which built in functions and libraries in OTP expect to find binary Unicode data. In lists, Unicode data is encoded as integers, each integer representing one character and encoded simply as the Unicode codepoint for the character.</p>
+ <p>Other Unicode encodings than integers representing codepoints or UTF-8 in binaries are referred to as &quot;external encodings&quot;. The iso-latin-1 encoding is in binaries and lists referred to as latin1-encoding.</p>
+ <p>It is recommended to only use external encodings for communication with external entities where this is required. When working inside the Erlang/OTP environment, it is recommended to keep binaries in UTF-8 when representing Unicode characters. Latin1 encoding is supported both for backward compatibility and for communication with external entities not supporting Unicode character sets.</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <marker id="charlist_definition"></marker>
+ <code type="none">
+unicode_binary() = binary() with characters encoded in UTF-8 coding standard
+unicode_char() = integer() representing valid unicode codepoint
+
+chardata() = charlist() | unicode_binary()
+
+charlist() = [unicode_char() | unicode_binary() | charlist()]
+ a unicode_binary is allowed as the tail of the list</code>
+
+ <code type="none">
+external_unicode_binary() = binary() with characters coded in a user specified Unicode encoding other than UTF-8 (UTF-16 or UTF-32)
+
+external_chardata() = external_charlist() | external_unicode_binary()
+
+external_charlist() = [unicode_char() | external_unicode_binary() | external_charlist()]
+ an external_unicode_binary is allowed as the tail of the list</code>
+
+ <code type="none">
+latin1_binary() = binary() with characters coded in iso-latin-1
+latin1_char() = integer() representing valid latin1 character (0-255)
+
+latin1_chardata() = latin1_charlist() | latin1_binary()
+
+latin1_charlist() = [latin1_char() | latin1_binary() | latin1_charlist()]
+ a latin1_binary is allowed as the tail of the list</code>
+ </section>
+ <funcs>
+ <func>
+ <name>bom_to_encoding(Bin) -> {Encoding,Length}</name>
+ <fsummary>Identify UTF byte order marks in a binary.</fsummary>
+ <type>
+ <v>Bin = binary() of byte_size 4 or more</v>
+ <v>Encoding = latin1 | utf8 | {utf16,little} | {utf16,big} | {utf32,little} | {utf32,big}</v>
+ <v>Length = int()</v>
+ </type>
+ <desc>
+
+ <p>Check for a UTF byte order mark (BOM) in the beginning of a
+ binary. If the supplied binary <c>Bin</c> begins with a valid
+ byte order mark for either UTF-8, UTF-16 or UTF-32, the function
+ returns the encoding identified along with the length of the BOM
+ in bytes.</p>
+
+ <p>If no BOM is found, the function returns <c>{latin1,0}</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>characters_to_list(Data) -> list() | {error, list(), RestData} | {incomplete, list(), binary()} </name>
+ <fsummary>Convert a collection of characters to list of Unicode characters</fsummary>
+ <type>
+ <v>Data = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>RestData = latin1_chardata() | chardata() | external_chardata()</v>
+ </type>
+ <desc>
+ <p>Same as characters_to_list(Data,unicode).</p>
+ </desc>
+ </func>
+ <func>
+ <name>characters_to_list(Data, InEncoding) -> list() | {error, list(), RestData} | {incomplete, list(), binary()} </name>
+ <fsummary>Convert a collection of characters to list of Unicode characters</fsummary>
+ <type>
+ <v>Data = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>RestData = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>InEncoding = latin1 | unicode | utf8 | utf16 | utf32 | {utf16,little} | {utf16,big} | {utf32,little} | {utf32,big}</v>
+ </type>
+ <desc>
+
+ <p>This function converts a possibly deep list of integers and
+ binaries into a list of integers representing unicode
+ characters. The binaries in the input may have characters
+ encoded as latin1 (0 - 255, one character per byte), in which
+ case the <c>InEncoding</c> parameter should be given as
+ <c>latin1</c>, or have characters encoded as one of the
+ UTF-encodings, which is given as the <c>InEncoding</c>
+ parameter. Only when the <c>InEncoding</c> is one of the UTF
+ encodings, integers in the list are allowed to be grater than
+ 255.</p>
+
+ <p>If <c>InEncoding</c> is <c>latin1</c>, the <c>Data</c> parameter
+ corresponds to the <c>iodata()</c> type, but for <c>unicode</c>,
+ the <c>Data</c> parameter can contain integers greater than 255
+ (unicode characters beyond the iso-latin-1 range), which would
+ make it invalid as <c>iodata()</c>.</p>
+
+ <p>The purpose of the function is mainly to be able to convert
+ combinations of unicode characters into a pure unicode
+ string in list representation for further processing. For
+ writing the data to an external entity, the reverse function
+ <seealso
+ marker="#characters_to_binary/3">characters_to_binary/3</seealso>
+ comes in handy.</p>
+
+ <p>The option <c>unicode</c> is an alias for <c>utf8</c>, as this is the
+ preferred encoding for Unicode characters in
+ binaries. <c>utf16</c> is an alias for <c>{utf16,big}</c> and
+ <c>utf32</c> is an alias for <c>{utf32,big}</c>. The <c>big</c>
+ and <c>little</c> atoms denote big or little endian
+ encoding.</p>
+
+ <p>If for some reason, the data cannot be converted, either
+ because of illegal unicode/latin1 characters in the list, or
+ because of invalid UTF encoding in any binaries, an error
+ tuple is returned. The error tuple contains the tag
+ <c>error</c>, a list representing the characters that could be
+ converted before the error occurred and a representation of the
+ characters including and after the offending integer/bytes. The
+ last part is mostly for debugging as it still constitutes a
+ possibly deep and/or mixed list, not necessarily of the same
+ depth as the original data. The error occurs when traversing the
+ list and whatever's left to decode is simply returned as is.</p>
+
+ <p>However, if the input <c>Data</c> is a pure binary, the third
+ part of the error tuple is guaranteed to be a binary as
+ well.</p>
+
+ <p>Errors occur for the following reasons:</p>
+ <list type="bulleted">
+
+ <item>Integers out of range - If <c>InEncoding</c> is
+ <c>latin1</c>, an error occurs whenever an integer greater
+ than 255 is found in the lists. If <c>InEncoding</c> is
+ of a Unicode type, error occurs whenever an integer greater than
+ <c>16#10FFFF</c> (the maximum unicode character) or in the
+ range <c>16#D800</c> to <c>16#DFFF</c> (invalid unicode
+ range) is found.</item>
+
+ <item>UTF encoding incorrect - If <c>InEncoding</c> is
+ one of the UTF types, the bytes in any binaries have to be valid
+ in that encoding. Errors can occur for various
+ reasons, including &quot;pure&quot; decoding errors
+ (like the upper
+ bits of the bytes being wrong), the bytes are decoded to a
+ too large number, the bytes are decoded to a code-point in the
+ invalid unicode
+ range or encoding is &quot;overlong&quot;, meaning that a
+ number should have been encoded in fewer bytes. The
+ case of a truncated UTF is handled specially, see the
+ paragraph about incomplete binaries below. If
+ <c>InEncoding</c> is <c>latin1</c>, binaries are always valid
+ as long as they contain whole bytes,
+ as each byte falls into the valid iso-latin-1 range.</item>
+
+ </list>
+
+ <p>A special type of error is when no actual invalid integers or
+ bytes are found, but a trailing <c>binary()</c> consists of too
+ few bytes to decode the last character. This error might occur
+ if bytes are read from a file in chunks or binaries in other
+ ways are split on non UTF character boundaries. In this case an
+ <c>incomplete</c> tuple is returned instead of the <c>error</c>
+ tuple. It consists of the same parts as the <c>error</c> tuple, but
+ the tag is <c>incomplete</c> instead of <c>error</c> and the
+ last element is always guaranteed to be a binary consisting of
+ the first part of a (so far) valid UTF character.</p>
+
+ <p>If one UTF characters is split over two consecutive
+ binaries in the <c>Data</c>, the conversion succeeds. This means
+ that a character can be decoded from a range of binaries as long
+ as the whole range is given as input without errors
+ occurring. Example:</p>
+
+<code>
+ decode_data(Data) ->
+ case unicode:characters_to_list(Data,unicode) of
+ {incomplete,Encoded, Rest} ->
+ More = get_some_more_data(),
+ Encoded ++ decode_data([Rest, More]);
+ {error,Encoded,Rest} ->
+ handle_error(Encoded,Rest);
+ List ->
+ List
+ end.
+</code>
+ <p>Bit-strings that are not whole bytes are however not allowed,
+ so a UTF character has to be split along 8-bit boundaries to
+ ever be decoded.</p>
+
+ <p>If any parameters are of the wrong type, the list structure
+ is invalid (a number as tail) or the binaries does not contain
+ whole bytes (bit-strings), a <c>badarg</c> exception is
+ thrown.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>characters_to_binary(Data) -> binary() | {error, binary(), RestData} | {incomplete, binary(), binary()} </name>
+ <fsummary>Convert a collection of characters to an UTF-8 binary</fsummary> <type>
+ <v>Data = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>RestData = latin1_chardata() | chardata() | external_chardata()</v>
+ </type>
+ <desc>
+ <p>Same as characters_to_binary(Data, unicode, unicode).</p>
+ </desc>
+ </func>
+ <func>
+ <name>characters_to_binary(Data,InEncoding) -> binary() | {error, binary(), RestData} | {incomplete, binary(), binary()} </name>
+ <fsummary>Convert a collection of characters to an UTF-8 binary</fsummary> <type>
+ <v>Data = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>RestData = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>InEncoding = latin1 | unicode | utf8 | utf16 | utf32 | {utf16,little} | {utf16,big} | {utf32,little} | {utf32,big}</v>
+ </type>
+ <desc>
+ <p>Same as characters_to_binary(Data, InEncoding, unicode).</p>
+ </desc>
+ </func>
+ <func>
+ <name>characters_to_binary(Data, InEncoding, OutEncoding) -> binary() | {error, binary(), RestData} | {incomplete, binary(), binary()} </name>
+ <fsummary>Convert a collection of characters to an UTF-8 binary</fsummary>
+ <type>
+ <v>Data = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>RestData = latin1_chardata() | chardata() | external_chardata()</v>
+ <v>InEncoding = latin1 | unicode | utf8 | utf16 | utf32 | {utf16,little} | {utf16,big} | {utf32,little} | {utf32,big}</v>
+ <v>OutEncoding = latin1 | unicode | utf8 | utf16 | utf32| {utf16,little} | {utf16,big} | {utf32,little} | {utf32,big}</v>
+ </type>
+ <desc>
+
+ <p>This function behaves as <seealso
+ marker="#characters_to_list/2">
+ characters_to_list/2</seealso>, but produces an binary
+ instead of a unicode list. The
+ <c>InEncoding</c> defines how input is to be interpreted if
+ binaries are present in the <c>Data</c>, while
+ <c>OutEncoding</c> defines in what format output is to be
+ generated.</p>
+
+ <p>The option <c>unicode</c> is an alias for <c>utf8</c>, as this is the
+ preferred encoding for Unicode characters in
+ binaries. <c>utf16</c> is an alias for <c>{utf16,big}</c> and
+ <c>utf32</c> is an alias for <c>{utf32,big}</c>. The <c>big</c>
+ and <c>little</c> atoms denote big or little endian
+ encoding.</p>
+
+ <p>Errors and exceptions occur as in <seealso
+ marker="#characters_to_list/2">
+ characters_to_list/2</seealso>, but the second element
+ in the <c>error</c> or
+ <c>incomplete</c> tuple will be a <c>binary()</c> and not a
+ <c>list()</c>.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>encoding_to_bom(InEncoding) -> Bin</name>
+ <fsummary>Create a binary UTF byte order mark from encoding.</fsummary>
+ <type>
+ <v>Bin = binary() of byte_size 4 or less</v>
+ <v>InEncoding = latin1 | unicode | utf8 | utf16 | utf32 | {utf16,little} | {utf16,big} | {utf32,little} | {utf32,big}</v>
+ <v>Length = int()</v>
+ </type>
+ <desc>
+
+ <p>Create an UTF byte order mark (BOM) as a binary from the
+ supplied <c>InEncoding</c>. The BOM is, if supported at all,
+ expected to be placed first in UTF encoded files or
+ messages.</p>
+
+ <p>The function returns <c>&lt;&lt;&gt;&gt;</c> for the
+ <c>latin1</c> encoding, there is no BOM for ISO-latin-1.</p>
+
+ <p>It can be noted that the BOM for UTF-8 is seldom used, and it
+ is really not a <em>byte order</em> mark. There are obviously no
+ byte order issues with UTF-8, so the BOM is only there to
+ differentiate UTF-8 encoding from other UTF formats.</p>
+
+ </desc>
+ </func>
+ </funcs>
+</erlref>
diff --git a/lib/stdlib/doc/src/unicode_usage.xml b/lib/stdlib/doc/src/unicode_usage.xml
new file mode 100644
index 0000000000..06347b3aae
--- /dev/null
+++ b/lib/stdlib/doc/src/unicode_usage.xml
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>1999</year>
+ <year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>Using Unicode in Erlang</title>
+ <prepared>Patrik Nyblom</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2009-02-25</date>
+ <rev>PA1</rev>
+ <file>unicode_usage.xml</file>
+ </header>
+<p>Implementing support for Unicode character sets is an ongoing process. The Erlang Enhancement Proposal (EEP) 10 outlines the basics of Unicode support and also specifies a default encoding in binaries that all Unicode-aware modules should handle in the future.</p>
+<p>The functionality described in EEP10 is implemented in Erlang/OTP as of R13A, but that's by no means the end of it. More functionality will be needed in the future and more OTP-libraries might need updating to cope with Unicode data. One example of future development is obvious when reading this manual, our documentation format is limited to the ISO-latin-1 character range, why no Unicode characters beyond that range will occur in this document.</p>
+<p>This guide outlines the current Unicode support and gives a couple of recipes for working with Unicode data.</p>
+<section>
+<title>What Unicode is</title>
+<p>Unicode is a standard defining codepoints (numbers) for all known, living or dead, scripts. In principle, every known symbol used in any language has a Unicode codepoint.</p>
+<p>Unicode codepoints are defined and published by the <em>Unicode Consortium</em>, which is a non profit organization.</p>
+<p>Support for Unicode is increasing throughout the world of computing, as the benefits of one common character set are overwhelming when programs are used in a global environment.</p>
+<p>Along with the base of the standard, the codepoints for all the scripts, there are a couple of encoding standards available. Different operating systems and tools support different encodings. For example Linux and MacOS X has chosen the UTF-8 encoding, which is backwards compatible with 7-bit ASCII and therefore affects programs written in plain English the least. Windows&reg; on the other hand supports a limited version of UTF-16, namely all the code planes where the characters can be stored in one single 16-bit entity, which includes most living languages.</p>
+<p>The most widely spread encodings are:</p>
+<taglist>
+<tag>UTF-8</tag>
+<item>Each character is stored in one to four bytes depending on codepoint. The encoding is backwards compatible with 7-bit ASCII as all 7-bit characters are stored in one single byte as is. The characters beyond codepoint 127 are stored in more bytes, letting the most significant bit in the first character indicate a multi-byte character. For details on the encoding, the RFC is publicly available.</item>
+<tag>UTF-16</tag>
+<item>This encoding has many similarities to UTF-8, but the basic unit is a 16-bit number. This means that all characters occupy at least two bytes, some high numbers even four bytes. Some programs and operating systems claiming to use UTF-16 only allows for characters that can be stored in one 16-bit entity, which is usually sufficient to handle living languages. As the basic unit is more than one byte, byte-order issues occur, why UTF-16 exists in both a big-endian and little-endian variant.</item>
+<tag>UTF-32</tag>
+<item>The most straight forward representation, each character is stored in one single 32-bit number. There is no need for escapes or any variable amount of entities for one character, all Unicode codepoints can be stored in one single 32-bit entity. As with UTF-16, there are byte-order issues, UTF-32 can be both big- and little-endian.</item>
+<tag>UCS-4</tag>
+<item>Basically the same as UTF-32, but without some Unicode semantics, defined by IEEE and has little use as a separate encoding standard. For all normal (and possibly abnormal) usages, UTF-32 and UCS-4 are interchangeable.</item>
+</taglist>
+<p>Certain ranges of characters are left unused and certain ranges are even deemed invalid. The most notable invalid range is 16#D800 - 16#DFFF, as the UTF-16 encoding does not allow for encoding of these numbers. It can be speculated that the UTF-16 encoding standard was, from the beginning, expected to be able to hold all Unicode characters in one 16-bit entity, but then had to be extended, leaving a whole in the Unicode range to cope with backward compatibility.</p>
+<p>Additionally, the codepoint 16#FEFF is used for byte order marks (BOM's) and use of that character is not encouraged in other contexts than that. It actually is valid though, as the character "ZWNBS" (Zero Width Non Breaking Space). BOM's are used to identify encodings and byte order for programs where such parameters are not known in advance. Byte order marks are more seldom used than one could expect, put their use is becoming more widely spread as they provide the means for programs to make educated guesses about the Unicode format of a certain file.</p>
+</section>
+<section>
+<title>Standard Unicode representation in Erlang</title>
+<p>In Erlang, strings are actually lists of integers. A string is defined to be encoded in the ISO-latin-1 (ISO8859-1) character set, which is, codepoint by codepoint, a sub-range of the Unicode character set.</p>
+<p>The standard list encoding for strings is therefore easily extendible to cope with the whole Unicode range: A Unicode string in Erlang is simply a list containing integers, each integer being a valid Unicode codepoint and representing one character in the Unicode character set.</p>
+<p>Regular Erlang strings in ISO-latin-1 are a subset of there Unicode strings.</p>
+
+<p>Binaries on the other hand are more troublesome. For performance reasons, programs often store textual data in binaries instead of lists, mainly because they are more compact (one byte per character instead of two words per character, as is the case with lists). Using erlang:list_to_binary/1, an regular Erlang string can be converted into a binary, effectively using the ISO-latin-1 encoding in the binary - one byte per character. This is very convenient for those regular Erlang strings, but cannot be done for Unicode lists.</p>
+<p>As the UTF-8 encoding is widely spread and provides the most compact storage, it is selected as the standard encoding of Unicode characters in binaries for Erlang.</p>
+<p>The standard binary encoding is used whenever a library function in Erlang should cope with Unicode data in binaries, but is of course not enforced when communicating externally. Functions and bit-syntax exist to encode and decode both UTF-8, UTF-16 and UTF-32 in binaries. Library functions dealing with binaries and Unicode in general, however, only deal with the default encoding.</p>
+
+<p>Character data may be combined from several sources, sometimes available in a mix of strings and binaries. Erlang has for long had the concept of iodata or iolists, where binaries and lists can be combined to represent a sequence of bytes. In the same way, the Unicode aware modules often allow for combinations of binaries and lists where the binaries have characters encoded in UTF-8 and the lists contain such binaries or numbers representing Unicode codepoints:</p>
+<code type="none">
+unicode_binary() = binary() with characters encoded in UTF-8 coding standard
+unicode_char() = integer() representing valid unicode codepoint
+
+chardata() = charlist() | unicode_binary()
+
+charlist() = [unicode_char() | unicode_binary() | charlist()]
+ a unicode_binary is allowed as the tail of the list</code>
+<p>The module <c>unicode</c> in stdlib even supports similar mixes with binaries containing other encodings than UTF-8, but that is a special case to allow for conversions to and from external data:</p>
+ <code type="none">
+external_unicode_binary() = binary() with characters coded in a user specified Unicode encoding other than UTF-8 (UTF-16 or UTF-32)
+
+external_chardata() = external_charlist() | external_unicode_binary()
+
+external_charlist() = [unicode_char() | external_unicode_binary() | external_charlist()]
+ an external_unicode_binary is allowed as the tail of the list</code>
+</section>
+<section>
+<title>Basic language support for Unicode</title>
+<p>First of all, Erlang is still defined to be written in the ISO-latin-1 character set. Functions have to be named in that character set, atoms are restricted to ISO-latin-1 and regular strings are still lists of characters 0..255 in the ISO-latin-1 encoding. This has not (yet) changed, but the language has been slightly extended to cope with Unicode characters and encodings.</p>
+
+<section>
+<title>Bit-syntax</title>
+<p>The bit-syntax contains types for coping with binary data in the three main encodings. The types are named <c>utf8</c>, <c>utf16</c> and <c>utf32</c> respectively. The <c>utf16</c> and <c>utf32</c> types can be in a big- or little-endian variant:</p>
+<code>
+&lt;&lt;Ch/utf8,_/binary&gt;&gt; = Bin1,
+&lt;&lt;Ch/utf16-little,_/binary&gt;&gt; = Bin2,
+Bin3 = &lt;&lt;$H/utf32-little, $e/utf32-little, $l/utf32-little, $l/utf32-little, $o/utf32-little&gt;&gt;,</code>
+<p>For convenience, literal strings can be encoded with a Unicode encoding in binaries using the following (or similar) syntax:</p>
+<code>
+Bin4 = &lt;&lt;"Hello"/utf16&gt;&gt;,</code>
+</section>
+<section>
+<title>String- and character-literals</title>
+<warning>
+<p>The literal syntax described here may be subject to change in R13B, it has not yet passed the usual process for language changes approval.</p>
+</warning>
+<p>It is convenient to be able to write a list of Unicode characters in the string syntax. However, the language specifies strings as being in the ISO-latin-1 character set which the compiler tool chain as well as many other tools expect.</p>
+<p>Also the source code is (for now) still expected to be written using the ISO-latin-1 character set, why Unicode characters beyond that range cannot be entered in string literals.</p>
+<p>To make it easier to enter Unicode characters in the shell, it allows strings with Unicode characters on input, immediately converting them to regular lists of integers. They will, by the evaluator etc be viewed as if they were input using the regular list syntax, which is - in the end - how the language actually treats them. They will in the same way not be output as strings by i.e <c>io:write/2</c> or <c>io:format/3</c> unless the format string supplied to <c>io:format</c> uses the Unicode translation modifier (which we will talk about later).</p>
+<p>For source code, there is an extension to the \OOO (backslash followed by three octal numbers) and \xHH (backslash followed by 'x', followed by two hexadecimal characters) syntax, namely \x{H ...} (a backslash followed by an 'x', followed by left curly bracket, any number of hexadecimal digits and a terminating right curly bracket). This allows for entering characters of any codepoint literally in a string. The string is immediately converted into a list by the scanner however, which is obvious when calling it directly:</p>
+<pre>
+1> <input>erl_scan:string("\"X\".").</input>
+{ok,[{string,1,"X"},{dot,1}],1}
+2> <input>erl_scan:string("\"\x{400}\".").</input>
+{ok,[{'[',1},{integer,1,1024},{']',1},{dot,1}],1}</pre>
+<p>Character literals, or rather integers representing Unicode codepoints can be expressed in a similar way using $\x{H ...}:</p>
+<pre>
+4> <input>$\x{400}.</input>
+1024</pre>
+<p>This also is a translation by the scanner:</p>
+<pre>
+5> <input>erl_scan:string("$Y.").</input>
+{ok,[{char,1,89},{dot,1}],1}
+6> <input>erl_scan:string("$\x{400}.").</input>
+{ok,[{integer,1,1024},{dot,1}],1}</pre>
+<p>In the shell, if using a Unicode input device, '$' can be followed directly by a Unicode character producing an integer. In the following example, let's imagine the character 'c' is actually a Cyrillic 's' (looking fairly similar):</p>
+<pre>
+7> <input>$c.</input>
+1089</pre>
+</section>
+<p>The literal syntax allowing Unicode characters is to be viewed as "syntactic sugar", but is, as such, fairly useful.</p>
+</section>
+<section>
+<title>The interactive shell</title>
+<p>The interactive Erlang shell, when started towards a terminal or started using the <c>werl</c> command on windows, can support Unicode input and output.</p>
+<p>On Windows&reg;, proper operation requires that a suitable font is installed and selected for the Erlang application to use. If no suitable font is available on your system, try installing the DejaVu fonts (dejavu-fonts.org), which are freely available and then select that font in the Erlang shell application.</p>
+<p>On Unix&reg;-like operating systems, the terminal should be able to handle UTF-8 on input and output (modern versions of XTerm, KDE konsole and the Gnome terminal do for example) and your locale settings have to be proper. As an example, my LANG environment variable is set as this:</p>
+<pre>
+$ <input>echo $LANG</input>
+en_US.UTF-8</pre>
+<p>Actually, most systems handle the LC_CTYPE variable before LANG, so if that is set, it has to be set to UTF-8:</p>
+<pre>
+$ echo <input>$LC_CTYPE</input>
+en_US.UTF-8</pre>
+<p>The LANG or LC_CTYPE setting should be consistent with what the terminal is capable of, there is no portable way for Erlang to ask the actual terminal about it's UTF-8 capacity, we have to rely on the language and character type settings.</p>
+<p>To investigate what Erlang thinks about the terminal, the <c>io:getopts()</c> call can be used when the shell is started:</p>
+<pre>
+$ <input>LC_CTYPE=en_US.ISO-8859-1 erl</input>
+Erlang R13A (erts-5.7) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
+
+Eshell V5.7 (abort with ^G)
+1> <input>lists:keyfind(encoding,1,io:getopts()).</input>
+{encoding,latin1}
+2> <input>q().</input>
+ok
+$ <input>LC_CTYPE=en_US.UTF-8 erl</input>
+Erlang R13A (erts-5.7) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
+
+Eshell V5.7 (abort with ^G)
+1> <input>lists:keyfind(encoding,1,io:getopts()).</input>
+{encoding,unicode}
+2></pre>
+<p>When (finally?) everything is in order with the locale settings, fonts and the terminal emulator, you probably also have discovered a way to input characters in the script you desire. For testing, the simplest way is to add some keyboard mappings for other languages, usually done with some applet in your desktop environment. In my KDE environment, I start the KDE Control Center (Personal Settings), select "Regional and Accessibility" and then "Keyboard Layout". On Windows XP&reg;, I start Control Panel->Regional and Language Options, select the Language tab and click the Details... button in the square named "Text services and input Languages". Your environment probably provides similar means of changing the keyboard layout. Make sure you have a way to easily switch back and forth between keyboards if you are not used to this, entering commands using a Cyrillic character set is, as an example, not easily done in the Erlang shell.</p>
+<p>Now you are set up for some Unicode input and output. The simplest thing to do is of course to enter a string in the shell:</p>
+<image file="ushell1.gif"><icaption>Cyrillic characters in an Erlang shell</icaption></image>
+<p>While strings can be input as Unicode characters, the language elements are still limited to the ISO-latin-1 character set. Only character constants and strings are allowed to be beyond that range:</p>
+<image file="ushell2.gif"><icaption>Unicode characters in allowed and disallowed context</icaption></image>
+</section>
+<section>
+<title>Unicode-aware modules</title>
+<p>Most of the modules in Erlang/OTP are of course Unicode-unaware in the sense that they have no notion of Unicode and really shouldn't have. Typically they handle non-textual or byte-oriented data (like <c>gen_tcp</c> etc).</p>
+<p>Modules that actually handle textual data (like <c>io_lib</c>, <c>string</c> etc) are sometimes subject to conversion or extension to be able to handle Unicode characters.</p>
+<p>Fortunately, most textual data has been stored in lists and range checking has been sparse, why modules like <c>string</c> works well for Unicode lists with little need for conversion or extension.</p>
+<p>Some modules are however changed to be explicitly Unicode-aware. These modules include:</p>
+<taglist>
+<tag><c>unicode</c></tag>
+<item>
+<p>The module <seealso marker="stdlib:unicode">unicode</seealso> is obviously Unicode-aware. It contains functions for conversion between different Unicode formats as well as some utilities for identifying byte order marks. Few programs handling Unicode data will survive without this module.</p>
+</item>
+<tag><c>io</c></tag>
+<item>
+<p>The <seealso marker="stdlib:io">io</seealso> module has been extended along with the actual I/O-protocol to handle Unicode data. This means that several functions require binaries to be in UTF-8 and there are modifiers to formatting control sequences to allow for outputting of Unicode strings.</p>
+</item>
+<tag><c>file</c>, <c>group</c> and <c>user</c></tag>
+<item>
+<p>I/O-servers throughout the system are able both to handle Unicode data and has options for converting data upon actual output or input to/from the device. As shown earlier, the <seealso marker="stdlib:shell">shell</seealso> has support for Unicode terminals and the <seealso marker="kernel:file">file</seealso> module allows for translation to and from various Unicode formats on disk.</p>
+<p>The actual reading and writing of files with Unicode data is however not best done with the <c>file</c> module as it's interface is byte oriented. A file opened with a Unicode encoding (like UTF-8), is then best read or written using the <seealso marker="stdlib:io">io</seealso> module.</p>
+</item>
+<tag><c>re</c></tag>
+<item>
+<p>The <seealso marker="stdlib:re">re</seealso> module allows for matching Unicode strings as a special option. As the library is actually centered on matching in binaries, the Unicode support is UTF-8-centered.</p>
+</item>
+<tag><c>wx</c></tag>
+<item>
+<p>The <seealso marker="wx:wx">wx</seealso> graphical library has extensive support for Unicode text</p>
+</item>
+</taglist>
+<p>The module <seealso marker="stdlib:string">string</seealso> works perfect for Unicode strings as well as for ISO-latin-1 strings with the exception of the language-dependent <seealso marker="stdlib:string#to_upper/1">to_upper</seealso> and <seealso marker="stdlib:string#to_lower/1">to_lower</seealso> functions, which are only correct for the ISO-latin-1 character set. Actually they can never function correctly for Unicode characters in their current form, there are language and locale issues as well as multi-character mappings to consider when conversion text between cases. Converting case in an international environment is a big subject not yet addressed in OTP.</p>
+</section>
+<section>
+<title>Unicode recipes</title>
+<p>When starting with Unicode, one often stumbles over some common issues. I try to outline some methods of dealing with Unicode data in this section.</p>
+<section>
+<title>Byte order marks</title>
+<p>A common method of identifying encoding in text-files is to put a byte order mark (BOM) first in the file. The BOM is the codepoint 16#FEFF encoded in the same way as the rest of the file. If such a file is to be read, the first few bytes (depending on encoding) is not part of the actual text. This code outlines how to open a file which is believed to have a BOM and set the files encoding and position for further sequential reading (preferably using the <seealso marker="stdlib:io">io</seealso> module). Note that error handling is omitted from the code:</p>
+<code>
+open_bom_file_for_reading(File) -&gt;
+ {ok,F} = file:open(File,[read,binary]),
+ {ok,Bin} = file:read(F,4),
+ {Type,Bytes} = unicode:bom_to_encoding(Bin),
+ file:position(F,Bytes),
+ io:setopts(F,[{encoding,Type}]),
+ {ok,F}.
+</code>
+<p>The <c>unicode:bom_to_encoding/1</c> function identifies the encoding from a binary of at least four bytes. It returns, along with an term suitable for setting the encoding of the file, the actual length of the BOM, so that the file position can be set accordingly. Note that <c>file:position</c> always works on byte-offsets, so that the actual byte-length of the BOM is needed.</p>
+<p>To open a file for writing and putting the BOM first is even simpler:</p>
+<code>
+open_bom_file_for_writing(File,Encoding) -&gt;
+ {ok,F} = file:open(File,[write,binary]),
+ ok = file:write(File,unicode:encoding_to_bom(Encoding)),
+ io:setopts(F,[{encoding,Encoding}]),
+ {ok,F}.
+</code>
+<p>In both cases the file is then best processed using the <c>io</c> module, as the functions in <c>io</c> can handle codepoints beyond the ISO-latin-1 range.</p>
+</section>
+<section>
+<title>Formatted input and output</title>
+<p>When reading and writing to Unicode-aware entities, like the User or a file opened for Unicode translation, you will probably want to format text strings using the functions in <seealso marker="stdlib:io">io</seealso> or <seealso marker="stdlib:io_lib">io_lib</seealso>. For backward compatibility reasons, these functions don't accept just any list as a string, but require e special "translation modifier" when working with Unicode texts. The modifier is "t". When applied to the "s" control character in a formatting string, it accepts all Unicode codepoints and expect binaries to be in UTF-8:</p>
+<pre>
+1> <input>io:format("~ts~n",[&lt;&lt;"���"/utf8&gt;&gt;]).</input>
+���
+ok
+2> <input>io:format("~s~n",[&lt;&lt;"���"/utf8&gt;&gt;]).</input>
+åäö
+ok</pre>
+<p>Obviously the second <c>io:format</c> gives undesired output because the UTF-8 binary is not in latin1. Because ISO-latin-1 is still the defined character set of Erlang, the non prefixed "s" control character expects ISO-latin-1 in binaries as well as lists.</p>
+<p>As long as the data is always lists, the "t" modifier can be used for any string, but when binary data is involved, care must be taken to make the tight choice of formatting characters.</p>
+<p>The function <c>format</c> in <c>io_lib</c> behaves similarly. This function is defined to return a deep list of characters and the output could easily be converted to binary data for outputting on a device of any kind by a simple <c>erlang:list_to_binary</c>. When the translation modifier is used, the list can however contain characters that cannot be stored in one byte. The call to <c>erlang:list_to_binary</c> will in that case fail. However, if the io_server you want to communicate with is Unicode-aware, the list returned can still be used directly:</p>
+<image file="ushell3.gif"><icaption>io_lib:format with Unicode translation</icaption></image>
+<p>The Unicode string is returned as a Unicode list, why the return value of <c>io_lib:format</c> no longer qualifies as a regular Erlang string (the function <seealso marker="stdlib:io_lib#deep_char_list/1">io_lib:deep_char_list</seealso> will, as an example, return <c>false</c>). The Unicode list is however valid input to the <seealso marker="stdlib:io#put_chars/2">io:put_chars</seealso> function, so data can be output on any Unicode capable device anyway. If the device is a terminal, characters will be output in the \x{H ...} format if encoding is <c>latin1</c> otherwise in UTF-8 (for the non-interactive terminal - "oldshell" or "noshell") or whatever is suitable to show the character properly (for an interactive terminal - the regular shell). The bottom line is that you can always send Unicode data to the <c>standard_io</c> device. Files will however only accept Unicode codepoints beyond ISO-latin-1 if <c>encoding</c> is set to something else than <c>latin1</c>.</p>
+</section>
+<section>
+<title>Heuristic identification of UTF-8</title>
+<p>While it's strongly encouraged that the actual encoding of characters in binary data is known prior to processing, that is not always possible. On a typical Linux&reg; system, there is a mix of UTF-8 and ISO-latin-1 text files and there are seldom any BOM's in the files to identify them.</p>
+<p>UTF-8 is designed in such a way that ISO-latin-1 characters with numbers beyond the 7-bit ASCII range are seldom considered valid when decoded as UTF-8. Therefore one can usually use heuristics to determine if a file is in UTF-8 or if it is encoded in ISO-latin-1 (one byte per character) encoding. The <c>unicode</c> module can be used to determine if data can be interpreted as UTF-8:</p>
+<code>
+heuristic_encoding_bin(Bin) when is_binary(Bin) -&gt;
+ case unicode:characters_to_binary(Bin,utf8,utf8) of
+ Bin ->
+ utf8;
+ _ ->
+ latin1
+ end.
+</code>
+<p>If one does not have a complete binary of the file content, one could instead chunk through the file and check part by part. The return-tuple <c>{incomplete,Decoded,Rest}</c> from <c>unicode:characters_to_binary/{1,2,3}</c> comes in handy. The incomplete rest from one chunk of data read from the file is prepended to the next chunk and we therefore circumvent the problem of character boundaries when reading chunks of bytes in UTF-8 encoding:</p>
+<code>
+heuristic_encoding_file(FileName) -&gt;
+ {ok,F} = file:open(FileName,[read,binary]),
+ loop_through_file(F,&lt;&lt;&gt;&gt;,file:read(F,1024)).
+
+loop_through_file(_,&lt;&lt;&gt;&gt;,eof) -&gt;
+ utf8;
+loop_through_file(_,_,eof) -&gt;
+ latin1;
+loop_through_file(F,Acc,{ok,Bin}) when is_binary(Bin) -&gt;
+ case unicode:characters_to_binary([Acc,Bin]) of
+ {error,_,_} ->
+ latin1;
+ {incomplete,_,Rest} ->
+ loop_through_file(F,Rest,file:read(F,1024));
+ Res when is_binary(Res) ->
+ loop_through_file(F,&lt;&lt;&gt;&gt;,file:read(F,1024))
+ end.
+</code>
+<p>Another option is to try to read the whole file in utf8 encoding and see if it fails. Here we need to read the file using <c>io:get_chars/3</c>, as we have to succeed in reading characters with a codepoint over 255:</p>
+<code>
+heuristic_encoding_file2(FileName) -&gt;
+ {ok,F} = file:open(FileName,[read,binary,{encoding,utf8}]),
+ loop_through_file2(F,io:get_chars(F,'',1024)).
+
+loop_through_file2(_,eof) -&gt;
+ utf8;
+loop_through_file2(_,{error,_Err}) -&gt;
+ latin1;
+loop_through_file2(F,Bin) when is_binary(Bin) -&gt;
+ loop_through_file2(F,io:get_chars(F,'',1024)).
+</code>
+</section>
+</section>
+</chapter>
diff --git a/lib/stdlib/doc/src/user_guide.gif b/lib/stdlib/doc/src/user_guide.gif
new file mode 100644
index 0000000000..e6275a803d
--- /dev/null
+++ b/lib/stdlib/doc/src/user_guide.gif
Binary files differ
diff --git a/lib/stdlib/doc/src/ushell1.gif b/lib/stdlib/doc/src/ushell1.gif
new file mode 100644
index 0000000000..7c46464fd2
--- /dev/null
+++ b/lib/stdlib/doc/src/ushell1.gif
Binary files differ
diff --git a/lib/stdlib/doc/src/ushell1.ps b/lib/stdlib/doc/src/ushell1.ps
new file mode 100644
index 0000000000..95bfebf194
--- /dev/null
+++ b/lib/stdlib/doc/src/ushell1.ps
@@ -0,0 +1,1196 @@
+%!PS-Adobe-3.0
+%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
+%%Title: ushell1.ps
+%%CreationDate: Mon Mar 16 09:53:27 2009
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%Pages: 1
+%%BoundingBox: 14 14 468 142
+%%EndComments
+%%BeginProlog
+% Use own dictionary to avoid conflicts
+10 dict begin
+%%EndProlog
+%%Page: 1 1
+% Translate for offset
+14.173228346456694 14.173228346456694 translate
+% Translate to begin of first scanline
+0 127.55905511811024 translate
+453.54330708661422 -127.55905511811024 scale
+% Image geometry
+640 180 8
+% Transformation matrix
+[ 640 0 0 180 0 0 ]
+% Strings to hold RGB-samples per scanline
+/rstr 640 string def
+/gstr 640 string def
+/bstr 640 string def
+{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
+true 3
+%%BeginData: 67571 ASCII Bytes
+colorimage
+JP1PeJP1PeJP1L~>
+JP1PeJP1PeJP1L~>
+JP1PeJP1PeJP1L~>
+!!e'9JNA?CJNABDJ,~>
+!!e'9JNA?CJNABDJ,~>
+!!e'9JNA?CJNABDJ,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$2%<!Lr-2rr]6=Dt\b^JcC<$JcD\KJ,~>
+!$2%<!Lr-2rr]6=Dt\b^JcC<$JcD\KJ,~>
+!$2%<!Lr-2rr]6=Dt\b^JcC<$JcD\KJ,~>
+#9EjEY>Q6)=6BMbc`J;XJcC<$JcC<$W;hA~>
+#9EjEY>Q6)=6BMbc`J;XJcC<$JcC<$W;hA~>
+#9EjEY>Q6)=6BMbc`J;XJcC<$JcC<$W;hA~>
+#T`rUH-/@H`Dbmu!A3bks+13$s+13Ks*t~>
+#T`rUH-/@H`Dbmu!A3bks+13$s+13Ks*t~>
+#T`rUH-/@H`Dbmu!A3bks+13$s+13Ks*t~>
+#T`rEG5Y4]rp0@Z!W@cP*=4P!s-oDP9H?1krVlmMmt(Lis+13$s/>sJ~>
+#T`rEG5Y4]rp0@Z!W@cP*=4P!s-oDP9H?1krVlmMmt(Lis+13$s/>sJ~>
+#T`rEG5Y4]rp0@Z!W@cP*=4P!s-oDP9H?1krVlmMmt(Lis+13$s/>sJ~>
+#9EjER>jY@r:g3lQA5D=$0F2Hs)Ak%s4op<rrF_?JcC<$JcC<$W;hA~>
+#9EjER>jY@r:g3lQA5D=$0F2Hs)Ak%s4op<rrF_?JcC<$JcC<$W;hA~>
+#9EjER>jY@r:g3lQA5D=$0F2Hs)Ak%s4op<rrF_?JcC<$JcC<$W;hA~>
+!$2(="-F9Q\G-"(Q=^'2$*?/ds)G.?s5e.srrF_?JcC<$JcC<$W;hA~>
+!$2(="-F9Q\G-"(Q=^'2$*?/ds)G.?s5e.srrF_?JcC<$JcC<$W;hA~>
+!$2(="-F9Q\G-"(Q=^'2$*?/ds)G.?s5e.srrF_?JcC<$JcC<$W;hA~>
+!$2%<"&I0WZ2">"Q8&8B"FpIOENK!9!A3bks+13$s+13Ks*t~>
+!$2%<"&I0WZ2">"Q8&8B"FpIOENK!9!A3bks+13$s+13Ks*t~>
+!$2%<"&I0WZ2">"Q8&8B"FpIOENK!9!A3bks+13$s+13Ks*t~>
+#T`rq^Ah!RpEo\8!LAK:rrI,@q>UIImt(Lis+13$s/>sJ~>
+#T`rq^Ah!RpEo\8!LAK:rrI,@q>UIImt(Lis+13$s/>sJ~>
+#T`rq^Ah!RpEo\8!LAK:rrI,@q>UIImt(Lis+13$s/>sJ~>
+#T`s"JSH?.CSgh+!RuStC&iMXrrI,@qu6esC(k*/JcC<$JcC<$WW.J~>
+#T`s"JSH?.CSgh+!RuStC&iMXrrI,@qu6esC(k*/JcC<$JcC<$WW.J~>
+#T`s"JSH?.CSgh+!RuStC&iMXrrI,@qu6esC(k*/JcC<$JcC<$WW.J~>
+#9EjEgp/o#[J'V#fD`&U])M^1eE6Z.!7LkP!5F*bJcC<$JcD_LJ,~>
+#9EjEgp/o#[J'V#fD`&U])M^1eE6Z.!7LkP!5F*bJcC<$JcD_LJ,~>
+#9EjEgp/o#[J'V#fD`&U])M^1eE6Z.!7LkP!5F*bJcC<$JcD_LJ,~>
+!$2%<!T*O$s+13$s+13+s*t~>
+!$2%<!T*O$s+13$s+13+s*t~>
+!$2%<!T*O$s+13$s+13+s*t~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$.X1!VbZgrrMcToD\g]rn%5Fci4"4rn%5Kp&>-Vf\#!)rr_95f\lE%"5DJ\kK!T&r;P=KmIgJZ
+p&<SDo\]ZKoD[ABp@\Fcm/GW;rJ1CTgXt0Ap&>$hrn%5=kPp&~>
+!$.X1!VbZgrrMcToD\g]rn%5Fci4"4rn%5Kp&>-Vf\#!)rr_95f\lE%"5DJ\kK!T&r;P=KmIgJZ
+p&<SDo\]ZKoD[ABp@\Fcm/GW;rJ1CTgXt0Ap&>$hrn%5=kPp&~>
+!$.X1!VbZgrrMcToD\g]rn%5Fci4"4rn%5Kp&>-Vf\#!)rr_95f\lE%"5DJ\kK!T&r;P=KmIgJZ
+p&<SDo\]ZKoD[ABp@\Fcm/GW;rJ1CTgXt0Ap&>$hrn%5=kPp&~>
+!Zh=)r4;sJp\t?M]=[dnrrC^M])^+RrrBt8rrCOG])^O`rrN,;oD\mfUOUkp!5JHD!:K[_!<2l-
+"nM]nb];P3rrf;C6q#_Srrf>i6k8JSrrZO_6rs9d"4Ffn`;BQ8r5SL2!SZc5rrL6To`##66p3I#
+rrWs'7#p%%s5ESL#i+SBs6i?D-]@Zt"M5R?7*O]k#gN&Bs2.5m(WQ.J"Fqsi7/+e&!SceorrM<;
+mf3=!!7LiG"&[email protected])jpjdIXT/=<lMlA~>
+!Zh=)r4;sJp\t?M]=[dnrrC^M])^+RrrBt8rrCOG])^O`rrN,;oD\mfUOUkp!5JHD!:K[_!<2l-
+"nM]nb];P3rrf;C6q#_Srrf>i6k8JSrrZO_6rs9d"4Ffn`;BQ8r5SL2!SZc5rrL6To`##66p3I#
+rrWs'7#p%%s5ESL#i+SBs6i?D-]@Zt"M5R?7*O]k#gN&Bs2.5m(WQ.J"Fqsi7/+e&!SceorrM<;
+mf3=!!7LiG"&[email protected])jpjdIXT/=<lMlA~>
+!Zh=)r4;sJp\t?M]=[dnrrC^M])^+RrrBt8rrCOG])^O`rrN,;oD\mfUOUkp!5JHD!:K[_!<2l-
+"nM]nb];P3rrf;C6q#_Srrf>i6k8JSrrZO_6rs9d"4Ffn`;BQ8r5SL2!SZc5rrL6To`##66p3I#
+rrWs'7#p%%s5ESL#i+SBs6i?D-]@Zt"M5R?7*O]k#gN&Bs2.5m(WQ.J"Fqsi7/+e&!SceorrM<;
+mf3=!!7LiG"&[email protected])jpjdIXT/=<lMlA~>
+"!.E>/H&uWGk_4?XC2t&hZ!Um6iLKpEPqeU!Nm==rrAJc@0&Z7rr3%sBt![6!U2E,rrMo!m/I&"
+r*]TWq>UHor*]UHci3qFSUgA4!NC.^rrFq?p&>%u_#438a`V$#Fo21>SLO=5!Hk=9rrIX^qu6]"
+qYL3mju2l3!$0bm!oI+@o`#!G9q_Fa!Ki<5rrK`?iVrtr.,4\"!ER55rrIY@bPqUQnGW@fGGa6s
+!Mju/rrf?1@<TfkrrKf@p&>'T2Z3RT*U<ZT~>
+"!.E>/H&uWGk_4?XC2t&hZ!Um6iLKpEPqeU!Nm==rrAJc@0&Z7rr3%sBt![6!U2E,rrMo!m/I&"
+r*]TWq>UHor*]UHci3qFSUgA4!NC.^rrFq?p&>%u_#438a`V$#Fo21>SLO=5!Hk=9rrIX^qu6]"
+qYL3mju2l3!$0bm!oI+@o`#!G9q_Fa!Ki<5rrK`?iVrtr.,4\"!ER55rrIY@bPqUQnGW@fGGa6s
+!Mju/rrf?1@<TfkrrKf@p&>'T2Z3RT*U<ZT~>
+"!.E>/H&uWGk_4?XC2t&hZ!Um6iLKpEPqeU!Nm==rrAJc@0&Z7rr3%sBt![6!U2E,rrMo!m/I&"
+r*]TWq>UHor*]UHci3qFSUgA4!NC.^rrFq?p&>%u_#438a`V$#Fo21>SLO=5!Hk=9rrIX^qu6]"
+qYL3mju2l3!$0bm!oI+@o`#!G9q_Fa!Ki<5rrK`?iVrtr.,4\"!ER55rrIY@bPqUQnGW@fGGa6s
+!Mju/rrf?1@<TfkrrKf@p&>'T2Z3RT*U<ZT~>
+"!.E>Fn#D352P/r!Go">rs$)Ds8SmD(]"(;jFF`>s."Z-J$8PF!JQp-rrML@m/I&+mf*?sJ*6h2
+!mQ8Ap&>&lF34F_1[4T4!F<M>rrU8keG9+GR!:(SrrI8?nc&V>o(r@eju2l3!$0en"6=u%O7iMT
+gjhP\r;Qe[MtR)N]4'_!"297-g\h'P<lXh4!J$`arrGO?rVlnBVV_=RUj2D/#+pAEs3lJ>rVlo3
[email protected]#ju3/;!$1A)J,~>
+"!.E>Fn#D352P/r!Go">rs$)Ds8SmD(]"(;jFF`>s."Z-J$8PF!JQp-rrML@m/I&+mf*?sJ*6h2
+!mQ8Ap&>&lF34F_1[4T4!F<M>rrU8keG9+GR!:(SrrI8?nc&V>o(r@eju2l3!$0en"6=u%O7iMT
+gjhP\r;Qe[MtR)N]4'_!"297-g\h'P<lXh4!J$`arrGO?rVlnBVV_=RUj2D/#+pAEs3lJ>rVlo3
[email protected]#ju3/;!$1A)J,~>
+"!.E>Fn#D352P/r!Go">rs$)Ds8SmD(]"(;jFF`>s."Z-J$8PF!JQp-rrML@m/I&+mf*?sJ*6h2
+!mQ8Ap&>&lF34F_1[4T4!F<M>rrU8keG9+GR!:(SrrI8?nc&V>o(r@eju2l3!$0en"6=u%O7iMT
+gjhP\r;Qe[MtR)N]4'_!"297-g\h'P<lXh4!J$`arrGO?rVlnBVV_=RUj2D/#+pAEs3lJ>rVlo3
[email protected]#ju3/;!$1A)J,~>
+"!.E>Fo21B`NfH(A8hDG!C#B=rrB%tA-dJ\s0]Y*A7U/grro!)A9-Unq#:A2Yl=Y,]4(`5N:.e`
+rsS:7]`8"tO7rV9LTgFO!$1t:!-S9O&?Pp3K(J/9A?l17I9dLsA@MR<!+u4@!5/(+!?L;$A,sX@
+rrHo@r;QeISb<!`WH8";!8%0["6KR_df!\YlMob-rr3C]n,M+2s.s7:o`+s/rF?$+s8Th3A,uW,
+rrFq?p&>%u_#=96O,`u8"-Sf5.JNiGFf1%+A;L6b!gR;1rVm%pA.`0ZP5P=\ju2l3!$1t:!.4]U%
+&a.*Lh(\`A?>h2KC/YK!3uM&!omgerVm#hE4h0krVlrPA?Pn2"QaaBIA$N<!Ki<5rrK`?rVm2rJ
+cEJqAD7(`h>Updc%Y]errTT=df'1K_1:6*g\h'P<lXh4!J$a=rrBe3A-;i2s1/,>"DIi8`ioCA#
+^67J_6fbDA97bM!36$/!-n8<'sV9g<Fg^]s*(>sA7UJqs/40BEb(..rrA/[A,qbkrr@uVA,qqpr
+s4?iA;[1-s8RZLA,r>%rrVe&PlC[`45p2=!K3->rrKf@p&>'T2Z3RT*W?!?Us]5;rF?8Ss8R9Bc
+X^S/s8RT~>
+"!.E>Fo21B`NfH(A8hDG!C#B=rrB%tA-dJ\s0]Y*A7U/grro!)A9-Unq#:A2Yl=Y,]4(`5N:.e`
+rsS:7]`8"tO7rV9LTgFO!$1t:!-S9O&?Pp3K(J/9A?l17I9dLsA@MR<!+u4@!5/(+!?L;$A,sX@
+rrHo@r;QeISb<!`WH8";!8%0["6KR_df!\YlMob-rr3C]n,M+2s.s7:o`+s/rF?$+s8Th3A,uW,
+rrFq?p&>%u_#=96O,`u8"-Sf5.JNiGFf1%+A;L6b!gR;1rVm%pA.`0ZP5P=\ju2l3!$1t:!.4]U%
+&a.*Lh(\`A?>h2KC/YK!3uM&!omgerVm#hE4h0krVlrPA?Pn2"QaaBIA$N<!Ki<5rrK`?rVm2rJ
+cEJqAD7(`h>Updc%Y]errTT=df'1K_1:6*g\h'P<lXh4!J$a=rrBe3A-;i2s1/,>"DIi8`ioCA#
+^67J_6fbDA97bM!36$/!-n8<'sV9g<Fg^]s*(>sA7UJqs/40BEb(..rrA/[A,qbkrr@uVA,qqpr
+s4?iA;[1-s8RZLA,r>%rrVe&PlC[`45p2=!K3->rrKf@p&>'T2Z3RT*W?!?Us]5;rF?8Ss8R9Bc
+X^S/s8RT~>
+"!.E>Fo21B`NfH(A8hDG!C#B=rrB%tA-dJ\s0]Y*A7U/grro!)A9-Unq#:A2Yl=Y,]4(`5N:.e`
+rsS:7]`8"tO7rV9LTgFO!$1t:!-S9O&?Pp3K(J/9A?l17I9dLsA@MR<!+u4@!5/(+!?L;$A,sX@
+rrHo@r;QeISb<!`WH8";!8%0["6KR_df!\YlMob-rr3C]n,M+2s.s7:o`+s/rF?$+s8Th3A,uW,
+rrFq?p&>%u_#=96O,`u8"-Sf5.JNiGFf1%+A;L6b!gR;1rVm%pA.`0ZP5P=\ju2l3!$1t:!.4]U%
+&a.*Lh(\`A?>h2KC/YK!3uM&!omgerVm#hE4h0krVlrPA?Pn2"QaaBIA$N<!Ki<5rrK`?rVm2rJ
+cEJqAD7(`h>Updc%Y]errTT=df'1K_1:6*g\h'P<lXh4!J$a=rrBe3A-;i2s1/,>"DIi8`ioCA#
+^67J_6fbDA97bM!36$/!-n8<'sV9g<Fg^]s*(>sA7UJqs/40BEb(..rrA/[A,qbkrr@uVA,qqpr
+s4?iA;[1-s8RZLA,r>%rrVe&PlC[`45p2=!K3->rrKf@p&>'T2Z3RT*W?!?Us]5;rF?8Ss8R9Bc
+X^S/s8RT~>
+"!.E>+oY"=nGiOLJ4Q':Y\F(9!C#B=rrC[M[h5T^s+Mqh\$r2\s.PF]\#s&_q#:A21B'fKA^g\.
+li/"%rrth%K`D'Nb5VDAU3cP5!$2";!HG1>[hOJ#s2@f;\$pmts21i\\$t2ns'E/5[fL`lq#:@.
+>Q2)4ER=CY!i_%@r;QeISb<!`WH8%<!Tdkj[f]k-he;tt"'>BB;Z?\)39C2.O@Y5;[oi[TF8`QM
+>5uWerO2c3r;QdRl1P&W?,6F="cnXr84[+erreOOo(+=)rsNs&T!u2#L]@D"[ho#C"k:"&Y.)XQ
+rrM7?o`"n3r;QeBVuF.6Lo^P:UT.b3KX(P47fKjtJ@GOX!mgp]rVm"#a8_-[rVlr7!.+YE"nc'C
+s*^R<rrJ7?p&>')@K$34qP-T)\$ol2lsTh%!c/D1rr3%Q!4Dh+"j;#Cs$>E;rrH0?p&>&CSc&Kf
+iVg4f>lVWdrjN$<@K3$Jrr3BIs8SH4NjlL"D?$enrjMp-F85bQgUAo,\&JCIG;#Mr\"8?Sen[gZ
+\!r?VOdQ-W"IG;Os2k8L%$R(YKV,`_S1]'dI\ZnO!K<iRrrVKd:]C@p45p2=!K3->rrKf@p&>'T
+2Z3R\*WQ/Q8;ZX3=oGf:EmOcj88>WCDUnc;~>
+"!.E>+oY"=nGiOLJ4Q':Y\F(9!C#B=rrC[M[h5T^s+Mqh\$r2\s.PF]\#s&_q#:A21B'fKA^g\.
+li/"%rrth%K`D'Nb5VDAU3cP5!$2";!HG1>[hOJ#s2@f;\$pmts21i\\$t2ns'E/5[fL`lq#:@.
+>Q2)4ER=CY!i_%@r;QeISb<!`WH8%<!Tdkj[f]k-he;tt"'>BB;Z?\)39C2.O@Y5;[oi[TF8`QM
+>5uWerO2c3r;QdRl1P&W?,6F="cnXr84[+erreOOo(+=)rsNs&T!u2#L]@D"[ho#C"k:"&Y.)XQ
+rrM7?o`"n3r;QeBVuF.6Lo^P:UT.b3KX(P47fKjtJ@GOX!mgp]rVm"#a8_-[rVlr7!.+YE"nc'C
+s*^R<rrJ7?p&>')@K$34qP-T)\$ol2lsTh%!c/D1rr3%Q!4Dh+"j;#Cs$>E;rrH0?p&>&CSc&Kf
+iVg4f>lVWdrjN$<@K3$Jrr3BIs8SH4NjlL"D?$enrjMp-F85bQgUAo,\&JCIG;#Mr\"8?Sen[gZ
+\!r?VOdQ-W"IG;Os2k8L%$R(YKV,`_S1]'dI\ZnO!K<iRrrVKd:]C@p45p2=!K3->rrKf@p&>'T
+2Z3R\*WQ/Q8;ZX3=oGf:EmOcj88>WCDUnc;~>
+"!.E>+oY"=nGiOLJ4Q':Y\F(9!C#B=rrC[M[h5T^s+Mqh\$r2\s.PF]\#s&_q#:A21B'fKA^g\.
+li/"%rrth%K`D'Nb5VDAU3cP5!$2";!HG1>[hOJ#s2@f;\$pmts21i\\$t2ns'E/5[fL`lq#:@.
+>Q2)4ER=CY!i_%@r;QeISb<!`WH8%<!Tdkj[f]k-he;tt"'>BB;Z?\)39C2.O@Y5;[oi[TF8`QM
+>5uWerO2c3r;QdRl1P&W?,6F="cnXr84[+erreOOo(+=)rsNs&T!u2#L]@D"[ho#C"k:"&Y.)XQ
+rrM7?o`"n3r;QeBVuF.6Lo^P:UT.b3KX(P47fKjtJ@GOX!mgp]rVm"#a8_-[rVlr7!.+YE"nc'C
+s*^R<rrJ7?p&>')@K$34qP-T)\$ol2lsTh%!c/D1rr3%Q!4Dh+"j;#Cs$>E;rrH0?p&>&CSc&Kf
+iVg4f>lVWdrjN$<@K3$Jrr3BIs8SH4NjlL"D?$enrjMp-F85bQgUAo,\&JCIG;#Mr\"8?Sen[gZ
+\!r?VOdQ-W"IG;Os2k8L%$R(YKV,`_S1]'dI\ZnO!K<iRrrVKd:]C@p45p2=!K3->rrKf@p&>'T
+2Z3R\*WQ/Q8;ZX3=oGf:EmOcj88>WCDUnc;~>
+"!.E>@K+aVr;Q`rJHPNDmOnJ<!C#B9rt+!Ns+Ppms8UV?s*^O>s62?6rrm5&e/6]nr;Qa;qYpcM
+e=;Er;p,+>!MXo6rr=);rrG.?rr3F`PlLb'g].;(SH&WV0`:qO+T;<>"6]4S55Zo5_Z/6Crr3"*
+^@hL,Lm7f:!Ip[5rrK*?rVloQ62gfch#5\orVlsEq8uV7rsUmKs8Sm*mJm2,s1)Y<rr^sSZ#'C=
+!$2";!Aj!5rrHE@rVmFi(nB+*^0^i/Nq35A.KBF4r$r%hs)j7ms8U&>rVlj<qu6[Ho(r@eju2l3
+!$2";!CPT?rs\;\s#T/u49(2%s"_RmrrI\?rr3&oeE6c1"\.)Cs,E*<rrViBl2L\d6/2G>IA$N<
+!Ki<5rrK`?r;R&L5j&+H4l>?\rr3&Z/Ed$4$gQ75s8UhXjo>?Hg\h'P<lXh4!J$a8rrX;AWH8(=
+"TI-TTmQe=$m#BJQu;Bms6)??OGs2="8^pTS,=c:AcD]17+hJ<&U0-*s8U)>s8Prqqu=?:s(8b>
+rrKN?qu7)+E;rqZs8VbKGlLIarr3#elMgebr7'^)rrG4?rr3"WP5YC]]jUO5!Tl<<rs0Y*.qmH"
+s8O,<rsE/Hs6;)ns8Q3>s*t~>
+"!.E>@K+aVr;Q`rJHPNDmOnJ<!C#B9rt+!Ns+Ppms8UV?s*^O>s62?6rrm5&e/6]nr;Qa;qYpcM
+e=;Er;p,+>!MXo6rr=);rrG.?rr3F`PlLb'g].;(SH&WV0`:qO+T;<>"6]4S55Zo5_Z/6Crr3"*
+^@hL,Lm7f:!Ip[5rrK*?rVloQ62gfch#5\orVlsEq8uV7rsUmKs8Sm*mJm2,s1)Y<rr^sSZ#'C=
+!$2";!Aj!5rrHE@rVmFi(nB+*^0^i/Nq35A.KBF4r$r%hs)j7ms8U&>rVlj<qu6[Ho(r@eju2l3
+!$2";!CPT?rs\;\s#T/u49(2%s"_RmrrI\?rr3&oeE6c1"\.)Cs,E*<rrViBl2L\d6/2G>IA$N<
+!Ki<5rrK`?r;R&L5j&+H4l>?\rr3&Z/Ed$4$gQ75s8UhXjo>?Hg\h'P<lXh4!J$a8rrX;AWH8(=
+"TI-TTmQe=$m#BJQu;Bms6)??OGs2="8^pTS,=c:AcD]17+hJ<&U0-*s8U)>s8Prqqu=?:s(8b>
+rrKN?qu7)+E;rqZs8VbKGlLIarr3#elMgebr7'^)rrG4?rr3"WP5YC]]jUO5!Tl<<rs0Y*.qmH"
+s8O,<rsE/Hs6;)ns8Q3>s*t~>
+"!.E>@K+aVr;Q`rJHPNDmOnJ<!C#B9rt+!Ns+Ppms8UV?s*^O>s62?6rrm5&e/6]nr;Qa;qYpcM
+e=;Er;p,+>!MXo6rr=);rrG.?rr3F`PlLb'g].;(SH&WV0`:qO+T;<>"6]4S55Zo5_Z/6Crr3"*
+^@hL,Lm7f:!Ip[5rrK*?rVloQ62gfch#5\orVlsEq8uV7rsUmKs8Sm*mJm2,s1)Y<rr^sSZ#'C=
+!$2";!Aj!5rrHE@rVmFi(nB+*^0^i/Nq35A.KBF4r$r%hs)j7ms8U&>rVlj<qu6[Ho(r@eju2l3
+!$2";!CPT?rs\;\s#T/u49(2%s"_RmrrI\?rr3&oeE6c1"\.)Cs,E*<rrViBl2L\d6/2G>IA$N<
+!Ki<5rrK`?r;R&L5j&+H4l>?\rr3&Z/Ed$4$gQ75s8UhXjo>?Hg\h'P<lXh4!J$a8rrX;AWH8(=
+"TI-TTmQe=$m#BJQu;Bms6)??OGs2="8^pTS,=c:AcD]17+hJ<&U0-*s8U)>s8Prqqu=?:s(8b>
+rrKN?qu7)+E;rqZs8VbKGlLIarr3#elMgebr7'^)rrG4?rr3"WP5YC]]jUO5!Tl<<rs0Y*.qmH"
+s8O,<rsE/Hs6;)ns8Q3>s*t~>
+"!.E>Fo)+AJXc]>rq-0h!C#B>rrMn@rZ)+Z5Q?G(rr38S8H4C6nF56up\tD5YlF#Po_e^h*VfX;
+WcJ,<(]GEU&pj9O!$2";!BD(t*<[.Hs8O,=rrr2os8VI?r;Qfh+9!8_PktFNr:'daqu6\'^@qR.
+Wd+@:rrIV?p&>&lF8c+>rO`"K"3gbn9)\bl,PfJ[rr3-]jo>@VGlI^FoDc@2qu6]%%/h1H.fB;I
+1[4T4!F<M>rt(6GqZ$TP56$WZ*??+'>Q=KrnH8IaFf56=!QA.=rr=):rrFV?qYpTY2Y@"L*W5p<
+ofW3o%!VLH3o]*[s,*$?1@+r>!J-a8rr=\N*WHZN[eTk&+9!8^%MHbZrrJ7?p&>')@Jp-+cqXN>
+"82WS3;rjX2<Xf8!S.q`*<HH`q>L<o<lXh4!J$a>rrMt`r>btZs8%lW*<[SWs.Of=rraABs-SK=
+rrhOCs,`3:rrDlmnGr7]rrGO?rVlnBVZ-T(``E->;9](?qtC&%(&f3V)0#WK;Z7[>'QF(PaSu2B
+Uj2q47/e2-Dts,-!B]9>rrJ%@[email protected]#ju3/;!ua,Xm/?qa')`gR'Ysb61B.:TpAFr@~>
+"!.E>Fo)+AJXc]>rq-0h!C#B>rrMn@rZ)+Z5Q?G(rr38S8H4C6nF56up\tD5YlF#Po_e^h*VfX;
+WcJ,<(]GEU&pj9O!$2";!BD(t*<[.Hs8O,=rrr2os8VI?r;Qfh+9!8_PktFNr:'daqu6\'^@qR.
+Wd+@:rrIV?p&>&lF8c+>rO`"K"3gbn9)\bl,PfJ[rr3-]jo>@VGlI^FoDc@2qu6]%%/h1H.fB;I
+1[4T4!F<M>rt(6GqZ$TP56$WZ*??+'>Q=KrnH8IaFf56=!QA.=rr=):rrFV?qYpTY2Y@"L*W5p<
+ofW3o%!VLH3o]*[s,*$?1@+r>!J-a8rr=\N*WHZN[eTk&+9!8^%MHbZrrJ7?p&>')@Jp-+cqXN>
+"82WS3;rjX2<Xf8!S.q`*<HH`q>L<o<lXh4!J$a>rrMt`r>btZs8%lW*<[SWs.Of=rraABs-SK=
+rrhOCs,`3:rrDlmnGr7]rrGO?rVlnBVZ-T(``E->;9](?qtC&%(&f3V)0#WK;Z7[>'QF(PaSu2B
+Uj2q47/e2-Dts,-!B]9>rrJ%@[email protected]#ju3/;!ua,Xm/?qa')`gR'Ysb61B.:TpAFr@~>
+"!.E>Fo)+AJXc]>rq-0h!C#B>rrMn@rZ)+Z5Q?G(rr38S8H4C6nF56up\tD5YlF#Po_e^h*VfX;
+WcJ,<(]GEU&pj9O!$2";!BD(t*<[.Hs8O,=rrr2os8VI?r;Qfh+9!8_PktFNr:'daqu6\'^@qR.
+Wd+@:rrIV?p&>&lF8c+>rO`"K"3gbn9)\bl,PfJ[rr3-]jo>@VGlI^FoDc@2qu6]%%/h1H.fB;I
+1[4T4!F<M>rt(6GqZ$TP56$WZ*??+'>Q=KrnH8IaFf56=!QA.=rr=):rrFV?qYpTY2Y@"L*W5p<
+ofW3o%!VLH3o]*[s,*$?1@+r>!J-a8rr=\N*WHZN[eTk&+9!8^%MHbZrrJ7?p&>')@Jp-+cqXN>
+"82WS3;rjX2<Xf8!S.q`*<HH`q>L<o<lXh4!J$a>rrMt`r>btZs8%lW*<[SWs.Of=rraABs-SK=
+rrhOCs,`3:rrDlmnGr7]rrGO?rVlnBVZ-T(``E->;9](?qtC&%(&f3V)0#WK;Z7[>'QF(PaSu2B
+Uj2q47/e2-Dts,-!B]9>rrJ%@[email protected]#ju3/;!ua,Xm/?qa')`gR'Ysb61B.:TpAFr@~>
+"!.E>Fo)+=JXcK8!C#B>s8S,ZrrrD25Q?G(rr35R8H7pi/1a!Yrs!;Ds8UtYjSf)Y*W?!=j7N?N
+"KHMB;p,+>!MXo6rrGC?rVlmYj8/cU*W#d@m4eS?kjSQ)rVln:Xn_nrf_tgN?G?F=!qFb*rVlrl
+R#h.E!q(Q@p&>&lF8c+>q;2)M""Woj9)\bl,PfJ[rr3-]jo>@VGl7RB\RYU<"5*XYD"mr11[4T4
+!F<M>rrJ1?rr3#U55bE]o-sG6#'Ggrs8U&>rVlj<qu6dKo)J:BrVlo\2Y@"L*W5p<htd9O%$HMJ
+3o]*[s,*$?1;s1l!J-a>rrVrDjneuXNK=&<!qat*qYpSET`"fjOc/o4!P;e<rrLJ@r;QfZ3<&pZ
+i@O0krrVK7o(r@e6/2>;!ER55rrIY@rVlo(C]=>:fH("]kPkJiq'?!6HiO-#)uor*K`:uSkV`C%
+N;ihXqVLrG#=R5EpYc'qVZ-T!``E->;9\t<!G8h<rt/POs8VeRE;rqZs8V_IGlQ^rrr3"hJc>ZN
+r6sX(rrucDs8VSDV>^Dp]jUO5!Tl<<rs0Y+/83N"s8O,9rrMC?qu;0~>
+"!.E>Fo)+=JXcK8!C#B>s8S,ZrrrD25Q?G(rr35R8H7pi/1a!Yrs!;Ds8UtYjSf)Y*W?!=j7N?N
+"KHMB;p,+>!MXo6rrGC?rVlmYj8/cU*W#d@m4eS?kjSQ)rVln:Xn_nrf_tgN?G?F=!qFb*rVlrl
+R#h.E!q(Q@p&>&lF8c+>q;2)M""Woj9)\bl,PfJ[rr3-]jo>@VGl7RB\RYU<"5*XYD"mr11[4T4
+!F<M>rrJ1?rr3#U55bE]o-sG6#'Ggrs8U&>rVlj<qu6dKo)J:BrVlo\2Y@"L*W5p<htd9O%$HMJ
+3o]*[s,*$?1;s1l!J-a>rrVrDjneuXNK=&<!qat*qYpSET`"fjOc/o4!P;e<rrLJ@r;QfZ3<&pZ
+i@O0krrVK7o(r@e6/2>;!ER55rrIY@rVlo(C]=>:fH("]kPkJiq'?!6HiO-#)uor*K`:uSkV`C%
+N;ihXqVLrG#=R5EpYc'qVZ-T!``E->;9\t<!G8h<rt/POs8VeRE;rqZs8V_IGlQ^rrr3"hJc>ZN
+r6sX(rrucDs8VSDV>^Dp]jUO5!Tl<<rs0Y+/83N"s8O,9rrMC?qu;0~>
+"!.E>Fo)+=JXcK8!C#B>s8S,ZrrrD25Q?G(rr35R8H7pi/1a!Yrs!;Ds8UtYjSf)Y*W?!=j7N?N
+"KHMB;p,+>!MXo6rrGC?rVlmYj8/cU*W#d@m4eS?kjSQ)rVln:Xn_nrf_tgN?G?F=!qFb*rVlrl
+R#h.E!q(Q@p&>&lF8c+>q;2)M""Woj9)\bl,PfJ[rr3-]jo>@VGl7RB\RYU<"5*XYD"mr11[4T4
+!F<M>rrJ1?rr3#U55bE]o-sG6#'Ggrs8U&>rVlj<qu6dKo)J:BrVlo\2Y@"L*W5p<htd9O%$HMJ
+3o]*[s,*$?1;s1l!J-a>rrVrDjneuXNK=&<!qat*qYpSET`"fjOc/o4!P;e<rrLJ@r;QfZ3<&pZ
+i@O0krrVK7o(r@e6/2>;!ER55rrIY@rVlo(C]=>:fH("]kPkJiq'?!6HiO-#)uor*K`:uSkV`C%
+N;ihXqVLrG#=R5EpYc'qVZ-T!``E->;9\t<!G8h<rt/POs8VeRE;rqZs8V_IGlQ^rrr3"hJc>ZN
+r6sX(rrucDs8VSDV>^Dp]jUO5!Tl<<rs0Y+/83N"s8O,9rrMC?qu;0~>
+"!.E><;j6._>jOdS,<3sfsWK(^&S,8L:4Ot5<o1%Qi@!feO]_7NW-?d`V9B5Cp<p=%(fsJe$c\"
+[^O]cMOa[S"Ho5R;p,+>!MXo6rrM@?rVln=WrBF,f`(mN*W#dAnm/]4I)#\h[JmT8GfBIX!*]?0
+!HHNdrrT>'N;`bW_HQg9!M+c5rrK*?rVlo[Ac9%>>P6lerO)f1qq5fb[KU*@s8Sm>r;QfBF8`NL
+>Q;`frO2V'!Aj!5rrHE@rVlo%L&SL]W-/%<!V7c7rsa*)S[PttL]@D![M?6mrVm"5Z*os^rVlo\
+2Y@"L*W5p<Gc(JK%#:qa3o]FGs,*$?1.V>P!J%]ZrrUOIC&7i1NK=&<!l+e^qYpSET`"fjOc/o4
+!P;e<rrLJ@r;Qfa@K*\:C_,_.rrRiR[JKn(6/2>;!ER55rrIY@rVlo>GQ,#R<ZM.VHN(>]ZXWsI
+I^Z[i'EA*"K`:uSkV`CEL&SL]WH@k6#C<5TTn35fVZ-T!``E->;9\t<!Ki`J[M6pbs,3AT[\#9n
+s+R&Q[[]!qs*pdB[K2>`rr3&c!)NRn"CAOFOdu@L!PMn6rrM7?r;R$Cs8Tc(M<Y%DrO)jhs8V@>
+qu;0~>
+"!.E><;j6._>jOdS,<3sfsWK(^&S,8L:4Ot5<o1%Qi@!feO]_7NW-?d`V9B5Cp<p=%(fsJe$c\"
+[^O]cMOa[S"Ho5R;p,+>!MXo6rrM@?rVln=WrBF,f`(mN*W#dAnm/]4I)#\h[JmT8GfBIX!*]?0
+!HHNdrrT>'N;`bW_HQg9!M+c5rrK*?rVlo[Ac9%>>P6lerO)f1qq5fb[KU*@s8Sm>r;QfBF8`NL
+>Q;`frO2V'!Aj!5rrHE@rVlo%L&SL]W-/%<!V7c7rsa*)S[PttL]@D![M?6mrVm"5Z*os^rVlo\
+2Y@"L*W5p<Gc(JK%#:qa3o]FGs,*$?1.V>P!J%]ZrrUOIC&7i1NK=&<!l+e^qYpSET`"fjOc/o4
+!P;e<rrLJ@r;Qfa@K*\:C_,_.rrRiR[JKn(6/2>;!ER55rrIY@rVlo>GQ,#R<ZM.VHN(>]ZXWsI
+I^Z[i'EA*"K`:uSkV`CEL&SL]WH@k6#C<5TTn35fVZ-T!``E->;9\t<!Ki`J[M6pbs,3AT[\#9n
+s+R&Q[[]!qs*pdB[K2>`rr3&c!)NRn"CAOFOdu@L!PMn6rrM7?r;R$Cs8Tc(M<Y%DrO)jhs8V@>
+qu;0~>
+"!.E><;j6._>jOdS,<3sfsWK(^&S,8L:4Ot5<o1%Qi@!feO]_7NW-?d`V9B5Cp<p=%(fsJe$c\"
+[^O]cMOa[S"Ho5R;p,+>!MXo6rrM@?rVln=WrBF,f`(mN*W#dAnm/]4I)#\h[JmT8GfBIX!*]?0
+!HHNdrrT>'N;`bW_HQg9!M+c5rrK*?rVlo[Ac9%>>P6lerO)f1qq5fb[KU*@s8Sm>r;QfBF8`NL
+>Q;`frO2V'!Aj!5rrHE@rVlo%L&SL]W-/%<!V7c7rsa*)S[PttL]@D![M?6mrVm"5Z*os^rVlo\
+2Y@"L*W5p<Gc(JK%#:qa3o]FGs,*$?1.V>P!J%]ZrrUOIC&7i1NK=&<!l+e^qYpSET`"fjOc/o4
+!P;e<rrLJ@r;Qfa@K*\:C_,_.rrRiR[JKn(6/2>;!ER55rrIY@rVlo>GQ,#R<ZM.VHN(>]ZXWsI
+I^Z[i'EA*"K`:uSkV`CEL&SL]WH@k6#C<5TTn35fVZ-T!``E->;9\t<!Ki`J[M6pbs,3AT[\#9n
+s+R&Q[[]!qs*pdB[K2>`rr3&c!)NRn"CAOFOdu@L!PMn6rrM7?r;R$Cs8Tc(M<Y%DrO)jhs8V@>
+qu;0~>
+!Zh<`r+6(Zs8TIDqu6Z!ral.Qrr38'AnGcCN;p?%rr3,`OoNUMral1M\bQ1*VpPGC"4j.FUASU*
+V>pRRral;$s8SGCrr3#>YP.ttno2/<rr@6AAcSt4rr@9=rr_7mB".d>!,):C!5/(+!;?A'!65!;
+!mYDhrVloQSGW<fj+Xf2rs,MlR@3@?s4I9^"6KR_e,<k\lMpn0ral>jGQ7]bY5A5!_u40Lq>^K/
+rFQ<6s8UK7R31\drrZPJRA9c]!36$1!."[email protected];Qh8As<5o"1">P
+2Y@"O(n$f.rr2tGral;2s8RrDrr3,2^AftMral/8rVlrZAu5A(!P?#CrrUkcYPS8)Z,ZhDp6h=M
+MtR)U]/uFKj8]/>Pl(I\hYq*gc8FearrTT?e,'(MNTpKChjKlh`qB?:J6nY3qu?]2ral<"GQ7]S
+ral.Frr383AnL-Fs8TpCrr3,kL&_1Rral.Uo`#,DAqU-`Xi^SB"l5XIs-DU?rrA2\AcS"nrt(-$
+AqnR0s8RjdArFa5s8R]MAcSS(rrVe(Q2L[^AcS:urr]!`EFAJ>#Nd.sRF;-8GQ%ODV:,D=rFQ2O
+s8VYCqu;0~>
+!Zh<`r+6(Zs8TIDqu6Z!ral.Qrr38'AnGcCN;p?%rr3,`OoNUMral1M\bQ1*VpPGC"4j.FUASU*
+V>pRRral;$s8SGCrr3#>YP.ttno2/<rr@6AAcSt4rr@9=rr_7mB".d>!,):C!5/(+!;?A'!65!;
+!mYDhrVloQSGW<fj+Xf2rs,MlR@3@?s4I9^"6KR_e,<k\lMpn0ral>jGQ7]bY5A5!_u40Lq>^K/
+rFQ<6s8UK7R31\drrZPJRA9c]!36$1!."[email protected];Qh8As<5o"1">P
+2Y@"O(n$f.rr2tGral;2s8RrDrr3,2^AftMral/8rVlrZAu5A(!P?#CrrUkcYPS8)Z,ZhDp6h=M
+MtR)U]/uFKj8]/>Pl(I\hYq*gc8FearrTT?e,'(MNTpKChjKlh`qB?:J6nY3qu?]2ral<"GQ7]S
+ral.Frr383AnL-Fs8TpCrr3,kL&_1Rral.Uo`#,DAqU-`Xi^SB"l5XIs-DU?rrA2\AcS"nrt(-$
+AqnR0s8RjdArFa5s8R]MAcSS(rrVe(Q2L[^AcS:urr]!`EFAJ>#Nd.sRF;-8GQ%ODV:,D=rFQ2O
+s8VYCqu;0~>
+!Zh<`r+6(Zs8TIDqu6Z!ral.Qrr38'AnGcCN;p?%rr3,`OoNUMral1M\bQ1*VpPGC"4j.FUASU*
+V>pRRral;$s8SGCrr3#>YP.ttno2/<rr@6AAcSt4rr@9=rr_7mB".d>!,):C!5/(+!;?A'!65!;
+!mYDhrVloQSGW<fj+Xf2rs,MlR@3@?s4I9^"6KR_e,<k\lMpn0ral>jGQ7]bY5A5!_u40Lq>^K/
+rFQ<6s8UK7R31\drrZPJRA9c]!36$1!."[email protected];Qh8As<5o"1">P
+2Y@"O(n$f.rr2tGral;2s8RrDrr3,2^AftMral/8rVlrZAu5A(!P?#CrrUkcYPS8)Z,ZhDp6h=M
+MtR)U]/uFKj8]/>Pl(I\hYq*gc8FearrTT?e,'(MNTpKChjKlh`qB?:J6nY3qu?]2ral<"GQ7]S
+ral.Frr383AnL-Fs8TpCrr3,kL&_1Rral.Uo`#,DAqU-`Xi^SB"l5XIs-DU?rrA2\AcS"nrt(-$
+AqnR0s8RjdArFa5s8R]MAcSS(rrVe(Q2L[^AcS:urr]!`EFAJ>#Nd.sRF;-8GQ%ODV:,D=rFQ2O
+s8VYCqu;0~>
+!$0_l!I^U>rrL>?c2Rh,WkJE5h6Z_Q!7h($!9VW-!65"j!;,sa"0dE1O3[b-Tn@ugo`##OK7gQ"
+rrFn@g]%9Grdt3jp&>$Krdt4,o)A_JkOAKOfD^C&j7WEP_>]&eqXFLccb08W!$.[2"-%qcZ1\+s
+oDX@BaQNSR~>
+!$0_l!I^U>rrL>?c2Rh,WkJE5h6Z_Q!7h($!9VW-!65"j!;,sa"0dE1O3[b-Tn@ugo`##OK7gQ"
+rrFn@g]%9Grdt3jp&>$Krdt4,o)A_JkOAKOfD^C&j7WEP_>]&eqXFLccb08W!$.[2"-%qcZ1\+s
+oDX@BaQNSR~>
+!$0_l!I^U>rrL>?c2Rh,WkJE5h6Z_Q!7h($!9VW-!65"j!;,sa"0dE1O3[b-Tn@ugo`##OK7gQ"
+rrFn@g]%9Grdt3jp&>$Krdt4,o)A_JkOAKOfD^C&j7WEP_>]&eqXFLccb08W!$.[2"-%qcZ1\+s
+oDX@BaQNSR~>
+!$0_l!R>og?NFuWJcC<$dJj5&laHfo2<W*]!Si8*?N?dNs6ou<~>
+!$0_l!R>og?NFuWJcC<$dJj5&laHfo2<W*]!Si8*?N?dNs6ou<~>
+!$0_l!R>og?NFuWJcC<$dJj5&laHfo2<W*]!Si8*?N?dNs6ou<~>
+!$0\k!7h(]!6TlmJcF*s!Qk2HrrL:<aSu7trk&7.JcG3=J,~>
+!$0\k!7h(]!6TlmJcF*s!Qk2HrrL:<aSu7trk&7.JcG3=J,~>
+!$0\k!7h(]!6TlmJcF*s!Qk2HrrL:<aSu7trk&7.JcG3=J,~>
+!$..#"#_JQH%H!Hs+13$s7lVE~>
+!$..#"#_JQH%H!Hs+13$s7lVE~>
+!$..#"#_JQH%H!Hs+13$s7lVE~>
+!$1P.",Is-hrXk>C)m`\rrQ[N'D)5,m2m?Wo_8@e73")Lrr`#hZsnUdJcC<$JcGNFJ,~>
+!$1P.",Is-hrXk>C)m`\rrQ[N'D)5,m2m?Wo_8@e73")Lrr`#hZsnUdJcC<$JcGNFJ,~>
+!$1P.",Is-hrXk>C)m`\rrQ[N'D)5,m2m?Wo_8@e73")Lrr`#hZsnUdJcC<$JcGNFJ,~>
+!$1J,!C#B#rr=)9rr=)2rraJBs/L,5rrMX?lMgms@Y+Q1s+13$s7lVE~>
+!$1J,!C#B#rr=)9rr=)2rraJBs/L,5rrMX?lMgms@Y+Q1s+13$s7lVE~>
+!$1J,!C#B#rr=)9rr=)2rraJBs/L,5rrMX?lMgms@Y+Q1s+13$s7lVE~>
+#9Ej*e^C_-[/U(*g&A5V[f$.+52Q#5"kqkTZ*D%BrrC@DYlMW<rr=)9rr=)9rrKOBr;R!Er;X^+
+s8Tq7YlN)JrrMX?r;Qc0rilIPrr2u0rilIRr;Qf0@Y+Q1s+13$s7lVE~>
+#9Ej*e^C_-[/U(*g&A5V[f$.+52Q#5"kqkTZ*D%BrrC@DYlMW<rr=)9rr=)9rrKOBr;R!Er;X^+
+s8Tq7YlN)JrrMX?r;Qc0rilIPrr2u0rilIRr;Qf0@Y+Q1s+13$s7lVE~>
+#9Ej*e^C_-[/U(*g&A5V[f$.+52Q#5"kqkTZ*D%BrrC@DYlMW<rr=)9rr=)9rrKOBr;R!Er;X^+
+s8Tq7YlN)JrrMX?r;Qc0rilIPrr2u0rilIRr;Qf0@Y+Q1s+13$s7lVE~>
+$Q]8F7!rccCo%*_JGs<bDQ*O6!C#B6rs=B]GACu7ZN&$mrbDOU[f-4+*W#d9*W#d:$nqPY!?h=<
+rr@rUCB8b%rr3#h/,fJKY]9YX"F\VrXDe)R!IiJqrrK`@JcC<$JcC<$q#>j~>
+$Q]8F7!rccCo%*_JGs<bDQ*O6!C#B6rs=B]GACu7ZN&$mrbDOU[f-4+*W#d9*W#d:$nqPY!?h=<
+rr@rUCB8b%rr3#h/,fJKY]9YX"F\VrXDe)R!IiJqrrK`@JcC<$JcC<$q#>j~>
+$Q]8F7!rccCo%*_JGs<bDQ*O6!C#B6rs=B]GACu7ZN&$mrbDOU[f-4+*W#d9*W#d:$nqPY!?h=<
+rr@rUCB8b%rr3#h/,fJKY]9YX"F\VrXDe)R!IiJqrrK`@JcC<$JcC<$q#>j~>
+$Q]8F50X',pEop4IfB?JmOnJ<!C#B>rrBt7G70i=Ki$S)s4'[?I@pN=!R+C=rr=)9rr=)9rrJ+N
+rr3,"G7Sk^q>UJiHN*pFnLOS<!CGQ?rrgQfs#K-=rrIq?rVlo1@Y+Q1s+13$s7lVE~>
+$Q]8F50X',pEop4IfB?JmOnJ<!C#B>rrBt7G70i=Ki$S)s4'[?I@pN=!R+C=rr=)9rr=)9rrJ+N
+rr3,"G7Sk^q>UJiHN*pFnLOS<!CGQ?rrgQfs#K-=rrIq?rVlo1@Y+Q1s+13$s7lVE~>
+$Q]8F50X',pEop4IfB?JmOnJ<!C#B>rrBt7G70i=Ki$S)s4'[?I@pN=!R+C=rr=)9rr=)9rrJ+N
+rr3,"G7Sk^q>UJiHN*pFnLOS<!CGQ?rrgQfs#K-=rrIq?rVlo1@Y+Q1s+13$s7lVE~>
+"!.E>FoMCDpEop4/cJoS<%e.L!C#B>rrC[KV$"@0KpVf="P$'CI@pN=!R+C=rr=)9rr=)4rrg<;
+'r/;;rr@KH=ogX0rr3#h/,fJK]Oh(G"JPkq3DocZ!AKc:rrK`@JcC<$JcC<$q#>j~>
+"!.E>FoMCDpEop4/cJoS<%e.L!C#B>rrC[KV$"@0KpVf="P$'CI@pN=!R+C=rr=)9rr=)4rrg<;
+'r/;;rr@KH=ogX0rr3#h/,fJK]Oh(G"JPkq3DocZ!AKc:rrK`@JcC<$JcC<$q#>j~>
+"!.E>FoMCDpEop4/cJoS<%e.L!C#B>rrC[KV$"@0KpVf="P$'CI@pN=!R+C=rr=)9rr=)4rrg<;
+'r/;;rr@KH=ogX0rr3#h/,fJK]Oh(G"JPkq3DocZ!AKc:rrK`@JcC<$JcC<$q#>j~>
+"!.E>FoMCDpEop4?i@e@c2IYC52Q#5!JQm>rrgkCs*^O=rrL>?rVlj<qYpO9oD\h6r;HWrI&?nZ
+!IK.lrrMX?r;Qc>rkS_oVuJcYrP8KqrVlo1@Y+Q1s+13$s7lVE~>
+"!.E>FoMCDpEop4?i@e@c2IYC52Q#5!JQm>rrgkCs*^O=rrL>?rVlj<qYpO9oD\h6r;HWrI&?nZ
+!IK.lrrMX?r;Qc>rkS_oVuJcYrP8KqrVlo1@Y+Q1s+13$s7lVE~>
+"!.E>FoMCDpEop4?i@e@c2IYC52Q#5!JQm>rrgkCs*^O=rrL>?rVlj<qYpO9oD\h6r;HWrI&?nZ
+!IK.lrrMX?r;Qc>rkS_oVuJcYrP8KqrVlo1@Y+Q1s+13$s7lVE~>
+"!.E>FoMCDpEop4Ie`pD52Q#5$&'&,s8UV?s*^O=rrL>?rVlj<qYpO9qYpRH9)S\i+T23<##i\E
+s2Pk#rr3#h/,fJK>2T>Z"HeWB3TKo7!P;fls+13$s+14Fs*t~>
+"!.E>FoMCDpEop4Ie`pD52Q#5$&'&,s8UV?s*^O=rrL>?rVlj<qYpO9qYpRH9)S\i+T23<##i\E
+s2Pk#rr3#h/,fJK>2T>Z"HeWB3TKo7!P;fls+13$s+14Fs*t~>
+"!.E>FoMCDpEop4Ie`pD52Q#5$&'&,s8UV?s*^O=rrL>?rVlj<qYpO9qYpRH9)S\i+T23<##i\E
+s2Pk#rr3#h/,fJK>2T>Z"HeWB3TKo7!P;fls+13$s+14Fs*t~>
+"!.EGKDtlRpH/ESNrC%!/H5YPL`IBR1\^nUKp>sb*CB](rG_`V#YFsos(WPm*Duh9"CiGj*Ei=?
+!@1&1rrG%UrVmK-9-#$QWJCNR73*9eGQ7^@4oQH)IJs3D2?"TrLAq2TkiSgQJcC<$JcC<$q#>j~>
+"!.EGKDtlRpH/ESNrC%!/H5YPL`IBR1\^nUKp>sb*CB](rG_`V#YFsos(WPm*Duh9"CiGj*Ei=?
+!@1&1rrG%UrVmK-9-#$QWJCNR73*9eGQ7^@4oQH)IJs3D2?"TrLAq2TkiSgQJcC<$JcC<$q#>j~>
+"!.EGKDtlRpH/ESNrC%!/H5YPL`IBR1\^nUKp>sb*CB](rG_`V#YFsos(WPm*Duh9"CiGj*Ei=?
+!@1&1rrG%UrVmK-9-#$QWJCNR73*9eGQ7^@4oQH)IJs3D2?"TrLAq2TkiSgQJcC<$JcC<$q#>j~>
+!$1"t!JQlGrrY\J2M6S\JcC<$JcGNFJ,~>
+!$1"t!JQlGrrY\J2M6S\JcC<$JcGNFJ,~>
+!$1"t!JQlGrrY\J2M6S\JcC<$JcGNFJ,~>
+!$1"t!JQkks+13$s+13Hs*t~>
+!$1"t!JQkks+13$s+13Hs*t~>
+!$1"t!JQkks+13$s+13Hs*t~>
+!$1"t!P?=%s+13$s+13Hs*t~>
+!$1"t!P?=%s+13$s+13Hs*t~>
+!$1"t!P?=%s+13$s+13Hs*t~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$.@)!V"X)rrM*>JcC<$JcFU,J,~>
+!$.@)!V"X)rrM*>JcC<$JcFU,J,~>
+!$.@)!V"X)rrM*>JcC<$JcFU,J,~>
+!Zh<ur1X1qq#:BJe+3M@aJ,F)rVlu=U8%SZrrL7$rr3)_`W*sUU&`:0rrBk4U&`O-rrVhUQM1=Z
+Z2;uL!p66)pAY0d\+'Cuce\R"!9*mN!;6?k!jdU@JcC<$JcFX-J,~>
+!Zh<ur1X1qq#:BJe+3M@aJ,F)rVlu=U8%SZrrL7$rr3)_`W*sUU&`:0rrBk4U&`O-rrVhUQM1=Z
+Z2;uL!p66)pAY0d\+'Cuce\R"!9*mN!;6?k!jdU@JcC<$JcFX-J,~>
+!Zh<ur1X1qq#:BJe+3M@aJ,F)rVlu=U8%SZrrL7$rr3)_`W*sUU&`:0rrBk4U&`O-rrVhUQM1=Z
+Z2;uL!p66)pAY0d\+'Cuce\R"!9*mN!;6?k!jdU@JcC<$JcFX-J,~>
+"!.E>3;n.'NqiVTQA4u1"0d(en,<7gXEkNRpAY/0Yl=Y*]4(_L=T*OGZ2">!V>Y]@9mZ7-!T6*5
+rrFP?lMgnMW9aHbgNpR2rrLi`rVloY3V!+Sc@:E's8V55rH\HtrVln5Z@W%,s+14-s*t~>
+"!.E>3;n.'NqiVTQA4u1"0d(en,<7gXEkNRpAY/0Yl=Y*]4(_L=T*OGZ2">!V>Y]@9mZ7-!T6*5
+rrFP?lMgnMW9aHbgNpR2rrLi`rVloY3V!+Sc@:E's8V55rH\HtrVln5Z@W%,s+14-s*t~>
+"!.E>3;n.'NqiVTQA4u1"0d(en,<7gXEkNRpAY/0Yl=Y*]4(_L=T*OGZ2">!V>Y]@9mZ7-!T6*5
+rrFP?lMgnMW9aHbgNpR2rrLi`rVloY3V!+Sc@:E's8V55rH\HtrVln5Z@W%,s+14-s*t~>
+"!.E>Fn>V6QA4o/!A3d;rrFG?pAY/0Yl=Y*]4(_L\aTP"fm1^.rrW1;YP%nr..lg)!RjX#rrKB?
+rVloY3V*1UdsK?QFR&nK;Z6UqU](2o_KOsjs+13$s5<p-~>
+"!.E>Fn>V6QA4o/!A3d;rrFG?pAY/0Yl=Y*]4(_L\aTP"fm1^.rrW1;YP%nr..lg)!RjX#rrKB?
+rVloY3V*1UdsK?QFR&nK;Z6UqU](2o_KOsjs+13$s5<p-~>
+"!.E>Fn>V6QA4o/!A3d;rrFG?pAY/0Yl=Y*]4(_L\aTP"fm1^.rrW1;YP%nr..lg)!RjX#rrKB?
+rVloY3V*1UdsK?QFR&nK;Z6UqU](2o_KOsjs+13$s5<p-~>
+"!.E>Fo)+<VuB<p=9&;dL5\bu?2jj(S,Q%\A,Q?-/arT:!@@L6rsdIoh#HGJd/RUdBM2!LoD\j+
+?1.^nrZD%;!*T:o"Ju.u.-LS&!3?,!!)<Gc&@)98@/nYJ9=Os$<^%9l9>1-#!WHO+rs")2s5QaF
+V>gK%nk1e^92!h8j#P!U9*!]@rs@Uudf9?f>5QH?p\t7gd"24Js+14.s*t~>
+"!.E>Fo)+<VuB<p=9&;dL5\bu?2jj(S,Q%\A,Q?-/arT:!@@L6rsdIoh#HGJd/RUdBM2!LoD\j+
+?1.^nrZD%;!*T:o"Ju.u.-LS&!3?,!!)<Gc&@)98@/nYJ9=Os$<^%9l9>1-#!WHO+rs")2s5QaF
+V>gK%nk1e^92!h8j#P!U9*!]@rs@Uudf9?f>5QH?p\t7gd"24Js+14.s*t~>
+"!.E>Fo)+<VuB<p=9&;dL5\bu?2jj(S,Q%\A,Q?-/arT:!@@L6rsdIoh#HGJd/RUdBM2!LoD\j+
+?1.^nrZD%;!*T:o"Ju.u.-LS&!3?,!!)<Gc&@)98@/nYJ9=Os$<^%9l9>1-#!WHO+rs")2s5QaF
+V>gK%nk1e^92!h8j#P!U9*!]@rs@Uudf9?f>5QH?p\t7gd"24Js+14.s*t~>
+"!.E>(&ffgmf3<kIf@_'``2u((m`Rs^g$i5M>km']41a=!A3d;rrFG?p&>IscMuQcs8QRQ\'`Tr
+L%YHIj&b4-rrN*@qu6ZGrm:k!Y5]n0rm:jp[JreDrm;:"]`6A3=4,E7_uJ2f4OMREq#:Bn+8u3D
+:!`k7d=?cHrsJ\OO]TrXo`*qYAcC'X:[RuX!VbILrr^mPb#8!6!DUpls+13$s5<p-~>
+"!.E>(&ffgmf3<kIf@_'``2u((m`Rs^g$i5M>km']41a=!A3d;rrFG?p&>IscMuQcs8QRQ\'`Tr
+L%YHIj&b4-rrN*@qu6ZGrm:k!Y5]n0rm:jp[JreDrm;:"]`6A3=4,E7_uJ2f4OMREq#:Bn+8u3D
+:!`k7d=?cHrsJ\OO]TrXo`*qYAcC'X:[RuX!VbILrr^mPb#8!6!DUpls+13$s5<p-~>
+"!.E>(&ffgmf3<kIf@_'``2u((m`Rs^g$i5M>km']41a=!A3d;rrFG?p&>IscMuQcs8QRQ\'`Tr
+L%YHIj&b4-rrN*@qu6ZGrm:k!Y5]n0rm:jp[JreDrm;:"]`6A3=4,E7_uJ2f4OMREq#:Bn+8u3D
+:!`k7d=?cHrsJ\OO]TrXo`*qYAcC'X:[RuX!VbILrr^mPb#8!6!DUpls+13$s5<p-~>
+"!.E>D>ro*rr<"nIfAsJoC;jHIJNpCju<=#M#R#Idm*g2!A3d;rrFG?p&>J%a8XI[s8V`Yr;Zf'
+C%_K,e4B!,!WF2<rrD`koE9K1s!Zt-rrdSCruh:>rs`nKs7mi/s8QrHs8UP>p&>9q+9/Bms%Ui=
+rrJ[@r;Qf&C]487j#-H-rrFt?nc&g9;ZHc1*>SMP!DUpls+13$s5<p-~>
+"!.E>D>ro*rr<"nIfAsJoC;jHIJNpCju<=#M#R#Idm*g2!A3d;rrFG?p&>J%a8XI[s8V`Yr;Zf'
+C%_K,e4B!,!WF2<rrD`koE9K1s!Zt-rrdSCruh:>rs`nKs7mi/s8QrHs8UP>p&>9q+9/Bms%Ui=
+rrJ[@r;Qf&C]487j#-H-rrFt?nc&g9;ZHc1*>SMP!DUpls+13$s5<p-~>
+"!.E>D>ro*rr<"nIfAsJoC;jHIJNpCju<=#M#R#Idm*g2!A3d;rrFG?p&>J%a8XI[s8V`Yr;Zf'
+C%_K,e4B!,!WF2<rrD`koE9K1s!Zt-rrdSCruh:>rs`nKs7mi/s8QrHs8UP>p&>9q+9/Bms%Ui=
+rrJ[@r;Qf&C]487j#-H-rrFt?nc&g9;ZHc1*>SMP!DUpls+13$s5<p-~>
+"!.E>Fo)+<Q2W071uA7uLAq2Uju<=#(B#W]?2ad(/arT:!@@L4rrOq7-MdZBZYB.5!W"&-rrN*@
+r;QfS2?#!,'V,1Oo`"jnGbtE_rVlg"Dls'8,PqE@dn064#Q5bEV0Dr6ci3qFSUgY<!O6G=rrM.?
+rVlmTkjeZRb#83<!$2";!DUpls+13$s5<p-~>
+"!.E>Fo)+<Q2W071uA7uLAq2Uju<=#(B#W]?2ad(/arT:!@@L4rrOq7-MdZBZYB.5!W"&-rrN*@
+r;QfS2?#!,'V,1Oo`"jnGbtE_rVlg"Dls'8,PqE@dn064#Q5bEV0Dr6ci3qFSUgY<!O6G=rrM.?
+rVlmTkjeZRb#83<!$2";!DUpls+13$s5<p-~>
+"!.E>Fo)+<Q2W071uA7uLAq2Uju<=#(B#W]?2ad(/arT:!@@L4rrOq7-MdZBZYB.5!W"&-rrN*@
+r;QfS2?#!,'V,1Oo`"jnGbtE_rVlg"Dls'8,PqE@dn064#Q5bEV0Dr6ci3qFSUgY<!O6G=rrM.?
+rVlmTkjeZRb#83<!$2";!DUpls+13$s5<p-~>
+"!.E>Fo21>jkTk8"R[oBQA5D="QhZCNfNo7!A3d;rrFG?o`"tIi[4[)!S-Q9rrKH?rVlo1ao)/>
+^0^1+!rc-@rVm0Ym/R+I?(CpC\,QC1GbtE_rVlg"Dls'8,PqEDdn0T>e*Zu2#Q5b5OaQ._ci3qF
+SUgY<#I/(Es2t)r3W8sY2!FK0!Qn==rr=)<rrUech1>TWs+14.s*t~>
+"!.E>Fo21>jkTk8"R[oBQA5D="QhZCNfNo7!A3d;rrFG?o`"tIi[4[)!S-Q9rrKH?rVlo1ao)/>
+^0^1+!rc-@rVm0Ym/R+I?(CpC\,QC1GbtE_rVlg"Dls'8,PqEDdn0T>e*Zu2#Q5b5OaQ._ci3qF
+SUgY<#I/(Es2t)r3W8sY2!FK0!Qn==rr=)<rrUech1>TWs+14.s*t~>
+"!.E>Fo21>jkTk8"R[oBQA5D="QhZCNfNo7!A3d;rrFG?o`"tIi[4[)!S-Q9rrKH?rVlo1ao)/>
+^0^1+!rc-@rVm0Ym/R+I?(CpC\,QC1GbtE_rVlg"Dls'8,PqEDdn0T>e*Zu2#Q5b5OaQ._ci3qF
+SUgY<#I/(Es2t)r3W8sY2!FK0!Qn==rr=)<rrUech1>TWs+14.s*t~>
+"!.E>8H#(]XT-4grga1[IfG^grr3,`2ugF@rga%hrr3,?SK*iqrr3,<SJRZup&>)DQ?rQ1!M>AN
+SH4YDrVlm#2uN[U*U<Y*i&pu<$%SG7SVAkhs!V@USHOD^s)sq3SHO>bs7mo9rrqJ)SXk#Wq>Us(
+Boe[jKQQ2Qn=<r_S`TkN#L@afST*rU3W8sY2!FK0!T%ttSH*F'rrHl?JcC<$JcFX-J,~>
+"!.E>8H#(]XT-4grga1[IfG^grr3,`2ugF@rga%hrr3,?SK*iqrr3,<SJRZup&>)DQ?rQ1!M>AN
+SH4YDrVlm#2uN[U*U<Y*i&pu<$%SG7SVAkhs!V@USHOD^s)sq3SHO>bs7mo9rrqJ)SXk#Wq>Us(
+Boe[jKQQ2Qn=<r_S`TkN#L@afST*rU3W8sY2!FK0!T%ttSH*F'rrHl?JcC<$JcFX-J,~>
+"!.E>8H#(]XT-4grga1[IfG^grr3,`2ugF@rga%hrr3,?SK*iqrr3,<SJRZup&>)DQ?rQ1!M>AN
+SH4YDrVlm#2uN[U*U<Y*i&pu<$%SG7SVAkhs!V@USHOD^s)sq3SHO>bs7mo9rrqJ)SXk#Wq>Us(
+Boe[jKQQ2Qn=<r_S`TkN#L@afST*rU3W8sY2!FK0!T%ttSH*F'rrHl?JcC<$JcFX-J,~>
+!Zh<ir-ng3s8Tn6IftQ,s2r4Xrrhn]s8TS-IfPT0rrBD)IfP`4rrB8%IfPl.rrA)WrrAemIfQ>C
+rrIY>r;QbWlMgqTJ$&\L#`4%\YeSH_POSR$!5ng9!.b&u"NUQBr/pgT"586Sd.dPGn"'LY[+PEY
+lhu;5h#76Waa\g!s7)TWrrJP[nG`L>rI4h<rr3&;J(]DQJcC<$huA3~>
+!Zh<ir-ng3s8Tn6IftQ,s2r4Xrrhn]s8TS-IfPT0rrBD)IfP`4rrB8%IfPl.rrA)WrrAemIfQ>C
+rrIY>r;QbWlMgqTJ$&\L#`4%\YeSH_POSR$!5ng9!.b&u"NUQBr/pgT"586Sd.dPGn"'LY[+PEY
+lhu;5h#76Waa\g!s7)TWrrJP[nG`L>rI4h<rr3&;J(]DQJcC<$huA3~>
+!Zh<ir-ng3s8Tn6IftQ,s2r4Xrrhn]s8TS-IfPT0rrBD)IfP`4rrB8%IfPl.rrA)WrrAemIfQ>C
+rrIY>r;QbWlMgqTJ$&\L#`4%\YeSH_POSR$!5ng9!.b&u"NUQBr/pgT"586Sd.dPGn"'LY[+PEY
+lhu;5h#76Waa\g!s7)TWrrJP[nG`L>rI4h<rr3&;J(]DQJcC<$huA3~>
+!$.@)!U.7_rrLKtJcC<$JcFU,J,~>
+!$.@)!U.7_rrLKtJcC<$JcFU,J,~>
+!$.@)!U.7_rrLKtJcC<$JcFU,J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$2%<!VH<grrMlioD\pdn*g5QrrVf\rndYUo_[nQ"9.cXpAP!ln+6&K!VZ?grrW(rKA$8/oDJXg
+qX=1=rrMiio`#!en,'Hk!THTHrrG4Kqu6[UnU^^ks2P(h~>
+!$2%<!VH<grrMlioD\pdn*g5QrrVf\rndYUo_[nQ"9.cXpAP!ln+6&K!VZ?grrW(rKA$8/oDJXg
+qX=1=rrMiio`#!en,'Hk!THTHrrG4Kqu6[UnU^^ks2P(h~>
+!$2%<!VH<grrMlioD\pdn*g5QrrVf\rndYUo_[nQ"9.cXpAP!ln+6&K!VZ?grrW(rKA$8/oDJXg
+qX=1=rrMiio`#!en,'Hk!THTHrrG4Kqu6[UnU^^ks2P(h~>
+!$2(=!p)_mrVlqdOn//E",\Z;k5>5\CG#,LrrLpOmJd2k_<Lt+`@WZ`lMpn`/RS#I!I1I?rrL/?
+f`).EfDklc/Y1u&!pa0mo`"uX/]ZZH!TE_;rrLpOo)A\1qYpO9qu6]o,(]cFs2P(h~>
+!$2(=!p)_mrVlqdOn//E",\Z;k5>5\CG#,LrrLpOmJd2k_<Lt+`@WZ`lMpn`/RS#I!I1I?rrL/?
+f`).EfDklc/Y1u&!pa0mo`"uX/]ZZH!TE_;rrLpOo)A\1qYpO9qu6]o,(]cFs2P(h~>
+!$2(=!p)_mrVlqdOn//E",\Z;k5>5\CG#,LrrLpOmJd2k_<Lt+`@WZ`lMpn`/RS#I!I1I?rrL/?
+f`).EfDklc/Y1u&!pa0mo`"uX/]ZZH!TE_;rrLpOo)A\1qYpO9qu6]o,(]cFs2P(h~>
+"s*aDh09a\r;Qh\Qh0hK!B0*,rrG1?mJd2k_<Lt&*WQ/%MsLBIGbtH?pRR)lrrGR?i;WoBL*<S@
+rrH*?li-uIiUd'M_-?d9!Ed>=rrW+#a+=8As2Y.i~>
+"s*aDh09a\r;Qh\Qh0hK!B0*,rrG1?mJd2k_<Lt&*WQ/%MsLBIGbtH?pRR)lrrGR?i;WoBL*<S@
+rrH*?li-uIiUd'M_-?d9!Ed>=rrW+#a+=8As2Y.i~>
+"s*aDh09a\r;Qh\Qh0hK!B0*,rrG1?mJd2k_<Lt&*WQ/%MsLBIGbtH?pRR)lrrGR?i;WoBL*<S@
+rrH*?li-uIiUd'M_-?d9!Ed>=rrW+#a+=8As2Y.i~>
+"s*a!J\Fq)qu6_[OS\VL!B0*=rr^-*R/$X[!5JN##d+.,a-\'+R'?Si!4`#q!42V'!M-:jrrH??
+rr3,W`rH(Arg3u*s8TOps8V)prVm0Es8TdDs8Sj]Yl4S&XRlFY!6bBA$(RBM_g&$Xs5Z0;rrE#r
+Qiu"?s.o&]Qil%As7ZDY"5NqVo)4pXi;`iGrg3rB7G%S*R$c5!rrVhrh#<ZClMpnGrg3cHWqlJj
+"R:fBEiSg4"4rp-jSf)YeGYd4rrT]tpAY'lc2O(5o;_ijao7Y5UAt8AQnilQV>gJp_Z#o6WW3"A
+fX$s4XT/=@Qm7?QYl=Y&\GhiqZi0n).e<H9!Tl<;rrLq?JcC<$a8^Y~>
+"s*a!J\Fq)qu6_[OS\VL!B0*=rr^-*R/$X[!5JN##d+.,a-\'+R'?Si!4`#q!42V'!M-:jrrH??
+rr3,W`rH(Arg3u*s8TOps8V)prVm0Es8TdDs8Sj]Yl4S&XRlFY!6bBA$(RBM_g&$Xs5Z0;rrE#r
+Qiu"?s.o&]Qil%As7ZDY"5NqVo)4pXi;`iGrg3rB7G%S*R$c5!rrVhrh#<ZClMpnGrg3cHWqlJj
+"R:fBEiSg4"4rp-jSf)YeGYd4rrT]tpAY'lc2O(5o;_ijao7Y5UAt8AQnilQV>gJp_Z#o6WW3"A
+fX$s4XT/=@Qm7?QYl=Y&\GhiqZi0n).e<H9!Tl<;rrLq?JcC<$a8^Y~>
+"s*a!J\Fq)qu6_[OS\VL!B0*=rr^-*R/$X[!5JN##d+.,a-\'+R'?Si!4`#q!42V'!M-:jrrH??
+rr3,W`rH(Arg3u*s8TOps8V)prVm0Es8TdDs8Sj]Yl4S&XRlFY!6bBA$(RBM_g&$Xs5Z0;rrE#r
+Qiu"?s.o&]Qil%As7ZDY"5NqVo)4pXi;`iGrg3rB7G%S*R$c5!rrVhrh#<ZClMpnGrg3cHWqlJj
+"R:fBEiSg4"4rp-jSf)YeGYd4rrT]tpAY'lc2O(5o;_ijao7Y5UAt8AQnilQV>gJp_Z#o6WW3"A
+fX$s4XT/=@Qm7?QYl=Y&\GhiqZi0n).e<H9!Tl<;rrLq?JcC<$a8^Y~>
+"s*`o]`1dPqYpS`K_59F2Wju<"1Ek"n,E=f]Rg'8%"b>U]S%>KKVAGGZ[r+/!J7HPrrEN]rr3:/
+_>j(QI_5WWK)UE/J\(kRdf9?VB)V`0*W,j<O`W_arrFipre(B(bl<e(KEcrfV>pSV3;idVL])l/
+J(ai;IK"m%Ibk$RrIb9%ir4?(KEH\cqL&9q#C[iJs6,,1M#RDUiu<IHKEHYom!\kd!e,arq>UTZ
+XT*=@p&>-?KJL1=rrL`$rIb0.rVm'u!9sO`c@>hH"FQ[<aajAD%"joHab23XKTuN:_1;N<'S<"X
+IS>?HKS$$"]n@GLKV8AFZ[r+/!J7HQrrFV?qYpTY2Z*LTh*6JjJcEdjJ,~>
+"s*`o]`1dPqYpS`K_59F2Wju<"1Ek"n,E=f]Rg'8%"b>U]S%>KKVAGGZ[r+/!J7HPrrEN]rr3:/
+_>j(QI_5WWK)UE/J\(kRdf9?VB)V`0*W,j<O`W_arrFipre(B(bl<e(KEcrfV>pSV3;idVL])l/
+J(ai;IK"m%Ibk$RrIb9%ir4?(KEH\cqL&9q#C[iJs6,,1M#RDUiu<IHKEHYom!\kd!e,arq>UTZ
+XT*=@p&>-?KJL1=rrL`$rIb0.rVm'u!9sO`c@>hH"FQ[<aajAD%"joHab23XKTuN:_1;N<'S<"X
+IS>?HKS$$"]n@GLKV8AFZ[r+/!J7HQrrFV?qYpTY2Z*LTh*6JjJcEdjJ,~>
+"s*`o]`1dPqYpS`K_59F2Wju<"1Ek"n,E=f]Rg'8%"b>U]S%>KKVAGGZ[r+/!J7HPrrEN]rr3:/
+_>j(QI_5WWK)UE/J\(kRdf9?VB)V`0*W,j<O`W_arrFipre(B(bl<e(KEcrfV>pSV3;idVL])l/
+J(ai;IK"m%Ibk$RrIb9%ir4?(KEH\cqL&9q#C[iJs6,,1M#RDUiu<IHKEHYom!\kd!e,arq>UTZ
+XT*=@p&>-?KJL1=rrL`$rIb0.rVm'u!9sO`c@>hH"FQ[<aajAD%"joHab23XKTuN:_1;N<'S<"X
+IS>?HKS$$"]n@GLKV8AFZ[r+/!J7HQrrFV?qYpTY2Z*LTh*6JjJcEdjJ,~>
+!$2%<!Dgu9rr=)3rrG%?qu6[Kn,E=fI@pN=!URQ"rrG1?rVln7Yl=Y'k/I<!!N!+$rs;oGF(M&\
+s8Q$?rr3M'HN-Ucs8TQ?s8Rm]$XStnrrM%?rVlmQR/[*pJ=QWfo`+sD9;V[gj>d);!$2%<"D>1C
+(pX)?"(&_A*W?!?_!:k?rVlsoch&XbrrU.ifDbdNP_f>=!okQBrr3'WkhAE&rrG^@p&>&5Wq65k
+@CuO=!O6J>rr=)<rs$32p](8dIK'6L*WQ/+L&V)Qk;N>>!E@/=rrIk?rr3;U8,n$Kk5YJ+:]C@p
+45p/<!H#%>rrM9#rr3!Ko(r@eju3,:!Sotks+13js*t~>
+!$2%<!Dgu9rr=)3rrG%?qu6[Kn,E=fI@pN=!URQ"rrG1?rVln7Yl=Y'k/I<!!N!+$rs;oGF(M&\
+s8Q$?rr3M'HN-Ucs8TQ?s8Rm]$XStnrrM%?rVlmQR/[*pJ=QWfo`+sD9;V[gj>d);!$2%<"D>1C
+(pX)?"(&_A*W?!?_!:k?rVlsoch&XbrrU.ifDbdNP_f>=!okQBrr3'WkhAE&rrG^@p&>&5Wq65k
+@CuO=!O6J>rr=)<rs$32p](8dIK'6L*WQ/+L&V)Qk;N>>!E@/=rrIk?rr3;U8,n$Kk5YJ+:]C@p
+45p/<!H#%>rrM9#rr3!Ko(r@eju3,:!Sotks+13js*t~>
+!$2%<!Dgu9rr=)3rrG%?qu6[Kn,E=fI@pN=!URQ"rrG1?rVln7Yl=Y'k/I<!!N!+$rs;oGF(M&\
+s8Q$?rr3M'HN-Ucs8TQ?s8Rm]$XStnrrM%?rVlmQR/[*pJ=QWfo`+sD9;V[gj>d);!$2%<"D>1C
+(pX)?"(&_A*W?!?_!:k?rVlsoch&XbrrU.ifDbdNP_f>=!okQBrr3'WkhAE&rrG^@p&>&5Wq65k
+@CuO=!O6J>rr=)<rs$32p](8dIK'6L*WQ/+L&V)Qk;N>>!E@/=rrIk?rr3;U8,n$Kk5YJ+:]C@p
+45p/<!H#%>rrM9#rr3!Ko(r@eju3,:!Sotks+13js*t~>
+!$2%<!Dgu:rrHN?o`"qMk55/Z/ar]=!T%ep5lbQrrrG1?rVloOB`:9tFSGe;>;EA"rVlms2#]cO
+.=2"ddf9?VB)hnV^)"H2r;QfU4T59\1$no>"G!$B..mN="aHmDs5Z0;rr<r85m&%6ruM(<"(&_A
+*W,j;r?)"<"&7,6./j/H7G%P=!L/E>rrM.?rVlsVkhAE&rrG^@p&>&5Wq65k@CuO=!O6J>rr=)7
+rrJd@rr3*As8SE0r]pQH2ZE^W<65%<!JZp>rrgnCs*gR=rrLA?rr3!\iVicWg1^IO!-A,=!@m[:
+rrM7?qu6]Q5Crics2Y.i~>
+!$2%<!Dgu:rrHN?o`"qMk55/Z/ar]=!T%ep5lbQrrrG1?rVloOB`:9tFSGe;>;EA"rVlms2#]cO
+.=2"ddf9?VB)hnV^)"H2r;QfU4T59\1$no>"G!$B..mN="aHmDs5Z0;rr<r85m&%6ruM(<"(&_A
+*W,j;r?)"<"&7,6./j/H7G%P=!L/E>rrM.?rVlsVkhAE&rrG^@p&>&5Wq65k@CuO=!O6J>rr=)7
+rrJd@rr3*As8SE0r]pQH2ZE^W<65%<!JZp>rrgnCs*gR=rrLA?rr3!\iVicWg1^IO!-A,=!@m[:
+rrM7?qu6]Q5Crics2Y.i~>
+!$2%<!Dgu:rrHN?o`"qMk55/Z/ar]=!T%ep5lbQrrrG1?rVloOB`:9tFSGe;>;EA"rVlms2#]cO
+.=2"ddf9?VB)hnV^)"H2r;QfU4T59\1$no>"G!$B..mN="aHmDs5Z0;rr<r85m&%6ruM(<"(&_A
+*W,j;r?)"<"&7,6./j/H7G%P=!L/E>rrM.?rVlsVkhAE&rrG^@p&>&5Wq65k@CuO=!O6J>rr=)7
+rrJd@rr3*As8SE0r]pQH2ZE^W<65%<!JZp>rrgnCs*gR=rrLA?rr3!\iVicWg1^IO!-A,=!@m[:
+rrM7?qu6]Q5Crics2Y.i~>
+!$2%<!Dgu;rrQWBrUg*j2Wjo:!A3d=rrD9^gB"`srr3![ir&fVk5O*9Wd+=="'_Wb48o0[;m$#Q
+#OMI_df9?VB)V`0*W#d:i&pu<!AWs?rrdkBs![O=rrmYDs8V.>r;Qa:r7_;GruM(<"(&_A*W,j;
+r?)"<"&7,6./j/H7G%P=!L/E>rrM.?rVlsVkk_C[,QXh=p&>&5Wq65k@CuO=!O6J>rr=)7rrMYA
+rZhWqs8SF+rS%>3rr3!uaSl,>L6hi="P-*CI\-Q=!R4F>rrG4?r;[email protected]<H9!Tl<;
+rrLq?JcC<$a8^Y~>
+!$2%<!Dgu;rrQWBrUg*j2Wjo:!A3d=rrD9^gB"`srr3![ir&fVk5O*9Wd+=="'_Wb48o0[;m$#Q
+#OMI_df9?VB)V`0*W#d:i&pu<!AWs?rrdkBs![O=rrmYDs8V.>r;Qa:r7_;GruM(<"(&_A*W,j;
+r?)"<"&7,6./j/H7G%P=!L/E>rrM.?rVlsVkk_C[,QXh=p&>&5Wq65k@CuO=!O6J>rr=)7rrMYA
+rZhWqs8SF+rS%>3rr3!uaSl,>L6hi="P-*CI\-Q=!R4F>rrG4?r;[email protected]<H9!Tl<;
+rrLq?JcC<$a8^Y~>
+!$2%<!Dgu;rrQWBrUg*j2Wjo:!A3d=rrD9^gB"`srr3![ir&fVk5O*9Wd+=="'_Wb48o0[;m$#Q
+#OMI_df9?VB)V`0*W#d:i&pu<!AWs?rrdkBs![O=rrmYDs8V.>r;Qa:r7_;GruM(<"(&_A*W,j;
+r?)"<"&7,6./j/H7G%P=!L/E>rrM.?rVlsVkk_C[,QXh=p&>&5Wq65k@CuO=!O6J>rr=)7rrMYA
+rZhWqs8SF+rS%>3rr3!uaSl,>L6hi="P-*CI\-Q=!R4F>rrG4?r;[email protected]<H9!Tl<;
+rrLq?JcC<$a8^Y~>
+%NYTElKUsRlL"WOoPM5>p&>0dl7pcSrr38pl71QUs8S%KrosO!?iL'19%*"9C]Ag_rosNhDZ0S9
+'/fdh$!Y7@i>l@us'`V?l3Qq[s&m;*`t`4brr=):rs7u:2sU&8s"Wm>rsjRLs#T-(lBDdTs8W)9
+>5eI$0`C8=pAb.5rVlt+^Afhal2tM?r@e-7"'3nD2u`(NC^AYmqs&uclMCP@3W8s[2!Egmro*tA
+qu-O&IgEmjs82*HBq4JRrVlq:#grYO#l!UeX6T]a[r;61!&4BO#$)#ps8T-Jro+(Hs8SaFrosLM
+rVm+4]^!gBs,`ECl3YL3s*bTflKZ/krr33rdH\>Ys*h!Il2e2-rr3#p,l7NA,Ph9;!rFA@r;Qii
+#YfmMJcF'rJ,~>
+%NYTElKUsRlL"WOoPM5>p&>0dl7pcSrr38pl71QUs8S%KrosO!?iL'19%*"9C]Ag_rosNhDZ0S9
+'/fdh$!Y7@i>l@us'`V?l3Qq[s&m;*`t`4brr=):rs7u:2sU&8s"Wm>rsjRLs#T-(lBDdTs8W)9
+>5eI$0`C8=pAb.5rVlt+^Afhal2tM?r@e-7"'3nD2u`(NC^AYmqs&uclMCP@3W8s[2!Egmro*tA
+qu-O&IgEmjs82*HBq4JRrVlq:#grYO#l!UeX6T]a[r;61!&4BO#$)#ps8T-Jro+(Hs8SaFrosLM
+rVm+4]^!gBs,`ECl3YL3s*bTflKZ/krr33rdH\>Ys*h!Il2e2-rr3#p,l7NA,Ph9;!rFA@r;Qii
+#YfmMJcF'rJ,~>
+%NYTElKUsRlL"WOoPM5>p&>0dl7pcSrr38pl71QUs8S%KrosO!?iL'19%*"9C]Ag_rosNhDZ0S9
+'/fdh$!Y7@i>l@us'`V?l3Qq[s&m;*`t`4brr=):rs7u:2sU&8s"Wm>rsjRLs#T-(lBDdTs8W)9
+>5eI$0`C8=pAb.5rVlt+^Afhal2tM?r@e-7"'3nD2u`(NC^AYmqs&uclMCP@3W8s[2!Egmro*tA
+qu-O&IgEmjs82*HBq4JRrVlq:#grYO#l!UeX6T]a[r;61!&4BO#$)#ps8T-Jro+(Hs8SaFrosLM
+rVm+4]^!gBs,`ECl3YL3s*bTflKZ/krr33rdH\>Ys*h!Il2e2-rr3#p,l7NA,Ph9;!rFA@r;Qii
+#YfmMJcF'rJ,~>
+!Zh<Nr%\CPs8SOkoD\fMr\=IJrr2tQr\=IQrr2tJr\=IXr;Qgi11L7_!-.un!,;B2!BaK`rrIJk
+rr3,5NrT,br\=J%rr3+%10(eqrVljiqu6Xfr\=V=s8Q6jrr3:mYlF_b1,=WJ\,QC/dUqP=rrDoo
+1'=ZYs%<7i"+JGnmJSdB])VfmrA"Jks8V!U1'FgqhuD@-1'>l&l!XJi"'k6gV>W.MZMa_%"dK_8
+s8UXI1&s31rrRrdd/O%FYPg3Yp](8mrA"BPrVm(C1@>,Am",-k"?b98s-3L<!(Qnd!a[WVrr2tR
+r\=aXs8RP>BJM>Hr;Qgj11C1^!-8&o!,2<1!CkZ<rrLS?qu6]C:&FqloeL<6s+13rs*t~>
+!Zh<Nr%\CPs8SOkoD\fMr\=IJrr2tQr\=IQrr2tJr\=IXr;Qgi11L7_!-.un!,;B2!BaK`rrIJk
+rr3,5NrT,br\=J%rr3+%10(eqrVljiqu6Xfr\=V=s8Q6jrr3:mYlF_b1,=WJ\,QC/dUqP=rrDoo
+1'=ZYs%<7i"+JGnmJSdB])VfmrA"Jks8V!U1'FgqhuD@-1'>l&l!XJi"'k6gV>W.MZMa_%"dK_8
+s8UXI1&s31rrRrdd/O%FYPg3Yp](8mrA"BPrVm(C1@>,Am",-k"?b98s-3L<!(Qnd!a[WVrr2tR
+r\=aXs8RP>BJM>Hr;Qgj11C1^!-8&o!,2<1!CkZ<rrLS?qu6]C:&FqloeL<6s+13rs*t~>
+!Zh<Nr%\CPs8SOkoD\fMr\=IJrr2tQr\=IQrr2tJr\=IXr;Qgi11L7_!-.un!,;B2!BaK`rrIJk
+rr3,5NrT,br\=J%rr3+%10(eqrVljiqu6Xfr\=V=s8Q6jrr3:mYlF_b1,=WJ\,QC/dUqP=rrDoo
+1'=ZYs%<7i"+JGnmJSdB])VfmrA"Jks8V!U1'FgqhuD@-1'>l&l!XJi"'k6gV>W.MZMa_%"dK_8
+s8UXI1&s31rrRrdd/O%FYPg3Yp](8mrA"BPrVm(C1@>,Am",-k"?b98s-3L<!(Qnd!a[WVrr2tR
+r\=aXs8RP>BJM>Hr;Qgj11C1^!-8&o!,2<1!CkZ<rrLS?qu6]C:&FqloeL<6s+13rs*t~>
+!$/$<"ip05s0lUqrrL'0a8Z1p62gfa/+NT<!F<J3rrGj@kPkRUIK'6Imk*c'!IgX(rrMb1r;Qe0
+r;6Ko@tFZ2s2G"g~>
+!$/$<"ip05s0lUqrrL'0a8Z1p62gfa/+NT<!F<J3rrGj@kPkRUIK'6Imk*c'!IgX(rrMb1r;Qe0
+r;6Ko@tFZ2s2G"g~>
+!$/$<"ip05s0lUqrrL'0a8Z1p62gfa/+NT<!F<J3rrGj@kPkRUIK'6Imk*c'!IgX(rrMb1r;Qe0
+r;6Ko@tFZ2s2G"g~>
+!$/$<"db13PDH-0rrMD.rK@8'p\t0oa*QJ3rrTu\jQHODaFF2Q!Ki-#rrIS?JcC<$SH"*~>
+!$/$<"db13PDH-0rrMD.rK@8'p\t0oa*QJ3rrTu\jQHODaFF2Q!Ki-#rrIS?JcC<$SH"*~>
+!$/$<"db13PDH-0rrMD.rK@8'p\t0oa*QJ3rrTu\jQHODaFF2Q!Ki-#rrIS?JcC<$SH"*~>
+!$/!;".k@+Wh04jiVeT5li$ha_US2W!P7(JrrC%;M#`Y#rrL$cJcC<$SH"*~>
+!$/!;".k@+Wh04jiVeT5li$ha_US2W!P7(JrrC%;M#`Y#rrL$cJcC<$SH"*~>
+!$/!;".k@+Wh04jiVeT5li$ha_US2W!P7(JrrC%;M#`Y#rrL$cJcC<$SH"*~>
+!$2";!d\!iJc>iPBN]t=s+13$s,R,0~>
+!$2";!d\!iJc>iPBN]t=s+13$s,R,0~>
+!$2";!d\!iJc>iPBN]t=s+13$s,R,0~>
+!$2%<"*pi_bhN-ufQmJr!GFXFrrGGskl1[N](l:-aMX^)JcC<$JcCf2J,~>
+!$2%<"*pi_bhN-ufQmJr!GFXFrrGGskl1[N](l:-aMX^)JcC<$JcCf2J,~>
+!$2%<"*pi_bhN-ufQmJr!GFXFrrGGskl1[N](l:-aMX^)JcC<$JcCf2J,~>
+!$2%<!DgtnrrL>?rr3#^i7%],i9'8'!I1I7rrKi?JcC<$JcCf2J,~>
+!$2%<!DgtnrrL>?rr3#^i7%],i9'8'!I1I7rrKi?JcC<$JcCf2J,~>
+!$2%<!DgtnrrL>?rr3#^i7%],i9'8'!I1I7rrKi?JcC<$JcCf2J,~>
+!$2%<!b/kArr2uhroFFJs8V`^mcEQnrr2ufroF.Drr2ueroF.Err3>ojlP[L;#gR`jlQI@"n;9O
+jlPk.rrqcNjluO/q#:fjq>^Kimf3=RqW?o$n,E=gkiM+-rrD9^jT+iMrrD6]jT+lNrs.]Jjm[Mk
+s8W&Z!;-9j!rK6?JcC<$JcCf2J,~>
+!$2%<!b/kArr2uhroFFJs8V`^mcEQnrr2ufroF.Drr2ueroF.Err3>ojlP[L;#gR`jlQI@"n;9O
+jlPk.rrqcNjluO/q#:fjq>^Kimf3=RqW?o$n,E=gkiM+-rrD9^jT+iMrrD6]jT+lNrs.]Jjm[Mk
+s8W&Z!;-9j!rK6?JcC<$JcCf2J,~>
+!$2%<!b/kArr2uhroFFJs8V`^mcEQnrr2ufroF.Drr2ueroF.Err3>ojlP[L;#gR`jlQI@"n;9O
+jlPk.rrqcNjluO/q#:fjq>^Kimf3=RqW?o$n,E=gkiM+-rrD9^jT+iMrrD6]jT+lNrs.]Jjm[Mk
+s8W&Z!;-9j!rK6?JcC<$JcCf2J,~>
+!$2(=#N*U%n,NFQJc7S:6.5e!BkoXd7b%J#GQ'N(9%Et'ErJ!-:Y5X,D)XC@TMY[gHoD</rsik&
+;`?XAh>c;<2`FX*MYR2b;p,.?^Kpm>8u4d32f[pYs&:a0rVloJ8,bFMHKbCW6N/nPJ*R'\4Z><@
+?D[\J3W:rBM=(?Cj'9[bJcC<$JcCi3J,~>
+!$2(=#N*U%n,NFQJc7S:6.5e!BkoXd7b%J#GQ'N(9%Et'ErJ!-:Y5X,D)XC@TMY[gHoD</rsik&
+;`?XAh>c;<2`FX*MYR2b;p,.?^Kpm>8u4d32f[pYs&:a0rVloJ8,bFMHKbCW6N/nPJ*R'\4Z><@
+?D[\J3W:rBM=(?Cj'9[bJcC<$JcCi3J,~>
+!$2(=#N*U%n,NFQJc7S:6.5e!BkoXd7b%J#GQ'N(9%Et'ErJ!-:Y5X,D)XC@TMY[gHoD</rsik&
+;`?XAh>c;<2`FX*MYR2b;p,.?^Kpm>8u4d32f[pYs&:a0rVloJ8,bFMHKbCW6N/nPJ*R'\4Z><@
+?D[\J3W:rBM=(?Cj'9[bJcC<$JcCi3J,~>
+!$2(=!c7q^rr3"kIfB?UmOnO*/AhGeju<=#NrK%]k^NPcQi@!keO]^gTE"r]6W!a]!$2%<%;\)^
+s8T]>s'rV>s3OI6rshuMs8Ti>s8P\[p&G&[KDtlOkqi;<!BK3>rrfR8s"Wm>rsXFJs![O>s0sGQ
+ruh:>rrI&?rr3&6!.0:sJcC<$OT0h~>
+!$2(=!c7q^rr3"kIfB?UmOnO*/AhGeju<=#NrK%]k^NPcQi@!keO]^gTE"r]6W!a]!$2%<%;\)^
+s8T]>s'rV>s3OI6rshuMs8Ti>s8P\[p&G&[KDtlOkqi;<!BK3>rrfR8s"Wm>rsXFJs![O>s0sGQ
+ruh:>rrI&?rr3&6!.0:sJcC<$OT0h~>
+!$2(=!c7q^rr3"kIfB?UmOnO*/AhGeju<=#NrK%]k^NPcQi@!keO]^gTE"r]6W!a]!$2%<%;\)^
+s8T]>s'rV>s3OI6rshuMs8Ti>s8P\[p&G&[KDtlOkqi;<!BK3>rrfR8s"Wm>rsXFJs![O>s0sGQ
+ruh:>rrI&?rr3&6!.0:sJcC<$OT0h~>
+!$2(=#Hi'&pAb/b7fJDNKeECkLAq2Uju<=#Nr/hWKpVf="P$'CI@pN=!R+C=rr=)<rrHr?rr389
+@K0iJs8UG>p\tOub5_LV?2spsdf07IR=kM=!U2E=rrG.?qu6[Om/I"fJ=QWfo`"jnGbtE_NW+qC
+5-=kbmtYnkJcC<$JcCi3J,~>
+!$2(=#Hi'&pAb/b7fJDNKeECkLAq2Uju<=#Nr/hWKpVf="P$'CI@pN=!R+C=rr=)<rrHr?rr389
+@K0iJs8UG>p\tOub5_LV?2spsdf07IR=kM=!U2E=rrG.?qu6[Om/I"fJ=QWfo`"jnGbtE_NW+qC
+5-=kbmtYnkJcC<$JcCi3J,~>
+!$2(=#Hi'&pAb/b7fJDNKeECkLAq2Uju<=#Nr/hWKpVf="P$'CI@pN=!R+C=rr=)<rrHr?rr389
+@K0iJs8UG>p\tOub5_LV?2spsdf07IR=kM=!U2E=rrG.?qu6[Om/I"fJ=QWfo`"jnGbtE_NW+qC
+5-=kbmtYnkJcC<$JcCi3J,~>
+!$2%<#+^SDs8S]\rJguSs-AE=rrhICs,N-:rrIh?rr3,O8H4+1rr3#C;#UCo*W?!=Cp<p=#eOOF
+\p8:8G-L`@#uf"Hs1_k>s%:`=rrJO?rr3#`1]@=S3TL#:!AWs?rrdkBs![O=rrdSCrud="NWn2;
+s6k`>JcC<$JcCf2J,~>
+!$2%<#+^SDs8S]\rJguSs-AE=rrhICs,N-:rrIh?rr3,O8H4+1rr3#C;#UCo*W?!=Cp<p=#eOOF
+\p8:8G-L`@#uf"Hs1_k>s%:`=rrJO?rr3#`1]@=S3TL#:!AWs?rrdkBs![O=rrdSCrud="NWn2;
+s6k`>JcC<$JcCf2J,~>
+!$2%<#+^SDs8S]\rJguSs-AE=rrhICs,N-:rrIh?rr3,O8H4+1rr3#C;#UCo*W?!=Cp<p=#eOOF
+\p8:8G-L`@#uf"Hs1_k>s%:`=rrJO?rr3#`1]@=S3TL#:!AWs?rrdkBs![O=rrdSCrud="NWn2;
+s6k`>JcC<$JcCf2J,~>
+!$2%<!Dgu>rrJa@qu6\_LAq2Uju<=#NrK%]k^NPcQi@!keO]^gTE"r]6W!a]!$2%<!Go">rs-/E
+s-JqMX1J3.!EOLFrs;WHs8Ti>s8P^>rr3"eKDtlOkqi;<!BK3>rrfR8s"Wm>rsXFJs![O>s0sGQ
+ruh:9rrKi?JcC<$JcCf2J,~>
+!$2%<!Dgu>rrJa@qu6\_LAq2Uju<=#NrK%]k^NPcQi@!keO]^gTE"r]6W!a]!$2%<!Go">rs-/E
+s-JqMX1J3.!EOLFrs;WHs8Ti>s8P^>rr3"eKDtlOkqi;<!BK3>rrfR8s"Wm>rsXFJs![O>s0sGQ
+ruh:9rrKi?JcC<$JcCf2J,~>
+!$2%<!Dgu>rrJa@qu6\_LAq2Uju<=#NrK%]k^NPcQi@!keO]^gTE"r]6W!a]!$2%<!Go">rs-/E
+s-JqMX1J3.!EOLFrs;WHs8Ti>s8P^>rr3"eKDtlOkqi;<!BK3>rrfR8s"Wm>rsXFJs![O>s0sGQ
+ruh:9rrKi?JcC<$JcCf2J,~>
+!$2%<!Dgu>rrMD9r\jsKs8SC>rr3,`2ui#ar\jsPec4`Or\kNefDjlJ2`F*s;#gQC2[23Qs8Qo>
+rr3,[email protected]\jgur;Qd"2u`g`g/U'j>EbER9%*_=%%2bJs&:`]2i[k;e49Ks"FB;3dR*pl%"IRB
+coj<HF'b^CcT1t`!3#kr!PVlks+13$s,[21~>
+!$2%<!Dgu>rrMD9r\jsKs8SC>rr3,`2ui#ar\jsPec4`Or\kNefDjlJ2`F*s;#gQC2[23Qs8Qo>
+rr3,[email protected]\jgur;Qd"2u`g`g/U'j>EbER9%*_=%%2bJs&:`]2i[k;e49Ks"FB;3dR*pl%"IRB
+coj<HF'b^CcT1t`!3#kr!PVlks+13$s,[21~>
+!$2%<!Dgu>rrMD9r\jsKs8SC>rr3,`2ui#ar\jsPec4`Or\kNefDjlJ2`F*s;#gQC2[23Qs8Qo>
+rr3,[email protected]\jgur;Qd"2u`g`g/U'j>EbER9%*_=%%2bJs&:`]2i[k;e49Ks"FB;3dR*pl%"IRB
+coj<HF'b^CcT1t`!3#kr!PVlks+13$s,[21~>
+!$2%<#44f!56(ZRroF:Fs8V`^rr3-!lMpnRroF.Drr2ueroF.Err38mjlP\%mJm4SroF:Ks8VT_
+rr3,rn,IL3roF0ZH2[aDc8Y_%#jUO5lh0fJm/$_]"SD9bs60ID!:^!f!9jFD!:g'g!9a@C!:p-h
+#Nk.0nF?5Ps8D$`o`+qH*Dc*Ss+13$s,[21~>
+!$2%<#44f!56(ZRroF:Fs8V`^rr3-!lMpnRroF.Drr2ueroF.Err38mjlP\%mJm4SroF:Ks8VT_
+rr3,rn,IL3roF0ZH2[aDc8Y_%#jUO5lh0fJm/$_]"SD9bs60ID!:^!f!9jFD!:g'g!9a@C!:p-h
+#Nk.0nF?5Ps8D$`o`+qH*Dc*Ss+13$s,[21~>
+!$2%<#44f!56(ZRroF:Fs8V`^rr3-!lMpnRroF.Drr2ueroF.Err38mjlP\%mJm4SroF:Ks8VT_
+rr3,rn,IL3roF0ZH2[aDc8Y_%#jUO5lh0fJm/$_]"SD9bs60ID!:^!f!9jFD!:g'g!9a@C!:p-h
+#Nk.0nF?5Ps8D$`o`+qH*Dc*Ss+13$s,[21~>
+!$/lT!I(UDhuT^&rVloB:kAXts+13$s3Udr~>
+!$/lT!I(UDhuT^&rVloB:kAXts+13$s3Udr~>
+!$/lT!I(UDhuT^&rVloB:kAXts+13$s3Udr~>
+!$/iS!-8'$!-\;?!)nIKJcC<$JcF'rJ,~>
+!$/iS!-8'$!-\;?!)nIKJcC<$JcF'rJ,~>
+!$/iS!-8'$!-\;?!)nIKJcC<$JcF'rJ,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+"!.FAao7+sNW/qY`R48V"0^kcRDo1.QN,gOJcC<$JcC<$iW"E~>
+"!.FAao7+sNW/qY`R48V"0^kcRDo1.QN,gOJcC<$JcC<$iW"E~>
+"!.FAao7+sNW/qY`R48V"0^kcRDo1.QN,gOJcC<$JcC<$iW"E~>
+"!.EcIf>N8P(N`6b'V_2rrZ3AqBEoY"!.ENAqBu5s+13$s5F!.~>
+"!.EcIf>N8P(N`6b'V_2rrZ3AqBEoY"!.ENAqBu5s+13$s5F!.~>
+"!.EcIf>N8P(N`6b'V_2rrZ3AqBEoY"!.ENAqBu5s+13$s5F!.~>
+"!.E>FoMCApEon>!mbW@o`#"land4#rrX;A[q:3kJcC<$JcF^/J,~>
+"!.E>FoMCApEon>!mbW@o`#"land4#rrX;A[q:3kJcC<$JcF^/J,~>
+"!.E>FoMCApEon>!mbW@o`#"land4#rrX;A[q:3kJcC<$JcF^/J,~>
+"!.F0jSo/[8C[V<!o$B@p&>9Qo`!>>s.-4irrh.os-Btirrgnos,aejrs6qrs,"Pks57Vjrr?j6
+1&u7krrYqM13WZs!+#RZ!.FhG"4R;?iIV#[s+13$s5F!.~>
+"!.F0jSo/[8C[V<!o$B@p&>9Qo`!>>s.-4irrh.os-Btirrgnos,aejrs6qrs,"Pks57Vjrr?j6
+1&u7krrYqM13WZs!+#RZ!.FhG"4R;?iIV#[s+13$s5F!.~>
+"!.F0jSo/[8C[V<!o$B@p&>9Qo`!>>s.-4irrh.os-Btirrgnos,aejrs6qrs,"Pks57Vjrr?j6
+1&u7krrYqM13WZs!+#RZ!.FhG"4R;?iIV#[s+13$s5F!.~>
+!$2%<!r>IAqu6]a0CSoAKpVf="P$'CI@pN='[0DRFK#6>hA08oCp<s>fgPl?E3K9+!NL>Brs'\0
+[oNJ.@D2[*!M"jps+13$s+14(s*t~>
+!$2%<!r>IAqu6]a0CSoAKpVf="P$'CI@pN='[0DRFK#6>hA08oCp<s>fgPl?E3K9+!NL>Brs'\0
+[oNJ.@D2[*!M"jps+13$s+14(s*t~>
+!$2%<!r>IAqu6]a0CSoAKpVf="P$'CI@pN='[0DRFK#6>hA08oCp<s>fgPl?E3K9+!NL>Brs'\0
+[oNJ.@D2[*!M"jps+13$s+14(s*t~>
+!$2(=!pX(@qYpTT4R`:NKpVf="P$'CI>RsX'X(@5FK#5r5/^V(Cp<s>fgPl?A%DU=!OHM>rs'\E
+aAr9?;m$&R!K28Ts+13$s+14(s*t~>
+!$2(=!pX(@qYpTT4R`:NKpVf="P$'CI>RsX'X(@5FK#5r5/^V(Cp<s>fgPl?A%DU=!OHM>rs'\E
+aAr9?;m$&R!K28Ts+13$s+14(s*t~>
+!$2(=!pX(@qYpTT4R`:NKpVf="P$'CI>RsX'X(@5FK#5r5/^V(Cp<s>fgPl?A%DU=!OHM>rs'\E
+aAr9?;m$&R!K28Ts+13$s+14(s*t~>
+"WdXCiHE09rrUPJp@&"^KpVf="P$'CI4>.]'JrYdFK!D[s28(?Cp<s>fgPl?A%DU=%'s[Js34F>
+aAr9?;`+G=!1\W?JcC<$JcFI(J,~>
+"WdXCiHE09rrUPJp@&"^KpVf="P$'CI4>.]'JrYdFK!D[s28(?Cp<s>fgPl?A%DU=%'s[Js34F>
+aAr9?;`+G=!1\W?JcC<$JcFI(J,~>
+"WdXCiHE09rrUPJp@&"^KpVf="P$'CI4>.]'JrYdFK!D[s28(?Cp<s>fgPl?A%DU=%'s[Js34F>
+aAr9?;`+G=!1\W?JcC<$JcFI(J,~>
+"<IO"K"LmZ!lf6Amf*TIQiI*^L.M>mTDnj)bY\=]C2NA<`**+VYlFaV70!9s\c2U;ZYBI>52ZC]
+<WE(tb45K5pS#?Qs+13$s+146s*t~>
+"<IO"K"LmZ!lf6Amf*TIQiI*^L.M>mTDnj)bY\=]C2NA<`**+VYlFaV70!9s\c2U;ZYBI>52ZC]
+<WE(tb45K5pS#?Qs+13$s+146s*t~>
+"<IO"K"LmZ!lf6Amf*TIQiI*^L.M>mTDnj)bY\=]C2NA<`**+VYlFaV70!9s\c2U;ZYBI>52ZC]
+<WE(tb45K5pS#?Qs+13$s+146s*t~>
+"!.E>4oL$;QN.!%J(j;i$,D(EKS*o.s*^O=rtN[Rs)_,ss8U#?s(knmKQ&0Bs0%"QKFEF;s0*Ve
+KP)jJs/(DIKE/:MrrVEb;1\aus+13$s60K5~>
+"!.E>4oL$;QN.!%J(j;i$,D(EKS*o.s*^O=rtN[Rs)_,ss8U#?s(knmKQ&0Bs0%"QKFEF;s0*Ve
+KP)jJs/(DIKE/:MrrVEb;1\aus+13$s60K5~>
+"!.E>4oL$;QN.!%J(j;i$,D(EKS*o.s*^O=rtN[Rs)_,ss8U#?s(knmKQ&0Bs0%"QKFEF;s0*Ve
+KP)jJs/(DIKE/:MrrVEb;1\aus+13$s60K5~>
+!Zh<rr0RVbs8U<pli.7-R$aGp8H6ibrr3,d\GsV)rr3)`]`5qKQiqA:s8TJ*Qisnus''RDQiq#A
+s8T8$QiOknrrVn]\Ujd3s+13$s60K5~>
+!Zh<rr0RVbs8U<pli.7-R$aGp8H6ibrr3,d\GsV)rr3)`]`5qKQiqA:s8TJ*Qisnus''RDQiq#A
+s8T8$QiOknrrVn]\Ujd3s+13$s60K5~>
+!Zh<rr0RVbs8U<pli.7-R$aGp8H6ibrr3,d\GsV)rr3)`]`5qKQiqA:s8TJ*Qisnus''RDQiq#A
+s8T8$QiOknrrVn]\Ujd3s+13$s60K5~>
+!$1"t!NEL+rrLY@li.!t@JKj'cgC`3!Tqsas+13$s+13us*t~>
+!$1"t!NEL+rrLY@li.!t@JKj'cgC`3!Tqsas+13$s+13us*t~>
+!$1"t!NEL+rrLY@li.!t@JKj'cgC`3!Tqsas+13$s+13us*t~>
+!$1"t!U4S&/HJH"li."Wj+75]s+13$s2+ed~>
+!$1"t!U4S&/HJH"li."Wj+75]s+13$s2+ed~>
+!$1"t!U4S&/HJH"li."Wj+75]s+13$s2+ed~>
+!$2(="8MHXoB-&Qp&=C[nU^_$rr`#do(.G4JcDABJ,~>
+!$2(="8MHXoB-&Qp&=C[nU^_$rr`#do(.G4JcDABJ,~>
+!$2(="8MHXoB-&Qp&=C[nU^_$rr`#do(.G4JcDABJ,~>
+!$2(=")T&-1&V%PiV<?N"6A%arr2otkl0-5g\h'Ph>HmE!8@>M!oDM`r;QiRf%p?*!9F(/!9j.V
+!WCjOrrUg'j8ArWec=e0rr`5Nf&cQ(!VYROrr`)Jf'<87!<)lI!:]mc!U0(HrrMTRr;Qlif%0?h
+rr_fBf(/e>!:^!=!;Q6e!U0(Ors%i@f(]4Eo_d8?g&D!Ol2K9+rrM*Rr;Qu_f%0j#s6]a8!oX+E
+r72)2s8RBo.2IL7JcDABJ,~>
+!$2(=")T&-1&V%PiV<?N"6A%arr2otkl0-5g\h'Ph>HmE!8@>M!oDM`r;QiRf%p?*!9F(/!9j.V
+!WCjOrrUg'j8ArWec=e0rr`5Nf&cQ(!VYROrr`)Jf'<87!<)lI!:]mc!U0(HrrMTRr;Qlif%0?h
+rr_fBf(/e>!:^!=!;Q6e!U0(Ors%i@f(]4Eo_d8?g&D!Ol2K9+rrM*Rr;Qu_f%0j#s6]a8!oX+E
+r72)2s8RBo.2IL7JcDABJ,~>
+!$2(=")T&-1&V%PiV<?N"6A%arr2otkl0-5g\h'Ph>HmE!8@>M!oDM`r;QiRf%p?*!9F(/!9j.V
+!WCjOrrUg'j8ArWec=e0rr`5Nf&cQ(!VYROrr`)Jf'<87!<)lI!:]mc!U0(HrrMTRr;Qlif%0?h
+rr_fBf(/e>!:^!=!;Q6e!U0(Ors%i@f(]4Eo_d8?g&D!Ol2K9+rrM*Rr;Qu_f%0j#s6]a8!oX+E
+r72)2s8RBo.2IL7JcDABJ,~>
+!$2(=!H#(<rrU\+ec#LRg2BGM^An5kH2^86<m(FA!mMnWo`"s6(&\(7c<Nh@bPqMIbZRD>cN!oK
+2>ouERJ-X]ZRbtR"N*i$CY/Rc"MdJrDV>$h"M.&lEnpBg!r.jWrVm0%WCB@+j8].frC-gJmJ[%d
+i)?WKrrVP,JGoK[ns2a7ORE/Kmug.0Pk"eRm>h08!9!SN!oR"WrVm,bK18>*qZ"e;7KaM,s5A>(
+7KEG]rrU_-eG]CPg2KMN^&S,8rC-lP56%S[7KGS@r;Qa;JcC<$TDsE~>
+!$2(=!H#(<rrU\+ec#LRg2BGM^An5kH2^86<m(FA!mMnWo`"s6(&\(7c<Nh@bPqMIbZRD>cN!oK
+2>ouERJ-X]ZRbtR"N*i$CY/Rc"MdJrDV>$h"M.&lEnpBg!r.jWrVm0%WCB@+j8].frC-gJmJ[%d
+i)?WKrrVP,JGoK[ns2a7ORE/Kmug.0Pk"eRm>h08!9!SN!oR"WrVm,bK18>*qZ"e;7KaM,s5A>(
+7KEG]rrU_-eG]CPg2KMN^&S,8rC-lP56%S[7KGS@r;Qa;JcC<$TDsE~>
+!$2(=!H#(<rrU\+ec#LRg2BGM^An5kH2^86<m(FA!mMnWo`"s6(&\(7c<Nh@bPqMIbZRD>cN!oK
+2>ouERJ-X]ZRbtR"N*i$CY/Rc"MdJrDV>$h"M.&lEnpBg!r.jWrVm0%WCB@+j8].frC-gJmJ[%d
+i)?WKrrVP,JGoK[ns2a7ORE/Kmug.0Pk"eRm>h08!9!SN!oR"WrVm,bK18>*qZ"e;7KaM,s5A>(
+7KEG]rrU_-eG]CPg2KMN^&S,8rC-lP56%S[7KGS@r;Qa;JcC<$TDsE~>
+!$2(=!H#(=rr^Pl*7b&g$.aRGs8Ql>s,N-=rs.@Es8U#Z'(>Mn!krU^rVmJLFlEAaEl.jnF6<M^
+FN+8a_=IU-X^NaYrt`"Xqu=rhdf6Ufs8U/hec2gjs8Tudg%YLJQVCKYrs5BFs8TH[ir:%trrHH?
+rr3)XK.X(srr__K4b3P3')7TQs8SOJoCS*2s8S@Gp@4-5oD\pMK.O&%rs%YBXT/<NSGW<iX)\0u
+KX^RQ"4)95eGfIPf7(a>s(nt<rrW1aM>[ATqNHs;rr='js+13Bs*t~>
+!$2(=!H#(=rr^Pl*7b&g$.aRGs8Ql>s,N-=rs.@Es8U#Z'(>Mn!krU^rVmJLFlEAaEl.jnF6<M^
+FN+8a_=IU-X^NaYrt`"Xqu=rhdf6Ufs8U/hec2gjs8Tudg%YLJQVCKYrs5BFs8TH[ir:%trrHH?
+rr3)XK.X(srr__K4b3P3')7TQs8SOJoCS*2s8S@Gp@4-5oD\pMK.O&%rs%YBXT/<NSGW<iX)\0u
+KX^RQ"4)95eGfIPf7(a>s(nt<rrW1aM>[ATqNHs;rr='js+13Bs*t~>
+!$2(=!H#(=rr^Pl*7b&g$.aRGs8Ql>s,N-=rs.@Es8U#Z'(>Mn!krU^rVmJLFlEAaEl.jnF6<M^
+FN+8a_=IU-X^NaYrt`"Xqu=rhdf6Ufs8U/hec2gjs8Tudg%YLJQVCKYrs5BFs8TH[ir:%trrHH?
+rr3)XK.X(srr__K4b3P3')7TQs8SOJoCS*2s8S@Gp@4-5oD\pMK.O&%rs%YBXT/<NSGW<iX)\0u
+KX^RQ"4)95eGfIPf7(a>s(nt<rrW1aM>[ATqNHs;rr='js+13Bs*t~>
+!$2(=!H#(>rrgYtbt$=`rrJF?rr3,`2uenurr35Z5QATNg+Dderr]f^iZ8!t!Go">rt)eNs0@.>
+s6"m>s'*=#It+Hlrr]'Il7;i5!CGQ?rsY$Js.+]?s3cD>s"Wm>rrIY?q#:GWNQ5&trr=)<rrZoB
+ruM(<#[Y7Fs5ea>F/es7"T!VCSUg_>!UMN=rsLsInURY>s/:J?gHkH2"QY'BEiT-=!Q&(>rr=):
+rra_Bs06D3rrgYtbt-@`rrJI?rr3#^2Z3RUq]Yk:!V7c;rr='js+13Bs*t~>
+!$2(=!H#(>rrgYtbt$=`rrJF?rr3,`2uenurr35Z5QATNg+Dderr]f^iZ8!t!Go">rt)eNs0@.>
+s6"m>s'*=#It+Hlrr]'Il7;i5!CGQ?rsY$Js.+]?s3cD>s"Wm>rrIY?q#:GWNQ5&trr=)<rrZoB
+ruM(<#[Y7Fs5ea>F/es7"T!VCSUg_>!UMN=rsLsInURY>s/:J?gHkH2"QY'BEiT-=!Q&(>rr=):
+rra_Bs06D3rrgYtbt-@`rrJI?rr3#^2Z3RUq]Yk:!V7c;rr='js+13Bs*t~>
+!$2(=!H#(>rrgYtbt$=`rrJF?rr3,`2uenurr35Z5QATNg+Dderr]f^iZ8!t!Go">rt)eNs0@.>
+s6"m>s'*=#It+Hlrr]'Il7;i5!CGQ?rsY$Js.+]?s3cD>s"Wm>rrIY?q#:GWNQ5&trr=)<rrZoB
+ruM(<#[Y7Fs5ea>F/es7"T!VCSUg_>!UMN=rsLsInURY>s/:J?gHkH2"QY'BEiT-=!Q&(>rr=):
+rra_Bs06D3rrgYtbt-@`rrJI?rr3#^2Z3RUq]Yk:!V7c;rr='js+13Bs*t~>
+!$2(=!H#(>rrh'#s$tW=rrJF?rr3Mk2uenus8W!s5QB*^s"3^5rr^F#ruM(<!Go">rt2kOs8TTE
+@Vr^Vs'&*]SXkV9q#:H,mdVh+rrGI@rr3ChMuWgT@UaQ)s8Oh?rr3"LSG<*dWW'q<rVlj<rVlt4
+[f7BIrs>hR^Am_@]`3&Pq#:KoXT+iArr3#c0`D"[4Q-8(M.0qOli5^*`Vs<HjS8`Xlb3==WrE#!
+_H[!=!$2";#)nfDs06C@raGm,q#:KNc2U>arr3"cL&V)Qk;N8<!HY:<rrI&?qu6X:JcC<$TDsE~>
+!$2(=!H#(>rrh'#s$tW=rrJF?rr3Mk2uenus8W!s5QB*^s"3^5rr^F#ruM(<!Go">rt2kOs8TTE
+@Vr^Vs'&*]SXkV9q#:H,mdVh+rrGI@rr3ChMuWgT@UaQ)s8Oh?rr3"LSG<*dWW'q<rVlj<rVlt4
+[f7BIrs>hR^Am_@]`3&Pq#:KoXT+iArr3#c0`D"[4Q-8(M.0qOli5^*`Vs<HjS8`Xlb3==WrE#!
+_H[!=!$2";#)nfDs06C@raGm,q#:KNc2U>arr3"cL&V)Qk;N8<!HY:<rrI&?qu6X:JcC<$TDsE~>
+!$2(=!H#(>rrh'#s$tW=rrJF?rr3Mk2uenus8W!s5QB*^s"3^5rr^F#ruM(<!Go">rt2kOs8TTE
+@Vr^Vs'&*]SXkV9q#:H,mdVh+rrGI@rr3ChMuWgT@UaQ)s8Oh?rr3"LSG<*dWW'q<rVlj<rVlt4
+[f7BIrs>hR^Am_@]`3&Pq#:KoXT+iArr3#c0`D"[4Q-8(M.0qOli5^*`Vs<HjS8`Xlb3==WrE#!
+_H[!=!$2";#)nfDs06C@raGm,q#:KNc2U>arr3"cL&V)Qk;N8<!HY:<rrI&?qu6X:JcC<$TDsE~>
+!$2(=!H#(;rrGX?rr3"bLAq2Zju<=uGst.lTg/MU!A3d3rr=)<rrHr?rr3SB@K6@PVn/[Ps8UQZ
+pAb/mEq]M6nLOS<!CGQ?rsY$Js8Q96\riK^s"Wm>rrIY?pAY06<W2pt*W?!?B=@iLr^@-E7DAe$
+aT)9]WV-8lSUg_>!UMN=rsV$Js42%u\n]pugCeK)\cGt,pAY/6WrE#!_H[!=!$2";#"7,Ds03St
+rji)5p&>%`eGfIKQ\GG=!TuB=rr=)9rr=)9rr='js+13Bs*t~>
+!$2(=!H#(;rrGX?rr3"bLAq2Zju<=uGst.lTg/MU!A3d3rr=)<rrHr?rr3SB@K6@PVn/[Ps8UQZ
+pAb/mEq]M6nLOS<!CGQ?rsY$Js8Q96\riK^s"Wm>rrIY?pAY06<W2pt*W?!?B=@iLr^@-E7DAe$
+aT)9]WV-8lSUg_>!UMN=rsV$Js42%u\n]pugCeK)\cGt,pAY/6WrE#!_H[!=!$2";#"7,Ds03St
+rji)5p&>%`eGfIKQ\GG=!TuB=rr=)9rr=)9rr='js+13Bs*t~>
+!$2(=!H#(;rrGX?rr3"bLAq2Zju<=uGst.lTg/MU!A3d3rr=)<rrHr?rr3SB@K6@PVn/[Ps8UQZ
+pAb/mEq]M6nLOS<!CGQ?rsY$Js8Q96\riK^s"Wm>rrIY?pAY06<W2pt*W?!?B=@iLr^@-E7DAe$
+aT)9]WV-8lSUg_>!UMN=rsV$Js42%u\n]pugCeK)\cGt,pAY/6WrE#!_H[!=!$2";#"7,Ds03St
+rji)5p&>%`eGfIKQ\GG=!TuB=rr=)9rr=)9rr='js+13Bs*t~>
+!$2(=!H#(;rrGX?rr3"bLAq2Zju<>?l.=P>h*:l<!A3d3rr=)<rrHr?rr3;:@K1_Qq>^5bJc#HJ
+WcIh5!V%]=rrGI@rr3ChMuQnts8VVDV>i::rr3"LSG)s`a&W*<!$2%<")#(BrVkCOls@F+$N;FT
+&f]0jrrJ[@rr3#c0`D"[4Q,F!jT#8@9^LLmk5PA]/+N?5!HP4>rrKu@rVlj<rVm+KW;6JnZ!6So
+rr=)4rrG[?rr3"cL&V)Qk;N;=!E[;<rrGp?qYpO9JcC<$TDsE~>
+!$2(=!H#(;rrGX?rr3"bLAq2Zju<>?l.=P>h*:l<!A3d3rr=)<rrHr?rr3;:@K1_Qq>^5bJc#HJ
+WcIh5!V%]=rrGI@rr3ChMuQnts8VVDV>i::rr3"LSG)s`a&W*<!$2%<")#(BrVkCOls@F+$N;FT
+&f]0jrrJ[@rr3#c0`D"[4Q,F!jT#8@9^LLmk5PA]/+N?5!HP4>rrKu@rVlj<rVm+KW;6JnZ!6So
+rr=)4rrG[?rr3"cL&V)Qk;N;=!E[;<rrGp?qYpO9JcC<$TDsE~>
+!$2(=!H#(;rrGX?rr3"bLAq2Zju<>?l.=P>h*:l<!A3d3rr=)<rrHr?rr3;:@K1_Qq>^5bJc#HJ
+WcIh5!V%]=rrGI@rr3ChMuQnts8VVDV>i::rr3"LSG)s`a&W*<!$2%<")#(BrVkCOls@F+$N;FT
+&f]0jrrJ[@rr3#c0`D"[4Q,F!jT#8@9^LLmk5PA]/+N?5!HP4>rrKu@rVlj<rVm+KW;6JnZ!6So
+rr=)4rrG[?rr3"cL&V)Qk;N;=!E[;<rrGp?qYpO9JcC<$TDsE~>
+!$2(=!H#(;rrGX?rr31nL&(cKfL5W:!ROO=rrF_?qu6]dn,*+b*W?!MG,kK?r3L5>DR'->r2b#?
+o_\Xf!NL2=rrMNXr;Qff/,fJ`:X9"?q2^a>8(IY>q1kI>5McA>pjf.=rrW,cp&+gja&W*<"t]9D
+s7^9?r;QlmA&J?FoE&p)r;Q]tq<dtTrrJ[@rr3St47iLPoL@j%6hC?Xo02Ho62gfa/+NW=!qu$Y
+r;Qe<WrE#&`aJN?s7%Z>rrVn+_#F?7Z#'C=!$2%<!qYgXr;QdgeGfIPSqQq>s4Kd=rrVdXkPY>]
+o/c@:rr='js+13Bs*t~>
+!$2(=!H#(;rrGX?rr31nL&(cKfL5W:!ROO=rrF_?qu6]dn,*+b*W?!MG,kK?r3L5>DR'->r2b#?
+o_\Xf!NL2=rrMNXr;Qff/,fJ`:X9"?q2^a>8(IY>q1kI>5McA>pjf.=rrW,cp&+gja&W*<"t]9D
+s7^9?r;QlmA&J?FoE&p)r;Q]tq<dtTrrJ[@rr3St47iLPoL@j%6hC?Xo02Ho62gfa/+NW=!qu$Y
+r;Qe<WrE#&`aJN?s7%Z>rrVn+_#F?7Z#'C=!$2%<!qYgXr;QdgeGfIPSqQq>s4Kd=rrVdXkPY>]
+o/c@:rr='js+13Bs*t~>
+!$2(=!H#(;rrGX?rr31nL&(cKfL5W:!ROO=rrF_?qu6]dn,*+b*W?!MG,kK?r3L5>DR'->r2b#?
+o_\Xf!NL2=rrMNXr;Qff/,fJ`:X9"?q2^a>8(IY>q1kI>5McA>pjf.=rrW,cp&+gja&W*<"t]9D
+s7^9?r;QlmA&J?FoE&p)r;Q]tq<dtTrrJ[@rr3St47iLPoL@j%6hC?Xo02Ho62gfa/+NW=!qu$Y
+r;Qe<WrE#&`aJN?s7%Z>rrVn+_#F?7Z#'C=!$2%<!qYgXr;QdgeGfIPSqQq>s4Kd=rrVdXkPY>]
+o/c@:rr='js+13Bs*t~>
+!$2(=!H#(>rseACd5nGXg&M)7QI4)Lrr3GhdF$63s8V?2d3Z]XhYmHT,o6L[%Hc'c(t$ais8R#B
+dC<ffrs3;IdBd]is*:UCd/f_qrr3&r!'pP`%GoLO,LOp&s8Pd:d@H*mrrbg=d?p!orrbd<d?9go
+rrULHCB"5Ef$U6^dF%I`qF/fZJueqOr;P(EIBrbM!HY7=ruS!:PlLd^dF!JXdF%ahf3m"RB?pPW
+d:L_Q@b(M<=T-VJ9(W&^'70_os8Vc>d9V[XdJs6SHdU58nG`FgJ"HW=!P`^Sd/ZZ7rt8-.l2UeO
+dEt%YdFnR"P*XMC7fE>f<64t:!DLl9rr='js+13Bs*t~>
+!$2(=!H#(>rseACd5nGXg&M)7QI4)Lrr3GhdF$63s8V?2d3Z]XhYmHT,o6L[%Hc'c(t$ais8R#B
+dC<ffrs3;IdBd]is*:UCd/f_qrr3&r!'pP`%GoLO,LOp&s8Pd:d@H*mrrbg=d?p!orrbd<d?9go
+rrULHCB"5Ef$U6^dF%I`qF/fZJueqOr;P(EIBrbM!HY7=ruS!:PlLd^dF!JXdF%ahf3m"RB?pPW
+d:L_Q@b(M<=T-VJ9(W&^'70_os8Vc>d9V[XdJs6SHdU58nG`FgJ"HW=!P`^Sd/ZZ7rt8-.l2UeO
+dEt%YdFnR"P*XMC7fE>f<64t:!DLl9rr='js+13Bs*t~>
+!$2(=!H#(>rseACd5nGXg&M)7QI4)Lrr3GhdF$63s8V?2d3Z]XhYmHT,o6L[%Hc'c(t$ais8R#B
+dC<ffrs3;IdBd]is*:UCd/f_qrr3&r!'pP`%GoLO,LOp&s8Pd:d@H*mrrbg=d?p!orrbd<d?9go
+rrULHCB"5Ef$U6^dF%I`qF/fZJueqOr;P(EIBrbM!HY7=ruS!:PlLd^dF!JXdF%ahf3m"RB?pPW
+d:L_Q@b(M<=T-VJ9(W&^'70_os8Vc>d9V[XdJs6SHdU58nG`FgJ"HW=!P`^Sd/ZZ7rt8-.l2UeO
+dEt%YdFnR"P*XMC7fE>f<64t:!DLl9rr='js+13Bs*t~>
+!$2(=#&Sap[f?B>r(@$+rr3(Z92#3^rr\``92GQe!3,lh!.OnH!G*,IrrAVf9)skKrrR(2B)MZ1
+BM31?rr@9B9)sbGrrW'`5lUc`JG`%?W;ceu:eQK?rr`6n98`]J"8GM^OSo+[h)/sHrr?O+9*WB8
+s5lsIUAk/mp&7SaW;?MpSAY=*"i)ONs8Vlk9*XGVs2[i+_#=98_FmPArr2uGrC[.KrVm%q*5DOR
+gAFSD:&b.pX%Q+BrVlnp]);R.Zi*Msrr)j#KFd>Is1J8&!,MT6"-#rHr;HWrLY2M(!J0,*rr^WT
+Zl=SrJcDABJ,~>
+!$2(=#&Sap[f?B>r(@$+rr3(Z92#3^rr\``92GQe!3,lh!.OnH!G*,IrrAVf9)skKrrR(2B)MZ1
+BM31?rr@9B9)sbGrrW'`5lUc`JG`%?W;ceu:eQK?rr`6n98`]J"8GM^OSo+[h)/sHrr?O+9*WB8
+s5lsIUAk/mp&7SaW;?MpSAY=*"i)ONs8Vlk9*XGVs2[i+_#=98_FmPArr2uGrC[.KrVm%q*5DOR
+gAFSD:&b.pX%Q+BrVlnp]);R.Zi*Msrr)j#KFd>Is1J8&!,MT6"-#rHr;HWrLY2M(!J0,*rr^WT
+Zl=SrJcDABJ,~>
+!$2(=#&Sap[f?B>r(@$+rr3(Z92#3^rr\``92GQe!3,lh!.OnH!G*,IrrAVf9)skKrrR(2B)MZ1
+BM31?rr@9B9)sbGrrW'`5lUc`JG`%?W;ceu:eQK?rr`6n98`]J"8GM^OSo+[h)/sHrr?O+9*WB8
+s5lsIUAk/mp&7SaW;?MpSAY=*"i)ONs8Vlk9*XGVs2[i+_#=98_FmPArr2uGrC[.KrVm%q*5DOR
+gAFSD:&b.pX%Q+BrVlnp]);R.Zi*Msrr)j#KFd>Is1J8&!,MT6"-#rHr;HWrLY2M(!J0,*rr^WT
+Zl=SrJcDABJ,~>
+!$2(="/omHD;5'j*S^Spgd'Wo!O?IprrIq?g]%:[^YAbh1[3cr"/0CAFFjICs.B=A~>
+!$2(="/omHD;5'j*S^Spgd'Wo!O?IprrIq?g]%:[^YAbh1[3cr"/0CAFFjICs.B=A~>
+!$2(="/omHD;5'j*S^Spgd'Wo!O?IprrIq?g]%:[^YAbh1[3cr"/0CAFFjICs.B=A~>
+!$0Vi!KE/qrrW+FUtktOk'abprrUPsbhW4"\q/eprrT!Kon!-os+14Fs*t~>
+!$0Vi!KE/qrrW+FUtktOk'abprrUPsbhW4"\q/eprrT!Kon!-os+14Fs*t~>
+!$0Vi!KE/qrrW+FUtktOk'abprrUPsbhW4"\q/eprrT!Kon!-os+14Fs*t~>
+!$0Vi!O2LWrrN)%g]%<B^>&YghTjmU!RKTVrrKn$JcC<$JcGKEJ,~>
+!$0Vi!O2LWrrN)%g]%<B^>&YghTjmU!RKTVrrKn$JcC<$JcGKEJ,~>
+!$0Vi!O2LWrrN)%g]%<B^>&YghTjmU!RKTVrrKn$JcC<$JcGKEJ,~>
+!$.X1!SDh6rrC7A5QK%#rrJ>#p&>&uU\t,l_>Z\"ec#LKV6GONJcC<$hZ&*~>
+!$.X1!SDh6rrC7A5QK%#rrJ>#p&>&uU\t,l_>Z\"ec#LKV6GONJcC<$hZ&*~>
+!$.X1!SDh6rrC7A5QK%#rrJ>#p&>&uU\t,l_>Z\"ec#LKV6GONJcC<$hZ&*~>
+!Zh<Ir$)>4s8S:^o)Ac,,O4Up!^%].ec,^;8E]sO"TCUbZAJS\"Z/[Js,sDOrs&<E>Q=_N_"[j3
+dV8`9[J'V'\lIP@p@\FeS;d+>rrC+<,66HsrrV_<Y5SA'o\A4na8Q#>mVojjs+13$s53j,~>
+!Zh<Ir$)>4s8S:^o)Ac,,O4Up!^%].ec,^;8E]sO"TCUbZAJS\"Z/[Js,sDOrs&<E>Q=_N_"[j3
+dV8`9[J'V'\lIP@p@\FeS;d+>rrC+<,66HsrrV_<Y5SA'o\A4na8Q#>mVojjs+13$s53j,~>
+!Zh<Ir$)>4s8S:^o)Ac,,O4Up!^%].ec,^;8E]sO"TCUbZAJS\"Z/[Js,sDOrs&<E>Q=_N_"[j3
+dV8`9[J'V'\lIP@p@\FeS;d+>rrC+<,66HsrrV_<Y5SA'o\A4na8Q#>mVojjs+13$s53j,~>
+!Zh=>rV6]S3WK-WF0PQ6rrW)nrp9Xf5Ml4O?/GS],PqE@f1,]<&cNmPVK`(?2WiUik^]N0s6V]9
+rrs_D_.`%J]_DF/b>J:\WV6>m\mk:2!r%S@rr3#S7/co^0_,/B!f!3?qu6[sa8Gr=GH(Ijs+13$
+s5<p-~>
+!Zh=>rV6]S3WK-WF0PQ6rrW)nrp9Xf5Ml4O?/GS],PqE@f1,]<&cNmPVK`(?2WiUik^]N0s6V]9
+rrs_D_.`%J]_DF/b>J:\WV6>m\mk:2!r%S@rr3#S7/co^0_,/B!f!3?qu6[sa8Gr=GH(Ijs+13$
+s5<p-~>
+!Zh=>rV6]S3WK-WF0PQ6rrW)nrp9Xf5Ml4O?/GS],PqE@f1,]<&cNmPVK`(?2WiUik^]N0s6V]9
+rrs_D_.`%J]_DF/b>J:\WV6>m\mk:2!r%S@rr3#S7/co^0_,/B!f!3?qu6[sa8Gr=GH(Ijs+13$
+s5<p-~>
+!$2";!S0d=rrR#CrV$6nilfOArVllVrlY;kr;QfHd/<nK3o^1dHN4$"rlYl+s8V$Is3AgBs8Us!
+bPB?;rr2uLrlYT(s8Ufr(X("[rVlj<qu7B09`MVcs8Q9ls2[$bs8UT^-HjTqrr2uBrlY`7s8Pal
+s0OVbs8UL?rlY<.rr3,OFoRN7pAY0)@f?<-qpkZF"k`YNs/C)<rrM+mrr3&R:%\Da!F<J;rrH-?
+qu6[kd"24Js+14.s*t~>
+!$2";!S0d=rrR#CrV$6nilfOArVllVrlY;kr;QfHd/<nK3o^1dHN4$"rlYl+s8V$Is3AgBs8Us!
+bPB?;rr2uLrlYT(s8Ufr(X("[rVlj<qu7B09`MVcs8Q9ls2[$bs8UT^-HjTqrr2uBrlY`7s8Pal
+s0OVbs8UL?rlY<.rr3,OFoRN7pAY0)@f?<-qpkZF"k`YNs/C)<rrM+mrr3&R:%\Da!F<J;rrH-?
+qu6[kd"24Js+14.s*t~>
+!$2";!S0d=rrR#CrV$6nilfOArVllVrlY;kr;QfHd/<nK3o^1dHN4$"rlYl+s8V$Is3AgBs8Us!
+bPB?;rr2uLrlYT(s8Ufr(X("[rVlj<qu7B09`MVcs8Q9ls2[$bs8UT^-HjTqrr2uBrlY`7s8Pal
+s0OVbs8UL?rlY<.rr3,OFoRN7pAY0)@f?<-qpkZF"k`YNs/C)<rrM+mrr3&R:%\Da!F<J;rrH-?
+qu6[kd"24Js+14.s*t~>
+!$2%<!luR\qu6\6YP.tuSPTp\rr3#PGQ(D>@)`*E!@8NZrrG1?rVloGD>m?GC!H[BQ*41aD:&;e
+4]%qNE7=jeFoG2CF4L;C:^KrpQMpg`*UNe4CeG:8:p'ct\P`?&!J]_2rrF"/r_NWSlgOiS\mkX<
+!UMN=rrkOCs8T'>q>UNBCZ53k!F<J;rrH-?qu6[kd"24Js+14.s*t~>
+!$2%<!luR\qu6\6YP.tuSPTp\rr3#PGQ(D>@)`*E!@8NZrrG1?rVloGD>m?GC!H[BQ*41aD:&;e
+4]%qNE7=jeFoG2CF4L;C:^KrpQMpg`*UNe4CeG:8:p'ct\P`?&!J]_2rrF"/r_NWSlgOiS\mkX<
+!UMN=rrkOCs8T'>q>UNBCZ53k!F<J;rrH-?qu6[kd"24Js+14.s*t~>
+!$2%<!luR\qu6\6YP.tuSPTp\rr3#PGQ(D>@)`*E!@8NZrrG1?rVloGD>m?GC!H[BQ*41aD:&;e
+4]%qNE7=jeFoG2CF4L;C:^KrpQMpg`*UNe4CeG:8:p'ct\P`?&!J]_2rrF"/r_NWSlgOiS\mkX<
+!UMN=rrkOCs8T'>q>UNBCZ53k!F<J;rrH-?qu6[kd"24Js+14.s*t~>
+!$2%<".O.FUAOrj*VB@42Wk#=!JQm>rrLY@rVlnMHN*pI`0)9pTDnikCp<p=&\DKOs$g8gs0HG>
+>J^=[s/L,:rrJj?rr2s=qYpO9li."Q0`:qO+T;<>!Nj`DrrEjhrr3"*^@2(&\mkX<$hSPIs8TTP
+mJm3cF8Gn<b'_b:rrHE?qu6[sa8>l;:!eIkJcC<$i;\<~>
+!$2%<".O.FUAOrj*VB@42Wk#=!JQm>rrLY@rVlnMHN*pI`0)9pTDnikCp<p=&\DKOs$g8gs0HG>
+>J^=[s/L,:rrJj?rr2s=qYpO9li."Q0`:qO+T;<>!Nj`DrrEjhrr3"*^@2(&\mkX<$hSPIs8TTP
+mJm3cF8Gn<b'_b:rrHE?qu6[sa8>l;:!eIkJcC<$i;\<~>
+!$2%<".O.FUAOrj*VB@42Wk#=!JQm>rrLY@rVlnMHN*pI`0)9pTDnikCp<p=&\DKOs$g8gs0HG>
+>J^=[s/L,:rrJj?rr2s=qYpO9li."Q0`:qO+T;<>!Nj`DrrEjhrr3"*^@2(&\mkX<$hSPIs8TTP
+mJm3cF8Gn<b'_b:rrHE?qu6[sa8>l;:!eIkJcC<$i;\<~>
+!$1t:!V[r=rrJ%?o`"qMk5PA]KpVf=!S'a9rrg)o++'CSrrHr?rr3\E@K6?sf)Pd*])Q!NruM->
+ErZ0&rcJ66HN*pE*W#d9*UE_+m4eJ<!1Ee.!5nR2!$2%<!FEM/rrK]?r;QrH:B1>t_uBZ:WH7t:
+!mH/@qYpS%^\e$3<QG":!DUpls+13$s5<p-~>
+!$1t:!V[r=rrJ%?o`"qMk5PA]KpVf=!S'a9rrg)o++'CSrrHr?rr3\E@K6?sf)Pd*])Q!NruM->
+ErZ0&rcJ66HN*pE*W#d9*UE_+m4eJ<!1Ee.!5nR2!$2%<!FEM/rrK]?r;QrH:B1>t_uBZ:WH7t:
+!mH/@qYpS%^\e$3<QG":!DUpls+13$s5<p-~>
+!$1t:!V[r=rrJ%?o`"qMk5PA]KpVf=!S'a9rrg)o++'CSrrHr?rr3\E@K6?sf)Pd*])Q!NruM->
+ErZ0&rcJ66HN*pE*W#d9*UE_+m4eJ<!1Ee.!5nR2!$2%<!FEM/rrK]?r;QrH:B1>t_uBZ:WH7t:
+!mH/@qYpS%^\e$3<QG":!DUpls+13$s5<p-~>
+!$1t:!V[r>rrRqDqt0mh2Wk#=!JQm>[email protected]/lXCp<p="hS4Cs$kT;rs;oGruM->ErV/d
+rhfd3HN*pE*W#d9*UE_+m4eG;!3#mp!IDl_rr=)<rrHH?n,EF"@f660mZ3s:MsC<A!NC/<rrU\m
+deWnD?,-::!EI2;rrGj@JcC<$JcF[.J,~>
+!$1t:!V[r>rrRqDqt0mh2Wk#=!JQm>[email protected]/lXCp<p="hS4Cs$kT;rs;oGruM->ErV/d
+rhfd3HN*pE*W#d9*UE_+m4eG;!3#mp!IDl_rr=)<rrHH?n,EF"@f660mZ3s:MsC<A!NC/<rrU\m
+deWnD?,-::!EI2;rrGj@JcC<$JcF[.J,~>
+!$1t:!V[r>rrRqDqt0mh2Wk#=!JQm>[email protected]/lXCp<p="hS4Cs$kT;rs;oGruM->ErV/d
+rhfd3HN*pE*W#d9*UE_+m4eG;!3#mp!IDl_rr=)<rrHH?n,EF"@f660mZ3s:MsC<A!NC/<rrU\m
+deWnD?,-::!EI2;rrGj@JcC<$JcF[.J,~>
+"!.ESQ2^dapEon>!f3H@oD\hLk5PA]KpVf=!S'a>rrH1pr;QdYir/lXCp<p="hS4Cs$kT;rsi8L
+ruM->ErT(\s8U@jHN*pI*WQ/ET`4rl\mt+,#jj>Gs-L=s;uQ^q!H5+8rr=)<rrHH?pAY3*40AJ]
+!P2b;rr]NAGGY9<!pbT@rr3&[?J>5T!h#5@qu6[sa8Gr=N19J;rrRp:iIV#[s+146s*t~>
+"!.ESQ2^dapEon>!f3H@oD\hLk5PA]KpVf=!S'a>rrH1pr;QdYir/lXCp<p="hS4Cs$kT;rsi8L
+ruM->ErT(\s8U@jHN*pI*WQ/ET`4rl\mt+,#jj>Gs-L=s;uQ^q!H5+8rr=)<rrHH?pAY3*40AJ]
+!P2b;rr]NAGGY9<!pbT@rr3&[?J>5T!h#5@qu6[sa8Gr=N19J;rrRp:iIV#[s+146s*t~>
+"!.ESQ2^dapEon>!f3H@oD\hLk5PA]KpVf=!S'a>rrH1pr;QdYir/lXCp<p="hS4Cs$kT;rsi8L
+ruM->ErT(\s8U@jHN*pI*WQ/ET`4rl\mt+,#jj>Gs-L=s;uQ^q!H5+8rr=)<rrHH?pAY3*40AJ]
+!P2b;rr]NAGGY9<!pbT@rr3&[?J>5T!h#5@qu6[sa8Gr=N19J;rrRp:iIV#[s+146s*t~>
+"!.F7L]/>!-0>1,OHfM3rs4/s"rf])s7("+*W\IhrVlmE(B"153o^,<!Ua.j*X,I.s8PR>r;R72
+_>iTrWcJ.*7ii^IO*^g*"P=b:D".H(!U2E,rs8S9*Zg(.s5bC&*W_5mq#:=7rVln)^@qR.V#ZbY
+rrK]?qu6clO,!<)rrJ(@rr3#R#lG_DoD\ajH_UB:!EI2<rrHl?qu6_+!8)l&JcC<$kl6/~>
+"!.F7L]/>!-0>1,OHfM3rs4/s"rf])s7("+*W\IhrVlmE(B"153o^,<!Ua.j*X,I.s8PR>r;R72
+_>iTrWcJ.*7ii^IO*^g*"P=b:D".H(!U2E,rs8S9*Zg(.s5bC&*W_5mq#:=7rVln)^@qR.V#ZbY
+rrK]?qu6clO,!<)rrJ(@rr3#R#lG_DoD\ajH_UB:!EI2<rrHl?qu6_+!8)l&JcC<$kl6/~>
+"!.F7L]/>!-0>1,OHfM3rs4/s"rf])s7("+*W\IhrVlmE(B"153o^,<!Ua.j*X,I.s8PR>r;R72
+_>iTrWcJ.*7ii^IO*^g*"P=b:D".H(!U2E,rs8S9*Zg(.s5bC&*W_5mq#:=7rVln)^@qR.V#ZbY
+rrK]?qu6clO,!<)rrJ(@rr3#R#lG_DoD\ajH_UB:!EI2<rrHl?qu6_+!8)l&JcC<$kl6/~>
+!Zh=@r;Q]q!<2Qhr;Q]qr;QWos8Mrr!<2lqr;Q]q!<2lq!<2or!ri6"rVcitrr)lrrqucsct2PC
+rr2osr;QHj!<2rs!<2Wj!Jcp<rrg.X*?E#qs8W)qrrW1K\,QF)rr2ouUltZU"L]<k*6nH^!L9>S
+s8W(Ls+13$s6'E4~>
+!Zh=@r;Q]q!<2Qhr;Q]qr;QWos8Mrr!<2lqr;Q]q!<2lq!<2or!ri6"rVcitrr)lrrqucsct2PC
+rr2osr;QHj!<2rs!<2Wj!Jcp<rrg.X*?E#qs8W)qrrW1K\,QF)rr2ouUltZU"L]<k*6nH^!L9>S
+s8W(Ls+13$s6'E4~>
+!Zh=@r;Q]q!<2Qhr;Q]qr;QWos8Mrr!<2lqr;Q]q!<2lq!<2or!ri6"rVcitrr)lrrqucsct2PC
+rr2osr;QHj!<2rs!<2Wj!Jcp<rrg.X*?E#qs8W)qrrW1K\,QF)rr2ouUltZU"L]<k*6nH^!L9>S
+s8W(Ls+13$s6'E4~>
+!$-XjhZ!ZCKWKb)JcC<$W;hA~>
+!$-XjhZ!ZCKWKb)JcC<$W;hA~>
+!$-XjhZ!ZCKWKb)JcC<$W;hA~>
+!$-XjhZ!VoV1JYts+13Js*t~>
+!$-XjhZ!VoV1JYts+13Js*t~>
+!$-XjhZ!VoV1JYts+13Js*t~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+"!.EGKDtlRpH/DRMZ3VYn3d5LP5bIij\#Q@RfEEb3<0$WGlA]a6N$i_#?;`a8,rVfC&T+R;>l(G
+JcC<$JcE=]J,~>
+"!.EGKDtlRpH/DRMZ3VYn3d5LP5bIij\#Q@RfEEb3<0$WGlA]a6N$i_#?;`a8,rVfC&T+R;>l(G
+JcC<$JcE=]J,~>
+"!.EGKDtlRpH/DRMZ3VYn3d5LP5bIij\#Q@RfEEb3<0$WGlA]a6N$i_#?;`a8,rVfC&T+R;>l(G
+JcC<$JcE=]J,~>
+"!.E>FoMCDpEop4IfB?]mOnO*LB%;6+?0=\NrT.U,6.[sQi@!ceO]]>#=@//1]RJsW;cet`*%Zk
+JcC<$JcE=]J,~>
+"!.E>FoMCDpEop4IfB?]mOnO*LB%;6+?0=\NrT.U,6.[sQi@!ceO]]>#=@//1]RJsW;cet`*%Zk
+JcC<$JcE=]J,~>
+"!.E>FoMCDpEop4IfB?]mOnO*LB%;6+?0=\NrT.U,6.[sQi@!ceO]]>#=@//1]RJsW;cet`*%Zk
+JcC<$JcE=]J,~>
+"!.E>FoMCDpEop4?iIkU[4_KFLB#Xsj#@!uNrT.U,6.[sQi@!ceO]]>#=@//1]RJsJ,[7bPZ`S;
+JcC<$JcE=]J,~>
+"!.E>FoMCDpEop4?iIkU[4_KFLB#Xsj#@!uNrT.U,6.[sQi@!ceO]]>#=@//1]RJsJ,[7bPZ`S;
+JcC<$JcE=]J,~>
+"!.E>FoMCDpEop4?iIkU[4_KFLB#Xsj#@!uNrT.U,6.[sQi@!ceO]]>#=@//1]RJsJ,[7bPZ`S;
+JcC<$JcE=]J,~>
+"!.E>FoMCDpEop4/cJof<%e3:L;e#pju<=#NrT.U,6.[sQi@!neO]`?`bsu(1]RJs4T/F`P_&jc
+s+13$s185\~>
+"!.E>FoMCDpEop4/cJof<%e3:L;e#pju<=#NrT.U,6.[sQi@!neO]`?`bsu(1]RJs4T/F`P_&jc
+s+13$s185\~>
+"!.E>FoMCDpEop4/cJof<%e3:L;e#pju<=#NrT.U,6.[sQi@!neO]`?`bsu(1]RJs4T/F`P_&jc
+s+13$s185\~>
+"!.E>FoMCDN@+[tIfB?]mOnO*7t'7oju<=#NrT.U,6.[sQi@!neO]`?@(lU)1]RJsW.Fu"s+13$
+s0_lW~>
+"!.E>FoMCDN@+[tIfB?]mOnO*7t'7oju<=#NrT.U,6.[sQi@!neO]`?@(lU)1]RJsW.Fu"s+13$
+s0_lW~>
+"!.E>FoMCDN@+[tIfB?]mOnO*7t'7oju<=#NrT.U,6.[sQi@!neO]`?@(lU)1]RJsW.Fu"s+13$
+s0_lW~>
+"!.EqJc9EfN$eRsIfB?]mOnO*:>,[Iju<=#4DS_H%ZgY6I/[miEjG_O-r4Vf'q,*@H2_RZNIh+\
+s+13$s1//[~>
+"!.EqJc9EfN$eRsIfB?]mOnO*:>,[Iju<=#4DS_H%ZgY6I/[miEjG_O-r4Vf'q,*@H2_RZNIh+\
+s+13$s1//[~>
+"!.EqJc9EfN$eRsIfB?]mOnO*:>,[Iju<=#4DS_H%ZgY6I/[miEjG_O-r4Vf'q,*@H2_RZNIh+\
+s+13$s1//[~>
+"!.FAg]"G\n0\1pfDbdQqS3'fgA_*SpVQscrNQKo5QCc2rilU?s8RLcrilTd;#gR>rilI@JcC<$
+JcC<$\c70~>
+"!.FAg]"G\n0\1pfDbdQqS3'fgA_*SpVQscrNQKo5QCc2rilU?s8RLcrilTd;#gR>rilI@JcC<$
+JcC<$\c70~>
+"!.FAg]"G\n0\1pfDbdQqS3'fgA_*SpVQscrNQKo5QCc2rilU?s8RLcrilTd;#gR>rilI@JcC<$
+JcC<$\c70~>
+"!.EOOT,7\pEo5+!Sp!8rrM9Lrr3#lh1>TWs+13$s0DZT~>
+"!.EOOT,7\pEo5+!Sp!8rrM9Lrr3#lh1>TWs+13$s0DZT~>
+"!.EOOT,7\pEo5+!Sp!8rrM9Lrr3#lh1>TWs+13$s0DZT~>
+"!.F:N;agu,jt!u!W;G<s+13$s+13Es*t~>
+"!.F:N;agu,jt!u!W;G<s+13$s+13Es*t~>
+"!.F:N;agu,jt!u!W;G<s+13$s+13Es*t~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$1k7!Tr3hs+13$s+130s*t~>
+!$1k7!Tr3hs+13$s+130s*t~>
+!$1k7!Tr3hs+13$s+130s*t~>
+!$1k7!M=gls+13$s+130s*t~>
+!$1k7!M=gls+13$s+130s*t~>
+!$1k7!M=gls+13$s+130s*t~>
+!$1k7!M=gls+13$s+130s*t~>
+!$1k7!M=gls+13$s+130s*t~>
+!$1k7!M=gls+13$s+130s*t~>
+"!.FA\c-1DC]FF,IfB?Jon%bpJcC<$JcCo5J,~>
+"!.FA\c-1DC]FF,IfB?Jon%bpJcC<$JcCo5J,~>
+"!.FA\c-1DC]FF,IfB?Jon%bpJcC<$JcCo5J,~>
+"!.EWHiCG[ZXj*.IfKF\:r@kaJcC<$JcCo5J,~>
+"!.EWHiCG[ZXj*.IfKF\:r@kaJcC<$JcCo5J,~>
+"!.EWHiCG[ZXj*.IfKF\:r@kaJcC<$JcCo5J,~>
+"!.E>FoMCHpEop4IX`oYeq*jPs+13$s,m>3~>
+"!.E>FoMCHpEop4IX`oYeq*jPs+13$s,m>3~>
+"!.E>FoMCHpEop4IX`oYeq*jPs+13$s,m>3~>
+"!.E>FoMCGpEop4%[H&MJcC<$JcC<$OT0h~>
+"!.E>FoMCGpEop4%[H&MJcC<$JcC<$OT0h~>
+"!.E>FoMCGpEop4%[H&MJcC<$JcC<$OT0h~>
+"!.E>FoMCHpEop4IXWfXf7EsQs+13$s,m>3~>
+"!.E>FoMCHpEop4IXWfXf7EsQs+13$s,m>3~>
+"!.E>FoMCHpEop4IXWfXf7EsQs+13$s,m>3~>
+"!.EWHiCJ\Zt'-.IfKF[:W.haJcC<$JcCo5J,~>
+"!.EWHiCJ\Zt'-.IfKF[:W.haJcC<$JcCo5J,~>
+"!.EWHiCJ\Zt'-.IfKF[:W.haJcC<$JcCo5J,~>
+"!.FA\Gg"ACB+=QZ2Xb(omhVnJcC<$JcCo5J,~>
+"!.FA\Gg"ACB+=QZ2Xb(omhVnJcC<$JcCo5J,~>
+"!.FA\Gg"ACB+=QZ2Xb(omhVnJcC<$JcCo5J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$1>(!5\M"!/Q4+JcC<$JcDMFJ,~>
+!$1>(!5\M"!/Q4+JcC<$JcDMFJ,~>
+!$1>(!5\M"!/Q4+JcC<$JcDMFJ,~>
+!$1>(!NTW+fE%aPJcC<$JcC<$U]5i~>
+!$1>(!NTW+fE%aPJcC<$JcC<$U]5i~>
+!$1>(!NTW+fE%aPJcC<$JcC<$U]5i~>
+!$2";!UqE7rrS:OqY'piX)\(;!Phrks+13$s+13Fs*t~>
+!$2";!UqE7rrS:OqY'piX)\(;!Phrks+13$s+13Fs*t~>
+!$2";!UqE7rrS:OqY'piX)\(;!Phrks+13$s+13Fs*t~>
+!$2%<!o?]]rVlqMU\FN_!NU5<rrKo?JcC<$JcC<$U]5i~>
+!$2%<!o?]]rVlqMU\FN_!NU5<rrKo?JcC<$JcC<$U]5i~>
+!$2%<!o?]]rVlqMU\FN_!NU5<rrKo?JcC<$JcC<$U]5i~>
+!$2(="4M)A*W5p=I\Q`7rrK0?r;Qf5>_2p+s+13$s.fUE~>
+!$2(="4M)A*W5p=I\Q`7rrK0?r;Qf5>_2p+s+13$s.fUE~>
+!$2(="4M)A*W5p=I\Q`7rrK0?r;Qf5>_2p+s+13$s.fUE~>
+#9EjEc[=+>*W,j;JsuK7!NU5<rrKo?JcC<$JcC<$U]5i~>
+#9EjEc[=+>*W,j;JsuK7!NU5<rrKo?JcC<$JcC<$U]5i~>
+#9EjEc[=+>*W,j;JsuK7!NU5<rrKo?JcC<$JcC<$U]5i~>
+#9EilJ'@rm*W#d9*VfX8X)\(;!Phrks+13$s+13Fs*t~>
+#9EilJ'@rm*W#d9*VfX8X)\(;!Phrks+13$s+13Fs*t~>
+#9EilJ'@rm*W#d9*VfX8X)\(;!Phrks+13$s+13Fs*t~>
+"!.E>.fNZR$Zu=H!Ht@8rrK0?r;Qf5>_2p+s+13$s.fUE~>
+"!.E>.fNZR$Zu=H!Ht@8rrK0?r;Qf5>_2p+s+13$s.fUE~>
+"!.E>.fNZR$Zu=H!Ht@8rrK0?r;Qf5>_2p+s+13$s.fUE~>
+!Zh=*rP/FLao25@FK>?7rrK0?r;Qf5>_2p+s+13$s.fUE~>
+!Zh=*rP/FLao25@FK>?7rrK0?r;Qf5>_2p+s+13$s.fUE~>
+!Zh=*rP/FLao25@FK>?7rrK0?r;Qf5>_2p+s+13$s.fUE~>
+!$1t:!$2%<!dUd@p\t8pEW#h;^g)HjJcC<$JcDMFJ,~>
+!$1t:!$2%<!dUd@p\t8pEW#h;^g)HjJcC<$JcDMFJ,~>
+!$1t:!$2%<!dUd@p\t8pEW#h;^g)HjJcC<$JcDMFJ,~>
+!$1t:!&FQR!g'2VpAY/oEW#h;^g)HjJcC<$JcDMFJ,~>
+!$1t:!&FQR!g'2VpAY/oEW#h;^g)HjJcC<$JcDMFJ,~>
+!$1t:!&FQR!g'2VpAY/oEW#h;^g)HjJcC<$JcDMFJ,~>
+!$1>(!NU5<rrKo?JcC<$JcC<$U]5i~>
+!$1>(!NU5<rrKo?JcC<$JcC<$U]5i~>
+!$1>(!NU5<rrKo?JcC<$JcC<$U]5i~>
+!$1>(!NSrmXT=#YJcC<$JcC<$U]5i~>
+!$1>(!NSrmXT=#YJcC<$JcC<$U]5i~>
+!$1>(!NSrmXT=#YJcC<$JcC<$U]5i~>
+!$1>(!71L[!2G,FJcC<$JcDMFJ,~>
+!$1>(!71L[!2G,FJcC<$JcDMFJ,~>
+!$1>(!71L[!2G,FJcC<$JcDMFJ,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+!$-XjJcC<$JcC?%J,~>
+%%EndData
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/lib/stdlib/doc/src/ushell2.gif b/lib/stdlib/doc/src/ushell2.gif
new file mode 100644
index 0000000000..273cf2078a
--- /dev/null
+++ b/lib/stdlib/doc/src/ushell2.gif
Binary files differ
diff --git a/lib/stdlib/doc/src/ushell2.ps b/lib/stdlib/doc/src/ushell2.ps
new file mode 100644
index 0000000000..e6db3c2be2
--- /dev/null
+++ b/lib/stdlib/doc/src/ushell2.ps
@@ -0,0 +1,404 @@
+%!PS-Adobe-3.0
+%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
+%%Title: ushell2.ps
+%%CreationDate: Mon Mar 16 09:52:14 2009
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%Pages: 1
+%%BoundingBox: 14 14 468 74
+%%EndComments
+%%BeginProlog
+% Use own dictionary to avoid conflicts
+10 dict begin
+%%EndProlog
+%%Page: 1 1
+% Translate for offset
+14.173228346456694 14.173228346456694 translate
+% Translate to begin of first scanline
+0 59.527559055118118 translate
+453.54330708661422 -59.527559055118118 scale
+% Image geometry
+640 84 8
+% Transformation matrix
+[ 640 0 0 84 0 0 ]
+% Strings to hold RGB-samples per scanline
+/rstr 640 string def
+/gstr 640 string def
+/bstr 640 string def
+{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
+true 3
+%%BeginData: 18704 ASCII Bytes
+colorimage
+J`MCCJ`MCCJ`M=~>
+J`MCCJ`MCCJ`M=~>
+J`MCCJ`MCCJ`M=~>
+!)nG7JO+iQJO+lRJ,~>
+!)nG7JO+iQJO+lRJ,~>
+!)nG7JO+iQJO+lRJ,~>
+!D=/Y=b0_,=b0_.=b$~>
+!D=/Y=b0_,=b0_.=b$~>
+!D=/Y=b0_,=b0_.=b$~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M>rrKOJQ2^i3JcC<$JcFU,J,~>
+!D>M>rrKOJQ2^i3JcC<$JcFU,J,~>
+!D>M>rrKOJQ2^i3JcC<$JcFU,J,~>
+"%t`UXo(ooJ,'$ES](+!!dROKr;Qh<:2'>#!I!f.rr]@2s).a0!581-!+,Ru!6ss3!kNp@p\t9X
+H-uWnOA=s3rrK.0nG`Jtr;QbGr_<HIrVlo/@tFZ2s+14-s*t~>
+"%t`UXo(ooJ,'$ES](+!!dROKr;Qh<:2'>#!I!f.rr]@2s).a0!581-!+,Ru!6ss3!kNp@p\t9X
+H-uWnOA=s3rrK.0nG`Jtr;QbGr_<HIrVlo/@tFZ2s+14-s*t~>
+"%t`UXo(ooJ,'$ES](+!!dROKr;Qh<:2'>#!I!f.rr]@2s).a0!581-!+,Ru!6ss3!kNp@p\t9X
+H-uWnOA=s3rrK.0nG`Jtr;QbGr_<HIrVlo/@tFZ2s+14-s*t~>
+"A:iVM0T!F!8RAL!HP42rrUp#*W5p=fZ5E4rrG1@rr3+VQ2`JQrQbK2q#:?CrQbLB\*j7sS:L>5
+!T-**rrJOjli.%FchRG<!HlihrrIe?nc&Yn]MJP,!J%!FcN0Vnrr3#u++aHCs+14-s*t~>
+"A:iVM0T!F!8RAL!HP42rrUp#*W5p=fZ5E4rrG1@rr3+VQ2`JQrQbK2q#:?CrQbLB\*j7sS:L>5
+!T-**rrJOjli.%FchRG<!HlihrrIe?nc&Yn]MJP,!J%!FcN0Vnrr3#u++aHCs+14-s*t~>
+"A:iVM0T!F!8RAL!HP42rrUp#*W5p=fZ5E4rrG1@rr3+VQ2`JQrQbK2q#:?CrQbLB\*j7sS:L>5
+!T-**rrJOjli.%FchRG<!HlihrrIe?nc&Yn]MJP,!J%!FcN0Vnrr3#u++aHCs+14-s*t~>
+"A:iVM3Rl:!;QQc#kn;uEiSm+o_8@b!;HKb!;HEk!$;%;!V[r6rs;$Hs8VtORf>#.rq$3eoD\lN
+QMU(M!l&CArVllrrU^6ks5-$;rq$<ks8W#qoFLs$rV6Ego^r._r:GB$o^qnX!W2]krs&2ss8)B[
+rVlg+qXdY#o_/=bKUDN+o_/%Z$2J#IrQSC<s'rY>rrMrfrVloH97d+os+14.s*t~>
+"A:iVM3Rl:!;QQc#kn;uEiSm+o_8@b!;HKb!;HEk!$;%;!V[r6rs;$Hs8VtORf>#.rq$3eoD\lN
+QMU(M!l&CArVllrrU^6ks5-$;rq$<ks8W#qoFLs$rV6Ego^r._r:GB$o^qnX!W2]krs&2ss8)B[
+rVlg+qXdY#o_/=bKUDN+o_/%Z$2J#IrQSC<s'rY>rrMrfrVloH97d+os+14.s*t~>
+"A:iVM3Rl:!;QQc#kn;uEiSm+o_8@b!;HKb!;HEk!$;%;!V[r6rs;$Hs8VtORf>#.rq$3eoD\lN
+QMU(M!l&CArVllrrU^6ks5-$;rq$<ks8W#qoFLs$rV6Ego^r._r:GB$o^qnX!W2]krs&2ss8)B[
+rVlg+qXdY#o_/=bKUDN+o_/%Z$2J#IrQSC<s'rY>rrMrfrVloH97d+os+14.s*t~>
+#>7/YM1+"-g&D!PlY?DI$r#a^Efa$E-rf=.k?@j5!F+acrr=,;rrMj@o`#1<s8Sp?s8O^er[7f!
+nFce_n:I\.rrK]@r;QcHr?qfQqqj5tr[7lYrV35f-kO2nd:1^L-n*1:a!_W=-n+farrK!?rr35q
+.KBF2-jn]-rsk_N%3R&fs8Rb&=stdhj7rWTKtI?a"NGcfA%MI8!T6+ls+13$s5<p-~>
+#>7/YM1+"-g&D!PlY?DI$r#a^Efa$E-rf=.k?@j5!F+acrr=,;rrMj@o`#1<s8Sp?s8O^er[7f!
+nFce_n:I\.rrK]@r;QcHr?qfQqqj5tr[7lYrV35f-kO2nd:1^L-n*1:a!_W=-n+farrK!?rr35q
+.KBF2-jn]-rsk_N%3R&fs8Rb&=stdhj7rWTKtI?a"NGcfA%MI8!T6+ls+13$s5<p-~>
+#>7/YM1+"-g&D!PlY?DI$r#a^Efa$E-rf=.k?@j5!F+acrr=,;rrMj@o`#1<s8Sp?s8O^er[7f!
+nFce_n:I\.rrK]@r;QcHr?qfQqqj5tr[7lYrV35f-kO2nd:1^L-n*1:a!_W=-n+farrK!?rr35q
+.KBF2-jn]-rsk_N%3R&fs8Rb&=stdhj7rWTKtI?a"NGcfA%MI8!T6+ls+13$s5<p-~>
+#>7/YM'th_:B(7oI@pN=%,$43E\H&Es1hq?B=@g>!OQP=rr=,;rrMj@o`#1<s8Sp?s8Qb.rr3"H
+U%SEdTmZ8-!P2e8rrb=Bh[t\DrrXkBdn0N<#U90GWZSDDruV1>!J6g6rs#?Ds3C3-.K08I6JDA;
+!EI5>rs"-+b5_Li9CVrcA%M^:\[g>jrrM%@JcC<$JcF[.J,~>
+#>7/YM'th_:B(7oI@pN=%,$43E\H&Es1hq?B=@g>!OQP=rr=,;rrMj@o`#1<s8Sp?s8Qb.rr3"H
+U%SEdTmZ8-!P2e8rrb=Bh[t\DrrXkBdn0N<#U90GWZSDDruV1>!J6g6rs#?Ds3C3-.K08I6JDA;
+!EI5>rs"-+b5_Li9CVrcA%M^:\[g>jrrM%@JcC<$JcF[.J,~>
+#>7/YM'th_:B(7oI@pN=%,$43E\H&Es1hq?B=@g>!OQP=rr=,;rrMj@o`#1<s8Sp?s8Qb.rr3"H
+U%SEdTmZ8-!P2e8rrb=Bh[t\DrrXkBdn0N<#U90GWZSDDruV1>!J6g6rs#?Ds3C3-.K08I6JDA;
+!EI5>rs"-+b5_Li9CVrcA%M^:\[g>jrrM%@JcC<$JcF[.J,~>
+"A:iVM3Ro;!O.@YS-6(rs)P.=rrg,Cs(?9LS,mM@rVlj=qu6]k-M7<@Ff=JZXSVqtI%g96!p59A
+mJd3uA,Q?,li!=U,37WFrVlsOnC'u-rraPCs/L,=rr@3@rrI_@p&>5nG5k:_oI9b=!CGN<rrH-@
+rVlnOR/[*ddS'*0#%P7Eo4(4*rVloV4b<Was+14.s*t~>
+"A:iVM3Ro;!O.@YS-6(rs)P.=rrg,Cs(?9LS,mM@rVlj=qu6]k-M7<@Ff=JZXSVqtI%g96!p59A
+mJd3uA,Q?,li!=U,37WFrVlsOnC'u-rraPCs/L,=rr@3@rrI_@p&>5nG5k:_oI9b=!CGN<rrH-@
+rVlnOR/[*ddS'*0#%P7Eo4(4*rVloV4b<Was+14.s*t~>
+"A:iVM3Ro;!O.@YS-6(rs)P.=rrg,Cs(?9LS,mM@rVlj=qu6]k-M7<@Ff=JZXSVqtI%g96!p59A
+mJd3uA,Q?,li!=U,37WFrVlsOnC'u-rraPCs/L,=rr@3@rrI_@p&>5nG5k:_oI9b=!CGN<rrH-@
+rVlnOR/[*ddS'*0#%P7Eo4(4*rVloV4b<Was+14.s*t~>
+"A:iVM3Rl:!3lHR"G!EMEiT-="Me=CB3bCd!4;_)!$;%;!V[r5rr`6B_HQd8!IUU7rrKi?m/I*t
+A,ZE.oRHgj"!$CZ55kK`/b%4\rVm!Equ<[:qYpSJS+ZdcVKVu6ch&[brrGI?r;QdtaSl,>KUDc=
+!RaX1rrHW@rr3#'D#OA7i&uYkJcC<$i;\<~>
+"A:iVM3Rl:!3lHR"G!EMEiT-="Me=CB3bCd!4;_)!$;%;!V[r5rr`6B_HQd8!IUU7rrKi?m/I*t
+A,ZE.oRHgj"!$CZ55kK`/b%4\rVm!Equ<[:qYpSJS+ZdcVKVu6ch&[brrGI?r;QdtaSl,>KUDc=
+!RaX1rrHW@rr3#'D#OA7i&uYkJcC<$i;\<~>
+"A:iVM3Rl:!3lHR"G!EMEiT-="Me=CB3bCd!4;_)!$;%;!V[r5rr`6B_HQd8!IUU7rrKi?m/I*t
+A,ZE.oRHgj"!$CZ55kK`/b%4\rVm!Equ<[:qYpSJS+ZdcVKVu6ch&[brrGI?r;QdtaSl,>KUDc=
+!RaX1rrHW@rr3#'D#OA7i&uYkJcC<$i;\<~>
+"A:iVM3Ro;!O'H@rrgMCs)P.=rrg,Cs(Ae8rr=,;rrMj@o`#$m+2V4\rrITArr3"HU&P&nhI3>\
+rrVG(_X.:(jbC#[email protected]($+6gRf<<h/b%4\rVm!Equ<[:qYp\MS,`M0q#:PqG,("4kUHK1
+!CGN<rs)QFs6>F?KUDc=!RaX1rrHW@rr3#'D#XG9rb/]js+13$s5<p-~>
+"A:iVM3Ro;!O'H@rrgMCs)P.=rrg,Cs(Ae8rr=,;rrMj@o`#$m+2V4\rrITArr3"HU&P&nhI3>\
+rrVG(_X.:(jbC#[email protected]($+6gRf<<h/b%4\rVm!Equ<[:qYp\MS,`M0q#:PqG,("4kUHK1
+!CGN<rs)QFs6>F?KUDc=!RaX1rrHW@rr3#'D#XG9rb/]js+13$s5<p-~>
+"A:iVM3Ro;!O'H@rrgMCs)P.=rrg,Cs(Ae8rr=,;rrMj@o`#$m+2V4\rrITArr3"HU&P&nhI3>\
+rrVG(_X.:(jbC#[email protected]($+6gRf<<h/b%4\rVm!Equ<[:qYp\MS,`M0q#:PqG,("4kUHK1
+!CGN<rs)QFs6>F?KUDc=!RaX1rrHW@rr3#'D#XG9rb/]js+13$s5<p-~>
+"A:iVM'r6h"*ae$E;i3->g`RlWrE#$^g6ulB)Y-tHN*pIEC1"!K)YcQBL)tmMtR)OX^KZXrrL26
+r^-^;k5PA^aT->ZrrLn@lMgmTJH#QLq5"!W"d:Fsh_(&j6Na7`oU5YF"K_V&WcIt9"Q"/d6bE-n%
+HrJ%lcE#Es8S]Y&NNhhrs-tX6W;80KUDc=!RaX1rrL\]r^-^"f`(mOrZ?^kJcC<$huA3~>
+"A:iVM'r6h"*ae$E;i3->g`RlWrE#$^g6ulB)Y-tHN*pIEC1"!K)YcQBL)tmMtR)OX^KZXrrL26
+r^-^;k5PA^aT->ZrrLn@lMgmTJH#QLq5"!W"d:Fsh_(&j6Na7`oU5YF"K_V&WcIt9"Q"/d6bE-n%
+HrJ%lcE#Es8S]Y&NNhhrs-tX6W;80KUDc=!RaX1rrL\]r^-^"f`(mOrZ?^kJcC<$huA3~>
+"A:iVM'r6h"*ae$E;i3->g`RlWrE#$^g6ulB)Y-tHN*pIEC1"!K)YcQBL)tmMtR)OX^KZXrrL26
+r^-^;k5PA^aT->ZrrLn@lMgmTJH#QLq5"!W"d:Fsh_(&j6Na7`oU5YF"K_V&WcIt9"Q"/d6bE-n%
+HrJ%lcE#Es8S]Y&NNhhrs-tX6W;80KUDc=!RaX1rrL\]r^-^"f`(mOrZ?^kJcC<$huA3~>
+"%t`Umek`?jT#8Drn.G5s8VBUrr3,pkPtSCrn.;4rr2uYrn.;6rr2uWrn.;8p&>'hir&fVh#>t,
+mJ[%dpY"j1rrMuVlMgqTJ#rYL!;QQH"nL[MqW%/Gf`V$Ls7H9C"T&/uoBQ/O"6eFkrV-<pmdL2U
+hZ!NTm/GZ<h>I9Win<2gs6]=TrrMoUn,ECKrn.;5rVlm_hLY]Xs+14-s*t~>
+"%t`Umek`?jT#8Drn.G5s8VBUrr3,pkPtSCrn.;4rr2uYrn.;6rr2uWrn.;8p&>'hir&fVh#>t,
+mJ[%dpY"j1rrMuVlMgqTJ#rYL!;QQH"nL[MqW%/Gf`V$Ls7H9C"T&/uoBQ/O"6eFkrV-<pmdL2U
+hZ!NTm/GZ<h>I9Win<2gs6]=TrrMoUn,ECKrn.;5rVlm_hLY]Xs+14-s*t~>
+"%t`Umek`?jT#8Drn.G5s8VBUrr3,pkPtSCrn.;4rr2uYrn.;6rr2uWrn.;8p&>'hir&fVh#>t,
+mJ[%dpY"j1rrMuVlMgqTJ#rYL!;QQH"nL[MqW%/Gf`V$Ls7H9C"T&/uoBQ/O"6eFkrV-<pmdL2U
+hZ!NTm/GZ<h>I9Win<2gs6]=TrrMoUn,ECKrn.;5rVlm_hLY]Xs+14-s*t~>
+!D>M>rrN#pQ2^jZJcC<$JcFU,J,~>
+!D>M>rrN#pQ2^jZJcC<$JcFU,J,~>
+!D>M>rrN#pQ2^jZJcC<$JcFU,J,~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>NQrrK"krVlo0ipm$KT)JZh\GVHh_h%i=s+13$s/Z0M~>
+!D>NQrrK"krVlo0ipm$KT)JZh\GVHh_h%i=s+13$s/Z0M~>
+!D>NQrrK"krVlo0ipm$KT)JZh\GVHh_h%i=s+13$s/Z0M~>
+!D>NRrrSc-mJ[%d[sIB4rsbK>%<J'>s0[duMN!M0JcC<$JcC<$X8d\~>
+!D>NRrrSc-mJ[%d[sIB4rsbK>%<J'>s0[duMN!M0JcC<$JcC<$X8d\~>
+!D>NRrrSc-mJ[%d[sIB4rsbK>%<J'>s0[duMN!M0JcC<$JcC<$X8d\~>
+#>7/Ys.,5omJQtc^LI'5rsP9XW>)=oS,`M<TDjEAJcC<$JcD\KJ,~>
+#>7/Ys.,5omJQtc^LI'5rsP9XW>)=oS,`M<TDjEAJcC<$JcD\KJ,~>
+#>7/Ys.,5omJQtc^LI'5rsP9XW>)=oS,`M<TDjEAJcC<$JcD\KJ,~>
+#>7/YUPnOomJHnacVF36$>6gI*rkZbs8P:?JcC<$JcC<$VuM8~>
+#>7/YUPnOomJHnacVF36$>6gI*rkZbs8P:?JcC<$JcC<$VuM8~>
+#>7/YUPnOomJHnacVF36$>6gI*rkZbs8P:?JcC<$JcC<$VuM8~>
+#>7/YoCW&:mJ?h`dn0<6"RfFC*W>s:"6h]b:P&Oss+13$s/H$K~>
+#>7/YoCW&:mJ?h`dn0<6"RfFC*W>s:"6h]b:P&Oss+13$s/H$K~>
+#>7/YoCW&:mJ?h`dn0<6"RfFC*W>s:"6h]b:P&Oss+13$s/H$K~>
+!D>NQrrFh@qYpTB:A+Vm7Kc'As8V@[j+75]s+13$s/5mI~>
+!D>NQrrFh@qYpTB:A+Vm7Kc'As8V@[j+75]s+13$s/5mI~>
+!D>NQrrFh@qYpTB:A+Vm7Kc'As8V@[j+75]s+13$s/5mI~>
+!D>NQrrFh@qu6]>;Y0nm*o1A]s'3Bks+13$s+13Is*t~>
+!D>NQrrFh@qu6]>;Y0nm*o1A]s'3Bks+13$s+13Is*t~>
+!D>NQrrFh@qu6]>;Y0nm*o1A]s'3Bks+13$s+13Is*t~>
+!D>NQrrFh@r;Qi1B);6$$-D</*riT\s'3D9rrW+o]Rg*6s+13$s0;TS~>
+!D>NQrrFh@r;Qi1B);6$$-D</*riT\s'3D9rrW+o]Rg*6s+13$s0;TS~>
+!D>NQrrFh@r;Qi1B);6$$-D</*riT\s'3D9rrW+o]Rg*6s+13$s0;TS~>
+%SJn`a+f<dI>4^#s0R1?p&>?%JqahkK!>9SKDpT*YPnJ&pAdU4s+13$s+13Ts*t~>
+%SJn`a+f<dI>4^#s0R1?p&>?%JqahkK!>9SKDpT*YPnJ&pAdU4s+13$s+13Ts*t~>
+%SJn`a+f<dI>4^#s0R1?p&>?%JqahkK!>9SKDpT*YPnJ&pAdU4s+13$s+13Ts*t~>
+"%t`UcMWt2ZiC'>jR`BS[$D;i[Jp1+Yl9phLTURU!rS@iJcC<$JcC<$Z2]=~>
+"%t`UcMWt2ZiC'>jR`BS[$D;i[Jp1+Yl9phLTURU!rS@iJcC<$JcC<$Z2]=~>
+"%t`UcMWt2ZiC'>jR`BS[$D;i[Jp1+Yl9phLTURU!rS@iJcC<$JcC<$Z2]=~>
+!D>N9rrBb-rrK*@JcC<$JcC<$X8d\~>
+!D>N9rrBb-rrK*@JcC<$JcC<$X8d\~>
+!D>N9rrBb-rrK*@JcC<$JcC<$X8d\~>
+!D>N1rr_L<A`eRDJcC<$JcDeNJ,~>
+!D>N1rr_L<A`eRDJcC<$JcDeNJ,~>
+!D>N1rr_L<A`eRDJcC<$JcDeNJ,~>
+!D>N1rrW/foR[$ns+13$s/Q*L~>
+!D>N1rrW/foR[$ns+13$s/Q*L~>
+!D>N1rrW/foR[$ns+13$s/Q*L~>
+"A:iVs5s=1"5EkUl29$2ir8rZh:1/0s+13$s+13<s*t~>
+"A:iVs5s=1"5EkUl29$2ir8rZh:1/0s+13$s+13<s*t~>
+"A:iVs5s=1"5EkUl29$2ir8rZh:1/0s+13$s+13<s*t~>
+"A:iVh08ih"Bs"KI4bCg#D<'VcX9:FaasJCs+13$s-it<~>
+"A:iVh08ih"Bs"KI4bCg#D<'VcX9:FaasJCs+13$s-it<~>
+"A:iVh08ih"Bs"KI4bCg#D<'VcX9:FaasJCs+13$s-it<~>
+"A:iVM3S#>"P-'BI@pE:#01rEs6Fa?JcC<$JcC<$S,\!~>
+"A:iVM3S#>"P-'BI@pE:#01rEs6Fa?JcC<$JcC<$S,\!~>
+"A:iVM3S#>"P-'BI@pE:#01rEs6Fa?JcC<$JcC<$S,\!~>
+"A:iVM3S#>&_9GOI@m*XH^=^3]7/f?mX;?ks+13$s+13>s*t~>
+"A:iVM3S#>&_9GOI@m*XH^=^3]7/f?mX;?ks+13$s+13>s*t~>
+"A:iVM3S#>&_9GOI@m*XH^=^3]7/f?mX;?ks+13$s+13>s*t~>
+(eZsjM3S&?peOBZI63OaTp(Z>s1iGWB=NNmJcC<$JcD2=J,~>
+(eZsjM3S&?peOBZI63OaTp(Z>s1iGWB=NNmJcC<$JcD2=J,~>
+(eZsjM3S&?peOBZI63OaTp(Z>s1iGWB=NNmJcC<$JcD2=J,~>
+(eZsjiH7CN7\E6NhS8LYs3"=?s+$lZY&JslJcC<$JcD2=J,~>
+(eZsjiH7CN7\E6NhS8LYs3"=?s+$lZY&JslJcC<$JcD2=J,~>
+(eZsjiH7CN7\E6NhS8LYs3"=?s+$lZY&JslJcC<$JcD2=J,~>
+#tmA[s6JIqhUY0nrs?kHs+R*>s8&m>JcC<$JcC<$S,\!~>
+#tmA[s6JIqhUY0nrs?kHs+R*>s8&m>JcC<$JcC<$S,\!~>
+#tmA[s6JIqhUY0nrs?kHs+R*>s8&m>JcC<$JcC<$S,\!~>
+!D>NPrrrDA=TA!drr3;J;ZD6@qZ$K"Ck;V;s+13$s-s%=~>
+!D>NPrrrDA=TA!drr3;J;ZD6@qZ$K"Ck;V;s+13$s-s%=~>
+!D>NPrrrDA=TA!drr3;J;ZD6@qZ$K"Ck;V;s+13$s-s%=~>
+$qi\^s5;#@_cHg;R?IQk#ci.Ks*1Qc[V16lJcC<$JcD2=J,~>
+$qi\^s5;#@_cHg;R?IQk#ci.Ks*1Qc[V16lJcC<$JcD2=J,~>
+$qi\^s5;#@_cHg;R?IQk#ci.Ks*1Qc[V16lJcC<$JcD2=J,~>
+#>7/Ys-lqc>Q+R%Pl=S]G5_FBGZ/@.s+13$s+13<s*t~>
+#>7/Ys-lqc>Q+R%Pl=S]G5_FBGZ/@.s+13$s+13<s*t~>
+#>7/Ys-lqc>Q+R%Pl=S]G5_FBGZ/@.s+13$s+13<s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+"A:iVrHeGa#!r.ds)PpSp&>3n>ok%TqqqDSq^h^f!K[21s+13$s+13ns*t~>
+"A:iVrHeGa#!r.ds)PpSp&>3n>ok%TqqqDSq^h^f!K[21s+13$s+13ns*t~>
+"A:iVrHeGa#!r.ds)PpSp&>3n>ok%TqqqDSq^h^f!K[21s+13$s+13ns*t~>
+"A:iVM3S#>!S0a>rrQHBrq??qB=@j?dS&Kt!DCl?rrJ._JcC<$JcC<$bQ!(~>
+"A:iVM3S#>!S0a>rrQHBrq??qB=@j?dS&Kt!DCl?rrJ._JcC<$JcC<$bQ!(~>
+"A:iVM3S#>!S0a>rrQHBrq??qB=@j?dS&Kt!DCl?rrJ._JcC<$JcC<$bQ!(~>
+#tmA[Vm$.#r2Oo<rrHB@pAYG3[f?BU9E5%Cn,E=fl.l:<!64s:$./AAbQ$Y^s8U+<^B!*hrr<u:
+^B!3krrBk6^B!;Fs+13$s+13us*t~>
+#tmA[Vm$.#r2Oo<rrHB@pAYG3[f?BU9E5%Cn,E=fl.l:<!64s:$./AAbQ$Y^s8U+<^B!*hrr<u:
+^B!3krrBk6^B!;Fs+13$s+13us*t~>
+#tmA[Vm$.#r2Oo<rrHB@pAYG3[f?BU9E5%Cn,E=fl.l:<!64s:$./AAbQ$Y^s8U+<^B!*hrr<u:
+^B!3krrBk6^B!;Fs+13$s+13us*t~>
+!D>NPrrJs@qu6\1[.jS,B60c?KhMIG>f$F>!NC2?rr=,<rs;-HlS8F'MZ:+rra#_Uf`/6dra#_X
+h#FNara#VXi.:oZs+13$s3q!u~>
+!D>NPrrJs@qu6\1[.jS,B60c?KhMIG>f$F>!NC2?rr=,<rs;-HlS8F'MZ:+rra#_Uf`/6dra#_X
+h#FNara#VXi.:oZs+13$s3q!u~>
+!D>NPrrJs@qu6\1[.jS,B60c?KhMIG>f$F>!NC2?rr=,<rs;-HlS8F'MZ:+rra#_Uf`/6dra#_X
+h#FNara#VXi.:oZs+13$s3q!u~>
+!D>NQrrJU@qYpT[2#%"[B39M-AkW1(X+Kg?jFOf>rr=,<rs;-HlS8F'MZ5`trr3+VQ2`K)rr3+K
+U&QA5rr3#-e:IXNs+13$s3q!u~>
+!D>NQrrJU@qYpT[2#%"[B39M-AkW1(X+Kg?jFOf>rr=,<rs;-HlS8F'MZ5`trr3+VQ2`K)rr3+K
+U&QA5rr3#-e:IXNs+13$s3q!u~>
+!D>NQrrJU@qYpT[2#%"[B39M-AkW1(X+Kg?jFOf>rr=,<rs;-HlS8F'MZ5`trr3+VQ2`K)rr3+K
+U&QA5rr3#-e:IXNs+13$s3q!u~>
+!D>NRrrSFGq"k!kDm&j7"_Y:Ds3aR>rrbOCs2S1=rr=,<rs;-HlS8F'MZ5`trr3+VQ2`K)rr34N
+U&V$'AnJ&os+13$s+13ts*t~>
+!D>NRrrSFGq"k!kDm&j7"_Y:Ds3aR>rrbOCs2S1=rr=,<rs;-HlS8F'MZ5`trr3+VQ2`K)rr34N
+U&V$'AnJ&os+13$s+13ts*t~>
+!D>NRrrSFGq"k!kDm&j7"_Y:Ds3aR>rrbOCs2S1=rr=,<rs;-HlS8F'MZ5`trr3+VQ2`K)rr34N
+U&V$'AnJ&os+13$s+13ts*t~>
+#"q&Xs-8l>qYpS+](Z.-B=@j?dS'T>"NXX/@F+oP!$;(<#t<M41&mGps#T3>rre+Bs"<a=rs*qF
+s'i@E\(?32JcC<$JcF-tJ,~>
+#"q&Xs-8l>qYpS+](Z.-B=@j?dS'T>"NXX/@F+oP!$;(<#t<M41&mGps#T3>rre+Bs"<a=rs*qF
+s'i@E\(?32JcC<$JcF-tJ,~>
+#"q&Xs-8l>qYpS+](Z.-B=@j?dS'T>"NXX/@F+oP!$;(<#t<M41&mGps#T3>rre+Bs"<a=rs*qF
+s'i@E\(?32JcC<$JcF-tJ,~>
+"\UrWSW34:rrQZBrq??qB=@j?dS'Q=![-G]r;R9Ks2TNes$bT+1&mGps#T3>rre+Bs"<a=rrd_C
+s!.@=rrItfrr3&A/!>J`JcC<$JcFF'J,~>
+"\UrWSW34:rrQZBrq??qB=@j?dS'Q=![-G]r;R9Ks2TNes$bT+1&mGps#T3>rre+Bs"<a=rrd_C
+s!.@=rrItfrr3&A/!>J`JcC<$JcFF'J,~>
+"\UrWSW34:rrQZBrq??qB=@j?dS'Q=![-G]r;R9Ks2TNes$bT+1&mGps#T3>rre+Bs"<a=rrd_C
+s!.@=rrItfrr3&A/!>J`JcC<$JcFF'J,~>
+"A:iVO;Rp;"^M*-EkD;Crrr.#*Zd]BrVlrj2YI"J&*t?<p](9d4rX\IGkqC42?"X"Jbf?</H-[n
+MYdAE,5rVaPl(I[!l>M%JcC<$JcC<$g&HR~>
+"A:iVO;Rp;"^M*-EkD;Crrr.#*Zd]BrVlrj2YI"J&*t?<p](9d4rX\IGkqC42?"X"Jbf?</H-[n
+MYdAE,5rVaPl(I[!l>M%JcC<$JcC<$g&HR~>
+"A:iVO;Rp;"^M*-EkD;Crrr.#*Zd]BrVlrj2YI"J&*t?<p](9d4rX\IGkqC42?"X"Jbf?</H-[n
+MYdAE,5rVaPl(I[!l>M%JcC<$JcC<$g&HR~>
+!D>N"rrMF?JcC<$JcC<$\Gq'~>
+!D>N"rrMF?JcC<$JcC<$\Gq'~>
+!D>N"rrMF?JcC<$JcC<$\Gq'~>
+!D>N"rrMF?JcC<$JcC<$\Gq'~>
+!D>N"rrMF?JcC<$JcC<$\Gq'~>
+!D>N"rrMF?JcC<$JcC<$\Gq'~>
+!D>N"rrM_GJcC<$JcC<$\Gq'~>
+!D>N"rrM_GJcC<$JcC<$\Gq'~>
+!D>N"rrM_GJcC<$JcC<$\Gq'~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>NQrrIH;oD\f.li.!"FSu.?F^'4jrrR%AK'!"7oNPOlmf*=XI"D<Ks+13$s7QDB~>
+!D>NQrrIH;oD\f.li.!"FSu.?F^'4jrrR%AK'!"7oNPOlmf*=XI"D<Ks+13$s7QDB~>
+!D>NQrrIH;oD\f.li.!"FSu.?F^'4jrrR%AK'!"7oNPOlmf*=XI"D<Ks+13$s7QDB~>
+#tmA[V69hPmGN'ZrrHa>li."*b5D8@bH`l#rrU9A4lu\Gqn\)'mf*=Q1YMd/Qb7nLJcC<$TDsE~>
+#tmA[V69hPmGN'ZrrHa>li."*b5D8@bH`l#rrU9A4lu\Gqn\)'mf*=Q1YMd/Qb7nLJcC<$TDsE~>
+#tmA[V69hPmGN'ZrrHa>li."*b5D8@bH`l#rrU9A4lu\Gqn\)'mf*=Q1YMd/Qb7nLJcC<$TDsE~>
+#tmA[n8Punj$2c%rrQrD)WUhulS8;:!T6-$rrKH@mf*=Q1YMd/G,BijJcC<$TDsE~>
+#tmA[n8Punj$2c%rrQrD)WUhulS8;:!T6-$rrKH@mf*=Q1YMd/G,BijJcC<$TDsE~>
+#tmA[n8Punj$2c%rrQrD)WUhulS8;:!T6-$rrKH@mf*=Q1YMd/G,BijJcC<$TDsE~>
+#YR8Zs6a"o,j+k$"+miX*r>m;&MNhW!_OI]qu6]_1&LtOi'%&=!U2-6*XdYos6(XJ*d)V6s8)[8
+!L<EYrrKH@pAY0m`Vgh\YP[T:VZ%V=\GZBJr>l?_rRb34I3'HPs8TJ)*WsmkpmV.&$f1io*Y&i)
+*rl96M#JG%-KtK-K&fcM0C",gJcC<$ZN#F~>
+#YR8Zs6a"o,j+k$"+miX*r>m;&MNhW!_OI]qu6]_1&LtOi'%&=!U2-6*XdYos6(XJ*d)V6s8)[8
+!L<EYrrKH@pAY0m`Vgh\YP[T:VZ%V=\GZBJr>l?_rRb34I3'HPs8TJ)*WsmkpmV.&$f1io*Y&i)
+*rl96M#JG%-KtK-K&fcM0C",gJcC<$ZN#F~>
+#YR8Zs6a"o,j+k$"+miX*r>m;&MNhW!_OI]qu6]_1&LtOi'%&=!U2-6*XdYos6(XJ*d)V6s8)[8
+!L<EYrrKH@pAY0m`Vgh\YP[T:VZ%V=\GZBJr>l?_rRb34I3'HPs8TJ)*WsmkpmV.&$f1io*Y&i)
+*rl96M#JG%-KtK-K&fcM0C",gJcC<$ZN#F~>
+#tmA[j`JBojA4A"rr=,:rrGJko`"sd-N!fDlS8;:!T6->rrFb?rr37OU&QA5s8S+>qYpS-\,QC.
+ZYK46!VIi=rrd2tl3hh8rrG+?qu6sSnGfp5T`>$9qtpBq*rio]rVljnrr3"BVZ$MqVKVt=$1o\H
+s*Z-8s6):js+13$s0DZT~>
+#tmA[j`JBojA4A"rr=,:rrGJko`"sd-N!fDlS8;:!T6->rrFb?rr37OU&QA5s8S+>qYpS-\,QC.
+ZYK46!VIi=rrd2tl3hh8rrG+?qu6sSnGfp5T`>$9qtpBq*rio]rVljnrr3"BVZ$MqVKVt=$1o\H
+s*Z-8s6):js+13$s0DZT~>
+#tmA[j`JBojA4A"rr=,:rrGJko`"sd-N!fDlS8;:!T6->rrFb?rr37OU&QA5s8S+>qYpS-\,QC.
+ZYK46!VIi=rrd2tl3hh8rrG+?qu6sSnGfp5T`>$9qtpBq*rio]rVljnrr3"BVZ$MqVKVt=$1o\H
+s*Z-8s6):js+13$s0DZT~>
+#tmA[Z+0ffmGruorr=,+rrMj@qu6]_1&LtOi'%&=!A9uCW=)Uts!.@>s,N->s8Dnq!CO?qrrKH@
+pAY0d-i<oEl8/D="$#BAlM[[b*q93<BE%o5D#F=nrM]l7s1Mh9rrI;?rVlnq:B%4!U+--BI@pQ>
+n9BNaJcC<$ZN#F~>
+#tmA[Z+0ffmGruorr=,+rrMj@qu6]_1&LtOi'%&=!A9uCW=)Uts!.@>s,N->s8Dnq!CO?qrrKH@
+pAY0d-i<oEl8/D="$#BAlM[[b*q93<BE%o5D#F=nrM]l7s1Mh9rrI;?rVlnq:B%4!U+--BI@pQ>
+n9BNaJcC<$ZN#F~>
+#tmA[Z+0ffmGruorr=,+rrMj@qu6]_1&LtOi'%&=!A9uCW=)Uts!.@>s,N->s8Dnq!CO?qrrKH@
+pAY0d-i<oEl8/D="$#BAlM[[b*q93<BE%o5D#F=nrM]l7s1Mh9rrI;?rVlnq:B%4!U+--BI@pQ>
+n9BNaJcC<$ZN#F~>
+!D>NQrrJ/QoD\e3li."[-N!fDlS8;:!T6->rrF`grGr=hs+cMkN.JhFs-EV)!A:k\rrKH@pAY0d
+-i<oEl8/D="$#B2LAc/r'_).2BDhc1k(N\S!tkRH@/9g'G,G6<!N%dREs.C#s*^Mjs+13$s/uBP~>
+!D>NQrrJ/QoD\e3li."[-N!fDlS8;:!T6->rrF`grGr=hs+cMkN.JhFs-EV)!A:k\rrKH@pAY0d
+-i<oEl8/D="$#B2LAc/r'_).2BDhc1k(N\S!tkRH@/9g'G,G6<!N%dREs.C#s*^Mjs+13$s/uBP~>
+!D>NQrrJ/QoD\e3li."[-N!fDlS8;:!T6->rrF`grGr=hs+cMkN.JhFs-EV)!A:k\rrKH@pAY0d
+-i<oEl8/D="$#B2LAc/r'_).2BDhc1k(N\S!tkRH@/9g'G,G6<!N%dREs.C#s*^Mjs+13$s/uBP~>
+!D>NArr=,:rrI;!o`"sd-N!fDlS8;:!T6->rrFb?qu6eNOHG[Brr2s>rr3&1/@YWY!OHP7rrMd?
+rVltal0:)'rrY7Ah`h&>"dftms0cS<rrL2@rr3+[&c]OPrVlkIrr34HVZ6[Gs/'u9rrIP?JcC<$
+JcDnQJ,~>
+!D>NArr=,:rrI;!o`"sd-N!fDlS8;:!T6->rrFb?qu6eNOHG[Brr2s>rr3&1/@YWY!OHP7rrMd?
+rVltal0:)'rrY7Ah`h&>"dftms0cS<rrL2@rr3+[&c]OPrVlkIrr34HVZ6[Gs/'u9rrIP?JcC<$
+JcDnQJ,~>
+!D>NArr=,:rrI;!o`"sd-N!fDlS8;:!T6->rrFb?qu6eNOHG[Brr2s>rr3&1/@YWY!OHP7rrMd?
+rVltal0:)'rrY7Ah`h&>"dftms0cS<rrL2@rr3+[&c]OPrVlkIrr34HVZ6[Gs/'u9rrIP?JcC<$
+JcDnQJ,~>
+!D>NDrs#)m;$g)sOT#1[%P@AR"ER<H;3V"Z"Dg[A;4IRb#\6F=;54*j]iY21"KMM%\l8T*"1%t,
+[/No/IRUaGo2.Lk;8N&-!W8Vh;$3-Ul8/D="$#B7R/TqdP99;o[V,O<!Uc$J;$<@(s6>O@;#mj"
+rs.%n;,Ok'j+I>.!*K7#!I^Sks+13$s/uBP~>
+!D>NDrs#)m;$g)sOT#1[%P@AR"ER<H;3V"Z"Dg[A;4IRb#\6F=;54*j]iY21"KMM%\l8T*"1%t,
+[/No/IRUaGo2.Lk;8N&-!W8Vh;$3-Ul8/D="$#B7R/TqdP99;o[V,O<!Uc$J;$<@(s6>O@;#mj"
+rs.%n;,Ok'j+I>.!*K7#!I^Sks+13$s/uBP~>
+!D>NDrs#)m;$g)sOT#1[%P@AR"ER<H;3V"Z"Dg[A;4IRb#\6F=;54*j]iY21"KMM%\l8T*"1%t,
+[/No/IRUaGo2.Lk;8N&-!W8Vh;$3-Ul8/D="$#B7R/TqdP99;o[V,O<!Uc$J;$<@(s6>O@;#mj"
+rs.%n;,Ok'j+I>.!*K7#!I^Sks+13$s/uBP~>
+!D>NDrrD*WbQ-Q!rrLHrp&>$CrlbB"rr2uJrlbB%rr2uHrlbB'rr2uErlbN-s8Pm:rlbIg^&S*2
+bQR%cnc/OcbQ.&)rrDflbQID8qpt`G"5!DLoDZr;nC@I:nDX9E!:Kj1"SL4Cs6T^.!<)lr!oD/F
+rr2u]rlbAfrr3#\m",1fs+13Qs*t~>
+!D>NDrrD*WbQ-Q!rrLHrp&>$CrlbB"rr2uJrlbB%rr2uHrlbB'rr2uErlbN-s8Pm:rlbIg^&S*2
+bQR%cnc/OcbQ.&)rrDflbQID8qpt`G"5!DLoDZr;nC@I:nDX9E!:Kj1"SL4Cs6T^.!<)lr!oD/F
+rr2u]rlbAfrr3#\m",1fs+13Qs*t~>
+!D>NDrrD*WbQ-Q!rrLHrp&>$CrlbB"rr2uJrlbB%rr2uHrlbB'rr2uErlbN-s8Pm:rlbIg^&S*2
+bQR%cnc/OcbQ.&)rrDflbQID8qpt`G"5!DLoDZr;nC@I:nDX9E!:Kj1"SL4Cs6T^.!<)lr!oD/F
+rr2u]rlbAfrr3#\m",1fs+13Qs*t~>
+!D>MarrFV?rq?G6YCZ_)s+13$s3q!u~>
+!D>MarrFV?rq?G6YCZ_)s+13$s3q!u~>
+!D>MarrFV?rq?G6YCZ_)s+13$s3q!u~>
+!D>M`rr=PJ-30Zhs+13$s+13us*t~>
+!D>M`rr=PJ-30Zhs+13$s+13us*t~>
+!D>M`rr=PJ-30Zhs+13$s+13us*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+"A:iVs2Y,1!3Z>$!Q4&Ns+13$s+132s*t~>
+"A:iVs2Y,1!3Z>$!Q4&Ns+13$s+132s*t~>
+"A:iVs2Y,1!3Z>$!Q4&Ns+13$s+132s*t~>
+"A:iV`..c8#(NHKs0$t?JcC<$JcC<$OT0h~>
+"A:iV`..c8#(NHKs0$t?JcC<$JcC<$OT0h~>
+"A:iV`..c8#(NHKs0$t?JcC<$JcC<$OT0h~>
+"A:iVM3S#>!S0a>rrTTDqgncus+13$s,m>3~>
+"A:iVM3S#>!S0a>rrTTDqgncus+13$s,m>3~>
+"A:iVM3S#>!S0a>rrTTDqgncus+13$s,m>3~>
+#tmA[hr=\9pK5Z<rrL#?JcC<$JcC<$OoKq~>
+#tmA[hr=\9pK5Z<rrL#?JcC<$JcC<$OoKq~>
+#tmA[hr=\9pK5Z<rrL#?JcC<$JcC<$OoKq~>
+!D>NPrrG@?qu6]<<.Y(#s+13$s-!D4~>
+!D>NPrrG@?qu6]<<.Y(#s+13$s-!D4~>
+!D>NPrrG@?qu6]<<.Y(#s+13$s-!D4~>
+!D>NRrrVaZjneuXg-^GkJcC<$JcCo5J,~>
+!D>NRrrVaZjneuXg-^GkJcC<$JcCo5J,~>
+!D>NRrrVaZjneuXg-^GkJcC<$JcCo5J,~>
+#"q&Xs5RM>qYpTI7tL\ks+13$s,m>3~>
+#"q&Xs5RM>qYpTI7tL\ks+13$s,m>3~>
+#"q&Xs5RM>qYpTI7tL\ks+13$s,m>3~>
+"\UrWgN^j:rrU/EqLSZts+13$s,m>3~>
+"\UrWgN^j:rrU/EqLSZts+13$s,m>3~>
+"\UrWgN^j:rrU/EqLSZts+13$s,m>3~>
+"A:iVM,sS>"gS+-]QWRks+13$s+133s*t~>
+"A:iVM,sS>"gS+-]QWRks+13$s+133s*t~>
+"A:iVM,sS>"gS+-]QWRks+13$s+133s*t~>
+"%t`UaS^ktWrN+,i.:oZs+13$s,[21~>
+"%t`UaS^ktWrN+,i.:oZs+13$s,[21~>
+"%t`UaS^ktWrN+,i.:oZs+13$s,[21~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+!D>M*s+13$s+13&s*t~>
+%%EndData
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/lib/stdlib/doc/src/ushell3.gif b/lib/stdlib/doc/src/ushell3.gif
new file mode 100644
index 0000000000..2141268b91
--- /dev/null
+++ b/lib/stdlib/doc/src/ushell3.gif
Binary files differ
diff --git a/lib/stdlib/doc/src/ushell3.ps b/lib/stdlib/doc/src/ushell3.ps
new file mode 100644
index 0000000000..dd64eeab5c
--- /dev/null
+++ b/lib/stdlib/doc/src/ushell3.ps
@@ -0,0 +1,662 @@
+%!PS-Adobe-3.0
+%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
+%%Title: ushell3.ps
+%%CreationDate: Mon Mar 16 12:15:17 2009
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%Pages: 1
+%%BoundingBox: 14 14 468 78
+%%EndComments
+%%BeginProlog
+% Use own dictionary to avoid conflicts
+10 dict begin
+%%EndProlog
+%%Page: 1 1
+% Translate for offset
+14.173228346456694 14.173228346456694 translate
+% Translate to begin of first scanline
+0 63.070866141732289 translate
+453.54330708661422 -63.070866141732289 scale
+% Image geometry
+640 89 8
+% Transformation matrix
+[ 640 0 0 89 0 0 ]
+% Strings to hold RGB-samples per scanline
+/rstr 640 string def
+/gstr 640 string def
+/bstr 640 string def
+{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
+{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
+true 3
+%%BeginData: 37475 ASCII Bytes
+colorimage
+!1\W%J`VIEJ`VLFJ,~>
+!1\W%J`VIEJ`VLFJ,~>
+!1\W%J`VIEJ`VLFJ,~>
+!T[+/6@hIS6@hIU6@]~>
+!T[+/6@hIS6@hIU6@]~>
+!T[+/6@hIS6@hIU6@]~>
+!ouWfJQ.2"JQ.2"KN*I~>
+!ouWfJQ.2"JQ.2"KN*I~>
+!ouWfJQ.2"JQ.2"KN*I~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLQ2^m!`0L?'S`PG&JcC<$i;\<~>
+!ouXLQ2^m!`0L?'S`PG&JcC<$i;\<~>
+!ouXLQ2^m!`0L?'S`PG&JcC<$i;\<~>
+"QVjNs.9-i!1*E[!L)+*rrR=EF8Z%>Dc_2VrrI?7rr3)%^&N-N<rg/5rr?^0<rgP6rrU,eg\:^K
+o6/O]!ioDNpAY05U[\9aJa!(4!2BHo!,VW6!lAR@JcC<$JcF^/J,~>
+"QVjNs.9-i!1*E[!L)+*rrR=EF8Z%>Dc_2VrrI?7rr3)%^&N-N<rg/5rr?^0<rgP6rrU,eg\:^K
+o6/O]!ioDNpAY05U[\9aJa!(4!2BHo!,VW6!lAR@JcC<$JcF^/J,~>
+"QVjNs.9-i!1*E[!L)+*rrR=EF8Z%>Dc_2VrrI?7rr3)%^&N-N<rg/5rr?^0<rgP6rrU,eg\:^K
+o6/O]!ioDNpAY05U[\9aJa!(4!2BHo!,VW6!lAR@JcC<$JcF^/J,~>
+"lqsOs)!Pi_uSNdrrHB@o)Ad;\gmXX!mS).p&>%Ili-neJXl`h]D]YJlM1AZb5K6Z:po`k!N()7
+rrML@l2LdgT^2UZil(<`rrJFsrVlntF7K88K;)-<s8T'RrkniDCAn/50C=>jJcC<$iW"E~>
+"lqsOs)!Pi_uSNdrrHB@o)Ad;\gmXX!mS).p&>%Ili-neJXl`h]D]YJlM1AZb5K6Z:po`k!N()7
+rrML@l2LdgT^2UZil(<`rrJFsrVlntF7K88K;)-<s8T'RrkniDCAn/50C=>jJcC<$iW"E~>
+"lqsOs)!Pi_uSNdrrHB@o)Ad;\gmXX!mS).p&>%Ili-neJXl`h]D]YJlM1AZb5K6Z:po`k!N()7
+rrML@l2LdgT^2UZil(<`rrJFsrVlntF7K88K;)-<s8T'RrkniDCAn/50C=>jJcC<$iW"E~>
+"lqsOs)#"3rrHB@nc&X\.f95Hkqi#4!AWp>rrdnCs!dR.rrSCIp@&"_b$si5rrML@l2LdPK]E(5
+GGY9<!NC/2rs4ONpZ4C7s,`6>rrN#\rVlo\2M(mZs+14/s*t~>
+"lqsOs)#"3rrHB@nc&X\.f95Hkqi#4!AWp>rrdnCs!dR.rrSCIp@&"_b$si5rrML@l2LdPK]E(5
+GGY9<!NC/2rs4ONpZ4C7s,`6>rrN#\rVlo\2M(mZs+14/s*t~>
+"lqsOs)#"3rrHB@nc&X\.f95Hkqi#4!AWp>rrdnCs!dR.rrSCIp@&"_b$si5rrML@l2LdPK]E(5
+GGY9<!NC/2rs4ONpZ4C7s,`6>rrN#\rVlo\2M(mZs+14/s*t~>
+#in9Rs)"@hjludE!Us(f*"5s=s'2Za*#r;Ts6`DV*!'XArr3#i.f95Hkqi#4$2ktGs.=c>s!c4l
+*!(ffp&>*eKVIo7!Q/(<rrCjQ*!EJBm4518*!<VHr42bG&A7u":Z[6L*5hd.*"`Z#*8gPk!Q/1@
+rrsVGs/-hF]Dhg?\f;.l*$"qWWG$>m*$OSArs>H^rr<#^9E1*5p\t46JcC<$JcF^/J,~>
+#in9Rs)"@hjludE!Us(f*"5s=s'2Za*#r;Ts6`DV*!'XArr3#i.f95Hkqi#4$2ktGs.=c>s!c4l
+*!(ffp&>*eKVIo7!Q/(<rrCjQ*!EJBm4518*!<VHr42bG&A7u":Z[6L*5hd.*"`Z#*8gPk!Q/1@
+rrsVGs/-hF]Dhg?\f;.l*$"qWWG$>m*$OSArs>H^rr<#^9E1*5p\t46JcC<$JcF^/J,~>
+#in9Rs)"@hjludE!Us(f*"5s=s'2Za*#r;Ts6`DV*!'XArr3#i.f95Hkqi#4$2ktGs.=c>s!c4l
+*!(ffp&>*eKVIo7!Q/(<rrCjQ*!EJBm4518*!<VHr42bG&A7u":Z[6L*5hd.*"`Z#*8gPk!Q/1@
+rrsVGs/-hF]Dhg?\f;.l*$"qWWG$>m*$OSArs>H^rr<#^9E1*5p\t46JcC<$JcF^/J,~>
+#in9Rs(s&?2H0VT!G&_>rsZf$s'-u6s8T->s&7&=rrJm@rr3#i.f95Hkqhu3#T`sFSpp_><^Zld
+!I1F5rrJj?mJd4)=nhq!7G$o6ErQ(@4l><[rVm<akl8@2QiI(:nGiNVK_>?L_HQutn,FF,rrHT?
+r;QeAV>^DuW>MT6s7dl/rs"REs8UEef)5OJ*J+6As+14/s*t~>
+#in9Rs(s&?2H0VT!G&_>rsZf$s'-u6s8T->s&7&=rrJm@rr3#i.f95Hkqhu3#T`sFSpp_><^Zld
+!I1F5rrJj?mJd4)=nhq!7G$o6ErQ(@4l><[rVm<akl8@2QiI(:nGiNVK_>?L_HQutn,FF,rrHT?
+r;QeAV>^DuW>MT6s7dl/rs"REs8UEef)5OJ*J+6As+14/s*t~>
+#in9Rs(s&?2H0VT!G&_>rsZf$s'-u6s8T->s&7&=rrJm@rr3#i.f95Hkqhu3#T`sFSpp_><^Zld
+!I1F5rrJj?mJd4)=nhq!7G$o6ErQ(@4l><[rVm<akl8@2QiI(:nGiNVK_>?L_HQutn,FF,rrHT?
+r;QeAV>^DuW>MT6s7dl/rs"REs8UEef)5OJ*J+6As+14/s*t~>
+"lqsOs)#";rrJFMriH=Cs8Q??rr3,%EW8soriH3>HN*pFngaP:!U2E4rrciCl&)D8rrIA?pAY3[
+N2>qA!Q/(<rrDWgXTL6.m4eM="$PQ&3;rj[2<b(S?N0s.E:s82R=t85#/XRDCU3s\rVln-]);R/
+GGY9<!NC/>rrMm?nG`]SNW9#h7m6eM!$-XjJcC<$iW"E~>
+"lqsOs)#";rrJFMriH=Cs8Q??rr3,%EW8soriH3>HN*pFngaP:!U2E4rrciCl&)D8rrIA?pAY3[
+N2>qA!Q/(<rrDWgXTL6.m4eM="$PQ&3;rj[2<b(S?N0s.E:s82R=t85#/XRDCU3s\rVln-]);R/
+GGY9<!NC/>rrMm?nG`]SNW9#h7m6eM!$-XjJcC<$iW"E~>
+"lqsOs)#";rrJFMriH=Cs8Q??rr3,%EW8soriH3>HN*pFngaP:!U2E4rrciCl&)D8rrIA?pAY3[
+N2>qA!Q/(<rrDWgXTL6.m4eM="$PQ&3;rj[2<b(S?N0s.E:s82R=t85#/XRDCU3s\rVln-]);R/
+GGY9<!NC/>rrMm?nG`]SNW9#h7m6eM!$-XjJcC<$iW"E~>
+"lqsOs)#":rrAAaD?P3us'3D>rrfBBs&2tsD?.$BrrM[?qu6]]1\C\Lp*RC[q>UJ?V"Xfh^Kp4+
+!Q/(=rrN"UrGD]YfBk9jrrYFAj>d,<"?#EC^0^[9!L\W6rs$>Ds(eq?*W?!=@_2L;!I(C=rrK*?
+rr3#o,k1g7OH'8>!T-'<rr='js+13$s5F!.~>
+"lqsOs)#":rrAAaD?P3us'3D>rrfBBs&2tsD?.$BrrM[?qu6]]1\C\Lp*RC[q>UJ?V"Xfh^Kp4+
+!Q/(=rrN"UrGD]YfBk9jrrYFAj>d,<"?#EC^0^[9!L\W6rs$>Ds(eq?*W?!=@_2L;!I(C=rrK*?
+rr3#o,k1g7OH'8>!T-'<rr='js+13$s5F!.~>
+"lqsOs)#":rrAAaD?P3us'3D>rrfBBs&2tsD?.$BrrM[?qu6]]1\C\Lp*RC[q>UJ?V"Xfh^Kp4+
+!Q/(=rrN"UrGD]YfBk9jrrYFAj>d,<"?#EC^0^[9!L\W6rs$>Ds(eq?*W?!=@_2L;!I(C=rrK*?
+rr3#o,k1g7OH'8>!T-'<rr='js+13$s5F!.~>
+"lqsOs)#";rrK1Rrr3,.C&_GSrr3,%EW8tZq>UKd.f95Hkqhu3"84(R@K-9-Mu!AP!I1F>rrV/%
+Zi0n*m!?)+rrV55[Jp1,odBb="fictm1u>nrrYFAj>d,<"?#EC^0^[9"dt&Ds,$[Lrs$>D^O^an
+*W?!=@_2L;#C!$Es+UKPF8l1?pa#A/!K`<?rrM"?rVlnZNIh+\s+14/s*t~>
+"lqsOs)#";rrK1Rrr3,.C&_GSrr3,%EW8tZq>UKd.f95Hkqhu3"84(R@K-9-Mu!AP!I1F>rrV/%
+Zi0n*m!?)+rrV55[Jp1,odBb="fictm1u>nrrYFAj>d,<"?#EC^0^[9"dt&Ds,$[Lrs$>D^O^an
+*W?!=@_2L;#C!$Es+UKPF8l1?pa#A/!K`<?rrM"?rVlnZNIh+\s+14/s*t~>
+"lqsOs)#";rrK1Rrr3,.C&_GSrr3,%EW8tZq>UKd.f95Hkqhu3"84(R@K-9-Mu!AP!I1F>rrV/%
+Zi0n*m!?)+rrV55[Jp1,odBb="fictm1u>nrrYFAj>d,<"?#EC^0^[9"dt&Ds,$[Lrs$>D^O^an
+*W?!=@_2L;#C!$Es+UKPF8l1?pa#A/!K`<?rrM"?rVlnZNIh+\s+14/s*t~>
+"lqsOs(spt=Tb&ka`%/]"E;N`>f$F>"KQPB_eK*Q!1!Q`"F!iV=e#Ej"E@<P=ePKg"8nU*aSu2?
+ZsEZ6!J95.rrU7AErH"=gd(0)!N()?rrN%dr`KD\/_BA3O8`8aPP"R6rE08hp]%s6qYp`RIT]gC
+rV?I'lAL)E_.]YEs1_\\6!=!^rs-kl=]qs.WH8(=!Vdr0rrLc"r`K83]D_a10C=>jJcC<$iW"E~>
+"lqsOs(spt=Tb&ka`%/]"E;N`>f$F>"KQPB_eK*Q!1!Q`"F!iV=e#Ej"E@<P=ePKg"8nU*aSu2?
+ZsEZ6!J95.rrU7AErH"=gd(0)!N()?rrN%dr`KD\/_BA3O8`8aPP"R6rE08hp]%s6qYp`RIT]gC
+rV?I'lAL)E_.]YEs1_\\6!=!^rs-kl=]qs.WH8(=!Vdr0rrLc"r`K83]D_a10C=>jJcC<$iW"E~>
+"lqsOs(spt=Tb&ka`%/]"E;N`>f$F>"KQPB_eK*Q!1!Q`"F!iV=e#Ej"E@<P=ePKg"8nU*aSu2?
+ZsEZ6!J95.rrU7AErH"=gd(0)!N()?rrN%dr`KD\/_BA3O8`8aPP"R6rE08hp]%s6qYp`RIT]gC
+rV?I'lAL)E_.]YEs1_\\6!=!^rs-kl=]qs.WH8(=!Vdr0rrLc"r`K83]D_a10C=>jJcC<$iW"E~>
+"QVjNs4mOh"53_Se,I2eeGoR$nG`FjleVU@ci1c]f`(mNc2PQ[gA_*PbPo?Yh"C[Jp<rm=!6+rS
+!93tW!qO4arVlomdH^`5l@c>>rrDcl_?K,Np&!#$rk\d,s8VZg_?BH0s7"\:rr_/q_Y3a(#jL4G
+s3:H@s6'?t!<)lr#1p`/s8VB?rr3#tb4#?1h>Y7kb5M>AGH(Ijs+13$s5F!.~>
+"QVjNs4mOh"53_Se,I2eeGoR$nG`FjleVU@ci1c]f`(mNc2PQ[gA_*PbPo?Yh"C[Jp<rm=!6+rS
+!93tW!qO4arVlomdH^`5l@c>>rrDcl_?K,Np&!#$rk\d,s8VZg_?BH0s7"\:rr_/q_Y3a(#jL4G
+s3:H@s6'?t!<)lr#1p`/s8VB?rr3#tb4#?1h>Y7kb5M>AGH(Ijs+13$s5F!.~>
+"QVjNs4mOh"53_Se,I2eeGoR$nG`FjleVU@ci1c]f`(mNc2PQ[gA_*PbPo?Yh"C[Jp<rm=!6+rS
+!93tW!qO4arVlomdH^`5l@c>>rrDcl_?K,Np&!#$rk\d,s8VZg_?BH0s7"\:rr_/q_Y3a(#jL4G
+s3:H@s6'?t!<)lr#1p`/s8VB?rr3#tb4#?1h>Y7kb5M>AGH(Ijs+13$s5F!.~>
+!ouXLQ2^mSnWj+TkkTf0JcC<$i;\<~>
+!ouXLQ2^mSnWj+TkkTf0JcC<$i;\<~>
+!ouXLQ2^mSnWj+TkkTf0JcC<$i;\<~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcG]K!TNR]rr_0&bgEm!"4YQ=h#.0PdXhFLs/5mI~>
+!ouXLJcG]K!TNR]rr_0&bgEm!"4YQ=h#.0PdXhFLs/5mI~>
+!ouXLJcG]K!TNR]rr_0&bgEm!"4YQ=h#.0PdXhFLs/5mI~>
+!ouXLrVll.r;Qf;o()e]\@V&,!kGPPr;QirYf6S@!PSC#rr_SkYi""2!o&"@rVm>df)Ne\s8V;e
+g&M)arUBgji3;2?s1.k'"4H?3\b5t)>rthhrVluBmf'fsrr^ZQYd<0c!6"l@!6Y!7"1eHrci3qH
+Bhpuir;Qe$_L_`<s/>sJ~>
+!ouXLrVll.r;Qf;o()e]\@V&,!kGPPr;QirYf6S@!PSC#rr_SkYi""2!o&"@rVm>df)Ne\s8V;e
+g&M)arUBgji3;2?s1.k'"4H?3\b5t)>rthhrVluBmf'fsrr^ZQYd<0c!6"l@!6Y!7"1eHrci3qH
+Bhpuir;Qe$_L_`<s/>sJ~>
+!ouXLrVll.r;Qf;o()e]\@V&,!kGPPr;QirYf6S@!PSC#rr_SkYi""2!o&"@rVm>df)Ne\s8V;e
+g&M)arUBgji3;2?s1.k'"4H?3\b5t)>rthhrVluBmf'fsrr^ZQYd<0c!6"l@!6Y!7"1eHrci3qH
+Bhpuir;Qe$_L_`<s/>sJ~>
+!ouXLrr3"u(&\(5P`>P2rrICpm/I-7?T\2c!r?\qrVlmIoCDnbn;;!sKAbYe!T1]hrrJ(?r;RA&
+HiH[ds6YVXJCjfHq>^KAch7;Bie2*#ir:%mrr[/AruLe4!F<J;rsMiIr#l%>c$X;BD5H.i!P<LR
+CB8[tpAY48s0uV;rrLA?r;QfW3e@<^s/>sJ~>
+!ouXLrr3"u(&\(5P`>P2rrICpm/I-7?T\2c!r?\qrVlmIoCDnbn;;!sKAbYe!T1]hrrJ(?r;RA&
+HiH[ds6YVXJCjfHq>^KAch7;Bie2*#ir:%mrr[/AruLe4!F<J;rsMiIr#l%>c$X;BD5H.i!P<LR
+CB8[tpAY48s0uV;rrLA?r;QfW3e@<^s/>sJ~>
+!ouXLrr3"u(&\(5P`>P2rrICpm/I-7?T\2c!r?\qrVlmIoCDnbn;;!sKAbYe!T1]hrrJ(?r;RA&
+HiH[ds6YVXJCjfHq>^KAch7;Bie2*#ir:%mrr[/AruLe4!F<J;rsMiIr#l%>c$X;BD5H.i!P<LR
+CB8[tpAY48s0uV;rrLA?r;QfW3e@<^s/>sJ~>
+#NS0Qs8SgH'Dqe1RtBDp!T?-5rrFS?o)AmK5l^js[GUubZ>0::!n)SAr;RA&HiH[ds3jR>_1$W_
+r;ZeYN:m2T\mk]II!C_Grr[/AruLe4!F<J;rsMiIr#l%>LR%o>oI8\t#.&^Es8U`apAY48s0uV;
+rrLA?r;QigKVj>#JcD_LJ,~>
+#NS0Qs8SgH'Dqe1RtBDp!T?-5rrFS?o)AmK5l^js[GUubZ>0::!n)SAr;RA&HiH[ds3jR>_1$W_
+r;ZeYN:m2T\mk]II!C_Grr[/AruLe4!F<J;rsMiIr#l%>LR%o>oI8\t#.&^Es8U`apAY48s0uV;
+rrLA?r;QigKVj>#JcD_LJ,~>
+#NS0Qs8SgH'Dqe1RtBDp!T?-5rrFS?o)AmK5l^js[GUubZ>0::!n)SAr;RA&HiH[ds3jR>_1$W_
+r;ZeYN:m2T\mk]II!C_Grr[/AruLe4!F<J;rsMiIr#l%>LR%o>oI8\t#.&^Es8U`apAY48s0uV;
+rrLA?r;QigKVj>#JcD_LJ,~>
+#NS0Qs.PG>*W#d:UNuP4!e+Bor;QbBr`fGnp&>'O48f*[Bj?GlrrFS4r`fH/rVlrS>c%E!#M_TE
+s2a:$pAJ50_Z0XSs3"YPaT)6E>a;X#c2[h+rEKWWs79J\-<sg.rr3#!F8PtRh9c29n,Mjis8U'5
+o)IQO1br<@mf3=+rEKd&s5qB%s1G-(s3748r`fI&rr3)Dir?1SrrHE?qu72Dli+*fs+cm>s7@c?
+s/j-:rrM&<rr3"Nm/?qjU<NXUs4*P;s,-e\!/pjV"@#[e>`o$c!.XuQ!1*T`"0V[da8Gr<btn6:
+!QA,ks+13Ls*t~>
+#NS0Qs.PG>*W#d:UNuP4!e+Bor;QbBr`fGnp&>'O48f*[Bj?GlrrFS4r`fH/rVlrS>c%E!#M_TE
+s2a:$pAJ50_Z0XSs3"YPaT)6E>a;X#c2[h+rEKWWs79J\-<sg.rr3#!F8PtRh9c29n,Mjis8U'5
+o)IQO1br<@mf3=+rEKd&s5qB%s1G-(s3748r`fI&rr3)Dir?1SrrHE?qu72Dli+*fs+cm>s7@c?
+s/j-:rrM&<rr3"Nm/?qjU<NXUs4*P;s,-e\!/pjV"@#[e>`o$c!.XuQ!1*T`"0V[da8Gr<btn6:
+!QA,ks+13Ls*t~>
+#NS0Qs.PG>*W#d:UNuP4!e+Bor;QbBr`fGnp&>'O48f*[Bj?GlrrFS4r`fH/rVlrS>c%E!#M_TE
+s2a:$pAJ50_Z0XSs3"YPaT)6E>a;X#c2[h+rEKWWs79J\-<sg.rr3#!F8PtRh9c29n,Mjis8U'5
+o)IQO1br<@mf3=+rEKd&s5qB%s1G-(s3748r`fI&rr3)Dir?1SrrHE?qu72Dli+*fs+cm>s7@c?
+s/j-:rrM&<rr3"Nm/?qjU<NXUs4*P;s,-e\!/pjV"@#[e>`o$c!.XuQ!1*T`"0V[da8Gr<btn6:
+!QA,ks+13Ls*t~>
+#NS0Qs4Zf=*W#d;ql"c5rrUHX,5qNBGcC\W!KEfHrrM(?r;Qi:U."t[!@^M;^B&_orr3&@!-8&<
+!Sfs<rr>sq^CtP2s0b1+^V=R6rYN>5HbX4Is7Q?*#\)ch^P5S"^[V7&!NL5.rsA82Ch^Z!q#AcZ
+rP&>0q#:E#)he4*!*/Ie!F<J5rs=H,5X7I]s8RS?rr3#E:B(7o4Q68=$#ZsH*WNf[s,<HH^BC!c
+s+R-F^BBjes*pjD^B'Ldp&>';:](.m`E.WjJcD_LJ,~>
+#NS0Qs4Zf=*W#d;ql"c5rrUHX,5qNBGcC\W!KEfHrrM(?r;Qi:U."t[!@^M;^B&_orr3&@!-8&<
+!Sfs<rr>sq^CtP2s0b1+^V=R6rYN>5HbX4Is7Q?*#\)ch^P5S"^[V7&!NL5.rsA82Ch^Z!q#AcZ
+rP&>0q#:E#)he4*!*/Ie!F<J5rs=H,5X7I]s8RS?rr3#E:B(7o4Q68=$#ZsH*WNf[s,<HH^BC!c
+s+R-F^BBjes*pjD^B'Ldp&>';:](.m`E.WjJcD_LJ,~>
+#NS0Qs4Zf=*W#d;ql"c5rrUHX,5qNBGcC\W!KEfHrrM(?r;Qi:U."t[!@^M;^B&_orr3&@!-8&<
+!Sfs<rr>sq^CtP2s0b1+^V=R6rYN>5HbX4Is7Q?*#\)ch^P5S"^[V7&!NL5.rsA82Ch^Z!q#AcZ
+rP&>0q#:E#)he4*!*/Ie!F<J5rs=H,5X7I]s8RS?rr3#E:B(7o4Q68=$#ZsH*WNf[s,<HH^BC!c
+s+R-F^BBjes*pjD^B'Ldp&>';:](.m`E.WjJcD_LJ,~>
+!ouXLrVlj<q>UKC9D/;cqBGn<!DCl?rrJR?pAY0P48]$YfgPf=!@cOtrrIA?rr3&og["k="?7J0
+,`Vg'!$2%<&RW6Okq)#ts&R/=+9/3^s%^l:rrkjCs8TB>qu6\sF6ii,OGs/<!Q&%=rrCsOrrTH1
+mf*4d*U`q.?,-(4%YoShgX7PNs6XR>s6`D>rr3!]iVic_DQj'\s1Me>A[h[="LVqC?,6I>"KZSB
+<65(=!V5UMrrLA?qu6]9=+UC&s/H$K~>
+!ouXLrVlj<q>UKC9D/;cqBGn<!DCl?rrJR?pAY0P48]$YfgPf=!@cOtrrIA?rr3&og["k="?7J0
+,`Vg'!$2%<&RW6Okq)#ts&R/=+9/3^s%^l:rrkjCs8TB>qu6\sF6ii,OGs/<!Q&%=rrCsOrrTH1
+mf*4d*U`q.?,-(4%YoShgX7PNs6XR>s6`D>rr3!]iVic_DQj'\s1Me>A[h[="LVqC?,6I>"KZSB
+<65(=!V5UMrrLA?qu6]9=+UC&s/H$K~>
+!ouXLrVlj<q>UKC9D/;cqBGn<!DCl?rrJR?pAY0P48]$YfgPf=!@cOtrrIA?rr3&og["k="?7J0
+,`Vg'!$2%<&RW6Okq)#ts&R/=+9/3^s%^l:rrkjCs8TB>qu6\sF6ii,OGs/<!Q&%=rrCsOrrTH1
+mf*4d*U`q.?,-(4%YoShgX7PNs6XR>s6`D>rr3!]iVic_DQj'\s1Me>A[h[="LVqC?,6I>"KZSB
+<65(=!V5UMrrLA?qu6]9=+UC&s/H$K~>
+!ouXLrVlj<qYpWR7/HTV!W"#=rrGd@rr3"fK(f3FiB-r:!SKm>rrFS?rr3"DV"jrmpsqY'qu6Tq
+*W?!@?bQL(1]IC^qYgC8s/(#?:<rj^r>YtHfDklVCA\#3WcRM+!K`9=rrN(ur>Ygmq#:B"C]485
+*U`q.?,-(4"c&BCs7@c>rrd5Er7aR:rrG7@rVm1AYQ#XC]jLeN\,QC1[:oSG_#F?=XDn4+7iWLI
+oD\j9:](.m`E.WjJcD_LJ,~>
+!ouXLrVlj<qYpWR7/HTV!W"#=rrGd@rr3"fK(f3FiB-r:!SKm>rrFS?rr3"DV"jrmpsqY'qu6Tq
+*W?!@?bQL(1]IC^qYgC8s/(#?:<rj^r>YtHfDklVCA\#3WcRM+!K`9=rrN(ur>Ygmq#:B"C]485
+*U`q.?,-(4"c&BCs7@c>rrd5Er7aR:rrG7@rVm1AYQ#XC]jLeN\,QC1[:oSG_#F?=XDn4+7iWLI
+oD\j9:](.m`E.WjJcD_LJ,~>
+!ouXLrVlj<qYpWR7/HTV!W"#=rrGd@rr3"fK(f3FiB-r:!SKm>rrFS?rr3"DV"jrmpsqY'qu6Tq
+*W?!@?bQL(1]IC^qYgC8s/(#?:<rj^r>YtHfDklVCA\#3WcRM+!K`9=rrN(ur>Ygmq#:B"C]485
+*U`q.?,-(4"c&BCs7@c>rrd5Er7aR:rrG7@rVm1AYQ#XC]jLeN\,QC1[:oSG_#F?=XDn4+7iWLI
+oD\j9:](.m`E.WjJcD_LJ,~>
+!ouXLrVlj<qu6]J7e?W\qBGn<!DCl?rrJR?pAY0P48]$YfgPf=!@c+hrrIA?rr3&lcfG')!Sfs<
+rr=)<rrc]Bs62?;rsAZHs/(#?:<rM[rr3Mf37n31Z>0F>gA1dK\p3N,rs+XEs8UUKoAKTI!$1k7
+!O6G=rr=)4rrV!%r;HWr?,-(4"c&BCs7@c=rrPU@*W5pJ4Q6.is8Qu?ruM-Q?iO]Hrr3,/B`DAR
+rr3,&E;rnYrr3#ejn8WSbtn9;!rAp@r;Qincf'HTJcE"TJ,~>
+!ouXLrVlj<qu6]J7e?W\qBGn<!DCl?rrJR?pAY0P48]$YfgPf=!@c+hrrIA?rr3&lcfG')!Sfs<
+rr=)<rrc]Bs62?;rsAZHs/(#?:<rM[rr3Mf37n31Z>0F>gA1dK\p3N,rs+XEs8UUKoAKTI!$1k7
+!O6G=rr=)4rrV!%r;HWr?,-(4"c&BCs7@c=rrPU@*W5pJ4Q6.is8Qu?ruM-Q?iO]Hrr3,/B`DAR
+rr3,&E;rnYrr3#ejn8WSbtn9;!rAp@r;Qincf'HTJcE"TJ,~>
+!ouXLrVlj<qu6]J7e?W\qBGn<!DCl?rrJR?pAY0P48]$YfgPf=!@c+hrrIA?rr3&lcfG')!Sfs<
+rr=)<rrc]Bs62?;rsAZHs/(#?:<rM[rr3Mf37n31Z>0F>gA1dK\p3N,rs+XEs8UUKoAKTI!$1k7
+!O6G=rr=)4rrV!%r;HWr?,-(4"c&BCs7@c=rrPU@*W5pJ4Q6.is8Qu?ruM-Q?iO]Hrr3,/B`DAR
+rr3,&E;rnYrr3#ejn8WSbtn9;!rAp@r;Qincf'HTJcE"TJ,~>
+&*-#Ys3n9Z'XG$js8U/Gp@S@j_ljo_cN!oqT`24tMQ$:L"M:6SZI]$T#e?<UZIo3W.8^#L!I_`^
+rrU7AErH"=gd(f;!+#Q/"EMoikqi8;$2u%H_4Ua&cM1ZDZ3k[gfDklmGI"Mcl2L\`N0*E+#I/ug
+Z<@4XE;d'B>PS4!Z"s=<!$1b4!_EC^rVln(^\.U1X,+'eY%dk7!dpbLr;R:NVPeS:s-]%U'XF*e
+s-&cJZN$0ks,EHGZ36;Zs+m3EZ2ouZp&>';:]14ni]?u:!nmW^JcC<$Z2]=~>
+&*-#Ys3n9Z'XG$js8U/Gp@S@j_ljo_cN!oqT`24tMQ$:L"M:6SZI]$T#e?<UZIo3W.8^#L!I_`^
+rrU7AErH"=gd(f;!+#Q/"EMoikqi8;$2u%H_4Ua&cM1ZDZ3k[gfDklmGI"Mcl2L\`N0*E+#I/ug
+Z<@4XE;d'B>PS4!Z"s=<!$1b4!_EC^rVln(^\.U1X,+'eY%dk7!dpbLr;R:NVPeS:s-]%U'XF*e
+s-&cJZN$0ks,EHGZ36;Zs+m3EZ2ouZp&>';:]14ni]?u:!nmW^JcC<$Z2]=~>
+&*-#Ys3n9Z'XG$js8U/Gp@S@j_ljo_cN!oqT`24tMQ$:L"M:6SZI]$T#e?<UZIo3W.8^#L!I_`^
+rrU7AErH"=gd(f;!+#Q/"EMoikqi8;$2u%H_4Ua&cM1ZDZ3k[gfDklmGI"Mcl2L\`N0*E+#I/ug
+Z<@4XE;d'B>PS4!Z"s=<!$1b4!_EC^rVln(^\.U1X,+'eY%dk7!dpbLr;R:NVPeS:s-]%U'XF*e
+s-&cJZN$0ks,EHGZ36;Zs+m3EZ2ouZp&>';:]14ni]?u:!nmW^JcC<$Z2]=~>
+"QVjNs/Gp1"JPkqP32B9!/UVg!2'8j!/(8b!i5k1qj%?1s8RBDB`P=8rr@0?B`tdBs+0V;B`P[A
+rrV%kXSr/"l]1oC!;HG+"NUQBnq[/C!WIHErr\VIs60Gr!l^>crr3)9Bkc?#rrTfah<b.GXD)D;
+s8U7?B`Rf#rrLNGrVlkDp&>)W'(Pr#"'oo4T_ABfVeKj`qu6\Hq>:0mH?oJh"INm3Boi8n!0dCr
+!0mK_!07%m!1Eid!/^\h!1roa"/_B0:]14nOc0,:!pOEmJcC<$Z2]=~>
+"QVjNs/Gp1"JPkqP32B9!/UVg!2'8j!/(8b!i5k1qj%?1s8RBDB`P=8rr@0?B`tdBs+0V;B`P[A
+rrV%kXSr/"l]1oC!;HG+"NUQBnq[/C!WIHErr\VIs60Gr!l^>crr3)9Bkc?#rrTfah<b.GXD)D;
+s8U7?B`Rf#rrLNGrVlkDp&>)W'(Pr#"'oo4T_ABfVeKj`qu6\Hq>:0mH?oJh"INm3Boi8n!0dCr
+!0mK_!07%m!1Eid!/^\h!1roa"/_B0:]14nOc0,:!pOEmJcC<$Z2]=~>
+"QVjNs/Gp1"JPkqP32B9!/UVg!2'8j!/(8b!i5k1qj%?1s8RBDB`P=8rr@0?B`tdBs+0V;B`P[A
+rrV%kXSr/"l]1oC!;HG+"NUQBnq[/C!WIHErr\VIs60Gr!l^>crr3)9Bkc?#rrTfah<b.GXD)D;
+s8U7?B`Rf#rrLNGrVlkDp&>)W'(Pr#"'oo4T_ABfVeKj`qu6\Hq>:0mH?oJh"INm3Boi8n!0dCr
+!0mK_!07%m!1Eid!/^\h!1roa"/_B0:]14nOc0,:!pOEmJcC<$Z2]=~>
+!ouXLg&D&Xqh5$jXoAF5fYd^i/+NT<"0mQ6OPKj9*SgYsQ\C-or;Qb\JcC<$VuM8~>
+!ouXLg&D&Xqh5$jXoAF5fYd^i/+NT<"0mQ6OPKj9*SgYsQ\C-or;Qb\JcC<$VuM8~>
+!ouXLg&D&Xqh5$jXoAF5fYd^i/+NT<"0mQ6OPKj9*SgYsQ\C-or;Qb\JcC<$VuM8~>
+!ouXLJcEIa!fWE@eGfLhJcC<$JcGNFJ,~>
+!ouXLJcEIa!fWE@eGfLhJcC<$JcGNFJ,~>
+!ouXLJcEIa!fWE@eGfLhJcC<$JcGNFJ,~>
+!ouXLJcEIa!RUJfrrBugs+13$s7lVE~>
+!ouXLJcEIa!RUJfrrBugs+13$s7lVE~>
+!ouXLJcEIa!RUJfrrBugs+13$s7lVE~>
+!ouXLrr3't-73*urrYk?->%i/VuHj>-71q,rrYb<->A&2JcDGDJ,~>
+!ouXLrr3't-73*urrYk?->%i/VuHj>-71q,rrYb<->A&2JcDGDJ,~>
+!ouXLrr3't-73*urrYk?->%i/VuHj>-71q,rrYb<->A&2JcDGDJ,~>
+!ouXLrr3'UiUusDrs(%<p%\ReoM>H+"C_!'=r@2X",R!J62hi)Dt`u+!V%uF*!C]as#M+t*!3#;
+5lDZ''<(^+!:'M&".8rb)Z1QV`W%.\*!$Vlq>UKpc2AUb[/Bt%Y5TCHaoDD<V]6\VqtU0lr3?2?
+"47(m&H*RN*rl,4r>Ygpq#:BkQMhd'mem(cnKn27"SQdS*96en!V^p>*!%tfr;Qr]1&q:ID#PCZ
+7.^HYq"=;#o`#1Qo$gS;s)5IHo)Ae=s1<%Frr_uf`D;'bJcDGDJ,~>
+!ouXLrr3'UiUusDrs(%<p%\ReoM>H+"C_!'=r@2X",R!J62hi)Dt`u+!V%uF*!C]as#M+t*!3#;
+5lDZ''<(^+!:'M&".8rb)Z1QV`W%.\*!$Vlq>UKpc2AUb[/Bt%Y5TCHaoDD<V]6\VqtU0lr3?2?
+"47(m&H*RN*rl,4r>Ygpq#:BkQMhd'mem(cnKn27"SQdS*96en!V^p>*!%tfr;Qr]1&q:ID#PCZ
+7.^HYq"=;#o`#1Qo$gS;s)5IHo)Ae=s1<%Frr_uf`D;'bJcDGDJ,~>
+!ouXLrr3'UiUusDrs(%<p%\ReoM>H+"C_!'=r@2X",R!J62hi)Dt`u+!V%uF*!C]as#M+t*!3#;
+5lDZ''<(^+!:'M&".8rb)Z1QV`W%.\*!$Vlq>UKpc2AUb[/Bt%Y5TCHaoDD<V]6\VqtU0lr3?2?
+"47(m&H*RN*rl,4r>Ygpq#:BkQMhd'mem(cnKn27"SQdS*96en!V^p>*!%tfr;Qr]1&q:ID#PCZ
+7.^HYq"=;#o`#1Qo$gS;s)5IHo)Ae=s1<%Frr_uf`D;'bJcDGDJ,~>
+!ouXLrr3!SlMLS^.eEW=!F3J?rrfBBs&7&:rrGd@rr3"fK)#?H3o^/="Gr?B1$eT6!pk9@q#:=7
+rVlt4[Jq9@rrV=od/!\BodB_<"@^r?ZYfX?#PtQEs8R)Bqt^6mdn0N<"!m]c;>^@o_HQp<!$1k7
+!O6G=rr=)<rsRa\$ig7pJueqO6eV87!K`<?rrM"?rVm)U2_"e,J"HZ>!R=I<rr=)3rrXeBdRsN=
+!C,E2rrX;A\7GO;!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eEW=!F3J?rrfBBs&7&:rrGd@rr3"fK)#?H3o^/="Gr?B1$eT6!pk9@q#:=7
+rVlt4[Jq9@rrV=od/!\BodB_<"@^r?ZYfX?#PtQEs8R)Bqt^6mdn0N<"!m]c;>^@o_HQp<!$1k7
+!O6G=rr=)<rsRa\$ig7pJueqO6eV87!K`<?rrM"?rVm)U2_"e,J"HZ>!R=I<rr=)3rrXeBdRsN=
+!C,E2rrX;A\7GO;!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eEW=!F3J?rrfBBs&7&:rrGd@rr3"fK)#?H3o^/="Gr?B1$eT6!pk9@q#:=7
+rVlt4[Jq9@rrV=od/!\BodB_<"@^r?ZYfX?#PtQEs8R)Bqt^6mdn0N<"!m]c;>^@o_HQp<!$1k7
+!O6G=rr=)<rsRa\$ig7pJueqO6eV87!K`<?rrM"?rVm)U2_"e,J"HZ>!R=I<rr=)3rrXeBdRsN=
+!C,E2rrX;A\7GO;!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eEW=!F3J?rt2;Os&7%P[C+B]s*rr+s6>N>q#:@Uir8r\M3Irokl.sigACmN
+QA>57!$2%<"D>.B*V99<!9="W!oJ@Aq#:Bf-iO&I7G$o\qu6of@a>#Rc!]u8rrLS?rVm!Gq9)Xm
+rj;k&s2%t<rr=)7rrKB?rVlj<rr3DWCt]p0s1Eg>s8TE?q#:AVNW/qYh`^u=#KpH:.KBEtT)S`j
+c;+<;!$1_3").AbJ,]HLGGkH=rrp[9]sY8prVlt,s36](rrLA?JcC<$U&TW~>
+!ouXLrr3!SlMLS^.eEW=!F3J?rt2;Os&7%P[C+B]s*rr+s6>N>q#:@Uir8r\M3Irokl.sigACmN
+QA>57!$2%<"D>.B*V99<!9="W!oJ@Aq#:Bf-iO&I7G$o\qu6of@a>#Rc!]u8rrLS?rVm!Gq9)Xm
+rj;k&s2%t<rr=)7rrKB?rVlj<rr3DWCt]p0s1Eg>s8TE?q#:AVNW/qYh`^u=#KpH:.KBEtT)S`j
+c;+<;!$1_3").AbJ,]HLGGkH=rrp[9]sY8prVlt,s36](rrLA?JcC<$U&TW~>
+!ouXLrr3!SlMLS^.eEW=!F3J?rt2;Os&7%P[C+B]s*rr+s6>N>q#:@Uir8r\M3Irokl.sigACmN
+QA>57!$2%<"D>.B*V99<!9="W!oJ@Aq#:Bf-iO&I7G$o\qu6of@a>#Rc!]u8rrLS?rVm!Gq9)Xm
+rj;k&s2%t<rr=)7rrKB?rVlj<rr3DWCt]p0s1Eg>s8TE?q#:AVNW/qYh`^u=#KpH:.KBEtT)S`j
+c;+<;!$1_3").AbJ,]HLGGkH=rrp[9]sY8prVlt,s36](rrLA?JcC<$U&TW~>
+!ouXLrr3!SlMLS^.eEW=%U?jMs6409s&42YAnH;1rVlreMkg%H#s.)Gs4gM9s"TH2Ac[D5rVlrX
+KX^^U!$2(="jT<>rtpjsAc[YJrr3)X=&duMrrMd?rr3/<.bF&Ai;T,MkPtS<GF=kBht[3Pdn0Q=
+"g\s>b;"YKAcn:t_HQs=!N[(3rrKB?rr38#*WQ/eI*Va4rr3,/Kpe?Lp\u"jNW9%XL->S:_fb#3
+.KBEtT)\ibHqsV>!$1G+!ILR>rruGICM%1'an>Z7btiojJcDGDJ,~>
+!ouXLrr3!SlMLS^.eEW=%U?jMs6409s&42YAnH;1rVlreMkg%H#s.)Gs4gM9s"TH2Ac[D5rVlrX
+KX^^U!$2(="jT<>rtpjsAc[YJrr3)X=&duMrrMd?rr3/<.bF&Ai;T,MkPtS<GF=kBht[3Pdn0Q=
+"g\s>b;"YKAcn:t_HQs=!N[(3rrKB?rr38#*WQ/eI*Va4rr3,/Kpe?Lp\u"jNW9%XL->S:_fb#3
+.KBEtT)\ibHqsV>!$1G+!ILR>rruGICM%1'an>Z7btiojJcDGDJ,~>
+!ouXLrr3!SlMLS^.eEW=%U?jMs6409s&42YAnH;1rVlreMkg%H#s.)Gs4gM9s"TH2Ac[D5rVlrX
+KX^^U!$2(="jT<>rtpjsAc[YJrr3)X=&duMrrMd?rr3/<.bF&Ai;T,MkPtS<GF=kBht[3Pdn0Q=
+"g\s>b;"YKAcn:t_HQs=!N[(3rrKB?rr38#*WQ/eI*Va4rr3,/Kpe?Lp\u"jNW9%XL->S:_fb#3
+.KBEtT)\ibHqsV>!$1G+!ILR>rruGICM%1'an>Z7btiojJcDGDJ,~>
+!ouXLrr3!SlMLS^.eEW=%^1>0I=>7;s/UbCs8Sj?rr3&pI\lc<$(cToI=F_Ms-e`BrrI\@rVlo)
+C%hQ,NrFG1Il4Y6MO4>B!FNP>rrgr*Jq)eUrrMtIrd=s#,M2<!If=p)Idd<rItE9%jS8`Ul@Jq_
+"b@<^jat#ArrX;AiILoU!J(s[rrLf'rd>''*<4K;aoDA]rr3,:J:`B,p\t98J,Xj)E]sH>JD'tp
+.KBF`J,XisBhnU+!$1D*!$2%<##o4*s8Sm?p&>';:P&Oss.TIC~>
+!ouXLrr3!SlMLS^.eEW=%^1>0I=>7;s/UbCs8Sj?rr3&pI\lc<$(cToI=F_Ms-e`BrrI\@rVlo)
+C%hQ,NrFG1Il4Y6MO4>B!FNP>rrgr*Jq)eUrrMtIrd=s#,M2<!If=p)Idd<rItE9%jS8`Ul@Jq_
+"b@<^jat#ArrX;AiILoU!J(s[rrLf'rd>''*<4K;aoDA]rr3,:J:`B,p\t98J,Xj)E]sH>JD'tp
+.KBF`J,XisBhnU+!$1D*!$2%<##o4*s8Sm?p&>';:P&Oss.TIC~>
+!ouXLrr3!SlMLS^.eEW=%^1>0I=>7;s/UbCs8Sj?rr3&pI\lc<$(cToI=F_Ms-e`BrrI\@rVlo)
+C%hQ,NrFG1Il4Y6MO4>B!FNP>rrgr*Jq)eUrrMtIrd=s#,M2<!If=p)Idd<rItE9%jS8`Ul@Jq_
+"b@<^jat#ArrX;AiILoU!J(s[rrLf'rd>''*<4K;aoDA]rr3,:J:`B,p\t98J,Xj)E]sH>JD'tp
+.KBF`J,XisBhnU+!$1D*!$2%<##o4*s8Sm?p&>';:P&Oss.TIC~>
+!ouXLrr3!SlMLS^.eET<"h("oZ`A*CrrJm@rr3":Y4V_tWLf]sM3Ii:!J-d?rrV2$`qKE4rViAi
+c"FH]rrHK?qu6[t`q]Q6nG]!_fgXN[*kVFO#sI26DSQ5O]l*B8rrD*YSc\%"q>:0o*WQ/qrgj/X
+*V]R6f)D6Do,[k1/H0&a$[;WhJ(Xf$TR?M6(!<,>TAMg3LH/dO>n;nos21G"VTqs7rr=)*rrJR?
+rr3!uaSu2?Uj;Y5!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eET<"h("oZ`A*CrrJm@rr3":Y4V_tWLf]sM3Ii:!J-d?rrV2$`qKE4rViAi
+c"FH]rrHK?qu6[t`q]Q6nG]!_fgXN[*kVFO#sI26DSQ5O]l*B8rrD*YSc\%"q>:0o*WQ/qrgj/X
+*V]R6f)D6Do,[k1/H0&a$[;WhJ(Xf$TR?M6(!<,>TAMg3LH/dO>n;nos21G"VTqs7rr=)*rrJR?
+rr3!uaSu2?Uj;Y5!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eET<"h("oZ`A*CrrJm@rr3":Y4V_tWLf]sM3Ii:!J-d?rrV2$`qKE4rViAi
+c"FH]rrHK?qu6[t`q]Q6nG]!_fgXN[*kVFO#sI26DSQ5O]l*B8rrD*YSc\%"q>:0o*WQ/qrgj/X
+*V]R6f)D6Do,[k1/H0&a$[;WhJ(Xf$TR?M6(!<,>TAMg3LH/dO>n;nos21G"VTqs7rr=)*rrJR?
+rr3!uaSu2?Uj;Y5!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eEH8!NU5;rro0Ds8Q]>n,EEFPPb@\JXl`>!ROO0rrHc?qu6\(^&.g1<lXb2
+"%Ci/0`D"R4l><[rVlmUkk"fT,l.?;!$1q9!$1Y1!Zh=)rON+H`;cKXrr3#g/G&lDh`_"srji0:
+*mOT^!R=I<rr=)*rs&7lJcGaLaSu2?Uj;Y5!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eEH8!NU5;rro0Ds8Q]>n,EEFPPb@\JXl`>!ROO0rrHc?qu6\(^&.g1<lXb2
+"%Ci/0`D"R4l><[rVlmUkk"fT,l.?;!$1q9!$1Y1!Zh=)rON+H`;cKXrr3#g/G&lDh`_"srji0:
+*mOT^!R=I<rr=)*rs&7lJcGaLaSu2?Uj;Y5!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eEH8!NU5;rro0Ds8Q]>n,EEFPPb@\JXl`>!ROO0rrHc?qu6\(^&.g1<lXb2
+"%Ci/0`D"R4l><[rVlmUkk"fT,l.?;!$1q9!$1Y1!Zh=)rON+H`;cKXrr3#g/G&lDh`_"srji0:
+*mOT^!R=I<rr=)*rs&7lJcGaLaSu2?Uj;Y5!R4Dks+13Ds*t~>
+!ouXLrr3!SlMLS^.eEK9"jpfCs)6?brro0Ds(Jn>qYpWa.;\k\"grXDs&/:ars"%Es8VR[iqr`W
+cmaeIrre^NoD_,TrrZWAs$?V`!ER5>rrTPVV>C2pI&Hf-0`D"X4l?.@E<#rUZMXY$!gkFQqYp\*
+^AcSurVlj<qu6[obPhGBHR[hJrrGL?qu6sCs8VS?WW2u_h>R?T?RbjKrrW-`i;ETUoI9\;!q&\@
+r;Qa;qu6[Y8Flcaa]&6><65(=!Mk#6rrLA?JcC<$U&TW~>
+!ouXLrr3!SlMLS^.eEK9"jpfCs)6?brro0Ds(Jn>qYpWa.;\k\"grXDs&/:ars"%Es8VR[iqr`W
+cmaeIrre^NoD_,TrrZWAs$?V`!ER5>rrTPVV>C2pI&Hf-0`D"X4l?.@E<#rUZMXY$!gkFQqYp\*
+^AcSurVlj<qu6[obPhGBHR[hJrrGL?qu6sCs8VS?WW2u_h>R?T?RbjKrrW-`i;ETUoI9\;!q&\@
+r;Qa;qu6[Y8Flcaa]&6><65(=!Mk#6rrLA?JcC<$U&TW~>
+!ouXLrr3!SlMLS^.eEK9"jpfCs)6?brro0Ds(Jn>qYpWa.;\k\"grXDs&/:ars"%Es8VR[iqr`W
+cmaeIrre^NoD_,TrrZWAs$?V`!ER5>rrTPVV>C2pI&Hf-0`D"X4l?.@E<#rUZMXY$!gkFQqYp\*
+^AcSurVlj<qu6[obPhGBHR[hJrrGL?qu6sCs8VS?WW2u_h>R?T?RbjKrrW-`i;ETUoI9\;!q&\@
+r;Qa;qu6[Y8Flcaa]&6><65(=!Mk#6rrLA?JcC<$U&TW~>
+!ouXLrr3'Uh!jt7rrXb3nFZ_[#ZMc`9(r;bp.>6'"D[`3<Z:oV!0I3[!p^7krVm-\*#rScs8VaD
+rYu)rqZ$T`48SsYb6i:hrrDop*!Cojs8V9\*!;u6s5<hq!NQ(srrT5,PlC[_h#88rTD\`gb5E:c
+^]"31WuN+^r;HWsM[$WhrrBe4*!EqQs89%u*!>!rs0)DA!QtE?rrQs=fDbdMT`,o2f_YUJ/,oPO
+q/RGpkPY>]9EP%hrrdon*$4bLrrMaLrr3+B*#pR'rr3)nnF.IqrrF>jo)AmkXapFYbPqMBWIagD
+"7krt:P&Oss.TIC~>
+!ouXLrr3'Uh!jt7rrXb3nFZ_[#ZMc`9(r;bp.>6'"D[`3<Z:oV!0I3[!p^7krVm-\*#rScs8VaD
+rYu)rqZ$T`48SsYb6i:hrrDop*!Cojs8V9\*!;u6s5<hq!NQ(srrT5,PlC[_h#88rTD\`gb5E:c
+^]"31WuN+^r;HWsM[$WhrrBe4*!EqQs89%u*!>!rs0)DA!QtE?rrQs=fDbdMT`,o2f_YUJ/,oPO
+q/RGpkPY>]9EP%hrrdon*$4bLrrMaLrr3+B*#pR'rr3)nnF.IqrrF>jo)AmkXapFYbPqMBWIagD
+"7krt:P&Oss.TIC~>
+!ouXLrr3'Uh!jt7rrXb3nFZ_[#ZMc`9(r;bp.>6'"D[`3<Z:oV!0I3[!p^7krVm-\*#rScs8VaD
+rYu)rqZ$T`48SsYb6i:hrrDop*!Cojs8V9\*!;u6s5<hq!NQ(srrT5,PlC[_h#88rTD\`gb5E:c
+^]"31WuN+^r;HWsM[$WhrrBe4*!EqQs89%u*!>!rs0)DA!QtE?rrQs=fDbdMT`,o2f_YUJ/,oPO
+q/RGpkPY>]9EP%hrrdon*$4bLrrMaLrr3+B*#pR'rr3)nnF.IqrrF>jo)AmkXapFYbPqMBWIagD
+"7krt:P&Oss.TIC~>
+!ouXLrr3(#.k>-.rrZ"H.r+/F!R+C#rrK-?j8T.9PiMcEB=?k#!CbW#rrFA?k5PO+.k=!arrMm@
+h>[RM.k>Ifs+13Ds*t~>
+!ouXLrr3(#.k>-.rrZ"H.r+/F!R+C#rrK-?j8T.9PiMcEB=?k#!CbW#rrFA?k5PO+.k=!arrMm@
+h>[RM.k>Ifs+13Ds*t~>
+!ouXLrr3(#.k>-.rrZ"H.r+/F!R+C#rrK-?j8T.9PiMcEB=?k#!CbW#rrFA?k5PO+.k=!arrMm@
+h>[RM.k>Ifs+13Ds*t~>
+!ouXLf)GgITn2;)!q2;?jSo;DK<jS<!o8"AjSo;1B&htP!m4UAh#@Dd@Y+Q1s+14Gs*t~>
+!ouXLf)GgITn2;)!q2;?jSo;DK<jS<!o8"AjSo;1B&htP!m4UAh#@Dd@Y+Q1s+14Gs*t~>
+!ouXLf)GgITn2;)!q2;?jSo;DK<jS<!o8"AjSo;1B&htP!m4UAh#@Dd@Y+Q1s+14Gs*t~>
+!ouXLf)GdAK&ck3h3[1%!QhG'rrKLBj8T.Pe`6Z1MsB's!-NkmJcC<$q#>j~>
+!ouXLf)GdAK&ck3h3[1%!QhG'rrKLBj8T.Pe`6Z1MsB's!-NkmJcC<$q#>j~>
+!ouXLf)GdAK&ck3h3[1%!QhG'rrKLBj8T.Pe`6Z1MsB's!-NkmJcC<$q#>j~>
+!ouXLL&V1cdZa\2NkPGmp\oXB_RKOFg\u[&hYmHTaMn&V!Q<*2s-Nb9~>
+!ouXLL&V1cdZa\2NkPGmp\oXB_RKOFg\u[&hYmHTaMn&V!Q<*2s-Nb9~>
+!ouXLL&V1cdZa\2NkPGmp\oXB_RKOFg\u[&hYmHTaMn&V!Q<*2s-Nb9~>
+"lqsOs8S>_@fVJerrJ2Co)A`3GfB[_Fl<9$^4G)<rrUAR]C#V#c!t8,r;Qi,A'k5T!S*h3rrSHq
+B[m%_G,G3;",$1/N;ih\O(7qad\H:2"b:Cgs2))8rr[EDb,b79"R[8"SC%98#OmHI]"7qAr;L1+
+Xks'Xj8EHfg%PFR`if?(j8]/>S=DXRrVlrKIEMKf!muA@JcD,;J,~>
+"lqsOs8S>_@fVJerrJ2Co)A`3GfB[_Fl<9$^4G)<rrUAR]C#V#c!t8,r;Qi,A'k5T!S*h3rrSHq
+B[m%_G,G3;",$1/N;ih\O(7qad\H:2"b:Cgs2))8rr[EDb,b79"R[8"SC%98#OmHI]"7qAr;L1+
+Xks'Xj8EHfg%PFR`if?(j8]/>S=DXRrVlrKIEMKf!muA@JcD,;J,~>
+"lqsOs8S>_@fVJerrJ2Co)A`3GfB[_Fl<9$^4G)<rrUAR]C#V#c!t8,r;Qi,A'k5T!S*h3rrSHq
+B[m%_G,G3;",$1/N;ih\O(7qad\H:2"b:Cgs2))8rr[EDb,b79"R[8"SC%98#OmHI]"7qAr;L1+
+Xks'Xj8EHfg%PFR`if?(j8]/>S=DXRrVlrKIEMKf!muA@JcD,;J,~>
+"lqsOs-ArL\-AC]s8U8Gp@J:b^VS_*!V0skrr=(trrJC?r;Qi]\DZcS"6d6nWVlbthRMhLrrK]?
+nc&`YPdn,0hZ!V$j8/cV*rc*;$o@/%6N@(IR(-<![:oR>!FYftrs!qIZ=X'*JbK*G*WN'\pAY0]
+0)PYRju37"NrT,`rjVu\[,Crbo3D7(!Ft<errf'Bs%:`;rrHK?r;Qe>W;HSqCpAQkR/_[~>
+"lqsOs-ArL\-AC]s8U8Gp@J:b^VS_*!V0skrr=(trrJC?r;Qi]\DZcS"6d6nWVlbthRMhLrrK]?
+nc&`YPdn,0hZ!V$j8/cV*rc*;$o@/%6N@(IR(-<![:oR>!FYftrs!qIZ=X'*JbK*G*WN'\pAY0]
+0)PYRju37"NrT,`rjVu\[,Crbo3D7(!Ft<errf'Bs%:`;rrHK?r;Qe>W;HSqCpAQkR/_[~>
+"lqsOs-ArL\-AC]s8U8Gp@J:b^VS_*!V0skrr=(trrJC?r;Qi]\DZcS"6d6nWVlbthRMhLrrK]?
+nc&`YPdn,0hZ!V$j8/cV*rc*;$o@/%6N@(IR(-<![:oR>!FYftrs!qIZ=X'*JbK*G*WN'\pAY0]
+0)PYRju37"NrT,`rjVu\[,Crbo3D7(!Ft<errf'Bs%:`;rrHK?r;Qe>W;HSqCpAQkR/_[~>
+"lqsOs)#">rrKc?rr3#R6HoH1m4[i+!$1%u!l&a@j8T.$WV6>m\mk40"E(CBpa"_r!CYT;rrHE@
+qu7'\l.SK(s(/\>b:?iZrr3!CqXsjm9[NaJ.rFSFrrX;AW-.h6!U_T;rs.[Es,N->ruM+=!JZs!
+rrW,+CB"56hYHpG"JU5B9%*Y;!FNP<rrV@pci!eEk$Q\js-`n;~>
+"lqsOs)#">rrKc?rr3#R6HoH1m4[i+!$1%u!l&a@j8T.$WV6>m\mk40"E(CBpa"_r!CYT;rrHE@
+qu7'\l.SK(s(/\>b:?iZrr3!CqXsjm9[NaJ.rFSFrrX;AW-.h6!U_T;rs.[Es,N->ruM+=!JZs!
+rrW,+CB"56hYHpG"JU5B9%*Y;!FNP<rrV@pci!eEk$Q\js-`n;~>
+"lqsOs)#">rrKc?rr3#R6HoH1m4[i+!$1%u!l&a@j8T.$WV6>m\mk40"E(CBpa"_r!CYT;rrHE@
+qu7'\l.SK(s(/\>b:?iZrr3!CqXsjm9[NaJ.rFSFrrX;AW-.h6!U_T;rs.[Es,N->ruM+=!JZs!
+rrW,+CB"56hYHpG"JU5B9%*Y;!FNP<rrV@pci!eEk$Q\js-`n;~>
+$KOKTs7Z<hs3js?rVlrY6h^6R!_aRerVlol6N.r*EV]V6!p^A$rr3!gf_uj%J,91/m/R*]LB%9=
+)?h*#PkY1Xn,44/S,<4+cMedcU\t/Xr>Z3Fr;JVFV]6\]rVt:D*!NkNs8TB>r;QlD*#=n?rrN)2
+r>Yj_rq??mF/f-<"0W[Z[f6:.\m:*j*!#='rrQ^Air/l^DQa$>o)JQDrYuV$o`(\EnkpG]p&C<n
+*5aNjp&G&<rYu2-pA^*l#8eFOrVlm\iVWWkoD\^\s8Vfis8MN[s8R!W"<8Cns8VdbrYuMaq#C!a
+s8DH\s8PmA,9.\Iq>UBrnc/I[pAY0]0)PYRrU^'aq#C@7rpg2AQiI(9rVm%8_#OG]>5\C+/,u]"
+s'!eLdet-g\,H<I&cNaP^A\&Lr>Z*^rr<#op]'m`r;Qe)^&.g1=2t.;!D^pks-`n;~>
+$KOKTs7Z<hs3js?rVlrY6h^6R!_aRerVlol6N.r*EV]V6!p^A$rr3!gf_uj%J,91/m/R*]LB%9=
+)?h*#PkY1Xn,44/S,<4+cMedcU\t/Xr>Z3Fr;JVFV]6\]rVt:D*!NkNs8TB>r;QlD*#=n?rrN)2
+r>Yj_rq??mF/f-<"0W[Z[f6:.\m:*j*!#='rrQ^Air/l^DQa$>o)JQDrYuV$o`(\EnkpG]p&C<n
+*5aNjp&G&<rYu2-pA^*l#8eFOrVlm\iVWWkoD\^\s8Vfis8MN[s8R!W"<8Cns8VdbrYuMaq#C!a
+s8DH\s8PmA,9.\Iq>UBrnc/I[pAY0]0)PYRrU^'aq#C@7rpg2AQiI(9rVm%8_#OG]>5\C+/,u]"
+s'!eLdet-g\,H<I&cNaP^A\&Lr>Z*^rr<#op]'m`r;Qe)^&.g1=2t.;!D^pks-`n;~>
+$KOKTs7Z<hs3js?rVlrY6h^6R!_aRerVlol6N.r*EV]V6!p^A$rr3!gf_uj%J,91/m/R*]LB%9=
+)?h*#PkY1Xn,44/S,<4+cMedcU\t/Xr>Z3Fr;JVFV]6\]rVt:D*!NkNs8TB>r;QlD*#=n?rrN)2
+r>Yj_rq??mF/f-<"0W[Z[f6:.\m:*j*!#='rrQ^Air/l^DQa$>o)JQDrYuV$o`(\EnkpG]p&C<n
+*5aNjp&G&<rYu2-pA^*l#8eFOrVlm\iVWWkoD\^\s8Vfis8MN[s8R!W"<8Cns8VdbrYuMaq#C!a
+s8DH\s8PmA,9.\Iq>UBrnc/I[pAY0]0)PYRrU^'aq#C@7rpg2AQiI(9rVm%8_#OG]>5\C+/,u]"
+s'!eLdet-g\,H<I&cNaP^A\&Lr>Z*^rr<#op]'m`r;Qe)^&.g1=2t.;!D^pks-`n;~>
+!ouXLrVloJ8c&Gfd7a04!W"#=rrGd@rr3"fK)YcOm3ulNrrG/,rr37\PQ*B's8S[>rr3#d0D,8E
+*W?!@JAM6u:&b.n?bQ@:$Wb:Ii!OT+s%^l4-iO&KA)71bZ>079!K<->rrLn@rVlmLnFlk_F/f':
+!Gf"?rrTbCPlC[_*W?!JF=H>Os7+ZGP1KO1s.Fc=rt4`Os8REaaoCN]s,`6?39B$\qu6]J7fNDg
+7+hD:!Bf?,rrF>?rVlmtaSu2?ZB"_[#"'X1s8S:?n,EFV0(f/D)ZD/q'pnt#5McA>l>"B>s34C<
+rs0\GVfi#8c1WL_rrYdBmP"P="$YT'2uWaW<pTGY!FNP;rrH3@qu6[lc@Q"`s*t~>
+!ouXLrVloJ8c&Gfd7a04!W"#=rrGd@rr3"fK)YcOm3ulNrrG/,rr37\PQ*B's8S[>rr3#d0D,8E
+*W?!@JAM6u:&b.n?bQ@:$Wb:Ii!OT+s%^l4-iO&KA)71bZ>079!K<->rrLn@rVlmLnFlk_F/f':
+!Gf"?rrTbCPlC[_*W?!JF=H>Os7+ZGP1KO1s.Fc=rt4`Os8REaaoCN]s,`6?39B$\qu6]J7fNDg
+7+hD:!Bf?,rrF>?rVlmtaSu2?ZB"_[#"'X1s8S:?n,EFV0(f/D)ZD/q'pnt#5McA>l>"B>s34C<
+rs0\GVfi#8c1WL_rrYdBmP"P="$YT'2uWaW<pTGY!FNP;rrH3@qu6[lc@Q"`s*t~>
+!ouXLrVloJ8c&Gfd7a04!W"#=rrGd@rr3"fK)YcOm3ulNrrG/,rr37\PQ*B's8S[>rr3#d0D,8E
+*W?!@JAM6u:&b.n?bQ@:$Wb:Ii!OT+s%^l4-iO&KA)71bZ>079!K<->rrLn@rVlmLnFlk_F/f':
+!Gf"?rrTbCPlC[_*W?!JF=H>Os7+ZGP1KO1s.Fc=rt4`Os8REaaoCN]s,`6?39B$\qu6]J7fNDg
+7+hD:!Bf?,rrF>?rVlmtaSu2?ZB"_[#"'X1s8S:?n,EFV0(f/D)ZD/q'pnt#5McA>l>"B>s34C<
+rs0\GVfi#8c1WL_rrYdBmP"P="$YT'2uWaW<pTGY!FNP;rrH3@qu6[lc@Q"`s*t~>
+!ouXLrr3#?<Vl^sqPAT4rrMs?rVlmkdf07IRY(>7!BT6>rs4IFs"Wj>s.4]=rrML?o`"n3qYpO9
+rVlt,^&Rp,\cb7;s56$=rr[`)pfIF*!:9^b!O?J:rrJ(?rr3#S62gfa/F`B5!HY7;rrHo@rr3#1
+@f?<,*VfX@^dT:t7IU9VTR?b='_![Rs*LL?s6a\&OH'9"jki6$s4@7]!M)^ZrrGO?qu6[ZiTpLE
+,5V9<!J7$E\cC4RrrGL?rr3"_MXUQGmOn/3!$2(=$&4fHs7.Z>s*^O=rrLA@r;R$Ds/1#>:X/S[
+rVlsif'Y3irrYIAlsB\&!9s+T!FNP;rrH3@qu6[lc@Q"`s*t~>
+!ouXLrr3#?<Vl^sqPAT4rrMs?rVlmkdf07IRY(>7!BT6>rs4IFs"Wj>s.4]=rrML?o`"n3qYpO9
+rVlt,^&Rp,\cb7;s56$=rr[`)pfIF*!:9^b!O?J:rrJ(?rr3#S62gfa/F`B5!HY7;rrHo@rr3#1
+@f?<,*VfX@^dT:t7IU9VTR?b='_![Rs*LL?s6a\&OH'9"jki6$s4@7]!M)^ZrrGO?qu6[ZiTpLE
+,5V9<!J7$E\cC4RrrGL?rr3"_MXUQGmOn/3!$2(=$&4fHs7.Z>s*^O=rrLA@r;R$Ds/1#>:X/S[
+rVlsif'Y3irrYIAlsB\&!9s+T!FNP;rrH3@qu6[lc@Q"`s*t~>
+!ouXLrr3#?<Vl^sqPAT4rrMs?rVlmkdf07IRY(>7!BT6>rs4IFs"Wj>s.4]=rrML?o`"n3qYpO9
+rVlt,^&Rp,\cb7;s56$=rr[`)pfIF*!:9^b!O?J:rrJ(?rr3#S62gfa/F`B5!HY7;rrHo@rr3#1
+@f?<,*VfX@^dT:t7IU9VTR?b='_![Rs*LL?s6a\&OH'9"jki6$s4@7]!M)^ZrrGO?qu6[ZiTpLE
+,5V9<!J7$E\cC4RrrGL?rr3"_MXUQGmOn/3!$2(=$&4fHs7.Z>s*^O=rrLA@r;R$Ds/1#>:X/S[
+rVlsif'Y3irrYIAlsB\&!9s+T!FNP;rrH3@qu6[lc@Q"`s*t~>
+#NS0Qs8T`Ko(r@eT76G4!W"#=rrGd@rr3"fK)#?H3o^/=#`4cF1$el>Spp\=!UVQ4rr=)9rr=)<
+rrZWAs-rsq"<kebi&po:!9a;h"c`$Os0?D9rrJ(?rr3#S62gfa/F`B5!HY7;rrHo@rr3#1@f?<,
+*VTL6DQ`s<!MFi>rrqmCs8RG?r;R(bNW2!"h`_"eHN%=R;+CQb!CYT;rrG7@li-u0qY^?nKDo9[
+GKfj^!CPQ>rrJ=@n,EFV0(f/D*WH'FL6qr?pM7=mJ&M?d!R4I=rs0\GVfi#8c1WL_rrYdBmP"P=
+"$YT7OoAbhbk(i8?bQ@:!E[;<rrGm?JcD/<J,~>
+#NS0Qs8T`Ko(r@eT76G4!W"#=rrGd@rr3"fK)#?H3o^/=#`4cF1$el>Spp\=!UVQ4rr=)9rr=)<
+rrZWAs-rsq"<kebi&po:!9a;h"c`$Os0?D9rrJ(?rr3#S62gfa/F`B5!HY7;rrHo@rr3#1@f?<,
+*VTL6DQ`s<!MFi>rrqmCs8RG?r;R(bNW2!"h`_"eHN%=R;+CQb!CYT;rrG7@li-u0qY^?nKDo9[
+GKfj^!CPQ>rrJ=@n,EFV0(f/D*WH'FL6qr?pM7=mJ&M?d!R4I=rs0\GVfi#8c1WL_rrYdBmP"P=
+"$YT7OoAbhbk(i8?bQ@:!E[;<rrGm?JcD/<J,~>
+#NS0Qs8T`Ko(r@eT76G4!W"#=rrGd@rr3"fK)#?H3o^/=#`4cF1$el>Spp\=!UVQ4rr=)9rr=)<
+rrZWAs-rsq"<kebi&po:!9a;h"c`$Os0?D9rrJ(?rr3#S62gfa/F`B5!HY7;rrHo@rr3#1@f?<,
+*VTL6DQ`s<!MFi>rrqmCs8RG?r;R(bNW2!"h`_"eHN%=R;+CQb!CYT;rrG7@li-u0qY^?nKDo9[
+GKfj^!CPQ>rrJ=@n,EFV0(f/D*WH'FL6qr?pM7=mJ&M?d!R4I=rs0\GVfi#8c1WL_rrYdBmP"P=
+"$YT7OoAbhbk(i8?bQ@:!E[;<rrGm?JcD/<J,~>
+#38'Ps0mL>qYpS^L@bEGqBGn<!DCl?rrJR?rr3&lJuSbL!BP?%rs4IFs"Wj>s.4]=rrqdCs8Td\
+q#:=7rVlu-g]&;mrrZWAruM+="hQ>*s56$;rrMt]rVm'jfDkm'J&M6a!K<->rrLn@rVlmLnFlk_
+F/f':!Gf"?rrTbe_>aH7*W?!>Y(H0ArrI#?rVlnkI/a-Ln1=V>H_UH<%Zl\M39B$\s+cm>s7pM$
+rr3*hf`1-5rr3%hJc>$9#9s$Ef"(g]jSo/[Uj;b8!CPQ>rrJ=@pAY3]K!G:S!U_T4rr=)=rrIk@
+rr3)N8",&.rt!@Ns1?e\ruV3<FoP7^p*Tb<"%Ur10E(nQ52PB[rVlngoC`+a?bQC;!kWsAr;Qi-
+J*-\/!jdLEJcDGDJ,~>
+#38'Ps0mL>qYpS^L@bEGqBGn<!DCl?rrJR?rr3&lJuSbL!BP?%rs4IFs"Wj>s.4]=rrqdCs8Td\
+q#:=7rVlu-g]&;mrrZWAruM+="hQ>*s56$;rrMt]rVm'jfDkm'J&M6a!K<->rrLn@rVlmLnFlk_
+F/f':!Gf"?rrTbe_>aH7*W?!>Y(H0ArrI#?rVlnkI/a-Ln1=V>H_UH<%Zl\M39B$\s+cm>s7pM$
+rr3*hf`1-5rr3%hJc>$9#9s$Ef"(g]jSo/[Uj;b8!CPQ>rrJ=@pAY3]K!G:S!U_T4rr=)=rrIk@
+rr3)N8",&.rt!@Ns1?e\ruV3<FoP7^p*Tb<"%Ur10E(nQ52PB[rVlngoC`+a?bQC;!kWsAr;Qi-
+J*-\/!jdLEJcDGDJ,~>
+#38'Ps0mL>qYpS^L@bEGqBGn<!DCl?rrJR?rr3&lJuSbL!BP?%rs4IFs"Wj>s.4]=rrqdCs8Td\
+q#:=7rVlu-g]&;mrrZWAruM+="hQ>*s56$;rrMt]rVm'jfDkm'J&M6a!K<->rrLn@rVlmLnFlk_
+F/f':!Gf"?rrTbe_>aH7*W?!>Y(H0ArrI#?rVlnkI/a-Ln1=V>H_UH<%Zl\M39B$\s+cm>s7pM$
+rr3*hf`1-5rr3%hJc>$9#9s$Ef"(g]jSo/[Uj;b8!CPQ>rrJ=@pAY3]K!G:S!U_T4rr=)=rrIk@
+rr3)N8",&.rt!@Ns1?e\ruV3<FoP7^p*Tb<"%Ur10E(nQ52PB[rVlngoC`+a?bQC;!kWsAr;Qi-
+J*-\/!jdLEJcDGDJ,~>
+"lqsOs(t7(Ad+k-s,iH?o`#2OA.E4Bs8TQHral1Wc2R_Ekl=QZrrG0:ralakeGlXcAnH==_uKc3
+OCi*Ug\CdKUA\[+K]2qOrVlt,^&OGuAd*cC`rFsZr;QfpOo8kmMspZF!L8H=rs7Fm3'([/s7)'H
+Ac\"hq#:rOAhHJ`n,NF0Ah-A`o`)KaJ,X$[TDeck=odLZrrI#?rVloOJGs-aBrh:6H_UH<%Zl\M
+YOp^8s3H%(AnZ`brr3,.DJ!jtrVlm>rp9XiVe9Uc`rEYjral1UanYl:6eVJ=!L&E7rrUmS@/^*+
+mOn/3!2BI)!IiSurr_\IHHlEg&*o'aK&$D+P@d08Ar5jNOo8koMsgA%rFQ.ko(A%AAc[qbp&>&"
+^&7m2FK#*:!Go%<rrQ[1eq*jps*t~>
+"lqsOs(t7(Ad+k-s,iH?o`#2OA.E4Bs8TQHral1Wc2R_Ekl=QZrrG0:ralakeGlXcAnH==_uKc3
+OCi*Ug\CdKUA\[+K]2qOrVlt,^&OGuAd*cC`rFsZr;QfpOo8kmMspZF!L8H=rs7Fm3'([/s7)'H
+Ac\"hq#:rOAhHJ`n,NF0Ah-A`o`)KaJ,X$[TDeck=odLZrrI#?rVloOJGs-aBrh:6H_UH<%Zl\M
+YOp^8s3H%(AnZ`brr3,.DJ!jtrVlm>rp9XiVe9Uc`rEYjral1UanYl:6eVJ=!L&E7rrUmS@/^*+
+mOn/3!2BI)!IiSurr_\IHHlEg&*o'aK&$D+P@d08Ar5jNOo8koMsgA%rFQ.ko(A%AAc[qbp&>&"
+^&7m2FK#*:!Go%<rrQ[1eq*jps*t~>
+"lqsOs(t7(Ad+k-s,iH?o`#2OA.E4Bs8TQHral1Wc2R_Ekl=QZrrG0:ralakeGlXcAnH==_uKc3
+OCi*Ug\CdKUA\[+K]2qOrVlt,^&OGuAd*cC`rFsZr;QfpOo8kmMspZF!L8H=rs7Fm3'([/s7)'H
+Ac\"hq#:rOAhHJ`n,NF0Ah-A`o`)KaJ,X$[TDeck=odLZrrI#?rVloOJGs-aBrh:6H_UH<%Zl\M
+YOp^8s3H%(AnZ`brr3,.DJ!jtrVlm>rp9XiVe9Uc`rEYjral1UanYl:6eVJ=!L&E7rrUmS@/^*+
+mOn/3!2BI)!IiSurr_\IHHlEg&*o'aK&$D+P@d08Ar5jNOo8koMsgA%rFQ.ko(A%AAc[qbp&>&"
+^&7m2FK#*:!Go%<rrQ[1eq*jps*t~>
+"QVjNs4.%T"O[8Lb4G6)!6+rF!7:`F!5e`C!7LiG!r97Jrr3![ir6=cfDbdR]=#&og>`,3"nB".
+h#DQp8cls2rVa,+iW&qlrVluIm/Qn\[0>C3mf3"#qu6ZgrNuXkrVlrWI)#[\!:Tlo"7Z?jm/=<m
+o(DiO!rDr[rNub%s8V3Z[06@+ldFMd[/g.'rrUNSpAP!le`Zl1!8IL\!4i+/!SQQ3rrM$6rr3;u
+a8c1h[C*O9ao25@^pV)XrrKi?li.$p[E\^N!6+rF!7:K?!Qk!5rrM'6pAY3dL1'u["RZ^k8u_Rb
+!<2u*!8.5L!UA,1rrVDlh>[E[p9f'B[HRYjo_l0"li7"TrNuaps8VQd[/f[irrgP<8fYPCrrTHY
+k5>5\X,-!:rrUWVo7?q8s*t~>
+"QVjNs4.%T"O[8Lb4G6)!6+rF!7:`F!5e`C!7LiG!r97Jrr3![ir6=cfDbdR]=#&og>`,3"nB".
+h#DQp8cls2rVa,+iW&qlrVluIm/Qn\[0>C3mf3"#qu6ZgrNuXkrVlrWI)#[\!:Tlo"7Z?jm/=<m
+o(DiO!rDr[rNub%s8V3Z[06@+ldFMd[/g.'rrUNSpAP!le`Zl1!8IL\!4i+/!SQQ3rrM$6rr3;u
+a8c1h[C*O9ao25@^pV)XrrKi?li.$p[E\^N!6+rF!7:K?!Qk!5rrM'6pAY3dL1'u["RZ^k8u_Rb
+!<2u*!8.5L!UA,1rrVDlh>[E[p9f'B[HRYjo_l0"li7"TrNuaps8VQd[/f[irrgP<8fYPCrrTHY
+k5>5\X,-!:rrUWVo7?q8s*t~>
+"QVjNs4.%T"O[8Lb4G6)!6+rF!7:`F!5e`C!7LiG!r97Jrr3![ir6=cfDbdR]=#&og>`,3"nB".
+h#DQp8cls2rVa,+iW&qlrVluIm/Qn\[0>C3mf3"#qu6ZgrNuXkrVlrWI)#[\!:Tlo"7Z?jm/=<m
+o(DiO!rDr[rNub%s8V3Z[06@+ldFMd[/g.'rrUNSpAP!le`Zl1!8IL\!4i+/!SQQ3rrM$6rr3;u
+a8c1h[C*O9ao25@^pV)XrrKi?li.$p[E\^N!6+rF!7:K?!Qk!5rrM'6pAY3dL1'u["RZ^k8u_Rb
+!<2u*!8.5L!UA,1rrVDlh>[E[p9f'B[HRYjo_l0"li7"TrNuaps8VQd[/f[irrgP<8fYPCrrTHY
+k5>5\X,-!:rrUWVo7?q8s*t~>
+!ouXLd/O,-ip?[Fh##J!nC@O>kO7p?!;u]@!<0,#!7o'f!PDh=rrDurd/`FerrK$?h>[KGrm:`;
+rVloalMLS^lKj*%QiDR~>
+!ouXLd/O,-ip?[Fh##J!nC@O>kO7p?!;u]@!<0,#!7o'f!PDh=rrDurd/`FerrK$?h>[KGrm:`;
+rVloalMLS^lKj*%QiDR~>
+!ouXLd/O,-ip?[Fh##J!nC@O>kO7p?!;u]@!<0,#!7o'f!PDh=rrDurd/`FerrK$?h>[KGrm:`;
+rVloalMLS^lKj*%QiDR~>
+!ouXLd/O,5jFR>grrV%Uo[NmAWdB<sJcF[.J,~>
+!ouXLd/O,5jFR>grrV%Uo[NmAWdB<sJcF[.J,~>
+!ouXLd/O,5jFR>grrV%Uo[NmAWdB<sJcF[.J,~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+#in9Rs8UK[]uSt/!65#O!82r'JcC<$JcE(VJ,~>
+#in9Rs8UK[]uSt/!65#O!82r'JcC<$JcE(VJ,~>
+#in9Rs8UK[]uSt/!65#O!82r'JcC<$JcE(VJ,~>
+$04BSs2f=g?>-n,rrKZDra#VPe:IXNs+13$s0VfV~>
+$04BSs2f=g?>-n,rrKZDra#VPe:IXNs+13$s0VfV~>
+$04BSs2f=g?>-n,rrKZDra#VPe:IXNs+13$s0VfV~>
+$04BSs)#"?s4Kcsrs#$Hqu?]9_L_`<s+13$s0VfV~>
+$04BSs)#"?s4Kcsrs#$Hqu?]9_L_`<s+13$s0VfV~>
+$04BSs)#"?s4Kcsrs#$Hqu?]9_L_`<s+13$s0VfV~>
+%HKfWs)#"?s4Kd>s.n3ErrLQFrr3"HrVca&Rc"$ks2_GEs+C;b!2TVo"=7VnBrV+3!."QX!3UnQ
+JcC<$JcE@^J,~>
+%HKfWs)#"?s4Kd>s.n3ErrLQFrr3"HrVca&Rc"$ks2_GEs+C;b!2TVo"=7VnBrV+3!."QX!3UnQ
+JcC<$JcE@^J,~>
+%HKfWs)#"?s4Kd>s.n3ErrLQFrr3"HrVca&Rc"$ks2_GEs+C;b!2TVo"=7VnBrV+3!."QX!3UnQ
+JcC<$JcE@^J,~>
+%HKfWs(sVe9L2&Gs(&Y=rrKK@rr3!Bqu-O$;p"k[s.ao?JY<"K"H'/XH_gYI"Ga/[Ff55F!J@_0
+s+13$s+13_s*t~>
+%HKfWs(sVe9L2&Gs(&Y=rrKK@rr3!Bqu-O$;p"k[s.ao?JY<"K"H'/XH_gYI"Ga/[Ff55F!J@_0
+s+13$s+13_s*t~>
+%HKfWs(sVe9L2&Gs(&Y=rrKK@rr3!Bqu-O$;p"k[s.ao?JY<"K"H'/XH_gYI"Ga/[Ff55F!J@_0
+s+13$s+13_s*t~>
+'')>\s)!eBc`fe:s5I;>s4Ui>rr3!Bqu-O$;p"k[s.ao?9@Eh>"IarB6JDG="Hn]C3o^/=!U8m#
+s+13$s+13_s*t~>
+'')>\s)!eBc`fe:s5I;>s4Ui>rr3!Bqu-O$;p"k[s.ao?9@Eh>"IarB6JDG="Hn]C3o^/=!U8m#
+s+13$s+13_s*t~>
+'')>\s)!eBc`fe:s5I;>s4Ui>rr3!Bqu-O$;p"k[s.ao?9@Eh>"IarB6JDG="Hn]C3o^/=!U8m#
+s+13$s+13_s*t~>
+$04BSs)#"?s4Kd=rrc$Brle7<rrF;?rVm1&and4]UO)r5df07LRY(Q+gA_*WP)KA)1)q9LJcC<$
+JcC<$])R9~>
+$04BSs)#"?s4Kd=rrc$Brle7<rrF;?rVm1&and4]UO)r5df07LRY(Q+gA_*WP)KA)1)q9LJcC<$
+JcC<$])R9~>
+$04BSs)#"?s4Kd=rrc$Brle7<rrF;?rVm1&and4]UO)r5df07LRY(Q+gA_*WP)KA)1)q9LJcC<$
+JcC<$])R9~>
+$04BSs)#"?s4Kd<rrO\00)Y_[+oD#ss8Q$>qBGs7HN-Xdrr3+iK)\0krr3+aMuPitrr3#fn:CUj
+s+13$s1JA^~>
+$04BSs)#"?s4Kd<rrO\00)Y_[+oD#ss8Q$>qBGs7HN-Xdrr3+iK)\0krr3+aMuPitrr3#fn:CUj
+s+13$s1JA^~>
+$04BSs)#"?s4Kd<rrO\00)Y_[+oD#ss8Q$>qBGs7HN-Xdrr3+iK)\0krr3+aMuPitrr3#fn:CUj
+s+13$s1JA^~>
+$04BSs,i\`^T;JSrrQQ4@f66:=hUV\s8RPD]G\JHS,[`4rkASqU&T,7rkASmW;gY<rkAJeY(?V(
+s+13$s1JA^~>
+$04BSs,i\`^T;JSrrQQ4@f66:=hUV\s8RPD]G\JHS,[`4rkASqU&T,7rkASmW;gY<rkAJeY(?V(
+s+13$s1JA^~>
+$04BSs,i\`^T;JSrrQQ4@f66:=hUV\s8RPD]G\JHS,[`4rkASqU&T,7rkASmW;gY<rkAJeY(?V(
+s+13$s1JA^~>
+#in9Rs8S-b>]flC!,q`6!FC9SrrddS$s].urr@?D>QC;nrr@0?>QCMtrr?s9>QC[Qs+13$s+13^
+s*t~>
+#in9Rs8S-b>]flC!,q`6!FC9SrrddS$s].urr@?D>QC;nrr@0?>QCMtrr?s9>QC[Qs+13$s+13^
+s*t~>
+#in9Rs8S-b>]flC!,q`6!FC9SrrddS$s].urr@?D>QC;nrr@0?>QCMtrr?s9>QC[Qs+13$s+13^
+s*t~>
+!ouXLk5PJY,(]cFs+13$s.TIC~>
+!ouXLk5PJY,(]cFs+13$s.TIC~>
+!ouXLk5PJY,(]cFs+13$s.TIC~>
+!ouXLk5PJY,(]cFs+13$s.TIC~>
+!ouXLk5PJY,(]cFs+13$s.TIC~>
+!ouXLk5PJY,(]cFs+13$s.TIC~>
+!ouXLk5PJ]\q0m4s+13$s.TIC~>
+!ouXLk5PJ]\q0m4s+13$s.TIC~>
+!ouXLk5PJ]\q0m4s+13$s.TIC~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLq#:A8^4H<8s+13$s,[21~>
+!ouXLq#:A8^4H<8s+13$s,[21~>
+!ouXLq#:A8^4H<8s+13$s,[21~>
+!ouXLq#:A*\:O[2s+13$s,[21~>
+!ouXLq#:A*\:O[2s+13$s,[21~>
+!ouXLq#:A*\:O[2s+13$s,[21~>
+"lqsOs8UXK_?A<es(&Y=rrMNAJcC<$JcC<$PlH7~>
+"lqsOs8UXK_?A<es(&Y=rrMNAJcC<$JcC<$PlH7~>
+"lqsOs8UXK_?A<es(&Y=rrMNAJcC<$JcC<$PlH7~>
+"lqsOs3,K^=UArcs(&Y>q504DJcC<$JcC<$PlH7~>
+"lqsOs3,K^=UArcs(&Y>q504DJcC<$JcC<$PlH7~>
+"lqsOs3,K^=UArcs(&Y>q504DJcC<$JcC<$PlH7~>
+"lqsOs)#">rs68Fs(&X1Lio;?s+13$s+136s*t~>
+"lqsOs)#">rs68Fs(&X1Lio;?s+13$s+136s*t~>
+"lqsOs)#">rs68Fs(&X1Lio;?s+13$s+136s*t~>
+"lqsOs)#">rs$,Ds("oaR=YBhs+13$s,m>3~>
+"lqsOs)#">rs$,Ds("oaR=YBhs+13$s,m>3~>
+"lqsOs)#">rs$,Ds("oaR=YBhs+13$s,m>3~>
+"lqsOs)#">rs$,Ds($Od?%N$,s+13$s,m>3~>
+"lqsOs)#">rs$,Ds($Od?%N$,s+13$s,m>3~>
+"lqsOs)#">rs$,Ds($Od?%N$,s+13$s,m>3~>
+"lqsOs)#">rs68Fs(&Xf^c$1`s+13$s+136s*t~>
+"lqsOs)#">rs68Fs(&Xf^c$1`s+13$s+136s*t~>
+"lqsOs)#">rs68Fs(&Xf^c$1`s+13$s+136s*t~>
+"lqsOs6s7l*!oL2s(8hArT1&)JcC<$JcC<$PlH7~>
+"lqsOs6s7l*!oL2s(8hArT1&)JcC<$JcC<$PlH7~>
+"lqsOs6s7l*!oL2s(8hArT1&)JcC<$JcC<$PlH7~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+!ouXLJcC<$JcC<$K`?Q~>
+"QVjNs/l3<"K)5!R-9,$JcC<$JcCi3J,~>
+"QVjNs/l3<"K)5!R-9,$JcC<$JcCi3J,~>
+"QVjNs/l3<"K)5!R-9,$JcC<$JcCi3J,~>
+"QVjNs3LYE#(Bt[s2&7>JcC<$JcC<$OoKq~>
+"QVjNs3LYE#(Bt[s2&7>JcC<$JcC<$OoKq~>
+"QVjNs3LYE#(Bt[s2&7>JcC<$JcC<$OoKq~>
+!ouXLr;QiX?JPP[!ROMks+13$s+134s*t~>
+!ouXLr;QiX?JPP[!ROMks+13$s+134s*t~>
+!ouXLr;QiX?JPP[!ROMks+13$s+134s*t~>
+!ouXLrVlrn3U6PB!S9bks+13$s+135s*t~>
+!ouXLrVlrn3U6PB!S9bks+13$s+135s*t~>
+!ouXLrVlrn3U6PB!S9bks+13$s+135s*t~>
+%%EndData
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/lib/stdlib/doc/src/win32reg.xml b/lib/stdlib/doc/src/win32reg.xml
new file mode 100644
index 0000000000..d8055047b0
--- /dev/null
+++ b/lib/stdlib/doc/src/win32reg.xml
@@ -0,0 +1,276 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2000</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>win32reg</title>
+ <prepared>Bjorn Gustavsson</prepared>
+ <responsible>NN</responsible>
+ <docno></docno>
+ <approved>nobody</approved>
+ <checked>no</checked>
+ <date>2000-08-10</date>
+ <rev>PA1</rev>
+ <file>win32reg.sgml</file>
+ </header>
+ <module>win32reg</module>
+ <modulesummary>win32reg provides access to the registry on Windows</modulesummary>
+ <description>
+ <p><c>win32reg</c> provides read and write access to the
+ registry on Windows. It is essentially a port driver wrapped around the
+ Win32 API calls for accessing the registry.</p>
+ <p>The registry is a hierarchical database, used to store various system
+ and software information in Windows. It is available in Windows 95 and
+ Windows NT. It contains installation data, and is updated by installers
+ and system programs. The Erlang installer updates the registry by adding
+ data that Erlang needs.</p>
+ <p>The registry contains keys and values. Keys are like the directories
+ in a file system, they form a hierarchy. Values are like files, they have
+ a name and a value, and also a type.</p>
+ <p>Paths to keys are left to right, with sub-keys to the right and backslash
+ between keys. (Remember that backslashes must be doubled in Erlang strings.)
+ Case is preserved but not significant.
+ Example: <c>"\\\\hkey_local_machine\\\\software\\\\Ericsson\\\\Erlang\\\\5.0"</c> is the key
+ for the installation data for the latest Erlang release.</p>
+ <p>There are six entry points in the Windows registry, top level keys. They can be
+ abbreviated in the <c>win32reg</c> module as:</p>
+ <pre>
+Abbrev. Registry key
+======= ============
+hkcr HKEY_CLASSES_ROOT
+current_user HKEY_CURRENT_USER
+hkcu HKEY_CURRENT_USER
+local_machine HKEY_LOCAL_MACHINE
+hklm HKEY_LOCAL_MACHINE
+users HKEY_USERS
+hku HKEY_USERS
+current_config HKEY_CURRENT_CONFIG
+hkcc HKEY_CURRENT_CONFIG
+dyn_data HKEY_DYN_DATA
+hkdd HKEY_DYN_DATA</pre>
+ <p>The key above could be written as <c>"\\\\hklm\\\\software\\\\ericsson\\\\erlang\\\\5.0"</c>.</p>
+ <p>The <c>win32reg</c> module uses a current key. It works much like the
+ current directory. From the current key, values can be fetched, sub-keys
+ can be listed, and so on.</p>
+ <p>Under a key, any number of named values can be stored. They have name, and
+ types, and data.</p>
+ <p>Currently, the <c>win32reg</c> module supports storing only the following
+ types: REG_DWORD, which is an
+ integer, REG_SZ which is a string and REG_BINARY which is a binary.
+ Other types can be read, and will be returned as binaries.</p>
+ <p>There is also a "default" value, which has the empty string as name. It is read and
+ written with the atom <c>default</c> instead of the name.</p>
+ <p>Some registry values are stored as strings with references to environment variables,
+ e.g. <c>"%SystemRoot%Windows"</c>. <c>SystemRoot</c> is an environment variable, and should be
+ replaced with its value. A function <c>expand/1</c> is provided, so that environment
+ variables surrounded in % can be expanded to their values.</p>
+ <p>For additional information on the Windows registry consult the Win32
+ Programmer's Reference.</p>
+ </description>
+ <funcs>
+ <func>
+ <name>change_key(RegHandle, Key) -> ReturnValue</name>
+ <fsummary>Move to a key in the registry</fsummary>
+ <type>
+ <v>RegHandle = term()</v>
+ <v>Key = string()</v>
+ </type>
+ <desc>
+ <p>Changes the current key to another key. Works like cd.
+ The key can be specified as a relative path or as an
+ absolute path, starting with \\.</p>
+ </desc>
+ </func>
+ <func>
+ <name>change_key_create(RegHandle, Key) -> ReturnValue</name>
+ <fsummary>Move to a key, create it if it is not there</fsummary>
+ <type>
+ <v>RegHandle = term()</v>
+ <v>Key = string()</v>
+ </type>
+ <desc>
+ <p>Creates a key, or just changes to it, if it is already there. Works
+ like a combination of <c>mkdir</c> and <c>cd</c>. Calls the Win32 API function
+ <c>RegCreateKeyEx()</c>.</p>
+ <p>The registry must have been opened in write-mode.</p>
+ </desc>
+ </func>
+ <func>
+ <name>close(RegHandle)-> ReturnValue</name>
+ <fsummary>Close the registry.</fsummary>
+ <type>
+ <v>RegHandle = term()</v>
+ </type>
+ <desc>
+ <p>Closes the registry. After that, the <c>RegHandle</c> cannot
+ be used.</p>
+ </desc>
+ </func>
+ <func>
+ <name>current_key(RegHandle) -> ReturnValue</name>
+ <fsummary>Return the path to the current key.</fsummary>
+ <type>
+ <v>RegHandle = term()</v>
+ <v>ReturnValue = {ok, string()}</v>
+ </type>
+ <desc>
+ <p>Returns the path to the current key. This is the equivalent of <c>pwd</c>.</p>
+ <p>Note that the current key is stored in the driver, and might be
+ invalid (e.g. if the key has been removed).</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_key(RegHandle) -> ReturnValue</name>
+ <fsummary>Delete the current key</fsummary>
+ <type>
+ <v>RegHandle = term()</v>
+ <v>ReturnValue = ok | {error, ErrorId}</v>
+ </type>
+ <desc>
+ <p>Deletes the current key, if it is valid. Calls the Win32 API
+ function <c>RegDeleteKey()</c>. Note that this call does not change the current key,
+ (unlike <c>change_key_create/2</c>.) This means that after the call, the
+ current key is invalid.</p>
+ </desc>
+ </func>
+ <func>
+ <name>delete_value(RegHandle, Name) -> ReturnValue</name>
+ <fsummary>Delete the named value on the current key.</fsummary>
+ <type>
+ <v>RegHandle = term()</v>
+ <v>ReturnValue = ok | {error, ErrorId}</v>
+ </type>
+ <desc>
+ <p>Deletes a named value on the current key. The atom <c>default</c> is
+ used for the the default value.</p>
+ <p>The registry must have been opened in write-mode.</p>
+ </desc>
+ </func>
+ <func>
+ <name>expand(String) -> ExpandedString</name>
+ <fsummary>Expand a string with environment variables</fsummary>
+ <type>
+ <v>String = string()</v>
+ <v>ExpandedString = string()</v>
+ </type>
+ <desc>
+ <p>Expands a string containing environment variables between percent
+ characters. Anything between two % is taken for a environment
+ variable, and is replaced by the value. Two consecutive % is replaced
+ by one %.</p>
+ <p>A variable name that is not in the environment, will result in an error.</p>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(ErrorId) -> ErrorString</name>
+ <fsummary>Convert an POSIX errorcode to a string</fsummary>
+ <type>
+ <v>ErrorId = atom()</v>
+ <v>ErrorString = string()</v>
+ </type>
+ <desc>
+ <p>Convert an POSIX errorcode to a string (by calling <c>erl_posix_msg:message</c>).</p>
+ </desc>
+ </func>
+ <func>
+ <name>open(OpenModeList)-> ReturnValue</name>
+ <fsummary>Open the registry for reading or writing</fsummary>
+ <type>
+ <v>OpenModeList = [OpenMode]</v>
+ <v>OpenMode = read | write</v>
+ </type>
+ <desc>
+ <p>Opens the registry for reading or writing. The current key will be the root
+ (<c>HKEY_CLASSES_ROOT</c>). The <c>read</c> flag in the mode list can be omitted.</p>
+ <p>Use <c>change_key/2</c> with an absolute path after <c>open</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>set_value(RegHandle, Name, Value) -> ReturnValue</name>
+ <fsummary>Set value at the current registry key with specified name.</fsummary>
+ <type>
+ <v>Name = string() | default</v>
+ <v>Value = string() | integer() | binary()</v>
+ </type>
+ <desc>
+ <p>Sets the named (or default) value to value. Calls the Win32
+ API function <c>RegSetValueEx()</c>. The value can be of three types, and
+ the corresponding registry type will be used. Currently the types supported
+ are: <c>REG_DWORD</c> for integers, <c>REG_SZ</c> for strings and
+ <c>REG_BINARY</c> for binaries. Other types cannot currently be added
+ or changed.</p>
+ <p>The registry must have been opened in write-mode.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sub_keys(RegHandle) -> ReturnValue</name>
+ <fsummary>Get subkeys to the current key.</fsummary>
+ <type>
+ <v>ReturnValue = {ok, SubKeys} | {error, ErrorId}</v>
+ <v>SubKeys = [SubKey]</v>
+ <v>SubKey = string()</v>
+ </type>
+ <desc>
+ <p>Returns a list of subkeys to the current key. Calls the Win32
+ API function <c>EnumRegKeysEx()</c>.</p>
+ <p>Avoid calling this on the root keys, it can be slow.</p>
+ </desc>
+ </func>
+ <func>
+ <name>value(RegHandle, Name) -> ReturnValue</name>
+ <fsummary>Get the named value on the current key.</fsummary>
+ <type>
+ <v>Name = string() | default</v>
+ <v>ReturnValue = {ok, Value}</v>
+ <v>Value = string() | integer() | binary()</v>
+ </type>
+ <desc>
+ <p>Retrieves the named value (or default) on the current key.
+ Registry values of type <c>REG_SZ</c>, are returned as strings. Type <c>REG_DWORD</c>
+ values are returned as integers. All other types are returned as binaries.</p>
+ </desc>
+ </func>
+ <func>
+ <name>values(RegHandle) -> ReturnValue</name>
+ <fsummary>Get all values on the current key.</fsummary>
+ <type>
+ <v>ReturnValue = {ok, ValuePairs} | {ok, ErrorId}</v>
+ <v>ValuePairs = [ValuePair]</v>
+ <v>ValuePair = {Name, Value}</v>
+ <v>Name = string | default</v>
+ <v>Value = string() | integer() | binary()</v>
+ </type>
+ <desc>
+ <p>Retrieves a list of all values on the current key. The values
+ have types corresponding to the registry types, see <c>value</c>.
+ Calls the Win32 API function <c>EnumRegValuesEx()</c>.</p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p>Win32 Programmer's Reference (from Microsoft)</p>
+ <p><c>erl_posix_msg</c></p>
+ <p>The Windows 95 Registry (book from O'Reilly)</p>
+ </section>
+</erlref>
+
diff --git a/lib/stdlib/doc/src/zip.xml b/lib/stdlib/doc/src/zip.xml
new file mode 100644
index 0000000000..e2ecfec8f0
--- /dev/null
+++ b/lib/stdlib/doc/src/zip.xml
@@ -0,0 +1,463 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2006</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>zip</title>
+ <prepared>Jakob Cederlund</prepared>
+ <responsible>Jakob Cederlund</responsible>
+ <docno>1</docno>
+ <approved></approved>
+ <checked></checked>
+ <date>05-11-02</date>
+ <rev>PA1</rev>
+ <file>zip.sgml</file>
+ </header>
+ <module>zip</module>
+ <modulesummary>Utility for reading and creating 'zip' archives.</modulesummary>
+ <description>
+ <p>The <c>zip</c> module archives and extract files to and from a zip
+ archive. The zip format is specified by the "ZIP Appnote.txt" file
+ available on PKWare's website www.pkware.com.</p>
+ <p>The zip module supports zip archive versions up to 6.1. However,
+ password-protection and Zip64 is not supported.</p>
+ <p>By convention, the name of a zip file should end in "<c>.zip</c>".
+ To abide to the convention, you'll need to add "<c>.zip</c>" yourself
+ to the name.</p>
+ <p>Zip archives are created with the
+ <seealso marker="#zip_2">zip/2</seealso> or the
+ <seealso marker="#zip_2">zip/3</seealso> function. (They are
+ also available as <c>create</c>, to resemble the <c>erl_tar</c>
+ module.)</p>
+ <p>To extract files from a zip archive, use the
+ <seealso marker="#unzip_1">unzip/1</seealso> or the
+ <seealso marker="#unzip_2">unzip/2</seealso> function. (They are
+ also available as <c>extract</c>.)</p>
+ <p>To return a list of the files in a zip archive, use the
+ <seealso marker="#list_dir_1">list_dir/1</seealso> or the
+ <seealso marker="#list_dir_2">list_dir/2</seealso> function. (They
+ are also available as <c>table</c>.)</p>
+ <p>To print a list of files to the Erlang shell,
+ use either the <seealso marker="#t_1">t/1</seealso> or
+ <seealso marker="#tt_1">tt/1</seealso> function.</p>
+ <p>In some cases, it is desirable to open a zip archive, and to
+ unzip files from it file by file, without having to reopen the
+ archive. The functions
+ <seealso marker="#zip_open">zip_open</seealso>,
+ <seealso marker="#zip_get">zip_get</seealso>,
+ <seealso marker="#zip_list_dir">zip_list_dir</seealso> and
+ <seealso marker="#zip_close">zip_close</seealso> do this.</p>
+ </description>
+
+ <section>
+ <title>LIMITATIONS</title>
+ <p>Zip64 archives are not currently supported.</p>
+ <p>Password-protected and encrypted archives are not currently
+ supported</p>
+ <p>Only the DEFLATE (zlib-compression) and the STORE (uncompressed
+ data) zip methods are supported.</p>
+ <p>The size of the archive is limited to 2 G-byte (32 bits).</p>
+ <p>Comments for individual files is not supported when creating zip
+ archives. The zip archive comment for the whole zip archive is
+ supported.</p>
+ <p>There is currently no support for altering an existing zip archive.
+ To add or remove a file from an archive, the whole archive must be
+ recreated.</p>
+ </section>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+zip_file() </code>
+ <p>The record <c>zip_file</c> contains the following fields.</p>
+ <taglist>
+ <tag><c>name = string()</c></tag>
+ <item>
+ <p>the name of the file</p>
+ </item>
+ <tag><c>info = file_info()</c></tag>
+ <item>
+ <p>file info as in
+ <seealso marker="erts:file#read_file_info/1">file:read_file_info/1</seealso></p>
+ </item>
+ <tag><c>comment = string()</c></tag>
+ <item>
+ <p>the comment for the file in the zip archive</p>
+ </item>
+ <tag><c>offset = integer()</c></tag>
+ <item>
+ <p>the offset of the file in the zip archive (used internally)</p>
+ </item>
+ <tag><c>comp_size = integer()</c></tag>
+ <item>
+ <p>the compressed size of the file (the uncompressed size is found
+ in <c>info</c>)</p>
+ </item>
+ </taglist>
+ <code type="none">zip_comment</code>
+ <p>The record <c>zip_comment</c> just contains the archive comment for
+ a zip archive</p>
+ <taglist>
+ <tag><c>comment = string()</c></tag>
+ <item>
+ <p>the comment for the zip archive</p>
+ </item>
+ </taglist>
+ </section>
+ <funcs>
+ <func>
+ <name>zip(Name, FileList) -> RetValue</name>
+ <name>zip(Name, FileList, Options) -> RetValue</name>
+ <name>create(Name, FileList) -> RetValue</name>
+ <name>create(Name, FileList, Options) -> RetValue</name>
+ <fsummary>Create a zip archive with options</fsummary>
+ <type>
+ <v>Name = filename()</v>
+ <v>FileList = [FileSpec]</v>
+ <v>FileSpec = filename() | {filename(), binary()}</v>
+ <v>Options = [Option]</v>
+ <v>Option = memory | cooked | verbose | {comment, Comment} | {cwd, CWD} | {compress, What} | {uncompress, What}</v>
+ <v>What = all | [Extension] | {add, [Extension]} | {del, [Extension]}</v>
+ <v>Extension = string()</v>
+ <v>Comment = CWD = string()</v>
+ <v>RetValue = {ok, Name} | {ok, {Name, binary()}} | {error, Reason}</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="zip_2"></marker><c>zip</c> function creates a
+ zip archive containing the files specified in <c>FileList</c>.</p>
+ <p>As synonyms, the functions <c>create/2</c> and <c>create/3</c>
+ are provided, to make it resemble the <c>erl_tar</c> module.</p>
+ <p>The file-list is a list of files, with paths relative to the
+ current directory, they will be stored with this path in the
+ archive. Files may also be specified with data in binaries,
+ to create an archive directly from data.</p>
+ <p>Files will be compressed using the DEFLATE compression, as
+ described in the Appnote.txt file. However, files will be
+ stored without compression if they already are compressed.
+ The <c>zip/2</c> and <c>zip/3</c> checks the file extension
+ to see whether the file should be stored without compression.
+ Files with the following extensions are not compressed:
+ <c>.Z</c>, <c>.zip</c>, <c>.zoo</c>, <c>.arc</c>, <c>.lzh</c>,
+ <c>.arj</c>.</p>
+ <p>It is possible to override the default behavior and
+ explicitly control what types of files that should be
+ compressed by using the <c>{compress, What}</c> and
+ <c>{uncompress, What}</c> options. It is possible to have
+ several <c>compress</c> and <c>uncompress</c> options. In
+ order to trigger compression of a file, its extension must
+ match with the
+ <c>compress</c> condition and must not match the
+ <c>uncompress</c> condition. For example if <c>compress</c> is
+ set to <c>["gif", "jpg"]</c> and <c>uncompress</c> is set to
+ <c>["jpg"]</c>, only files with <c>"gif"</c> as extension will
+ be compressed. No other files will be compressed.</p>
+ <p>The following options are available:</p>
+ <taglist>
+ <tag><c>cooked</c></tag>
+ <item>
+ <p>By default, the <c>open/2</c> function will open the
+ zip file in <c>raw</c> mode, which is faster but does not allow
+ a remote (erlang) file server to be used. Adding <c>cooked</c>
+ to the mode list will override the default and open the zip file
+ without the <c>raw</c> option. The same goes for the files
+ added.</p>
+ </item>
+ <tag><c>verbose</c></tag>
+ <item>
+ <p>Print an informational message about each file
+ being added.</p>
+ </item>
+ <tag><c>memory</c></tag>
+ <item>
+ <p>The output will not be to a file, but instead as a tuple
+ <c>{FileName, binary()}</c>. The binary will be a full zip
+ archive with header, and can be extracted with for instance
+ <c>unzip/2</c>.</p>
+ </item>
+ <tag><c>{comment, Comment}</c></tag>
+ <item>
+ <p>Add a comment to the zip-archive.</p>
+ </item>
+ <tag><c>{cwd, CWD}</c></tag>
+ <item>
+ <p>Use the given directory as current directory, it will be
+ prepended to file names when adding them, although it will not
+ be in the zip-archive. (Acting like a file:set_cwd/1, but
+ without changing the global cwd property.)</p>
+ </item>
+ <tag><c>{compress, What}</c></tag>
+ <item>
+ <p>Controls what types of files that will be
+ compressed. It is by default set to <c>all</c>. The
+ following values of <c>What</c> are allowed:</p>
+ <taglist>
+ <tag><c>all</c></tag>
+ <item><p> means that all files will be compressed (as long
+ as they pass the <c>uncompress</c> condition).</p></item>
+ <tag><c>[Extension]</c></tag>
+ <item><p>means that only files with exactly these extensions
+ will be compressed.</p></item>
+ <tag><c>{add,[Extension]}</c></tag>
+ <item><p>adds these extensions to the list of compress
+ extensions.</p></item>
+ <tag><c>{del,[Extension]}</c></tag>
+ <item><p>deletes these extensions from the list of compress
+ extensions.</p></item>
+ </taglist>
+ </item>
+ <tag><c>{uncompress, What}</c></tag>
+ <item>
+ <p>Controls what types of files that will be uncompressed. It is by
+ default set to <c>[".Z",".zip",".zoo",".arc",".lzh",".arj"]</c>.
+ The following values of <c>What</c> are allowed:</p>
+ <taglist>
+ <tag><c>all</c></tag>
+ <item><p> means that no files will be compressed.</p></item>
+ <tag><c>[Extension]</c></tag>
+ <item><p>means that files with these extensions will be
+ uncompressed.</p></item>
+ <tag><c>{add,[Extension]}</c></tag>
+ <item><p>adds these extensions to the list of uncompress
+ extensions.</p></item>
+ <tag><c>{del,[Extension]}</c></tag>
+ <item><p>deletes these extensions from the list of uncompress
+ extensions.</p></item>
+ </taglist>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>unzip(Archive) -> RetValue</name>
+ <name>unzip(Archive, Options) -> RetValue</name>
+ <name>extract(Archive) -> RetValue</name>
+ <name>extract(Archive, Options) -> RetValue</name>
+ <fsummary>Extract files from a zip archive</fsummary>
+ <type>
+ <v>Archive = filename() | binary()</v>
+ <v>Options = [Option]</v>
+ <v>Option = {file_list, FileList} | keep_old_files | verbose | memory | {file_filter, FileFilter} | {cwd, CWD}</v>
+ <v>FileList = [filename()]</v>
+ <v>FileBinList = [{filename(),binary()}]</v>
+ <v>FileFilter = fun(ZipFile) -> true | false</v>
+ <v>CWD = string()</v>
+ <v>ZipFile = zip_file()</v>
+ <v>RetValue = {ok,FileList} | {ok,FileBinList} | {error, Reason} | {error, {Name, Reason}}</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="unzip_1"></marker>
+<c>unzip/1</c> function extracts
+ all files from a zip archive. The
+ <marker id="unzip_2"></marker>
+<c>unzip/2</c> function provides options
+ to extract some files, and more.</p>
+ <p>If the <c>Archive</c> argument is given as a binary,
+ the contents of the binary is assumed to be a zip archive,
+ otherwise it should be a filename.</p>
+ <p>The following options are available:</p>
+ <taglist>
+ <tag><c>{file_list, FileList}</c></tag>
+ <item>
+ <p>By default, all files will be extracted from the zip
+ archive. With the <c>{file_list,FileList}</c> option,
+ the <c>unzip/2</c> function will only extract the files
+ whose names are included in <c>FileList</c>. The full
+ paths, including the names of all sub directories within
+ the zip archive, must be specified.</p>
+ </item>
+ <tag><c>cooked</c></tag>
+ <item>
+ <p>By default, the <c>open/2</c> function will open the
+ zip file in <c>raw</c> mode, which is faster but does not allow
+ a remote (erlang) file server to be used. Adding <c>cooked</c>
+ to the mode list will override the default and open zip file
+ without the <c>raw</c> option. The same goes for the files
+ extracted.</p>
+ </item>
+ <tag><c>keep_old_files</c></tag>
+ <item>
+ <p>By default, all existing files with the same name as file in
+ the zip archive will be overwritten. With the <c>keep_old_files</c>
+ option, the <c>unzip/2</c> function will not overwrite any existing
+ files. Not that even with the <c>memory</c> option given, which
+ means that no files will be overwritten, files existing will be
+ excluded from the result.</p>
+ </item>
+ <tag><c>verbose</c></tag>
+ <item>
+ <p>Print an informational message as each file is being
+ extracted.</p>
+ </item>
+ <tag><c>memory</c></tag>
+ <item>
+ <p>Instead of extracting to the current directory, the
+ <c>memory</c> option will give the result as a list of tuples
+ <c>{Filename, Binary}</c>, where <c>Binary</c> is a binary
+ containing the extracted data of the file named <c>Filename</c>
+ in the zip archive.</p>
+ </item>
+ <tag><c>{cwd, CWD}</c></tag>
+ <item>
+ <p>Use the given directory as current directory, it will be
+ prepended to file names when extracting them from the
+ zip-archive. (Acting like a file:set_cwd/1, but without
+ changing the global cwd property.)</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>list_dir(Archive) -> RetValue</name>
+ <name>list_dir(Archive, Options)</name>
+ <name>table(Archive) -> RetValue</name>
+ <name>table(Archive, Options)</name>
+ <fsummary>Retrieve the name of all files in a zip archive</fsummary>
+ <type>
+ <v>Archive = filename() | binary()</v>
+ <v>RetValue = {ok, [Comment, Files]} | {error, Reason}</v>
+ <v>Comment = zip_comment()</v>
+ <v>Files = [zip_file()]</v>
+ <v>Options = [Option]</v>
+ <v>Option = cooked</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>The <marker id="list_dir_1"></marker>
+<c>list_dir/1</c> function retrieves
+ the names of all files in the zip archive <c>Archive</c>. The
+ <marker id="list_dir_2"></marker>
+<c>list_dir/2</c> function provides options.</p>
+ <p>As synonyms, the functions <c>table/2</c> and <c>table/3</c>
+ are provided, to make it resemble the <c>erl_tar</c> module.</p>
+ <p>The result value is the tuple <c>{ok, List}</c>, where <c>List</c>
+ contains the zip archive comment as the first element.</p>
+ <p>The following options are available:</p>
+ <taglist>
+ <tag><c>cooked</c></tag>
+ <item>
+ <p>By default, the <c>open/2</c> function will open the
+ zip file in <c>raw</c> mode, which is faster but does not allow
+ a remote (erlang) file server to be used. Adding <c>cooked</c>
+ to the mode list will override the default and open zip file
+ without the <c>raw</c> option.</p>
+ </item>
+ </taglist>
+ </desc>
+ </func>
+ <func>
+ <name>t(Archive)</name>
+ <fsummary>Print the name of each file in a zip archive</fsummary>
+ <type>
+ <v>Archive = filename() | binary() | ZipHandle</v>
+ <v>ZipHandle = pid()</v>
+ </type>
+ <desc>
+ <p>The <marker id="t_1"></marker>
+<c>t/1</c> function prints the names
+ of all files in the zip archive <c>Archive</c> to the Erlang shell.
+ (Similar to "<c>tar&nbsp;t</c>".)</p>
+ </desc>
+ </func>
+ <func>
+ <name>tt(Archive)</name>
+ <fsummary>Print name and information for each file in a zip archive</fsummary>
+ <type>
+ <v>Archive = filename() | binary()</v>
+ </type>
+ <desc>
+ <p>The <marker id="tt_1"></marker>
+<c>tt/1</c> function prints names and
+ information about all files in the zip archive <c>Archive</c> to
+ the Erlang shell. (Similar to "<c>tar tv</c>".)</p>
+ </desc>
+ </func>
+ <func>
+ <name>zip_open(Archive) -> {ok, ZipHandle} | {error, Reason}</name>
+ <name>zip_open(Archive, Options) -> {ok, ZipHandle} | {error, Reason}</name>
+ <fsummary>Open an archive and return a handle to it</fsummary>
+ <type>
+ <v>Archive = filename() | binary()</v>
+ <v>Options = [Option]</v>
+ <v>Options = cooked | memory | {cwd, CWD}</v>
+ <v>CWD = string()</v>
+ <v>ZipHandle = pid()</v>
+ </type>
+ <desc>
+ <p>The <marker id="zip_open"></marker>
+<c>zip_open</c> function opens a
+ zip archive, and reads and saves its directory. This
+ means that subsequently reading files from the archive will be
+ faster than unzipping files one at a time with <c>unzip</c>.</p>
+ <p>The archive must be closed with <c>zip_close/1</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>zip_list_dir(ZipHandle) -> Result | {error, Reason}</name>
+ <fsummary>Return a table of files in open zip archive</fsummary>
+ <type>
+ <v>Result = [ZipComment, ZipFile...]</v>
+ <v>ZipComment = #zip_comment{}</v>
+ <v>ZipFile = #zip_file{}</v>
+ <v>ZipHandle = pid()</v>
+ </type>
+ <desc>
+ <p>The <marker id="zip_list_dir"></marker>
+<c>zip_list_dir/1</c> function
+ returns the file list of an open zip archive.</p>
+ </desc>
+ </func>
+ <func>
+ <name>zip_get(ZipHandle) -> {ok, [Result]} | {error, Reason}</name>
+ <name>zip_get(FileName, ZipHandle) -> {ok, Result} | {error, Reason}</name>
+ <fsummary>Extract files from an open archive</fsummary>
+ <type>
+ <v>FileName = filename()</v>
+ <v>ZipHandle = pid()</v>
+ <v>Result = filename() | {filename(), binary()}</v>
+ </type>
+ <desc>
+ <p>The <marker id="zip_get"></marker>
+<c>zip_get</c> function extracts
+ one or all files from an open archive.</p>
+ <p>The files will be unzipped to memory or to file, depending on
+ the options given to the <c>zip_open</c> function when the
+ archive was opened.</p>
+ </desc>
+ </func>
+ <func>
+ <name>zip_close(ZipHandle) -> ok | {error, einval}</name>
+ <fsummary>Close an open archive</fsummary>
+ <type>
+ <v>ZipHandle = pid()</v>
+ </type>
+ <desc>
+ <p>The <marker id="zip_close"></marker>
+<c>zip_close/1</c> function closes
+ a zip archive, previously opened with <c>zip_open</c>. All
+ resources are closed, and the handle should not be used after
+ closing.</p>
+ </desc>
+ </func>
+ </funcs>
+</erlref>
+
diff --git a/lib/stdlib/ebin/.gitignore b/lib/stdlib/ebin/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/ebin/.gitignore
diff --git a/lib/stdlib/examples/Makefile b/lib/stdlib/examples/Makefile
new file mode 100644
index 0000000000..25c88d1de3
--- /dev/null
+++ b/lib/stdlib/examples/Makefile
@@ -0,0 +1,52 @@
+# ``The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved via the world wide web at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+# AB. All Rights Reserved.''
+#
+# $Id$
+#
+include $(ERL_TOP)/make/target.mk
+
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+include ../vsn.mk
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt:
+
+clean:
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+RELSYSDIR = $(RELEASE_PATH)/lib/stdlib-$(STDLIB_VSN)/examples
+
+# Pack and install the complete directory structure from
+# here (CWD) and down, for all examples.
+
+EXAMPLES = erl_id_trans.erl
+
+release_spec:
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) $(EXAMPLES) $(RELSYSDIR)
+release_docs_spec:
diff --git a/lib/stdlib/examples/erl_id_trans.erl b/lib/stdlib/examples/erl_id_trans.erl
new file mode 100644
index 0000000000..b63acdd40a
--- /dev/null
+++ b/lib/stdlib/examples/erl_id_trans.erl
@@ -0,0 +1,518 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(erl_id_trans).
+
+%% A identity transformer of Erlang abstract syntax.
+
+%% This module only traverses legal Erlang code. This is most noticeable
+%% in guards where only a limited number of expressions are allowed.
+%% N.B. if this module is to be used as a basis for tranforms then
+%% all the error cases must be handled otherwise this module just crashes!
+
+-export([parse_transform/2]).
+
+parse_transform(Forms, _Options) ->
+ forms(Forms).
+
+%% forms(Fs) -> lists:map(fun (F) -> form(F) end, Fs).
+
+forms([F0|Fs0]) ->
+ F1 = form(F0),
+ Fs1 = forms(Fs0),
+ [F1|Fs1];
+forms([]) -> [].
+
+%% -type form(Form) -> Form.
+%% Here we show every known form and valid internal structure. We do not
+%% that the ordering is correct!
+
+%% First the various attributes.
+form({attribute,Line,module,Mod}) ->
+ {attribute,Line,module,Mod};
+form({attribute,Line,file,{File,Line}}) -> %This is valid anywhere.
+ {attribute,Line,file,{File,Line}};
+form({attribute,Line,export,Es0}) ->
+ Es1 = farity_list(Es0),
+ {attribute,Line,export,Es1};
+form({attribute,Line,import,{Mod,Is0}}) ->
+ Is1 = farity_list(Is0),
+ {attribute,Line,import,{Mod,Is1}};
+form({attribute,Line,compile,C}) ->
+ {attribute,Line,compile,C};
+form({attribute,Line,record,{Name,Defs0}}) ->
+ Defs1 = record_defs(Defs0),
+ {attribute,Line,record,{Name,Defs1}};
+form({attribute,Line,asm,{function,N,A,Code}}) ->
+ {attribute,Line,asm,{function,N,A,Code}};
+form({attribute,Line,Attr,Val}) -> %The general attribute.
+ {attribute,Line,Attr,Val};
+form({function,Line,Name0,Arity0,Clauses0}) ->
+ {Name,Arity,Clauses} = function(Name0, Arity0, Clauses0),
+ {function,Line,Name,Arity,Clauses};
+% Mnemosyne, ignore...
+form({rule,Line,Name,Arity,Body}) ->
+ {rule,Line,Name,Arity,Body}; % Dont dig into this
+%% Extra forms from the parser.
+form({error,E}) -> {error,E};
+form({warning,W}) -> {warning,W};
+form({eof,Line}) -> {eof,Line}.
+
+%% -type farity_list([Farity]) -> [Farity] when Farity <= {atom(),integer()}.
+
+farity_list([{Name,Arity}|Fas]) ->
+ [{Name,Arity}|farity_list(Fas)];
+farity_list([]) -> [].
+
+%% -type record_defs([RecDef]) -> [RecDef].
+%% N.B. Field names are full expressions here but only atoms are allowed
+%% by the *parser*!
+
+record_defs([{record_field,Line,{atom,La,A},Val0}|Is]) ->
+ Val1 = expr(Val0),
+ [{record_field,Line,{atom,La,A},Val1}|record_defs(Is)];
+record_defs([{record_field,Line,{atom,La,A}}|Is]) ->
+ [{record_field,Line,{atom,La,A}}|record_defs(Is)];
+record_defs([]) -> [].
+
+%% -type function(atom(), integer(), [Clause]) -> {atom(),integer(),[Clause]}.
+
+function(Name, Arity, Clauses0) ->
+ Clauses1 = clauses(Clauses0),
+ {Name,Arity,Clauses1}.
+
+%% -type clauses([Clause]) -> [Clause].
+
+clauses([C0|Cs]) ->
+ C1 = clause(C0),
+ [C1|clauses(Cs)];
+clauses([]) -> [].
+
+%% -type clause(Clause) -> Clause.
+
+clause({clause,Line,H0,G0,B0}) ->
+ H1 = head(H0),
+ G1 = guard(G0),
+ B1 = exprs(B0),
+ {clause,Line,H1,G1,B1}.
+
+%% -type head([Pattern]) -> [Pattern].
+
+head(Ps) -> patterns(Ps).
+
+%% -type patterns([Pattern]) -> [Pattern].
+%% These patterns are processed "sequentially" for purposes of variable
+%% definition etc.
+
+patterns([P0|Ps]) ->
+ P1 = pattern(P0),
+ [P1|patterns(Ps)];
+patterns([]) -> [].
+
+%% -type pattern(Pattern) -> Pattern.
+%% N.B. Only valid patterns are included here.
+
+pattern({var,Line,V}) -> {var,Line,V};
+pattern({match,Line,L0,R0}) ->
+ L1 = pattern(L0),
+ R1 = pattern(R0),
+ {match,Line,L1,R1};
+pattern({integer,Line,I}) -> {integer,Line,I};
+pattern({char,Line,C}) -> {char,Line,C};
+pattern({float,Line,F}) -> {float,Line,F};
+pattern({atom,Line,A}) -> {atom,Line,A};
+pattern({string,Line,S}) -> {string,Line,S};
+pattern({nil,Line}) -> {nil,Line};
+pattern({cons,Line,H0,T0}) ->
+ H1 = pattern(H0),
+ T1 = pattern(T0),
+ {cons,Line,H1,T1};
+pattern({tuple,Line,Ps0}) ->
+ Ps1 = pattern_list(Ps0),
+ {tuple,Line,Ps1};
+%%pattern({struct,Line,Tag,Ps0}) ->
+%% Ps1 = pattern_list(Ps0),
+%% {struct,Line,Tag,Ps1};
+pattern({record,Line,Name,Pfs0}) ->
+ Pfs1 = pattern_fields(Pfs0),
+ {record,Line,Name,Pfs1};
+pattern({record_index,Line,Name,Field0}) ->
+ Field1 = pattern(Field0),
+ {record_index,Line,Name,Field1};
+%% record_field occurs in query expressions
+pattern({record_field,Line,Rec0,Name,Field0}) ->
+ Rec1 = expr(Rec0),
+ Field1 = expr(Field0),
+ {record_field,Line,Rec1,Name,Field1};
+pattern({record_field,Line,Rec0,Field0}) ->
+ Rec1 = expr(Rec0),
+ Field1 = expr(Field0),
+ {record_field,Line,Rec1,Field1};
+pattern({bin,Line,Fs}) ->
+ Fs2 = pattern_grp(Fs),
+ {bin,Line,Fs2};
+pattern({op,Line,Op,A}) ->
+ {op,Line,Op,A};
+pattern({op,Line,Op,L,R}) ->
+ {op,Line,Op,L,R}.
+
+pattern_grp([{bin_element,L1,E1,S1,T1} | Fs]) ->
+ S2 = case S1 of
+ default ->
+ default;
+ _ ->
+ expr(S1)
+ end,
+ T2 = case T1 of
+ default ->
+ default;
+ _ ->
+ bit_types(T1)
+ end,
+ [{bin_element,L1,expr(E1),S2,T2} | pattern_grp(Fs)];
+pattern_grp([]) ->
+ [].
+
+bit_types([]) ->
+ [];
+bit_types([Atom | Rest]) when atom(Atom) ->
+ [Atom | bit_types(Rest)];
+bit_types([{Atom, Integer} | Rest]) when atom(Atom), integer(Integer) ->
+ [{Atom, Integer} | bit_types(Rest)].
+
+
+
+%% -type pattern_list([Pattern]) -> [Pattern].
+%% These patterns are processed "in parallel" for purposes of variable
+%% definition etc.
+
+pattern_list([P0|Ps]) ->
+ P1 = pattern(P0),
+ [P1|pattern_list(Ps)];
+pattern_list([]) -> [].
+
+%% -type pattern_fields([Field]) -> [Field].
+%% N.B. Field names are full expressions here but only atoms are allowed
+%% by the *linter*!.
+
+pattern_fields([{record_field,Lf,{atom,La,F},P0}|Pfs]) ->
+ P1 = pattern(P0),
+ [{record_field,Lf,{atom,La,F},P1}|pattern_fields(Pfs)];
+pattern_fields([{record_field,Lf,{var,La,'_'},P0}|Pfs]) ->
+ P1 = pattern(P0),
+ [{record_field,Lf,{var,La,'_'},P1}|pattern_fields(Pfs)];
+pattern_fields([]) -> [].
+
+%% -type guard([GuardTest]) -> [GuardTest].
+
+guard([G0|Gs]) when list(G0) ->
+ [guard0(G0) | guard(Gs)];
+guard(L) ->
+ guard0(L).
+
+guard0([G0|Gs]) ->
+ G1 = guard_test(G0),
+ [G1|guard0(Gs)];
+guard0([]) -> [].
+
+guard_test(Expr={call,Line,{atom,La,F},As0}) ->
+ case erl_internal:type_test(F, length(As0)) of
+ true ->
+ As1 = gexpr_list(As0),
+ {call,Line,{atom,La,F},As1};
+ _ ->
+ gexpr(Expr)
+ end;
+guard_test(Any) ->
+ gexpr(Any).
+
+%% Before R9, there were special rules regarding the expressions on
+%% top level in guards. Those limitations are now lifted - therefore
+%% there is no need for a special clause for the toplevel expressions.
+%% -type gexpr(GuardExpr) -> GuardExpr.
+
+gexpr({var,Line,V}) -> {var,Line,V};
+gexpr({integer,Line,I}) -> {integer,Line,I};
+gexpr({char,Line,C}) -> {char,Line,C};
+gexpr({float,Line,F}) -> {float,Line,F};
+gexpr({atom,Line,A}) -> {atom,Line,A};
+gexpr({string,Line,S}) -> {string,Line,S};
+gexpr({nil,Line}) -> {nil,Line};
+gexpr({cons,Line,H0,T0}) ->
+ H1 = gexpr(H0),
+ T1 = gexpr(T0), %They see the same variables
+ {cons,Line,H1,T1};
+gexpr({tuple,Line,Es0}) ->
+ Es1 = gexpr_list(Es0),
+ {tuple,Line,Es1};
+gexpr({record_index,Line,Name,Field0}) ->
+ Field1 = gexpr(Field0),
+ {record_index,Line,Name,Field1};
+gexpr({record_field,Line,Rec0,Name,Field0}) ->
+ Rec1 = gexpr(Rec0),
+ Field1 = gexpr(Field0),
+ {record_field,Line,Rec1,Name,Field1};
+gexpr({record,Line,Name,Inits0}) ->
+ Inits1 = grecord_inits(Inits0),
+ {record,Line,Name,Inits1};
+gexpr({call,Line,{atom,La,F},As0}) ->
+ case erl_internal:guard_bif(F, length(As0)) of
+ true -> As1 = gexpr_list(As0),
+ {call,Line,{atom,La,F},As1}
+ end;
+% Guard bif's can be remote, but only in the module erlang...
+gexpr({call,Line,{remote,La,{atom,Lb,erlang},{atom,Lc,F}},As0}) ->
+ case erl_internal:guard_bif(F, length(As0)) or
+ erl_internal:arith_op(F, length(As0)) or
+ erl_internal:comp_op(F, length(As0)) or
+ erl_internal:bool_op(F, length(As0)) of
+ true -> As1 = gexpr_list(As0),
+ {call,Line,{remote,La,{atom,Lb,erlang},{atom,Lc,F}},As1}
+ end;
+% Unfortunately, writing calls as {M,F}(...) is also allowed.
+gexpr({call,Line,{tuple,La,[{atom,Lb,erlang},{atom,Lc,F}]},As0}) ->
+ case erl_internal:guard_bif(F, length(As0)) or
+ erl_internal:arith_op(F, length(As0)) or
+ erl_internal:comp_op(F, length(As0)) or
+ erl_internal:bool_op(F, length(As0)) of
+ true -> As1 = gexpr_list(As0),
+ {call,Line,{tuple,La,[{atom,Lb,erlang},{atom,Lc,F}]},As1}
+ end;
+gexpr({bin,Line,Fs}) ->
+ Fs2 = pattern_grp(Fs),
+ {bin,Line,Fs2};
+gexpr({op,Line,Op,A0}) ->
+ case erl_internal:arith_op(Op, 1) or
+ erl_internal:bool_op(Op, 1) of
+ true -> A1 = gexpr(A0),
+ {op,Line,Op,A1}
+ end;
+gexpr({op,Line,Op,L0,R0}) when Op =:= 'andalso'; Op =:= 'orelse' ->
+ %% R11B: andalso/orelse are now allowed in guards.
+ L1 = gexpr(L0),
+ R1 = gexpr(R0), %They see the same variables
+ {op,Line,Op,L1,R1};
+gexpr({op,Line,Op,L0,R0}) ->
+ case erl_internal:arith_op(Op, 2) or
+ erl_internal:bool_op(Op, 2) or
+ erl_internal:comp_op(Op, 2) of
+ true ->
+ L1 = gexpr(L0),
+ R1 = gexpr(R0), %They see the same variables
+ {op,Line,Op,L1,R1}
+ end.
+
+%% -type gexpr_list([GuardExpr]) -> [GuardExpr].
+%% These expressions are processed "in parallel" for purposes of variable
+%% definition etc.
+
+gexpr_list([E0|Es]) ->
+ E1 = gexpr(E0),
+ [E1|gexpr_list(Es)];
+gexpr_list([]) -> [].
+
+grecord_inits([{record_field,Lf,{atom,La,F},Val0}|Is]) ->
+ Val1 = gexpr(Val0),
+ [{record_field,Lf,{atom,La,F},Val1}|grecord_inits(Is)];
+grecord_inits([{record_field,Lf,{var,La,'_'},Val0}|Is]) ->
+ Val1 = gexpr(Val0),
+ [{record_field,Lf,{var,La,'_'},Val1}|grecord_inits(Is)];
+grecord_inits([]) -> [].
+
+%% -type exprs([Expression]) -> [Expression].
+%% These expressions are processed "sequentially" for purposes of variable
+%% definition etc.
+
+exprs([E0|Es]) ->
+ E1 = expr(E0),
+ [E1|exprs(Es)];
+exprs([]) -> [].
+
+%% -type expr(Expression) -> Expression.
+
+expr({var,Line,V}) -> {var,Line,V};
+expr({integer,Line,I}) -> {integer,Line,I};
+expr({float,Line,F}) -> {float,Line,F};
+expr({atom,Line,A}) -> {atom,Line,A};
+expr({string,Line,S}) -> {string,Line,S};
+expr({char,Line,C}) -> {char,Line,C};
+expr({nil,Line}) -> {nil,Line};
+expr({cons,Line,H0,T0}) ->
+ H1 = expr(H0),
+ T1 = expr(T0), %They see the same variables
+ {cons,Line,H1,T1};
+expr({lc,Line,E0,Qs0}) ->
+ Qs1 = lc_bc_quals(Qs0),
+ E1 = expr(E0),
+ {lc,Line,E1,Qs1};
+expr({bc,Line,E0,Qs0}) ->
+ Qs1 = lc_bc_quals(Qs0),
+ E1 = expr(E0),
+ {bc,Line,E1,Qs1};
+expr({tuple,Line,Es0}) ->
+ Es1 = expr_list(Es0),
+ {tuple,Line,Es1};
+%%expr({struct,Line,Tag,Es0}) ->
+%% Es1 = pattern_list(Es0),
+%% {struct,Line,Tag,Es1};
+expr({record_index,Line,Name,Field0}) ->
+ Field1 = expr(Field0),
+ {record_index,Line,Name,Field1};
+expr({record,Line,Name,Inits0}) ->
+ Inits1 = record_inits(Inits0),
+ {record,Line,Name,Inits1};
+expr({record_field,Line,Rec0,Name,Field0}) ->
+ Rec1 = expr(Rec0),
+ Field1 = expr(Field0),
+ {record_field,Line,Rec1,Name,Field1};
+expr({record,Line,Rec0,Name,Upds0}) ->
+ Rec1 = expr(Rec0),
+ Upds1 = record_updates(Upds0),
+ {record,Line,Rec1,Name,Upds1};
+expr({record_field,Line,Rec0,Field0}) ->
+ Rec1 = expr(Rec0),
+ Field1 = expr(Field0),
+ {record_field,Line,Rec1,Field1};
+expr({block,Line,Es0}) ->
+ %% Unfold block into a sequence.
+ Es1 = exprs(Es0),
+ {block,Line,Es1};
+expr({'if',Line,Cs0}) ->
+ Cs1 = icr_clauses(Cs0),
+ {'if',Line,Cs1};
+expr({'case',Line,E0,Cs0}) ->
+ E1 = expr(E0),
+ Cs1 = icr_clauses(Cs0),
+ {'case',Line,E1,Cs1};
+expr({'receive',Line,Cs0}) ->
+ Cs1 = icr_clauses(Cs0),
+ {'receive',Line,Cs1};
+expr({'receive',Line,Cs0,To0,ToEs0}) ->
+ To1 = expr(To0),
+ ToEs1 = exprs(ToEs0),
+ Cs1 = icr_clauses(Cs0),
+ {'receive',Line,Cs1,To1,ToEs1};
+expr({'try',Line,Es0,Scs0,Ccs0,As0}) ->
+ Es1 = exprs(Es0),
+ Scs1 = icr_clauses(Scs0),
+ Ccs1 = icr_clauses(Ccs0),
+ As1 = exprs(As0),
+ {'try',Line,Es1,Scs1,Ccs1,As1};
+expr({'fun',Line,Body}) ->
+ case Body of
+ {clauses,Cs0} ->
+ Cs1 = fun_clauses(Cs0),
+ {'fun',Line,{clauses,Cs1}};
+ {function,F,A} ->
+ {'fun',Line,{function,F,A}};
+ {function,M,F,A} -> %R10B-6: fun M:F/A.
+ {'fun',Line,{function,M,F,A}}
+ end;
+expr({call,Line,F0,As0}) ->
+ %% N.B. If F an atom then call to local function or BIF, if F a
+ %% remote structure (see below) then call to other module,
+ %% otherwise apply to "function".
+ F1 = expr(F0),
+ As1 = expr_list(As0),
+ {call,Line,F1,As1};
+expr({'catch',Line,E0}) ->
+ %% No new variables added.
+ E1 = expr(E0),
+ {'catch',Line,E1};
+expr({'query', Line, E0}) ->
+ %% lc expression
+ E = expr(E0),
+ {'query', Line, E};
+expr({match,Line,P0,E0}) ->
+ E1 = expr(E0),
+ P1 = pattern(P0),
+ {match,Line,P1,E1};
+expr({bin,Line,Fs}) ->
+ Fs2 = pattern_grp(Fs),
+ {bin,Line,Fs2};
+expr({op,Line,Op,A0}) ->
+ A1 = expr(A0),
+ {op,Line,Op,A1};
+expr({op,Line,Op,L0,R0}) ->
+ L1 = expr(L0),
+ R1 = expr(R0), %They see the same variables
+ {op,Line,Op,L1,R1};
+%% The following are not allowed to occur anywhere!
+expr({remote,Line,M0,F0}) ->
+ M1 = expr(M0),
+ F1 = expr(F0),
+ {remote,Line,M1,F1}.
+
+%% -type expr_list([Expression]) -> [Expression].
+%% These expressions are processed "in parallel" for purposes of variable
+%% definition etc.
+
+expr_list([E0|Es]) ->
+ E1 = expr(E0),
+ [E1|expr_list(Es)];
+expr_list([]) -> [].
+
+%% -type record_inits([RecordInit]) -> [RecordInit].
+%% N.B. Field names are full expressions here but only atoms are allowed
+%% by the *linter*!.
+
+record_inits([{record_field,Lf,{atom,La,F},Val0}|Is]) ->
+ Val1 = expr(Val0),
+ [{record_field,Lf,{atom,La,F},Val1}|record_inits(Is)];
+record_inits([{record_field,Lf,{var,La,'_'},Val0}|Is]) ->
+ Val1 = expr(Val0),
+ [{record_field,Lf,{var,La,'_'},Val1}|record_inits(Is)];
+record_inits([]) -> [].
+
+%% -type record_updates([RecordUpd]) -> [RecordUpd].
+%% N.B. Field names are full expressions here but only atoms are allowed
+%% by the *linter*!.
+
+record_updates([{record_field,Lf,{atom,La,F},Val0}|Us]) ->
+ Val1 = expr(Val0),
+ [{record_field,Lf,{atom,La,F},Val1}|record_updates(Us)];
+record_updates([]) -> [].
+
+%% -type icr_clauses([Clause]) -> [Clause].
+
+icr_clauses([C0|Cs]) ->
+ C1 = clause(C0),
+ [C1|icr_clauses(Cs)];
+icr_clauses([]) -> [].
+
+%% -type lc_bc_quals([Qualifier]) -> [Qualifier].
+%% Allow filters to be both guard tests and general expressions.
+
+lc_bc_quals([{generate,Line,P0,E0}|Qs]) ->
+ E1 = expr(E0),
+ P1 = pattern(P0),
+ [{generate,Line,P1,E1}|lc_bc_quals(Qs)];
+lc_bc_quals([{b_generate,Line,P0,E0}|Qs]) ->
+ E1 = expr(E0),
+ P1 = pattern(P0),
+ [{b_generate,Line,P1,E1}|lc_bc_quals(Qs)];
+lc_bc_quals([E0|Qs]) ->
+ E1 = expr(E0),
+ [E1|lc_bc_quals(Qs)];
+lc_bc_quals([]) -> [].
+
+%% -type fun_clauses([Clause]) -> [Clause].
+
+fun_clauses([C0|Cs]) ->
+ C1 = clause(C0),
+ [C1|fun_clauses(Cs)];
+fun_clauses([]) -> [].
diff --git a/lib/stdlib/include/erl_bits.hrl b/lib/stdlib/include/erl_bits.hrl
new file mode 100644
index 0000000000..54ebe58585
--- /dev/null
+++ b/lib/stdlib/include/erl_bits.hrl
@@ -0,0 +1,48 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% This is an -*- erlang -*- file.
+%% Generic compiler options, passed from the erl_compile module.
+
+-type bt_endian():: 'big' | 'little' | 'native'.
+-type bt_sign() :: 'signed' | 'unsigned'.
+-type bt_type() :: 'integer' | 'float' | 'binary' | 'utf8' | 'utf16' | 'utf32'.
+-type bt_unit() :: 1..256.
+
+-record(bittype, {
+ type :: bt_type(),
+ unit :: bt_unit(), %% element unit
+ sign :: bt_sign(),
+ endian :: bt_endian()
+ }).
+
+-record(bitdefault, {
+ integer, %% default type for integer
+ float, %% default type for float
+ binary %% default type for binary
+ }).
+
+%%% (From config.hrl in the bitsyntax branch.)
+-define(SYS_ENDIAN, big).
+-define(SIZEOF_CHAR, 1).
+-define(SIZEOF_DOUBLE, 8).
+-define(SIZEOF_FLOAT, 4).
+-define(SIZEOF_INT, 4).
+-define(SIZEOF_LONG, 4).
+-define(SIZEOF_LONG_LONG, 8).
+-define(SIZEOF_SHORT, 2).
diff --git a/lib/stdlib/include/erl_compile.hrl b/lib/stdlib/include/erl_compile.hrl
new file mode 100644
index 0000000000..f779c4382c
--- /dev/null
+++ b/lib/stdlib/include/erl_compile.hrl
@@ -0,0 +1,43 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% Generic compiler options, passed from the erl_compile module.
+
+-record(options,
+ {includes=[] :: [file:filename()], % Include paths (list of
+ % absolute directory names).
+ outdir="." :: file:filename(), % Directory for result
+ % (absolute path).
+ output_type=undefined :: atom(), % Type of output file.
+ defines=[] :: [atom() | {atom(),_}], % Preprocessor defines. Each
+ % element is an atom
+ % (the name to define), or
+ % a {Name, Value} tuple.
+ warning=1 :: non_neg_integer(), % Warning level (0 - no
+ % warnings, 1 - standard level,
+ % 2, 3, ... - more warnings).
+ verbose=false :: boolean(), % Verbose (true/false).
+ optimize=999, % Optimize options.
+ specific=[] :: [_], % Compiler specific options.
+ outfile="" :: file:filename(), % Name of output file (internal
+ % use in erl_compile.erl).
+ cwd :: file:filename() % Current working directory
+ % for erlc.
+ }).
+
diff --git a/lib/stdlib/include/ms_transform.hrl b/lib/stdlib/include/ms_transform.hrl
new file mode 100644
index 0000000000..9937d48fef
--- /dev/null
+++ b/lib/stdlib/include/ms_transform.hrl
@@ -0,0 +1,19 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-compile({parse_transform,ms_transform}).
diff --git a/lib/stdlib/include/qlc.hrl b/lib/stdlib/include/qlc.hrl
new file mode 100644
index 0000000000..067fb83060
--- /dev/null
+++ b/lib/stdlib/include/qlc.hrl
@@ -0,0 +1,19 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-compile({parse_transform,qlc}).
diff --git a/lib/stdlib/include/zip.hrl b/lib/stdlib/include/zip.hrl
new file mode 100644
index 0000000000..2b5ddc1dfe
--- /dev/null
+++ b/lib/stdlib/include/zip.hrl
@@ -0,0 +1,31 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-record(zip_file, {
+ name :: string(), % file name
+ info :: #file_info{},
+ comment :: string(), % zip file comment
+ offset :: non_neg_integer(), % offset of file's local header in archive
+ comp_size :: non_neg_integer() % compressed size
+ }).
+
+-record(zip_comment, {
+ comment :: string() % zip archive comment
+ }).
+
diff --git a/lib/stdlib/info b/lib/stdlib/info
new file mode 100644
index 0000000000..af2495a6a9
--- /dev/null
+++ b/lib/stdlib/info
@@ -0,0 +1,2 @@
+group: basic
+short: The Erlang standard libraries
diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile
new file mode 100644
index 0000000000..68708d6b02
--- /dev/null
+++ b/lib/stdlib/src/Makefile
@@ -0,0 +1,227 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1996-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+
+ifdef BOOTSTRAP
+EGEN=$(BOOTSTRAP_TOP)/lib/stdlib/egen
+EBIN=$(BOOTSTRAP_TOP)/lib/stdlib/ebin
+endif
+
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(STDLIB_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/stdlib-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+MODULES= \
+ array \
+ base64 \
+ beam_lib \
+ c \
+ calendar \
+ dets \
+ dets_server \
+ dets_sup \
+ dets_utils \
+ dets_v8 \
+ dets_v9 \
+ dict \
+ digraph \
+ digraph_utils \
+ edlin \
+ edlin_expand \
+ epp \
+ erl_bits \
+ erl_compile \
+ erl_eval \
+ erl_expand_records \
+ erl_internal \
+ erl_lint \
+ erl_parse \
+ erl_posix_msg \
+ erl_pp \
+ erl_scan \
+ erl_tar \
+ error_logger_file_h \
+ error_logger_tty_h \
+ escript \
+ ets \
+ eval_bits \
+ file_sorter \
+ filelib \
+ filename \
+ gb_trees \
+ gb_sets \
+ gen \
+ gen_event \
+ gen_fsm \
+ gen_server \
+ io \
+ io_lib \
+ io_lib_format \
+ io_lib_fread \
+ io_lib_pretty \
+ lib \
+ lists \
+ log_mf_h \
+ math \
+ ms_transform \
+ otp_internal \
+ orddict \
+ ordsets \
+ pg \
+ re \
+ pool \
+ proc_lib \
+ proplists \
+ qlc \
+ qlc_pt \
+ queue \
+ random \
+ regexp \
+ sets \
+ shell \
+ shell_default \
+ slave \
+ sofs \
+ string \
+ supervisor \
+ supervisor_bridge \
+ sys \
+ timer \
+ unicode \
+ win32reg \
+ zip
+
+HRL_FILES= \
+ ../include/erl_compile.hrl \
+ ../include/erl_bits.hrl \
+ ../include/ms_transform.hrl \
+ ../include/qlc.hrl \
+ ../include/zip.hrl
+
+INTERNAL_HRL_FILES= dets.hrl
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
+
+APP_FILE= stdlib.app
+
+APP_SRC= $(APP_FILE).src
+APP_TARGET= $(EBIN)/$(APP_FILE)
+
+APPUP_FILE= stdlib.appup
+
+APPUP_SRC= $(APPUP_FILE).src
+APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+ERL_COMPILE_FLAGS += -I../include -I../../kernel/include
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f core
+ rm -f erl_parse.erl
+
+docs:
+
+# This is a trick so that the preloaded files will get the correct type
+# specifications.
+primary_bootstrap_compiler: \
+ $(BOOTSTRAP_COMPILER)/ebin/erl_scan.beam \
+ $(BOOTSTRAP_COMPILER)/ebin/erl_parse.beam \
+ $(BOOTSTRAP_COMPILER)/ebin/erl_lint.beam \
+ $(BOOTSTRAP_COMPILER)/ebin/otp_internal.beam
+
+$(BOOTSTRAP_COMPILER)/ebin/erl_parse.beam: erl_parse.yrl
+ $(ERLC) -o $(BOOTSTRAP_COMPILER)/egen erl_parse.yrl
+ $(ERLC) -o $(BOOTSTRAP_COMPILER)/ebin $(BOOTSTRAP_COMPILER)/egen/erl_parse.erl
+
+#$(BOOTSTRAP_COMPILER)/ebin/erl_lint.beam: erl_lint.erl
+# $(ERLC) -o $(BOOTSTRAP_COMPILER)/ebin erl_lint.erl
+
+$(BOOTSTRAP_COMPILER)/ebin/%.beam: %.erl
+ $(ERLC) -o $(BOOTSTRAP_COMPILER)/ebin $<
+
+# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(ERL_FILES) erl_parse.yrl $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/include
+ $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
+
+release_docs_spec:
+
+# ----------------------------------------------------
+# Dependencies -- alphabetically, please
+# ----------------------------------------------------
+
+$(EBIN)/beam_lib.beam: ../include/erl_compile.hrl ../../kernel/include/file.hrl
+$(EBIN)/dets.beam: dets.hrl ../../kernel/include/file.hrl
+$(EBIN)/dets_server.beam: dets.hrl
+$(EBIN)/dets_utils.beam: dets.hrl
+$(EBIN)/dets_v8.beam: dets.hrl
+$(EBIN)/dets_v9.beam: dets.hrl
+$(EBIN)/erl_bits.beam: ../include/erl_bits.hrl
+$(EBIN)/erl_compile.beam: ../include/erl_compile.hrl ../../kernel/include/file.hrl
+$(EBIN)/erl_lint.beam: ../include/erl_bits.hrl
+$(EBIN)/erl_tar.beam: ../../kernel/include/file.hrl
+$(EBIN)/file_sorter.beam: ../../kernel/include/file.hrl
+$(EBIN)/filelib.beam: ../../kernel/include/file.hrl
+$(EBIN)/filename.beam: ../../kernel/include/file.hrl
+$(EBIN)/qlc_pt.beam: ../include/ms_transform.hrl
+$(EBIN)/shell.beam: ../../kernel/include/file.hrl
+$(EBIN)/zip.beam: ../include/zip.hrl ../../kernel/include/file.hrl
diff --git a/lib/stdlib/src/array.erl b/lib/stdlib/src/array.erl
new file mode 100644
index 0000000000..295eeac221
--- /dev/null
+++ b/lib/stdlib/src/array.erl
@@ -0,0 +1,1926 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% @author Richard Carlsson <[email protected]>
+%% @author Dan Gudmundsson <[email protected]>
+%% @version 1.0
+
+%% @doc Functional, extendible arrays. Arrays can have fixed size, or
+%% can grow automatically as needed. A default value is used for entries
+%% that have not been explicitly set.
+%%
+%% Arrays uses <b>zero</b> based indexing. This is a deliberate design
+%% choice and differs from other erlang datastructures, e.g. tuples.
+%%
+%% Unless specified by the user when the array is created, the default
+%% value is the atom `undefined'. There is no difference between an
+%% unset entry and an entry which has been explicitly set to the same
+%% value as the default one (cf. {@link reset/2}). If you need to
+%% differentiate between unset and set entries, you must make sure that
+%% the default value cannot be confused with the values of set entries.
+%%
+%% The array never shrinks automatically; if an index `I' has been used
+%% successfully to set an entry, all indices in the range [0,`I'] will
+%% stay accessible unless the array size is explicitly changed by
+%% calling {@link resize/2}.
+%%
+%% Examples:
+%% ```
+%% %% Create a fixed-size array with entries 0-9 set to 'undefined'
+%% A0 = array:new(10).
+%% 10 = array:size(A0).
+%%
+%% %% Create an extendible array and set entry 17 to 'true',
+%% %% causing the array to grow automatically
+%% A1 = array:set(17, true, array:new()).
+%% 18 = array:size(A1).
+%%
+%% %% Read back a stored value
+%% true = array:get(17, A1).
+%%
+%% %% Accessing an unset entry returns the default value
+%% undefined = array:get(3, A1).
+%%
+%% %% Accessing an entry beyond the last set entry also returns the
+%% %% default value, if the array does not have fixed size
+%% undefined = array:get(18, A1).
+%%
+%% %% "sparse" functions ignore default-valued entries
+%% A2 = array:set(4, false, A1).
+%% [{4, false}, {17, true}] = array:sparse_to_orddict(A2).
+%%
+%% %% An extendible array can be made fixed-size later
+%% A3 = array:fix(A2).
+%%
+%% %% A fixed-size array does not grow automatically and does not
+%% %% allow accesses beyond the last set entry
+%% {'EXIT',{badarg,_}} = (catch array:set(18, true, A3)).
+%% {'EXIT',{badarg,_}} = (catch array:get(18, A3)).
+%% '''
+
+%% @type array(). A functional, extendible array. The representation is
+%% not documented and is subject to change without notice. Note that
+%% arrays cannot be directly compared for equality.
+
+-module(array).
+
+-export([new/0, new/1, new/2, is_array/1, set/3, get/2, size/1,
+ sparse_size/1, default/1, reset/2, to_list/1, sparse_to_list/1,
+ from_list/1, from_list/2, to_orddict/1, sparse_to_orddict/1,
+ from_orddict/1, from_orddict/2, map/2, sparse_map/2, foldl/3,
+ foldr/3, sparse_foldl/3, sparse_foldr/3, fix/1, relax/1, is_fix/1,
+ resize/1, resize/2]).
+
+%%-define(TEST,1).
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+
+%% Developers:
+%%
+%% For OTP devs: Both tests and documentation is extracted from this
+%% file, keep and update this file,
+%% test are extracted with array_SUITE:extract_tests().
+%% Doc with docb_gen array.erl
+%%
+%% The key to speed is to minimize the number of tests, on
+%% large input. Always make the most probable path as short as possible.
+%% In particular, keep in mind that for large trees, the probability of
+%% a leaf node is small relative to that of an internal node.
+%%
+%% If you try to tweak the set_1 and get_1 loops: Measure, look at the
+%% generated Beam code, and measure again! The argument order matters!
+
+
+%% Representation:
+%%
+%% A tree is either a leaf, with LEAFSIZE elements (the "base"), an
+%% internal node with LEAFSIZE+1 elements, or an unexpanded tree,
+%% represented by a single integer: the number of elements that may be
+%% stored in the tree when it is expanded. The last element of an
+%% internal node caches the number of elements that may be stored in
+%% each of its subtrees.
+%%
+%% Note that to update an entry in a tree of height h = log[b] n, the
+%% total number of written words is (b+1)+(h-1)*(b+2), since tuples use
+%% a header word on the heap. 4 is the optimal base for minimizing the
+%% number of words written, but causes higher trees, which takes time.
+%% The best compromise between speed and memory usage seems to lie
+%% around 8-10. Measurements indicate that the optimum base for speed is
+%% 24 - above that, it gets slower again due to the high memory usage.
+%% Base 10 is a good choice, giving 2/3 of the possible speedup from
+%% base 4, but only using 1/3 more memory. (Base 24 uses 65% more memory
+%% per write than base 10, but the speedup is only 21%.)
+
+-define(DEFAULT, undefined).
+-define(LEAFSIZE, 10). % the "base"
+-define(NODESIZE, ?LEAFSIZE). % (no reason to have a different size)
+-define(NODEPATTERN(S), {_,_,_,_,_,_,_,_,_,_,S}). % NODESIZE+1 elements!
+-define(NEW_NODE(S), % beware of argument duplication!
+ setelement((?NODESIZE+1),erlang:make_tuple((?NODESIZE+1),(S)),(S))).
+-define(NEW_LEAF(D), erlang:make_tuple(?LEAFSIZE,(D))).
+-define(NODELEAFS, ?NODESIZE*?LEAFSIZE).
+
+%% These make the code a little easier to experiment with.
+%% It turned out that using shifts (when NODESIZE=2^n) was not faster.
+-define(reduce(X), ((X) div (?NODESIZE))).
+-define(extend(X), ((X) * (?NODESIZE))).
+
+%%--------------------------------------------------------------------------
+
+-record(array, {size :: non_neg_integer(), %% number of defined entries
+ max :: non_neg_integer(), %% maximum number of entries
+ %% in current tree
+ default, %% the default value (usually 'undefined')
+ elements %% the tuple tree
+ }).
+%% A declaration equivalent to the following one is hard-coded in erl_types.
+%% That declaration contains hard-coded information about the #array{}
+%% structure and the types of its fields. So, please make sure that any
+%% changes to its structure are also propagated to erl_types.erl.
+%%
+%% -opaque array() :: #array{}.
+
+%%
+%% Types
+%%
+
+-type array_indx() :: non_neg_integer().
+
+-type array_opt() :: 'fixed' | non_neg_integer()
+ | {'default', term()} | {'fixed', boolean()}
+ | {'size', non_neg_integer()}.
+-type array_opts() :: array_opt() | [array_opt()].
+
+-type indx_pair() :: {array_indx(), term()}.
+-type indx_pairs() :: [indx_pair()].
+
+%%--------------------------------------------------------------------------
+
+%% @spec () -> array()
+%% @doc Create a new, extendible array with initial size zero.
+%% @equiv new([])
+%%
+%% @see new/1
+%% @see new/2
+
+-spec new() -> array().
+
+new() ->
+ new([]).
+
+%% @spec (Options::term()) -> array()
+%% @doc Create a new array according to the given options. By default,
+%% the array is extendible and has initial size zero. Array indices
+%% start at 0.
+%%
+%% `Options' is a single term or a list of terms, selected from the
+%% following:
+%% <dl>
+%% <dt>`N::integer()' or `{size, N::integer()}'</dt>
+%% <dd>Specifies the initial size of the array; this also implies
+%% `{fixed, true}'. If `N' is not a nonnegative integer, the call
+%% fails with reason `badarg'.</dd>
+%% <dt>`fixed' or `{fixed, true}'</dt>
+%% <dd>Creates a fixed-size array; see also {@link fix/1}.</dd>
+%% <dt>`{fixed, false}'</dt>
+%% <dd>Creates an extendible (non fixed-size) array.</dd>
+%% <dt>`{default, Value}'</dt>
+%% <dd>Sets the default value for the array to `Value'.</dd>
+%% </dl>
+%% Options are processed in the order they occur in the list, i.e.,
+%% later options have higher precedence.
+%%
+%% The default value is used as the value of uninitialized entries, and
+%% cannot be changed once the array has been created.
+%%
+%% Examples:
+%% ```array:new(100)''' creates a fixed-size array of size 100.
+%% ```array:new({default,0})''' creates an empty, extendible array
+%% whose default value is 0.
+%% ```array:new([{size,10},{fixed,false},{default,-1}])''' creates an
+%% extendible array with initial size 10 whose default value is -1.
+%%
+%% @see new/0
+%% @see new/2
+%% @see set/3
+%% @see get/2
+%% @see from_list/2
+%% @see fix/1
+
+-spec new(array_opts()) -> array().
+
+new(Options) ->
+ new_0(Options, 0, false).
+
+%% @spec (Size::integer(), Options::term()) -> array()
+%% @doc Create a new array according to the given size and options. If
+%% `Size' is not a nonnegative integer, the call fails with reason
+%% `badarg'. By default, the array has fixed size. Note that any size
+%% specifications in `Options' will override the `Size' parameter.
+%%
+%% If `Options' is a list, this is simply equivalent to `new([{size,
+%% Size} | Options]', otherwise it is equivalent to `new([{size, Size} |
+%% [Options]]'. However, using this function directly is more efficient.
+%%
+%% Example:
+%% ```array:new(100, {default,0})''' creates a fixed-size array of size
+%% 100, whose default value is 0.
+%%
+%% @see new/1
+
+-spec new(non_neg_integer(), array_opts()) -> array().
+
+new(Size, Options) when is_integer(Size), Size >= 0 ->
+ new_0(Options, Size, true);
+new(_, _) ->
+ erlang:error(badarg).
+
+new_0(Options, Size, Fixed) when is_list(Options) ->
+ new_1(Options, Size, Fixed, ?DEFAULT);
+new_0(Options, Size, Fixed) ->
+ new_1([Options], Size, Fixed, ?DEFAULT).
+
+new_1([fixed | Options], Size, _, Default) ->
+ new_1(Options, Size, true, Default);
+new_1([{fixed, Fixed} | Options], Size, _, Default)
+ when is_boolean(Fixed) ->
+ new_1(Options, Size, Fixed, Default);
+new_1([{default, Default} | Options], Size, Fixed, _) ->
+ new_1(Options, Size, Fixed, Default);
+new_1([{size, Size} | Options], _, _, Default)
+ when is_integer(Size), Size >= 0 ->
+ new_1(Options, Size, true, Default);
+new_1([Size | Options], _, _, Default)
+ when is_integer(Size), Size >= 0 ->
+ new_1(Options, Size, true, Default);
+new_1([], Size, Fixed, Default) ->
+ new(Size, Fixed, Default);
+new_1(_Options, _Size, _Fixed, _Default) ->
+ erlang:error(badarg).
+
+new(0, false, undefined) ->
+ %% Constant empty array
+ #array{size=0, max=?LEAFSIZE, elements=?LEAFSIZE};
+new(Size, Fixed, Default) ->
+ E = find_max(Size - 1, ?LEAFSIZE),
+ M = if Fixed -> 0;
+ true -> E
+ end,
+ #array{size = Size, max = M, default = Default, elements = E}.
+
+-spec find_max(integer(), integer()) -> integer().
+
+find_max(I, M) when I >= M ->
+ find_max(I, ?extend(M));
+find_max(_I, M) ->
+ M.
+
+
+%% @spec (X::term()) -> boolean()
+%% @doc Returns `true' if `X' appears to be an array, otherwise `false'.
+%% Note that the check is only shallow; there is no guarantee that `X'
+%% is a well-formed array representation even if this function returns
+%% `true'.
+
+-spec is_array(term()) -> boolean().
+
+is_array(#array{size = Size, max = Max})
+ when is_integer(Size), is_integer(Max) ->
+ true;
+is_array(_) ->
+ false.
+
+
+%% @spec (array()) -> integer()
+%% @doc Get the number of entries in the array. Entries are numbered
+%% from 0 to `size(Array)-1'; hence, this is also the index of the first
+%% entry that is guaranteed to not have been previously set.
+%% @see set/3
+%% @see sparse_size/1
+
+-spec size(array()) -> non_neg_integer().
+
+size(#array{size = N}) -> N;
+size(_) -> erlang:error(badarg).
+
+
+%% @spec (array()) -> term()
+%% @doc Get the value used for uninitialized entries.
+%%
+%% @see new/2
+
+-spec default(array()) -> term().
+
+default(#array{default = D}) -> D;
+default(_) -> erlang:error(badarg).
+
+
+-ifdef(EUNIT).
+new_test_() ->
+ N0 = ?LEAFSIZE,
+ N01 = N0+1,
+ N1 = ?NODESIZE*N0,
+ N11 = N1+1,
+ N2 = ?NODESIZE*N1,
+ [?_test(new()),
+
+ ?_test(new([])),
+ ?_test(new(10)),
+ ?_test(new({size,10})),
+ ?_test(new(fixed)),
+ ?_test(new({fixed,true})),
+ ?_test(new({fixed,false})),
+ ?_test(new({default,undefined})),
+ ?_test(new([{size,100},{fixed,false},{default,undefined}])),
+ ?_test(new([100,fixed,{default,0}])),
+
+ ?_assert(new() =:= new([])),
+ ?_assert(new() =:= new([{size,0},{default,undefined},{fixed,false}])),
+ ?_assert(new() =:= new(0, {fixed,false})),
+ ?_assert(new(fixed) =:= new(0)),
+ ?_assert(new(fixed) =:= new(0, [])),
+ ?_assert(new(10) =:= new([{size,0},{size,5},{size,10}])),
+ ?_assert(new(10) =:= new(0, {size,10})),
+ ?_assert(new(10, []) =:= new(10, [{default,undefined},{fixed,true}])),
+
+ ?_assertError(badarg, new(-1)),
+ ?_assertError(badarg, new(10.0)),
+ ?_assertError(badarg, new(undefined)),
+ ?_assertError(badarg, new([undefined])),
+ ?_assertError(badarg, new([{default,0} | fixed])),
+
+ ?_assertError(badarg, new(-1, [])),
+ ?_assertError(badarg, new(10.0, [])),
+ ?_assertError(badarg, new(undefined, [])),
+
+ ?_assertMatch(#array{size=0,max=N0,default=undefined,elements=N0},
+ new()),
+ ?_assertMatch(#array{size=0,max=0,default=undefined,elements=N0},
+ new(fixed)),
+ ?_assertMatch(#array{size=N0,max=N0,elements=N0},
+ new(N0, {fixed,false})),
+ ?_assertMatch(#array{size=N01,max=N1,elements=N1},
+ new(N01, {fixed,false})),
+ ?_assertMatch(#array{size=N1,max=N1,elements=N1},
+ new(N1, {fixed,false})),
+ ?_assertMatch(#array{size=N11,max=N2,elements=N2},
+ new(N11, {fixed,false})),
+ ?_assertMatch(#array{size=N2, max=N2, default=42,elements=N2},
+ new(N2, [{fixed,false},{default,42}])),
+
+ ?_assert(0 =:= array:size(new())),
+ ?_assert(17 =:= array:size(new(17))),
+ ?_assert(100 =:= array:size(array:set(99,0,new()))),
+ ?_assertError(badarg, array:size({bad_data,gives_error})),
+
+ ?_assert(undefined =:= default(new())),
+ ?_assert(4711 =:= default(new({default,4711}))),
+ ?_assert(0 =:= default(new(10, {default,0}))),
+ ?_assertError(badarg, default({bad_data,gives_error})),
+
+ ?_assert(is_array(new())),
+ ?_assert(false =:= is_array({foobar, 23, 23})),
+ ?_assert(false =:= is_array(#array{size=bad})),
+ ?_assert(false =:= is_array(#array{max=bad})),
+ ?_assert(is_array(new(10))),
+ ?_assert(is_array(new(10, {fixed,false})))
+ ].
+-endif.
+
+
+%% @spec (array()) -> array()
+%% @doc Fix the size of the array. This prevents it from growing
+%% automatically upon insertion; see also {@link set/3}.
+%% @see relax/1
+
+-spec fix(array()) -> array().
+
+fix(#array{}=A) ->
+ A#array{max = 0}.
+
+
+%% @spec (array()) -> boolean()
+%% @doc Check if the array has fixed size.
+%% Returns `true' if the array is fixed, otherwise `false'.
+%% @see fix/1
+
+-spec is_fix(array()) -> boolean().
+
+is_fix(#array{max = 0}) -> true;
+is_fix(#array{}) -> false.
+
+
+-ifdef(EUNIT).
+fix_test_() ->
+ [?_assert(is_array(fix(new()))),
+ ?_assert(fix(new()) =:= new(fixed)),
+
+ ?_assertNot(is_fix(new())),
+ ?_assertNot(is_fix(new([]))),
+ ?_assertNot(is_fix(new({fixed,false}))),
+ ?_assertNot(is_fix(new(10, {fixed,false}))),
+ ?_assert(is_fix(new({fixed,true}))),
+ ?_assert(is_fix(new(fixed))),
+ ?_assert(is_fix(new(10))),
+ ?_assert(is_fix(new(10, []))),
+ ?_assert(is_fix(new(10, {fixed,true}))),
+ ?_assert(is_fix(fix(new()))),
+ ?_assert(is_fix(fix(new({fixed,false})))),
+
+ ?_test(set(0, 17, new())),
+ ?_assertError(badarg, set(0, 17, new(fixed))),
+ ?_assertError(badarg, set(1, 42, fix(set(0, 17, new())))),
+
+ ?_test(set(9, 17, new(10))),
+ ?_assertError(badarg, set(10, 17, new(10))),
+ ?_assertError(badarg, set(10, 17, fix(new(10, {fixed,false}))))
+ ].
+-endif.
+
+
+%% @spec (array()) -> array()
+%% @doc Make the array resizable. (Reverses the effects of {@link
+%% fix/1}.)
+%% @see fix/1
+
+-spec relax(array()) -> array().
+
+relax(#array{size = N}=A) ->
+ A#array{max = find_max(N-1, ?LEAFSIZE)}.
+
+
+-ifdef(EUNIT).
+relax_test_() ->
+ [?_assert(is_array(relax(new(fixed)))),
+ ?_assertNot(is_fix(relax(fix(new())))),
+ ?_assertNot(is_fix(relax(new(fixed)))),
+
+ ?_assert(new() =:= relax(new(fixed))),
+ ?_assert(new() =:= relax(new(0))),
+ ?_assert(new(17, {fixed,false}) =:= relax(new(17))),
+ ?_assert(new(100, {fixed,false})
+ =:= relax(fix(new(100, {fixed,false}))))
+ ].
+-endif.
+
+
+%% @spec (integer(), array()) -> array()
+%% @doc Change the size of the array. If `Size' is not a nonnegative
+%% integer, the call fails with reason `badarg'. If the given array has
+%% fixed size, the resulting array will also have fixed size.
+
+-spec resize(non_neg_integer(), array()) -> array().
+
+resize(Size, #array{size = N, max = M, elements = E}=A)
+ when is_integer(Size), Size >= 0 ->
+ if Size > N ->
+ {E1, M1} = grow(Size-1, E,
+ if M > 0 -> M;
+ true -> find_max(N-1, ?LEAFSIZE)
+ end),
+ A#array{size = Size,
+ max = if M > 0 -> M1;
+ true -> M
+ end,
+ elements = E1};
+ Size < N ->
+ %% TODO: shrink physical representation when shrinking the array
+ A#array{size = Size};
+ true ->
+ A
+ end;
+resize(_Size, _) ->
+ erlang:error(badarg).
+
+
+%% @spec (array()) -> array()
+
+%% @doc Change the size of the array to that reported by {@link
+%% sparse_size/1}. If the given array has fixed size, the resulting
+%% array will also have fixed size.
+%% @equiv resize(sparse_size(Array), Array)
+%% @see resize/2
+%% @see sparse_size/1
+
+-spec resize(array()) -> array().
+
+resize(Array) ->
+ resize(sparse_size(Array), Array).
+
+
+-ifdef(EUNIT).
+resize_test_() ->
+ [?_assert(resize(0, new()) =:= new()),
+ ?_assert(resize(99, new(99)) =:= new(99)),
+ ?_assert(resize(99, relax(new(99))) =:= relax(new(99))),
+ ?_assert(is_fix(resize(100, new(10)))),
+ ?_assertNot(is_fix(resize(100, relax(new(10))))),
+
+ ?_assert(array:size(resize(100, new())) =:= 100),
+ ?_assert(array:size(resize(0, new(100))) =:= 0),
+ ?_assert(array:size(resize(99, new(10))) =:= 99),
+ ?_assert(array:size(resize(99, new(1000))) =:= 99),
+
+ ?_assertError(badarg, set(99, 17, new(10))),
+ ?_test(set(99, 17, resize(100, new(10)))),
+ ?_assertError(badarg, set(100, 17, resize(100, new(10)))),
+
+ ?_assert(array:size(resize(new())) =:= 0),
+ ?_assert(array:size(resize(new(8))) =:= 0),
+ ?_assert(array:size(resize(array:set(7, 0, new()))) =:= 8),
+ ?_assert(array:size(resize(array:set(7, 0, new(10)))) =:= 8),
+ ?_assert(array:size(resize(array:set(99, 0, new(10,{fixed,false}))))
+ =:= 100),
+ ?_assert(array:size(resize(array:set(7, undefined, new()))) =:= 0),
+ ?_assert(array:size(resize(array:from_list([1,2,3,undefined])))
+ =:= 3),
+ ?_assert(array:size(
+ resize(array:from_orddict([{3,0},{17,0},{99,undefined}])))
+ =:= 18),
+ ?_assertError(badarg, resize(foo, bad_argument))
+ ].
+-endif.
+
+
+%% @spec (integer(), term(), array()) -> array()
+%% @doc Set entry `I' of the array to `Value'. If `I' is not a
+%% nonnegative integer, or if the array has fixed size and `I' is larger
+%% than the maximum index, the call fails with reason `badarg'.
+%%
+%% If the array does not have fixed size, and `I' is greater than
+%% `size(Array)-1', the array will grow to size `I+1'.
+%%
+%% @see get/2
+%% @see reset/2
+
+-spec set(array_indx(), term(), array()) -> array().
+
+set(I, Value, #array{size = N, max = M, default = D, elements = E}=A)
+ when is_integer(I), I >= 0 ->
+ if I < N ->
+ A#array{elements = set_1(I, E, Value, D)};
+ I < M ->
+ %% (note that this cannot happen if M == 0, since N >= 0)
+ A#array{size = I+1, elements = set_1(I, E, Value, D)};
+ M > 0 ->
+ {E1, M1} = grow(I, E, M),
+ A#array{size = I+1, max = M1,
+ elements = set_1(I, E1, Value, D)};
+ true ->
+ erlang:error(badarg)
+ end;
+set(_I, _V, _A) ->
+ erlang:error(badarg).
+
+%% See get_1/3 for details about switching and the NODEPATTERN macro.
+
+set_1(I, E=?NODEPATTERN(S), X, D) ->
+ I1 = I div S + 1,
+ setelement(I1, E, set_1(I rem S, element(I1, E), X, D));
+set_1(I, E, X, D) when is_integer(E) ->
+ expand(I, E, X, D);
+set_1(I, E, X, _D) ->
+ setelement(I+1, E, X).
+
+
+%% Enlarging the array upwards to accommodate an index `I'
+
+grow(I, E, _M) when is_integer(E) ->
+ M1 = find_max(I, E),
+ {M1, M1};
+grow(I, E, M) ->
+ grow_1(I, E, M).
+
+grow_1(I, E, M) when I >= M ->
+ grow(I, setelement(1, ?NEW_NODE(M), E), ?extend(M));
+grow_1(_I, E, M) ->
+ {E, M}.
+
+
+%% Insert an element in an unexpanded node, expanding it as necessary.
+
+expand(I, S, X, D) when S > ?LEAFSIZE ->
+ S1 = ?reduce(S),
+ setelement(I div S1 + 1, ?NEW_NODE(S1),
+ expand(I rem S1, S1, X, D));
+expand(I, _S, X, D) ->
+ setelement(I+1, ?NEW_LEAF(D), X).
+
+
+%% @spec (integer(), array()) -> term()
+%% @doc Get the value of entry `I'. If `I' is not a nonnegative
+%% integer, or if the array has fixed size and `I' is larger than the
+%% maximum index, the call fails with reason `badarg'.
+%%
+%% If the array does not have fixed size, this function will return the
+%% default value for any index `I' greater than `size(Array)-1'.
+
+%% @see set/3
+
+-spec get(array_indx(), array()) -> term().
+
+get(I, #array{size = N, max = M, elements = E, default = D})
+ when is_integer(I), I >= 0 ->
+ if I < N ->
+ get_1(I, E, D);
+ M > 0 ->
+ D;
+ true ->
+ erlang:error(badarg)
+ end;
+get(_I, _A) ->
+ erlang:error(badarg).
+
+%% The use of NODEPATTERN(S) to select the right clause is just a hack,
+%% but it is the only way to get the maximum speed out of this loop
+%% (using the Beam compiler in OTP 11).
+
+get_1(I, E=?NODEPATTERN(S), D) ->
+ get_1(I rem S, element(I div S + 1, E), D);
+get_1(_I, E, D) when is_integer(E) ->
+ D;
+get_1(I, E, _D) ->
+ element(I+1, E).
+
+
+%% @spec (integer(), array()) -> array()
+%% @doc Reset entry `I' to the default value for the array.
+%% If the value of entry `I' is the default value the array will be
+%% returned unchanged. Reset will never change size of the array.
+%% Shrinking can be done explicitly by calling {@link resize/2}.
+%%
+%% If `I' is not a nonnegative integer, or if the array has fixed size
+%% and `I' is larger than the maximum index, the call fails with reason
+%% `badarg'; cf. {@link set/3}
+%%
+%% @see new/2
+%% @see set/3
+
+%% TODO: a reset_range function
+
+-spec reset(array_indx(), array()) -> array().
+
+reset(I, #array{size = N, max = M, default = D, elements = E}=A)
+ when is_integer(I), I >= 0 ->
+ if I < N ->
+ try A#array{elements = reset_1(I, E, D)}
+ catch throw:default -> A
+ end;
+ M > 0 ->
+ A;
+ true ->
+ erlang:error(badarg)
+ end;
+reset(_I, _A) ->
+ erlang:error(badarg).
+
+reset_1(I, E=?NODEPATTERN(S), D) ->
+ I1 = I div S + 1,
+ setelement(I1, E, reset_1(I rem S, element(I1, E), D));
+reset_1(_I, E, _D) when is_integer(E) ->
+ throw(default);
+reset_1(I, E, D) ->
+ Indx = I+1,
+ case element(Indx, E) of
+ D -> throw(default);
+ _ -> setelement(I+1, E, D)
+ end.
+
+
+-ifdef(EUNIT).
+set_get_test_() ->
+ N0 = ?LEAFSIZE,
+ N1 = ?NODESIZE*N0,
+ [?_assert(array:get(0, new()) =:= undefined),
+ ?_assert(array:get(1, new()) =:= undefined),
+ ?_assert(array:get(99999, new()) =:= undefined),
+
+ ?_assert(array:get(0, new(1)) =:= undefined),
+ ?_assert(array:get(0, new(1,{default,0})) =:= 0),
+ ?_assert(array:get(9, new(10)) =:= undefined),
+
+ ?_assertError(badarg, array:get(0, new(fixed))),
+ ?_assertError(badarg, array:get(1, new(1))),
+ ?_assertError(badarg, array:get(-1, new(1))),
+ ?_assertError(badarg, array:get(10, new(10))),
+ ?_assertError(badarg, array:set(-1, foo, new(10))),
+ ?_assertError(badarg, array:set(10, foo, no_array)),
+
+ ?_assert(array:size(set(0, 17, new())) =:= 1),
+ ?_assert(array:size(set(N1-1, 17, new())) =:= N1),
+ ?_assert(array:size(set(0, 42, set(0, 17, new()))) =:= 1),
+ ?_assert(array:size(set(9, 42, set(0, 17, new()))) =:= 10),
+
+ ?_assert(array:get(0, set(0, 17, new())) =:= 17),
+ ?_assert(array:get(0, set(1, 17, new())) =:= undefined),
+ ?_assert(array:get(1, set(1, 17, new())) =:= 17),
+
+ ?_assert(array:get(0, fix(set(0, 17, new()))) =:= 17),
+ ?_assertError(badarg, array:get(1, fix(set(0, 17, new())))),
+
+ ?_assert(array:get(N1-2, set(N1-1, 17, new())) =:= undefined),
+ ?_assert(array:get(N1-1, set(N1-1, 17, new())) =:= 17),
+ ?_assertError(badarg, array:get(N1, fix(set(N1-1, 17, new())))),
+
+ ?_assert(array:get(0, set(0, 42, set(0, 17, new()))) =:= 42),
+
+ ?_assert(array:get(0, reset(0, new())) =:= undefined),
+ ?_assert(array:get(0, reset(0, set(0, 17, new()))) =:= undefined),
+ ?_assert(array:get(0, reset(0, new({default,42}))) =:= 42),
+ ?_assert(array:get(0, reset(0, set(0, 17, new({default,42}))))
+ =:= 42)
+ ].
+-endif.
+
+
+%% @spec (array()) -> list()
+%% @doc Converts the array to a list.
+%%
+%% @see from_list/2
+%% @see sparse_to_list/1
+
+-spec to_list(array()) -> list().
+
+to_list(#array{size = 0}) ->
+ [];
+to_list(#array{size = N, elements = E, default = D}) ->
+ to_list_1(E, D, N - 1);
+to_list(_) ->
+ erlang:error(badarg).
+
+%% this part handles the rightmost subtrees
+
+to_list_1(E=?NODEPATTERN(S), D, I) ->
+ N = I div S,
+ to_list_3(N, D, to_list_1(element(N+1, E), D, I rem S), E);
+to_list_1(E, D, I) when is_integer(E) ->
+ push(I+1, D, []);
+to_list_1(E, _D, I) ->
+ push_tuple(I+1, E, []).
+
+%% this part handles full trees only
+
+to_list_2(E=?NODEPATTERN(_S), D, L) ->
+ to_list_3(?NODESIZE, D, L, E);
+to_list_2(E, D, L) when is_integer(E) ->
+ push(E, D, L);
+to_list_2(E, _D, L) ->
+ push_tuple(?LEAFSIZE, E, L).
+
+to_list_3(0, _D, L, _E) ->
+ L;
+to_list_3(N, D, L, E) ->
+ to_list_3(N-1, D, to_list_2(element(N, E), D, L), E).
+
+push(0, _E, L) ->
+ L;
+push(N, E, L) ->
+ push(N - 1, E, [E | L]).
+
+push_tuple(0, _T, L) ->
+ L;
+push_tuple(N, T, L) ->
+ push_tuple(N - 1, T, [element(N, T) | L]).
+
+
+-ifdef(EUNIT).
+to_list_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= to_list(new())),
+ ?_assert([undefined] =:= to_list(new(1))),
+ ?_assert([undefined,undefined] =:= to_list(new(2))),
+ ?_assert(lists:duplicate(N0,0) =:= to_list(new(N0,{default,0}))),
+ ?_assert(lists:duplicate(N0+1,1) =:= to_list(new(N0+1,{default,1}))),
+ ?_assert(lists:duplicate(N0+2,2) =:= to_list(new(N0+2,{default,2}))),
+ ?_assert(lists:duplicate(666,6) =:= to_list(new(666,{default,6}))),
+ ?_assert([1,2,3] =:= to_list(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([3,2,1] =:= to_list(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([1|lists:duplicate(N0-2,0)++[1]] =:=
+ to_list(set(N0-1,1,set(0,1,new({default,0}))))),
+ ?_assert([1|lists:duplicate(N0-1,0)++[1]] =:=
+ to_list(set(N0,1,set(0,1,new({default,0}))))),
+ ?_assert([1|lists:duplicate(N0,0)++[1]] =:=
+ to_list(set(N0+1,1,set(0,1,new({default,0}))))),
+ ?_assert([1|lists:duplicate(N0*3,0)++[1]] =:=
+ to_list(set((N0*3)+1,1,set(0,1,new({default,0}))))),
+ ?_assertError(badarg, to_list(no_array))
+ ].
+-endif.
+
+
+%% @spec (array()) -> list()
+%% @doc Converts the array to a list, skipping default-valued entries.
+%%
+%% @see to_list/1
+
+-spec sparse_to_list(array()) -> list().
+
+sparse_to_list(#array{size = 0}) ->
+ [];
+sparse_to_list(#array{size = N, elements = E, default = D}) ->
+ sparse_to_list_1(E, D, N - 1);
+sparse_to_list(_) ->
+ erlang:error(badarg).
+
+%% see to_list/1 for details
+
+sparse_to_list_1(E=?NODEPATTERN(S), D, I) ->
+ N = I div S,
+ sparse_to_list_3(N, D,
+ sparse_to_list_1(element(N+1, E), D, I rem S),
+ E);
+sparse_to_list_1(E, _D, _I) when is_integer(E) ->
+ [];
+sparse_to_list_1(E, D, I) ->
+ sparse_push_tuple(I+1, D, E, []).
+
+sparse_to_list_2(E=?NODEPATTERN(_S), D, L) ->
+ sparse_to_list_3(?NODESIZE, D, L, E);
+sparse_to_list_2(E, _D, L) when is_integer(E) ->
+ L;
+sparse_to_list_2(E, D, L) ->
+ sparse_push_tuple(?LEAFSIZE, D, E, L).
+
+sparse_to_list_3(0, _D, L, _E) ->
+ L;
+sparse_to_list_3(N, D, L, E) ->
+ sparse_to_list_3(N-1, D, sparse_to_list_2(element(N, E), D, L), E).
+
+sparse_push_tuple(0, _D, _T, L) ->
+ L;
+sparse_push_tuple(N, D, T, L) ->
+ case element(N, T) of
+ D -> sparse_push_tuple(N - 1, D, T, L);
+ E -> sparse_push_tuple(N - 1, D, T, [E | L])
+ end.
+
+
+-ifdef(EUNIT).
+sparse_to_list_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= sparse_to_list(new())),
+ ?_assert([] =:= sparse_to_list(new(1))),
+ ?_assert([] =:= sparse_to_list(new(1,{default,0}))),
+ ?_assert([] =:= sparse_to_list(new(2))),
+ ?_assert([] =:= sparse_to_list(new(2,{default,0}))),
+ ?_assert([] =:= sparse_to_list(new(N0,{default,0}))),
+ ?_assert([] =:= sparse_to_list(new(N0+1,{default,1}))),
+ ?_assert([] =:= sparse_to_list(new(N0+2,{default,2}))),
+ ?_assert([] =:= sparse_to_list(new(666,{default,6}))),
+ ?_assert([1,2,3] =:= sparse_to_list(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([3,2,1] =:= sparse_to_list(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([0,1] =:= sparse_to_list(set(N0-1,1,set(0,0,new())))),
+ ?_assert([0,1] =:= sparse_to_list(set(N0,1,set(0,0,new())))),
+ ?_assert([0,1] =:= sparse_to_list(set(N0+1,1,set(0,0,new())))),
+ ?_assert([0,1,2] =:= sparse_to_list(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
+ ?_assertError(badarg, sparse_to_list(no_array))
+ ].
+-endif.
+
+
+%% @spec (list()) -> array()
+%% @equiv from_list(List, undefined)
+
+-spec from_list(list()) -> array().
+
+from_list(List) ->
+ from_list(List, undefined).
+
+%% @spec (list(), term()) -> array()
+%% @doc Convert a list to an extendible array. `Default' is used as the value
+%% for uninitialized entries of the array. If `List' is not a proper list,
+%% the call fails with reason `badarg'.
+%%
+%% @see new/2
+%% @see to_list/1
+
+-spec from_list(list(), term()) -> array().
+
+from_list([], Default) ->
+ new({default,Default});
+from_list(List, Default) when is_list(List) ->
+ {E, N, M} = from_list_1(?LEAFSIZE, List, Default, 0, [], []),
+ #array{size = N, max = M, default = Default, elements = E};
+from_list(_, _) ->
+ erlang:error(badarg).
+
+%% Note: A cleaner but slower algorithm is to first take the length of
+%% the list and compute the max size of the final tree, and then
+%% decompose the list. The below algorithm is almost twice as fast,
+%% however.
+
+%% Building the leaf nodes (padding the last one as necessary) and
+%% counting the total number of elements.
+from_list_1(0, Xs, D, N, As, Es) ->
+ E = list_to_tuple(lists:reverse(As)),
+ case Xs of
+ [] ->
+ case Es of
+ [] ->
+ {E, N, ?LEAFSIZE};
+ _ ->
+ from_list_2_0(N, [E | Es], ?LEAFSIZE)
+ end;
+ [_|_] ->
+ from_list_1(?LEAFSIZE, Xs, D, N, [], [E | Es]);
+ _ ->
+ erlang:error(badarg)
+ end;
+from_list_1(I, Xs, D, N, As, Es) ->
+ case Xs of
+ [X | Xs1] ->
+ from_list_1(I-1, Xs1, D, N+1, [X | As], Es);
+ _ ->
+ from_list_1(I-1, Xs, D, N, [D | As], Es)
+ end.
+
+%% Building the internal nodes (note that the input is reversed).
+from_list_2_0(N, Es, S) ->
+ from_list_2(?NODESIZE, pad((N-1) div S + 1, ?NODESIZE, S, Es),
+ S, N, [S], []).
+
+from_list_2(0, Xs, S, N, As, Es) ->
+ E = list_to_tuple(As),
+ case Xs of
+ [] ->
+ case Es of
+ [] ->
+ {E, N, ?extend(S)};
+ _ ->
+ from_list_2_0(N, lists:reverse([E | Es]),
+ ?extend(S))
+ end;
+ _ ->
+ from_list_2(?NODESIZE, Xs, S, N, [S], [E | Es])
+ end;
+from_list_2(I, [X | Xs], S, N, As, Es) ->
+ from_list_2(I-1, Xs, S, N, [X | As], Es).
+
+
+%% left-padding a list Es with elements P to the nearest multiple of K
+%% elements from N (adding 0 to K-1 elements).
+pad(N, K, P, Es) ->
+ push((K - (N rem K)) rem K, P, Es).
+
+
+-ifdef(EUNIT).
+from_list_test_() ->
+ N0 = ?LEAFSIZE,
+ N1 = ?NODESIZE*N0,
+ N2 = ?NODESIZE*N1,
+ N3 = ?NODESIZE*N2,
+ N4 = ?NODESIZE*N3,
+ [?_assert(array:size(from_list([])) =:= 0),
+ ?_assert(array:is_fix(from_list([])) =:= false),
+ ?_assert(array:size(from_list([undefined])) =:= 1),
+ ?_assert(array:is_fix(from_list([undefined])) =:= false),
+ ?_assert(array:size(from_list(lists:seq(1,N1))) =:= N1),
+ ?_assert(to_list(from_list(lists:seq(1,N0))) =:= lists:seq(1,N0)),
+ ?_assert(to_list(from_list(lists:seq(1,N0+1))) =:= lists:seq(1,N0+1)),
+ ?_assert(to_list(from_list(lists:seq(1,N0+2))) =:= lists:seq(1,N0+2)),
+ ?_assert(to_list(from_list(lists:seq(1,N2))) =:= lists:seq(1,N2)),
+ ?_assert(to_list(from_list(lists:seq(1,N2+1))) =:= lists:seq(1,N2+1)),
+ ?_assert(to_list(from_list(lists:seq(0,N3))) =:= lists:seq(0,N3)),
+ ?_assert(to_list(from_list(lists:seq(0,N4))) =:= lists:seq(0,N4)),
+ ?_assertError(badarg, from_list([a,b,a,c|d])),
+ ?_assertError(badarg, from_list(no_array))
+ ].
+-endif.
+
+
+%% @spec (array()) -> [{Index::integer(), Value::term()}]
+%% @doc Convert the array to an ordered list of pairs `{Index, Value}'.
+%%
+%% @see from_orddict/2
+%% @see sparse_to_orddict/1
+
+-spec to_orddict(array()) -> indx_pairs().
+
+to_orddict(#array{size = 0}) ->
+ [];
+to_orddict(#array{size = N, elements = E, default = D}) ->
+ I = N - 1,
+ to_orddict_1(E, I, D, I);
+to_orddict(_) ->
+ erlang:error(badarg).
+
+%% see to_list/1 for comparison
+
+to_orddict_1(E=?NODEPATTERN(S), R, D, I) ->
+ N = I div S,
+ I1 = I rem S,
+ to_orddict_3(N, R - I1 - 1, D,
+ to_orddict_1(element(N+1, E), R, D, I1),
+ E, S);
+to_orddict_1(E, R, D, I) when is_integer(E) ->
+ push_pairs(I+1, R, D, []);
+to_orddict_1(E, R, _D, I) ->
+ push_tuple_pairs(I+1, R, E, []).
+
+to_orddict_2(E=?NODEPATTERN(S), R, D, L) ->
+ to_orddict_3(?NODESIZE, R, D, L, E, S);
+to_orddict_2(E, R, D, L) when is_integer(E) ->
+ push_pairs(E, R, D, L);
+to_orddict_2(E, R, _D, L) ->
+ push_tuple_pairs(?LEAFSIZE, R, E, L).
+
+to_orddict_3(0, _R, _D, L, _E, _S) -> %% when is_integer(R) ->
+ L;
+to_orddict_3(N, R, D, L, E, S) ->
+ to_orddict_3(N-1, R - S, D,
+ to_orddict_2(element(N, E), R, D, L),
+ E, S).
+
+-spec push_pairs(non_neg_integer(), array_indx(), term(), indx_pairs()) ->
+ indx_pairs().
+
+push_pairs(0, _I, _E, L) ->
+ L;
+push_pairs(N, I, E, L) ->
+ push_pairs(N-1, I-1, E, [{I, E} | L]).
+
+-spec push_tuple_pairs(non_neg_integer(), array_indx(), term(), indx_pairs()) ->
+ indx_pairs().
+
+push_tuple_pairs(0, _I, _T, L) ->
+ L;
+push_tuple_pairs(N, I, T, L) ->
+ push_tuple_pairs(N-1, I-1, T, [{I, element(N, T)} | L]).
+
+
+-ifdef(EUNIT).
+to_orddict_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= to_orddict(new())),
+ ?_assert([{0,undefined}] =:= to_orddict(new(1))),
+ ?_assert([{0,undefined},{1,undefined}] =:= to_orddict(new(2))),
+ ?_assert([{N,0}||N<-lists:seq(0,N0-1)]
+ =:= to_orddict(new(N0,{default,0}))),
+ ?_assert([{N,1}||N<-lists:seq(0,N0)]
+ =:= to_orddict(new(N0+1,{default,1}))),
+ ?_assert([{N,2}||N<-lists:seq(0,N0+1)]
+ =:= to_orddict(new(N0+2,{default,2}))),
+ ?_assert([{N,6}||N<-lists:seq(0,665)]
+ =:= to_orddict(new(666,{default,6}))),
+ ?_assert([{0,1},{1,2},{2,3}] =:=
+ to_orddict(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([{0,3},{1,2},{2,1}] =:=
+ to_orddict(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0-2)]++[{N0-1,1}]]
+ =:= to_orddict(set(N0-1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0-1)]++[{N0,1}]]
+ =:= to_orddict(set(N0,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0)]++[{N0+1,1}]]
+ =:= to_orddict(set(N0+1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,0} | [{N,undefined}||N<-lists:seq(1,N0*2)]] ++
+ [{N0*2+1,1} | [{N,undefined}||N<-lists:seq(N0*2+2,N0*10)]] ++
+ [{N0*10+1,2}] =:=
+ to_orddict(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
+ ?_assertError(badarg, to_orddict(no_array))
+ ].
+-endif.
+
+
+%% @spec (array()) -> [{Index::integer(), Value::term()}]
+%% @doc Convert the array to an ordered list of pairs `{Index, Value}',
+%% skipping default-valued entries.
+%%
+%% @see to_orddict/1
+
+-spec sparse_to_orddict(array()) -> indx_pairs().
+
+sparse_to_orddict(#array{size = 0}) ->
+ [];
+sparse_to_orddict(#array{size = N, elements = E, default = D}) ->
+ I = N - 1,
+ sparse_to_orddict_1(E, I, D, I);
+sparse_to_orddict(_) ->
+ erlang:error(badarg).
+
+%% see to_orddict/1 for details
+
+sparse_to_orddict_1(E=?NODEPATTERN(S), R, D, I) ->
+ N = I div S,
+ I1 = I rem S,
+ sparse_to_orddict_3(N, R - I1 - 1, D,
+ sparse_to_orddict_1(element(N+1, E), R, D, I1),
+ E, S);
+sparse_to_orddict_1(E, _R, _D, _I) when is_integer(E) ->
+ [];
+sparse_to_orddict_1(E, R, D, I) ->
+ sparse_push_tuple_pairs(I+1, R, D, E, []).
+
+sparse_to_orddict_2(E=?NODEPATTERN(S), R, D, L) ->
+ sparse_to_orddict_3(?NODESIZE, R, D, L, E, S);
+sparse_to_orddict_2(E, _R, _D, L) when is_integer(E) ->
+ L;
+sparse_to_orddict_2(E, R, D, L) ->
+ sparse_push_tuple_pairs(?LEAFSIZE, R, D, E, L).
+
+sparse_to_orddict_3(0, _R, _D, L, _E, _S) -> % when is_integer(R) ->
+ L;
+sparse_to_orddict_3(N, R, D, L, E, S) ->
+ sparse_to_orddict_3(N-1, R - S, D,
+ sparse_to_orddict_2(element(N, E), R, D, L),
+ E, S).
+
+-spec sparse_push_tuple_pairs(non_neg_integer(), array_indx(),
+ _, _, indx_pairs()) -> indx_pairs().
+
+sparse_push_tuple_pairs(0, _I, _D, _T, L) ->
+ L;
+sparse_push_tuple_pairs(N, I, D, T, L) ->
+ case element(N, T) of
+ D -> sparse_push_tuple_pairs(N-1, I-1, D, T, L);
+ E -> sparse_push_tuple_pairs(N-1, I-1, D, T, [{I, E} | L])
+ end.
+
+
+-ifdef(EUNIT).
+sparse_to_orddict_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= sparse_to_orddict(new())),
+ ?_assert([] =:= sparse_to_orddict(new(1))),
+ ?_assert([] =:= sparse_to_orddict(new(1,{default,0}))),
+ ?_assert([] =:= sparse_to_orddict(new(2))),
+ ?_assert([] =:= sparse_to_orddict(new(2,{default,0}))),
+ ?_assert([] =:= sparse_to_orddict(new(N0,{default,0}))),
+ ?_assert([] =:= sparse_to_orddict(new(N0+1,{default,1}))),
+ ?_assert([] =:= sparse_to_orddict(new(N0+2,{default,2}))),
+ ?_assert([] =:= sparse_to_orddict(new(666,{default,6}))),
+ ?_assert([{0,1},{1,2},{2,3}] =:=
+ sparse_to_orddict(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([{0,3},{1,2},{2,1}] =:=
+ sparse_to_orddict(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([{0,1},{N0-1,1}] =:=
+ sparse_to_orddict(set(N0-1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1},{N0,1}] =:=
+ sparse_to_orddict(set(N0,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1},{N0+1,1}] =:=
+ sparse_to_orddict(set(N0+1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,0},{N0*2+1,1},{N0*10+1,2}] =:=
+ sparse_to_orddict(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
+ ?_assertError(badarg, sparse_to_orddict(no_array))
+ ].
+-endif.
+
+
+%% @spec (list()) -> array()
+%% @equiv from_orddict(Orddict, undefined)
+
+-spec from_orddict(indx_pairs()) -> array().
+
+from_orddict(Orddict) ->
+ from_orddict(Orddict, undefined).
+
+%% @spec (list(), term()) -> array()
+%% @doc Convert an ordered list of pairs `{Index, Value}' to a
+%% corresponding extendible array. `Default' is used as the value for
+%% uninitialized entries of the array. If `List' is not a proper,
+%% ordered list of pairs whose first elements are nonnegative
+%% integers, the call fails with reason `badarg'.
+%%
+%% @see new/2
+%% @see to_orddict/1
+
+-spec from_orddict(indx_pairs(), term()) -> array().
+
+from_orddict([], Default) ->
+ new({default,Default});
+from_orddict(List, Default) when is_list(List) ->
+ {E, N, M} = from_orddict_0(List, 0, ?LEAFSIZE, Default, []),
+ #array{size = N, max = M, default = Default, elements = E};
+from_orddict(_, _) ->
+ erlang:error(badarg).
+
+%% 2 pass implementation, first pass builds the needed leaf nodes
+%% and adds hole sizes.
+%% (inserts default elements for missing list entries in the leafs
+%% and pads the last tuple if necessary).
+%% Second pass builds the tree from the leafs and the holes.
+%%
+%% Doesn't build/expand unnecessary leaf nodes which costs memory
+%% and time for sparse arrays.
+
+from_orddict_0([], N, _Max, _D, Es) ->
+ %% Finished, build the resulting tree
+ case Es of
+ [E] ->
+ {E, N, ?LEAFSIZE};
+ _ ->
+ collect_leafs(N, Es, ?LEAFSIZE)
+ end;
+
+from_orddict_0(Xs=[{Ix1, _}|_], Ix, Max0, D, Es0)
+ when Ix1 > Max0, is_integer(Ix1) ->
+ %% We have a hole larger than a leaf
+ Hole = Ix1-Ix,
+ Step = Hole - (Hole rem ?LEAFSIZE),
+ Next = Ix+Step,
+ from_orddict_0(Xs, Next, Next+?LEAFSIZE, D, [Step|Es0]);
+from_orddict_0(Xs0=[{_, _}|_], Ix0, Max, D, Es) ->
+ %% Fill a leaf
+ {Xs,E,Ix} = from_orddict_1(Ix0, Max, Xs0, Ix0, D, []),
+ from_orddict_0(Xs, Ix, Ix+?LEAFSIZE, D, [E|Es]);
+from_orddict_0(Xs, _, _, _,_) ->
+ erlang:error({badarg, Xs}).
+
+from_orddict_1(Ix, Ix, Xs, N, _D, As) ->
+ %% Leaf is full
+ E = list_to_tuple(lists:reverse(As)),
+ {Xs, E, N};
+from_orddict_1(Ix, Max, Xs, N0, D, As) ->
+ case Xs of
+ [{Ix, Val} | Xs1] ->
+ N = Ix+1,
+ from_orddict_1(N, Max, Xs1, N, D, [Val | As]);
+ [{Ix1, _} | _] when is_integer(Ix1), Ix1 > Ix ->
+ N = Ix+1,
+ from_orddict_1(N, Max, Xs, N, D, [D | As]);
+ [_ | _] ->
+ erlang:error({badarg, Xs});
+ _ ->
+ from_orddict_1(Ix+1, Max, Xs, N0, D, [D | As])
+ end.
+
+%% Es is reversed i.e. starting from the largest leafs
+collect_leafs(N, Es, S) ->
+ I = (N-1) div S + 1,
+ Pad = ((?NODESIZE - (I rem ?NODESIZE)) rem ?NODESIZE) * S,
+ case Pad of
+ 0 ->
+ collect_leafs(?NODESIZE, Es, S, N, [S], []);
+ _ -> %% Pad the end
+ collect_leafs(?NODESIZE, [Pad|Es], S, N, [S], [])
+ end.
+
+collect_leafs(0, Xs, S, N, As, Es) ->
+ E = list_to_tuple(As),
+ case Xs of
+ [] ->
+ case Es of
+ [] ->
+ {E, N, ?extend(S)};
+ _ ->
+ collect_leafs(N, lists:reverse([E | Es]),
+ ?extend(S))
+ end;
+ _ ->
+ collect_leafs(?NODESIZE, Xs, S, N, [S], [E | Es])
+ end;
+collect_leafs(I, [X | Xs], S, N, As0, Es0)
+ when is_integer(X) ->
+ %% A hole, pad accordingly.
+ Step0 = (X div S),
+ if
+ Step0 < I ->
+ As = push(Step0, S, As0),
+ collect_leafs(I-Step0, Xs, S, N, As, Es0);
+ I =:= ?NODESIZE ->
+ Step = Step0 rem ?NODESIZE,
+ As = push(Step, S, As0),
+ collect_leafs(I-Step, Xs, S, N, As, [X|Es0]);
+ I =:= Step0 ->
+ As = push(I, S, As0),
+ collect_leafs(0, Xs, S, N, As, Es0);
+ true ->
+ As = push(I, S, As0),
+ Step = Step0 - I,
+ collect_leafs(0, [Step*S|Xs], S, N, As, Es0)
+ end;
+collect_leafs(I, [X | Xs], S, N, As, Es) ->
+ collect_leafs(I-1, Xs, S, N, [X | As], Es);
+collect_leafs(?NODESIZE, [], S, N, [_], Es) ->
+ collect_leafs(N, lists:reverse(Es), ?extend(S)).
+
+-ifdef(EUNIT).
+from_orddict_test_() ->
+ N0 = ?LEAFSIZE,
+ N1 = ?NODESIZE*N0,
+ N2 = ?NODESIZE*N1,
+ N3 = ?NODESIZE*N2,
+ N4 = ?NODESIZE*N3,
+ [?_assert(array:size(from_orddict([])) =:= 0),
+ ?_assert(array:is_fix(from_orddict([])) =:= false),
+ ?_assert(array:size(from_orddict([{0,undefined}])) =:= 1),
+ ?_assert(array:is_fix(from_orddict([{0,undefined}])) =:= false),
+ ?_assert(array:size(from_orddict([{N0-1,undefined}])) =:= N0),
+ ?_assert(array:size(from_orddict([{N,0}||N<-lists:seq(0,N1-1)]))
+ =:= N1),
+ ?_assertError({badarg,_}, from_orddict([foo])),
+ ?_assertError({badarg,_}, from_orddict([{200,foo},{1,bar}])),
+ ?_assertError({badarg,_}, from_orddict([{N,0}||N<-lists:seq(0,N0-1)] ++ not_a_list)),
+ ?_assertError(badarg, from_orddict(no_array)),
+
+
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N0-1)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N0)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N2-1)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N2)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N3-1)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N4-1)],
+ L =:= to_orddict(from_orddict(L)))),
+
+ %% Hole in the begining
+ ?_assert(?LET(L, [{0,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N0,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N3,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N4,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N0-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N1-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N3-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N4-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+
+ %% Hole in middle
+
+ ?_assert(?LET(L, [{0,0},{N0,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N3,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N4,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N0-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N1-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N3-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N4-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L))))
+
+ ].
+-endif.
+
+
+%% @spec (Function, array()) -> array()
+%% Function = (Index::integer(), Value::term()) -> term()
+%% @doc Map the given function onto each element of the array. The
+%% elements are visited in order from the lowest index to the highest.
+%% If `Function' is not a function, the call fails with reason `badarg'.
+%%
+%% @see foldl/3
+%% @see foldr/3
+%% @see sparse_map/2
+
+-spec map(fun((array_indx(), _) -> _), array()) -> array().
+
+map(Function, Array=#array{size = N, elements = E, default = D})
+ when is_function(Function, 2) ->
+ if N > 0 ->
+ A = Array#array{elements = []}, % kill reference, for GC
+ A#array{elements = map_1(N-1, E, 0, Function, D)};
+ true ->
+ Array
+ end;
+map(_, _) ->
+ erlang:error(badarg).
+
+%% It might be simpler to traverse the array right-to-left, as done e.g.
+%% in the to_orddict/1 function, but it is better to guarantee
+%% left-to-right application over the elements - that is more likely to
+%% be a generally useful property.
+
+map_1(N, E=?NODEPATTERN(S), Ix, F, D) ->
+ list_to_tuple(lists:reverse([S | map_2(1, E, Ix, F, D, [],
+ N div S + 1, N rem S, S)]));
+map_1(N, E, Ix, F, D) when is_integer(E) ->
+ map_1(N, unfold(E, D), Ix, F, D);
+map_1(N, E, Ix, F, D) ->
+ list_to_tuple(lists:reverse(map_3(1, E, Ix, F, D, N+1, []))).
+
+map_2(I, E, Ix, F, D, L, I, R, _S) ->
+ map_2_1(I+1, E, [map_1(R, element(I, E), Ix, F, D) | L]);
+map_2(I, E, Ix, F, D, L, N, R, S) ->
+ map_2(I+1, E, Ix + S, F, D,
+ [map_1(S-1, element(I, E), Ix, F, D) | L],
+ N, R, S).
+
+map_2_1(I, E, L) when I =< ?NODESIZE ->
+ map_2_1(I+1, E, [element(I, E) | L]);
+map_2_1(_I, _E, L) ->
+ L.
+
+-spec map_3(pos_integer(), _, array_indx(),
+ fun((array_indx(),_) -> _), _, non_neg_integer(), [X]) -> [X].
+
+map_3(I, E, Ix, F, D, N, L) when I =< N ->
+ map_3(I+1, E, Ix+1, F, D, N, [F(Ix, element(I, E)) | L]);
+map_3(I, E, Ix, F, D, N, L) when I =< ?LEAFSIZE ->
+ map_3(I+1, E, Ix+1, F, D, N, [D | L]);
+map_3(_I, _E, _Ix, _F, _D, _N, L) ->
+ L.
+
+
+unfold(S, _D) when S > ?LEAFSIZE ->
+ ?NEW_NODE(?reduce(S));
+unfold(_S, D) ->
+ ?NEW_LEAF(D).
+
+
+-ifdef(EUNIT).
+map_test_() ->
+ N0 = ?LEAFSIZE,
+ Id = fun (_,X) -> X end,
+ Plus = fun(N) -> fun (_,X) -> X+N end end,
+ Default = fun(_K,undefined) -> no_value;
+ (K,V) -> K+V
+ end,
+ [?_assertError(badarg, map([], new())),
+ ?_assertError(badarg, map([], new(10))),
+ ?_assert(to_list(map(Id, new())) =:= []),
+ ?_assert(to_list(map(Id, new(1))) =:= [undefined]),
+ ?_assert(to_list(map(Id, new(5,{default,0}))) =:= [0,0,0,0,0]),
+ ?_assert(to_list(map(Id, from_list([1,2,3,4]))) =:= [1,2,3,4]),
+ ?_assert(to_list(map(Plus(1), from_list([0,1,2,3]))) =:= [1,2,3,4]),
+ ?_assert(to_list(map(Plus(-1), from_list(lists:seq(1,11))))
+ =:= lists:seq(0,10)),
+ ?_assert(to_list(map(Plus(11), from_list(lists:seq(0,99999))))
+ =:= lists:seq(11,100010)),
+ ?_assert([{0,0},{N0*2+1,N0*2+1+1},{N0*100+1,N0*100+1+2}] =:=
+ sparse_to_orddict((map(Default,
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))#array{default = no_value}))
+ ].
+-endif.
+
+
+%% @spec (Function, array()) -> array()
+%% Function = (Index::integer(), Value::term()) -> term()
+%% @doc Map the given function onto each element of the array, skipping
+%% default-valued entries. The elements are visited in order from the
+%% lowest index to the highest. If `Function' is not a function, the
+%% call fails with reason `badarg'.
+%%
+%% @see map/2
+
+-spec sparse_map(fun((array_indx(), _) -> _), array()) -> array().
+
+sparse_map(Function, Array=#array{size = N, elements = E, default = D})
+ when is_function(Function, 2) ->
+ if N > 0 ->
+ A = Array#array{elements = []}, % kill reference, for GC
+ A#array{elements = sparse_map_1(N-1, E, 0, Function, D)};
+ true ->
+ Array
+ end;
+sparse_map(_, _) ->
+ erlang:error(badarg).
+
+%% see map/2 for details
+%% TODO: we can probably optimize away the use of div/rem here
+
+sparse_map_1(N, E=?NODEPATTERN(S), Ix, F, D) ->
+ list_to_tuple(lists:reverse([S | sparse_map_2(1, E, Ix, F, D, [],
+ N div S + 1,
+ N rem S, S)]));
+sparse_map_1(_N, E, _Ix, _F, _D) when is_integer(E) ->
+ E;
+sparse_map_1(_N, E, Ix, F, D) ->
+ list_to_tuple(lists:reverse(sparse_map_3(1, E, Ix, F, D, []))).
+
+sparse_map_2(I, E, Ix, F, D, L, I, R, _S) ->
+ sparse_map_2_1(I+1, E,
+ [sparse_map_1(R, element(I, E), Ix, F, D) | L]);
+sparse_map_2(I, E, Ix, F, D, L, N, R, S) ->
+ sparse_map_2(I+1, E, Ix + S, F, D,
+ [sparse_map_1(S-1, element(I, E), Ix, F, D) | L],
+ N, R, S).
+
+sparse_map_2_1(I, E, L) when I =< ?NODESIZE ->
+ sparse_map_2_1(I+1, E, [element(I, E) | L]);
+sparse_map_2_1(_I, _E, L) ->
+ L.
+
+-spec sparse_map_3(pos_integer(), _, array_indx(),
+ fun((array_indx(),_) -> _), _, [X]) -> [X].
+
+sparse_map_3(I, T, Ix, F, D, L) when I =< ?LEAFSIZE ->
+ case element(I, T) of
+ D -> sparse_map_3(I+1, T, Ix+1, F, D, [D | L]);
+ E -> sparse_map_3(I+1, T, Ix+1, F, D, [F(Ix, E) | L])
+ end;
+sparse_map_3(_I, _E, _Ix, _F, _D, L) ->
+ L.
+
+
+-ifdef(EUNIT).
+sparse_map_test_() ->
+ N0 = ?LEAFSIZE,
+ Id = fun (_,X) -> X end,
+ Plus = fun(N) -> fun (_,X) -> X+N end end,
+ KeyPlus = fun (K,X) -> K+X end,
+ [?_assertError(badarg, sparse_map([], new())),
+ ?_assertError(badarg, sparse_map([], new(10))),
+ ?_assert(to_list(sparse_map(Id, new())) =:= []),
+ ?_assert(to_list(sparse_map(Id, new(1))) =:= [undefined]),
+ ?_assert(to_list(sparse_map(Id, new(5,{default,0}))) =:= [0,0,0,0,0]),
+ ?_assert(to_list(sparse_map(Id, from_list([1,2,3,4]))) =:= [1,2,3,4]),
+ ?_assert(to_list(sparse_map(Plus(1), from_list([0,1,2,3])))
+ =:= [1,2,3,4]),
+ ?_assert(to_list(sparse_map(Plus(-1), from_list(lists:seq(1,11))))
+ =:= lists:seq(0,10)),
+ ?_assert(to_list(sparse_map(Plus(11), from_list(lists:seq(0,99999))))
+ =:= lists:seq(11,100010)),
+ ?_assert(to_list(sparse_map(Plus(1), set(1,1,new({default,0}))))
+ =:= [0,2]),
+ ?_assert(to_list(sparse_map(Plus(1),
+ set(3,4,set(0,1,new({default,0})))))
+ =:= [2,0,0,5]),
+ ?_assert(to_list(sparse_map(Plus(1),
+ set(9,9,set(1,1,new({default,0})))))
+ =:= [0,2,0,0,0,0,0,0,0,10]),
+ ?_assert([{0,0},{N0*2+1,N0*2+1+1},{N0*100+1,N0*100+1+2}] =:=
+ sparse_to_orddict(sparse_map(KeyPlus,
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new()))))))
+
+ ].
+-endif.
+
+
+%% @spec (Function, InitialAcc::term(), array()) -> term()
+%% Function = (Index::integer(), Value::term(), Acc::term()) ->
+%% term()
+%% @doc Fold the elements of the array using the given function and
+%% initial accumulator value. The elements are visited in order from the
+%% lowest index to the highest. If `Function' is not a function, the
+%% call fails with reason `badarg'.
+%%
+%% @see foldr/3
+%% @see map/2
+%% @see sparse_foldl/3
+
+-spec foldl(fun((array_indx(), _, A) -> B), A, array()) -> B.
+
+foldl(Function, A, #array{size = N, elements = E, default = D})
+ when is_function(Function, 3) ->
+ if N > 0 ->
+ foldl_1(N-1, E, A, 0, Function, D);
+ true ->
+ A
+ end;
+foldl(_, _, _) ->
+ erlang:error(badarg).
+
+foldl_1(N, E=?NODEPATTERN(S), A, Ix, F, D) ->
+ foldl_2(1, E, A, Ix, F, D, N div S + 1, N rem S, S);
+foldl_1(N, E, A, Ix, F, D) when is_integer(E) ->
+ foldl_1(N, unfold(E, D), A, Ix, F, D);
+foldl_1(N, E, A, Ix, F, _D) ->
+ foldl_3(1, E, A, Ix, F, N+1).
+
+foldl_2(I, E, A, Ix, F, D, I, R, _S) ->
+ foldl_1(R, element(I, E), A, Ix, F, D);
+foldl_2(I, E, A, Ix, F, D, N, R, S) ->
+ foldl_2(I+1, E, foldl_1(S-1, element(I, E), A, Ix, F, D),
+ Ix + S, F, D, N, R, S).
+
+-spec foldl_3(pos_integer(), _, A, array_indx(),
+ fun((array_indx, _, A) -> B), integer()) -> B.
+
+foldl_3(I, E, A, Ix, F, N) when I =< N ->
+ foldl_3(I+1, E, F(Ix, element(I, E), A), Ix+1, F, N);
+foldl_3(_I, _E, A, _Ix, _F, _N) ->
+ A.
+
+
+-ifdef(EUNIT).
+foldl_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ Reverse = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, foldl([], 0, new())),
+ ?_assertError(badarg, foldl([], 0, new(10))),
+ ?_assert(foldl(Count, 0, new()) =:= 0),
+ ?_assert(foldl(Count, 0, new(1)) =:= 1),
+ ?_assert(foldl(Count, 0, new(10)) =:= 10),
+ ?_assert(foldl(Count, 0, from_list([1,2,3,4])) =:= 4),
+ ?_assert(foldl(Count, 10, from_list([0,1,2,3,4,5,6,7,8,9])) =:= 20),
+ ?_assert(foldl(Count, 1000, from_list(lists:seq(0,999))) =:= 2000),
+ ?_assert(foldl(Sum, 0, from_list(lists:seq(0,10))) =:= 55),
+ ?_assert(foldl(Reverse, [], from_list(lists:seq(0,1000)))
+ =:= lists:reverse(lists:seq(0,1000))),
+ ?_assert({999,[N0*100+1+2,N0*2+1+1,0]} =:=
+ foldl(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+
+ ].
+-endif.
+
+
+%% @spec (Function, InitialAcc::term(), array()) -> term()
+%% Function = (Index::integer(), Value::term(), Acc::term()) ->
+%% term()
+%% @doc Fold the elements of the array using the given function and
+%% initial accumulator value, skipping default-valued entries. The
+%% elements are visited in order from the lowest index to the highest.
+%% If `Function' is not a function, the call fails with reason `badarg'.
+%%
+%% @see foldl/3
+%% @see sparse_foldr/3
+
+-spec sparse_foldl(fun((array_indx(), _, A) -> B), A, array()) -> B.
+
+sparse_foldl(Function, A, #array{size = N, elements = E, default = D})
+ when is_function(Function, 3) ->
+ if N > 0 ->
+ sparse_foldl_1(N-1, E, A, 0, Function, D);
+ true ->
+ A
+ end;
+sparse_foldl(_, _, _) ->
+ erlang:error(badarg).
+
+%% see foldl/3 for details
+%% TODO: this can be optimized
+
+sparse_foldl_1(N, E=?NODEPATTERN(S), A, Ix, F, D) ->
+ sparse_foldl_2(1, E, A, Ix, F, D, N div S + 1, N rem S, S);
+sparse_foldl_1(_N, E, A, _Ix, _F, _D) when is_integer(E) ->
+ A;
+sparse_foldl_1(N, E, A, Ix, F, D) ->
+ sparse_foldl_3(1, E, A, Ix, F, D, N+1).
+
+sparse_foldl_2(I, E, A, Ix, F, D, I, R, _S) ->
+ sparse_foldl_1(R, element(I, E), A, Ix, F, D);
+sparse_foldl_2(I, E, A, Ix, F, D, N, R, S) ->
+ sparse_foldl_2(I+1, E, sparse_foldl_1(S-1, element(I, E), A, Ix, F, D),
+ Ix + S, F, D, N, R, S).
+
+sparse_foldl_3(I, T, A, Ix, F, D, N) when I =< N ->
+ case element(I, T) of
+ D -> sparse_foldl_3(I+1, T, A, Ix+1, F, D, N);
+ E -> sparse_foldl_3(I+1, T, F(Ix, E, A), Ix+1, F, D, N)
+ end;
+sparse_foldl_3(_I, _T, A, _Ix, _F, _D, _N) ->
+ A.
+
+
+-ifdef(EUNIT).
+sparse_foldl_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ Reverse = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, sparse_foldl([], 0, new())),
+ ?_assertError(badarg, sparse_foldl([], 0, new(10))),
+ ?_assert(sparse_foldl(Count, 0, new()) =:= 0),
+ ?_assert(sparse_foldl(Count, 0, new(1)) =:= 0),
+ ?_assert(sparse_foldl(Count, 0, new(10,{default,1})) =:= 0),
+ ?_assert(sparse_foldl(Count, 0, from_list([0,1,2,3,4],0)) =:= 4),
+ ?_assert(sparse_foldl(Count, 0, from_list([0,1,2,3,4,5,6,7,8,9,0],0))
+ =:= 9),
+ ?_assert(sparse_foldl(Count, 0, from_list(lists:seq(0,999),0))
+ =:= 999),
+ ?_assert(sparse_foldl(Sum, 0, from_list(lists:seq(0,10), 5)) =:= 50),
+ ?_assert(sparse_foldl(Reverse, [], from_list(lists:seq(0,1000), 0))
+ =:= lists:reverse(lists:seq(1,1000))),
+ ?_assert({0,[N0*100+1+2,N0*2+1+1,0]} =:=
+ sparse_foldl(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+ ].
+-endif.
+
+
+%% @spec (Function, InitialAcc::term(), array()) -> term()
+%% Function = (Index::integer(), Value::term(), Acc::term()) ->
+%% term()
+%% @doc Fold the elements of the array right-to-left using the given
+%% function and initial accumulator value. The elements are visited in
+%% order from the highest index to the lowest. If `Function' is not a
+%% function, the call fails with reason `badarg'.
+%%
+%% @see foldl/3
+%% @see map/2
+
+-spec foldr(fun((array_indx(), _, A) -> B), A, array()) -> B.
+
+foldr(Function, A, #array{size = N, elements = E, default = D})
+ when is_function(Function, 3) ->
+ if N > 0 ->
+ I = N - 1,
+ foldr_1(I, E, I, A, Function, D);
+ true ->
+ A
+ end;
+foldr(_, _, _) ->
+ erlang:error(badarg).
+
+%% this is based on to_orddict/1
+
+foldr_1(I, E=?NODEPATTERN(S), Ix, A, F, D) ->
+ foldr_2(I div S + 1, E, Ix, A, F, D, I rem S, S-1);
+foldr_1(I, E, Ix, A, F, D) when is_integer(E) ->
+ foldr_1(I, unfold(E, D), Ix, A, F, D);
+foldr_1(I, E, Ix, A, F, _D) ->
+ I1 = I+1,
+ foldr_3(I1, E, Ix-I1, A, F).
+
+foldr_2(0, _E, _Ix, A, _F, _D, _R, _R0) ->
+ A;
+foldr_2(I, E, Ix, A, F, D, R, R0) ->
+ foldr_2(I-1, E, Ix - R - 1,
+ foldr_1(R, element(I, E), Ix, A, F, D),
+ F, D, R0, R0).
+
+-spec foldr_3(array_indx(), term(), integer(), A,
+ fun((array_indx(), _, A) -> B)) -> B.
+
+foldr_3(0, _E, _Ix, A, _F) ->
+ A;
+foldr_3(I, E, Ix, A, F) ->
+ foldr_3(I-1, E, Ix, F(Ix+I, element(I, E), A), F).
+
+
+-ifdef(EUNIT).
+foldr_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ List = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, foldr([], 0, new())),
+ ?_assertError(badarg, foldr([], 0, new(10))),
+ ?_assert(foldr(Count, 0, new()) =:= 0),
+ ?_assert(foldr(Count, 0, new(1)) =:= 1),
+ ?_assert(foldr(Count, 0, new(10)) =:= 10),
+ ?_assert(foldr(Count, 0, from_list([1,2,3,4])) =:= 4),
+ ?_assert(foldr(Count, 10, from_list([0,1,2,3,4,5,6,7,8,9])) =:= 20),
+ ?_assert(foldr(Count, 1000, from_list(lists:seq(0,999))) =:= 2000),
+ ?_assert(foldr(Sum, 0, from_list(lists:seq(0,10))) =:= 55),
+ ?_assert(foldr(List, [], from_list(lists:seq(0,1000)))
+ =:= lists:seq(0,1000)),
+ ?_assert({999,[0,N0*2+1+1,N0*100+1+2]} =:=
+ foldr(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+
+ ].
+-endif.
+
+
+%% @spec (Function, InitialAcc::term(), array()) -> term()
+%% Function = (Index::integer(), Value::term(), Acc::term()) ->
+%% term()
+%% @doc Fold the elements of the array right-to-left using the given
+%% function and initial accumulator value, skipping default-valued
+%% entries. The elements are visited in order from the highest index to
+%% the lowest. If `Function' is not a function, the call fails with
+%% reason `badarg'.
+%%
+%% @see foldr/3
+%% @see sparse_foldl/3
+
+-spec sparse_foldr(fun((array_indx(), _, A) -> B), A, array()) -> B.
+
+sparse_foldr(Function, A, #array{size = N, elements = E, default = D})
+ when is_function(Function, 3) ->
+ if N > 0 ->
+ I = N - 1,
+ sparse_foldr_1(I, E, I, A, Function, D);
+ true ->
+ A
+ end;
+sparse_foldr(_, _, _) ->
+ erlang:error(badarg).
+
+%% see foldr/3 for details
+%% TODO: this can be optimized
+
+sparse_foldr_1(I, E=?NODEPATTERN(S), Ix, A, F, D) ->
+ sparse_foldr_2(I div S + 1, E, Ix, A, F, D, I rem S, S-1);
+sparse_foldr_1(_I, E, _Ix, A, _F, _D) when is_integer(E) ->
+ A;
+sparse_foldr_1(I, E, Ix, A, F, D) ->
+ I1 = I+1,
+ sparse_foldr_3(I1, E, Ix-I1, A, F, D).
+
+sparse_foldr_2(0, _E, _Ix, A, _F, _D, _R, _R0) ->
+ A;
+sparse_foldr_2(I, E, Ix, A, F, D, R, R0) ->
+ sparse_foldr_2(I-1, E, Ix - R - 1,
+ sparse_foldr_1(R, element(I, E), Ix, A, F, D),
+ F, D, R0, R0).
+
+-spec sparse_foldr_3(array_indx(), _, array_indx(), A,
+ fun((array_indx(), _, A) -> B), _) -> B.
+
+sparse_foldr_3(0, _T, _Ix, A, _F, _D) ->
+ A;
+sparse_foldr_3(I, T, Ix, A, F, D) ->
+ case element(I, T) of
+ D -> sparse_foldr_3(I-1, T, Ix, A, F, D);
+ E -> sparse_foldr_3(I-1, T, Ix, F(Ix+I, E, A), F, D)
+ end.
+
+
+%% @spec (array()) -> integer()
+%% @doc Get the number of entries in the array up until the last
+%% non-default valued entry. In other words, returns `I+1' if `I' is the
+%% last non-default valued entry in the array, or zero if no such entry
+%% exists.
+%% @see size/1
+%% @see resize/1
+
+-spec sparse_size(array()) -> non_neg_integer().
+
+sparse_size(A) ->
+ F = fun (I, _V, _A) -> throw({value, I}) end,
+ try sparse_foldr(F, [], A) of
+ [] -> 0
+ catch
+ {value, I} ->
+ I + 1
+ end.
+
+
+-ifdef(EUNIT).
+sparse_foldr_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ List = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, sparse_foldr([], 0, new())),
+ ?_assertError(badarg, sparse_foldr([], 0, new(10))),
+ ?_assert(sparse_foldr(Count, 0, new()) =:= 0),
+ ?_assert(sparse_foldr(Count, 0, new(1)) =:= 0),
+ ?_assert(sparse_foldr(Count, 0, new(10,{default,1})) =:= 0),
+ ?_assert(sparse_foldr(Count, 0, from_list([0,1,2,3,4],0)) =:= 4),
+ ?_assert(sparse_foldr(Count, 0, from_list([0,1,2,3,4,5,6,7,8,9,0],0))
+ =:= 9),
+ ?_assert(sparse_foldr(Count, 0, from_list(lists:seq(0,999),0))
+ =:= 999),
+ ?_assert(sparse_foldr(Sum, 0, from_list(lists:seq(0,10),5)) =:= 50),
+ ?_assert(sparse_foldr(List, [], from_list(lists:seq(0,1000),0))
+ =:= lists:seq(1,1000)),
+
+ ?_assert(sparse_size(new()) =:= 0),
+ ?_assert(sparse_size(new(8)) =:= 0),
+ ?_assert(sparse_size(array:set(7, 0, new())) =:= 8),
+ ?_assert(sparse_size(array:set(7, 0, new(10))) =:= 8),
+ ?_assert(sparse_size(array:set(99, 0, new(10,{fixed,false})))
+ =:= 100),
+ ?_assert(sparse_size(array:set(7, undefined, new())) =:= 0),
+ ?_assert(sparse_size(array:from_list([1,2,3,undefined])) =:= 3),
+ ?_assert(sparse_size(array:from_orddict([{3,0},{17,0},{99,undefined}]))
+ =:= 18),
+ ?_assert({0,[0,N0*2+1+1,N0*100+1+2]} =:=
+ sparse_foldr(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+ ].
+-endif.
diff --git a/lib/stdlib/src/base64.erl b/lib/stdlib/src/base64.erl
new file mode 100644
index 0000000000..ebef998ee1
--- /dev/null
+++ b/lib/stdlib/src/base64.erl
@@ -0,0 +1,304 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Description: Implements base 64 encode and decode. See RFC4648.
+
+-module(base64).
+
+-export([encode/1, decode/1, mime_decode/1,
+ encode_to_string/1, decode_to_string/1, mime_decode_to_string/1]).
+
+%%-------------------------------------------------------------------------
+%% The following type is a subtype of string() for return values
+%% of (some) functions of this module.
+%%-------------------------------------------------------------------------
+
+-type ascii_string() :: [1..255].
+
+%%-------------------------------------------------------------------------
+%% encode_to_string(ASCII) -> Base64String
+%% ASCII - string() | binary()
+%% Base64String - string()
+%%
+%% Description: Encodes a plain ASCII string (or binary) into base64.
+%%-------------------------------------------------------------------------
+
+-spec encode_to_string(string() | binary()) -> ascii_string().
+
+encode_to_string(Bin) when is_binary(Bin) ->
+ encode_to_string(binary_to_list(Bin));
+encode_to_string(List) when is_list(List) ->
+ encode_l(List).
+
+%%-------------------------------------------------------------------------
+%% encode(ASCII) -> Base64
+%% ASCII - string() | binary()
+%% Base64 - binary()
+%%
+%% Description: Encodes a plain ASCII string (or binary) into base64.
+%%-------------------------------------------------------------------------
+
+-spec encode(string() | binary()) -> binary().
+
+encode(Bin) when is_binary(Bin) ->
+ encode_binary(Bin);
+encode(List) when is_list(List) ->
+ list_to_binary(encode_l(List)).
+
+-spec encode_l(string()) -> ascii_string().
+
+encode_l([]) ->
+ [];
+encode_l([A]) ->
+ [b64e(A bsr 2),
+ b64e((A band 3) bsl 4), $=, $=];
+encode_l([A,B]) ->
+ [b64e(A bsr 2),
+ b64e(((A band 3) bsl 4) bor (B bsr 4)),
+ b64e((B band 15) bsl 2), $=];
+encode_l([A,B,C|Ls]) ->
+ BB = (A bsl 16) bor (B bsl 8) bor C,
+ [b64e(BB bsr 18),
+ b64e((BB bsr 12) band 63),
+ b64e((BB bsr 6) band 63),
+ b64e(BB band 63) | encode_l(Ls)].
+
+encode_binary(Bin) ->
+ Split = 3*(byte_size(Bin) div 3),
+ <<Main0:Split/binary,Rest/binary>> = Bin,
+ Main = << <<(b64e(C)):8>> || <<C:6>> <= Main0 >>,
+ case Rest of
+ <<A:6,B:6,C:4>> ->
+ <<Main/binary,(b64e(A)):8,(b64e(B)):8,(b64e(C bsl 2)):8,$=:8>>;
+ <<A:6,B:2>> ->
+ <<Main/binary,(b64e(A)):8,(b64e(B bsl 4)):8,$=:8,$=:8>>;
+ <<>> ->
+ Main
+ end.
+
+%%-------------------------------------------------------------------------
+%% mime_decode(Base64) -> ASCII
+%% decode(Base64) -> ASCII
+%% Base64 - string() | binary()
+%% ASCII - binary()
+%%
+%% Description: Decodes an base64 encoded string to plain ASCII.
+%% mime_decode strips away all characters not Base64 before converting,
+%% whereas decode crashes if an illegal character is found
+%%-------------------------------------------------------------------------
+
+-spec decode(string() | binary()) -> binary().
+
+decode(Bin) when is_binary(Bin) ->
+ decode_binary(<<>>, Bin);
+decode(List) when is_list(List) ->
+ list_to_binary(decode_l(List)).
+
+-spec mime_decode(string() | binary()) -> binary().
+
+mime_decode(Bin) when is_binary(Bin) ->
+ mime_decode_binary(<<>>, Bin);
+mime_decode(List) when is_list(List) ->
+ list_to_binary(mime_decode_l(List)).
+
+-spec decode_l(string()) -> string().
+
+decode_l(List) ->
+ L = strip_spaces(List, []),
+ decode(L, []).
+
+-spec mime_decode_l(string()) -> string().
+
+mime_decode_l(List) ->
+ L = strip_illegal(List, []),
+ decode(L, []).
+
+%%-------------------------------------------------------------------------
+%% mime_decode_to_string(Base64) -> ASCII
+%% decode_to_string(Base64) -> ASCII
+%% Base64 - string() | binary()
+%% ASCII - binary()
+%%
+%% Description: Decodes an base64 encoded string to plain ASCII.
+%% mime_decode strips away all characters not Base64 before converting,
+%% whereas decode crashes if an illegal character is found
+%%-------------------------------------------------------------------------
+
+-spec decode_to_string(string() | binary()) -> string().
+
+decode_to_string(Bin) when is_binary(Bin) ->
+ decode_to_string(binary_to_list(Bin));
+decode_to_string(List) when is_list(List) ->
+ decode_l(List).
+
+-spec mime_decode_to_string(string() | binary()) -> string().
+
+mime_decode_to_string(Bin) when is_binary(Bin) ->
+ mime_decode_to_string(binary_to_list(Bin));
+mime_decode_to_string(List) when is_list(List) ->
+ mime_decode_l(List).
+
+%% One-based decode map.
+-define(DECODE_MAP,
+ {bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %1-15
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31
+ ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad,bad,63, %32-47
+ 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-63
+ bad,0,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,bad,bad,bad,bad,bad,
+ bad,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,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad}).
+
+decode_binary(Result0, <<C:8,T0/bits>>) ->
+ case element(C, ?DECODE_MAP) of
+ bad ->
+ erlang:error({badarg,C});
+ ws ->
+ decode_binary(Result0, T0);
+ eq ->
+ case strip_ws(T0) of
+ <<$=:8,T/binary>> ->
+ <<>> = strip_ws(T),
+ Split = byte_size(Result0) - 1,
+ <<Result:Split/bytes,_:4>> = Result0,
+ Result;
+ T ->
+ <<>> = strip_ws(T),
+ Split = byte_size(Result0) - 1,
+ <<Result:Split/bytes,_:2>> = Result0,
+ Result
+ end;
+ Bits ->
+ decode_binary(<<Result0/bits,Bits:6>>, T0)
+ end;
+decode_binary(Result, <<>>) ->
+ true = is_binary(Result),
+ Result.
+
+mime_decode_binary(Result, <<0:8,T/bits>>) ->
+ mime_decode_binary(Result, T);
+mime_decode_binary(Result0, <<C:8,T/bits>>) ->
+ case element(C, ?DECODE_MAP) of
+ Bits when is_integer(Bits) ->
+ mime_decode_binary(<<Result0/bits,Bits:6>>, T);
+ eq ->
+ case tail_contains_equal(T) of
+ true ->
+ Split = byte_size(Result0) - 1,
+ <<Result:Split/bytes,_:4>> = Result0,
+ Result;
+ false ->
+ Split = byte_size(Result0) - 1,
+ <<Result:Split/bytes,_:2>> = Result0,
+ Result
+ end;
+ _ ->
+ mime_decode_binary(Result0, T)
+ end;
+mime_decode_binary(Result, <<>>) ->
+ true = is_binary(Result),
+ Result.
+
+decode([], A) -> A;
+decode([$=,$=,C2,C1|Cs], A) ->
+ Bits2x6 = (b64d(C1) bsl 18) bor (b64d(C2) bsl 12),
+ Octet1 = Bits2x6 bsr 16,
+ decode(Cs, [Octet1|A]);
+decode([$=,C3,C2,C1|Cs], A) ->
+ Bits3x6 = (b64d(C1) bsl 18) bor (b64d(C2) bsl 12)
+ bor (b64d(C3) bsl 6),
+ Octet1 = Bits3x6 bsr 16,
+ Octet2 = (Bits3x6 bsr 8) band 16#ff,
+ decode(Cs, [Octet1,Octet2|A]);
+decode([C4,C3,C2,C1| Cs], A) ->
+ Bits4x6 = (b64d(C1) bsl 18) bor (b64d(C2) bsl 12)
+ bor (b64d(C3) bsl 6) bor b64d(C4),
+ Octet1 = Bits4x6 bsr 16,
+ Octet2 = (Bits4x6 bsr 8) band 16#ff,
+ Octet3 = Bits4x6 band 16#ff,
+ decode(Cs, [Octet1,Octet2,Octet3|A]).
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+
+strip_spaces([], A) -> A;
+strip_spaces([$\s|Cs], A) -> strip_spaces(Cs, A);
+strip_spaces([$\t|Cs], A) -> strip_spaces(Cs, A);
+strip_spaces([$\r|Cs], A) -> strip_spaces(Cs, A);
+strip_spaces([$\n|Cs], A) -> strip_spaces(Cs, A);
+strip_spaces([C|Cs], A) -> strip_spaces(Cs, [C | A]).
+
+strip_ws(<<$\t,T/binary>>) ->
+ strip_ws(T);
+strip_ws(<<$\n,T/binary>>) ->
+ strip_ws(T);
+strip_ws(<<$\r,T/binary>>) ->
+ strip_ws(T);
+strip_ws(<<$\s,T/binary>>) ->
+ strip_ws(T);
+strip_ws(T) -> T.
+
+strip_illegal([0|Cs], A) ->
+ strip_illegal(Cs, A);
+strip_illegal([C|Cs], A) ->
+ case element(C, ?DECODE_MAP) of
+ bad -> strip_illegal(Cs, A);
+ ws -> strip_illegal(Cs, A);
+ eq -> strip_illegal_end(Cs, [$=|A]);
+ _ -> strip_illegal(Cs, [C|A])
+ end;
+strip_illegal([], A) -> A.
+
+strip_illegal_end([0|Cs], A) ->
+ strip_illegal_end(Cs, A);
+strip_illegal_end([C|Cs], A) ->
+ case element(C, ?DECODE_MAP) of
+ bad -> strip_illegal(Cs, A);
+ ws -> strip_illegal(Cs, A);
+ eq -> [C|A];
+ _ -> strip_illegal(Cs, [C|A])
+ end;
+strip_illegal_end([], A) -> A.
+
+tail_contains_equal(<<$=,_/binary>>) -> true;
+tail_contains_equal(<<_,T/binary>>) -> tail_contains_equal(T);
+tail_contains_equal(<<>>) -> false.
+
+%% accessors
+b64e(X) ->
+ element(X+1,
+ {$A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N,
+ $O, $P, $Q, $R, $S, $T, $U, $V, $W, $X, $Y, $Z,
+ $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n,
+ $o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z,
+ $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $+, $/}).
+
+
+b64d(X) ->
+ b64d_ok(element(X, ?DECODE_MAP)).
+
+b64d_ok(I) when is_integer(I) -> I.
diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl
new file mode 100644
index 0000000000..820afd3739
--- /dev/null
+++ b/lib/stdlib/src/beam_lib.erl
@@ -0,0 +1,1027 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(beam_lib).
+-behaviour(gen_server).
+
+-export([info/1,
+ cmp/2,
+ cmp_dirs/2,
+ chunks/2,
+ chunks/3,
+ all_chunks/1,
+ diff_dirs/2,
+ strip/1,
+ strip_files/1,
+ strip_release/1,
+ build_module/1,
+ version/1,
+ md5/1,
+ format_error/1]).
+
+%% The following functions implement encrypted debug info.
+
+-export([crypto_key_fun/1, clear_crypto_key_fun/0]).
+-export([init/1,handle_call/3,handle_cast/2,handle_info/2,
+ terminate/2,code_change/3]).
+-export([make_crypto_key/2, get_crypto_key/1]). %Utilities used by compiler
+
+-import(lists, [append/1, delete/2, foreach/2, keysort/2,
+ member/2, reverse/1, sort/1, splitwith/2]).
+
+-include_lib("kernel/include/file.hrl").
+-include("erl_compile.hrl").
+
+%%-------------------------------------------------------------------------
+
+-type beam() :: module() | file:filename() | binary().
+
+%% XXX: THE FOLLOWING SHOULD BE IMPORTED FROM SOMEWHERE ELSE
+-type forms() :: term().
+
+-type abst_vsn() :: atom().
+-type abst_code() :: {abst_vsn(), forms()} | 'no_abstract_code'.
+-type attribute() :: atom().
+-type attrvalue() :: term().
+-type dataB() :: binary().
+-type index() :: non_neg_integer().
+-type label() :: integer().
+
+-type chunkid() :: nonempty_string(). % approximation of the strings below
+%% "Abst" | "Attr" | "CInf" | "ExpT" | "ImpT" | "LocT" | "Atom".
+-type chunkname() :: 'abstract_code' | 'attributes' | 'compile_info'
+ | 'exports' | 'labeled_exports'
+ | 'imports' | 'indexed_imports'
+ | 'locals' | 'labeled_locals'
+ | 'atoms'.
+-type chunkref() :: chunkname() | chunkid().
+
+-type attrib_entry() :: {attribute(), [attrvalue()]}.
+-type compinfo_entry() :: {atom(), term()}.
+-type labeled_entry() :: {atom(), arity(), label()}.
+
+-type chunkdata() :: {chunkid(), dataB()}
+ | {'abstract_code', abst_code()}
+ | {'attributes', [attrib_entry()]}
+ | {'compile_info', [compinfo_entry()]}
+ | {'exports', [{atom(), arity()}]}
+ | {'labeled_exports', [labeled_entry()]}
+ | {'imports', [mfa()]}
+ | {'indexed_imports', [{index(), module(), atom(), arity()}]}
+ | {'locals', [{atom(), arity()}]}
+ | {'labeled_locals', [labeled_entry()]}
+ | {'atoms', [{integer(), atom()}]}.
+
+-type info_pair() :: {'file', file:filename()}
+ | {'binary', binary()}
+ | {'module', module()}
+ | {'chunks', [{chunkid(), integer(), integer()}]}.
+
+%% Error reasons
+-type info_rsn() :: {'chunk_too_big', file:filename(),
+ chunkid(), integer(), integer()}
+ | {'invalid_beam_file', file:filename(), integer()}
+ | {'invalid_chunk', file:filename(), chunkid()}
+ | {'missing_chunk', file:filename(), chunkid()}
+ | {'not_a_beam_file', file:filename()}
+ | {'file_error', file:filename(), file:posix()}.
+-type chnk_rsn() :: {'unknown_chunk', file:filename(), atom()}
+ | {'key_missing_or_invalid', file:filename(),
+ 'abstract_code'}
+ | info_rsn().
+-type cmp_rsn() :: {'modules_different', module(), module()}
+ | {'chunks_different', chunkid()}
+ | info_rsn().
+
+%%-------------------------------------------------------------------------
+
+%%
+%% Exported functions
+%%
+
+-spec info(beam()) -> [info_pair()] | {'error', 'beam_lib', info_rsn()}.
+
+info(File) ->
+ read_info(beam_filename(File)).
+
+-spec chunks(beam(), [chunkref()]) ->
+ {'ok', {module(), [chunkdata()]}} | {'error', 'beam_lib', chnk_rsn()}.
+
+chunks(File, Chunks) ->
+ read_chunk_data(File, Chunks).
+
+-spec chunks(beam(), [chunkref()], ['allow_missing_chunks']) ->
+ {'ok', {module(), [{chunkref(), chunkdata() | 'missing_chunk'}]}}
+ | {'error', 'beam_lib', chnk_rsn()}.
+
+chunks(File, Chunks, Options) ->
+ try read_chunk_data(File, Chunks, Options)
+ catch Error -> Error end.
+
+-spec all_chunks(beam()) -> {'ok', 'beam_lib', [{chunkid(), dataB()}]}.
+
+all_chunks(File) ->
+ read_all_chunks(File).
+
+-spec cmp(beam(), beam()) -> 'ok' | {'error', 'beam_lib', cmp_rsn()}.
+
+cmp(File1, File2) ->
+ try cmp_files(File1, File2)
+ catch Error -> Error end.
+
+-spec cmp_dirs(atom() | file:filename(), atom() | file:filename()) ->
+ {[file:filename()], [file:filename()],
+ [{file:filename(), file:filename()}]}
+ | {'error', 'beam_lib', {'not_a_directory', term()} | info_rsn()}.
+
+cmp_dirs(Dir1, Dir2) ->
+ catch compare_dirs(Dir1, Dir2).
+
+-spec diff_dirs(atom() | file:filename(), atom() | file:filename()) ->
+ 'ok' | {'error', 'beam_lib', {'not_a_directory', term()} | info_rsn()}.
+
+diff_dirs(Dir1, Dir2) ->
+ catch diff_directories(Dir1, Dir2).
+
+-spec strip(beam()) ->
+ {'ok', {module(), beam()}} | {'error', 'beam_lib', info_rsn()}.
+
+strip(FileName) ->
+ try strip_file(FileName)
+ catch Error -> Error end.
+
+-spec strip_files([beam()]) ->
+ {'ok', [{module(), beam()}]} | {'error', 'beam_lib', info_rsn()}.
+
+strip_files(Files) when is_list(Files) ->
+ try strip_fils(Files)
+ catch Error -> Error end.
+
+-spec strip_release(atom() | file:filename()) ->
+ {'ok', [{module(), file:filename()}]}
+ | {'error', 'beam_lib', {'not_a_directory', term()} | info_rsn()}.
+
+strip_release(Root) ->
+ catch strip_rel(Root).
+
+-spec version(beam()) ->
+ {'ok', {module(), [term()]}} | {'error', 'beam_lib', chnk_rsn()}.
+
+version(File) ->
+ case catch read_chunk_data(File, [attributes]) of
+ {ok, {Module, [{attributes, Attrs}]}} ->
+ {vsn, Version} = lists:keyfind(vsn, 1, Attrs),
+ {ok, {Module, Version}};
+ Error ->
+ Error
+ end.
+
+-spec md5(beam()) ->
+ {'ok', {module(), binary()}} | {'error', 'beam_lib', chnk_rsn()}.
+
+md5(File) ->
+ case catch read_significant_chunks(File) of
+ {ok, {Module, Chunks0}} ->
+ Chunks = filter_funtab(Chunks0),
+ {ok, {Module, erlang:md5([C || {_Id, C} <- Chunks])}};
+ Error ->
+ Error
+ end.
+
+-spec format_error(term()) -> [char() | string()].
+
+format_error({error, Error}) ->
+ format_error(Error);
+format_error({error, Module, Error}) ->
+ Module:format_error(Error);
+format_error({unknown_chunk, File, ChunkName}) ->
+ io_lib:format("~p: Cannot find chunk ~p~n", [File, ChunkName]);
+format_error({invalid_chunk, File, ChunkId}) ->
+ io_lib:format("~p: Invalid contents of chunk ~p~n", [File, ChunkId]);
+format_error({not_a_beam_file, File}) ->
+ io_lib:format("~p: Not a BEAM file~n", [File]);
+format_error({file_error, File, Reason}) ->
+ io_lib:format("~p: ~p~n", [File, file:format_error(Reason)]);
+format_error({missing_chunk, File, ChunkId}) ->
+ io_lib:format("~p: Not a BEAM file: no IFF \"~s\" chunk~n",
+ [File, ChunkId]);
+format_error({invalid_beam_file, File, Pos}) ->
+ io_lib:format("~p: Invalid format of BEAM file near byte number ~p~n",
+ [File, Pos]);
+format_error({chunk_too_big, File, ChunkId, Size, Len}) ->
+ io_lib:format("~p: Size of chunk \"~s\" is ~p bytes, "
+ "but only ~p bytes could be read~n",
+ [File, ChunkId, Size, Len]);
+format_error({chunks_different, Id}) ->
+ io_lib:format("Chunk \"~s\" differs in the two files~n", [Id]);
+format_error(different_chunks) ->
+ "The two files have different chunks\n";
+format_error({modules_different, Module1, Module2}) ->
+ io_lib:format("Module names ~p and ~p differ in the two files~n",
+ [Module1, Module2]);
+format_error({not_a_directory, Name}) ->
+ io_lib:format("~p: Not a directory~n", [Name]);
+format_error({key_missing_or_invalid, File, abstract_code}) ->
+ io_lib:format("~p: Cannot decrypt abstract code because key is missing or invalid",
+ [File]);
+format_error(badfun) ->
+ "not a fun or the fun has the wrong arity";
+format_error(exists) ->
+ "a fun has already been installed";
+format_error(E) ->
+ io_lib:format("~p~n", [E]).
+
+%%
+%% Exported functions for encrypted debug info.
+%%
+
+-type mode() :: 'des3_cbc'.
+-type crypto_fun_arg() :: 'init'
+ | 'clear'
+ | {'debug_info', mode(), module(), file:filename()}.
+-type crypto_fun() :: fun((crypto_fun_arg()) -> term()).
+
+-spec crypto_key_fun(crypto_fun()) -> 'ok' | {'error', term()}.
+
+crypto_key_fun(F) ->
+ call_crypto_server({crypto_key_fun, F}).
+
+-spec clear_crypto_key_fun() -> 'undefined' | {'ok', term()}.
+
+clear_crypto_key_fun() ->
+ call_crypto_server(clear_crypto_key_fun).
+
+-spec make_crypto_key(mode(), string()) ->
+ {binary(), binary(), binary(), binary()}.
+
+make_crypto_key(des3_cbc, String) ->
+ <<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
+ <<K3:8/binary,IVec:8/binary>> = erlang:md5([First|reverse(String)]),
+ {K1,K2,K3,IVec}.
+
+%%
+%% Local functions
+%%
+
+read_info(File) ->
+ try
+ {ok, Module, Data} = scan_beam(File, info),
+ [if
+ is_binary(File) -> {binary, File};
+ true -> {file, File}
+ end, {module, Module}, {chunks, Data}]
+ catch Error -> Error end.
+
+diff_directories(Dir1, Dir2) ->
+ {OnlyDir1, OnlyDir2, Diff} = compare_dirs(Dir1, Dir2),
+ diff_only(Dir1, OnlyDir1),
+ diff_only(Dir2, OnlyDir2),
+ foreach(fun(D) -> io:format("** different: ~p~n", [D]) end, Diff),
+ ok.
+
+diff_only(_Dir, []) ->
+ ok;
+diff_only(Dir, Only) ->
+ io:format("Only in ~p: ~p~n", [Dir, Only]).
+
+%% -> {OnlyInDir1, OnlyInDir2, Different} | throw(Error)
+compare_dirs(Dir1, Dir2) ->
+ R1 = sofs:relation(beam_files(Dir1)),
+ R2 = sofs:relation(beam_files(Dir2)),
+ F1 = sofs:domain(R1),
+ F2 = sofs:domain(R2),
+ {O1, Both, O2} = sofs:symmetric_partition(F1, F2),
+ OnlyL1 = sofs:image(R1, O1),
+ OnlyL2 = sofs:image(R2, O2),
+ B1 = sofs:to_external(sofs:restriction(R1, Both)),
+ B2 = sofs:to_external(sofs:restriction(R2, Both)),
+ Diff = compare_files(B1, B2, []),
+ {sofs:to_external(OnlyL1), sofs:to_external(OnlyL2), Diff}.
+
+compare_files([], [], Acc) ->
+ lists:reverse(Acc);
+compare_files([{_,F1} | R1], [{_,F2} | R2], Acc) ->
+ NAcc = case catch cmp_files(F1, F2) of
+ {error, _Mod, _Reason} ->
+ [{F1, F2} | Acc];
+ ok ->
+ Acc
+ end,
+ compare_files(R1, R2, NAcc).
+
+beam_files(Dir) ->
+ ok = assert_directory(Dir),
+ L = filelib:wildcard(filename:join(Dir, "*.beam")),
+ [{filename:basename(Path), Path} || Path <- L].
+
+%% -> ok | throw(Error)
+cmp_files(File1, File2) ->
+ {ok, {M1, L1}} = read_significant_chunks(File1),
+ {ok, {M2, L2}} = read_significant_chunks(File2),
+ if
+ M1 =:= M2 ->
+ List1 = filter_funtab(L1),
+ List2 = filter_funtab(L2),
+ cmp_lists(List1, List2);
+ true ->
+ error({modules_different, M1, M2})
+ end.
+
+cmp_lists([], []) ->
+ ok;
+cmp_lists([{Id, C1} | R1], [{Id, C2} | R2]) ->
+ if
+ C1 =:= C2 ->
+ cmp_lists(R1, R2);
+ true ->
+ error({chunks_different, Id})
+ end;
+cmp_lists(_, _) ->
+ error(different_chunks).
+
+strip_rel(Root) ->
+ ok = assert_directory(Root),
+ strip_fils(filelib:wildcard(filename:join(Root, "lib/*/ebin/*.beam"))).
+
+%% -> {ok, [{Mod, BinaryOrFileName}]} | throw(Error)
+strip_fils(Files) ->
+ {ok, [begin {ok, Reply} = strip_file(F), Reply end || F <- Files]}.
+
+%% -> {ok, {Mod, FileName}} | {ok, {Mod, binary()}} | throw(Error)
+strip_file(File) ->
+ {ok, {Mod, Chunks}} = read_significant_chunks(File),
+ {ok, Stripped0} = build_module(Chunks),
+ Stripped = compress(Stripped0),
+ case File of
+ _ when is_binary(File) ->
+ {ok, {Mod, Stripped}};
+ _ ->
+ FileName = beam_filename(File),
+ case file:open(FileName, [raw, binary, write]) of
+ {ok, Fd} ->
+ case file:write(Fd, Stripped) of
+ ok ->
+ ok = file:close(Fd),
+ {ok, {Mod, FileName}};
+ Error ->
+ ok = file:close(Fd),
+ file_error(FileName, Error)
+ end;
+ Error ->
+ file_error(FileName, Error)
+ end
+ end.
+
+build_module(Chunks0) ->
+ Chunks = list_to_binary(build_chunks(Chunks0)),
+ Size = byte_size(Chunks),
+ 0 = Size rem 4, % Assertion: correct padding?
+ {ok, <<"FOR1", (Size+4):32, "BEAM", Chunks/binary>>}.
+
+build_chunks([{Id, Data} | Chunks]) ->
+ BId = list_to_binary(Id),
+ Size = byte_size(Data),
+ Chunk = [<<BId/binary, Size:32>>, Data | pad(Size)],
+ [Chunk | build_chunks(Chunks)];
+build_chunks([]) ->
+ [].
+
+pad(Size) ->
+ case Size rem 4 of
+ 0 -> [];
+ Rem -> lists:duplicate(4 - Rem, 0)
+ end.
+
+%% -> {ok, {Module, Chunks}} | throw(Error)
+read_significant_chunks(File) ->
+ case read_chunk_data(File, significant_chunks(), [allow_missing_chunks]) of
+ {ok, {Module, Chunks0}} ->
+ Mandatory = mandatory_chunks(),
+ Chunks = filter_significant_chunks(Chunks0, Mandatory, File, Module),
+ {ok, {Module, Chunks}}
+ end.
+
+filter_significant_chunks([{_, Data}=Pair|Cs], Mandatory, File, Mod)
+ when is_binary(Data) ->
+ [Pair|filter_significant_chunks(Cs, Mandatory, File, Mod)];
+filter_significant_chunks([{Id, missing_chunk}|Cs], Mandatory, File, Mod) ->
+ case member(Id, Mandatory) of
+ false ->
+ filter_significant_chunks(Cs, Mandatory, File, Mod);
+ true ->
+ error({missing_chunk, File, Id})
+ end;
+filter_significant_chunks([], _, _, _) -> [].
+
+filter_funtab([{"FunT"=Tag, <<L:4/binary, Data0/binary>>}|Cs]) ->
+ Data = filter_funtab_1(Data0, <<0:32>>),
+ Funtab = <<L/binary, (iolist_to_binary(Data))/binary>>,
+ [{Tag, Funtab}|filter_funtab(Cs)];
+filter_funtab([H|T]) ->
+ [H|filter_funtab(T)];
+filter_funtab([]) -> [].
+
+filter_funtab_1(<<Important:20/binary,_OldUniq:4/binary,T/binary>>, Zero) ->
+ [Important,Zero|filter_funtab_1(T, Zero)];
+filter_funtab_1(Tail, _) when is_binary(Tail) -> [Tail].
+
+read_all_chunks(File0) when is_atom(File0);
+ is_list(File0);
+ is_binary(File0) ->
+ try
+ File = beam_filename(File0),
+ {ok, Module, ChunkIds0} = scan_beam(File, info),
+ ChunkIds = [Name || {Name,_,_} <- ChunkIds0],
+ {ok, Module, Chunks} = scan_beam(File, ChunkIds),
+ {ok, Module, lists:reverse(Chunks)}
+ catch Error -> Error end.
+
+read_chunk_data(File0, ChunkNames) ->
+ try read_chunk_data(File0, ChunkNames, [])
+ catch Error -> Error end.
+
+%% -> {ok, {Module, Symbols}} | throw(Error)
+read_chunk_data(File0, ChunkNames0, Options)
+ when is_atom(File0); is_list(File0); is_binary(File0) ->
+ File = beam_filename(File0),
+ {ChunkIds, Names} = check_chunks(ChunkNames0, File, [], []),
+ AllowMissingChunks = member(allow_missing_chunks, Options),
+ {ok, Module, Chunks} = scan_beam(File, ChunkIds, AllowMissingChunks),
+ AT = ets:new(beam_symbols, []),
+ T = {empty, AT},
+ try chunks_to_data(Names, Chunks, File, Chunks, Module, T, [])
+ after ets:delete(AT)
+ end.
+
+%% -> {ok, list()} | throw(Error)
+check_chunks([ChunkName | Ids], File, IL, L) when is_atom(ChunkName) ->
+ ChunkId = chunk_name_to_id(ChunkName, File),
+ check_chunks(Ids, File, [ChunkId | IL], [{ChunkId, ChunkName} | L]);
+check_chunks([ChunkId | Ids], File, IL, L) -> % when is_list(ChunkId)
+ check_chunks(Ids, File, [ChunkId | IL], [{ChunkId, ChunkId} | L]);
+check_chunks([], _File, IL, L) ->
+ {lists:usort(IL), reverse(L)}.
+
+%% -> {ok, Module, Data} | throw(Error)
+scan_beam(File, What) ->
+ scan_beam(File, What, false).
+
+%% -> {ok, Module, Data} | throw(Error)
+scan_beam(File, What0, AllowMissingChunks) ->
+ case scan_beam1(File, What0) of
+ {missing, _FD, Mod, Data, What} when AllowMissingChunks ->
+ {ok, Mod, [{Id, missing_chunk} || Id <- What] ++ Data};
+ {missing, FD, _Mod, _Data, What} ->
+ error({missing_chunk, filename(FD), hd(What)});
+ R ->
+ R
+ end.
+
+%% -> {ok, Module, Data} | throw(Error)
+scan_beam1(File, What) ->
+ FD = open_file(File),
+ case catch scan_beam2(FD, What) of
+ Error when error =:= element(1, Error) ->
+ throw(Error);
+ R ->
+ R
+ end.
+
+scan_beam2(FD, What) ->
+ case pread(FD, 0, 12) of
+ {NFD, {ok, <<"FOR1", _Size:32, "BEAM">>}} ->
+ Start = 12,
+ scan_beam(NFD, Start, What, 17, []);
+ _Error ->
+ error({not_a_beam_file, filename(FD)})
+ end.
+
+scan_beam(_FD, _Pos, [], Mod, Data) when Mod =/= 17 ->
+ {ok, Mod, Data};
+scan_beam(FD, Pos, What, Mod, Data) ->
+ case pread(FD, Pos, 8) of
+ {_NFD, eof} when Mod =:= 17 ->
+ error({missing_chunk, filename(FD), "Atom"});
+ {_NFD, eof} when What =:= info ->
+ {ok, Mod, reverse(Data)};
+ {NFD, eof} ->
+ {missing, NFD, Mod, Data, What};
+ {NFD, {ok, <<IdL:4/binary, Sz:32>>}} ->
+ Id = binary_to_list(IdL),
+ Pos1 = Pos + 8,
+ Pos2 = (4 * trunc((Sz+3) / 4)) + Pos1,
+ get_data(What, Id, NFD, Sz, Pos1, Pos2, Mod, Data);
+ {_NFD, {ok, _ChunkHead}} ->
+ error({invalid_beam_file, filename(FD), Pos})
+ end.
+
+get_data(Cs, "Atom"=Id, FD, Size, Pos, Pos2, _Mod, Data) ->
+ NewCs = del_chunk(Id, Cs),
+ {NFD, Chunk} = get_chunk(Id, Pos, Size, FD),
+ <<_Num:32, Chunk2/binary>> = Chunk,
+ {Module, _} = extract_atom(Chunk2),
+ C = case Cs of
+ info ->
+ {Id, Pos, Size};
+ _ ->
+ {Id, Chunk}
+ end,
+ scan_beam(NFD, Pos2, NewCs, Module, [C | Data]);
+get_data(info, Id, FD, Size, Pos, Pos2, Mod, Data) ->
+ scan_beam(FD, Pos2, info, Mod, [{Id, Pos, Size} | Data]);
+get_data(Chunks, Id, FD, Size, Pos, Pos2, Mod, Data) ->
+ {NFD, NewData} = case member(Id, Chunks) of
+ true ->
+ {FD1, Chunk} = get_chunk(Id, Pos, Size, FD),
+ {FD1, [{Id, Chunk} | Data]};
+ false ->
+ {FD, Data}
+ end,
+ NewChunks = del_chunk(Id, Chunks),
+ scan_beam(NFD, Pos2, NewChunks, Mod, NewData).
+
+del_chunk(_Id, info) ->
+ info;
+del_chunk(Id, Chunks) ->
+ delete(Id, Chunks).
+
+%% -> {NFD, binary()} | throw(Error)
+get_chunk(Id, Pos, Size, FD) ->
+ case pread(FD, Pos, Size) of
+ {NFD, eof} when Size =:= 0 -> % cannot happen
+ {NFD, <<>>};
+ {_NFD, eof} when Size > 0 ->
+ error({chunk_too_big, filename(FD), Id, Size, 0});
+ {_NFD, {ok, Chunk}} when Size > byte_size(Chunk) ->
+ error({chunk_too_big, filename(FD), Id, Size, byte_size(Chunk)});
+ {NFD, {ok, Chunk}} -> % when Size =:= size(Chunk)
+ {NFD, Chunk}
+ end.
+
+chunks_to_data([{Id, Name} | CNs], Chunks, File, Cs, Module, Atoms, L) ->
+ {_Id, Chunk} = lists:keyfind(Id, 1, Chunks),
+ {NewAtoms, Ret} = chunk_to_data(Name, Chunk, File, Cs, Atoms, Module),
+ chunks_to_data(CNs, Chunks, File, Cs, Module, NewAtoms, [Ret | L]);
+chunks_to_data([], _Chunks, _File, _Cs, Module, _Atoms, L) ->
+ {ok, {Module, reverse(L)}}.
+
+chunk_to_data(attributes=Id, Chunk, File, _Cs, AtomTable, _Mod) ->
+ try
+ Term = binary_to_term(Chunk),
+ {AtomTable, {Id, attributes(Term)}}
+ catch
+ error:badarg ->
+ error({invalid_chunk, File, chunk_name_to_id(Id, File)})
+ end;
+chunk_to_data(compile_info=Id, Chunk, File, _Cs, AtomTable, _Mod) ->
+ try
+ {AtomTable, {Id, binary_to_term(Chunk)}}
+ catch
+ error:badarg ->
+ error({invalid_chunk, File, chunk_name_to_id(Id, File)})
+ end;
+chunk_to_data(abstract_code=Id, Chunk, File, _Cs, AtomTable, Mod) ->
+ case Chunk of
+ <<>> ->
+ {AtomTable, {Id, no_abstract_code}};
+ <<0:8,N:8,Mode0:N/binary,Rest/binary>> ->
+ Mode = list_to_atom(binary_to_list(Mode0)),
+ decrypt_abst(Mode, Mod, File, Id, AtomTable, Rest);
+ _ ->
+ case catch binary_to_term(Chunk) of
+ {'EXIT', _} ->
+ error({invalid_chunk, File, chunk_name_to_id(Id, File)});
+ Term ->
+ {AtomTable, {Id, Term}}
+ end
+ end;
+chunk_to_data(atoms=Id, _Chunk, _File, Cs, AtomTable0, _Mod) ->
+ AtomTable = ensure_atoms(AtomTable0, Cs),
+ Atoms = ets:tab2list(AtomTable),
+ {AtomTable, {Id, lists:sort(Atoms)}};
+chunk_to_data(ChunkName, Chunk, File,
+ Cs, AtomTable, _Mod) when is_atom(ChunkName) ->
+ case catch symbols(Chunk, AtomTable, Cs, ChunkName) of
+ {ok, NewAtomTable, S} ->
+ {NewAtomTable, {ChunkName, S}};
+ {'EXIT', _} ->
+ error({invalid_chunk, File, chunk_name_to_id(ChunkName, File)})
+ end;
+chunk_to_data(ChunkId, Chunk, _File,
+ _Cs, AtomTable, _Module) when is_list(ChunkId) ->
+ {AtomTable, {ChunkId, Chunk}}. % Chunk is a binary
+
+chunk_name_to_id(atoms, _) -> "Atom";
+chunk_name_to_id(indexed_imports, _) -> "ImpT";
+chunk_name_to_id(imports, _) -> "ImpT";
+chunk_name_to_id(exports, _) -> "ExpT";
+chunk_name_to_id(labeled_exports, _) -> "ExpT";
+chunk_name_to_id(locals, _) -> "LocT";
+chunk_name_to_id(labeled_locals, _) -> "LocT";
+chunk_name_to_id(attributes, _) -> "Attr";
+chunk_name_to_id(abstract_code, _) -> "Abst";
+chunk_name_to_id(compile_info, _) -> "CInf";
+chunk_name_to_id(Other, File) ->
+ error({unknown_chunk, File, Other}).
+
+%% Extract attributes
+
+attributes(Attrs) ->
+ attributes(keysort(1, Attrs), []).
+
+attributes([], R) ->
+ reverse(R);
+attributes(L, R) ->
+ K = element(1, hd(L)),
+ {L1, L2} = splitwith(fun(T) -> element(1, T) =:= K end, L),
+ V = append([A || {_, A} <- L1]),
+ attributes(L2, [{K, V} | R]).
+
+%% Extract symbols
+
+symbols(<<_Num:32, B/binary>>, AT0, Cs, Name) ->
+ AT = ensure_atoms(AT0, Cs),
+ symbols1(B, AT, Name, [], 1).
+
+symbols1(<<I1:32, I2:32, I3:32, B/binary>>, AT, Name, S, Cnt) ->
+ Symbol = symbol(Name, AT, I1, I2, I3, Cnt),
+ symbols1(B, AT, Name, [Symbol|S], Cnt+1);
+symbols1(<<>>, AT, _Name, S, _Cnt) ->
+ {ok, AT, sort(S)}.
+
+symbol(indexed_imports, AT, I1, I2, I3, Cnt) ->
+ {Cnt, atm(AT, I1), atm(AT, I2), I3};
+symbol(imports, AT, I1, I2, I3, _Cnt) ->
+ {atm(AT, I1), atm(AT, I2), I3};
+symbol(labeled_exports, AT, I1, I2, I3, _Cnt) ->
+ {atm(AT, I1), I2, I3};
+symbol(labeled_locals, AT, I1, I2, I3, _Cnt) ->
+ {atm(AT, I1), I2, I3};
+symbol(_, AT, I1, I2, _I3, _Cnt) ->
+ {atm(AT, I1), I2}.
+
+atm(AT, N) ->
+ [{_N, S}] = ets:lookup(AT, N),
+ S.
+
+%% AT is updated.
+ensure_atoms({empty, AT}, Cs) ->
+ {_Id, AtomChunk} = lists:keyfind("Atom", 1, Cs),
+ extract_atoms(AtomChunk, AT),
+ AT;
+ensure_atoms(AT, _Cs) ->
+ AT.
+
+extract_atoms(<<_Num:32, B/binary>>, AT) ->
+ extract_atoms(B, 1, AT).
+
+extract_atoms(<<>>, _I, _AT) ->
+ true;
+extract_atoms(B, I, AT) ->
+ {Atom, B1} = extract_atom(B),
+ true = ets:insert(AT, {I, Atom}),
+ extract_atoms(B1, I+1, AT).
+
+extract_atom(<<Len, B/binary>>) ->
+ <<SB:Len/binary, Tail/binary>> = B,
+ {list_to_atom(binary_to_list(SB)), Tail}.
+
+%%% Utils.
+
+-record(bb, {pos = 0 :: integer(),
+ bin :: binary(),
+ source :: binary() | string()}).
+
+open_file(<<"FOR1",_/binary>>=Binary) ->
+ #bb{bin = Binary, source = Binary};
+open_file(Binary0) when is_binary(Binary0) ->
+ Binary = uncompress(Binary0),
+ #bb{bin = Binary, source = Binary};
+open_file(FileName) ->
+ case file:open(FileName, [read, raw, binary]) of
+ {ok, Fd} ->
+ read_all(Fd, FileName, []);
+ Error ->
+ file_error(FileName, Error)
+ end.
+
+read_all(Fd, FileName, Bins) ->
+ case file:read(Fd, 1 bsl 18) of
+ {ok, Bin} ->
+ read_all(Fd, FileName, [Bin | Bins]);
+ eof ->
+ ok = file:close(Fd),
+ #bb{bin = uncompress(reverse(Bins)), source = FileName};
+ Error ->
+ ok = file:close(Fd),
+ file_error(FileName, Error)
+ end.
+
+pread(FD, AtPos, Size) ->
+ #bb{pos = Pos, bin = Binary} = FD,
+ Skip = AtPos-Pos,
+ case Binary of
+ <<_:Skip/binary, B:Size/binary, Bin/binary>> ->
+ NFD = FD#bb{pos = AtPos+Size, bin = Bin},
+ {NFD, {ok, B}};
+ <<_:Skip/binary, Bin/binary>> when byte_size(Bin) > 0 ->
+ NFD = FD#bb{pos = AtPos+byte_size(Bin), bin = <<>>},
+ {NFD, {ok, Bin}};
+ _ ->
+ {FD, eof}
+ end.
+
+filename(BB) when is_binary(BB#bb.source) ->
+ BB#bb.source;
+filename(BB) ->
+ list_to_atom(BB#bb.source).
+
+beam_filename(Bin) when is_binary(Bin) ->
+ Bin;
+beam_filename(File) ->
+ filename:rootname(File, ".beam") ++ ".beam".
+
+
+uncompress(Binary0) ->
+ {ok, Fd} = ram_file:open(Binary0, [write, binary]),
+ {ok, _} = ram_file:uncompress(Fd),
+ {ok, Binary} = ram_file:get_file(Fd),
+ ok = ram_file:close(Fd),
+ Binary.
+
+compress(Binary0) ->
+ {ok, Fd} = ram_file:open(Binary0, [write, binary]),
+ {ok, _} = ram_file:compress(Fd),
+ {ok, Binary} = ram_file:get_file(Fd),
+ ok = ram_file:close(Fd),
+ Binary.
+
+%% -> ok | throw(Error)
+assert_directory(FileName) ->
+ case filelib:is_dir(FileName) of
+ true ->
+ ok;
+ false ->
+ error({not_a_directory, FileName})
+ end.
+
+-spec file_error(file:filename(), {'error',atom()}) -> no_return().
+
+file_error(FileName, {error, Reason}) ->
+ error({file_error, FileName, Reason}).
+
+-spec error(term()) -> no_return().
+
+error(Reason) ->
+ throw({error, ?MODULE, Reason}).
+
+
+%% The following chunks are significant when calculating the MD5 for a module,
+%% and also the modules that must be retained when stripping a file.
+%% They are listed in the order that they should be MD5:ed.
+
+significant_chunks() ->
+ ["Atom", "Code", "StrT", "ImpT", "ExpT", "FunT", "LitT"].
+
+%% The following chunks are mandatory in every Beam file.
+
+mandatory_chunks() ->
+ ["Code", "ExpT", "ImpT", "StrT", "Atom"].
+
+%%% ====================================================================
+%%% The rest of the file handles encrypted debug info.
+%%%
+%%% Encrypting the debug info is only useful if you want to
+%%% have the debug info available all the time (maybe even in a live
+%%% system), but don't want to risk that anyone else but yourself
+%%% can use it.
+%%% ====================================================================
+
+-record(state, {crypto_key_f :: crypto_fun()}).
+
+-define(CRYPTO_KEY_SERVER, beam_lib__crypto_key_server).
+
+decrypt_abst(Mode, Module, File, Id, AtomTable, Bin) ->
+ try
+ KeyString = get_crypto_key({debug_info, Mode, Module, File}),
+ Key = make_crypto_key(des3_cbc, KeyString),
+ Term = decrypt_abst_1(Mode, Key, Bin),
+ {AtomTable, {Id, Term}}
+ catch
+ _:_ ->
+ error({key_missing_or_invalid, File, Id})
+ end.
+
+decrypt_abst_1(des3_cbc, {K1, K2, K3, IVec}, Bin) ->
+ ok = start_crypto(),
+ NewBin = crypto:des3_cbc_decrypt(K1, K2, K3, IVec, Bin),
+ binary_to_term(NewBin).
+
+start_crypto() ->
+ case crypto:start() of
+ {error, {already_started, _}} ->
+ ok;
+ ok ->
+ ok
+ end.
+
+get_crypto_key(What) ->
+ call_crypto_server({get_crypto_key, What}).
+
+call_crypto_server(Req) ->
+ try
+ gen_server:call(?CRYPTO_KEY_SERVER, Req, infinity)
+ catch
+ exit:{noproc,_} ->
+ start_crypto_server(),
+ erlang:yield(),
+ call_crypto_server(Req)
+ end.
+
+start_crypto_server() ->
+ gen_server:start({local,?CRYPTO_KEY_SERVER}, ?MODULE, [], []).
+
+-spec init([]) -> {'ok', #state{}}.
+
+init([]) ->
+ {ok, #state{}}.
+
+-type calls() :: 'clear_crypto_key_fun'
+ | {'crypto_key_fun', _}
+ | {'get_crypto_key', _}.
+
+-spec handle_call(calls(), {pid(), term()}, #state{}) ->
+ {'noreply', #state{}} |
+ {'reply', 'error' | {'error','badfun' | 'exists'}, #state{}} |
+ {'stop', 'normal', 'undefined' | {'ok', term()}, #state{}}.
+
+handle_call({get_crypto_key, _}=R, From, #state{crypto_key_f=undefined}=S) ->
+ case crypto_key_fun_from_file() of
+ error ->
+ {reply, error, S};
+ F when is_function(F) ->
+ %% The init function for the fun has already been called.
+ handle_call(R, From, S#state{crypto_key_f=F})
+ end;
+handle_call({get_crypto_key, What}, From, #state{crypto_key_f=F}=S) ->
+ try
+ Result = F(What),
+ %% The result may hold information that we don't want
+ %% lying around. Reply first, then GC, then noreply.
+ gen_server:reply(From, Result),
+ erlang:garbage_collect(),
+ {noreply, S}
+ catch
+ _:_ ->
+ {reply, error, S}
+ end;
+handle_call({crypto_key_fun, F}, {_,_} = From, S) ->
+ case S#state.crypto_key_f of
+ undefined ->
+ %% Don't allow tuple funs here. (They weren't allowed before,
+ %% so there is no reason to allow them now.)
+ if is_function(F), is_function(F, 1) ->
+ {Result, Fun, Reply} =
+ case catch F(init) of
+ ok ->
+ {true, F, ok};
+ {ok, F1} when is_function(F1) ->
+ if
+ is_function(F1, 1) ->
+ {true, F1, ok};
+ true ->
+ {false, undefined,
+ {error, badfun}}
+ end;
+ {error, Reason} ->
+ {false, undefined, {error, Reason}};
+ {'EXIT', Reason} ->
+ {false, undefined, {error, Reason}}
+ end,
+ gen_server:reply(From, Reply),
+ erlang:garbage_collect(),
+ NewS = case Result of
+ true ->
+ S#state{crypto_key_f = Fun};
+ false ->
+ S
+ end,
+ {noreply, NewS};
+ true ->
+ {reply, {error, badfun}, S}
+ end;
+ OtherF when is_function(OtherF) ->
+ {reply, {error, exists}, S}
+ end;
+handle_call(clear_crypto_key_fun, _From, S) ->
+ case S#state.crypto_key_f of
+ undefined ->
+ {stop,normal,undefined,S};
+ F ->
+ Result = (catch F(clear)),
+ {stop,normal,{ok,Result},S}
+ end.
+
+-spec handle_cast(term(), #state{}) -> {'noreply', #state{}}.
+
+handle_cast(_, State) ->
+ {noreply, State}.
+
+-spec handle_info(term(), #state{}) -> {'noreply', #state{}}.
+
+handle_info(_, State) ->
+ {noreply, State}.
+
+-spec code_change(term(), #state{}, term()) -> {'ok', #state{}}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+-spec terminate(term(), #state{}) -> 'ok'.
+
+terminate(_Reason, _State) ->
+ ok.
+
+crypto_key_fun_from_file() ->
+ case init:get_argument(home) of
+ {ok,[[Home]]} ->
+ crypto_key_fun_from_file_1([".",Home]);
+ _ ->
+ crypto_key_fun_from_file_1(["."])
+ end.
+
+crypto_key_fun_from_file_1(Path) ->
+ case f_p_s(Path, ".erlang.crypt") of
+ {ok, KeyInfo, _} ->
+ try_load_crypto_fun(KeyInfo);
+ _ ->
+ error
+ end.
+
+f_p_s(P, F) ->
+ case file:path_script(P, F) of
+ {error, enoent} ->
+ {error, enoent};
+ {error, {Line, _Mod, _Term}=E} ->
+ error("file:path_script(~p,~p): error on line ~p: ~s~n",
+ [P, F, Line, file:format_error(E)]),
+ ok;
+ {error, E} when is_atom(E) ->
+ error("file:path_script(~p,~p): ~s~n",
+ [P, F, file:format_error(E)]),
+ ok;
+ Other ->
+ Other
+ end.
+
+try_load_crypto_fun(KeyInfo) when is_list(KeyInfo) ->
+ T = ets:new(keys, [private, set]),
+ foreach(
+ fun({debug_info, Mode, M, Key}) when is_atom(M) ->
+ ets:insert(T, {{debug_info,Mode,M,[]}, Key});
+ ({debug_info, Mode, [], Key}) ->
+ ets:insert(T, {{debug_info, Mode, [], []}, Key});
+ (Other) ->
+ error("unknown key: ~p~n", [Other])
+ end, KeyInfo),
+ fun({debug_info, Mode, M, F}) ->
+ alt_lookup_key(
+ [{debug_info,Mode,M,F},
+ {debug_info,Mode,M,[]},
+ {debug_info,Mode,[],[]}], T);
+ (clear) ->
+ ets:delete(T);
+ (_) ->
+ error
+ end;
+try_load_crypto_fun(KeyInfo) ->
+ error("unrecognized crypto key info: ~p\n", [KeyInfo]).
+
+alt_lookup_key([H|T], Tab) ->
+ case ets:lookup(Tab, H) of
+ [] ->
+ alt_lookup_key(T, Tab);
+ [{_, Val}] ->
+ Val
+ end;
+alt_lookup_key([], _) ->
+ error.
+
+error(Fmt, Args) ->
+ error_logger:error_msg(Fmt, Args),
+ error.
diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl
new file mode 100644
index 0000000000..9e4cec5db2
--- /dev/null
+++ b/lib/stdlib/src/c.erl
@@ -0,0 +1,700 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(c).
+
+%% Utilities to use from shell.
+
+-export([help/0,lc/1,c/1,c/2,nc/1,nc/2, nl/1,l/1,i/0,i/1,ni/0,
+ y/1, y/2,
+ lc_batch/0, lc_batch/1,
+ i/3,pid/3,m/0,m/1,
+ bt/1, q/0,
+ erlangrc/0,erlangrc/1,bi/1, flush/0, regs/0,
+ nregs/0,pwd/0,ls/0,ls/1,cd/1,memory/1,memory/0, xm/1]).
+
+-export([display_info/1]).
+-export([appcall/4]).
+
+-import(lists, [reverse/1,flatten/1,sublist/3,sort/1,keysearch/3,keysort/2,
+ concat/1,max/1,min/1,foreach/2,foldl/3,flatmap/2]).
+-import(io, [format/1, format/2]).
+
+help() ->
+ format("bt(Pid) -- stack backtrace for a process\n"
+ "c(File) -- compile and load code in <File>\n"
+ "cd(Dir) -- change working directory\n"
+ "flush() -- flush any messages sent to the shell\n"
+ "help() -- help info\n"
+ "i() -- information about the system\n"
+ "ni() -- information about the networked system\n"
+ "i(X,Y,Z) -- information about pid <X,Y,Z>\n"
+ "l(Module) -- load or reload module\n"
+ "lc([File]) -- compile a list of Erlang modules\n"
+ "ls() -- list files in the current directory\n"
+ "ls(Dir) -- list files in directory <Dir>\n"
+ "m() -- which modules are loaded\n"
+ "m(Mod) -- information about module <Mod>\n"
+ "memory() -- memory allocation information\n"
+ "memory(T) -- memory allocation information of type <T>\n"
+ "nc(File) -- compile and load code in <File> on all nodes\n"
+ "nl(Module) -- load module on all nodes\n"
+ "pid(X,Y,Z) -- convert X,Y,Z to a Pid\n"
+ "pwd() -- print working directory\n"
+ "q() -- quit - shorthand for init:stop()\n"
+ "regs() -- information about registered processes\n"
+ "nregs() -- information about all registered processes\n"
+ "xm(M) -- cross reference check a module\n"
+ "y(File) -- generate a Yecc parser\n").
+
+%% c(FileName)
+%% Compile a file/module.
+
+c(File) -> c(File, []).
+
+c(File, Opts0) when is_list(Opts0) ->
+ Opts = [report_errors,report_warnings|Opts0],
+ case compile:file(File, Opts) of
+ {ok,Mod} -> %Listing file.
+ machine_load(Mod, File, Opts);
+ {ok,Mod,_Ws} -> %Warnings maybe turned on.
+ machine_load(Mod, File, Opts);
+ Other -> %Errors go here
+ Other
+ end;
+c(File, Opt) ->
+ c(File, [Opt]).
+
+%%% Obtain the 'outdir' option from the argument. Return "." if no
+%%% such option was given.
+outdir([]) ->
+ ".";
+outdir([Opt|Rest]) ->
+ case Opt of
+ {outdir, D} ->
+ D;
+ _ ->
+ outdir(Rest)
+ end.
+
+%%% We have compiled File with options Opts. Find out where the
+%%% output file went to, and load it.
+machine_load(Mod, File, Opts) ->
+ Dir = outdir(Opts),
+ File2 = filename:join(Dir, filename:basename(File, ".erl")),
+ case compile:output_generated(Opts) of
+ true ->
+ Base = packages:last(Mod),
+ case filename:basename(File, ".erl") of
+ Base ->
+ code:purge(Mod),
+ check_load(code:load_abs(File2,Mod), Mod);
+ _OtherMod ->
+ format("** Module name '~p' does not match file name '~p' **~n",
+ [Mod,File]),
+ {error, badfile}
+ end;
+ false ->
+ format("** Warning: No object file created - nothing loaded **~n", []),
+ ok
+ end.
+
+%%% This function previously warned if the loaded module was
+%%% loaded from some other place than current directory.
+%%% Now, loading from other than current directory is supposed to work.
+%%% so this function does nothing special.
+check_load({error, R}, _) -> {error, R};
+check_load(_, X) -> {ok, X}.
+
+%% Compile a list of modules
+%% enables the nice unix shell cmd
+%% erl -s c lc f1 f2 f3 @d c1=v1 @c2 @i IDir @o ODir -s erlang halt
+%% to compile files f1.erl , f2.erl ....... from a unix shell
+%% with constant c2 defined, c1=v1 (v1 must be a term!), include dir
+%% IDir, outdir ODir.
+
+lc(Args) ->
+ case catch split(Args, [], []) of
+ error -> error;
+ {Opts, Files} ->
+ COpts = [report_errors, report_warnings | reverse(Opts)],
+ foreach(fun(File) -> compile:file(File, COpts) end, reverse(Files))
+ end.
+
+%%% lc_batch/1 works like lc/1, but halts afterwards, with appropriate
+%%% exit code. This is meant to be called by "erl -compile".
+
+-spec lc_batch() -> no_return().
+
+lc_batch() ->
+ io:format("Error: no files to compile~n"),
+ halt(1).
+
+-spec lc_batch([_]) -> no_return().
+
+lc_batch(Args) ->
+ try split(Args, [], []) of
+ {Opts, Files} ->
+ COpts = [report_errors, report_warnings | reverse(Opts)],
+ Res = [compile:file(File, COpts) || File <- reverse(Files)],
+ case lists:member(error, Res) of
+ true ->
+ halt(1);
+ false ->
+ halt(0)
+ end
+ catch
+ throw:error -> halt(1)
+ end.
+
+split(['@i', Dir | T], Opts, Files) ->
+ split(T, [{i, atom_to_list(Dir)} | Opts], Files);
+split(['@o', Dir | T], Opts, Files) ->
+ split(T, [{outdir, atom_to_list(Dir)} | Opts], Files);
+split(['@d', Def | T], Opts, Files) ->
+ split(T, [split_def(atom_to_list(Def), []) | Opts], Files);
+split([File | T], Opts, Files) ->
+ split(T, Opts, [File | Files]);
+split([], Opts, Files) ->
+ {Opts, Files}.
+
+split_def([$= | T], Res) -> {d, list_to_atom(reverse(Res)),make_term(T)};
+split_def([H | T], Res) -> split_def(T, [H | Res]);
+split_def([], Res) -> {d, list_to_atom(reverse(Res))}.
+
+make_term(Str) ->
+ case erl_scan:string(Str) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
+ {ok, Term} -> Term;
+ {error, {_,_,Reason}} ->
+ io:format("~s: ~s~n", [Reason, Str]),
+ throw(error)
+ end;
+ {error, {_,_,Reason}, _} ->
+ io:format("~s: ~s~n", [Reason, Str]),
+ throw(error)
+ end.
+
+nc(File) -> nc(File, []).
+
+nc(File, Opts0) when is_list(Opts0) ->
+ Opts = Opts0 ++ [report_errors, report_warnings],
+ case compile:file(File, Opts) of
+ {ok,Mod} ->
+ Fname = concat([File, code:objfile_extension()]),
+ case file:read_file(Fname) of
+ {ok,Bin} ->
+ rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]),
+ {ok,Mod};
+ Other ->
+ Other
+ end;
+ Other -> %Errors go here
+ Other
+ end;
+nc(File, Opt) when is_atom(Opt) ->
+ nc(File, [Opt]).
+
+%% l(Mod)
+%% Reload module Mod from file of same name
+
+l(Mod) ->
+ code:purge(Mod),
+ code:load_file(Mod).
+
+%% Network version of l/1
+nl(Mod) ->
+ case code:get_object_code(Mod) of
+ {_Module, Bin, Fname} ->
+ rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]);
+ Other ->
+ Other
+ end.
+
+i() -> i(processes()).
+ni() -> i(all_procs()).
+
+i(Ps) ->
+ i(Ps, length(Ps)).
+
+i(Ps, N) when N =< 100 ->
+ iformat("Pid", "Initial Call", "Heap", "Reds",
+ "Msgs"),
+ iformat("Registered", "Current Function", "Stack", "",
+ ""),
+ {R,M,H,S} = foldl(fun(Pid, {R0,M0,H0,S0}) ->
+ {A,B,C,D} = display_info(Pid),
+ {R0+A,M0+B,H0+C,S0+D}
+ end, {0,0,0,0}, Ps),
+ iformat("Total", "", w(H), w(R), w(M)),
+ iformat("", "", w(S), "", "");
+i(Ps, N) ->
+ iformat("Pid", "Initial Call", "Heap", "Reds",
+ "Msgs"),
+ iformat("Registered", "Current Function", "Stack", "",
+ ""),
+ paged_i(Ps, {0,0,0,0}, N, 50).
+
+paged_i([], {R,M,H,S}, _, _) ->
+ iformat("Total", "", w(H), w(R), w(M)),
+ iformat("", "", w(S), "", "");
+paged_i(Ps, Acc, N, Page) ->
+ {Pids, Rest, N1} =
+ if N > Page ->
+ {L1,L2} = lists:split(Page, Ps),
+ {L1,L2,N-Page};
+ true ->
+ {Ps, [], 0}
+ end,
+ NewAcc = foldl(fun(Pid, {R,M,H,S}) ->
+ {A,B,C,D} = display_info(Pid),
+ {R+A,M+B,H+C,S+D}
+ end, Acc, Pids),
+ case Rest of
+ [_|_] ->
+ choice(fun() -> paged_i(Rest, NewAcc, N1, Page) end);
+ [] ->
+ paged_i([], NewAcc, 0, Page)
+ end.
+
+
+choice(F) ->
+ case get_line('(c)ontinue (q)uit -->', "c\n") of
+ "c\n" ->
+ F();
+ "q\n" ->
+ quit;
+ _ ->
+ choice(F)
+ end.
+
+
+get_line(P, Default) ->
+ case io:get_line(P) of
+ "\n" ->
+ Default;
+ L ->
+ L
+ end.
+
+mfa_string(Fun) when is_function(Fun) ->
+ {module,M} = erlang:fun_info(Fun, module),
+ {name,F} = erlang:fun_info(Fun, name),
+ {arity,A} = erlang:fun_info(Fun, arity),
+ mfa_string({M,F,A});
+mfa_string({M,F,A}) ->
+ io_lib:format("~w:~w/~w", [M,F,A]);
+mfa_string(X) ->
+ w(X).
+
+
+display_info(Pid) ->
+ case pinfo(Pid) of
+ undefined -> {0,0,0,0};
+ Info ->
+ Call = initial_call(Info),
+ Curr = case fetch(current_function, Info) of
+ {Mod,F,Args} when is_list(Args) ->
+ {Mod,F,length(Args)};
+ Other ->
+ Other
+ end,
+ Reds = fetch(reductions, Info),
+ LM = length(fetch(messages, Info)),
+ HS = fetch(heap_size, Info),
+ SS = fetch(stack_size, Info),
+ iformat(w(Pid), mfa_string(Call),
+ w(HS),
+ w(Reds), w(LM)),
+ iformat(case fetch(registered_name, Info) of
+ 0 -> "";
+ X -> w(X)
+ end,
+ mfa_string(Curr),
+ w(SS),
+ "",
+ ""),
+ {Reds, LM, HS, SS}
+ end.
+
+%% We have to do some assumptions about the initial call.
+%% If the initial call is proc_lib:init_p/3,5 we can find more information
+%% calling the function proc_lib:initial_call/1.
+
+initial_call(Info) ->
+ case fetch(initial_call, Info) of
+ {proc_lib, init_p, _} ->
+ proc_lib:translate_initial_call(Info);
+ ICall ->
+ ICall
+ end.
+
+iformat(A1, A2, A3, A4, A5) ->
+ format("~-21s ~-33s ~8s ~8s ~4s~n", [A1,A2,A3,A4,A5]).
+
+all_procs() ->
+ case is_alive() of
+ true -> flatmap(fun (N) -> rpc:call(N,erlang,processes,[]) end,
+ [node()|nodes()]);
+ false -> processes()
+ end.
+
+pinfo(Pid) ->
+ case is_alive() of
+ true -> rpc:call(node(Pid), erlang, process_info, [Pid]);
+ false -> process_info(Pid)
+ end.
+
+fetch(Key, Info) ->
+ case keysearch(Key, 1, Info) of
+ {value, {_, Val}} -> Val;
+ false -> 0
+ end.
+
+pid(X,Y,Z) ->
+ list_to_pid("<" ++ integer_to_list(X) ++ "." ++
+ integer_to_list(Y) ++ "." ++
+ integer_to_list(Z) ++ ">").
+
+i(X,Y,Z) -> pinfo(pid(X,Y,Z)).
+
+q() ->
+ init:stop().
+
+bt(Pid) ->
+ case catch erlang:process_display(Pid, backtrace) of
+ {'EXIT', _} ->
+ undefined;
+ _ ->
+ ok
+ end.
+
+m() ->
+ mformat("Module", "File"),
+ foreach(fun ({Mod,File}) -> mformat(Mod, File) end, sort(code:all_loaded())).
+
+mformat(A1, A2) ->
+ format("~-20s ~s\n", [A1,A2]).
+
+%% erlangrc(Home)
+%% Try to run a ".erlang" file, first in the current directory
+%% else in home directory.
+
+erlangrc() ->
+ case init:get_argument(home) of
+ {ok,[[Home]]} ->
+ erlangrc([Home]);
+ _ ->
+ f_p_e(["."], ".erlang")
+ end.
+
+erlangrc([Home]) ->
+ f_p_e([".",Home], ".erlang").
+
+error(Fmt, Args) ->
+ error_logger:error_msg(Fmt, Args).
+
+f_p_e(P, F) ->
+ case file:path_eval(P, F) of
+ {error, enoent} ->
+ {error, enoent};
+ {error, E={Line, _Mod, _Term}} ->
+ error("file:path_eval(~p,~p): error on line ~p: ~s~n",
+ [P, F, Line, file:format_error(E)]),
+ ok;
+ {error, E} ->
+ error("file:path_eval(~p,~p): ~s~n",
+ [P, F, file:format_error(E)]),
+ ok;
+ Other ->
+ Other
+ end.
+
+bi(I) ->
+ case erlang:system_info(I) of
+ X when is_binary(X) -> io:put_chars(binary_to_list(X));
+ X when is_list(X) -> io:put_chars(X);
+ X -> format("~w", [X])
+ end.
+
+%%
+%% Short and nice form of module info
+%%
+
+m(M) ->
+ L = M:module_info(),
+ {value,{exports,E}} = keysearch(exports, 1, L),
+ Time = get_compile_time(L),
+ COpts = get_compile_options(L),
+ format("Module ~w compiled: ",[M]), print_time(Time),
+ format("Compiler options: ~p~n", [COpts]),
+ print_object_file(M),
+ format("Exports: ~n",[]), print_exports(keysort(1, E)).
+
+print_object_file(Mod) ->
+ case code:is_loaded(Mod) of
+ {file,File} ->
+ format("Object file: ~s\n", [File]);
+ _ ->
+ ignore
+ end.
+
+get_compile_time(L) ->
+ case get_compile_info(L, time) of
+ {ok,Val} -> Val;
+ error -> notime
+ end.
+
+get_compile_options(L) ->
+ case get_compile_info(L, options) of
+ {ok,Val} -> Val;
+ error -> []
+ end.
+
+get_compile_info(L, Tag) ->
+ case keysearch(compile, 1, L) of
+ {value, {compile, I}} ->
+ case keysearch(Tag, 1, I) of
+ {value, {Tag, Val}} -> {ok,Val};
+ false -> error
+ end;
+ false -> error
+ end.
+
+print_exports(X) when length(X) > 16 ->
+ split_print_exports(X);
+print_exports([]) -> ok;
+print_exports([{F, A} |Tail]) ->
+ format(" ~w/~w~n",[F, A]),
+ print_exports(Tail).
+
+split_print_exports(L) ->
+ Len = length(L),
+ Mid = Len div 2,
+ L1 = sublist(L, 1, Mid),
+ L2 = sublist(L, Mid +1, Len - Mid + 1),
+ split_print_exports(L1, L2).
+
+split_print_exports([], [{F, A}|T]) ->
+ Str = " ",
+ format("~-30s~w/~w~n", [Str, F, A]),
+ split_print_exports([], T);
+split_print_exports([{F1, A1}|T1], [{F2, A2} | T2]) ->
+ Str = flatten(io_lib:format("~w/~w", [F1, A1])),
+ format("~-30s~w/~w~n", [Str, F2, A2]),
+ split_print_exports(T1, T2);
+split_print_exports([], []) -> ok.
+
+print_time({Year,Month,Day,Hour,Min,_Secs}) ->
+ format("Date: ~s ~w ~w, ", [month(Month),Day,Year]),
+ format("Time: ~.2.0w.~.2.0w~n", [Hour,Min]);
+print_time(notime) ->
+ format("No compile time info available~n",[]).
+
+month(1) -> "January";
+month(2) -> "February";
+month(3) -> "March";
+month(4) -> "April";
+month(5) -> "May";
+month(6) -> "June";
+month(7) -> "July";
+month(8) -> "August";
+month(9) -> "September";
+month(10) -> "October";
+month(11) -> "November";
+month(12) -> "December".
+
+%% Just because we can't eval receive statements...
+flush() ->
+ receive
+ X ->
+ format("Shell got ~p~n",[X]),
+ flush()
+ after 0 ->
+ ok
+ end.
+
+%% Print formatted info about all registered names in the system
+nregs() ->
+ foreach(fun (N) -> print_node_regs(N) end, all_regs()).
+
+regs() ->
+ print_node_regs({node(),registered()}).
+
+all_regs() ->
+ case is_alive() of
+ true -> [{N,rpc:call(N, erlang, registered, [])} ||
+ N <- [node()|nodes()]];
+ false -> [{node(),registered()}]
+ end.
+
+print_node_regs({N, List}) when is_list(List) ->
+ {Pids,Ports,_Dead} = pids_and_ports(N, sort(List), [], [], []),
+ %% print process info
+ format("~n** Registered procs on node ~w **~n",[N]),
+ procformat("Name", "Pid", "Initial Call", "Reds", "Msgs"),
+ foreach(fun({Name,PI,Pid}) -> procline(Name, PI, Pid) end, Pids),
+ %% print port info
+ format("~n** Registered ports on node ~w **~n",[N]),
+ portformat("Name", "Id", "Command"),
+ foreach(fun({Name,PI,Id}) -> portline(Name, PI, Id) end, Ports).
+
+pids_and_ports(_, [], Pids, Ports, Dead) ->
+ {reverse(Pids),reverse(Ports),reverse(Dead)};
+
+pids_and_ports(Node, [Name|Names], Pids, Ports, Dead) ->
+ case pwhereis(Node, Name) of
+ Pid when is_pid(Pid) ->
+ pids_and_ports(Node, Names, [{Name,pinfo(Pid),Pid}|Pids],
+ Ports, Dead);
+ Id when is_port(Id) ->
+ pids_and_ports(Node, Names, Pids,
+ [{Name,portinfo(Id),Id}|Ports], Dead);
+ undefined ->
+ pids_and_ports(Node, Names, Pids, Ports, [Name|Dead])
+ end.
+
+pwhereis(Node, Name) ->
+ case is_alive() of
+ true -> rpc:call(Node, erlang, whereis, [Name]);
+ false -> whereis(Name)
+ end.
+
+portinfo(Id) ->
+ case is_alive() of
+ true -> [ rpc:call(node(Id), erlang, port_info, [Id,name]) ];
+ false -> [ erlang:port_info(Id, name) ]
+ end.
+
+procline(Name, Info, Pid) ->
+ Call = initial_call(Info),
+ Reds = fetch(reductions, Info),
+ LM = length(fetch(messages, Info)),
+ procformat(io_lib:format("~w",[Name]),
+ io_lib:format("~w",[Pid]),
+ io_lib:format("~s",[mfa_string(Call)]),
+ integer_to_list(Reds), integer_to_list(LM)).
+
+procformat(Name, Pid, Call, Reds, LM) ->
+ format("~-21s ~-12s ~-25s ~12s ~4s~n", [Name,Pid,Call,Reds,LM]).
+
+portline(Name, Info, Id) ->
+ Cmd = fetch(name, Info),
+ portformat(io_lib:format("~w",[Name]),
+ erlang:port_to_list(Id),
+ Cmd).
+
+portformat(Name, Id, Cmd) ->
+ format("~-21s ~-15s ~-40s~n", [Name,Id,Cmd]).
+
+%% pwd()
+%% cd(Directory)
+%% These are just wrappers around the file:get/set_cwd functions.
+
+pwd() ->
+ case file:get_cwd() of
+ {ok, Str} ->
+ ok = io:format("~s\n", [Str]);
+ {error, _} ->
+ ok = io:format("Cannot determine current directory\n")
+ end.
+
+cd(Dir) ->
+ file:set_cwd(Dir),
+ pwd().
+
+%% ls()
+%% ls(Directory)
+%% The strategy is to print in fixed width files.
+
+ls() ->
+ ls(".").
+
+ls(Dir) ->
+ case file:list_dir(Dir) of
+ {ok, Entries} ->
+ ls_print(sort(Entries));
+ {error,_E} ->
+ format("Invalid directory\n")
+ end.
+
+ls_print([]) -> ok;
+ls_print(L) ->
+ Width = min([max(lengths(L, [])), 40]) + 5,
+ ls_print(L, Width, 0).
+
+ls_print(X, Width, Len) when Width + Len >= 80 ->
+ io:nl(),
+ ls_print(X, Width, 0);
+ls_print([H|T], Width, Len) ->
+ io:format("~-*s",[Width,H]),
+ ls_print(T, Width, Len+Width);
+ls_print([], _, _) ->
+ io:nl().
+
+lengths([H|T], L) -> lengths(T, [length(H)|L]);
+lengths([], L) -> L.
+
+w(X) ->
+ io_lib:write(X).
+
+%%
+%% memory/[0,1]
+%%
+
+memory() -> erlang:memory().
+memory(TypeSpec) -> erlang:memory(TypeSpec).
+
+%%
+%% Cross Reference Check
+%%
+
+xm(M) ->
+ appcall(tools, xref, m, [M]).
+
+%%
+%% Call yecc
+%%
+
+y(File) -> y(File, []).
+
+y(File, Opts) ->
+ appcall(parsetools, yecc, file, [File,Opts]).
+
+
+%%
+%% Avoid creating strong components in xref and dialyzer by making calls
+%% from helper functions to other applications indirect.
+%%
+
+appcall(App, M, F, Args) ->
+ try
+ apply(M, F, Args)
+ catch
+ error:undef ->
+ case erlang:get_stacktrace() of
+ [{M,F,Args}|_] ->
+ Arity = length(Args),
+ io:format("Call to ~w:~w/~w in application ~w failed.\n",
+ [M,F,Arity,App]);
+ Stk ->
+ erlang:raise(error, undef, Stk)
+ end
+ end.
+
diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl
new file mode 100644
index 0000000000..ddc0666f77
--- /dev/null
+++ b/lib/stdlib/src/calendar.erl
@@ -0,0 +1,459 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(calendar).
+
+%% local and universal time, time conversions
+
+-export([date_to_gregorian_days/1,
+ date_to_gregorian_days/3,
+ datetime_to_gregorian_seconds/1,
+ day_of_the_week/1,
+ day_of_the_week/3,
+ gregorian_days_to_date/1,
+ gregorian_seconds_to_datetime/1,
+ is_leap_year/1,
+ last_day_of_the_month/2,
+ local_time/0,
+ local_time_to_universal_time/1,
+ local_time_to_universal_time/2,
+ local_time_to_universal_time_dst/1,
+ now_to_datetime/1, % = now_to_universal_time/1
+ now_to_local_time/1,
+ now_to_universal_time/1,
+ seconds_to_daystime/1,
+ seconds_to_time/1,
+ time_difference/2,
+ time_to_seconds/1,
+ universal_time/0,
+ universal_time_to_local_time/1,
+ valid_date/1,
+ valid_date/3]).
+
+-deprecated([{local_time_to_universal_time,1}]).
+
+-define(SECONDS_PER_MINUTE, 60).
+-define(SECONDS_PER_HOUR, 3600).
+-define(SECONDS_PER_DAY, 86400).
+-define(DAYS_PER_YEAR, 365).
+-define(DAYS_PER_LEAP_YEAR, 366).
+-define(DAYS_PER_4YEARS, 1461).
+-define(DAYS_PER_100YEARS, 36524).
+-define(DAYS_PER_400YEARS, 146097).
+-define(DAYS_FROM_0_TO_1970, 719528).
+
+%%----------------------------------------------------------------------
+%% Types
+%%----------------------------------------------------------------------
+
+-type year() :: non_neg_integer().
+-type year1970() :: 1970..10000. % should probably be 1970..
+-type month() :: 1..12.
+-type day() :: 1..31.
+-type hour() :: 0..23.
+-type minute() :: 0..59.
+-type second() :: 0..59.
+-type daynum() :: 1..7.
+-type ldom() :: 28 | 29 | 30 | 31. % last day of month
+
+-type t_now() :: {non_neg_integer(),non_neg_integer(),non_neg_integer()}.
+
+-type t_date() :: {year(),month(),day()}.
+-type t_time() :: {hour(),minute(),second()}.
+-type t_datetime() :: {t_date(),t_time()}.
+-type t_datetime1970() :: {{year1970(),month(),day()},t_time()}.
+
+%%----------------------------------------------------------------------
+
+%% All dates are according the the Gregorian calendar. In this module
+%% the Gregorian calendar is extended back to year 0 for convenience.
+%%
+%% A year Y is a leap year if and only if either
+%%
+%% (1) Y is divisible by 4, but not by 100, or
+%% (2) Y is divisible by 400.
+%%
+%% Hence, e.g. 1996 is a leap year, 1900 is not, but 2000 is.
+%%
+
+%%
+%% EXPORTS
+%%
+
+%% date_to_gregorian_days(Year, Month, Day) = Integer
+%% date_to_gregorian_days({Year, Month, Day}) = Integer
+%%
+%% Computes the total number of days starting from year 0,
+%% January 1st.
+%%
+%% df/2 catches the case Year<0
+-spec date_to_gregorian_days(year(),month(),day()) -> non_neg_integer().
+date_to_gregorian_days(Year, Month, Day) when is_integer(Day), Day > 0 ->
+ Last = last_day_of_the_month(Year, Month),
+ if
+ Day =< Last ->
+ dy(Year) + dm(Month) + df(Year, Month) + Day - 1
+ end.
+
+-spec date_to_gregorian_days(t_date()) -> non_neg_integer().
+date_to_gregorian_days({Year, Month, Day}) ->
+ date_to_gregorian_days(Year, Month, Day).
+
+
+%% datetime_to_gregorian_seconds(DateTime) = Integer
+%%
+%% Computes the total number of seconds starting from year 0,
+%% January 1st.
+%%
+-spec datetime_to_gregorian_seconds(t_datetime()) -> non_neg_integer().
+datetime_to_gregorian_seconds({Date, Time}) ->
+ ?SECONDS_PER_DAY*date_to_gregorian_days(Date) +
+ time_to_seconds(Time).
+
+
+%% day_of_the_week(Year, Month, Day)
+%% day_of_the_week({Year, Month, Day})
+%%
+%% Returns: 1 | .. | 7. Monday = 1, Tuesday = 2, ..., Sunday = 7.
+%%
+-spec day_of_the_week(year(), month(), day()) -> daynum().
+day_of_the_week(Year, Month, Day) ->
+ (date_to_gregorian_days(Year, Month, Day) + 5) rem 7 + 1.
+
+-spec day_of_the_week(t_date()) -> daynum().
+day_of_the_week({Year, Month, Day}) ->
+ day_of_the_week(Year, Month, Day).
+
+
+%% gregorian_days_to_date(Days) = {Year, Month, Day}
+%%
+-spec gregorian_days_to_date(non_neg_integer()) -> t_date().
+gregorian_days_to_date(Days) ->
+ {Year, DayOfYear} = day_to_year(Days),
+ {Month, DayOfMonth} = year_day_to_date(Year, DayOfYear),
+ {Year, Month, DayOfMonth}.
+
+
+%% gregorian_seconds_to_datetime(Secs)
+%%
+-spec gregorian_seconds_to_datetime(non_neg_integer()) -> t_datetime().
+gregorian_seconds_to_datetime(Secs) when Secs >= 0 ->
+ Days = Secs div ?SECONDS_PER_DAY,
+ Rest = Secs rem ?SECONDS_PER_DAY,
+ {gregorian_days_to_date(Days), seconds_to_time(Rest)}.
+
+
+%% is_leap_year(Year) = true | false
+%%
+-spec is_leap_year(year()) -> boolean().
+is_leap_year(Y) when is_integer(Y), Y >= 0 ->
+ is_leap_year1(Y).
+
+-spec is_leap_year1(year()) -> boolean().
+is_leap_year1(Year) when Year rem 4 =:= 0, Year rem 100 > 0 ->
+ true;
+is_leap_year1(Year) when Year rem 400 =:= 0 ->
+ true;
+is_leap_year1(_) -> false.
+
+
+%% last_day_of_the_month(Year, Month)
+%%
+%% Returns the number of days in a month.
+%%
+-spec last_day_of_the_month(year(), month()) -> ldom().
+last_day_of_the_month(Y, M) when is_integer(Y), Y >= 0 ->
+ last_day_of_the_month1(Y, M).
+
+-spec last_day_of_the_month1(year(),month()) -> ldom().
+last_day_of_the_month1(_, 4) -> 30;
+last_day_of_the_month1(_, 6) -> 30;
+last_day_of_the_month1(_, 9) -> 30;
+last_day_of_the_month1(_,11) -> 30;
+last_day_of_the_month1(Y, 2) ->
+ case is_leap_year(Y) of
+ true -> 29;
+ _ -> 28
+ end;
+last_day_of_the_month1(_, M) when is_integer(M), M > 0, M < 13 ->
+ 31.
+
+
+%% local_time()
+%%
+%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
+-spec local_time() -> t_datetime().
+local_time() ->
+ erlang:localtime().
+
+
+%% local_time_to_universal_time(DateTime)
+%%
+-spec local_time_to_universal_time(t_datetime1970()) -> t_datetime1970().
+local_time_to_universal_time(DateTime) ->
+ erlang:localtime_to_universaltime(DateTime).
+
+-spec local_time_to_universal_time(t_datetime1970(),
+ 'true' | 'false' | 'undefined') ->
+ t_datetime1970().
+local_time_to_universal_time(DateTime, IsDst) ->
+ erlang:localtime_to_universaltime(DateTime, IsDst).
+
+-spec local_time_to_universal_time_dst(t_datetime1970()) -> [t_datetime1970()].
+local_time_to_universal_time_dst(DateTime) ->
+ UtDst = erlang:localtime_to_universaltime(DateTime, true),
+ Ut = erlang:localtime_to_universaltime(DateTime, false),
+ %% Reverse check the universal times
+ LtDst = erlang:universaltime_to_localtime(UtDst),
+ Lt = erlang:universaltime_to_localtime(Ut),
+ %% Return the valid universal times
+ case {LtDst,Lt} of
+ {DateTime,DateTime} when UtDst =/= Ut ->
+ [UtDst,Ut];
+ {DateTime,_} ->
+ [UtDst];
+ {_,DateTime} ->
+ [Ut];
+ {_,_} ->
+ []
+ end.
+
+%% now_to_universal_time(Now)
+%% now_to_datetime(Now)
+%%
+%% Convert from now() to UTC.
+%%
+%% Args: Now = now(); now() = {MegaSec, Sec, MilliSec}, MegaSec = Sec
+%% = MilliSec = integer()
+%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
+%%
+-spec now_to_datetime(t_now()) -> t_datetime1970().
+now_to_datetime({MSec, Sec, _uSec}) ->
+ Sec0 = MSec*1000000 + Sec + ?DAYS_FROM_0_TO_1970*?SECONDS_PER_DAY,
+ gregorian_seconds_to_datetime(Sec0).
+
+-spec now_to_universal_time(t_now()) -> t_datetime1970().
+now_to_universal_time(Now) ->
+ now_to_datetime(Now).
+
+
+%% now_to_local_time(Now)
+%%
+%% Args: Now = now()
+%%
+-spec now_to_local_time(t_now()) -> t_datetime1970().
+now_to_local_time({MSec, Sec, _uSec}) ->
+ erlang:universaltime_to_localtime(
+ now_to_universal_time({MSec, Sec, _uSec})).
+
+
+
+%% seconds_to_daystime(Secs) = {Days, {Hour, Minute, Second}}
+%%
+-spec seconds_to_daystime(integer()) -> {integer(), t_time()}.
+seconds_to_daystime(Secs) ->
+ Days0 = Secs div ?SECONDS_PER_DAY,
+ Secs0 = Secs rem ?SECONDS_PER_DAY,
+ if
+ Secs0 < 0 ->
+ {Days0 - 1, seconds_to_time(Secs0 + ?SECONDS_PER_DAY)};
+ true ->
+ {Days0, seconds_to_time(Secs0)}
+ end.
+
+
+%%
+%% seconds_to_time(Secs)
+%%
+%% Wraps.
+%%
+-type secs_per_day() :: 0..?SECONDS_PER_DAY.
+-spec seconds_to_time(secs_per_day()) -> t_time().
+seconds_to_time(Secs) when Secs >= 0, Secs < ?SECONDS_PER_DAY ->
+ Secs0 = Secs rem ?SECONDS_PER_DAY,
+ Hour = Secs0 div ?SECONDS_PER_HOUR,
+ Secs1 = Secs0 rem ?SECONDS_PER_HOUR,
+ Minute = Secs1 div ?SECONDS_PER_MINUTE,
+ Second = Secs1 rem ?SECONDS_PER_MINUTE,
+ {Hour, Minute, Second}.
+
+%% time_difference(T1, T2) = Tdiff
+%%
+%% Returns the difference between two {Date, Time} structures.
+%%
+%% T1 = T2 = {Date, Time}, Tdiff = {Day, {Hour, Min, Sec}},
+%% Date = {Year, Month, Day}, Time = {Hour, Minute, Sec},
+%% Year = Month = Day = Hour = Minute = Sec = integer()
+%%
+-type timediff() :: {integer(), t_time()}.
+-spec time_difference(t_datetime(), t_datetime()) -> timediff().
+time_difference({{Y1, Mo1, D1}, {H1, Mi1, S1}},
+ {{Y2, Mo2, D2}, {H2, Mi2, S2}}) ->
+ Secs = datetime_to_gregorian_seconds({{Y2, Mo2, D2}, {H2, Mi2, S2}}) -
+ datetime_to_gregorian_seconds({{Y1, Mo1, D1}, {H1, Mi1, S1}}),
+ seconds_to_daystime(Secs).
+
+
+%%
+%% time_to_seconds(Time)
+%%
+-spec time_to_seconds(t_time()) -> secs_per_day().
+time_to_seconds({H, M, S}) when is_integer(H), is_integer(M), is_integer(S) ->
+ H * ?SECONDS_PER_HOUR +
+ M * ?SECONDS_PER_MINUTE + S.
+
+
+%% universal_time()
+%%
+%% Returns: {date(), time()}, date() = {Y, M, D}, time() = {H, M, S}.
+-spec universal_time() -> t_datetime().
+universal_time() ->
+ erlang:universaltime().
+
+
+%% universal_time_to_local_time(DateTime)
+%%
+-spec universal_time_to_local_time(t_datetime()) -> t_datetime().
+universal_time_to_local_time(DateTime) ->
+ erlang:universaltime_to_localtime(DateTime).
+
+
+%% valid_date(Year, Month, Day) = true | false
+%% valid_date({Year, Month, Day}) = true | false
+%%
+-spec valid_date(integer(), integer(), integer()) -> boolean().
+valid_date(Y, M, D) when is_integer(Y), is_integer(M), is_integer(D) ->
+ valid_date1(Y, M, D).
+
+-spec valid_date1(integer(), integer(), integer()) -> boolean().
+valid_date1(Y, M, D) when Y >= 0, M > 0, M < 13, D > 0 ->
+ D =< last_day_of_the_month(Y, M);
+valid_date1(_, _, _) ->
+ false.
+
+-spec valid_date({integer(),integer(),integer()}) -> boolean().
+valid_date({Y, M, D}) ->
+ valid_date(Y, M, D).
+
+
+%%
+%% LOCAL FUNCTIONS
+%%
+-type day_of_year() :: 0..365.
+
+%% day_to_year(DayOfEpoch) = {Year, DayOfYear}
+%%
+%% The idea here is to first guess a year, and then adjust. Although
+%% the implementation is recursive, at most 1 or 2 recursive steps
+%% are taken.
+%% If DayOfEpoch is very large, we need far more than 1 or 2 iterations,
+%% since we just subtract a yearful of days at a time until we're there.
+%%
+-spec day_to_year(non_neg_integer()) -> {year(), day_of_year()}.
+day_to_year(DayOfEpoch) when DayOfEpoch >= 0 ->
+ Y0 = DayOfEpoch div ?DAYS_PER_YEAR,
+ {Y1, D1} = dty(Y0, DayOfEpoch, dy(Y0)),
+ {Y1, DayOfEpoch - D1}.
+
+-spec dty(year(), non_neg_integer(), non_neg_integer()) ->
+ {year(), non_neg_integer()}.
+dty(Y, D1, D2) when D1 < D2 ->
+ dty(Y-1, D1, dy(Y-1));
+dty(Y, _D1, D2) ->
+ {Y, D2}.
+
+%% year_day_to_date(Year, DayOfYear) = {Month, DayOfMonth}
+%%
+%% Note: 1 is the first day of the month.
+%%
+-spec year_day_to_date(year(), day_of_year()) -> {month(), day()}.
+year_day_to_date(Year, DayOfYear) ->
+ ExtraDay = case is_leap_year(Year) of
+ true ->
+ 1;
+ false ->
+ 0
+ end,
+ {Month, Day} = year_day_to_date2(ExtraDay, DayOfYear),
+ {Month, Day + 1}.
+
+
+%% Note: 0 is the first day of the month
+%%
+-spec year_day_to_date2(0 | 1, day_of_year()) -> {month(), 0..30}.
+year_day_to_date2(_, Day) when Day < 31 ->
+ {1, Day};
+year_day_to_date2(E, Day) when 31 =< Day, Day < 59 + E ->
+ {2, Day - 31};
+year_day_to_date2(E, Day) when 59 + E =< Day, Day < 90 + E ->
+ {3, Day - (59 + E)};
+year_day_to_date2(E, Day) when 90 + E =< Day, Day < 120 + E ->
+ {4, Day - (90 + E)};
+year_day_to_date2(E, Day) when 120 + E =< Day, Day < 151 + E ->
+ {5, Day - (120 + E)};
+year_day_to_date2(E, Day) when 151 + E =< Day, Day < 181 + E ->
+ {6, Day - (151 + E)};
+year_day_to_date2(E, Day) when 181 + E =< Day, Day < 212 + E ->
+ {7, Day - (181 + E)};
+year_day_to_date2(E, Day) when 212 + E =< Day, Day < 243 + E ->
+ {8, Day - (212 + E)};
+year_day_to_date2(E, Day) when 243 + E =< Day, Day < 273 + E ->
+ {9, Day - (243 + E)};
+year_day_to_date2(E, Day) when 273 + E =< Day, Day < 304 + E ->
+ {10, Day - (273 + E)};
+year_day_to_date2(E, Day) when 304 + E =< Day, Day < 334 + E ->
+ {11, Day - (304 + E)};
+year_day_to_date2(E, Day) when 334 + E =< Day ->
+ {12, Day - (334 + E)}.
+
+%% dy(Year)
+%%
+%% Days in previous years.
+%%
+-spec dy(integer()) -> non_neg_integer().
+dy(Y) when Y =< 0 ->
+ 0;
+dy(Y) ->
+ X = Y - 1,
+ (X div 4) - (X div 100) + (X div 400) +
+ X*?DAYS_PER_YEAR + ?DAYS_PER_LEAP_YEAR.
+
+%% dm(Month)
+%%
+%% Returns the total number of days in all months
+%% preceeding Month, for an ordinary year.
+%%
+-spec dm(month()) ->
+ 0 | 31 | 59 | 90 | 120 | 151 | 181 | 212 | 243 | 273 | 304 | 334.
+dm(1) -> 0; dm(2) -> 31; dm(3) -> 59; dm(4) -> 90;
+dm(5) -> 120; dm(6) -> 151; dm(7) -> 181; dm(8) -> 212;
+dm(9) -> 243; dm(10) -> 273; dm(11) -> 304; dm(12) -> 334.
+
+%% df(Year, Month)
+%%
+%% Accounts for an extra day in February if Year is
+%% a leap year, and if Month > 2.
+%%
+-spec df(year(), month()) -> 0 | 1.
+df(_, Month) when Month < 3 ->
+ 0;
+df(Year, _) ->
+ case is_leap_year(Year) of
+ true -> 1;
+ false -> 0
+ end.
diff --git a/lib/stdlib/src/dets.erl b/lib/stdlib/src/dets.erl
new file mode 100644
index 0000000000..7f1c13770b
--- /dev/null
+++ b/lib/stdlib/src/dets.erl
@@ -0,0 +1,2989 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dets).
+
+%% Disk based linear hashing lookup dictionary.
+
+%% Public.
+-export([all/0,
+ bchunk/2,
+ close/1,
+ delete/2,
+ delete_all_objects/1,
+ delete_object/2,
+ first/1,
+ foldl/3,
+ foldr/3,
+ from_ets/2,
+ info/1,
+ info/2,
+ init_table/2,
+ init_table/3,
+ insert/2,
+ insert_new/2,
+ is_compatible_bchunk_format/2,
+ is_dets_file/1,
+ lookup/2,
+ match/1,
+ match/2,
+ match/3,
+ match_delete/2,
+ match_object/1,
+ match_object/2,
+ match_object/3,
+ member/2,
+ next/2,
+ open_file/1,
+ open_file/2,
+ pid2name/1,
+ repair_continuation/2,
+ safe_fixtable/2,
+ select/1,
+ select/2,
+ select/3,
+ select_delete/2,
+ slot/2,
+ sync/1,
+ table/1,
+ table/2,
+ to_ets/2,
+ traverse/2,
+ update_counter/3]).
+
+%% Server export.
+-export([start/0, stop/0]).
+
+%% Internal exports.
+-export([istart_link/1, init/2, internal_open/3, add_user/3,
+ internal_close/1, remove_user/2,
+ system_continue/3, system_terminate/4, system_code_change/4]).
+
+%% Debug.
+-export([file_info/1,
+ fsck/1,
+ fsck/2,
+ get_head_field/2,
+ view/1,
+ where/2,
+ verbose/0,
+ verbose/1
+ ]).
+
+%% Not documented, or not ready for publication.
+-export([lookup_keys/2]).
+
+
+-compile({inline, [{einval,2},{badarg,2},{undefined,1},
+ {badarg_exit,2},{lookup_reply,2}]}).
+
+-include_lib("kernel/include/file.hrl").
+
+-include("dets.hrl").
+
+-type object() :: tuple().
+-type pattern() :: atom() | tuple().
+-type tab_name() :: atom() | reference().
+
+%%% This is the implementation of the mnesia file storage. Each (non
+%%% ram-copy) table is maintained in a corresponding .DAT file. The
+%%% dat file is organized as a segmented linear hashlist. The head of
+%%% the file with the split indicator, size etc is held in ram by the
+%%% server at all times.
+%%%
+%%% The parts specific for formats up to and including 8(c) are
+%%% implemented in dets_v8.erl, parts specific for format 9 are
+%%% implemented in dets_v9.erl.
+
+%% The method of hashing is the so called linear hashing algorithm
+%% with segments.
+%%
+%% Linear hashing:
+%%
+%% - n indicates next bucket to split (initially zero);
+%% - m is the size of the hash table
+%% - initially next = m and n = 0
+%%
+%% - to insert:
+%% - hash = key mod m
+%% - if hash < n then hash = key mod 2m
+%% - when the number of objects exceeds the initial size
+%% of the hash table, each insertion of an object
+%% causes bucket n to be split:
+%% - add a new bucket to the end of the table
+%% - redistribute the contents of bucket n
+%% using hash = key mod 2m
+%% - increment n
+%% - if n = m then m = 2m, n = 0
+%% - to search:
+%% hash = key mod m
+%% if hash < n then hash = key mod 2m
+%% do linear scan of the bucket
+%%
+
+%%% If a file error occurs on a working dets file, update_mode is set
+%%% to the error tuple. When in 'error' mode, the free lists are not
+%%% written, and a repair is forced next time the file is opened.
+
+-record(dets_cont, {
+ what, % object | bindings | select | bchunk
+ no_objs, % requested number of objects: default | integer() > 0
+ bin, % small chunk not consumed, or 'eof' at end-of-file
+ alloc, % the part of the file not yet scanned, mostly a binary
+ tab,
+ match_program % true | compiled_match_spec() | undefined
+ }).
+
+-record(open_args, {
+ file,
+ type,
+ keypos,
+ repair,
+ min_no_slots,
+ max_no_slots,
+ ram_file,
+ delayed_write,
+ auto_save,
+ access,
+ version,
+ debug
+ }).
+
+-define(PATTERN_TO_OBJECT_MATCH_SPEC(Pat), [{Pat,[],['$_']}]).
+-define(PATTERN_TO_BINDINGS_MATCH_SPEC(Pat), [{Pat,[],['$$']}]).
+-define(PATTERN_TO_TRUE_MATCH_SPEC(Pat), [{Pat,[],[true]}]).
+
+%%-define(DEBUGM(X, Y), io:format(X, Y)).
+-define(DEBUGM(X, Y), true).
+
+%%-define(DEBUGF(X,Y), io:format(X, Y)).
+-define(DEBUGF(X,Y), void).
+
+%%-define(PROFILE(C), C).
+-define(PROFILE(C), void).
+
+%%% Some further debug code was added in R12B-1 (stdlib-1.15.1):
+%%% - there is a new open_file() option 'debug';
+%%% - there is a new OS environment variable 'DETS_DEBUG';
+%%% - verbose(true) implies that info messages are written onto
+%%% the error log whenever an unsafe traversal is started.
+%%% The 'debug' mode (set by the open_file() option 'debug' or
+%%% by os:putenv("DETS_DEBUG", "true")) implies that the results of
+%%% calling pwrite() and pread() are tested to some extent. It also
+%%% means a considerable overhead when it comes to RAM usage. The
+%%% operation of Dets is also slowed down a bit. Note that in debug
+%%% mode terms will be output on the error logger.
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+
+add_user(Pid, Tab, Args) ->
+ req(Pid, {add_user, Tab, Args}).
+
+-spec all() -> [tab_name()].
+
+all() ->
+ dets_server:all().
+
+-type cont() :: #dets_cont{}.
+-spec bchunk(tab_name(), 'start' | cont()) ->
+ {cont(), binary() | tuple()} | '$end_of_table' | {'error', term()}.
+
+bchunk(Tab, start) ->
+ badarg(treq(Tab, {bchunk_init, Tab}), [Tab, start]);
+bchunk(Tab, #dets_cont{bin = eof, tab = Tab}) ->
+ '$end_of_table';
+bchunk(Tab, #dets_cont{what = bchunk, tab = Tab} = State) ->
+ badarg(treq(Tab, {bchunk, State}), [Tab, State]);
+bchunk(Tab, Term) ->
+ erlang:error(badarg, [Tab, Term]).
+
+-spec close(tab_name()) -> 'ok' | {'error', term()}.
+
+close(Tab) ->
+ case dets_server:close(Tab) of
+ badarg -> % Should not happen.
+ {error, not_owner}; % Backwards compatibility...
+ Reply ->
+ Reply
+ end.
+
+-spec delete(tab_name(), term()) -> 'ok' | {'error', term()}.
+
+delete(Tab, Key) ->
+ badarg(treq(Tab, {delete_key, [Key]}), [Tab, Key]).
+
+-spec delete_all_objects(tab_name()) -> 'ok' | {'error', term()}.
+
+delete_all_objects(Tab) ->
+ case treq(Tab, delete_all_objects) of
+ badarg ->
+ erlang:error(badarg, [Tab]);
+ fixed ->
+ match_delete(Tab, '_');
+ Reply ->
+ Reply
+ end.
+
+-spec delete_object(tab_name(), object()) -> 'ok' | {'error', term()}.
+
+delete_object(Tab, O) ->
+ badarg(treq(Tab, {delete_object, [O]}), [Tab, O]).
+
+%% Given a filename, fsck it. Debug.
+fsck(Fname) ->
+ fsck(Fname, default).
+
+fsck(Fname, Version) ->
+ catch begin
+ {ok, Fd, FH} = read_file_header(Fname, read, false),
+ ?DEBUGF("FileHeader: ~p~n", [FH]),
+ case (FH#fileheader.mod):check_file_header(FH, Fd) of
+ {error, not_closed} ->
+ fsck(Fd, make_ref(), Fname, FH, default, default, Version);
+ {ok, _Head, _Extra} ->
+ fsck(Fd, make_ref(), Fname, FH, default, default, Version);
+ Error ->
+ Error
+ end
+ end.
+
+-spec first(tab_name()) -> term() | '$end_of_table'.
+
+first(Tab) ->
+ badarg_exit(treq(Tab, first), [Tab]).
+
+-spec foldr(fun((object(), Acc) -> Acc), Acc, tab_name()) -> Acc | {'error', term()}.
+
+foldr(Fun, Acc, Tab) ->
+ foldl(Fun, Acc, Tab).
+
+-spec foldl(fun((object(), Acc) -> Acc), Acc, tab_name()) -> Acc | {'error', term()}.
+
+foldl(Fun, Acc, Tab) ->
+ Ref = make_ref(),
+ do_traverse(Fun, Acc, Tab, Ref).
+
+-spec from_ets(tab_name(), ets:tab()) -> 'ok' | {'error', term()}.
+
+from_ets(DTab, ETab) ->
+ ets:safe_fixtable(ETab, true),
+ Spec = ?PATTERN_TO_OBJECT_MATCH_SPEC('_'),
+ LC = ets:select(ETab, Spec, 100),
+ InitFun = from_ets_fun(LC, ETab),
+ Reply = treq(DTab, {initialize, InitFun, term, default}),
+ ets:safe_fixtable(ETab, false),
+ case Reply of
+ {thrown, Thrown} -> throw(Thrown);
+ Else -> badarg(Else, [DTab, ETab])
+ end.
+
+from_ets_fun(LC, ETab) ->
+ fun(close) ->
+ ok;
+ (read) when LC =:= '$end_of_table' ->
+ end_of_input;
+ (read) ->
+ {L, C} = LC,
+ {L, from_ets_fun(ets:select(C), ETab)}
+ end.
+
+info(Tab) ->
+ case catch dets_server:get_pid(Tab) of
+ {'EXIT', _Reason} ->
+ undefined;
+ Pid ->
+ undefined(req(Pid, info))
+ end.
+
+info(Tab, owner) ->
+ case catch dets_server:get_pid(Tab) of
+ Pid when is_pid(Pid) ->
+ Pid;
+ _ ->
+ undefined
+ end;
+info(Tab, users) -> % undocumented
+ case dets_server:users(Tab) of
+ [] ->
+ undefined;
+ Users ->
+ Users
+ end;
+info(Tab, Tag) ->
+ case catch dets_server:get_pid(Tab) of
+ {'EXIT', _Reason} ->
+ undefined;
+ Pid ->
+ undefined(req(Pid, {info, Tag}))
+ end.
+
+init_table(Tab, InitFun) ->
+ init_table(Tab, InitFun, []).
+
+init_table(Tab, InitFun, Options) when is_function(InitFun) ->
+ case options(Options, [format, min_no_slots]) of
+ {badarg,_} ->
+ erlang:error(badarg, [Tab, InitFun, Options]);
+ [Format, MinNoSlots] ->
+ case treq(Tab, {initialize, InitFun, Format, MinNoSlots}) of
+ {thrown, Thrown} -> throw(Thrown);
+ Else -> badarg(Else, [Tab, InitFun, Options])
+ end
+ end;
+init_table(Tab, InitFun, Options) ->
+ erlang:error(badarg, [Tab, InitFun, Options]).
+
+insert(Tab, Objs) when is_list(Objs) ->
+ badarg(treq(Tab, {insert, Objs}), [Tab, Objs]);
+insert(Tab, Obj) ->
+ badarg(treq(Tab, {insert, [Obj]}), [Tab, Obj]).
+
+insert_new(Tab, Objs) when is_list(Objs) ->
+ badarg(treq(Tab, {insert_new, Objs}), [Tab, Objs]);
+insert_new(Tab, Obj) ->
+ badarg(treq(Tab, {insert_new, [Obj]}), [Tab, Obj]).
+
+internal_close(Pid) ->
+ req(Pid, close).
+
+internal_open(Pid, Ref, Args) ->
+ req(Pid, {internal_open, Ref, Args}).
+
+is_compatible_bchunk_format(Tab, Term) ->
+ badarg(treq(Tab, {is_compatible_bchunk_format, Term}), [Tab, Term]).
+
+is_dets_file(FileName) ->
+ case catch read_file_header(FileName, read, false) of
+ {ok, Fd, FH} ->
+ file:close(Fd),
+ FH#fileheader.cookie =:= ?MAGIC;
+ {error, {tooshort, _}} ->
+ false;
+ {error, {not_a_dets_file, _}} ->
+ false;
+ Other ->
+ Other
+ end.
+
+lookup(Tab, Key) ->
+ badarg(treq(Tab, {lookup_keys, [Key]}), [Tab, Key]).
+
+%% Not public.
+lookup_keys(Tab, Keys) ->
+ case catch lists:usort(Keys) of
+ UKeys when is_list(UKeys), UKeys =/= [] ->
+ badarg(treq(Tab, {lookup_keys, UKeys}), [Tab, Keys]);
+ _Else ->
+ erlang:error(badarg, [Tab, Keys])
+ end.
+
+match(Tab, Pat) ->
+ badarg(safe_match(Tab, Pat, bindings), [Tab, Pat]).
+
+match(Tab, Pat, N) ->
+ badarg(init_chunk_match(Tab, Pat, bindings, N), [Tab, Pat, N]).
+
+match(State) when State#dets_cont.what =:= bindings ->
+ badarg(chunk_match(State), [State]);
+match(Term) ->
+ erlang:error(badarg, [Term]).
+
+-spec match_delete(tab_name(), pattern()) ->
+ non_neg_integer() | 'ok' | {'error', term()}.
+
+match_delete(Tab, Pat) ->
+ badarg(match_delete(Tab, Pat, delete), [Tab, Pat]).
+
+match_delete(Tab, Pat, What) ->
+ safe_fixtable(Tab, true),
+ case compile_match_spec(What, Pat) of
+ {Spec, MP} ->
+ Proc = dets_server:get_pid(Tab),
+ R = req(Proc, {match_delete_init, MP, Spec}),
+ do_match_delete(Tab, Proc, R, What, 0);
+ badarg ->
+ badarg
+ end.
+
+do_match_delete(Tab, _Proc, {done, N1}, select, N) ->
+ safe_fixtable(Tab, false),
+ N + N1;
+do_match_delete(Tab, _Proc, {done, _N1}, _What, _N) ->
+ safe_fixtable(Tab, false),
+ ok;
+do_match_delete(Tab, Proc, {cont, State, N1}, What, N) ->
+ do_match_delete(Tab, Proc, req(Proc, {match_delete, State}), What, N+N1);
+do_match_delete(Tab, _Proc, Error, _What, _N) ->
+ safe_fixtable(Tab, false),
+ Error.
+
+match_object(Tab, Pat) ->
+ badarg(safe_match(Tab, Pat, object), [Tab, Pat]).
+
+match_object(Tab, Pat, N) ->
+ badarg(init_chunk_match(Tab, Pat, object, N), [Tab, Pat, N]).
+
+match_object(State) when State#dets_cont.what =:= object ->
+ badarg(chunk_match(State), [State]);
+match_object(Term) ->
+ erlang:error(badarg, [Term]).
+
+member(Tab, Key) ->
+ badarg(treq(Tab, {member, Key}), [Tab, Key]).
+
+next(Tab, Key) ->
+ badarg_exit(treq(Tab, {next, Key}), [Tab, Key]).
+
+%% Assuming that a file already exists, open it with the
+%% parameters as already specified in the file itself.
+%% Return a ref leading to the file.
+open_file(File) ->
+ case dets_server:open_file(to_list(File)) of
+ badarg -> % Should not happen.
+ erlang:error(dets_process_died, [File]);
+ Reply ->
+ einval(Reply, [File])
+ end.
+
+open_file(Tab, Args) when is_list(Args) ->
+ case catch defaults(Tab, Args) of
+ OpenArgs when is_record(OpenArgs, open_args) ->
+ case dets_server:open_file(Tab, OpenArgs) of
+ badarg -> % Should not happen.
+ erlang:error(dets_process_died, [Tab, Args]);
+ Reply ->
+ einval(Reply, [Tab, Args])
+ end;
+ _ ->
+ erlang:error(badarg, [Tab, Args])
+ end;
+open_file(Tab, Arg) ->
+ open_file(Tab, [Arg]).
+
+pid2name(Pid) ->
+ dets_server:pid2name(Pid).
+
+remove_user(Pid, From) ->
+ req(Pid, {close, From}).
+
+repair_continuation(#dets_cont{match_program = B}=Cont, MS)
+ when is_binary(B) ->
+ case ets:is_compiled_ms(B) of
+ true ->
+ Cont;
+ false ->
+ Cont#dets_cont{match_program = ets:match_spec_compile(MS)}
+ end;
+repair_continuation(#dets_cont{}=Cont, _MS) ->
+ Cont;
+repair_continuation(T, MS) ->
+ erlang:error(badarg, [T, MS]).
+
+safe_fixtable(Tab, Bool) when Bool; not Bool ->
+ badarg(treq(Tab, {safe_fixtable, Bool}), [Tab, Bool]);
+safe_fixtable(Tab, Term) ->
+ erlang:error(badarg, [Tab, Term]).
+
+select(Tab, Pat) ->
+ badarg(safe_match(Tab, Pat, select), [Tab, Pat]).
+
+select(Tab, Pat, N) ->
+ badarg(init_chunk_match(Tab, Pat, select, N), [Tab, Pat, N]).
+
+select(State) when State#dets_cont.what =:= select ->
+ badarg(chunk_match(State), [State]);
+select(Term) ->
+ erlang:error(badarg, [Term]).
+
+select_delete(Tab, Pat) ->
+ badarg(match_delete(Tab, Pat, select), [Tab, Pat]).
+
+slot(Tab, Slot) when is_integer(Slot), Slot >= 0 ->
+ badarg(treq(Tab, {slot, Slot}), [Tab, Slot]);
+slot(Tab, Term) ->
+ erlang:error(badarg, [Tab, Term]).
+
+start() ->
+ dets_server:start().
+
+stop() ->
+ dets_server:stop().
+
+istart_link(Server) ->
+ {ok, proc_lib:spawn_link(dets, init, [self(), Server])}.
+
+sync(Tab) ->
+ badarg(treq(Tab, sync), [Tab]).
+
+table(Tab) ->
+ table(Tab, []).
+
+table(Tab, Opts) ->
+ case options(Opts, [traverse, n_objects]) of
+ {badarg,_} ->
+ erlang:error(badarg, [Tab, Opts]);
+ [Traverse, NObjs] ->
+ TF = case Traverse of
+ first_next ->
+ fun() -> qlc_next(Tab, first(Tab)) end;
+ select ->
+ fun(MS) -> qlc_select(select(Tab, MS, NObjs)) end;
+ {select, MS} ->
+ fun() -> qlc_select(select(Tab, MS, NObjs)) end
+ end,
+ PreFun = fun(_) -> safe_fixtable(Tab, true) end,
+ PostFun = fun() -> safe_fixtable(Tab, false) end,
+ InfoFun = fun(Tag) -> table_info(Tab, Tag) end,
+ %% lookup_keys is not public, but convenient
+ LookupFun =
+ case Traverse of
+ {select, _MS} ->
+ undefined;
+ _ ->
+ fun(_KeyPos, [K]) -> lookup(Tab, K);
+ (_KeyPos, Ks) -> lookup_keys(Tab, Ks)
+ end
+ end,
+ FormatFun =
+ fun({all, _NElements, _ElementFun}) ->
+ As = [Tab | [Opts || _ <- [[]], Opts =/= []]],
+ {?MODULE, table, As};
+ ({match_spec, MS}) ->
+ {?MODULE, table, [Tab, [{traverse, {select, MS}} |
+ listify(Opts)]]};
+ ({lookup, _KeyPos, [Value], _NElements, ElementFun}) ->
+ io_lib:format("~w:lookup(~w, ~w)",
+ [?MODULE, Tab, ElementFun(Value)]);
+ ({lookup, _KeyPos, Values, _NElements, ElementFun}) ->
+ Vals = [ElementFun(V) || V <- Values],
+ io_lib:format("lists:flatmap(fun(V) -> "
+ "~w:lookup(~w, V) end, ~w)",
+ [?MODULE, Tab, Vals])
+ end,
+ qlc:table(TF, [{pre_fun, PreFun}, {post_fun, PostFun},
+ {info_fun, InfoFun}, {format_fun, FormatFun},
+ {key_equality, '=:='},
+ {lookup_fun, LookupFun}])
+ end.
+
+qlc_next(_Tab, '$end_of_table') ->
+ [];
+qlc_next(Tab, Key) ->
+ case lookup(Tab, Key) of
+ Objects when is_list(Objects) ->
+ Objects ++ fun() -> qlc_next(Tab, next(Tab, Key)) end;
+ Error ->
+ %% Do what first and next do.
+ exit(Error)
+ end.
+
+qlc_select('$end_of_table') ->
+ [];
+qlc_select({Objects, Cont}) when is_list(Objects) ->
+ Objects ++ fun() -> qlc_select(select(Cont)) end;
+qlc_select(Error) ->
+ Error.
+
+table_info(Tab, num_of_objects) ->
+ info(Tab, size);
+table_info(Tab, keypos) ->
+ info(Tab, keypos);
+table_info(Tab, is_unique_objects) ->
+ info(Tab, type) =/= duplicate_bag;
+table_info(_Tab, _) ->
+ undefined.
+
+%% End of table/2.
+
+to_ets(DTab, ETab) ->
+ case ets:info(ETab, protection) of
+ undefined ->
+ erlang:error(badarg, [DTab, ETab]);
+ _ ->
+ Fun = fun(X, T) -> true = ets:insert(T, X), T end,
+ foldl(Fun, ETab, DTab)
+ end.
+
+traverse(Tab, Fun) ->
+ Ref = make_ref(),
+ TFun =
+ fun(O, Acc) ->
+ case Fun(O) of
+ continue ->
+ Acc;
+ {continue, Val} ->
+ [Val | Acc];
+ {done, Value} ->
+ throw({Ref, [Value | Acc]});
+ Other ->
+ throw({Ref, Other})
+ end
+ end,
+ do_traverse(TFun, [], Tab, Ref).
+
+update_counter(Tab, Key, C) ->
+ badarg(treq(Tab, {update_counter, Key, C}), [Tab, Key, C]).
+
+verbose() ->
+ verbose(true).
+
+verbose(What) ->
+ ok = dets_server:verbose(What),
+ All = dets_server:all(),
+ Fun = fun(Tab) -> treq(Tab, {set_verbose, What}) end,
+ lists:foreach(Fun, All),
+ All.
+
+%% Where in the (open) table is Object located?
+%% The address of the first matching object is returned.
+%% Format 9 returns the address of the object collection.
+%% -> {ok, Address} | false
+where(Tab, Object) ->
+ badarg(treq(Tab, {where, Object}), [Tab, Object]).
+
+do_traverse(Fun, Acc, Tab, Ref) ->
+ safe_fixtable(Tab, true),
+ Proc = dets_server:get_pid(Tab),
+ try
+ do_trav(Proc, Acc, Fun)
+ catch {Ref, Result} ->
+ Result
+ after
+ safe_fixtable(Tab, false)
+ end.
+
+do_trav(Proc, Acc, Fun) ->
+ {Spec, MP} = compile_match_spec(object, '_'),
+ %% MP not used
+ case req(Proc, {match, MP, Spec, default}) of
+ {cont, State} ->
+ do_trav(State, Proc, Acc, Fun);
+ Error ->
+ Error
+ end.
+
+do_trav(#dets_cont{bin = eof}, _Proc, Acc, _Fun) ->
+ Acc;
+do_trav(State, Proc, Acc, Fun) ->
+ case req(Proc, {match_init, State}) of
+ {cont, {Bins, NewState}} ->
+ do_trav_bins(NewState, Proc, Acc, Fun, lists:reverse(Bins));
+ Error ->
+ Error
+ end.
+
+do_trav_bins(State, Proc, Acc, Fun, []) ->
+ do_trav(State, Proc, Acc, Fun);
+do_trav_bins(State, Proc, Acc, Fun, [Bin | Bins]) ->
+ %% Unpack one binary at a time, using the client's heap.
+ case catch binary_to_term(Bin) of
+ {'EXIT', _} ->
+ req(Proc, {corrupt, dets_utils:bad_object(do_trav_bins, Bin)});
+ Term ->
+ NewAcc = Fun(Term, Acc),
+ do_trav_bins(State, Proc, NewAcc, Fun, Bins)
+ end.
+
+safe_match(Tab, Pat, What) ->
+ safe_fixtable(Tab, true),
+ R = do_safe_match(init_chunk_match(Tab, Pat, What, default), []),
+ safe_fixtable(Tab, false),
+ R.
+
+do_safe_match({error, Error}, _L) ->
+ {error, Error};
+do_safe_match({L, C}, LL) ->
+ do_safe_match(chunk_match(C), L++LL);
+do_safe_match('$end_of_table', L) ->
+ L;
+do_safe_match(badarg, _L) ->
+ badarg.
+
+%% What = object | bindings | select
+init_chunk_match(Tab, Pat, What, N) when is_integer(N), N >= 0;
+ N =:= default ->
+ case compile_match_spec(What, Pat) of
+ {Spec, MP} ->
+ case req(dets_server:get_pid(Tab), {match, MP, Spec, N}) of
+ {done, L} ->
+ {L, #dets_cont{tab = Tab, what = What, bin = eof}};
+ {cont, State} ->
+ chunk_match(State#dets_cont{what = What, tab = Tab});
+ Error ->
+ Error
+ end;
+ badarg ->
+ badarg
+ end;
+init_chunk_match(_Tab, _Pat, _What, _) ->
+ badarg.
+
+chunk_match(State) ->
+ case catch dets_server:get_pid(State#dets_cont.tab) of
+ {'EXIT', _Reason} ->
+ badarg;
+ _Proc when State#dets_cont.bin =:= eof ->
+ '$end_of_table';
+ Proc ->
+ case req(Proc, {match_init, State}) of
+ {cont, {Bins, NewState}} ->
+ MP = NewState#dets_cont.match_program,
+ case catch do_foldl_bins(Bins, MP) of
+ {'EXIT', _} ->
+ case ets:is_compiled_ms(MP) of
+ true ->
+ Bad = dets_utils:bad_object(chunk_match,
+ Bins),
+ req(Proc, {corrupt, Bad});
+ false ->
+ badarg
+ end;
+ [] ->
+ chunk_match(NewState);
+ Terms ->
+ {Terms, NewState}
+ end;
+ Error ->
+ Error
+ end
+ end.
+
+do_foldl_bins(Bins, true) ->
+ foldl_bins(Bins, []);
+do_foldl_bins(Bins, MP) ->
+ foldl_bins(Bins, MP, []).
+
+foldl_bins([], Terms) ->
+ %% Preserve time order (version 9).
+ Terms;
+foldl_bins([Bin | Bins], Terms) ->
+ foldl_bins(Bins, [binary_to_term(Bin) | Terms]).
+
+foldl_bins([], _MP, Terms) ->
+ %% Preserve time order (version 9).
+ Terms;
+foldl_bins([Bin | Bins], MP, Terms) ->
+ Term = binary_to_term(Bin),
+ case ets:match_spec_run([Term], MP) of
+ [] ->
+ foldl_bins(Bins, MP, Terms);
+ [Result] ->
+ foldl_bins(Bins, MP, [Result | Terms])
+ end.
+
+%% -> {Spec, binary()} | badarg
+compile_match_spec(select, ?PATTERN_TO_OBJECT_MATCH_SPEC('_') = Spec) ->
+ {Spec, true};
+compile_match_spec(select, Spec) ->
+ case catch ets:match_spec_compile(Spec) of
+ X when is_binary(X) ->
+ {Spec, X};
+ _ ->
+ badarg
+ end;
+compile_match_spec(object, Pat) ->
+ compile_match_spec(select, ?PATTERN_TO_OBJECT_MATCH_SPEC(Pat));
+compile_match_spec(bindings, Pat) ->
+ compile_match_spec(select, ?PATTERN_TO_BINDINGS_MATCH_SPEC(Pat));
+compile_match_spec(delete, Pat) ->
+ compile_match_spec(select, ?PATTERN_TO_TRUE_MATCH_SPEC(Pat)).
+
+%% Process the args list as provided to open_file/2.
+defaults(Tab, Args) ->
+ Defaults0 = #open_args{file = to_list(Tab),
+ type = set,
+ keypos = 1,
+ repair = true,
+ min_no_slots = default,
+ max_no_slots = default,
+ ram_file = false,
+ delayed_write = ?DEFAULT_CACHE,
+ auto_save = timer:minutes(?DEFAULT_AUTOSAVE),
+ access = read_write,
+ version = default,
+ debug = false},
+ Fun = fun repl/2,
+ Defaults = lists:foldl(Fun, Defaults0, Args),
+ case Defaults#open_args.version of
+ 8 ->
+ Defaults#open_args{max_no_slots = default};
+ _ ->
+ is_comp_min_max(Defaults)
+ end.
+
+to_list(T) when is_atom(T) -> atom_to_list(T);
+to_list(T) -> T.
+
+repl({access, A}, Defs) ->
+ mem(A, [read, read_write]),
+ Defs#open_args{access = A};
+repl({auto_save, Int}, Defs) when is_integer(Int), Int >= 0 ->
+ Defs#open_args{auto_save = Int};
+repl({auto_save, infinity}, Defs) ->
+ Defs#open_args{auto_save =infinity};
+repl({cache_size, Int}, Defs) when is_integer(Int), Int >= 0 ->
+ %% Recognized, but ignored.
+ Defs;
+repl({cache_size, infinity}, Defs) ->
+ Defs;
+repl({delayed_write, default}, Defs) ->
+ Defs#open_args{delayed_write = ?DEFAULT_CACHE};
+repl({delayed_write, {Delay,Size} = C}, Defs)
+ when is_integer(Delay), Delay >= 0, is_integer(Size), Size >= 0 ->
+ Defs#open_args{delayed_write = C};
+repl({estimated_no_objects, I}, Defs) ->
+ repl({min_no_slots, I}, Defs);
+repl({file, File}, Defs) ->
+ Defs#open_args{file = to_list(File)};
+repl({keypos, P}, Defs) when is_integer(P), P > 0 ->
+ Defs#open_args{keypos =P};
+repl({max_no_slots, I}, Defs) ->
+ %% Version 9 only.
+ MaxSlots = is_max_no_slots(I),
+ Defs#open_args{max_no_slots = MaxSlots};
+repl({min_no_slots, I}, Defs) ->
+ MinSlots = is_min_no_slots(I),
+ Defs#open_args{min_no_slots = MinSlots};
+repl({ram_file, Bool}, Defs) ->
+ mem(Bool, [true, false]),
+ Defs#open_args{ram_file = Bool};
+repl({repair, T}, Defs) ->
+ mem(T, [true, false, force]),
+ Defs#open_args{repair = T};
+repl({type, T}, Defs) ->
+ mem(T, [set, bag, duplicate_bag]),
+ Defs#open_args{type =T};
+repl({version, Version}, Defs) ->
+ V = is_version(Version),
+ Defs#open_args{version = V};
+repl({debug, Bool}, Defs) ->
+ %% Not documented.
+ mem(Bool, [true, false]),
+ Defs#open_args{debug = Bool};
+repl({_, _}, _) ->
+ exit(badarg).
+
+is_min_no_slots(default) -> default;
+is_min_no_slots(I) when is_integer(I), I >= ?DEFAULT_MIN_NO_SLOTS -> I;
+is_min_no_slots(I) when is_integer(I), I >= 0 -> ?DEFAULT_MIN_NO_SLOTS.
+
+is_max_no_slots(default) -> default;
+is_max_no_slots(I) when is_integer(I), I > 0, I < 1 bsl 31 -> I.
+
+is_comp_min_max(Defs) ->
+ #open_args{max_no_slots = Max, min_no_slots = Min, version = V} = Defs,
+ case V of
+ _ when Min =:= default -> Defs;
+ _ when Max =:= default -> Defs;
+ _ -> true = Min =< Max, Defs
+ end.
+
+is_version(default) -> default;
+is_version(8) -> 8;
+is_version(9) -> 9.
+
+mem(X, L) ->
+ case lists:member(X, L) of
+ true -> true;
+ false -> exit(badarg)
+ end.
+
+options(Options, Keys) when is_list(Options) ->
+ options(Options, Keys, []);
+options(Option, Keys) ->
+ options([Option], Keys, []).
+
+options(Options, [Key | Keys], L) when is_list(Options) ->
+ V = case lists:keysearch(Key, 1, Options) of
+ {value, {format, Format}} when Format =:= term;
+ Format =:= bchunk ->
+ {ok, Format};
+ {value, {min_no_slots, I}} ->
+ case catch is_min_no_slots(I) of
+ {'EXIT', _} -> badarg;
+ MinNoSlots -> {ok, MinNoSlots}
+ end;
+ {value, {n_objects, default}} ->
+ {ok, default_option(Key)};
+ {value, {n_objects, NObjs}} when is_integer(NObjs),
+ NObjs >= 1 ->
+ {ok, NObjs};
+ {value, {traverse, select}} ->
+ {ok, select};
+ {value, {traverse, {select, MS}}} ->
+ {ok, {select, MS}};
+ {value, {traverse, first_next}} ->
+ {ok, first_next};
+ {value, {Key, _}} ->
+ badarg;
+ false ->
+ Default = default_option(Key),
+ {ok, Default}
+ end,
+ case V of
+ badarg ->
+ {badarg, Key};
+ {ok, Value} ->
+ NewOptions = lists:keydelete(Key, 1, Options),
+ options(NewOptions, Keys, [Value | L])
+ end;
+options([], [], L) ->
+ lists:reverse(L);
+options(Options, _, _L) ->
+ {badarg,Options}.
+
+default_option(format) -> term;
+default_option(min_no_slots) -> default;
+default_option(traverse) -> select;
+default_option(n_objects) -> default.
+
+listify(L) when is_list(L) ->
+ L;
+listify(T) ->
+ [T].
+
+treq(Tab, R) ->
+ case catch dets_server:get_pid(Tab) of
+ Pid when is_pid(Pid) ->
+ req(Pid, R);
+ _ ->
+ badarg
+ end.
+
+req(Proc, R) ->
+ Ref = erlang:monitor(process, Proc),
+ Proc ! ?DETS_CALL(self(), R),
+ receive
+ {'DOWN', Ref, process, Proc, _Info} ->
+ badarg;
+ {Proc, Reply} ->
+ erlang:demonitor(Ref),
+ receive
+ {'DOWN', Ref, process, Proc, _Reason} ->
+ Reply
+ after 0 ->
+ Reply
+ end
+ end.
+
+%% Inlined.
+einval({error, {file_error, _, einval}}, A) ->
+ erlang:error(badarg, A);
+einval({error, {file_error, _, badarg}}, A) ->
+ erlang:error(badarg, A);
+einval(Reply, _A) ->
+ Reply.
+
+%% Inlined.
+badarg(badarg, A) ->
+ erlang:error(badarg, A);
+badarg(Reply, _A) ->
+ Reply.
+
+%% Inlined.
+undefined(badarg) ->
+ undefined;
+undefined(Reply) ->
+ Reply.
+
+%% Inlined.
+badarg_exit(badarg, A) ->
+ erlang:error(badarg, A);
+badarg_exit({ok, Reply}, _A) ->
+ Reply;
+badarg_exit(Reply, _A) ->
+ exit(Reply).
+
+%%%-----------------------------------------------------------------
+%%% Server functions
+%%%-----------------------------------------------------------------
+
+init(Parent, Server) ->
+ process_flag(trap_exit, true),
+ open_file_loop(#head{parent = Parent, server = Server}).
+
+open_file_loop(Head) ->
+ open_file_loop(Head, 0).
+
+open_file_loop(Head, N) when element(1, Head#head.update_mode) =:= error ->
+ open_file_loop2(Head, N);
+open_file_loop(Head, N) ->
+ receive
+ %% When the table is fixed it can be assumed that at least one
+ %% traversal is in progress. To speed the traversal up three
+ %% things have been done:
+ %% - prioritize match_init, bchunk, next, and match_delete_init;
+ %% - do not peek the message queue for updates;
+ %% - wait 1 ms after each update.
+ %% next is normally followed by lookup, but since lookup is also
+ %% used when not traversing the table, it is not prioritized.
+ ?DETS_CALL(From, {match_init, _State} = Op) ->
+ do_apply_op(Op, From, Head, N);
+ ?DETS_CALL(From, {bchunk, _State} = Op) ->
+ do_apply_op(Op, From, Head, N);
+ ?DETS_CALL(From, {next, _Key} = Op) ->
+ do_apply_op(Op, From, Head, N);
+ ?DETS_CALL(From, {match_delete_init, _MP, _Spec} = Op) ->
+ do_apply_op(Op, From, Head, N);
+ {'EXIT', Pid, Reason} when Pid =:= Head#head.parent ->
+ %% Parent orders shutdown.
+ _NewHead = do_stop(Head),
+ exit(Reason);
+ {'EXIT', Pid, Reason} when Pid =:= Head#head.server ->
+ %% The server is gone.
+ _NewHead = do_stop(Head),
+ exit(Reason);
+ {'EXIT', Pid, _Reason} ->
+ %% A process fixing the table exits.
+ H2 = remove_fix(Head, Pid, close),
+ open_file_loop(H2, N);
+ {system, From, Req} ->
+ sys:handle_system_msg(Req, From, Head#head.parent,
+ ?MODULE, [], Head)
+ after 0 ->
+ open_file_loop2(Head, N)
+ end.
+
+open_file_loop2(Head, N) ->
+ receive
+ ?DETS_CALL(From, Op) ->
+ do_apply_op(Op, From, Head, N);
+ {'EXIT', Pid, Reason} when Pid =:= Head#head.parent ->
+ %% Parent orders shutdown.
+ _NewHead = do_stop(Head),
+ exit(Reason);
+ {'EXIT', Pid, Reason} when Pid =:= Head#head.server ->
+ %% The server is gone.
+ _NewHead = do_stop(Head),
+ exit(Reason);
+ {'EXIT', Pid, _Reason} ->
+ %% A process fixing the table exits.
+ H2 = remove_fix(Head, Pid, close),
+ open_file_loop(H2, N);
+ {system, From, Req} ->
+ sys:handle_system_msg(Req, From, Head#head.parent,
+ ?MODULE, [], Head);
+ Message ->
+ error_logger:format("** dets: unexpected message"
+ "(ignored): ~w~n", [Message]),
+ open_file_loop(Head, N)
+ end.
+
+do_apply_op(Op, From, Head, N) ->
+ try apply_op(Op, From, Head, N) of
+ ok ->
+ open_file_loop(Head, N);
+ {N2, H2} when is_record(H2, head), is_integer(N2) ->
+ open_file_loop(H2, N2);
+ H2 when is_record(H2, head) ->
+ open_file_loop(H2, N)
+ catch
+ exit:normal ->
+ exit(normal);
+ _:Bad ->
+ Name = Head#head.name,
+ case dets_utils:debug_mode() of
+ true ->
+ %% If stream_op/5 found more requests, this is not
+ %% the last operation.
+ error_logger:format
+ ("** dets: Bug was found when accessing table ~w,~n"
+ "** dets: operation was ~p and reply was ~w.~n"
+ "** dets: Stacktrace: ~w~n",
+ [Name, Op, Bad, erlang:get_stacktrace()]);
+ false ->
+ error_logger:format
+ ("** dets: Bug was found when accessing table ~w~n",
+ [Name])
+ end,
+ if
+ From =/= self() ->
+ From ! {self(), {error, {dets_bug, Name, Op, Bad}}};
+ true -> % auto_save | may_grow | {delayed_write, _}
+ ok
+ end,
+ open_file_loop(Head, N)
+ end.
+
+apply_op(Op, From, Head, N) ->
+ case Op of
+ {add_user, Tab, OpenArgs}->
+ #open_args{file = Fname, type = Type, keypos = Keypos,
+ ram_file = Ram, access = Access,
+ version = Version} = OpenArgs,
+ VersionOK = (Version =:= default) or
+ (Head#head.version =:= Version),
+ %% min_no_slots and max_no_slots are not tested
+ Res = if
+ Tab =:= Head#head.name,
+ Head#head.keypos =:= Keypos,
+ Head#head.type =:= Type,
+ Head#head.ram_file =:= Ram,
+ Head#head.access =:= Access,
+ VersionOK,
+ Fname =:= Head#head.filename ->
+ ok;
+ true ->
+ err({error, incompatible_arguments})
+ end,
+ From ! {self(), Res},
+ ok;
+ auto_save ->
+ case Head#head.update_mode of
+ saved ->
+ Head;
+ {error, _Reason} ->
+ Head;
+ _Dirty when N =:= 0 -> % dirty or new_dirty
+ %% The updates seems to have declined
+ dets_utils:vformat("** dets: Auto save of ~p\n",
+ [Head#head.name]),
+ {NewHead, _Res} = perform_save(Head, true),
+ erlang:garbage_collect(),
+ {0, NewHead};
+ dirty ->
+ %% Reset counter and try later
+ start_auto_save_timer(Head),
+ {0, Head}
+ end;
+ close ->
+ From ! {self(), fclose(Head)},
+ _NewHead = unlink_fixing_procs(Head),
+ ?PROFILE(ep:done()),
+ exit(normal);
+ {close, Pid} ->
+ %% Used from dets_server when Pid has closed the table,
+ %% but the table is still opened by some process.
+ NewHead = remove_fix(Head, Pid, close),
+ From ! {self(), status(NewHead)},
+ NewHead;
+ {corrupt, Reason} ->
+ {H2, Error} = dets_utils:corrupt_reason(Head, Reason),
+ From ! {self(), Error},
+ H2;
+ {delayed_write, WrTime} ->
+ delayed_write(Head, WrTime);
+ info ->
+ {H2, Res} = finfo(Head),
+ From ! {self(), Res},
+ H2;
+ {info, Tag} ->
+ {H2, Res} = finfo(Head, Tag),
+ From ! {self(), Res},
+ H2;
+ {is_compatible_bchunk_format, Term} ->
+ Res = test_bchunk_format(Head, Term),
+ From ! {self(), Res},
+ ok;
+ {internal_open, Ref, Args} ->
+ ?PROFILE(ep:do()),
+ case do_open_file(Args, Head#head.parent, Head#head.server,Ref) of
+ {ok, H2} ->
+ From ! {self(), ok},
+ H2;
+ Error ->
+ From ! {self(), Error},
+ exit(normal)
+ end;
+ may_grow when Head#head.update_mode =/= saved ->
+ if
+ Head#head.update_mode =:= dirty ->
+ %% Won't grow more if the table is full.
+ {H2, _Res} =
+ (Head#head.mod):may_grow(Head, 0, many_times),
+ {N + 1, H2};
+ true ->
+ ok
+ end;
+ {set_verbose, What} ->
+ set_verbose(What),
+ From ! {self(), ok},
+ ok;
+ {where, Object} ->
+ {H2, Res} = where_is_object(Head, Object),
+ From ! {self(), Res},
+ H2;
+ _Message when element(1, Head#head.update_mode) =:= error ->
+ From ! {self(), status(Head)},
+ ok;
+ %% The following messages assume that the status of the table is OK.
+ {bchunk_init, Tab} ->
+ {H2, Res} = do_bchunk_init(Head, Tab),
+ From ! {self(), Res},
+ H2;
+ {bchunk, State} ->
+ {H2, Res} = do_bchunk(Head, State),
+ From ! {self(), Res},
+ H2;
+ delete_all_objects ->
+ {H2, Res} = fdelete_all_objects(Head),
+ From ! {self(), Res},
+ erlang:garbage_collect(),
+ {0, H2};
+ {delete_key, Keys} when Head#head.update_mode =:= dirty ->
+ if
+ Head#head.version =:= 8 ->
+ {H2, Res} = fdelete_key(Head, Keys),
+ From ! {self(), Res},
+ {N + 1, H2};
+ true ->
+ stream_op(Op, From, [], Head, N)
+ end;
+ {delete_object, Objs} when Head#head.update_mode =:= dirty ->
+ case check_objects(Objs, Head#head.keypos) of
+ true when Head#head.version =:= 8 ->
+ {H2, Res} = fdelete_object(Head, Objs),
+ From ! {self(), Res},
+ {N + 1, H2};
+ true ->
+ stream_op(Op, From, [], Head, N);
+ false ->
+ From ! {self(), badarg},
+ ok
+ end;
+ first ->
+ {H2, Res} = ffirst(Head),
+ From ! {self(), Res},
+ H2;
+ {initialize, InitFun, Format, MinNoSlots} ->
+ {H2, Res} = finit(Head, InitFun, Format, MinNoSlots),
+ From ! {self(), Res},
+ erlang:garbage_collect(),
+ H2;
+ {insert, Objs} when Head#head.update_mode =:= dirty ->
+ case check_objects(Objs, Head#head.keypos) of
+ true when Head#head.version =:= 8 ->
+ {H2, Res} = finsert(Head, Objs),
+ From ! {self(), Res},
+ {N + 1, H2};
+ true ->
+ stream_op(Op, From, [], Head, N);
+ false ->
+ From ! {self(), badarg},
+ ok
+ end;
+ {insert_new, Objs} when Head#head.update_mode =:= dirty ->
+ {H2, Res} = finsert_new(Head, Objs),
+ From ! {self(), Res},
+ {N + 1, H2};
+ {lookup_keys, Keys} when Head#head.version =:= 8 ->
+ {H2, Res} = flookup_keys(Head, Keys),
+ From ! {self(), Res},
+ H2;
+ {lookup_keys, _Keys} ->
+ stream_op(Op, From, [], Head, N);
+ {match_init, State} ->
+ {H2, Res} = fmatch_init(Head, State),
+ From ! {self(), Res},
+ H2;
+ {match, MP, Spec, NObjs} ->
+ {H2, Res} = fmatch(Head, MP, Spec, NObjs),
+ From ! {self(), Res},
+ H2;
+ {member, Key} when Head#head.version =:= 8 ->
+ {H2, Res} = fmember(Head, Key),
+ From ! {self(), Res},
+ H2;
+ {member, _Key} = Op ->
+ stream_op(Op, From, [], Head, N);
+ {next, Key} ->
+ {H2, Res} = fnext(Head, Key),
+ From ! {self(), Res},
+ H2;
+ {match_delete, State} when Head#head.update_mode =:= dirty ->
+ {H2, Res} = fmatch_delete(Head, State),
+ From ! {self(), Res},
+ {N + 1, H2};
+ {match_delete_init, MP, Spec} when Head#head.update_mode =:= dirty ->
+ {H2, Res} = fmatch_delete_init(Head, MP, Spec),
+ From ! {self(), Res},
+ {N + 1, H2};
+ {safe_fixtable, Bool} ->
+ NewHead = do_safe_fixtable(Head, From, Bool),
+ From ! {self(), ok},
+ NewHead;
+ {slot, Slot} ->
+ {H2, Res} = fslot(Head, Slot),
+ From ! {self(), Res},
+ H2;
+ sync ->
+ {NewHead, Res} = perform_save(Head, true),
+ From ! {self(), Res},
+ erlang:garbage_collect(),
+ {0, NewHead};
+ {update_counter, Key, Incr} when Head#head.update_mode =:= dirty ->
+ {NewHead, Res} = do_update_counter(Head, Key, Incr),
+ From ! {self(), Res},
+ {N + 1, NewHead};
+ WriteOp when Head#head.update_mode =:= new_dirty ->
+ H2 = Head#head{update_mode = dirty},
+ apply_op(WriteOp, From, H2, 0);
+ WriteOp when Head#head.access =:= read_write,
+ Head#head.update_mode =:= saved ->
+ case catch (Head#head.mod):mark_dirty(Head) of
+ ok ->
+ start_auto_save_timer(Head),
+ H2 = Head#head{update_mode = dirty},
+ apply_op(WriteOp, From, H2, 0);
+ {NewHead, Error} when is_record(NewHead, head) ->
+ From ! {self(), Error},
+ NewHead
+ end;
+ WriteOp when is_tuple(WriteOp), Head#head.access =:= read ->
+ Reason = {access_mode, Head#head.filename},
+ From ! {self(), err({error, Reason})},
+ ok
+ end.
+
+start_auto_save_timer(Head) when Head#head.auto_save =:= infinity ->
+ ok;
+start_auto_save_timer(Head) ->
+ Millis = Head#head.auto_save,
+ erlang:send_after(Millis, self(), ?DETS_CALL(self(), auto_save)).
+
+%% Version 9: Peek the message queue and try to evaluate several
+%% lookup requests in parallel. Evalute delete_object, delete and
+%% insert as well.
+stream_op(Op, Pid, Pids, Head, N) ->
+ stream_op(Head, Pids, [], N, Pid, Op, Head#head.fixed).
+
+stream_loop(Head, Pids, C, N, false = Fxd) ->
+ receive
+ ?DETS_CALL(From, Message) ->
+ stream_op(Head, Pids, C, N, From, Message, Fxd)
+ after 0 ->
+ stream_end(Head, Pids, C, N, no_more)
+ end;
+stream_loop(Head, Pids, C, N, _Fxd) ->
+ stream_end(Head, Pids, C, N, no_more).
+
+stream_op(Head, Pids, C, N, Pid, {lookup_keys,Keys}, Fxd) ->
+ NC = [{{lookup,Pid},Keys} | C],
+ stream_loop(Head, Pids, NC, N, Fxd);
+stream_op(Head, Pids, C, N, Pid, {insert, _Objects} = Op, Fxd) ->
+ NC = [Op | C],
+ stream_loop(Head, [Pid | Pids], NC, N, Fxd);
+stream_op(Head, Pids, C, N, Pid, {insert_new, _Objects} = Op, Fxd) ->
+ NC = [Op | C],
+ stream_loop(Head, [Pid | Pids], NC, N, Fxd);
+stream_op(Head, Pids, C, N, Pid, {delete_key, _Keys} = Op, Fxd) ->
+ NC = [Op | C],
+ stream_loop(Head, [Pid | Pids], NC, N, Fxd);
+stream_op(Head, Pids, C, N, Pid, {delete_object, _Objects} = Op, Fxd) ->
+ NC = [Op | C],
+ stream_loop(Head, [Pid | Pids], NC, N, Fxd);
+stream_op(Head, Pids, C, N, Pid, {member, Key}, Fxd) ->
+ NC = [{{lookup,[Pid]},[Key]} | C],
+ stream_loop(Head, Pids, NC, N, Fxd);
+stream_op(Head, Pids, C, N, Pid, Op, _Fxd) ->
+ stream_end(Head, Pids, C, N, {Pid,Op}).
+
+stream_end(Head, Pids0, C, N, Next) ->
+ case catch update_cache(Head, lists:reverse(C)) of
+ {Head1, [], PwriteList} ->
+ stream_end1(Pids0, Next, N, C, Head1, PwriteList);
+ {Head1, Found, PwriteList} ->
+ %% Possibly an optimization: reply to lookup requests
+ %% first, then write stuff. This makes it possible for
+ %% clients to continue while the disk is accessed.
+ %% (Replies to lookup requests are sent earlier than
+ %% replies to delete and insert requests even if the
+ %% latter requests were made before the lookup requests,
+ %% which can be confusing.)
+ lookup_replies(Found),
+ stream_end1(Pids0, Next, N, C, Head1, PwriteList);
+ Head1 when is_record(Head1, head) ->
+ stream_end2(Pids0, Pids0, Next, N, C, Head1, ok);
+ {Head1, Error} when is_record(Head1, head) ->
+ %% Dig out the processes that did lookup or member.
+ Fun = fun({{lookup,[Pid]},_Keys}, L) -> [Pid | L];
+ ({{lookup,Pid},_Keys}, L) -> [Pid | L];
+ (_, L) -> L
+ end,
+ LPs0 = lists:foldl(Fun, [], C),
+ LPs = lists:usort(lists:flatten(LPs0)),
+ stream_end2(Pids0 ++ LPs, Pids0, Next, N, C, Head1, Error);
+ DetsError ->
+ throw(DetsError)
+ end.
+
+stream_end1(Pids, Next, N, C, Head, []) ->
+ stream_end2(Pids, Pids, Next, N, C, Head, ok);
+stream_end1(Pids, Next, N, C, Head, PwriteList) ->
+ {Head1, PR} = (catch dets_utils:pwrite(Head, PwriteList)),
+ stream_end2(Pids, Pids, Next, N, C, Head1, PR).
+
+stream_end2([Pid | Pids], Ps, Next, N, C, Head, Reply) ->
+ Pid ! {self(), Reply},
+ stream_end2(Pids, Ps, Next, N+1, C, Head, Reply);
+stream_end2([], Ps, no_more, N, C, Head, _Reply) ->
+ penalty(Head, Ps, C),
+ {N, Head};
+stream_end2([], _Ps, {From, Op}, N, _C, Head, _Reply) ->
+ apply_op(Op, From, Head, N).
+
+penalty(H, _Ps, _C) when H#head.fixed =:= false ->
+ ok;
+penalty(_H, _Ps, [{{lookup,_Pids},_Keys}]) ->
+ ok;
+penalty(#head{fixed = {_,[{Pid,_}]}}, [Pid], _C) ->
+ ok;
+penalty(_H, _Ps, _C) ->
+ timer:sleep(1).
+
+lookup_replies([{P,O}]) ->
+ lookup_reply(P, O);
+lookup_replies(Q) ->
+ [{P,O} | L] = dets_utils:family(Q),
+ lookup_replies(P, lists:append(O), L).
+
+lookup_replies(P, O, []) ->
+ lookup_reply(P, O);
+lookup_replies(P, O, [{P2,O2} | L]) ->
+ lookup_reply(P, O),
+ lookup_replies(P2, lists:append(O2), L).
+
+%% If a list of Pid then op was {member, Key}. Inlined.
+lookup_reply([P], O) ->
+ P ! {self(), O =/= []};
+lookup_reply(P, O) ->
+ P ! {self(), O}.
+
+%%-----------------------------------------------------------------
+%% Callback functions for system messages handling.
+%%-----------------------------------------------------------------
+system_continue(_Parent, _, Head) ->
+ open_file_loop(Head).
+
+system_terminate(Reason, _Parent, _, Head) ->
+ _NewHead = do_stop(Head),
+ exit(Reason).
+
+%%-----------------------------------------------------------------
+%% Code for upgrade.
+%%-----------------------------------------------------------------
+system_code_change(State, _Module, _OldVsn, _Extra) ->
+ {ok, State}.
+
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
+constants(FH, FileName) ->
+ Version = FH#fileheader.version,
+ if
+ Version =< 8 ->
+ dets_v8:constants();
+ Version =:= 9 ->
+ dets_v9:constants();
+ true ->
+ throw({error, {not_a_dets_file, FileName}})
+ end.
+
+%% -> {ok, Fd, fileheader()} | throw(Error)
+read_file_header(FileName, Access, RamFile) ->
+ BF = if
+ RamFile ->
+ case file:read_file(FileName) of
+ {ok, B} -> B;
+ Err -> dets_utils:file_error(FileName, Err)
+ end;
+ true ->
+ FileName
+ end,
+ {ok, Fd} = dets_utils:open(BF, open_args(Access, RamFile)),
+ {ok, <<Version:32>>} =
+ dets_utils:pread_close(Fd, FileName, ?FILE_FORMAT_VERSION_POS, 4),
+ if
+ Version =< 8 ->
+ dets_v8:read_file_header(Fd, FileName);
+ Version =:= 9 ->
+ dets_v9:read_file_header(Fd, FileName);
+ true ->
+ throw({error, {not_a_dets_file, FileName}})
+ end.
+
+fclose(Head) ->
+ {Head1, Res} = perform_save(Head, false),
+ case Head1#head.ram_file of
+ true ->
+ ignore;
+ false ->
+ dets_utils:stop_disk_map(),
+ file:close(Head1#head.fptr)
+ end,
+ Res.
+
+%% -> {NewHead, Res}
+perform_save(Head, DoSync) when Head#head.update_mode =:= dirty;
+ Head#head.update_mode =:= new_dirty ->
+ case catch begin
+ {Head1, []} = write_cache(Head),
+ {Head2, ok} = (Head1#head.mod):do_perform_save(Head1),
+ ok = ensure_written(Head2, DoSync),
+ {Head2#head{update_mode = saved}, ok}
+ end of
+ {NewHead, _} = Reply when is_record(NewHead, head) ->
+ Reply
+ end;
+perform_save(Head, _DoSync) ->
+ {Head, status(Head)}.
+
+ensure_written(Head, DoSync) when Head#head.ram_file ->
+ {ok, EOF} = dets_utils:position(Head, eof),
+ {ok, Bin} = dets_utils:pread(Head, 0, EOF, 0),
+ if
+ DoSync ->
+ dets_utils:write_file(Head, Bin);
+ not DoSync ->
+ case file:write_file(Head#head.filename, Bin) of
+ ok ->
+ ok;
+ Error ->
+ dets_utils:corrupt_file(Head, Error)
+ end
+ end;
+ensure_written(Head, true) when not Head#head.ram_file ->
+ dets_utils:sync(Head);
+ensure_written(Head, false) when not Head#head.ram_file ->
+ ok.
+
+%% -> {NewHead, {cont(), [binary()]}} | {NewHead, Error}
+do_bchunk_init(Head, Tab) ->
+ case catch write_cache(Head) of
+ {H2, []} ->
+ case (H2#head.mod):table_parameters(H2) of
+ undefined ->
+ {H2, {error, old_version}};
+ Parms ->
+ L = dets_utils:all_allocated(H2),
+ C0 = #dets_cont{no_objs = default, bin = <<>>, alloc = L},
+ BinParms = term_to_binary(Parms),
+ {H2, {C0#dets_cont{tab = Tab, what = bchunk}, [BinParms]}}
+ end;
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end.
+
+%% -> {NewHead, {cont(), [binary()]}} | {NewHead, Error}
+do_bchunk(Head, State) ->
+ case dets_v9:read_bchunks(Head, State#dets_cont.alloc) of
+ {error, Reason} ->
+ dets_utils:corrupt_reason(Head, Reason);
+ {finished, Bins} ->
+ {Head, {State#dets_cont{bin = eof}, Bins}};
+ {Bins, NewL} ->
+ {Head, {State#dets_cont{alloc = NewL}, Bins}}
+ end.
+
+%% -> {NewHead, Result}
+fdelete_all_objects(Head) when Head#head.fixed =:= false ->
+ case catch do_delete_all_objects(Head) of
+ {ok, NewHead} ->
+ start_auto_save_timer(NewHead),
+ {NewHead, ok};
+ {error, Reason} ->
+ dets_utils:corrupt_reason(Head, Reason)
+ end;
+fdelete_all_objects(Head) ->
+ {Head, fixed}.
+
+do_delete_all_objects(Head) ->
+ #head{fptr = Fd, name = Tab, filename = Fname, type = Type, keypos = Kp,
+ ram_file = Ram, auto_save = Auto, min_no_slots = MinSlots,
+ max_no_slots = MaxSlots, cache = Cache} = Head,
+ CacheSz = dets_utils:cache_size(Cache),
+ ok = dets_utils:truncate(Fd, Fname, bof),
+ (Head#head.mod):initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots,
+ Ram, CacheSz, Auto, true).
+
+%% -> {NewHead, Reply}, Reply = ok | Error.
+fdelete_key(Head, Keys) ->
+ do_delete(Head, Keys, delete_key).
+
+%% -> {NewHead, Reply}, Reply = ok | badarg | Error.
+fdelete_object(Head, Objects) ->
+ do_delete(Head, Objects, delete_object).
+
+ffirst(H) ->
+ Ref = make_ref(),
+ case catch {Ref, ffirst1(H)} of
+ {Ref, {NH, R}} ->
+ {NH, {ok, R}};
+ {NH, R} when is_record(NH, head) ->
+ {NH, {error, R}}
+ end.
+
+ffirst1(H) ->
+ check_safe_fixtable(H),
+ {NH, []} = write_cache(H),
+ ffirst(NH, 0).
+
+ffirst(H, Slot) ->
+ case (H#head.mod):slot_objs(H, Slot) of
+ '$end_of_table' -> {H, '$end_of_table'};
+ [] -> ffirst(H, Slot+1);
+ [X|_] -> {H, element(H#head.keypos, X)}
+ end.
+
+%% -> {NewHead, Reply}, Reply = ok | badarg | Error.
+finsert(Head, Objects) ->
+ case catch update_cache(Head, Objects, insert) of
+ {NewHead, []} ->
+ {NewHead, ok};
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end.
+
+%% -> {NewHead, Reply}, Reply = ok | badarg | Error.
+finsert_new(Head, Objects) ->
+ KeyPos = Head#head.keypos,
+ case catch lists:map(fun(Obj) -> element(KeyPos, Obj) end, Objects) of
+ Keys when is_list(Keys) ->
+ case catch update_cache(Head, Keys, {lookup, nopid}) of
+ {Head1, PidObjs} when is_list(PidObjs) ->
+ case lists:all(fun({_P,OL}) -> OL =:= [] end, PidObjs) of
+ true ->
+ case catch update_cache(Head1, Objects, insert) of
+ {NewHead, []} ->
+ {NewHead, true};
+ {NewHead, Error} when is_record(NewHead, head) ->
+ {NewHead, Error}
+ end;
+ false=Reply ->
+ {Head1, Reply}
+ end;
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end;
+ _ ->
+ {Head, badarg}
+ end.
+
+do_safe_fixtable(Head, Pid, true) ->
+ case Head#head.fixed of
+ false ->
+ link(Pid),
+ Fixed = {erlang:now(), [{Pid, 1}]},
+ Ftab = dets_utils:get_freelists(Head),
+ Head#head{fixed = Fixed, freelists = {Ftab, Ftab}};
+ {TimeStamp, Counters} ->
+ case lists:keysearch(Pid, 1, Counters) of
+ {value, {Pid, Counter}} -> % when Counter > 1
+ NewCounters = lists:keyreplace(Pid, 1, Counters,
+ {Pid, Counter+1}),
+ Head#head{fixed = {TimeStamp, NewCounters}};
+ false ->
+ link(Pid),
+ Fixed = {TimeStamp, [{Pid, 1} | Counters]},
+ Head#head{fixed = Fixed}
+ end
+ end;
+do_safe_fixtable(Head, Pid, false) ->
+ remove_fix(Head, Pid, false).
+
+remove_fix(Head, Pid, How) ->
+ case Head#head.fixed of
+ false ->
+ Head;
+ {TimeStamp, Counters} ->
+ case lists:keysearch(Pid, 1, Counters) of
+ %% How =:= close when Pid closes the table.
+ {value, {Pid, Counter}} when Counter =:= 1; How =:= close ->
+ unlink(Pid),
+ case lists:keydelete(Pid, 1, Counters) of
+ [] ->
+ check_growth(Head),
+ erlang:garbage_collect(),
+ Head#head{fixed = false,
+ freelists = dets_utils:get_freelists(Head)};
+ NewCounters ->
+ Head#head{fixed = {TimeStamp, NewCounters}}
+ end;
+ {value, {Pid, Counter}} ->
+ NewCounters = lists:keyreplace(Pid, 1, Counters,
+ {Pid, Counter-1}),
+ Head#head{fixed = {TimeStamp, NewCounters}};
+ false ->
+ Head
+ end
+ end.
+
+do_stop(Head) ->
+ unlink_fixing_procs(Head),
+ fclose(Head).
+
+unlink_fixing_procs(Head) ->
+ case Head#head.fixed of
+ false ->
+ Head;
+ {_, Counters} ->
+ lists:map(fun({Pid, _Counter}) -> unlink(Pid) end, Counters),
+ Head#head{fixed = false,
+ freelists = dets_utils:get_freelists(Head)}
+ end.
+
+check_growth(#head{access = read}) ->
+ ok;
+check_growth(Head) ->
+ NoThings = no_things(Head),
+ if
+ NoThings > Head#head.next ->
+ erlang:send_after(200, self(),
+ ?DETS_CALL(self(), may_grow)); % Catch up.
+ true ->
+ ok
+ end.
+
+finfo(H) ->
+ case catch write_cache(H) of
+ {H2, []} ->
+ Info = (catch [{type, H2#head.type},
+ {keypos, H2#head.keypos},
+ {size, H2#head.no_objects},
+ {file_size,
+ file_size(H2#head.fptr, H2#head.filename)},
+ {filename, H2#head.filename}]),
+ {H2, Info};
+ {H2, _} = HeadError when is_record(H2, head) ->
+ HeadError
+ end.
+
+finfo(H, access) -> {H, H#head.access};
+finfo(H, auto_save) -> {H, H#head.auto_save};
+finfo(H, bchunk_format) ->
+ case catch write_cache(H) of
+ {H2, []} ->
+ case (H2#head.mod):table_parameters(H2) of
+ undefined = Undef ->
+ {H2, Undef};
+ Parms ->
+ {H2, term_to_binary(Parms)}
+ end;
+ {H2, _} = HeadError when is_record(H2, head) ->
+ HeadError
+ end;
+finfo(H, delayed_write) -> % undocumented
+ {H, dets_utils:cache_size(H#head.cache)};
+finfo(H, filename) -> {H, H#head.filename};
+finfo(H, file_size) ->
+ case catch write_cache(H) of
+ {H2, []} ->
+ {H2, catch file_size(H#head.fptr, H#head.filename)};
+ {H2, _} = HeadError when is_record(H2, head) ->
+ HeadError
+ end;
+finfo(H, fixed) ->
+ %% true if fixtable/2 has been called
+ {H, not (H#head.fixed =:= false)};
+finfo(H, hash) -> {H, H#head.hash_bif};
+finfo(H, keypos) -> {H, H#head.keypos};
+finfo(H, memory) -> finfo(H, file_size);
+finfo(H, no_objects) -> finfo(H, size);
+finfo(H, no_keys) ->
+ case catch write_cache(H) of
+ {H2, []} ->
+ {H2, H2#head.no_keys};
+ {H2, _} = HeadError when is_record(H2, head) ->
+ HeadError
+ end;
+finfo(H, no_slots) -> {H, (H#head.mod):no_slots(H)};
+finfo(H, pid) -> {H, self()};
+finfo(H, ram_file) -> {H, H#head.ram_file};
+finfo(H, safe_fixed) -> {H, H#head.fixed};
+finfo(H, size) ->
+ case catch write_cache(H) of
+ {H2, []} ->
+ {H2, H2#head.no_objects};
+ {H2, _} = HeadError when is_record(H2, head) ->
+ HeadError
+ end;
+finfo(H, type) -> {H, H#head.type};
+finfo(H, version) -> {H, H#head.version};
+finfo(H, _) -> {H, undefined}.
+
+file_size(Fd, FileName) ->
+ {ok, Pos} = dets_utils:position(Fd, FileName, eof),
+ Pos.
+
+test_bchunk_format(_Head, undefined) ->
+ false;
+test_bchunk_format(Head, _Term) when Head#head.version =:= 8 ->
+ false;
+test_bchunk_format(Head, Term) ->
+ dets_v9:try_bchunk_header(Term, Head) =/= not_ok.
+
+do_open_file([Fname, Verbose], Parent, Server, Ref) ->
+ case catch fopen2(Fname, Ref) of
+ {error, _Reason} = Error ->
+ err(Error);
+ {ok, Head} ->
+ maybe_put(verbose, Verbose),
+ {ok, Head#head{parent = Parent, server = Server}};
+ {'EXIT', _Reason} = Error ->
+ Error;
+ Bad ->
+ error_logger:format
+ ("** dets: Bug was found in open_file/1, reply was ~w.~n",
+ [Bad]),
+ {error, {dets_bug, Fname, Bad}}
+ end;
+do_open_file([Tab, OpenArgs, Verb], Parent, Server, Ref) ->
+ case catch fopen3(Tab, OpenArgs) of
+ {error, {tooshort, _}} ->
+ file:delete(OpenArgs#open_args.file),
+ do_open_file([Tab, OpenArgs, Verb], Parent, Server, Ref);
+ {error, _Reason} = Error ->
+ err(Error);
+ {ok, Head} ->
+ maybe_put(verbose, Verb),
+ {ok, Head#head{parent = Parent, server = Server}};
+ {'EXIT', _Reason} = Error ->
+ Error;
+ Bad ->
+ error_logger:format
+ ("** dets: Bug was found in open_file/2, arguments were~n"
+ "** dets: ~w and reply was ~w.~n",
+ [OpenArgs, Bad]),
+ {error, {dets_bug, Tab, {open_file, OpenArgs}, Bad}}
+ end.
+
+maybe_put(_, undefined) ->
+ ignore;
+maybe_put(K, V) ->
+ put(K, V).
+
+%% -> {Head, Result}, Result = ok | Error | {thrown, Error} | badarg
+finit(Head, InitFun, _Format, _NoSlots) when Head#head.access =:= read ->
+ _ = (catch InitFun(close)),
+ {Head, {error, {access_mode, Head#head.filename}}};
+finit(Head, InitFun, _Format, _NoSlots) when Head#head.fixed =/= false ->
+ _ = (catch InitFun(close)),
+ {Head, {error, {fixed_table, Head#head.name}}};
+finit(Head, InitFun, Format, NoSlots) ->
+ case catch do_finit(Head, InitFun, Format, NoSlots) of
+ {ok, NewHead} ->
+ check_growth(NewHead),
+ start_auto_save_timer(NewHead),
+ {NewHead, ok};
+ badarg ->
+ {Head, badarg};
+ Error ->
+ dets_utils:corrupt(Head, Error)
+ end.
+
+%% -> {ok, NewHead} | throw(badarg) | throw(Error)
+do_finit(Head, Init, Format, NoSlots) ->
+ #head{fptr = Fd, type = Type, keypos = Kp, auto_save = Auto,
+ cache = Cache, filename = Fname, ram_file = Ram,
+ min_no_slots = MinSlots0, max_no_slots = MaxSlots,
+ name = Tab, update_mode = UpdateMode, mod = HMod} = Head,
+ CacheSz = dets_utils:cache_size(Cache),
+ {How, Head1} =
+ case Format of
+ term when is_integer(NoSlots), NoSlots > MaxSlots ->
+ throw(badarg);
+ term ->
+ MinSlots = choose_no_slots(NoSlots, MinSlots0),
+ if
+ UpdateMode =:= new_dirty, MinSlots =:= MinSlots0 ->
+ {general_init, Head};
+ true ->
+ ok = dets_utils:truncate(Fd, Fname, bof),
+ {ok, H} = HMod:initiate_file(Fd, Tab, Fname, Type, Kp,
+ MinSlots, MaxSlots, Ram,
+ CacheSz, Auto, false),
+ {general_init, H}
+ end;
+ bchunk ->
+ ok = dets_utils:truncate(Fd, Fname, bof),
+ {bchunk_init, Head}
+ end,
+ case How of
+ bchunk_init ->
+ case HMod:bchunk_init(Head1, Init) of
+ {ok, NewHead} ->
+ {ok, NewHead#head{update_mode = dirty}};
+ Error ->
+ Error
+ end;
+ general_init ->
+ Cntrs = ets:new(dets_init, []),
+ Input = HMod:bulk_input(Head1, Init, Cntrs),
+ SlotNumbers = {Head1#head.min_no_slots, bulk_init, MaxSlots},
+ {Reply, SizeData} =
+ do_sort(Head1, SlotNumbers, Input, Cntrs, Fname, not_used),
+ Bulk = true,
+ case Reply of
+ {ok, NoDups, H1} ->
+ fsck_copy(SizeData, H1, Bulk, NoDups);
+ Else ->
+ close_files(Bulk, SizeData, Head1),
+ Else
+ end
+ end.
+
+%% -> {NewHead, [LookedUpObject]} | {NewHead, Error}
+flookup_keys(Head, Keys) ->
+ case catch update_cache(Head, Keys, {lookup, nopid}) of
+ {NewHead, [{_NoPid,Objs}]} ->
+ {NewHead, Objs};
+ {NewHead, L} when is_list(L) ->
+ {NewHead, lists:flatmap(fun({_Pid,OL}) -> OL end, L)};
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end.
+
+%% -> {NewHead, Result}
+fmatch_init(Head, C) ->
+ case scan(Head, C) of
+ {scan_error, Reason} ->
+ dets_utils:corrupt_reason(Head, Reason);
+ {Ts, NC} ->
+ {Head, {cont, {Ts, NC}}}
+ end.
+
+%% -> {NewHead, Result}
+fmatch(Head, MP, Spec, N) ->
+ KeyPos = Head#head.keypos,
+ case find_all_keys(Spec, KeyPos, []) of
+ [] ->
+ %% Complete match
+ case catch write_cache(Head) of
+ {NewHead, []} ->
+ C0 = init_scan(NewHead, N),
+ {NewHead, {cont, C0#dets_cont{match_program = MP}}};
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end;
+ List ->
+ Keys = lists:usort(List),
+ {NewHead, Reply} = flookup_keys(Head, Keys),
+ case Reply of
+ Objs when is_list(Objs) ->
+ MatchingObjs = ets:match_spec_run(Objs, MP),
+ {NewHead, {done, MatchingObjs}};
+ Error ->
+ {NewHead, Error}
+ end
+ end.
+
+find_all_keys([], _, Ks) ->
+ Ks;
+find_all_keys([{H,_,_} | T], KeyPos, Ks) when is_tuple(H) ->
+ case tuple_size(H) of
+ Enough when Enough >= KeyPos ->
+ Key = element(KeyPos, H),
+ case contains_variable(Key) of
+ true ->
+ [];
+ false ->
+ find_all_keys(T, KeyPos, [Key | Ks])
+ end;
+ _ ->
+ find_all_keys(T, KeyPos, Ks)
+ end;
+find_all_keys(_, _, _) ->
+ [].
+
+contains_variable('_') ->
+ true;
+contains_variable(A) when is_atom(A) ->
+ case atom_to_list(A) of
+ [$$ | T] ->
+ case (catch list_to_integer(T)) of
+ {'EXIT', _} ->
+ false;
+ _ ->
+ true
+ end;
+ _ ->
+ false
+ end;
+contains_variable(T) when is_tuple(T) ->
+ contains_variable(tuple_to_list(T));
+contains_variable([]) ->
+ false;
+contains_variable([H|T]) ->
+ case contains_variable(H) of
+ true ->
+ true;
+ false ->
+ contains_variable(T)
+ end;
+contains_variable(_) ->
+ false.
+
+%% -> {NewHead, Res}
+fmatch_delete_init(Head, MP, Spec) ->
+ KeyPos = Head#head.keypos,
+ case catch
+ case find_all_keys(Spec, KeyPos, []) of
+ [] ->
+ do_fmatch_delete_var_keys(Head, MP, Spec);
+ List ->
+ Keys = lists:usort(List),
+ do_fmatch_constant_keys(Head, Keys, MP)
+ end of
+ {NewHead, _} = Reply when is_record(NewHead, head) ->
+ Reply
+ end.
+
+%% A note: If deleted objects reside in a bucket with other objects
+%% that are not deleted, the bucket is moved. If the address of the
+%% moved bucket is greater than original bucket address the kept
+%% objects will be read once again later on.
+%% -> {NewHead, Res}
+fmatch_delete(Head, C) ->
+ case scan(Head, C) of
+ {scan_error, Reason} ->
+ dets_utils:corrupt_reason(Head, Reason);
+ {[], _} ->
+ {Head, {done, 0}};
+ {RTs, NC} ->
+ MP = C#dets_cont.match_program,
+ case catch filter_binary_terms(RTs, MP, []) of
+ {'EXIT', _} ->
+ Bad = dets_utils:bad_object(fmatch_delete, RTs),
+ dets_utils:corrupt_reason(Head, Bad);
+ Terms ->
+ do_fmatch_delete(Head, Terms, NC)
+ end
+ end.
+
+do_fmatch_delete_var_keys(Head, _MP, ?PATTERN_TO_TRUE_MATCH_SPEC('_'))
+ when Head#head.fixed =:= false ->
+ %% Handle the case where the file is emptied efficiently.
+ %% Empty the cache just to get the number of objects right.
+ {Head1, []} = write_cache(Head),
+ N = Head1#head.no_objects,
+ case fdelete_all_objects(Head1) of
+ {NewHead, ok} ->
+ {NewHead, {done, N}};
+ Reply ->
+ Reply
+ end;
+do_fmatch_delete_var_keys(Head, MP, _Spec) ->
+ {NewHead, []} = write_cache(Head),
+ C0 = init_scan(NewHead, default),
+ {NewHead, {cont, C0#dets_cont{match_program = MP}, 0}}.
+
+do_fmatch_constant_keys(Head, Keys, MP) ->
+ case flookup_keys(Head, Keys) of
+ {NewHead, ReadTerms} when is_list(ReadTerms) ->
+ Terms = filter_terms(ReadTerms, MP, []),
+ do_fmatch_delete(NewHead, Terms, fixed);
+ Reply ->
+ Reply
+ end.
+
+filter_binary_terms([Bin | Bins], MP, L) ->
+ Term = binary_to_term(Bin),
+ case ets:match_spec_run([Term], MP) of
+ [true] ->
+ filter_binary_terms(Bins, MP, [Term | L]);
+ _ ->
+ filter_binary_terms(Bins, MP, L)
+ end;
+filter_binary_terms([], _MP, L) ->
+ L.
+
+filter_terms([Term | Terms], MP, L) ->
+ case ets:match_spec_run([Term], MP) of
+ [true] ->
+ filter_terms(Terms, MP, [Term | L]);
+ _ ->
+ filter_terms(Terms, MP, L)
+ end;
+filter_terms([], _MP, L) ->
+ L.
+
+do_fmatch_delete(Head, Terms, What) ->
+ N = length(Terms),
+ case do_delete(Head, Terms, delete_object) of
+ {NewHead, ok} when What =:= fixed ->
+ {NewHead, {done, N}};
+ {NewHead, ok} ->
+ {NewHead, {cont, What, N}};
+ Reply ->
+ Reply
+ end.
+
+do_delete(Head, Things, What) ->
+ case catch update_cache(Head, Things, What) of
+ {NewHead, []} ->
+ {NewHead, ok};
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end.
+
+fmember(Head, Key) ->
+ case catch begin
+ {Head2, [{_NoPid,Objs}]} =
+ update_cache(Head, [Key], {lookup, nopid}),
+ {Head2, Objs =/= []}
+ end of
+ {NewHead, _} = Reply when is_record(NewHead, head) ->
+ Reply
+ end.
+
+fnext(Head, Key) ->
+ Slot = (Head#head.mod):db_hash(Key, Head),
+ Ref = make_ref(),
+ case catch {Ref, fnext(Head, Key, Slot)} of
+ {Ref, {H, R}} ->
+ {H, {ok, R}};
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end.
+
+fnext(H, Key, Slot) ->
+ {NH, []} = write_cache(H),
+ case (H#head.mod):slot_objs(NH, Slot) of
+ '$end_of_table' -> {NH, '$end_of_table'};
+ L -> fnext_search(NH, Key, Slot, L)
+ end.
+
+fnext_search(H, K, Slot, L) ->
+ Kp = H#head.keypos,
+ case beyond_key(K, Kp, L) of
+ [] -> fnext_slot(H, K, Slot+1);
+ L2 -> {H, element(H#head.keypos, hd(L2))}
+ end.
+
+%% We've got to continue to search for the next key in the next slot
+fnext_slot(H, K, Slot) ->
+ case (H#head.mod):slot_objs(H, Slot) of
+ '$end_of_table' -> {H, '$end_of_table'};
+ [] -> fnext_slot(H, K, Slot+1);
+ L -> {H, element(H#head.keypos, hd(L))}
+ end.
+
+beyond_key(_K, _Kp, []) -> [];
+beyond_key(K, Kp, [H|T]) ->
+ case dets_utils:cmp(element(Kp, H), K) of
+ 0 -> beyond_key2(K, Kp, T);
+ _ -> beyond_key(K, Kp, T)
+ end.
+
+beyond_key2(_K, _Kp, []) -> [];
+beyond_key2(K, Kp, [H|T]=L) ->
+ case dets_utils:cmp(element(Kp, H), K) of
+ 0 -> beyond_key2(K, Kp, T);
+ _ -> L
+ end.
+
+%% Open an already existing file, no arguments
+%% -> {ok, head()} | throw(Error)
+fopen2(Fname, Tab) ->
+ case file:read_file_info(Fname) of
+ {ok, _} ->
+ Acc = read_write,
+ Ram = false,
+ %% Fd is not always closed upon error, but exit is soon called.
+ {ok, Fd, FH} = read_file_header(Fname, Acc, Ram),
+ Mod = FH#fileheader.mod,
+ case Mod:check_file_header(FH, Fd) of
+ {error, not_closed} ->
+ io:format(user,"dets: file ~p not properly closed, "
+ "repairing ...~n", [Fname]),
+ Version = default,
+ case fsck(Fd, Tab, Fname, FH, default, default, Version) of
+ ok ->
+ fopen2(Fname, Tab);
+ Error ->
+ throw(Error)
+ end;
+ {ok, Head, ExtraInfo} ->
+ open_final(Head, Fname, Acc, Ram, ?DEFAULT_CACHE,
+ Tab, ExtraInfo, false);
+ {error, Reason} ->
+ throw({error, {Reason, Fname}})
+ end;
+ Error ->
+ dets_utils:file_error(Fname, Error)
+ end.
+
+%% Open and possibly create and initialize a file
+%% -> {ok, head()} | throw(Error)
+fopen3(Tab, OpenArgs) ->
+ FileName = OpenArgs#open_args.file,
+ case file:read_file_info(FileName) of
+ {ok, _} ->
+ fopen_existing_file(Tab, OpenArgs);
+ Error when OpenArgs#open_args.access =:= read ->
+ dets_utils:file_error(FileName, Error);
+ _Error ->
+ fopen_init_file(Tab, OpenArgs)
+ end.
+
+fopen_existing_file(Tab, OpenArgs) ->
+ #open_args{file = Fname, type = Type, keypos = Kp, repair = Rep,
+ min_no_slots = MinSlots, max_no_slots = MaxSlots,
+ ram_file = Ram, delayed_write = CacheSz, auto_save =
+ Auto, access = Acc, version = Version, debug = Debug} =
+ OpenArgs,
+ %% Fd is not always closed upon error, but exit is soon called.
+ {ok, Fd, FH} = read_file_header(Fname, Acc, Ram),
+ V9 = (Version =:= 9) or (Version =:= default),
+ MinF = (MinSlots =:= default) or (MinSlots =:= FH#fileheader.min_no_slots),
+ MaxF = (MaxSlots =:= default) or (MaxSlots =:= FH#fileheader.max_no_slots),
+ Do = case (FH#fileheader.mod):check_file_header(FH, Fd) of
+ {ok, Head, true} when Rep =:= force, Acc =:= read_write,
+ FH#fileheader.version =:= 9,
+ FH#fileheader.no_colls =/= undefined,
+ MinF, MaxF, V9 ->
+ {compact, Head};
+ {ok, _Head, _Extra} when Rep =:= force, Acc =:= read ->
+ throw({error, {access_mode, Fname}});
+ {ok, Head, need_compacting} when Acc =:= read ->
+ {final, Head, true}; % Version 8 only.
+ {ok, _Head, need_compacting} when Rep =:= true ->
+ %% The file needs to be compacted due to a very big
+ %% and fragmented free_list. Version 8 only.
+ M = " is now compacted ...",
+ {repair, M};
+ {ok, _Head, _Extra} when Rep =:= force ->
+ M = ", repair forced.",
+ {repair, M};
+ {ok, Head, ExtraInfo} ->
+ {final, Head, ExtraInfo};
+ {error, not_closed} when Rep =:= force, Acc =:= read_write ->
+ M = ", repair forced.",
+ {repair, M};
+ {error, not_closed} when Rep =:= true, Acc =:= read_write ->
+ M = " not properly closed, repairing ...",
+ {repair, M};
+ {error, not_closed} when Rep =:= false ->
+ throw({error, {needs_repair, Fname}});
+ {error, version_bump} when Rep =:= true, Acc =:= read_write ->
+ %% Version 8 only
+ M = " old version, upgrading ...",
+ {repair, M};
+ {error, Reason} ->
+ throw({error, {Reason, Fname}})
+ end,
+ case Do of
+ _ when FH#fileheader.type =/= Type ->
+ throw({error, {type_mismatch, Fname}});
+ _ when FH#fileheader.keypos =/= Kp ->
+ throw({error, {keypos_mismatch, Fname}});
+ {compact, SourceHead} ->
+ io:format(user, "dets: file ~p is now compacted ...~n", [Fname]),
+ {ok, NewSourceHead} = open_final(SourceHead, Fname, read, false,
+ ?DEFAULT_CACHE, Tab, true,
+ Debug),
+ case catch compact(NewSourceHead) of
+ ok ->
+ erlang:garbage_collect(),
+ fopen3(Tab, OpenArgs#open_args{repair = false});
+ _Err ->
+ _ = file:close(Fd),
+ dets_utils:stop_disk_map(),
+ io:format(user, "dets: compaction of file ~p failed, "
+ "now repairing ...~n", [Fname]),
+ {ok, Fd2, _FH} = read_file_header(Fname, Acc, Ram),
+ do_repair(Fd2, Tab, Fname, FH, MinSlots, MaxSlots,
+ Version, OpenArgs)
+ end;
+ {repair, Mess} ->
+ io:format(user, "dets: file ~p~s~n", [Fname, Mess]),
+ do_repair(Fd, Tab, Fname, FH, MinSlots, MaxSlots,
+ Version, OpenArgs);
+ _ when FH#fileheader.version =/= Version, Version =/= default ->
+ throw({error, {version_mismatch, Fname}});
+ {final, H, EI} ->
+ H1 = H#head{auto_save = Auto},
+ open_final(H1, Fname, Acc, Ram, CacheSz, Tab, EI, Debug)
+ end.
+
+do_repair(Fd, Tab, Fname, FH, MinSlots, MaxSlots, Version, OpenArgs) ->
+ case fsck(Fd, Tab, Fname, FH, MinSlots, MaxSlots, Version) of
+ ok ->
+ %% No need to update 'version'.
+ erlang:garbage_collect(),
+ fopen3(Tab, OpenArgs#open_args{repair = false});
+ Error ->
+ throw(Error)
+ end.
+
+%% -> {ok, head()} | throw(Error)
+open_final(Head, Fname, Acc, Ram, CacheSz, Tab, ExtraInfo, Debug) ->
+ Head1 = Head#head{access = Acc,
+ ram_file = Ram,
+ filename = Fname,
+ name = Tab,
+ cache = dets_utils:new_cache(CacheSz)},
+ init_disk_map(Head1#head.version, Tab, Debug),
+ Mod = Head#head.mod,
+ Mod:cache_segps(Head1#head.fptr, Fname, Head1#head.next),
+ Ftab = Mod:init_freelist(Head1, ExtraInfo),
+ check_growth(Head1),
+ NewHead = Head1#head{freelists = Ftab},
+ {ok, NewHead}.
+
+%% -> {ok, head()} | throw(Error)
+fopen_init_file(Tab, OpenArgs) ->
+ #open_args{file = Fname, type = Type, keypos = Kp,
+ min_no_slots = MinSlotsArg, max_no_slots = MaxSlotsArg,
+ ram_file = Ram, delayed_write = CacheSz, auto_save = Auto,
+ version = UseVersion, debug = Debug} = OpenArgs,
+ MinSlots = choose_no_slots(MinSlotsArg, ?DEFAULT_MIN_NO_SLOTS),
+ MaxSlots = choose_no_slots(MaxSlotsArg, ?DEFAULT_MAX_NO_SLOTS),
+ FileSpec = if
+ Ram -> [];
+ true -> Fname
+ end,
+ {ok, Fd} = dets_utils:open(FileSpec, open_args(read_write, Ram)),
+ Version = if
+ UseVersion =:= default ->
+ case os:getenv("DETS_USE_FILE_FORMAT") of
+ "8" -> 8;
+ _ -> 9
+ end;
+ true ->
+ UseVersion
+ end,
+ Mod = version2module(Version),
+ %% No need to truncate an empty file.
+ init_disk_map(Version, Tab, Debug),
+ case catch Mod:initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots,
+ Ram, CacheSz, Auto, true) of
+ {error, Reason} when Ram ->
+ file:close(Fd),
+ throw({error, Reason});
+ {error, Reason} ->
+ file:close(Fd),
+ file:delete(Fname),
+ throw({error, Reason});
+ {ok, Head} ->
+ start_auto_save_timer(Head),
+ %% init_table does not need to truncate and write header
+ {ok, Head#head{update_mode = new_dirty}}
+ end.
+
+%% Debug.
+init_disk_map(9, Name, Debug) ->
+ case Debug orelse dets_utils:debug_mode() of
+ true ->
+ dets_utils:init_disk_map(Name);
+ false ->
+ ok
+ end;
+init_disk_map(_Version, _Name, _Debug) ->
+ ok.
+
+open_args(Access, RamFile) ->
+ A1 = case Access of
+ read -> [];
+ read_write -> [write]
+ end,
+ A2 = case RamFile of
+ true -> [ram];
+ false -> [raw]
+ end,
+ A1 ++ A2 ++ [binary, read].
+
+version2module(V) when V =< 8 -> dets_v8;
+version2module(9) -> dets_v9.
+
+module2version(dets_v8) -> 8;
+module2version(dets_v9) -> 9;
+module2version(not_used) -> 9.
+
+%% -> ok | throw(Error)
+%% For version 9 tables only.
+compact(SourceHead) ->
+ #head{name = Tab, filename = Fname, fptr = SFd, type = Type, keypos = Kp,
+ ram_file = Ram, auto_save = Auto} = SourceHead,
+ Tmp = tempfile(Fname),
+ TblParms = dets_v9:table_parameters(SourceHead),
+ {ok, Fd} = dets_utils:open(Tmp, open_args(read_write, false)),
+ CacheSz = ?DEFAULT_CACHE,
+ %% It is normally not possible to have two open tables in the same
+ %% process since the process dictionary is used for caching
+ %% segment pointers, but here is works anyway--when reading a file
+ %% serially the pointers to not need to be used.
+ Head = case catch dets_v9:prep_table_copy(Fd, Tab, Tmp, Type, Kp, Ram,
+ CacheSz, Auto, TblParms) of
+ {ok, H} ->
+ H;
+ Error ->
+ file:close(Fd),
+ file:delete(Tmp),
+ throw(Error)
+ end,
+
+ case dets_v9:compact_init(SourceHead, Head, TblParms) of
+ {ok, NewHead} ->
+ R = case fclose(NewHead) of
+ ok ->
+ ok = file:close(SFd),
+ %% Save (rename) Fname first?
+ dets_utils:rename(Tmp, Fname);
+ E ->
+ E
+ end,
+ if
+ R =:= ok -> ok;
+ true ->
+ file:delete(Tmp),
+ throw(R)
+ end;
+ Err ->
+ file:close(Fd),
+ file:delete(Tmp),
+ throw(Err)
+ end.
+
+%% -> ok | Error
+%% Closes Fd.
+fsck(Fd, Tab, Fname, FH, MinSlotsArg, MaxSlotsArg, Version) ->
+ %% MinSlots and MaxSlots are the option values.
+ #fileheader{min_no_slots = MinSlotsFile,
+ max_no_slots = MaxSlotsFile} = FH,
+ EstNoSlots0 = file_no_things(FH),
+ MinSlots = choose_no_slots(MinSlotsArg, MinSlotsFile),
+ MaxSlots = choose_no_slots(MaxSlotsArg, MaxSlotsFile),
+ EstNoSlots = erlang:min(MaxSlots, erlang:max(MinSlots, EstNoSlots0)),
+ SlotNumbers = {MinSlots, EstNoSlots, MaxSlots},
+ %% When repairing: We first try and sort on slots using MinSlots.
+ %% If the number of objects (keys) turns out to be significantly
+ %% different from NoSlots, we try again with the correct number of
+ %% objects (keys).
+ case fsck_try(Fd, Tab, FH, Fname, SlotNumbers, Version) of
+ {try_again, BetterNoSlots} ->
+ BetterSlotNumbers = {MinSlots, BetterNoSlots, MaxSlots},
+ case fsck_try(Fd, Tab, FH, Fname, BetterSlotNumbers, Version) of
+ {try_again, _} ->
+ file:close(Fd),
+ {error, {cannot_repair, Fname}};
+ Else ->
+ Else
+ end;
+ Else ->
+ Else
+ end.
+
+choose_no_slots(default, NoSlots) -> NoSlots;
+choose_no_slots(NoSlots, _) -> NoSlots.
+
+%% -> ok | {try_again, integer()} | Error
+%% Closes Fd unless {try_again, _} is returned.
+%% Initiating a table using a fun and repairing (or converting) a
+%% file are completely different things, but nevertheless the same
+%% method is used in both cases...
+fsck_try(Fd, Tab, FH, Fname, SlotNumbers, Version) ->
+ Tmp = tempfile(Fname),
+ #fileheader{type = Type, keypos = KeyPos} = FH,
+ {_MinSlots, EstNoSlots, MaxSlots} = SlotNumbers,
+ OpenArgs = #open_args{file = Tmp, type = Type, keypos = KeyPos,
+ repair = false, min_no_slots = EstNoSlots,
+ max_no_slots = MaxSlots,
+ ram_file = false, delayed_write = ?DEFAULT_CACHE,
+ auto_save = infinity, access = read_write,
+ version = Version, debug = false},
+ case catch fopen3(Tab, OpenArgs) of
+ {ok, Head} ->
+ case fsck_try_est(Head, Fd, Fname, SlotNumbers, FH) of
+ {ok, NewHead} ->
+ R = case fclose(NewHead) of
+ ok ->
+ %% Save (rename) Fname first?
+ dets_utils:rename(Tmp, Fname);
+ Error ->
+ Error
+ end,
+ if
+ R =:= ok -> ok;
+ true ->
+ file:delete(Tmp),
+ R
+ end;
+ TryAgainOrError ->
+ file:delete(Tmp),
+ TryAgainOrError
+ end;
+ Error ->
+ file:close(Fd),
+ Error
+ end.
+
+tempfile(Fname) ->
+ Tmp = lists:concat([Fname, ".TMP"]),
+ case file:delete(Tmp) of
+ {error, eacces} -> % 'dets_process_died' happened anyway... (W-nd-ws)
+ timer:sleep(5000),
+ file:delete(Tmp);
+ _ ->
+ ok
+ end,
+ Tmp.
+
+%% -> {ok, NewHead} | {try_again, integer()} | Error
+fsck_try_est(Head, Fd, Fname, SlotNumbers, FH) ->
+ %% Mod is the module to use for reading input when repairing.
+ Mod = FH#fileheader.mod,
+ Cntrs = ets:new(dets_repair, []),
+ Input = Mod:fsck_input(Head, Fd, Cntrs, FH),
+ {Reply, SizeData} = do_sort(Head, SlotNumbers, Input, Cntrs, Fname, Mod),
+ Bulk = false,
+ case Reply of
+ {ok, NoDups, H1} ->
+ file:close(Fd),
+ fsck_copy(SizeData, H1, Bulk, NoDups);
+ {try_again, _} = Return ->
+ close_files(Bulk, SizeData, Head),
+ Return;
+ Else ->
+ file:close(Fd),
+ close_files(Bulk, SizeData, Head),
+ Else
+ end.
+
+do_sort(Head, SlotNumbers, Input, Cntrs, Fname, Mod) ->
+ OldV = module2version(Mod),
+ %% output_objs/4 replaces {LogSize,NoObjects} in Cntrs by
+ %% {LogSize,Position,Data,NoObjects | NoCollections}.
+ %% Data = {FileName,FileDescriptor} | [object()]
+ %% For small tables Data may be a list of objects which is more
+ %% efficient since no temporary files are created.
+ Output = (Head#head.mod):output_objs(OldV, Head, SlotNumbers, Cntrs),
+ TmpDir = filename:dirname(Fname),
+ Reply = (catch file_sorter:sort(Input, Output,
+ [{format, binary},{tmpdir, TmpDir}])),
+ L = ets:tab2list(Cntrs),
+ ets:delete(Cntrs),
+ {Reply, lists:reverse(lists:keysort(1, L))}.
+
+fsck_copy([{_LogSz, Pos, Bins, _NoObjects} | SizeData], Head, _Bulk, NoDups)
+ when is_list(Bins) ->
+ true = NoDups =:= 0,
+ PWs = [{Pos,Bins} | lists:map(fun({_, P, B, _}) -> {P, B} end, SizeData)],
+ #head{fptr = Fd, filename = FileName} = Head,
+ dets_utils:pwrite(Fd, FileName, PWs),
+ {ok, Head#head{update_mode = dirty}};
+fsck_copy(SizeData, Head, Bulk, NoDups) ->
+ catch fsck_copy1(SizeData, Head, Bulk, NoDups).
+
+fsck_copy1([SzData | L], Head, Bulk, NoDups) ->
+ Out = Head#head.fptr,
+ {LogSz, Pos, {FileName, Fd}, NoObjects} = SzData,
+ Size = if NoObjects =:= 0 -> 0; true -> ?POW(LogSz-1) end,
+ ExpectedSize = Size * NoObjects,
+ close_tmp(Fd),
+ case file:position(Out, Pos) of
+ {ok, Pos} -> ok;
+ PError -> dets_utils:file_error(FileName, PError)
+ end,
+ {ok, Pos} = file:position(Out, Pos),
+ CR = file:copy({FileName, [raw,binary]}, Out),
+ file:delete(FileName),
+ case CR of
+ {ok, Copied} when Copied =:= ExpectedSize;
+ NoObjects =:= 0 -> % the segments
+ fsck_copy1(L, Head, Bulk, NoDups);
+ {ok, Copied} when Bulk, Head#head.version =:= 8 ->
+ NoZeros = ExpectedSize - Copied,
+ Dups = NoZeros div Size,
+ Addr = Pos+Copied,
+ NewHead = free_n_objects(Head, Addr, Size-1, NoDups),
+ NewNoDups = NoDups - Dups,
+ fsck_copy1(L, NewHead, Bulk, NewNoDups);
+ {ok, _Copied} -> % should never happen
+ close_files(Bulk, L, Head),
+ Reason = if Bulk -> initialization_failed;
+ true -> repair_failed end,
+ {error, {Reason, Head#head.filename}};
+ FError ->
+ close_files(Bulk, L, Head),
+ dets_utils:file_error(FileName, FError)
+ end;
+fsck_copy1([], Head, _Bulk, NoDups) when NoDups =/= 0 ->
+ {error, {initialization_failed, Head#head.filename}};
+fsck_copy1([], Head, _Bulk, _NoDups) ->
+ {ok, Head#head{update_mode = dirty}}.
+
+free_n_objects(Head, _Addr, _Size, 0) ->
+ Head;
+free_n_objects(Head, Addr, Size, N) ->
+ {NewHead, _} = dets_utils:free(Head, Addr, Size),
+ NewAddr = Addr + Size + 1,
+ free_n_objects(NewHead, NewAddr, Size, N-1).
+
+close_files(false, SizeData, Head) ->
+ file:close(Head#head.fptr),
+ close_files(true, SizeData, Head);
+close_files(true, SizeData, _Head) ->
+ Fun = fun({_Size, _Pos, {FileName, Fd}, _No}) ->
+ close_tmp(Fd),
+ file:delete(FileName);
+ (_) ->
+ ok
+ end,
+ lists:foreach(Fun, SizeData).
+
+close_tmp(Fd) ->
+ file:close(Fd).
+
+fslot(H, Slot) ->
+ case catch begin
+ {NH, []} = write_cache(H),
+ Objs = (NH#head.mod):slot_objs(NH, Slot),
+ {NH, Objs}
+ end of
+ {NewHead, _Objects} = Reply when is_record(NewHead, head) ->
+ Reply
+ end.
+
+do_update_counter(Head, _Key, _Incr) when Head#head.type =/= set ->
+ {Head, badarg};
+do_update_counter(Head, Key, Incr) ->
+ case flookup_keys(Head, [Key]) of
+ {H1, [O]} ->
+ Kp = H1#head.keypos,
+ case catch try_update_tuple(O, Kp, Incr) of
+ {'EXIT', _} ->
+ {H1, badarg};
+ {New, Term} ->
+ case finsert(H1, [Term]) of
+ {H2, ok} ->
+ {H2, New};
+ Reply ->
+ Reply
+ end
+ end;
+ {H1, []} ->
+ {H1, badarg};
+ HeadError ->
+ HeadError
+ end.
+
+try_update_tuple(O, _Kp, {Pos, Incr}) ->
+ try_update_tuple2(O, Pos, Incr);
+try_update_tuple(O, Kp, Incr) ->
+ try_update_tuple2(O, Kp+1, Incr).
+
+try_update_tuple2(O, Pos, Incr) ->
+ New = element(Pos, O) + Incr,
+ {New, setelement(Pos, O, New)}.
+
+set_verbose(true) ->
+ put(verbose, yes);
+set_verbose(_) ->
+ erase(verbose).
+
+where_is_object(Head, Object) ->
+ Keypos = Head#head.keypos,
+ case check_objects([Object], Keypos) of
+ true ->
+ case catch write_cache(Head) of
+ {NewHead, []} ->
+ {NewHead, (Head#head.mod):find_object(NewHead, Object)};
+ {NewHead, _} = HeadError when is_record(NewHead, head) ->
+ HeadError
+ end;
+ false ->
+ {Head, badarg}
+ end.
+
+check_objects([T | Ts], Kp) when tuple_size(T) >= Kp ->
+ check_objects(Ts, Kp);
+check_objects(L, _Kp) ->
+ L =:= [].
+
+no_things(Head) when Head#head.no_keys =:= undefined ->
+ Head#head.no_objects;
+no_things(Head) ->
+ Head#head.no_keys.
+
+file_no_things(FH) when FH#fileheader.no_keys =:= undefined ->
+ FH#fileheader.no_objects;
+file_no_things(FH) ->
+ FH#fileheader.no_keys.
+
+%%% The write cache is list of {Key, [Item]} where Item is one of
+%%% {Seq, delete_key}, {Seq, {lookup,Pid}}, {Seq, {delete_object,object()}},
+%%% or {Seq, {insert,object()}}. Seq is a number that increases
+%%% monotonically for each item put in the cache. The purpose is to
+%%% make sure that items are sorted correctly. Sequences of delete and
+%%% insert operations are inserted in the cache without doing any file
+%%% operations. When the cache is considered full, a lookup operation
+%%% is requested, or after some delay, the contents of the cache are
+%%% written to the file, and the cache emptied.
+%%%
+%%% Data is not allowed to linger more than 'delay' milliseconds in
+%%% the write cache. A delayed_write message is received when some
+%%% datum has become too old. If 'wrtime' is equal to 'undefined',
+%%% then the cache is empty and no such delayed_write message has been
+%%% scheduled. Otherwise there is a delayed_write message scheduled,
+%%% and the value of 'wrtime' is the time when the cache was last
+%%% written, or when it was first updated after the cache was last
+%%% written.
+
+update_cache(Head, KeysOrObjects, What) ->
+ {Head1, LU, PwriteList} = update_cache(Head, [{What,KeysOrObjects}]),
+ {NewHead, ok} = dets_utils:pwrite(Head1, PwriteList),
+ {NewHead, LU}.
+
+%% -> {NewHead, [object()], pwrite_list()} | throw({Head, Error})
+update_cache(Head, ToAdd) ->
+ Cache = Head#head.cache,
+ #cache{cache = C, csize = Size0, inserts = Ins} = Cache,
+ NewSize = Size0 + erlang:external_size(ToAdd),
+ %% The size is used as a sequence number here; it increases monotonically.
+ {NewC, NewIns, Lookup, Found} =
+ cache_binary(Head, ToAdd, C, Size0, Ins, false, []),
+ NewCache = Cache#cache{cache = NewC, csize = NewSize, inserts = NewIns},
+ Head1 = Head#head{cache = NewCache},
+ if
+ Lookup; NewSize >= Cache#cache.tsize ->
+ %% The cache is considered full, or some lookup.
+ {NewHead, LU, PwriteList} = (Head#head.mod):write_cache(Head1),
+ {NewHead, Found ++ LU, PwriteList};
+ NewC =:= [] ->
+ {Head1, Found, []};
+ Cache#cache.wrtime =:= undefined ->
+ %% Empty cache. Schedule a delayed write.
+ Now = now(), Me = self(),
+ Call = ?DETS_CALL(Me, {delayed_write, Now}),
+ erlang:send_after(Cache#cache.delay, Me, Call),
+ {Head1#head{cache = NewCache#cache{wrtime = Now}}, Found, []};
+ Size0 =:= 0 ->
+ %% Empty cache that has been written after the
+ %% currently scheduled delayed write.
+ {Head1#head{cache = NewCache#cache{wrtime = now()}}, Found, []};
+ true ->
+ %% Cache is not empty, delayed write has been scheduled.
+ {Head1, Found, []}
+ end.
+
+cache_binary(Head, [{Q,Os} | L], C, Seq, Ins, Lu,F) when Q =:= delete_object ->
+ cache_obj_op(Head, L, C, Seq, Ins, Lu, F, Os, Head#head.keypos, Q);
+cache_binary(Head, [{Q,Os} | L], C, Seq, Ins, Lu, F) when Q =:= insert ->
+ NewIns = Ins + length(Os),
+ cache_obj_op(Head, L, C, Seq, NewIns, Lu, F, Os, Head#head.keypos, Q);
+cache_binary(Head, [{Q,Ks} | L], C, Seq, Ins, Lu, F) when Q =:= delete_key ->
+ cache_key_op(Head, L, C, Seq, Ins, Lu, F, Ks, Q);
+cache_binary(Head, [{Q,Ks} | L], C, Seq, Ins, _Lu, F) when C =:= [] -> % lookup
+ cache_key_op(Head, L, C, Seq, Ins, true, F, Ks, Q);
+cache_binary(Head, [{Q,Ks} | L], C, Seq, Ins, Lu, F) -> % lookup
+ case dets_utils:cache_lookup(Head#head.type, Ks, C, []) of
+ false ->
+ cache_key_op(Head, L, C, Seq, Ins, true, F, Ks, Q);
+ Found ->
+ {lookup,Pid} = Q,
+ cache_binary(Head, L, C, Seq, Ins, Lu, [{Pid,Found} | F])
+ end;
+cache_binary(_Head, [], C, _Seq, Ins, Lu, F) ->
+ {C, Ins, Lu, F}.
+
+cache_key_op(Head, L, C, Seq, Ins, Lu, F, [K | Ks], Q) ->
+ E = {K, {Seq, Q}},
+ cache_key_op(Head, L, [E | C], Seq+1, Ins, Lu, F, Ks, Q);
+cache_key_op(Head, L, C, Seq, Ins, Lu, F, [], _Q) ->
+ cache_binary(Head, L, C, Seq, Ins, Lu, F).
+
+cache_obj_op(Head, L, C, Seq, Ins, Lu, F, [O | Os], Kp, Q) ->
+ E = {element(Kp, O), {Seq, {Q, O}}},
+ cache_obj_op(Head, L, [E | C], Seq+1, Ins, Lu, F, Os, Kp, Q);
+cache_obj_op(Head, L, C, Seq, Ins, Lu, F, [], _Kp, _Q) ->
+ cache_binary(Head, L, C, Seq, Ins, Lu, F).
+
+%% Called after some delay.
+%% -> NewHead
+delayed_write(Head, WrTime) ->
+ Cache = Head#head.cache,
+ LastWrTime = Cache#cache.wrtime,
+ if
+ LastWrTime =:= WrTime ->
+ %% The cache was not emptied during the last delay.
+ case catch write_cache(Head) of
+ {Head2, []} ->
+ NewCache = (Head2#head.cache)#cache{wrtime = undefined},
+ Head2#head{cache = NewCache};
+ {NewHead, _Error} -> % Head.update_mode has been updated
+ NewHead
+ end;
+ true ->
+ %% The cache was emptied during the delay.
+ %% Has anything been written since then?
+ if
+ Cache#cache.csize =:= 0 ->
+ %% No, further delayed write not needed.
+ NewCache = Cache#cache{wrtime = undefined},
+ Head#head{cache = NewCache};
+ true ->
+ %% Yes, schedule a new delayed write.
+ {MS1,S1,M1} = WrTime,
+ {MS2,S2,M2} = LastWrTime,
+ WrT = M1+1000000*(S1+1000000*MS1),
+ LastWrT = M2+1000000*(S2+1000000*MS2),
+ When = round((LastWrT - WrT)/1000), Me = self(),
+ Call = ?DETS_CALL(Me, {delayed_write, LastWrTime}),
+ erlang:send_after(When, Me, Call),
+ Head
+ end
+ end.
+
+%% -> {NewHead, [LookedUpObject]} | throw({NewHead, Error})
+write_cache(Head) ->
+ {Head1, LU, PwriteList} = (Head#head.mod):write_cache(Head),
+ {NewHead, ok} = dets_utils:pwrite(Head1, PwriteList),
+ {NewHead, LU}.
+
+status(Head) ->
+ case Head#head.update_mode of
+ saved -> ok;
+ dirty -> ok;
+ new_dirty -> ok;
+ Error -> Error
+ end.
+
+%%% Scan the file from start to end by reading chunks.
+
+%% -> dets_cont()
+init_scan(Head, NoObjs) ->
+ check_safe_fixtable(Head),
+ FreeLists = dets_utils:get_freelists(Head),
+ Base = Head#head.base,
+ {From, To} = dets_utils:find_next_allocated(FreeLists, Base, Base),
+ #dets_cont{no_objs = NoObjs, bin = <<>>, alloc = {From, To, <<>>}}.
+
+check_safe_fixtable(Head) ->
+ case (Head#head.fixed =:= false) andalso
+ ((get(verbose) =:= yes) orelse dets_utils:debug_mode()) of
+ true ->
+ error_logger:format
+ ("** dets: traversal of ~p needs safe_fixtable~n",
+ [Head#head.name]);
+ false ->
+ ok
+ end.
+
+%% -> {[RTerm], dets_cont()} | {scan_error, Reason}
+%% RTerm = {Pos, Next, Size, Status, Term}
+scan(_Head, #dets_cont{alloc = <<>>}=C) ->
+ {[], C};
+scan(Head, C) -> % when is_record(C, dets_cont)
+ #dets_cont{no_objs = No, alloc = L0, bin = Bin} = C,
+ {From, To, L} = L0,
+ R = case No of
+ default ->
+ 0;
+ _ when is_integer(No) ->
+ -No-1
+ end,
+ scan(Bin, Head, From, To, L, [], R, {C, Head#head.type}).
+
+scan(Bin, H, From, To, L, Ts, R, {C0, Type} = C) ->
+ case (H#head.mod):scan_objs(H, Bin, From, To, L, Ts, R, Type) of
+ {more, NFrom, NTo, NL, NTs, NR, Sz} ->
+ scan_read(H, NFrom, NTo, Sz, NL, NTs, NR, C);
+ {stop, <<>>=B, NFrom, NTo, <<>>=NL, NTs} ->
+ Ftab = dets_utils:get_freelists(H),
+ case dets_utils:find_next_allocated(Ftab, NFrom, H#head.base) of
+ none ->
+ {NTs, C0#dets_cont{bin = eof, alloc = B}};
+ _ ->
+ {NTs, C0#dets_cont{bin = B, alloc = {NFrom, NTo, NL}}}
+ end;
+ {stop, B, NFrom, NTo, NL, NTs} ->
+ {NTs, C0#dets_cont{bin = B, alloc = {NFrom, NTo, NL}}};
+ bad_object ->
+ {scan_error, dets_utils:bad_object(scan, {From, To, Bin})}
+ end.
+
+scan_read(_H, From, To, _Min, L0, Ts,
+ R, {C, _Type}) when R >= ?CHUNK_SIZE ->
+ %% We may have read (much) more than CHUNK_SIZE, if there are holes.
+ L = {From, To, L0},
+ {Ts, C#dets_cont{bin = <<>>, alloc = L}};
+scan_read(H, From, _To, Min, _L, Ts, R, C) ->
+ Max = if
+ Min < ?CHUNK_SIZE -> ?CHUNK_SIZE;
+ true -> Min
+ end,
+ FreeLists = dets_utils:get_freelists(H),
+ case dets_utils:find_allocated(FreeLists, From, Max, H#head.base) of
+ <<>>=Bin0 ->
+ {Cont, _} = C,
+ {Ts, Cont#dets_cont{bin = eof, alloc = Bin0}};
+ <<From1:32,To1:32,L1/binary>> ->
+ case dets_utils:pread_n(H#head.fptr, From1, Max) of
+ eof ->
+ {scan_error, premature_eof};
+ NewBin ->
+ scan(NewBin, H, From1, To1, L1, Ts, R, C)
+ end
+ end.
+
+err(Error) ->
+ case get(verbose) of
+ yes ->
+ error_logger:format("** dets: failed with ~w~n", [Error]),
+ Error;
+ undefined ->
+ Error
+ end.
+
+%%%%%%%%%%%%%%%%% DEBUG functions %%%%%%%%%%%%%%%%
+
+file_info(FileName) ->
+ case catch read_file_header(FileName, read, false) of
+ {ok, Fd, FH} ->
+ file:close(Fd),
+ (FH#fileheader.mod):file_info(FH);
+ Other ->
+ Other
+ end.
+
+get_head_field(Fd, Field) ->
+ dets_utils:read_4(Fd, Field).
+
+%% Dump the contents of a DAT file to the tty
+%% internal debug function which ignores the closed properly thingie
+%% and just tries anyway
+
+view(FileName) ->
+ case catch read_file_header(FileName, read, false) of
+ {ok, Fd, FH} ->
+ Mod = FH#fileheader.mod,
+ case Mod:check_file_header(FH, Fd) of
+ {ok, H0, ExtraInfo} ->
+ Ftab = Mod:init_freelist(H0, ExtraInfo),
+ {_Bump, Base} = constants(FH, FileName),
+ H = H0#head{freelists=Ftab, base = Base},
+ v_free_list(H),
+ Mod:v_segments(H),
+ file:close(Fd);
+ X ->
+ file:close(Fd),
+ X
+ end;
+ X ->
+ X
+ end.
+
+v_free_list(Head) ->
+ io:format("FREE LIST ...... \n",[]),
+ io:format("~p~n", [dets_utils:all_free(Head)]),
+ io:format("END OF FREE LIST \n",[]).
diff --git a/lib/stdlib/src/dets.hrl b/lib/stdlib/src/dets.hrl
new file mode 100644
index 0000000000..6e59770753
--- /dev/null
+++ b/lib/stdlib/src/dets.hrl
@@ -0,0 +1,126 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-define(DEFAULT_MIN_NO_SLOTS, 256).
+-define(DEFAULT_MAX_NO_SLOTS, 2*1024*1024).
+-define(DEFAULT_AUTOSAVE, 3). % minutes
+-define(DEFAULT_CACHE, {3000, 14000}). % {delay,size} in {milliseconds,bytes}
+
+%% Type.
+-define(SET, 1).
+-define(BAG, 2).
+-define(DUPLICATE_BAG, 3).
+
+-define(MAGIC, 16#0abcdef). % dets cookie, won't ever change.
+%% Status values.
+-define(FREE, 16#3abcdef).
+-define(ACTIVE, 16#12345678).
+
+-define(FILE_FORMAT_VERSION_POS, 16).
+
+-define(CHUNK_SIZE, 8192).
+
+-define(SERVER_NAME, dets).
+
+-define(POW(X), (1 bsl (X))).
+
+%% REM2(A,B) = A rem B, if B is a power of 2.
+-define(REM2(A, B), ((A) band ((B)-1))).
+
+-define(DETS_CALL(Pid, Req), {'$dets_call', Pid, Req}).
+
+%% Record holding the file header and more.
+-record(head, {
+ m, % size
+ m2, % m * 2
+ next, % next position for growth (segm mgmt only)
+ fptr, % the file descriptor
+ no_objects, % number of objects in table,
+ no_keys, % number of keys (version 9 only)
+ maxobjsize, % 2-log of the size of the biggest object
+ % collection (version 9 only)
+ n, % split indicator
+ type, % set | bag | duplicate_bag
+ keypos, % default is 1 as for ets
+ freelists, % tuple of free lists of buddies
+ % if fixed =/= false, then a pair of freelists
+ freelists_p, % cached FreelistsPointer
+ no_collections, % [{LogSize,NoCollections}] | undefined; number of
+ % object collections per size (version 9(b))
+ auto_save, % Integer | infinity
+ update_mode, % saved | dirty | new_dirty | {error, Reason}
+ fixed = false, % false | {now_time(), [{pid(),Counter}]}
+ % time of first fix, and number of fixes per process
+ hash_bif, % hash bif used for this file (phash2, phash, hash)
+ has_md5, % whether the header has an MD5 sum (version 9(c))
+ min_no_slots, % minimum number of slots (default or integer)
+ max_no_slots, % maximum number of slots (default or integer)
+ cache, % cache(). Write cache.
+
+ filename, % name of the file being used
+ access = read_write, % read | read_write
+ ram_file = false, % true | false
+ name, % the name of the table
+
+ parent, % The supervisor of Dets processes.
+ server, % The creator of Dets processes.
+
+ %% Depending on the file format:
+ version,
+ mod,
+ bump,
+ base
+
+ }).
+
+%% Info extracted from the file header.
+-record(fileheader, {
+ freelist,
+ cookie,
+ closed_properly,
+ type,
+ version,
+ m,
+ next,
+ keypos,
+ no_objects,
+ no_keys,
+ min_no_slots,
+ max_no_slots,
+ no_colls,
+ hash_method,
+ read_md5,
+ has_md5,
+ md5,
+ trailer,
+ eof,
+ n,
+ mod
+ }).
+
+%% Write Cache.
+-record(cache, {
+ cache, % [{Key,{Seq,Item}}], write cache, last item first
+ csize, % current size of the cached items
+ inserts, % upper limit on number of inserted keys
+ wrtime, % last write or update time
+ tsize, % threshold size of cache, in bytes
+ delay % max time items are kept in RAM only, in milliseconds
+ }).
+
diff --git a/lib/stdlib/src/dets_server.erl b/lib/stdlib/src/dets_server.erl
new file mode 100644
index 0000000000..931112088e
--- /dev/null
+++ b/lib/stdlib/src/dets_server.erl
@@ -0,0 +1,402 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dets_server).
+
+%% Disk based linear hashing lookup dictionary. Server part.
+
+-behaviour(gen_server).
+
+%% External exports.
+-export([all/0, close/1, get_pid/1, open_file/1, open_file/2, pid2name/1,
+ users/1, verbose/1]).
+
+%% Internal.
+-export([start_link/0, start/0, stop/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+%% record for not yet handled reqeusts to open or close files
+-record(pending, {tab, ref, pid, from, reqtype, clients}). % [{From,Args}]
+
+%% state for the dets server
+-record(state, {store, parent, pending}). % [pending()]
+
+-include("dets.hrl").
+
+-define(REGISTRY, dets_registry). % {Table, NoUsers, TablePid}
+-define(OWNERS, dets_owners). % {TablePid, Table}
+-define(STORE, dets). % {User, Table} and {{links,User}, NoLinks}
+
+%%-define(DEBUGF(X,Y), io:format(X, Y)).
+-define(DEBUGF(X,Y), void).
+
+-compile({inline, [{pid2name_1,1}]}).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+
+%% Internal.
+start_link() ->
+ gen_server:start_link({local, ?SERVER_NAME}, dets_server, [self()], []).
+
+start() ->
+ ensure_started().
+
+stop() ->
+ case whereis(?SERVER_NAME) of
+ undefined ->
+ stopped;
+ _Pid ->
+ gen_server:call(?SERVER_NAME, stop, infinity)
+ end.
+
+all() ->
+ call(all).
+
+close(Tab) ->
+ call({close, Tab}).
+
+get_pid(Tab) ->
+ ets:lookup_element(?REGISTRY, Tab, 3).
+
+open_file(File) ->
+ call({open, File}).
+
+open_file(Tab, OpenArgs) ->
+ call({open, Tab, OpenArgs}).
+
+pid2name(Pid) ->
+ ensure_started(),
+ pid2name_1(Pid).
+
+users(Tab) ->
+ call({users, Tab}).
+
+verbose(What) ->
+ call({set_verbose, What}).
+
+call(Message) ->
+ ensure_started(),
+ gen_server:call(?SERVER_NAME, Message, infinity).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+init(Parent) ->
+ Store = init(),
+ {ok, #state{store=Store, parent=Parent, pending = []}}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_call(all, _From, State) ->
+ F = fun(X, A) -> [element(1, X) | A] end,
+ {reply, ets:foldl(F, [], ?REGISTRY), State};
+handle_call({close, Tab}, From, State) ->
+ request([{{close, Tab}, From}], State);
+handle_call({open, File}, From, State) ->
+ request([{{open, File}, From}], State);
+handle_call({open, Tab, OpenArgs}, From, State) ->
+ request([{{open, Tab, OpenArgs}, From}], State);
+handle_call(stop, _From, State) ->
+ {stop, normal, stopped, State};
+handle_call({set_verbose, What}, _From, State) ->
+ set_verbose(What),
+ {reply, ok, State};
+handle_call({users, Tab}, _From, State) ->
+ Users = ets:select(State#state.store, [{{'$1', Tab}, [], ['$1']}]),
+ {reply, Users, State}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+handle_info({pending_reply, {Ref, Result0}}, State) ->
+ {value, #pending{tab = Tab, pid = Pid, from = {FromPid,_Tag}=From,
+ reqtype = ReqT, clients = Clients}} =
+ lists:keysearch(Ref, #pending.ref, State#state.pending),
+ Store = State#state.store,
+ Result =
+ case {Result0, ReqT} of
+ {ok, add_user} ->
+ do_link(Store, FromPid),
+ true = ets:insert(Store, {FromPid, Tab}),
+ ets:update_counter(?REGISTRY, Tab, 1),
+ {ok, Tab};
+ {ok, internal_open} ->
+ link(Pid),
+ do_link(Store, FromPid),
+ true = ets:insert(Store, {FromPid, Tab}),
+ true = ets:insert(?REGISTRY, {Tab, 1, Pid}),
+ true = ets:insert(?OWNERS, {Pid, Tab}),
+ {ok, Tab};
+ {Reply, _} -> % ok or Error
+ Reply
+ end,
+ gen_server:reply(From, Result),
+ NP = lists:keydelete(Pid, #pending.pid, State#state.pending),
+ State1 = State#state{pending = NP},
+ request(Clients, State1);
+handle_info({'EXIT', Pid, _Reason}, State) ->
+ Store = State#state.store,
+ case pid2name_1(Pid) of
+ {ok, Tab} ->
+ %% A table was killed.
+ true = ets:delete(?REGISTRY, Tab),
+ true = ets:delete(?OWNERS, Pid),
+ Users = ets:select(State#state.store, [{{'$1', Tab}, [], ['$1']}]),
+ true = ets:match_delete(Store, {'_', Tab}),
+ lists:foreach(fun(User) -> do_unlink(Store, User) end, Users),
+ {noreply, State};
+ undefined ->
+ %% Close all tables used by Pid.
+ F = fun({FromPid, Tab}, S) ->
+ {_, S1} = handle_close(S, {close, Tab},
+ {FromPid, notag}, Tab),
+ S1
+ end,
+ State1 = lists:foldl(F, State, ets:lookup(Store, Pid)),
+ {noreply, State1}
+ end;
+handle_info(_Message, State) ->
+ {noreply, State}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
+ensure_started() ->
+ case whereis(?SERVER_NAME) of
+ undefined ->
+ DetsSup = {dets_sup, {dets_sup, start_link, []}, permanent,
+ 1000, supervisor, [dets_sup]},
+ _ = supervisor:start_child(kernel_safe_sup, DetsSup),
+ DetsServer = {?SERVER_NAME, {?MODULE, start_link, []},
+ permanent, 2000, worker, [?MODULE]},
+ _ = supervisor:start_child(kernel_safe_sup, DetsServer),
+ ok;
+ _ -> ok
+ end.
+
+init() ->
+ set_verbose(verbose_flag()),
+ process_flag(trap_exit, true),
+ ets:new(?REGISTRY, [set, named_table]),
+ ets:new(?OWNERS, [set, named_table]),
+ ets:new(?STORE, [duplicate_bag]).
+
+verbose_flag() ->
+ case init:get_argument(dets) of
+ {ok, Args} ->
+ lists:member(["verbose"], Args);
+ _ ->
+ false
+ end.
+
+set_verbose(true) ->
+ put(verbose, yes);
+set_verbose(_) ->
+ erase(verbose).
+
+%% Inlined.
+pid2name_1(Pid) ->
+ case ets:lookup(?OWNERS, Pid) of
+ [] -> undefined;
+ [{_Pid,Tab}] -> {ok, Tab}
+ end.
+
+request([{Req, From} | L], State) ->
+ Res = case Req of
+ {close, Tab} ->
+ handle_close(State, Req, From, Tab);
+ {open, File} ->
+ do_internal_open(State, From, [File, get(verbose)]);
+ {open, Tab, OpenArgs} ->
+ do_open(State, Req, From, OpenArgs, Tab)
+ end,
+ State2 = case Res of
+ {pending, State1} ->
+ State1;
+ {Reply, State1} ->
+ gen_server:reply(From, Reply),
+ State1
+ end,
+ request(L, State2);
+request([], State) ->
+ {noreply, State}.
+
+%% -> {pending, NewState} | {Reply, NewState}
+do_open(State, Req, From, Args, Tab) ->
+ case check_pending(Tab, From, State, Req) of
+ {pending, NewState} -> {pending, NewState};
+ false ->
+ case ets:lookup(?REGISTRY, Tab) of
+ [] ->
+ A = [Tab, Args, get(verbose)],
+ do_internal_open(State, From, A);
+ [{Tab, _Counter, Pid}] ->
+ pending_call(Tab, Pid, make_ref(), From, Args,
+ add_user, State)
+ end
+ end.
+
+%% -> {pending, NewState} | {Reply, NewState}
+do_internal_open(State, From, Args) ->
+ case supervisor:start_child(dets_sup, [self()]) of
+ {ok, Pid} ->
+ Ref = make_ref(),
+ Tab = case Args of
+ [T, _, _] -> T;
+ [_, _] -> Ref
+ end,
+ pending_call(Tab, Pid, Ref, From, Args, internal_open, State);
+ Error ->
+ {Error, State}
+ end.
+
+%% -> {pending, NewState} | {Reply, NewState}
+handle_close(State, Req, {FromPid,_Tag}=From, Tab) ->
+ case check_pending(Tab, From, State, Req) of
+ {pending, NewState} -> {pending, NewState};
+ false ->
+ Store = State#state.store,
+ case ets:match_object(Store, {FromPid, Tab}) of
+ [] ->
+ ?DEBUGF("DETS: Table ~w close attempt by non-owner~w~n",
+ [Tab, FromPid]),
+ {{error, not_owner}, State};
+ [_ | Keep] ->
+ case ets:lookup(?REGISTRY, Tab) of
+ [{Tab, 1, Pid}] ->
+ do_unlink(Store, FromPid),
+ true = ets:delete(?REGISTRY, Tab),
+ true = ets:delete(?OWNERS, Pid),
+ true = ets:match_delete(Store, {FromPid, Tab}),
+ unlink(Pid),
+ pending_call(Tab, Pid, make_ref(), From, [],
+ internal_close, State);
+ [{Tab, _Counter, Pid}] ->
+ do_unlink(Store, FromPid),
+ true = ets:match_delete(Store, {FromPid, Tab}),
+ [true = ets:insert(Store, K) || K <- Keep],
+ ets:update_counter(?REGISTRY, Tab, -1),
+ pending_call(Tab, Pid, make_ref(), From, [],
+ remove_user, State)
+ end
+ end
+ end.
+
+%% Links with counters
+do_link(Store, Pid) ->
+ Key = {links, Pid},
+ case ets:lookup(Store, Key) of
+ [] ->
+ true = ets:insert(Store, {Key, 1}),
+ link(Pid);
+ [{_, C}] ->
+ true = ets:delete(Store, Key),
+ true = ets:insert(Store, {Key, C+1})
+ end.
+
+do_unlink(Store, Pid) ->
+ Key = {links, Pid},
+ case ets:lookup(Store, Key) of
+ [{_, C}] when C > 1 ->
+ true = ets:delete(Store, Key),
+ true = ets:insert(Store, {Key, C-1});
+ _ ->
+ true = ets:delete(Store, Key),
+ unlink(Pid)
+
+ end.
+
+pending_call(Tab, Pid, Ref, {FromPid, _Tag}=From, Args, ReqT, State) ->
+ Server = self(),
+ F = fun() ->
+ Res = case ReqT of
+ add_user ->
+ dets:add_user(Pid, Tab, Args);
+ internal_open ->
+ dets:internal_open(Pid, Ref, Args);
+ internal_close ->
+ dets:internal_close(Pid);
+ remove_user ->
+ dets:remove_user(Pid, FromPid)
+ end,
+ Server ! {pending_reply, {Ref, Res}}
+ end,
+ _ = spawn(F),
+ PD = #pending{tab = Tab, ref = Ref, pid = Pid, reqtype = ReqT,
+ from = From, clients = []},
+ P = [PD | State#state.pending],
+ {pending, State#state{pending = P}}.
+
+check_pending(Tab, From, State, Req) ->
+ case lists:keysearch(Tab, #pending.tab, State#state.pending) of
+ {value, #pending{tab = Tab, clients = Clients}=P} ->
+ NP = lists:keyreplace(Tab, #pending.tab, State#state.pending,
+ P#pending{clients = Clients++[{Req,From}]}),
+ {pending, State#state{pending = NP}};
+ false ->
+ false
+ end.
diff --git a/lib/stdlib/src/dets_sup.erl b/lib/stdlib/src/dets_sup.erl
new file mode 100644
index 0000000000..5c6caa787d
--- /dev/null
+++ b/lib/stdlib/src/dets_sup.erl
@@ -0,0 +1,31 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dets_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0, init/1]).
+
+start_link() ->
+ supervisor:start_link({local, dets_sup}, dets_sup, []).
+
+init([]) ->
+ SupFlags = {simple_one_for_one, 4, 3600},
+ Child = {dets, {dets, istart_link, []}, temporary, 30000, worker, [dets]},
+ {ok, {SupFlags, [Child]}}.
diff --git a/lib/stdlib/src/dets_utils.erl b/lib/stdlib/src/dets_utils.erl
new file mode 100644
index 0000000000..5db2ad3049
--- /dev/null
+++ b/lib/stdlib/src/dets_utils.erl
@@ -0,0 +1,1801 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dets_utils).
+
+%% Utility functions common to several dets file formats.
+%% To be used from dets, dets_v8 and dets_v9 only.
+
+-export([cmp/2, msort/1, mkeysort/2, mkeysearch/3, family/1]).
+
+-export([rename/2, pread/2, pread/4, ipread/3, pwrite/2, write/2,
+ truncate/2, position/2, sync/1, open/2, truncate/3, fwrite/3,
+ write_file/2, position/3, position_close/3, pwrite/4,
+ pwrite/3, pread_close/4, read_n/2, pread_n/3, read_4/2]).
+
+-export([code_to_type/1, type_to_code/1]).
+
+-export([corrupt_reason/2, corrupt/2, corrupt_file/2,
+ vformat/2, file_error/2]).
+
+-export([debug_mode/0, bad_object/2]).
+
+-export([cache_lookup/4, cache_size/1, new_cache/1,
+ reset_cache/1, is_empty_cache/1]).
+
+-export([empty_free_lists/0, init_alloc/1, alloc_many/4, alloc/2,
+ free/3, get_freelists/1, all_free/1, all_allocated/1,
+ all_allocated_as_list/1, find_allocated/4, find_next_allocated/3,
+ log2/1, make_zeros/1]).
+
+-export([init_slots_from_old_file/2]).
+
+-export([list_to_tree/1, tree_to_bin/5]).
+
+-compile({inline, [{sz2pos,1}, {adjust_addr,3}]}).
+-compile({inline, [{bplus_mk_leaf,1}, {bplus_get_size,1},
+ {bplus_get_tree,2}, {bplus_get_lkey,2},
+ {bplus_get_rkey,2}]}).
+
+%% Debug
+-export([init_disk_map/1, stop_disk_map/0,
+ disk_map_segment_p/2, disk_map_segment/2]).
+
+-include("dets.hrl").
+
+%%% A total ordering of all Erlang terms.
+
+%% -> -1 | 0 | 1. T1 is (smaller than | equal | greater than) T2.
+%% If is_integer(I), is_float(F), I == F then I is deemed smaller than F.
+cmp(T, T) ->
+ 0;
+cmp([E1 | T1], [E2 | T2]) ->
+ case cmp(E1, E2) of
+ 0 -> cmp(T1, T2);
+ R -> R
+ end;
+cmp(T1, T2) when tuple_size(T1) =:= tuple_size(T2) ->
+ tcmp(T1, T2, 1, tuple_size(T1));
+cmp(I, F) when is_integer(I), is_float(F) ->
+ -1;
+cmp(F, I) when is_float(F), is_integer(I) ->
+ 1;
+cmp(T1, T2) when T1 < T2 ->
+ -1;
+cmp(_T1, _T2) -> % when _T1 > _T2
+ 1.
+
+tcmp(T1, T2, I, I) ->
+ cmp(element(I, T1), element(I, T2));
+tcmp(T1, T2, I, N) ->
+ case cmp(element(I, T1), element(I, T2)) of
+ 0 -> tcmp(T1, T2, I + 1, N);
+ R -> R
+ end.
+
+msort(L) ->
+ %% sort is very much faster than msort, let it do most of the work.
+ F = fun(X, Y) -> cmp(X, Y) =< 0 end,
+ lists:sort(F, lists:sort(L)).
+
+mkeysort(I, L) ->
+ F = fun(X, Y) -> cmp(element(I, X), element(I, Y)) =< 0 end,
+ %% keysort is much faster than mkeysort, let it do most of the work.
+ lists:sort(F, lists:keysort(I, L)).
+
+mkeysearch(Key, I, L) ->
+ case lists:keysearch(Key, I, L) of
+ {value, Value}=Reply when element(I, Value) =:= Key ->
+ Reply;
+ false ->
+ false;
+ _ ->
+ mkeysearch2(Key, I, L)
+ end.
+
+mkeysearch2(_Key, _I, []) ->
+ false;
+mkeysearch2(Key, I, [E | _L]) when element(I, E) =:= Key ->
+ {value, E};
+mkeysearch2(Key, I, [_ | L]) ->
+ mkeysearch2(Key, I, L).
+
+%% Be careful never to compare keys, but use matching instead.
+%% Otherwise sofs could have been used:
+%% sofs:to_external(sofs:relation_to_family(sofs:relation(L, 2))).
+family([]) ->
+ [];
+family(L) ->
+ [{K,V}|KVL] = mkeysort(1, L),
+ per_key(KVL, K, [V], []).
+
+per_key([], K, Vs, KVs) ->
+ lists:reverse(KVs, [{K,msort(Vs)}]);
+per_key([{K,V}|L], K, Vs, KVs) -> % match
+ per_key(L, K, [V|Vs], KVs);
+per_key([{K1,V}|L], K, Vs, KVs) ->
+ per_key(L, K1, [V], [{K,msort(Vs)}|KVs]).
+
+rename(From, To) ->
+ case file:rename(From, To) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ {error, {file_error, {From, To}, Reason}}
+ end.
+
+%% -> {ok, Bins} | throw({NewHead, Error})
+pread(Positions, Head) ->
+ R = case file:pread(Head#head.fptr, Positions) of
+ {ok, Bins} ->
+ %% file:pread/2 can return 'eof' as "data".
+ case lists:member(eof, Bins) of
+ true ->
+ {error, {premature_eof, Head#head.filename}};
+ false ->
+ {ok, Bins}
+ end;
+ {error, Reason} when enomem =:= Reason; einval =:= Reason ->
+ {error, {bad_object_header, Head#head.filename}};
+ {error, Reason} ->
+ {file_error, Head#head.filename, Reason}
+ end,
+ case R of
+ {ok, _Bins} ->
+ R;
+ Error ->
+ throw(corrupt(Head, Error))
+ end.
+
+%% -> {ok, binary()} | throw({NewHead, Error})
+pread(Head, Pos, Min, Extra) ->
+ R = case file:pread(Head#head.fptr, Pos, Min+Extra) of
+ {error, Reason} when enomem =:= Reason; einval =:= Reason ->
+ {error, {bad_object_header, Head#head.filename}};
+ {error, Reason} ->
+ {file_error, Head#head.filename, Reason};
+ {ok, Bin} when byte_size(Bin) < Min ->
+ {error, {premature_eof, Head#head.filename}};
+ OK -> OK
+ end,
+ case R of
+ {ok, _Bin} ->
+ R;
+ Error ->
+ throw(corrupt(Head, Error))
+ end.
+
+%% -> eof | [] | {ok, {Size, Pointer, binary()}}
+ipread(Head, Pos1, MaxSize) ->
+ try
+ disk_map_pread(Pos1)
+ catch Bad ->
+ throw(corrupt_reason(Head, {disk_map, Bad}))
+ end,
+ case file:ipread_s32bu_p32bu(Head#head.fptr, Pos1, MaxSize) of
+ {ok, {0, 0, eof}} ->
+ [];
+ {ok, Reply} ->
+ {ok, Reply};
+ _Else ->
+ eof
+ end.
+
+%% -> {Head, ok} | throw({Head, Error})
+pwrite(Head, []) ->
+ {Head, ok};
+pwrite(Head, Bins) ->
+ try
+ disk_map(Bins)
+ catch Bad ->
+ throw(corrupt_reason(Head, {disk_map, Bad, Bins}))
+ end,
+ case file:pwrite(Head#head.fptr, Bins) of
+ ok ->
+ {Head, ok};
+ Error ->
+ corrupt_file(Head, Error)
+ end.
+
+%% -> ok | throw({Head, Error})
+write(_Head, []) ->
+ ok;
+write(Head, Bins) ->
+ case file:write(Head#head.fptr, Bins) of
+ ok ->
+ ok;
+ Error ->
+ corrupt_file(Head, Error)
+ end.
+
+%% -> ok | throw({Head, Error})
+%% Same as file:write_file/2, but calls file:sync/1.
+write_file(Head, Bin) ->
+ R = case file:open(Head#head.filename, [binary, raw, write]) of
+ {ok, Fd} ->
+ R1 = file:write(Fd, Bin),
+ R2 = file:sync(Fd),
+ file:close(Fd),
+ if R1 =:= ok -> R2; true -> R1 end;
+ Else ->
+ Else
+ end,
+ case R of
+ ok ->
+ ok;
+ Error ->
+ corrupt_file(Head, Error)
+ end.
+
+%% -> ok | throw({Head, Error})
+truncate(Head, Pos) ->
+ case catch truncate(Head#head.fptr, Head#head.filename, Pos) of
+ ok ->
+ ok;
+ Error ->
+ throw(corrupt(Head, Error))
+ end.
+
+%% -> {ok, Pos} | throw({Head, Error})
+position(Head, Pos) ->
+ case file:position(Head#head.fptr, Pos) of
+ {error, _Reason} = Error ->
+ corrupt_file(Head, Error);
+ OK -> OK
+ end.
+
+%% -> ok | throw({Head, Error})
+sync(Head) ->
+ case file:sync(Head#head.fptr) of
+ ok ->
+ ok;
+ Error ->
+ corrupt_file(Head, Error)
+ end.
+
+open(FileSpec, Args) ->
+ case file:open(FileSpec, Args) of
+ {ok, Fd} ->
+ {ok, Fd};
+ Error ->
+ file_error(FileSpec, Error)
+ end.
+
+truncate(Fd, FileName, Pos) ->
+ if
+ Pos =:= cur ->
+ ok;
+ true ->
+ position(Fd, FileName, Pos)
+ end,
+ case file:truncate(Fd) of
+ ok ->
+ ok;
+ Error ->
+ file_error(FileName, {error, Error})
+ end.
+
+fwrite(Fd, FileName, B) ->
+ case file:write(Fd, B) of
+ ok -> ok;
+ Error -> file_error_close(Fd, FileName, Error)
+ end.
+
+position(Fd, FileName, Pos) ->
+ case file:position(Fd, Pos) of
+ {error, Error} -> file_error(FileName, {error, Error});
+ OK -> OK
+ end.
+
+position_close(Fd, FileName, Pos) ->
+ case file:position(Fd, Pos) of
+ {error, Error} -> file_error_close(Fd, FileName, {error, Error});
+ OK -> OK
+ end.
+
+pwrite(Fd, FileName, Position, B) ->
+ case file:pwrite(Fd, Position, B) of
+ ok -> ok;
+ Error -> file_error(FileName, {error, Error})
+ end.
+
+pwrite(Fd, FileName, Bins) ->
+ case file:pwrite(Fd, Bins) of
+ ok ->
+ ok;
+ {error, {_NoWrites, Reason}} ->
+ file_error(FileName, {error, Reason})
+ end.
+
+pread_close(Fd, FileName, Pos, Size) ->
+ case file:pread(Fd, Pos, Size) of
+ {error, Error} ->
+ file_error_close(Fd, FileName, {error, Error});
+ {ok, Bin} when byte_size(Bin) < Size ->
+ file:close(Fd),
+ throw({error, {tooshort, FileName}});
+ eof ->
+ file:close(Fd),
+ throw({error, {tooshort, FileName}});
+ OK -> OK
+ end.
+
+file_error(FileName, {error, Reason}) ->
+ throw({error, {file_error, FileName, Reason}}).
+
+file_error_close(Fd, FileName, {error, Reason}) ->
+ file:close(Fd),
+ throw({error, {file_error, FileName, Reason}}).
+
+debug_mode() ->
+ os:getenv("DETS_DEBUG") =:= "true".
+
+bad_object(Where, Extra) ->
+ case debug_mode() of
+ true ->
+ {bad_object, Where, Extra};
+ false ->
+ %% Avoid showing possibly secret data on the error logger.
+ {bad_object, Where}
+ end.
+
+read_n(Fd, Max) ->
+ case file:read(Fd, Max) of
+ {ok, Bin} ->
+ Bin;
+ _Else ->
+ eof
+ end.
+
+pread_n(Fd, Position, Max) ->
+ case file:pread(Fd, Position, Max) of
+ {ok, Bin} ->
+ Bin;
+ _ ->
+ eof
+ end.
+
+read_4(Fd, Position) ->
+ {ok, _} = file:position(Fd, Position),
+ <<Four:32>> = dets_utils:read_n(Fd, 4),
+ Four.
+
+corrupt_file(Head, {error, Reason}) ->
+ Error = {error, {file_error, Head#head.filename, Reason}},
+ throw(corrupt(Head, Error)).
+
+%% -> {NewHead, Error}
+corrupt_reason(Head, Reason0) ->
+ Reason = case get_disk_map() of
+ no_disk_map ->
+ Reason0;
+ DM ->
+ ST = erlang:get_stacktrace(),
+ PD = get(),
+ {Reason0, ST, PD, DM}
+ end,
+ Error = {error, {Reason, Head#head.filename}},
+ corrupt(Head, Error).
+
+corrupt(Head, Error) ->
+ case get(verbose) of
+ yes ->
+ error_logger:format("** dets: Corrupt table ~p: ~p\n",
+ [Head#head.name, Error]);
+ _ -> ok
+ end,
+ case Head#head.update_mode of
+ {error, _} ->
+ {Head, Error};
+ _ ->
+ {Head#head{update_mode = Error}, Error}
+ end.
+
+vformat(F, As) ->
+ case get(verbose) of
+ yes -> error_logger:format(F, As);
+ _ -> ok
+ end.
+
+code_to_type(?SET) -> set;
+code_to_type(?BAG) -> bag;
+code_to_type(?DUPLICATE_BAG) -> duplicate_bag;
+code_to_type(_Type) -> badtype.
+
+type_to_code(set) -> ?SET;
+type_to_code(bag) -> ?BAG;
+type_to_code(duplicate_bag) -> ?DUPLICATE_BAG.
+
+%%%
+%%% Write Cache
+%%%
+
+cache_size(C) ->
+ {C#cache.delay, C#cache.tsize}.
+
+%% -> [object()] | false
+cache_lookup(Type, [Key | Keys], CL, LU) ->
+ %% mkeysearch returns the _first_ tuple with a matching key.
+ case mkeysearch(Key, 1, CL) of
+ {value, {Key,{_Seq,{insert,Object}}}} when Type =:= set ->
+ cache_lookup(Type, Keys, CL, [Object | LU]);
+ {value, {Key,{_Seq,delete_key}}} ->
+ cache_lookup(Type, Keys, CL, LU);
+ _ ->
+ false
+ end;
+cache_lookup(_Type, [], _CL, LU) ->
+ LU.
+
+reset_cache(C) ->
+ WrTime = C#cache.wrtime,
+ NewWrTime = if
+ WrTime =:= undefined ->
+ WrTime;
+ true ->
+ now()
+ end,
+ PK = family(C#cache.cache),
+ NewC = C#cache{cache = [], csize = 0, inserts = 0, wrtime = NewWrTime},
+ {NewC, C#cache.inserts, PK}.
+
+is_empty_cache(Cache) ->
+ Cache#cache.cache =:= [].
+
+new_cache({Delay, Size}) ->
+ #cache{cache = [], csize = 0, inserts = 0,
+ tsize = Size, wrtime = undefined, delay = Delay}.
+
+%%%
+%%% Buddy System
+%%%
+
+%% Definitions for the buddy allocator.
+-define(MAXBUD, 32). % 2 GB is maximum file size
+-define(MAXFREELISTS, 50000000). % Bytes reserved for the free lists (at end).
+
+%%-define(DEBUG(X, Y), io:format(X, Y)).
+-define(DEBUG(X, Y), true).
+
+%%% Algorithm : We use a buddy system on each file. This is nicely described
+%%% in i.e. the last chapter of the first-grade text book
+%%% Data structures and algorithms by Aho, Hopcroft and
+%%% Ullman. I think buddy systems were invented by Knuth, a long
+%%% time ago.
+
+init_slots_from_old_file([{Slot,Addr} | T], Ftab) ->
+ init_slot(Slot+1,[{Slot,Addr} | T], Ftab);
+init_slots_from_old_file([], Ftab) ->
+ Ftab.
+
+init_slot(_Slot,[], Ftab) ->
+ Ftab; % should never happen
+init_slot(_Slot,[{_Addr,0}|T], Ftab) ->
+ init_slots_from_old_file(T, Ftab);
+init_slot(Slot,[{_Slot1,Addr}|T], Ftab) ->
+ Stree = element(Slot, Ftab),
+ %% io:format("init_slot ~p:~p~n",[Slot, Addr]),
+ init_slot(Slot,T,setelement(Slot, Ftab, bplus_insert(Stree, Addr))).
+
+%%% The free lists are kept in RAM, and written to the end of the file
+%%% from time to time. It is possible that a considerable amount of
+%%% memory is used for a fragmented file.
+%%%
+%%% To make things (slightly) worse (from a memory usage point of
+%%% view), each traversal of the file starts with making a "map" of
+%%% the allocated areas; only the allocated areas will be
+%%% traversed. Creating a map involves inspecting and sorting the free
+%%% lists. Since the map is passed on between client and server, it
+%%% has to be a binary (to avoid copying a possibly huge term).
+%%%
+%%% An active map should always be protected by fixing the table. This
+%%% prevents insertion of objects into the mapped area (where some
+%%% objects may have been deleted). The means for implementing this
+%%% protection is a copy of the free lists (using even more memory, if
+%%% objects are inserted). The position to write an inserted object is
+%%% found by looking at the free lists from the time when the table
+%%% was fixed; areas within the mapped area that have been freed are
+%%% hidden from the allocator.
+
+%% -> free_table()
+%% A free table is a tuple of ?MAXBUD elements, element i handling
+%% buddies of size 2^(i-1).
+init_alloc(Base) ->
+ Ftab = empty_free_lists(),
+ Empty = bplus_empty_tree(),
+ setelement(?MAXBUD, Ftab, bplus_insert(Empty, Base)).
+
+empty_free_lists() ->
+ Empty = bplus_empty_tree(),
+ %% initiate a tuple with ?MAXBUD "Empty" elements
+ erlang:make_tuple(?MAXBUD, Empty).
+
+%% Only used when repairing or initiating.
+alloc_many(Head, _Sz, 0, _A0) ->
+ Head;
+alloc_many(Head, Sz, N, A0) ->
+ Ftab = Head#head.freelists,
+ Head#head{freelists = alloc_many1(Ftab, 1, Sz * N, A0, Head)}.
+
+%% -> NewFtab | throw(Error)
+alloc_many1(Ftab, Pos, Size, A0, H) ->
+ {FPos, Addr} = find_first_free(Ftab, Pos, Pos, H),
+ true = Addr >= A0, % assertion
+ if
+ ?POW(FPos - 1) >= Size ->
+ alloc_many2(Ftab, sz2pos(Size), Size, A0, H);
+ true ->
+ NewFtab = reserve_buddy(Ftab, FPos, FPos, Addr),
+ NSize = Size - ?POW(FPos-1),
+ alloc_many1(NewFtab, FPos, NSize, Addr, H)
+ end.
+
+alloc_many2(Ftab, _Pos, 0, _A0, _H) ->
+ Ftab;
+alloc_many2(Ftab, Pos, Size, A0, H) when Size band ?POW(Pos-1) > 0 ->
+ {FPos, Addr} = find_first_free(Ftab, Pos, Pos, H),
+ true = Addr >= A0, % assertion
+ NewFtab = reserve_buddy(Ftab, FPos, Pos, Addr),
+ NSize = Size - ?POW(Pos - 1),
+ alloc_many2(NewFtab, Pos-1, NSize, Addr, H);
+alloc_many2(Ftab, Pos, Size, A0, H) ->
+ alloc_many2(Ftab, Pos-1, Size, A0, H).
+
+%% -> {NewHead, Addr, Log2} | throw(Error)
+alloc(Head, Sz) when Head#head.fixed =/= false -> % when Sz > 0
+ ?DEBUG("alloc of size ~p (fixed)", [Sz]),
+ Pos = sz2pos(Sz),
+ {Frozen, Ftab} = Head#head.freelists,
+ {FPos, Addr} = find_first_free(Frozen, Pos, Pos, Head),
+ NewFrozen = reserve_buddy(Frozen, FPos, Pos, Addr),
+ Ftab1 = undo_free(Ftab, FPos, Addr, Head#head.base),
+ NewFtab = move_down(Ftab1, FPos, Pos, Addr),
+ NewFreelists = {NewFrozen, NewFtab},
+ {Head#head{freelists = NewFreelists}, Addr, Pos};
+alloc(Head, Sz) when Head#head.fixed =:= false -> % when Sz > 0
+ ?DEBUG("alloc of size ~p", [Sz]),
+ Pos = sz2pos(Sz),
+ Ftab = Head#head.freelists,
+ {FPos, Addr} = find_first_free(Ftab, Pos, Pos, Head),
+ NewFtab = reserve_buddy(Ftab, FPos, Pos, Addr),
+ {Head#head{freelists = NewFtab}, Addr, Pos}.
+
+find_first_free(_Ftab, Pos, _Pos0, Head) when Pos > ?MAXBUD ->
+ throw({error, {no_more_space_on_file, Head#head.filename}});
+find_first_free(Ftab, Pos, Pos0, Head) ->
+ PosTab = element(Pos, Ftab),
+ case bplus_lookup_first(PosTab) of
+ undefined ->
+ find_first_free(Ftab, Pos+1, Pos0, Head);
+ {ok, Addr} when Addr + ?POW(Pos0-1) > ?POW(?MAXBUD-1)-?MAXFREELISTS ->
+ %% We would occupy (some of) the area reserved for the free lists.
+ throw({error, {no_more_space_on_file, Head#head.filename}});
+ {ok, Addr} ->
+ {Pos, Addr}
+ end.
+
+%% When the table is fixed, free/4 may have joined buddies so that the
+%% requested block is now part of some larger block. We have to find
+%% that block, and insert free buddies along the way.
+undo_free(Ftab, Pos, Addr, Base) ->
+ PosTab = element(Pos, Ftab),
+ case bplus_lookup(PosTab, Addr) of
+ undefined ->
+ {BuddyAddr, MoveUpAddr} = my_buddy(Addr, ?POW(Pos-1), Base),
+ NewFtab = setelement(Pos, Ftab, bplus_insert(PosTab, BuddyAddr)),
+ undo_free(NewFtab, Pos+1, MoveUpAddr, Base);
+ {ok, Addr} ->
+ NewPosTab = bplus_delete(PosTab, Addr),
+ setelement(Pos, Ftab, NewPosTab)
+ end.
+
+reserve_buddy(Ftab, Pos, Pos0, Addr) ->
+ PosTab = element(Pos, Ftab),
+ NewPosTab = bplus_delete(PosTab, Addr),
+ NewFtab = setelement(Pos, Ftab, NewPosTab),
+ move_down(NewFtab, Pos, Pos0, Addr).
+
+move_down(Ftab, Pos, Pos, _Addr) ->
+ ?DEBUG(" to address ~p, table ~p (~p bytes)~n",
+ [_Addr, Pos, ?POW(Pos-1)]),
+ Ftab;
+move_down(Ftab, Pos, Pos0, Addr) ->
+ Pos_1 = Pos - 1,
+ Size = ?POW(Pos_1),
+ HighBuddy = (Addr + (Size bsr 1)),
+ NewPosTab_1 = bplus_insert(element(Pos_1, Ftab), HighBuddy),
+ NewFtab = setelement(Pos_1, Ftab, NewPosTab_1),
+ move_down(NewFtab, Pos_1, Pos0, Addr).
+
+%% -> {Head, Log2}
+free(Head, Addr, Sz) ->
+ ?DEBUG("free of size ~p at address ~p~n", [Sz, Addr]),
+ Ftab = get_freelists(Head),
+ Pos = sz2pos(Sz),
+ {set_freelists(Head, free_in_pos(Ftab, Addr, Pos, Head#head.base)), Pos}.
+
+free_in_pos(Ftab, _Addr, Pos, _Base) when Pos > ?MAXBUD ->
+ Ftab;
+free_in_pos(Ftab, Addr, Pos, Base) ->
+ PosTab = element(Pos, Ftab),
+ {BuddyAddr, MoveUpAddr} = my_buddy(Addr, ?POW(Pos-1), Base),
+ case bplus_lookup(PosTab, BuddyAddr) of
+ undefined -> % no buddy found
+ ?DEBUG(" table ~p, no buddy~n", [Pos]),
+ setelement(Pos, Ftab, bplus_insert(PosTab, Addr));
+ {ok, BuddyAddr} -> % buddy found
+ PosTab1 = bplus_delete(PosTab, Addr),
+ PosTab2 = bplus_delete(PosTab1, BuddyAddr),
+ ?DEBUG(" table ~p, with buddy ~p~n", [Pos, BuddyAddr]),
+ NewFtab = setelement(Pos, Ftab, PosTab2),
+ free_in_pos(NewFtab, MoveUpAddr, Pos+1, Base)
+ end.
+
+get_freelists(Head) when Head#head.fixed =:= false ->
+ Head#head.freelists;
+get_freelists(Head) when Head#head.fixed =/= false ->
+ {_Frozen, Current} = Head#head.freelists,
+ Current.
+
+set_freelists(Head, Ftab) when Head#head.fixed =:= false ->
+ Head#head{freelists = Ftab};
+set_freelists(Head, Ftab) when Head#head.fixed =/= false ->
+ {Frozen, _} = Head#head.freelists,
+ Head#head{freelists = {Frozen,Ftab}}.
+
+%% Bug: If Sz0 is equal to 2^k for some k, then 2^(k+1) bytes are
+%% allocated (wasting 2^k bytes). Inlined.
+sz2pos(N) when N > 0 ->
+ 1 + log2(N+1).
+
+%% Returns the i such that 2^(i-1) < N =< 2^i.
+log2(N) when is_integer(N), N >= 0 ->
+ if N > ?POW(8) ->
+ if N > ?POW(10) ->
+ if N > ?POW(11) ->
+ if N > ?POW(12) ->
+ 12 + if N band (?POW(12)-1) =:= 0 ->
+ log2(N bsr 12);
+ true -> log2(1 + (N bsr 12))
+ end;
+ true -> 12
+ end;
+ true -> 11
+ end;
+ N > ?POW(9) -> 10;
+ true -> 9
+ end;
+ N > ?POW(4) ->
+ if N > ?POW(6) ->
+ if N > ?POW(7) -> 8;
+ true -> 7
+ end;
+ N > ?POW(5) -> 6;
+ true -> 5
+ end;
+ N > ?POW(2) ->
+ if
+ N > ?POW(3) -> 4;
+ true -> 3
+ end;
+ N > ?POW(1) -> 2;
+ N >= ?POW(0) -> 1;
+ true -> 0
+ end.
+
+make_zeros(0) -> [];
+make_zeros(N) when N rem 2 =:= 0 ->
+ P = make_zeros(N div 2),
+ [P|P];
+make_zeros(N) ->
+ P = make_zeros(N div 2),
+ [0,P|P].
+
+%% Calculate the buddy of Addr
+my_buddy(Addr, Sz, Base) ->
+ case (Addr - Base) band Sz of
+ 0 -> % even, buddy is higher addr
+ {Addr+Sz, Addr};
+ _ -> % odd, buddy is lower addr
+ T = Addr-Sz,
+ {T, T}
+ end.
+
+all_free(Head) ->
+ Tab = get_freelists(Head),
+ Base = Head#head.base,
+ case all_free(all(Tab), Base, Base, []) of
+ [{Base,Base} | L] -> L;
+ L -> L
+ end.
+
+all_free([], X0, Y0, F) ->
+ lists:reverse([{X0,Y0} | F]);
+all_free([{X,Y} | L], X0, Y0, F) when Y0 =:= X ->
+ all_free(L, X0, Y, F);
+all_free([{X,Y} | L], X0, Y0, F) when Y0 < X ->
+ all_free(L, X, Y, [{X0,Y0} | F]).
+
+all_allocated(Head) ->
+ all_allocated(all(get_freelists(Head)), 0, Head#head.base, []).
+
+all_allocated([], _X0, _Y0, []) ->
+ <<>>;
+all_allocated([], _X0, _Y0, A0) ->
+ [<<From:32, To:32>> | A] = lists:reverse(A0),
+ {From, To, list_to_binary(A)};
+all_allocated([{X,Y} | L], X0, Y0, A) when Y0 =:= X ->
+ all_allocated(L, X0, Y, A);
+all_allocated([{X,Y} | L], _X0, Y0, A) when Y0 < X ->
+ all_allocated(L, X, Y, [<<Y0:32,X:32>> | A]).
+
+all_allocated_as_list(Head) ->
+ all_allocated_as_list(all(get_freelists(Head)), 0, Head#head.base, []).
+
+all_allocated_as_list([], _X0, _Y0, []) ->
+ [];
+all_allocated_as_list([], _X0, _Y0, A) ->
+ lists:reverse(A);
+all_allocated_as_list([{X,Y} | L], X0, Y0, A) when Y0 =:= X ->
+ all_allocated_as_list(L, X0, Y, A);
+all_allocated_as_list([{X,Y} | L], _X0, Y0, A) when Y0 < X ->
+ all_allocated_as_list(L, X, Y, [[Y0 | X] | A]).
+
+all(Tab) ->
+ all(Tab, tuple_size(Tab), []).
+
+all(_Tab, 0, L) ->
+ %% This is not as bad as it looks. L contains less than 32 runs,
+ %% so there will be only a small number of merges.
+ lists:sort(L);
+all(Tab, I, L) ->
+ LL = collect_tree(element(I, Tab), I, L),
+ all(Tab, I-1, LL).
+
+%% Finds allocated areas between Addr (approx.) and Addr+Length.
+find_allocated(Ftab, Addr, Length, Base) ->
+ MaxAddr = Addr + Length,
+ Ints = collect_all_interval(Ftab, Addr, MaxAddr, Base),
+ allocated(Ints, Addr, MaxAddr, Ftab, Base).
+
+allocated(Some, Addr, Max, Ftab, Base) ->
+ case allocated1(Some, Addr, Max, []) of
+ [] ->
+ case find_next_allocated(Ftab, Addr, Base) of
+ {From,_} ->
+ find_allocated(Ftab, From, ?CHUNK_SIZE, Base);
+ none ->
+ <<>>
+ end;
+ L ->
+ list_to_binary(lists:reverse(L))
+ end.
+
+allocated1([], Y0, Max, A) when Y0 < Max ->
+ [<<Y0:32,Max:32>> | A];
+allocated1([], _Y0, _Max, A) ->
+ A;
+allocated1([{X,Y} | L], Y0, Max, A) when Y0 >= X ->
+ allocated1(L, Y, Max, A);
+allocated1([{X,Y} | L], Y0, Max, A) -> % when Y0 < X
+ allocated1(L, Y, Max, [<<Y0:32,X:32>> | A]).
+
+%% Finds the first allocated area starting at Addr or later.
+find_next_allocated(Ftab, Addr, Base) ->
+ case find_next_free(Ftab, Addr, Base) of
+ none ->
+ none;
+ {Addr1, Pos} when Addr1 =< Addr ->
+ find_next_allocated(Ftab, Addr1 + ?POW(Pos-1), Base);
+ {Next, _Pos} ->
+ {Addr, Next}
+ end.
+
+%% Finds the first free address starting att Addr or later.
+%% -> none | {FirstFreeAddress, FtabPosition}
+find_next_free(Ftab, Addr, Base) ->
+ MaxBud = tuple_size(Ftab),
+ find_next_free(Ftab, Addr, 1, MaxBud, -1, -1, Base).
+
+find_next_free(Ftab, Addr0, Pos, MaxBud, Next, PosN, Base)
+ when Pos =< MaxBud ->
+ Addr = adjust_addr(Addr0, Pos, Base),
+ PosTab = element(Pos, Ftab),
+ case bplus_lookup_next(PosTab, Addr-1) of
+ undefined ->
+ find_next_free(Ftab, Addr0, Pos+1, MaxBud, Next, PosN, Base);
+ {ok, Next1} when PosN =:= -1; Next1 < Next ->
+ find_next_free(Ftab, Addr0, Pos+1, MaxBud, Next1, Pos, Base);
+ {ok, _} ->
+ find_next_free(Ftab, Addr0, Pos+1, MaxBud, Next, PosN, Base)
+ end;
+find_next_free(_Ftab, _Addr, _Pos, _MaxBud, -1, _PosN, _Base) ->
+ none;
+find_next_free(_Ftab, _Addr, _Pos, _MaxBud, Next, PosN, _Base) ->
+ {Next, PosN}.
+
+collect_all_interval(Ftab, Addr, MaxAddr, Base) ->
+ MaxBud = tuple_size(Ftab),
+ collect_all_interval(Ftab, Addr, MaxAddr, 1, MaxBud, Base, []).
+
+collect_all_interval(Ftab, L0, U, Pos, MaxBud, Base, Acc0) when Pos =< MaxBud ->
+ PosTab = element(Pos, Ftab),
+ L = adjust_addr(L0, Pos, Base),
+ Acc = collect_interval(PosTab, Pos, L, U, Acc0),
+ collect_all_interval(Ftab, L0, U, Pos+1, MaxBud, Base, Acc);
+collect_all_interval(_Ftab, _L, _U, _Pos, _MaxBud, _Base, Acc) ->
+ lists:sort(Acc).
+
+%% It could be that Addr is inside a free area. This function adjusts
+%% the address so that is placed on a boundary in the Pos tree. Inlined.
+adjust_addr(Addr, Pos, Base) ->
+ Pow = ?POW(Pos - 1),
+ Rem = (Addr - Base) rem Pow,
+ if
+ Rem =:= 0 ->
+ Addr;
+ Addr < Pow ->
+ Addr;
+ true ->
+ Addr - Rem
+ end.
+
+%%%-----------------------------------------------------------------
+%%% The Disk Map is used for debugging only.
+%%% Very tightly coupled to the way dets_v9 works.
+%%%-----------------------------------------------------------------
+
+-define(DM, disk_map).
+
+get_disk_map() ->
+ case get(?DM) of
+ undefined -> no_disk_map;
+ T -> {disk_map, ets:tab2list(T)}
+ end.
+
+init_disk_map(Name) ->
+ error_logger:info_msg("** dets: (debug) using disk map for ~p~n", [Name]),
+ put(?DM, ets:new(any,[ordered_set])).
+
+stop_disk_map() ->
+ catch ets:delete(erase(?DM)).
+
+disk_map_segment_p(Fd, P) ->
+ case get(?DM) of
+ undefined ->
+ ok;
+ _T ->
+ disk_map_segment(P, pread_n(Fd, P, 8*256))
+ end.
+
+disk_map_segment(P, Segment) ->
+ case get(?DM) of
+ undefined ->
+ ok;
+ T ->
+ Ps = segment_fragment_to_pointers(P, iolist_to_binary(Segment)),
+ Ss = [{X,<<Sz:32,?ACTIVE:32>>} ||
+ {_P1,<<Sz:32,X:32>>} <- Ps,
+ X > 0], % optimization
+ dm(Ps ++ Ss, T)
+ end.
+
+disk_map_pread(P) ->
+ case get(?DM) of
+ undefined ->
+ ok;
+ T ->
+ case ets:lookup(T, P) of
+ [] ->
+ throw({pread, P, 8});
+ [{P,{pointer,0,0}}] ->
+ ok;
+ [{P,{pointer,Pointer,Sz}}] ->
+ case ets:lookup(T, Pointer) of
+ %% _P =/= P after re-hash...
+ [{Pointer,{slot,_P,Sz}}] ->
+ ok;
+ Got ->
+ throw({pread, P, Pointer, Got})
+ end;
+ Got ->
+ throw({pread, P, Got})
+ end
+ end.
+
+-define(STATUS_POS, 4).
+-define(BASE, 1336).
+disk_map(Bins) ->
+ case get(?DM) of
+ undefined ->
+ ok;
+ T ->
+ Bs = [{P,iolist_to_binary(Io)} || {P,Io} <- Bins],
+ dm(Bs, T)
+ end.
+
+dm([{P,_Header} | Bs], T) when P < ?BASE ->
+ dm(Bs, T);
+dm([{P0,<<?FREE:32>>} | Bs], T) ->
+ P = P0 - ?STATUS_POS,
+ case ets:lookup(T, P) of
+ [] ->
+ throw({free, P0});
+ [{P,_OldSz}] ->
+ true = ets:delete(T, P)
+ end,
+ dm(Bs, T);
+dm([{SlotP,<<Sz:32,?ACTIVE:32,_/binary>>} | Bs], T) ->
+ Ptr = case ets:lookup(T, {pointer,SlotP}) of
+ [{{pointer,SlotP}, Pointer}] ->
+ case ets:lookup(T, Pointer) of
+ [{Pointer,{pointer,SlotP,Sz2}}] ->
+ case log2(Sz) =:= log2(Sz2) of
+ true ->
+ Pointer;
+ false ->
+ throw({active, SlotP, Sz, Pointer, Sz2})
+ end;
+ Got ->
+ throw({active, SlotP, Sz, Got})
+ end;
+ [] ->
+ throw({active, SlotP, Sz})
+ end,
+ true = ets:insert(T, {SlotP,{slot,Ptr,Sz}}),
+ dm(Bs, T);
+dm([{P,<<Sz:32,X:32>>} | Bs], T) ->
+ %% Look for slot object in Bs?
+ case prev(P, T) of
+ {Prev, PrevSz} ->
+ throw({prev, P, Sz, X, Prev, PrevSz});
+ ok ->
+ ok
+ end,
+ case next(P, 8, T) of
+ {next, Next} ->
+ %% Can (should?) do more...
+ throw({next, P, Sz, X, Next});
+ ok ->
+ ok
+ end,
+ true = ets:insert(T, {P,{pointer,X,Sz}}),
+ if
+ Sz =:= 0 ->
+ X = 0;
+ true ->
+ true = ets:insert(T, {{pointer,X}, P})
+ end,
+ dm(Bs, T);
+dm([{P,<<X:32>>} | Bs], T) ->
+ case ets:lookup(T, X) of
+ [] -> throw({segment, P, X});
+ [{X,{pointer,0,0}}] -> ok;
+ [{X,{pointer,P,X}}] -> ok
+ end,
+ dm(Bs, T);
+dm([{P,<<_Sz:32,B0/binary>>=B} | Bs], T) ->
+ Overwrite =
+ case catch binary_to_term(B0) of % accepts garbage at end of binary
+ {'EXIT', _} ->
+ <<_Sz1:32,B1/binary>> = B0,
+ case catch binary_to_term(B1) of
+ {'EXIT', _} ->
+ false;
+ _ ->
+ true
+ end;
+ _ ->
+ true
+ end,
+ if
+ Overwrite ->
+ %% overwrite same
+ dm([{P-8,<<(byte_size(B) + 8):32,?ACTIVE:32,B/binary>>} | Bs], T);
+ true ->
+ dm(segment_fragment_to_pointers(P, B)++Bs, T)
+ end;
+dm([], _T) ->
+ ok.
+
+segment_fragment_to_pointers(_P, <<>>) ->
+ [];
+segment_fragment_to_pointers(P, <<SzP:8/binary,B/binary>>) ->
+ [{P,SzP} | segment_fragment_to_pointers(P+8, B)].
+
+prev(P, T) ->
+ case ets:prev(T, P) of
+ '$end_of_table' -> ok;
+ Prev ->
+ case ets:lookup(T, Prev) of
+ [{Prev,{pointer,_Ptr,_}}] when Prev + 8 > P ->
+ {Prev, 8};
+ [{Prev,{slot,_,Sz}}] when Prev + Sz > P ->
+ {Prev, Sz};
+ _ ->
+ ok
+ end
+ end.
+
+next(P, PSz, T) ->
+ case ets:next(T, P) of
+ '$end_of_table' -> ok;
+ Next when P + PSz > Next ->
+ {next, Next};
+ _ ->
+ ok
+ end.
+
+%%%-----------------------------------------------------------------
+%%% These functions implement a B+ tree.
+%%%-----------------------------------------------------------------
+
+-define(max_size, 16).
+-define(min_size, 8).
+%%-----------------------------------------------------------------
+%% Finds out the type of the node: 'l' or 'n'.
+%%-----------------------------------------------------------------
+-define(NODE_TYPE(Tree), element(1, Tree)).
+%% Finds out if a node/leaf is full or not.
+-define(FULL(Tree), (bplus_get_size(Tree) >= ?max_size)).
+%% Finds out if a node/leaf is filled up over its limit.
+-define(OVER_FULL(Tree), (bplus_get_size(Tree) > ?max_size)).
+%% Finds out if a node/leaf has less items than allowed.
+-define(UNDER_FILLED(Tree), (bplus_get_size(Tree) < ?min_size)).
+%% Finds out if a node/leaf has as few items as minimum allowed.
+-define(LOW_FILLED(Tree), (bplus_get_size(Tree) =< ?min_size)).
+%%Returns a key in a leaf at position Pos.
+-define(GET_LEAF_KEY(Leaf, Pos), element(Pos+1, Leaf)).
+
+%% Special for dets.
+collect_tree(v, _TI, Acc) -> Acc;
+collect_tree(T, TI, Acc) ->
+ Pow = ?POW(TI-1),
+ collect_tree2(T, Pow, Acc).
+
+collect_tree2(Tree, Pow, Acc) ->
+ S = bplus_get_size(Tree),
+ case ?NODE_TYPE(Tree) of
+ l ->
+ collect_leaf(Tree, S, Pow, Acc);
+ n ->
+ collect_node(Tree, S, Pow, Acc)
+ end.
+
+collect_leaf(_Leaf, 0, _Pow, Acc) ->
+ Acc;
+collect_leaf(Leaf, I, Pow, Acc) ->
+ Key = ?GET_LEAF_KEY(Leaf, I),
+ V = {Key, Key+Pow},
+ collect_leaf(Leaf, I-1, Pow, [V | Acc]).
+
+collect_node(_Node, 0, _Pow, Acc) ->
+ Acc;
+collect_node(Node, I, Pow, Acc) ->
+ Acc1 = collect_tree2(bplus_get_tree(Node, I), Pow, Acc),
+ collect_node(Node, I-1, Pow, Acc1).
+
+%% Special for dets.
+tree_to_bin(v, _F, _Max, Ws, WsSz) -> {Ws, WsSz};
+tree_to_bin(T, F, Max, Ws, WsSz) ->
+ {N, L1, Ws1, WsSz1} = tree_to_bin2(T, F, Max, 0, [], Ws, WsSz),
+ {N1, L2, Ws2, WsSz2} = F(N, lists:reverse(L1), Ws1, WsSz1),
+ {0, [], NWs, NWsSz} = F(N1, L2, Ws2, WsSz2),
+ {NWs, NWsSz}.
+
+tree_to_bin2(Tree, F, Max, N, Acc, Ws, WsSz) when N >= Max ->
+ {NN, NAcc, NWs, NWsSz} = F(N, lists:reverse(Acc), Ws, WsSz),
+ tree_to_bin2(Tree, F, Max, NN, lists:reverse(NAcc), NWs, NWsSz);
+tree_to_bin2(Tree, F, Max, N, Acc, Ws, WsSz) ->
+ S = bplus_get_size(Tree),
+ case ?NODE_TYPE(Tree) of
+ l ->
+ {N+S, leaf_to_bin(bplus_leaf_to_list(Tree), Acc), Ws, WsSz};
+ n ->
+ node_to_bin(Tree, F, Max, N, Acc, 1, S, Ws, WsSz)
+ end.
+
+node_to_bin(_Node, _F, _Max, N, Acc, I, S, Ws, WsSz) when I > S ->
+ {N, Acc, Ws, WsSz};
+node_to_bin(Node, F, Max, N, Acc, I, S, Ws, WsSz) ->
+ {N1,Acc1,Ws1,WsSz1} =
+ tree_to_bin2(bplus_get_tree(Node, I), F, Max, N, Acc, Ws, WsSz),
+ node_to_bin(Node, F, Max, N1, Acc1, I+1, S, Ws1, WsSz1).
+
+leaf_to_bin([N | L], Acc) ->
+ leaf_to_bin(L, [<<N:32>> | Acc]);
+leaf_to_bin([], Acc) ->
+ Acc.
+
+%% Special for dets.
+list_to_tree(L) ->
+ leafs_to_nodes(L, length(L), fun bplus_mk_leaf/1, []).
+
+leafs_to_nodes([], 0, _F, [T]) ->
+ T;
+leafs_to_nodes([], 0, _F, L) ->
+ leafs_to_nodes(lists:reverse(L), length(L), fun mk_node/1, []);
+leafs_to_nodes(Ls, Sz, F, L) ->
+ I = if
+ Sz =< 16 -> Sz;
+ Sz =< 32 -> Sz div 2;
+ true -> 12
+ end,
+ {L1, R} = split_list(Ls, I, []),
+ N = F(L1),
+ Sz1 = Sz - I,
+ leafs_to_nodes(R, Sz1, F, [N | L]).
+
+mk_node([E | Es]) ->
+ NL = [E | lists:foldr(fun(X, A) -> [get_first_key(X), X | A] end, [], Es)],
+ bplus_mk_node(NL).
+
+split_list(L, 0, SL) ->
+ {SL, L};
+split_list([E | Es], I, SL) ->
+ split_list(Es, I-1, [E | SL]).
+
+get_first_key(T) ->
+ case ?NODE_TYPE(T) of
+ l ->
+ ?GET_LEAF_KEY(T, 1);
+ n ->
+ get_first_key(bplus_get_tree(T, 1))
+ end.
+
+%% Special for dets.
+collect_interval(v, _TI, _L, _U, Acc) -> Acc;
+collect_interval(T, TI, L, U, Acc) ->
+ Pow = ?POW(TI-1),
+ collect_interval2(T, Pow, L, U, Acc).
+
+collect_interval2(Tree, Pow, L, U, Acc) ->
+ S = bplus_get_size(Tree),
+ case ?NODE_TYPE(Tree) of
+ l ->
+ collect_leaf_interval(Tree, S, Pow, L, U, Acc);
+ n ->
+ {Max, _} = bplus_select_sub_tree(Tree, U),
+ {Min, _} = bplus_select_sub_tree_2(Tree, L, Max),
+ collect_node_interval(Tree, Min, Max, Pow, L, U, Acc)
+ end.
+
+collect_leaf_interval(_Leaf, 0, _Pow, _L, _U, Acc) ->
+ Acc;
+collect_leaf_interval(Leaf, I, Pow, L, U, Acc) ->
+ Key = ?GET_LEAF_KEY(Leaf, I),
+ if
+ Key < L ->
+ Acc;
+ Key > U ->
+ collect_leaf_interval(Leaf, I-1, Pow, L, U, Acc);
+ true ->
+ collect_leaf_interval(Leaf, I-1, Pow, L, U, [{Key,Key+Pow} | Acc])
+ end.
+
+collect_node_interval(_Node, I, UP, _Pow, _L, _U, Acc) when I > UP ->
+ Acc;
+collect_node_interval(Node, I, UP, Pow, L, U, Acc) ->
+ Acc1 = collect_interval2(bplus_get_tree(Node, I), Pow, L, U, Acc),
+ collect_node_interval(Node, I+1, UP, Pow, L, U, Acc1).
+
+%%-----------------------------------------------------------------
+%% Func: empty_tree/0
+%% Purpose: Creates a new empty tree.
+%% Returns: tree()
+%%-----------------------------------------------------------------
+bplus_empty_tree() -> v.
+
+%%-----------------------------------------------------------------
+%% Func: lookup/2
+%% Purpose: Looks for Key in the Tree.
+%% Returns: {ok, {Key, Val}} | 'undefined'.
+%%-----------------------------------------------------------------
+bplus_lookup(v, _Key) -> undefined;
+bplus_lookup(Tree, Key) ->
+ case ?NODE_TYPE(Tree) of
+ l ->
+ bplus_lookup_leaf(Key, Tree);
+ n ->
+ {_, SubTree} = bplus_select_sub_tree(Tree, Key),
+ bplus_lookup(SubTree, Key)
+ end.
+
+%%-----------------------------------------------------------------
+%% Searches through a leaf until the Key is ok or
+%% when it is determined that it does not exist.
+%%-----------------------------------------------------------------
+bplus_lookup_leaf(Key, Leaf) ->
+ bplus_lookup_leaf_2(Key, Leaf, bplus_get_size(Leaf)).
+
+bplus_lookup_leaf_2(_, _, 0) -> undefined;
+bplus_lookup_leaf_2(Key, Leaf, N) ->
+ case ?GET_LEAF_KEY(Leaf, N) of
+ Key -> {ok, Key};
+ _ ->
+ bplus_lookup_leaf_2(Key, Leaf, N-1)
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: lookup_first/1
+%% Purpose: Finds the smallest key in the entire Tree.
+%% Returns: {ok, {Key, Val}} | 'undefined'.
+%%-----------------------------------------------------------------
+bplus_lookup_first(v) -> undefined;
+bplus_lookup_first(Tree) ->
+ case ?NODE_TYPE(Tree) of
+ l ->
+ % Then it is the leftmost key here.
+ {ok, ?GET_LEAF_KEY(Tree, 1)};
+ n ->
+ % Look in the leftmost subtree.
+ bplus_lookup_first(bplus_get_tree(Tree, 1))
+ end.
+
+
+%%-----------------------------------------------------------------
+%% Func: lookup_next/2
+%% Purpose: Finds the next key nearest after Key.
+%% Returns: {ok, {Key, Val}} | 'undefined'. NIX!!!
+%%-----------------------------------------------------------------
+bplus_lookup_next(v, _) -> undefined;
+bplus_lookup_next(Tree, Key) ->
+ case ?NODE_TYPE(Tree) of
+ l ->
+ lookup_next_leaf(Key, Tree);
+ n ->
+ {Pos, SubTree} = bplus_select_sub_tree(Tree, Key),
+ case bplus_lookup_next(SubTree, Key) of
+ undefined ->
+ S = bplus_get_size(Tree),
+ if
+ % There is a right brother.
+ S > Pos ->
+ bplus_lookup_first(bplus_get_tree(Tree, Pos+1));
+ % No there is no right brother.
+ true ->
+ undefined
+ end;
+ % We ok a next item.
+ Result ->
+ Result
+ end
+ end.
+
+%%-----------------------------------------------------------------
+%% Returns {ok, NextKey} if there is a key in the leaf which is greater.
+%% If there is no such key we return 'undefined' instead.
+%% Key does not have to be a key in the structure, just a search value.
+%%-----------------------------------------------------------------
+lookup_next_leaf(Key, Leaf) ->
+ lookup_next_leaf_2(Key, Leaf, bplus_get_size(Leaf), 1).
+
+lookup_next_leaf_2(Key, Leaf, Size, Size) ->
+ % This is the rightmost key.
+ K = ?GET_LEAF_KEY(Leaf, Size),
+ if
+ K > Key ->
+ {ok, ?GET_LEAF_KEY(Leaf, Size)};
+ true ->
+ undefined
+ end;
+lookup_next_leaf_2(Key, Leaf, Size, N) ->
+ K = ?GET_LEAF_KEY(Leaf, N),
+ if
+ K < Key ->
+ % K is still smaller, try next in the leaf.
+ lookup_next_leaf_2(Key, Leaf, Size, N+1);
+ Key == K ->
+ % Since this is exact Key it must be the next.
+ {ok, ?GET_LEAF_KEY(Leaf, N+1)};
+ true ->
+ % Key was not an exact specification.
+ % It must be K that is next greater.
+ {ok, ?GET_LEAF_KEY(Leaf, N)}
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: insert/3
+%% Purpose: Inserts a new {Key, Value} into the tree.
+%% Returns: tree()
+%%-----------------------------------------------------------------
+bplus_insert(v, Key) -> bplus_mk_leaf([Key]);
+bplus_insert(Tree, Key) ->
+ NewTree = bplus_insert_in(Tree, Key),
+ case ?OVER_FULL(NewTree) of
+ false ->
+ NewTree;
+ % If the node is over-full the tree will grow.
+ true ->
+ {LTree, DKey, RTree} =
+ case ?NODE_TYPE(NewTree) of
+ l ->
+ bplus_split_leaf(NewTree);
+ n ->
+ bplus_split_node(NewTree)
+ end,
+ bplus_mk_node([LTree, DKey, RTree])
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: delete/2
+%% Purpose: Deletes a key from the tree (if present).
+%% Returns: tree()
+%%-----------------------------------------------------------------
+bplus_delete(v, _Key) -> v;
+bplus_delete(Tree, Key) ->
+ NewTree = bplus_delete_in(Tree, Key),
+ S = bplus_get_size(NewTree),
+ case ?NODE_TYPE(NewTree) of
+ l ->
+ if
+ S =:= 0 ->
+ v;
+ true ->
+ NewTree
+ end;
+ n ->
+ if
+ S =:= 1 ->
+ bplus_get_tree(NewTree, 1);
+ true ->
+ NewTree
+ end
+ end.
+
+
+%%% -----------------------
+%%% Help function to insert.
+%%% -----------------------
+
+bplus_insert_in(Tree, Key) ->
+ case ?NODE_TYPE(Tree) of
+ l ->
+ bplus_insert_in_leaf(Tree, Key);
+ n ->
+ {Pos, SubTree} = bplus_select_sub_tree(Tree, Key),
+ % Pos = "the position of the subtree".
+ NewSubTree = bplus_insert_in(SubTree, Key),
+ case ?OVER_FULL(NewSubTree) of
+ false ->
+ bplus_put_subtree(Tree, [NewSubTree, Pos]);
+ true ->
+ case bplus_reorganize_tree_ins(Tree, NewSubTree, Pos) of
+ {left, {LeftT, DKey, MiddleT}} ->
+ bplus_put_subtree(bplus_put_lkey(Tree, DKey, Pos),
+ [LeftT, Pos-1, MiddleT, Pos]);
+ {right, {MiddleT, DKey, RightT}} ->
+ bplus_put_subtree(bplus_put_rkey(Tree, DKey, Pos),
+ [MiddleT, Pos, RightT, Pos+1]);
+ {split, {LeftT, DKey, RightT}} ->
+ bplus_extend_tree(Tree, {LeftT, DKey, RightT}, Pos)
+ end
+ end
+ end.
+
+%%-----------------------------------------------------------------
+%% Inserts a key in correct position in a leaf.
+%%-----------------------------------------------------------------
+bplus_insert_in_leaf(Leaf, Key) ->
+ bplus_insert_in_leaf_2(Leaf, Key, bplus_get_size(Leaf), []).
+
+bplus_insert_in_leaf_2(Leaf, Key, 0, Accum) ->
+ bplus_insert_in_leaf_3(Leaf, 0, [Key|Accum]);
+bplus_insert_in_leaf_2(Leaf, Key, N, Accum) ->
+ K = ?GET_LEAF_KEY(Leaf, N),
+ if
+ Key < K ->
+ % Not here!
+ bplus_insert_in_leaf_2(Leaf, Key, N-1, [K|Accum]);
+ K < Key ->
+ % Insert here.
+ bplus_insert_in_leaf_3(Leaf, N-1, [K, Key|Accum]);
+ K == Key ->
+ % Replace (?).
+ bplus_insert_in_leaf_3(Leaf, N-1, [ Key|Accum])
+ end.
+
+bplus_insert_in_leaf_3(_Leaf, 0, LeafList) ->
+ bplus_mk_leaf(LeafList);
+bplus_insert_in_leaf_3(Leaf, N, LeafList) ->
+ bplus_insert_in_leaf_3(Leaf, N-1, [?GET_LEAF_KEY(Leaf, N)|LeafList]).
+
+
+%%% -------------------------
+%%% Help functions for delete.
+%%% -------------------------
+
+bplus_delete_in(Tree, Key) ->
+ case ?NODE_TYPE(Tree) of
+ l ->
+ bplus_delete_in_leaf(Tree, Key);
+ n ->
+ {Pos, SubTree} = bplus_select_sub_tree(Tree, Key),
+ % Pos = "the position of the subtree".
+ NewSubTree = bplus_delete_in(SubTree, Key),
+ % Check if it has become to small now
+ case ?UNDER_FILLED(NewSubTree) of
+ false ->
+ bplus_put_subtree(Tree, [NewSubTree, Pos]);
+ true ->
+ case bplus_reorganize_tree_del(Tree, NewSubTree, Pos) of
+ {left, {LeftT, DKey, MiddleT}} ->
+ bplus_put_subtree(bplus_put_lkey(Tree, DKey, Pos),
+ [LeftT, Pos-1, MiddleT, Pos]);
+ {right, {MiddleT, DKey, RightT}} ->
+ bplus_put_subtree(bplus_put_rkey(Tree, DKey, Pos),
+ [MiddleT, Pos, RightT, Pos+1]);
+ {join_left, JoinedTree} ->
+ bplus_joinleft_tree(Tree, JoinedTree, Pos);
+ {join_right, JoinedTree} ->
+ bplus_joinright_tree(Tree, JoinedTree, Pos)
+ end
+ end
+ end.
+
+%%-----------------------------------------------------------------
+%% Deletes a key from the leaf returning a new (smaller) leaf.
+%%-----------------------------------------------------------------
+bplus_delete_in_leaf(Leaf, Key) ->
+ bplus_delete_in_leaf_2(Leaf, Key, bplus_get_size(Leaf), []).
+
+bplus_delete_in_leaf_2(Leaf, _, 0, _) -> Leaf;
+bplus_delete_in_leaf_2(Leaf, Key, N, Accum) ->
+ K = ?GET_LEAF_KEY(Leaf, N),
+ if
+ Key == K ->
+ % Remove this one!
+ bplus_delete_in_leaf_3(Leaf, N-1, Accum);
+ true ->
+ bplus_delete_in_leaf_2(Leaf, Key, N-1, [K|Accum])
+ end.
+
+bplus_delete_in_leaf_3(_Leaf, 0, LeafList) ->
+ bplus_mk_leaf(LeafList);
+bplus_delete_in_leaf_3(Leaf, N, LeafList) ->
+ bplus_delete_in_leaf_3(Leaf, N-1, [?GET_LEAF_KEY(Leaf, N)|LeafList]).
+
+
+
+%%-----------------------------------------------------------------
+%% Selects and returns which subtree the search should continue in.
+%%-----------------------------------------------------------------
+bplus_select_sub_tree(Tree, Key) ->
+ bplus_select_sub_tree_2(Tree, Key, bplus_get_size(Tree)).
+
+bplus_select_sub_tree_2(Tree, _Key, 1) -> {1, bplus_get_tree(Tree, 1)};
+bplus_select_sub_tree_2(Tree, Key, N) ->
+ K = bplus_get_lkey(Tree, N),
+ if
+ K > Key ->
+ bplus_select_sub_tree_2(Tree, Key, N-1);
+ K =< Key ->
+ % Here it is!
+ {N, bplus_get_tree(Tree, N)}
+ end.
+
+%%-----------------------------------------------------------------
+%% Selects which brother that should take over some of our items.
+%% Or if they are both full makes a split.
+%%-----------------------------------------------------------------
+bplus_reorganize_tree_ins(Tree, NewSubTree, 1) ->
+ RTree = bplus_get_tree(Tree, 2), % 2 = Pos+1 = 1+1.
+ case ?FULL(RTree) of
+ false ->
+ bplus_reorganize_tree_r(Tree, NewSubTree, 1, RTree);
+ true ->
+ % It is full, we must split this one!
+ bplus_reorganize_tree_s(NewSubTree)
+ end;
+bplus_reorganize_tree_ins(Tree, NewSubTree, Pos) ->
+ Size = bplus_get_size(Tree),
+ if
+ Pos == Size ->
+ % Pos is the rightmost postion!.
+ % Our only chance is the left one.
+ LTree = bplus_get_tree(Tree, Pos-1),
+ case ?FULL(LTree) of
+ false ->
+ bplus_reorganize_tree_l(Tree, NewSubTree, Pos, LTree);
+ true ->
+ % It is full, we must split this one!
+ bplus_reorganize_tree_s(NewSubTree)
+ end;
+ true ->
+ % Pos is somewhere inside the node.
+ LTree = bplus_get_tree(Tree, Pos-1),
+ RTree = bplus_get_tree(Tree, Pos+1),
+ SL = bplus_get_size(LTree),
+ SR = bplus_get_size(RTree),
+ if
+ SL > SR ->
+ bplus_reorganize_tree_r(Tree, NewSubTree, Pos, RTree);
+ SL < SR ->
+ bplus_reorganize_tree_l(Tree, NewSubTree, Pos, LTree);
+ true ->
+ case ?FULL(LTree) of
+ false ->
+ bplus_reorganize_tree_l(Tree, NewSubTree, Pos, LTree);
+ true ->
+ bplus_reorganize_tree_s(NewSubTree)
+ end
+ end
+ end.
+
+%%-----------------------------------------------------------------
+%% This function fills over items from brothers to maintain the minimum
+%% number of items per node/leaf.
+%%-----------------------------------------------------------------
+bplus_reorganize_tree_del(Tree, NewSubTree, 1) ->
+ % The case when Pos is at leftmost position.
+ RTree = bplus_get_tree(Tree, 2), % 2 = Pos+1 = 1+1.
+ case ?LOW_FILLED(RTree) of
+ false ->
+ bplus_reorganize_tree_r(Tree, NewSubTree, 1, RTree);
+ true ->
+ % It is to small, we must join them!
+ bplus_reorganize_tree_jr(Tree, NewSubTree, 1, RTree)
+ end;
+bplus_reorganize_tree_del(Tree, NewSubTree, Pos) ->
+ Size = bplus_get_size(Tree),
+ if
+ Pos == Size ->
+ % Pos is the rightmost postion!.
+ % Our only chance is the left one.
+ LTree = bplus_get_tree(Tree, Pos-1),
+ case ?LOW_FILLED(LTree) of
+ false ->
+ bplus_reorganize_tree_l(Tree, NewSubTree, Pos, LTree);
+ true ->
+ % It is to small, we must join this one!
+ bplus_reorganize_tree_jl(Tree, NewSubTree, Pos, LTree)
+ end;
+ true ->
+ % Pos is somewhere inside the node.
+ LTree = bplus_get_tree(Tree, Pos-1),
+ RTree = bplus_get_tree(Tree, Pos+1),
+ SL = bplus_get_size(LTree),
+ SR = bplus_get_size(RTree),
+ if
+ SL>SR ->
+ bplus_reorganize_tree_l(Tree, NewSubTree, Pos, LTree);
+ SL < SR ->
+ bplus_reorganize_tree_r(Tree, NewSubTree, Pos, RTree);
+ true ->
+ case ?LOW_FILLED(LTree) of
+ false ->
+ bplus_reorganize_tree_l(Tree, NewSubTree, Pos, LTree);
+ true ->
+ bplus_reorganize_tree_jl(Tree, NewSubTree, Pos, LTree)
+ end
+ end
+ end.
+
+
+bplus_reorganize_tree_l(Tree, NewSubTree, Pos, LTree) ->
+ case ?NODE_TYPE(NewSubTree) of
+ l ->
+ {left, bplus_split_leaf(
+ bplus_mk_leaf(
+ lists:append(bplus_leaf_to_list(LTree),
+ bplus_leaf_to_list(NewSubTree))))};
+ n ->
+ {left, bplus_split_node(
+ bplus_mk_node(
+ lists:append([bplus_node_to_list(LTree),
+ [bplus_get_lkey(Tree, Pos)],
+ bplus_node_to_list(NewSubTree)])))}
+ end.
+
+bplus_reorganize_tree_r(Tree, NewSubTree, Pos, RTree) ->
+ case ?NODE_TYPE(NewSubTree) of
+ l ->
+ {right,
+ bplus_split_leaf(
+ bplus_mk_leaf(
+ lists:append([bplus_leaf_to_list(NewSubTree),
+ bplus_leaf_to_list(RTree)])))};
+ n ->
+ {right,
+ bplus_split_node(
+ bplus_mk_node(
+ lists:append([bplus_node_to_list(NewSubTree),
+ [bplus_get_rkey(Tree, Pos)],
+ bplus_node_to_list(RTree)])))}
+ end.
+
+bplus_reorganize_tree_s(NewSubTree) ->
+ case ?NODE_TYPE(NewSubTree) of
+ l ->
+ {split, bplus_split_leaf(NewSubTree)};
+ n ->
+ {split, bplus_split_node(NewSubTree)}
+ end.
+
+bplus_reorganize_tree_jl(Tree, NewSubTree, Pos, LTree) ->
+ case ?NODE_TYPE(NewSubTree) of
+ l ->
+ {join_left,
+ bplus_mk_leaf(lists:append([bplus_leaf_to_list(LTree),
+ bplus_leaf_to_list(NewSubTree)]))};
+ n ->
+ {join_left,
+ bplus_mk_node(lists:append([bplus_node_to_list(LTree),
+ [bplus_get_lkey(Tree, Pos)],
+ bplus_node_to_list(NewSubTree)]))}
+ end.
+
+bplus_reorganize_tree_jr(Tree, NewSubTree, Pos, RTree) ->
+ case ?NODE_TYPE(NewSubTree) of
+ l ->
+ {join_right,
+ bplus_mk_leaf(lists:append([bplus_leaf_to_list(NewSubTree),
+ bplus_leaf_to_list(RTree)]))};
+ n ->
+ {join_right,
+ bplus_mk_node(lists:append([bplus_node_to_list(NewSubTree),
+ [bplus_get_rkey(Tree, Pos)],
+ bplus_node_to_list(RTree)]))}
+ end.
+
+
+%%-----------------------------------------------------------------
+%% Takes a leaf and divides it into two equal big leaves.
+%% The result is returned in a tuple. The dividing key is also returned.
+%%-----------------------------------------------------------------
+bplus_split_leaf(Leaf) ->
+ S = bplus_get_size(Leaf),
+ bplus_split_leaf_2(Leaf, S, S div 2, []).
+
+bplus_split_leaf_2(Leaf, Pos, 1, Accum) ->
+ K = ?GET_LEAF_KEY(Leaf, Pos),
+ bplus_split_leaf_3(Leaf, Pos-1, [], K, [K|Accum]);
+bplus_split_leaf_2(Leaf, Pos, N, Accum) ->
+ bplus_split_leaf_2(Leaf, Pos-1, N-1, [?GET_LEAF_KEY(Leaf, Pos)|Accum]).
+
+bplus_split_leaf_3(_, 0, LeftAcc, DKey, RightAcc) ->
+ {bplus_mk_leaf(LeftAcc), DKey, bplus_mk_leaf(RightAcc)};
+bplus_split_leaf_3(Leaf, Pos, LeftAcc, DKey, RightAcc) ->
+ bplus_split_leaf_3(Leaf, Pos-1, [?GET_LEAF_KEY(Leaf, Pos)|LeftAcc],
+ DKey, RightAcc).
+
+%%-----------------------------------------------------------------
+%% Takes a node and divides it into two equal big nodes.
+%% The result is returned in a tuple. The dividing key is also returned.
+%%-----------------------------------------------------------------
+bplus_split_node(Node) ->
+ S = bplus_get_size(Node),
+ bplus_split_node_2(Node, S, S div 2, []).
+
+bplus_split_node_2(Node, Pos, 1, Accum) ->
+ bplus_split_node_3(Node, Pos-1, [], bplus_get_lkey(Node, Pos),
+ [bplus_get_tree(Node, Pos)|Accum]);
+bplus_split_node_2(Node, Pos, N, Accum) ->
+ bplus_split_node_2(Node, Pos-1, N-1, [bplus_get_lkey(Node, Pos),
+ bplus_get_tree(Node, Pos)|Accum]).
+
+bplus_split_node_3(Node, 1, LeftAcc, DKey, RightAcc) ->
+ {bplus_mk_node([bplus_get_tree(Node, 1)|LeftAcc]), DKey,
+ bplus_mk_node(RightAcc)};
+bplus_split_node_3(Node, Pos, LeftAcc, DKey, RightAcc) ->
+ bplus_split_node_3(Node, Pos-1,
+ [bplus_get_lkey(Node, Pos),
+ bplus_get_tree(Node, Pos)|LeftAcc],
+ DKey, RightAcc).
+
+%%-----------------------------------------------------------------
+%% Inserts a joined tree insted of the old one at position Pos and
+%% the one nearest left/right brother.
+%%-----------------------------------------------------------------
+bplus_joinleft_tree(Tree, JoinedTree, Pos) ->
+ bplus_join_tree_2(Tree, JoinedTree, Pos, bplus_get_size(Tree), []).
+bplus_joinright_tree(Tree, JoinedTree, Pos) ->
+ bplus_join_tree_2(Tree, JoinedTree, Pos+1, bplus_get_size(Tree), []).
+
+bplus_join_tree_2(Tree, JoinedTree, Pos, Pos, Accum) ->
+ bplus_join_tree_3(Tree, Pos-2, [JoinedTree|Accum]);
+bplus_join_tree_2(Tree, JoinedTree, Pos, N, Accum) ->
+ bplus_join_tree_2(Tree, JoinedTree, Pos, N-1,
+ [bplus_get_lkey(Tree, N), bplus_get_tree(Tree, N)|Accum]).
+
+bplus_join_tree_3(_Tree, 0, Accum) -> bplus_mk_node(Accum);
+bplus_join_tree_3(Tree, Pos, Accum) ->
+ bplus_join_tree_3(Tree, Pos-1, [bplus_get_tree(Tree, Pos),
+ bplus_get_rkey(Tree, Pos)|Accum]).
+
+%%% ---------------------------------
+%%% Primitive datastructure functions.
+%%% ---------------------------------
+
+%%-----------------------------------------------------------------
+%% Constructs a node out of list format.
+%%-----------------------------------------------------------------
+bplus_mk_node(NodeList) -> list_to_tuple([ n |NodeList]).
+
+%%-----------------------------------------------------------------
+%% Converts the node into list format.
+%%-----------------------------------------------------------------
+bplus_node_to_list(Node) ->
+ [_|NodeList] = tuple_to_list(Node),
+ NodeList.
+
+%%-----------------------------------------------------------------
+%% Constructs a leaf out of list format.
+%%-----------------------------------------------------------------
+bplus_mk_leaf(KeyList) -> list_to_tuple([l|KeyList]).
+
+%%-----------------------------------------------------------------
+%% Converts a leaf into list format.
+%%-----------------------------------------------------------------
+bplus_leaf_to_list(Leaf) ->
+ [_|LeafList] = tuple_to_list(Leaf),
+ LeafList.
+
+%%-----------------------------------------------------------------
+%% Changes subtree "pointers" in a node.
+%%-----------------------------------------------------------------
+bplus_put_subtree(Tree, []) -> Tree;
+bplus_put_subtree(Tree, [NewSubTree, Pos|Rest]) ->
+ bplus_put_subtree(setelement(Pos*2, Tree, NewSubTree), Rest).
+
+%%-----------------------------------------------------------------
+%% Replaces the tree at position Pos with two new trees.
+%%-----------------------------------------------------------------
+bplus_extend_tree(Tree, Inserts, Pos) ->
+ bplus_extend_tree_2(Tree, Inserts, Pos, bplus_get_size(Tree), []).
+
+bplus_extend_tree_2(Tree, {T1, DKey, T2}, Pos, Pos, Accum) ->
+ bplus_extend_tree_3(Tree, Pos-1, [T1, DKey, T2|Accum]);
+bplus_extend_tree_2(Tree, Inserts, Pos, N, Accum) ->
+ bplus_extend_tree_2(Tree, Inserts, Pos, N-1,
+ [bplus_get_lkey(Tree, N), bplus_get_tree(Tree, N)|Accum]).
+
+bplus_extend_tree_3(_, 0, Accum) -> bplus_mk_node(Accum);
+bplus_extend_tree_3(Tree, N, Accum) ->
+ bplus_extend_tree_3(Tree, N-1, [bplus_get_tree(Tree, N),
+ bplus_get_rkey(Tree, N)|Accum]).
+
+%%-----------------------------------------------------------------
+%% Changes the dividing key between two trees.
+%%-----------------------------------------------------------------
+bplus_put_lkey(Tree, DKey, Pos) -> setelement(Pos*2-1, Tree, DKey).
+bplus_put_rkey(Tree, DKey, Pos) -> setelement(Pos*2+1, Tree, DKey).
+
+
+%%-----------------------------------------------------------------
+%% Calculates the number of items in a node/leaf.
+%%-----------------------------------------------------------------
+bplus_get_size(Tree) ->
+ case ?NODE_TYPE(Tree) of
+ l ->
+ tuple_size(Tree)-1;
+ n ->
+ tuple_size(Tree) div 2
+ end.
+
+%%-----------------------------------------------------------------
+%% Returns a tree at position Pos from an internal node.
+%%-----------------------------------------------------------------
+bplus_get_tree(Tree, Pos) -> element(Pos*2, Tree).
+
+%%-----------------------------------------------------------------
+%% Returns dividing keys, left of or right of a tree.
+%%-----------------------------------------------------------------
+bplus_get_lkey(Tree, Pos) -> element(Pos*2-1, Tree).
+bplus_get_rkey(Tree, Pos) -> element(Pos*2+1, Tree).
+
diff --git a/lib/stdlib/src/dets_v8.erl b/lib/stdlib/src/dets_v8.erl
new file mode 100644
index 0000000000..b24df02882
--- /dev/null
+++ b/lib/stdlib/src/dets_v8.erl
@@ -0,0 +1,1591 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dets_v8).
+
+%% Dets files, implementation part. This module handles versions up to
+%% and including 8(c). To be called from dets.erl only.
+
+-export([constants/0, mark_dirty/1, read_file_header/2,
+ check_file_header/2, do_perform_save/1, initiate_file/11,
+ init_freelist/2, fsck_input/4,
+ bulk_input/3, output_objs/4, write_cache/1, may_grow/3,
+ find_object/2, re_hash/2, slot_objs/2, scan_objs/8,
+ db_hash/2, no_slots/1, table_parameters/1]).
+
+-export([file_info/1, v_segments/1]).
+
+-export([cache_segps/3]).
+
+%% For backward compatibility.
+-export([sz2pos/1]).
+
+-compile({inline, [{sz2pos,1},{scan_skip,7}]}).
+-compile({inline, [{skip_bytes,5}, {get_segp,1}]}).
+-compile({inline, [{wl_lookup,5}]}).
+-compile({inline, [{actual_seg_size,0}]}).
+
+-include("dets.hrl").
+
+%% The layout of the file is :
+%%
+%% bytes decsription
+%% ---------------------- File header
+%% 4 FreelistsPointer
+%% 4 Cookie
+%% 4 ClosedProperly (pos=8)
+%% 4 Type (pos=12)
+%% 4 Version (pos=16)
+%% 4 M
+%% 4 Next
+%% 4 KeyPos
+%% 4 NoObjects
+%% 4 N
+%% ------------------ end of file header
+%% 4*8192 SegmentArray
+%% ------------------
+%% 4*256 First segment
+%% ----------------------------- This is BASE.
+%% ??? Objects (free and alive)
+%% 4*256 Second segment (2 kB now, due to a bug)
+%% ??? Objects (free and alive)
+%% ... more objects and segments ...
+%% -----------------------------
+%% ??? Free lists
+%% -----------------------------
+%% 4 File size, in bytes.
+
+%% The first slot (0) in the segment array always points to the
+%% pre-allocated first segment.
+%% Before we can find an object we must find the slot where the
+%% object resides. Each slot is a (possibly empty) list (or chain) of
+%% objects that hash to the same slot. If the value stored in the
+%% slot is zero, the slot chain is empty. If the slot value is
+%% non-zero, the value points to a position in the file where the
+%% chain starts. Each object in a chain has the following layout:
+%%
+%% bytes decsription
+%% --------------------
+%% 4 Pointer to the next object of the chain.
+%% 4 Size of the object in bytes (Sz).
+%% 4 Status (FREE or ACTIVE)
+%% Sz Binary representing the object
+%%
+%% The status field is used while repairing a file (but not next or size).
+%%
+%%|---------------|
+%%| head |
+%%| |
+%%| |
+%%|_______________|
+%%| |------|
+%%|___seg ptr1____| |
+%%| | |
+%%|__ seg ptr 2___| |
+%%| | | segment 1
+%%| .... | V _____________
+%% | |
+%% | |
+%% |___slot 0 ____|
+%% | |
+%% |___slot 1 ____|-----|
+%% | | |
+%% | ..... | | 1:st obj in slot 1
+%% V segment 1
+%% |-----------|
+%% | next |
+%% |___________|
+%% | size |
+%% |___________|
+%% | status |
+%% |___________|
+%% | |
+%% | |
+%% | obj |
+%% | |
+
+%%%
+%%% File header
+%%%
+
+-define(HEADSZ, 40). % The size of the file header, in bytes.
+-define(SEGSZ, 256). % Size of a segment, in words.
+-define(SEGSZ_LOG2, 8).
+-define(SEGARRSZ, 8192). % Maximal number of segments.
+-define(SEGADDR(SegN), (?HEADSZ + (4 * (SegN)))).
+-define(BASE, ?SEGADDR((?SEGSZ + ?SEGARRSZ))).
+-define(MAXOBJS, (?SEGSZ * ?SEGARRSZ)). % 2 M objects
+
+-define(SLOT2SEG(S), ((S) bsr ?SEGSZ_LOG2)).
+
+%% BIG is used for hashing. BIG must be greater than the maximum
+%% number of slots, currently MAXOBJS.
+-define(BIG, 16#ffffff).
+
+%% Hard coded positions into the file header:
+-define(FREELIST_POS, 0).
+-define(CLOSED_PROPERLY_POS, 8).
+-define(D_POS, 20).
+-define(NO_OBJECTS_POS, (?D_POS + 12)).
+
+%% The version of a dets file is indicated by the ClosedProperly
+%% field. Version 6 was used in the R1A release, and version 7 in the
+%% R1B release up to and including the R3B01 release. Both version 6
+%% and version 7 indicate properly closed files by the value
+%% CLOSED_PROPERLY.
+%%
+%% The current version, 8, has three sub-versions:
+%%
+%% - 8(a), indicated by the value CLOSED_PROPERLY (same as in versions 6
+%% and 7), introduced in R3B02;
+%% - 8(b), indicated by the value CLOSED_PROPERLY2(_NEED_COMPACTING),
+%% introduced in R5A and used up to and including R6A;
+%% - 8(c), indicated by the value CLOSED_PROPERLY_NEW_HASH(_NEED_COMPACTING),
+%% in use since R6B.
+%%
+%% The difference between the 8(a) and the 8(b) versions is the format
+%% used for free lists saved on dets files.
+%% The 8(c) version uses a different hashing algorithm, erlang:phash
+%% (former versions use erlang:hash).
+%% Version 8(b) files are only converted to version 8(c) if repair is
+%% done, so we need compatability with 8(b) for a _long_ time.
+%%
+%% There are known bugs due to the fact that keys and objects are
+%% sometimes compared (==) and sometimes matched (=:=). The version
+%% used by default (9, see dets_v9.erl) does not have this problem.
+
+-define(NOT_PROPERLY_CLOSED,0).
+-define(CLOSED_PROPERLY,1).
+-define(CLOSED_PROPERLY2,2).
+-define(CLOSED_PROPERLY2_NEED_COMPACTING,3).
+-define(CLOSED_PROPERLY_NEW_HASH,4).
+-define(CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING,5).
+
+-define(FILE_FORMAT_VERSION, 8).
+-define(CAN_BUMP_BY_REPAIR, [6, 7]).
+-define(CAN_CONVERT_FREELIST, [8]).
+
+%%%
+%%% Object header (next, size, status).
+%%%
+
+-define(OHDSZ, 12). % The size of the object header, in bytes.
+-define(STATUS_POS, 8). % Position of the status field.
+
+%% The size of each object is a multiple of 16.
+%% BUMP is used when repairing files.
+-define(BUMP, 16).
+
+-define(ReadAhead, 512).
+
+%%-define(DEBUGF(X,Y), io:format(X, Y)).
+-define(DEBUGF(X,Y), void).
+
+%% {Bump}
+constants() ->
+ {?BUMP, ?BASE}.
+
+%% -> ok | throw({NewHead,Error})
+mark_dirty(Head) ->
+ Dirty = [{?CLOSED_PROPERLY_POS, <<?NOT_PROPERLY_CLOSED:32>>}],
+ dets_utils:pwrite(Head, Dirty),
+ dets_utils:sync(Head),
+ dets_utils:position(Head, Head#head.freelists_p),
+ dets_utils:truncate(Head, cur).
+
+%% -> {ok, head()} | throw(Error)
+initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots,
+ Ram, CacheSz, Auto, _DoInitSegments) ->
+ Freelist = 0,
+ Cookie = ?MAGIC,
+ ClosedProperly = ?NOT_PROPERLY_CLOSED, % immediately overwritten
+ Version = ?FILE_FORMAT_VERSION,
+ Factor = est_no_segments(MinSlots),
+ N = 0,
+ M = Next = ?SEGSZ * Factor,
+ NoObjects = 0,
+ dets_utils:pwrite(Fd, Fname, 0,
+ <<Freelist:32,
+ Cookie:32,
+ ClosedProperly:32,
+ (dets_utils:type_to_code(Type)):32,
+ Version:32,
+ M:32,
+ Next:32,
+ Kp:32,
+ NoObjects:32,
+ N:32,
+ 0:(?SEGARRSZ*4)/unit:8, % Initialize SegmentArray
+ 0:(?SEGSZ*4)/unit:8>>), % Initialize first segment
+ %% We must set the first slot of the segment pointer array to
+ %% point to the first segment
+ Pos = ?SEGADDR(0),
+ SegP = (?HEADSZ + (4 * ?SEGARRSZ)),
+ dets_utils:pwrite(Fd, Fname, Pos, <<SegP:32>>),
+ segp_cache(Pos, SegP),
+
+ Ftab = dets_utils:init_alloc(?BASE),
+ H0 = #head{freelists=Ftab, fptr = Fd, base = ?BASE},
+ {H1, Ws} = init_more_segments(H0, 1, Factor, undefined, []),
+
+ %% This is not optimal but simple: always initiate the segments.
+ dets_utils:pwrite(Fd, Fname, Ws),
+
+ %% Return a new nice head structure
+ Head = #head{
+ m = M,
+ m2 = M * 2,
+ next = Next,
+ fptr = Fd,
+ no_objects = NoObjects,
+ n = N,
+ type = Type,
+ update_mode = dirty,
+ freelists = H1#head.freelists,
+ auto_save = Auto,
+ hash_bif = phash,
+ keypos = Kp,
+ min_no_slots = Factor * ?SEGSZ,
+ max_no_slots = no_segs(MaxSlots) * ?SEGSZ,
+
+ ram_file = Ram,
+ filename = Fname,
+ name = Tab,
+ cache = dets_utils:new_cache(CacheSz),
+ version = Version,
+ bump = ?BUMP,
+ base = ?BASE,
+ mod = ?MODULE
+ },
+ {ok, Head}.
+
+est_no_segments(MinSlots) when 1 + ?SLOT2SEG(MinSlots) > ?SEGARRSZ ->
+ ?SEGARRSZ;
+est_no_segments(MinSlots) ->
+ 1 + ?SLOT2SEG(MinSlots).
+
+init_more_segments(Head, SegNo, Factor, undefined, Ws) when SegNo < Factor ->
+ init_more_segments(Head, SegNo, Factor, seg_zero(), Ws);
+init_more_segments(Head, SegNo, Factor, SegZero, Ws) when SegNo < Factor ->
+ {NewHead, W} = allocate_segment(Head, SegZero, SegNo),
+ init_more_segments(NewHead, SegNo+1, Factor, SegZero, W++Ws);
+init_more_segments(Head, _SegNo, _Factor, _SegZero, Ws) ->
+ {Head, Ws}.
+
+allocate_segment(Head, SegZero, SegNo) ->
+ %% may throw error:
+ {NewHead, Segment, _} = dets_utils:alloc(Head, 4 * ?SEGSZ),
+ InitSegment = {Segment, SegZero},
+ Pos = ?SEGADDR(SegNo),
+ segp_cache(Pos, Segment),
+ SegPointer = {Pos, <<Segment:32>>},
+ {NewHead, [InitSegment, SegPointer]}.
+
+%% Read free lists (using a Buddy System) from file.
+init_freelist(Head, {convert_freelist,_Version}) ->
+ %% This function converts the saved freelist of the form
+ %% [{Slot1,Addr1},{Addr1,Addr2},...,{AddrN,0},{Slot2,Addr},...]
+ %% i.e each slot is a linked list which ends with a 0.
+ %% This is stored in a bplus_tree per Slot.
+ %% Each Slot is a position in a tuple.
+
+ Ftab = dets_utils:empty_free_lists(),
+ Pos = Head#head.freelists_p,
+ case catch prterm(Head, Pos, ?OHDSZ) of
+ {0, _Sz, Term} ->
+ FreeList = lists:reverse(Term),
+ dets_utils:init_slots_from_old_file(FreeList, Ftab);
+ _ ->
+ throw({error, {bad_freelists, Head#head.filename}})
+ end;
+init_freelist(Head, _) ->
+ %% bplus_tree stored as is
+ Pos = Head#head.freelists_p,
+ case catch prterm(Head, Pos, ?OHDSZ) of
+ {0, _Sz, Term} ->
+ Term;
+ _ ->
+ throw({error, {bad_freelists, Head#head.filename}})
+ end.
+
+%% -> {ok, Fd, fileheader()} | throw(Error)
+read_file_header(Fd, FileName) ->
+ {ok, Bin} = dets_utils:pread_close(Fd, FileName, 0, ?HEADSZ),
+ [Freelist, Cookie, CP, Type2, Version, M, Next, Kp, NoObjects, N] =
+ bin2ints(Bin),
+ {ok, EOF} = dets_utils:position_close(Fd, FileName, eof),
+ {ok, <<FileSize:32>>} = dets_utils:pread_close(Fd, FileName, EOF-4, 4),
+ FH = #fileheader{freelist = Freelist,
+ cookie = Cookie,
+ closed_properly = CP,
+ type = dets_utils:code_to_type(Type2),
+ version = Version,
+ m = M,
+ next = Next,
+ keypos = Kp,
+ no_objects = NoObjects,
+ min_no_slots = ?DEFAULT_MIN_NO_SLOTS,
+ max_no_slots = ?DEFAULT_MAX_NO_SLOTS,
+ trailer = FileSize,
+ eof = EOF,
+ n = N,
+ mod = ?MODULE},
+ {ok, Fd, FH}.
+
+%% -> {ok, head(), ExtraInfo} | {error, Reason} (Reason lacking file name)
+%% ExtraInfo = {convert_freelist, Version} | true | need_compacting
+check_file_header(FH, Fd) ->
+ Test =
+ if
+ FH#fileheader.cookie =/= ?MAGIC ->
+ {error, not_a_dets_file};
+ FH#fileheader.type =:= badtype ->
+ {error, invalid_type_code};
+ FH#fileheader.version =/= ?FILE_FORMAT_VERSION ->
+ case lists:member(FH#fileheader.version,
+ ?CAN_BUMP_BY_REPAIR) of
+ true ->
+ {error, version_bump};
+ false ->
+ {error, bad_version}
+ end;
+ FH#fileheader.trailer =/= FH#fileheader.eof ->
+ {error, not_closed};
+ FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY ->
+ case lists:member(FH#fileheader.version,
+ ?CAN_CONVERT_FREELIST) of
+ true ->
+ {ok, {convert_freelist, FH#fileheader.version}, hash};
+ false ->
+ {error, not_closed} % should not happen
+ end;
+ FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY2 ->
+ {ok, true, hash};
+ FH#fileheader.closed_properly =:=
+ ?CLOSED_PROPERLY2_NEED_COMPACTING ->
+ {ok, need_compacting, hash};
+ FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY_NEW_HASH ->
+ {ok, true, phash};
+ FH#fileheader.closed_properly =:=
+ ?CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING ->
+ {ok, need_compacting, phash};
+ FH#fileheader.closed_properly =:= ?NOT_PROPERLY_CLOSED ->
+ {error, not_closed};
+ FH#fileheader.closed_properly >
+ ?CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING ->
+ {error, not_closed};
+ true ->
+ {error, not_a_dets_file}
+ end,
+ case Test of
+ {ok, ExtraInfo, HashAlg} ->
+ H = #head{
+ m = FH#fileheader.m,
+ m2 = FH#fileheader.m * 2,
+ next = FH#fileheader.next,
+ fptr = Fd,
+ no_objects= FH#fileheader.no_objects,
+ n = FH#fileheader.n,
+ type = FH#fileheader.type,
+ update_mode = saved,
+ auto_save = infinity, % not saved on file
+ fixed = false, % not saved on file
+ freelists_p = FH#fileheader.freelist,
+ hash_bif = HashAlg,
+ keypos = FH#fileheader.keypos,
+ min_no_slots = FH#fileheader.min_no_slots,
+ max_no_slots = FH#fileheader.max_no_slots,
+ version = ?FILE_FORMAT_VERSION,
+ mod = ?MODULE,
+ bump = ?BUMP,
+ base = ?BASE},
+ {ok, H, ExtraInfo};
+ Error ->
+ Error
+ end.
+
+cache_segps(Fd, FileName, M) ->
+ NSegs = no_segs(M),
+ {ok, Bin} = dets_utils:pread_close(Fd, FileName, ?HEADSZ, 4 * NSegs),
+ Fun = fun(S, P) -> segp_cache(P, S), P+4 end,
+ lists:foldl(Fun, ?HEADSZ, bin2ints(Bin)).
+
+no_segs(NoSlots) ->
+ ?SLOT2SEG(NoSlots - 1) + 1.
+
+bin2ints(<<Int:32, B/binary>>) ->
+ [Int | bin2ints(B)];
+bin2ints(<<>>) ->
+ [].
+
+%%%
+%%% Repair, conversion and initialization of a dets file.
+%%%
+
+bulk_input(Head, InitFun, Cntrs) ->
+ bulk_input(Head, InitFun, Cntrs, make_ref()).
+
+bulk_input(Head, InitFun, Cntrs, Ref) ->
+ fun(close) ->
+ ok;
+ (read) ->
+ case catch {Ref, InitFun(read)} of
+ {Ref, end_of_input} ->
+ end_of_input;
+ {Ref, {L0, NewInitFun}} when is_list(L0),
+ is_function(NewInitFun) ->
+ Kp = Head#head.keypos,
+ case catch bulk_objects(L0, Head, Cntrs, Kp, []) of
+ {'EXIT', _Error} ->
+ _ = (catch NewInitFun(close)),
+ {error, invalid_objects_list};
+ L ->
+ {L, bulk_input(Head, NewInitFun, Cntrs, Ref)}
+ end;
+ {Ref, Value} ->
+ {error, {init_fun, Value}};
+ Error ->
+ throw({thrown, Error})
+ end
+ end.
+
+bulk_objects([T | Ts], Head, Cntrs, Kp, L) ->
+ BT = term_to_binary(T),
+ Sz = byte_size(BT),
+ LogSz = sz2pos(Sz+?OHDSZ),
+ count_object(Cntrs, LogSz),
+ Key = element(Kp, T),
+ bulk_objects(Ts, Head, Cntrs, Kp, [make_object(Head, Key, LogSz, BT) | L]);
+bulk_objects([], _Head, _Cntrs, _Kp, L) ->
+ L.
+
+-define(FSCK_SEGMENT, 10000).
+
+-define(DCT(D, CT), [D | CT]).
+
+-define(VNEW(N, E), erlang:make_tuple(N, E)).
+-define(VSET(I, V, E), setelement(I, V, E)).
+-define(VGET(I, V), element(I, V)).
+
+%% OldVersion not used, assuming later versions have been converted already.
+output_objs(OldVersion, Head, SlotNumbers, Cntrs) ->
+ fun(close) ->
+ {ok, 0, Head};
+ ([]) ->
+ output_objs(OldVersion, Head, SlotNumbers, Cntrs);
+ (L) ->
+ %% Descending sizes.
+ Count = lists:sort(ets:tab2list(Cntrs)),
+ RCount = lists:reverse(Count),
+ NoObjects = lists:foldl(fun({_Sz,No}, A) -> A + No end, 0, Count),
+ {_, MinSlots, _} = SlotNumbers,
+ if
+ %% Using number of objects for bags and duplicate bags
+ %% is not ideal; number of (unique) keys should be
+ %% used instead. The effect is that there will be more
+ %% segments than "necessary".
+ MinSlots =/= bulk_init,
+ abs(?SLOT2SEG(NoObjects) - ?SLOT2SEG(MinSlots)) > 5,
+ (NoObjects < ?MAXOBJS) ->
+ {try_again, NoObjects};
+ true ->
+ Head1 = Head#head{no_objects = NoObjects},
+ SegSz = actual_seg_size(),
+ {_, End, _} = dets_utils:alloc(Head, SegSz-1),
+ %% Now {LogSize,NoObjects} in Cntrs is replaced by
+ %% {LogSize,Position,{FileName,FileDescriptor},NoObjects}.
+ {Head2, CT} = allocate_all_objects(Head1, RCount, Cntrs),
+ [E | Es] = bin2term(L, []),
+ {NE, Acc, DCT1} =
+ output_slots(E, Es, [E], Head2, ?DCT(0, CT)),
+ NDCT = write_all_sizes(DCT1, Cntrs),
+ Max = ets:info(Cntrs, size),
+ output_objs2(NE, Acc, Head2, Cntrs, NDCT, End, Max,Max)
+ end
+ end.
+
+output_objs2(E, Acc, Head, Cntrs, DCT, End, 0, MaxNoChunks) ->
+ NDCT = write_all_sizes(DCT, Cntrs),
+ output_objs2(E, Acc, Head, Cntrs, NDCT, End, MaxNoChunks, MaxNoChunks);
+output_objs2(E, Acc, Head, Cntrs, DCT, End, ChunkI, MaxNoChunks) ->
+ fun(close) ->
+ DCT1 = output_slot(Acc, Head, DCT),
+ NDCT = write_all_sizes(DCT1, Cntrs),
+ ?DCT(NoDups, CT) = NDCT,
+ [SegAddr | []] = ?VGET(tuple_size(CT), CT),
+ FinalZ = End - SegAddr,
+ [{?FSCK_SEGMENT, _, {FileName, Fd}, _}] =
+ ets:lookup(Cntrs, ?FSCK_SEGMENT),
+ ok = dets_utils:fwrite(Fd, FileName,
+ dets_utils:make_zeros(FinalZ)),
+ NewHead = Head#head{no_objects = Head#head.no_objects - NoDups},
+ {ok, NoDups, NewHead};
+ (L) ->
+ Es = bin2term(L, []),
+ {NE, NAcc, NDCT} = output_slots(E, Es, Acc, Head, DCT),
+ output_objs2(NE, NAcc, Head, Cntrs, NDCT, End,
+ ChunkI-1, MaxNoChunks)
+ end.
+
+%% By allocating bigger objects before smaller ones, holes in the
+%% buddy system memory map are avoided. Unfortunately, the segments
+%% are always allocated first, so if there are objects bigger than a
+%% segment, there is a hole to handle. (Haven't considered placing the
+%% segments among other objects of the same size.)
+allocate_all_objects(Head, Count, Cntrs) ->
+ SegSize = actual_seg_size(),
+ {Head1, HSz, HN, HA} = alloc_hole(Count, Head, SegSize),
+ {Max, _} = hd(Count),
+ CT = ?VNEW(Max+1, not_used),
+ {Head2, NCT} = allocate_all(Head1, Count, Cntrs, CT),
+ Head3 = free_hole(Head2, HSz, HN, HA),
+ {Head3, NCT}.
+
+alloc_hole([{LSize,_} | _], Head, SegSz) when ?POW(LSize-1) > SegSz ->
+ {_, SegAddr, _} = dets_utils:alloc(Head, SegSz-1),
+ Size = ?POW(LSize-1)-1,
+ {_, Addr, _} = dets_utils:alloc(Head, Size),
+ N = (Addr - SegAddr) div SegSz,
+ Head1 = dets_utils:alloc_many(Head, SegSz, N, SegAddr),
+ {Head1, SegSz-1, N, SegAddr};
+alloc_hole(_Count, Head, _SegSz) ->
+ {Head, 0, 0, 0}.
+
+free_hole(Head, _Size, 0, _Addr) ->
+ Head;
+free_hole(Head, Size, N, Addr) ->
+ {Head1, _} = dets_utils:free(Head, Addr, Size),
+ free_hole(Head1, Size, N-1, Addr+Size+1).
+
+%% One (temporary) file for each buddy size, write all objects of that
+%% size to the file.
+allocate_all(Head, [{LSize,NoObjects} | Count], Cntrs, CT) ->
+ Size = ?POW(LSize-1)-1,
+ {_Head, Addr, _} = dets_utils:alloc(Head, Size),
+ NewHead = dets_utils:alloc_many(Head, Size+1, NoObjects, Addr),
+ {FileName, Fd} = temp_file(Head, LSize),
+ true = ets:insert(Cntrs, {LSize, Addr, {FileName, Fd}, NoObjects}),
+ NCT = ?VSET(LSize, CT, [Addr | []]),
+ allocate_all(NewHead, Count, Cntrs, NCT);
+allocate_all(Head, [], Cntrs, CT) ->
+ %% Note that space for the segments has been allocated already.
+ %% And one file for the segments...
+ {FileName, Fd} = temp_file(Head, ?FSCK_SEGMENT),
+ Addr = ?SEGADDR(?SEGARRSZ),
+ true = ets:insert(Cntrs, {?FSCK_SEGMENT, Addr, {FileName, Fd}, 0}),
+ NCT = ?VSET(tuple_size(CT), CT, [Addr | []]),
+ {Head, NCT}.
+
+temp_file(Head, N) ->
+ TmpName = lists:concat([Head#head.filename, '.', N]),
+ {ok, Fd} = dets_utils:open(TmpName, [raw, binary, write]),
+ {TmpName, Fd}.
+
+bin2term([<<Slot:32, LogSize:8, BinTerm/binary>> | BTs], L) ->
+ bin2term(BTs, [{Slot, LogSize, BinTerm} | L]);
+bin2term([], L) ->
+ lists:reverse(L).
+
+write_all_sizes(?DCT(D, CT), Cntrs) ->
+ ?DCT(D, write_sizes(1, tuple_size(CT), CT, Cntrs)).
+
+write_sizes(Sz, Sz, CT, Cntrs) ->
+ write_size(Sz, ?FSCK_SEGMENT, CT, Cntrs);
+write_sizes(Sz, MaxSz, CT, Cntrs) ->
+ NCT = write_size(Sz, Sz, CT, Cntrs),
+ write_sizes(Sz+1, MaxSz, NCT, Cntrs).
+
+write_size(Sz, I, CT, Cntrs) ->
+ case ?VGET(Sz, CT) of
+ not_used ->
+ CT;
+ [Addr | L] ->
+ {FileName, Fd} = ets:lookup_element(Cntrs, I, 3),
+ case file:write(Fd, lists:reverse(L)) of
+ ok ->
+ ?VSET(Sz, CT, [Addr | []]);
+ Error ->
+ dets_utils:file_error(FileName, Error)
+ end
+ end.
+
+output_slots(E, [E1 | Es], Acc, Head, DCT)
+ when element(1, E) =:= element(1, E1) ->
+ output_slots(E1, Es, [E1 | Acc], Head, DCT);
+output_slots(_E, [E | L], Acc, Head, DCT) ->
+ NDCT = output_slot(Acc, Head, DCT),
+ output_slots(E, L, [E], Head, NDCT);
+output_slots(E, [], Acc, _Head, DCT) ->
+ {E, Acc, DCT}.
+
+output_slot([E], _Head, ?DCT(D, CT)) ->
+ ?DCT(D, output_slot([{foo, E}], 0, foo, CT));
+output_slot(Es0, Head, ?DCT(D, CT)) ->
+ Kp = Head#head.keypos,
+ Fun = fun({_Slot, _LSize, BinTerm} = E) ->
+ Key = element(Kp, binary_to_term(BinTerm)),
+ {Key, E}
+ end,
+ Es = lists:map(Fun, Es0),
+ NEs = case Head#head.type of
+ set ->
+ [{Key0,_} = E | L0] = lists:sort(Es),
+ choose_one(lists:sort(L0), Key0, [E]);
+ bag ->
+ lists:usort(Es);
+ duplicate_bag ->
+ lists:sort(Es)
+ end,
+ Dups = D + length(Es) - length(NEs),
+ ?DCT(Dups, output_slot(NEs, 0, foo, CT)).
+
+choose_one([{Key,_} | Es], Key, L) ->
+ choose_one(Es, Key, L);
+choose_one([{Key,_} = E | Es], _Key, L) ->
+ choose_one(Es, Key, [E | L]);
+choose_one([], _Key, L) ->
+ L.
+
+output_slot([E | Es], Next, _Slot, CT) ->
+ {_Key, {Slot, LSize, BinTerm}} = E,
+ Size = byte_size(BinTerm),
+ Size2 = ?POW(LSize-1),
+ Pad = <<0:(Size2-Size-?OHDSZ)/unit:8>>,
+ BinObject = [<<Next:32, Size:32, ?ACTIVE:32>>, BinTerm | Pad],
+ [Addr | L] = ?VGET(LSize, CT),
+ NCT = ?VSET(LSize, CT, [Addr+Size2 | [BinObject | L]]),
+ output_slot(Es, Addr, Slot, NCT);
+output_slot([], Next, Slot, CT) ->
+ I = tuple_size(CT),
+ [Addr | L] = ?VGET(I, CT),
+ {Pos, _} = slot_position(Slot),
+ NoZeros = Pos - Addr,
+ BinObject = if
+ NoZeros > 100 ->
+ [dets_utils:make_zeros(NoZeros) | <<Next:32>>];
+ true ->
+ <<0:NoZeros/unit:8,Next:32>>
+ end,
+ Size = NoZeros+4,
+ ?VSET(I, CT, [Addr+Size | [BinObject | L]]).
+
+%% Does not close Fd.
+fsck_input(Head, Fd, Cntrs, _FileHeader) ->
+ %% The file is not compressed, so the object size cannot exceed
+ %% the filesize, for all objects.
+ MaxSz = case file:position(Fd, eof) of
+ {ok, Pos} ->
+ Pos;
+ _ ->
+ (1 bsl 32) - 1
+ end,
+ State0 = fsck_read(?BASE, Fd, []),
+ fsck_input1(Head, State0, Fd, MaxSz, Cntrs).
+
+fsck_input1(Head, State, Fd, MaxSz, Cntrs) ->
+ fun(close) ->
+ ok;
+ (read) ->
+ case State of
+ done ->
+ end_of_input;
+ {done, L} ->
+ R = count_input(Cntrs, L, []),
+ {R, fsck_input1(Head, done, Fd, MaxSz, Cntrs)};
+ {cont, L, Bin, Pos} ->
+ R = count_input(Cntrs, L, []),
+ FR = fsck_objs(Bin, Head#head.keypos, Head, []),
+ NewState = fsck_read(FR, Pos, Fd, MaxSz, Head),
+ {R, fsck_input1(Head, NewState, Fd, MaxSz, Cntrs)}
+ end
+ end.
+
+%% The ets table Cntrs is used for counting objects per size.
+count_input(Cntrs, [[LogSz | B] | Ts], L) ->
+ count_object(Cntrs, LogSz),
+ count_input(Cntrs, Ts, [B | L]);
+count_input(_Cntrs, [], L) ->
+ L.
+
+count_object(Cntrs, LogSz) ->
+ case catch ets:update_counter(Cntrs, LogSz, 1) of
+ N when is_integer(N) -> ok;
+ _Badarg -> true = ets:insert(Cntrs, {LogSz, 1})
+ end.
+
+fsck_read(Pos, F, L) ->
+ case file:position(F, Pos) of
+ {ok, _} ->
+ read_more_bytes(<<>>, 0, Pos, F, L);
+ _Error ->
+ {done, L}
+ end.
+
+fsck_read({more, Bin, Sz, L}, Pos, F, MaxSz, Head) when Sz > MaxSz ->
+ FR = skip_bytes(Bin, ?BUMP, Head#head.keypos, Head, L),
+ fsck_read(FR, Pos, F, MaxSz, Head);
+fsck_read({more, Bin, Sz, L}, Pos, F, _MaxSz, _Head) ->
+ read_more_bytes(Bin, Sz, Pos, F, L);
+fsck_read({new, Skip, L}, Pos, F, _MaxSz, _Head) ->
+ NewPos = Pos + Skip,
+ fsck_read(NewPos, F, L).
+
+read_more_bytes(B, Min, Pos, F, L) ->
+ Max = if
+ Min < ?CHUNK_SIZE -> ?CHUNK_SIZE;
+ true -> Min
+ end,
+ case dets_utils:read_n(F, Max) of
+ eof ->
+ {done, L};
+ Bin ->
+ NewPos = Pos + byte_size(Bin),
+ {cont, L, list_to_binary([B, Bin]), NewPos}
+ end.
+
+fsck_objs(Bin = <<_N:32, Sz:32, Status:32, Tail/binary>>, Kp, Head, L) ->
+ if
+ Status =:= ?ACTIVE ->
+ case Tail of
+ <<BinTerm:Sz/binary, Tail2/binary>> ->
+ case catch element(Kp, binary_to_term(BinTerm)) of
+ {'EXIT', _} ->
+ skip_bytes(Bin, ?BUMP, Kp, Head, L);
+ Key ->
+ LogSz = sz2pos(Sz+?OHDSZ),
+ Obj = make_object(Head, Key, LogSz, BinTerm),
+ NL = [[LogSz | Obj] | L],
+ Skip = ?POW(LogSz-1) - Sz - ?OHDSZ,
+ skip_bytes(Tail2, Skip, Kp, Head, NL)
+ end;
+ _ ->
+ {more, Bin, Sz, L}
+ end;
+ true ->
+ skip_bytes(Bin, ?BUMP, Kp, Head, L)
+ end;
+fsck_objs(Bin, _Kp, _Head, L) ->
+ {more, Bin, 0, L}.
+
+%% Version 8 has to know about version 9.
+make_object(Head, Key, _LogSz, BT) when Head#head.version =:= 9 ->
+ Slot = dets_v9:db_hash(Key, Head),
+ <<Slot:32, BT/binary>>;
+make_object(Head, Key, LogSz, BT) ->
+ Slot = db_hash(Key, Head),
+ <<Slot:32, LogSz:8, BT/binary>>.
+
+%% Inlined.
+skip_bytes(Bin, Skip, Kp, Head, L) ->
+ case Bin of
+ <<_:Skip/binary, Tail/binary>> ->
+ fsck_objs(Tail, Kp, Head, L);
+ _ ->
+ {new, Skip - byte_size(Bin), L}
+ end.
+
+%% -> {NewHead, ok} | throw({Head, Error})
+do_perform_save(H) ->
+ FL = dets_utils:get_freelists(H),
+ B = term_to_binary(FL),
+ Size = byte_size(B),
+ ?DEBUGF("size of freelist = ~p~n", [Size]),
+ ?DEBUGF("head.m = ~p~n", [H#head.m]),
+ ?DEBUGF("head.no_objects = ~p~n", [H#head.no_objects]),
+
+ {ok, Pos} = dets_utils:position(H, eof),
+ H1 = H#head{freelists_p = Pos},
+ W1 = {?FREELIST_POS, <<Pos:32>>},
+ W2 = {Pos, [<<0:32, Size:32, ?FREE:32>>, B]},
+
+ W3 = {?D_POS, <<(H1#head.m):32,
+ (H1#head.next):32,
+ (H1#head.keypos):32,
+ (H1#head.no_objects):32,
+ (H1#head.n):32>>},
+ {ClosedProperly, ClosedProperlyNeedCompacitng} =
+ case H1#head.hash_bif of
+ hash ->
+ {?CLOSED_PROPERLY2, ?CLOSED_PROPERLY2_NEED_COMPACTING};
+ phash ->
+ {?CLOSED_PROPERLY_NEW_HASH,
+ ?CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING}
+ end,
+ W4 =
+ if
+ Size > 1000, Size > H1#head.no_objects ->
+ {?CLOSED_PROPERLY_POS,
+ <<ClosedProperlyNeedCompacitng:32>>};
+ true ->
+ {?CLOSED_PROPERLY_POS, <<ClosedProperly:32>>}
+ end,
+ W5 = {?FILE_FORMAT_VERSION_POS, <<?FILE_FORMAT_VERSION:32>>},
+ {H2, ok} = dets_utils:pwrite(H1, [W1,W2,W3,W4,W5]),
+ {ok, Pos2} = dets_utils:position(H2, eof),
+ ?DEBUGF("Writing file size ~p, eof at ~p~n", [Pos2+4, Pos2]),
+ dets_utils:pwrite(H2, [{Pos2, <<(Pos2 + 4):32>>}]).
+
+%% -> [term()] | throw({Head, Error})
+slot_objs(H, Slot) when Slot >= H#head.next ->
+ '$end_of_table';
+slot_objs(H, Slot) ->
+ {_Pos, Chain} = chain(H, Slot),
+ collect_chain(H, Chain).
+
+collect_chain(_H, 0) -> [];
+collect_chain(H, Pos) ->
+ {Next, _Sz, Term} = prterm(H, Pos, ?ReadAhead),
+ [Term | collect_chain(H, Next)].
+
+db_hash(Key, Head) ->
+ H = h(Key, Head#head.hash_bif),
+ Hash = H rem Head#head.m,
+ if
+ Hash < Head#head.n ->
+ H rem (Head#head.m2); % H rem (2 * m)
+ true ->
+ Hash
+ end.
+
+h(I, phash) -> erlang:phash(I, ?BIG) - 1;
+h(I, HF) -> erlang:HF(I, ?BIG) - 1. %% stupid BIF has 1 counts.
+
+no_slots(_Head) ->
+ undefined.
+
+table_parameters(_Head) ->
+ undefined.
+
+%% Re-hashing a segment, starting with SlotStart.
+%%
+%% On the average, half of the objects of the chain are put into a new
+%% chain. If the slot of the old chain is i, then the slot of the new
+%% chain is i+m.
+%% Note that the insertion of objects into the new chain is simplified
+%% by the fact that the chains are not sorted on key, which means that
+%% each moved object can be inserted first in the new chain.
+%% (It is also a fact that the objects with the same key are not sorted.)
+%%
+%% -> {ok, Writes} | throw({Head, Error})
+re_hash(Head, SlotStart) ->
+ {SlotPos, _4} = slot_position(SlotStart),
+ {ok, Bin} = dets_utils:pread(Head, SlotPos, 4*?SEGSZ, 0),
+ {Read, Cs} = split_bin(SlotPos, Bin, [], []),
+ re_hash_read(Head, [], Read, Cs).
+
+split_bin(Pos, <<P:32, B/binary>>, R, Cs) ->
+ if
+ P =:= 0 ->
+ split_bin(Pos+4, B, R, Cs);
+ true ->
+ split_bin(Pos+4, B, [{P,?ReadAhead} | R], [[Pos] | Cs])
+ end;
+split_bin(_Pos, <<>>, R, Cs) ->
+ {R, Cs}.
+
+re_hash_read(Head, Cs, R, RCs) ->
+ {ok, Bins} = dets_utils:pread(R, Head),
+ re_hash_read(Head, R, RCs, Bins, Cs, [], []).
+
+re_hash_read(Head, [{Pos, Size} | Ps], [C | Cs],
+ [<<Next:32, Sz:32, _Status:32, Bin0/binary>> | Bins],
+ DoneCs, R, RCs) ->
+ case byte_size(Bin0) of
+ BinSz when BinSz >= Sz ->
+ case catch binary_to_term(Bin0) of
+ {'EXIT', _Error} ->
+ throw(dets_utils:corrupt_reason(Head, bad_object));
+ Term ->
+ Key = element(Head#head.keypos, Term),
+ New = h(Key, Head#head.hash_bif) rem Head#head.m2,
+ NC = case New >= Head#head.m of
+ true -> [{Pos,New} | C];
+ false -> [Pos | C]
+ end,
+ if
+ Next =:= 0 ->
+ NDoneCs = [NC | DoneCs],
+ re_hash_read(Head, Ps, Cs, Bins, NDoneCs, R, RCs);
+ true ->
+ NR = [{Next,?ReadAhead} | R],
+ NRCs = [NC | RCs],
+ re_hash_read(Head, Ps, Cs, Bins, DoneCs, NR, NRCs)
+ end
+ end;
+ BinSz when Size =:= BinSz+?OHDSZ ->
+ NR = [{Pos, Sz+?OHDSZ} | R],
+ re_hash_read(Head, Ps, Cs, Bins, DoneCs, NR, [C | RCs]);
+ _BinSz ->
+ throw({Head, {error, {premature_eof, Head#head.filename}}})
+ end;
+re_hash_read(Head, [], [], [], Cs, [], []) ->
+ re_hash_traverse_chains(Cs, Head, [], [], []);
+re_hash_read(Head, [], [], [], Cs, R, RCs) ->
+ re_hash_read(Head, Cs, R, RCs).
+
+re_hash_traverse_chains([C | Cs], Head, Rs, Ns, Ws) ->
+ case re_hash_find_new(C, Rs, start, start) of
+ false ->
+ re_hash_traverse_chains(Cs, Head, Rs, Ns, Ws);
+ {NRs, FirstNew, LastNew} ->
+ LastInNew = case C of
+ [{_,_} | _] -> true;
+ _ -> false
+ end,
+ N = {FirstNew, LastNew, LastInNew},
+ NWs = re_hash_link(C, start, start, start, Ws),
+ re_hash_traverse_chains(Cs, Head, NRs, [N | Ns], NWs)
+ end;
+re_hash_traverse_chains([], Head, Rs, Ns, Ws) ->
+ {ok, Bins} = dets_utils:pread(Rs, Head),
+ {ok, insert_new(Rs, Bins, Ns, Ws)}.
+
+re_hash_find_new([{Pos,NewSlot} | C], R, start, start) ->
+ {SPos, _4} = slot_position(NewSlot),
+ re_hash_find_new(C, [{SPos,4} | R], Pos, Pos);
+re_hash_find_new([{Pos,_SPos} | C], R, _FirstNew, LastNew) ->
+ re_hash_find_new(C, R, Pos, LastNew);
+re_hash_find_new([_Pos | C], R, FirstNew, LastNew) ->
+ re_hash_find_new(C, R, FirstNew, LastNew);
+re_hash_find_new([], _R, start, start) ->
+ false;
+re_hash_find_new([], R, FirstNew, LastNew) ->
+ {R, FirstNew, LastNew}.
+
+re_hash_link([{Pos,_SPos} | C], LastOld, start, _LastInNew, Ws) ->
+ re_hash_link(C, LastOld, Pos, true, Ws);
+re_hash_link([{Pos,_SPos} | C], LastOld, LastNew, false, Ws) ->
+ re_hash_link(C, LastOld, Pos, true, [{Pos,<<LastNew:32>>} | Ws]);
+re_hash_link([{Pos,_SPos} | C], LastOld, _LastNew, LastInNew, Ws) ->
+ re_hash_link(C, LastOld, Pos, LastInNew, Ws);
+re_hash_link([Pos | C], start, LastNew, true, Ws) ->
+ re_hash_link(C, Pos, LastNew, false, [{Pos,<<0:32>>} | Ws]);
+re_hash_link([Pos | C], LastOld, LastNew, true, Ws) ->
+ re_hash_link(C, Pos, LastNew, false, [{Pos,<<LastOld:32>>} | Ws]);
+re_hash_link([Pos | C], _LastOld, LastNew, LastInNew, Ws) ->
+ re_hash_link(C, Pos, LastNew, LastInNew, Ws);
+re_hash_link([], _LastOld, _LastNew, _LastInNew, Ws) ->
+ Ws.
+
+insert_new([{NewSlotPos,_4} | Rs], [<<P:32>> = PB | Bins], [N | Ns], Ws) ->
+ {FirstNew, LastNew, LastInNew} = N,
+ Ws1 = case P of
+ 0 when LastInNew ->
+ Ws;
+ 0 ->
+ [{LastNew, <<0:32>>} | Ws];
+ _ ->
+ [{LastNew, PB} | Ws]
+ end,
+ NWs = [{NewSlotPos, <<FirstNew:32>>} | Ws1],
+ insert_new(Rs, Bins, Ns, NWs);
+insert_new([], [], [], Ws) ->
+ Ws.
+
+%% When writing the cache, a 'work list' is first created:
+%% WorkList = [{Key, {Delete,Lookup,[Inserted]}}]
+%% Delete = keep | delete
+%% Lookup = skip | lookup
+%% Inserted = {object(), No}
+%% No = integer()
+%% If No =< 0 then there will be -No instances of object() on the file
+%% when the cache has been written. If No > 0 then No instances of
+%% object() will be added to the file.
+%% If Delete has the value 'delete', then all objects with the key Key
+%% have been deleted. (This could be viewed as a shorthand for {Object,0}
+%% for each object Object on the file not mentioned in some Inserted.)
+%% If Lookup has the value 'lookup', all objects with the key Key will
+%% be returned.
+%%
+
+%% -> {NewHead, [LookedUpObject], pwrite_list()} | throw({NewHead, Error})
+write_cache(Head) ->
+ #head{cache = C, type = Type} = Head,
+ case dets_utils:is_empty_cache(C) of
+ true -> {Head, [], []};
+ false ->
+ {NewC, _MaxInserts, PerKey} = dets_utils:reset_cache(C),
+ %% NoInsertedKeys is an upper limit on the number of new keys.
+ {WL, NoInsertedKeys} = make_wl(PerKey, Type),
+ Head1 = Head#head{cache = NewC},
+ case may_grow(Head1, NoInsertedKeys, once) of
+ {Head2, ok} ->
+ eval_work_list(Head2, WL);
+ HeadError ->
+ throw(HeadError)
+ end
+ end.
+
+make_wl(PerKey, Type) ->
+ make_wl(PerKey, Type, [], 0).
+
+make_wl([{Key,L} | PerKey], Type, WL, Ins) ->
+ [Cs | I] = wl(L, Type),
+ make_wl(PerKey, Type, [{Key,Cs} | WL], Ins+I);
+make_wl([], _Type, WL, Ins) ->
+ {WL, Ins}.
+
+wl(L, Type) ->
+ wl(L, Type, keep, skip, 0, []).
+
+wl([{_Seq, delete_key} | Cs], Type, _Del, Lookup, _I, _Objs) ->
+ wl(Cs, Type, delete, Lookup, 0, []);
+wl([{_Seq, {delete_object, Object}} | Cs], Type, Del, Lookup, I, Objs) ->
+ NObjs = lists:keydelete(Object, 1, Objs),
+ wl(Cs, Type, Del, Lookup, I, [{Object,0} | NObjs]);
+wl([{_Seq, {insert, Object}} | Cs], Type, _Del, Lookup, _I, _Objs)
+ when Type =:= set ->
+ wl(Cs, Type, delete, Lookup, 1, [{Object,-1}]);
+wl([{_Seq, {insert, Object}} | Cs], Type, Del, Lookup, _I, Objs) ->
+ NObjs =
+ case lists:keysearch(Object, 1, Objs) of
+ {value, {_, 0}} ->
+ lists:keyreplace(Object, 1, Objs, {Object,-1});
+ {value, {_, _C}} when Type =:= bag -> % C =:= 1; C =:= -1
+ Objs;
+ {value, {_, C}} when C < 0 -> % when Type =:= duplicate_bag
+ lists:keyreplace(Object, 1, Objs, {Object,C-1});
+ {value, {_, C}} -> % when C > 0, Type =:= duplicate_bag
+ lists:keyreplace(Object, 1, Objs, {Object,C+1});
+ false when Del =:= delete ->
+ [{Object, -1} | Objs];
+ false ->
+ [{Object, 1} | Objs]
+ end,
+ wl(Cs, Type, Del, Lookup, 1, NObjs);
+wl([{_Seq, {lookup,_Pid}=Lookup} | Cs], Type, Del, _Lookup, I, Objs) ->
+ wl(Cs, Type, Del, Lookup, I, Objs);
+wl([], _Type, Del, Lookup, I, Objs) ->
+ [{Del, Lookup, Objs} | I].
+
+%% -> {NewHead, ok} | {NewHead, Error}
+may_grow(Head, _N, _How) when Head#head.fixed =/= false ->
+ {Head, ok};
+may_grow(#head{access = read}=Head, _N, _How) ->
+ {Head, ok};
+may_grow(Head, _N, _How) when Head#head.next >= ?MAXOBJS ->
+ {Head, ok};
+may_grow(Head, N, How) ->
+ Extra = erlang:min(2*?SEGSZ, Head#head.no_objects + N - Head#head.next),
+ case catch may_grow1(Head, Extra, How) of
+ {error, Reason} -> % alloc may throw error
+ {Head, {error, Reason}};
+ Reply ->
+ Reply
+ end.
+
+may_grow1(Head, Extra, many_times) when Extra > ?SEGSZ ->
+ Reply = grow(Head, 1, undefined),
+ self() ! ?DETS_CALL(self(), may_grow),
+ Reply;
+may_grow1(Head, Extra, _How) ->
+ grow(Head, Extra, undefined).
+
+%% -> {Head, ok} | throw({Head, Error})
+grow(Head, Extra, _SegZero) when Extra =< 0 ->
+ {Head, ok};
+grow(Head, Extra, undefined) ->
+ grow(Head, Extra, seg_zero());
+grow(Head, Extra, SegZero) ->
+ #head{n = N, next = Next, m = M} = Head,
+ SegNum = ?SLOT2SEG(Next),
+ {Head0, Ws1} = allocate_segment(Head, SegZero, SegNum),
+ {Head1, ok} = dets_utils:pwrite(Head0, Ws1),
+ %% If re_hash fails, segp_cache has been called, but it does not matter.
+ {ok, Ws2} = re_hash(Head1, N),
+ {Head2, ok} = dets_utils:pwrite(Head1, Ws2),
+ NewHead =
+ if
+ N + ?SEGSZ =:= M ->
+ Head2#head{n = 0, next = Next + ?SEGSZ, m = 2 * M, m2 = 4 * M};
+ true ->
+ Head2#head{n = N + ?SEGSZ, next = Next + ?SEGSZ}
+ end,
+ grow(NewHead, Extra - ?SEGSZ, SegZero).
+
+seg_zero() ->
+ <<0:(4*?SEGSZ)/unit:8>>.
+
+find_object(Head, Object) ->
+ Key = element(Head#head.keypos, Object),
+ Slot = db_hash(Key, Head),
+ find_object(Head, Object, Slot).
+
+find_object(H, _Obj, Slot) when Slot >= H#head.next ->
+ false;
+find_object(H, Obj, Slot) ->
+ {_Pos, Chain} = chain(H, Slot),
+ case catch find_obj(H, Obj, Chain) of
+ {ok, Pos} ->
+ {ok, Pos};
+ _Else ->
+ false
+ end.
+
+find_obj(H, Obj, Pos) when Pos > 0 ->
+ {Next, _Sz, Term} = prterm(H, Pos, ?ReadAhead),
+ if
+ Term == Obj ->
+ {ok, Pos};
+ true ->
+ find_obj(H, Obj, Next)
+ end.
+
+%% Given, a slot, return the {Pos, Chain} in the file where the
+%% objects hashed to this slot reside. Pos is the position in the
+%% file where the chain pointer is written and Chain is the position
+%% in the file where the first object resides.
+chain(Head, Slot) ->
+ Pos = ?SEGADDR(?SLOT2SEG(Slot)),
+ Segment = get_segp(Pos),
+ FinalPos = Segment + (4 * ?REM2(Slot, ?SEGSZ)),
+ {ok, <<Chain:32>>} = dets_utils:pread(Head, FinalPos, 4, 0),
+ {FinalPos, Chain}.
+
+%%%
+%%% Cache routines depending on the dets file format.
+%%%
+
+%% -> {Head, [LookedUpObject], pwrite_list()} | throw({Head, Error})
+eval_work_list(Head, WorkLists) ->
+ SWLs = tag_with_slot(WorkLists, Head, []),
+ P1 = dets_utils:family(SWLs),
+ {PerSlot, SlotPositions} = remove_slot_tag(P1, [], []),
+ {ok, Bins} = dets_utils:pread(SlotPositions, Head),
+ first_object(PerSlot, SlotPositions, Bins, Head, [], [], [], []).
+
+tag_with_slot([{K,_} = WL | WLs], Head, L) ->
+ tag_with_slot(WLs, Head, [{db_hash(K, Head), WL} | L]);
+tag_with_slot([], _Head, L) ->
+ L.
+
+remove_slot_tag([{S,SWLs} | SSWLs], Ls, SPs) ->
+ remove_slot_tag(SSWLs, [SWLs | Ls], [slot_position(S) | SPs]);
+remove_slot_tag([], Ls, SPs) ->
+ {Ls, SPs}.
+
+%% The initial chain pointers and the first object in each chain are
+%% read "in parallel", that is, with one call to file:pread/2 (two
+%% calls altogether). The following chain objects are read one by
+%% one. This is a compromise: if the chains are long and threads are
+%% active, it would be faster to keep a state for each chain and read
+%% the objects of the chains in parallel, but the overhead would be
+%% quite substantial.
+
+first_object([WorkLists | SPs], [{P1,_4} | Ss], [<<P2:32>> | Bs], Head,
+ ObjsToRead, ToRead, Ls, LU) when P2 =:= 0 ->
+ L0 = [{old,P1}],
+ {L, NLU} = eval_slot(Head, ?ReadAhead, P2, WorkLists, L0, LU),
+ first_object(SPs, Ss, Bs, Head, ObjsToRead, ToRead, [L | Ls], NLU);
+first_object([WorkLists | SPs], [{P1,_4} | Ss], [<<P2:32>> | Bs], Head,
+ ObjsToRead, ToRead, Ls, LU) ->
+ E = {P1,P2,WorkLists},
+ first_object(SPs, Ss, Bs, Head,
+ [E | ObjsToRead], [{P2, ?ReadAhead} | ToRead], Ls, LU);
+first_object([], [], [], Head, ObjsToRead, ToRead, Ls, LU) ->
+ {ok, Bins} = dets_utils:pread(ToRead, Head),
+ case catch eval_first(Bins, ObjsToRead, Head, Ls, LU) of
+ {ok, NLs, NLU} ->
+ case create_writes(NLs, Head, [], 0) of
+ {Head1, [], 0} ->
+ {Head1, NLU, []};
+ {Head1, Ws, No} ->
+ {NewHead, Ws2} = update_no_objects(Head1, Ws, No),
+ {NewHead, NLU, Ws2}
+ end;
+ _Error ->
+ throw(dets_utils:corrupt_reason(Head, bad_object))
+ end.
+
+%% Update no_objects on the file too, if the number of segments that
+%% dets:fsck/6 use for estimate has changed.
+update_no_objects(Head, Ws, 0) -> {Head, Ws};
+update_no_objects(Head, Ws, Delta) ->
+ No = Head#head.no_objects,
+ NewNo = No + Delta,
+ NWs =
+ if
+ NewNo > ?MAXOBJS ->
+ Ws;
+ ?SLOT2SEG(No) =:= ?SLOT2SEG(NewNo) ->
+ Ws;
+ true ->
+ [{?NO_OBJECTS_POS, <<NewNo:32>>} | Ws]
+ end,
+ {Head#head{no_objects = NewNo}, NWs}.
+
+eval_first([<<Next:32, Sz:32, _Status:32, Bin/binary>> | Bins],
+ [SP | SPs], Head, Ls, LU) ->
+ {P1, P2, WLs} = SP,
+ L0 = [{old,P1}],
+ case byte_size(Bin) of
+ BinSz when BinSz >= Sz ->
+ Term = binary_to_term(Bin),
+ Key = element(Head#head.keypos, Term),
+ {L, NLU} = find_key(Head, P2, Next, Sz, Term, Key, WLs, L0, LU),
+ eval_first(Bins, SPs, Head, [L | Ls], NLU);
+ _BinSz ->
+ {L, NLU} = eval_slot(Head, Sz+?OHDSZ, P2, WLs, L0, LU),
+ eval_first(Bins, SPs, Head, [L | Ls], NLU)
+ end;
+eval_first([], [], _Head, Ls, LU) ->
+ {ok, Ls, LU}.
+
+eval_slot(_Head, _TrySize, _Pos=0, [], L, LU) ->
+ {L, LU};
+eval_slot(Head, _TrySize, Pos=0, [WL | WLs], L, LU) ->
+ {_Key, {_Delete, LookUp, Objects}} = WL,
+ {NL, NLU} = end_of_key(Objects, LookUp, L, []),
+ eval_slot(Head, ?ReadAhead, Pos, WLs, NL, NLU++LU);
+eval_slot(Head, TrySize, Pos, WLs, L, LU) ->
+ {NextPos, Size, Term} = prterm(Head, Pos, TrySize),
+ Key = element(Head#head.keypos, Term),
+ find_key(Head, Pos, NextPos, Size, Term, Key, WLs, L, LU).
+
+find_key(Head, Pos, NextPos, Size, Term, Key, WLs, L, LU) ->
+ case lists:keysearch(Key, 1, WLs) of
+ {value, {_, {Delete, LookUp, Objects}} = WL} ->
+ NWLs = lists:delete(WL, WLs),
+ {NewObjects, NL, LUK} = eval_object(Size, Term, Delete, LookUp,
+ Objects, Head, Pos, L, []),
+ eval_key(Key, Delete, LookUp, NewObjects, Head, NextPos,
+ NWLs, NL, LU, LUK);
+ false ->
+ L0 = [{old,Pos} | L],
+ eval_slot(Head, ?ReadAhead, NextPos, WLs, L0, LU)
+ end.
+
+eval_key(_Key, _Delete, Lookup, _Objects, Head, Pos, WLs, L, LU, LUK)
+ when Head#head.type =:= set ->
+ NLU = case Lookup of
+ {lookup, Pid} -> [{Pid,LUK} | LU];
+ skip -> LU
+ end,
+ eval_slot(Head, ?ReadAhead, Pos, WLs, L, NLU);
+eval_key(_Key, _Delete, LookUp, Objects, Head, Pos, WLs, L, LU, LUK)
+ when Pos =:= 0 ->
+ {NL, NLU} = end_of_key(Objects, LookUp, L, LUK),
+ eval_slot(Head, ?ReadAhead, Pos, WLs, NL, NLU++LU);
+eval_key(Key, Delete, LookUp, Objects, Head, Pos, WLs, L, LU, LUK) ->
+ {NextPos, Size, Term} = prterm(Head, Pos, ?ReadAhead),
+ case element(Head#head.keypos, Term) of
+ Key ->
+ {NewObjects, NL, LUK1} =
+ eval_object(Size, Term, Delete, LookUp,Objects,Head,Pos,L,LUK),
+ eval_key(Key, Delete, LookUp, NewObjects, Head, NextPos, WLs,
+ NL, LU, LUK1);
+ Key2 ->
+ {L1, NLU} = end_of_key(Objects, LookUp, L, LUK),
+ find_key(Head, Pos, NextPos, Size, Term, Key2, WLs, L1, NLU++LU)
+ end.
+
+%% All objects in Objects have the key Key.
+eval_object(Size, Term, Delete, LookUp, Objects, Head, Pos, L, LU) ->
+ Type = Head#head.type,
+ case lists:keysearch(Term, 1, Objects) of
+ {value, {_Object, N}} when N =:= 0 ->
+ L1 = [{delete,Pos,Size} | L],
+ {Objects, L1, LU};
+ {value, {_Object, N}} when N < 0, Type =:= set ->
+ L1 = [{old,Pos} | L],
+ wl_lookup(LookUp, Objects, Term, L1, LU);
+ {value, {Object, _N}} when Type =:= bag -> % when N =:= 1; N =:= -1
+ L1 = [{old,Pos} | L],
+ Objects1 = lists:keydelete(Object, 1, Objects),
+ wl_lookup(LookUp, Objects1, Term, L1, LU);
+ {value, {Object, N}} when N < 0, Type =:= duplicate_bag ->
+ L1 = [{old,Pos} | L],
+ Objects1 = lists:keyreplace(Object, 1, Objects, {Object,N+1}),
+ wl_lookup(LookUp, Objects1, Term, L1, LU);
+ {value, {_Object, N}} when N > 0, Type =:= duplicate_bag ->
+ L1 = [{old,Pos} | L],
+ wl_lookup(LookUp, Objects, Term, L1, LU);
+ false when Type =:= set, Delete =:= delete ->
+ case lists:keysearch(-1, 2, Objects) of
+ false -> % no inserted object, perhaps deleted objects
+ L1 = [{delete,Pos,Size} | L],
+ {[], L1, LU};
+ {value, {Term2,-1}} ->
+ Bin2 = term_to_binary(Term2),
+ NSize = byte_size(Bin2),
+ Overwrite =
+ if
+ NSize =:= Size ->
+ true;
+ true ->
+ SizePos = sz2pos(Size+?OHDSZ),
+ NSizePos = sz2pos(NSize+?OHDSZ),
+ SizePos =:= NSizePos
+ end,
+ E = if
+ Overwrite ->
+ {overwrite,Bin2,Pos};
+ true ->
+ {replace,Bin2,Pos,Size}
+ end,
+ wl_lookup(LookUp, [], Term2, [E | L], LU)
+ end;
+ false when Delete =:= delete ->
+ L1 = [{delete,Pos,Size} | L],
+ {Objects, L1, LU};
+ false ->
+ L1 = [{old,Pos} | L],
+ wl_lookup(LookUp, Objects, Term, L1, LU)
+ end.
+
+%% Inlined.
+wl_lookup({lookup,_}, Objects, Term, L, LU) ->
+ {Objects, L, [Term | LU]};
+wl_lookup(skip, Objects, _Term, L, LU) ->
+ {Objects, L, LU}.
+
+end_of_key([{Object,N0} | Objs], LookUp, L, LU) when N0 =/= 0 ->
+ N = abs(N0),
+ NL = [{insert,N,term_to_binary(Object)} | L],
+ NLU = case LookUp of
+ {lookup, _} ->
+ lists:duplicate(N, Object) ++ LU;
+ skip ->
+ LU
+ end,
+ end_of_key(Objs, LookUp, NL, NLU);
+end_of_key([_ | Objects], LookUp, L, LU) ->
+ end_of_key(Objects, LookUp, L, LU);
+end_of_key([], {lookup,Pid}, L, LU) ->
+ {L, [{Pid,LU}]};
+end_of_key([], skip, L, LU) ->
+ {L, LU}.
+
+create_writes([L | Ls], H, Ws, No) ->
+ {NH, NWs, NNo} = create_writes(L, H, Ws, No, 0, true),
+ create_writes(Ls, NH, NWs, NNo);
+create_writes([], H, Ws, No) ->
+ {H, lists:reverse(Ws), No}.
+
+create_writes([{old,Pos} | L], H, Ws, No, _Next, true) ->
+ create_writes(L, H, Ws, No, Pos, true);
+create_writes([{old,Pos} | L], H, Ws, No, Next, false) ->
+ W = {Pos, <<Next:32>>},
+ create_writes(L, H, [W | Ws], No, Pos, true);
+create_writes([{insert,N,Bin} | L], H, Ws, No, Next, _NextIsOld) ->
+ {NH, NWs, Pos} = create_inserts(N, H, Ws, Next, byte_size(Bin), Bin),
+ create_writes(L, NH, NWs, No+N, Pos, false);
+create_writes([{overwrite,Bin,Pos} | L], H, Ws, No, Next, _) ->
+ Size = byte_size(Bin),
+ W = {Pos, [<<Next:32, Size:32, ?ACTIVE:32>>, Bin]},
+ create_writes(L, H, [W | Ws], No, Pos, true);
+create_writes([{replace,Bin,Pos,OSize} | L], H, Ws, No, Next, _) ->
+ Size = byte_size(Bin),
+ {H1, _} = dets_utils:free(H, Pos, OSize+?OHDSZ),
+ {NH, NewPos, _} = dets_utils:alloc(H1, ?OHDSZ + Size),
+ W1 = {NewPos, [<<Next:32, Size:32, ?ACTIVE:32>>, Bin]},
+ NWs = if
+ Pos =:= NewPos ->
+ [W1 | Ws];
+ true ->
+ W2 = {Pos+?STATUS_POS, <<?FREE:32>>},
+ [W1,W2 | Ws]
+ end,
+ create_writes(L, NH, NWs, No, NewPos, false);
+create_writes([{delete,Pos,Size} | L], H, Ws, No, Next, _) ->
+ {NH, _} = dets_utils:free(H, Pos, Size+?OHDSZ),
+ NWs = [{Pos+?STATUS_POS,<<?FREE:32>>} | Ws],
+ create_writes(L, NH, NWs, No-1, Next, false);
+create_writes([], H, Ws, No, _Next, _NextIsOld) ->
+ {H, Ws, No}.
+
+create_inserts(0, H, Ws, Next, _Size, _Bin) ->
+ {H, Ws, Next};
+create_inserts(N, H, Ws, Next, Size, Bin) ->
+ {NH, Pos, _} = dets_utils:alloc(H, ?OHDSZ + Size),
+ W = {Pos, [<<Next:32, Size:32, ?ACTIVE:32>>, Bin]},
+ create_inserts(N-1, NH, [W | Ws], Pos, Size, Bin).
+
+slot_position(S) ->
+ Pos = ?SEGADDR(?SLOT2SEG(S)),
+ Segment = get_segp(Pos),
+ FinalPos = Segment + (4 * ?REM2(S, ?SEGSZ)),
+ {FinalPos, 4}.
+
+%% Twice the size of a segment due to the bug in sz2pos/1. Inlined.
+actual_seg_size() ->
+ ?POW(sz2pos(?SEGSZ*4)-1).
+
+segp_cache(Pos, Segment) ->
+ put(Pos, Segment).
+
+%% Inlined.
+get_segp(Pos) ->
+ get(Pos).
+
+%% Bug: If Sz0 is equal to 2**k for some k, then 2**(k+1) bytes are
+%% allocated (wasting 2**k bytes).
+sz2pos(N) ->
+ 1 + dets_utils:log2(N+1).
+
+scan_objs(_Head, Bin, From, To, L, Ts, R, _Type) ->
+ scan_objs(Bin, From, To, L, Ts, R).
+
+scan_objs(Bin, From, To, L, Ts, -1) ->
+ {stop, Bin, From, To, L, Ts};
+scan_objs(B = <<_N:32, Sz:32, St:32, T/binary>>, From, To, L, Ts, R) ->
+ if
+ St =:= ?ACTIVE;
+ St =:= ?FREE -> % deleted after scanning started
+ case T of
+ <<BinTerm:Sz/binary, T2/binary>> ->
+ NTs = [BinTerm | Ts],
+ OSz = Sz + ?OHDSZ,
+ Skip = ?POW(sz2pos(OSz)-1) - OSz,
+ F2 = From + OSz,
+ NR = if
+ R < 0 ->
+ R + 1;
+ true ->
+ R + OSz + Skip
+ end,
+ scan_skip(T2, F2, To, Skip, L, NTs, NR);
+ _ ->
+ {more, From, To, L, Ts, R, Sz+?OHDSZ}
+ end;
+ true -> % a segment
+ scan_skip(B, From, To, actual_seg_size(), L, Ts, R)
+ end;
+scan_objs(_B, From, To, L, Ts, R) ->
+ {more, From, To, L, Ts, R, 0}.
+
+scan_skip(Bin, From, To, Skip, L, Ts, R) when From + Skip < To ->
+ SkipPos = From + Skip,
+ case Bin of
+ <<_:Skip/binary, Tail/binary>> ->
+ scan_objs(Tail, SkipPos, To, L, Ts, R);
+ _ ->
+ {more, SkipPos, To, L, Ts, R, 0}
+ end;
+scan_skip(Bin, From, To, Skip, L, Ts, R) when From + Skip =:= To ->
+ scan_next_allocated(Bin, From, To, L, Ts, R);
+scan_skip(_Bin, From, _To, Skip, L, Ts, R) -> % when From + Skip > _To
+ From1 = From + Skip,
+ {more, From1, From1, L, Ts, R, 0}.
+
+scan_next_allocated(_Bin, _From, To, <<>>=L, Ts, R) ->
+ {more, To, To, L, Ts, R, 0};
+scan_next_allocated(Bin, From0, _To, <<From:32, To:32, L/binary>>, Ts, R) ->
+ Skip = From - From0,
+ scan_skip(Bin, From0, To, Skip, L, Ts, R).
+
+%% Read term from file at position Pos
+prterm(Head, Pos, ReadAhead) ->
+ Res = dets_utils:pread(Head, Pos, ?OHDSZ, ReadAhead),
+ ?DEBUGF("file:pread(~p, ~p, ?) -> ~p~n", [Head#head.filename, Pos, Res]),
+ {ok, <<Next:32, Sz:32, _Status:32, Bin0/binary>>} = Res,
+ ?DEBUGF("{Next, Sz} = ~p~n", [{Next, Sz}]),
+ Bin = case byte_size(Bin0) of
+ Actual when Actual >= Sz ->
+ Bin0;
+ _ ->
+ {ok, Bin1} = dets_utils:pread(Head, Pos + ?OHDSZ, Sz, 0),
+ Bin1
+ end,
+ Term = binary_to_term(Bin),
+ {Next, Sz, Term}.
+
+%%%%%%%%%%%%%%%%% DEBUG functions %%%%%%%%%%%%%%%%
+
+file_info(FH) ->
+ #fileheader{closed_properly = CP, keypos = Kp,
+ m = M, next = Next, n = N, version = Version,
+ type = Type, no_objects = NoObjects}
+ = FH,
+ if
+ CP =:= 0 ->
+ {error, not_closed};
+ FH#fileheader.cookie =/= ?MAGIC ->
+ {error, not_a_dets_file};
+ FH#fileheader.version =/= ?FILE_FORMAT_VERSION ->
+ {error, bad_version};
+ true ->
+ {ok, [{closed_properly,CP},{keypos,Kp},{m, M},
+ {n,N},{next,Next},{no_objects,NoObjects},
+ {type,Type},{version,Version}]}
+ end.
+
+v_segments(H) ->
+ v_segments(H, 0).
+
+v_segments(_H, ?SEGARRSZ) ->
+ done;
+v_segments(H, SegNo) ->
+ Seg = dets_utils:read_4(H#head.fptr, ?SEGADDR(SegNo)),
+ if
+ Seg =:= 0 ->
+ done;
+ true ->
+ io:format("SEGMENT ~w ", [SegNo]),
+ io:format("At position ~w~n", [Seg]),
+ v_segment(H, SegNo, Seg, 0),
+ v_segments(H, SegNo+1)
+ end.
+
+v_segment(_H, _, _SegPos, ?SEGSZ) ->
+ done;
+v_segment(H, SegNo, SegPos, SegSlot) ->
+ Slot = SegSlot + (SegNo * ?SEGSZ),
+ Chain = dets_utils:read_4(H#head.fptr, SegPos + (4 * SegSlot)),
+ if
+ Chain =:= 0 -> %% don't print empty chains
+ true;
+ true ->
+ io:format(" <~p>~p: [",[SegPos + (4 * SegSlot), Slot]),
+ print_chain(H, Chain)
+ end,
+ v_segment(H, SegNo, SegPos, SegSlot+1).
+
+print_chain(_H, 0) ->
+ io:format("] \n", []);
+print_chain(H, Pos) ->
+ {ok, _} = file:position(H#head.fptr, Pos),
+ case rterm(H#head.fptr) of
+ {ok, 0, _Sz, Term} ->
+ io:format("<~p>~p] \n",[Pos, Term]);
+ {ok, Next, _Sz, Term} ->
+ io:format("<~p>~p, ", [Pos, Term]),
+ print_chain(H, Next);
+ Other ->
+ io:format("~nERROR ~p~n", [Other])
+ end.
+
+%% Can't be used at the bucket level!!!!
+%% Only when we go down a chain
+rterm(F) ->
+ case catch rterm2(F) of
+ {'EXIT', Reason} -> %% truncated DAT file
+ dets_utils:vformat("** dets: Corrupt or truncated dets file~n",
+ []),
+ {error, Reason};
+ Other ->
+ Other
+ end.
+
+rterm2(F) ->
+ {ok, <<Next:32, Sz:32, _:32>>} = file:read(F, ?OHDSZ),
+ {ok, Bin} = file:read(F, Sz),
+ Term = binary_to_term(Bin),
+ {ok, Next, Sz, Term}.
+
+
diff --git a/lib/stdlib/src/dets_v9.erl b/lib/stdlib/src/dets_v9.erl
new file mode 100644
index 0000000000..53238e962f
--- /dev/null
+++ b/lib/stdlib/src/dets_v9.erl
@@ -0,0 +1,2761 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dets_v9).
+
+%% Dets files, implementation part. This module handles version 9.
+%% To be called from dets.erl only.
+
+-export([constants/0, mark_dirty/1, read_file_header/2,
+ check_file_header/2, do_perform_save/1, initiate_file/11,
+ prep_table_copy/9, init_freelist/2, fsck_input/4,
+ bulk_input/3, output_objs/4, bchunk_init/2,
+ try_bchunk_header/2, compact_init/3, read_bchunks/2,
+ write_cache/1, may_grow/3, find_object/2, slot_objs/2,
+ scan_objs/8, db_hash/2, no_slots/1, table_parameters/1]).
+
+-export([file_info/1, v_segments/1]).
+
+-export([cache_segps/3]).
+
+-compile({inline, [{max_objsize,1},{maxobjsize,1}]}).
+-compile({inline, [{write_segment_file,6}]}).
+-compile({inline, [{sz2pos,1},{adjsz,1}]}).
+-compile({inline, [{skip_bytes,6},{make_object,4}]}).
+-compile({inline, [{segp_cache,2},{get_segp,1},{get_arrpart,1}]}).
+-compile({inline, [{h,2}]}).
+
+-include("dets.hrl").
+
+%% The layout of the file is :
+%%
+%% bytes decsription
+%% ---------------------- File header
+%% 4 FreelistsPointer
+%% 4 Cookie
+%% 4 ClosedProperly (pos=8)
+%% 4 Type (pos=12)
+%% 4 Version (pos=16)
+%% 4 M
+%% 4 Next
+%% 4 KeyPos
+%% 4 NoObjects
+%% 4 NoKeys
+%% 4 MinNoSlots
+%% 4 MaxNoSlots
+%% 4 HashMethod
+%% 4 N
+%% ---
+%% 256 Version 9(a): Reserved for future versions. Initially zeros.
+%% Version 9(b) has instead:
+%% 112 28 counters for the buddy system sizes 2^4 to 2^31.
+%% 144 Reserved for future versions. Initially zeros.
+%% Version 9(c) has instead:
+%% 112 28 counters for the buddy system sizes (as for 9(b)).
+%% 16 MD5-sum for the 44 plus 112 bytes before the MD5-sum.
+%% (FreelistsPointer, Cookie and ClosedProperly are not digested.)
+%% 128 Reserved for future versions. Initially zeros.
+%% ---
+%% ------------------ end of file header
+%% 4*256 SegmentArray Pointers.
+%% ------------------ This is BASE.
+%% 4*512 SegmentArray Part 1
+%% ... More SegmentArray Parts
+%% 8*256 First segment
+%% ??? Objects (free and alive)
+%% 4*512 Further SegmentArray Part.
+%% ??? Objects (free and alive)
+%% 8*256 Further segment.
+%% ??? Objects (free and alive)
+%% ... more objects, segment array parts, and segments ...
+%% -----------------------------
+%% ??? Free lists
+%% -----------------------------
+%% 4 File size, in bytes.
+
+%% Before we can find an object we must find the slot where the
+%% object resides. Each slot is a (possibly empty) list (or chain) of
+%% objects that hash to the same slot. If the value stored in the
+%% slot is zero, the slot chain is empty. If the slot value is
+%% non-zero, the value points to a position in the file where the
+%% collection of objects resides. Each collection has the following
+%% layout:
+%%
+%% bytes decsription
+%% --------------------
+%% 4 Size of the area allocated for the collection (8+Sz)
+%% 4 Status (FREE or ACTIVE). These two are the Object Header.
+%% Sz A binary containing the objects per key, sorted on key.
+%%
+%% When repairing or converting a file, the status field is used.
+%%
+%% The binary containing the objects per key of a table of type 'set'
+%% has the following layout:
+%%
+%% bytes decsription
+%% --------------------
+%% 4 Size of the object of the first key (4+OSz1)
+%% OSz1 The object of the first key
+%% ...
+%% 4 Size of the object of the ith key (4+OSzi)
+%% OSzi The object of the ith key
+%%
+%% The binary containing the objects per key of a table of type 'bag'
+%% or 'duplicate_bag' has the following layout:
+%%
+%% bytes decsription
+%% ----------------------
+%% 4 Size of the objects of the first key (4 + OSz1_1+...+OSz1_j+...)
+%% 4 Size of the first object of the first key (4+OSz1_1)
+%% OSz1_1 The first object of the first key
+%% ...
+%% 4 Size of the jth object of the first key (4+OSz1_j)
+%% OSz1_j The jth object of the first key
+%% ...
+%% 4 Size of the objects of the ith key (4 + OSzi_1+...+OSzi_k+...)
+%% 4 Size of the first object of the ith key (4+OSzi_1)
+%% OSzi_1 The first object of the ith key
+%% ...
+%% 4 Size of the kth object of the ith key (4+OSzi_k)
+%% OSzi_k The kth object of the ith key
+%% ...
+%%
+%% The objects of a key are placed in time order, that is, the older
+%% objects come first. If a new object is inserted, it is inserted
+%% last.
+%%
+%%
+%%
+%%|---------------|
+%%| head |
+%%| |
+%%| |
+%%|_______________|
+%%| |--|
+%%|___part ptr 1__| |
+%%| | | segarr part 1
+%%|___part ptr 2__| V______________|
+%%| | | p1 |
+%%| | |______________|--|
+%%| .... | | p2 | |
+%% (256) |______________| |
+%% | | |
+%% | .... | | segment 1
+%% | (512) | V __slot 0 ____|
+%% | size |
+%% | pointer |--|
+%% |___slot 1 ____| |
+%% | | |
+%% | .... | | objects in slot 0
+%% (256) V segment 1
+%% |___________|
+%% | size |
+%% |___________|
+%% | status |
+%% |___________|
+%% | |
+%% | object |
+%% | collec. |
+%% |___________|
+
+%%%
+%%% File header
+%%%
+
+-define(RESERVED, 128). % Reserved for future use.
+
+-define(COLL_CNTRS, (28*4)). % Counters for the buddy system.
+-define(MD5SZ, 16).
+
+-define(HEADSZ,
+ 56+?COLL_CNTRS+?MD5SZ). % The size of the file header, in bytes,
+ % not including the reserved part.
+-define(HEADEND, (?HEADSZ+?RESERVED)).
+ % End of header and reserved area.
+-define(SEGSZ, 512). % Size of a segment, in words. SZOBJP*SEGSZP.
+-define(SEGSZP, 256). % Size of a segment, in number of pointers.
+-define(SEGSZP_LOG2, 8).
+-define(SEGOBJSZ, (4 * ?SZOBJP)).
+-define(SEGPARTSZ, 512). % Size of segment array part, in words.
+-define(SEGPARTSZ_LOG2, 9).
+-define(SEGARRSZ, 256). % Maximal number of segment array parts..
+-define(SEGARRADDR(PartN), (?HEADEND + (4 * (PartN)))).
+-define(SEGPARTADDR(P,SegN), ((P) + (4 * ?REM2(SegN, ?SEGPARTSZ)))).
+-define(BASE, ?SEGARRADDR(?SEGARRSZ)).
+-define(MAXSLOTS, (?SEGARRSZ * ?SEGPARTSZ * ?SEGSZP)).
+
+-define(SLOT2SEG(S), ((S) bsr ?SEGSZP_LOG2)).
+-define(SEG2SEGARRPART(S), ((S) bsr ?SEGPARTSZ_LOG2)).
+
+-define(PHASH, 0).
+-define(PHASH2, 1).
+
+%% BIG is used for hashing. BIG must be greater than the maximum
+%% number of slots, currently 32 M (MAXSLOTS).
+-define(BIG, 16#3ffffff). % 64 M
+
+%% Hard coded positions into the file header:
+-define(FREELIST_POS, 0).
+-define(CLOSED_PROPERLY_POS, 8).
+-define(D_POS, 20).
+
+%%% Dets file versions up to 8 are handled in dets_v8. This module
+%%% handles version 9, introduced in R8.
+%%%
+%%% Version 9(a) tables have 256 reserved bytes in the file header,
+%%% all initialized to zero.
+%%% Version 9(b) tables use the first 112 of these bytes for storing
+%%% number of objects for each size of the buddy system. An empty 9(b)
+%%% table cannot be distinguished from an empty 9(a) table.
+%%% 9(c) has an MD5-sum for the file header.
+
+-define(FILE_FORMAT_VERSION, 9).
+
+-define(NOT_PROPERLY_CLOSED,0).
+-define(CLOSED_PROPERLY,1).
+
+%% Size of object pointer, in words. SEGSZ = SZOBJP * SEGSZP.
+-define(SZOBJP, 2).
+
+-define(OHDSZ, 8). % The size of the object header, in bytes.
+-define(STATUS_POS, 4). % Position of the status field.
+
+-define(OHDSZ_v8, 12). % The size of the version 8 object header.
+
+%% The size of each object is a multiple of 16.
+%% BUMP is used when repairing files.
+-define(BUMP, 16).
+
+%%% '$hash' is the value of HASH_PARMS in R8, '$hash2' is the value in R9.
+%%%
+%%% The fields of the ?HASH_PARMS records are the same, but having
+%%% different tags makes bchunk_init on R8 nodes reject data from R9
+%%% nodes, and vice versa. This is overkill, and due to an oversight.
+%%% What should have been done in R8 was to check the hash method, not
+%%% only the type of the table and the key position. R8 nodes cannot
+%%% handle the phash2 method.
+-define(HASH_PARMS, '$hash2').
+
+-define(BCHUNK_FORMAT_VERSION, 1).
+
+-record(?HASH_PARMS, {
+ file_format_version,
+ bchunk_format_version,
+ file, type, keypos, hash_method,
+ n,m,next,
+ min,max,
+ no_objects,no_keys,
+ no_colls % [{LogSz,NoColls}], NoColls >= 0
+ }).
+
+-define(ACTUAL_SEG_SIZE, (?SEGSZ*4)).
+
+-define(MAXBUD, 32).
+
+%%-define(DEBUGF(X,Y), io:format(X, Y)).
+-define(DEBUGF(X,Y), void).
+
+%% {Bump}
+constants() ->
+ {?BUMP, ?BASE}.
+
+%% -> ok | throw({NewHead,Error})
+mark_dirty(Head) ->
+ Dirty = [{?CLOSED_PROPERLY_POS, <<?NOT_PROPERLY_CLOSED:32>>}],
+ dets_utils:pwrite(Head, Dirty),
+ dets_utils:sync(Head),
+ dets_utils:position(Head, Head#head.freelists_p),
+ dets_utils:truncate(Head, cur).
+
+%% -> {ok, head()} | throw(Error) | throw(badarg)
+prep_table_copy(Fd, Tab, Fname, Type, Kp, Ram, CacheSz, Auto, Parms) ->
+ case Parms of
+ #?HASH_PARMS{file_format_version = ?FILE_FORMAT_VERSION,
+ bchunk_format_version = ?BCHUNK_FORMAT_VERSION,
+ n = N, m = M, next = Next,
+ min = Min, max = Max,
+ hash_method = HashMethodCode,
+ no_objects = NoObjects, no_keys = NoKeys,
+ no_colls = _NoColls}
+ when is_integer(N), is_integer(M), is_integer(Next),
+ is_integer(Min), is_integer(Max),
+ is_integer(NoObjects), is_integer(NoKeys),
+ NoObjects >= NoKeys ->
+ HashMethod = code_to_hash_method(HashMethodCode),
+ case hash_invars(N, M, Next, Min, Max) of
+ false ->
+ throw(badarg);
+ true ->
+ init_file(Fd, Tab, Fname, Type, Kp, Min, Max, Ram,
+ CacheSz, Auto, false, M, N, Next, HashMethod,
+ NoObjects, NoKeys)
+ end;
+ _ ->
+ throw(badarg)
+ end.
+
+%% -> {ok, head()} | throw(Error)
+%% The File header and the SegmentArray Pointers are written here.
+%% SegmentArray Parts are also written, but the segments are are not
+%% initialized on file unless DoInitSegments is 'true'. (When
+%% initializing a file by calling init_table, some time is saved by
+%% not writing the segments twice.)
+initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots0, MaxSlots0,
+ Ram, CacheSz, Auto, DoInitSegments) ->
+ MaxSlots1 = erlang:min(MaxSlots0, ?MAXSLOTS),
+ MinSlots1 = erlang:min(MinSlots0, MaxSlots1),
+ MinSlots = slots2(MinSlots1),
+ MaxSlots = slots2(MaxSlots1),
+ M = Next = MinSlots,
+ N = 0,
+ init_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots, Ram, CacheSz,
+ Auto, DoInitSegments, M, N, Next, phash2, 0, 0).
+
+init_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots, Ram, CacheSz,
+ Auto, DoInitSegments, M, N, Next, HashMethod, NoObjects, NoKeys) ->
+ Ftab = dets_utils:init_alloc(?BASE),
+
+ Head0 = #head{
+ m = M,
+ m2 = M * 2,
+ next = Next,
+ fptr = Fd,
+ no_objects = NoObjects,
+ no_keys = NoKeys,
+ maxobjsize = 0,
+ n = N,
+ type = Type,
+ update_mode = dirty,
+ freelists = Ftab,
+ no_collections = orddict:new(),
+ auto_save = Auto,
+ hash_bif = HashMethod,
+ has_md5 = true,
+ keypos = Kp,
+ min_no_slots = MinSlots,
+ max_no_slots = MaxSlots,
+
+ ram_file = Ram,
+ filename = Fname,
+ name = Tab,
+ cache = dets_utils:new_cache(CacheSz),
+ version = ?FILE_FORMAT_VERSION,
+ bump = ?BUMP,
+ base = ?BASE,
+ mod = ?MODULE
+ },
+
+ FreeListsPointer = 0,
+ NoColls = <<0:?COLL_CNTRS/unit:8>>, %% Buddy system counters.
+ FileHeader = file_header(Head0, FreeListsPointer,
+ ?NOT_PROPERLY_CLOSED, NoColls),
+ W0 = {0, [FileHeader |
+ <<0:(4*?SEGARRSZ)/unit:8>>]}, %% SegmentArray Pointers
+
+ %% Remove cached pointers to segment array parts and segments:
+ lists:foreach(fun({I1,I2}) when is_integer(I1), is_integer(I2) -> ok;
+ ({K,V}) -> put(K, V)
+ end, erase()),
+
+ %% Initialize array parts.
+ %% All parts before segments, for the sake of repair and initialization.
+ Zero = seg_zero(),
+ {Head1, Ws1} = init_parts(Head0, 0, no_parts(Next), Zero, []),
+ NoSegs = no_segs(Next),
+
+ {Head, WsI, WsP} = init_segments(Head1, 0, NoSegs, Zero, [], []),
+ Ws2 = if
+ DoInitSegments -> WsP ++ WsI;
+ true -> WsP
+ end,
+ dets_utils:pwrite(Fd, Fname, [W0 | lists:append(Ws1) ++ Ws2]),
+ true = hash_invars(Head),
+ {ok, Head}.
+
+%% Returns a power of two not less than 256.
+slots2(NoSlots) when NoSlots >= 256 ->
+ ?POW(dets_utils:log2(NoSlots)).
+
+init_parts(Head, PartNo, NoParts, Zero, Ws) when PartNo < NoParts ->
+ PartPos = ?SEGARRADDR(PartNo),
+ {NewHead, W, _Part} = alloc_part(Head, Zero, PartPos),
+ init_parts(NewHead, PartNo+1, NoParts, Zero, [W | Ws]);
+init_parts(Head, _PartNo, _NoParts, _Zero, Ws) ->
+ {Head, Ws}.
+
+%% -> {Head, SegInitList, OtherList};
+%% SegPtrList = SegInitList = pwrite_list().
+init_segments(Head, SegNo, NoSegs, SegZero, WsP, WsI) when SegNo < NoSegs ->
+ {NewHead, WI, Ws} = allocate_segment(Head, SegZero, SegNo),
+ init_segments(NewHead, SegNo+1, NoSegs, SegZero, Ws ++ WsP, [WI | WsI]);
+init_segments(Head, _SegNo, _NoSegs, _SegZero, WsP, WsI) ->
+ {Head, WsI, WsP}.
+
+%% -> {NewHead, SegInit, [SegPtr | PartStuff]}
+allocate_segment(Head, SegZero, SegNo) ->
+ PartPos = ?SEGARRADDR(SegNo div ?SEGPARTSZ),
+ case get_arrpart(PartPos) of
+ undefined ->
+ %% may throw error:
+ {Head1, [InitArrPart, ArrPartPointer], Part} =
+ alloc_part(Head, SegZero, PartPos),
+ {NewHead, InitSegment, [SegPointer]} =
+ alloc_seg(Head1, SegZero, SegNo, Part),
+ {NewHead, InitSegment, [InitArrPart, SegPointer, ArrPartPointer]};
+ Part ->
+ alloc_seg(Head, SegZero, SegNo, Part)
+ end.
+
+alloc_part(Head, PartZero, PartPos) ->
+ %% may throw error:
+ {NewHead, Part, _} = dets_utils:alloc(Head, adjsz(4 * ?SEGPARTSZ)),
+ arrpart_cache(PartPos, Part),
+ InitArrPart = {Part, PartZero}, % same size as segment
+ ArrPartPointer = {PartPos, <<Part:32>>},
+ {NewHead, [InitArrPart, ArrPartPointer], Part}.
+
+alloc_seg(Head, SegZero, SegNo, Part) ->
+ %% may throw error:
+ {NewHead, Segment, _} = dets_utils:alloc(Head, adjsz(4 * ?SEGSZ)),
+ InitSegment = {Segment, SegZero},
+ Pos = ?SEGPARTADDR(Part, SegNo),
+ segp_cache(Pos, Segment),
+ dets_utils:disk_map_segment(Segment, SegZero),
+ SegPointer = {Pos, <<Segment:32>>},
+ {NewHead, InitSegment, [SegPointer]}.
+
+%% Read free lists (using a Buddy System) from file.
+init_freelist(Head, true) ->
+ Pos = Head#head.freelists_p,
+ free_lists_from_file(Head, Pos).
+
+%% -> {ok, Fd, fileheader()} | throw(Error)
+read_file_header(Fd, FileName) ->
+ {ok, Bin} = dets_utils:pread_close(Fd, FileName, 0, ?HEADSZ),
+ <<FreeList:32, Cookie:32, CP:32, Type2:32,
+ Version:32, M:32, Next:32, Kp:32,
+ NoObjects:32, NoKeys:32, MinNoSlots:32, MaxNoSlots:32,
+ HashMethod:32, N:32, NoCollsB:?COLL_CNTRS/binary,
+ MD5:?MD5SZ/binary>> = Bin,
+ <<_:12/binary,MD5DigestedPart:(?HEADSZ-?MD5SZ-12)/binary,_/binary>> = Bin,
+ {ok, EOF} = dets_utils:position_close(Fd, FileName, eof),
+ {ok, <<FileSize:32>>} = dets_utils:pread_close(Fd, FileName, EOF-4, 4),
+ {CL, <<>>} = lists:foldl(fun(LSz, {Acc,<<NN:32,R/binary>>}) ->
+ if
+ NN =:= 0 -> {Acc, R};
+ true -> {[{LSz,NN} | Acc], R}
+ end
+ end, {[], NoCollsB}, lists:seq(4, ?MAXBUD-1)),
+ NoColls =
+ if
+ CL =:= [], NoObjects > 0 -> % Version 9(a)
+ undefined;
+ true ->
+ lists:reverse(CL)
+ end,
+
+ FH = #fileheader{freelist = FreeList,
+ cookie = Cookie,
+ closed_properly = CP,
+ type = dets_utils:code_to_type(Type2),
+ version = Version,
+ m = M,
+ next = Next,
+ keypos = Kp,
+ no_objects = NoObjects,
+ no_keys = NoKeys,
+ min_no_slots = MinNoSlots,
+ max_no_slots = MaxNoSlots,
+ no_colls = NoColls,
+ hash_method = HashMethod,
+ read_md5 = MD5,
+ has_md5 = <<0:?MD5SZ/unit:8>> =/= MD5,
+ md5 = erlang:md5(MD5DigestedPart),
+ trailer = FileSize,
+ eof = EOF,
+ n = N,
+ mod = ?MODULE},
+ {ok, Fd, FH}.
+
+%% -> {ok, head(), ExtraInfo} | {error, Reason} (Reason lacking file name)
+%% ExtraInfo = true
+check_file_header(FH, Fd) ->
+ HashBif = code_to_hash_method(FH#fileheader.hash_method),
+ Test =
+ if
+ FH#fileheader.cookie =/= ?MAGIC ->
+ {error, not_a_dets_file};
+ FH#fileheader.type =:= badtype ->
+ {error, invalid_type_code};
+ FH#fileheader.version =/= ?FILE_FORMAT_VERSION ->
+ {error, bad_version};
+ FH#fileheader.has_md5,
+ FH#fileheader.read_md5 =/= FH#fileheader.md5 ->
+ {error, not_a_dets_file}; % harsh but fair
+ FH#fileheader.trailer =/= FH#fileheader.eof ->
+ {error, not_closed};
+ HashBif =:= undefined ->
+ {error, bad_hash_bif};
+ FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY ->
+ {ok, true};
+ FH#fileheader.closed_properly =:= ?NOT_PROPERLY_CLOSED ->
+ {error, not_closed};
+ true ->
+ {error, not_a_dets_file}
+ end,
+ case Test of
+ {ok, ExtraInfo} ->
+ MaxObjSize = max_objsize(FH#fileheader.no_colls),
+ H = #head{
+ m = FH#fileheader.m,
+ m2 = FH#fileheader.m * 2,
+ next = FH#fileheader.next,
+ fptr = Fd,
+ no_objects = FH#fileheader.no_objects,
+ no_keys = FH#fileheader.no_keys,
+ maxobjsize = MaxObjSize,
+ n = FH#fileheader.n,
+ type = FH#fileheader.type,
+ update_mode = saved,
+ auto_save = infinity, % not saved on file
+ fixed = false, % not saved on file
+ freelists_p = FH#fileheader.freelist,
+ hash_bif = HashBif,
+ has_md5 = FH#fileheader.has_md5,
+ keypos = FH#fileheader.keypos,
+ min_no_slots = FH#fileheader.min_no_slots,
+ max_no_slots = FH#fileheader.max_no_slots,
+ no_collections = FH#fileheader.no_colls,
+ version = ?FILE_FORMAT_VERSION,
+ mod = ?MODULE,
+ bump = ?BUMP,
+ base = ?BASE},
+ {ok, H, ExtraInfo};
+ Error ->
+ Error
+ end.
+
+%% Inlined.
+max_objsize(NoColls = undefined) ->
+ NoColls;
+max_objsize(NoColls) ->
+ max_objsize(NoColls, 0).
+
+max_objsize([], Max) ->
+ Max;
+max_objsize([{_,0} | L], Max) ->
+ max_objsize(L, Max);
+max_objsize([{I,_} | L], _Max) ->
+ max_objsize(L, I).
+
+cache_segps(Fd, FileName, M) ->
+ NoParts = no_parts(M),
+ ArrStart = ?SEGARRADDR(0),
+ {ok, Bin} = dets_utils:pread_close(Fd, FileName, ArrStart, 4 * NoParts),
+ cache_arrparts(Bin, ?HEADEND, Fd, FileName).
+
+cache_arrparts(<<ArrPartPos:32, B/binary>>, Pos, Fd, FileName) ->
+ arrpart_cache(Pos, ArrPartPos),
+ {ok, ArrPartBin} = dets_utils:pread_close(Fd, FileName,
+ ArrPartPos,
+ ?SEGPARTSZ*4),
+ cache_segps1(Fd, ArrPartBin, ArrPartPos),
+ cache_arrparts(B, Pos+4, Fd, FileName);
+cache_arrparts(<<>>, _Pos, _Fd, _FileName) ->
+ ok.
+
+cache_segps1(_Fd, <<0:32,_/binary>>, _P) ->
+ ok;
+cache_segps1(Fd, <<S:32,B/binary>>, P) ->
+ dets_utils:disk_map_segment_p(Fd, S),
+ segp_cache(P, S),
+ cache_segps1(Fd, B, P+4);
+cache_segps1(_Fd, <<>>, _P) ->
+ ok.
+
+no_parts(NoSlots) ->
+ ((NoSlots - 1) div (?SEGSZP * ?SEGPARTSZ)) + 1.
+
+no_segs(NoSlots) ->
+ ((NoSlots - 1) div ?SEGSZP) + 1.
+
+%%%
+%%% Repair, conversion and initialization of a dets file.
+%%%
+
+%%% bulk_input/3. Initialization, the general case (any stream of objects).
+%%% output_objs/4. Initialization (general case) and repair.
+%%% bchunk_init/2. Initialization using bchunk.
+
+bulk_input(Head, InitFun, _Cntrs) ->
+ bulk_input(Head, InitFun, make_ref(), 0).
+
+bulk_input(Head, InitFun, Ref, Seq) ->
+ fun(close) ->
+ _ = (catch InitFun(close));
+ (read) ->
+ case catch {Ref, InitFun(read)} of
+ {Ref, end_of_input} ->
+ end_of_input;
+ {Ref, {L0, NewInitFun}} when is_list(L0),
+ is_function(NewInitFun) ->
+ Kp = Head#head.keypos,
+ case catch bulk_objects(L0, Head, Kp, Seq, []) of
+ {'EXIT', _Error} ->
+ _ = (catch NewInitFun(close)),
+ {error, invalid_objects_list};
+ {L, NSeq} ->
+ {L, bulk_input(Head, NewInitFun, Ref, NSeq)}
+ end;
+ {Ref, Value} ->
+ {error, {init_fun, Value}};
+ Error ->
+ throw({thrown, Error})
+ end
+ end.
+
+bulk_objects([T | Ts], Head, Kp, Seq, L) ->
+ BT = term_to_binary(T),
+ Key = element(Kp, T),
+ bulk_objects(Ts, Head, Kp, Seq+1, [make_object(Head, Key, Seq, BT) | L]);
+bulk_objects([], _Head, Kp, Seq, L) when is_integer(Kp), is_integer(Seq) ->
+ {L, Seq}.
+
+-define(FSCK_SEGMENT, 1).
+-define(FSCK_SEGMENT2, 10000).
+
+-define(VEMPTY, {}).
+-define(VSET(I, V, E), setelement(I, V, E)).
+-define(VGET(I, V), element(I, V)).
+-define(VEXT(S, V, T),
+ list_to_tuple(tuple_to_list(V) ++ lists:duplicate(S-tuple_size(V), T))).
+
+%% Number of bytes that will be handled before the cache is written to
+%% file. Used when compacting or writing chunks.
+-define(CACHE_SIZE, (60*?CHUNK_SIZE)).
+
+%% {LogSize,NoObjects} in Cntrs is replaced by
+%% {LogSize,Position,{FileName,FileDescriptor},NoCollections}.
+%% There is also an object {no, NoObjects, NoKeys}.
+-define(COUNTERS, no).
+-define(OBJ_COUNTER, 2).
+-define(KEY_COUNTER, 3).
+
+output_objs(OldV, Head, SlotNums, Cntrs) when OldV =< 9 ->
+ fun(close) ->
+ %% Make sure that the segments are initialized in case
+ %% init_table has been called.
+ Cache = ?VEMPTY,
+ Acc = [], % This is the only way Acc can be empty.
+ true = ets:insert(Cntrs, {?FSCK_SEGMENT,0,[],0}),
+ true = ets:insert(Cntrs, {?COUNTERS, 0, 0}),
+ Fun = output_objs2(foo, Acc, OldV, Head, Cache, Cntrs,
+ SlotNums, bar),
+ Fun(close);
+ ([]) ->
+ output_objs(OldV, Head, SlotNums, Cntrs);
+ (L) ->
+ %% Information about number of objects per size is not
+ %% relevant for version 9. It is the number of collections
+ %% that matters.
+ true = ets:delete_all_objects(Cntrs),
+ true = ets:insert(Cntrs, {?COUNTERS, 0, 0}),
+ Es = bin2term(L, OldV, Head#head.keypos),
+ %% The cache is a tuple indexed by the (log) size. An element
+ %% is [BinaryObject].
+ Cache = ?VEMPTY,
+ {NE, NAcc, NCache} = output_slots(Es, Head, Cache, Cntrs, 0, 0),
+ output_objs2(NE, NAcc, OldV, Head, NCache, Cntrs, SlotNums, 1)
+ end.
+
+output_objs2(E, Acc, OldV, Head, Cache, SizeT, SlotNums, 0) ->
+ NCache = write_all_sizes(Cache, SizeT, Head, more),
+ %% Number of handled file_sorter chunks before writing:
+ Max = erlang:max(1, erlang:min(tuple_size(NCache), 10)),
+ output_objs2(E, Acc, OldV, Head, NCache, SizeT, SlotNums, Max);
+output_objs2(E, Acc, OldV, Head, Cache, SizeT, SlotNums, ChunkI) ->
+ fun(close) ->
+ {_, [], Cache1} =
+ if
+ Acc =:= [] -> {foo, [], Cache};
+ true -> output_slot(Acc, Head, Cache, [], SizeT, 0, 0)
+ end,
+ _NCache = write_all_sizes(Cache1, SizeT, Head, no_more),
+ SegSz = ?ACTUAL_SEG_SIZE,
+ {_, SegEnd, _} = dets_utils:alloc(Head, adjsz(SegSz)),
+ [{?COUNTERS,NoObjects,NoKeys}] = ets:lookup(SizeT, ?COUNTERS),
+ Head1 = Head#head{no_objects = NoObjects, no_keys = NoKeys},
+ true = ets:delete(SizeT, ?COUNTERS),
+ {NewHead, NL, _MaxSz, _End} = allocate_all_objects(Head1, SizeT),
+ %% It is not known until all objects have been collected
+ %% how many object collections there are per size. Now
+ %% that is known and the absolute positions of the object
+ %% collections can be calculated.
+ segment_file(SizeT, NewHead, NL, SegEnd),
+ {MinSlots, EstNoSlots, MaxSlots} = SlotNums,
+ if
+ EstNoSlots =:= bulk_init ->
+ {ok, 0, NewHead};
+ true ->
+ EstNoSegs = no_segs(EstNoSlots),
+ MinNoSegs = no_segs(MinSlots),
+ MaxNoSegs = no_segs(MaxSlots),
+ NoSegs = no_segs(NoKeys),
+ Diff = abs(NoSegs - EstNoSegs),
+ if
+ Diff > 5, NoSegs =< MaxNoSegs, NoSegs >= MinNoSegs ->
+ {try_again, NoKeys};
+ true ->
+ {ok, 0, NewHead}
+ end
+ end;
+ (L) ->
+ Es = bin2term(L, OldV, Head#head.keypos),
+ {NE, NAcc, NCache} =
+ output_slots(E, Es, Acc, Head, Cache, SizeT, 0, 0),
+ output_objs2(NE, NAcc, OldV, Head, NCache, SizeT, SlotNums,
+ ChunkI-1)
+ end.
+
+%%% Compaction.
+
+compact_init(ReadHead, WriteHead, TableParameters) ->
+ SizeT = ets:new(dets_compact, []),
+ #head{no_keys = NoKeys, no_objects = NoObjects} = ReadHead,
+
+ NoObjsPerSize = TableParameters#?HASH_PARMS.no_colls,
+ {NewWriteHead, Bases, SegAddr, SegEnd} =
+ prepare_file_init(NoObjects, NoKeys, NoObjsPerSize, SizeT, WriteHead),
+
+ Input = compact_input(ReadHead, NewWriteHead, SizeT, tuple_size(Bases)),
+ Output = fast_output(NewWriteHead, SizeT, Bases, SegAddr, SegEnd),
+ TmpDir = filename:dirname(NewWriteHead#head.filename),
+ Reply = (catch file_sorter:sort(Input, Output,
+ [{format, binary},{tmpdir, TmpDir},
+ {header, 1}])), % compact_objs/9: 13 bytes
+ ets:delete(SizeT),
+ Reply.
+
+compact_input(Head, WHead, SizeT, NoSizes) ->
+ L = dets_utils:all_allocated_as_list(Head),
+ Cache = ?VEXT(NoSizes, ?VEMPTY, [0 | []]),
+ compact_input(Head, WHead, SizeT, Cache, L).
+
+compact_input(Head, WHead, SizeT, Cache, L) ->
+ fun(close) ->
+ ok;
+ (read) ->
+ compact_read(Head, WHead, SizeT, Cache, L, 0, [], 0)
+ end.
+
+compact_read(_Head, WHead, SizeT, Cache, [], _Min, [], _ASz) ->
+ _ = fast_write_all_sizes(Cache, SizeT, WHead),
+ end_of_input;
+compact_read(Head, WHead, SizeT, Cache, L, Min, SegBs, ASz)
+ when ASz + Min >= ?CACHE_SIZE, ASz > 0 ->
+ NCache = fast_write_all_sizes(Cache, SizeT, WHead),
+ {SegBs, compact_input(Head, WHead, SizeT, NCache, L)};
+compact_read(Head, WHead, SizeT, Cache, [[From | To] | L], Min, SegBs, ASz) ->
+ Max = erlang:max(?CHUNK_SIZE*3, Min),
+ case check_pread_arg(Max, Head) of
+ true ->
+ case dets_utils:pread_n(Head#head.fptr, From, Max) of
+ eof ->
+ %% Should never happen since compaction will not
+ %% be tried unless the file trailer is valid.
+ not_ok; % try a proper repair
+ Bin1 when byte_size(Bin1) < Min ->
+ %% The last object may not be padded.
+ Pad = Min - byte_size(Bin1),
+ NewBin = <<Bin1/binary, 0:Pad/unit:8>>,
+ compact_objs(Head, WHead, SizeT, NewBin, L,
+ From, To, SegBs, Cache, ASz);
+ NewBin ->
+ compact_objs(Head, WHead, SizeT, NewBin, L,
+ From, To, SegBs, Cache, ASz)
+ end;
+ false ->
+ not_ok % try a proper repair
+ end.
+
+compact_objs(Head, WHead, SizeT, Bin, L, From, To, SegBs, Cache, ASz)
+ when From =:= To ->
+ case L of
+ [] ->
+ {SegBs, compact_input(Head, WHead, SizeT, Cache, L)};
+ [[From1 | To1] | L1] ->
+ Skip1 = From1 - From,
+ case Bin of
+ <<_:Skip1/binary,NewBin/binary>> ->
+ compact_objs(Head, WHead, SizeT, NewBin, L1, From1, To1,
+ SegBs, Cache, ASz);
+ _ when byte_size(Bin) < Skip1 ->
+ compact_read(Head, WHead, SizeT, Cache, L, 0, SegBs, ASz)
+ end
+ end;
+compact_objs(Head, WHead, SizeT, <<Size:32, St:32, _Sz:32, KO/binary>> = Bin,
+ L, From, To, SegBs, Cache, ASz) when St =:= ?ACTIVE ->
+ LSize = sz2pos(Size),
+ Size2 = ?POW(LSize-1),
+ if
+ byte_size(Bin) >= Size2 ->
+ NASz = ASz + Size2,
+ <<SlotObjs:Size2/binary, NewBin/binary>> = Bin,
+ Term = if
+ Head#head.type =:= set ->
+ binary_to_term(KO);
+ true ->
+ <<_KSz:32,B2/binary>> = KO,
+ binary_to_term(B2)
+ end,
+ Key = element(Head#head.keypos, Term),
+ Slot = db_hash(Key, Head),
+ From1 = From + Size2,
+ [Addr | AL] = ?VGET(LSize, Cache),
+ NCache = ?VSET(LSize, Cache, [Addr + Size2 | [SlotObjs | AL]]),
+ NSegBs = [<<Slot:32,Size:32,Addr:32,LSize:8>> | SegBs],
+ compact_objs(Head, WHead, SizeT, NewBin, L, From1,
+ To, NSegBs, NCache, NASz);
+ true ->
+ compact_read(Head, WHead, SizeT, Cache, [[From|To] | L],
+ Size2, SegBs, ASz)
+ end;
+compact_objs(Head, WHead, SizeT, <<_:32, _St:32, _:32, _/binary>> = Bin,
+ L, From, To, SegBs, Cache, ASz)
+ when byte_size(Bin) >= ?ACTUAL_SEG_SIZE -> % , _St =/= ?ACTIVE
+ <<_:?ACTUAL_SEG_SIZE/binary, NewBin/binary>> = Bin,
+ compact_objs(Head, WHead, SizeT, NewBin, L, From + ?ACTUAL_SEG_SIZE,
+ To, SegBs, Cache, ASz);
+compact_objs(Head, WHead, SizeT, <<_:32, _St:32, _:32, _/binary>> = Bin,
+ L, From, To, SegBs, Cache, ASz)
+ when byte_size(Bin) < ?ACTUAL_SEG_SIZE -> % , _St =/= ?ACTIVE
+ compact_read(Head, WHead, SizeT, Cache, [[From|To] | L],
+ ?ACTUAL_SEG_SIZE, SegBs, ASz);
+compact_objs(Head, WHead, SizeT, _Bin, L, From, To, SegBs, Cache, ASz) ->
+ compact_read(Head, WHead, SizeT, Cache, [[From|To] | L], 0, SegBs, ASz).
+
+%%% End compaction.
+
+%%% Bchunk.
+
+read_bchunks(Head, L) ->
+ read_bchunks(Head, L, 0, [], 0).
+
+read_bchunks(_Head, L, Min, Bs, ASz) when ASz + Min >= 4*?CHUNK_SIZE,
+ Bs =/= [] ->
+ {lists:reverse(Bs), L};
+read_bchunks(Head, {From, To, L}, Min, Bs, ASz) ->
+ Max = erlang:max(?CHUNK_SIZE*2, Min),
+ case check_pread_arg(Max, Head) of
+ true ->
+ case dets_utils:pread_n(Head#head.fptr, From, Max) of
+ eof ->
+ %% Should never happen.
+ {error, premature_eof};
+ NewBin when byte_size(NewBin) >= Min ->
+ bchunks(Head, L, NewBin, Bs, ASz, From, To);
+ Bin1 when To - From =:= Min, L =:= <<>> ->
+ %% when byte_size(Bin1) < Min.
+ %% The last object may not be padded.
+ Pad = Min - byte_size(Bin1),
+ NewBin = <<Bin1/binary, 0:Pad/unit:8>>,
+ bchunks(Head, L, NewBin, Bs, ASz, From, To);
+ _ ->
+ {error, premature_eof}
+ end;
+ false ->
+ {error, dets_utils:bad_object(bad_object, {read_bchunks, Max})}
+ end.
+
+bchunks(Head, L, Bin, Bs, ASz, From, To) when From =:= To ->
+ if
+ L =:= <<>> ->
+ {finished, lists:reverse(Bs)};
+ true ->
+ <<From1:32, To1:32, L1/binary>> = L,
+ Skip1 = From1 - From,
+ case Bin of
+ <<_:Skip1/binary,NewBin/binary>> ->
+ bchunks(Head, L1, NewBin, Bs, ASz, From1, To1);
+ _ when byte_size(Bin) < Skip1 ->
+ read_bchunks(Head, {From1,To1,L1}, 0, Bs, ASz)
+ end
+ end;
+bchunks(Head, L, <<Size:32, St:32, _Sz:32, KO/binary>> = Bin, Bs, ASz,
+ From, To) when St =:= ?ACTIVE; St =:= ?FREE ->
+ LSize = sz2pos(Size),
+ Size2 = ?POW(LSize-1),
+ if
+ byte_size(Bin) >= Size2 ->
+ <<B0:Size2/binary, NewBin/binary>> = Bin,
+ %% LSize and Slot are used in make_slots/6. The reason to
+ %% calculate Slot here is to reduce the CPU load in
+ %% make_slots/6.
+ Term = if
+ Head#head.type =:= set ->
+ binary_to_term(KO);
+ true ->
+ <<_KSz:32,B2/binary>> = KO,
+ binary_to_term(B2)
+ end,
+ Key = element(Head#head.keypos, Term),
+ Slot = db_hash(Key, Head),
+ B = {LSize,Slot,B0},
+ bchunks(Head, L, NewBin, [B | Bs], ASz + Size2, From+Size2, To);
+ true ->
+ read_bchunks(Head, {From, To, L}, Size2, Bs, ASz)
+ end;
+bchunks(Head, L, <<_:32, _St:32, _:32, _/binary>> = Bin, Bs, ASz, From, To)
+ when byte_size(Bin) >= ?ACTUAL_SEG_SIZE ->
+ <<_:?ACTUAL_SEG_SIZE/binary, NewBin/binary>> = Bin,
+ bchunks(Head, L, NewBin, Bs, ASz, From + ?ACTUAL_SEG_SIZE, To);
+bchunks(Head, L, <<_:32, _St:32, _:32, _/binary>> = Bin, Bs, ASz, From, To)
+ when byte_size(Bin) < ?ACTUAL_SEG_SIZE ->
+ read_bchunks(Head, {From, To, L}, ?ACTUAL_SEG_SIZE, Bs, ASz);
+bchunks(Head, L, _Bin, Bs, ASz, From, To) ->
+ read_bchunks(Head, {From, To, L}, 0, Bs, ASz).
+
+%%% End bchunk.
+
+%% -> {ok, NewHead} | throw(Error) | Error
+bchunk_init(Head, InitFun) ->
+ Ref = make_ref(),
+ %% The non-empty list of data begins with the table parameters.
+ case catch {Ref, InitFun(read)} of
+ {Ref, end_of_input} ->
+ {error, {init_fun, end_of_input}};
+ {Ref, {[], NInitFun}} when is_function(NInitFun) ->
+ bchunk_init(Head, NInitFun);
+ {Ref, {[ParmsBin | L], NInitFun}}
+ when is_list(L), is_function(NInitFun) ->
+ #head{fptr = Fd, type = Type, keypos = Kp,
+ auto_save = Auto, cache = Cache,
+ filename = Fname, ram_file = Ram,
+ name = Tab} = Head,
+ case try_bchunk_header(ParmsBin, Head) of
+ {ok, Parms} ->
+ #?HASH_PARMS{no_objects = NoObjects,
+ no_keys = NoKeys,
+ no_colls = NoObjsPerSize} = Parms,
+ CacheSz = dets_utils:cache_size(Cache),
+ {ok, Head1} =
+ prep_table_copy(Fd, Tab, Fname, Type,
+ Kp, Ram, CacheSz,
+ Auto, Parms),
+ SizeT = ets:new(dets_init, []),
+ {NewHead, Bases, SegAddr, SegEnd} =
+ prepare_file_init(NoObjects, NoKeys,
+ NoObjsPerSize, SizeT, Head1),
+ ECache = ?VEXT(tuple_size(Bases), ?VEMPTY, [0 | []]),
+ Input =
+ fun(close) ->
+ _ = (catch NInitFun(close));
+ (read) ->
+ do_make_slots(L, ECache, SizeT, NewHead, Ref,
+ 0, NInitFun)
+ end,
+ Output = fast_output(NewHead, SizeT, Bases, SegAddr,SegEnd),
+ TmpDir = filename:dirname(Head#head.filename),
+ Reply = (catch file_sorter:sort(Input, Output,
+ [{format, binary},
+ {tmpdir, TmpDir},
+ {header, 1}])),
+ ets:delete(SizeT),
+ Reply;
+ not_ok ->
+ {error, {init_fun, ParmsBin}}
+ end;
+ {Ref, Value} ->
+ {error, {init_fun, Value}};
+ Error ->
+ {thrown, Error}
+ end.
+
+try_bchunk_header(ParmsBin, Head) ->
+ #head{type = Type, keypos = Kp, hash_bif = HashBif} = Head,
+ HashMethod = hash_method_to_code(HashBif),
+ case catch binary_to_term(ParmsBin) of
+ Parms when is_record(Parms, ?HASH_PARMS),
+ Parms#?HASH_PARMS.type =:= Type,
+ Parms#?HASH_PARMS.keypos =:= Kp,
+ Parms#?HASH_PARMS.hash_method =:= HashMethod,
+ Parms#?HASH_PARMS.bchunk_format_version =:=
+ ?BCHUNK_FORMAT_VERSION ->
+ {ok, Parms};
+ _ ->
+ not_ok
+ end.
+
+bchunk_input(InitFun, SizeT, Head, Ref, Cache, ASz) ->
+ fun(close) ->
+ _ = (catch InitFun(close));
+ (read) ->
+ case catch {Ref, InitFun(read)} of
+ {Ref, end_of_input} ->
+ _ = fast_write_all_sizes(Cache, SizeT, Head),
+ end_of_input;
+ {Ref, {L, NInitFun}} when is_list(L), is_function(NInitFun) ->
+ do_make_slots(L, Cache, SizeT, Head, Ref, ASz,
+ NInitFun);
+ {Ref, Value} ->
+ {error, {init_fun, Value}};
+ Error ->
+ throw({thrown, Error})
+ end
+ end.
+
+do_make_slots(L, Cache, SizeT, Head, Ref, ASz, InitFun) ->
+ case catch make_slots(L, Cache, [], ASz) of
+ {'EXIT', _} ->
+ _ = (catch InitFun(close)),
+ {error, invalid_objects_list};
+ {Cache1, SegBs, NASz} when NASz > ?CACHE_SIZE ->
+ NCache = fast_write_all_sizes(Cache1, SizeT, Head),
+ F = bchunk_input(InitFun, SizeT, Head, Ref, NCache, 0),
+ {SegBs, F};
+ {NCache, SegBs, NASz} ->
+ F = bchunk_input(InitFun, SizeT, Head, Ref, NCache, NASz),
+ {SegBs, F}
+ end.
+
+make_slots([{LSize,Slot,<<Size:32, St:32, Sz:32, KO/binary>> = Bin0} | Bins],
+ Cache, SegBs, ASz) ->
+ Bin = if
+ St =:= ?ACTIVE ->
+ Bin0;
+ St =:= ?FREE ->
+ <<Size:32,?ACTIVE:32,Sz:32,KO/binary>>
+ end,
+ BSz = byte_size(Bin0),
+ true = (BSz =:= ?POW(LSize-1)),
+ NASz = ASz + BSz,
+ [Addr | L] = ?VGET(LSize, Cache),
+ NSegBs = [<<Slot:32,Size:32,Addr:32,LSize:8>> | SegBs],
+ NCache = ?VSET(LSize, Cache, [Addr + BSz | [Bin | L]]),
+ make_slots(Bins, NCache, NSegBs, NASz);
+make_slots([], Cache, SegBs, ASz) ->
+ {Cache, SegBs, ASz}.
+
+fast_output(Head, SizeT, Bases, SegAddr, SegEnd) ->
+ fun(close) ->
+ fast_output_end(Head, SizeT);
+ (L) ->
+ case file:position(Head#head.fptr, SegAddr) of
+ {ok, SegAddr} ->
+ NewSegAddr = write_segment_file(L, Bases, Head, [],
+ SegAddr, SegAddr),
+ fast_output2(Head, SizeT, Bases, NewSegAddr,
+ SegAddr, SegEnd);
+ Error ->
+ catch dets_utils:file_error(Error, Head#head.filename)
+ end
+ end.
+
+fast_output2(Head, SizeT, Bases, SegAddr, SS, SegEnd) ->
+ fun(close) ->
+ FinalZ = SegEnd - SegAddr,
+ dets_utils:write(Head, dets_utils:make_zeros(FinalZ)),
+ fast_output_end(Head, SizeT);
+ (L) ->
+ NewSegAddr = write_segment_file(L, Bases, Head, [], SegAddr, SS),
+ fast_output2(Head, SizeT, Bases, NewSegAddr, SS, SegEnd)
+ end.
+
+fast_output_end(Head, SizeT) ->
+ case ets:foldl(fun({_Sz,_Pos,Cnt,NoC}, Acc) -> (Cnt =:= NoC) and Acc end,
+ true, SizeT) of
+ true -> {ok, Head};
+ false -> {error, invalid_objects_list}
+ end.
+
+%% Inlined.
+write_segment_file([<<Slot:32,BSize:32,AddrToBe:32,LSize:8>> | Bins],
+ Bases, Head, Ws, SegAddr, SS) ->
+ %% Should call slot_position/1, but since all segments are
+ %% allocated in a sequence, the position of a slot can be
+ %% calculated faster.
+ Pos = SS + ?SZOBJP*4 * Slot, % Same as Pos = slot_position(Slot).
+ write_segment_file(Bins, Bases, Head, Ws, SegAddr, SS, Pos,
+ BSize, AddrToBe, LSize);
+write_segment_file([], _Bases, Head, Ws, SegAddr, _SS) ->
+ dets_utils:write(Head, Ws),
+ SegAddr.
+
+write_segment_file(Bins, Bases, Head, Ws, SegAddr, SS, Pos, BSize,
+ AddrToBe, LSize) when Pos =:= SegAddr ->
+ Addr = AddrToBe + element(LSize, Bases),
+ NWs = [Ws | <<BSize:32,Addr:32>>],
+ write_segment_file(Bins, Bases, Head, NWs, SegAddr + ?SZOBJP*4, SS);
+write_segment_file(Bins, Bases, Head, Ws, SegAddr, SS, Pos, BSize,
+ AddrToBe, LSize) when Pos - SegAddr < 100 ->
+ Addr = AddrToBe + element(LSize, Bases),
+ NoZeros = Pos - SegAddr,
+ NWs = [Ws | <<0:NoZeros/unit:8,BSize:32,Addr:32>>],
+ NSegAddr = SegAddr + NoZeros + ?SZOBJP*4,
+ write_segment_file(Bins, Bases, Head, NWs, NSegAddr, SS);
+write_segment_file(Bins, Bases, Head, Ws, SegAddr, SS, Pos, BSize,
+ AddrToBe, LSize) ->
+ Addr = AddrToBe + element(LSize, Bases),
+ NoZeros = Pos - SegAddr,
+ NWs = [Ws, dets_utils:make_zeros(NoZeros) | <<BSize:32,Addr:32>>],
+ NSegAddr = SegAddr + NoZeros + ?SZOBJP*4,
+ write_segment_file(Bins, Bases, Head, NWs, NSegAddr, SS).
+
+fast_write_all_sizes(Cache, SizeT, Head) ->
+ CacheL = lists:reverse(tuple_to_list(Cache)),
+ fast_write_sizes(CacheL, tuple_size(Cache), SizeT, Head, [], []).
+
+fast_write_sizes([], _Sz, _SizeT, Head, NCL, PwriteList) ->
+ #head{filename = FileName, fptr = Fd} = Head,
+ ok = dets_utils:pwrite(Fd, FileName, PwriteList),
+ list_to_tuple(NCL);
+fast_write_sizes([[_Addr] = C | CL], Sz, SizeT, Head, NCL, PwriteList) ->
+ fast_write_sizes(CL, Sz-1, SizeT, Head, [C | NCL], PwriteList);
+fast_write_sizes([[Addr | C] | CL], Sz, SizeT, Head, NCL, PwriteList) ->
+ case ets:lookup(SizeT, Sz) of
+ [] ->
+ throw({error, invalid_objects_list});
+ [{Sz,Position,_ObjCounter,_NoCollections}] ->
+ %% Update ObjCounter:
+ NoColls = length(C),
+ _ = ets:update_counter(SizeT, Sz, {3, NoColls}),
+ Pos = Position + Addr - NoColls*?POW(Sz-1),
+ fast_write_sizes(CL, Sz-1, SizeT, Head, [[Addr] | NCL],
+ [{Pos,lists:reverse(C)} | PwriteList])
+ end.
+
+prepare_file_init(NoObjects, NoKeys, NoObjsPerSize, SizeT, Head) ->
+ SegSz = ?ACTUAL_SEG_SIZE,
+ {_, SegEnd, _} = dets_utils:alloc(Head, adjsz(SegSz)),
+ Head1 = Head#head{no_objects = NoObjects, no_keys = NoKeys},
+ true = ets:insert(SizeT, {?FSCK_SEGMENT,0,[],0}),
+ lists:foreach(fun({LogSz,NoColls}) ->
+ true = ets:insert(SizeT, {LogSz+1,0,0,NoColls})
+ end, NoObjsPerSize),
+ {NewHead, NL0, MaxSz, EndOfFile} = allocate_all_objects(Head1, SizeT),
+ [{?FSCK_SEGMENT,SegAddr,[],0} | NL] = NL0,
+ true = ets:delete_all_objects(SizeT),
+ lists:foreach(fun(X) -> true = ets:insert(SizeT, X) end, NL),
+ Bases = lists:foldl(fun({LSz,P,_D,_N}, A) -> setelement(LSz,A,P) end,
+ erlang:make_tuple(MaxSz, 0), NL),
+ Est = lists:foldl(fun({LSz,_,_,N}, A) -> A + ?POW(LSz-1)*N end, 0, NL),
+ ok = write_bytes(NewHead, EndOfFile, Est),
+ {NewHead, Bases, SegAddr, SegEnd}.
+
+%% Writes "zeros" to the file. This ensures that the file blocks are
+%% allocated more or less contiguously, which reduces the seek times
+%% to a minimum when the file is later read serially from beginning to
+%% end (as is done when calling select and the like). A well-formed
+%% file will be created also if nothing is written (as is the case for
+%% small files, for efficiency).
+write_bytes(_Head, _EndOfFile, Est) when Est < ?CACHE_SIZE ->
+ ok;
+write_bytes(Head, EndOfFile, _Est) ->
+ Fd = Head#head.fptr,
+ {ok, Start} = file:position(Fd, eof),
+ BytesToWrite = EndOfFile - Start,
+ SizeInKB = 64,
+ Bin = list_to_binary(lists:duplicate(SizeInKB * 4, lists:seq(0, 255))),
+ write_loop(Head, BytesToWrite, Bin).
+
+write_loop(Head, BytesToWrite, Bin) when BytesToWrite >= byte_size(Bin) ->
+ case file:write(Head#head.fptr, Bin) of
+ ok -> write_loop(Head, BytesToWrite - byte_size(Bin), Bin);
+ Error -> dets_utils:file_error(Error, Head#head.filename)
+ end;
+write_loop(_Head, 0, _Bin) ->
+ ok;
+write_loop(Head, BytesToWrite, Bin) ->
+ <<SmallBin:BytesToWrite/binary,_/binary>> = Bin,
+ write_loop(Head, BytesToWrite, SmallBin).
+
+%% By allocating bigger objects before smaller ones, holes in the
+%% buddy system memory map are avoided. Unfortunately, the segments
+%% are always allocated first, so if there are objects bigger than a
+%% segment, there is a hole to handle. (Haven't considered placing the
+%% segments among other objects of the same size.)
+allocate_all_objects(Head, SizeT) ->
+ DTL = lists:reverse(lists:keysort(1, ets:tab2list(SizeT))),
+ MaxSz = element(1, hd(DTL)),
+ SegSize = ?ACTUAL_SEG_SIZE,
+ {Head1, HSz, HN, HA} = alloc_hole(MaxSz, Head, SegSize),
+ {Head2, NL} = allocate_all(Head1, DTL, []),
+ %% Find the position that will be the end of the file by allocating
+ %% a minimal object.
+ {_Head, EndOfFile, _} = dets_utils:alloc(Head2, ?BUMP),
+ Head3 = free_hole(Head2, HSz, HN, HA),
+ NewHead = Head3#head{maxobjsize = max_objsize(Head3#head.no_collections)},
+ {NewHead, NL, MaxSz, EndOfFile}.
+
+alloc_hole(LSize, Head, SegSz) when ?POW(LSize-1) > SegSz ->
+ Size = ?POW(LSize-1),
+ {_, SegAddr, _} = dets_utils:alloc(Head, adjsz(SegSz)),
+ {_, Addr, _} = dets_utils:alloc(Head, adjsz(Size)),
+ N = (Addr - SegAddr) div SegSz,
+ Head1 = dets_utils:alloc_many(Head, SegSz, N, SegAddr),
+ {Head1, SegSz, N, SegAddr};
+alloc_hole(_MaxSz, Head, _SegSz) ->
+ {Head, 0, 0, 0}.
+
+free_hole(Head, _Size, 0, _Addr) ->
+ Head;
+free_hole(Head, Size, N, Addr) ->
+ {Head1, _} = dets_utils:free(Head, Addr, adjsz(Size)),
+ free_hole(Head1, Size, N-1, Addr+Size).
+
+%% One (temporary) file for each buddy size, write all objects of that
+%% size to the file.
+allocate_all(Head, [{?FSCK_SEGMENT,_,Data,_}], L) ->
+ %% And one file for the segments...
+ %% Note that space for the array parts and the segments has
+ %% already been allocated, but the segments have not been
+ %% initialized on disk.
+ NoParts = no_parts(Head#head.next),
+ %% All parts first, ensured by init_segments/6.
+ Addr = ?BASE + NoParts * 4 * ?SEGPARTSZ,
+ {Head, [{?FSCK_SEGMENT,Addr,Data,0} | L]};
+allocate_all(Head, [{LSize,_,Data,NoCollections} | DTL], L) ->
+ Size = ?POW(LSize-1),
+ {_Head, Addr, _} = dets_utils:alloc(Head, adjsz(Size)),
+ Head1 = dets_utils:alloc_many(Head, Size, NoCollections, Addr),
+ NoColls = Head1#head.no_collections,
+ NewNoColls = orddict:update_counter(LSize-1, NoCollections, NoColls),
+ NewHead = Head1#head{no_collections = NewNoColls},
+ E = {LSize,Addr,Data,NoCollections},
+ allocate_all(NewHead, DTL, [E | L]).
+
+bin2term(Bin, 9, Kp) ->
+ bin2term1(Bin, Kp, []);
+bin2term(Bin, 8, Kp) ->
+ bin2term_v8(Bin, Kp, []).
+
+bin2term1([<<Slot:32, Seq:32, BinTerm/binary>> | BTs], Kp, L) ->
+ Term = binary_to_term(BinTerm),
+ Key = element(Kp, Term),
+ bin2term1(BTs, Kp, [{Slot, Key, Seq, Term, BinTerm} | L]);
+bin2term1([], _Kp, L) ->
+ lists:reverse(L).
+
+bin2term_v8([<<Slot:32, BinTerm/binary>> | BTs], Kp, L) ->
+ Term = binary_to_term(BinTerm),
+ Key = element(Kp, Term),
+ bin2term_v8(BTs, Kp, [{Slot, Key, foo, Term, BinTerm} | L]);
+bin2term_v8([], _Kp, L) ->
+ lists:reverse(L).
+
+write_all_sizes({}=Cache, _SizeT, _Head, _More) ->
+ Cache;
+write_all_sizes(Cache, SizeT, Head, More) ->
+ CacheL = lists:reverse(tuple_to_list(Cache)),
+ Sz = length(CacheL),
+ NCL = case ets:info(SizeT, size) of
+ 1 when More =:= no_more -> % COUNTERS only...
+ all_sizes(CacheL, Sz, SizeT);
+ _ ->
+ write_sizes(CacheL, Sz, SizeT, Head)
+ end,
+ list_to_tuple(NCL).
+
+all_sizes([]=CL, _Sz, _SizeT) ->
+ CL;
+all_sizes([[]=C | CL], Sz, SizeT) ->
+ [C | all_sizes(CL, Sz-1, SizeT)];
+all_sizes([C0 | CL], Sz, SizeT) ->
+ C = lists:reverse(C0),
+ NoCollections = length(C),
+ true = ets:insert(SizeT, {Sz,0,C,NoCollections}),
+ [[] | all_sizes(CL, Sz-1, SizeT)].
+
+write_sizes([]=CL, _Sz, _SizeT, _Head) ->
+ CL;
+write_sizes([[]=C | CL], Sz, SizeT, Head) ->
+ [C | write_sizes(CL, Sz-1, SizeT, Head)];
+write_sizes([C | CL], Sz, SizeT, Head) ->
+ {FileName, Fd} =
+ case ets:lookup(SizeT, Sz) of
+ [] ->
+ temp_file(Head, SizeT, Sz);
+ [{_,_,{FN,F},_}] ->
+ {FN, F}
+ end,
+ NoCollections = length(C),
+ _ = ets:update_counter(SizeT, Sz, {4,NoCollections}),
+ case file:write(Fd, lists:reverse(C)) of
+ ok ->
+ [[] | write_sizes(CL, Sz-1, SizeT, Head)];
+ Error ->
+ dets_utils:file_error(FileName, Error)
+ end.
+
+output_slots([E | Es], Head, Cache, SizeT, NoKeys, NoObjs) ->
+ output_slots(E, Es, [E], Head, Cache, SizeT, NoKeys, NoObjs);
+output_slots([], _Head, Cache, SizeT, NoKeys, NoObjs) ->
+ _ = ets:update_counter(SizeT, ?COUNTERS, {?OBJ_COUNTER,NoObjs}),
+ _ = ets:update_counter(SizeT, ?COUNTERS, {?KEY_COUNTER,NoKeys}),
+ {not_a_tuple, [], Cache}.
+
+output_slots(E, [E1 | Es], Acc, Head, Cache, SizeT, NoKeys, NoObjs)
+ when element(1, E) =:= element(1, E1) ->
+ output_slots(E1, Es, [E1 | Acc], Head, Cache, SizeT, NoKeys, NoObjs);
+output_slots(E, [], Acc, _Head, Cache, SizeT, NoKeys, NoObjs) ->
+ _ = ets:update_counter(SizeT, ?COUNTERS, {?OBJ_COUNTER,NoObjs}),
+ _ = ets:update_counter(SizeT, ?COUNTERS, {?KEY_COUNTER,NoKeys}),
+ {E, Acc, Cache};
+output_slots(_E, L, Acc, Head, Cache, SizeT, NoKeys, NoObjs) ->
+ output_slot(Acc, Head, Cache, L, SizeT, NoKeys, NoObjs).
+
+output_slot(Es, Head, Cache, L, SizeT, NoKeys, NoObjs) ->
+ Slot = element(1, hd(Es)),
+ %% Plain lists:sort/1 will do.
+ {Bins, Size, No, KNo} = prep_slot(lists:sort(Es), Head),
+ NNoKeys = NoKeys + KNo,
+ NNoObjs = NoObjs + No,
+
+ %% First the object collection.
+ BSize = Size + ?OHDSZ,
+ LSize = sz2pos(BSize),
+ Size2 = ?POW(LSize-1),
+ Pad = <<0:(Size2-BSize)/unit:8>>,
+ BinObject = [<<BSize:32, ?ACTIVE:32>>, Bins | Pad],
+ Cache1 =
+ if
+ LSize > tuple_size(Cache) ->
+ C1 = ?VEXT(LSize, Cache, []),
+ ?VSET(LSize, C1, [BinObject]);
+ true ->
+ CL = ?VGET(LSize, Cache),
+ ?VSET(LSize, Cache, [BinObject | CL])
+ end,
+
+ %% Then the pointer to the object collection.
+ %% Cannot yet determine the absolute pointers; segment_file/4 does that.
+ PBin = <<Slot:32,BSize:32,LSize:8>>,
+ PL = ?VGET(?FSCK_SEGMENT, Cache1),
+ NCache = ?VSET(?FSCK_SEGMENT, Cache1, [PBin | PL]),
+ output_slots(L, Head, NCache, SizeT, NNoKeys, NNoObjs).
+
+prep_slot(L, Head) when Head#head.type =/= set ->
+ prep_slot(L, Head, []);
+prep_slot([{_Slot,Key,_Seq,_T,BT} | L], _Head) ->
+ prep_set_slot(L, Key, BT, 0, 0, 0, []).
+
+prep_slot([{_Slot, Key, Seq, T, _BT} | L], Head, W) ->
+ prep_slot(L, Head, [{Key, {Seq, {insert,T}}} | W]);
+prep_slot([], Head, W) ->
+ WLs = dets_utils:family(W),
+ {[], Bins, Size, No, KNo, _} =
+ eval_slot(WLs, [], Head#head.type, [], [], 0, 0, 0, false),
+ {Bins, Size, No, KNo}.
+
+%% Optimization, prep_slot/3 would work for set tables as well.
+prep_set_slot([{_,K,_Seq,_T1,BT1} | L], K, _BT, Sz, NoKeys, NoObjs, Ws) ->
+ prep_set_slot(L, K, BT1, Sz, NoKeys, NoObjs, Ws);
+prep_set_slot([{_,K1,_Seq,_T1,BT1} | L], _K, BT, Sz, NoKeys, NoObjs, Ws) ->
+ BSize = byte_size(BT) + 4,
+ NWs = [Ws,<<BSize:32>>|BT],
+ prep_set_slot(L, K1, BT1, Sz+BSize, NoKeys+1, NoObjs+1, NWs);
+prep_set_slot([], _K, BT, Sz, NoKeys, NoObjs, Ws) ->
+ BSize = byte_size(BT) + 4,
+ {[Ws, <<BSize:32>> | BT], Sz + BSize, NoKeys+1, NoObjs+1}.
+
+segment_file(SizeT, Head, FileData, SegEnd) ->
+ I = 2,
+ true = ets:delete_all_objects(SizeT),
+ lists:foreach(fun(X) -> true = ets:insert(SizeT, X) end, FileData),
+ [{?FSCK_SEGMENT,SegAddr,Data,0} | FileData1] = FileData,
+ NewData =
+ case Data of
+ {InFile,In0} ->
+ {OutFile, Out} = temp_file(Head, SizeT, I),
+ file:close(In0),
+ {ok, In} = dets_utils:open(InFile, [raw,binary,read]),
+ {ok, 0} = dets_utils:position(In, InFile, bof),
+ seg_file(SegAddr, SegAddr, In, InFile, Out, OutFile, SizeT,
+ SegEnd),
+ file:close(In),
+ file:delete(InFile),
+ {OutFile,Out};
+ Objects ->
+ {LastAddr, B} = seg_file(Objects, SegAddr, SegAddr, SizeT, []),
+ dets_utils:disk_map_segment(SegAddr, B),
+ FinalZ = SegEnd - LastAddr,
+ [B | dets_utils:make_zeros(FinalZ)]
+ end,
+ %% Restore the positions.
+ true = ets:delete_all_objects(SizeT),
+ %% To get the segments copied first by dets:fsck_copy/4, use a big
+ %% number here, FSCK_SEGMENT2.
+ lists:foreach(fun(X) -> true = ets:insert(SizeT, X) end,
+ [{?FSCK_SEGMENT2,SegAddr,NewData,0} | FileData1]),
+ ok.
+
+seg_file(Addr, SS, In, InFile, Out, OutFile, SizeT, SegEnd) ->
+ case dets_utils:read_n(In, 4500) of
+ eof ->
+ FinalZ = SegEnd - Addr,
+ dets_utils:fwrite(Out, OutFile, dets_utils:make_zeros(FinalZ));
+ Bin ->
+ {NewAddr, L} = seg_file(Bin, Addr, SS, SizeT, []),
+ dets_utils:disk_map_segment(Addr, L),
+ ok = dets_utils:fwrite(Out, OutFile, L),
+ seg_file(NewAddr, SS, In, InFile, Out, OutFile, SizeT, SegEnd)
+ end.
+
+seg_file(<<Slot:32,BSize:32,LSize:8,T/binary>>, Addr, SS, SizeT, L) ->
+ seg_file_item(T, Addr, SS, SizeT, L, Slot, BSize, LSize);
+seg_file([<<Slot:32,BSize:32,LSize:8>> | T], Addr, SS, SizeT, L) ->
+ seg_file_item(T, Addr, SS, SizeT, L, Slot, BSize, LSize);
+seg_file([], Addr, _SS, _SizeT, L) ->
+ {Addr, lists:reverse(L)};
+seg_file(<<>>, Addr, _SS, _SizeT, L) ->
+ {Addr, lists:reverse(L)}.
+
+seg_file_item(T, Addr, SS, SizeT, L, Slot, BSize, LSize) ->
+ %% Should call slot_position/1, but since all segments are
+ %% allocated in a sequence, the position of a slot can be
+ %% calculated faster.
+ SlotPos = SS + ?SZOBJP*4 * Slot, % SlotPos = slot_position(Slot)
+ NoZeros = SlotPos - Addr,
+ PSize = NoZeros+?SZOBJP*4,
+ Inc = ?POW(LSize-1),
+ CollP = ets:update_counter(SizeT, LSize, Inc) - Inc,
+ PointerBin = if
+ NoZeros =:= 0 ->
+ <<BSize:32, CollP:32>>;
+ NoZeros > 100 ->
+ [dets_utils:make_zeros(NoZeros) |
+ <<BSize:32, CollP:32>>];
+ true ->
+ <<0:NoZeros/unit:8, BSize:32, CollP:32>>
+ end,
+ seg_file(T, Addr + PSize, SS, SizeT, [PointerBin | L]).
+
+temp_file(Head, SizeT, N) ->
+ TmpName = lists:concat([Head#head.filename, '.', N]),
+ {ok, Fd} = dets_utils:open(TmpName, [raw, binary, write]),
+ %% The file table is consulted when cleaning up.
+ true = ets:insert(SizeT, {N,0,{TmpName,Fd},0}),
+ {TmpName, Fd}.
+
+%% Does not close Fd.
+fsck_input(Head, Fd, Cntrs, FileHeader) ->
+ MaxSz0 = case FileHeader#fileheader.has_md5 of
+ true when is_integer(FileHeader#fileheader.no_colls) ->
+ ?POW(max_objsize(FileHeader#fileheader.no_colls));
+ _ ->
+ %% The file is not compressed, so the bucket size
+ %% cannot exceed the filesize, for all buckets.
+ case file:position(Fd, eof) of
+ {ok, Pos} ->
+ Pos;
+ _ ->
+ 1 bsl 32
+ end
+ end,
+ MaxSz = erlang:max(MaxSz0, ?CHUNK_SIZE),
+ State0 = fsck_read(?BASE, Fd, [], 0),
+ fsck_input(Head, State0, Fd, MaxSz, Cntrs).
+
+fsck_input(Head, State, Fd, MaxSz, Cntrs) ->
+ fun(close) ->
+ ok;
+ (read) ->
+ case State of
+ done ->
+ end_of_input;
+ {done, L, _Seq} ->
+ R = count_input(Head, Cntrs, L),
+ {R, fsck_input(Head, done, Fd, MaxSz, Cntrs)};
+ {cont, L, Bin, Pos, Seq} ->
+ R = count_input(Head, Cntrs, L),
+ FR = fsck_objs(Bin, Head#head.keypos, Head, [], Seq),
+ NewState = fsck_read(FR, Pos, Fd, MaxSz, Head),
+ {R, fsck_input(Head, NewState, Fd, MaxSz, Cntrs)}
+ end
+ end.
+
+%% The ets table Cntrs is used for counting objects per size.
+count_input(Head, Cntrs, L) when Head#head.version =:= 8 ->
+ count_input1(Cntrs, L, []);
+count_input(_Head, _Cntrs, L) ->
+ lists:reverse(L).
+
+count_input1(Cntrs, [[LogSz | B] | Ts], L) ->
+ case catch ets:update_counter(Cntrs, LogSz, 1) of
+ N when is_integer(N) -> ok;
+ _Badarg -> true = ets:insert(Cntrs, {LogSz, 1})
+ end,
+ count_input1(Cntrs, Ts, [B | L]);
+count_input1(_Cntrs, [], L) ->
+ L.
+
+fsck_read(Pos, F, L, Seq) ->
+ case file:position(F, Pos) of
+ {ok, _} ->
+ read_more_bytes([], 0, Pos, F, L, Seq);
+ _Error ->
+ {done, L, Seq}
+ end.
+
+fsck_read({more, Bin, Sz, L, Seq}, Pos, F, MaxSz, Head) when Sz > MaxSz ->
+ FR = skip_bytes(Bin, ?BUMP, Head#head.keypos, Head, L, Seq),
+ fsck_read(FR, Pos, F, MaxSz, Head);
+fsck_read({more, Bin, Sz, L, Seq}, Pos, F, _MaxSz, _Head) ->
+ read_more_bytes(Bin, Sz, Pos, F, L, Seq);
+fsck_read({new, Skip, L, Seq}, Pos, F, _MaxSz, _Head) ->
+ NewPos = Pos + Skip,
+ fsck_read(NewPos, F, L, Seq).
+
+read_more_bytes(B, Min, Pos, F, L, Seq) ->
+ Max = if
+ Min < ?CHUNK_SIZE -> ?CHUNK_SIZE;
+ true -> Min
+ end,
+ case dets_utils:read_n(F, Max) of
+ eof ->
+ {done, L, Seq};
+ Bin ->
+ NewPos = Pos + byte_size(Bin),
+ {cont, L, list_to_binary([B, Bin]), NewPos, Seq}
+ end.
+
+fsck_objs(Bin = <<Sz:32, Status:32, Tail/binary>>, Kp, Head, L, Seq) ->
+ if
+ Status =:= ?ACTIVE ->
+ Sz1 = Sz-?OHDSZ,
+ case Tail of
+ <<BinTerm:Sz1/binary, Tail2/binary>> ->
+ case catch bin2keybins(BinTerm, Head) of
+ {'EXIT', _Reason} ->
+ %% The whole collection of objects is skipped.
+ skip_bytes(Bin, ?BUMP, Kp, Head, L, Seq);
+ BOs ->
+ {NL, NSeq} = make_objects(BOs, Seq, Kp, Head, L),
+ Skip = ?POW(sz2pos(Sz)-1) - Sz,
+ skip_bytes(Tail2, Skip, Kp, Head, NL, NSeq)
+ end;
+ _ when byte_size(Tail) < Sz1 ->
+ {more, Bin, Sz, L, Seq}
+ end;
+ true ->
+ skip_bytes(Bin, ?BUMP, Kp, Head, L, Seq)
+ end;
+fsck_objs(Bin, _Kp, _Head, L, Seq) ->
+ {more, Bin, 0, L, Seq}.
+
+make_objects([{K,BT}|Os], Seq, Kp, Head, L) when Head#head.version =:= 8 ->
+ LogSz = dets_v8:sz2pos(byte_size(BT)+?OHDSZ_v8),
+ Slot = dets_v8:db_hash(K, Head),
+ Obj = [LogSz | <<Slot:32, LogSz:8, BT/binary>>],
+ make_objects(Os, Seq, Kp, Head, [Obj | L]);
+make_objects([{K,BT} | Os], Seq, Kp, Head, L) ->
+ Obj = make_object(Head, K, Seq, BT),
+ make_objects(Os, Seq+1, Kp, Head, [Obj | L]);
+make_objects([], Seq, _Kp, _Head, L) ->
+ {L, Seq}.
+
+%% Inlined.
+make_object(Head, Key, Seq, BT) ->
+ Slot = db_hash(Key, Head),
+ <<Slot:32, Seq:32, BT/binary>>.
+
+%% Inlined.
+skip_bytes(Bin, Skip, Kp, Head, L, Seq) ->
+ case Bin of
+ <<_:Skip/binary, Tail/binary>> ->
+ fsck_objs(Tail, Kp, Head, L, Seq);
+ _ when byte_size(Bin) < Skip ->
+ {new, Skip - byte_size(Bin), L, Seq}
+ end.
+
+%%%
+%%% End of repair, conversion and initialization of a dets file.
+%%%
+
+%% -> {NewHead, ok} | throw({Head, Error})
+do_perform_save(H) ->
+ {ok, FreeListsPointer} = dets_utils:position(H, eof),
+ H1 = H#head{freelists_p = FreeListsPointer},
+ {FLW, FLSize} = free_lists_to_file(H1),
+ FileSize = FreeListsPointer + FLSize + 4,
+ ok = dets_utils:write(H1, [FLW | <<FileSize:32>>]),
+ FileHeader = file_header(H1, FreeListsPointer, ?CLOSED_PROPERLY),
+ case dets_utils:debug_mode() of
+ true ->
+ TmpHead = H1#head{freelists = init_freelist(H1, true),
+ fixed = false},
+ case
+ catch dets_utils:all_allocated_as_list(TmpHead)
+ =:= dets_utils:all_allocated_as_list(H1)
+ of
+ true ->
+ dets_utils:pwrite(H1, [{0, FileHeader}]);
+ _ ->
+ dets_utils:corrupt_reason(H1, {failed_to_save_free_lists,
+ FreeListsPointer,
+ TmpHead#head.freelists,
+ H1#head.freelists})
+ end;
+ false ->
+ dets_utils:pwrite(H1, [{0, FileHeader}])
+ end.
+
+file_header(Head, FreeListsPointer, ClosedProperly) ->
+ NoColls = case Head#head.no_collections of
+ undefined -> [];
+ NC -> NC
+ end,
+ L = orddict:merge(fun(_K, V1, V2) -> V1 + V2 end,
+ NoColls,
+ lists:map(fun(X) -> {X,0} end, lists:seq(4,?MAXBUD-1))),
+ CW = lists:map(fun({_LSz,N}) -> <<N:32>> end, L),
+ file_header(Head, FreeListsPointer, ClosedProperly, CW).
+
+file_header(Head, FreeListsPointer, ClosedProperly, NoColls) ->
+ Cookie = ?MAGIC,
+ TypeCode = dets_utils:type_to_code(Head#head.type),
+ Version = ?FILE_FORMAT_VERSION,
+ HashMethod = hash_method_to_code(Head#head.hash_bif),
+ H1 = <<FreeListsPointer:32, Cookie:32, ClosedProperly:32>>,
+ H2 = <<TypeCode:32,
+ Version:32,
+ (Head#head.m):32,
+ (Head#head.next):32,
+ (Head#head.keypos):32,
+ (Head#head.no_objects):32,
+ (Head#head.no_keys):32,
+ (Head#head.min_no_slots):32,
+ (Head#head.max_no_slots):32,
+ HashMethod:32,
+ (Head#head.n):32>>,
+ DigH = [H2 | NoColls],
+ MD5 = case Head#head.has_md5 of
+ true -> erlang:md5(DigH);
+ false -> <<0:?MD5SZ/unit:8>>
+ end,
+ [H1, DigH, MD5 | <<0:?RESERVED/unit:8>>].
+
+%% Going through some trouble to avoid creating one single binary for
+%% the free lists. If the free lists are huge, binary_to_term and
+%% term_to_binary could otherwise stop the emulator for quite some time.
+
+-define(MAXFREEOBJ, 4096).
+-define(ENDFREE, 12345).
+
+free_lists_to_file(H) ->
+ FL = dets_utils:get_freelists(H),
+ free_list_to_file(FL, H, 1, tuple_size(FL), [], 0).
+
+free_list_to_file(_Ftab, _H, Pos, Sz, Ws, WsSz) when Pos > Sz ->
+ {[Ws | <<(4+?OHDSZ):32, ?FREE:32, ?ENDFREE:32>>], WsSz+4+?OHDSZ};
+free_list_to_file(Ftab, H, Pos, Sz, Ws, WsSz) ->
+ Max = (?MAXFREEOBJ - 4 - ?OHDSZ) div 4,
+ F = fun(N, L, W, S) when N =:= 0 -> {N, L, W, S};
+ (N, L, W, S) ->
+ {L1, N1, More} =
+ if
+ N > Max ->
+ {lists:sublist(L, Max), Max,
+ {N-Max, lists:nthtail(Max, L)}};
+ true ->
+ {L, N, no_more}
+ end,
+ Size = N1*4 + 4 + ?OHDSZ,
+ Header = <<Size:32, ?FREE:32, Pos:32>>,
+ NW = [W, Header | L1],
+ case More of
+ no_more ->
+ {0, [], NW, S+Size};
+ {NN, NL} ->
+ ok = dets_utils:write(H, NW),
+ {NN, NL, [], S+Size}
+ end
+ end,
+ {NWs,NWsSz} = dets_utils:tree_to_bin(element(Pos, Ftab), F, Max, Ws, WsSz),
+ free_list_to_file(Ftab, H, Pos+1, Sz, NWs, NWsSz).
+
+free_lists_from_file(H, Pos) ->
+ dets_utils:position(H#head.fptr, H#head.filename, Pos),
+ FL = dets_utils:empty_free_lists(),
+ case catch bin_to_tree([], H, start, FL, -1, []) of
+ {'EXIT', _} ->
+ throw({error, {bad_freelists, H#head.filename}});
+ Reply ->
+ Reply
+ end.
+
+bin_to_tree(Bin, H, LastPos, Ftab, A0, L) ->
+ case Bin of
+ <<_Size:32,?FREE:32,?ENDFREE:32,_/binary>> when L =:= [] ->
+ Ftab;
+ <<_Size:32,?FREE:32,?ENDFREE:32,_/binary>> ->
+ setelement(LastPos, Ftab, dets_utils:list_to_tree(L));
+ <<Size:32,?FREE:32,Pos:32,T/binary>>
+ when byte_size(T) >= Size-4-?OHDSZ ->
+ {NFtab, L1, A1} =
+ if
+ Pos =/= LastPos, LastPos =/= start ->
+ Tree = dets_utils:list_to_tree(L),
+ {setelement(LastPos, Ftab, Tree), [], -1};
+ true ->
+ {Ftab, L, A0}
+ end,
+ {NL, B2, A2} = bin_to_tree1(T, Size-?OHDSZ-4, A1, L1),
+ bin_to_tree(B2, H, Pos, NFtab, A2, NL);
+ _ ->
+ Bin2 = dets_utils:read_n(H#head.fptr, ?MAXFREEOBJ),
+ bin_to_tree(list_to_binary([Bin | Bin2]), H, LastPos, Ftab, A0, L)
+ end.
+
+bin_to_tree1(<<A1:32,A2:32,A3:32,A4:32,T/binary>>, Size, A, L)
+ when Size >= 16, A < A1, A1 < A2, A2 < A3, A3 < A4 ->
+ bin_to_tree1(T, Size-16, A4, [A4, A3, A2, A1 | L]);
+bin_to_tree1(<<A1:32,T/binary>>, Size, A, L) when Size >= 4, A < A1 ->
+ bin_to_tree1(T, Size - 4, A1, [A1 | L]);
+bin_to_tree1(B, 0, A, L) ->
+ {L, B, A}.
+
+%% -> [term()] | throw({Head, Error})
+slot_objs(H, Slot) when Slot >= H#head.next ->
+ '$end_of_table';
+slot_objs(H, Slot) ->
+ {ok, _Pointer, Objects} = slot_objects(H, Slot),
+ Objects.
+
+%% Inlined.
+h(I, phash2) -> erlang:phash2(I); % -> [0..2^27-1]
+h(I, phash) -> erlang:phash(I, ?BIG) - 1.
+
+db_hash(Key, Head) when Head#head.hash_bif =:= phash2 ->
+ H = erlang:phash2(Key),
+ Hash = ?REM2(H, Head#head.m),
+ if
+ Hash < Head#head.n ->
+ ?REM2(H, Head#head.m2); % H rem (2 * m)
+ true ->
+ Hash
+ end;
+db_hash(Key, Head) ->
+ H = h(Key, Head#head.hash_bif),
+ Hash = H rem Head#head.m,
+ if
+ Hash < Head#head.n ->
+ H rem (Head#head.m2); % H rem (2 * m)
+ true ->
+ Hash
+ end.
+
+hash_method_to_code(phash2) -> ?PHASH2;
+hash_method_to_code(phash) -> ?PHASH.
+
+code_to_hash_method(?PHASH2) -> phash2;
+code_to_hash_method(?PHASH) -> phash;
+code_to_hash_method(_) -> undefined.
+
+no_slots(Head) ->
+ {Head#head.min_no_slots, Head#head.next, Head#head.max_no_slots}.
+
+table_parameters(Head) ->
+ case Head#head.no_collections of
+ undefined ->
+ undefined; % Version 9(a)
+ CL ->
+ NoColls0 = lists:foldl(fun({_,0}, A) -> A;
+ (E, A) -> [E | A]
+ end, [], CL),
+ NoColls = lists:reverse(NoColls0),
+ #?HASH_PARMS{file_format_version = Head#head.version,
+ bchunk_format_version = ?BCHUNK_FORMAT_VERSION,
+ file = filename:basename(Head#head.filename),
+ type = Head#head.type,
+ keypos = Head#head.keypos,
+ hash_method = hash_method_to_code(Head#head.hash_bif),
+ n = Head#head.n, m = Head#head.m,
+ next = Head#head.next,
+ min = Head#head.min_no_slots,
+ max = Head#head.max_no_slots,
+ no_objects = Head#head.no_objects,
+ no_keys = Head#head.no_keys, no_colls = NoColls}
+ end.
+
+%% Allow quite a lot when reading object collections.
+-define(MAXCOLL, (10 * ?CHUNK_SIZE)).
+
+%% Re-hashing a segment, starting with SlotStart.
+%%
+%% On the average, half of the keys of the slot are put in a new slot.
+%% If the old slot is i, then the new slot is i+m. The new slots
+%% reside in a newly allocated segment.
+%%
+%% -> {NewHead, ok} | throw({Head, Error})
+re_hash(Head, SlotStart) ->
+ FromSlotPos = slot_position(SlotStart),
+ ToSlotPos = slot_position(SlotStart + Head#head.m),
+ RSpec = [{FromSlotPos, 4 * ?SEGSZ}],
+ {ok, [FromBin]} = dets_utils:pread(RSpec, Head),
+ split_bins(FromBin, Head, FromSlotPos, ToSlotPos, [], [], 0).
+
+split_bins(<<>>, Head, _Pos1, _Pos2, _ToRead, _L, 0) ->
+ {Head, ok};
+split_bins(<<>>, Head, Pos1, Pos2, ToRead, L, _SoFar) ->
+ re_hash_write(Head, ToRead, L, Pos1, Pos2);
+split_bins(FB, Head, Pos1, Pos2, ToRead, L, SoFar) ->
+ <<Sz1:32, P1:32, FT/binary>> = FB,
+ <<B1:?OHDSZ/binary, _/binary>> = FB,
+ NSoFar = SoFar + Sz1,
+ NPos1 = Pos1 + ?SZOBJP*4,
+ NPos2 = Pos2 + ?SZOBJP*4,
+ if
+ NSoFar > ?MAXCOLL, ToRead =/= [] ->
+ {NewHead, ok} = re_hash_write(Head, ToRead, L, Pos1, Pos2),
+ split_bins(FB, NewHead, Pos1, Pos2, [], [], 0);
+ Sz1 =:= 0 ->
+ E = {skip,B1},
+ split_bins(FT, Head, NPos1, NPos2, ToRead, [E | L], NSoFar);
+ true ->
+ E = {Sz1,P1,B1,Pos1,Pos2},
+ NewToRead = [{P1,Sz1} | ToRead],
+ split_bins(FT, Head, NPos1, NPos2, NewToRead, [E | L], NSoFar)
+ end.
+
+re_hash_write(Head, ToRead, L, Pos1, Pos2) ->
+ check_pread2_arg(ToRead, Head),
+ {ok, Bins} = dets_utils:pread(ToRead, Head),
+ Z = <<0:32, 0:32>>,
+ {Head1, BinFS, BinTS, WsB} = re_hash_slots(Bins, L, Head, Z, [],[],[]),
+ WPos1 = Pos1 - ?SZOBJP*4*length(L),
+ WPos2 = Pos2 - ?SZOBJP*4*length(L),
+ ToWrite = [{WPos1,BinFS}, {WPos2, BinTS} | WsB],
+ dets_utils:pwrite(Head1, ToWrite).
+
+re_hash_slots(Bins, [{skip,B1} | L], Head, Z, BinFS, BinTS, WsB) ->
+ re_hash_slots(Bins, L, Head, Z, [B1 | BinFS], [Z | BinTS], WsB);
+re_hash_slots([FB | Bins], [E | L], Head, Z, BinFS, BinTS, WsB) ->
+ {Sz1,P1,B1,Pos1,Pos2} = E,
+ KeyObjs = case catch per_key(Head, FB) of
+ {'EXIT', _Error} ->
+ Bad = dets_utils:bad_object(re_hash_slots, {FB, E}),
+ throw(dets_utils:corrupt_reason(Head, Bad));
+ Else ->
+ Else
+ end,
+ case re_hash_split(KeyObjs, Head, [], 0, [], 0) of
+ {_KL, _KSz, [], 0} ->
+ Sz1 = _KSz + ?OHDSZ,
+ re_hash_slots(Bins, L, Head, Z, [B1 | BinFS], [Z | BinTS], WsB);
+ {[], 0, _ML, _MSz} -> %% Optimization.
+ Sz1 = _MSz + ?OHDSZ,
+ re_hash_slots(Bins, L, Head, Z, [Z | BinFS], [B1 | BinTS], WsB);
+ {KL, KSz, ML, MSz} when KL =/= [], KSz > 0, ML =/= [], MSz > 0 ->
+ {Head1, FS1, Ws1} =
+ updated(Head, P1, Sz1, KSz, Pos1, KL, true, foo, bar),
+ {NewHead, [{Pos2,Bin2}], Ws2} =
+ updated(Head1, 0, 0, MSz, Pos2, ML, true, foo, bar),
+ NewBinFS = case FS1 of
+ [{Pos1,Bin1}] -> [Bin1 | BinFS];
+ [] -> [B1 | BinFS] % cannot happen
+ end,
+ NewBinTS = [Bin2 | BinTS],
+ NewWsB = Ws2 ++ Ws1 ++ WsB,
+ re_hash_slots(Bins, L, NewHead, Z, NewBinFS, NewBinTS, NewWsB)
+ end;
+re_hash_slots([], [], Head, _Z, BinFS, BinTS, WsB) ->
+ {Head, BinFS, BinTS, lists:reverse(WsB)}.
+
+re_hash_split([E | KeyObjs], Head, KL, KSz, ML, MSz) ->
+ {Key,Sz,Bin,_Item,_Objs} = E,
+ New = h(Key, Head#head.hash_bif) rem Head#head.m2, % h(key) rem (m * 2)
+ if
+ New >= Head#head.m ->
+ re_hash_split(KeyObjs, Head, KL, KSz, [Bin | ML], MSz + Sz);
+ true ->
+ re_hash_split(KeyObjs, Head, [Bin | KL], KSz + Sz, ML, MSz)
+ end;
+re_hash_split([], _Head, KL, KSz, ML, MSz) ->
+ {lists:reverse(KL), KSz, lists:reverse(ML), MSz}.
+
+%% -> {NewHead, [LookedUpObject], pwrite_list()} | throw({NewHead, Error})
+write_cache(Head) ->
+ C = Head#head.cache,
+ case dets_utils:is_empty_cache(C) of
+ true -> {Head, [], []};
+ false ->
+ {NewC, MaxInserts, PerKey} = dets_utils:reset_cache(C),
+ %% MaxNoInsertedKeys is an upper limit on the number of new keys.
+ MaxNoInsertedKeys = erlang:min(MaxInserts, length(PerKey)),
+ Head1 = Head#head{cache = NewC},
+ case may_grow(Head1, MaxNoInsertedKeys, once) of
+ {Head2, ok} ->
+ eval_work_list(Head2, PerKey);
+ HeadError ->
+ throw(HeadError)
+ end
+ end.
+
+%% -> {NewHead, ok} | {NewHead, Error}
+may_grow(Head, _N, _How) when Head#head.fixed =/= false ->
+ {Head, ok};
+may_grow(#head{access = read}=Head, _N, _How) ->
+ {Head, ok};
+may_grow(Head, _N, _How) when Head#head.next >= Head#head.max_no_slots ->
+ {Head, ok};
+may_grow(Head, N, How) ->
+ Extra = erlang:min(2*?SEGSZP, Head#head.no_keys + N - Head#head.next),
+ case catch may_grow1(Head, Extra, How) of
+ {error, _Reason} = Error -> % alloc may throw error
+ dets_utils:corrupt(Head, Error);
+ {NewHead, Reply} when is_record(Head, head) ->
+ {NewHead, Reply}
+ end.
+
+may_grow1(Head, Extra, many_times) when Extra > ?SEGSZP ->
+ Reply = grow(Head, 1, undefined),
+ self() ! ?DETS_CALL(self(), may_grow),
+ Reply;
+may_grow1(Head, Extra, _How) ->
+ grow(Head, Extra, undefined).
+
+%% -> {Head, ok} | throw({Head, Error})
+grow(Head, Extra, _SegZero) when Extra =< 0 ->
+ {Head, ok};
+grow(Head, Extra, undefined) ->
+ grow(Head, Extra, seg_zero());
+grow(Head, _Extra, _SegZero) when Head#head.next >= Head#head.max_no_slots ->
+ {Head, ok};
+grow(Head, Extra, SegZero) ->
+ #head{n = N, next = Next, m = M} = Head,
+ SegNum = Next div ?SEGSZP,
+ {Head0, W, Ws1} = allocate_segment(Head, SegZero, SegNum),
+ %% re_hash/2 will overwrite the segment, but initialize it anyway...
+ {Head1, ok} = dets_utils:pwrite(Head0, [W | Ws1]),
+ %% If re_hash fails, segp_cache has been called, but it does not matter.
+ {Head2, ok} = re_hash(Head1, N),
+ NewHead =
+ if
+ N + ?SEGSZP =:= M ->
+ Head2#head{n = 0, next = Next + ?SEGSZP, m = 2 * M, m2 = 4 * M};
+ true ->
+ Head2#head{n = N + ?SEGSZP, next = Next + ?SEGSZP}
+ end,
+ true = hash_invars(NewHead),
+ grow(NewHead, Extra - ?SEGSZP, SegZero).
+
+hash_invars(H) ->
+ hash_invars(H#head.n, H#head.m, H#head.next, H#head.min_no_slots,
+ H#head.max_no_slots).
+
+-define(M8(X), (((X) band (?SEGSZP - 1)) =:= 0)).
+hash_invars(N, M, Next, Min, Max) ->
+ ?M8(N) and ?M8(M) and ?M8(Next) and ?M8(Min) and ?M8(Max)
+ and (0 =< N) and (N =< M) and (N =< 2*Next) and (M =< Next)
+ and (Next =< 2*M) and (0 =< Min) and (Min =< Next) and (Next =< Max)
+ and (Min =< M).
+
+seg_zero() ->
+ <<0:(4*?SEGSZ)/unit:8>>.
+
+find_object(Head, Object) ->
+ Key = element(Head#head.keypos, Object),
+ Slot = db_hash(Key, Head),
+ find_object(Head, Object, Slot).
+
+find_object(H, _Obj, Slot) when Slot >= H#head.next ->
+ false;
+find_object(H, Obj, Slot) ->
+ case catch slot_objects(H, Slot) of
+ {ok, Pointer, Objects} ->
+ case lists:member(Obj, Objects) of
+ true -> {ok, Pointer};
+ false -> false
+ end;
+ _ -> false
+ end.
+
+%% -> {ok, BucketP, Objects} | throw({Head, Error})
+slot_objects(Head, Slot) ->
+ SlotPos = slot_position(Slot),
+ MaxSize = maxobjsize(Head),
+ case dets_utils:ipread(Head, SlotPos, MaxSize) of
+ {ok, {BucketSz, Pointer, <<BucketSz:32, _St:32, KeysObjs/binary>>}} ->
+ case catch bin2objs(KeysObjs, Head#head.type, []) of
+ {'EXIT', _Error} ->
+ Bad = dets_utils:bad_object(slot_objects,
+ {SlotPos, KeysObjs}),
+ throw(dets_utils:corrupt_reason(Head, Bad));
+ Objs when is_list(Objs) ->
+ {ok, Pointer, lists:reverse(Objs)}
+ end;
+ [] ->
+ {ok, 0, []};
+ BadRead -> % eof or bad badly formed binary
+ Bad = dets_utils:bad_object(slot_objects, {SlotPos, BadRead}),
+ throw(dets_utils:corrupt_reason(Head, Bad))
+ end.
+
+%%%
+%%% Cache routines depending on the dets file format.
+%%%
+
+%% -> {Head, [LookedUpObject], pwrite_list()} | throw({Head, Error})
+eval_work_list(Head, [{Key,[{_Seq,{lookup,Pid}}]}]) ->
+ SlotPos = slot_position(db_hash(Key, Head)),
+ MaxSize = maxobjsize(Head),
+ Objs = case dets_utils:ipread(Head, SlotPos, MaxSize) of
+ {ok, {_BucketSz, _Pointer, Bin}} ->
+ case catch per_key(Head, Bin) of
+ {'EXIT', _Error} ->
+ Bad = dets_utils:bad_object(eval_work_list,
+ {SlotPos, Bin}),
+ throw(dets_utils:corrupt_reason(Head, Bad));
+ KeyObjs when is_list(KeyObjs) ->
+ case dets_utils:mkeysearch(Key, 1, KeyObjs) of
+ false ->
+ [];
+ {value, {Key,_KS,_KB,O,Os}} ->
+ case catch binobjs2terms(Os) of
+ {'EXIT', _Error} ->
+ Bad = dets_utils:bad_object
+ (eval_work_list,
+ {SlotPos, Bin, KeyObjs}),
+ throw(dets_utils:corrupt_reason
+ (Head, Bad));
+ Terms when is_list(Terms) ->
+ get_objects([O | Terms])
+ end
+ end
+ end;
+ [] ->
+ [];
+ BadRead -> % eof or bad badly formed binary
+ Bad = dets_utils:bad_object(eval_work_list,
+ {SlotPos, BadRead}),
+ throw(dets_utils:corrupt_reason(Head, Bad))
+ end,
+ {Head, [{Pid,Objs}], []};
+eval_work_list(Head, PerKey) ->
+ SWLs = tag_with_slot(PerKey, Head, []),
+ P1 = dets_utils:family(SWLs),
+ {PerSlot, SlotPositions} = remove_slot_tag(P1, [], []),
+ {ok, Bins} = dets_utils:pread(SlotPositions, Head),
+ read_buckets(PerSlot, SlotPositions, Bins, Head, [], [], [], [], 0, 0, 0).
+
+tag_with_slot([{K,_} = WL | WLs], Head, L) ->
+ tag_with_slot(WLs, Head, [{db_hash(K, Head), WL} | L]);
+tag_with_slot([], _Head, L) ->
+ L.
+
+remove_slot_tag([{S,SWLs} | SSWLs], Ls, SPs) ->
+ remove_slot_tag(SSWLs, [SWLs | Ls], [{slot_position(S), ?SEGOBJSZ} | SPs]);
+remove_slot_tag([], Ls, SPs) ->
+ {Ls, SPs}.
+
+read_buckets([WLs | SPs], [{P1,_8} | Ss], [<<_Zero:32,P2:32>> | Bs], Head,
+ PWLs, ToRead, LU, Ws, NoObjs, NoKeys, SoFar) when P2 =:= 0 ->
+ {NewHead, NLU, NWs, No, KNo} =
+ eval_bucket_keys(WLs, P1, 0, 0, [], Head, Ws, LU),
+ NewNoObjs = No + NoObjs,
+ NewNoKeys = KNo + NoKeys,
+ read_buckets(SPs, Ss, Bs, NewHead, PWLs, ToRead, NLU, NWs,
+ NewNoObjs, NewNoKeys, SoFar);
+read_buckets([WorkLists| SPs], [{P1,_8} | Ss], [<<Size:32,P2:32>> | Bs], Head,
+ PWLs, ToRead, LU, Ws, NoObjs, NoKeys, SoFar)
+ when SoFar + Size < ?MAXCOLL; ToRead =:= [] ->
+ NewToRead = [{P2, Size} | ToRead],
+ NewPWLs = [{P2,P1,WorkLists} | PWLs],
+ NewSoFar = SoFar + Size,
+ read_buckets(SPs, Ss, Bs, Head, NewPWLs, NewToRead, LU, Ws,
+ NoObjs, NoKeys, NewSoFar);
+read_buckets(SPs, Ss, Bs, Head, PWLs0, ToRead0, LU, Ws, NoObjs, NoKeys, SoFar)
+ when SoFar > 0 ->
+ %% It pays off to sort the positions. The seek times are reduced,
+ %% at least if the file blocks are reasonably contiguous, as is
+ %% often the case.
+ PWLs = lists:keysort(1, PWLs0),
+ ToRead = lists:keysort(1, ToRead0),
+ check_pread2_arg(ToRead, Head),
+ {ok, Bins} = dets_utils:pread(ToRead, Head),
+ case catch eval_buckets(Bins, PWLs, Head, LU, Ws, 0, 0) of
+ {ok, NewHead, NLU, [], 0, 0} ->
+ read_buckets(SPs, Ss, Bs, NewHead, [], [], NLU, [],
+ NoObjs, NoKeys, 0);
+ {ok, Head1, NLU, NWs, No, KNo} ->
+ NewNoObjs = NoObjs + No,
+ NewNoKeys = NoKeys + KNo,
+ %% It does not seem to reduce seek times to sort positions
+ %% when writing (maybe because it takes several calls to
+ %% write_cache/1 to fill the file system's buffer cache).
+ {NewHead, ok} = dets_utils:pwrite(Head1, lists:reverse(NWs)),
+ read_buckets(SPs, Ss, Bs, NewHead, [], [], NLU, [],
+ NewNoObjs, NewNoKeys, 0);
+ Error ->
+ Bad = dets_utils:bad_object(read_buckets, {Bins, Error}),
+ throw(dets_utils:corrupt_reason(Head, Bad))
+ end;
+read_buckets([], [], [], Head, [], [], LU, Ws, NoObjs, NoKeys, 0) ->
+ {NewHead, NWs} = update_no_keys(Head, Ws, NoObjs, NoKeys),
+ {NewHead, LU, lists:reverse(NWs)}.
+
+eval_buckets([Bin | Bins], [SP | SPs], Head, LU, Ws, NoObjs, NoKeys) ->
+ {Pos, P1, WLs} = SP,
+ KeyObjs = per_key(Head, Bin),
+ {NewHead, NLU, NWs, No, KNo} =
+ eval_bucket_keys(WLs, P1, Pos, byte_size(Bin), KeyObjs, Head,Ws,LU),
+ eval_buckets(Bins, SPs, NewHead, NLU, NWs, NoObjs + No, NoKeys + KNo);
+eval_buckets([], [], Head, LU, Ws, NoObjs, NoKeys) ->
+ {ok, Head, LU, Ws, NoObjs, NoKeys}.
+
+eval_bucket_keys(WLs, SlotPos, Pos, OldSize, KeyObjs, Head, Ws, LU) ->
+ {NLU, Bins, BSize, No, KNo, Ch} =
+ eval_slot(WLs, KeyObjs, Head#head.type, LU, [], 0, 0, 0, false),
+ {NewHead, W1, W2} =
+ updated(Head, Pos, OldSize, BSize, SlotPos, Bins, Ch, No, KNo),
+ {NewHead, NLU, W2++W1++Ws, No, KNo}.
+
+updated(Head, Pos, OldSize, BSize, SlotPos, Bins, Ch, DeltaNoOs, DeltaNoKs) ->
+ BinsSize = BSize + ?OHDSZ,
+ if
+ Pos =:= 0, BSize =:= 0 ->
+ {Head, [], []};
+ Pos =:= 0, BSize > 0 ->
+ {Head1, NewPos, FPos} = dets_utils:alloc(Head, adjsz(BinsSize)),
+ NewHead = one_bucket_added(Head1, FPos-1),
+ W1 = {NewPos, [<<BinsSize:32, ?ACTIVE:32>> | Bins]},
+ W2 = {SlotPos, <<BinsSize:32, NewPos:32>>},
+ {NewHead, [W2], [W1]};
+ Pos =/= 0, BSize =:= 0 ->
+ {Head1, FPos} = dets_utils:free(Head, Pos, adjsz(OldSize)),
+ NewHead = one_bucket_removed(Head1, FPos-1),
+ W1 = {Pos+?STATUS_POS, <<?FREE:32>>},
+ W2 = {SlotPos, <<0:32, 0:32>>},
+ {NewHead, [W2], [W1]};
+ Pos =/= 0, BSize > 0, Ch =:= false ->
+ {Head, [], []};
+ Pos =/= 0, BSize > 0 ->
+ %% Doubtful. The scan function has to be careful since
+ %% partly scanned objects may be overwritten.
+ Overwrite0 = if
+ OldSize =:= BinsSize -> same;
+ true -> sz2pos(OldSize) =:= sz2pos(BinsSize)
+ end,
+ Overwrite = if
+ Head#head.fixed =/= false ->
+ %% Make sure that if the table is
+ %% fixed, nothing is overwritten,
+ %% unless the number of objects and
+ %% the number of keys remain the same.
+ %% This is used by bchunk, which
+ %% assumes that it traverses exactly
+ %% the same number of objects and keys
+ %% (and collections) as were present
+ %% when chunking started (the table
+ %% must have been fixed).
+ (Overwrite0 =/= false) and
+ (DeltaNoOs =:= 0) and (DeltaNoKs =:= 0);
+ true ->
+ Overwrite0
+ end,
+ if
+ Overwrite =:= same ->
+ W1 = {Pos+?OHDSZ, Bins},
+ {Head, [], [W1]};
+ Overwrite ->
+ W1 = {Pos, [<<BinsSize:32, ?ACTIVE:32>> | Bins]},
+ %% Pos is already there, but return {SlotPos, <8 bytes>}.
+ W2 = {SlotPos, <<BinsSize:32, Pos:32>>},
+ {Head, [W2], [W1]};
+ true ->
+ {Head1, FPosF} = dets_utils:free(Head, Pos, adjsz(OldSize)),
+ {Head2, NewPos, FPosA} =
+ dets_utils:alloc(Head1, adjsz(BinsSize)),
+ Head3 = one_bucket_added(Head2, FPosA-1),
+ NewHead = one_bucket_removed(Head3, FPosF-1),
+ W0 = {NewPos, [<<BinsSize:32, ?ACTIVE:32>> | Bins]},
+ W2 = {SlotPos, <<BinsSize:32, NewPos:32>>},
+ W1 = if
+ Pos =/= NewPos ->
+ %% W0 first.
+ [W0, {Pos+?STATUS_POS, <<?FREE:32>>}];
+ true ->
+ [W0]
+ end,
+ {NewHead, [W2], W1}
+ end
+ end.
+
+one_bucket_added(H, _Log2) when H#head.no_collections =:= undefined ->
+ H;
+one_bucket_added(H, Log2) when H#head.maxobjsize >= Log2 ->
+ NewNoColls = orddict:update_counter(Log2, 1, H#head.no_collections),
+ H#head{no_collections = NewNoColls};
+one_bucket_added(H, Log2) ->
+ NewNoColls = orddict:update_counter(Log2, 1, H#head.no_collections),
+ H#head{no_collections = NewNoColls, maxobjsize = Log2}.
+
+one_bucket_removed(H, _FPos) when H#head.no_collections =:= undefined ->
+ H;
+one_bucket_removed(H, Log2) when H#head.maxobjsize > Log2 ->
+ NewNoColls = orddict:update_counter(Log2, -1, H#head.no_collections),
+ H#head{no_collections = NewNoColls};
+one_bucket_removed(H, Log2) when H#head.maxobjsize =:= Log2 ->
+ NewNoColls = orddict:update_counter(Log2, -1, H#head.no_collections),
+ MaxObjSize = max_objsize(NewNoColls),
+ H#head{no_collections = NewNoColls, maxobjsize = MaxObjSize}.
+
+eval_slot([{Key,Commands} | WLs] = WLs0, [{K,KS,KB,O,Os} | KOs1]=KOs,
+ Type, LU, Ws, No, KNo,BSz, Ch) ->
+ case dets_utils:cmp(K, Key) of
+ 0 ->
+ Old = [O | binobjs2terms(Os)],
+ {NLU, NWs, Sz, No1, KNo1, NCh} =
+ eval_key(Key, Commands, Old, Type, KB, KS, LU, Ws, Ch),
+ eval_slot(WLs, KOs1, Type, NLU, NWs, No1 + No,
+ KNo1 + KNo, Sz + BSz, NCh);
+ -1 ->
+ eval_slot(WLs0, KOs1, Type, LU, [Ws | KB], No,
+ KNo, KS + BSz, Ch);
+ 1 ->
+ {NLU, NWs, Sz, No1, KNo1, NCh} =
+ eval_key(Key, Commands, [], Type, [], 0, LU, Ws, Ch),
+ eval_slot(WLs, KOs, Type, NLU, NWs, No1 + No,
+ KNo1 + KNo, Sz + BSz, NCh)
+ end;
+eval_slot([{Key,Commands} | WLs], [], Type, LU, Ws, No, KNo,BSz, Ch) ->
+ {NLU, NWs, Sz, No1, KNo1, NCh} =
+ eval_key(Key, Commands, [], Type, [], 0, LU, Ws, Ch),
+ eval_slot(WLs, [], Type, NLU, NWs, No1 + No, KNo1 + KNo, Sz + BSz, NCh);
+eval_slot([], [{_Key,Size,KeyBin,_,_} | KOs], Type, LU, Ws, No, KNo,BSz, Ch) ->
+ eval_slot([], KOs, Type, LU, [Ws | KeyBin], No, KNo, Size + BSz, Ch);
+eval_slot([], [], _Type, LU, Ws, No, KNo, BSz, Ch) ->
+ {LU, Ws, BSz, No, KNo, Ch}.
+
+eval_key(_K, [{_Seq,{lookup,Pid}}], [], _Type, _KeyBin, _KeySz, LU, Ws, Ch) ->
+ NLU = [{Pid, []} | LU],
+ {NLU, Ws, 0, 0, 0, Ch};
+eval_key(_K, [{_Seq,{lookup,Pid}}], Old0, _Type, KeyBin, KeySz, LU, Ws, Ch) ->
+ Old = lists:keysort(2, Old0), % sort on sequence number
+ Objs = get_objects(Old),
+ NLU = [{Pid, Objs} | LU],
+ {NLU, [Ws | KeyBin], KeySz, 0, 0, Ch};
+eval_key(K, Comms, Orig, Type, KeyBin, KeySz, LU, Ws, Ch) ->
+ Old = dets_utils:msort(Orig),
+ case eval_key1(Comms, [], Old, Type, K, LU, Ws, 0, Orig) of
+ {ok, NLU} when Old =:= [] ->
+ {NLU, Ws, 0, 0, 0, Ch};
+ {ok, NLU} ->
+ {NLU, [Ws | KeyBin], KeySz, 0, 0, Ch};
+ {NLU, NWs, NSz, No} when Old =:= [], NSz > 0 ->
+ {NLU, NWs, NSz, No, 1, true};
+ {NLU, NWs, NSz, No} when Old =/= [], NSz =:= 0 ->
+ {NLU, NWs, NSz, No, -1, true};
+ {NLU, NWs, NSz, No} ->
+ {NLU, NWs, NSz, No, 0, true}
+ end.
+
+%% First find 'delete_key' and 'lookup' commands, and handle the 'set' type.
+eval_key1([{_Seq,{insert,Term}} | L], Cs, [{Term,_,_}] = Old, Type=set, K,
+ LU, Ws, No, Orig) ->
+ eval_key1(L, Cs, Old, Type, K, LU, Ws, No, Orig);
+eval_key1([{Seq,{insert,Term}} | L], Cs, Old, Type=set, K, LU, Ws, No, Orig)
+ ->
+ NNo = No + 1 - length(Old),
+ eval_key1(L, Cs, [{Term,Seq,insert}], Type, K, LU, Ws, NNo, Orig);
+eval_key1([{_Seq,{lookup,Pid}} | L], Cs, Old, Type, Key, LU, Ws, No, Orig) ->
+ {ok, New0, NewNo} = eval_comms(Cs, Old, Type, No),
+ New = lists:keysort(2, New0), % sort on sequence number
+ Objs = get_objects(New),
+ NLU = [{Pid, Objs} | LU],
+ if
+ L =:= [] ->
+ eval_end(New, NLU, Type, Ws, NewNo, Orig);
+ true ->
+ NewOld = dets_utils:msort(New),
+ eval_key1(L, [], NewOld, Type, Key, NLU, Ws, NewNo, Orig)
+ end;
+eval_key1([{_Seq,delete_key} | L], _Cs, Old, Type, K, LU, Ws, No, Orig) ->
+ NewNo = No - length(Old),
+ eval_key1(L, [], [], Type, K, LU, Ws, NewNo, Orig);
+eval_key1([{_Seq,{delete_object,Term}} | L], Cs, [{Term,_,_}], Type=set, K,
+ LU, Ws, No, Orig) ->
+ eval_key1(L, Cs, [], Type, K, LU, Ws, No-1, Orig);
+eval_key1([{_Seq,{delete_object,_T}}| L], Cs, Old1, Type=set, K, LU,
+ Ws, No, Orig) ->
+ eval_key1(L, Cs, Old1, Type, K, LU, Ws, No, Orig);
+eval_key1([{Seq,{Comm,Term}} | L], Cs, Old, Type, K, LU, Ws, No, Orig)
+ when Type =/= set ->
+ eval_key1(L, [{Term,Seq,Comm} | Cs], Old, Type, K, LU, Ws, No, Orig);
+eval_key1([], Cs, Old, Type=set, _Key, LU, Ws, No, Orig) ->
+ [] = Cs,
+ eval_end(Old, LU, Type, Ws, No, Orig);
+eval_key1([], Cs, Old, Type, _Key, LU, Ws, No, Orig) ->
+ {ok, New, NewNo} = eval_comms(Cs, Old, Type, No),
+ eval_end(New, LU, Type, Ws, NewNo, Orig).
+
+eval_comms([], L, _Type=set, No) ->
+ {ok, L, No};
+eval_comms(Cs, Old, Type, No) ->
+ Commands = dets_utils:msort(Cs),
+ case Type of
+ bag -> eval_bag(Commands, Old, [], No);
+ duplicate_bag -> eval_dupbag(Commands, Old, [], No)
+ end.
+
+eval_end(New0, LU, Type, Ws, NewNo, Orig) ->
+ New = lists:keysort(2, New0), % sort on sequence number
+ NoChange = if
+ length(New) =/= length(Orig) -> false;
+ true ->
+ same_terms(Orig, New)
+ end,
+ if
+ NoChange ->
+ %% The key's objects have not changed.
+ {ok, LU};
+ New =:= [] ->
+ {LU, Ws, 0, NewNo};
+ true ->
+ {Ws1, Sz} = make_bins(New, [], 0),
+ if
+ Type =:= set ->
+ {LU, [Ws | Ws1], Sz, NewNo};
+ true ->
+ NSz = Sz + 4,
+ {LU, [Ws, <<NSz:32>> | Ws1], NSz, NewNo}
+ end
+ end.
+
+same_terms([E1 | L1], [E2 | L2]) when element(1, E1) =:= element(1, E2) ->
+ same_terms(L1, L2);
+same_terms([], []) ->
+ true;
+same_terms(_L1, _L2) ->
+ false.
+
+make_bins([{_Term,_Seq,B} | L], W, Sz) when is_binary(B) ->
+ make_bins(L, [W | B], Sz + byte_size(B));
+make_bins([{Term,_Seq,insert} | L], W, Sz) ->
+ B = term_to_binary(Term),
+ BSize = byte_size(B) + 4,
+ make_bins(L, [W, [<<BSize:32>> | B]], Sz + BSize);
+make_bins([], W, Sz) ->
+ {W, Sz}.
+
+get_objects([{T,_S,_BT} | L]) ->
+ [T | get_objects(L)];
+get_objects([]) ->
+ [].
+
+eval_bag([{Term1,_S1,Op}=N | L]=L0, [{Term2,_,_}=O | Old]=Old0, New, No) ->
+ case {Op, dets_utils:cmp(Term1, Term2)} of
+ {delete_object, -1} ->
+ eval_bag(L, Old0, New, No);
+ {insert, -1} ->
+ bag_object(L, Old0, New, No, [N], Term1);
+ {delete_object, 0} ->
+ bag_object(L, Old, New, No-1, [], Term1);
+ {insert, 0} ->
+ bag_object(L, Old, New, No-1, [N], Term1);
+ {_, 1} ->
+ eval_bag(L0, Old, [O | New], No)
+ end;
+eval_bag([{_Term1,_Seq1,delete_object} | L], []=Old, New, No) ->
+ eval_bag(L, Old, New, No);
+eval_bag([{Term,_Seq1,insert} = N | L], []=Old, New, No) ->
+ bag_object(L, Old, New, No, [N], Term);
+eval_bag([]=L, [O | Old], New, No) ->
+ eval_bag(L, Old, [O | New], No);
+eval_bag([], [], New, No) ->
+ {ok, New, No}.
+
+bag_object([{Term,_,insert} = N | L], Old, New, No, _N, Term) ->
+ bag_object(L, Old, New, No, [N], Term);
+bag_object([{Term,_,delete_object} | L], Old, New, No, _N, Term) ->
+ bag_object(L, Old, New, No, [], Term);
+bag_object(L, Old, New, No, [], _Term) ->
+ eval_bag(L, Old, New, No);
+bag_object(L, Old, New, No, [N], _Term) ->
+ eval_bag(L, Old, [N | New], No+1).
+
+eval_dupbag([{Term1,_S1,Op}=N | L]=L0, [{Term2,_,_}=O | Old]=Old0, New, No) ->
+ case {Op, dets_utils:cmp(Term1, Term2)} of
+ {delete_object, -1} ->
+ eval_dupbag(L, Old0, New, No);
+ {insert, -1} ->
+ dup_object(L, Old0, New, No+1, Term1, [N]);
+ {_, 0} ->
+ old_dup_object(L0, Old, New, No, Term1, [O]);
+ {_, 1} ->
+ eval_dupbag(L0, Old, [O | New], No)
+ end;
+eval_dupbag([{_Term1,_Seq1,delete_object} | L], []=Old, New, No) ->
+ eval_dupbag(L, Old, New, No);
+eval_dupbag([{Term,_Seq1,insert} = N | L], []=Old, New, No) ->
+ dup_object(L, Old, New, No+1, Term, [N]);
+eval_dupbag([]=L, [O | Old], New, No) ->
+ eval_dupbag(L, Old, [O | New], No);
+eval_dupbag([], [], New, No) ->
+ {ok, New, No}.
+
+old_dup_object(L, [{Term,_,_} = Obj | Old], New, No, Term, N) ->
+ old_dup_object(L, Old, New, No, Term, [Obj | N]);
+old_dup_object(L, Old, New, No, Term, N) ->
+ dup_object(L, Old, New, No, Term, N).
+
+dup_object([{Term,_,insert} = Obj | L], Old, New, No, Term, Q) ->
+ dup_object(L, Old, New, No+1, Term, [Obj | Q]);
+dup_object([{Term,_Seq,delete_object} | L], Old, New, No, Term, Q) ->
+ %% All objects are deleted.
+ NewNo = No - length(Q),
+ dup_object(L, Old, New, NewNo, Term, []);
+dup_object(L, Old, New, No, _Term, Q) ->
+ eval_dupbag(L, Old, Q ++ New, No).
+
+%% Update no_keys on the file too, if the number of segments that
+%% dets:fsck/6 uses for estimate has changed.
+update_no_keys(Head, Ws, 0, 0) -> {Head, Ws};
+update_no_keys(Head, Ws, DeltaObjects, DeltaKeys) ->
+ NoKeys = Head#head.no_keys,
+ NewNoKeys = NoKeys + DeltaKeys,
+ NewNoObject = Head#head.no_objects + DeltaObjects,
+ NewHead = Head#head{no_objects = NewNoObject, no_keys = NewNoKeys},
+ NWs =
+ if
+ NewNoKeys > NewHead#head.max_no_slots ->
+ Ws;
+ NoKeys div ?SEGSZP =:= NewNoKeys div ?SEGSZP ->
+ Ws;
+ true ->
+ [{0, file_header(NewHead, 0, ?NOT_PROPERLY_CLOSED)} | Ws]
+ end,
+ {NewHead, NWs}.
+
+slot_position(S) ->
+ SegNo = ?SLOT2SEG(S), % S div ?SEGSZP
+ PartPos = ?SEGARRADDR(?SEG2SEGARRPART(SegNo)), % SegNo div ?SEGPARTSZ
+ Part = get_arrpart(PartPos),
+ Pos = ?SEGPARTADDR(Part, SegNo),
+ get_segp(Pos) + (?SEGOBJSZ * ?REM2(S, ?SEGSZP)).
+
+check_pread2_arg([{_Pos,Sz}], Head) when Sz > ?MAXCOLL ->
+ case check_pread_arg(Sz, Head) of
+ true ->
+ ok;
+ false ->
+ Bad = dets_utils:bad_object(check_pread2_arg, Sz),
+ throw(dets_utils:corrupt_reason(Head, Bad))
+ end;
+check_pread2_arg(_ToRead, _Head) ->
+ ok.
+
+check_pread_arg(Sz, Head) when Sz > ?MAXCOLL ->
+ maxobjsize(Head) >= Sz;
+check_pread_arg(_Sz, _Head) ->
+ true.
+
+%% Inlined.
+segp_cache(Pos, Segment) ->
+ put(Pos, Segment).
+
+%% Inlined.
+get_segp(Pos) ->
+ get(Pos).
+
+arrpart_cache(Pos, ArrPart) ->
+ put(Pos, ArrPart).
+
+%% Inlined.
+get_arrpart(Pos) ->
+ get(Pos).
+
+sz2pos(N) ->
+ 1 + dets_utils:log2(N).
+
+%% Inlined. Compensates for the bug in dets_utils:sz2pos/1.
+adjsz(N) ->
+ N-1.
+
+%% Inlined.
+maxobjsize(Head) when Head#head.maxobjsize =:= undefined ->
+ ?POW(32);
+maxobjsize(Head) ->
+ ?POW(Head#head.maxobjsize).
+
+scan_objs(Head, Bin, From, To, L, Ts, R, Type) ->
+ case catch scan_skip(Bin, From, To, L, Ts, R, Type, 0) of
+ {'EXIT', _Reason} ->
+ bad_object;
+ Reply = {more, _From1, _To, _L, _Ts, _R, Size} when Size > ?MAXCOLL ->
+ case check_pread_arg(Size, Head) of
+ true -> Reply;
+ false -> bad_object
+ end;
+ Reply ->
+ Reply
+ end.
+
+scan_skip(Bin, From, To, L, Ts, R, Type, Skip) ->
+ From1 = From + Skip,
+ case Bin of
+ _ when From1 >= To ->
+ if
+ From1 > To; L =:= <<>> ->
+ {more, From1, To, L, Ts, R, 0};
+ true ->
+ <<From2:32, To1:32, L1/binary>> = L,
+ Skip1 = From2 - From,
+ scan_skip(Bin, From, To1, L1, Ts, R, Type, Skip1)
+ end;
+ <<_:Skip/binary, _Size:32, St:32, _Sz:32, KO/binary>>
+ when St =/= ?ACTIVE, St =/= ?FREE ->
+ %% Neither ?ACTIVE nor ?FREE is a multiple of ?BUMP and
+ %% thus cannot be found in segments or segment array
+ %% parts.
+ scan_skip(KO, From1+12, To, L, Ts, R, Type, ?ACTUAL_SEG_SIZE-12);
+ <<_:Skip/binary, Size:32, _St:32, Sz:32, KO/binary>>
+ when Size-12 =< byte_size(KO) ->
+ %% St = ?FREE means that the object was deleted after
+ %% scanning started
+ bin2bins(KO, From1+12, To, L, Ts, R, Type, Size, Sz);
+ <<_:Skip/binary, Size:32, _St:32, _Sz:32, _KO/binary>> ->
+ {more, From1, To, L, Ts, R, Size};
+ _ when Skip >= 0 ->
+ {more, From1, To, L, Ts, R, 0}
+ end.
+
+%% Appends objects in reversed order. All objects of the slot are
+%% extracted. Note that binary_to_term/1 ignores garbage at the end.
+bin2bins(Bin, From, To, L, Ts, R, Type=set, Size, ObjSz0) ->
+ ObjsSz1 = Size - ObjSz0,
+ if
+ ObjsSz1 =:= ?OHDSZ ->
+ slot_end(Bin, From, To, L, [Bin | Ts], R, Type, Size, 1);
+ true ->
+ ObjSz = ObjSz0-4,
+ <<_:ObjSz/binary, NObjSz:32, T/binary>> = Bin,
+ bins_set(T, From, To, L, [Bin | Ts], R, Type, Size, 2,
+ NObjSz, ObjsSz1-NObjSz, Bin)
+ end;
+bin2bins(<<ObjSz:32, Bin/binary>> = KO, From, To, L, Ts, R, Type, Size, Sz) ->
+ bins_bag(Bin, From, To, L, Ts, R, Type, Size, 1,
+ Sz-ObjSz-4, ObjSz-4, Size-Sz, KO).
+
+bins_set(Bin, From, To, L, Ts, R, Type, Size, NoObjs, _ObjSz0, ?OHDSZ, KO) ->
+ slot_end(KO, From, To, L, [Bin | Ts], R, Type, Size, NoObjs);
+bins_set(Bin, From, To, L, Ts, R, Type, Size, NoObjs, ObjSz0, ObjsSz, KO) ->
+ ObjSz = ObjSz0 - 4,
+ <<_:ObjSz/binary, NObjSz:32, T/binary>> = Bin,
+ bins_set(T, From, To, L, [Bin | Ts], R, Type, Size, NoObjs + 1,
+ NObjSz, ObjsSz-NObjSz, KO).
+
+bins_bag(Bin, From, To, L, Ts, R, Type, Size, NoObjs, Sz, ObjSz, ObjsSz, KO)
+ when Sz > 0 ->
+ <<_:ObjSz/binary, NObjSz:32, T/binary>> = Bin,
+ bins_bag(T, From, To, L, [Bin | Ts], R, Type, Size, NoObjs + 1,
+ Sz-NObjSz, NObjSz-4, ObjsSz, KO);
+bins_bag(Bin, From, To, L, Ts, R, Type, Size, NoObjs, _Z, _ObjSz, ?OHDSZ, KO) ->
+ slot_end(KO, From, To, L, [Bin | Ts], R, Type, Size, NoObjs);
+bins_bag(Bin, From, To, L, Ts, R, Type, Size, NoObjs, _Z, ObjSz, ObjsSz, KO) ->
+ <<_:ObjSz/binary, Sz:32, NObjSz:32, T/binary>> = Bin,
+ bins_bag(T, From, To, L, [Bin | Ts], R, Type, Size, NoObjs + 1,
+ Sz-NObjSz-4, NObjSz-4, ObjsSz-Sz, KO).
+
+slot_end(KO, From, To, L, Ts, R, Type, Size, NoObjs) ->
+ Skip = ?POW(dets_utils:log2(Size)) - 12, % expensive...
+ if
+ R >= 0 ->
+ scan_skip(KO, From, To, L, Ts, R+Size, Type, Skip);
+ true ->
+ %% Should check this at the end of every key.
+ case R + NoObjs of
+ R1 when R1 >= -1 ->
+ From1 = From + Skip,
+ Bin1 = case KO of
+ <<_:Skip/binary, B/binary>> -> B;
+ _ -> <<>>
+ end,
+ {stop, Bin1, From1, To, L, Ts};
+ R1 ->
+ scan_skip(KO, From, To, L, Ts, R1, Type, Skip)
+ end
+ end.
+
+%%%%%%%%%%%%%%%%% DEBUG functions %%%%%%%%%%%%%%%%
+
+file_info(FH) ->
+ #fileheader{closed_properly = CP, keypos = Kp,
+ m = M, next = Next, n = N, version = Version,
+ type = Type, no_objects = NoObjects, no_keys = NoKeys}
+ = FH,
+ if
+ CP =:= 0 ->
+ {error, not_closed};
+ FH#fileheader.cookie =/= ?MAGIC ->
+ {error, not_a_dets_file};
+ FH#fileheader.version =/= ?FILE_FORMAT_VERSION ->
+ {error, bad_version};
+ true ->
+ {ok, [{closed_properly,CP},{keypos,Kp},{m, M},{n,N},
+ {next,Next},{no_objects,NoObjects},{no_keys,NoKeys},
+ {type,Type},{version,Version}]}
+ end.
+
+v_segments(#head{}=H) ->
+ v_parts(H, 0, 0).
+
+v_parts(_H, ?SEGARRSZ, _SegNo) ->
+ done;
+v_parts(H, PartNo, SegNo) ->
+ Fd = H#head.fptr,
+ PartPos = dets_utils:read_4(Fd, ?SEGARRADDR(PartNo)),
+ if
+ PartPos =:= 0 ->
+ done;
+ true ->
+ PartBin = dets_utils:pread_n(Fd, PartPos, ?SEGPARTSZ*4),
+ v_segments(H, PartBin, PartNo+1, SegNo)
+ end.
+
+v_segments(H, <<>>, PartNo, SegNo) ->
+ v_parts(H, PartNo, SegNo);
+v_segments(_H, <<0:32,_/binary>>, _PartNo, _SegNo) ->
+ done;
+v_segments(H, <<Seg:32,T/binary>>, PartNo, SegNo) ->
+ io:format("<~w>SEGMENT ~w~n", [Seg, SegNo]),
+ v_segment(H, SegNo, Seg, 0),
+ v_segments(H, T, PartNo, SegNo+1).
+
+v_segment(_H, _, _SegPos, ?SEGSZP) ->
+ done;
+v_segment(H, SegNo, SegPos, SegSlot) ->
+ Slot = SegSlot + (SegNo * ?SEGSZP),
+ BucketP = SegPos + (4 * ?SZOBJP * SegSlot),
+ case catch read_bucket(H, BucketP, H#head.type) of
+ {'EXIT', Reason} ->
+ dets_utils:vformat("** dets: Corrupt or truncated dets file~n",
+ []),
+ io:format("~nERROR ~p~n", [Reason]);
+ [] -> %% don't print empty buckets
+ true;
+ {Size, CollP, Objects} ->
+ io:format(" <~w>~w: <~w:~p>~w~n",
+ [BucketP, Slot, CollP, Size, Objects])
+ end,
+ v_segment(H, SegNo, SegPos, SegSlot+1).
+
+%% -> [] | {Pointer, [object()]} | throw(EXIT)
+read_bucket(Head, Position, Type) ->
+ MaxSize = maxobjsize(Head),
+ case dets_utils:ipread(Head, Position, MaxSize) of
+ {ok, {Size, Pointer, <<Size:32, _Status:32, KeysObjs/binary>>}} ->
+ Objs = bin2objs(KeysObjs, Type, []),
+ {Size, Pointer, lists:reverse(Objs)};
+ [] ->
+ []
+ end.
+
+-define(SEQSTART, -(1 bsl 26)).
+
+%% -> [{Key,SizeOfWholeKey,WholeKeyBin,FirstObject,OtherObjects}] |throw(EXIT)
+%% FirstObject = {Term, Seq, Binary}
+%% Seq < 0 (and ascending).
+per_key(Head, <<BinSize:32, ?ACTIVE:32, Bin/binary>> = B) ->
+ true = (byte_size(B) =:= BinSize),
+ if
+ Head#head.type =:= set ->
+ per_set_key(Bin, Head#head.keypos, []);
+ true ->
+ per_bag_key(Bin, Head#head.keypos, [])
+ end.
+
+per_set_key(<<Size:32, T/binary>> = B, KeyPos, L) ->
+ <<KeyBin:Size/binary, R/binary>> = B,
+ Term = binary_to_term(T),
+ Key = element(KeyPos, Term),
+ Item = {Term, ?SEQSTART, KeyBin},
+ per_set_key(R, KeyPos, [{Key,Size,KeyBin,Item,[]} | L]);
+per_set_key(<<>>, KeyPos, L) when is_integer(KeyPos) ->
+ lists:reverse(L).
+
+per_bag_key(<<Size:32, ObjSz:32, T/binary>> = B, KeyPos, L) ->
+ <<KeyBin:Size/binary, R/binary>> = B,
+ ObjSz1 = ObjSz - 4,
+ Size1 = Size - ObjSz - 4,
+ <<_:ObjSz1/binary, KeyObjs:Size1/binary, _/binary>> = T,
+ <<_Size:32, Bin:ObjSz/binary, _/binary>> = B,
+ Term = binary_to_term(T),
+ Key = element(KeyPos, Term),
+ Item = {Term, ?SEQSTART, Bin},
+ per_bag_key(R, KeyPos, [{Key,Size,KeyBin,Item,KeyObjs} | L]);
+per_bag_key(<<>>, KeyPos, L) when is_integer(KeyPos) ->
+ lists:reverse(L).
+
+
+binobjs2terms(<<ObjSz:32, T/binary>> = B) ->
+ binobjs2terms(B, T, ObjSz, byte_size(B)-ObjSz, ?SEQSTART+1, []);
+binobjs2terms([] = B) ->
+ B;
+binobjs2terms(<<>>) ->
+ [].
+
+binobjs2terms(Bin, Obj, _ObjSz, _Size=0, N, L) ->
+ lists:reverse(L, [{binary_to_term(Obj), N, Bin}]);
+binobjs2terms(Bin, Bin1, ObjSz, Size, N, L) ->
+ <<B:ObjSz/binary, T/binary>> = Bin,
+ <<NObjSz:32, T1/binary>> = T,
+ Item = {binary_to_term(Bin1), N, B},
+ binobjs2terms(T, T1, NObjSz, Size-NObjSz, N+1, [Item | L]).
+
+
+%% Appends objects in reversed order.
+bin2objs(KeysObjs, set, Ts) ->
+ <<ObjSz:32, T/binary>> = KeysObjs,
+ bin2objs(T, ObjSz-4, byte_size(KeysObjs)-ObjSz, Ts);
+bin2objs(KeysObjs, _Type, Ts) ->
+ bin2objs2(KeysObjs, Ts).
+
+bin2objs2(<<Size:32, ObjSz:32, T/binary>>, Ts) ->
+ bin2objs(T, ObjSz-4, Size-ObjSz-4, Ts);
+bin2objs2(<<>>, Ts) ->
+ Ts.
+
+bin2objs(Bin, ObjSz, _Size=0, Ts) ->
+ <<_:ObjSz/binary, T/binary>> = Bin,
+ bin2objs2(T, [binary_to_term(Bin) | Ts]);
+bin2objs(Bin, ObjSz, Size, Ts) ->
+ <<_:ObjSz/binary, NObjSz:32, T/binary>> = Bin,
+ bin2objs(T, NObjSz-4, Size-NObjSz, [binary_to_term(Bin) | Ts]).
+
+
+bin2keybins(KeysObjs, Head) when Head#head.type =:= set ->
+ <<ObjSz:32, T/binary>> = KeysObjs,
+ bin2keybins(T, Head#head.keypos, ObjSz-4, byte_size(KeysObjs)-ObjSz,[]);
+bin2keybins(KeysObjs, Head) ->
+ bin2keybins2(KeysObjs, Head#head.keypos, []).
+
+bin2keybins2(<<Size:32, ObjSz:32, T/binary>>, Kp, L) ->
+ bin2keybins(T, Kp, ObjSz-4, Size-ObjSz-4, L);
+bin2keybins2(<<>>, Kp, L) when is_integer(Kp) ->
+ lists:reverse(L).
+
+bin2keybins(Bin, Kp, ObjSz, _Size=0, L) ->
+ <<Obj:ObjSz/binary, T/binary>> = Bin,
+ Term = binary_to_term(Obj),
+ bin2keybins2(T, Kp, [{element(Kp, Term),Obj} | L]);
+bin2keybins(Bin, Kp, ObjSz, Size, L) ->
+ <<Obj:ObjSz/binary, NObjSz:32, T/binary>> = Bin,
+ Term = binary_to_term(Obj),
+ bin2keybins(T, Kp, NObjSz-4, Size-NObjSz, [{element(Kp,Term),Obj} | L]).
diff --git a/lib/stdlib/src/dict.erl b/lib/stdlib/src/dict.erl
new file mode 100644
index 0000000000..7e51141098
--- /dev/null
+++ b/lib/stdlib/src/dict.erl
@@ -0,0 +1,547 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% We use the dynamic hashing techniques by Per-�ke Larsson as
+%% described in "The Design and Implementation of Dynamic Hashing for
+%% Sets and Tables in Icon" by Griswold and Townsend. Much of the
+%% terminology comes from that paper as well.
+%%
+%% The segments are all of the same fixed size and we just keep
+%% increasing the size of the top tuple as the table grows. At the
+%% end of the segments tuple we keep an empty segment which we use
+%% when we expand the segments. The segments are expanded by doubling
+%% every time n reaches maxn instead of increasing the tuple one
+%% element at a time. It is easier and does not seem detrimental to
+%% speed. The same applies when contracting the segments.
+%%
+%% Note that as the order of the keys is undefined we may freely
+%% reorder keys within a bucket.
+
+-module(dict).
+
+%% Standard interface.
+-export([new/0,is_key/2,to_list/1,from_list/1,size/1]).
+-export([fetch/2,find/2,fetch_keys/1,erase/2]).
+-export([store/3,append/3,append_list/3,update/3,update/4,update_counter/3]).
+-export([fold/3,map/2,filter/2,merge/3]).
+
+%% Low-level interface.
+%%-export([get_slot/2,get_bucket/2,on_bucket/3,fold_dict/3,
+%% maybe_expand/2,maybe_contract/2]).
+
+%% Note: mk_seg/1 must be changed too if seg_size is changed.
+-define(seg_size, 16).
+-define(max_seg, 32).
+-define(expand_load, 5).
+-define(contract_load, 3).
+-define(exp_size, (?seg_size * ?expand_load)).
+-define(con_size, (?seg_size * ?contract_load)).
+
+%% Define a hashtable. The default values are the standard ones.
+-record(dict,
+ {size=0 :: non_neg_integer(), % Number of elements
+ n=?seg_size :: non_neg_integer(), % Number of active slots
+ maxn=?seg_size :: non_neg_integer(), % Maximum slots
+ bso=?seg_size div 2 :: non_neg_integer(), % Buddy slot offset
+ exp_size=?exp_size :: non_neg_integer(), % Size to expand at
+ con_size=?con_size :: non_neg_integer(), % Size to contract at
+ empty :: tuple(), % Empty segment
+ segs :: tuple() % Segments
+ }).
+%% A declaration equivalent to the following one is hard-coded in erl_types.
+%% That declaration contains hard-coded information about the #dict{}
+%% structure and the types of its fields. So, please make sure that any
+%% changes to its structure are also propagated to erl_types.erl.
+%%
+%% -opaque dict() :: #dict{}.
+
+-define(kv(K,V), [K|V]). % Key-Value pair format
+%%-define(kv(K,V), {K,V}). % Key-Value pair format
+
+-spec new() -> dict().
+
+new() ->
+ Empty = mk_seg(?seg_size),
+ #dict{empty=Empty,segs={Empty}}.
+
+-spec is_key(term(), dict()) -> boolean().
+
+is_key(Key, D) ->
+ Slot = get_slot(D, Key),
+ Bkt = get_bucket(D, Slot),
+ find_key(Key, Bkt).
+
+find_key(K, [?kv(K,_Val)|_]) -> true;
+find_key(K, [_|Bkt]) -> find_key(K, Bkt);
+find_key(_, []) -> false.
+
+-spec to_list(dict()) -> [{term(), term()}].
+
+to_list(D) ->
+ fold(fun (Key, Val, List) -> [{Key,Val}|List] end, [], D).
+
+-spec from_list([{term(), term()}]) -> dict().
+
+from_list(L) ->
+ lists:foldl(fun ({K,V}, D) -> store(K, V, D) end, new(), L).
+
+-spec size(dict()) -> non_neg_integer().
+
+size(#dict{size=N}) when is_integer(N), N >= 0 -> N.
+
+-spec fetch(term(), dict()) -> term().
+
+fetch(Key, D) ->
+ Slot = get_slot(D, Key),
+ Bkt = get_bucket(D, Slot),
+ try fetch_val(Key, Bkt)
+ catch
+ badarg -> erlang:error(badarg, [Key, D])
+ end.
+
+fetch_val(K, [?kv(K,Val)|_]) -> Val;
+fetch_val(K, [_|Bkt]) -> fetch_val(K, Bkt);
+fetch_val(_, []) -> throw(badarg).
+
+-spec find(term(), dict()) -> {'ok', term()} | 'error'.
+
+find(Key, D) ->
+ Slot = get_slot(D, Key),
+ Bkt = get_bucket(D, Slot),
+ find_val(Key, Bkt).
+
+find_val(K, [?kv(K,Val)|_]) -> {ok,Val};
+find_val(K, [_|Bkt]) -> find_val(K, Bkt);
+find_val(_, []) -> error.
+
+-spec fetch_keys(dict()) -> [term()].
+
+fetch_keys(D) ->
+ fold(fun (Key, _Val, Keys) -> [Key|Keys] end, [], D).
+
+-spec erase(term(), dict()) -> dict().
+%% Erase all elements with key Key.
+
+erase(Key, D0) ->
+ Slot = get_slot(D0, Key),
+ {D1,Dc} = on_bucket(fun (B0) -> erase_key(Key, B0) end,
+ D0, Slot),
+ maybe_contract(D1, Dc).
+
+erase_key(Key, [?kv(Key,_Val)|Bkt]) -> {Bkt,1};
+erase_key(Key, [E|Bkt0]) ->
+ {Bkt1,Dc} = erase_key(Key, Bkt0),
+ {[E|Bkt1],Dc};
+erase_key(_, []) -> {[],0}.
+
+-spec store(term(), term(), dict()) -> dict().
+
+store(Key, Val, D0) ->
+ Slot = get_slot(D0, Key),
+ {D1,Ic} = on_bucket(fun (B0) -> store_bkt_val(Key, Val, B0) end,
+ D0, Slot),
+ maybe_expand(D1, Ic).
+
+%% store_bkt_val(Key, Val, Bucket) -> {NewBucket,PutCount}.
+
+store_bkt_val(Key, New, [?kv(Key,_Old)|Bkt]) -> {[?kv(Key,New)|Bkt],0};
+store_bkt_val(Key, New, [Other|Bkt0]) ->
+ {Bkt1,Ic} = store_bkt_val(Key, New, Bkt0),
+ {[Other|Bkt1],Ic};
+store_bkt_val(Key, New, []) -> {[?kv(Key,New)],1}.
+
+-spec append(term(), term(), dict()) -> dict().
+
+append(Key, Val, D0) ->
+ Slot = get_slot(D0, Key),
+ {D1,Ic} = on_bucket(fun (B0) -> append_bkt(Key, Val, B0) end,
+ D0, Slot),
+ maybe_expand(D1, Ic).
+
+%% append_bkt(Key, Val, Bucket) -> {NewBucket,PutCount}.
+
+append_bkt(Key, Val, [?kv(Key,Bag)|Bkt]) -> {[?kv(Key,Bag ++ [Val])|Bkt],0};
+append_bkt(Key, Val, [Other|Bkt0]) ->
+ {Bkt1,Ic} = append_bkt(Key, Val, Bkt0),
+ {[Other|Bkt1],Ic};
+append_bkt(Key, Val, []) -> {[?kv(Key,[Val])],1}.
+
+-spec append_list(term(), [term()], dict()) -> dict().
+
+append_list(Key, L, D0) ->
+ Slot = get_slot(D0, Key),
+ {D1,Ic} = on_bucket(fun (B0) -> app_list_bkt(Key, L, B0) end,
+ D0, Slot),
+ maybe_expand(D1, Ic).
+
+%% app_list_bkt(Key, L, Bucket) -> {NewBucket,PutCount}.
+
+app_list_bkt(Key, L, [?kv(Key,Bag)|Bkt]) -> {[?kv(Key,Bag ++ L)|Bkt],0};
+app_list_bkt(Key, L, [Other|Bkt0]) ->
+ {Bkt1,Ic} = app_list_bkt(Key, L, Bkt0),
+ {[Other|Bkt1],Ic};
+app_list_bkt(Key, L, []) -> {[?kv(Key,L)],1}.
+
+%% %% first_key(Table) -> {ok,Key} | error.
+%% %% Find the "first" key in a Table.
+
+%% first_key(T) ->
+%% case next_bucket(T, 1) of
+%% [?kv(K,Val)|Bkt] -> {ok,K};
+%% [] -> error %No elements
+%% end.
+
+%% next_bucket(T, Slot) when Slot > T#dict.n -> [];
+%% next_bucket(T, Slot) ->
+%% case get_bucket(T, Slot) of
+%% [] -> next_bucket(T, Slot+1); %Empty bucket
+%% B -> B
+%% end.
+
+%% %% next_key(Table, Key) -> {ok,NextKey} | error.
+
+%% next_key(T, Key) ->
+%% Slot = get_slot(T, Key),
+%% B = get_bucket(T, Slot),
+%% %% Find a bucket with something in it.
+%% Bkt = case bucket_after_key(Key, B) of
+%% no_key -> exit(badarg);
+%% [] -> next_bucket(T, Slot+1);
+%% Rest -> Rest
+%% end,
+%% case Bkt of
+%% [?kv(Next,Val)|_] -> {ok,Next};
+%% [] -> error %We have reached the end!
+%% end.
+
+%% bucket_after_key(Key, [?kv(Key,Val)|Bkt]) -> Bkt;
+%% bucket_after_key(Key, [Other|Bkt]) ->
+%% bucket_after_key(Key, Bkt);
+%% bucket_after_key(Key, []) -> no_key. %Key not found!
+
+%% %% on_key(Fun, Key, Dictionary) -> Dictionary.
+
+%% on_key(F, Key, D0) ->
+%% Slot = get_slot(D0, Key),
+%% {D1,Dc} = on_bucket(fun (B0) -> on_key_bkt(Key, F, B0) end,
+%% D0, Slot),
+%% maybe_contract(D1, Dc).
+
+%% on_key_bkt(Key, F, [?kv(Key,Val)|Bkt]) ->
+%% case F(Val) of
+%% {ok,New} -> {[?kv(Key,New)|Bkt],0};
+%% erase -> {Bkt,1}
+%% end;
+%% on_key_bkt(Key, F, [Other|Bkt0]) ->
+%% {Bkt1,Dc} = on_key_bkt(Key, F, Bkt0),
+%% {[Other|Bkt1],Dc}.
+
+-spec update(term(), fun((term()) -> term()), dict()) -> dict().
+
+update(Key, F, D0) ->
+ Slot = get_slot(D0, Key),
+ try on_bucket(fun (B0) -> update_bkt(Key, F, B0) end, D0, Slot) of
+ {D1,_Uv} -> D1
+ catch
+ badarg -> erlang:error(badarg, [Key, F, D0])
+ end.
+
+update_bkt(Key, F, [?kv(Key,Val)|Bkt]) ->
+ Upd = F(Val),
+ {[?kv(Key,Upd)|Bkt],Upd};
+update_bkt(Key, F, [Other|Bkt0]) ->
+ {Bkt1,Upd} = update_bkt(Key, F, Bkt0),
+ {[Other|Bkt1],Upd};
+update_bkt(_Key, _F, []) ->
+ throw(badarg).
+
+-spec update(term(), fun((term()) -> term()), term(), dict()) -> dict().
+
+update(Key, F, Init, D0) ->
+ Slot = get_slot(D0, Key),
+ {D1,Ic} = on_bucket(fun (B0) -> update_bkt(Key, F, Init, B0) end,
+ D0, Slot),
+ maybe_expand(D1, Ic).
+
+update_bkt(Key, F, _, [?kv(Key,Val)|Bkt]) ->
+ {[?kv(Key,F(Val))|Bkt],0};
+update_bkt(Key, F, I, [Other|Bkt0]) ->
+ {Bkt1,Ic} = update_bkt(Key, F, I, Bkt0),
+ {[Other|Bkt1],Ic};
+update_bkt(Key, F, I, []) when is_function(F, 1) -> {[?kv(Key,I)],1}.
+
+-spec update_counter(term(), number(), dict()) -> dict().
+
+update_counter(Key, Incr, D0) when is_number(Incr) ->
+ Slot = get_slot(D0, Key),
+ {D1,Ic} = on_bucket(fun (B0) -> counter_bkt(Key, Incr, B0) end,
+ D0, Slot),
+ maybe_expand(D1, Ic).
+
+counter_bkt(Key, I, [?kv(Key,Val)|Bkt]) ->
+ {[?kv(Key,Val+I)|Bkt],0};
+counter_bkt(Key, I, [Other|Bkt0]) ->
+ {Bkt1,Ic} = counter_bkt(Key, I, Bkt0),
+ {[Other|Bkt1],Ic};
+counter_bkt(Key, I, []) -> {[?kv(Key,I)],1}.
+
+-spec fold(fun((term(), term(), term()) -> term()), term(), dict()) -> term().
+%% Fold function Fun over all "bags" in Table and return Accumulator.
+
+fold(F, Acc, D) -> fold_dict(F, Acc, D).
+
+-spec map(fun((term(), term()) -> term()), dict()) -> dict().
+
+map(F, D) -> map_dict(F, D).
+
+-spec filter(fun((term(), term()) -> boolean()), dict()) -> dict().
+
+filter(F, D) -> filter_dict(F, D).
+
+-spec merge(fun((term(), term(), term()) -> term()), dict(), dict()) -> dict().
+
+merge(F, D1, D2) when D1#dict.size < D2#dict.size ->
+ fold_dict(fun (K, V1, D) ->
+ update(K, fun (V2) -> F(K, V1, V2) end, V1, D)
+ end, D2, D1);
+merge(F, D1, D2) ->
+ fold_dict(fun (K, V2, D) ->
+ update(K, fun (V1) -> F(K, V1, V2) end, V2, D)
+ end, D1, D2).
+
+
+%% get_slot(Hashdb, Key) -> Slot.
+%% Get the slot. First hash on the new range, if we hit a bucket
+%% which has not been split use the unsplit buddy bucket.
+
+get_slot(T, Key) ->
+ H = erlang:phash(Key, T#dict.maxn),
+ if
+ H > T#dict.n -> H - T#dict.bso;
+ true -> H
+ end.
+
+%% get_bucket(Hashdb, Slot) -> Bucket.
+
+get_bucket(T, Slot) -> get_bucket_s(T#dict.segs, Slot).
+
+%% on_bucket(Fun, Hashdb, Slot) -> {NewHashDb,Result}.
+%% Apply Fun to the bucket in Slot and replace the returned bucket.
+
+on_bucket(F, T, Slot) ->
+ SegI = ((Slot-1) div ?seg_size) + 1,
+ BktI = ((Slot-1) rem ?seg_size) + 1,
+ Segs = T#dict.segs,
+ Seg = element(SegI, Segs),
+ B0 = element(BktI, Seg),
+ {B1,Res} = F(B0), %Op on the bucket.
+ {T#dict{segs=setelement(SegI, Segs, setelement(BktI, Seg, B1))},Res}.
+
+%% fold_dict(Fun, Acc, Dictionary) -> Acc.
+%% map_dict(Fun, Dictionary) -> Dictionary.
+%% filter_dict(Fun, Dictionary) -> Dictionary.
+%%
+%% Work functions for fold, map and filter operations. These
+%% traverse the hash structure rebuilding as necessary. Note we
+%% could have implemented map and filter using fold but these are
+%% faster. We hope!
+
+fold_dict(F, Acc, D) ->
+ Segs = D#dict.segs,
+ fold_segs(F, Acc, Segs, tuple_size(Segs)).
+
+fold_segs(F, Acc, Segs, I) when I >= 1 ->
+ Seg = element(I, Segs),
+ fold_segs(F, fold_seg(F, Acc, Seg, tuple_size(Seg)), Segs, I-1);
+fold_segs(F, Acc, _, 0) when is_function(F, 3) -> Acc.
+
+fold_seg(F, Acc, Seg, I) when I >= 1 ->
+ fold_seg(F, fold_bucket(F, Acc, element(I, Seg)), Seg, I-1);
+fold_seg(F, Acc, _, 0) when is_function(F, 3) -> Acc.
+
+fold_bucket(F, Acc, [?kv(Key,Val)|Bkt]) ->
+ fold_bucket(F, F(Key, Val, Acc), Bkt);
+fold_bucket(F, Acc, []) when is_function(F, 3) -> Acc.
+
+map_dict(F, D) ->
+ Segs0 = tuple_to_list(D#dict.segs),
+ Segs1 = map_seg_list(F, Segs0),
+ D#dict{segs=list_to_tuple(Segs1)}.
+
+map_seg_list(F, [Seg|Segs]) ->
+ Bkts0 = tuple_to_list(Seg),
+ Bkts1 = map_bkt_list(F, Bkts0),
+ [list_to_tuple(Bkts1)|map_seg_list(F, Segs)];
+map_seg_list(F, []) when is_function(F, 2) -> [].
+
+map_bkt_list(F, [Bkt0|Bkts]) ->
+ [map_bucket(F, Bkt0)|map_bkt_list(F, Bkts)];
+map_bkt_list(F, []) when is_function(F, 2) -> [].
+
+map_bucket(F, [?kv(Key,Val)|Bkt]) ->
+ [?kv(Key,F(Key, Val))|map_bucket(F, Bkt)];
+map_bucket(F, []) when is_function(F, 2) -> [].
+
+filter_dict(F, D) ->
+ Segs0 = tuple_to_list(D#dict.segs),
+ {Segs1,Fc} = filter_seg_list(F, Segs0, [], 0),
+ maybe_contract(D#dict{segs=list_to_tuple(Segs1)}, Fc).
+
+filter_seg_list(F, [Seg|Segs], Fss, Fc0) ->
+ Bkts0 = tuple_to_list(Seg),
+ {Bkts1,Fc1} = filter_bkt_list(F, Bkts0, [], Fc0),
+ filter_seg_list(F, Segs, [list_to_tuple(Bkts1)|Fss], Fc1);
+filter_seg_list(F, [], Fss, Fc) when is_function(F, 2) ->
+ {lists:reverse(Fss, []),Fc}.
+
+filter_bkt_list(F, [Bkt0|Bkts], Fbs, Fc0) ->
+ {Bkt1,Fc1} = filter_bucket(F, Bkt0, [], Fc0),
+ filter_bkt_list(F, Bkts, [Bkt1|Fbs], Fc1);
+filter_bkt_list(F, [], Fbs, Fc) when is_function(F, 2) ->
+ {lists:reverse(Fbs),Fc}.
+
+filter_bucket(F, [?kv(Key,Val)=E|Bkt], Fb, Fc) ->
+ case F(Key, Val) of
+ true -> filter_bucket(F, Bkt, [E|Fb], Fc);
+ false -> filter_bucket(F, Bkt, Fb, Fc+1)
+ end;
+filter_bucket(F, [], Fb, Fc) when is_function(F, 2) ->
+ {lists:reverse(Fb),Fc}.
+
+%% get_bucket_s(Segments, Slot) -> Bucket.
+%% put_bucket_s(Segments, Slot, Bucket) -> NewSegments.
+
+get_bucket_s(Segs, Slot) ->
+ SegI = ((Slot-1) div ?seg_size) + 1,
+ BktI = ((Slot-1) rem ?seg_size) + 1,
+ element(BktI, element(SegI, Segs)).
+
+put_bucket_s(Segs, Slot, Bkt) ->
+ SegI = ((Slot-1) div ?seg_size) + 1,
+ BktI = ((Slot-1) rem ?seg_size) + 1,
+ Seg = setelement(BktI, element(SegI, Segs), Bkt),
+ setelement(SegI, Segs, Seg).
+
+%% In maybe_expand(), the variable Ic only takes the values 0 or 1,
+%% but type inference is not strong enough to infer this. Thus, the
+%% use of explicit pattern matching and an auxiliary function.
+
+maybe_expand(T, 0) -> maybe_expand_aux(T, 0);
+maybe_expand(T, 1) -> maybe_expand_aux(T, 1).
+
+maybe_expand_aux(T0, Ic) when T0#dict.size + Ic > T0#dict.exp_size ->
+ T = maybe_expand_segs(T0), %Do we need more segments.
+ N = T#dict.n + 1, %Next slot to expand into
+ Segs0 = T#dict.segs,
+ Slot1 = N - T#dict.bso,
+ B = get_bucket_s(Segs0, Slot1),
+ Slot2 = N,
+ [B1|B2] = rehash(B, Slot1, Slot2, T#dict.maxn),
+ Segs1 = put_bucket_s(Segs0, Slot1, B1),
+ Segs2 = put_bucket_s(Segs1, Slot2, B2),
+ T#dict{size=T#dict.size + Ic,
+ n=N,
+ exp_size=N * ?expand_load,
+ con_size=N * ?contract_load,
+ segs=Segs2};
+maybe_expand_aux(T, Ic) -> T#dict{size=T#dict.size + Ic}.
+
+maybe_expand_segs(T) when T#dict.n =:= T#dict.maxn ->
+ T#dict{maxn=2 * T#dict.maxn,
+ bso=2 * T#dict.bso,
+ segs=expand_segs(T#dict.segs, T#dict.empty)};
+maybe_expand_segs(T) -> T.
+
+maybe_contract(T, Dc) when T#dict.size - Dc < T#dict.con_size,
+ T#dict.n > ?seg_size ->
+ N = T#dict.n,
+ Slot1 = N - T#dict.bso,
+ Segs0 = T#dict.segs,
+ B1 = get_bucket_s(Segs0, Slot1),
+ Slot2 = N,
+ B2 = get_bucket_s(Segs0, Slot2),
+ Segs1 = put_bucket_s(Segs0, Slot1, B1 ++ B2),
+ Segs2 = put_bucket_s(Segs1, Slot2, []), %Clear the upper bucket
+ N1 = N - 1,
+ maybe_contract_segs(T#dict{size=T#dict.size - Dc,
+ n=N1,
+ exp_size=N1 * ?expand_load,
+ con_size=N1 * ?contract_load,
+ segs=Segs2});
+maybe_contract(T, Dc) -> T#dict{size=T#dict.size - Dc}.
+
+maybe_contract_segs(T) when T#dict.n =:= T#dict.bso ->
+ T#dict{maxn=T#dict.maxn div 2,
+ bso=T#dict.bso div 2,
+ segs=contract_segs(T#dict.segs)};
+maybe_contract_segs(T) -> T.
+
+%% rehash(Bucket, Slot1, Slot2, MaxN) -> [Bucket1|Bucket2].
+%% Yes, we should return a tuple, but this is more fun.
+
+rehash([?kv(Key,_Bag)=KeyBag|T], Slot1, Slot2, MaxN) ->
+ [L1|L2] = rehash(T, Slot1, Slot2, MaxN),
+ case erlang:phash(Key, MaxN) of
+ Slot1 -> [[KeyBag|L1]|L2];
+ Slot2 -> [L1|[KeyBag|L2]]
+ end;
+rehash([], _Slot1, _Slot2, _MaxN) -> [[]|[]].
+
+%% mk_seg(Size) -> Segment.
+
+mk_seg(16) -> {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}.
+
+%% expand_segs(Segs, EmptySeg) -> NewSegs.
+%% contract_segs(Segs) -> NewSegs.
+%% Expand/contract the segment tuple by doubling/halving the number
+%% of segments. We special case the powers of 2 upto 32, this should
+%% catch most case. N.B. the last element in the segments tuple is
+%% an extra element containing a default empty segment.
+
+expand_segs({B1}, Empty) ->
+ {B1,Empty};
+expand_segs({B1,B2}, Empty) ->
+ {B1,B2,Empty,Empty};
+expand_segs({B1,B2,B3,B4}, Empty) ->
+ {B1,B2,B3,B4,Empty,Empty,Empty,Empty};
+expand_segs({B1,B2,B3,B4,B5,B6,B7,B8}, Empty) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8,
+ Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty};
+expand_segs({B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16}, Empty) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16,
+ Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,
+ Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty};
+expand_segs(Segs, Empty) ->
+ list_to_tuple(tuple_to_list(Segs)
+ ++ lists:duplicate(tuple_size(Segs), Empty)).
+
+contract_segs({B1,_}) ->
+ {B1};
+contract_segs({B1,B2,_,_}) ->
+ {B1,B2};
+contract_segs({B1,B2,B3,B4,_,_,_,_}) ->
+ {B1,B2,B3,B4};
+contract_segs({B1,B2,B3,B4,B5,B6,B7,B8,_,_,_,_,_,_,_,_}) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8};
+contract_segs({B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16,
+ _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_}) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16};
+contract_segs(Segs) ->
+ Ss = tuple_size(Segs) div 2,
+ list_to_tuple(lists:sublist(tuple_to_list(Segs), 1, Ss)).
diff --git a/lib/stdlib/src/digraph.erl b/lib/stdlib/src/digraph.erl
new file mode 100644
index 0000000000..9bdea671a9
--- /dev/null
+++ b/lib/stdlib/src/digraph.erl
@@ -0,0 +1,570 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(digraph).
+
+-export([new/0, new/1, delete/1, info/1]).
+
+-export([add_vertex/1, add_vertex/2, add_vertex/3]).
+-export([del_vertex/2, del_vertices/2]).
+-export([vertex/2, no_vertices/1, vertices/1]).
+-export([source_vertices/1, sink_vertices/1]).
+
+-export([add_edge/3, add_edge/4, add_edge/5]).
+-export([del_edge/2, del_edges/2, del_path/3]).
+-export([edge/2, no_edges/1, edges/1]).
+
+-export([out_neighbours/2, in_neighbours/2]).
+-export([out_edges/2, in_edges/2, edges/2]).
+-export([out_degree/2, in_degree/2]).
+-export([get_path/3, get_cycle/2]).
+
+-export([get_short_path/3, get_short_cycle/2]).
+
+-record(digraph, {vtab = notable :: ets:tab(),
+ etab = notable :: ets:tab(),
+ ntab = notable :: ets:tab(),
+ cyclic = true :: boolean()}).
+%% A declaration equivalent to the following one is hard-coded in erl_types.
+%% That declaration contains hard-coded information about the #digraph{}
+%% record and the types of its fields. So, please make sure that any
+%% changes to its structure are also propagated to erl_types.erl.
+%%
+%% -opaque digraph() :: #digraph{}.
+
+-type edge() :: term().
+-type label() :: term().
+-type vertex() :: term().
+
+-type add_edge_err_rsn() :: {'bad_edge', [vertex()]} | {'bad_vertex', vertex()}.
+
+%%
+%% Type is a list of
+%% protected | private
+%% acyclic | cyclic
+%%
+%% default is [cyclic,protected]
+%%
+-type d_protection() :: 'private' | 'protected'.
+-type d_cyclicity() :: 'acyclic' | 'cyclic'.
+-type d_type() :: d_cyclicity() | d_protection().
+
+-spec new() -> digraph().
+
+new() -> new([]).
+
+-spec new([d_type()]) -> digraph().
+
+new(Type) ->
+ case check_type(Type, protected, []) of
+ {Access, Ts} ->
+ V = ets:new(vertices, [set, Access]),
+ E = ets:new(edges, [set, Access]),
+ N = ets:new(neighbours, [bag, Access]),
+ ets:insert(N, [{'$vid', 0}, {'$eid', 0}]),
+ set_type(Ts, #digraph{vtab=V, etab=E, ntab=N});
+ error ->
+ erlang:error(badarg)
+ end.
+
+%%
+%% Check type of graph
+%%
+%-spec check_type([d_type()], d_protection(), [{'cyclic', boolean()}]) ->
+% {d_protection(), [{'cyclic', boolean()}]}.
+
+check_type([acyclic|Ts], A, L) ->
+ check_type(Ts, A,[{cyclic,false} | L]);
+check_type([cyclic | Ts], A, L) ->
+ check_type(Ts, A, [{cyclic,true} | L]);
+check_type([protected | Ts], _, L) ->
+ check_type(Ts, protected, L);
+check_type([private | Ts], _, L) ->
+ check_type(Ts, private, L);
+check_type([], A, L) -> {A, L};
+check_type(_, _, _) -> error.
+
+%%
+%% Set graph type
+%%
+-spec set_type([{'cyclic', boolean()}], digraph()) -> digraph().
+
+set_type([{cyclic,V} | Ks], G) ->
+ set_type(Ks, G#digraph{cyclic = V});
+set_type([], G) -> G.
+
+
+%% Data access functions
+
+-spec delete(digraph()) -> 'true'.
+
+delete(G) ->
+ ets:delete(G#digraph.vtab),
+ ets:delete(G#digraph.etab),
+ ets:delete(G#digraph.ntab).
+
+-spec info(digraph()) -> [{'cyclicity', d_cyclicity()} |
+ {'memory', non_neg_integer()} |
+ {'protection', d_protection()}].
+info(G) ->
+ VT = G#digraph.vtab,
+ ET = G#digraph.etab,
+ NT = G#digraph.ntab,
+ Cyclicity = case G#digraph.cyclic of
+ true -> cyclic;
+ false -> acyclic
+ end,
+ Protection = ets:info(VT, protection),
+ Memory = ets:info(VT, memory) + ets:info(ET, memory) + ets:info(NT, memory),
+ [{cyclicity, Cyclicity}, {memory, Memory}, {protection, Protection}].
+
+-spec add_vertex(digraph()) -> vertex().
+
+add_vertex(G) ->
+ do_add_vertex({new_vertex_id(G), []}, G).
+
+-spec add_vertex(digraph(), vertex()) -> vertex().
+
+add_vertex(G, V) ->
+ do_add_vertex({V, []}, G).
+
+-spec add_vertex(digraph(), vertex(), label()) -> vertex().
+
+add_vertex(G, V, D) ->
+ do_add_vertex({V, D}, G).
+
+-spec del_vertex(digraph(), vertex()) -> 'true'.
+
+del_vertex(G, V) ->
+ do_del_vertex(V, G).
+
+-spec del_vertices(digraph(), [vertex()]) -> 'true'.
+
+del_vertices(G, Vs) ->
+ do_del_vertices(Vs, G).
+
+-spec vertex(digraph(), vertex()) -> {vertex(), label()} | 'false'.
+
+vertex(G, V) ->
+ case ets:lookup(G#digraph.vtab, V) of
+ [] -> false;
+ [Vertex] -> Vertex
+ end.
+
+-spec no_vertices(digraph()) -> non_neg_integer().
+
+no_vertices(G) ->
+ ets:info(G#digraph.vtab, size).
+
+-spec vertices(digraph()) -> [vertex()].
+
+vertices(G) ->
+ ets:select(G#digraph.vtab, [{{'$1', '_'}, [], ['$1']}]).
+
+-spec source_vertices(digraph()) -> [vertex()].
+
+source_vertices(G) ->
+ collect_vertices(G, in).
+
+-spec sink_vertices(digraph()) -> [vertex()].
+
+sink_vertices(G) ->
+ collect_vertices(G, out).
+
+-spec in_degree(digraph(), vertex()) -> non_neg_integer().
+
+in_degree(G, V) ->
+ length(ets:lookup(G#digraph.ntab, {in, V})).
+
+-spec in_neighbours(digraph(), vertex()) -> [vertex()].
+
+in_neighbours(G, V) ->
+ ET = G#digraph.etab,
+ NT = G#digraph.ntab,
+ collect_elems(ets:lookup(NT, {in, V}), ET, 2).
+
+-spec in_edges(digraph(), vertex()) -> [edge()].
+
+in_edges(G, V) ->
+ ets:select(G#digraph.ntab, [{{{in, V}, '$1'}, [], ['$1']}]).
+
+-spec out_degree(digraph(), vertex()) -> non_neg_integer().
+
+out_degree(G, V) ->
+ length(ets:lookup(G#digraph.ntab, {out, V})).
+
+-spec out_neighbours(digraph(), vertex()) -> [vertex()].
+
+out_neighbours(G, V) ->
+ ET = G#digraph.etab,
+ NT = G#digraph.ntab,
+ collect_elems(ets:lookup(NT, {out, V}), ET, 3).
+
+-spec out_edges(digraph(), vertex()) -> [edge()].
+
+out_edges(G, V) ->
+ ets:select(G#digraph.ntab, [{{{out, V}, '$1'}, [], ['$1']}]).
+
+-spec add_edge(digraph(), vertex(), vertex()) ->
+ edge() | {'error', add_edge_err_rsn()}.
+
+add_edge(G, V1, V2) ->
+ do_add_edge({new_edge_id(G), V1, V2, []}, G).
+
+-spec add_edge(digraph(), vertex(), vertex(), label()) ->
+ edge() | {'error', add_edge_err_rsn()}.
+
+add_edge(G, V1, V2, D) ->
+ do_add_edge({new_edge_id(G), V1, V2, D}, G).
+
+-spec add_edge(digraph(), edge(), vertex(), vertex(), label()) ->
+ edge() | {'error', add_edge_err_rsn()}.
+
+add_edge(G, E, V1, V2, D) ->
+ do_add_edge({E, V1, V2, D}, G).
+
+-spec del_edge(digraph(), edge()) -> 'true'.
+
+del_edge(G, E) ->
+ do_del_edges([E], G).
+
+-spec del_edges(digraph(), [edge()]) -> 'true'.
+
+del_edges(G, Es) ->
+ do_del_edges(Es, G).
+
+-spec no_edges(digraph()) -> non_neg_integer().
+
+no_edges(G) ->
+ ets:info(G#digraph.etab, size).
+
+-spec edges(digraph()) -> [edge()].
+
+edges(G) ->
+ ets:select(G#digraph.etab, [{{'$1', '_', '_', '_'}, [], ['$1']}]).
+
+-spec edges(digraph(), vertex()) -> [edge()].
+
+edges(G, V) ->
+ ets:select(G#digraph.ntab, [{{{out, V},'$1'}, [], ['$1']},
+ {{{in, V}, '$1'}, [], ['$1']}]).
+
+-spec edge(digraph(), edge()) -> {edge(),vertex(),vertex(),label()} | 'false'.
+
+edge(G, E) ->
+ case ets:lookup(G#digraph.etab,E) of
+ [] -> false;
+ [Edge] -> Edge
+ end.
+
+%%
+%% Generate a "unique" edge identifier (relative to this graph)
+%%
+-spec new_edge_id(digraph()) -> nonempty_improper_list('$e', non_neg_integer()).
+
+new_edge_id(G) ->
+ NT = G#digraph.ntab,
+ [{'$eid', K}] = ets:lookup(NT, '$eid'),
+ true = ets:delete(NT, '$eid'),
+ true = ets:insert(NT, {'$eid', K+1}),
+ ['$e' | K].
+
+%%
+%% Generate a "unique" vertex identifier (relative to this graph)
+%%
+-spec new_vertex_id(digraph()) -> nonempty_improper_list('$v', non_neg_integer()).
+
+new_vertex_id(G) ->
+ NT = G#digraph.ntab,
+ [{'$vid', K}] = ets:lookup(NT, '$vid'),
+ true = ets:delete(NT, '$vid'),
+ true = ets:insert(NT, {'$vid', K+1}),
+ ['$v' | K].
+
+%%
+%% Collect elements for a index in a tuple
+%%
+collect_elems(Keys, Table, Index) ->
+ collect_elems(Keys, Table, Index, []).
+
+collect_elems([{_,Key}|Keys], Table, Index, Acc) ->
+ collect_elems(Keys, Table, Index,
+ [ets:lookup_element(Table, Key, Index)|Acc]);
+collect_elems([], _, _, Acc) -> Acc.
+
+-spec do_add_vertex({vertex(), label()}, digraph()) -> vertex().
+
+do_add_vertex({V, _Label} = VL, G) ->
+ ets:insert(G#digraph.vtab, VL),
+ V.
+
+%%
+%% Collect either source or sink vertices.
+%%
+collect_vertices(G, Type) ->
+ Vs = vertices(G),
+ lists:foldl(fun(V, A) ->
+ case ets:member(G#digraph.ntab, {Type, V}) of
+ true -> A;
+ false -> [V|A]
+ end
+ end, [], Vs).
+
+%%
+%% Delete vertices
+%%
+do_del_vertices([V | Vs], G) ->
+ do_del_vertex(V, G),
+ do_del_vertices(Vs, G);
+do_del_vertices([], #digraph{}) -> true.
+
+do_del_vertex(V, G) ->
+ do_del_nedges(ets:lookup(G#digraph.ntab, {in, V}), G),
+ do_del_nedges(ets:lookup(G#digraph.ntab, {out, V}), G),
+ ets:delete(G#digraph.vtab, V).
+
+do_del_nedges([{_, E}|Ns], G) ->
+ case ets:lookup(G#digraph.etab, E) of
+ [{E, V1, V2, _}] ->
+ do_del_edge(E, V1, V2, G),
+ do_del_nedges(Ns, G);
+ [] -> % cannot happen
+ do_del_nedges(Ns, G)
+ end;
+do_del_nedges([], #digraph{}) -> true.
+
+%%
+%% Delete edges
+%%
+do_del_edges([E|Es], G) ->
+ case ets:lookup(G#digraph.etab, E) of
+ [{E,V1,V2,_}] ->
+ do_del_edge(E,V1,V2,G),
+ do_del_edges(Es, G);
+ [] ->
+ do_del_edges(Es, G)
+ end;
+do_del_edges([], #digraph{}) -> true.
+
+do_del_edge(E, V1, V2, G) ->
+ ets:select_delete(G#digraph.ntab, [{{{in, V2}, E}, [], [true]},
+ {{{out,V1}, E}, [], [true]}]),
+ ets:delete(G#digraph.etab, E).
+
+-spec rm_edges([vertex(),...], digraph()) -> 'true'.
+
+rm_edges([V1, V2|Vs], G) ->
+ rm_edge(V1, V2, G),
+ rm_edges([V2|Vs], G);
+rm_edges(_, _) -> true.
+
+-spec rm_edge(vertex(), vertex(), digraph()) -> 'ok'.
+
+rm_edge(V1, V2, G) ->
+ Es = out_edges(G, V1),
+ rm_edge_0(Es, V1, V2, G).
+
+rm_edge_0([E|Es], V1, V2, G) ->
+ case ets:lookup(G#digraph.etab, E) of
+ [{E, V1, V2, _}] ->
+ do_del_edge(E, V1, V2, G),
+ rm_edge_0(Es, V1, V2, G);
+ _ ->
+ rm_edge_0(Es, V1, V2, G)
+ end;
+rm_edge_0([], _, _, #digraph{}) -> ok.
+
+%%
+%% Check that endpoints exist
+%%
+-spec do_add_edge({edge(), vertex(), vertex(), label()}, digraph()) ->
+ edge() | {'error', add_edge_err_rsn()}.
+
+do_add_edge({E, V1, V2, Label}, G) ->
+ case ets:member(G#digraph.vtab, V1) of
+ false -> {error, {bad_vertex, V1}};
+ true ->
+ case ets:member(G#digraph.vtab, V2) of
+ false -> {error, {bad_vertex, V2}};
+ true ->
+ case other_edge_exists(G, E, V1, V2) of
+ true -> {error, {bad_edge, [V1, V2]}};
+ false when G#digraph.cyclic =:= false ->
+ acyclic_add_edge(E, V1, V2, Label, G);
+ false ->
+ do_insert_edge(E, V1, V2, Label, G)
+ end
+ end
+ end.
+
+other_edge_exists(#digraph{etab = ET}, E, V1, V2) ->
+ case ets:lookup(ET, E) of
+ [{E, Vert1, Vert2, _}] when Vert1 =/= V1; Vert2 =/= V2 ->
+ true;
+ _ ->
+ false
+ end.
+
+-spec do_insert_edge(edge(), vertex(), vertex(), label(), digraph()) -> edge().
+
+do_insert_edge(E, V1, V2, Label, #digraph{ntab=NT, etab=ET}) ->
+ ets:insert(NT, [{{out, V1}, E}, {{in, V2}, E}]),
+ ets:insert(ET, {E, V1, V2, Label}),
+ E.
+
+-spec acyclic_add_edge(edge(), vertex(), vertex(), label(), digraph()) ->
+ edge() | {'error', {'bad_edge', [vertex()]}}.
+
+acyclic_add_edge(_E, V1, V2, _L, _G) when V1 =:= V2 ->
+ {error, {bad_edge, [V1, V2]}};
+acyclic_add_edge(E, V1, V2, Label, G) ->
+ case get_path(G, V2, V1) of
+ false -> do_insert_edge(E, V1, V2, Label, G);
+ Path -> {error, {bad_edge, Path}}
+ end.
+
+%%
+%% Delete all paths from vertex V1 to vertex V2
+%%
+
+-spec del_path(digraph(), vertex(), vertex()) -> 'true'.
+
+del_path(G, V1, V2) ->
+ case get_path(G, V1, V2) of
+ false -> true;
+ Path ->
+ rm_edges(Path, G),
+ del_path(G, V1, V2)
+ end.
+
+%%
+%% Find a cycle through V
+%% return the cycle as list of vertices [V ... V]
+%% if no cycle exists false is returned
+%% if only a cycle of length one exists it will be
+%% returned as [V] but only after longer cycles have
+%% been searched.
+%%
+
+-spec get_cycle(digraph(), vertex()) -> [vertex(),...] | 'false'.
+
+get_cycle(G, V) ->
+ case one_path(out_neighbours(G, V), V, [], [V], [V], 2, G, 1) of
+ false ->
+ case lists:member(V, out_neighbours(G, V)) of
+ true -> [V];
+ false -> false
+ end;
+ Vs -> Vs
+ end.
+
+%%
+%% Find a path from V1 to V2
+%% return the path as list of vertices [V1 ... V2]
+%% if no path exists false is returned
+%%
+
+-spec get_path(digraph(), vertex(), vertex()) -> [vertex(),...] | 'false'.
+
+get_path(G, V1, V2) ->
+ one_path(out_neighbours(G, V1), V2, [], [V1], [V1], 1, G, 1).
+
+%%
+%% prune_short_path (evaluate conditions on path)
+%% short : if path is too short
+%% ok : if path is ok
+%%
+prune_short_path(Counter, Min) when Counter < Min ->
+ short;
+prune_short_path(_Counter, _Min) ->
+ ok.
+
+one_path([W|Ws], W, Cont, Xs, Ps, Prune, G, Counter) ->
+ case prune_short_path(Counter, Prune) of
+ short -> one_path(Ws, W, Cont, Xs, Ps, Prune, G, Counter);
+ ok -> lists:reverse([W|Ps])
+ end;
+one_path([V|Vs], W, Cont, Xs, Ps, Prune, G, Counter) ->
+ case lists:member(V, Xs) of
+ true -> one_path(Vs, W, Cont, Xs, Ps, Prune, G, Counter);
+ false -> one_path(out_neighbours(G, V), W,
+ [{Vs,Ps} | Cont], [V|Xs], [V|Ps],
+ Prune, G, Counter+1)
+ end;
+one_path([], W, [{Vs,Ps}|Cont], Xs, _, Prune, G, Counter) ->
+ one_path(Vs, W, Cont, Xs, Ps, Prune, G, Counter-1);
+one_path([], _, [], _, _, _, _, _Counter) -> false.
+
+%%
+%% Like get_cycle/2, but a cycle of length one is preferred.
+%%
+
+-spec get_short_cycle(digraph(), vertex()) -> [vertex(),...] | 'false'.
+
+get_short_cycle(G, V) ->
+ get_short_path(G, V, V).
+
+%%
+%% Like get_path/3, but using a breadth-first search makes it possible
+%% to find a short path.
+%%
+
+-spec get_short_path(digraph(), vertex(), vertex()) -> [vertex(),...] | 'false'.
+
+get_short_path(G, V1, V2) ->
+ T = new(),
+ add_vertex(T, V1),
+ Q = queue:new(),
+ Q1 = queue_out_neighbours(V1, G, Q),
+ L = spath(Q1, G, V2, T),
+ delete(T),
+ L.
+
+spath(Q, G, Sink, T) ->
+ case queue:out(Q) of
+ {{value, E}, Q1} ->
+ {_E, V1, V2, _Label} = edge(G, E),
+ if
+ Sink =:= V2 ->
+ follow_path(V1, T, [V2]);
+ true ->
+ case vertex(T, V2) of
+ false ->
+ add_vertex(T, V2),
+ add_edge(T, V2, V1),
+ NQ = queue_out_neighbours(V2, G, Q1),
+ spath(NQ, G, Sink, T);
+ _V ->
+ spath(Q1, G, Sink, T)
+ end
+ end;
+ {empty, _Q1} ->
+ false
+ end.
+
+follow_path(V, T, P) ->
+ P1 = [V | P],
+ case out_neighbours(T, V) of
+ [N] ->
+ follow_path(N, T, P1);
+ [] ->
+ P1
+ end.
+
+queue_out_neighbours(V, G, Q0) ->
+ lists:foldl(fun(E, Q) -> queue:in(E, Q) end, Q0, out_edges(G, V)).
diff --git a/lib/stdlib/src/digraph_utils.erl b/lib/stdlib/src/digraph_utils.erl
new file mode 100644
index 0000000000..080cae4742
--- /dev/null
+++ b/lib/stdlib/src/digraph_utils.erl
@@ -0,0 +1,338 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(digraph_utils).
+
+%%% Operations on directed (and undirected) graphs.
+%%%
+%%% Implementation based on Launchbury, John: Graph Algorithms with a
+%%% Functional Flavour, in Jeuring, Johan, and Meijer, Erik (Eds.):
+%%% Advanced Functional Programming, Lecture Notes in Computer
+%%% Science 925, Springer Verlag, 1995.
+
+-export([components/1, strong_components/1, cyclic_strong_components/1,
+ reachable/2, reachable_neighbours/2,
+ reaching/2, reaching_neighbours/2,
+ topsort/1, is_acyclic/1,
+ arborescence_root/1, is_arborescence/1, is_tree/1,
+ loop_vertices/1,
+ subgraph/2, subgraph/3, condensation/1,
+ preorder/1, postorder/1]).
+
+%%
+%% A convenient type alias
+%%
+
+-type vertices() :: [digraph:vertex()].
+
+%%
+%% Exported functions
+%%
+
+-spec components(digraph()) -> vertices().
+
+components(G) ->
+ forest(G, fun inout/3).
+
+-spec strong_components(digraph()) -> vertices().
+
+strong_components(G) ->
+ forest(G, fun in/3, revpostorder(G)).
+
+-spec cyclic_strong_components(digraph()) -> vertices().
+
+cyclic_strong_components(G) ->
+ remove_singletons(strong_components(G), G, []).
+
+-spec reachable(vertices(), digraph()) -> vertices().
+
+reachable(Vs, G) when is_list(Vs) ->
+ lists:append(forest(G, fun out/3, Vs, first)).
+
+-spec reachable_neighbours(vertices(), digraph()) -> vertices().
+
+reachable_neighbours(Vs, G) when is_list(Vs) ->
+ lists:append(forest(G, fun out/3, Vs, not_first)).
+
+-spec reaching(vertices(), digraph()) -> vertices().
+
+reaching(Vs, G) when is_list(Vs) ->
+ lists:append(forest(G, fun in/3, Vs, first)).
+
+-spec reaching_neighbours(vertices(), digraph()) -> vertices().
+
+reaching_neighbours(Vs, G) when is_list(Vs) ->
+ lists:append(forest(G, fun in/3, Vs, not_first)).
+
+-spec topsort(digraph()) -> vertices() | 'false'.
+
+topsort(G) ->
+ L = revpostorder(G),
+ case length(forest(G, fun in/3, L)) =:= length(digraph:vertices(G)) of
+ true -> L;
+ false -> false
+ end.
+
+-spec is_acyclic(digraph()) -> boolean().
+
+is_acyclic(G) ->
+ loop_vertices(G) =:= [] andalso topsort(G) =/= false.
+
+-spec arborescence_root(digraph()) -> 'no' | {'yes', digraph:vertex()}.
+
+arborescence_root(G) ->
+ case digraph:no_edges(G) =:= digraph:no_vertices(G) - 1 of
+ true ->
+ try
+ F = fun(V, Z) ->
+ case digraph:in_degree(G, V) of
+ 1 -> Z;
+ 0 when Z =:= [] -> [V]
+ end
+ end,
+ [Root] = lists:foldl(F, [], digraph:vertices(G)),
+ {yes, Root}
+ catch _:_ ->
+ no
+ end;
+ false ->
+ no
+ end.
+
+-spec is_arborescence(digraph()) -> boolean().
+
+is_arborescence(G) ->
+ arborescence_root(G) =/= no.
+
+-spec is_tree(digraph()) -> boolean().
+
+is_tree(G) ->
+ (digraph:no_edges(G) =:= digraph:no_vertices(G) - 1)
+ andalso (length(components(G)) =:= 1).
+
+-spec loop_vertices(digraph()) -> vertices().
+
+loop_vertices(G) ->
+ [V || V <- digraph:vertices(G), is_reflexive_vertex(V, G)].
+
+-spec subgraph(digraph(), vertices()) -> digraph().
+
+subgraph(G, Vs) ->
+ try
+ subgraph_opts(G, Vs, [])
+ catch
+ throw:badarg ->
+ erlang:error(badarg)
+ end.
+
+-type option() :: {'type', 'inherit' | [digraph:d_type()]}
+ | {'keep_labels', boolean()}.
+
+-spec subgraph(digraph(), vertices(), [option()]) -> digraph().
+
+subgraph(G, Vs, Opts) ->
+ try
+ subgraph_opts(G, Vs, Opts)
+ catch
+ throw:badarg ->
+ erlang:error(badarg)
+ end.
+
+-spec condensation(digraph()) -> digraph().
+
+condensation(G) ->
+ SCs = strong_components(G),
+ %% Each component is assigned a number.
+ %% V2I: from vertex to number.
+ %% I2C: from number to component.
+ V2I = ets:new(condensation, []),
+ I2C = ets:new(condensation, []),
+ CFun = fun(SC, N) -> lists:foreach(fun(V) ->
+ true = ets:insert(V2I, {V,N})
+ end,
+ SC),
+ true = ets:insert(I2C, {N, SC}),
+ N + 1
+ end,
+ lists:foldl(CFun, 1, SCs),
+ SCG = subgraph_opts(G, [], []),
+ lists:foreach(fun(SC) -> condense(SC, G, SCG, V2I, I2C) end, SCs),
+ ets:delete(V2I),
+ ets:delete(I2C),
+ SCG.
+
+-spec preorder(digraph()) -> vertices().
+
+preorder(G) ->
+ lists:reverse(revpreorder(G)).
+
+-spec postorder(digraph()) -> vertices().
+
+postorder(G) ->
+ lists:reverse(revpostorder(G)).
+
+%%
+%% Local functions
+%%
+
+forest(G, SF) ->
+ forest(G, SF, digraph:vertices(G)).
+
+forest(G, SF, Vs) ->
+ forest(G, SF, Vs, first).
+
+forest(G, SF, Vs, HandleFirst) ->
+ T = ets:new(forest, [set]),
+ F = fun(V, LL) -> pretraverse(HandleFirst, V, SF, G, T, LL) end,
+ LL = lists:foldl(F, [], Vs),
+ ets:delete(T),
+ LL.
+
+pretraverse(first, V, SF, G, T, LL) ->
+ ptraverse([V], SF, G, T, [], LL);
+pretraverse(not_first, V, SF, G, T, LL) ->
+ case ets:member(T, V) of
+ false -> ptraverse(SF(G, V, []), SF, G, T, [], LL);
+ true -> LL
+ end.
+
+ptraverse([V | Vs], SF, G, T, Rs, LL) ->
+ case ets:member(T, V) of
+ false ->
+ ets:insert(T, {V}),
+ ptraverse(SF(G, V, Vs), SF, G, T, [V | Rs], LL);
+ true ->
+ ptraverse(Vs, SF, G, T, Rs, LL)
+ end;
+ptraverse([], _SF, _G, _T, [], LL) ->
+ LL;
+ptraverse([], _SF, _G, _T, Rs, LL) ->
+ [Rs | LL].
+
+revpreorder(G) ->
+ lists:append(forest(G, fun out/3)).
+
+revpostorder(G) ->
+ T = ets:new(forest, [set]),
+ L = posttraverse(digraph:vertices(G), G, T, []),
+ ets:delete(T),
+ L.
+
+posttraverse([V | Vs], G, T, L) ->
+ L1 = case ets:member(T, V) of
+ false ->
+ ets:insert(T, {V}),
+ [V | posttraverse(out(G, V, []), G, T, L)];
+ true ->
+ L
+ end,
+ posttraverse(Vs, G, T, L1);
+posttraverse([], _G, _T, L) ->
+ L.
+
+in(G, V, Vs) ->
+ digraph:in_neighbours(G, V) ++ Vs.
+
+out(G, V, Vs) ->
+ digraph:out_neighbours(G, V) ++ Vs.
+
+inout(G, V, Vs) ->
+ in(G, V, out(G, V, Vs)).
+
+remove_singletons([C=[V] | Cs], G, L) ->
+ case is_reflexive_vertex(V, G) of
+ true -> remove_singletons(Cs, G, [C | L]);
+ false -> remove_singletons(Cs, G, L)
+ end;
+remove_singletons([C | Cs], G, L) ->
+ remove_singletons(Cs, G, [C | L]);
+remove_singletons([], _G, L) ->
+ L.
+
+is_reflexive_vertex(V, G) ->
+ lists:member(V, digraph:out_neighbours(G, V)).
+
+subgraph_opts(G, Vs, Opts) ->
+ subgraph_opts(Opts, inherit, true, G, Vs).
+
+subgraph_opts([{type, Type} | Opts], _Type0, Keep, G, Vs)
+ when Type =:= inherit; is_list(Type) ->
+ subgraph_opts(Opts, Type, Keep, G, Vs);
+subgraph_opts([{keep_labels, Keep} | Opts], Type, _Keep0, G, Vs)
+ when is_boolean(Keep) ->
+ subgraph_opts(Opts, Type, Keep, G, Vs);
+subgraph_opts([], inherit, Keep, G, Vs) ->
+ Info = digraph:info(G),
+ {_, {_, Cyclicity}} = lists:keysearch(cyclicity, 1, Info),
+ {_, {_, Protection}} = lists:keysearch(protection, 1, Info),
+ subgraph(G, Vs, [Cyclicity, Protection], Keep);
+subgraph_opts([], Type, Keep, G, Vs) ->
+ subgraph(G, Vs, Type, Keep);
+subgraph_opts(_, _Type, _Keep, _G, _Vs) ->
+ throw(badarg).
+
+subgraph(G, Vs, Type, Keep) ->
+ try digraph:new(Type) of
+ SG ->
+ lists:foreach(fun(V) -> subgraph_vertex(V, G, SG, Keep) end, Vs),
+ EFun = fun(V) -> lists:foreach(fun(E) ->
+ subgraph_edge(E, G, SG, Keep)
+ end,
+ digraph:out_edges(G, V))
+ end,
+ lists:foreach(EFun, digraph:vertices(SG)),
+ SG
+ catch
+ error:badarg ->
+ throw(badarg)
+ end.
+
+subgraph_vertex(V, G, SG, Keep) ->
+ case digraph:vertex(G, V) of
+ false -> ok;
+ _ when not Keep -> digraph:add_vertex(SG, V);
+ {_V, Label} when Keep -> digraph:add_vertex(SG, V, Label)
+ end.
+
+subgraph_edge(E, G, SG, Keep) ->
+ {_E, V1, V2, Label} = digraph:edge(G, E),
+ case digraph:vertex(SG, V2) of
+ false -> ok;
+ _ when not Keep -> digraph:add_edge(SG, E, V1, V2, []);
+ _ when Keep -> digraph:add_edge(SG, E, V1, V2, Label)
+ end.
+
+condense(SC, G, SCG, V2I, I2C) ->
+ T = ets:new(condense, []),
+ NFun = fun(Neighbour) ->
+ [{_V,I}] = ets:lookup(V2I, Neighbour),
+ ets:insert(T, {I})
+ end,
+ VFun = fun(V) -> lists:foreach(NFun, digraph:out_neighbours(G, V)) end,
+ lists:foreach(VFun, SC),
+ digraph:add_vertex(SCG, SC),
+ condense(ets:first(T), T, SC, G, SCG, I2C),
+ ets:delete(T).
+
+condense('$end_of_table', _T, _SC, _G, _SCG, _I2C) ->
+ ok;
+condense(I, T, SC, G, SCG, I2C) ->
+ [{_,C}] = ets:lookup(I2C, I),
+ digraph:add_vertex(SCG, C),
+ digraph:add_edge(SCG, SC, C),
+ condense(ets:next(T, I), T, SC, G, SCG, I2C).
diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl
new file mode 100644
index 0000000000..31a653bda0
--- /dev/null
+++ b/lib/stdlib/src/edlin.erl
@@ -0,0 +1,575 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(edlin).
+
+%% A simple Emacs-like line editor.
+%% About Latin-1 characters: see the beginning of erl_scan.erl.
+
+-export([init/0,start/1,edit_line/2,prefix_arg/1]).
+-export([erase_line/1,erase_inp/1,redraw_line/1]).
+-export([length_before/1,length_after/1,prompt/1]).
+%%-export([expand/1]).
+
+-export([edit_line1/2]).
+
+-import(lists, [reverse/1, reverse/2]).
+
+%-import([nthtail/2, keysearch/3, prefix/2]).
+
+-export([over_word/3]).
+
+
+%% A Continuation has the structure:
+%% {line,Prompt,CurrentLine,EditPrefix}
+
+%% init()
+%% Initialise the line editor. This must be done once per process using
+%% the editor.
+
+init() ->
+ put(kill_buffer, []).
+
+%% start(Prompt)
+%% edit(Characters, Continuation)
+%% Return
+%% {done,Line,Rest,Requests}
+%% {more_chars,Cont,Requests}
+%% {blink,Cont,Requests}
+%% {undefined,Char,Rest,Cont,Requests}
+
+start(Pbs) ->
+ {more_chars,{line,Pbs,{[],[]},none},[{put_chars,unicode,Pbs}]}.
+
+edit_line(Cs, {line,P,L,{blink,N}}) ->
+ edit(Cs, P, L, none, [{move_rel,N}]);
+edit_line(Cs, {line,P,L,M}) ->
+ edit(Cs, P, L, M, []).
+
+edit_line1(Cs, {line,P,L,{blink,N}}) ->
+ edit(Cs, P, L, none, [{move_rel,N}]);
+edit_line1(Cs, {line,P,{[],[]},none}) ->
+ {more_chars, {line,P,{lists:reverse(Cs),[]},none},[{put_chars, unicode, Cs}]};
+edit_line1(Cs, {line,P,L,M}) ->
+ edit(Cs, P, L, M, []).
+
+edit([C|Cs], P, Line, {blink,_}, [_|Rs]) -> %Remove blink here
+ edit([C|Cs], P, Line, none, Rs);
+edit([C|Cs], P, {Bef,Aft}, Prefix, Rs0) ->
+ case key_map(C, Prefix) of
+ meta ->
+ edit(Cs, P, {Bef,Aft}, meta, Rs0);
+ meta_left_sq_bracket ->
+ edit(Cs, P, {Bef,Aft}, meta_left_sq_bracket, Rs0);
+ ctlx ->
+ edit(Cs, P, {Bef,Aft}, ctlx, Rs0);
+ new_line ->
+ {done, reverse(Bef, Aft ++ "\n"), Cs,
+ reverse(Rs0, [{move_rel,length(Aft)},{put_chars,unicode,"\n"}])};
+ redraw_line ->
+ Rs1 = erase(P, Bef, Aft, Rs0),
+ Rs = redraw(P, Bef, Aft, Rs1),
+ edit(Cs, P, {Bef,Aft}, none, Rs);
+ tab_expand ->
+ {expand, Bef, Cs,
+ {line, P, {Bef, Aft}, none},
+ reverse(Rs0)};
+
+%% tab ->
+%% %% Always redraw the line since expand/1 might have printed
+%% %% possible expansions.
+%% case expand(Bef) of
+%% {yes,Str} ->
+%% edit([redraw_line|
+%% (Str ++ Cs)], P, {Bef,Aft}, none, Rs0);
+%% no ->
+%% %% don't beep if there's only whitespace before
+%% %% us - user may have pasted in a lot of indented stuff.
+%% case whitespace_only(Bef) of
+%% false ->
+%% edit([redraw_line|Cs], P, {Bef,Aft}, none,
+%% [beep|Rs0]);
+%% true ->
+%% edit([redraw_line|Cs], P, {Bef,Aft}, none, [Rs0])
+%% end
+%% end;
+ {undefined,C} ->
+ {undefined,{none,Prefix,C},Cs,{line,P,{Bef,Aft},none},
+ reverse(Rs0)};
+ Op ->
+ case do_op(Op, Bef, Aft, Rs0) of
+ {blink,N,Line,Rs} ->
+ edit(Cs, P, Line, {blink,N}, Rs);
+ {Line,Rs} ->
+ edit(Cs, P, Line, none, Rs)
+ end
+ end;
+edit([], P, L, {blink,N}, Rs) ->
+ {blink,{line,P,L,{blink,N}},reverse(Rs)};
+edit([], P, L, Prefix, Rs) ->
+ {more_chars,{line,P,L,Prefix},reverse(Rs)};
+edit(eof, _, {Bef,Aft}, _, Rs) ->
+ {done,reverse(Bef, Aft),[],reverse(Rs, [{move_rel,length(Aft)}])}.
+
+%% %% Assumes that arg is a string
+%% %% Horizontal whitespace only.
+%% whitespace_only([]) ->
+%% true;
+%% whitespace_only([C|Rest]) ->
+%% case C of
+%% $\s ->
+%% whitespace_only(Rest);
+%% $\t ->
+%% whitespace_only(Rest);
+%% _ ->
+%% false
+%% end.
+
+%% prefix_arg(Argument)
+%% Take a prefix argument and return its numeric value.
+
+prefix_arg(none) -> 1;
+prefix_arg({ctlu,N}) -> N;
+prefix_arg(N) -> N.
+
+%% key_map(Char, Prefix)
+%% Map a character and a prefix to an action.
+
+key_map(A, _) when is_atom(A) -> A; % so we can push keywords
+key_map($\^A, none) -> beginning_of_line;
+key_map($\^B, none) -> backward_char;
+key_map($\^D, none) -> forward_delete_char;
+key_map($\^E, none) -> end_of_line;
+key_map($\^F, none) -> forward_char;
+key_map($\^H, none) -> backward_delete_char;
+key_map($\t, none) -> tab_expand;
+key_map($\^L, none) -> redraw_line;
+key_map($\n, none) -> new_line;
+key_map($\^K, none) -> kill_line;
+key_map($\r, none) -> new_line;
+key_map($\^T, none) -> transpose_char;
+key_map($\^U, none) -> ctlu;
+key_map($\^], none) -> auto_blink;
+key_map($\^X, none) -> ctlx;
+key_map($\^Y, none) -> yank;
+key_map($\e, none) -> meta;
+key_map($), Prefix) when Prefix =/= meta -> {blink,$),$(};
+key_map($}, Prefix) when Prefix =/= meta -> {blink,$},${};
+key_map($], Prefix) when Prefix =/= meta -> {blink,$],$[};
+key_map($B, meta) -> backward_word;
+key_map($D, meta) -> kill_word;
+key_map($F, meta) -> forward_word;
+key_map($T, meta) -> transpose_word;
+key_map($Y, meta) -> yank_pop;
+key_map($b, meta) -> backward_word;
+key_map($d, meta) -> kill_word;
+key_map($f, meta) -> forward_word;
+key_map($t, meta) -> transpose_word;
+key_map($y, meta) -> yank_pop;
+key_map($\177, none) -> backward_delete_char;
+key_map($\177, meta) -> backward_kill_word;
+key_map($[, meta) -> meta_left_sq_bracket;
+key_map($D, meta_left_sq_bracket) -> backward_char;
+key_map($C, meta_left_sq_bracket) -> forward_char;
+key_map(C, none) when C >= $\s ->
+ {insert,C};
+key_map(C, _) -> {undefined,C}.
+
+%% do_op(Action, Before, After, Requests)
+
+do_op({insert,C}, Bef, [], Rs) ->
+ {{[C|Bef],[]},[{put_chars, unicode,[C]}|Rs]};
+do_op({insert,C}, Bef, Aft, Rs) ->
+ {{[C|Bef],Aft},[{insert_chars, unicode, [C]}|Rs]};
+%% do blink after $$
+do_op({blink,C,M}, Bef=[$$,$$|_], Aft, Rs) ->
+ N = over_paren(Bef, C, M),
+ {blink,N+1,{[C|Bef],Aft},[{move_rel,-(N+1)},{insert_chars, unicode,[C]}|Rs]};
+%% don't blink after a $
+do_op({blink,C,_}, Bef=[$$|_], Aft, Rs) ->
+ do_op({insert,C}, Bef, Aft, Rs);
+%do_op({blink,C,M}, Bef, [], Rs) ->
+% N = over_paren(Bef, C, M),
+% {blink,N+1,{[C|Bef],[]},[{move_rel,-(N+1)},{put_chars,[C]}|Rs]};
+do_op({blink,C,M}, Bef, Aft, Rs) ->
+ case over_paren(Bef, C, M) of
+ beep ->
+ {{[C|Bef], Aft}, [beep,{insert_chars, unicode, [C]}|Rs]};
+ N -> {blink,N+1,{[C|Bef],Aft},
+ [{move_rel,-(N+1)},{insert_chars, unicode,[C]}|Rs]}
+ end;
+do_op(auto_blink, Bef, Aft, Rs) ->
+ case over_paren_auto(Bef) of
+ {N, Paren} ->
+ {blink,N+1,
+ {[Paren|Bef], Aft},[{move_rel,-(N+1)},{insert_chars, unicode,[Paren]}|Rs]};
+ % N is likely 0
+ N -> {blink,N+1,{Bef,Aft},
+ [{move_rel,-(N+1)}|Rs]}
+ end;
+do_op(forward_delete_char, Bef, [_|Aft], Rs) ->
+ {{Bef,Aft},[{delete_chars,1}|Rs]};
+do_op(backward_delete_char, [_|Bef], Aft, Rs) ->
+ {{Bef,Aft},[{delete_chars,-1}|Rs]};
+do_op(transpose_char, [C1,C2|Bef], [], Rs) ->
+ {{[C2,C1|Bef],[]},[{put_chars, unicode,[C1,C2]},{move_rel,-2}|Rs]};
+do_op(transpose_char, [C2|Bef], [C1|Aft], Rs) ->
+ {{[C2,C1|Bef],Aft},[{put_chars, unicode,[C1,C2]},{move_rel,-1}|Rs]};
+do_op(kill_word, Bef, Aft0, Rs) ->
+ {Aft1,Kill0,N0} = over_non_word(Aft0, [], 0),
+ {Aft,Kill,N} = over_word(Aft1, Kill0, N0),
+ put(kill_buffer, reverse(Kill)),
+ {{Bef,Aft},[{delete_chars,N}|Rs]};
+do_op(backward_kill_word, Bef0, Aft, Rs) ->
+ {Bef1,Kill0,N0} = over_non_word(Bef0, [], 0),
+ {Bef,Kill,N} = over_word(Bef1, Kill0, N0),
+ put(kill_buffer, Kill),
+ {{Bef,Aft},[{delete_chars,-N}|Rs]};
+do_op(kill_line, Bef, Aft, Rs) ->
+ put(kill_buffer, Aft),
+ {{Bef,[]},[{delete_chars,length(Aft)}|Rs]};
+do_op(yank, Bef, [], Rs) ->
+ Kill = get(kill_buffer),
+ {{reverse(Kill, Bef),[]},[{put_chars, unicode,Kill}|Rs]};
+do_op(yank, Bef, Aft, Rs) ->
+ Kill = get(kill_buffer),
+ {{reverse(Kill, Bef),Aft},[{insert_chars, unicode,Kill}|Rs]};
+do_op(forward_char, Bef, [C|Aft], Rs) ->
+ {{[C|Bef],Aft},[{move_rel,1}|Rs]};
+do_op(backward_char, [C|Bef], Aft, Rs) ->
+ {{Bef,[C|Aft]},[{move_rel,-1}|Rs]};
+do_op(forward_word, Bef0, Aft0, Rs) ->
+ {Aft1,Bef1,N0} = over_non_word(Aft0, Bef0, 0),
+ {Aft,Bef,N} = over_word(Aft1, Bef1, N0),
+ {{Bef,Aft},[{move_rel,N}|Rs]};
+do_op(backward_word, Bef0, Aft0, Rs) ->
+ {Bef1,Aft1,N0} = over_non_word(Bef0, Aft0, 0),
+ {Bef,Aft,N} = over_word(Bef1, Aft1, N0),
+ {{Bef,Aft},[{move_rel,-N}|Rs]};
+do_op(beginning_of_line, [C|Bef], Aft, Rs) ->
+ {{[],reverse(Bef, [C|Aft])},[{move_rel,-(length(Bef)+1)}|Rs]};
+do_op(beginning_of_line, [], Aft, Rs) ->
+ {{[],Aft},Rs};
+do_op(end_of_line, Bef, [C|Aft], Rs) ->
+ {{reverse(Aft, [C|Bef]),[]},[{move_rel,length(Aft)+1}|Rs]};
+do_op(end_of_line, Bef, [], Rs) ->
+ {{Bef,[]},Rs};
+do_op(beep, Bef, Aft, Rs) ->
+ {{Bef,Aft},[beep|Rs]};
+do_op(_, Bef, Aft, Rs) ->
+ {{Bef,Aft},[beep|Rs]}.
+
+%% over_word(Chars, InitialStack, InitialCount) ->
+%% {RemainingChars,CharStack,Count}
+%% over_non_word(Chars, InitialStack, InitialCount) ->
+%% {RemainingChars,CharStack,Count}
+%% Step over word/non-word characters pushing the stepped over ones on
+%% the stack.
+
+over_word([C|Cs], Stack, N) ->
+ case word_char(C) of
+ true -> over_word(Cs, [C|Stack], N+1);
+ false -> {[C|Cs],Stack,N}
+ end;
+over_word([], Stack, N) when is_integer(N) ->
+ {[],Stack,N}.
+
+over_non_word([C|Cs], Stack, N) ->
+ case word_char(C) of
+ true -> {[C|Cs],Stack,N};
+ false -> over_non_word(Cs, [C|Stack], N+1)
+ end;
+over_non_word([], Stack, N) ->
+ {[],Stack,N}.
+
+word_char(C) when C >= $A, C =< $Z -> true;
+word_char(C) when C >= $�, C =< $�, C =/= $� -> true;
+word_char(C) when C >= $a, C =< $z -> true;
+word_char(C) when C >= $�, C =< $�, C =/= $� -> true;
+word_char(C) when C >= $0, C =< $9 -> true;
+word_char(C) when C =:= $_ -> true;
+word_char(C) when C =:= $. -> true; % accept dot-separated names
+word_char(_) -> false.
+
+%% over_white(Chars, InitialStack, InitialCount) ->
+%% {RemainingChars,CharStack,Count}
+
+%% over_white([$\s|Cs], Stack, N) ->
+%% over_white(Cs, [$\s|Stack], N+1);
+%% over_white([$\t|Cs], Stack, N) ->
+%% over_white(Cs, [$\t|Stack], N+1);
+%% over_white(Cs, Stack, N) ->
+%% {Cs,Stack,N}.
+
+%% over_paren(Chars, Paren, Match)
+%% over_paren(Chars, Paren, Match, Depth, N)
+%% Step over parentheses until matching Paren is found at depth 0. Don't
+%% do proper parentheses matching check. Paren has NOT been added.
+
+over_paren(Chars, Paren, Match) ->
+ over_paren(Chars, Paren, Match, 1, 1, []).
+
+
+over_paren([C,$$,$$|Cs], Paren, Match, D, N, L) ->
+ over_paren([C|Cs], Paren, Match, D, N+2, L);
+over_paren([_,$$|Cs], Paren, Match, D, N, L) ->
+ over_paren(Cs, Paren, Match, D, N+2, L);
+over_paren([Match|_], _Paren, Match, 1, N, _) ->
+ N;
+over_paren([Match|Cs], Paren, Match, D, N, [Match|L]) ->
+ over_paren(Cs, Paren, Match, D-1, N+1, L);
+over_paren([Paren|Cs], Paren, Match, D, N, L) ->
+ over_paren(Cs, Paren, Match, D+1, N+1, [Match|L]);
+
+over_paren([$)|Cs], Paren, Match, D, N, L) ->
+ over_paren(Cs, Paren, Match, D, N+1, [$(|L]);
+over_paren([$]|Cs], Paren, Match, D, N, L) ->
+ over_paren(Cs, Paren, Match, D, N+1, [$[|L]);
+over_paren([$}|Cs], Paren, Match, D, N, L) ->
+ over_paren(Cs, Paren, Match, D, N+1, [${|L]);
+
+over_paren([$(|Cs], Paren, Match, D, N, [$(|L]) ->
+ over_paren(Cs, Paren, Match, D, N+1, L);
+over_paren([$[|Cs], Paren, Match, D, N, [$[|L]) ->
+ over_paren(Cs, Paren, Match, D, N+1, L);
+over_paren([${|Cs], Paren, Match, D, N, [${|L]) ->
+ over_paren(Cs, Paren, Match, D, N+1, L);
+
+over_paren([$(|_], _, _, _, _, _) ->
+ beep;
+over_paren([$[|_], _, _, _, _, _) ->
+ beep;
+over_paren([${|_], _, _, _, _, _) ->
+ beep;
+
+over_paren([_|Cs], Paren, Match, D, N, L) ->
+ over_paren(Cs, Paren, Match, D, N+1, L);
+over_paren([], _, _, _, _, _) ->
+ 0.
+
+over_paren_auto(Chars) ->
+ over_paren_auto(Chars, 1, 1, []).
+
+
+over_paren_auto([C,$$,$$|Cs], D, N, L) ->
+ over_paren_auto([C|Cs], D, N+2, L);
+over_paren_auto([_,$$|Cs], D, N, L) ->
+ over_paren_auto(Cs, D, N+2, L);
+
+over_paren_auto([$(|_], _, N, []) ->
+ {N, $)};
+over_paren_auto([$[|_], _, N, []) ->
+ {N, $]};
+over_paren_auto([${|_], _, N, []) ->
+ {N, $}};
+
+over_paren_auto([$)|Cs], D, N, L) ->
+ over_paren_auto(Cs, D, N+1, [$(|L]);
+over_paren_auto([$]|Cs], D, N, L) ->
+ over_paren_auto(Cs, D, N+1, [$[|L]);
+over_paren_auto([$}|Cs], D, N, L) ->
+ over_paren_auto(Cs, D, N+1, [${|L]);
+
+over_paren_auto([$(|Cs], D, N, [$(|L]) ->
+ over_paren_auto(Cs, D, N+1, L);
+over_paren_auto([$[|Cs], D, N, [$[|L]) ->
+ over_paren_auto(Cs, D, N+1, L);
+over_paren_auto([${|Cs], D, N, [${|L]) ->
+ over_paren_auto(Cs, D, N+1, L);
+
+over_paren_auto([_|Cs], D, N, L) ->
+ over_paren_auto(Cs, D, N+1, L);
+over_paren_auto([], _, _, _) ->
+ 0.
+
+%% erase_line(Line)
+%% erase_inp(Line)
+%% redraw_line(Line)
+%% length_before(Line)
+%% length_after(Line)
+%% prompt(Line)
+%% Various functions for accessing bits of a line.
+
+erase_line({line,Pbs,{Bef,Aft},_}) ->
+ reverse(erase(Pbs, Bef, Aft, [])).
+
+erase_inp({line,_,{Bef,Aft},_}) ->
+ reverse(erase([], Bef, Aft, [])).
+
+erase(Pbs, Bef, Aft, Rs) ->
+ [{delete_chars,-length(Pbs)-length(Bef)},{delete_chars,length(Aft)}|Rs].
+
+redraw_line({line,Pbs,{Bef,Aft},_}) ->
+ reverse(redraw(Pbs, Bef, Aft, [])).
+
+redraw(Pbs, Bef, Aft, Rs) ->
+ [{move_rel,-length(Aft)},{put_chars, unicode,reverse(Bef, Aft)},{put_chars, unicode,Pbs}|Rs].
+
+length_before({line,Pbs,{Bef,_Aft},_}) ->
+ length(Pbs) + length(Bef).
+
+length_after({line,_,{_Bef,Aft},_}) ->
+ length(Aft).
+
+prompt({line,Pbs,_,_}) ->
+ Pbs.
+
+%% %% expand(CurrentBefore) ->
+%% %% {yes,Expansion} | no
+%% %% Try to expand the word before as either a module name or a function
+%% %% name. We can handle white space around the seperating ':' but the
+%% %% function name must be on the same line. CurrentBefore is reversed
+%% %% and over_word/3 reverses the characters it finds. In certain cases
+%% %% possible expansions are printed.
+
+%% expand(Bef0) ->
+%% {Bef1,Word,_} = over_word(Bef0, [], 0),
+%% case over_white(Bef1, [], 0) of
+%% {[$:|Bef2],_White,_Nwh} ->
+%% {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
+%% {_,Mod,_Nm} = over_word(Bef3, [], 0),
+%% expand_function_name(Mod, Word);
+%% {_,_,_} ->
+%% expand_module_name(Word)
+%% end.
+
+%% expand_module_name(Prefix) ->
+%% match(Prefix, code:all_loaded(), ":").
+
+%% expand_function_name(ModStr, FuncPrefix) ->
+%% Mod = list_to_atom(ModStr),
+%% case erlang:module_loaded(Mod) of
+%% true ->
+%% L = apply(Mod, module_info, []),
+%% case keysearch(exports, 1, L) of
+%% {value, {_, Exports}} ->
+%% match(FuncPrefix, Exports, "(");
+%% _ ->
+%% no
+%% end;
+%% false ->
+%% no
+%% end.
+
+%% match(Prefix, Alts, Extra) ->
+%% Matches = match1(Prefix, Alts),
+%% case longest_common_head([N || {N,_} <- Matches]) of
+%% {partial, []} ->
+%% print_matches(Matches),
+%% no;
+%% {partial, Str} ->
+%% case nthtail(length(Prefix), Str) of
+%% [] ->
+%% print_matches(Matches),
+%% {yes, []};
+%% Remain ->
+%% {yes, Remain}
+%% end;
+%% {complete, Str} ->
+%% {yes, nthtail(length(Prefix), Str) ++ Extra};
+%% no ->
+%% no
+%% end.
+
+%% %% Print the list of names L in multiple columns.
+%% print_matches(L) ->
+%% io:nl(),
+%% col_print(lists:sort(L)),
+%% ok.
+
+%% col_print([]) -> ok;
+%% col_print(L) -> col_print(L, field_width(L), 0).
+
+%% col_print(X, Width, Len) when Width + Len > 79 ->
+%% io:nl(),
+%% col_print(X, Width, 0);
+%% col_print([{H0,A}|T], Width, Len) ->
+%% H = if
+%% %% If the second element is an integer, we assume it's an
+%% %% arity, and meant to be printed.
+%% integer(A) ->
+%% H0 ++ "/" ++ integer_to_list(A);
+%% true ->
+%% H0
+%% end,
+%% io:format("~-*s",[Width,H]),
+%% col_print(T, Width, Len+Width);
+%% col_print([], _, _) ->
+%% io:nl().
+
+%% field_width([{H,_}|T]) -> field_width(T, length(H)).
+
+%% field_width([{H,_}|T], W) ->
+%% case length(H) of
+%% L when L > W -> field_width(T, L);
+%% _ -> field_width(T, W)
+%% end;
+%% field_width([], W) when W < 40 ->
+%% W + 4;
+%% field_width([], _) ->
+%% 40.
+
+%% match1(Prefix, Alts) ->
+%% match1(Prefix, Alts, []).
+
+%% match1(Prefix, [{H,A}|T], L) ->
+%% case prefix(Prefix, Str = atom_to_list(H)) of
+%% true ->
+%% match1(Prefix, T, [{Str,A}|L]);
+%% false ->
+%% match1(Prefix, T, L)
+%% end;
+%% match1(_, [], L) ->
+%% L.
+
+%% longest_common_head([]) ->
+%% no;
+%% longest_common_head(LL) ->
+%% longest_common_head(LL, []).
+
+%% longest_common_head([[]|_], L) ->
+%% {partial, reverse(L)};
+%% longest_common_head(LL, L) ->
+%% case same_head(LL) of
+%% true ->
+%% [[H|_]|_] = LL,
+%% LL1 = all_tails(LL),
+%% case all_nil(LL1) of
+%% false ->
+%% longest_common_head(LL1, [H|L]);
+%% true ->
+%% {complete, reverse([H|L])}
+%% end;
+%% false ->
+%% {partial, reverse(L)}
+%% end.
+
+%% same_head([[H|_]|T1]) -> same_head(H, T1).
+
+%% same_head(H, [[H|_]|T]) -> same_head(H, T);
+%% same_head(_, []) -> true;
+%% same_head(_, _) -> false.
+
+%% all_tails(LL) -> all_tails(LL, []).
+
+%% all_tails([[_|T]|T1], L) -> all_tails(T1, [T|L]);
+%% all_tails([], L) -> L.
+
+%% all_nil([]) -> true;
+%% all_nil([[] | Rest]) -> all_nil(Rest);
+%% all_nil(_) -> false.
diff --git a/lib/stdlib/src/edlin_expand.erl b/lib/stdlib/src/edlin_expand.erl
new file mode 100644
index 0000000000..7ed76a6b09
--- /dev/null
+++ b/lib/stdlib/src/edlin_expand.erl
@@ -0,0 +1,168 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(edlin_expand).
+
+%% a default expand function for edlin, expanding modules and functions
+
+-export([expand/1, format_matches/1]).
+
+-import(lists, [reverse/1, nthtail/2, prefix/2]).
+
+%% expand(CurrentBefore) ->
+%% {yes, Expansion, Matches} | {no, Matches}
+%% Try to expand the word before as either a module name or a function
+%% name. We can handle white space around the seperating ':' but the
+%% function name must be on the same line. CurrentBefore is reversed
+%% and over_word/3 reverses the characters it finds. In certain cases
+%% possible expansions are printed.
+expand(Bef0) ->
+ {Bef1,Word,_} = edlin:over_word(Bef0, [], 0),
+ case over_white(Bef1, [], 0) of
+ {[$:|Bef2],_White,_Nwh} ->
+ {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0),
+ {_,Mod,_Nm} = edlin:over_word(Bef3, [], 0),
+ expand_function_name(Mod, Word);
+ {_,_,_} ->
+ expand_module_name(Word)
+ end.
+
+expand_module_name(Prefix) ->
+ match(Prefix, code:all_loaded(), ":").
+
+expand_function_name(ModStr, FuncPrefix) ->
+ Mod = list_to_atom(ModStr),
+ case erlang:module_loaded(Mod) of
+ true ->
+ L = Mod:module_info(),
+ case lists:keyfind(exports, 1, L) of
+ {_, Exports} ->
+ match(FuncPrefix, Exports, "(");
+ _ ->
+ {no, [], []}
+ end;
+ false ->
+ {no, [], []}
+ end.
+
+match(Prefix, Alts, Extra) ->
+ Len = length(Prefix),
+ Matches = [{S, A} || {H, A} <- Alts, prefix(Prefix, S=atom_to_list(H))],
+ case longest_common_head([N || {N, _} <- Matches]) of
+ {partial, []} ->
+ {no, [], Matches}; % format_matches(Matches)};
+ {partial, Str} ->
+ case nthtail(Len, Str) of
+ [] ->
+ {yes, [], Matches}; % format_matches(Matches)};
+ Remain ->
+ {yes, Remain, []}
+ end;
+ {complete, Str} ->
+ {yes, nthtail(Len, Str) ++ Extra, []};
+ no ->
+ {no, [], []}
+ end.
+
+%% Return the list of names L in multiple columns.
+format_matches(L) ->
+ S = format_col(lists:sort(L), []),
+ ["\n" | S].
+
+format_col([], _) -> [];
+format_col(L, Acc) -> format_col(L, field_width(L), 0, Acc).
+
+format_col(X, Width, Len, Acc) when Width + Len > 79 ->
+ format_col(X, Width, 0, ["\n" | Acc]);
+format_col([A|T], Width, Len, Acc0) ->
+ H = case A of
+ %% If it's a tuple {string(), integer()}, we assume it's an
+ %% arity, and meant to be printed.
+ {H0, I} when is_integer(I) ->
+ H0 ++ "/" ++ integer_to_list(I);
+ {H1, _} -> H1;
+ H2 -> H2
+ end,
+ Acc = [io_lib:format("~-*s", [Width,H]) | Acc0],
+ format_col(T, Width, Len+Width, Acc);
+format_col([], _, _, Acc) ->
+ lists:reverse(Acc, "\n").
+
+field_width(L) -> field_width(L, 0).
+
+field_width([{H,_}|T], W) ->
+ case length(H) of
+ L when L > W -> field_width(T, L);
+ _ -> field_width(T, W)
+ end;
+field_width([H|T], W) ->
+ case length(H) of
+ L when L > W -> field_width(T, L);
+ _ -> field_width(T, W)
+ end;
+field_width([], W) when W < 40 ->
+ W + 4;
+field_width([], _) ->
+ 40.
+
+longest_common_head([]) ->
+ no;
+longest_common_head(LL) ->
+ longest_common_head(LL, []).
+
+longest_common_head([[]|_], L) ->
+ {partial, reverse(L)};
+longest_common_head(LL, L) ->
+ case same_head(LL) of
+ true ->
+ [[H|_]|_] = LL,
+ LL1 = all_tails(LL),
+ case all_nil(LL1) of
+ false ->
+ longest_common_head(LL1, [H|L]);
+ true ->
+ {complete, reverse([H|L])}
+ end;
+ false ->
+ {partial, reverse(L)}
+ end.
+
+same_head([[H|_]|T1]) -> same_head(H, T1).
+
+same_head(H, [[H|_]|T]) -> same_head(H, T);
+same_head(_, []) -> true;
+same_head(_, _) -> false.
+
+all_tails(LL) -> all_tails(LL, []).
+
+all_tails([[_|T]|T1], L) -> all_tails(T1, [T|L]);
+all_tails([], L) -> L.
+
+all_nil([]) -> true;
+all_nil([[] | Rest]) -> all_nil(Rest);
+all_nil(_) -> false.
+
+%% over_white(Chars, InitialStack, InitialCount) ->
+%% {RemainingChars,CharStack,Count}.
+
+over_white([$\s|Cs], Stack, N) ->
+ over_white(Cs, [$\s|Stack], N+1);
+over_white([$\t|Cs], Stack, N) ->
+ over_white(Cs, [$\t|Stack], N+1);
+over_white(Cs, Stack, N) when is_list(Cs) ->
+ {Cs,Stack,N}.
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
new file mode 100644
index 0000000000..8b702c005b
--- /dev/null
+++ b/lib/stdlib/src/epp.erl
@@ -0,0 +1,1146 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(epp).
+
+%% An Erlang code preprocessor.
+
+-export([open/2,open/3,open/5,close/1,format_error/1]).
+-export([scan_erl_form/1,parse_erl_form/1,macro_defs/1]).
+-export([parse_file/1, parse_file/3]).
+-export([interpret_file_attribute/1]).
+-export([normalize_typed_record_fields/1,restore_typed_record_fields/1]).
+
+%%------------------------------------------------------------------------
+
+-type macros() :: [{atom(), term()}].
+
+%% Epp state record.
+-record(epp, {file, %Current file
+ location, %Current location
+ name="", %Current file name
+ istk=[], %Ifdef stack
+ sstk=[], %State stack
+ path=[], %Include-path
+ macs = dict:new() :: dict(), %Macros (don't care locations)
+ uses = dict:new() :: dict(), %Macro use structure
+ pre_opened = false :: boolean()
+ }).
+
+%%% Note on representation: as tokens, both {var, Location, Name} and
+%%% {atom, Location, Name} can occur as macro identifiers. However, keeping
+%%% this distinction here is done for historical reasons only: previously,
+%%% ?FOO and ?'FOO' were not the same, but now they are. Removing the
+%%% distinction in the internal representation would simplify the code
+%%% a little.
+
+%% open(FileName, IncludePath)
+%% open(FileName, IncludePath, PreDefMacros)
+%% open(FileName, IoDevice, StartLocation, IncludePath, PreDefMacros)
+%% close(Epp)
+%% scan_erl_form(Epp)
+%% parse_erl_form(Epp)
+%% parse_file(Epp)
+%% parse_file(FileName, IncludePath, PreDefMacros)
+%% macro_defs(Epp)
+
+-spec open(file:name(), [file:name()]) ->
+ {'ok', pid()} | {'error', term()}.
+
+open(Name, Path) ->
+ open(Name, Path, []).
+
+-spec open(file:name(), [file:name()], macros()) ->
+ {'ok', pid()} | {'error', term()}.
+
+open(Name, Path, Pdm) ->
+ Self = self(),
+ Epp = spawn(fun() -> server(Self, Name, Path, Pdm) end),
+ epp_request(Epp).
+
+open(Name, File, StartLocation, Path, Pdm) ->
+ Self = self(),
+ Epp = spawn(fun() -> server(Self, Name, File, StartLocation,Path,Pdm) end),
+ epp_request(Epp).
+
+-spec close(pid()) -> 'ok'.
+
+close(Epp) ->
+ %% Make sure that close is synchronous as a courtesy to test
+ %% cases that test for resource leaks.
+ Ref = erlang:monitor(process, Epp),
+ R = epp_request(Epp, close),
+ receive {'DOWN',Ref,_,_,_} -> ok end,
+ R.
+
+scan_erl_form(Epp) ->
+ epp_request(Epp, scan_erl_form).
+
+parse_erl_form(Epp) ->
+ case epp_request(Epp, scan_erl_form) of
+ {ok,Toks} ->
+ erl_parse:parse_form(Toks);
+ Other ->
+ Other
+ end.
+
+macro_defs(Epp) ->
+ epp_request(Epp, macro_defs).
+
+%% format_error(ErrorDescriptor) -> String
+%% Return a string describing the error.
+
+format_error(cannot_parse) ->
+ io_lib:format("cannot parse file, giving up", []);
+format_error({bad,W}) ->
+ io_lib:format("badly formed '~s'", [W]);
+format_error({call,What}) ->
+ io_lib:format("illegal macro call '~s'",[What]);
+format_error({undefined,M}) ->
+ io_lib:format("undefined macro '~w'", [M]);
+format_error({depth,What}) ->
+ io_lib:format("~s too deep",[What]);
+format_error({mismatch,M}) ->
+ io_lib:format("argument mismatch for macro '~w'", [M]);
+format_error({arg_error,M}) ->
+ io_lib:format("badly formed argument for macro '~w'", [M]);
+format_error({redefine,M}) ->
+ io_lib:format("redefining macro '~w'", [M]);
+format_error({circular,M}) ->
+ io_lib:format("circular macro '~w'", [M]);
+format_error({include,W,F}) ->
+ io_lib:format("can't find include ~s \"~s\"", [W,F]);
+format_error({illegal,How,What}) ->
+ io_lib:format("~s '-~s'", [How,What]);
+format_error({'NYI',What}) ->
+ io_lib:format("not yet implemented '~s'", [What]);
+format_error(E) -> file:format_error(E).
+
+%% parse_file(FileName, IncludePath, [PreDefMacro]) ->
+%% {ok,[Form]} | {error,OpenError}
+
+parse_file(Ifile, Path, Predefs) ->
+ case open(Ifile, Path, Predefs) of
+ {ok,Epp} ->
+ Forms = parse_file(Epp),
+ close(Epp),
+ {ok,Forms};
+ {error,E} ->
+ {error,E}
+ end.
+
+%% parse_file(Epp) ->
+%% [Form]
+
+parse_file(Epp) ->
+ case parse_erl_form(Epp) of
+ {ok,Form} ->
+ case Form of
+ {attribute,La,record,{Record, Fields}} ->
+ case normalize_typed_record_fields(Fields) of
+ {typed, NewFields} ->
+ [{attribute, La, record, {Record, NewFields}},
+ {attribute, La, type,
+ {{record, Record}, Fields, []}}
+ |parse_file(Epp)];
+ not_typed ->
+ [Form|parse_file(Epp)]
+ end;
+ _ ->
+ [Form|parse_file(Epp)]
+ end;
+ {error,E} ->
+ [{error,E}|parse_file(Epp)];
+ {eof,Location} ->
+ [{eof,Location}]
+ end.
+
+normalize_typed_record_fields(Fields) ->
+ normalize_typed_record_fields(Fields, [], false).
+
+normalize_typed_record_fields([], NewFields, Typed) ->
+ case Typed of
+ true -> {typed, lists:reverse(NewFields)};
+ false -> not_typed
+ end;
+normalize_typed_record_fields([{typed_record_field,Field,_}|Rest],
+ NewFields, _Typed) ->
+ normalize_typed_record_fields(Rest, [Field|NewFields], true);
+normalize_typed_record_fields([Field|Rest], NewFields, Typed) ->
+ normalize_typed_record_fields(Rest, [Field|NewFields], Typed).
+
+restore_typed_record_fields([]) ->
+ [];
+restore_typed_record_fields([{attribute,La,record,{Record,_NewFields}},
+ {attribute,La,type,{{record,Record},Fields,[]}}|
+ Forms]) ->
+ [{attribute,La,record,{Record,Fields}}|
+ restore_typed_record_fields(Forms)];
+restore_typed_record_fields([{attribute,La,type,{{record,Record},Fields,[]}}|
+ Forms]) ->
+ %% This clause is due to the compiler's 'E' option.
+ %% Record information kept by erl_expand_records.
+ [{attribute,La,record,{Record,Fields}}|
+ restore_typed_record_fields(Forms)];
+restore_typed_record_fields([Form|Forms]) ->
+ [Form|restore_typed_record_fields(Forms)].
+
+%% server(StarterPid, FileName, Path, PreDefMacros)
+
+server(Pid, Name, Path, Pdm) ->
+ process_flag(trap_exit, true),
+ case file:open(Name, [read]) of
+ {ok,File} ->
+ Location = 1,
+ init_server(Pid, Name, File, Location, Path, Pdm, false);
+ {error,E} ->
+ epp_reply(Pid, {error,E})
+ end.
+
+%% server(StarterPid, FileName, IoDevice, Location, Path, PreDefMacros)
+server(Pid, Name, File, AtLocation, Path, Pdm) ->
+ process_flag(trap_exit, true),
+ init_server(Pid, Name, File, AtLocation, Path, Pdm, true).
+
+init_server(Pid, Name, File, AtLocation, Path, Pdm, Pre) ->
+ Ms0 = predef_macros(Name),
+ case user_predef(Pdm, Ms0) of
+ {ok,Ms1} ->
+ epp_reply(Pid, {ok,self()}),
+ St = #epp{file=File, location=AtLocation, name=Name,
+ path=Path, macs=Ms1, pre_opened = Pre},
+ From = wait_request(St),
+ enter_file_reply(From, Name, AtLocation, AtLocation),
+ wait_req_scan(St);
+ {error,E} ->
+ epp_reply(Pid, {error,E})
+ end.
+
+%% predef_macros(FileName) -> Macrodict
+%% Initialise the macro dictionary with the default predefined macros,
+%% FILE, LINE, MODULE as undefined, MACHINE and MACHINE value.
+
+predef_macros(File) ->
+ Machine = list_to_atom(erlang:system_info(machine)),
+ dict:from_list([
+ {{atom,'FILE'}, {none,[{string,1,File}]}},
+ {{atom,'LINE'}, {none,[{integer,1,1}]}},
+ {{atom,'MODULE'}, undefined},
+ {{atom,'MODULE_STRING'}, undefined},
+ {{atom,'BASE_MODULE'}, undefined},
+ {{atom,'BASE_MODULE_STRING'}, undefined},
+ {{atom,'MACHINE'}, {none,[{atom,1,Machine}]}},
+ {{atom,Machine}, {none,[{atom,1,true}]}}
+ ]).
+
+%% user_predef(PreDefMacros, Macros) ->
+%% {ok,MacroDict} | {error,E}
+%% Add the predefined macros to the macros dictionary. A macro without a
+%% value gets the value 'true'.
+
+user_predef([{M,Val,redefine}|Pdm], Ms) when is_atom(M) ->
+ Exp = erl_parse:tokens(erl_parse:abstract(Val)),
+ user_predef(Pdm, dict:store({atom,M}, {none,Exp}, Ms));
+user_predef([{M,Val}|Pdm], Ms) when is_atom(M) ->
+ case dict:find({atom,M}, Ms) of
+ {ok,_Def} ->
+ {error,{redefine,M}};
+ error ->
+ Exp = erl_parse:tokens(erl_parse:abstract(Val)),
+ user_predef(Pdm, dict:store({atom,M}, {none,Exp}, Ms))
+ end;
+user_predef([M|Pdm], Ms) when is_atom(M) ->
+ case dict:find({atom,M}, Ms) of
+ {ok,_Def} ->
+ {error,{redefine,M}};
+ error ->
+ user_predef(Pdm, dict:store({atom,M}, {none,[{atom,1,true}]}, Ms))
+ end;
+user_predef([Md|_Pdm], _Ms) -> {error,{bad,Md}};
+user_predef([], Ms) -> {ok,Ms}.
+
+%% wait_request(EppState) -> RequestFrom
+%% wait_req_scan(EppState)
+%% wait_req_skip(EppState, SkipIstack)
+%% Handle requests, processing trivial requests directly. Either return
+%% requestor or scan/skip tokens.
+
+wait_request(St) ->
+ receive
+ {epp_request,From,scan_erl_form} -> From;
+ {epp_request,From,macro_defs} ->
+ epp_reply(From, dict:to_list(St#epp.macs)),
+ wait_request(St);
+ {epp_request,From,close} ->
+ close_file(St),
+ epp_reply(From, ok),
+ exit(normal);
+ {'EXIT',_,R} ->
+ exit(R);
+ Other ->
+ io:fwrite("Epp: unknown '~w'\n", [Other]),
+ wait_request(St)
+ end.
+
+close_file(#epp{pre_opened = true}) ->
+ ok;
+close_file(#epp{pre_opened = false, file = File}) ->
+ ok = file:close(File).
+
+wait_req_scan(St) ->
+ From = wait_request(St),
+ scan_toks(From, St).
+
+wait_req_skip(St, Sis) ->
+ From = wait_request(St),
+ skip_toks(From, St, Sis).
+
+%% enter_file(Path, FileName, IncludeToken, From, EppState)
+%% leave_file(From, EppState)
+%% Handle entering and leaving included files. Notify caller when the
+%% current file is changed. Note it is an error to exit a file if we are
+%% in a conditional. These functions never return.
+
+enter_file(_Path, _NewName, Inc, From, St)
+ when length(St#epp.sstk) >= 8 ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{depth,"include"}}}),
+ wait_req_scan(St);
+enter_file(Path, NewName, Inc, From, St) ->
+ case file:path_open(Path, NewName, [read]) of
+ {ok,NewF,Pname} ->
+ Loc = start_loc(St#epp.location),
+ wait_req_scan(enter_file2(NewF, Pname, From, St, Loc));
+ {error,_E} ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{include,file,NewName}}}),
+ wait_req_scan(St)
+ end.
+
+%% enter_file2(File, FullName, From, EppState, AtLocation) -> EppState.
+%% Set epp to use this file and "enter" it.
+
+enter_file2(NewF, Pname, From, St, AtLocation) ->
+ enter_file2(NewF, Pname, From, St, AtLocation, []).
+
+enter_file2(NewF, Pname, From, St, AtLocation, ExtraPath) ->
+ Loc = start_loc(AtLocation),
+ enter_file_reply(From, Pname, Loc, AtLocation),
+ Ms = dict:store({atom,'FILE'}, {none,[{string,Loc,Pname}]}, St#epp.macs),
+ Path = St#epp.path ++ ExtraPath,
+ #epp{location=Loc,file=NewF,
+ name=Pname,sstk=[St|St#epp.sstk],path=Path,macs=Ms}.
+
+enter_file_reply(From, Name, Location, AtLocation) ->
+ Attr = loc_attr(AtLocation),
+ Rep = {ok, [{'-',Attr},{atom,Attr,file},{'(',Attr},
+ {string,Attr,file_name(Name)},{',',Attr},
+ {integer,Attr,get_line(Location)},{')',Location},
+ {dot,Attr}]},
+ epp_reply(From, Rep).
+
+%% Flatten filename to a string. Must be a valid filename.
+
+file_name([C | T]) when is_integer(C), C > 0, C =< 255 ->
+ [C | file_name(T)];
+file_name([H|T]) ->
+ file_name(H) ++ file_name(T);
+file_name([]) ->
+ [];
+file_name(N) when is_atom(N) ->
+ atom_to_list(N).
+
+leave_file(From, St) ->
+ case St#epp.istk of
+ [I|Cis] ->
+ epp_reply(From,
+ {error,{St#epp.location,epp,
+ {illegal,"unterminated",I}}}),
+ leave_file(wait_request(St),St#epp{istk=Cis});
+ [] ->
+ case St#epp.sstk of
+ [OldSt|Sts] ->
+ close_file(St),
+ enter_file_reply(From, OldSt#epp.name,
+ OldSt#epp.location, OldSt#epp.location),
+ Ms = dict:store({atom,'FILE'},
+ {none,
+ [{string,OldSt#epp.location,
+ OldSt#epp.name}]},
+ St#epp.macs),
+ wait_req_scan(OldSt#epp{sstk=Sts,macs=Ms});
+ [] ->
+ epp_reply(From, {eof,St#epp.location}),
+ wait_req_scan(St)
+ end
+ end.
+
+%% scan_toks(From, EppState)
+%% scan_toks(Tokens, From, EppState)
+
+scan_toks(From, St) ->
+ case io:scan_erl_form(St#epp.file, '', St#epp.location) of
+ {ok,Toks,Cl} ->
+ scan_toks(Toks, From, St#epp{location=Cl});
+ {error,E,Cl} ->
+ epp_reply(From, {error,E}),
+ wait_req_scan(St#epp{location=Cl});
+ {eof,Cl} ->
+ leave_file(From, St#epp{location=Cl});
+ {error,_E} ->
+ epp_reply(From, {error,{St#epp.location,epp,cannot_parse}}),
+ leave_file(From, St) %This serious, just exit!
+ end.
+
+scan_toks([{'-',_Lh},{atom,_Ld,define}=Define|Toks], From, St) ->
+ scan_define(Toks, Define, From, St);
+scan_toks([{'-',_Lh},{atom,_Ld,undef}=Undef|Toks], From, St) ->
+ scan_undef(Toks, Undef, From, St);
+scan_toks([{'-',_Lh},{atom,_Li,include}=Inc|Toks], From, St) ->
+ scan_include(Toks, Inc, From, St);
+scan_toks([{'-',_Lh},{atom,_Li,include_lib}=IncLib|Toks], From, St) ->
+ scan_include_lib(Toks, IncLib, From, St);
+scan_toks([{'-',_Lh},{atom,_Li,ifdef}=IfDef|Toks], From, St) ->
+ scan_ifdef(Toks, IfDef, From, St);
+scan_toks([{'-',_Lh},{atom,_Li,ifndef}=IfnDef|Toks], From, St) ->
+ scan_ifndef(Toks, IfnDef, From, St);
+scan_toks([{'-',_Lh},{atom,_Le,'else'}=Else|Toks], From, St) ->
+ scan_else(Toks, Else, From, St);
+scan_toks([{'-',_Lh},{'if',_Le}=If|Toks], From, St) ->
+ scan_if(Toks, If, From, St);
+scan_toks([{'-',_Lh},{atom,_Le,elif}=Elif|Toks], From, St) ->
+ scan_elif(Toks, Elif, From, St);
+scan_toks([{'-',_Lh},{atom,_Le,endif}=Endif|Toks], From, St) ->
+ scan_endif(Toks, Endif, From, St);
+scan_toks([{'-',_Lh},{atom,_Lf,file}=FileToken|Toks0], From, St) ->
+ case catch expand_macros(Toks0, {St#epp.macs, St#epp.uses}) of
+ Toks1 when is_list(Toks1) ->
+ scan_file(Toks1, FileToken, From, St);
+ {error,ErrL,What} ->
+ epp_reply(From, {error,{ErrL,epp,What}}),
+ wait_req_scan(St)
+ end;
+scan_toks(Toks0, From, St) ->
+ case catch expand_macros(Toks0, {St#epp.macs, St#epp.uses}) of
+ Toks1 when is_list(Toks1) ->
+ epp_reply(From, {ok,Toks1}),
+ wait_req_scan(St#epp{macs=scan_module(Toks1, St#epp.macs)});
+ {error,ErrL,What} ->
+ epp_reply(From, {error,{ErrL,epp,What}}),
+ wait_req_scan(St)
+ end.
+
+scan_module([{'-',_Lh},{atom,_Lm,module},{'(',_Ll}|Ts], Ms) ->
+ scan_module_1(Ts, [], Ms);
+scan_module([{'-',_Lh},{atom,_Lm,extends},{'(',_Ll}|Ts], Ms) ->
+ scan_extends(Ts, [], Ms);
+scan_module(_Ts, Ms) -> Ms.
+
+scan_module_1([{atom,_,_}=A,{',',L}|Ts], As, Ms) ->
+ %% Parameterized modules.
+ scan_module_1([A,{')',L}|Ts], As, Ms);
+scan_module_1([{atom,Ln,A},{')',_Lr}|_Ts], As, Ms0) ->
+ Mod = lists:concat(lists:reverse([A|As])),
+ Ms = dict:store({atom,'MODULE'},
+ {none,[{atom,Ln,list_to_atom(Mod)}]}, Ms0),
+ dict:store({atom,'MODULE_STRING'}, {none,[{string,Ln,Mod}]}, Ms);
+scan_module_1([{atom,_Ln,A},{'.',_Lr}|Ts], As, Ms) ->
+ scan_module_1(Ts, [".",A|As], Ms);
+scan_module_1([{'.',_Lr}|Ts], As, Ms) ->
+ scan_module_1(Ts, As, Ms);
+scan_module_1(_Ts, _As, Ms) -> Ms.
+
+scan_extends([{atom,Ln,A},{')',_Lr}|_Ts], As, Ms0) ->
+ Mod = lists:concat(lists:reverse([A|As])),
+ Ms = dict:store({atom,'BASE_MODULE'},
+ {none,[{atom,Ln,list_to_atom(Mod)}]}, Ms0),
+ dict:store({atom,'BASE_MODULE_STRING'}, {none,[{string,Ln,Mod}]}, Ms);
+scan_extends([{atom,_Ln,A},{'.',_Lr}|Ts], As, Ms) ->
+ scan_extends(Ts, [".",A|As], Ms);
+scan_extends([{'.',_Lr}|Ts], As, Ms) ->
+ scan_extends(Ts, As, Ms);
+scan_extends(_Ts, _As, Ms) -> Ms.
+
+%% scan_define(Tokens, DefineToken, From, EppState)
+
+scan_define([{'(',_Lp},{atom,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_OldDef} ->
+ epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}),
+ wait_req_scan(St);
+ error ->
+ scan_define_cont(From, St,
+ {atom, M},
+ {none,macro_expansion(Toks)})
+ end;
+scan_define([{'(',_Lp},{atom,_Lm,M}=Mac,{'(',_Lc}|Toks], Def, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}),
+ wait_req_scan(St);
+ error ->
+ case catch macro_pars(Toks, []) of
+ {ok, {As, Me}} ->
+ scan_define_cont(From, St,
+ {atom, M},
+ {As, Me});
+ _ ->
+ epp_reply(From, {error,{loc(Def),epp,{bad,define}}}),
+ wait_req_scan(St)
+ end
+ end;
+scan_define([{'(',_Lp},{var,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_OldDef} ->
+ epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}),
+ wait_req_scan(St);
+ error ->
+ scan_define_cont(From, St,
+ {atom, M},
+ {none,macro_expansion(Toks)})
+ end;
+scan_define([{'(',_Lp},{var,_Lm,M}=Mac,{'(',_Lc}|Toks], Def, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ epp_reply(From, {error,{loc(Mac),epp,{redefine,M}}}),
+ wait_req_scan(St);
+ error ->
+ case catch macro_pars(Toks, []) of
+ {ok, {As, Me}} ->
+ scan_define_cont(From, St,
+ {atom, M},
+ {As, Me});
+ _ ->
+ epp_reply(From, {error,{loc(Def),epp,{bad,define}}}),
+ wait_req_scan(St)
+ end
+ end;
+scan_define(_Toks, Def, From, St) ->
+ epp_reply(From, {error,{loc(Def),epp,{bad,define}}}),
+ wait_req_scan(St).
+
+%%% Detection of circular macro expansions (which would either keep
+%%% the compiler looping forever, or run out of memory):
+%%% When a macro is defined, we store the names of other macros it
+%%% uses in St#epp.uses. If any macro is undef'ed, that information
+%%% becomes invalid, so we redo it for all remaining macros.
+%%% The circularity detection itself is done when a macro is expanded:
+%%% the information from St#epp.uses is traversed, and if a circularity
+%%% is detected, an error message is thrown.
+
+scan_define_cont(F, St, M, Def) ->
+ Ms = dict:store(M, Def, St#epp.macs),
+ U = dict:store(M, macro_uses(Def), St#epp.uses),
+ scan_toks(F, St#epp{uses=U, macs=Ms}).
+
+macro_uses(undefined) ->
+ undefined;
+macro_uses({_Args, Tokens}) ->
+ Uses0 = macro_ref(Tokens),
+ lists:usort(Uses0).
+
+macro_ref([]) ->
+ [];
+macro_ref([{'?', _}, {'?', _} | Rest]) ->
+ macro_ref(Rest);
+macro_ref([{'?', _}, {atom, _, A} | Rest]) ->
+ [{atom, A} | macro_ref(Rest)];
+macro_ref([{'?', _}, {var, _, A} | Rest]) ->
+ [{atom, A} | macro_ref(Rest)];
+macro_ref([_Token | Rest]) ->
+ macro_ref(Rest).
+
+all_macro_uses(D0) ->
+ L = dict:to_list(D0),
+ D = dict:new(),
+ add_macro_uses(L, D).
+
+add_macro_uses([], D) ->
+ D;
+add_macro_uses([{Key, Def} | Rest], D0) ->
+ add_macro_uses(Rest, dict:store(Key, macro_uses(Def), D0)).
+
+%% scan_undef(Tokens, UndefToken, From, EppState)
+
+scan_undef([{'(',_Llp},{atom,_Lm,M},{')',_Lrp},{dot,_Ld}], _Undef, From, St) ->
+ scan_toks(From, St#epp{macs=dict:erase({atom,M}, St#epp.macs),
+ uses=all_macro_uses(St#epp.macs)});
+scan_undef([{'(',_Llp},{var,_Lm,M},{')',_Lrp},{dot,_Ld}], _Undef, From,St) ->
+ scan_toks(From, St#epp{macs=dict:erase({atom,M}, St#epp.macs),
+ uses=all_macro_uses(St#epp.macs)});
+scan_undef(_Toks, Undef, From, St) ->
+ epp_reply(From, {error,{loc(Undef),epp,{bad,undef}}}),
+ wait_req_scan(St).
+
+%% scan_include(Tokens, IncludeToken, From, St)
+
+scan_include([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}], Inc,
+ From, St) ->
+ NewName = expand_var(NewName0),
+ enter_file(St#epp.path, NewName, Inc, From, St);
+scan_include(_Toks, Inc, From, St) ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{bad,include}}}),
+ wait_req_scan(St).
+
+%% scan_include_lib(Tokens, IncludeToken, From, EppState)
+%% For include_lib we first test if we can find the file through the
+%% normal search path, if not we assume that the first directory name
+%% is a library name, find its true directory and try with that.
+
+find_lib_dir(NewName) ->
+ [Lib | Rest] = filename:split(NewName),
+ {code:lib_dir(list_to_atom(Lib)), Rest}.
+
+scan_include_lib([{'(',_Llp},{string,_Lf,_NewName0},{')',_Lrp},{dot,_Ld}],
+ Inc, From, St)
+ when length(St#epp.sstk) >= 8 ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{depth,"include_lib"}}}),
+ wait_req_scan(St);
+scan_include_lib([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}],
+ Inc, From, St) ->
+ NewName = expand_var(NewName0),
+ Loc = start_loc(St#epp.location),
+ case file:path_open(St#epp.path, NewName, [read]) of
+ {ok,NewF,Pname} ->
+ wait_req_scan(enter_file2(NewF, Pname, From, St, Loc));
+ {error,_E1} ->
+ case catch find_lib_dir(NewName) of
+ {LibDir, Rest} when is_list(LibDir) ->
+ LibName = filename:join([LibDir | Rest]),
+ case file:open(LibName, [read]) of
+ {ok,NewF} ->
+ ExtraPath = [filename:dirname(LibName)],
+ wait_req_scan(enter_file2(NewF, LibName, From,
+ St, Loc, ExtraPath));
+ {error,_E2} ->
+ epp_reply(From,
+ {error,{abs_loc(Inc),epp,
+ {include,lib,NewName}}}),
+ wait_req_scan(St)
+ end;
+ _Error ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,
+ {include,lib,NewName}}}),
+ wait_req_scan(St)
+ end
+ end;
+scan_include_lib(_Toks, Inc, From, St) ->
+ epp_reply(From, {error,{abs_loc(Inc),epp,{bad,include_lib}}}),
+ wait_req_scan(St).
+
+%% scan_ifdef(Tokens, IfdefToken, From, EppState)
+%% scan_ifndef(Tokens, IfdefToken, From, EppSate)
+%% Handle the conditional parsing of a file.
+%% Report a badly formed if[n]def test and then treat as undefined macro.
+
+scan_ifdef([{'(',_Llp},{atom,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ scan_toks(From, St#epp{istk=[ifdef|St#epp.istk]});
+ error ->
+ skip_toks(From, St, [ifdef])
+ end;
+scan_ifdef([{'(',_Llp},{var,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ scan_toks(From, St#epp{istk=[ifdef|St#epp.istk]});
+ error ->
+ skip_toks(From, St, [ifdef])
+ end;
+scan_ifdef(_Toks, IfDef, From, St) ->
+ epp_reply(From, {error,{loc(IfDef),epp,{bad,ifdef}}}),
+ wait_req_skip(St, [ifdef]).
+
+scan_ifndef([{'(',_Llp},{atom,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfnD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ skip_toks(From, St, [ifndef]);
+ error ->
+ scan_toks(From, St#epp{istk=[ifndef|St#epp.istk]})
+ end;
+scan_ifndef([{'(',_Llp},{var,_Lm,M},{')',_Lrp},{dot,_Ld}], _IfnD, From, St) ->
+ case dict:find({atom,M}, St#epp.macs) of
+ {ok,_Def} ->
+ skip_toks(From, St, [ifndef]);
+ error ->
+ scan_toks(From, St#epp{istk=[ifndef|St#epp.istk]})
+ end;
+scan_ifndef(_Toks, IfnDef, From, St) ->
+ epp_reply(From, {error,{loc(IfnDef),epp,{bad,ifndef}}}),
+ wait_req_skip(St, [ifndef]).
+
+%% scan_else(Tokens, ElseToken, From, EppState)
+%% If we are in an if body then convert to else and skip, if we are in an
+%% else or not in anything report an error.
+
+scan_else([{dot,_Ld}], Else, From, St) ->
+ case St#epp.istk of
+ ['else'|Cis] ->
+ epp_reply(From, {error,{loc(Else),
+ epp,{illegal,"repeated",'else'}}}),
+ wait_req_skip(St#epp{istk=Cis}, ['else']);
+ [_I|Cis] ->
+ skip_toks(From, St#epp{istk=Cis}, ['else']);
+ [] ->
+ epp_reply(From, {error,{loc(Else),epp,
+ {illegal,"unbalanced",'else'}}}),
+ wait_req_scan(St)
+ end;
+scan_else(_Toks, Else, From, St) ->
+ epp_reply(From, {error,{loc(Else),epp,{bad,'else'}}}),
+ wait_req_scan(St).
+
+%% scan_if(Tokens, EndifToken, From, EppState)
+%% Handle the conditional parsing of a file.
+%% Report a badly formed if test and then treat as false macro.
+
+scan_if(_Toks, If, From, St) ->
+ epp_reply(From, {error,{loc(If),epp,{'NYI','if'}}}),
+ wait_req_skip(St, ['if']).
+
+%% scan_elif(Tokens, EndifToken, From, EppState)
+%% Handle the conditional parsing of a file.
+%% Report a badly formed if test and then treat as false macro.
+
+scan_elif(_Toks, Elif, From, St) ->
+ epp_reply(From, {error,{loc(Elif),epp,{'NYI','elif'}}}),
+ wait_req_scan(St).
+
+%% scan_endif(Tokens, EndifToken, From, EppState)
+%% If we are in an if body then exit it, else report an error.
+
+scan_endif([{dot,_Ld}], Endif, From, St) ->
+ case St#epp.istk of
+ [_I|Cis] ->
+ scan_toks(From, St#epp{istk=Cis});
+ [] ->
+ epp_reply(From, {error,{loc(Endif),epp,
+ {illegal,"unbalanced",endif}}}),
+ wait_req_scan(St)
+ end;
+scan_endif(_Toks, Endif, From, St) ->
+ epp_reply(From, {error,{loc(Endif),epp,{bad,endif}}}),
+ wait_req_scan(St).
+
+%% scan_file(Tokens, FileToken, From, EppState)
+%% Set the current file and line to the given file and line.
+%% Note that the line of the attribute itself is kept.
+
+scan_file([{'(',_Llp},{string,_Ls,Name},{',',_Lc},{integer,_Li,Ln},{')',_Lrp},
+ {dot,_Ld}], Tf, From, St) ->
+ enter_file_reply(From, Name, Ln, neg_line(abs_loc(Tf))),
+ Ms = dict:store({atom,'FILE'}, {none,[{string,1,Name}]}, St#epp.macs),
+ Locf = loc(Tf),
+ NewLoc = new_location(Ln, St#epp.location, Locf),
+ scan_toks(From, St#epp{name=Name,location=NewLoc,macs=Ms});
+scan_file(_Toks, Tf, From, St) ->
+ epp_reply(From, {error,{loc(Tf),epp,{bad,file}}}),
+ wait_req_scan(St).
+
+new_location(Ln, Le, Lf) when is_integer(Lf) ->
+ Ln+(Le-Lf);
+new_location(Ln, {Le,_}, {Lf,_}) ->
+ {Ln+(Le-Lf),1}.
+
+%% skip_toks(From, EppState, SkipIstack)
+%% Skip over forms until current conditional has been exited. Handle
+%% nested conditionals and repeated 'else's.
+
+skip_toks(From, St, [I|Sis]) ->
+ case io:scan_erl_form(St#epp.file, '', St#epp.location) of
+ {ok,[{'-',_Lh},{atom,_Li,ifdef}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [ifdef,I|Sis]);
+ {ok,[{'-',_Lh},{atom,_Li,ifndef}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [ifndef,I|Sis]);
+ {ok,[{'-',_Lh},{'if',_Li}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, ['if',I|Sis]);
+ {ok,[{'-',_Lh},{atom,_Le,'else'}=Else|_Toks],Cl}->
+ skip_else(Else, From, St#epp{location=Cl}, [I|Sis]);
+ {ok,[{'-',_Lh},{atom,_Le,endif}|_Toks],Cl} ->
+ skip_toks(From, St#epp{location=Cl}, Sis);
+ {ok,_Toks,Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [I|Sis]);
+ {error,_E,Cl} ->
+ skip_toks(From, St#epp{location=Cl}, [I|Sis]);
+ {eof,Cl} ->
+ leave_file(From, St#epp{location=Cl,istk=[I|Sis]});
+ {error,_E} ->
+ epp_reply(From, {error,{St#epp.location,epp,cannot_parse}}),
+ leave_file(From, St) %This serious, just exit!
+ end;
+skip_toks(From, St, []) ->
+ scan_toks(From, St).
+
+skip_else(Else, From, St, ['else'|Sis]) ->
+ epp_reply(From, {error,{loc(Else),epp,{illegal,"repeated",'else'}}}),
+ wait_req_skip(St, ['else'|Sis]);
+skip_else(_Else, From, St, [_I]) ->
+ scan_toks(From, St#epp{istk=['else'|St#epp.istk]});
+skip_else(_Else, From, St, Sis) ->
+ skip_toks(From, St, Sis).
+
+%% macro_pars(Tokens, ArgStack)
+%% macro_expansion(Tokens)
+%% Extract the macro parameters and the expansion from a macro definition.
+
+macro_pars([{')',_Lp}, {',',_Ld}|Ex], Args) ->
+ {ok, {lists:reverse(Args), macro_expansion(Ex)}};
+macro_pars([{var,_,Name}, {')',_Lp}, {',',_Ld}|Ex], Args) ->
+ false = lists:member(Name, Args), %Prolog is nice
+ {ok, {lists:reverse([Name|Args]), macro_expansion(Ex)}};
+macro_pars([{var,_L,Name}, {',',_}|Ts], Args) ->
+ false = lists:member(Name, Args),
+ macro_pars(Ts, [Name|Args]).
+
+macro_expansion([{')',_Lp},{dot,_Ld}]) -> [];
+macro_expansion([{dot,_Ld}]) -> []; %Be nice, allow no right paren!
+macro_expansion([T|Ts]) ->
+ [T|macro_expansion(Ts)].
+
+%% expand_macros(Tokens, Macros)
+%% expand_macro(Tokens, MacroToken, RestTokens)
+%% Expand the macros in a list of tokens, making sure that an expansion
+%% gets the same location as the macro call.
+
+expand_macros(Type, MacT, M, Toks, Ms0) ->
+ %% (Type will always be 'atom')
+ {Ms, U} = Ms0,
+ Lm = loc(MacT),
+ check_uses([{Type,M}], [], U, Lm),
+ Tinfo = element(2, MacT),
+ case dict:find({Type,M}, Ms) of
+ {ok,{none,Exp}} ->
+ expand_macros(expand_macro(Exp, Tinfo, Toks, dict:new()), Ms0);
+ {ok,{As,Exp}} ->
+ {Bs,Toks1} = bind_args(Toks, Lm, M, As, dict:new()),
+ %%io:format("Bound arguments to macro ~w (~w)~n", [M,Bs]),
+ expand_macros(expand_macro(Exp, Tinfo, Toks1, Bs), Ms0);
+ {ok,undefined} ->
+ throw({error,Lm,{undefined,M}});
+ error ->
+ throw({error,Lm,{undefined,M}})
+ end.
+
+check_uses(undefined, _Anc, _U, _Lm) ->
+ ok;
+check_uses([], _Anc, _U, _Lm) ->
+ ok;
+check_uses([M|Rest], Anc, U, Lm) ->
+ case lists:member(M, Anc) of
+ true ->
+ {_, Name} = M,
+ throw({error,Lm,{circular,Name}});
+ false ->
+ L = get_macro_uses(M, U),
+ check_uses(L, [M|Anc], U, Lm),
+ check_uses(Rest, Anc, U, Lm)
+ end.
+
+get_macro_uses(M, U) ->
+ case dict:find(M, U) of
+ error ->
+ [];
+ {ok, L} ->
+ L
+ end.
+
+%% Macro expansion
+%% Note: io:scan_erl_form() does not return comments or white spaces.
+expand_macros([{'?',_Lq},{atom,_Lm,M}=MacT|Toks], Ms) ->
+ expand_macros(atom, MacT, M, Toks, Ms);
+%% Special macros
+expand_macros([{'?',_Lq},{var,Lm,'LINE'}=Tok|Toks], Ms) ->
+ {line,Line} = erl_scan:token_info(Tok, line),
+ [{integer,Lm,Line}|expand_macros(Toks, Ms)];
+expand_macros([{'?',_Lq},{var,_Lm,M}=MacT|Toks], Ms) ->
+ expand_macros(atom, MacT, M, Toks, Ms);
+%% Illegal macros
+expand_macros([{'?',_Lq},Token|_Toks], _Ms) ->
+ T = case erl_scan:token_info(Token, text) of
+ {text,Text} ->
+ Text;
+ undefined ->
+ {symbol,Symbol} = erl_scan:token_info(Token, symbol),
+ io_lib:write(Symbol)
+ end,
+ throw({error,loc(Token),{call,[$?|T]}});
+expand_macros([T|Ts], Ms) ->
+ [T|expand_macros(Ts, Ms)];
+expand_macros([], _Ms) -> [].
+
+%% bind_args(Tokens, MacroLocation, MacroName, ArgumentVars, Bindings)
+%% Collect the arguments to a macro call and check for correct number.
+
+bind_args([{'(',_Llp},{')',_Lrp}|Toks], _Lm, _M, [], Bs) ->
+ {Bs,Toks};
+bind_args([{'(',_Llp}|Toks0], Lm, M, [A|As], Bs) ->
+ {Arg,Toks1} = macro_arg(Toks0, [], []),
+ macro_args(Toks1, Lm, M, As, store_arg(Lm, M, A, Arg, Bs));
+bind_args(_Toks, Lm, M, _As, _Bs) ->
+ throw({error,Lm,{mismatch,M}}).
+
+macro_args([{')',_Lrp}|Toks], _Lm, _M, [], Bs) ->
+ {Bs,Toks};
+macro_args([{',',_Lc}|Toks0], Lm, M, [A|As], Bs) ->
+ {Arg,Toks1} = macro_arg(Toks0, [], []),
+ macro_args(Toks1, Lm, M, As, store_arg(Lm, M, A, Arg, Bs));
+macro_args([], Lm, M, _As, _Bs) ->
+ throw({error,Lm,{arg_error,M}});
+macro_args(_Toks, Lm, M, _As, _Bs) ->
+ throw({error,Lm,{mismatch,M}}).
+
+store_arg(L, M, _A, [], _Bs) ->
+ throw({error,L,{mismatch,M}});
+store_arg(_L, _M, A, Arg, Bs) ->
+ dict:store(A, Arg, Bs).
+
+%% macro_arg([Tok], [ClosePar], [ArgTok]) -> {[ArgTok],[RestTok]}.
+%% Collect argument tokens until we hit a ',' or a ')'. We know a
+%% enough about syntax to recognise "open parentheses" and keep
+%% scanning until matching "close parenthesis".
+
+macro_arg([{',',Lc}|Toks], [], Arg) ->
+ {lists:reverse(Arg),[{',',Lc}|Toks]};
+macro_arg([{')',Lrp}|Toks], [], Arg) ->
+ {lists:reverse(Arg),[{')',Lrp}|Toks]};
+macro_arg([{'(',Llp}|Toks], E, Arg) ->
+ macro_arg(Toks, [')'|E], [{'(',Llp}|Arg]);
+macro_arg([{'<<',Lls}|Toks], E, Arg) ->
+ macro_arg(Toks, ['>>'|E], [{'<<',Lls}|Arg]);
+macro_arg([{'[',Lls}|Toks], E, Arg) ->
+ macro_arg(Toks, [']'|E], [{'[',Lls}|Arg]);
+macro_arg([{'{',Llc}|Toks], E, Arg) ->
+ macro_arg(Toks, ['}'|E], [{'{',Llc}|Arg]);
+macro_arg([{'begin',Lb}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'begin',Lb}|Arg]);
+macro_arg([{'if',Li}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'if',Li}|Arg]);
+macro_arg([{'case',Lc}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'case',Lc}|Arg]);
+macro_arg([{'fun',Lc}|[{'(',_}|_]=Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'fun',Lc}|Arg]);
+macro_arg([{'receive',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'receive',Lr}|Arg]);
+macro_arg([{'try',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'try',Lr}|Arg]);
+macro_arg([{'cond',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'cond',Lr}|Arg]);
+macro_arg([{'query',Lr}|Toks], E, Arg) ->
+ macro_arg(Toks, ['end'|E], [{'query',Lr}|Arg]);
+macro_arg([{Rb,Lrb}|Toks], [Rb|E], Arg) -> %Found matching close
+ macro_arg(Toks, E, [{Rb,Lrb}|Arg]);
+macro_arg([T|Toks], E, Arg) ->
+ macro_arg(Toks, E, [T|Arg]);
+macro_arg([], _E, Arg) ->
+ {lists:reverse(Arg),[]}.
+
+%% expand_macro(MacroDef, MacroTokenInfo, RestTokens, Bindings)
+%% expand_arg(Argtokens, MacroTokens, MacroLocation, RestTokens, Bindings)
+%% Insert the macro expansion replacing macro parameters with their
+%% argument values, inserting the location of first the macro call
+%% and then the macro arguments, i.e. simulate textual expansion.
+
+expand_macro([{var,_Lv,V}|Ts], L, Rest, Bs) ->
+ case dict:find(V, Bs) of
+ {ok,Val} ->
+ %% lists:append(Val, expand_macro(Ts, L, Rest, Bs));
+ expand_arg(Val, Ts, L, Rest, Bs);
+ error ->
+ [{var,L,V}|expand_macro(Ts, L, Rest, Bs)]
+ end;
+expand_macro([{'?', _}, {'?', _}, {var,_Lv,V}|Ts], L, Rest, Bs) ->
+ case dict:find(V, Bs) of
+ {ok,Val} ->
+ %% lists:append(Val, expand_macro(Ts, L, Rest, Bs));
+ expand_arg(stringify(Val, L), Ts, L, Rest, Bs);
+ error ->
+ [{var,L,V}|expand_macro(Ts, L, Rest, Bs)]
+ end;
+expand_macro([T|Ts], L, Rest, Bs) ->
+ [setelement(2, T, L)|expand_macro(Ts, L, Rest, Bs)];
+expand_macro([], _L, Rest, _Bs) -> Rest.
+
+expand_arg([A|As], Ts, _L, Rest, Bs) ->
+ %% It is not obvious that the location of arguments should replace L.
+ NextL = element(2, A),
+ [A|expand_arg(As, Ts, NextL, Rest, Bs)];
+expand_arg([], Ts, L, Rest, Bs) ->
+ expand_macro(Ts, L, Rest, Bs).
+
+%%% stringify(Ts, L) returns a list of one token: a string which when
+%%% tokenized would yield the token list Ts.
+
+%% erl_scan:token_info(T, text) is not backward compatible with this.
+token_src({dot, _}) ->
+ ".";
+token_src({X, _}) when is_atom(X) ->
+ atom_to_list(X);
+token_src({var, _, X}) ->
+ atom_to_list(X);
+token_src({char,_,C}) ->
+ io_lib:write_char(C);
+token_src({string, _, X}) ->
+ lists:flatten(io_lib:format("~p", [X]));
+token_src({_, _, X}) ->
+ lists:flatten(io_lib:format("~w", [X])).
+
+stringify1([]) ->
+ [];
+stringify1([T | Tokens]) ->
+ [io_lib:format(" ~s", [token_src(T)]) | stringify1(Tokens)].
+
+stringify(Ts, L) ->
+ [$\s | S] = lists:flatten(stringify1(Ts)),
+ [{string, L, S}].
+
+%% epp_request(Epp)
+%% epp_request(Epp, Request)
+%% epp_reply(From, Reply)
+%% Handle communication with the epp.
+
+epp_request(Epp) ->
+ wait_epp_reply(Epp, erlang:monitor(process, Epp)).
+
+epp_request(Epp, Req) ->
+ Epp ! {epp_request,self(),Req},
+ wait_epp_reply(Epp, erlang:monitor(process, Epp)).
+
+epp_reply(From, Rep) ->
+ From ! {epp_reply,self(),Rep},
+ ok.
+
+wait_epp_reply(Epp, Mref) ->
+ receive
+ {epp_reply,Epp,Rep} ->
+ erlang:demonitor(Mref),
+ receive {'DOWN',Mref,_,_,_} -> ok after 0 -> ok end,
+ Rep;
+ {'DOWN',Mref,_,_,E} ->
+ receive {epp_reply,Epp,Rep} -> Rep
+ after 0 -> exit(E)
+ end
+ end.
+
+expand_var([$$ | _] = NewName) ->
+ case catch expand_var1(NewName) of
+ {ok, ExpName} ->
+ ExpName;
+ _ ->
+ NewName
+ end;
+expand_var(NewName) ->
+ NewName.
+
+expand_var1(NewName) ->
+ [[$$ | Var] | Rest] = filename:split(NewName),
+ Value = os:getenv(Var),
+ true = Value =/= false,
+ {ok, filename:join([Value | Rest])}.
+
+%% The line only. (Other tokens may have the column and text as well...)
+loc_attr(Line) when is_integer(Line) ->
+ Line;
+loc_attr({Line,_Column}) ->
+ Line.
+
+loc(Token) ->
+ {location,Location} = erl_scan:token_info(Token, location),
+ Location.
+
+abs_loc(Token) ->
+ loc(setelement(2, Token, abs_line(element(2, Token)))).
+
+neg_line(L) ->
+ erl_scan:set_attribute(line, L, fun(Line) -> -abs(Line) end).
+
+abs_line(L) ->
+ erl_scan:set_attribute(line, L, fun(Line) -> abs(Line) end).
+
+start_loc(Line) when is_integer(Line) ->
+ 1;
+start_loc({_Line, _Column}) ->
+ {1,1}.
+
+get_line(Line) when is_integer(Line) ->
+ Line;
+get_line({Line,_Column}) ->
+ Line.
+
+%% epp has always output -file attributes when entering and leaving
+%% included files (-include, -include_lib). Starting with R11B the
+%% -file attribute is also recognized in the input file. This is
+%% mainly aimed at yecc, the parser generator, which uses the -file
+%% attribute to get correct lines in messages referring to code
+%% supplied by the user (actions etc in .yrl files).
+%%
+%% In a perfect world (read: perfectly implemented applications such
+%% as Xref, Cover, Debugger, etc.) it would not be necessary to
+%% distinguish -file attributes from epp and the input file. The
+%% Debugger for example could have one window for each referred file,
+%% each window with its own set of breakpoints etc. The line numbers
+%% of the abstract code would then point into different windows
+%% depending on the -file attribute. [Note that if, as is the case for
+%% yecc, code has been copied into the file, then it is possible that
+%% the copied code differ from the one referred to by the -file
+%% attribute, which means that line numbers can mismatch.] In practice
+%% however it is very rare with Erlang functions in included files, so
+%% only one window is used per module. This means that the line
+%% numbers of the abstract code have to be adjusted to refer to the
+%% top-most source file. The function interpret_file_attributes/1
+%% below interprets the -file attribute and returns forms where line
+%% numbers refer to the top-most file. The -file attribute forms that
+%% have been output by epp (corresponding to -include and
+%% -include_lib) are kept, but the user's -file attributes are
+%% removed. This seems sufficient for now.
+%%
+%% It turns out to be difficult to distinguish -file attributes in the
+%% input file from the ones added by epp unless some action is taken.
+%% The (less than perfect) solution employed is to let epp assign
+%% negative line numbers to user supplied -file attributes.
+
+%% Note: it is assumed that the second element is a line or a key-list
+%% where 'line' can be found.
+
+interpret_file_attribute(Forms) ->
+ interpret_file_attr(Forms, 0, []).
+
+interpret_file_attr([{attribute,Loc,file,{File,Line}}=Form | Forms],
+ Delta, Fs) ->
+ {line, L} = erl_scan:attributes_info(Loc, line),
+ if
+ L < 0 ->
+ %% -file attribute
+ interpret_file_attr(Forms, (abs(L) + Delta) - Line, Fs);
+ true ->
+ %% -include or -include_lib
+ % true = L =:= Line,
+ case Fs of
+ [_, Delta1, File | Fs1] -> % end of included file
+ [Form | interpret_file_attr(Forms, Delta1, [File | Fs1])];
+ _ -> % start of included file
+ [Form | interpret_file_attr(Forms, 0, [File, Delta | Fs])]
+ end
+ end;
+interpret_file_attr([Form0 | Forms], Delta, Fs) ->
+ F = fun(Attrs) ->
+ F2 = fun(L) -> abs(L) + Delta end,
+ erl_scan:set_attribute(line, Attrs, F2)
+ end,
+ Form = erl_lint:modify_line(Form0, F),
+ [Form | interpret_file_attr(Forms, Delta, Fs)];
+interpret_file_attr([], _Delta, _Fs) ->
+ [].
+
diff --git a/lib/stdlib/src/erl_bits.erl b/lib/stdlib/src/erl_bits.erl
new file mode 100644
index 0000000000..62f6d00fae
--- /dev/null
+++ b/lib/stdlib/src/erl_bits.erl
@@ -0,0 +1,186 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(erl_bits).
+
+-export([system_bittypes/0,
+ system_bitdefault/0,
+ set_bit_type/2,
+ as_list/1]).
+
+-include("../include/erl_bits.hrl").
+
+%% Dummies.
+
+-spec system_bitdefault() -> 'no_system_bitdefault'.
+
+system_bitdefault() -> no_system_bitdefault.
+
+-spec system_bittypes() -> 'no_system_types'.
+
+system_bittypes() -> no_system_types.
+
+-spec as_list(#bittype{}) ->
+ [bt_endian() | bt_sign() | bt_type() | {'unit', 'undefined' | bt_unit()}].
+
+as_list(Bt) ->
+ [Bt#bittype.type,{unit,Bt#bittype.unit},Bt#bittype.sign,Bt#bittype.endian].
+
+%% XXX: tuple() below stands for what's produced by the parser
+%% {integer,L,M} | {var,L,VAR} | {atom,L,ATOM} | {op,L,OP,OP1,OP2} | ...
+-type size() :: 'all' | 'unknown' | non_neg_integer() | tuple(). % XXX: REFINE
+-type type() :: 'bytes' | 'bitstring' | 'bits'
+ | bt_type() | bt_endian() | bt_sign()
+ | {'unit', 'undefined' | bt_unit()}.
+
+-spec set_bit_type('default' | size(), 'default' | [type()]) ->
+ {'ok', 'undefined' | size(), #bittype{}} |
+ {'error', {'undefined_bittype', term()}} |
+ {'error', {'bittype_mismatch', term(), term(), string()}}.
+
+set_bit_type(Size, default) ->
+ set_bit_type(Size, []);
+set_bit_type(Size, TypeList) ->
+ try
+ #bittype{type=Type,unit=Unit,sign=Sign,endian=Endian} =
+ set_bit(TypeList),
+ apply_defaults(Type, Size, Unit, Sign, Endian)
+ catch
+ throw:Error -> Error
+ end.
+
+set_bit([]) -> #bittype{};
+set_bit([H|T]) -> set_bit_1(T, type_to_record(H)).
+
+set_bit_1([T0|Ts], Bt0) ->
+ Type = type_to_record(T0),
+ Bt = merge_bittype(Type, Bt0),
+ set_bit_1(Ts, Bt);
+set_bit_1([], Bt) -> Bt.
+
+type_to_record(integer) -> #bittype{type = integer};
+type_to_record(utf8) -> #bittype{type = utf8};
+type_to_record(utf16) -> #bittype{type = utf16};
+type_to_record(utf32) -> #bittype{type = utf32};
+type_to_record(float) -> #bittype{type = float};
+type_to_record(binary) -> #bittype{type = binary};
+type_to_record(bytes) -> #bittype{type = binary, unit = 8};
+type_to_record(bitstring) -> #bittype{type = binary, unit = 1};
+type_to_record(bits) -> #bittype{type = binary, unit = 1};
+
+type_to_record({unit,undefined}) ->
+ #bittype{unit=undefined};
+type_to_record({unit,Sz}) when is_integer(Sz), Sz > 0, Sz =< 256 ->
+ #bittype{unit=Sz};
+
+type_to_record(big) -> #bittype{endian = big};
+type_to_record(little) -> #bittype{endian = little};
+type_to_record(native) -> #bittype{endian = native};
+
+type_to_record(signed) -> #bittype{sign = signed};
+type_to_record(unsigned) -> #bittype{sign = unsigned};
+
+type_to_record(Name) -> throw({error,{undefined_bittype,Name}}).
+
+%%
+%% Merge two bit type specifications.
+%%
+merge_bittype(B1, B2) ->
+ Endian = merge_field(B1#bittype.endian, B2#bittype.endian, endianness),
+ Sign = merge_field(B1#bittype.sign, B2#bittype.sign, sign),
+ Type = merge_field(B1#bittype.type, B2#bittype.type, type),
+ Unit = merge_field(B1#bittype.unit, B2#bittype.unit, unit),
+ #bittype{type=Type,unit=Unit,endian=Endian,sign=Sign}.
+
+merge_field(undefined, B, _) -> B;
+merge_field(A, undefined, _) -> A;
+merge_field(A, A, _) -> A;
+merge_field(X, Y, What) ->
+ throw({error,{bittype_mismatch,X,Y,atom_to_list(What)}}).
+
+%%
+%% Defaults are as follows.
+%%
+%% The default is integer.
+%% The default size is 'all' for binaries, 8 for integers, 64 for floats.
+%% No unit must be given if the size is not given.
+%% The default unit size is 8 for binaries, and 1 for integers and floats.
+%% The default sign is always unsigned.
+%% The default endian is always big.
+%%
+
+apply_defaults(undefined, Size, Unit, Sign, Endian) -> %default type
+ apply_defaults(integer, Size, Unit, Sign, Endian);
+
+apply_defaults(binary, default, Unit, Sign, Endian) -> %default size
+ %% check_unit(Unit), removed to allow bitlevel binaries
+ apply_defaults(binary, all, Unit, Sign, Endian);
+apply_defaults(integer, default, Unit, Sign, Endian) ->
+ check_unit(Unit),
+ apply_defaults(integer, 8, 1, Sign, Endian);
+apply_defaults(utf8=Type, default, Unit, Sign, Endian) ->
+ apply_defaults(Type, undefined, Unit, Sign, Endian);
+apply_defaults(utf16=Type, default, Unit, Sign, Endian) ->
+ apply_defaults(Type, undefined, Unit, Sign, Endian);
+apply_defaults(utf32=Type, default, Unit, Sign, Endian) ->
+ apply_defaults(Type, undefined, Unit, Sign, Endian);
+apply_defaults(float, default, Unit, Sign, Endian) ->
+ check_unit(Unit),
+ apply_defaults(float, 64, 1, Sign, Endian);
+
+apply_defaults(binary, Size, undefined, Sign, Endian) -> %default unit
+ apply_defaults(binary, Size, 8, Sign, Endian);
+apply_defaults(integer, Size, undefined, Sign, Endian) ->
+ apply_defaults(integer, Size, 1, Sign, Endian);
+apply_defaults(float, Size, undefined, Sign, Endian) ->
+ apply_defaults(float, Size, 1, Sign, Endian);
+
+apply_defaults(Type, Size, Unit, undefined, Endian) -> %default sign
+ apply_defaults(Type, Size, Unit, unsigned, Endian);
+
+apply_defaults(Type, Size, Unit, Sign, undefined) -> %default endian
+ apply_defaults(Type, Size, Unit, Sign, big);
+
+apply_defaults(Type, Size, Unit, Sign, Endian) -> %done
+ check_size_unit(Type, Size, Unit),
+ {ok,Size,#bittype{type=Type,unit=Unit,sign=Sign,endian=Endian}}.
+
+check_size_unit(utf8, Size, Unit) ->
+ check_size_unit_1(Size, Unit);
+check_size_unit(utf16, Size, Unit) ->
+ check_size_unit_1(Size, Unit);
+check_size_unit(utf32, Size, Unit) ->
+ check_size_unit_1(Size, Unit);
+check_size_unit(_, _, _) -> ok.
+
+check_size_unit_1(Size, Unit) ->
+ case Size of
+ default -> ok;
+ undefined -> ok;
+ {atom,_,undefined} -> ok;
+ {value,_,undefined} -> ok;
+ _ -> throw({error,utf_bittype_size_or_unit})
+ end,
+ case Unit of
+ undefined -> ok;
+ _ -> throw({error,utf_bittype_size_or_unit})
+ end.
+
+check_unit(undefined) -> ok;
+check_unit(_) -> throw({error,bittype_unit}).
diff --git a/lib/stdlib/src/erl_compile.erl b/lib/stdlib/src/erl_compile.erl
new file mode 100644
index 0000000000..d9d15e05f8
--- /dev/null
+++ b/lib/stdlib/src/erl_compile.erl
@@ -0,0 +1,233 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_compile).
+
+-include("erl_compile.hrl").
+-include("file.hrl").
+
+-export([compile_cmdline/1]).
+
+%% Mapping from extension to {M,F} to run the correct compiler.
+
+compiler(".erl") -> {compile, compile};
+compiler(".S") -> {compile, compile_asm};
+compiler(".beam") -> {compile, compile_beam};
+compiler(".core") -> {compile, compile_core};
+compiler(".mib") -> {snmpc, compile};
+compiler(".bin") -> {snmpc, mib_to_hrl};
+compiler(".xrl") -> {leex, compile};
+compiler(".yrl") -> {yecc, compile};
+compiler(".script") -> {systools, script2boot};
+compiler(".rel") -> {systools, compile_rel};
+compiler(".idl") -> {ic, compile};
+compiler(".asn1") -> {asn1ct, compile_asn1};
+compiler(".asn") -> {asn1ct, compile_asn};
+compiler(".py") -> {asn1ct, compile_py};
+compiler(".xml") -> {xmerl_scan, process};
+compiler(_) -> no.
+
+%% Entry from command line.
+
+-type cmd_line_arg() :: atom() | string().
+
+-spec compile_cmdline([cmd_line_arg()]) -> no_return().
+
+compile_cmdline(List) ->
+ case compile(List) of
+ ok -> my_halt(0);
+ error -> my_halt(1);
+ _ -> my_halt(2)
+ end.
+
+my_halt(Reason) ->
+ case process_info(group_leader(), status) of
+ {_,waiting} ->
+ %% Now all output data is down in the driver.
+ %% Give the driver some extra time before halting.
+ receive after 1 -> ok end,
+ halt(Reason);
+ _ ->
+ %% Probably still processing I/O requests.
+ erlang:yield(),
+ my_halt(Reason)
+ end.
+
+%% Run the the compiler in a separate process, trapping EXITs.
+
+compile(List) ->
+ process_flag(trap_exit, true),
+ Pid = spawn_link(fun() -> compiler_runner(List) end),
+ receive
+ {'EXIT', Pid, {compiler_result, Result}} ->
+ Result;
+ {'EXIT', Pid, Reason} ->
+ io:format("Runtime error: ~p~n", [Reason]),
+ error
+ end.
+
+-spec compiler_runner([cmd_line_arg()]) -> no_return().
+
+compiler_runner(List) ->
+ %% We don't want the current directory in the code path.
+ %% Remove it.
+ Path = [D || D <- code:get_path(), D =/= "."],
+ true = code:set_path(Path),
+ exit({compiler_result, compile1(List)}).
+
+%% Parses the first part of the option list.
+
+compile1(['@cwd', Cwd|Rest]) ->
+ CwdL = atom_to_list(Cwd),
+ compile1(Rest, CwdL, #options{outdir=CwdL, cwd=CwdL});
+compile1(Args) ->
+ %% From R13B02, the @cwd argument is optional.
+ {ok, Cwd} = file:get_cwd(),
+ compile1(Args, Cwd, #options{outdir=Cwd, cwd=Cwd}).
+
+%% Parses all options.
+
+compile1(['@i', Dir|Rest], Cwd, Opts) ->
+ AbsDir = filename:absname(Dir, Cwd),
+ compile1(Rest, Cwd, Opts#options{includes=[AbsDir|Opts#options.includes]});
+compile1(['@outdir', Dir|Rest], Cwd, Opts) ->
+ AbsName = filename:absname(Dir, Cwd),
+ case file_or_directory(AbsName) of
+ file ->
+ compile1(Rest, Cwd, Opts#options{outfile=AbsName});
+ directory ->
+ compile1(Rest, Cwd, Opts#options{outdir=AbsName})
+ end;
+compile1(['@d', Name|Rest], Cwd, Opts) ->
+ Defines = Opts#options.defines,
+ compile1(Rest, Cwd, Opts#options{defines=[Name|Defines]});
+compile1(['@dv', Name, Term|Rest], Cwd, Opts) ->
+ Defines = Opts#options.defines,
+ Value = make_term(atom_to_list(Term)),
+ compile1(Rest, Cwd, Opts#options{defines=[{Name, Value}|Defines]});
+compile1(['@warn', Level0|Rest], Cwd, Opts) ->
+ case catch list_to_integer(atom_to_list(Level0)) of
+ Level when is_integer(Level) ->
+ compile1(Rest, Cwd, Opts#options{warning=Level});
+ _ ->
+ compile1(Rest, Cwd, Opts)
+ end;
+compile1(['@verbose', false|Rest], Cwd, Opts) ->
+ compile1(Rest, Cwd, Opts#options{verbose=false});
+compile1(['@verbose', true|Rest], Cwd, Opts) ->
+ compile1(Rest, Cwd, Opts#options{verbose=true});
+compile1(['@optimize', Atom|Rest], Cwd, Opts) ->
+ Term = make_term(atom_to_list(Atom)),
+ compile1(Rest, Cwd, Opts#options{optimize=Term});
+compile1(['@option', Atom|Rest], Cwd, Opts) ->
+ Term = make_term(atom_to_list(Atom)),
+ Specific = Opts#options.specific,
+ compile1(Rest, Cwd, Opts#options{specific=[Term|Specific]});
+compile1(['@output_type', OutputType|Rest], Cwd, Opts) ->
+ compile1(Rest, Cwd, Opts#options{output_type=OutputType});
+compile1(['@files'|Rest], Cwd, Opts) ->
+ Includes = lists:reverse(Opts#options.includes),
+ compile2(Rest, Cwd, Opts#options{includes=Includes}).
+
+compile2(Files, Cwd, Opts) ->
+ case {Opts#options.outfile, length(Files)} of
+ {"", _} ->
+ compile3(Files, Cwd, Opts);
+ {[_|_], 1} ->
+ compile3(Files, Cwd, Opts);
+ {[_|_], _N} ->
+ io:format("Output file name given, but more than one input file.~n"),
+ error
+ end.
+
+%% Compiles the list of files, until done or compilation fails.
+
+compile3([File|Rest], Cwd, Options) ->
+ Ext = filename:extension(File),
+ Root = filename:rootname(File),
+ InFile = filename:absname(Root, Cwd),
+ OutFile =
+ case Options#options.outfile of
+ "" ->
+ filename:join(Options#options.outdir, filename:basename(Root));
+ Outfile ->
+ filename:rootname(Outfile)
+ end,
+ case compile_file(Ext, InFile, OutFile, Options) of
+ ok ->
+ compile3(Rest, Cwd, Options);
+ Other ->
+ Other
+ end;
+compile3([], _Cwd, _Options) -> ok.
+
+%% Invokes the appropriate compiler, depending on the file extension.
+
+compile_file("", Input, _Output, _Options) ->
+ io:format("File has no extension: ~s~n", [Input]),
+ error;
+compile_file(Ext, Input, Output, Options) ->
+ case compiler(Ext) of
+ no ->
+ io:format("Unknown extension: '~s'\n", [Ext]),
+ error;
+ {M, F} ->
+ case catch M:F(Input, Output, Options) of
+ ok -> ok;
+ error -> error;
+ {'EXIT',Reason} ->
+ io:format("Compiler function ~w:~w/3 failed:\n~p~n",
+ [M,F,Reason]),
+ error;
+ Other ->
+ io:format("Compiler function ~w:~w/3 returned:\n~p~n",
+ [M,F,Other]),
+ error
+ end
+ end.
+
+%% Guesses if a give name refers to a file or a directory.
+
+file_or_directory(Name) ->
+ case file:read_file_info(Name) of
+ {ok, #file_info{type=regular}} ->
+ file;
+ {ok, _} ->
+ directory;
+ {error, _} ->
+ case filename:extension(Name) of
+ [] -> directory;
+ _Other -> file
+ end
+ end.
+
+%% Makes an Erlang term given a string.
+
+make_term(Str) ->
+ case erl_scan:string(Str) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
+ {ok, Term} -> Term;
+ {error, {_,_,Reason}} ->
+ io:format("~s: ~s~n", [Reason, Str]),
+ throw(error)
+ end;
+ {error, {_,_,Reason}, _} ->
+ io:format("~s: ~s~n", [Reason, Str]),
+ throw(error)
+ end.
diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl
new file mode 100644
index 0000000000..ea1b179ee5
--- /dev/null
+++ b/lib/stdlib/src/erl_eval.erl
@@ -0,0 +1,1108 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_eval).
+
+%% An evaluator for Erlang abstract syntax.
+
+-export([exprs/2,exprs/3,exprs/4,expr/2,expr/3,expr/4,expr/5,
+ expr_list/2,expr_list/3,expr_list/4]).
+-export([new_bindings/0,bindings/1,binding/2,add_binding/3,del_binding/2]).
+
+-export([is_constant_expr/1, partial_eval/1]).
+
+%% Is used by standalone Erlang (escript).
+%% Also used by shell.erl.
+-export([match_clause/4]).
+
+-export([check_command/2, fun_data/1]).
+
+-import(lists, [reverse/1,foldl/3,member/2]).
+
+%% exprs(ExpressionSeq, Bindings)
+%% exprs(ExpressionSeq, Bindings, LocalFuncHandler)
+%% exprs(ExpressionSeq, Bindings, LocalFuncHandler, ExternalFuncHandler)
+%% Returns:
+%% {value,Value,NewBindings}
+%% or {'EXIT', Reason}
+%% Only exprs/2 checks the command by calling erl_lint. The reason is
+%% that if there is a function handler present, then it is possible
+%% that there are valid constructs in Expression to be taken care of
+%% by a function handler but considerad errors by erl_lint.
+
+exprs(Exprs, Bs) ->
+ case check_command(Exprs, Bs) of
+ ok ->
+ exprs(Exprs, Bs, none, none, none);
+ {error,{_Line,_Mod,Error}} ->
+ erlang:raise(error, Error, [{?MODULE,exprs,2}])
+ end.
+
+exprs(Exprs, Bs, Lf) ->
+ exprs(Exprs, Bs, Lf, none, none).
+
+exprs(Exprs, Bs, Lf, Ef) ->
+ exprs(Exprs, Bs, Lf, Ef, none).
+
+exprs([E], Bs0, Lf, Ef, RBs) ->
+ expr(E, Bs0, Lf, Ef, RBs);
+exprs([E|Es], Bs0, Lf, Ef, RBs) ->
+ RBs1 = none,
+ {value,_V,Bs} = expr(E, Bs0, Lf, Ef, RBs1),
+ exprs(Es, Bs, Lf, Ef, RBs).
+
+%% expr(Expression, Bindings)
+%% expr(Expression, Bindings, LocalFuncHandler)
+%% expr(Expression, Bindings, LocalFuncHandler, ExternalFuncHandler)
+%% Returns:
+%% {value,Value,NewBindings}
+%% or {'EXIT', Reason}
+%%
+%% Only expr/2 checks the command by calling erl_lint. See exprs/2.
+
+expr(E, Bs) ->
+ case check_command([E], Bs) of
+ ok ->
+ expr(E, Bs, none, none, none);
+ {error,{_Line,_Mod,Error}} ->
+ erlang:raise(error, Error, [{?MODULE,expr,2}])
+ end.
+
+expr(E, Bs, Lf) ->
+ expr(E, Bs, Lf, none, none).
+
+expr(E, Bs, Lf, Ef) ->
+ expr(E, Bs, Lf, Ef, none).
+
+%% Check a command (a list of expressions) by calling erl_lint.
+
+check_command(Es, Bs) ->
+ Opts = [bitlevel_binaries,binary_comprehension],
+ case erl_lint:exprs_opt(Es, bindings(Bs), Opts) of
+ {ok,_Ws} ->
+ ok;
+ {error,[{_File,[Error|_]}],_Ws} ->
+ {error,Error}
+ end.
+
+%% Check whether a term F is a function created by this module.
+%% Returns 'false' if not, otherwise {fun_data,Imports,Clauses}.
+
+fun_data(F) when is_function(F) ->
+ case erlang:fun_info(F, module) of
+ {module,erl_eval} ->
+ {env, [FBs,_FEf,_FLf,FCs]} = erlang:fun_info(F, env),
+ {fun_data,FBs,FCs};
+ _ ->
+ false
+ end;
+fun_data(_T) ->
+ false.
+
+expr({var,_,V}, Bs, _Lf, _Ef, RBs) ->
+ case binding(V, Bs) of
+ {value,Val} ->
+ ret_expr(Val, Bs, RBs);
+ unbound -> % Should not happen.
+ erlang:raise(error, {unbound,V}, stacktrace())
+ end;
+expr({char,_,C}, Bs, _Lf, _Ef, RBs) ->
+ ret_expr(C, Bs, RBs);
+expr({integer,_,I}, Bs, _Lf, _Ef, RBs) ->
+ ret_expr(I, Bs, RBs);
+expr({float,_,F}, Bs, _Lf, _Ef, RBs) ->
+ ret_expr(F, Bs, RBs);
+expr({atom,_,A}, Bs, _Lf, _Ef, RBs) ->
+ ret_expr(A, Bs, RBs);
+expr({string,_,S}, Bs, _Lf, _Ef, RBs) ->
+ ret_expr(S, Bs, RBs);
+expr({nil, _}, Bs, _Lf, _Ef, RBs) ->
+ ret_expr([], Bs, RBs);
+expr({cons,_,H0,T0}, Bs0, Lf, Ef, RBs) ->
+ {value,H,Bs1} = expr(H0, Bs0, Lf, Ef, none),
+ {value,T,Bs2} = expr(T0, Bs0, Lf, Ef, none),
+ ret_expr([H|T], merge_bindings(Bs1, Bs2), RBs);
+expr({lc,_,E,Qs}, Bs, Lf, Ef, RBs) ->
+ eval_lc(E, Qs, Bs, Lf, Ef, RBs);
+expr({bc,_,E,Qs}, Bs, Lf, Ef, RBs) ->
+ eval_bc(E, Qs, Bs, Lf, Ef, RBs);
+expr({tuple,_,Es}, Bs0, Lf, Ef, RBs) ->
+ {Vs,Bs} = expr_list(Es, Bs0, Lf, Ef),
+ ret_expr(list_to_tuple(Vs), Bs, RBs);
+expr({record_field,_,_,_}=Mod, Bs, _Lf, _Ef, RBs) ->
+ case expand_module_name(Mod, Bs) of
+ {atom,_,A} ->
+ ret_expr(A, Bs, RBs); %% This is the "x.y" syntax
+ _ ->
+ erlang:raise(error, {badexpr, '.'}, stacktrace())
+ end;
+expr({record_field,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+ erlang:raise(error, {undef_record,Name}, stacktrace());
+expr({record_index,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+ erlang:raise(error, {undef_record,Name}, stacktrace());
+expr({record,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+ erlang:raise(error, {undef_record,Name}, stacktrace());
+expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) ->
+ erlang:raise(error, {undef_record,Name}, stacktrace());
+expr({block,_,Es}, Bs, Lf, Ef, RBs) ->
+ exprs(Es, Bs, Lf, Ef, RBs);
+expr({'if',_,Cs}, Bs, Lf, Ef, RBs) ->
+ if_clauses(Cs, Bs, Lf, Ef, RBs);
+expr({'case',_,E,Cs}, Bs0, Lf, Ef, RBs) ->
+ {value,Val,Bs} = expr(E, Bs0, Lf, Ef, none),
+ case_clauses(Val, Cs, Bs, Lf, Ef, RBs);
+expr({'try',_,B,Cases,Catches,AB}, Bs, Lf, Ef, RBs) ->
+ try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs);
+expr({'receive',_,Cs}, Bs, Lf, Ef, RBs) ->
+ receive_clauses(Cs, Bs, Lf, Ef, [], RBs);
+expr({'receive',_, Cs, E, TB}, Bs0, Lf, Ef, RBs) ->
+ {value,T,Bs} = expr(E, Bs0, Lf, Ef, none),
+ receive_clauses(T, Cs, {TB,Bs}, Bs0, Lf, Ef, [], RBs);
+expr({'fun',_Line,{function,Mod,Name,Arity}}, Bs, _Lf, _Ef, RBs) ->
+ F = erlang:make_fun(Mod, Name, Arity),
+ ret_expr(F, Bs, RBs);
+expr({'fun',_Line,{function,Name,Arity}}, _Bs0, _Lf, _Ef, _RBs) -> % R8
+ %% Don't know what to do...
+ erlang:raise(error, undef, [{erl_eval,Name,Arity}|stacktrace()]);
+expr({'fun',Line,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs) ->
+ %% Save only used variables in the function environment.
+ %% {value,L,V} are hidden while lint finds used variables.
+ {Ex1, _} = hide_calls(Ex, 0),
+ {ok,Used} = erl_lint:used_vars([Ex1], Bs),
+ En = orddict:filter(fun(K,_V) -> member(K,Used) end, Bs),
+ %% This is a really ugly hack!
+ F =
+ case length(element(3,hd(Cs))) of
+ 0 -> fun () -> eval_fun(Cs, [], En, Lf, Ef) end;
+ 1 -> fun (A) -> eval_fun(Cs, [A], En, Lf, Ef) end;
+ 2 -> fun (A,B) -> eval_fun(Cs, [A,B], En, Lf, Ef) end;
+ 3 -> fun (A,B,C) -> eval_fun(Cs, [A,B,C], En, Lf, Ef) end;
+ 4 -> fun (A,B,C,D) -> eval_fun(Cs, [A,B,C,D], En, Lf, Ef) end;
+ 5 -> fun (A,B,C,D,E) -> eval_fun(Cs, [A,B,C,D,E], En, Lf, Ef) end;
+ 6 -> fun (A,B,C,D,E,F) -> eval_fun(Cs, [A,B,C,D,E,F], En, Lf, Ef) end;
+ 7 -> fun (A,B,C,D,E,F,G) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G], En, Lf, Ef) end;
+ 8 -> fun (A,B,C,D,E,F,G,H) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H], En, Lf, Ef) end;
+ 9 -> fun (A,B,C,D,E,F,G,H,I) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I], En, Lf, Ef) end;
+ 10 -> fun (A,B,C,D,E,F,G,H,I,J) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J], En, Lf, Ef) end;
+ 11 -> fun (A,B,C,D,E,F,G,H,I,J,K) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K], En, Lf, Ef) end;
+ 12 -> fun (A,B,C,D,E,F,G,H,I,J,K,L) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L], En, Lf, Ef) end;
+ 13 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M], En, Lf, Ef) end;
+ 14 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N], En, Lf, Ef) end;
+ 15 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O], En, Lf, Ef) end;
+ 16 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P], En, Lf, Ef) end;
+ 17 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q], En, Lf, Ef) end;
+ 18 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R], En, Lf, Ef) end;
+ 19 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S],
+ En, Lf, Ef) end;
+ 20 -> fun (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T],
+ En, Lf, Ef) end;
+ _Other ->
+ erlang:raise(error, {'argument_limit',{'fun',Line,Cs}},
+ stacktrace())
+ end,
+ ret_expr(F, Bs, RBs);
+expr({call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[{lc,_,_E,_Qs}=LC | As0]},
+ Bs0, Lf, Ef, RBs) when length(As0) =< 1 ->
+ %% No expansion or evaluation of module name or function name.
+ MaxLine = find_maxline(LC),
+ {LC1, D} = hide_calls(LC, MaxLine),
+ case qlc:transform_from_evaluator(LC1, Bs0) of
+ {ok,{call,L,Remote,[QLC]}} ->
+ QLC1 = unhide_calls(QLC, MaxLine, D),
+ expr({call,L,Remote,[QLC1 | As0]}, Bs0, Lf, Ef, RBs);
+ {not_ok,Error} ->
+ ret_expr(Error, Bs0, RBs)
+ end;
+expr({call,L1,{remote,L2,{record_field,_,{atom,_,''},{atom,_,qlc}=Mod},
+ {atom,_,q}=Func},
+ [{lc,_,_E,_Qs} | As0]=As},
+ Bs, Lf, Ef, RBs) when length(As0) =< 1 ->
+ expr({call,L1,{remote,L2,Mod,Func},As}, Bs, Lf, Ef, RBs);
+expr({call,_,{remote,_,Mod,Func},As0}, Bs0, Lf, Ef, RBs) ->
+ Mod1 = expand_module_name(Mod, Bs0),
+ {value,M,Bs1} = expr(Mod1, Bs0, Lf, Ef, none),
+ {value,F,Bs2} = expr(Func, Bs0, Lf, Ef, none),
+ {As,Bs3} = expr_list(As0, merge_bindings(Bs1, Bs2), Lf, Ef),
+ %% M could be a parameterized module (not an atom).
+ case is_atom(M) andalso erl_internal:bif(M, F, length(As)) of
+ true ->
+ bif(F, As, Bs3, Ef, RBs);
+ false ->
+ do_apply({M,F}, As, Bs3, Ef, RBs)
+ end;
+expr({call,_,{atom,_,Func},As0}, Bs0, Lf, Ef, RBs) ->
+ case erl_internal:bif(Func, length(As0)) of
+ true ->
+ {As,Bs} = expr_list(As0, Bs0, Lf, Ef),
+ bif(Func, As, Bs, Ef, RBs);
+ false ->
+ local_func(Func, As0, Bs0, Lf, RBs)
+ end;
+expr({call,_,Func0,As0}, Bs0, Lf, Ef, RBs) -> % function or {Mod,Fun}
+ {value,Func,Bs1} = expr(Func0, Bs0, Lf, Ef, none),
+ {As,Bs2} = expr_list(As0, Bs1, Lf, Ef),
+ do_apply(Func, As, Bs2, Ef, RBs);
+expr({'catch',_,Expr}, Bs0, Lf, Ef, RBs) ->
+ Ref = make_ref(),
+ case catch {Ref,expr(Expr, Bs0, Lf, Ef, none)} of
+ {Ref,{value,V,Bs}} -> % Nothing was thrown (guaranteed).
+ ret_expr(V, Bs, RBs);
+ Other ->
+ ret_expr(Other, Bs0, RBs)
+ end;
+expr({match,_,Lhs,Rhs0}, Bs0, Lf, Ef, RBs) ->
+ {value,Rhs,Bs1} = expr(Rhs0, Bs0, Lf, Ef, none),
+ case match(Lhs, Rhs, Bs1) of
+ {match,Bs} ->
+ ret_expr(Rhs, Bs, RBs);
+ nomatch ->
+ erlang:raise(error, {badmatch,Rhs}, stacktrace())
+ end;
+expr({op,_,Op,A0}, Bs0, Lf, Ef, RBs) ->
+ {value,A,Bs} = expr(A0, Bs0, Lf, Ef, none),
+ eval_op(Op, A, Bs, Ef, RBs);
+expr({op,_,'andalso',L0,R0}, Bs0, Lf, Ef, RBs) ->
+ {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none),
+ V = case L of
+ true ->
+ {value,R,_} = expr(R0, Bs1, Lf, Ef, none),
+ R;
+ false -> false;
+ _ -> erlang:raise(error, {badarg,L}, stacktrace())
+ end,
+ ret_expr(V, Bs1, RBs);
+expr({op,_,'orelse',L0,R0}, Bs0, Lf, Ef, RBs) ->
+ {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none),
+ V = case L of
+ true -> true;
+ false ->
+ {value,R,_} = expr(R0, Bs1, Lf, Ef, none),
+ R;
+ _ -> erlang:raise(error, {badarg,L}, stacktrace())
+ end,
+ ret_expr(V, Bs1, RBs);
+expr({op,_,Op,L0,R0}, Bs0, Lf, Ef, RBs) ->
+ {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none),
+ {value,R,Bs2} = expr(R0, Bs0, Lf, Ef, none),
+ eval_op(Op, L, R, merge_bindings(Bs1, Bs2), Ef, RBs);
+expr({bin,_,Fs}, Bs0, Lf, Ef, RBs) ->
+ EvalFun = fun(E, B) -> expr(E, B, Lf, Ef, none) end,
+ {value,V,Bs} = eval_bits:expr_grp(Fs, Bs0, EvalFun),
+ ret_expr(V, Bs, RBs);
+expr({remote,_,_,_}, _Bs, _Lf, _Ef, _RBs) ->
+ erlang:raise(error, {badexpr,':'}, stacktrace());
+expr({value,_,Val}, Bs, _Lf, _Ef, RBs) -> % Special case straight values.
+ ret_expr(Val, Bs, RBs).
+
+find_maxline(LC) ->
+ put('$erl_eval_max_line', 0),
+ F = fun(L) ->
+ case is_integer(L) and (L > get('$erl_eval_max_line')) of
+ true -> put('$erl_eval_max_line', L);
+ false -> ok
+ end end,
+ _ = erl_lint:modify_line(LC, F),
+ erase('$erl_eval_max_line').
+
+hide_calls(LC, MaxLine) ->
+ LineId0 = MaxLine + 1,
+ {NLC, _, D} = hide(LC, LineId0, dict:new()),
+ {NLC, D}.
+
+%% v/1 and local calls are hidden.
+hide({value,L,V}, Id, D) ->
+ {{atom,Id,ok}, Id+1, dict:store(Id, {value,L,V}, D)};
+hide({call,L,{atom,_,N}=Atom,Args}, Id0, D0) ->
+ {NArgs, Id, D} = hide(Args, Id0, D0),
+ C = case erl_internal:bif(N, length(Args)) of
+ true ->
+ {call,L,Atom,NArgs};
+ false ->
+ {call,Id,{remote,L,{atom,L,m},{atom,L,f}},NArgs}
+ end,
+ {C, Id+1, dict:store(Id, {call,Atom}, D)};
+hide(T0, Id0, D0) when is_tuple(T0) ->
+ {L, Id, D} = hide(tuple_to_list(T0), Id0, D0),
+ {list_to_tuple(L), Id, D};
+hide([E0 | Es0], Id0, D0) ->
+ {E, Id1, D1} = hide(E0, Id0, D0),
+ {Es, Id, D} = hide(Es0, Id1, D1),
+ {[E | Es], Id, D};
+hide(E, Id, D) ->
+ {E, Id, D}.
+
+unhide_calls({atom,Id,ok}, MaxLine, D) when Id > MaxLine ->
+ dict:fetch(Id, D);
+unhide_calls({call,Id,{remote,L,_M,_F},Args}, MaxLine, D) when Id > MaxLine ->
+ {call,Atom} = dict:fetch(Id, D),
+ {call,L,Atom,unhide_calls(Args, MaxLine, D)};
+unhide_calls(T, MaxLine, D) when is_tuple(T) ->
+ list_to_tuple(unhide_calls(tuple_to_list(T), MaxLine, D));
+unhide_calls([E | Es], MaxLine, D) ->
+ [unhide_calls(E, MaxLine, D) | unhide_calls(Es, MaxLine, D)];
+unhide_calls(E, _MaxLine, _D) ->
+ E.
+
+%% local_func(Function, Arguments, Bindings, LocalFuncHandler, RBs) ->
+%% {value,Value,Bindings} | Value when
+%% LocalFuncHandler = {value,F} | {value,F,Eas} |
+%% {eval,F} | {eval,F,Eas} | none.
+
+local_func(Func, As0, Bs0, {value,F}, value) ->
+ {As1,_Bs1} = expr_list(As0, Bs0, {value,F}),
+ %% Make tail recursive calls when possible.
+ F(Func, As1);
+local_func(Func, As0, Bs0, {value,F}, RBs) ->
+ {As1,Bs1} = expr_list(As0, Bs0, {value,F}),
+ ret_expr(F(Func, As1), Bs1, RBs);
+local_func(Func, As0, Bs0, {value,F,Eas}, value) ->
+ {As1,_Bs1} = expr_list(As0, Bs0, {value,F,Eas}),
+ apply(F, [Func,As1|Eas]);
+local_func(Func, As0, Bs0, {value,F,Eas}, RBs) ->
+ {As1,Bs1} = expr_list(As0, Bs0, {value,F,Eas}),
+ ret_expr(apply(F, [Func,As1|Eas]), Bs1, RBs);
+local_func(Func, As, Bs, {eval,F}, RBs) ->
+ local_func2(F(Func, As, Bs), RBs);
+local_func(Func, As, Bs, {eval,F,Eas}, RBs) ->
+ local_func2(apply(F, [Func,As,Bs|Eas]), RBs);
+%% These two clauses are for backwards compatibility.
+local_func(Func, As0, Bs0, {M,F}, RBs) ->
+ {As1,Bs1} = expr_list(As0, Bs0, {M,F}),
+ ret_expr(M:F(Func,As1), Bs1, RBs);
+local_func(Func, As, _Bs, {M,F,Eas}, RBs) ->
+ local_func2(apply(M, F, [Func,As|Eas]), RBs);
+%% Default unknown function handler to undefined function.
+local_func(Func, As0, _Bs0, none, _RBs) ->
+ erlang:raise(error, undef, [{erl_eval,Func,length(As0)}|stacktrace()]).
+
+local_func2({value,V,Bs}, RBs) ->
+ ret_expr(V, Bs, RBs);
+local_func2({eval,F,As,Bs}, RBs) -> % This reply is not documented.
+ %% The shell found F. erl_eval tries to do a tail recursive call,
+ %% something the shell cannot do. Do not use Ef here.
+ do_apply(F, As, Bs, none, RBs).
+
+%% bif(Name, Arguments, RBs)
+%% Evaluate the Erlang auto-imported function Name. erlang:apply/2,3
+%% are "hidden" from the external function handler.
+
+bif(apply, [erlang,apply,As], Bs, Ef, RBs) ->
+ bif(apply, As, Bs, Ef, RBs);
+bif(apply, [M,F,As], Bs, Ef, RBs) ->
+ do_apply({M,F}, As, Bs, Ef, RBs);
+bif(apply, [F,As], Bs, Ef, RBs) ->
+ do_apply(F, As, Bs, Ef, RBs);
+bif(Name, As, Bs, Ef, RBs) ->
+ do_apply({erlang,Name}, As, Bs, Ef, RBs).
+
+%% do_apply(MF, Arguments, Bindings, ExternalFuncHandler, RBs) ->
+%% {value,Value,Bindings} | Value when
+%% ExternalFuncHandler = {value,F} | none.
+%% MF is a tuple {Module,Function} or a fun.
+
+do_apply({M,F}=Func, As, Bs0, Ef, RBs)
+ when tuple_size(M) >= 1, is_atom(element(1, M)), is_atom(F) ->
+ case Ef of
+ none when RBs =:= value ->
+ %% Make tail recursive calls when possible.
+ apply(M, F, As);
+ none ->
+ ret_expr(apply(M, F, As), Bs0, RBs);
+ {value,Fun} when RBs =:= value ->
+ Fun(Func, As);
+ {value,Fun} ->
+ ret_expr(Fun(Func, As), Bs0, RBs)
+ end;
+do_apply(Func, As, Bs0, Ef, RBs) ->
+ Env = if
+ is_function(Func) ->
+ case {erlang:fun_info(Func, module),
+ erlang:fun_info(Func, env)} of
+ {{module,?MODULE},{env,Env1}} when Env1 =/= [] ->
+ {env,Env1};
+ _ ->
+ no_env
+ end;
+ true ->
+ no_env
+ end,
+ case {Env,Ef} of
+ {{env,[FBs, FEf, FLf, FCs]},_} ->
+ %% If we are evaluting within another function body
+ %% (RBs =/= none), we return RBs when this function body
+ %% has been evalutated, otherwise we return Bs0, the
+ %% bindings when evalution of this function body started.
+ NRBs = if
+ RBs =:= none -> Bs0;
+ true -> RBs
+ end,
+ case {erlang:fun_info(Func, arity), length(As)} of
+ {{arity, Arity}, Arity} ->
+ eval_fun(FCs, As, FBs, FLf, FEf, NRBs);
+ _ ->
+ erlang:raise(error, {badarity,{Func,As}},stacktrace())
+ end;
+ {no_env,none} when RBs =:= value ->
+ %% Make tail recursive calls when possible.
+ apply(Func, As);
+ {no_env,none} ->
+ ret_expr(apply(Func, As), Bs0, RBs);
+ {no_env,{value,F}} when RBs =:= value ->
+ F(Func,As);
+ {no_env,{value,F}} ->
+ ret_expr(F(Func, As), Bs0, RBs)
+ end.
+
+%% eval_lc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
+%% ExternalFuncHandler, RetBindings) ->
+%% {value,Value,Bindings} | Value
+
+eval_lc(E, Qs, Bs, Lf, Ef, RBs) ->
+ ret_expr(lists:reverse(eval_lc1(E, Qs, Bs, Lf, Ef, [])), Bs, RBs).
+
+eval_lc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
+ {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
+ CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end,
+ eval_generate(L1, P, Bs0, Lf, Ef, CompFun, Acc0);
+eval_lc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
+ {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
+ CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end,
+ eval_b_generate(Bin, P, Bs0, Lf, Ef, CompFun, Acc0);
+eval_lc1(E, [F|Qs], Bs0, Lf, Ef, Acc) ->
+ CompFun = fun(Bs) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end,
+ eval_filter(F, Bs0, Lf, Ef, CompFun, Acc);
+eval_lc1(E, [], Bs, Lf, Ef, Acc) ->
+ {value,V,_} = expr(E, Bs, Lf, Ef, none),
+ [V|Acc].
+
+%% eval_bc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
+%% ExternalFuncHandler, RetBindings) ->
+%% {value,Value,Bindings} | Value
+
+eval_bc(E, Qs, Bs, Lf, Ef, RBs) ->
+ ret_expr(eval_bc1(E, Qs, Bs, Lf, Ef, <<>>), Bs, RBs).
+
+eval_bc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
+ {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
+ CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end,
+ eval_b_generate(Bin, P, Bs0, Lf, Ef, CompFun, Acc0);
+eval_bc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) ->
+ {value,List,_Bs1} = expr(L0, Bs0, Lf, Ef, none),
+ CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end,
+ eval_generate(List, P, Bs0, Lf, Ef, CompFun, Acc0);
+eval_bc1(E, [F|Qs], Bs0, Lf, Ef, Acc) ->
+ CompFun = fun(Bs) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end,
+ eval_filter(F, Bs0, Lf, Ef, CompFun, Acc);
+eval_bc1(E, [], Bs, Lf, Ef, Acc) ->
+ {value,V,_} = expr(E, Bs, Lf, Ef, none),
+ <<Acc/bitstring,V/bitstring>>.
+
+eval_generate([V|Rest], P, Bs0, Lf, Ef, CompFun, Acc) ->
+ case match(P, V, new_bindings(), Bs0) of
+ {match,Bsn} ->
+ Bs2 = add_bindings(Bsn, Bs0),
+ NewAcc = CompFun(Bs2, Acc),
+ eval_generate(Rest, P, Bs0, Lf, Ef, CompFun, NewAcc);
+ nomatch ->
+ eval_generate(Rest, P, Bs0, Lf, Ef, CompFun, Acc)
+ end;
+eval_generate([], _P, _Bs0, _Lf, _Ef, _CompFun, Acc) ->
+ Acc;
+eval_generate(Term, _P, _Bs0, _Lf, _Ef, _CompFun, _Acc) ->
+ erlang:raise(error, {bad_generator,Term}, stacktrace()).
+
+eval_b_generate(<<_/bitstring>>=Bin, P, Bs0, Lf, Ef, CompFun, Acc) ->
+ Mfun = fun(L, R, Bs) -> match1(L, R, Bs, Bs0) end,
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, Lf, Ef, none) end,
+ case eval_bits:bin_gen(P, Bin, new_bindings(), Bs0, Mfun, Efun) of
+ {match, Rest, Bs1} ->
+ Bs2 = add_bindings(Bs1, Bs0),
+ NewAcc = CompFun(Bs2, Acc),
+ eval_b_generate(Rest, P, Bs0, Lf, Ef, CompFun, NewAcc);
+ {nomatch, Rest} ->
+ eval_b_generate(Rest, P, Bs0, Lf, Ef, CompFun, Acc);
+ done ->
+ Acc
+ end;
+eval_b_generate(Term, _P, _Bs0, _Lf, _Ef, _CompFun, _Acc) ->
+ erlang:raise(error, {bad_generator,Term}, stacktrace()).
+
+eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) ->
+ case erl_lint:is_guard_test(F) of
+ true ->
+ case guard_test(F, Bs0, Lf, Ef) of
+ {value,true,Bs1} -> CompFun(Bs1);
+ {value,false,_} -> Acc
+ end;
+ false ->
+ case expr(F, Bs0, Lf, Ef, none) of
+ {value,true,Bs1} -> CompFun(Bs1);
+ {value,false,_} -> Acc;
+ {value,V,_} ->
+ erlang:raise(error, {bad_filter,V}, stacktrace())
+ end
+ end.
+
+
+%% RBs is the bindings to return when the evalution of a function
+%% (fun) has finished. If RBs =:= none, then the evalution took place
+%% outside a function. If RBs =:= value, only the value (not the bindings)
+%% is to be returned (to a compiled function).
+
+ret_expr(V, _Bs, value) ->
+ V;
+ret_expr(V, Bs, none) ->
+ {value,V,Bs};
+ret_expr(V, _Bs, RBs) when is_list(RBs) ->
+ {value,V,RBs}.
+
+%% eval_fun(Clauses, Arguments, Bindings, LocalFunctionHandler,
+%% ExternalFunctionHandler) -> Value
+%% This function is called when the fun is called from compiled code
+%% or from apply.
+
+eval_fun(Cs, As, Bs0, Lf, Ef) ->
+ eval_fun(Cs, As, Bs0, Lf, Ef, value).
+
+eval_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, RBs) ->
+ case match_list(H, As, new_bindings(), Bs0) of
+ {match,Bsn} -> % The new bindings for the head
+ Bs1 = add_bindings(Bsn, Bs0), % which then shadow!
+ case guard(G, Bs1, Lf, Ef) of
+ true -> exprs(B, Bs1, Lf, Ef, RBs);
+ false -> eval_fun(Cs, As, Bs0, Lf, Ef, RBs)
+ end;
+ nomatch ->
+ eval_fun(Cs, As, Bs0, Lf, Ef, RBs)
+ end;
+eval_fun([], As, _Bs, _Lf, _Ef, _RBs) ->
+ erlang:raise(error, function_clause,
+ [{?MODULE,'-inside-an-interpreted-fun-',As}|stacktrace()]).
+
+%% expr_list(ExpressionList, Bindings)
+%% expr_list(ExpressionList, Bindings, LocalFuncHandler)
+%% expr_list(ExpressionList, Bindings, LocalFuncHandler, ExternalFuncHandler)
+%% Evaluate a list of expressions "in parallel" at the same level.
+
+expr_list(Es, Bs) ->
+ expr_list(Es, Bs, none, none).
+
+expr_list(Es, Bs, Lf) ->
+ expr_list(Es, Bs, Lf, none).
+
+expr_list(Es, Bs, Lf, Ef) ->
+ expr_list(Es, [], Bs, Bs, Lf, Ef).
+
+expr_list([E|Es], Vs, BsOrig, Bs0, Lf, Ef) ->
+ {value,V,Bs1} = expr(E, BsOrig, Lf, Ef, none),
+ expr_list(Es, [V|Vs], BsOrig, merge_bindings(Bs1, Bs0), Lf, Ef);
+expr_list([], Vs, _, Bs, _Lf, _Ef) ->
+ {reverse(Vs),Bs}.
+
+eval_op(Op, Arg1, Arg2, Bs, Ef, RBs) ->
+ do_apply({erlang,Op}, [Arg1,Arg2], Bs, Ef, RBs).
+
+eval_op(Op, Arg, Bs, Ef, RBs) ->
+ do_apply({erlang,Op}, [Arg], Bs, Ef, RBs).
+
+%% if_clauses(Clauses, Bindings, LocalFuncHandler, ExtFuncHandler, RBs)
+
+if_clauses([{clause,_,[],G,B}|Cs], Bs, Lf, Ef, RBs) ->
+ case guard(G, Bs, Lf, Ef) of
+ true -> exprs(B, Bs, Lf, Ef, RBs);
+ false -> if_clauses(Cs, Bs, Lf, Ef, RBs)
+ end;
+if_clauses([], _Bs, _Lf, _Ef, _RBs) ->
+ erlang:raise(error, if_clause, stacktrace()).
+
+%% try_clauses(Body, CaseClauses, CatchClauses, AfterBody, Bindings,
+%% LocalFuncHandler, ExtFuncHandler, RBs)
+%% When/if variable bindings between the different parts of a
+%% try-catch expression are introduced this will have to be rewritten.
+try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs) ->
+ try exprs(B, Bs, Lf, Ef, none) of
+ {value,V,Bs1} when Cases =:= [] ->
+ ret_expr(V, Bs1, RBs);
+ {value,V,Bs1} ->
+ case match_clause(Cases, [V], Bs1, Lf, Ef) of
+ {B2,Bs2} ->
+ exprs(B2, Bs2, Lf, Ef, RBs);
+ nomatch ->
+ erlang:raise(error, {try_clause,V}, stacktrace())
+ end
+ catch
+ Class:Reason when Catches =:= [] ->
+ %% Rethrow
+ erlang:raise(Class, Reason, stacktrace());
+ Class:Reason ->
+%%% %% Set stacktrace
+%%% try erlang:raise(Class, Reason, stacktrace())
+%%% catch _:_ -> ok
+%%% end,
+ V = {Class,Reason,erlang:get_stacktrace()},
+ case match_clause(Catches, [V],Bs, Lf, Ef) of
+ {B2,Bs2} ->
+ exprs(B2, Bs2, Lf, Ef, RBs);
+ nomatch ->
+ erlang:raise(Class, Reason, stacktrace())
+ end
+ after
+ if AB =:= [] ->
+ Bs; % any
+ true ->
+ exprs(AB, Bs, Lf, Ef, none)
+ end
+ end.
+
+%% case_clauses(Value, Clauses, Bindings, LocalFuncHandler, ExtFuncHandler,
+%% RBs)
+
+case_clauses(Val, Cs, Bs, Lf, Ef, RBs) ->
+ case match_clause(Cs, [Val], Bs, Lf, Ef) of
+ {B, Bs1} ->
+ exprs(B, Bs1, Lf, Ef, RBs);
+ nomatch ->
+ erlang:raise(error, {case_clause,Val}, stacktrace())
+ end.
+
+%%
+%% receive_clauses(Clauses, Bindings, LocalFuncHnd,ExtFuncHnd, Messages, RBs)
+%%
+receive_clauses(Cs, Bs, Lf, Ef, Ms, RBs) ->
+ receive
+ Val ->
+ case match_clause(Cs, [Val], Bs, Lf, Ef) of
+ {B, Bs1} ->
+ merge_queue(Ms),
+ exprs(B, Bs1, Lf, Ef, RBs);
+ nomatch ->
+ receive_clauses(Cs, Bs, Lf, Ef, [Val|Ms], RBs)
+ end
+ end.
+%%
+%% receive_clauses(TimeOut, Clauses, TimeoutBody, Bindings,
+%% ExternalFuncHandler, LocalFuncHandler, RBs)
+%%
+receive_clauses(T, Cs, TB, Bs, Lf, Ef, Ms, RBs) ->
+ {_,_} = statistics(runtime),
+ receive
+ Val ->
+ case match_clause(Cs, [Val], Bs, Lf, Ef) of
+ {B, Bs1} ->
+ merge_queue(Ms),
+ exprs(B, Bs1, Lf, Ef, RBs);
+ nomatch ->
+ {_,T1} = statistics(runtime),
+ if
+ T =:= infinity ->
+ receive_clauses(T, Cs, TB,Bs,Lf,Ef,[Val|Ms],RBs);
+ T-T1 =< 0 ->
+ receive_clauses(0, Cs, TB,Bs,Lf,Ef,[Val|Ms],RBs);
+ true ->
+ receive_clauses(T-T1, Cs,TB,Bs,Lf,Ef,[Val|Ms],RBs)
+ end
+ end
+ after T ->
+ merge_queue(Ms),
+ {B, Bs1} = TB,
+ exprs(B, Bs1, Lf, Ef, RBs)
+ end.
+
+merge_queue([]) ->
+ true;
+merge_queue(Ms) ->
+ send_all(recv_all(Ms), self()).
+
+recv_all(Xs) ->
+ receive
+ X -> recv_all([X|Xs])
+ after 0 ->
+ reverse(Xs)
+ end.
+
+send_all([X|Xs], Self) ->
+ Self ! X,
+ send_all(Xs, Self);
+send_all([], _) -> true.
+
+
+%% match_clause -> {Body, Bindings} or nomatch
+
+match_clause(Cs, Vs, Bs, Lf) ->
+ match_clause(Cs, Vs, Bs, Lf, none).
+
+match_clause([{clause,_,H,G,B}|Cs], Vals, Bs, Lf, Ef) ->
+ case match_list(H, Vals, Bs) of
+ {match, Bs1} ->
+ case guard(G, Bs1, Lf, Ef) of
+ true -> {B, Bs1};
+ false -> match_clause(Cs, Vals, Bs, Lf, Ef)
+ end;
+ nomatch -> match_clause(Cs, Vals, Bs, Lf, Ef)
+ end;
+match_clause([], _Vals, _Bs, _Lf, _Ef) ->
+ nomatch.
+
+%% guard(GuardTests, Bindings, LocalFuncHandler, ExtFuncHandler) -> bool()
+%% Evaluate a guard. We test if the guard is a true guard.
+
+guard(L=[G|_], Bs0, Lf, Ef) when is_list(G) ->
+ guard1(L, Bs0, Lf, Ef);
+guard(L, Bs0, Lf, Ef) ->
+ guard0(L, Bs0, Lf, Ef).
+
+%% disjunction of guard conjunctions
+guard1([G|Gs], Bs0, Lf, Ef) when is_list(G) ->
+ case guard0(G, Bs0, Lf, Ef) of
+ true ->
+ true;
+ false ->
+ guard1(Gs, Bs0, Lf, Ef)
+ end;
+guard1([], _Bs, _Lf, _Ef) -> false.
+
+%% guard conjunction
+guard0([G|Gs], Bs0, Lf, Ef) ->
+ case erl_lint:is_guard_test(G) of
+ true ->
+ case guard_test(G, Bs0, Lf, Ef) of
+ {value,true,Bs} -> guard0(Gs, Bs, Lf, Ef);
+ {value,false,_} -> false
+ end;
+ false ->
+ erlang:raise(error, guard_expr, stacktrace())
+ end;
+guard0([], _Bs, _Lf, _Ef) -> true.
+
+%% guard_test(GuardTest, Bindings, LocalFuncHandler, ExtFuncHandler) ->
+%% {value,bool(),NewBindings}.
+%% Evaluate one guard test. Never fails, returns bool().
+
+guard_test({call,L,{atom,Ln,F},As0}, Bs0, Lf, Ef) ->
+ TT = type_test(F),
+ guard_test({call,L,{tuple,Ln,[{atom,Ln,erlang},{atom,Ln,TT}]},As0},
+ Bs0, Lf, Ef);
+guard_test({call,L,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,_F}=T},As0},
+ Bs0, Lf, Ef) ->
+ guard_test({call,L,T,As0}, Bs0, Lf, Ef);
+guard_test(G, Bs0, Lf, Ef) ->
+ try {value,true,_} = expr(G, Bs0, Lf, Ef, none)
+ catch error:_ -> {value,false,Bs0} end.
+
+type_test(integer) -> is_integer;
+type_test(float) -> is_float;
+type_test(number) -> is_number;
+type_test(atom) -> is_atom;
+type_test(constant) -> is_constant;
+type_test(list) -> is_list;
+type_test(tuple) -> is_tuple;
+type_test(pid) -> is_pid;
+type_test(reference) -> is_reference;
+type_test(port) -> is_port;
+type_test(function) -> is_function;
+type_test(binary) -> is_binary;
+type_test(record) -> is_record;
+type_test(Test) -> Test.
+
+
+%% match(Pattern, Term, Bindings) ->
+%% {match,NewBindings} | nomatch
+%% or erlang:error({illegal_pattern, Pattern}).
+%% Try to match Pattern against Term with the current bindings.
+
+match(Pat, Term, Bs) ->
+ match(Pat, Term, Bs, Bs).
+
+%% Bs are the bindings that are augmented with new bindings. BBs are
+%% the bindings used for "binsize" variables (in <<X:Y>>, Y is a
+%% binsize variable).
+
+match(Pat, Term, Bs, BBs) ->
+ case catch match1(Pat, Term, Bs, BBs) of
+ invalid ->
+ erlang:raise(error, {illegal_pattern,Pat}, stacktrace());
+ Other ->
+ Other
+ end.
+
+string_to_conses([], _, Tail) -> Tail;
+string_to_conses([E|Rest], Line, Tail) ->
+ {cons, Line, {integer, Line, E}, string_to_conses(Rest, Line, Tail)}.
+
+match1({atom,_,A0}, A, Bs, _BBs) ->
+ case A of
+ A0 -> {match,Bs};
+ _ -> throw(nomatch)
+ end;
+match1({integer,_,I0}, I, Bs, _BBs) ->
+ case I of
+ I0 -> {match,Bs};
+ _ -> throw(nomatch)
+ end;
+match1({float,_,F0}, F, Bs, _BBs) ->
+ case F of
+ F0 -> {match,Bs};
+ _ -> throw(nomatch)
+ end;
+match1({char,_,C0}, C, Bs, _BBs) ->
+ case C of
+ C0 -> {match,Bs};
+ _ -> throw(nomatch)
+ end;
+match1({var,_,'_'}, _, Bs, _BBs) -> %Anonymous variable matches
+ {match,Bs}; % everything, no new bindings
+match1({var,_,Name}, Term, Bs, _BBs) ->
+ case binding(Name, Bs) of
+ {value,Term} ->
+ {match,Bs};
+ {value,_} ->
+ throw(nomatch);
+ unbound ->
+ {match,add_binding(Name, Term, Bs)}
+ end;
+match1({match,_,Pat1,Pat2}, Term, Bs0, BBs) ->
+ {match, Bs1} = match1(Pat1, Term, Bs0, BBs),
+ match1(Pat2, Term, Bs1, BBs);
+match1({string,_,S0}, S, Bs, _BBs) ->
+ case S of
+ S0 -> {match,Bs};
+ _ -> throw(nomatch)
+ end;
+match1({nil,_}, Nil, Bs, _BBs) ->
+ case Nil of
+ [] -> {match,Bs};
+ _ -> throw(nomatch)
+ end;
+match1({cons,_,H,T}, [H1|T1], Bs0, BBs) ->
+ {match,Bs} = match1(H, H1, Bs0, BBs),
+ match1(T, T1, Bs, BBs);
+match1({cons,_,_,_}, _, _Bs, _BBs) ->
+ throw(nomatch);
+match1({tuple,_,Elts}, Tuple, Bs, BBs)
+ when length(Elts) =:= tuple_size(Tuple) ->
+ match_tuple(Elts, Tuple, 1, Bs, BBs);
+match1({tuple,_,_}, _, _Bs, _BBs) ->
+ throw(nomatch);
+match1({bin, _, Fs}, <<_/bitstring>>=B, Bs0, BBs) ->
+ eval_bits:match_bits(Fs, B, Bs0, BBs,
+ fun(L, R, Bs) -> match1(L, R, Bs, BBs) end,
+ fun(E, Bs) -> expr(E, Bs, none, none, none) end);
+match1({bin,_,_}, _, _Bs, _BBs) ->
+ throw(nomatch);
+match1({op,_,'++',{nil,_},R}, Term, Bs, BBs) ->
+ match1(R, Term, Bs, BBs);
+match1({op,_,'++',{cons,Li,{integer,L2,I},T},R}, Term, Bs, BBs) ->
+ match1({cons,Li,{integer,L2,I},{op,Li,'++',T,R}}, Term, Bs, BBs);
+match1({op,_,'++',{cons,Li,{char,L2,C},T},R}, Term, Bs, BBs) ->
+ match1({cons,Li,{char,L2,C},{op,Li,'++',T,R}}, Term, Bs, BBs);
+match1({op,_,'++',{string,Li,L},R}, Term, Bs, BBs) ->
+ match1(string_to_conses(L, Li, R), Term, Bs, BBs);
+match1({op,Line,Op,A}, Term, Bs, BBs) ->
+ case partial_eval({op,Line,Op,A}) of
+ {op,Line,Op,A} ->
+ throw(invalid);
+ X ->
+ match1(X, Term, Bs, BBs)
+ end;
+match1({op,Line,Op,L,R}, Term, Bs, BBs) ->
+ case partial_eval({op,Line,Op,L,R}) of
+ {op,Line,Op,L,R} ->
+ throw(invalid);
+ X ->
+ match1(X, Term, Bs, BBs)
+ end;
+match1(_, _, _Bs, _BBs) ->
+ throw(invalid).
+
+match_tuple([E|Es], Tuple, I, Bs0, BBs) ->
+ {match,Bs} = match1(E, element(I, Tuple), Bs0, BBs),
+ match_tuple(Es, Tuple, I+1, Bs, BBs);
+match_tuple([], _, _, Bs, _BBs) ->
+ {match,Bs}.
+
+%% match_list(PatternList, TermList, Bindings) ->
+%% {match,NewBindings} | nomatch
+%% Try to match a list of patterns against a list of terms with the
+%% current bindings.
+
+match_list(Ps, Ts, Bs) ->
+ match_list(Ps, Ts, Bs, Bs).
+
+match_list([P|Ps], [T|Ts], Bs0, BBs) ->
+ case match(P, T, Bs0, BBs) of
+ {match,Bs1} -> match_list(Ps, Ts, Bs1, BBs);
+ nomatch -> nomatch
+ end;
+match_list([], [], Bs, _BBs) ->
+ {match,Bs};
+match_list(_, _, _Bs, _BBs) ->
+ nomatch.
+
+%% new_bindings()
+%% bindings(Bindings)
+%% binding(Name, Bindings)
+%% add_binding(Name, Value, Bindings)
+%% del_binding(Name, Bindings)
+
+new_bindings() -> orddict:new().
+
+bindings(Bs) -> orddict:to_list(Bs).
+
+binding(Name, Bs) ->
+ case orddict:find(Name, Bs) of
+ {ok,Val} -> {value,Val};
+ error -> unbound
+ end.
+
+add_binding(Name, Val, Bs) -> orddict:store(Name, Val, Bs).
+
+del_binding(Name, Bs) -> orddict:erase(Name, Bs).
+
+add_bindings(Bs1, Bs2) ->
+ foldl(fun ({Name,Val}, Bs) -> orddict:store(Name, Val, Bs) end,
+ Bs2, orddict:to_list(Bs1)).
+
+merge_bindings(Bs1, Bs2) ->
+ foldl(fun ({Name,Val}, Bs) ->
+ case orddict:find(Name, Bs) of
+ {ok,Val} -> Bs; %Already with SAME value
+ {ok,V1} ->
+ erlang:raise(error, {badmatch,V1}, stacktrace());
+ error -> orddict:store(Name, Val, Bs)
+ end end,
+ Bs2, orddict:to_list(Bs1)).
+
+%% del_bindings(Bs1, Bs2) -> % del all in Bs1 from Bs2
+%% orddict:fold(
+%% fun (Name, Val, Bs) ->
+%% case orddict:find(Name, Bs) of
+%% {ok,Val} -> orddict:erase(Name, Bs);
+%% {ok,V1} -> erlang:raise(error,{badmatch,V1},stacktrace());
+%% error -> Bs
+%% end
+%% end, Bs2, Bs1).
+%%----------------------------------------------------------------------------
+%%
+%% Evaluate expressions:
+%% constants and
+%% op A
+%% L op R
+%% Things that evaluate to constants are accepted
+%% and guard_bifs are allowed in constant expressions
+%%----------------------------------------------------------------------------
+
+is_constant_expr(Expr) ->
+ case eval_expr(Expr) of
+ {ok, X} when is_number(X) -> true;
+ _ -> false
+ end.
+
+eval_expr(Expr) ->
+ case catch ev_expr(Expr) of
+ X when is_integer(X) -> {ok, X};
+ X when is_float(X) -> {ok, X};
+ X when is_atom(X) -> {ok,X};
+ {'EXIT',Reason} -> {error, Reason};
+ _ -> {error, badarg}
+ end.
+
+partial_eval(Expr) ->
+ Line = line(Expr),
+ case catch ev_expr(Expr) of
+ X when is_integer(X) -> ret_expr(Expr,{integer,Line,X});
+ X when is_float(X) -> ret_expr(Expr,{float,Line,X});
+ X when is_atom(X) -> ret_expr(Expr,{atom,Line,X});
+ _ ->
+ Expr
+ end.
+
+ev_expr({op,_,Op,L,R}) -> erlang:Op(ev_expr(L), ev_expr(R));
+ev_expr({op,_,Op,A}) -> erlang:Op(ev_expr(A));
+ev_expr({integer,_,X}) -> X;
+ev_expr({float,_,X}) -> X;
+ev_expr({atom,_,X}) -> X;
+ev_expr({tuple,_,Es}) ->
+ list_to_tuple([ev_expr(X) || X <- Es]);
+ev_expr({nil,_}) -> [];
+ev_expr({cons,_,H,T}) -> [ev_expr(H) | ev_expr(T)].
+%%ev_expr({call,Line,{atom,_,F},As}) ->
+%% true = erl_internal:guard_bif(F, length(As)),
+%% apply(erlang, F, [ev_expr(X) || X <- As]);
+%%ev_expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,F}},As}) ->
+%% true = erl_internal:guard_bif(F, length(As)),
+%% apply(erlang, F, [ev_expr(X) || X <- As]);
+
+ret_expr(_Old, New) ->
+ %% io:format("~w: reduced ~s => ~s~n",
+ %% [line(Old), erl_pp:expr(Old), erl_pp:expr(New)]),
+ New.
+
+line(Expr) -> element(2, Expr).
+
+%% In syntax trees, module/package names are atoms or lists of atoms.
+
+expand_module_name({atom,L,A} = M, Bs) ->
+ case binding({module,A}, Bs) of
+ {value, A1} ->
+ {atom,L,A1};
+ unbound ->
+ case packages:is_segmented(A) of
+ true ->
+ M;
+ false ->
+%%% P = case binding({module,'$package'}, Bs) of
+%%% {value, P1} -> P1;
+%%% unbound -> ""
+%%% end,
+%%% A1 = list_to_atom(packages:concat(P, A)),
+%%% {atom,L,list_to_atom(A1)}
+ {atom,L,A}
+ end
+ end;
+expand_module_name(M, _) ->
+ case erl_parse:package_segments(M) of
+ error ->
+ M;
+ M1 ->
+ L = element(2,M),
+ Mod = packages:concat(M1),
+ case packages:is_valid(Mod) of
+ true ->
+ {atom,L,list_to_atom(Mod)};
+ false ->
+ erlang:raise(error, {bad_module_name, Mod}, stacktrace())
+ end
+ end.
+
+%% {?MODULE,expr,3} is still the stacktrace, despite the
+%% fact that expr() now takes two, three or four arguments...
+stacktrace() -> [{?MODULE,expr,3}].
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
new file mode 100644
index 0000000000..6fa77f2c3b
--- /dev/null
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -0,0 +1,808 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Purpose : Expand records into tuples.
+
+%% N.B. Although structs (tagged tuples) are not yet allowed in the
+%% language there is code included in pattern/2 and expr/3 (commented out)
+%% that handles them.
+
+-module(erl_expand_records).
+
+-export([module/2]).
+
+-import(lists, [map/2,foldl/3,foldr/3,sort/1,reverse/1,duplicate/2]).
+
+-record(exprec, {compile=[], % Compile flags
+ vcount=0, % Variable counter
+ imports=[], % Imports
+ records=dict:new(), % Record definitions
+ trecords=sets:new(), % Typed records
+ uses_types=false, % Are there -spec or -type in the module
+ strict_ra=[], % strict record accesses
+ checked_ra=[] % succesfully accessed records
+ }).
+
+%% Is is assumed that Fs is a valid list of forms. It should pass
+%% erl_lint without errors.
+module(Fs0, Opts0) ->
+ Opts = compiler_options(Fs0) ++ Opts0,
+ TRecs = typed_records(Fs0),
+ UsesTypes = uses_types(Fs0),
+ St0 = #exprec{compile = Opts, trecords = TRecs, uses_types = UsesTypes},
+ {Fs,_St} = forms(Fs0, St0),
+ Fs.
+
+compiler_options(Forms) ->
+ lists:flatten([C || {attribute,_,compile,C} <- Forms]).
+
+typed_records(Fs) ->
+ typed_records(Fs, sets:new()).
+
+typed_records([{attribute,_L,type,{{record, Name},_Defs,[]}} | Fs], Trecs) ->
+ typed_records(Fs, sets:add_element(Name, Trecs));
+typed_records([_|Fs], Trecs) ->
+ typed_records(Fs, Trecs);
+typed_records([], Trecs) ->
+ Trecs.
+
+uses_types([{attribute,_L,spec,_}|_]) -> true;
+uses_types([{attribute,_L,type,_}|_]) -> true;
+uses_types([{attribute,_L,opaque,_}|_]) -> true;
+uses_types([_|Fs]) -> uses_types(Fs);
+uses_types([]) -> false.
+
+forms([{attribute,L,record,{Name,Defs}} | Fs], St0) ->
+ NDefs = normalise_fields(Defs),
+ St = St0#exprec{records=dict:store(Name, NDefs, St0#exprec.records)},
+ {Fs1, St1} = forms(Fs, St),
+ %% Check if we need to keep the record information for usage in types.
+ case St#exprec.uses_types of
+ true ->
+ case sets:is_element(Name, St#exprec.trecords) of
+ true -> {Fs1, St1};
+ false -> {[{attribute,L,type,{{record,Name},Defs,[]}}|Fs1], St1}
+ end;
+ false ->
+ {Fs1, St1}
+ end;
+forms([{attribute,L,import,Is} | Fs0], St0) ->
+ St1 = import(Is, St0),
+ {Fs,St2} = forms(Fs0, St1),
+ {[{attribute,L,import,Is} | Fs], St2};
+forms([{function,L,N,A,Cs0} | Fs0], St0) ->
+ {Cs,St1} = clauses(Cs0, St0),
+ {Fs,St2} = forms(Fs0, St1),
+ {[{function,L,N,A,Cs} | Fs],St2};
+forms([F | Fs0], St0) ->
+ {Fs,St} = forms(Fs0, St0),
+ {[F | Fs], St};
+forms([], St) -> {[],St}.
+
+clauses([{clause,Line,H0,G0,B0} | Cs0], St0) ->
+ {H,St1} = head(H0, St0),
+ {G,St2} = guard(G0, St1),
+ {B,St3} = exprs(B0, St2),
+ {Cs,St4} = clauses(Cs0, St3),
+ {[{clause,Line,H,G,B} | Cs],St4};
+clauses([], St) -> {[],St}.
+
+head(As, St) -> pattern_list(As, St).
+
+pattern({var,_,'_'}=Var, St) ->
+ {Var,St};
+pattern({var,_,_}=Var, St) ->
+ {Var,St};
+pattern({char,_,_}=Char, St) ->
+ {Char,St};
+pattern({integer,_,_}=Int, St) ->
+ {Int,St};
+pattern({float,_,_}=Float, St) ->
+ {Float,St};
+pattern({atom,_,_}=Atom, St) ->
+ {Atom,St};
+pattern({string,_,_}=String, St) ->
+ {String,St};
+pattern({nil,_}=Nil, St) ->
+ {Nil,St};
+pattern({cons,Line,H,T}, St0) ->
+ {TH,St1} = pattern(H, St0),
+ {TT,St2} = pattern(T, St1),
+ {{cons,Line,TH,TT},St2};
+pattern({tuple,Line,Ps}, St0) ->
+ {TPs,St1} = pattern_list(Ps, St0),
+ {{tuple,Line,TPs},St1};
+%%pattern({struct,Line,Tag,Ps}, St0) ->
+%% {TPs,TPsvs,St1} = pattern_list(Ps, St0),
+%% {{struct,Line,Tag,TPs},TPsvs,St1};
+pattern({record_field,_,_,_}=M, St) ->
+ {M,St}; % must be a package name
+pattern({record_index,Line,Name,Field}, St) ->
+ {index_expr(Line, Field, Name, record_fields(Name, St)),St};
+pattern({record,Line,Name,Pfs}, St0) ->
+ Fs = record_fields(Name, St0),
+ {TMs,St1} = pattern_list(pattern_fields(Fs, Pfs), St0),
+ {{tuple,Line,[{atom,Line,Name} | TMs]},St1};
+pattern({bin,Line,Es0}, St0) ->
+ {Es1,St1} = pattern_bin(Es0, St0),
+ {{bin,Line,Es1},St1};
+pattern({match,Line,Pat1, Pat2}, St0) ->
+ {TH,St1} = pattern(Pat2, St0),
+ {TT,St2} = pattern(Pat1, St1),
+ {{match,Line,TT,TH},St2};
+pattern({op,Line,Op,A0}, St0) ->
+ {A,St1} = pattern(A0, St0),
+ {{op,Line,Op,A},St1};
+pattern({op,Line,Op,L0,R0}, St0) ->
+ {L,St1} = pattern(L0, St0),
+ {R,St2} = pattern(R0, St1),
+ {{op,Line,Op,L,R},St2}.
+
+pattern_list([P0 | Ps0], St0) ->
+ {P,St1} = pattern(P0, St0),
+ {Ps,St2} = pattern_list(Ps0, St1),
+ {[P | Ps],St2};
+pattern_list([], St) -> {[],St}.
+
+guard([G0 | Gs0], St0) ->
+ {G,St1} = guard_tests(G0, St0),
+ {Gs,St2} = guard(Gs0, St1),
+ {[G | Gs],St2};
+guard([], St) -> {[],St}.
+
+guard_tests(Gts0, St0) ->
+ {Gts1,St1} = guard_tests1(Gts0, St0),
+ {Gts1,St1#exprec{checked_ra = []}}.
+
+guard_tests1([Gt0 | Gts0], St0) ->
+ {Gt1,St1} = guard_test(Gt0, St0),
+ {Gts1,St2} = guard_tests1(Gts0, St1),
+ {[Gt1 | Gts1],St2};
+guard_tests1([], St) -> {[],St}.
+
+guard_test(G0, St0) ->
+ in_guard(fun() ->
+ {G1,St1} = guard_test1(G0, St0),
+ strict_record_access(G1, St1)
+ end).
+
+%% Normalising guard tests ensures that none of the Boolean operands
+%% created by strict_record_access/2 calls any of the old guard tests.
+guard_test1({call,Line,{atom,Lt,Tname},As}, St) ->
+ Test = {atom,Lt,normalise_test(Tname, length(As))},
+ expr({call,Line,Test,As}, St);
+guard_test1(Test, St) ->
+ expr(Test, St).
+
+normalise_test(atom, 1) -> is_atom;
+normalise_test(binary, 1) -> is_binary;
+normalise_test(constant, 1) -> is_constant;
+normalise_test(float, 1) -> is_float;
+normalise_test(function, 1) -> is_function;
+normalise_test(integer, 1) -> is_integer;
+normalise_test(list, 1) -> is_list;
+normalise_test(number, 1) -> is_number;
+normalise_test(pid, 1) -> is_pid;
+normalise_test(port, 1) -> is_port;
+normalise_test(record, 2) -> is_record;
+normalise_test(reference, 1) -> is_reference;
+normalise_test(tuple, 1) -> is_tuple;
+normalise_test(Name, _) -> Name.
+
+is_in_guard() ->
+ get(erl_expand_records_in_guard) =/= undefined.
+
+in_guard(F) ->
+ undefined = put(erl_expand_records_in_guard, true),
+ Res = F(),
+ true = erase(erl_expand_records_in_guard),
+ Res.
+
+%% record_test(Line, Term, Name, Vs, St) -> TransformedExpr
+%% Generate code for is_record/1.
+
+record_test(Line, Term, Name, St) ->
+ case is_in_guard() of
+ false ->
+ record_test_in_body(Line, Term, Name, St);
+ true ->
+ record_test_in_guard(Line, Term, Name, St)
+ end.
+
+record_test_in_guard(Line, Term, Name, St) ->
+ case not_a_tuple(Term) of
+ true ->
+ %% In case that later optimization passes have been turned off.
+ expr({atom,Line,false}, St);
+ false ->
+ Fs = record_fields(Name, St),
+ NLine = neg_line(Line),
+ expr({call,NLine,{remote,NLine,{atom,NLine,erlang},{atom,NLine,is_record}},
+ [Term,{atom,Line,Name},{integer,Line,length(Fs)+1}]},
+ St)
+ end.
+
+not_a_tuple({atom,_,_}) -> true;
+not_a_tuple({integer,_,_}) -> true;
+not_a_tuple({float,_,_}) -> true;
+not_a_tuple({nil,_}) -> true;
+not_a_tuple({cons,_,_,_}) -> true;
+not_a_tuple({char,_,_}) -> true;
+not_a_tuple({string,_,_}) -> true;
+not_a_tuple({record_index,_,_,_}) -> true;
+not_a_tuple({bin,_,_}) -> true;
+not_a_tuple({op,_,_,_}) -> true;
+not_a_tuple({op,_,_,_,_}) -> true;
+not_a_tuple(_) -> false.
+
+record_test_in_body(Line, Expr, Name, St0) ->
+ %% As Expr may have side effects, we must evaluate it
+ %% first and bind the value to a new variable.
+ %% We must use also handle the case that Expr does not
+ %% evaluate to a tuple properly.
+ Fs = record_fields(Name, St0),
+ {Var,St} = new_var(Line, St0),
+ NLine = neg_line(Line),
+ expr({block,Line,
+ [{match,Line,Var,Expr},
+ {call,NLine,{remote,NLine,{atom,NLine,erlang},
+ {atom,NLine,is_record}},
+ [Var,{atom,Line,Name},{integer,Line,length(Fs)+1}]}]}, St).
+
+exprs([E0 | Es0], St0) ->
+ {E,St1} = expr(E0, St0),
+ {Es,St2} = exprs(Es0, St1),
+ {[E | Es],St2};
+exprs([], St) -> {[],St}.
+
+expr({var,_,_}=Var, St) ->
+ {Var,St};
+expr({char,_,_}=Char, St) ->
+ {Char,St};
+expr({integer,_,_}=Int, St) ->
+ {Int,St};
+expr({float,_,_}=Float, St) ->
+ {Float,St};
+expr({atom,_,_}=Atom, St) ->
+ {Atom,St};
+expr({string,_,_}=String, St) ->
+ {String,St};
+expr({nil,_}=Nil, St) ->
+ {Nil,St};
+expr({cons,Line,H0,T0}, St0) ->
+ {H,St1} = expr(H0, St0),
+ {T,St2} = expr(T0, St1),
+ {{cons,Line,H,T},St2};
+expr({lc,Line,E0,Qs0}, St0) ->
+ {Qs1,St1} = lc_tq(Line, Qs0, St0),
+ {E1,St2} = expr(E0, St1),
+ {{lc,Line,E1,Qs1},St2};
+expr({bc,Line,E0,Qs0}, St0) ->
+ {Qs1,St1} = lc_tq(Line, Qs0, St0),
+ {E1,St2} = expr(E0, St1),
+ {{bc,Line,E1,Qs1},St2};
+expr({tuple,Line,Es0}, St0) ->
+ {Es1,St1} = expr_list(Es0, St0),
+ {{tuple,Line,Es1},St1};
+%%expr({struct,Line,Tag,Es0}, Vs, St0) ->
+%% {Es1,Esvs,Esus,St1} = expr_list(Es0, Vs, St0),
+%% {{struct,Line,Tag,Es1},Esvs,Esus,St1};
+expr({record_field,_,_,_}=M, St) ->
+ {M,St}; % must be a package name
+expr({record_index,Line,Name,F}, St) ->
+ I = index_expr(Line, F, Name, record_fields(Name, St)),
+ expr(I, St);
+expr({record,Line,Name,Is}, St) ->
+ expr({tuple,Line,[{atom,Line,Name} |
+ record_inits(record_fields(Name, St), Is)]},
+ St);
+expr({record_field,Line,R,Name,F}, St) ->
+ get_record_field(Line, R, F, Name, St);
+expr({record,_,R,Name,Us}, St0) ->
+ {Ue,St1} = record_update(R, Name, record_fields(Name, St0), Us, St0),
+ expr(Ue, St1);
+expr({bin,Line,Es0}, St0) ->
+ {Es1,St1} = expr_bin(Es0, St0),
+ {{bin,Line,Es1},St1};
+expr({block,Line,Es0}, St0) ->
+ {Es,St1} = exprs(Es0, St0),
+ {{block,Line,Es},St1};
+expr({'if',Line,Cs0}, St0) ->
+ {Cs,St1} = clauses(Cs0, St0),
+ {{'if',Line,Cs},St1};
+expr({'case',Line,E0,Cs0}, St0) ->
+ {E,St1} = expr(E0, St0),
+ {Cs,St2} = clauses(Cs0, St1),
+ {{'case',Line,E,Cs},St2};
+expr({'receive',Line,Cs0}, St0) ->
+ {Cs,St1} = clauses(Cs0, St0),
+ {{'receive',Line,Cs},St1};
+expr({'receive',Line,Cs0,To0,ToEs0}, St0) ->
+ {To,St1} = expr(To0, St0),
+ {ToEs,St2} = exprs(ToEs0, St1),
+ {Cs,St3} = clauses(Cs0, St2),
+ {{'receive',Line,Cs,To,ToEs},St3};
+expr({'fun',_,{function,_F,_A}}=Fun, St) ->
+ {Fun,St};
+expr({'fun',_,{function,_M,_F,_A}}=Fun, St) ->
+ {Fun,St};
+expr({'fun',Line,{clauses,Cs0}}, St0) ->
+ {Cs,St1} = clauses(Cs0, St0),
+ {{'fun',Line,{clauses,Cs}},St1};
+expr({call,Line,{atom,_,is_record},[A,{atom,_,Name}]}, St) ->
+ record_test(Line, A, Name, St);
+expr({'cond',Line,Cs0}, St0) ->
+ {Cs,St1} = clauses(Cs0, St0),
+ {{'cond',Line,Cs},St1};
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}},
+ [A,{atom,_,Name}]}, St) ->
+ record_test(Line, A, Name, St);
+expr({call,Line,{tuple,_,[{atom,_,erlang},{atom,_,is_record}]},
+ [A,{atom,_,Name}]}, St) ->
+ record_test(Line, A, Name, St);
+expr({call,Line,{atom,_La,N}=Atom,As0}, St0) ->
+ {As,St1} = expr_list(As0, St0),
+ Ar = length(As),
+ case erl_internal:bif(N, Ar) of
+ true ->
+ {{call,Line,Atom,As},St1};
+ false ->
+ case imported(N, Ar, St1) of
+ {yes,_Mod} ->
+ {{call,Line,Atom,As},St1};
+ no ->
+ case {N,Ar} of
+ {record_info,2} ->
+ record_info_call(Line, As, St1);
+ _ ->
+ {{call,Line,Atom,As},St1}
+ end
+ end
+ end;
+expr({call,Line,{record_field,_,_,_}=M,As0}, St0) ->
+ {As,St1} = expr_list(As0, St0),
+ {{call,Line,M,As},St1};
+expr({call,Line,{remote,Lr,M,F},As0}, St0) ->
+ {[M1,F1 | As1],St1} = expr_list([M,F | As0], St0),
+ {{call,Line,{remote,Lr,M1,F1},As1},St1};
+expr({call,Line,{tuple,Lt,[{atom,_,_}=M,{atom,_,_}=F]},As0}, St0) ->
+ {As,St1} = expr_list(As0, St0),
+ {{call,Line,{tuple,Lt,[M,F]},As},St1};
+expr({call,Line,F,As0}, St0) ->
+ {[Fun1 | As1],St1} = expr_list([F | As0], St0),
+ {{call,Line,Fun1,As1},St1};
+expr({'try',Line,Es0,Scs0,Ccs0,As0}, St0) ->
+ {Es1,St1} = exprs(Es0, St0),
+ {Scs1,St2} = clauses(Scs0, St1),
+ {Ccs1,St3} = clauses(Ccs0, St2),
+ {As1,St4} = exprs(As0, St3),
+ {{'try',Line,Es1,Scs1,Ccs1,As1},St4};
+expr({'catch',Line,E0}, St0) ->
+ {E,St1} = expr(E0, St0),
+ {{'catch',Line,E},St1};
+expr({match,Line,P0,E0}, St0) ->
+ {E,St1} = expr(E0, St0),
+ {P,St2} = pattern(P0, St1),
+ {{match,Line,P,E},St2};
+expr({op,Line,'not',A0}, St0) ->
+ {A,St1} = bool_operand(A0, St0),
+ {{op,Line,'not',A},St1};
+expr({op,Line,Op,A0}, St0) ->
+ {A,St1} = expr(A0, St0),
+ {{op,Line,Op,A},St1};
+expr({op,Line,Op,L0,R0}, St0) when Op =:= 'and';
+ Op =:= 'or' ->
+ {L,St1} = bool_operand(L0, St0),
+ {R,St2} = bool_operand(R0, St1),
+ {{op,Line,Op,L,R},St2};
+expr({op,Line,Op,L0,R0}, St0) when Op =:= 'andalso';
+ Op =:= 'orelse' ->
+ {L,St1} = bool_operand(L0, St0),
+ {R,St2} = bool_operand(R0, St1),
+ {{op,Line,Op,L,R},St2#exprec{checked_ra = St1#exprec.checked_ra}};
+expr({op,Line,Op,L0,R0}, St0) ->
+ {L,St1} = expr(L0, St0),
+ {R,St2} = expr(R0, St1),
+ {{op,Line,Op,L,R},St2}.
+
+expr_list([E0 | Es0], St0) ->
+ {E,St1} = expr(E0, St0),
+ {Es,St2} = expr_list(Es0, St1),
+ {[E | Es],St2};
+expr_list([], St) -> {[],St}.
+
+bool_operand(E0, St0) ->
+ {E1,St1} = expr(E0, St0),
+ strict_record_access(E1, St1).
+
+strict_record_access(E, #exprec{strict_ra = []} = St) ->
+ {E, St};
+strict_record_access(E0, St0) ->
+ #exprec{strict_ra = StrictRA, checked_ra = CheckedRA} = St0,
+ {New,NC} = lists:foldl(fun ({Key,_L,_R,_Sz}=A, {L,C}) ->
+ case lists:keymember(Key, 1, C) of
+ true -> {L,C};
+ false -> {[A|L],[A|C]}
+ end
+ end, {[],CheckedRA}, StrictRA),
+ E1 = if New =:= [] -> E0; true -> conj(New, E0) end,
+ St1 = St0#exprec{strict_ra = [], checked_ra = NC},
+ expr(E1, St1).
+
+%% Make it look nice (?) when compiled with the 'E' flag
+%% ('and'/2 is left recursive).
+conj([], _E) ->
+ empty;
+conj([{{Name,_Rp},L,R,Sz} | AL], E) ->
+ NL = neg_line(L),
+ T1 = {op,NL,'orelse',
+ {call,NL,{atom,NL,is_record},[R,{atom,NL,Name},{integer,NL,Sz}]},
+ {atom,NL,fail}},
+ T2 = case conj(AL, none) of
+ empty -> T1;
+ C -> {op,NL,'and',C,T1}
+ end,
+ case E of
+ none ->
+ case T2 of
+ {op,_,'and',_,_} ->
+ T2;
+ _ ->
+ %% Wrap the 'orelse' expression in an dummy 'and true' to make
+ %% sure that the entire guard fails if the 'orelse'
+ %% expression returns 'fail'. ('orelse' used to verify
+ %% that its right operand was a boolean, but that is no
+ %% longer the case.)
+ {op,NL,'and',T2,{atom,NL,true}}
+ end;
+ _ ->
+ {op,NL,'and',T2,E}
+ end.
+
+%% lc_tq(Line, Qualifiers, State) ->
+%% {[TransQual],State'}
+
+lc_tq(Line, [{generate,Lg,P0,G0} | Qs0], St0) ->
+ {G1,St1} = expr(G0, St0),
+ {P1,St2} = pattern(P0, St1),
+ {Qs1,St3} = lc_tq(Line, Qs0, St2),
+ {[{generate,Lg,P1,G1} | Qs1],St3};
+lc_tq(Line, [{b_generate,Lg,P0,G0} | Qs0], St0) ->
+ {G1,St1} = expr(G0, St0),
+ {P1,St2} = pattern(P0, St1),
+ {Qs1,St3} = lc_tq(Line, Qs0, St2),
+ {[{b_generate,Lg,P1,G1} | Qs1],St3};
+lc_tq(Line, [F0 | Qs0], St0) ->
+ %% Allow record/2 and expand out as guard test.
+ case erl_lint:is_guard_test(F0) of
+ true ->
+ {F1,St1} = guard_test(F0, St0),
+ {Qs1,St2} = lc_tq(Line, Qs0, St1),
+ {[F1|Qs1],St2};
+ false ->
+ {F1,St1} = expr(F0, St0),
+ {Qs1,St2} = lc_tq(Line, Qs0, St1),
+ {[F1 | Qs1],St2}
+ end;
+lc_tq(_Line, [], St0) ->
+ {[],St0#exprec{checked_ra = []}}.
+
+
+%% normalise_fields([RecDef]) -> [Field].
+%% Normalise the field definitions to always have a default value. If
+%% none has been given then use 'undefined'.
+
+normalise_fields(Fs) ->
+ map(fun ({record_field,Lf,Field}) ->
+ {record_field,Lf,Field,{atom,Lf,undefined}};
+ ({typed_record_field,{record_field,Lf,Field},_Type}) ->
+ {record_field,Lf,Field,{atom,Lf,undefined}};
+ ({typed_record_field,Field,_Type}) ->
+ Field;
+ (F) -> F
+ end, Fs).
+
+%% record_fields(RecordName, State)
+%% find_field(FieldName, Fields)
+
+record_fields(R, St) -> dict:fetch(R, St#exprec.records).
+
+find_field(F, [{record_field,_,{atom,_,F},Val} | _]) -> {ok,Val};
+find_field(F, [_ | Fs]) -> find_field(F, Fs);
+find_field(_, []) -> error.
+
+%% field_names(RecFields) -> [Name].
+%% Return a list of the field names structures.
+
+field_names(Fs) ->
+ map(fun ({record_field,_,Field,_Val}) -> Field end, Fs).
+
+%% index_expr(Line, FieldExpr, Name, Fields) -> IndexExpr.
+%% Return an expression which evaluates to the index of a
+%% field. Currently only handle the case where the field is an
+%% atom. This expansion must be passed through expr again.
+
+index_expr(Line, {atom,_,F}, _Name, Fs) ->
+ {integer,Line,index_expr(F, Fs, 2)}.
+
+index_expr(F, [{record_field,_,{atom,_,F},_} | _], I) -> I;
+index_expr(F, [_ | Fs], I) -> index_expr(F, Fs, I+1).
+
+%% get_record_field(Line, RecExpr, FieldExpr, Name, St) -> {Expr,St'}.
+%% Return an expression which verifies that the type of record
+%% is correct and then returns the value of the field.
+%% This expansion must be passed through expr again.
+
+get_record_field(Line, R, Index, Name, St) ->
+ case strict_record_tests(St#exprec.compile) of
+ false ->
+ sloppy_get_record_field(Line, R, Index, Name, St);
+ true ->
+ strict_get_record_field(Line, R, Index, Name, St)
+ end.
+
+strict_get_record_field(Line, R, {atom,_,F}=Index, Name, St0) ->
+ case is_in_guard() of
+ false -> %Body context.
+ {Var,St} = new_var(Line, St0),
+ Fs = record_fields(Name, St),
+ I = index_expr(F, Fs, 2),
+ P = record_pattern(2, I, Var, length(Fs)+1, Line, [{atom,Line,Name}]),
+ NLine = neg_line(Line),
+ E = {'case',NLine,R,
+ [{clause,NLine,[{tuple,NLine,P}],[],[Var]},
+ {clause,NLine,[{var,NLine,'_'}],[],
+ [{call,NLine,{remote,NLine,
+ {atom,NLine,erlang},
+ {atom,NLine,error}},
+ [{tuple,NLine,[{atom,NLine,badrecord},{atom,NLine,Name}]}]}]}]},
+ expr(E, St);
+ true -> %In a guard.
+ Fs = record_fields(Name, St0),
+ I = index_expr(Line, Index, Name, Fs),
+ {ExpR,St1} = expr(R, St0),
+ %% Just to make comparison simple:
+ ExpRp = erl_lint:modify_line(ExpR, fun(_L) -> 0 end),
+ RA = {{Name,ExpRp},Line,ExpR,length(Fs)+1},
+ St2 = St1#exprec{strict_ra = [RA | St1#exprec.strict_ra]},
+ {{call,Line,{atom,Line,element},[I,ExpR]},St2}
+ end.
+
+record_pattern(I, I, Var, Sz, Line, Acc) ->
+ record_pattern(I+1, I, Var, Sz, Line, [Var | Acc]);
+record_pattern(Cur, I, Var, Sz, Line, Acc) when Cur =< Sz ->
+ record_pattern(Cur+1, I, Var, Sz, Line, [{var,Line,'_'} | Acc]);
+record_pattern(_, _, _, _, _, Acc) -> reverse(Acc).
+
+sloppy_get_record_field(Line, R, Index, Name, St) ->
+ Fs = record_fields(Name, St),
+ I = index_expr(Line, Index, Name, Fs),
+ expr({call,Line,{atom,Line,element},[I,R]}, St).
+
+strict_record_tests([strict_record_tests | _]) -> true;
+strict_record_tests([no_strict_record_tests | _]) -> false;
+strict_record_tests([_ | Os]) -> strict_record_tests(Os);
+strict_record_tests([]) -> true. %Default.
+
+strict_record_updates([strict_record_updates | _]) -> true;
+strict_record_updates([no_strict_record_updates | _]) -> false;
+strict_record_updates([_ | Os]) -> strict_record_updates(Os);
+strict_record_updates([]) -> false. %Default.
+
+%% pattern_fields([RecDefField], [Match]) -> [Pattern].
+%% Build a list of match patterns for the record tuple elements.
+%% This expansion must be passed through pattern again. N.B. We are
+%% scanning the record definition field list!
+
+pattern_fields(Fs, Ms) ->
+ Wildcard = record_wildcard_init(Ms),
+ map(fun ({record_field,L,{atom,_,F},_}) ->
+ case find_field(F, Ms) of
+ {ok,Match} -> Match;
+ error when Wildcard =:= none -> {var,L,'_'};
+ error -> Wildcard
+ end
+ end, Fs).
+
+%% record_inits([RecDefField], [Init]) -> [InitExpr].
+%% Build a list of initialisation expressions for the record tuple
+%% elements. This expansion must be passed through expr
+%% again. N.B. We are scanning the record definition field list!
+
+record_inits(Fs, Is) ->
+ WildcardInit = record_wildcard_init(Is),
+ map(fun ({record_field,_,{atom,_,F},D}) ->
+ case find_field(F, Is) of
+ {ok,Init} -> Init;
+ error when WildcardInit =:= none -> D;
+ error -> WildcardInit
+ end
+ end, Fs).
+
+record_wildcard_init([{record_field,_,{var,_,'_'},D} | _]) -> D;
+record_wildcard_init([_ | Is]) -> record_wildcard_init(Is);
+record_wildcard_init([]) -> none.
+
+%% record_update(Record, RecordName, [RecDefField], [Update], State) ->
+%% {Expr,State'}
+%% Build an expression to update fields in a record returning a new
+%% record. Try to be smart and optimise this. This expansion must be
+%% passed through expr again.
+
+record_update(R, Name, Fs, Us0, St0) ->
+ Line = element(2, R),
+ {Pre,Us,St1} = record_exprs(Us0, St0),
+ Nf = length(Fs), %# of record fields
+ Nu = length(Us), %# of update fields
+ Nc = Nf - Nu, %# of copy fields
+
+ %% We need a new variable for the record expression
+ %% to guarantee that it is only evaluated once.
+ {Var,St2} = new_var(Line, St1),
+
+ StrictUpdates = strict_record_updates(St2#exprec.compile),
+
+ %% Try to be intelligent about which method of updating record to use.
+ {Update,St} =
+ if
+ Nu =:= 0 ->
+ record_match(Var, Name, Line, Fs, Us, St2);
+ Nu =< Nc, not StrictUpdates -> %Few fields updated
+ {record_setel(Var, Name, Fs, Us), St2};
+ true -> %The wide area inbetween
+ record_match(Var, Name, element(2, hd(Us)), Fs, Us, St2)
+ end,
+ {{block,Line,Pre ++ [{match,Line,Var,R},Update]},St}.
+
+%% record_match(Record, RecordName, [RecDefField], [Update], State)
+%% Build a 'case' expression to modify record fields.
+
+record_match(R, Name, Lr, Fs, Us, St0) ->
+ {Ps,News,St1} = record_upd_fs(Fs, Us, St0),
+ NLr = neg_line(Lr),
+ {{'case',Lr,R,
+ [{clause,Lr,[{tuple,Lr,[{atom,Lr,Name} | Ps]}],[],
+ [{tuple,Lr,[{atom,Lr,Name} | News]}]},
+ {clause,NLr,[{var,NLr,'_'}],[],
+ [call_error(NLr, {tuple,NLr,[{atom,NLr,badrecord},{atom,NLr,Name}]})]}
+ ]},
+ St1}.
+
+record_upd_fs([{record_field,Lf,{atom,_La,F},_Val} | Fs], Us, St0) ->
+ {P,St1} = new_var(Lf, St0),
+ {Ps,News,St2} = record_upd_fs(Fs, Us, St1),
+ case find_field(F, Us) of
+ {ok,New} -> {[P | Ps],[New | News],St2};
+ error -> {[P | Ps],[P | News],St2}
+ end;
+record_upd_fs([], _, St) -> {[],[],St}.
+
+%% record_setel(Record, RecordName, [RecDefField], [Update])
+%% Build a nested chain of setelement calls to build the
+%% updated record tuple.
+
+record_setel(R, Name, Fs, Us0) ->
+ Us1 = foldl(fun ({record_field,Lf,Field,Val}, Acc) ->
+ {integer,_,FieldIndex} = I = index_expr(Lf, Field, Name, Fs),
+ [{FieldIndex,{I,Lf,Val}} | Acc]
+ end, [], Us0),
+ Us2 = sort(Us1),
+ Us = [T || {_,T} <- Us2],
+ Lr = element(2, hd(Us)),
+ Wildcards = duplicate(length(Fs), {var,Lr,'_'}),
+ NLr = neg_line(Lr),
+ {'case',Lr,R,
+ [{clause,Lr,[{tuple,Lr,[{atom,Lr,Name} | Wildcards]}],[],
+ [foldr(fun ({I,Lf,Val}, Acc) ->
+ {call,Lf,{atom,Lf,setelement},[I,Acc,Val]} end,
+ R, Us)]},
+ {clause,NLr,[{var,NLr,'_'}],[],
+ [call_error(NLr, {tuple,NLr,[{atom,NLr,badrecord},{atom,NLr,Name}]})]}]}.
+
+%% Expand a call to record_info/2. We have checked that it is not
+%% shadowed by an import.
+
+record_info_call(Line, [{atom,_Li,Info},{atom,_Ln,Name}], St) ->
+ case Info of
+ size ->
+ {{integer,Line,1+length(record_fields(Name, St))},St};
+ fields ->
+ {make_list(field_names(record_fields(Name, St)), Line),St}
+ end.
+
+%% Break out expressions from an record update list and bind to new
+%% variables. The idea is that we will evaluate all update expressions
+%% before starting to update the record.
+
+record_exprs(Us, St) ->
+ record_exprs(Us, St, [], []).
+
+record_exprs([{record_field,Lf,{atom,_La,_F}=Name,Val}=Field0 | Us], St0, Pre, Fs) ->
+ case is_simple_val(Val) of
+ true ->
+ record_exprs(Us, St0, Pre, [Field0 | Fs]);
+ false ->
+ {Var,St} = new_var(Lf, St0),
+ Bind = {match,Lf,Var,Val},
+ Field = {record_field,Lf,Name,Var},
+ record_exprs(Us, St, [Bind | Pre], [Field | Fs])
+ end;
+record_exprs([], St, Pre, Fs) ->
+ {reverse(Pre),Fs,St}.
+
+is_simple_val({var,_,_}) -> true;
+is_simple_val(Val) ->
+ try
+ erl_parse:normalise(Val),
+ true
+ catch error:_ ->
+ false
+ end.
+
+%% pattern_bin([Element], State) -> {[Element],[Variable],[UsedVar],State}.
+
+pattern_bin(Es0, St) ->
+ foldr(fun (E, Acc) -> pattern_element(E, Acc) end, {[],St}, Es0).
+
+pattern_element({bin_element,Line,Expr0,Size,Type}, {Es,St0}) ->
+ {Expr,St1} = pattern(Expr0, St0),
+ {[{bin_element,Line,Expr,Size,Type} | Es],St1}.
+
+%% expr_bin([Element], State) -> {[Element],State}.
+
+expr_bin(Es0, St) ->
+ foldr(fun (E, Acc) -> bin_element(E, Acc) end, {[],St}, Es0).
+
+bin_element({bin_element,Line,Expr,Size,Type}, {Es,St0}) ->
+ {Expr1,St1} = expr(Expr, St0),
+ {Size1,St2} = if Size =:= default -> {default,St1};
+ true -> expr(Size, St1)
+ end,
+ {[{bin_element,Line,Expr1,Size1,Type} | Es],St2}.
+
+new_var(L, St0) ->
+ {New,St1} = new_var_name(St0),
+ {{var,L,New},St1}.
+
+new_var_name(St) ->
+ C = St#exprec.vcount,
+ {list_to_atom("rec" ++ integer_to_list(C)),St#exprec{vcount=C+1}}.
+
+make_list(Ts, Line) ->
+ foldr(fun (H, T) -> {cons,Line,H,T} end, {nil,Line}, Ts).
+
+call_error(L, R) ->
+ {call,L,{remote,L,{atom,L,erlang},{atom,L,error}},[R]}.
+
+import({Mod,Fs}, St) ->
+ St#exprec{imports=add_imports(Mod, Fs, St#exprec.imports)};
+import(_Mod0, St) ->
+ St.
+
+add_imports(Mod, [F | Fs], Is) ->
+ add_imports(Mod, Fs, orddict:store(F, Mod, Is));
+add_imports(_, [], Is) -> Is.
+
+imported(F, A, St) ->
+ case orddict:find({F,A}, St#exprec.imports) of
+ {ok,Mod} -> {yes,Mod};
+ error -> no
+ end.
+
+neg_line(L) ->
+ erl_parse:set_line(L, fun(Line) -> -abs(Line) end).
diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl
new file mode 100644
index 0000000000..16173d8210
--- /dev/null
+++ b/lib/stdlib/src/erl_internal.erl
@@ -0,0 +1,351 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_internal).
+
+%% Define Erlang bifs, guard tests and other internal stuff.
+%%
+%% NOTE: All guard_bif(), arith_op(), bool_op() and comp_op() must be
+%% defined in bif.tab as 'ubif', i.e bif without trace wrapper.
+%%
+%% Why?
+%%
+%% Because the compiler uses an optimized instruction for
+%% the call to these bifs, which when loaded gets a direct
+%% entry pointer inserted into itself by the loader,
+%% instead of a bif table index as for regular bifs.
+%%
+%% If tracing is enabled on these bifs, when a module is loaded,
+%% the direct entry pointer inserted into the call instruction
+%% will be pointing to the trace wrapper, so even if tracing is
+%% disabled for bifs, the loaded module will call these bifs through
+%% the trace wrappers.
+%%
+%% The call instruction in question does not give enough information
+%% to call trace match function {caller} for it to succeed
+%% other then by chance, and the 'return_to' trace flag works just
+%% as bad, so both will mostly say that the caller is 'undefined'.
+%% Furthermore the calls to these bifs will still generate
+%% trace messages from the loaded module even if tracing is disabled
+%% for them, and no one knows what else might be messed up.
+%%
+%% That's why!
+%%
+
+-export([bif/2,bif/3,guard_bif/2,
+ type_test/2,new_type_test/2,old_type_test/2]).
+-export([arith_op/2,bool_op/2,comp_op/2,list_op/2,send_op/2,op_type/2]).
+
+%%---------------------------------------------------------------------------
+
+%% Erlang builtin functions allowed in guards.
+-spec guard_bif(Name::atom(), Arity::arity()) -> boolean().
+
+guard_bif(abs, 1) -> true;
+guard_bif(float, 1) -> true;
+guard_bif(trunc, 1) -> true;
+guard_bif(round, 1) -> true;
+guard_bif(length, 1) -> true;
+guard_bif(hd, 1) -> true;
+guard_bif(tl, 1) -> true;
+guard_bif(size, 1) -> true;
+guard_bif(bit_size, 1) -> true;
+guard_bif(byte_size, 1) -> true;
+guard_bif(element, 2) -> true;
+guard_bif(self, 0) -> true;
+guard_bif(node, 0) -> true;
+guard_bif(node, 1) -> true;
+guard_bif(tuple_size, 1) -> true;
+guard_bif(is_atom, 1) -> true;
+guard_bif(is_binary, 1) -> true;
+guard_bif(is_bitstring, 1) -> true;
+guard_bif(is_boolean, 1) -> true;
+guard_bif(is_float, 1) -> true;
+guard_bif(is_function, 1) -> true;
+guard_bif(is_function, 2) -> true;
+guard_bif(is_integer, 1) -> true;
+guard_bif(is_list, 1) -> true;
+guard_bif(is_number, 1) -> true;
+guard_bif(is_pid, 1) -> true;
+guard_bif(is_port, 1) -> true;
+guard_bif(is_reference, 1) -> true;
+guard_bif(is_tuple, 1) -> true;
+guard_bif(is_record, 2) -> true;
+guard_bif(is_record, 3) -> true;
+guard_bif(Name, A) when is_atom(Name), is_integer(A) -> false.
+
+%% Erlang type tests.
+-spec type_test(Name::atom(), Arity::arity()) -> boolean().
+
+type_test(Name, Arity) ->
+ new_type_test(Name, Arity) orelse old_type_test(Name, Arity).
+
+%% Erlang new-style type tests.
+-spec new_type_test(Name::atom(), Arity::arity()) -> boolean().
+
+new_type_test(is_atom, 1) -> true;
+new_type_test(is_boolean, 1) -> true;
+new_type_test(is_binary, 1) -> true;
+new_type_test(is_bitstring, 1) -> true;
+new_type_test(is_float, 1) -> true;
+new_type_test(is_function, 1) -> true;
+new_type_test(is_function, 2) -> true;
+new_type_test(is_integer, 1) -> true;
+new_type_test(is_list, 1) -> true;
+new_type_test(is_number, 1) -> true;
+new_type_test(is_pid, 1) -> true;
+new_type_test(is_port, 1) -> true;
+new_type_test(is_reference, 1) -> true;
+new_type_test(is_tuple, 1) -> true;
+new_type_test(is_record, 2) -> true;
+new_type_test(is_record, 3) -> true;
+new_type_test(Name, A) when is_atom(Name), is_integer(A) -> false.
+
+%% Erlang old-style type tests.
+-spec old_type_test(Name::atom(), Arity::arity()) -> boolean().
+
+old_type_test(integer, 1) -> true;
+old_type_test(float, 1) -> true;
+old_type_test(number, 1) -> true;
+old_type_test(atom, 1) -> true;
+old_type_test(list, 1) -> true;
+old_type_test(tuple, 1) -> true;
+old_type_test(pid, 1) -> true;
+old_type_test(reference, 1) -> true;
+old_type_test(port, 1) -> true;
+old_type_test(binary, 1) -> true;
+old_type_test(record, 2) -> true;
+old_type_test(function, 1) -> true;
+old_type_test(Name, A) when is_atom(Name), is_integer(A) -> false.
+
+-spec arith_op(Op::atom(), Arity::arity()) -> boolean().
+
+arith_op('+', 1) -> true;
+arith_op('-', 1) -> true;
+arith_op('*', 2) -> true;
+arith_op('/', 2) -> true;
+arith_op('+', 2) -> true;
+arith_op('-', 2) -> true;
+arith_op('bnot', 1) -> true;
+arith_op('div', 2) -> true;
+arith_op('rem', 2) -> true;
+arith_op('band', 2) -> true;
+arith_op('bor', 2) -> true;
+arith_op('bxor', 2) -> true;
+arith_op('bsl', 2) -> true;
+arith_op('bsr', 2) -> true;
+arith_op(Op, A) when is_atom(Op), is_integer(A) -> false.
+
+-spec bool_op(Op::atom(), Arity::arity()) -> boolean().
+
+bool_op('not', 1) -> true;
+bool_op('and', 2) -> true;
+bool_op('or', 2) -> true;
+bool_op('xor', 2) -> true;
+bool_op(Op, A) when is_atom(Op), is_integer(A) -> false.
+
+-spec comp_op(Op::atom(), Arity::arity()) -> boolean().
+
+comp_op('==', 2) -> true;
+comp_op('/=', 2) -> true;
+comp_op('=<', 2) -> true;
+comp_op('<', 2) -> true;
+comp_op('>=', 2) -> true;
+comp_op('>', 2) -> true;
+comp_op('=:=', 2) -> true;
+comp_op('=/=', 2) -> true;
+comp_op(Op, A) when is_atom(Op), is_integer(A) -> false.
+
+-spec list_op(Op::atom(), Arity::arity()) -> boolean().
+
+list_op('++', 2) -> true;
+list_op('--', 2) -> true;
+list_op(Op, A) when is_atom(Op), is_integer(A) -> false.
+
+-spec send_op(Op::atom(), Arity::arity()) -> boolean().
+
+send_op('!', 2) -> true;
+send_op(Op, A) when is_atom(Op), is_integer(A) -> false.
+
+-spec op_type(atom(), arity()) -> 'arith' | 'bool' | 'comp' | 'list' | 'send'.
+
+op_type('+', 1) -> arith;
+op_type('-', 1) -> arith;
+op_type('*', 2) -> arith;
+op_type('/', 2) -> arith;
+op_type('+', 2) -> arith;
+op_type('-', 2) -> arith;
+op_type('bnot', 1) -> arith;
+op_type('div', 2) -> arith;
+op_type('rem', 2) -> arith;
+op_type('band', 2) -> arith;
+op_type('bor', 2) -> arith;
+op_type('bxor', 2) -> arith;
+op_type('bsl', 2) -> arith;
+op_type('bsr', 2) -> arith;
+op_type('not', 1) -> bool;
+op_type('and', 2) -> bool;
+op_type('or', 2) -> bool;
+op_type('xor', 2) -> bool;
+op_type('==', 2) -> comp;
+op_type('/=', 2) -> comp;
+op_type('=<', 2) -> comp;
+op_type('<', 2) -> comp;
+op_type('>=', 2) -> comp;
+op_type('>', 2) -> comp;
+op_type('=:=', 2) -> comp;
+op_type('=/=', 2) -> comp;
+op_type('++', 2) -> list;
+op_type('--', 2) -> list;
+op_type('!', 2) -> send.
+
+-spec bif(Mod::atom(), Name::atom(), Arity::arity()) -> boolean().
+
+bif(erlang, Name, Arity) -> bif(Name, Arity);
+bif(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> false.
+
+-spec bif(Name::atom(), Arity::arity()) -> boolean().
+%% Returns true if erlang:Name/Arity is an auto-imported BIF, false otherwise.
+%% Use erlang:is_bultin(Mod, Name, Arity) to find whether a function is a BIF
+%% (meaning implemented in C) or not.
+
+bif(abs, 1) -> true;
+bif(apply, 2) -> true;
+bif(apply, 3) -> true;
+bif(atom_to_binary, 2) -> true;
+bif(atom_to_list, 1) -> true;
+bif(binary_to_atom, 2) -> true;
+bif(binary_to_existing_atom, 2) -> true;
+bif(binary_to_list, 1) -> true;
+bif(binary_to_list, 3) -> true;
+bif(binary_to_term, 1) -> true;
+bif(bitsize, 1) -> true;
+bif(bit_size, 1) -> true;
+bif(bitstring_to_list, 1) -> true;
+bif(byte_size, 1) -> true;
+bif(check_process_code, 2) -> true;
+bif(concat_binary, 1) -> true;
+bif(date, 0) -> true;
+bif(delete_module, 1) -> true;
+bif(disconnect_node, 1) -> true;
+bif(element, 2) -> true;
+bif(erase, 0) -> true;
+bif(erase, 1) -> true;
+bif(exit, 1) -> true;
+bif(exit, 2) -> true;
+bif(float, 1) -> true;
+bif(float_to_list, 1) -> true;
+bif(garbage_collect, 0) -> true;
+bif(garbage_collect, 1) -> true;
+bif(get, 0) -> true;
+bif(get, 1) -> true;
+bif(get_keys, 1) -> true;
+bif(group_leader, 0) -> true;
+bif(group_leader, 2) -> true;
+bif(halt, 0) -> true;
+bif(halt, 1) -> true;
+bif(hd, 1) -> true;
+bif(integer_to_list, 1) -> true;
+bif(iolist_size, 1) -> true;
+bif(iolist_to_binary, 1) -> true;
+bif(is_alive, 0) -> true;
+bif(is_process_alive, 1) -> true;
+bif(is_atom, 1) -> true;
+bif(is_boolean, 1) -> true;
+bif(is_binary, 1) -> true;
+bif(is_bitstr, 1) -> true;
+bif(is_bitstring, 1) -> true;
+bif(is_float, 1) -> true;
+bif(is_function, 1) -> true;
+bif(is_function, 2) -> true;
+bif(is_integer, 1) -> true;
+bif(is_list, 1) -> true;
+bif(is_number, 1) -> true;
+bif(is_pid, 1) -> true;
+bif(is_port, 1) -> true;
+bif(is_reference, 1) -> true;
+bif(is_tuple, 1) -> true;
+bif(is_record, 2) -> true;
+bif(is_record, 3) -> true;
+bif(length, 1) -> true;
+bif(link, 1) -> true;
+bif(list_to_atom, 1) -> true;
+bif(list_to_binary, 1) -> true;
+bif(list_to_bitstring, 1) -> true;
+bif(list_to_existing_atom, 1) -> true;
+bif(list_to_float, 1) -> true;
+bif(list_to_integer, 1) -> true;
+bif(list_to_pid, 1) -> true;
+bif(list_to_tuple, 1) -> true;
+bif(load_module, 2) -> true;
+bif(make_ref, 0) -> true;
+bif(module_loaded, 1) -> true;
+bif(monitor_node, 2) -> true;
+bif(node, 0) -> true;
+bif(node, 1) -> true;
+bif(nodes, 0) -> true;
+bif(nodes, 1) -> true;
+bif(now, 0) -> true;
+bif(open_port, 2) -> true;
+bif(pid_to_list, 1) -> true;
+bif(port_close, 1) -> true;
+bif(port_command, 2) -> true;
+bif(port_connect, 2) -> true;
+bif(port_control, 3) -> true;
+bif(pre_loaded, 0) -> true;
+bif(process_flag, 2) -> true;
+bif(process_flag, 3) -> true;
+bif(process_info, 1) -> true;
+bif(process_info, 2) -> true;
+bif(processes, 0) -> true;
+bif(purge_module, 1) -> true;
+bif(put, 2) -> true;
+bif(register, 2) -> true;
+bif(registered, 0) -> true;
+bif(round, 1) -> true;
+bif(self, 0) -> true;
+bif(setelement, 3) -> true;
+bif(size, 1) -> true;
+bif(spawn, 1) -> true;
+bif(spawn, 2) -> true;
+bif(spawn, 3) -> true;
+bif(spawn, 4) -> true;
+bif(spawn_link, 1) -> true;
+bif(spawn_link, 2) -> true;
+bif(spawn_link, 3) -> true;
+bif(spawn_link, 4) -> true;
+bif(spawn_monitor, 1) -> true;
+bif(spawn_monitor, 3) -> true;
+bif(spawn_opt, 2) -> true;
+bif(spawn_opt, 3) -> true;
+bif(spawn_opt, 4) -> true;
+bif(spawn_opt, 5) -> true;
+bif(split_binary, 2) -> true;
+bif(statistics, 1) -> true;
+bif(term_to_binary, 1) -> true;
+bif(term_to_binary, 2) -> true;
+bif(throw, 1) -> true;
+bif(time, 0) -> true;
+bif(tl, 1) -> true;
+bif(trunc, 1) -> true;
+bif(tuple_size, 1) -> true;
+bif(tuple_to_list, 1) -> true;
+bif(unlink, 1) -> true;
+bif(unregister, 1) -> true;
+bif(whereis, 1) -> true;
+bif(Name, A) when is_atom(Name), is_integer(A) -> false.
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
new file mode 100644
index 0000000000..156d68554e
--- /dev/null
+++ b/lib/stdlib/src/erl_lint.erl
@@ -0,0 +1,3489 @@
+%% -*- erlang-indent-level: 4 -*-
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Do necessary checking of Erlang code.
+
+%% N.B. All the code necessary for checking structs (tagged tuples) is
+%% here. Just comment out the lines in pattern/2, gexpr/3 and expr/3.
+
+-module(erl_lint).
+
+-export([module/1,module/2,module/3,format_error/1]).
+-export([exprs/2,exprs_opt/3,used_vars/2]). % Used from erl_eval.erl.
+-export([is_pattern_expr/1,is_guard_test/1,is_guard_test/2]).
+-export([is_guard_expr/1]).
+-export([bool_option/4,value_option/3,value_option/7]).
+
+-export([modify_line/2]).
+
+-import(lists, [member/2,map/2,foldl/3,foldr/3,mapfoldl/3,all/2,reverse/1]).
+
+%% bool_option(OnOpt, OffOpt, Default, Options) -> boolean().
+%% value_option(Flag, Default, Options) -> Value.
+%% value_option(Flag, Default, OnOpt, OnVal, OffOpt, OffVal, Options) ->
+%% Value.
+%% The option handling functions.
+
+-spec bool_option(atom(), atom(), boolean(), [_]) -> boolean().
+
+bool_option(On, Off, Default, Opts) ->
+ foldl(fun (Opt, _Def) when Opt =:= On -> true;
+ (Opt, _Def) when Opt =:= Off -> false;
+ (_Opt, Def) -> Def
+ end, Default, Opts).
+
+value_option(Flag, Default, Opts) ->
+ foldl(fun ({Opt,Val}, _Def) when Opt =:= Flag -> Val;
+ (_Opt, Def) -> Def
+ end, Default, Opts).
+
+value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
+ foldl(fun ({Opt,Val}, _Def) when Opt =:= Flag -> Val;
+ (Opt, _Def) when Opt =:= On -> OnVal;
+ (Opt, _Def) when Opt =:= Off -> OffVal;
+ (_Opt, Def) -> Def
+ end, Default, Opts).
+
+%% The error and warning info structures, {Line,Module,Descriptor},
+%% are kept in their seperate fields in the lint state record together
+%% with the name of the file (when a new file is entered, marked by
+%% the 'file' attribute, then the field 'file' of the lint record is
+%% set). At the end of the run these lists are packed into a list of
+%% {FileName,ErrorDescList} pairs which are returned.
+
+-include_lib("stdlib/include/erl_bits.hrl").
+
+%%-define(DEBUGF(X,Y), io:format(X, Y)).
+-define(DEBUGF(X,Y), void).
+
+%% Usage of records, functions, and imports. The variable table, which
+%% is passed on as an argument, holds the usage of variables.
+-record(usage, {
+ calls = dict:new(), %Who calls who
+ imported = [], %Actually imported functions
+ used_records=sets:new() :: set(), %Used record definitions
+ used_types = sets:new() :: set() %Used type definitions
+ }).
+
+%% Define the lint state record.
+%% 'called' and 'exports' contain {Line, {Function, Arity}},
+%% the other function collections contain {Function, Arity}.
+-record(lint, {state=start :: 'start' | 'attribute' | 'function',
+ module=[], %Module
+ package="", %Module package
+ extends=[], %Extends
+ behaviour=[], %Behaviour
+ exports=gb_sets:empty() :: gb_set(), %Exports
+ imports=[], %Imports
+ mod_imports=dict:new() :: dict(), %Module Imports
+ compile=[], %Compile flags
+ records=dict:new() :: dict(), %Record definitions
+ defined=gb_sets:empty() :: gb_set(), %Defined fuctions
+ on_load=[] :: [{atom(),integer()}], %On-load function
+ on_load_line=0 :: integer(), %Line for on_load
+ clashes=[], %Exported functions named as BIFs
+ not_deprecated=[], %Not considered deprecated
+ func=[], %Current function
+ warn_format=0, %Warn format calls
+ enabled_warnings=[], %All enabled warnings (ordset).
+ errors=[], %Current errors
+ warnings=[], %Current warnings
+ global_vt=[], %The global VarTable
+ file = "" :: string(), %From last file attribute
+ recdef_top=false :: boolean(), %true in record initialisation
+ %outside any fun or lc
+ xqlc= false :: boolean(), %true if qlc.hrl included
+ new = false :: boolean(), %Has user-defined 'new/N'
+ called= [], %Called functions
+ usage = #usage{} :: #usage{},
+ specs = dict:new() :: dict(), %Type specifications
+ types = dict:new() :: dict() %Type definitions
+ }).
+
+-type lint_state() :: #lint{}.
+
+%% format_error(Error)
+%% Return a string describing the error.
+
+format_error(undefined_module) ->
+ "no module definition";
+format_error({bad_module_name, M}) ->
+ io_lib:format("bad module name '~s'", [M]);
+format_error(redefine_module) ->
+ "redefining module";
+format_error(redefine_extends) ->
+ "redefining extends attribute";
+format_error(extends_self) ->
+ "cannot extend from self";
+%% format_error({redefine_mod_import, M, P}) ->
+%% io_lib:format("module '~s' already imported from package '~s'", [M, P]);
+
+format_error(invalid_call) ->
+ "invalid function call";
+format_error(invalid_record) ->
+ "invalid record expression";
+
+format_error({attribute,A}) ->
+ io_lib:format("attribute '~w' after function definitions", [A]);
+format_error({missing_qlc_hrl,A}) ->
+ io_lib:format("qlc:q/~w called, but \"qlc.hrl\" not included", [A]);
+format_error({redefine_import,{bif,{F,A},M}}) ->
+ io_lib:format("function ~w/~w already auto-imported from ~w", [F,A,M]);
+format_error({redefine_import,{{F,A},M}}) ->
+ io_lib:format("function ~w/~w already imported from ~w", [F,A,M]);
+format_error({bad_inline,{F,A}}) ->
+ io_lib:format("inlined function ~w/~w undefined", [F,A]);
+format_error({invalid_deprecated,D}) ->
+ io_lib:format("badly formed deprecated attribute ~w", [D]);
+format_error(invalid_extends) ->
+ "badly formed extends attribute";
+format_error(define_instance) ->
+ "defining instance function not allowed in abstract module";
+format_error({bad_deprecated,{F,A}}) ->
+ io_lib:format("deprecated function ~w/~w undefined or not exported", [F,A]);
+format_error({bad_nowarn_unused_function,{F,A}}) ->
+ io_lib:format("function ~w/~w undefined", [F,A]);
+format_error({bad_nowarn_bif_clash,{F,A}}) ->
+ io_lib:format("function ~w/~w undefined", [F,A]);
+format_error({bad_nowarn_deprecated_function,{M,F,A}}) ->
+ io_lib:format("~w:~w/~w is not a deprecated function", [M,F,A]);
+format_error({bad_on_load,Term}) ->
+ io_lib:format("badly formed on_load attribute: ~w", [Term]);
+format_error(multiple_on_loads) ->
+ "more than one on_load attribute";
+format_error({bad_on_load_arity,{F,A}}) ->
+ io_lib:format("function ~w/~w has wrong arity (must be 0)", [F,A]);
+format_error({undefined_on_load,{F,A}}) ->
+ io_lib:format("function ~w/~w undefined", [F,A]);
+
+format_error(export_all) ->
+ "export_all flag enabled - all functions will be exported";
+format_error({duplicated_export, {F,A}}) ->
+ io_lib:format("function ~w/~w already exported", [F,A]);
+format_error({unused_import,{{F,A},M}}) ->
+ io_lib:format("import ~w:~w/~w is unused", [M,F,A]);
+format_error({undefined_function,{F,A}}) ->
+ io_lib:format("function ~w/~w undefined", [F,A]);
+format_error({redefine_function,{F,A}}) ->
+ io_lib:format("function ~w/~w already defined", [F,A]);
+format_error({define_import,{F,A}}) ->
+ io_lib:format("defining imported function ~w/~w", [F,A]);
+format_error({unused_function,{F,A}}) ->
+ io_lib:format("function ~w/~w is unused", [F,A]);
+format_error({redefine_bif,{F,A}}) ->
+ io_lib:format("defining BIF ~w/~w", [F,A]);
+format_error({call_to_redefined_bif,{F,A}}) ->
+ io_lib:format("call to ~w/~w will call erlang:~w/~w; "
+ "not ~w/~w in this module \n"
+ " (add an explicit module name to the call to avoid this error)",
+ [F,A,F,A,F,A]);
+
+format_error({deprecated, MFA, ReplacementMFA, Rel}) ->
+ io_lib:format("~s is deprecated and will be removed in ~s; use ~s",
+ [format_mfa(MFA), Rel, format_mfa(ReplacementMFA)]);
+format_error({deprecated, {M1, F1, A1}, String}) when is_list(String) ->
+ io_lib:format("~p:~p/~p: ~s", [M1, F1, A1, String]);
+format_error({removed, MFA, ReplacementMFA, Rel}) ->
+ io_lib:format("call to ~s will fail, since it was removed in ~s; "
+ "use ~s", [format_mfa(MFA), Rel, format_mfa(ReplacementMFA)]);
+format_error({removed, MFA, String}) when is_list(String) ->
+ io_lib:format("~s: ~s", [format_mfa(MFA), String]);
+format_error({obsolete_guard, {F, A}}) ->
+ io_lib:format("~p/~p obsolete", [F, A]);
+format_error({reserved_for_future,K}) ->
+ io_lib:format("atom ~w: future reserved keyword - rename or quote", [K]);
+%% --- patterns and guards ---
+format_error(illegal_pattern) -> "illegal pattern";
+format_error(illegal_bin_pattern) ->
+ "binary patterns cannot be matched in parallel using '='";
+format_error(illegal_expr) -> "illegal expression";
+format_error(illegal_guard_expr) -> "illegal guard expression";
+%% --- exports ---
+format_error({explicit_export,F,A}) ->
+ io_lib:format("in this release, the call to ~w/~w must be written "
+ "like this: erlang:~w/~w",
+ [F,A,F,A]);
+%% --- records ---
+format_error({undefined_record,T}) ->
+ io_lib:format("record ~w undefined", [T]);
+format_error({redefine_record,T}) ->
+ io_lib:format("record ~w already defined", [T]);
+format_error({redefine_field,T,F}) ->
+ io_lib:format("field ~w already defined in record ~w", [F,T]);
+format_error({undefined_field,T,F}) ->
+ io_lib:format("field ~w undefined in record ~w", [F,T]);
+format_error(illegal_record_info) ->
+ "illegal record info";
+format_error({field_name_is_variable,T,F}) ->
+ io_lib:format("field ~w is not an atom or _ in record ~w", [F,T]);
+format_error({wildcard_in_update,T}) ->
+ io_lib:format("meaningless use of _ in update of record ~w", [T]);
+format_error({unused_record,T}) ->
+ io_lib:format("record ~w is unused", [T]);
+format_error({untyped_record,T}) ->
+ io_lib:format("record ~w has field(s) without type information", [T]);
+%% --- variables ----
+format_error({unbound_var,V}) ->
+ io_lib:format("variable ~w is unbound", [V]);
+format_error({unsafe_var,V,{What,Where}}) ->
+ io_lib:format("variable ~w unsafe in ~w ~s",
+ [V,What,format_where(Where)]);
+format_error({exported_var,V,{What,Where}}) ->
+ io_lib:format("variable ~w exported from ~w ~s",
+ [V,What,format_where(Where)]);
+format_error({shadowed_var,V,In}) ->
+ io_lib:format("variable ~w shadowed in ~w", [V,In]);
+format_error({unused_var, V}) ->
+ io_lib:format("variable ~w is unused", [V]);
+format_error({variable_in_record_def,V}) ->
+ io_lib:format("variable ~w in record definition", [V]);
+%% --- binaries ---
+format_error({undefined_bittype,Type}) ->
+ io_lib:format("bit type ~w undefined", [Type]);
+format_error({bittype_mismatch,T1,T2,What}) ->
+ io_lib:format("bit type mismatch (~s) between ~p and ~p", [What,T1,T2]);
+format_error(bittype_unit) ->
+ "a bit unit size must not be specified unless a size is specified too";
+format_error(illegal_bitsize) ->
+ "illegal bit size";
+format_error(unsized_binary_not_at_end) ->
+ "a binary field without size is only allowed at the end of a binary pattern";
+format_error(typed_literal_string) ->
+ "a literal string in a binary pattern must not have a type or a size";
+format_error(utf_bittype_size_or_unit) ->
+ "neither size nor unit must be given for segments of type utf8/utf16/utf32";
+format_error({bad_bitsize,Type}) ->
+ io_lib:format("bad ~s bit size", [Type]);
+%% --- behaviours ---
+format_error({conflicting_behaviours,{Name,Arity},B,FirstL,FirstB}) ->
+ io_lib:format("conflicting behaviours - callback ~w/~w required by both '~p' "
+ "and '~p' ~s", [Name,Arity,B,FirstB,format_where(FirstL)]);
+format_error({undefined_behaviour_func, {Func,Arity}, Behaviour}) ->
+ io_lib:format("undefined callback function ~w/~w (behaviour '~w')",
+ [Func,Arity,Behaviour]);
+format_error({undefined_behaviour,Behaviour}) ->
+ io_lib:format("behaviour ~w undefined", [Behaviour]);
+format_error({undefined_behaviour_callbacks,Behaviour}) ->
+ io_lib:format("behaviour ~w callback functions are undefined",
+ [Behaviour]);
+format_error({ill_defined_behaviour_callbacks,Behaviour}) ->
+ io_lib:format("behaviour ~w callback functions erroneously defined",
+ [Behaviour]);
+%% --- types and specs ---
+format_error({singleton_typevar, Name}) ->
+ io_lib:format("type variable ~w is only used once (is unbound)", [Name]);
+format_error({type_ref, {TypeName, Arity}}) ->
+ io_lib:format("type ~w~s undefined", [TypeName, gen_type_paren(Arity)]);
+format_error({unused_type, {TypeName, Arity}}) ->
+ io_lib:format("type ~w~s is unused", [TypeName, gen_type_paren(Arity)]);
+format_error({new_builtin_type, {TypeName, Arity}}) ->
+ io_lib:format("type ~w~s is a new builtin type; "
+ "its (re)definition is allowed only until the next release",
+ [TypeName, gen_type_paren(Arity)]);
+format_error({builtin_type, {TypeName, Arity}}) ->
+ io_lib:format("type ~w~s is a builtin type; it cannot be redefined",
+ [TypeName, gen_type_paren(Arity)]);
+format_error({renamed_type, OldName, NewName}) ->
+ io_lib:format("type ~w() is now called ~w(); "
+ "please use the new name instead", [OldName, NewName]);
+format_error({redefine_type, {TypeName, Arity}}) ->
+ io_lib:format("type ~w~s already defined",
+ [TypeName, gen_type_paren(Arity)]);
+format_error({type_syntax, Constr}) ->
+ io_lib:format("bad ~w type", [Constr]);
+format_error({redefine_spec, {M, F, A}}) ->
+ io_lib:format("spec for ~w:~w/~w already defined", [M, F, A]);
+format_error({spec_fun_undefined, {M, F, A}}) ->
+ io_lib:format("spec for undefined function ~w:~w/~w", [M, F, A]);
+format_error({missing_spec, {F,A}}) ->
+ io_lib:format("missing specification for function ~w/~w", [F, A]);
+format_error(spec_wrong_arity) ->
+ "spec has the wrong arity";
+format_error({imported_predefined_type, Name}) ->
+ io_lib:format("referring to built-in type ~w as a remote type; "
+ "please take out the module name", [Name]);
+%% --- obsolete? unused? ---
+format_error({format_error, {Fmt, Args}}) ->
+ io_lib:format(Fmt, Args);
+format_error({mnemosyne, What}) ->
+ "mnemosyne " ++ What ++ ", missing transformation".
+
+gen_type_paren(Arity) when is_integer(Arity), Arity >= 0 ->
+ gen_type_paren_1(Arity, ")").
+
+gen_type_paren_1(0, Acc) -> "(" ++ Acc;
+gen_type_paren_1(1, Acc) -> "(_" ++ Acc;
+gen_type_paren_1(N, Acc) -> gen_type_paren_1(N - 1, ",_" ++ Acc).
+
+format_mfa({M, F, [_|_]=As}) ->
+ ","++ArityString = lists:append([[$,|integer_to_list(A)] || A <- As]),
+ format_mf(M, F, ArityString);
+format_mfa({M, F, A}) when is_integer(A) ->
+ format_mf(M, F, integer_to_list(A)).
+
+format_mf(M, F, ArityString) when is_atom(M), is_atom(F) ->
+ atom_to_list(M) ++ ":" ++ atom_to_list(F) ++ "/" ++ ArityString.
+
+format_where(L) when is_integer(L) ->
+ io_lib:format("(line ~p)", [L]);
+format_where({L,C}) when is_integer(L), is_integer(C) ->
+ io_lib:format("(line ~p, column ~p)", [L, C]).
+
+%% Local functions that are somehow automatically generated.
+
+pseudolocals() ->
+ [{module_info,0}, {module_info,1}, {record_info,2}].
+
+%%
+%% Used by erl_eval.erl to check commands.
+%%
+exprs(Exprs, BindingsList) ->
+ exprs_opt(Exprs, BindingsList, []).
+
+exprs_opt(Exprs, BindingsList, Opts) ->
+ {St0,Vs} = foldl(fun({{record,_SequenceNumber,_Name},Attr0}, {St1,Vs1}) ->
+ Attr = zip_file_and_line(Attr0, "none"),
+ {attribute_state(Attr, St1),Vs1};
+ ({V,_}, {St1,Vs1}) ->
+ {St1,[{V,{bound,unused,[]}} | Vs1]}
+ end, {start("nofile",Opts),[]}, BindingsList),
+ Vt = orddict:from_list(Vs),
+ {_Evt,St} = exprs(zip_file_and_line(Exprs, "nofile"), Vt, St0),
+ return_status(St).
+
+used_vars(Exprs, BindingsList) ->
+ Vs = foldl(fun({{record,_SequenceNumber,_Name},_Attr}, Vs0) -> Vs0;
+ ({V,_Val}, Vs0) -> [{V,{bound,unused,[]}} | Vs0]
+ end, [], BindingsList),
+ Vt = orddict:from_list(Vs),
+ {Evt,_St} = exprs(zip_file_and_line(Exprs, "nofile"), Vt, start()),
+ {ok, foldl(fun({V,{_,used,_}}, L) -> [V | L];
+ (_, L) -> L
+ end, [], Evt)}.
+
+%% module([Form]) ->
+%% module([Form], FileName) ->
+%% module([Form], FileName, [CompileOption]) ->
+%% {ok,[Warning]} | {error,[Error],[Warning]}
+%% Start processing a module. Define predefined functions and exports and
+%% apply_lambda/2 has been called to shut lint up. N.B. these lists are
+%% really all ordsets!
+
+module(Forms) ->
+ Opts = compiler_options(Forms),
+ St = forms(Forms, start("nofile", Opts)),
+ return_status(St).
+
+module(Forms, FileName) ->
+ Opts = compiler_options(Forms),
+ St = forms(Forms, start(FileName, Opts)),
+ return_status(St).
+
+module(Forms, FileName, Opts0) ->
+ %% We want the options given on the command line to take
+ %% precedence over options in the module.
+ Opts = compiler_options(Forms) ++ Opts0,
+ St = forms(Forms, start(FileName, Opts)),
+ return_status(St).
+
+compiler_options(Forms) ->
+ lists:flatten([C || {attribute,_,compile,C} <- Forms]).
+
+%% start() -> State
+%% start(FileName, [Option]) -> State
+
+start() ->
+ start("nofile", []).
+
+start(File, Opts) ->
+ Enabled0 =
+ [{unused_vars,
+ bool_option(warn_unused_vars, nowarn_unused_vars,
+ true, Opts)},
+ {export_all,
+ bool_option(warn_export_all, nowarn_export_all,
+ false, Opts)},
+ {export_vars,
+ bool_option(warn_export_vars, nowarn_export_vars,
+ false, Opts)},
+ {shadow_vars,
+ bool_option(warn_shadow_vars, nowarn_shadow_vars,
+ true, Opts)},
+ {unused_import,
+ bool_option(warn_unused_import, nowarn_unused_import,
+ false, Opts)},
+ {unused_function,
+ bool_option(warn_unused_function, nowarn_unused_function,
+ true, Opts)},
+ {bif_clash,
+ bool_option(warn_bif_clash, nowarn_bif_clash,
+ true, Opts)},
+ {unused_record,
+ bool_option(warn_unused_record, nowarn_unused_record,
+ true, Opts)},
+ {deprecated_function,
+ bool_option(warn_deprecated_function, nowarn_deprecated_function,
+ true, Opts)},
+ {obsolete_guard,
+ bool_option(warn_obsolete_guard, nowarn_obsolete_guard,
+ true, Opts)},
+ {untyped_record,
+ bool_option(warn_untyped_record, nowarn_untyped_record,
+ false, Opts)},
+ {missing_spec,
+ bool_option(warn_missing_spec, nowarn_missing_spec,
+ false, Opts)},
+ {missing_spec_all,
+ bool_option(warn_missing_spec_all, nowarn_missing_spec_all,
+ false, Opts)}
+ ],
+ Enabled1 = [Category || {Category,true} <- Enabled0],
+ Enabled = ordsets:from_list(Enabled1),
+ Calls = case ordsets:is_element(unused_function, Enabled) of
+ true ->
+ dict:from_list([{{module_info,1},pseudolocals()}]);
+ false ->
+ undefined
+ end,
+ #lint{state = start,
+ exports = gb_sets:from_list([{module_info,0},{module_info,1}]),
+ mod_imports = dict:from_list([{erlang,erlang}]),
+ compile = Opts,
+ %% Internal pseudo-functions must appear as defined/reached.
+ defined = gb_sets:from_list(pseudolocals()),
+ called = [{F,0} || F <- pseudolocals()],
+ usage = #usage{calls=Calls},
+ warn_format = value_option(warn_format, 1, warn_format, 1,
+ nowarn_format, 0, Opts),
+ enabled_warnings = Enabled,
+ file = File,
+ types = default_types()
+ }.
+
+%% is_warn_enabled(Category, St) -> boolean().
+%% Check whether a warning of category Category is enabled.
+is_warn_enabled(Type, #lint{enabled_warnings=Enabled}) ->
+ ordsets:is_element(Type, Enabled).
+
+%% return_status(State) ->
+%% {ok,[Warning]} | {error,[Error],[Warning]}
+%% Pack errors and warnings properly and return ok | error.
+
+return_status(St) ->
+ Ws = pack_warnings(St#lint.warnings),
+ case pack_errors(St#lint.errors) of
+ [] -> {ok,Ws};
+ Es -> {error,Es,Ws}
+ end.
+
+%% pack_errors([{File,ErrD}]) -> [{File,[ErrD]}].
+%% Sort on (reversed) insertion order.
+
+pack_errors(Es) ->
+ {Es1,_} = mapfoldl(fun ({File,E}, I) -> {{File,{I,E}}, I-1} end, -1, Es),
+ map(fun ({File,EIs}) -> {File, map(fun ({_I,E}) -> E end, EIs)} end,
+ pack_warnings(Es1)).
+
+%% pack_warnings([{File,ErrD}]) -> [{File,[ErrD]}]
+%% Sort on line number.
+
+pack_warnings(Ws) ->
+ [{File,lists:sort([W || {F,W} <- Ws, F =:= File])} ||
+ File <- lists:usort([F || {F,_} <- Ws])].
+
+%% add_error(ErrorDescriptor, State) -> State'
+%% add_error(Line, Error, State) -> State'
+%% add_warning(ErrorDescriptor, State) -> State'
+%% add_warning(Line, Error, State) -> State'
+
+add_error(E, St) -> St#lint{errors=[{St#lint.file,E}|St#lint.errors]}.
+
+add_error(FileLine, E, St) ->
+ {File,Location} = loc(FileLine),
+ add_error({Location,erl_lint,E}, St#lint{file = File}).
+
+add_warning(W, St) -> St#lint{warnings=[{St#lint.file,W}|St#lint.warnings]}.
+
+add_warning(FileLine, W, St) ->
+ {File,Location} = loc(FileLine),
+ add_warning({Location,erl_lint,W}, St#lint{file = File}).
+
+loc(L) ->
+ case erl_parse:get_attribute(L, location) of
+ {location,{{File,Line},Column}} ->
+ {File,{Line,Column}};
+ {location,{File,Line}} ->
+ {File,Line}
+ end.
+
+%% forms([Form], State) -> State'
+
+forms(Forms0, St0) ->
+ Forms = eval_file_attribute(Forms0, St0),
+ %% Line numbers are from now on pairs {File,Line}.
+ St1 = includes_qlc_hrl(Forms, St0),
+ St2 = bif_clashes(Forms, St1),
+ St3 = not_deprecated(Forms, St2),
+ St4 = foldl(fun form/2, pre_scan(Forms, St3), Forms),
+ post_traversal_check(Forms, St4).
+
+pre_scan([{function,_L,new,_A,_Cs} | Fs], St) ->
+ pre_scan(Fs, St#lint{new=true});
+pre_scan([{attribute,_L,extends,M} | Fs], St) when is_atom(M) ->
+ pre_scan(Fs, St#lint{extends=true});
+pre_scan([{attribute,L,compile,C} | Fs], St) ->
+ case is_warn_enabled(export_all, St) andalso
+ member(export_all, lists:flatten([C])) of
+ true ->
+ pre_scan(Fs, add_warning(L, export_all, St));
+ false ->
+ pre_scan(Fs, St)
+ end;
+pre_scan([_ | Fs], St) ->
+ pre_scan(Fs, St);
+pre_scan([], St) ->
+ St.
+
+includes_qlc_hrl(Forms, St) ->
+ %% QLC calls erl_lint several times, sometimes with the compile
+ %% attribute removed. The file attribute, however, is left as is.
+ QH = [File || {attribute,_,file,{File,_line}} <- Forms,
+ filename:basename(File) =:= "qlc.hrl"],
+ St#lint{xqlc = QH =/= []}.
+
+eval_file_attribute(Forms, St) ->
+ eval_file_attr(Forms, St#lint.file).
+
+eval_file_attr([{attribute,_L,file,{File,_Line}}=Form | Forms], _File) ->
+ [Form | eval_file_attr(Forms, File)];
+eval_file_attr([Form0 | Forms], File) ->
+ Form = zip_file_and_line(Form0, File),
+ [Form | eval_file_attr(Forms, File)];
+eval_file_attr([], _File) ->
+ [].
+
+zip_file_and_line(T, File) ->
+ F0 = fun(Line) -> {File,Line} end,
+ F = fun(L) -> erl_parse:set_line(L, F0) end,
+ modify_line(T, F).
+
+%% form(Form, State) -> State'
+%% Check a form returning the updated State. Handle generic cases here.
+
+form({error,E}, St) -> add_error(E, St);
+form({warning,W}, St) -> add_warning(W, St);
+form({attribute,_L,file,{File,_Line}}, St) ->
+ St#lint{file = File};
+form({attribute,_L,compile,_}, St) ->
+ St;
+form(Form, #lint{state=State}=St) ->
+ case State of
+ start -> start_state(Form, St);
+ attribute -> attribute_state(Form, St);
+ function -> function_state(Form, St)
+ end.
+
+%% start_state(Form, State) -> State'
+
+start_state({attribute,L,module,{M,Ps}}, St) ->
+ St1 = set_module(M, L, St),
+ Arity = length(Ps),
+ Ps1 = if is_atom(St1#lint.extends) ->
+ ['BASE', 'THIS' | Ps];
+ true ->
+ ['THIS' | Ps]
+ end,
+ Vt = orddict:from_list([{V, {bound, used, []}} || V <- Ps1]),
+ St2 = add_instance(Arity, St1),
+ St3 = ensure_new(Arity, St2),
+ St3#lint{state=attribute, extends=[], global_vt=Vt};
+start_state({attribute,L,module,M}, St) ->
+ St1 = set_module(M, L, St),
+ St1#lint{state=attribute, extends=[]};
+start_state(Form, St) ->
+ St1 = add_error(element(2, Form), undefined_module, St),
+ attribute_state(Form, St1#lint{state=attribute, extends=[]}).
+
+set_module(M, L, St) ->
+ M1 = package_to_string(M),
+ case packages:is_valid(M1) of
+ true ->
+ St#lint{module=list_to_atom(M1),
+ package=packages:strip_last(M1)};
+ false ->
+ add_error(L, {bad_module_name, M1}, St)
+ end.
+
+ensure_new(Arity, St) ->
+ case St#lint.new of
+ true ->
+ St;
+ false ->
+ add_func(new, Arity, St)
+ end.
+
+add_instance(Arity, St) ->
+ A = Arity + (if is_atom(St#lint.extends) -> 1; true -> 0 end),
+ add_func(instance, A, St).
+
+add_func(Name, Arity, St) ->
+ F = {Name, Arity},
+ St#lint{exports = gb_sets:add_element(F, St#lint.exports),
+ defined = gb_sets:add_element(F, St#lint.defined)}.
+
+%% attribute_state(Form, State) ->
+%% State'
+
+attribute_state({attribute,_L,module,_M}, #lint{module=[]}=St) ->
+ St;
+attribute_state({attribute,L,module,_M}, St) ->
+ add_error(L, redefine_module, St);
+attribute_state({attribute,L,extends,M}, #lint{module=M}=St) when is_atom(M) ->
+ add_error(L, extends_self, St);
+attribute_state({attribute,_L,extends,M}, #lint{extends=[]}=St)
+ when is_atom(M) ->
+ St#lint{extends=M};
+attribute_state({attribute,L,extends,M}, St) when is_atom(M) ->
+ add_error(L, redefine_extends, St);
+attribute_state({attribute,L,extends,_M}, St) ->
+ add_error(L, invalid_extends, St);
+attribute_state({attribute,L,export,Es}, St) ->
+ export(L, Es, St);
+attribute_state({attribute,L,import,Is}, St) ->
+ import(L, Is, St);
+attribute_state({attribute,L,record,{Name,Fields}}, St) ->
+ record_def(L, Name, Fields, St);
+attribute_state({attribute,La,behaviour,Behaviour}, St) ->
+ St#lint{behaviour=St#lint.behaviour ++ [{La,Behaviour}]};
+attribute_state({attribute,La,behavior,Behaviour}, St) ->
+ St#lint{behaviour=St#lint.behaviour ++ [{La,Behaviour}]};
+attribute_state({attribute,L,type,{TypeName,TypeDef,Args}}, St) ->
+ type_def(type, L, TypeName, TypeDef, Args, St);
+attribute_state({attribute,L,opaque,{TypeName,TypeDef,Args}}, St) ->
+ type_def(opaque, L, TypeName, TypeDef, Args, St);
+attribute_state({attribute,L,spec,{Fun,Types}}, St) ->
+ spec_decl(L, Fun, Types, St);
+attribute_state({attribute,L,on_load,Val}, St) ->
+ on_load(L, Val, St);
+attribute_state({attribute,_L,_Other,_Val}, St) -> % Ignore others
+ St;
+attribute_state(Form, St) ->
+ function_state(Form, St#lint{state=function}).
+
+%% function_state(Form, State) ->
+%% State'
+%% Allow for record, type and opaque type definitions and spec
+%% declarations to be intersperced within function definitions.
+
+function_state({attribute,L,record,{Name,Fields}}, St) ->
+ record_def(L, Name, Fields, St);
+function_state({attribute,L,type,{TypeName,TypeDef,Args}}, St) ->
+ type_def(type, L, TypeName, TypeDef, Args, St);
+function_state({attribute,L,opaque,{TypeName,TypeDef,Args}}, St) ->
+ type_def(opaque, L, TypeName, TypeDef, Args, St);
+function_state({attribute,L,spec,{Fun,Types}}, St) ->
+ spec_decl(L, Fun, Types, St);
+function_state({attribute,La,Attr,_Val}, St) ->
+ add_error(La, {attribute,Attr}, St);
+function_state({function,L,N,A,Cs}, St) ->
+ function(L, N, A, Cs, St);
+function_state({rule,L,_N,_A,_Cs}, St) ->
+ add_error(L, {mnemosyne,"rule"}, St);
+function_state({eof,L}, St) -> eof(L, St).
+
+%% eof(LastLine, State) ->
+%% State'
+
+eof(_Line, St0) ->
+ St0.
+
+%% bif_clashes(Forms, State0) -> State.
+
+bif_clashes(Forms, St) ->
+ Nowarn = nowarn_function(nowarn_bif_clash, St#lint.compile),
+ Clashes0 = [{Name,Arity} || {function,_L,Name,Arity,_Cs} <- Forms,
+ erl_internal:bif(Name, Arity)],
+ Clashes = ordsets:subtract(ordsets:from_list(Clashes0), Nowarn),
+ St#lint{clashes=Clashes}.
+
+-spec is_bif_clash(atom(), byte(), lint_state()) -> boolean().
+
+is_bif_clash(_Name, _Arity, #lint{clashes=[]}) ->
+ false;
+is_bif_clash(Name, Arity, #lint{clashes=Clashes}) ->
+ ordsets:is_element({Name,Arity}, Clashes).
+
+%% not_deprecated(Forms, State0) -> State
+
+not_deprecated(Forms, St0) ->
+ %% There are no line numbers in St0#lint.compile.
+ MFAsL = [{MFA,L} ||
+ {attribute, L, compile, Args} <- Forms,
+ {nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]),
+ MFA <- lists:flatten([MFAs0])],
+ Nowarn = [MFA || {MFA,_L} <- MFAsL],
+ Bad = [MFAL || {{M,F,A},_L}=MFAL <- MFAsL,
+ otp_internal:obsolete(M, F, A) =:= no],
+ St1 = func_line_warning(bad_nowarn_deprecated_function, Bad, St0),
+ St1#lint{not_deprecated = ordsets:from_list(Nowarn)}.
+
+%% post_traversal_check(Forms, State0) -> State.
+%% Do some further checking after the forms have been traversed and
+%% data about calls etc. have been collected.
+
+post_traversal_check(Forms, St0) ->
+ St1 = check_behaviour(St0),
+ St2 = check_deprecated(Forms, St1),
+ St3 = check_imports(Forms, St2),
+ St4 = check_inlines(Forms, St3),
+ St5 = check_undefined_functions(St4),
+ St6 = check_unused_functions(Forms, St5),
+ St7 = check_bif_clashes(Forms, St6),
+ St8 = check_specs_without_function(St7),
+ St9 = check_functions_without_spec(Forms, St8),
+ StA = check_unused_types(Forms, St9),
+ StB = check_untyped_records(Forms, StA),
+ StC = check_on_load(StB),
+ check_unused_records(Forms, StC).
+
+%% check_behaviour(State0) -> State
+%% Check that the behaviour attribute is valid.
+
+check_behaviour(St0) ->
+ behaviour_check(St0#lint.behaviour, St0).
+
+%% behaviour_check([{Line,Behaviour}], State) -> State'
+%% Check behaviours for existence and defined functions.
+
+behaviour_check(Bs, St0) ->
+ {AllBfs,St1} = all_behaviour_callbacks(Bs, [], St0),
+ St = behaviour_missing_callbacks(AllBfs, St1),
+ behaviour_conflicting(AllBfs, St).
+
+all_behaviour_callbacks([{Line,B}|Bs], Acc, St0) ->
+ {Bfs0,St} = behaviour_callbacks(Line, B, St0),
+ all_behaviour_callbacks(Bs, [{{Line,B},Bfs0}|Acc], St);
+all_behaviour_callbacks([], Acc, St) -> {reverse(Acc),St}.
+
+behaviour_callbacks(Line, B, St0) ->
+ try B:behaviour_info(callbacks) of
+ Funcs when is_list(Funcs) ->
+ All = all(fun({FuncName, Arity}) ->
+ is_atom(FuncName) andalso is_integer(Arity);
+ (_Other) ->
+ false
+ end,
+ Funcs),
+ if
+ All =:= true ->
+ {Funcs, St0};
+ true ->
+ St1 = add_warning(Line,
+ {ill_defined_behaviour_callbacks,B},
+ St0),
+ {[], St1}
+ end;
+ undefined ->
+ St1 = add_warning(Line, {undefined_behaviour_callbacks,B}, St0),
+ {[], St1};
+ _Other ->
+ St1 = add_warning(Line, {ill_defined_behaviour_callbacks,B}, St0),
+ {[], St1}
+ catch
+ _:_ ->
+ St1 = add_warning(Line, {undefined_behaviour,B}, St0),
+ {[], St1}
+ end.
+
+behaviour_missing_callbacks([{{Line,B},Bfs}|T], #lint{exports=Exp}=St0) ->
+ Missing = ordsets:subtract(ordsets:from_list(Bfs), gb_sets:to_list(Exp)),
+ St = foldl(fun (F, S0) ->
+ add_warning(Line, {undefined_behaviour_func,F,B}, S0)
+ end, St0, Missing),
+ behaviour_missing_callbacks(T, St);
+behaviour_missing_callbacks([], St) -> St.
+
+behaviour_conflicting(AllBfs, St) ->
+ R0 = sofs:relation(AllBfs, [{item,[callback]}]),
+ R1 = sofs:family_to_relation(R0),
+ R2 = sofs:converse(R1),
+ R3 = sofs:relation_to_family(R2),
+ R4 = sofs:family_specification(fun(S) -> sofs:no_elements(S) > 1 end, R3),
+ R = sofs:to_external(R4),
+ behaviour_add_conflicts(R, St).
+
+behaviour_add_conflicts([{Cb,[{FirstLoc,FirstB}|Cs]}|T], St0) ->
+ FirstL = element(2, loc(FirstLoc)),
+ St = behaviour_add_conflict(Cs, Cb, FirstL, FirstB, St0),
+ behaviour_add_conflicts(T, St);
+behaviour_add_conflicts([], St) -> St.
+
+behaviour_add_conflict([{Line,B}|Cs], Cb, FirstL, FirstB, St0) ->
+ St = add_warning(Line, {conflicting_behaviours,Cb,B,FirstL,FirstB}, St0),
+ behaviour_add_conflict(Cs, Cb, FirstL, FirstB, St);
+behaviour_add_conflict([], _, _, _, St) -> St.
+
+%% check_deprecated(Forms, State0) -> State
+
+check_deprecated(Forms, St0) ->
+ %% Get the correct list of exported functions.
+ Exports = case member(export_all, St0#lint.compile) of
+ true -> St0#lint.defined;
+ false -> St0#lint.exports
+ end,
+ X = gb_sets:to_list(Exports),
+ #lint{module = Mod} = St0,
+ Bad = [{E,L} || {attribute, L, deprecated, Depr} <- Forms,
+ D <- lists:flatten([Depr]),
+ E <- depr_cat(D, X, Mod)],
+ foldl(fun ({E,L}, St1) ->
+ add_error(L, E, St1)
+ end, St0, Bad).
+
+depr_cat({F, A, Flg}=D, X, Mod) ->
+ case deprecated_flag(Flg) of
+ false -> [{invalid_deprecated,D}];
+ true -> depr_fa(F, A, X, Mod)
+ end;
+depr_cat({F, A}, X, Mod) ->
+ depr_fa(F, A, X, Mod);
+depr_cat(module, _X, _Mod) ->
+ [];
+depr_cat(D, _X, _Mod) ->
+ [{invalid_deprecated,D}].
+
+depr_fa('_', '_', _X, _Mod) ->
+ [];
+depr_fa(F, '_', X, _Mod) when is_atom(F) ->
+ %% Don't use this syntax for built-in functions.
+ case lists:filter(fun({F1,_}) -> F1 =:= F end, X) of
+ [] -> [{bad_deprecated,{F,'_'}}];
+ _ -> []
+ end;
+depr_fa(F, A, X, Mod) when is_atom(F), is_integer(A), A >= 0 ->
+ case lists:member({F,A}, X) of
+ true -> [];
+ false ->
+ case erlang:is_builtin(Mod, F, A) of
+ true -> [];
+ false -> [{bad_deprecated,{F,A}}]
+ end
+ end;
+depr_fa(F, A, _X, _Mod) ->
+ [{invalid_deprecated,{F,A}}].
+
+deprecated_flag(next_version) -> true;
+deprecated_flag(next_major_release) -> true;
+deprecated_flag(eventually) -> true;
+deprecated_flag(_) -> false.
+
+%% check_imports(Forms, State0) -> State
+
+check_imports(Forms, St0) ->
+ case is_warn_enabled(unused_import, St0) of
+ false ->
+ St0;
+ true ->
+ Usage = St0#lint.usage,
+ Unused = ordsets:subtract(St0#lint.imports, Usage#usage.imported),
+ Imports = [{{FA,list_to_atom(package_to_string(Mod))},L}
+ || {attribute,L,import,{Mod,Fs}} <- Forms,
+ FA <- lists:usort(Fs)],
+ Bad = [{FM,L} || FM <- Unused, {FM2,L} <- Imports, FM =:= FM2],
+ func_line_warning(unused_import, Bad, St0)
+ end.
+
+%% check_inlines(Forms, State0) -> State
+
+check_inlines(Forms, St0) ->
+ check_option_functions(Forms, inline, bad_inline, St0).
+
+%% check_unused_functions(Forms, State0) -> State
+
+check_unused_functions(Forms, St0) ->
+ St1 = check_option_functions(Forms, nowarn_unused_function,
+ bad_nowarn_unused_function, St0),
+ Opts = St1#lint.compile,
+ case member(export_all, Opts) orelse
+ not is_warn_enabled(unused_function, St1) of
+ true ->
+ St1;
+ false ->
+ Nowarn = nowarn_function(nowarn_unused_function, Opts),
+ Usage = St1#lint.usage,
+ Used = reached_functions(initially_reached(St1),
+ Usage#usage.calls),
+ UsedOrNowarn = ordsets:union(Used, Nowarn),
+ Unused = ordsets:subtract(gb_sets:to_list(St1#lint.defined),
+ UsedOrNowarn),
+ Functions = [{{N,A},L} || {function,L,N,A,_} <- Forms],
+ Bad = [{FA,L} || FA <- Unused, {FA2,L} <- Functions, FA =:= FA2],
+ func_line_warning(unused_function, Bad, St1)
+ end.
+
+initially_reached(#lint{exports=Exp,on_load=OnLoad}) ->
+ OnLoad ++ gb_sets:to_list(Exp).
+
+%% reached_functions(RootSet, CallRef) -> [ReachedFunc].
+%% reached_functions(RootSet, CallRef, [ReachedFunc]) -> [ReachedFunc].
+
+reached_functions(Root, Ref) ->
+ reached_functions(Root, [], Ref, gb_sets:empty()).
+
+reached_functions([R|Rs], More0, Ref, Reached0) ->
+ case gb_sets:is_element(R, Reached0) of
+ true -> reached_functions(Rs, More0, Ref, Reached0);
+ false ->
+ Reached = gb_sets:add_element(R, Reached0), %It IS reached
+ case dict:find(R, Ref) of
+ {ok,More} -> reached_functions(Rs, [More|More0], Ref, Reached);
+ error -> reached_functions(Rs, More0, Ref, Reached)
+ end
+ end;
+reached_functions([], [_|_]=More, Ref, Reached) ->
+ reached_functions(lists:append(More), [], Ref, Reached);
+reached_functions([], [], _Ref, Reached) -> gb_sets:to_list(Reached).
+
+%% check_undefined_functions(State0) -> State
+
+check_undefined_functions(#lint{called=Called0,defined=Def0}=St0) ->
+ Called = sofs:relation(Called0, [{func,location}]),
+ Def = sofs:from_external(gb_sets:to_list(Def0), [func]),
+ Undef = sofs:to_external(sofs:drestriction(Called, Def)),
+ foldl(fun ({NA,L}, St) ->
+ add_error(L, {undefined_function,NA}, St)
+ end, St0, Undef).
+
+%% check_bif_clashes(Forms, State0) -> State
+
+check_bif_clashes(Forms, St0) ->
+ %% St0#lint.defined is now complete.
+ check_option_functions(Forms, nowarn_bif_clash,
+ bad_nowarn_bif_clash, St0).
+
+check_option_functions(Forms, Tag0, Type, St0) ->
+ %% There are no line numbers in St0#lint.compile.
+ FAsL = [{FA,L} || {attribute, L, compile, Args} <- Forms,
+ {Tag, FAs0} <- lists:flatten([Args]),
+ Tag0 =:= Tag,
+ FA <- lists:flatten([FAs0])],
+ DefFunctions = gb_sets:to_list(St0#lint.defined) -- pseudolocals(),
+ Bad = [{FA,L} || {FA,L} <- FAsL, not member(FA, DefFunctions)],
+ func_line_error(Type, Bad, St0).
+
+nowarn_function(Tag, Opts) ->
+ ordsets:from_list([FA || {Tag1,FAs} <- Opts,
+ Tag1 =:= Tag,
+ FA <- lists:flatten([FAs])]).
+
+func_line_warning(Type, Fs, St) ->
+ foldl(fun ({F,Line}, St0) -> add_warning(Line, {Type,F}, St0) end, St, Fs).
+
+func_line_error(Type, Fs, St) ->
+ foldl(fun ({F,Line}, St0) -> add_error(Line, {Type,F}, St0) end, St, Fs).
+
+check_untyped_records(Forms, St0) ->
+ case is_warn_enabled(untyped_record, St0) of
+ true ->
+ %% One possibility is to use the names of all records
+ %% RecNames = dict:fetch_keys(St0#lint.records),
+ %% but I think it's better to keep those that are used by the file
+ Usage = St0#lint.usage,
+ UsedRecNames = sets:to_list(Usage#usage.used_records),
+ %% these are the records with field(s) containing type info
+ TRecNames = [Name ||
+ {attribute,_,type,{{record,Name},Fields,_}} <- Forms,
+ lists:all(fun ({typed_record_field,_,_}) -> true;
+ (_) -> false
+ end, Fields)],
+ foldl(fun (N, St) ->
+ {L, Fields} = dict:fetch(N, St0#lint.records),
+ case Fields of
+ [] -> St; % exclude records with no fields
+ [_|_] -> add_warning(L, {untyped_record, N}, St)
+ end
+ end, St0, UsedRecNames -- TRecNames);
+ false ->
+ St0
+ end.
+
+check_unused_records(Forms, St0) ->
+ AttrFiles = [File || {attribute,_L,file,{File,_Line}} <- Forms],
+ case {is_warn_enabled(unused_record, St0),AttrFiles} of
+ {true,[FirstFile|_]} ->
+ %% The check is a bit imprecise in that uses from unused
+ %% functions count.
+ Usage = St0#lint.usage,
+ UsedRecords = sets:to_list(Usage#usage.used_records),
+ URecs = foldl(fun (Used, Recs) ->
+ dict:erase(Used, Recs)
+ end, St0#lint.records, UsedRecords),
+ Unused = [{Name,FileLine} ||
+ {Name,{FileLine,_Fields}} <- dict:to_list(URecs),
+ element(1, loc(FileLine)) =:= FirstFile],
+ foldl(fun ({N,L}, St) ->
+ add_warning(L, {unused_record, N}, St)
+ end, St0, Unused);
+ _ ->
+ St0
+ end.
+
+%% For storing the import list we use the orddict module.
+%% We know an empty set is [].
+
+%% export(Line, Exports, State) -> State.
+%% Mark functions as exported, also as called from the export line.
+
+export(Line, Es, #lint{exports = Es0, called = Called} = St0) ->
+ {Es1,C1,St1} =
+ foldl(fun (NA, {E,C,St2}) ->
+ St = case gb_sets:is_element(NA, E) of
+ true ->
+ add_warning(Line, {duplicated_export, NA}, St2);
+ false ->
+ St2
+ end,
+ {gb_sets:add_element(NA, E), [{NA,Line}|C], St}
+ end,
+ {Es0,Called,St0}, Es),
+ St1#lint{exports = Es1, called = C1}.
+
+%% import(Line, Imports, State) -> State.
+%% imported(Name, Arity, State) -> {yes,Module} | no.
+
+import(Line, {Mod,Fs}, St) ->
+ Mod1 = package_to_string(Mod),
+ case packages:is_valid(Mod1) of
+ true ->
+ Mfs = ordsets:from_list(Fs),
+ case check_imports(Line, Mfs, St#lint.imports) of
+ [] ->
+ St#lint{imports=add_imports(list_to_atom(Mod1), Mfs,
+ St#lint.imports)};
+ Efs ->
+ foldl(fun (Ef, St0) ->
+ add_error(Line, {redefine_import,Ef},
+ St0)
+ end,
+ St, Efs)
+ end;
+ false ->
+ add_error(Line, {bad_module_name, Mod1}, St)
+ end;
+import(Line, Mod, St) ->
+ Mod1 = package_to_string(Mod),
+ case packages:is_valid(Mod1) of
+ true ->
+ Key = list_to_atom(packages:last(Mod1)),
+ Imps = St#lint.mod_imports,
+%%% case dict:is_key(Key, Imps) of
+%%% true ->
+%%% M = packages:last(Mod1),
+%%% P = packages:strip_last(Mod1),
+%%% add_error(Line, {redefine_mod_import, M, P}, St);
+%%% false ->
+%%% St#lint{mod_imports =
+%%% dict:store(Key, list_to_atom(Mod1), Imps)}
+%%% end;
+ St#lint{mod_imports = dict:store(Key, list_to_atom(Mod1),
+ Imps)};
+ false ->
+ add_error(Line, {bad_module_name, Mod1}, St)
+ end.
+
+check_imports(_Line, Fs, Is) ->
+ foldl(fun (F, Efs) ->
+ case orddict:find(F, Is) of
+ {ok,Mod} -> [{F,Mod}|Efs];
+ error ->
+ {N,A} = F,
+ case erl_internal:bif(N, A) of
+ true ->
+ [{bif,F,erlang}|Efs];
+ false ->
+ Efs
+ end
+ end end, [], Fs).
+
+add_imports(Mod, Fs, Is) ->
+ foldl(fun (F, Is0) -> orddict:store(F, Mod, Is0) end, Is, Fs).
+
+imported(F, A, St) ->
+ case orddict:find({F,A}, St#lint.imports) of
+ {ok,Mod} -> {yes,Mod};
+ error -> no
+ end.
+
+%% on_load(Line, Val, State) -> State.
+%% Check an on_load directive and remember it.
+
+on_load(Line, {Name,Arity}=Fa, #lint{on_load=OnLoad0}=St0)
+ when is_atom(Name), is_integer(Arity) ->
+ %% Always add the function name (even if there is a problem),
+ %% to avoid irrelevant warnings for unused functions.
+ St = St0#lint{on_load=[Fa|OnLoad0],on_load_line=Line},
+ case St of
+ #lint{on_load=[{_,0}]} ->
+ %% This is the first on_load attribute seen in the module
+ %% and it has the correct arity.
+ St;
+ #lint{on_load=[{_,_}]} ->
+ %% Wrong arity.
+ add_error(Line, {bad_on_load_arity,Fa}, St);
+ #lint{on_load=[_,_|_]} ->
+ %% Multiple on_load attributes.
+ add_error(Line, multiple_on_loads, St)
+ end;
+on_load(Line, Val, St) ->
+ %% Bad syntax.
+ add_error(Line, {bad_on_load,Val}, St).
+
+check_on_load(#lint{defined=Defined,on_load=[{_,0}=Fa],
+ on_load_line=Line}=St) ->
+ case gb_sets:is_member(Fa, Defined) of
+ true -> St;
+ false -> add_error(Line, {undefined_on_load,Fa}, St)
+ end;
+check_on_load(St) -> St.
+
+%% call_function(Line, Name, Arity, State) -> State.
+%% Add to both called and calls.
+
+call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func}=St) ->
+ #usage{calls = Cs} = Usage0,
+ NA = {F,A},
+ Usage = case Cs of
+ undefined -> Usage0;
+ _ -> Usage0#usage{calls=dict:append(Func, NA, Cs)}
+ end,
+ St#lint{called=[{NA,Line}|Cd], usage=Usage}.
+
+%% is_function_exported(Name, Arity, State) -> false|true.
+
+is_function_exported(Name, Arity, #lint{exports=Exports,compile=Compile}) ->
+ gb_sets:is_element({Name,Arity}, Exports) orelse
+ member(export_all, Compile).
+
+%% function(Line, Name, Arity, Clauses, State) -> State.
+
+function(Line, instance, _Arity, _Cs, St) when St#lint.global_vt =/= [] ->
+ add_error(Line, define_instance, St);
+function(Line, Name, Arity, Cs, St0) ->
+ St1 = define_function(Line, Name, Arity, St0#lint{func={Name,Arity}}),
+ clauses(Cs, St1#lint.global_vt, St1).
+
+%% define_function(Line, Name, Arity, State) -> State.
+
+define_function(Line, Name, Arity, St0) ->
+ St1 = keyword_warning(Line, Name, St0),
+ NA = {Name,Arity},
+ case gb_sets:is_member(NA, St1#lint.defined) of
+ true ->
+ add_error(Line, {redefine_function,NA}, St1);
+ false ->
+ St2 = St1#lint{defined=gb_sets:add_element(NA, St1#lint.defined)},
+ St = case erl_internal:bif(Name, Arity) andalso
+ not is_function_exported(Name, Arity, St2) of
+ true -> add_warning(Line, {redefine_bif,NA}, St2);
+ false -> St2
+ end,
+ case imported(Name, Arity, St) of
+ {yes,_M} -> add_error(Line, {define_import,NA}, St);
+ no -> St
+ end
+ end.
+
+%% clauses([Clause], VarTable, State) -> {VarTable, State}.
+
+clauses(Cs, Vt, St) ->
+ foldl(fun (C, St0) ->
+ {_,St1} = clause(C, Vt, St0),
+ St1
+ end, St, Cs).
+
+clause({clause,_Line,H,G,B}, Vt0, St0) ->
+ {Hvt,Binvt,St1} = head(H, Vt0, St0),
+ %% Cannot ignore BinVt since "binsize variables" may have been used.
+ Vt1 = vtupdate(Hvt, vtupdate(Binvt, Vt0)),
+ {Gvt,St2} = guard(G, Vt1, St1),
+ Vt2 = vtupdate(Gvt, Vt1),
+ {Bvt,St3} = exprs(B, Vt2, St2),
+ Upd = vtupdate(Bvt, Vt2),
+ check_unused_vars(Upd, Vt0, St3).
+
+%% head([HeadPattern], VarTable, State) ->
+%% {VarTable,BinVarTable,State}
+%% Check a patterns in head returning "all" variables. Not updating the
+%% known variable list will result in multiple error messages/warnings.
+
+head(Ps, Vt, St0) ->
+ head(Ps, Vt, Vt, St0). % Old = Vt
+
+head([P|Ps], Vt, Old, St0) ->
+ {Pvt,Bvt1,St1} = pattern(P, Vt, Old, [], St0),
+ {Psvt,Bvt2,St2} = head(Ps, Vt, Old, St1),
+ {vtmerge_pat(Pvt, Psvt),vtmerge_pat(Bvt1,Bvt2),St2};
+head([], _Vt, _Env, St) -> {[],[],St}.
+
+%% pattern(Pattern, VarTable, Old, BinVarTable, State) ->
+%% {UpdVarTable,BinVarTable,State}.
+%% Check pattern return variables. Old is the set of variables used for
+%% deciding whether an occurrence is a binding occurrence or a use, and
+%% VarTable is the set of variables used for arguments to binary
+%% patterns. UpdVarTable is updated when same variable in VarTable is
+%% used in the size part of a bit segment. All other information about
+%% used variables are recorded in BinVarTable. The caller can then decide
+%% what to do with it depending on whether variables in the pattern shadow
+%% variabler or not. This separation is one way of dealing with these:
+%% A = 4, fun(<<A:A>>) -> % A #2 unused
+%% A = 4, fun(<<A:8,16:A>>) -> % A #1 unused
+
+pattern(P, Vt, St) ->
+ pattern(P, Vt, Vt, [], St). % Old = Vt
+
+pattern({var,_Line,'_'}, _Vt, _Old, _Bvt, St) ->
+ {[],[],St}; %Ignore anonymous variable
+pattern({var,Line,V}, _Vt, Old, Bvt, St) ->
+ pat_var(V, Line, Old, Bvt, St);
+pattern({char,_Line,_C}, _Vt, _Old, _Bvt, St) -> {[],[],St};
+pattern({integer,_Line,_I}, _Vt, _Old, _Bvt, St) -> {[],[],St};
+pattern({float,_Line,_F}, _Vt, _Old, _Bvt, St) -> {[],[],St};
+pattern({atom,Line,A}, _Vt, _Old, _Bvt, St) ->
+ {[],[],keyword_warning(Line, A, St)};
+pattern({string,_Line,_S}, _Vt, _Old, _Bvt, St) -> {[],[],St};
+pattern({nil,_Line}, _Vt, _Old, _Bvt, St) -> {[],[],St};
+pattern({cons,_Line,H,T}, Vt, Old, Bvt, St0) ->
+ {Hvt,Bvt1,St1} = pattern(H, Vt, Old, Bvt, St0),
+ {Tvt,Bvt2,St2} = pattern(T, Vt, Old, Bvt, St1),
+ {vtmerge_pat(Hvt, Tvt),vtmerge_pat(Bvt1,Bvt2),St2};
+pattern({tuple,_Line,Ps}, Vt, Old, Bvt, St) ->
+ pattern_list(Ps, Vt, Old, Bvt, St);
+%%pattern({struct,_Line,_Tag,Ps}, Vt, Old, Bvt, St) ->
+%% pattern_list(Ps, Vt, Old, Bvt, St);
+pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) ->
+ {Vt1,St1} =
+ check_record(Line, Name, St,
+ fun (Dfs, St1) ->
+ pattern_field(Field, Name, Dfs, St1)
+ end),
+ {Vt1,[],St1};
+pattern({record_field,Line,_,_}=M, _Vt, _Old, _Bvt, St0) ->
+ case expand_package(M, St0) of
+ {error, St1} ->
+ {[],[],add_error(Line, illegal_expr, St1)};
+ {_, St1} ->
+ {[],[],St1}
+ end;
+pattern({record,Line,Name,Pfs}, Vt, Old, Bvt, St) ->
+ case dict:find(Name, St#lint.records) of
+ {ok,{_Line,Fields}} ->
+ St1 = used_record(Name, St),
+ pattern_fields(Pfs, Name, Fields, Vt, Old, Bvt, St1);
+ error -> {[],[],add_error(Line, {undefined_record,Name}, St)}
+ end;
+pattern({bin,_,Fs}, Vt, Old, Bvt, St) ->
+ pattern_bin(Fs, Vt, Old, Bvt, St);
+pattern({op,_Line,'++',{nil,_},R}, Vt, Old, Bvt, St) ->
+ pattern(R, Vt, Old, Bvt, St);
+pattern({op,_Line,'++',{cons,Li,{char,_L2,_C},T},R}, Vt, Old, Bvt, St) ->
+ pattern({op,Li,'++',T,R}, Vt, Old, Bvt, St); %Char unimportant here
+pattern({op,_Line,'++',{cons,Li,{integer,_L2,_I},T},R}, Vt, Old, Bvt, St) ->
+ pattern({op,Li,'++',T,R}, Vt, Old, Bvt, St); %Weird, but compatible!
+pattern({op,_Line,'++',{string,_Li,_S},R}, Vt, Old, Bvt, St) ->
+ pattern(R, Vt, Old, Bvt, St); %String unimportant here
+pattern({match,_Line,Pat1,Pat2}, Vt, Old, Bvt, St0) ->
+ {Lvt,Bvt1,St1} = pattern(Pat1, Vt, Old, Bvt, St0),
+ {Rvt,Bvt2,St2} = pattern(Pat2, Vt, Old, Bvt, St1),
+ St3 = reject_bin_alias(Pat1, Pat2, St2),
+ {vtmerge_pat(Lvt, Rvt),vtmerge_pat(Bvt1,Bvt2),St3};
+%% Catch legal constant expressions, including unary +,-.
+pattern(Pat, _Vt, _Old, _Bvt, St) ->
+ case is_pattern_expr(Pat) of
+ true -> {[],[],St};
+ false -> {[],[],add_error(element(2, Pat), illegal_pattern, St)}
+ end.
+
+pattern_list(Ps, Vt, Old, Bvt0, St) ->
+ foldl(fun (P, {Psvt,Bvt,St0}) ->
+ {Pvt,Bvt1,St1} = pattern(P, Vt, Old, Bvt0, St0),
+ {vtmerge_pat(Pvt, Psvt),vtmerge_pat(Bvt,Bvt1),St1}
+ end, {[],[],St}, Ps).
+
+%% reject_bin_alias(Pat, Expr, St) -> St'
+%% Reject aliases for binary patterns at the top level.
+
+reject_bin_alias_expr({bin,_,_}=P, {match,_,P0,E}, St0) ->
+ St = reject_bin_alias(P, P0, St0),
+ reject_bin_alias_expr(P, E, St);
+reject_bin_alias_expr({match,_,_,_}=P, {match,_,P0,E}, St0) ->
+ St = reject_bin_alias(P, P0, St0),
+ reject_bin_alias_expr(P, E, St);
+reject_bin_alias_expr(_, _, St) -> St.
+
+
+%% reject_bin_alias(Pat1, Pat2, St) -> St'
+%% Aliases of binary patterns, such as <<A:8>> = <<B:4,C:4>> or even
+%% <<A:8>> = <<A:8>>, are not allowed. Traverse the patterns in parallel
+%% and generate an error if any binary aliases are found.
+%% We generate an error even if is obvious that the overall pattern can't
+%% possibly match, for instance, {a,<<A:8>>,c}={x,<<A:8>>} WILL generate an
+%% error.
+
+reject_bin_alias({bin,Line,_}, {bin,_,_}, St) ->
+ add_error(Line, illegal_bin_pattern, St);
+reject_bin_alias({cons,_,H1,T1}, {cons,_,H2,T2}, St0) ->
+ St = reject_bin_alias(H1, H2, St0),
+ reject_bin_alias(T1, T2, St);
+reject_bin_alias({tuple,_,Es1}, {tuple,_,Es2}, St) ->
+ reject_bin_alias_list(Es1, Es2, St);
+reject_bin_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2},
+ #lint{records=Recs}=St) ->
+ case {dict:find(Name1, Recs),dict:find(Name2, Recs)} of
+ {{ok,{_Line1,Fields1}},{ok,{_Line2,Fields2}}} ->
+ reject_bin_alias_rec(Pfs1, Pfs2, Fields1, Fields2, St);
+ {_,_} ->
+ %% One or more non-existing records. (An error messages has
+ %% already been generated, so we are done here.)
+ St
+ end;
+reject_bin_alias({match,_,P1,P2}, P, St0) ->
+ St = reject_bin_alias(P1, P, St0),
+ reject_bin_alias(P2, P, St);
+reject_bin_alias(P, {match,_,_,_}=M, St) ->
+ reject_bin_alias(M, P, St);
+reject_bin_alias(_P1, _P2, St) -> St.
+
+reject_bin_alias_list([E1|Es1], [E2|Es2], St0) ->
+ St = reject_bin_alias(E1, E2, St0),
+ reject_bin_alias_list(Es1, Es2, St);
+reject_bin_alias_list(_, _, St) -> St.
+
+reject_bin_alias_rec(PfsA0, PfsB0, FieldsA0, FieldsB0, St) ->
+ %% We treat records as if they have been converted to tuples.
+ PfsA1 = rbia_field_vars(PfsA0),
+ PfsB1 = rbia_field_vars(PfsB0),
+ FieldsA1 = rbia_fields(lists:reverse(FieldsA0), 0, []),
+ FieldsB1 = rbia_fields(lists:reverse(FieldsB0), 0, []),
+ FieldsA = sofs:relation(FieldsA1),
+ PfsA = sofs:relation(PfsA1),
+ A = sofs:join(FieldsA, 1, PfsA, 1),
+ FieldsB = sofs:relation(FieldsB1),
+ PfsB = sofs:relation(PfsB1),
+ B = sofs:join(FieldsB, 1, PfsB, 1),
+ C = sofs:join(A, 2, B, 2),
+ D = sofs:projection({external,fun({_,_,P1,_,P2}) -> {P1,P2} end}, C),
+ E = sofs:to_external(D),
+ {Ps1,Ps2} = lists:unzip(E),
+ reject_bin_alias_list(Ps1, Ps2, St).
+
+rbia_field_vars(Fs) ->
+ [{Name,Pat} || {record_field,_,{atom,_,Name},Pat} <- Fs].
+
+rbia_fields([{record_field,_,{atom,_,Name},_}|Fs], I, Acc) ->
+ rbia_fields(Fs, I+1, [{Name,I}|Acc]);
+rbia_fields([_|Fs], I, Acc) ->
+ rbia_fields(Fs, I+1, Acc);
+rbia_fields([], _, Acc) -> Acc.
+
+%% is_pattern_expr(Expression) -> boolean().
+%% Test if a general expression is a valid pattern expression.
+
+is_pattern_expr(Expr) ->
+ case is_pattern_expr_1(Expr) of
+ false -> false;
+ true ->
+ %% Expression is syntactically correct - make sure that it
+ %% also can be evaluated.
+ case erl_eval:partial_eval(Expr) of
+ {integer,_,_} -> true;
+ {char,_,_} -> true;
+ {float,_,_} -> true;
+ {atom,_,_} -> true;
+ _ -> false
+ end
+ end.
+
+is_pattern_expr_1({char,_Line,_C}) -> true;
+is_pattern_expr_1({integer,_Line,_I}) -> true;
+is_pattern_expr_1({float,_Line,_F}) -> true;
+is_pattern_expr_1({atom,_Line,_A}) -> true;
+is_pattern_expr_1({tuple,_Line,Es}) ->
+ all(fun is_pattern_expr/1, Es);
+is_pattern_expr_1({nil,_Line}) -> true;
+is_pattern_expr_1({cons,_Line,H,T}) ->
+ case is_pattern_expr_1(H) of
+ true -> is_pattern_expr_1(T);
+ false -> false
+ end;
+is_pattern_expr_1({op,_Line,Op,A}) ->
+ case erl_internal:arith_op(Op, 1) of
+ true -> is_pattern_expr_1(A);
+ false -> false
+ end;
+is_pattern_expr_1({op,_Line,Op,A1,A2}) ->
+ case erl_internal:arith_op(Op, 2) of
+ true -> all(fun is_pattern_expr/1, [A1,A2]);
+ false -> false
+ end;
+is_pattern_expr_1(_Other) -> false.
+
+%% pattern_bin([Element], VarTable, Old, BinVarTable, State) ->
+%% {UpdVarTable,UpdBinVarTable,State}.
+%% Check a pattern group. BinVarTable are used binsize variables.
+
+pattern_bin(Es, Vt, Old, Bvt0, St0) ->
+ {_Sz,Esvt,Bvt,St1} = foldl(fun (E, Acc) ->
+ pattern_element(E, Vt, Old, Acc)
+ end,
+ {0,[],Bvt0,St0}, Es),
+ {Esvt,Bvt,St1}.
+
+pattern_element({bin_element,Line,{string,_,_},Size,Ts}=Be, Vt,
+ Old, {Sz,Esvt,Bvt,St0}=Acc) ->
+ case good_string_size_type(Size, Ts) of
+ true ->
+ pattern_element_1(Be, Vt, Old, Acc);
+ false ->
+ St = add_error(Line, typed_literal_string, St0),
+ {Sz,Esvt,Bvt,St}
+ end;
+pattern_element(Be, Vt, Old, Acc) ->
+ pattern_element_1(Be, Vt, Old, Acc).
+
+pattern_element_1({bin_element,Line,E,Sz0,Ts}, Vt, Old, {Size0,Esvt,Bvt,St0}) ->
+ {Pevt,Bvt1,St1} = pat_bit_expr(E, Old, Bvt, St0),
+ %% vtmerge or vtmerge_pat doesn't matter here
+ {Sz1,Szvt,Bvt2,St2} = pat_bit_size(Sz0, vtmerge(Vt, Esvt), Bvt, St1),
+ {Sz2,Bt,St3} = bit_type(Line, Sz1, Ts, St2),
+ {Sz3,St4} = bit_size_check(Line, Sz2, Bt, St3),
+ Sz4 = case {E,Sz3} of
+ {{string,_,S},all} -> 8*length(S);
+ {_,_} -> Sz3
+ end,
+ {Size1,St5} = add_bit_size(Line, Sz4, Size0, false, St4),
+ {Size1,vtmerge(Szvt,vtmerge(Pevt, Esvt)),
+ vtmerge(Bvt2,vtmerge(Bvt, Bvt1)), St5}.
+
+good_string_size_type(default, default) ->
+ true;
+good_string_size_type(default, Ts) ->
+ lists:any(fun(utf8) -> true;
+ (utf16) -> true;
+ (utf32) -> true;
+ (_) -> false
+ end, Ts);
+good_string_size_type(_, _) -> false.
+
+%% pat_bit_expr(Pattern, OldVarTable, BinVarTable,State) ->
+%% {UpdVarTable,UpdBinVarTable,State}.
+%% Check pattern bit expression, only allow really valid patterns!
+
+pat_bit_expr({var,_,'_'}, _Old, _Bvt, St) -> {[],[],St};
+pat_bit_expr({var,Ln,V}, Old, Bvt, St) -> pat_var(V, Ln, Old, Bvt, St);
+pat_bit_expr({string,_,_}, _Old, _Bvt, St) -> {[],[],St};
+pat_bit_expr({bin,L,_}, _Old, _Bvt, St) ->
+ {[],[],add_error(L, illegal_pattern, St)};
+pat_bit_expr(P, _Old, _Bvt, St) ->
+ case is_pattern_expr(P) of
+ true -> {[],[],St};
+ false -> {[],[],add_error(element(2, P), illegal_pattern, St)}
+ end.
+
+%% pat_bit_size(Size, VarTable, BinVarTable, State) ->
+%% {Value,UpdVarTable,UpdBinVarTable,State}.
+%% Check pattern size expression, only allow really valid sizes!
+
+pat_bit_size(default, _Vt, _Bvt, St) -> {default,[],[],St};
+pat_bit_size({atom,_Line,all}, _Vt, _Bvt, St) -> {all,[],[],St};
+pat_bit_size({var,Lv,V}, Vt0, Bvt0, St0) ->
+ {Vt,Bvt,St1} = pat_binsize_var(V, Lv, Vt0, Bvt0, St0),
+ {unknown,Vt,Bvt,St1};
+pat_bit_size(Size, _Vt, _Bvt, St) ->
+ Line = element(2, Size),
+ case is_pattern_expr(Size) of
+ true ->
+ case erl_eval:partial_eval(Size) of
+ {integer,Line,I} -> {I,[],[],St};
+ _Other -> {unknown,[],[],add_error(Line, illegal_bitsize, St)}
+ end;
+ false -> {unknown,[],[],add_error(Line, illegal_bitsize, St)}
+ end.
+
+%% expr_bin(Line, [Element], VarTable, State, CheckFun) -> {UpdVarTable,State}.
+%% Check an expression group.
+
+expr_bin(Es, Vt, St0, Check) ->
+ {_Sz,Esvt,St1} = foldl(fun (E, Acc) -> bin_element(E, Vt, Acc, Check) end,
+ {0,[],St0}, Es),
+ {Esvt,St1}.
+
+bin_element({bin_element,Line,E,Sz0,Ts}, Vt, {Size0,Esvt,St0}, Check) ->
+ {Vt1,St1} = Check(E, Vt, St0),
+ {Sz1,Vt2,St2} = bit_size(Sz0, Vt, St1, Check),
+ {Sz2,Bt,St3} = bit_type(Line, Sz1, Ts, St2),
+ {Sz3,St4} = bit_size_check(Line, Sz2, Bt, St3),
+ {Size1,St5} = add_bit_size(Line, Sz3, Size0, true, St4),
+ {Size1,vtmerge([Vt2,Vt1,Esvt]),St5}.
+
+bit_size(default, _Vt, St, _Check) -> {default,[],St};
+bit_size({atom,_Line,all}, _Vt, St, _Check) -> {all,[],St};
+bit_size(Size, Vt, St, Check) ->
+ %% Try to safely evaluate Size if constant to get size,
+ %% otherwise just treat it as an expression.
+ case is_gexpr(Size, St#lint.records) of
+ true ->
+ case erl_eval:partial_eval(Size) of
+ {integer,_ILn,I} -> {I,[],St};
+ _Other ->
+ {Evt,St1} = Check(Size, Vt, St),
+ {unknown,Evt,St1}
+ end;
+ false ->
+ {Evt,St1} = Check(Size, Vt, St),
+ {unknown,Evt,St1}
+ end.
+
+%% bit_type(Line, Size, TypeList, State) -> {Size,#bittype,St}.
+%% Perform warning check on type and size.
+
+bit_type(Line, Size0, Type, St) ->
+ case erl_bits:set_bit_type(Size0, Type) of
+ {ok,Size1,Bt} -> {Size1,Bt,St};
+ {error,What} ->
+ %% Flag error and generate a default.
+ {ok,Size1,Bt} = erl_bits:set_bit_type(default, []),
+ {Size1,Bt,add_error(Line, What, St)}
+ end.
+
+%% bit_size_check(Line, Size, BitType, State) -> {BitSize,State}.
+%% Do some checking & warnings on types
+%% float == 32 or 64
+
+bit_size_check(_Line, unknown, _, St) -> {unknown,St};
+bit_size_check(_Line, undefined, #bittype{type=Type}, St) ->
+ true = (Type =:= utf8) or (Type =:= utf16) or (Type =:= utf32), %Assertion.
+ {undefined,St};
+bit_size_check(Line, all, #bittype{type=Type}, St) ->
+ case Type of
+ binary -> {all,St};
+ _ -> {unknown,add_error(Line, illegal_bitsize, St)}
+ end;
+bit_size_check(Line, Size, #bittype{type=Type,unit=Unit}, St) ->
+ Sz = Unit * Size, %Total number of bits!
+ St2 = elemtype_check(Line, Type, Sz, St),
+ {Sz,St2}.
+
+elemtype_check(_Line, float, 32, St) -> St;
+elemtype_check(_Line, float, 64, St) -> St;
+elemtype_check(Line, float, _Size, St) ->
+ add_warning(Line, {bad_bitsize,"float"}, St);
+elemtype_check(_Line, _Type, _Size, St) -> St.
+
+
+%% add_bit_size(Line, ElementSize, BinSize, Build, State) -> {Size,State}.
+%% Add bits to group size.
+
+add_bit_size(Line, _Sz1, all, false, St) ->
+ {all,add_error(Line, unsized_binary_not_at_end, St)};
+add_bit_size(_Line, _Sz1, all, true, St) ->
+ {all,St};
+add_bit_size(_Line, all, _Sz2, _B, St) -> {all,St};
+add_bit_size(_Line, undefined, _Sz2, _B, St) -> {undefined,St};
+add_bit_size(_Line, unknown, _Sz2, _B, St) -> {unknown,St};
+add_bit_size(_Line, _Sz1, undefined, _B, St) -> {unknown,St};
+add_bit_size(_Line, _Sz1, unknown, _B, St) -> {unknown,St};
+add_bit_size(_Line, Sz1, Sz2, _B, St) -> {Sz1 + Sz2,St}.
+
+%% guard([GuardTest], VarTable, State) ->
+%% {UsedVarTable,State}
+%% Check a guard, return all variables.
+
+%% Disjunction of guard conjunctions
+guard([L|R], Vt, St0) when is_list(L) ->
+ {Gvt, St1} = guard_tests(L, Vt, St0),
+ {Gsvt, St2} = guard(R, vtupdate(Gvt, Vt), St1),
+ {vtupdate(Gvt, Gsvt),St2};
+guard(L, Vt, St0) ->
+ guard_tests(L, Vt, St0).
+
+%% guard conjunction
+guard_tests([G|Gs], Vt, St0) ->
+ {Gvt,St1} = guard_test(G, Vt, St0),
+ {Gsvt,St2} = guard_tests(Gs, vtupdate(Gvt, Vt), St1),
+ {vtupdate(Gvt, Gsvt),St2};
+guard_tests([], _Vt, St) -> {[],St}.
+
+%% guard_test(Test, VarTable, State) ->
+%% {UsedVarTable,State'}
+%% Check one guard test, returns NewVariables. We now allow more
+%% expressions in guards including the new is_XXX type tests, but
+%% only allow the old type tests at the top level.
+
+guard_test(G, Vt, St0) ->
+ St1 = obsolete_guard(G, St0),
+ guard_test2(G, Vt, St1).
+
+%% Specially handle record type test here.
+guard_test2({call,Line,{atom,Lr,record},[E,A]}, Vt, St0) ->
+ gexpr({call,Line,{atom,Lr,is_record},[E,A]}, Vt, St0);
+guard_test2({call,_Line,{atom,_La,F},As}=G, Vt, St0) ->
+ {Asvt,St1} = gexpr_list(As, Vt, St0), %Always check this.
+ A = length(As),
+ case erl_internal:type_test(F, A) of
+ true when F =/= is_record -> {Asvt,St1};
+ _ -> gexpr(G, Vt, St0)
+ end;
+guard_test2(G, Vt, St) ->
+ %% Everything else is a guard expression.
+ gexpr(G, Vt, St).
+
+%% gexpr(GuardExpression, VarTable, State) ->
+%% {UsedVarTable,State'}
+%% Check a guard expression, returns NewVariables.
+
+gexpr({var,Line,V}, Vt, St) ->
+ expr_var(V, Line, Vt, St);
+gexpr({char,_Line,_C}, _Vt, St) -> {[],St};
+gexpr({integer,_Line,_I}, _Vt, St) -> {[],St};
+gexpr({float,_Line,_F}, _Vt, St) -> {[],St};
+gexpr({atom,Line,A}, _Vt, St) ->
+ {[],keyword_warning(Line, A, St)};
+gexpr({string,_Line,_S}, _Vt, St) -> {[],St};
+gexpr({nil,_Line}, _Vt, St) -> {[],St};
+gexpr({cons,_Line,H,T}, Vt, St) ->
+ gexpr_list([H,T], Vt, St);
+gexpr({tuple,_Line,Es}, Vt, St) ->
+ gexpr_list(Es, Vt, St);
+%%gexpr({struct,_Line,_Tag,Es}, Vt, St) ->
+%% gexpr_list(Es, Vt, St);
+gexpr({record_index,Line,Name,Field}, _Vt, St) ->
+ check_record(Line, Name, St,
+ fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end );
+gexpr({record_field,Line,_,_}=M, _Vt, St0) ->
+ case expand_package(M, St0) of
+ {error, St1} ->
+ {[],add_error(Line, illegal_expr, St1)};
+ {_, St1} ->
+ {[], St1}
+ end;
+gexpr({record_field,Line,Rec,Name,Field}, Vt, St0) ->
+ {Rvt,St1} = gexpr(Rec, Vt, St0),
+ {Fvt,St2} = check_record(Line, Name, St1,
+ fun (Dfs, St) ->
+ record_field(Field, Name, Dfs, St)
+ end),
+ {vtmerge(Rvt, Fvt),St2};
+gexpr({record,Line,Name,Inits}, Vt, St) ->
+ check_record(Line, Name, St,
+ fun (Dfs, St1) ->
+ ginit_fields(Inits, Line, Name, Dfs, Vt, St1)
+ end);
+gexpr({bin,_Line,Fs}, Vt,St) ->
+ expr_bin(Fs, Vt, St, fun gexpr/3);
+gexpr({call,_Line,{atom,_Lr,is_record},[E,{atom,Ln,Name}]}, Vt, St0) ->
+ {Rvt,St1} = gexpr(E, Vt, St0),
+ {Rvt,exist_record(Ln, Name, St1)};
+gexpr({call,Line,{atom,_Lr,is_record},[E,R]}, Vt, St0) ->
+ {Asvt,St1} = gexpr_list([E,R], Vt, St0),
+ {Asvt,add_error(Line, illegal_guard_expr, St1)};
+gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]},
+ Vt, St0) ->
+ gexpr({call,Line,{atom,Lf,is_record},[E,A]}, Vt, St0);
+gexpr({call,_Line,{atom,_Lr,is_record},[E,{atom,_,_Name},{integer,_,_}]},
+ Vt, St0) ->
+ gexpr(E, Vt, St0);
+gexpr({call,Line,{atom,_Lr,is_record},[_,_,_]=Asvt0}, Vt, St0) ->
+ {Asvt,St1} = gexpr_list(Asvt0, Vt, St0),
+ {Asvt,add_error(Line, illegal_guard_expr, St1)};
+gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}=Isr},[_,_,_]=Args},
+ Vt, St0) ->
+ gexpr({call,Line,Isr,Args}, Vt, St0);
+gexpr({call,Line,{atom,_La,F},As}, Vt, St0) ->
+ {Asvt,St1} = gexpr_list(As, Vt, St0),
+ A = length(As),
+ case erl_internal:guard_bif(F, A) of
+ true ->
+ %% Also check that it is auto-imported.
+ case erl_internal:bif(F, A) of
+ true -> {Asvt,St1};
+ false -> {Asvt,add_error(Line, {explicit_export,F,A}, St1)}
+ end;
+ false -> {Asvt,add_error(Line, illegal_guard_expr, St1)}
+ end;
+gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, Vt, St0) ->
+ {Asvt,St1} = gexpr_list(As, Vt, St0),
+ A = length(As),
+ case erl_internal:guard_bif(F, A) orelse is_gexpr_op(F, A) of
+ true -> {Asvt,St1};
+ false -> {Asvt,add_error(Line, illegal_guard_expr, St1)}
+ end;
+gexpr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,F}]},As}, Vt, St) ->
+ gexpr({call,L,{remote,Lt,{atom,Lm,erlang},{atom,Lf,F}},As}, Vt, St);
+gexpr({op,Line,Op,A}, Vt, St0) ->
+ {Avt,St1} = gexpr(A, Vt, St0),
+ case is_gexpr_op(Op, 1) of
+ true -> {Avt,St1};
+ false -> {Avt,add_error(Line, illegal_guard_expr, St1)}
+ end;
+gexpr({op,Line,Op,L,R}, Vt, St0) ->
+ {Avt,St1} = gexpr_list([L,R], Vt, St0),
+ case is_gexpr_op(Op, 2) of
+ true -> {Avt,St1};
+ false -> {Avt,add_error(Line, illegal_guard_expr, St1)}
+ end;
+%% Everything else is illegal! You could put explicit tests here to
+%% better error diagnostics.
+gexpr(E, _Vt, St) ->
+ {[],add_error(element(2, E), illegal_guard_expr, St)}.
+
+%% gexpr_list(Expressions, VarTable, State) ->
+%% {UsedVarTable,State'}
+
+gexpr_list(Es, Vt, St) ->
+ foldl(fun (E, {Esvt,St0}) ->
+ {Evt,St1} = gexpr(E, Vt, St0),
+ {vtmerge(Evt, Esvt),St1}
+ end, {[],St}, Es).
+
+%% is_guard_test(Expression) -> boolean().
+%% Test if a general expression is a guard test.
+is_guard_test(E) ->
+ is_guard_test2(E, dict:new()).
+
+%% is_guard_test(Expression, Forms) -> boolean().
+is_guard_test(Expression, Forms) ->
+ RecordAttributes = [A || A = {attribute, _, record, _D} <- Forms],
+ St0 = foldl(fun(Attr0, St1) ->
+ Attr = zip_file_and_line(Attr0, "none"),
+ attribute_state(Attr, St1)
+ end, start(), RecordAttributes),
+ is_guard_test2(zip_file_and_line(Expression, "nofile"), St0#lint.records).
+
+%% is_guard_test2(Expression, RecordDefs :: dict()) -> boolean().
+is_guard_test2({call,Line,{atom,Lr,record},[E,A]}, RDs) ->
+ is_gexpr({call,Line,{atom,Lr,is_record},[E,A]}, RDs);
+is_guard_test2({call,_Line,{atom,_La,Test},As}=Call, RDs) ->
+ case erl_internal:type_test(Test, length(As)) of
+ true -> is_gexpr_list(As, RDs);
+ false -> is_gexpr(Call, RDs)
+ end;
+is_guard_test2(G, RDs) ->
+ %%Everything else is a guard expression.
+ is_gexpr(G, RDs).
+
+%% is_guard_expr(Expression) -> boolean().
+%% Test if an expression is a guard expression.
+
+is_guard_expr(E) -> is_gexpr(E, []).
+
+is_gexpr({var,_L,_V}, _RDs) -> true;
+is_gexpr({char,_L,_C}, _RDs) -> true;
+is_gexpr({integer,_L,_I}, _RDs) -> true;
+is_gexpr({float,_L,_F}, _RDs) -> true;
+is_gexpr({atom,_L,_A}, _RDs) -> true;
+is_gexpr({string,_L,_S}, _RDs) -> true;
+is_gexpr({nil,_L}, _RDs) -> true;
+is_gexpr({cons,_L,H,T}, RDs) -> is_gexpr_list([H,T], RDs);
+is_gexpr({tuple,_L,Es}, RDs) -> is_gexpr_list(Es, RDs);
+%%is_gexpr({struct,_L,_Tag,Es}, RDs) ->
+%% is_gexpr_list(Es, RDs);
+is_gexpr({record_index,_L,_Name,Field}, RDs) ->
+ is_gexpr(Field, RDs);
+is_gexpr({record_field,_L,_,_}=M, _RDs) ->
+ erl_parse:package_segments(M) =/= error;
+is_gexpr({record_field,_L,Rec,_Name,Field}, RDs) ->
+ is_gexpr_list([Rec,Field], RDs);
+is_gexpr({record,L,Name,Inits}, RDs) ->
+ is_gexpr_fields(Inits, L, Name, RDs);
+is_gexpr({bin,_L,Fs}, RDs) ->
+ all(fun ({bin_element,_Line,E,Sz,_Ts}) ->
+ is_gexpr(E, RDs) and (Sz =:= default orelse is_gexpr(Sz, RDs))
+ end, Fs);
+is_gexpr({call,_L,{atom,_Lf,F},As}, RDs) ->
+ A = length(As),
+ case erl_internal:guard_bif(F, A) of
+ true -> is_gexpr_list(As, RDs);
+ false -> false
+ end;
+is_gexpr({call,_L,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, RDs) ->
+ A = length(As),
+ case erl_internal:guard_bif(F, A) orelse is_gexpr_op(F, A) of
+ true -> is_gexpr_list(As, RDs);
+ false -> false
+ end;
+is_gexpr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,F}]},As}, RDs) ->
+ is_gexpr({call,L,{remote,Lt,{atom,Lm,erlang},{atom,Lf,F}},As}, RDs);
+is_gexpr({op,_L,Op,A}, RDs) ->
+ case is_gexpr_op(Op, 1) of
+ true -> is_gexpr(A, RDs);
+ false -> false
+ end;
+is_gexpr({op,_L,Op,A1,A2}, RDs) ->
+ case is_gexpr_op(Op, 2) of
+ true -> is_gexpr_list([A1,A2], RDs);
+ false -> false
+ end;
+is_gexpr(_Other, _RDs) -> false.
+
+is_gexpr_op('andalso', 2) -> true;
+is_gexpr_op('orelse', 2) -> true;
+is_gexpr_op(Op, A) ->
+ try erl_internal:op_type(Op, A) of
+ arith -> true;
+ bool -> true;
+ comp -> true;
+ list -> false;
+ send -> false
+ catch _:_ -> false
+ end.
+
+is_gexpr_list(Es, RDs) -> all(fun (E) -> is_gexpr(E, RDs) end, Es).
+
+is_gexpr_fields(Fs, L, Name, RDs) ->
+ IFs = case dict:find(Name, RDs) of
+ {ok,{_Line,Fields}} -> Fs ++ init_fields(Fs, L, Fields);
+ error -> Fs
+ end,
+ all(fun ({record_field,_Lf,_Name,V}) -> is_gexpr(V, RDs);
+ (_Other) -> false end, IFs).
+
+%% exprs(Sequence, VarTable, State) ->
+%% {UsedVarTable,State'}
+%% Check a sequence of expressions, return all variables.
+
+exprs([E|Es], Vt, St0) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ {Esvt,St2} = exprs(Es, vtupdate(Evt, Vt), St1),
+ {vtupdate(Evt, Esvt),St2};
+exprs([], _Vt, St) -> {[],St}.
+
+%% expr(Expression, VarTable, State) ->
+%% {UsedVarTable,State'}
+%% Check an expression, returns NewVariables. Assume naive users and
+%% mark illegally exported variables, e.g. from catch, as unsafe to better
+%% show why unbound.
+
+expr({var,Line,V}, Vt, St) ->
+ expr_var(V, Line, Vt, St);
+expr({char,_Line,_C}, _Vt, St) -> {[],St};
+expr({integer,_Line,_I}, _Vt, St) -> {[],St};
+expr({float,_Line,_F}, _Vt, St) -> {[],St};
+expr({atom,Line,A}, _Vt, St) ->
+ {[],keyword_warning(Line, A, St)};
+expr({string,_Line,_S}, _Vt, St) -> {[],St};
+expr({nil,_Line}, _Vt, St) -> {[],St};
+expr({cons,_Line,H,T}, Vt, St) ->
+ expr_list([H,T], Vt, St);
+expr({lc,_Line,E,Qs}, Vt0, St0) ->
+ {Vt,St} = handle_comprehension(E, Qs, Vt0, St0),
+ {vtold(Vt, Vt0),St}; %Don't export local variables
+expr({bc,_Line,E,Qs}, Vt0, St0) ->
+ {Vt,St} = handle_comprehension(E, Qs, Vt0, St0),
+ {vtold(Vt,Vt0),St}; %Don't export local variables
+expr({tuple,_Line,Es}, Vt, St) ->
+ expr_list(Es, Vt, St);
+%%expr({struct,Line,Tag,Es}, Vt, St) ->
+%% expr_list(Es, Vt, St);
+expr({record_index,Line,Name,Field}, _Vt, St) ->
+ check_record(Line, Name, St,
+ fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end);
+expr({record,Line,Name,Inits}, Vt, St) ->
+ check_record(Line, Name, St,
+ fun (Dfs, St1) ->
+ init_fields(Inits, Line, Name, Dfs, Vt, St1)
+ end);
+expr({record_field,Line,_,_}=M, _Vt, St0) ->
+ case expand_package(M, St0) of
+ {error, St1} ->
+ {[],add_error(Line, illegal_expr, St1)};
+ {_, St1} ->
+ {[], St1}
+ end;
+expr({record_field,Line,Rec,Name,Field}, Vt, St0) ->
+ {Rvt,St1} = record_expr(Line, Rec, Vt, St0),
+ {Fvt,St2} = check_record(Line, Name, St1,
+ fun (Dfs, St) ->
+ record_field(Field, Name, Dfs, St)
+ end),
+ {vtmerge(Rvt, Fvt),St2};
+expr({record,Line,Rec,Name,Upds}, Vt, St0) ->
+ {Rvt,St1} = record_expr(Line, Rec, Vt, St0),
+ {Usvt,St2} = check_record(Line, Name, St1,
+ fun (Dfs, St) ->
+ update_fields(Upds, Name, Dfs, Vt, St)
+ end ),
+ case has_wildcard_field(Upds) of
+ true -> {[],add_error(Line, {wildcard_in_update,Name}, St2)};
+ false -> {vtmerge(Rvt, Usvt),St2}
+ end;
+expr({bin,_Line,Fs}, Vt, St) ->
+ expr_bin(Fs, Vt, St, fun expr/3);
+expr({block,_Line,Es}, Vt, St) ->
+ %% Unfold block into a sequence.
+ exprs(Es, Vt, St);
+expr({'if',Line,Cs}, Vt, St) ->
+ icrt_clauses(Cs, {'if',Line}, Vt, St);
+expr({'case',Line,E,Cs}, Vt, St0) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ {Cvt,St2} = icrt_clauses(Cs, {'case',Line}, vtupdate(Evt, Vt), St1),
+ {vtmerge(Evt, Cvt),St2};
+expr({'cond',Line,Cs}, Vt, St) ->
+ cond_clauses(Cs,{'cond',Line}, Vt, St);
+expr({'receive',Line,Cs}, Vt, St) ->
+ icrt_clauses(Cs, {'receive',Line}, Vt, St);
+expr({'receive',Line,Cs,To,ToEs}, Vt, St0) ->
+ %% Are variables from the timeout expression visible in the clauses? NO!
+ {Tvt,St1} = expr(To, Vt, St0),
+ {Tevt,St2} = exprs(ToEs, Vt, St1),
+ {Cvt,St3} = icrt_clauses(Cs, Vt, St2),
+ %% Csvts = [vtnew(Tevt, Vt)|Cvt], %This is just NEW variables!
+ Csvts = [Tevt|Cvt],
+ {Rvt,St4} = icrt_export(Csvts, Vt, {'receive',Line}, St3),
+ {vtmerge([Tvt,Tevt,Rvt]),St4};
+expr({'fun',Line,Body}, Vt, St) ->
+ %%No one can think funs export!
+ case Body of
+ {clauses,Cs} ->
+ {Bvt, St1} = fun_clauses(Cs, Vt, St),
+ {vtupdate(Bvt, Vt), St1};
+ {function,F,A} ->
+ %% N.B. Only allows BIFs here as well, NO IMPORTS!!
+ case erl_internal:bif(F, A) of
+ true -> {[],St};
+ false -> {[],call_function(Line, F, A, St)}
+ end;
+ {function,_M,_F,_A} ->
+ {[],St}
+ end;
+expr({call,_Line,{atom,_Lr,is_record},[E,{atom,Ln,Name}]}, Vt, St0) ->
+ {Rvt,St1} = expr(E, Vt, St0),
+ {Rvt,exist_record(Ln, Name, St1)};
+expr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]},
+ Vt, St0) ->
+ expr({call,Line,{atom,Lf,is_record},[E,A]}, Vt, St0);
+expr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,is_record}]},As}, Vt, St) ->
+ expr({call,L,{remote,Lt,{atom,Lm,erlang},{atom,Lf,is_record}},As}, Vt, St);
+expr({call,Line,{remote,_Lr,M,F},As}, Vt, St0) ->
+ case expand_package(M, St0) of
+ {error, _} ->
+ expr_list([M,F|As], Vt, St0);
+ {{atom,_La,M1}, St1} ->
+ case F of
+ {atom,Lf,F1} ->
+ St2 = keyword_warning(Lf, F1, St1),
+ St3 = check_remote_function(Line, M1, F1, As, St2),
+ expr_list(As, Vt, St3);
+ _ ->
+ expr_list([F|As], Vt, St1)
+ end
+ end;
+expr({call,Line,{atom,La,F},As}, Vt, St0) ->
+ St1 = keyword_warning(La, F, St0),
+ {Asvt,St2} = expr_list(As, Vt, St1),
+ A = length(As),
+ case erl_internal:bif(F, A) of
+ true ->
+ St3 = deprecated_function(Line, erlang, F, As, St2),
+ {Asvt,case is_warn_enabled(bif_clash, St3) andalso
+ is_bif_clash(F, A, St3) of
+ false ->
+ St3;
+ true ->
+ add_error(Line, {call_to_redefined_bif,{F,A}}, St3)
+ end};
+ false ->
+ {Asvt,case imported(F, A, St2) of
+ {yes,M} ->
+ St3 = check_remote_function(Line, M, F, As, St2),
+ U0 = St3#lint.usage,
+ Imp = ordsets:add_element({{F,A},M},U0#usage.imported),
+ St3#lint{usage=U0#usage{imported = Imp}};
+ no ->
+ case {F,A} of
+ {record_info,2} ->
+ check_record_info_call(Line,La,As,St2);
+ N when N =:= St2#lint.func -> St2;
+ _ -> call_function(Line, F, A, St2)
+ end
+ end}
+ end;
+expr({call,Line,{record_field,_,_,_}=F,As}, Vt, St0) ->
+ case expand_package(F, St0) of
+ {error, _} ->
+ expr_list([F|As], Vt, St0);
+ {A, St1} ->
+ expr({call,Line,A,As}, Vt, St1)
+ end;
+expr({call,Line,F,As}, Vt, St0) ->
+ St = warn_invalid_call(Line,F,St0),
+ expr_list([F|As], Vt, St); %They see the same variables
+expr({'try',Line,Es,Scs,Ccs,As}, Vt, St0) ->
+ %% Currently, we don't allow any exports because later
+ %% passes cannot handle exports in combination with 'after'.
+ {Evt0,St1} = exprs(Es, Vt, St0),
+ TryLine = {'try',Line},
+ Uvt = vtunsafe(vtnames(vtnew(Evt0, Vt)), TryLine, []),
+ Evt1 = vtupdate(Uvt, vtupdate(Evt0, Vt)),
+ {Sccs,St2} = icrt_clauses(Scs++Ccs, TryLine, Evt1, St1),
+ Rvt0 = Sccs,
+ Rvt1 = vtupdate(vtunsafe(vtnames(vtnew(Rvt0, Vt)), TryLine, []), Rvt0),
+ Evt2 = vtmerge(Evt1, Rvt1),
+ {Avt0,St} = exprs(As, Evt2, St2),
+ Avt1 = vtupdate(vtunsafe(vtnames(vtnew(Avt0, Vt)), TryLine, []), Avt0),
+ Avt = vtmerge(Evt2, Avt1),
+ {Avt,St};
+expr({'catch',Line,E}, Vt, St0) ->
+ %% No new variables added, flag new variables as unsafe.
+ {Evt,St1} = expr(E, Vt, St0),
+ Uvt = vtunsafe(vtnames(vtnew(Evt, Vt)), {'catch',Line}, []),
+ {vtupdate(Uvt,vtupdate(Evt, Vt)),St1};
+expr({match,_Line,P,E}, Vt, St0) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ {Pvt,Bvt,St2} = pattern(P, vtupdate(Evt, Vt), St1),
+ St = reject_bin_alias_expr(P, E, St2),
+ {vtupdate(Bvt, vtmerge(Evt, Pvt)),St};
+%% No comparison or boolean operators yet.
+expr({op,_Line,_Op,A}, Vt, St) ->
+ expr(A, Vt, St);
+expr({op,Line,Op,L,R}, Vt, St0) when Op =:= 'orelse'; Op =:= 'andalso' ->
+ {Evt1,St1} = expr(L, Vt, St0),
+ Vt1 = vtupdate(Evt1, Vt),
+ {Evt2,St2} = expr(R, Vt1, St1),
+ Vt2 = vtmerge(Evt2, Vt1),
+ {Vt3,St3} = icrt_export([Vt1,Vt2], Vt1, {Op,Line}, St2),
+ {vtmerge(Evt1, Vt3),St3};
+expr({op,_Line,_Op,L,R}, Vt, St) ->
+ expr_list([L,R], Vt, St); %They see the same variables
+%% The following are not allowed to occur anywhere!
+expr({remote,Line,_M,_F}, _Vt, St) ->
+ {[],add_error(Line, illegal_expr, St)};
+expr({'query',Line,_Q}, _Vt, St) ->
+ {[],add_error(Line, {mnemosyne,"query"}, St)}.
+
+%% expr_list(Expressions, Variables, State) ->
+%% {UsedVarTable,State}
+
+expr_list(Es, Vt, St) ->
+ foldl(fun (E, {Esvt,St0}) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ {vtmerge(Evt, Esvt),St1}
+ end, {[],St}, Es).
+
+record_expr(Line, Rec, Vt, St0) ->
+ St1 = warn_invalid_record(Line, Rec, St0),
+ expr(Rec, Vt, St1).
+
+%% warn_invalid_record(Line, Record, State0) -> State
+%% Adds warning if the record is invalid.
+
+warn_invalid_record(Line, R, St) ->
+ case is_valid_record(R) of
+ true -> St;
+ false -> add_warning(Line, invalid_record, St)
+ end.
+
+%% is_valid_record(Record) -> boolean().
+
+is_valid_record(Rec) ->
+ case Rec of
+ {char, _, _} -> false;
+ {integer, _, _} -> false;
+ {float, _, _} -> false;
+ {atom, _, _} -> false;
+ {string, _, _} -> false;
+ {cons, _, _, _} -> false;
+ {nil, _} -> false;
+ {lc, _, _, _} -> false;
+ {record_index, _, _, _} -> false;
+ {'fun', _, _} -> false;
+ _ -> true
+ end.
+
+%% warn_invalid_call(Line, Call, State0) -> State
+%% Adds warning if the call is invalid.
+
+warn_invalid_call(Line, F, St) ->
+ case is_valid_call(F) of
+ true -> St;
+ false -> add_warning(Line, invalid_call, St)
+ end.
+
+%% is_valid_call(Call) -> boolean().
+
+is_valid_call(Call) ->
+ case Call of
+ {char, _, _} -> false;
+ {integer, _, _} -> false;
+ {float, _, _} -> false;
+ {string, _, _} -> false;
+ {cons, _, _, _} -> false;
+ {nil, _} -> false;
+ {lc, _, _, _} -> false;
+ {record_index, _, _, _} -> false;
+ {tuple, _, Exprs} when length(Exprs) =/= 2 -> false;
+ _ -> true
+ end.
+
+%% record_def(Line, RecordName, [RecField], State) -> State.
+%% Add a record definition if it does not already exist. Normalise
+%% so that all fields have explicit initial value.
+
+record_def(Line, Name, Fs0, St0) ->
+ case dict:is_key(Name, St0#lint.records) of
+ true -> add_error(Line, {redefine_record,Name}, St0);
+ false ->
+ {Fs1,St1} = def_fields(normalise_fields(Fs0), Name, St0),
+ St1#lint{records=dict:store(Name, {Line,Fs1}, St1#lint.records)}
+ end.
+
+%% def_fields([RecDef], RecordName, State) -> {[DefField],State}.
+%% Check (normalised) fields for duplicates. Return unduplicated
+%% record and set State.
+
+def_fields(Fs0, Name, St0) ->
+ foldl(fun ({record_field,Lf,{atom,La,F},V}, {Fs,St}) ->
+ case exist_field(F, Fs) of
+ true -> {Fs,add_error(Lf, {redefine_field,Name,F}, St)};
+ false ->
+ St1 = St#lint{recdef_top = true},
+ {_,St2} = expr(V, [], St1),
+ %% Warnings and errors found are kept, but
+ %% updated calls, records, etc. are discarded.
+ St3 = St1#lint{warnings = St2#lint.warnings,
+ errors = St2#lint.errors,
+ called = St2#lint.called,
+ recdef_top = false},
+ %% This is one way of avoiding a loop for
+ %% "recursive" definitions.
+ NV = case St2#lint.errors =:= St1#lint.errors of
+ true -> V;
+ false -> {atom,La,undefined}
+ end,
+ {[{record_field,Lf,{atom,La,F},NV}|Fs],St3}
+ end
+ end, {[],St0}, Fs0).
+
+%% normalise_fields([RecDef]) -> [Field].
+%% Normalise the field definitions to always have a default value. If
+%% none has been given then use 'undefined'.
+%% Also, strip type information from typed record fields.
+
+normalise_fields(Fs) ->
+ map(fun ({record_field,Lf,Field}) ->
+ {record_field,Lf,Field,{atom,Lf,undefined}};
+ ({typed_record_field,{record_field,Lf,Field},_Type}) ->
+ {record_field,Lf,Field,{atom,Lf,undefined}};
+ ({typed_record_field,Field,_Type}) ->
+ Field;
+ (F) -> F end, Fs).
+
+%% exist_record(Line, RecordName, State) -> State.
+%% Check if a record exists. Set State.
+
+exist_record(Line, Name, St) ->
+ case dict:is_key(Name, St#lint.records) of
+ true -> used_record(Name, St);
+ false -> add_error(Line, {undefined_record,Name}, St)
+ end.
+
+%% check_record(Line, RecordName, State, CheckFun) ->
+%% {UpdVarTable, State}.
+%% The generic record checking function, first checks that the record
+%% exists then calls the specific check function. N.B. the check
+%% function can safely assume that the record exists.
+%%
+%% The check function is called:
+%% CheckFun(RecordDefFields, State)
+%% and must return
+%% {UpdatedVarTable,State}
+
+check_record(Line, Name, St, CheckFun) ->
+ case dict:find(Name, St#lint.records) of
+ {ok,{_Line,Fields}} -> CheckFun(Fields, used_record(Name, St));
+ error -> {[],add_error(Line, {undefined_record,Name}, St)}
+ end.
+
+used_record(Name, #lint{usage=Usage}=St) ->
+ UsedRecs = sets:add_element(Name, Usage#usage.used_records),
+ St#lint{usage = Usage#usage{used_records=UsedRecs}}.
+
+%%% Record check functions.
+
+%% check_fields([ChkField], RecordName, [RecDefField], VarTable, State, CheckFun) ->
+%% {UpdVarTable,State}.
+
+check_fields(Fs, Name, Fields, Vt, St0, CheckFun) ->
+ {_SeenFields,Uvt,St1} =
+ foldl(fun (Field, {Sfsa,Vta,Sta}) ->
+ {Sfsb,{Vtb,Stb}} = check_field(Field, Name, Fields,
+ Vt, Sta, Sfsa, CheckFun),
+ {Sfsb,vtmerge_pat(Vta, Vtb),Stb}
+ end, {[],[],St0}, Fs),
+ {Uvt,St1}.
+
+check_field({record_field,Lf,{atom,La,F},Val}, Name, Fields,
+ Vt, St, Sfs, CheckFun) ->
+ case member(F, Sfs) of
+ true -> {Sfs,{Vt,add_error(Lf, {redefine_field,Name,F}, St)}};
+ false ->
+ {[F|Sfs],
+ case find_field(F, Fields) of
+ {ok,_I} -> CheckFun(Val, Vt, St);
+ error -> {[],add_error(La, {undefined_field,Name,F}, St)}
+ end}
+ end;
+check_field({record_field,_Lf,{var,_La,'_'},Val}, _Name, _Fields,
+ Vt, St, Sfs, CheckFun) ->
+ {Sfs,CheckFun(Val, Vt, St)};
+check_field({record_field,_Lf,{var,La,V},_Val}, Name, _Fields,
+ Vt, St, Sfs, _CheckFun) ->
+ {Sfs,{Vt,add_error(La, {field_name_is_variable,Name,V}, St)}}.
+
+%% pattern_field(Field, RecordName, [RecDefField], State) ->
+%% {UpdVarTable,State}.
+%% Test if record RecordName has field Field. Set State.
+
+pattern_field({atom,La,F}, Name, Fields, St) ->
+ case find_field(F, Fields) of
+ {ok,_I} -> {[],St};
+ error -> {[],add_error(La, {undefined_field,Name,F}, St)}
+ end.
+
+%% pattern_fields([PatField],RecordName,[RecDefField],
+%% VarTable,Old,Bvt,State) ->
+%% {UpdVarTable,UpdBinVarTable,State}.
+
+pattern_fields(Fs, Name, Fields, Vt0, Old, Bvt, St0) ->
+ CheckFun = fun (Val, Vt, St) -> pattern(Val, Vt, Old, Bvt, St) end,
+ {_SeenFields,Uvt,Bvt1,St1} =
+ foldl(fun (Field, {Sfsa,Vta,Bvt1,Sta}) ->
+ case check_field(Field, Name, Fields,
+ Vt0, Sta, Sfsa, CheckFun) of
+ {Sfsb,{Vtb,Stb}} ->
+ {Sfsb,vtmerge_pat(Vta, Vtb),[],Stb};
+ {Sfsb,{Vtb,Bvt2,Stb}} ->
+ {Sfsb,vtmerge_pat(Vta, Vtb),
+ vtmerge_pat(Bvt1,Bvt2),Stb}
+ end
+ end, {[],[],[],St0}, Fs),
+ {Uvt,Bvt1,St1}.
+
+%% record_field(Field, RecordName, [RecDefField], State) ->
+%% {UpdVarTable,State}.
+%% Test if record RecordName has field Field. Set State.
+
+record_field({atom,La,F}, Name, Fields, St) ->
+ case find_field(F, Fields) of
+ {ok,_I} -> {[],St};
+ error -> {[],add_error(La, {undefined_field,Name,F}, St)}
+ end.
+
+%% init_fields([InitField], InitLine, RecordName, [DefField], VarTable, State) ->
+%% {UpdVarTable,State}.
+%% ginit_fields([InitField], InitLine, RecordName, [DefField], VarTable, State) ->
+%% {UpdVarTable,State}.
+%% Check record initialisation. Explicit initialisations are checked
+%% as is, while default values are checked only if there are no
+%% explicit inititialisations of the fields. Be careful not to
+%% duplicate warnings (and possibly errors, but def_fields
+%% substitutes 'undefined' for bogus inititialisations) from when the
+%% record definitions were checked. Usage of records, imports, and
+%% functions is collected.
+
+init_fields(Ifs, Line, Name, Dfs, Vt0, St0) ->
+ {Vt1,St1} = check_fields(Ifs, Name, Dfs, Vt0, St0, fun expr/3),
+ Defs = init_fields(Ifs, Line, Dfs),
+ {_,St2} = check_fields(Defs, Name, Dfs, Vt1, St1, fun expr/3),
+ {Vt1,St1#lint{usage = St2#lint.usage}}.
+
+ginit_fields(Ifs, Line, Name, Dfs, Vt0, St0) ->
+ {Vt1,St1} = check_fields(Ifs, Name, Dfs, Vt0, St0, fun gexpr/3),
+ Defs = init_fields(Ifs, Line, Dfs),
+ St2 = St1#lint{errors = []},
+ {_,St3} = check_fields(Defs, Name, Dfs, Vt1, St2, fun gexpr/3),
+ #lint{usage = Usage, errors = Errors} = St3,
+ IllErrs = [E || {_File,{_Line,erl_lint,illegal_guard_expr}}=E <- Errors],
+ St4 = St1#lint{usage = Usage, errors = IllErrs ++ St1#lint.errors},
+ {Vt1,St4}.
+
+%% Default initializations to be carried out
+init_fields(Ifs, Line, Dfs) ->
+ [ {record_field,Lf,{atom,La,F},copy_expr(Di, Line)} ||
+ {record_field,Lf,{atom,La,F},Di} <- Dfs,
+ not exist_field(F, Ifs) ].
+
+%% update_fields(UpdFields, RecordName, RecDefFields, VarTable, State) ->
+%% {UpdVarTable,State}
+
+update_fields(Ufs, Name, Dfs, Vt, St) ->
+ check_fields(Ufs, Name, Dfs, Vt, St, fun expr/3).
+
+%% exist_field(FieldName, [Field]) -> boolean().
+%% Find a record field in a field list.
+
+exist_field(F, [{record_field,_Lf,{atom,_La,F},_Val}|_Fs]) -> true;
+exist_field(F, [_|Fs]) -> exist_field(F, Fs);
+exist_field(_F, []) -> false.
+
+%% find_field(FieldName, [Field]) -> {ok,Val} | error.
+%% Find a record field in a field list.
+
+find_field(_F, [{record_field,_Lf,{atom,_La,_F},Val}|_Fs]) -> {ok,Val};
+find_field(F, [_|Fs]) -> find_field(F, Fs);
+find_field(_F, []) -> error.
+
+%% type_def(Attr, Line, TypeName, PatField, Args, State) -> State.
+%% Attr :: 'type' | 'opaque'
+%% Checks that a type definition is valid.
+
+type_def(_Attr, _Line, {record, _RecName}, Fields, [], St0) ->
+ %% The record field names and such are checked in the record format.
+ %% We only need to check the types.
+ Types = [T || {typed_record_field, _, T} <- Fields],
+ check_type({type, -1, product, Types}, St0);
+type_def(_Attr, Line, TypeName, ProtoType, Args, St0) ->
+ TypeDefs = St0#lint.types,
+ Arity = length(Args),
+ TypePair = {TypeName, Arity},
+ case (dict:is_key(TypePair, TypeDefs) orelse is_var_arity_type(TypeName)) of
+ true ->
+ case dict:is_key(TypePair, default_types()) of
+ true ->
+ case is_newly_introduced_builtin_type(TypePair) of
+ %% allow some types just for bootstrapping
+ true ->
+ Warn = {new_builtin_type, TypePair},
+ St1 = add_warning(Line, Warn, St0),
+ NewDefs = dict:store(TypePair, Line, TypeDefs),
+ CheckType = {type, -1, product, [ProtoType|Args]},
+ check_type(CheckType, St1#lint{types=NewDefs});
+ false ->
+ add_error(Line, {builtin_type, TypePair}, St0)
+ end;
+ false -> add_error(Line, {redefine_type, TypePair}, St0)
+ end;
+ false ->
+ NewDefs = dict:store(TypePair, Line, TypeDefs),
+ CheckType = {type, -1, product, [ProtoType|Args]},
+ check_type(CheckType, St0#lint{types=NewDefs})
+ end.
+
+check_type(Types, St) ->
+ {SeenVars, St1} = check_type(Types, dict:new(), St),
+ dict:fold(fun(Var, {seen_once, Line}, AccSt) ->
+ case atom_to_list(Var) of
+ [$_|_] -> AccSt;
+ _ -> add_error(Line, {singleton_typevar, Var}, AccSt)
+ end;
+ (_Var, seen_multiple, AccSt) ->
+ AccSt
+ end, St1, SeenVars).
+
+check_type({ann_type, _L, [_Var, Type]}, SeenVars, St) ->
+ check_type(Type, SeenVars, St);
+check_type({paren_type, _L, [Type]}, SeenVars, St) ->
+ check_type(Type, SeenVars, St);
+check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]},
+ SeenVars, St = #lint{module=CurrentMod}) ->
+ St1 =
+ case (dict:is_key({Name, length(Args)}, default_types())
+ orelse is_var_arity_type(Name)) of
+ true -> add_error(L, {imported_predefined_type, Name}, St);
+ false -> St
+ end,
+ case Mod =:= CurrentMod of
+ true -> check_type({type, L, Name, Args}, SeenVars, St1);
+ false ->
+ lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
+ check_type(T, AccSeenVars, AccSt)
+ end, {SeenVars, St1}, Args)
+ end;
+check_type({integer, _L, _}, SeenVars, St) -> {SeenVars, St};
+check_type({atom, _L, _}, SeenVars, St) -> {SeenVars, St};
+check_type({var, _L, '_'}, SeenVars, St) -> {SeenVars, St};
+check_type({var, L, Name}, SeenVars, St) ->
+ NewSeenVars =
+ case dict:find(Name, SeenVars) of
+ {ok, {seen_once, _}} -> dict:store(Name, seen_multiple, SeenVars);
+ {ok, seen_multiple} -> SeenVars;
+ error -> dict:store(Name, {seen_once, L}, SeenVars)
+ end,
+ {NewSeenVars, St};
+check_type({type, L, bool, []}, SeenVars, St) ->
+ {SeenVars, add_warning(L, {renamed_type, bool, boolean}, St)};
+check_type({type, L, 'fun', [Dom, Range]}, SeenVars, St) ->
+ St1 =
+ case Dom of
+ {type, _, product, _} -> St;
+ {type, _, any} -> St;
+ _ -> add_error(L, {type_syntax, 'fun'}, St)
+ end,
+ check_type({type, -1, product, [Dom, Range]}, SeenVars, St1);
+check_type({type, L, range, [From, To]}, SeenVars, St) ->
+ St1 =
+ case {From, To} of
+ {{integer, _, X}, {integer, _, Y}} when X < Y -> St;
+ _ -> add_error(L, {type_syntax, range}, St)
+ end,
+ {SeenVars, St1};
+check_type({type, _L, tuple, any}, SeenVars, St) -> {SeenVars, St};
+check_type({type, _L, any}, SeenVars, St) -> {SeenVars, St};
+check_type({type, L, binary, [Base, Unit]}, SeenVars, St) ->
+ St1 =
+ case {Base, Unit} of
+ {{integer, _, BaseVal},
+ {integer, _, UnitVal}} when BaseVal >= 0, UnitVal >= 0 -> St;
+ _ -> add_error(L, {type_syntax, binary}, St)
+ end,
+ {SeenVars, St1};
+check_type({type, L, record, [Name|Fields]}, SeenVars, St) ->
+ case Name of
+ {atom, _, Atom} ->
+ St1 = used_record(Atom, St),
+ check_record_types(L, Atom, Fields, SeenVars, St1);
+ _ -> {SeenVars, add_error(L, {type_syntax, record}, St)}
+ end;
+check_type({type, _L, product, Args}, SeenVars, St) ->
+ lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
+ check_type(T, AccSeenVars, AccSt)
+ end, {SeenVars, St}, Args);
+check_type({type, La, TypeName, Args}, SeenVars,
+ St = #lint{types=Defs, usage=Usage}) ->
+ Arity = length(Args),
+ St1 =
+ case dict:is_key({TypeName, Arity}, Defs) of
+ true ->
+ UsedTypes1 = Usage#usage.used_types,
+ UsedTypes2 = sets:add_element({TypeName, Arity}, UsedTypes1),
+ St#lint{usage=Usage#usage{used_types=UsedTypes2}};
+ false ->
+ case is_var_arity_type(TypeName) of
+ true -> St;
+ false -> add_error(La, {type_ref, {TypeName, Arity}}, St)
+ end
+ end,
+ check_type({type, -1, product, Args}, SeenVars, St1).
+
+check_record_types(Line, Name, Fields, SeenVars, St) ->
+ case dict:find(Name, St#lint.records) of
+ {ok,{_L,DefFields}} ->
+ case lists:all(fun({type, _, field_type, _}) -> true;
+ (_) -> false
+ end, Fields) of
+ true ->
+ check_record_types(Fields, Name, DefFields, SeenVars, St, []);
+ false ->
+ {SeenVars, add_error(Line, {type_syntax, record}, St)}
+ end;
+ error ->
+ {SeenVars, add_error(Line, {undefined_record, Name}, St)}
+ end.
+
+check_record_types([{type, _, field_type, [{atom, AL, FName}, Type]}|Left],
+ Name, DefFields, SeenVars, St, SeenFields) ->
+ %% Check that the field name is valid
+ St1 = case exist_field(FName, DefFields) of
+ true -> St;
+ false -> add_error(AL, {undefined_field, Name, FName}, St)
+ end,
+ %% Check for duplicates
+ St2 = case ordsets:is_element(FName, SeenFields) of
+ true -> add_error(AL, {redefine_field, Name, FName}, St1);
+ false -> St1
+ end,
+ %% Check Type
+ {NewSeenVars, St3} = check_type(Type, SeenVars, St2),
+ NewSeenFields = ordsets:add_element(FName, SeenFields),
+ check_record_types(Left, Name, DefFields, NewSeenVars, St3, NewSeenFields);
+check_record_types([], _Name, _DefFields, SeenVars, St, _SeenFields) ->
+ {SeenVars, St}.
+
+is_var_arity_type(tuple) -> true;
+is_var_arity_type(product) -> true;
+is_var_arity_type(union) -> true;
+is_var_arity_type(record) -> true;
+is_var_arity_type(_) -> false.
+
+default_types() ->
+ DefTypes = [{any, 0},
+ {arity, 0},
+ {array, 0},
+ {atom, 0},
+ {atom, 1},
+ {binary, 0},
+ {binary, 2},
+ {bitstring, 0},
+ {bool, 0},
+ {boolean, 0},
+ {byte, 0},
+ {char, 0},
+ {dict, 0},
+ {digraph, 0},
+ {float, 0},
+ {'fun', 0},
+ {'fun', 2},
+ {function, 0},
+ {gb_set, 0},
+ {gb_tree, 0},
+ {identifier, 0},
+ {integer, 0},
+ {integer, 1},
+ {iodata, 0},
+ {iolist, 0},
+ {list, 0},
+ {list, 1},
+ {maybe_improper_list, 0},
+ {maybe_improper_list, 2},
+ {mfa, 0},
+ {module, 0},
+ {neg_integer, 0},
+ {nil, 0},
+ {no_return, 0},
+ {node, 0},
+ {non_neg_integer, 0},
+ {none, 0},
+ {nonempty_list, 0},
+ {nonempty_list, 1},
+ {nonempty_improper_list, 2},
+ {nonempty_maybe_improper_list, 0},
+ {nonempty_maybe_improper_list, 2},
+ {nonempty_string, 0},
+ {number, 0},
+ {pid, 0},
+ {port, 0},
+ {pos_integer, 0},
+ {queue, 0},
+ {range, 2},
+ {reference, 0},
+ {set, 0},
+ {string, 0},
+ {term, 0},
+ {tid, 0},
+ {timeout, 0},
+ {var, 1}],
+ dict:from_list([{T, -1} || T <- DefTypes]).
+
+%% R12B-5
+is_newly_introduced_builtin_type({module, 0}) -> true;
+is_newly_introduced_builtin_type({node, 0}) -> true;
+is_newly_introduced_builtin_type({nonempty_string, 0}) -> true;
+is_newly_introduced_builtin_type({term, 0}) -> true;
+is_newly_introduced_builtin_type({timeout, 0}) -> true;
+%% R13
+is_newly_introduced_builtin_type({arity, 0}) -> true;
+is_newly_introduced_builtin_type({array, 0}) -> true; % opaque
+is_newly_introduced_builtin_type({bitstring, 0}) -> true;
+is_newly_introduced_builtin_type({dict, 0}) -> true; % opaque
+is_newly_introduced_builtin_type({digraph, 0}) -> true; % opaque
+is_newly_introduced_builtin_type({gb_set, 0}) -> true; % opaque
+is_newly_introduced_builtin_type({gb_tree, 0}) -> true; % opaque
+is_newly_introduced_builtin_type({iodata, 0}) -> true;
+is_newly_introduced_builtin_type({queue, 0}) -> true; % opaque
+is_newly_introduced_builtin_type({set, 0}) -> true; % opaque
+is_newly_introduced_builtin_type({tid, 0}) -> true; % opaque
+%% R13B01
+is_newly_introduced_builtin_type({boolean, 0}) -> true;
+is_newly_introduced_builtin_type({Name, _}) when is_atom(Name) -> false.
+
+%% spec_decl(Line, Fun, Types, State) -> State.
+
+spec_decl(Line, MFA0, TypeSpecs, St0 = #lint{specs = Specs, module = Mod}) ->
+ MFA = case MFA0 of
+ {F, Arity} -> {Mod, F, Arity};
+ {_M, _F, Arity} -> MFA0
+ end,
+ St1 = St0#lint{specs = dict:store(MFA, Line, Specs)},
+ case dict:is_key(MFA, Specs) of
+ true -> add_error(Line, {redefine_spec, MFA}, St1);
+ false -> check_specs(TypeSpecs, Arity, St1)
+ end.
+
+check_specs([FunType|Left], Arity, St0) ->
+ {FunType1, CTypes} =
+ case FunType of
+ {type, _, bounded_fun, [FT = {type, _, 'fun', _}, Cs]} ->
+ Types0 = [T || {type, _, constraint, [_, T]} <- Cs],
+ {FT, lists:append(Types0)};
+ {type, _, 'fun', _} = FT -> {FT, []}
+ end,
+ SpecArity =
+ case FunType1 of
+ {type, L, 'fun', [any, _]} -> any;
+ {type, L, 'fun', [{type, _, product, D}, _]} -> length(D)
+ end,
+ St1 = case Arity =:= SpecArity of
+ true -> St0;
+ false -> add_error(L, spec_wrong_arity, St0)
+ end,
+ St2 = check_type({type, -1, product, [FunType1|CTypes]}, St1),
+ check_specs(Left, Arity, St2);
+check_specs([], _Arity, St) ->
+ St.
+
+check_specs_without_function(St = #lint{module=Mod, defined=Funcs}) ->
+ Fun = fun({M, F, A} = MFA, Line, AccSt) when M =:= Mod ->
+ case gb_sets:is_element({F, A}, Funcs) of
+ true -> AccSt;
+ false -> add_error(Line, {spec_fun_undefined, MFA}, AccSt)
+ end;
+ ({_M, _F, _A}, _Line, AccSt) -> AccSt
+ end,
+ dict:fold(Fun, St, St#lint.specs).
+
+%% This generates warnings for functions without specs; if the user has
+%% specified both options, we do not generate the same warnings twice.
+check_functions_without_spec(Forms, St0) ->
+ case is_warn_enabled(missing_spec_all, St0) of
+ true ->
+ add_missing_spec_warnings(Forms, St0, all);
+ false ->
+ case is_warn_enabled(missing_spec, St0) of
+ true ->
+ add_missing_spec_warnings(Forms, St0, exported);
+ false ->
+ St0
+ end
+ end.
+
+add_missing_spec_warnings(Forms, St0, Type) ->
+ Specs = [{F,A} || {_M,F,A} <- dict:fetch_keys(St0#lint.specs)],
+ Warns = %% functions + line numbers for which we should warn
+ case Type of
+ all ->
+ [{FA,L} || {function,L,F,A,_} <- Forms,
+ not lists:member(FA = {F,A}, Specs)];
+ exported ->
+ Exps = gb_sets:to_list(St0#lint.exports) -- pseudolocals(),
+ [{FA,L} || {function,L,F,A,_} <- Forms,
+ member(FA = {F,A}, Exps -- Specs)]
+ end,
+ foldl(fun ({FA,L}, St) ->
+ add_warning(L, {missing_spec,FA}, St)
+ end, St0, Warns).
+
+check_unused_types(Forms, St = #lint{usage=Usage, types=Types}) ->
+ case [File || {attribute,_L,file,{File,_Line}} <- Forms] of
+ [FirstFile|_] ->
+ UsedTypes = Usage#usage.used_types,
+ FoldFun =
+ fun(_Type, -1, AccSt) ->
+ %% Default type
+ AccSt;
+ (Type, FileLine, AccSt) ->
+ case loc(FileLine) of
+ {FirstFile, _} ->
+ case sets:is_element(Type, UsedTypes) of
+ true -> AccSt;
+ false ->
+ add_warning(FileLine,
+ {unused_type, Type},
+ AccSt)
+ end;
+ _ ->
+ %% Don't warn about unused types in include file
+ AccSt
+ end
+ end,
+ dict:fold(FoldFun, St, Types);
+ [] ->
+ St
+ end.
+
+%% icrt_clauses(Clauses, In, ImportVarTable, State) ->
+%% {NewVts,State}.
+
+icrt_clauses(Cs, In, Vt, St0) ->
+ {Csvt,St1} = icrt_clauses(Cs, Vt, St0),
+ icrt_export(Csvt, Vt, In, St1).
+
+%% icrt_clauses(Clauses, ImportVarTable, State) ->
+%% {NewVts,State}.
+
+icrt_clauses(Cs, Vt, St) ->
+ mapfoldl(fun (C, St0) -> icrt_clause(C, Vt, St0) end, St, Cs).
+
+icrt_clause({clause,_Line,H,G,B}, Vt0, St0) ->
+ {Hvt,Binvt,St1} = head(H, Vt0, St0),
+ Vt1 = vtupdate(Hvt, vtupdate(Binvt, Vt0)),
+ {Gvt,St2} = guard(G, Vt1, St1),
+ Vt2 = vtupdate(Gvt, Vt1),
+ {Bvt,St3} = exprs(B, Vt2, St2),
+ {vtupdate(Bvt, Vt2),St3}.
+
+%% The tests of 'cond' clauses are normal expressions - not guards.
+%% Variables bound in a test is visible both in the corresponding body
+%% and in the tests and bodies of subsequent clauses: a 'cond' is
+%% *equivalent* to nested case-switches on boolean expressions.
+
+cond_clauses([C], In, Vt, St) ->
+ last_cond_clause(C, In, Vt, St);
+cond_clauses([C | Cs], In, Vt, St) ->
+ cond_clause(C, Cs, In, Vt, St).
+
+%% see expr/3 for 'case'
+cond_clause({clause,_L,[],[[E]],B}, Cs, In, Vt, St0) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ {Cvt, St2} = cond_cases(B, Cs, In, vtupdate(Evt, Vt), St1),
+ Mvt = vtmerge(Evt, Cvt),
+ {Mvt,St2}.
+
+%% see icrt_clauses/4
+cond_cases(B, Cs, In, Vt, St0) ->
+ %% note that Vt is used for both cases
+ {Bvt,St1} = exprs(B, Vt, St0), % true case
+ Vt1 = vtupdate(Bvt, Vt),
+ {Cvt, St2} = cond_clauses(Cs, In, Vt, St1), % false case
+ Vt2 = vtupdate(Cvt, Vt),
+ %% and this also uses Vt
+ icrt_export([Vt1,Vt2], Vt, In, St2).
+
+%% last case must call icrt_export/4 with only one vartable
+last_cond_clause({clause,_L,[],[[E]],B}, In, Vt, St0) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ {Cvt, St2} = last_cond_case(B, In, vtupdate(Evt, Vt), St1),
+ Mvt = vtmerge(Evt, Cvt),
+ {Mvt,St2}.
+
+last_cond_case(B, In, Vt, St0) ->
+ {Bvt,St1} = exprs(B, Vt, St0),
+ Vt1 = vtupdate(Bvt, Vt),
+ icrt_export([Vt1], Vt, In, St1).
+
+icrt_export(Csvt, Vt, In, St) ->
+ Vt1 = vtmerge(Csvt),
+ All = ordsets:subtract(vintersection(Csvt), vtnames(Vt)),
+ Some = ordsets:subtract(vtnames(Vt1), vtnames(Vt)),
+ Xvt = vtexport(All, In, []),
+ Evt = vtunsafe(ordsets:subtract(Some, All), In, Xvt),
+ Unused = vtmerge([unused_vars(Vt0, Vt, St) || Vt0 <- Csvt]),
+ %% Exported and unsafe variables may be unused:
+ Uvt = vtmerge(Evt, Unused),
+ %% Make exported and unsafe unused variables unused in subsequent code:
+ Vt2 = vtmerge(Uvt, vtsubtract(Vt1, Uvt)),
+ {Vt2,St}.
+
+handle_comprehension(E, Qs, Vt0, St0) ->
+ {Vt1, Uvt, St1} = lc_quals(Qs, Vt0, St0),
+ {Evt,St2} = expr(E, Vt1, St1),
+ Vt2 = vtupdate(Evt, Vt1),
+ %% Shadowed global variables.
+ {_,St3} = check_old_unused_vars(Vt2, Uvt, St2),
+ %% There may be local variables in Uvt that are not global.
+ {_,St4} = check_unused_vars(Uvt, Vt0, St3),
+ %% Local variables that have not been shadowed.
+ {_,St} = check_unused_vars(Vt2, Vt0, St4),
+ Vt3 = vtmerge(vtsubtract(Vt2, Uvt), Uvt),
+ {Vt3,St}.
+
+%% lc_quals(Qualifiers, ImportVarTable, State) ->
+%% {VarTable,ShadowedVarTable,State}
+%% Test list comprehension qualifiers, return all variables. Allow
+%% filters to be both guard tests and general expressions, but the errors
+%% will be for expressions. Return the complete updated vartable including
+%% local variables and all updates. ShadowVarTable contains the state of
+%% each shadowed variable. All variable states of variables in ImportVarTable
+%% that have been shadowed are included in ShadowVarTable. In addition, all
+%% shadowed variables that are not included in ImportVarTable are included
+%% in ShadowVarTable (these are local variables that are not global variables).
+
+lc_quals(Qs, Vt0, St0) ->
+ OldRecDef = St0#lint.recdef_top,
+ {Vt,Uvt,St} = lc_quals(Qs, Vt0, [], St0#lint{recdef_top = false}),
+ {Vt,Uvt,St#lint{recdef_top = OldRecDef}}.
+
+lc_quals([{generate,_Line,P,E} | Qs], Vt0, Uvt0, St0) ->
+ {Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St0),
+ lc_quals(Qs, Vt, Uvt, St);
+lc_quals([{b_generate,_Line,P,E} | Qs], Vt0, Uvt0, St0) ->
+ {Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St0),
+ lc_quals(Qs, Vt, Uvt, St);
+lc_quals([F|Qs], Vt, Uvt, St0) ->
+ {Fvt,St1} = case is_guard_test2(F, St0#lint.records) of
+ true -> guard_test(F, Vt, St0);
+ false -> expr(F, Vt, St0)
+ end,
+ lc_quals(Qs, vtupdate(Fvt, Vt), Uvt, St1);
+lc_quals([], Vt, Uvt, St) ->
+ {Vt, Uvt, St}.
+
+handle_generator(P,E,Vt,Uvt,St0) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ %% Forget variables local to E immediately.
+ Vt1 = vtupdate(vtold(Evt, Vt), Vt),
+ {_, St2} = check_unused_vars(Evt, Vt, St1),
+ {Pvt,Binvt,St3} = pattern(P, Vt1, [], [], St2),
+ %% Have to keep fresh variables separated from used variables somehow
+ %% in order to handle for example X = foo(), [X || <<X:X>> <- bar()].
+ %% 1 2 2 1
+ Vt2 = vtupdate(Pvt, Vt1),
+ St4 = shadow_vars(Binvt, Vt1, generate, St3),
+ Svt = vtold(Vt2, Binvt),
+ {_, St5} = check_old_unused_vars(Svt, Uvt, St4),
+ NUvt = vtupdate(vtnew(Svt, Uvt), Uvt),
+ Vt3 = vtupdate(vtsubtract(Vt2, Binvt), Binvt),
+ {Vt3,NUvt,St5}.
+
+%% fun_clauses(Clauses, ImportVarTable, State) ->
+%% {UsedVars, State}.
+%% Fun's cannot export any variables.
+
+%% It is an error if variable is bound inside a record definition
+%% unless it was introduced in a fun or an lc. Only if pat_var finds
+%% such variables can the correct line number be given.
+
+fun_clauses(Cs, Vt, St) ->
+ OldRecDef = St#lint.recdef_top,
+ {Bvt,St2} = foldl(fun (C, {Bvt0, St0}) ->
+ {Cvt,St1} = fun_clause(C, Vt, St0),
+ {vtmerge(Cvt, Bvt0),St1}
+ end, {[],St#lint{recdef_top = false}}, Cs),
+ {Bvt,St2#lint{recdef_top = OldRecDef}}.
+
+fun_clause({clause,_Line,H,G,B}, Vt0, St0) ->
+ {Hvt,Binvt,St1} = head(H, Vt0, [], St0), % No imported pattern variables
+ Vt1 = vtupdate(Hvt, Vt0),
+ St2 = shadow_vars(Binvt, Vt0, 'fun', St1),
+ Vt2 = vtupdate(vtsubtract(Vt1, Binvt), Binvt),
+ {Gvt,St3} = guard(G, Vt2, St2),
+ Vt3 = vtupdate(Gvt, Vt2),
+ {Bvt,St4} = exprs(B, Vt3, St3),
+ Cvt = vtupdate(Bvt, Vt3),
+ %% Check new local variables.
+ {_, St5} = check_unused_vars(Cvt, Vt0, St4),
+ %% Check all shadowing variables.
+ Svt = vtold(Vt1, Binvt),
+ {_, St6} = check_old_unused_vars(Cvt, Svt, St5),
+ Vt4 = vtmerge(Svt, vtsubtract(Cvt, Svt)),
+ {vtold(Vt4, Vt0),St6}.
+
+%% In the variable table we store information about variables. The
+%% information is a tuple {State,Usage,Lines}, the variables state and
+%% usage. A variable can be in the following states:
+%%
+%% bound everything is normal
+%% {export,From} variable has been exported
+%% {unsafe,In} variable is unsafe
+%%
+%% The usage information has the following form:
+%%
+%% used variable has been used
+%% unused variable has been bound but not used
+%%
+%% Lines is a list of line numbers where the variable was bound.
+%%
+%% Report variable errors/warnings as soon as possible and then change
+%% the state to ok. This simplifies the code and reports errors only
+%% once. Having the usage information like this makes it easy too when
+%% merging states.
+
+%% For keeping track of which variables are bound, ordsets are used.
+%% In order to be able to give warnings about unused variables, a
+%% possible value is {bound, unused, [Line]}. The usual value when a
+%% variable is used is {bound, used, [Line]}. An exception occurs for
+%% variables in the size position in a bin element in a pattern.
+%% Currently, such a variable is never matched out, always used, and
+%% therefore it makes no sense to warn for "variable imported in
+%% match".
+
+%% For storing the variable table we use the orddict module.
+%% We know an empty set is [].
+
+%% pat_var(Variable, LineNo, VarTable, State) -> {UpdVarTable,State}
+%% A pattern variable has been found. Handle errors and warnings. Return
+%% all variables as bound so errors and warnings are only reported once.
+%% Bvt "shadows" Vt here, which is necessary in order to separate uses of
+%% shadowed and shadowing variables. See also pat_binsize_var.
+
+pat_var(V, Line, Vt, Bvt, St) ->
+ case orddict:find(V, Bvt) of
+ {ok, {bound,_Usage,Ls}} ->
+ {[],[{V,{bound,used,Ls}}],St};
+ error ->
+ case orddict:find(V, Vt) of
+ {ok,{bound,_Usage,Ls}} ->
+ {[{V,{bound,used,Ls}}],[],St};
+ {ok,{{unsafe,In},_Usage,Ls}} ->
+ {[{V,{bound,used,Ls}}],[],
+ add_error(Line, {unsafe_var,V,In}, St)};
+ {ok,{{export,From},_Usage,Ls}} ->
+ {[{V,{bound,used,Ls}}],[],
+ %% As this is matching, exported vars are risky.
+ add_warning(Line, {exported_var,V,From}, St)};
+ error when St#lint.recdef_top ->
+ {[],[{V,{bound,unused,[Line]}}],
+ add_error(Line, {variable_in_record_def,V}, St)};
+ error -> {[],[{V,{bound,unused,[Line]}}],St}
+ end
+ end.
+
+%% pat_binsize_var(Variable, LineNo, VarTable, BinVarTable, State) ->
+%% {UpdVarTable,UpdBinVarTable,State'}
+%% A pattern variable has been found. Handle errors and warnings. Return
+%% all variables as bound so errors and warnings are only reported once.
+
+pat_binsize_var(V, Line, Vt, Bvt, St) ->
+ case orddict:find(V, Bvt) of
+ {ok,{bound,_Used,Ls}} ->
+ {[],[{V,{bound,used,Ls}}],St};
+ error ->
+ case orddict:find(V, Vt) of
+ {ok,{bound,_Used,Ls}} ->
+ {[{V,{bound,used,Ls}}],[],St};
+ {ok,{{unsafe,In},_Used,Ls}} ->
+ {[{V,{bound,used,Ls}}],[],
+ add_error(Line, {unsafe_var,V,In}, St)};
+ {ok,{{export,From},_Used,Ls}} ->
+ {[{V,{bound,used,Ls}}],[],
+ %% As this is not matching, exported vars are
+ %% probably safe.
+ exported_var(Line, V, From, St)};
+ error ->
+ {[{V,{bound,used,[Line]}}],[],
+ add_error(Line, {unbound_var,V}, St)}
+ end
+ end.
+
+%% expr_var(Variable, LineNo, VarTable, State) ->
+%% {UpdVarTable,State}
+%% Check if a variable is defined, or if there is an error or warning
+%% connected to its usage. Return all variables as bound so errors
+%% and warnings are only reported once. As this is not matching
+%% exported vars are probably safe, warn only if warn_export_vars is
+%% set.
+
+expr_var(V, Line, Vt, St0) ->
+ case orddict:find(V, Vt) of
+ {ok,{bound,_Usage,Ls}} ->
+ {[{V,{bound,used,Ls}}],St0};
+ {ok,{{unsafe,In},_Usage,Ls}} ->
+ {[{V,{bound,used,Ls}}],
+ add_error(Line, {unsafe_var,V,In}, St0)};
+ {ok,{{export,From},_Usage,Ls}} ->
+ {[{V,{bound,used,Ls}}],
+ exported_var(Line, V, From, St0)};
+ error ->
+ {[{V,{bound,used,[Line]}}],
+ add_error(Line, {unbound_var,V}, St0)}
+ end.
+
+exported_var(Line, V, From, St) ->
+ case is_warn_enabled(export_vars, St) of
+ true -> add_warning(Line, {exported_var,V,From}, St);
+ false -> St
+ end.
+
+shadow_vars(Vt, Vt0, In, St0) ->
+ case is_warn_enabled(shadow_vars, St0) of
+ true ->
+ foldl(fun ({V,{_,_,[L | _]}}, St) ->
+ add_warning(L, {shadowed_var,V,In}, St);
+ (_, St) -> St
+ end, St0, vtold(Vt, vt_no_unsafe(Vt0)));
+ false -> St0
+ end.
+
+check_unused_vars(Vt, Vt0, St0) ->
+ U = unused_vars(Vt, Vt0, St0),
+ warn_unused_vars(U, Vt, St0).
+
+check_old_unused_vars(Vt, Vt0, St0) ->
+ U = unused_vars(vtold(Vt, Vt0), [], St0),
+ warn_unused_vars(U, Vt, St0).
+
+unused_vars(Vt, Vt0, _St0) ->
+ U0 = orddict:filter(fun (V, {_State,unused,_Ls}) ->
+ case atom_to_list(V) of
+ [$_|_] -> false;
+ _ -> true
+ end;
+ (_V, _How) -> false
+ end, Vt),
+ vtnew(U0, Vt0). % Only new variables.
+
+warn_unused_vars([], Vt, St0) ->
+ {Vt,St0};
+warn_unused_vars(U, Vt, St0) ->
+ St1 = case is_warn_enabled(unused_vars, St0) of
+ false -> St0;
+ true ->
+ foldl(fun ({V,{_,unused,Ls}}, St) ->
+ foldl(fun (L, St2) ->
+ add_warning(L, {unused_var,V},
+ St2)
+ end, St, Ls)
+ end, St0, U)
+ end,
+ %% Return all variables as bound so warnings are only reported once.
+ UVt = map(fun ({V,{State,_,Ls}}) -> {V,{State,used,Ls}} end, U),
+ {vtmerge(Vt, UVt), St1}.
+
+%% vtupdate(UpdVarTable, VarTable) -> VarTable.
+%% Add the variables in the updated vartable to VarTable. The variables
+%% will be updated with their property in UpdVarTable. The state of
+%% the variables in UpdVarTable will be returned.
+
+vtupdate(Uvt, Vt0) ->
+ orddict:merge(fun (_V, {S,U1,L1}, {_S,U2,L2}) ->
+ {S, merge_used(U1, U2), merge_lines(L1, L2)}
+ end, Uvt, Vt0).
+
+%% vtexport([Variable], From, VarTable) -> VarTable.
+%% vtunsafe([Variable], From, VarTable) -> VarTable.
+%% Add the variables to VarTable either as exported from From or as unsafe.
+
+vtexport(Vs, {InTag,FileLine}, Vt0) ->
+ {_File,Line} = loc(FileLine),
+ vtupdate([{V,{{export,{InTag,Line}},unused,[]}} || V <- Vs], Vt0).
+
+vtunsafe(Vs, {InTag,FileLine}, Vt0) ->
+ {_File,Line} = loc(FileLine),
+ vtupdate([{V,{{unsafe,{InTag,Line}},unused,[]}} || V <- Vs], Vt0).
+
+%% vtmerge(VarTable, VarTable) -> VarTable.
+%% Merge two variables tables generating a new vartable. Give priority to
+%% errors then warnings.
+
+vtmerge(Vt1, Vt2) ->
+ orddict:merge(fun (_V, {S1,U1,L1}, {S2,U2,L2}) ->
+ {merge_state(S1, S2),
+ merge_used(U1, U2),
+ merge_lines(L1, L2)}
+ end, Vt1, Vt2).
+
+vtmerge(Vts) -> foldl(fun (Vt, Mvts) -> vtmerge(Vt, Mvts) end, [], Vts).
+
+vtmerge_pat(Vt1, Vt2) ->
+ orddict:merge(fun (_V, {S1,_Usage1,L1}, {S2,_Usage2,L2}) ->
+ {merge_state(S1, S2),used, merge_lines(L1, L2)}
+ end, Vt1, Vt2).
+
+merge_lines(Ls1, Ls2) ->
+ ordsets:union(Ls1,Ls2).
+
+merge_state({unsafe,_F1}=S1, _S2) -> S1; %Take the error case
+merge_state(_S1, {unsafe,_F2}=S2) -> S2;
+merge_state(bound, S2) -> S2; %Take the warning
+merge_state(S1, bound) -> S1;
+merge_state({export,F1},{export,_F2}) -> %Sanity check
+ %% We want to report the outermost construct
+ {export,F1}.
+
+merge_used(used, _Usage2) -> used;
+merge_used(_Usage1, used) -> used;
+merge_used(unused, unused) -> unused.
+
+%% vtnew(NewVarTable, OldVarTable) -> NewVarTable.
+%% Return all the truly new variables in NewVarTable.
+
+vtnew(New, Old) ->
+ orddict:filter(fun (V, _How) -> not orddict:is_key(V, Old) end, New).
+
+%% vtsubtract(VarTable1, VarTable2) -> NewVarTable.
+%% Return all the variables in VarTable1 which don't occur in VarTable2.
+%% Same thing as vtnew, but a more intuitive name for some uses.
+vtsubtract(New, Old) ->
+ vtnew(New, Old).
+
+%% vtold(NewVarTable, OldVarTable) -> OldVarTable.
+%% Return all the truly old variables in NewVarTable.
+
+vtold(New, Old) ->
+ orddict:filter(fun (V, _How) -> orddict:is_key(V, Old) end, New).
+
+vtnames(Vt) -> [ V || {V,_How} <- Vt ].
+
+vt_no_unsafe(Vt) -> [V || {_,{S,_U,_L}}=V <- Vt,
+ case S of
+ {unsafe,_} -> false;
+ _ -> true
+ end].
+
+%% vunion(VarTable1, VarTable2) -> [VarName].
+%% vunion([VarTable]) -> [VarName].
+%% vintersection(VarTable1, VarTable2) -> [VarName].
+%% vintersection([VarTable]) -> [VarName].
+%% Union/intersection of names of vars in VarTable.
+
+-ifdef(NOTUSED).
+vunion(Vs1, Vs2) -> ordsets:union(vtnames(Vs1), vtnames(Vs2)).
+
+vunion(Vss) -> foldl(fun (Vs, Uvs) ->
+ ordsets:union(vtnames(Vs), Uvs)
+ end, [], Vss).
+
+vintersection(Vs1, Vs2) -> ordsets:intersection(vtnames(Vs1), vtnames(Vs2)).
+-endif.
+
+vintersection([Vs]) ->
+ vtnames(Vs); %Boundary conditions!!!
+vintersection([Vs|Vss]) ->
+ ordsets:intersection(vtnames(Vs), vintersection(Vss));
+vintersection([]) ->
+ [].
+
+%% copy_expr(Expr, Line) -> Expr.
+%% Make a copy of Expr converting all line numbers to Line.
+
+copy_expr(Expr, Line) ->
+ modify_line(Expr, fun(_L) -> Line end).
+
+%% modify_line(Form, Fun) -> Form
+%% modify_line(Expression, Fun) -> Expression
+%% Applies Fun to each line number occurrence.
+
+modify_line(T, F0) ->
+ modify_line1(T, F0).
+
+%% Forms.
+modify_line1({function,F,A}, _Mf) -> {function,F,A};
+modify_line1({function,M,F,A}, _Mf) -> {function,M,F,A};
+modify_line1({attribute,L,record,{Name,Fields}}, Mf) ->
+ {attribute,Mf(L),record,{Name,modify_line1(Fields, Mf)}};
+modify_line1({attribute,L,spec,{Fun,Types}}, Mf) ->
+ {attribute,Mf(L),spec,{Fun,modify_line1(Types, Mf)}};
+modify_line1({attribute,L,type,{TypeName,TypeDef,Args}}, Mf) ->
+ {attribute,Mf(L),type,{TypeName,modify_line1(TypeDef, Mf),
+ modify_line1(Args, Mf)}};
+modify_line1({attribute,L,opaque,{TypeName,TypeDef,Args}}, Mf) ->
+ {attribute,Mf(L),opaque,{TypeName,modify_line1(TypeDef, Mf),
+ modify_line1(Args, Mf)}};
+modify_line1({attribute,L,Attr,Val}, Mf) -> {attribute,Mf(L),Attr,Val};
+modify_line1({warning,W}, _Mf) -> {warning,W};
+modify_line1({error,W}, _Mf) -> {error,W};
+%% Expressions.
+modify_line1({clauses,Cs}, Mf) -> {clauses,modify_line1(Cs, Mf)};
+modify_line1({typed_record_field,Field,Type}, Mf) ->
+ {typed_record_field,modify_line1(Field, Mf),modify_line1(Type, Mf)};
+modify_line1({Tag,L}, Mf) -> {Tag,Mf(L)};
+modify_line1({Tag,L,E1}, Mf) ->
+ {Tag,Mf(L),modify_line1(E1, Mf)};
+modify_line1({Tag,L,E1,E2}, Mf) ->
+ {Tag,Mf(L),modify_line1(E1, Mf),modify_line1(E2, Mf)};
+modify_line1({bin_element,L,E1,E2,TSL}, Mf) ->
+ {bin_element,Mf(L),modify_line1(E1, Mf),modify_line1(E2, Mf), TSL};
+modify_line1({Tag,L,E1,E2,E3}, Mf) ->
+ {Tag,Mf(L),modify_line1(E1, Mf),modify_line1(E2, Mf),modify_line1(E3, Mf)};
+modify_line1({Tag,L,E1,E2,E3,E4}, Mf) ->
+ {Tag,Mf(L),
+ modify_line1(E1, Mf),
+ modify_line1(E2, Mf),
+ modify_line1(E3, Mf),
+ modify_line1(E4, Mf)};
+modify_line1([H|T], Mf) ->
+ [modify_line1(H, Mf)|modify_line1(T, Mf)];
+modify_line1([], _Mf) -> [];
+modify_line1(E, _Mf) when not is_tuple(E), not is_list(E) -> E.
+
+%% Check a record_info call. We have already checked that it is not
+%% shadowed by an import.
+
+check_record_info_call(_Line,La,[{atom,Li,Info},{atom,_Ln,Name}],St) ->
+ case member(Info, [fields,size]) of
+ true -> exist_record(La, Name, St);
+ false -> add_error(Li, illegal_record_info, St)
+ end;
+check_record_info_call(Line,_La,_As,St) ->
+ add_error(Line, illegal_record_info, St).
+
+has_wildcard_field([{record_field,_Lf,{var,_La,'_'},_Val}|_Fs]) -> true;
+has_wildcard_field([_|Fs]) -> has_wildcard_field(Fs);
+has_wildcard_field([]) -> false.
+
+%% check_remote_function(Line, ModuleName, FuncName, [Arg], State) -> State.
+%% Perform checks on known remote calls.
+
+check_remote_function(Line, M, F, As, St0) ->
+ St1 = deprecated_function(Line, M, F, As, St0),
+ St2 = check_qlc_hrl(Line, M, F, As, St1),
+ format_function(Line, M, F, As, St2).
+
+%% check_qlc_hrl(Line, ModName, FuncName, [Arg], State) -> State
+%% Add warning if qlc:q/1,2 has been called but qlc.hrl has not
+%% been included.
+
+check_qlc_hrl(Line, M, F, As, St) ->
+ Arity = length(As),
+ case As of
+ [{lc,_L,_E,_Qs}|_] when M =:= qlc, F =:= q,
+ Arity < 3, not St#lint.xqlc ->
+ add_warning(Line, {missing_qlc_hrl, Arity}, St);
+ _ ->
+ St
+ end.
+
+%% deprecated_function(Line, ModName, FuncName, [Arg], State) -> State.
+%% Add warning for calls to deprecated functions.
+
+deprecated_function(Line, M, F, As, St) ->
+ Arity = length(As),
+ MFA = {M, F, Arity},
+ case otp_internal:obsolete(M, F, Arity) of
+ {deprecated, String} when is_list(String) ->
+ case not is_warn_enabled(deprecated_function, St) orelse
+ ordsets:is_element(MFA, St#lint.not_deprecated) of
+ true ->
+ St;
+ false ->
+ add_warning(Line, {deprecated, MFA, String}, St)
+ end;
+ {deprecated, Replacement, Rel} ->
+ case not is_warn_enabled(deprecated_function, St) orelse
+ ordsets:is_element(MFA, St#lint.not_deprecated) of
+ true ->
+ St;
+ false ->
+ add_warning(Line, {deprecated, MFA, Replacement, Rel}, St)
+ end;
+ {removed, String} when is_list(String) ->
+ add_warning(Line, {removed, MFA, String}, St);
+ {removed, Replacement, Rel} ->
+ add_warning(Line, {removed, MFA, Replacement, Rel}, St);
+ no ->
+ St
+ end.
+
+obsolete_guard({call,Line,{atom,Lr,F},As}, St0) ->
+ Arity = length(As),
+ case erl_internal:old_type_test(F, Arity) of
+ false ->
+ deprecated_function(Line, erlang, F, As, St0);
+ true ->
+ St1 = case F of
+ constant ->
+ deprecated_function(Lr, erlang, is_constant, As, St0);
+ _ ->
+ St0
+ end,
+ case is_warn_enabled(obsolete_guard, St1) of
+ true ->
+ add_warning(Lr,{obsolete_guard, {F, Arity}}, St1);
+ false ->
+ St1
+ end
+ end;
+obsolete_guard(_G, St) ->
+ St.
+
+%% keyword_warning(Line, Atom, State) -> State.
+%% Add warning for atoms that will be reserved keywords in the future.
+%% (Currently, no such keywords to warn for.)
+keyword_warning(_Line, _A, St) -> St.
+
+%% format_function(Line, ModName, FuncName, [Arg], State) -> State.
+%% Add warning for bad calls to io:fwrite/format functions.
+
+format_function(Line, M, F, As, St) ->
+ case is_format_function(M, F) of
+ true ->
+ case St#lint.warn_format of
+ Lev when Lev > 0 ->
+ case check_format_1(As) of
+ {warn,Level,Fmt,Fas} when Level =< Lev ->
+ add_warning(Line, {format_error,{Fmt,Fas}}, St);
+ _ -> St
+ end;
+ _Lev -> St
+ end;
+ false -> St
+ end.
+
+is_format_function(io, fwrite) -> true;
+is_format_function(io, format) -> true;
+is_format_function(io_lib, fwrite) -> true;
+is_format_function(io_lib, format) -> true;
+is_format_function(M, F) when is_atom(M), is_atom(F) -> false.
+
+%% check_format_1([Arg]) -> ok | {warn,Level,Format,[Arg]}.
+
+check_format_1([Fmt]) ->
+ check_format_1([Fmt,{nil,0}]);
+check_format_1([Fmt,As]) ->
+ check_format_2(Fmt, canonicalize_string(As));
+check_format_1([_Dev,Fmt,As]) ->
+ check_format_1([Fmt,As]);
+check_format_1(_As) ->
+ {warn,1,"format call with wrong number of arguments",[]}.
+
+canonicalize_string({string,Line,Cs}) ->
+ foldr(fun (C, T) -> {cons,Line,{integer,Line,C},T} end, {nil,Line}, Cs);
+canonicalize_string(Term) ->
+ Term.
+
+%% check_format_2([Arg]) -> ok | {warn,Level,Format,[Arg]}.
+
+check_format_2(Fmt, As) ->
+ case Fmt of
+ {string,_L,S} -> check_format_2a(S, As);
+ {atom,_L,A} -> check_format_2a(atom_to_list(A), As);
+ _ -> {warn,2,"format string not a textual constant",[]}
+ end.
+
+check_format_2a(Fmt, As) ->
+ case args_list(As) of
+ true -> check_format_3(Fmt, As);
+ false -> {warn,1,"format arguments not a list",[]};
+ maybe -> {warn,2,"format arguments perhaps not a list",[]}
+ end.
+
+%% check_format_3(FormatString, [Arg]) -> ok | {warn,Level,Format,[Arg]}.
+
+check_format_3(Fmt, As) ->
+ case check_format_string(Fmt) of
+ {ok,Need} ->
+ case args_length(As) of
+ Len when length(Need) =:= Len -> ok;
+ _Len -> {warn,1,"wrong number of arguments in format call",[]}
+ end;
+ {error,S} ->
+ {warn,1,"format string invalid (~s)",[S]}
+ end.
+
+args_list({cons,_L,_H,T}) -> args_list(T);
+%% Strange case: user has written something like [a | "bcd"]; pretend
+%% we don't know:
+args_list({string,_L,_Cs}) -> maybe;
+args_list({nil,_L}) -> true;
+args_list({atom,_,_}) -> false;
+args_list({integer,_,_}) -> false;
+args_list({float,_,_}) -> false;
+args_list(_Other) -> maybe.
+
+args_length({cons,_L,_H,T}) -> 1 + args_length(T);
+args_length({nil,_L}) -> 0.
+
+check_format_string(Fmt) ->
+ extract_sequences(Fmt, []).
+
+extract_sequences(Fmt, Need0) ->
+ case string:chr(Fmt, $~) of
+ 0 -> {ok,lists:reverse(Need0)}; %That's it
+ Pos ->
+ Fmt1 = string:substr(Fmt, Pos+1), %Skip ~
+ case extract_sequence(1, Fmt1, Need0) of
+ {ok,Need1,Rest} -> extract_sequences(Rest, Need1);
+ Error -> Error
+ end
+ end.
+
+extract_sequence(1, [$-,C|Fmt], Need) when C >= $0, C =< $9 ->
+ extract_sequence_digits(1, Fmt, Need);
+extract_sequence(1, [C|Fmt], Need) when C >= $0, C =< $9 ->
+ extract_sequence_digits(1, Fmt, Need);
+extract_sequence(1, [$-,$*|Fmt], Need) ->
+ extract_sequence(2, Fmt, [int|Need]);
+extract_sequence(1, [$*|Fmt], Need) ->
+ extract_sequence(2, Fmt, [int|Need]);
+extract_sequence(1, Fmt, Need) ->
+ extract_sequence(2, Fmt, Need);
+extract_sequence(2, [$.,C|Fmt], Need) when C >= $0, C =< $9 ->
+ extract_sequence_digits(2, Fmt, Need);
+extract_sequence(2, [$.,$*|Fmt], Need) ->
+ extract_sequence(3, Fmt, [int|Need]);
+extract_sequence(2, [$.|Fmt], Need) ->
+ extract_sequence(3, Fmt, Need);
+extract_sequence(2, Fmt, Need) ->
+ extract_sequence(4, Fmt, Need);
+extract_sequence(3, [$.,$*|Fmt], Need) ->
+ extract_sequence(4, Fmt, [int|Need]);
+extract_sequence(3, [$.,_|Fmt], Need) ->
+ extract_sequence(4, Fmt, Need);
+extract_sequence(3, Fmt, Need) ->
+ extract_sequence(4, Fmt, Need);
+extract_sequence(4, [$t, $c | Fmt], Need) ->
+ extract_sequence(5, [$c|Fmt], Need);
+extract_sequence(4, [$t, $s | Fmt], Need) ->
+ extract_sequence(5, [$s|Fmt], Need);
+extract_sequence(4, [$t, C | _Fmt], _Need) ->
+ {error,"invalid control ~t" ++ [C]};
+extract_sequence(4, Fmt, Need) ->
+ extract_sequence(5, Fmt, Need);
+extract_sequence(5, [C|Fmt], Need0) ->
+ case control_type(C, Need0) of
+ error -> {error,"invalid control ~" ++ [C]};
+ Need1 -> {ok,Need1,Fmt}
+ end;
+extract_sequence(_, [], _Need) -> {error,"truncated"}.
+
+extract_sequence_digits(Fld, [C|Fmt], Need) when C >= $0, C =< $9 ->
+ extract_sequence_digits(Fld, Fmt, Need);
+extract_sequence_digits(Fld, Fmt, Need) ->
+ extract_sequence(Fld+1, Fmt, Need).
+
+control_type($~, Need) -> Need;
+control_type($c, Need) -> [int|Need];
+control_type($f, Need) -> [float|Need];
+control_type($e, Need) -> [float|Need];
+control_type($g, Need) -> [float|Need];
+control_type($s, Need) -> [string|Need];
+control_type($w, Need) -> [term|Need];
+control_type($p, Need) -> [term|Need];
+control_type($W, Need) -> [int,term|Need]; %% Note: reversed
+control_type($P, Need) -> [int,term|Need]; %% Note: reversed
+control_type($b, Need) -> [term|Need];
+control_type($B, Need) -> [term|Need];
+control_type($x, Need) -> [string,term|Need]; %% Note: reversed
+control_type($X, Need) -> [string,term|Need]; %% Note: reversed
+control_type($+, Need) -> [term|Need];
+control_type($#, Need) -> [term|Need];
+control_type($n, Need) -> Need;
+control_type($i, Need) -> [term|Need];
+control_type(_C, _Need) -> error.
+
+%% In syntax trees, module/package names are atoms or lists of atoms.
+
+package_to_string(A) when is_atom(A) -> atom_to_list(A);
+package_to_string(L) when is_list(L) -> packages:concat(L).
+
+expand_package({atom,L,A} = M, St0) ->
+ St1 = keyword_warning(L, A, St0),
+ case dict:find(A, St1#lint.mod_imports) of
+ {ok, A1} ->
+ {{atom,L,A1}, St1};
+ error ->
+ Name = atom_to_list(A),
+ case packages:is_valid(Name) of
+ true ->
+ case packages:is_segmented(Name) of
+ true ->
+ {M, St1};
+ false ->
+ M1 = packages:concat(St1#lint.package,
+ Name),
+ {{atom,L,list_to_atom(M1)}, St1}
+ end;
+ false ->
+ St2 = add_error(L, {bad_module_name, Name}, St1),
+ {error, St2}
+ end
+ end;
+expand_package(M, St0) ->
+ L = element(2, M),
+ case erl_parse:package_segments(M) of
+ error ->
+ {error, St0};
+ M1 ->
+ Name = package_to_string(M1),
+ case packages:is_valid(Name) of
+ true ->
+ {{atom,L,list_to_atom(Name)}, St0};
+ false ->
+ St1 = add_error(L, {bad_module_name, Name}, St0),
+ {error, St1}
+ end
+ end.
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
new file mode 100644
index 0000000000..fd5d905797
--- /dev/null
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -0,0 +1,1028 @@
+%% -*- erlang -*-
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% Definition of the Erlang grammar.
+
+Nonterminals
+form
+attribute attr_val
+function function_clauses function_clause
+clause_args clause_guard clause_body
+expr expr_100 expr_150 expr_160 expr_200 expr_300 expr_400 expr_500
+expr_600 expr_700 expr_800 expr_900
+expr_max
+list tail
+list_comprehension lc_expr lc_exprs
+binary_comprehension
+tuple
+atom1
+%struct
+record_expr record_tuple record_field record_fields
+if_expr if_clause if_clauses case_expr cr_clause cr_clauses receive_expr
+fun_expr fun_clause fun_clauses
+%% cond_expr cond_clause cond_clauses
+try_expr try_catch try_clause try_clauses query_expr
+function_call argument_list
+exprs guard
+atomic strings
+prefix_op mult_op add_op list_op comp_op
+rule rule_clauses rule_clause rule_body
+binary bin_elements bin_element bit_expr
+opt_bit_size_expr bit_size_expr opt_bit_type_list bit_type_list bit_type
+top_type top_type_100 top_types type typed_expr typed_attr_val
+type_sig type_sigs type_guard type_guards fun_type fun_type_100 binary_type
+type_spec spec_fun typed_exprs typed_record_fields field_types field_type
+bin_base_type bin_unit_type int_type.
+
+Terminals
+char integer float atom string var
+
+'(' ')' ',' '->' ':-' '{' '}' '[' ']' '|' '||' '<-' ';' ':' '#' '.'
+'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'if' 'of' 'receive' 'when'
+'andalso' 'orelse' 'query' 'spec'
+%% 'cond'
+'bnot' 'not'
+'*' '/' 'div' 'rem' 'band' 'and'
+'+' '-' 'bor' 'bxor' 'bsl' 'bsr' 'or' 'xor'
+'++' '--'
+'==' '/=' '=<' '<' '>=' '>' '=:=' '=/=' '<='
+'<<' '>>'
+'!' '=' '::'
+dot.
+
+Expect 2.
+
+Rootsymbol form.
+
+form -> attribute dot : '$1'.
+form -> function dot : '$1'.
+form -> rule dot : '$1'.
+
+attribute -> '-' atom attr_val : build_attribute('$2', '$3').
+attribute -> '-' atom typed_attr_val : build_typed_attribute('$2','$3').
+attribute -> '-' atom '(' typed_attr_val ')' : build_typed_attribute('$2','$4').
+attribute -> '-' 'spec' type_spec : build_type_spec('$2', '$3').
+
+atom1 -> 'spec' : {atom, ?line('$1'), 'spec'}.
+atom1 -> atom : '$1'.
+
+type_spec -> spec_fun type_sigs : {'$1', '$2'}.
+type_spec -> '(' spec_fun type_sigs ')' : {'$2', '$3'}.
+
+spec_fun -> atom1 : '$1'.
+spec_fun -> atom1 ':' atom1 : {'$1', '$3'}.
+%% The following two are retained only for backwards compatibility;
+%% they are not part of the EEP syntax and should be removed.
+spec_fun -> atom1 '/' integer '::' : {'$1', '$3'}.
+spec_fun -> atom1 ':' atom1 '/' integer '::' : {'$1', '$3', '$5'}.
+
+typed_attr_val -> expr ',' typed_record_fields : {typed_record, '$1', '$3'}.
+typed_attr_val -> expr '::' top_type : {type_def, '$1', '$3'}.
+
+typed_record_fields -> '{' typed_exprs '}' : {tuple, ?line('$1'), '$2'}.
+
+typed_exprs -> typed_expr : ['$1'].
+typed_exprs -> typed_expr ',' typed_exprs : ['$1'|'$3'].
+typed_exprs -> expr ',' typed_exprs : ['$1'|'$3'].
+typed_exprs -> typed_expr ',' exprs : ['$1'|'$3'].
+
+typed_expr -> expr '::' top_type : {typed,'$1','$3'}.
+
+type_sigs -> type_sig : ['$1'].
+type_sigs -> type_sig ';' type_sigs : ['$1'|'$3'].
+
+type_sig -> fun_type : '$1'.
+type_sig -> fun_type 'when' type_guards : {type, ?line('$1'), bounded_fun,
+ ['$1','$3']}.
+
+type_guards -> type_guard : ['$1'].
+type_guards -> type_guard ',' type_guards : ['$1'|'$3'].
+
+type_guard -> atom1 '(' top_types ')' : {type, ?line('$1'), constraint,
+ ['$1', '$3']}.
+
+top_types -> top_type : ['$1'].
+top_types -> top_type ',' top_types : ['$1'|'$3'].
+
+top_type -> var '::' top_type_100 : {ann_type, ?line('$1'), ['$1','$3']}.
+top_type -> top_type_100 : '$1'.
+
+top_type_100 -> type : '$1'.
+top_type_100 -> type '|' top_type_100 : lift_unions('$1','$3').
+
+type -> '(' top_type ')' : {paren_type, ?line('$2'), ['$2']}.
+type -> var : '$1'.
+type -> atom1 : '$1'.
+type -> atom1 '(' ')' : build_gen_type('$1').
+type -> atom1 '(' top_types ')' : {type, ?line('$1'),
+ normalise('$1'), '$3'}.
+type -> atom1 ':' atom1 '(' ')' : {remote_type, ?line('$1'),
+ ['$1', '$3', []]}.
+type -> atom1 ':' atom1 '(' top_types ')' : {remote_type, ?line('$1'),
+ ['$1', '$3', '$5']}.
+type -> '[' ']' : {type, ?line('$1'), nil, []}.
+type -> '[' top_type ']' : {type, ?line('$1'), list, ['$2']}.
+type -> '[' top_type ',' '.' '.' '.' ']' : {type, ?line('$1'),
+ nonempty_list, ['$2']}.
+type -> '{' '}' : {type, ?line('$1'), tuple, []}.
+type -> '{' top_types '}' : {type, ?line('$1'), tuple, '$2'}.
+type -> '#' atom1 '{' '}' : {type, ?line('$1'), record, ['$2']}.
+type -> '#' atom1 '{' field_types '}' : {type, ?line('$1'),
+ record, ['$2'|'$4']}.
+type -> binary_type : '$1'.
+type -> int_type : '$1'.
+type -> int_type '.' '.' int_type : {type, ?line('$1'), range,
+ ['$1', '$4']}.
+type -> 'fun' '(' ')' : {type, ?line('$1'), 'fun', []}.
+type -> 'fun' '(' fun_type_100 ')' : '$3'.
+
+int_type -> integer : '$1'.
+int_type -> '-' integer : abstract(-normalise('$2'),
+ ?line('$2')).
+
+fun_type_100 -> '(' '.' '.' '.' ')' '->' top_type
+ : {type, ?line('$1'), 'fun',
+ [{type, ?line('$1'), any}, '$7']}.
+fun_type_100 -> fun_type : '$1'.
+
+fun_type -> '(' ')' '->' top_type : {type, ?line('$1'), 'fun',
+ [{type, ?line('$1'), product, []}, '$4']}.
+fun_type -> '(' top_types ')' '->' top_type
+ : {type, ?line('$1'), 'fun',
+ [{type, ?line('$1'), product, '$2'},'$5']}.
+
+field_types -> field_type : ['$1'].
+field_types -> field_type ',' field_types : ['$1'|'$3'].
+
+field_type -> atom1 '::' top_type : {type, ?line('$1'), field_type,
+ ['$1', '$3']}.
+
+binary_type -> '<<' '>>' : {type, ?line('$1'),binary,
+ [abstract(0, ?line('$1')),
+ abstract(0, ?line('$1'))]}.
+binary_type -> '<<' bin_base_type '>>' : {type, ?line('$1'),binary,
+ ['$2', abstract(0, ?line('$1'))]}.
+binary_type -> '<<' bin_unit_type '>>' : {type, ?line('$1'),binary,
+ [abstract(0, ?line('$1')), '$2']}.
+binary_type -> '<<' bin_base_type ',' bin_unit_type '>>'
+ : {type, ?line('$1'), binary, ['$2', '$4']}.
+
+bin_base_type -> var ':' integer : build_bin_type(['$1'], '$3').
+
+bin_unit_type -> var ':' var '*' integer : build_bin_type(['$1', '$3'], '$5').
+
+attr_val -> expr : ['$1'].
+attr_val -> expr ',' exprs : ['$1' | '$3'].
+attr_val -> '(' expr ',' exprs ')' : ['$2' | '$4'].
+
+function -> function_clauses : build_function('$1').
+
+function_clauses -> function_clause : ['$1'].
+function_clauses -> function_clause ';' function_clauses : ['$1'|'$3'].
+
+function_clause -> atom1 clause_args clause_guard clause_body :
+ {clause,?line('$1'),element(3, '$1'),'$2','$3','$4'}.
+
+
+clause_args -> argument_list : element(1, '$1').
+
+clause_guard -> 'when' guard : '$2'.
+clause_guard -> '$empty' : [].
+
+clause_body -> '->' exprs: '$2'.
+
+
+expr -> 'catch' expr : {'catch',?line('$1'),'$2'}.
+expr -> expr_100 : '$1'.
+
+expr_100 -> expr_150 '=' expr_100 : {match,?line('$2'),'$1','$3'}.
+expr_100 -> expr_150 '!' expr_100 : ?mkop2('$1', '$2', '$3').
+expr_100 -> expr_150 : '$1'.
+
+expr_150 -> expr_160 'orelse' expr_150 : ?mkop2('$1', '$2', '$3').
+expr_150 -> expr_160 : '$1'.
+
+expr_160 -> expr_200 'andalso' expr_160 : ?mkop2('$1', '$2', '$3').
+expr_160 -> expr_200 : '$1'.
+
+expr_200 -> expr_300 comp_op expr_300 :
+ ?mkop2('$1', '$2', '$3').
+expr_200 -> expr_300 : '$1'.
+
+expr_300 -> expr_400 list_op expr_300 :
+ ?mkop2('$1', '$2', '$3').
+expr_300 -> expr_400 : '$1'.
+
+expr_400 -> expr_400 add_op expr_500 :
+ ?mkop2('$1', '$2', '$3').
+expr_400 -> expr_500 : '$1'.
+
+expr_500 -> expr_500 mult_op expr_600 :
+ ?mkop2('$1', '$2', '$3').
+expr_500 -> expr_600 : '$1'.
+
+expr_600 -> prefix_op expr_700 :
+ ?mkop1('$1', '$2').
+expr_600 -> expr_700 : '$1'.
+
+expr_700 -> function_call : '$1'.
+expr_700 -> record_expr : '$1'.
+expr_700 -> expr_800 : '$1'.
+
+expr_800 -> expr_900 ':' expr_max :
+ {remote,?line('$2'),'$1','$3'}.
+expr_800 -> expr_900 : '$1'.
+
+expr_900 -> '.' atom1 :
+ {record_field,?line('$1'),{atom,?line('$1'),''},'$2'}.
+expr_900 -> expr_900 '.' atom1 :
+ {record_field,?line('$2'),'$1','$3'}.
+expr_900 -> expr_max : '$1'.
+
+expr_max -> var : '$1'.
+expr_max -> atomic : '$1'.
+expr_max -> list : '$1'.
+expr_max -> binary : '$1'.
+expr_max -> list_comprehension : '$1'.
+expr_max -> binary_comprehension : '$1'.
+expr_max -> tuple : '$1'.
+%%expr_max -> struct : '$1'.
+expr_max -> '(' expr ')' : '$2'.
+expr_max -> 'begin' exprs 'end' : {block,?line('$1'),'$2'}.
+expr_max -> if_expr : '$1'.
+expr_max -> case_expr : '$1'.
+expr_max -> receive_expr : '$1'.
+expr_max -> fun_expr : '$1'.
+%%expr_max -> cond_expr : '$1'.
+expr_max -> try_expr : '$1'.
+expr_max -> query_expr : '$1'.
+
+
+list -> '[' ']' : {nil,?line('$1')}.
+list -> '[' expr tail : {cons,?line('$1'),'$2','$3'}.
+
+tail -> ']' : {nil,?line('$1')}.
+tail -> '|' expr ']' : '$2'.
+tail -> ',' expr tail : {cons,?line('$2'),'$2','$3'}.
+
+
+binary -> '<<' '>>' : {bin,?line('$1'),[]}.
+binary -> '<<' bin_elements '>>' : {bin,?line('$1'),'$2'}.
+
+bin_elements -> bin_element : ['$1'].
+bin_elements -> bin_element ',' bin_elements : ['$1'|'$3'].
+
+bin_element -> bit_expr opt_bit_size_expr opt_bit_type_list :
+ {bin_element,?line('$1'),'$1','$2','$3'}.
+
+bit_expr -> prefix_op expr_max : ?mkop1('$1', '$2').
+bit_expr -> expr_max : '$1'.
+
+opt_bit_size_expr -> ':' bit_size_expr : '$2'.
+opt_bit_size_expr -> '$empty' : default.
+
+opt_bit_type_list -> '/' bit_type_list : '$2'.
+opt_bit_type_list -> '$empty' : default.
+
+bit_type_list -> bit_type '-' bit_type_list : ['$1' | '$3'].
+bit_type_list -> bit_type : ['$1'].
+
+bit_type -> atom1 : element(3,'$1').
+bit_type -> atom1 ':' integer : { element(3,'$1'), element(3,'$3') }.
+
+bit_size_expr -> expr_max : '$1'.
+
+
+list_comprehension -> '[' expr '||' lc_exprs ']' :
+ {lc,?line('$1'),'$2','$4'}.
+binary_comprehension -> '<<' binary '||' lc_exprs '>>' :
+ {bc,?line('$1'),'$2','$4'}.
+lc_exprs -> lc_expr : ['$1'].
+lc_exprs -> lc_expr ',' lc_exprs : ['$1'|'$3'].
+
+lc_expr -> expr : '$1'.
+lc_expr -> expr '<-' expr : {generate,?line('$2'),'$1','$3'}.
+lc_expr -> binary '<=' expr : {b_generate,?line('$2'),'$1','$3'}.
+
+tuple -> '{' '}' : {tuple,?line('$1'),[]}.
+tuple -> '{' exprs '}' : {tuple,?line('$1'),'$2'}.
+
+
+%%struct -> atom1 tuple :
+%% {struct,?line('$1'),element(3, '$1'),element(3, '$2')}.
+
+
+%% N.B. This is called from expr_700.
+%% N.B. Field names are returned as the complete object, even if they are
+%% always atoms for the moment, this might change in the future.
+
+record_expr -> '#' atom1 '.' atom1 :
+ {record_index,?line('$1'),element(3, '$2'),'$4'}.
+record_expr -> '#' atom1 record_tuple :
+ {record,?line('$1'),element(3, '$2'),'$3'}.
+record_expr -> expr_max '#' atom1 '.' atom1 :
+ {record_field,?line('$2'),'$1',element(3, '$3'),'$5'}.
+record_expr -> expr_max '#' atom1 record_tuple :
+ {record,?line('$2'),'$1',element(3, '$3'),'$4'}.
+
+record_tuple -> '{' '}' : [].
+record_tuple -> '{' record_fields '}' : '$2'.
+
+record_fields -> record_field : ['$1'].
+record_fields -> record_field ',' record_fields : ['$1' | '$3'].
+
+record_field -> var '=' expr : {record_field,?line('$1'),'$1','$3'}.
+record_field -> atom1 '=' expr : {record_field,?line('$1'),'$1','$3'}.
+
+%% N.B. This is called from expr_700.
+
+function_call -> expr_800 argument_list :
+ {call,?line('$1'),'$1',element(1, '$2')}.
+
+
+if_expr -> 'if' if_clauses 'end' : {'if',?line('$1'),'$2'}.
+
+if_clauses -> if_clause : ['$1'].
+if_clauses -> if_clause ';' if_clauses : ['$1' | '$3'].
+
+if_clause -> guard clause_body :
+ {clause,?line(hd(hd('$1'))),[],'$1','$2'}.
+
+
+case_expr -> 'case' expr 'of' cr_clauses 'end' :
+ {'case',?line('$1'),'$2','$4'}.
+
+cr_clauses -> cr_clause : ['$1'].
+cr_clauses -> cr_clause ';' cr_clauses : ['$1' | '$3'].
+
+cr_clause -> expr clause_guard clause_body :
+ {clause,?line('$1'),['$1'],'$2','$3'}.
+
+receive_expr -> 'receive' cr_clauses 'end' :
+ {'receive',?line('$1'),'$2'}.
+receive_expr -> 'receive' 'after' expr clause_body 'end' :
+ {'receive',?line('$1'),[],'$3','$4'}.
+receive_expr -> 'receive' cr_clauses 'after' expr clause_body 'end' :
+ {'receive',?line('$1'),'$2','$4','$5'}.
+
+
+fun_expr -> 'fun' atom1 '/' integer :
+ {'fun',?line('$1'),{function,element(3, '$2'),element(3, '$4')}}.
+fun_expr -> 'fun' atom1 ':' atom1 '/' integer :
+ {'fun',?line('$1'),{function,element(3, '$2'),element(3, '$4'),element(3,'$6')}}.
+fun_expr -> 'fun' fun_clauses 'end' :
+ build_fun(?line('$1'), '$2').
+
+fun_clauses -> fun_clause : ['$1'].
+fun_clauses -> fun_clause ';' fun_clauses : ['$1' | '$3'].
+
+fun_clause -> argument_list clause_guard clause_body :
+ {Args,Pos} = '$1',
+ {clause,Pos,'fun',Args,'$2','$3'}.
+
+try_expr -> 'try' exprs 'of' cr_clauses try_catch :
+ build_try(?line('$1'),'$2','$4','$5').
+try_expr -> 'try' exprs try_catch :
+ build_try(?line('$1'),'$2',[],'$3').
+
+try_catch -> 'catch' try_clauses 'end' :
+ {'$2',[]}.
+try_catch -> 'catch' try_clauses 'after' exprs 'end' :
+ {'$2','$4'}.
+try_catch -> 'after' exprs 'end' :
+ {[],'$2'}.
+
+try_clauses -> try_clause : ['$1'].
+try_clauses -> try_clause ';' try_clauses : ['$1' | '$3'].
+
+try_clause -> expr clause_guard clause_body :
+ L = ?line('$1'),
+ {clause,L,[{tuple,L,[{atom,L,throw},'$1',{var,L,'_'}]}],'$2','$3'}.
+try_clause -> atom1 ':' expr clause_guard clause_body :
+ L = ?line('$1'),
+ {clause,L,[{tuple,L,['$1','$3',{var,L,'_'}]}],'$4','$5'}.
+try_clause -> var ':' expr clause_guard clause_body :
+ L = ?line('$1'),
+ {clause,L,[{tuple,L,['$1','$3',{var,L,'_'}]}],'$4','$5'}.
+
+%%cond_expr -> 'cond' cond_clauses 'end' : {'cond',?line('$1'),'$2'}.
+
+%%cond_clauses -> cond_clause : ['$1'].
+%%cond_clauses -> cond_clause ';' cond_clauses : ['$1' | '$3'].
+
+%%cond_clause -> expr clause_body :
+%% {clause,?line('$1'),[],[['$1']],'$2'}.
+
+query_expr -> 'query' list_comprehension 'end' :
+ {'query',?line('$1'),'$2'}.
+
+
+argument_list -> '(' ')' : {[],?line('$1')}.
+argument_list -> '(' exprs ')' : {'$2',?line('$1')}.
+
+
+exprs -> expr : ['$1'].
+exprs -> expr ',' exprs : ['$1' | '$3'].
+
+guard -> exprs : ['$1'].
+guard -> exprs ';' guard : ['$1'|'$3'].
+
+atomic -> char : '$1'.
+atomic -> integer : '$1'.
+atomic -> float : '$1'.
+atomic -> atom1 : '$1'.
+atomic -> strings : '$1'.
+
+strings -> string : '$1'.
+strings -> string strings :
+ {string,?line('$1'),element(3, '$1') ++ element(3, '$2')}.
+
+prefix_op -> '+' : '$1'.
+prefix_op -> '-' : '$1'.
+prefix_op -> 'bnot' : '$1'.
+prefix_op -> 'not' : '$1'.
+
+mult_op -> '/' : '$1'.
+mult_op -> '*' : '$1'.
+mult_op -> 'div' : '$1'.
+mult_op -> 'rem' : '$1'.
+mult_op -> 'band' : '$1'.
+mult_op -> 'and' : '$1'.
+
+add_op -> '+' : '$1'.
+add_op -> '-' : '$1'.
+add_op -> 'bor' : '$1'.
+add_op -> 'bxor' : '$1'.
+add_op -> 'bsl' : '$1'.
+add_op -> 'bsr' : '$1'.
+add_op -> 'or' : '$1'.
+add_op -> 'xor' : '$1'.
+
+list_op -> '++' : '$1'.
+list_op -> '--' : '$1'.
+
+comp_op -> '==' : '$1'.
+comp_op -> '/=' : '$1'.
+comp_op -> '=<' : '$1'.
+comp_op -> '<' : '$1'.
+comp_op -> '>=' : '$1'.
+comp_op -> '>' : '$1'.
+comp_op -> '=:=' : '$1'.
+comp_op -> '=/=' : '$1'.
+
+rule -> rule_clauses : build_rule('$1').
+
+rule_clauses -> rule_clause : ['$1'].
+rule_clauses -> rule_clause ';' rule_clauses : ['$1'|'$3'].
+
+rule_clause -> atom1 clause_args clause_guard rule_body :
+ {clause,?line('$1'),element(3, '$1'),'$2','$3','$4'}.
+
+rule_body -> ':-' lc_exprs: '$2'.
+
+
+Erlang code.
+
+-export([parse_form/1,parse_exprs/1,parse_term/1]).
+-export([normalise/1,abstract/1,tokens/1,tokens/2]).
+-export([abstract/2, package_segments/1]).
+-export([inop_prec/1,preop_prec/1,func_prec/0,max_prec/0]).
+-export([set_line/2,get_attribute/2,get_attributes/1]).
+
+%% The following directive is needed for (significantly) faster compilation
+%% of the generated .erl file by the HiPE compiler. Please do not remove.
+-compile([{hipe,[{regalloc,linear_scan}]}]).
+
+
+%% mkop(Op, Arg) -> {op,Line,Op,Arg}.
+%% mkop(Left, Op, Right) -> {op,Line,Op,Left,Right}.
+
+-define(mkop2(L, OpPos, R),
+ begin
+ {Op,Pos} = OpPos,
+ {op,Pos,Op,L,R}
+ end).
+
+-define(mkop1(OpPos, A),
+ begin
+ {Op,Pos} = OpPos,
+ {op,Pos,Op,A}
+ end).
+
+%% keep track of line info in tokens
+-define(line(Tup), element(2, Tup)).
+
+%% Entry points compatible to old erl_parse.
+%% These really suck and are only here until Calle gets multiple
+%% entry points working.
+
+parse_form(Tokens) ->
+ parse(Tokens).
+
+parse_exprs(Tokens) ->
+ case parse([{atom,0,f},{'(',0},{')',0},{'->',0}|Tokens]) of
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],Exprs}]}} ->
+ {ok,Exprs};
+ {error,_} = Err -> Err
+ end.
+
+parse_term(Tokens) ->
+ case parse([{atom,0,f},{'(',0},{')',0},{'->',0}|Tokens]) of
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],[Expr]}]}} ->
+ try normalise(Expr) of
+ Term -> {ok,Term}
+ catch
+ _:_R -> {error,{?line(Expr),?MODULE,"bad term"}}
+ end;
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],[_E1,E2|_Es]}]}} ->
+ {error,{?line(E2),?MODULE,"bad term"}};
+ {error,_} = Err -> Err
+ end.
+
+-type attributes() :: 'export' | 'file' | 'import' | 'module'
+ | 'opaque' | 'record' | 'type'.
+
+build_typed_attribute({atom,La,record},
+ {typed_record, {atom,_Ln,RecordName}, RecTuple}) ->
+ {attribute,La,record,{RecordName,record_tuple(RecTuple)}};
+build_typed_attribute({atom,La,Attr},
+ {type_def, {call,_,{atom,_,TypeName},Args}, Type})
+ when Attr =:= 'type' ; Attr =:= 'opaque' ->
+ case lists:all(fun({var, _, _}) -> true;
+ (_) -> false
+ end, Args) of
+ true -> {attribute,La,Attr,{TypeName,Type,Args}};
+ false -> error_bad_decl(La, Attr)
+ end;
+build_typed_attribute({atom,La,Attr},_) ->
+ case Attr of
+ record -> error_bad_decl(La, record);
+ type -> error_bad_decl(La, type);
+ opaque -> error_bad_decl(La, opaque);
+ _ -> ret_err(La, "bad attribute")
+ end.
+
+build_type_spec({spec,La}, {SpecFun, TypeSpecs}) ->
+ NewSpecFun =
+ case SpecFun of
+ {atom, _, Fun} ->
+ {Fun, find_arity_from_specs(TypeSpecs)};
+ {{atom,_, Mod}, {atom,_, Fun}} ->
+ {Mod,Fun,find_arity_from_specs(TypeSpecs)};
+ {{atom, _, Fun}, {integer, _, Arity}} ->
+ %% Old style spec. Allow this for now.
+ {Fun,Arity};
+ {{atom,_, Mod}, {atom, _, Fun}, {integer, _, Arity}} ->
+ %% Old style spec. Allow this for now.
+ {Mod,Fun,Arity}
+ end,
+ {attribute,La,spec,{NewSpecFun, TypeSpecs}}.
+
+find_arity_from_specs([Spec|_]) ->
+ %% Use the first spec to find the arity. If all are not the same,
+ %% erl_lint will find this.
+ Fun = case Spec of
+ {type, _, bounded_fun, [F, _]} -> F;
+ {type, _, 'fun', _} = F -> F
+ end,
+ {type, _, 'fun', [{type, _, product, Args},_]} = Fun,
+ length(Args).
+
+lift_unions(T1, {type, _La, union, List}) ->
+ {type, ?line(T1), union, [T1|List]};
+lift_unions(T1, T2) ->
+ {type, ?line(T1), union, [T1, T2]}.
+
+build_gen_type({atom, La, tuple}) ->
+ {type, La, tuple, any};
+build_gen_type({atom, La, Name}) ->
+ {type, La, Name, []}.
+
+build_bin_type([{var, _, '_'}|Left], Int) ->
+ build_bin_type(Left, Int);
+build_bin_type([], Int) ->
+ Int;
+build_bin_type([{var, La, _}|_], _) ->
+ ret_err(La, "Bad binary type").
+
+%% build_attribute(AttrName, AttrValue) ->
+%% {attribute,Line,module,Module}
+%% {attribute,Line,export,Exports}
+%% {attribute,Line,import,Imports}
+%% {attribute,Line,record,{Name,Inits}}
+%% {attribute,Line,file,{Name,Line}}
+%% {attribute,Line,Name,Val}
+
+build_attribute({atom,La,module}, Val) ->
+ case Val of
+ [{atom,_Lm,Module}] ->
+ {attribute,La,module,Module};
+ [{atom,_Lm,Module},ExpList] ->
+ {attribute,La,module,{Module,var_list(ExpList)}};
+ [Name] ->
+ case package_segments(Name) of
+ error ->
+ error_bad_decl(La, module);
+ Module ->
+ {attribute,La,module,Module}
+ end;
+ [Name,ExpList] ->
+ case package_segments(Name) of
+ error ->
+ error_bad_decl(La, module);
+ Module ->
+ {attribute,La,module,{Module,var_list(ExpList)}}
+ end;
+ _Other ->
+ error_bad_decl(La, module)
+ end;
+build_attribute({atom,La,export}, Val) ->
+ case Val of
+ [ExpList] ->
+ {attribute,La,export,farity_list(ExpList)};
+ _Other -> error_bad_decl(La, export)
+ end;
+build_attribute({atom,La,import}, Val) ->
+ case Val of
+ [Name] ->
+ case package_segments(Name) of
+ error ->
+ error_bad_decl(La, import);
+ Module ->
+ {attribute,La,import,Module}
+ end;
+ [{atom,_Lm,Mod},ImpList] ->
+ {attribute,La,import,{Mod,farity_list(ImpList)}};
+ [Name, ImpList] ->
+ case package_segments(Name) of
+ error ->
+ error_bad_decl(La, import);
+ Module ->
+ {attribute,La,import,{Module,farity_list(ImpList)}}
+ end;
+ _Other -> error_bad_decl(La, import)
+ end;
+build_attribute({atom,La,record}, Val) ->
+ case Val of
+ [{atom,_Ln,Record},RecTuple] ->
+ {attribute,La,record,{Record,record_tuple(RecTuple)}};
+ _Other -> error_bad_decl(La, record)
+ end;
+build_attribute({atom,La,file}, Val) ->
+ case Val of
+ [{string,_Ln,Name},{integer,_Ll,Line}] ->
+ {attribute,La,file,{Name,Line}};
+ _Other -> error_bad_decl(La, file)
+ end;
+build_attribute({atom,La,Attr}, Val) ->
+ case Val of
+ [Expr0] ->
+ Expr = attribute_farity(Expr0),
+ {attribute,La,Attr,term(Expr)};
+ _Other -> ret_err(La, "bad attribute")
+ end.
+
+var_list({cons,_Lc,{var,_,V},Tail}) ->
+ [V|var_list(Tail)];
+var_list({nil,_Ln}) -> [];
+var_list(Other) ->
+ ret_err(?line(Other), "bad variable list").
+
+attribute_farity({cons,L,H,T}) ->
+ {cons,L,attribute_farity(H),attribute_farity(T)};
+attribute_farity({tuple,L,Args0}) ->
+ Args = attribute_farity_list(Args0),
+ {tuple,L,Args};
+attribute_farity({op,L,'/',{atom,_,_}=Name,{integer,_,_}=Arity}) ->
+ {tuple,L,[Name,Arity]};
+attribute_farity(Other) -> Other.
+
+attribute_farity_list(Args) ->
+ [attribute_farity(A) || A <- Args].
+
+-spec error_bad_decl(integer(), attributes()) -> no_return().
+
+error_bad_decl(L, S) ->
+ ret_err(L, io_lib:format("bad ~w declaration", [S])).
+
+farity_list({cons,_Lc,{op,_Lo,'/',{atom,_La,A},{integer,_Li,I}},Tail}) ->
+ [{A,I}|farity_list(Tail)];
+farity_list({nil,_Ln}) -> [];
+farity_list(Other) ->
+ ret_err(?line(Other), "bad function arity").
+
+record_tuple({tuple,_Lt,Fields}) ->
+ record_fields(Fields);
+record_tuple(Other) ->
+ ret_err(?line(Other), "bad record declaration").
+
+record_fields([{atom,La,A}|Fields]) ->
+ [{record_field,La,{atom,La,A}}|record_fields(Fields)];
+record_fields([{match,_Lm,{atom,La,A},Expr}|Fields]) ->
+ [{record_field,La,{atom,La,A},Expr}|record_fields(Fields)];
+record_fields([{typed,Expr,TypeInfo}|Fields]) ->
+ [Field] = record_fields([Expr]),
+ TypeInfo1 =
+ case Expr of
+ {match, _, _, _} -> TypeInfo; %% If we have an initializer.
+ {atom, La, _} ->
+ lift_unions(abstract(undefined, La), TypeInfo)
+ end,
+ [{typed_record_field,Field,TypeInfo1}|record_fields(Fields)];
+record_fields([Other|_Fields]) ->
+ ret_err(?line(Other), "bad record field");
+record_fields([]) -> [].
+
+term(Expr) ->
+ try normalise(Expr)
+ catch _:_R -> ret_err(?line(Expr), "bad attribute")
+ end.
+
+package_segments(Name) ->
+ package_segments(Name, [], []).
+
+package_segments({record_field, _, F1, F2}, Fs, As) ->
+ package_segments(F1, [F2 | Fs], As);
+package_segments({atom, _, A}, [F | Fs], As) ->
+ package_segments(F, Fs, [A | As]);
+package_segments({atom, _, A}, [], As) ->
+ lists:reverse([A | As]);
+package_segments(_, _, _) ->
+ error.
+
+%% build_function([Clause]) -> {function,Line,Name,Arity,[Clause]}
+
+build_function(Cs) ->
+ Name = element(3, hd(Cs)),
+ Arity = length(element(4, hd(Cs))),
+ {function,?line(hd(Cs)),Name,Arity,check_clauses(Cs, Name, Arity)}.
+
+%% build_rule([Clause]) -> {rule,Line,Name,Arity,[Clause]'}
+
+build_rule(Cs) ->
+ Name = element(3, hd(Cs)),
+ Arity = length(element(4, hd(Cs))),
+ {rule,?line(hd(Cs)),Name,Arity,check_clauses(Cs, Name, Arity)}.
+
+%% build_fun(Line, [Clause]) -> {'fun',Line,{clauses,[Clause]}}.
+
+build_fun(Line, Cs) ->
+ Arity = length(element(4, hd(Cs))),
+ {'fun',Line,{clauses,check_clauses(Cs, 'fun', Arity)}}.
+
+check_clauses(Cs, Name, Arity) ->
+ mapl(fun ({clause,L,N,As,G,B}) when N =:= Name, length(As) =:= Arity ->
+ {clause,L,As,G,B};
+ ({clause,L,_N,_As,_G,_B}) ->
+ ret_err(L, "head mismatch") end, Cs).
+
+build_try(L,Es,Scs,{Ccs,As}) ->
+ {'try',L,Es,Scs,Ccs,As}.
+
+ret_err(L, S) ->
+ {location,Location} = get_attribute(L, location),
+ return_error(Location, S).
+
+%% mapl(F,List)
+%% an alternative map which always maps from left to right
+%% and makes it possible to interrupt the mapping with throw on
+%% the first occurence from left as expected.
+%% can be removed when the jam machine (and all other machines)
+%% uses the standardized (Erlang 5.0) evaluation order (from left to right)
+mapl(F, [H|T]) ->
+ V = F(H),
+ [V | mapl(F,T)];
+mapl(_, []) ->
+ [].
+
+%% normalise(AbsTerm)
+%% abstract(Term)
+%% Convert between the abstract form of a term and a term.
+
+normalise({char,_,C}) -> C;
+normalise({integer,_,I}) -> I;
+normalise({float,_,F}) -> F;
+normalise({atom,_,A}) -> A;
+normalise({string,_,S}) -> S;
+normalise({nil,_}) -> [];
+normalise({bin,_,Fs}) ->
+ {value, B, _} =
+ eval_bits:expr_grp(Fs, [],
+ fun(E, _) ->
+ {value, normalise(E), []}
+ end, [], true),
+ B;
+normalise({cons,_,Head,Tail}) ->
+ [normalise(Head)|normalise(Tail)];
+normalise({tuple,_,Args}) ->
+ list_to_tuple(normalise_list(Args));
+%% Atom dot-notation, as in 'foo.bar.baz'
+normalise({record_field,_,_,_}=A) ->
+ case package_segments(A) of
+ error -> erlang:error({badarg, A});
+ As -> list_to_atom(packages:concat(As))
+ end;
+%% Special case for unary +/-.
+normalise({op,_,'+',{char,_,I}}) -> I;
+normalise({op,_,'+',{integer,_,I}}) -> I;
+normalise({op,_,'+',{float,_,F}}) -> F;
+normalise({op,_,'-',{char,_,I}}) -> -I; %Weird, but compatible!
+normalise({op,_,'-',{integer,_,I}}) -> -I;
+normalise({op,_,'-',{float,_,F}}) -> -F;
+normalise(X) -> erlang:error({badarg, X}).
+
+normalise_list([H|T]) ->
+ [normalise(H)|normalise_list(T)];
+normalise_list([]) ->
+ [].
+
+abstract(T) when is_integer(T) -> {integer,0,T};
+abstract(T) when is_float(T) -> {float,0,T};
+abstract(T) when is_atom(T) -> {atom,0,T};
+abstract([]) -> {nil,0};
+abstract(B) when is_bitstring(B) ->
+ {bin, 0, [abstract_byte(Byte, 0) || Byte <- bitstring_to_list(B)]};
+abstract([C|T]) when is_integer(C), 0 =< C, C < 256 ->
+ abstract_string(T, [C]);
+abstract([H|T]) ->
+ {cons,0,abstract(H),abstract(T)};
+abstract(Tuple) when is_tuple(Tuple) ->
+ {tuple,0,abstract_list(tuple_to_list(Tuple))}.
+
+abstract_string([C|T], String) when is_integer(C), 0 =< C, C < 256 ->
+ abstract_string(T, [C|String]);
+abstract_string([], String) ->
+ {string, 0, lists:reverse(String)};
+abstract_string(T, String) ->
+ not_string(String, abstract(T)).
+
+not_string([C|T], Result) ->
+ not_string(T, {cons, 0, {integer, 0, C}, Result});
+not_string([], Result) ->
+ Result.
+
+abstract_list([H|T]) ->
+ [abstract(H)|abstract_list(T)];
+abstract_list([]) ->
+ [].
+
+abstract_byte(Byte, Line) when is_integer(Byte) ->
+ {bin_element, Line, {integer, Line, Byte}, default, default};
+abstract_byte(Bits, Line) ->
+ Sz = bit_size(Bits),
+ <<Val:Sz>> = Bits,
+ {bin_element, Line, {integer, Line, Val}, {integer, Line, Sz}, default}.
+
+%%% abstract/2 keeps the line number
+abstract(T, Line) when is_integer(T) -> {integer,Line,T};
+abstract(T, Line) when is_float(T) -> {float,Line,T};
+abstract(T, Line) when is_atom(T) -> {atom,Line,T};
+abstract([], Line) -> {nil,Line};
+abstract(B, Line) when is_bitstring(B) ->
+ {bin, Line, [abstract_byte(Byte, Line) || Byte <- bitstring_to_list(B)]};
+abstract([C|T], Line) when is_integer(C), 0 =< C, C < 256 ->
+ abstract_string(T, [C], Line);
+abstract([H|T], Line) ->
+ {cons,Line,abstract(H, Line),abstract(T, Line)};
+abstract(Tuple, Line) when is_tuple(Tuple) ->
+ {tuple,Line,abstract_list(tuple_to_list(Tuple), Line)}.
+
+abstract_string([C|T], String, Line) when is_integer(C), 0 =< C, C < 256 ->
+ abstract_string(T, [C|String], Line);
+abstract_string([], String, Line) ->
+ {string, Line, lists:reverse(String)};
+abstract_string(T, String, Line) ->
+ not_string(String, abstract(T, Line), Line).
+
+not_string([C|T], Result, Line) ->
+ not_string(T, {cons, Line, {integer, Line, C}, Result}, Line);
+not_string([], Result, _Line) ->
+ Result.
+
+abstract_list([H|T], Line) ->
+ [abstract(H, Line)|abstract_list(T, Line)];
+abstract_list([], _Line) ->
+ [].
+
+%% tokens(AbsTerm) -> [Token]
+%% tokens(AbsTerm, More) -> [Token]
+%% Generate a list of tokens representing the abstract term.
+
+tokens(Abs) ->
+ tokens(Abs, []).
+
+tokens({char,L,C}, More) -> [{char,L,C}|More];
+tokens({integer,L,N}, More) -> [{integer,L,N}|More];
+tokens({float,L,F}, More) -> [{float,L,F}|More];
+tokens({atom,L,A}, More) -> [{atom,L,A}|More];
+tokens({var,L,V}, More) -> [{var,L,V}|More];
+tokens({string,L,S}, More) -> [{string,L,S}|More];
+tokens({nil,L}, More) -> [{'[',L},{']',L}|More];
+tokens({cons,L,Head,Tail}, More) ->
+ [{'[',L}|tokens(Head, tokens_tail(Tail, More))];
+tokens({tuple,L,[]}, More) ->
+ [{'{',L},{'}',L}|More];
+tokens({tuple,L,[E|Es]}, More) ->
+ [{'{',L}|tokens(E, tokens_tuple(Es, ?line(E), More))].
+
+tokens_tail({cons,L,Head,Tail}, More) ->
+ [{',',L}|tokens(Head, tokens_tail(Tail, More))];
+tokens_tail({nil,L}, More) ->
+ [{']',L}|More];
+tokens_tail(Other, More) ->
+ L = ?line(Other),
+ [{'|',L}|tokens(Other, [{']',L}|More])].
+
+tokens_tuple([E|Es], Line, More) ->
+ [{',',Line}|tokens(E, tokens_tuple(Es, ?line(E), More))];
+tokens_tuple([], Line, More) ->
+ [{'}',Line}|More].
+
+%% Give the relative precedences of operators.
+
+inop_prec('=') -> {150,100,100};
+inop_prec('!') -> {150,100,100};
+inop_prec('orelse') -> {160,150,150};
+inop_prec('andalso') -> {200,160,160};
+inop_prec('==') -> {300,200,300};
+inop_prec('/=') -> {300,200,300};
+inop_prec('=<') -> {300,200,300};
+inop_prec('<') -> {300,200,300};
+inop_prec('>=') -> {300,200,300};
+inop_prec('>') -> {300,200,300};
+inop_prec('=:=') -> {300,200,300};
+inop_prec('=/=') -> {300,200,300};
+inop_prec('++') -> {400,300,300};
+inop_prec('--') -> {400,300,300};
+inop_prec('+') -> {400,400,500};
+inop_prec('-') -> {400,400,500};
+inop_prec('bor') -> {400,400,500};
+inop_prec('bxor') -> {400,400,500};
+inop_prec('bsl') -> {400,400,500};
+inop_prec('bsr') -> {400,400,500};
+inop_prec('or') -> {400,400,500};
+inop_prec('xor') -> {400,400,500};
+inop_prec('*') -> {500,500,600};
+inop_prec('/') -> {500,500,600};
+inop_prec('div') -> {500,500,600};
+inop_prec('rem') -> {500,500,600};
+inop_prec('band') -> {500,500,600};
+inop_prec('and') -> {500,500,600};
+inop_prec('#') -> {800,700,800};
+inop_prec(':') -> {900,800,900};
+inop_prec('.') -> {900,900,1000}.
+
+-type pre_op() :: 'catch' | '+' | '-' | 'bnot' | '#'.
+
+-spec preop_prec(pre_op()) -> {0 | 600 | 700, 100 | 700 | 800}.
+
+preop_prec('catch') -> {0,100};
+preop_prec('+') -> {600,700};
+preop_prec('-') -> {600,700};
+preop_prec('bnot') -> {600,700};
+preop_prec('not') -> {600,700};
+preop_prec('#') -> {700,800}.
+
+-spec func_prec() -> {800,700}.
+
+func_prec() -> {800,700}.
+
+-spec max_prec() -> 1000.
+
+max_prec() -> 1000.
+
+%%% [Experimental]. The parser just copies the attributes of the
+%%% scanner tokens to the abstract format. This design decision has
+%%% been hidden to some extent: use set_line() and get_attribute() to
+%%% access the second element of (almost all) of the abstract format
+%%% tuples. A typical use is to negate line numbers to prevent the
+%%% compiler from emitting warnings and errors. The second element can
+%%% (of course) be set to any value, but then these functions no
+%%% longer apply. To get all present attributes as a property list
+%%% get_attributes() should be used.
+
+set_line(L, F) ->
+ erl_scan:set_attribute(line, L, F).
+
+get_attribute(L, Name) ->
+ erl_scan:attributes_info(L, Name).
+
+get_attributes(L) ->
+ erl_scan:attributes_info(L).
diff --git a/lib/stdlib/src/erl_posix_msg.erl b/lib/stdlib/src/erl_posix_msg.erl
new file mode 100644
index 0000000000..fe981b23a7
--- /dev/null
+++ b/lib/stdlib/src/erl_posix_msg.erl
@@ -0,0 +1,166 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_posix_msg).
+
+%% Converts from errno identifiers to error messages.
+
+-export([message/1]).
+
+-spec message(atom()) -> string().
+
+message(e2big) -> "argument list too long";
+message(eacces) -> "permission denied";
+message(eaddrinuse) -> "address already in use";
+message(eaddrnotavail) -> "can't assign requested address";
+message(eadv) -> "advertise error";
+message(eafnosupport) -> "address family not supported by protocol family";
+message(eagain) -> "resource temporarily unavailable";
+message(ealign) -> "EALIGN";
+message(ealready) -> "operation already in progress";
+message(ebade) -> "bad exchange descriptor";
+message(ebadf) -> "bad file number";
+message(ebadfd) -> "file descriptor in bad state";
+message(ebadmsg) -> "not a data message";
+message(ebadr) -> "bad request descriptor";
+message(ebadrpc) -> "RPC structure is bad";
+message(ebadrqc) -> "bad request code";
+message(ebadslt) -> "invalid slot";
+message(ebfont) -> "bad font file format";
+message(ebusy) -> "file busy";
+message(echild) -> "no children";
+message(echrng) -> "channel number out of range";
+message(ecomm) -> "communication error on send";
+message(econnaborted) -> "software caused connection abort";
+message(econnrefused) -> "connection refused";
+message(econnreset) -> "connection reset by peer";
+message(edeadlk) -> "resource deadlock avoided";
+message(edeadlock) -> "resource deadlock avoided";
+message(edestaddrreq) -> "destination address required";
+message(edirty) -> "mounting a dirty fs w/o force";
+message(edom) -> "math argument out of range";
+message(edotdot) -> "cross mount point";
+message(edquot) -> "disk quota exceeded";
+message(eduppkg) -> "duplicate package name";
+message(eexist) -> "file already exists";
+message(efault) -> "bad address in system call argument";
+message(efbig) -> "file too large";
+message(ehostdown) -> "host is down";
+message(ehostunreach) -> "host is unreachable";
+message(eidrm) -> "identifier removed";
+message(einit) -> "initialization error";
+message(einprogress) -> "operation now in progress";
+message(eintr) -> "interrupted system call";
+message(einval) -> "invalid argument";
+message(eio) -> "I/O error";
+message(eisconn) -> "socket is already connected";
+message(eisdir) -> "illegal operation on a directory";
+message(eisnam) -> "is a name file";
+message(elbin) -> "ELBIN";
+message(el2hlt) -> "level 2 halted";
+message(el2nsync) -> "level 2 not synchronized";
+message(el3hlt) -> "level 3 halted";
+message(el3rst) -> "level 3 reset";
+message(elibacc) -> "can not access a needed shared library";
+message(elibbad) -> "accessing a corrupted shared library";
+message(elibexec) -> "can not exec a shared library directly";
+message(elibmax) ->
+ "attempting to link in more shared libraries than system limit";
+message(elibscn) -> ".lib section in a.out corrupted";
+message(elnrng) -> "link number out of range";
+message(eloop) -> "too many levels of symbolic links";
+message(emfile) -> "too many open files";
+message(emlink) -> "too many links";
+message(emsgsize) -> "message too long";
+message(emultihop) -> "multihop attempted";
+message(enametoolong) -> "file name too long";
+message(enavail) -> "not available";
+message(enet) -> "ENET";
+message(enetdown) -> "network is down";
+message(enetreset) -> "network dropped connection on reset";
+message(enetunreach) -> "network is unreachable";
+message(enfile) -> "file table overflow";
+message(enoano) -> "anode table overflow";
+message(enobufs) -> "no buffer space available";
+message(enocsi) -> "no CSI structure available";
+message(enodata) -> "no data available";
+message(enodev) -> "no such device";
+message(enoent) -> "no such file or directory";
+message(enoexec) -> "exec format error";
+message(enolck) -> "no locks available";
+message(enolink) -> "link has be severed";
+message(enomem) -> "not enough memory";
+message(enomsg) -> "no message of desired type";
+message(enonet) -> "machine is not on the network";
+message(enopkg) -> "package not installed";
+message(enoprotoopt) -> "bad proocol option";
+message(enospc) -> "no space left on device";
+message(enosr) -> "out of stream resources or not a stream device";
+message(enosym) -> "unresolved symbol name";
+message(enosys) -> "function not implemented";
+message(enotblk) -> "block device required";
+message(enotconn) -> "socket is not connected";
+message(enotdir) -> "not a directory";
+message(enotempty) -> "directory not empty";
+message(enotnam) -> "not a name file";
+message(enotsock) -> "socket operation on non-socket";
+message(enotsup) -> "operation not supported";
+message(enotty) -> "inappropriate device for ioctl";
+message(enotuniq) -> "name not unique on network";
+message(enxio) -> "no such device or address";
+message(eopnotsupp) -> "operation not supported on socket";
+message(eperm) -> "not owner";
+message(epfnosupport) -> "protocol family not supported";
+message(epipe) -> "broken pipe";
+message(eproclim) -> "too many processes";
+message(eprocunavail) -> "bad procedure for program";
+message(eprogmismatch) -> "program version wrong";
+message(eprogunavail) -> "RPC program not available";
+message(eproto) -> "protocol error";
+message(eprotonosupport) -> "protocol not suppored";
+message(eprototype) -> "protocol wrong type for socket";
+message(erange) -> "math result unrepresentable";
+message(erefused) -> "EREFUSED";
+message(eremchg) -> "remote address changed";
+message(eremdev) -> "remote device";
+message(eremote) -> "pathname hit remote file system";
+message(eremoteio) -> "remote i/o error";
+message(eremoterelease) -> "EREMOTERELEASE";
+message(erofs) -> "read-only file system";
+message(erpcmismatch) -> "RPC version is wrong";
+message(erremote) -> "object is remote";
+message(eshutdown) -> "can't send after socket shutdown";
+message(esocktnosupport) -> "socket type not supported";
+message(espipe) -> "invalid seek";
+message(esrch) -> "no such process";
+message(esrmnt) -> "srmount error";
+message(estale) -> "stale remote file handle";
+message(esuccess) -> "Error 0";
+message(etime) -> "timer expired";
+message(etimedout) -> "connection timed out";
+message(etoomanyrefs) -> "too many references: can't splice";
+message(etxtbsy) -> "text file or pseudo-device busy";
+message(euclean) -> "structure needs cleaning";
+message(eunatch) -> "protocol driver not attached";
+message(eusers) -> "too many users";
+message(eversion) -> "version mismatch";
+message(ewouldblock) -> "operation would block";
+message(exdev) -> "cross-domain link";
+message(exfull) -> "message tables full";
+message(nxdomain) -> "non-existing domain";
+message(_) -> "unknown POSIX error".
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
new file mode 100644
index 0000000000..b1b5bad294
--- /dev/null
+++ b/lib/stdlib/src/erl_pp.erl
@@ -0,0 +1,992 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_pp).
+
+%%% Pretty printer for Erlang code in the same format as returned from
+%%% the parser. It does not always produce pretty code.
+
+-export([form/1,form/2,
+ attribute/1,attribute/2,function/1,function/2,rule/1,rule/2,
+ guard/1,guard/2,exprs/1,exprs/2,exprs/3,expr/1,expr/2,expr/3,expr/4]).
+
+-import(lists, [append/1,foldr/3,mapfoldl/3,reverse/1,reverse/2]).
+-import(io_lib, [write/1,format/2,write_char/1,write_string/1]).
+-import(erl_parse, [inop_prec/1,preop_prec/1,func_prec/0,max_prec/0]).
+
+-define(MAXLINE, 72).
+
+%%%
+%%% Exported functions
+%%%
+
+form(Thing) ->
+ form(Thing, none).
+
+form(Thing, Hook) ->
+ frmt(lform(Thing, Hook)).
+
+attribute(Thing) ->
+ attribute(Thing, none).
+
+attribute(Thing, Hook) ->
+ frmt(lattribute(Thing, Hook)).
+
+function(F) ->
+ function(F, none).
+
+function(F, Hook) ->
+ frmt(lfunction(F, Hook)).
+
+rule(R) ->
+ rule(R, none).
+
+rule(R, Hook) ->
+ frmt(lrule(R, Hook)).
+
+guard(Gs) ->
+ guard(Gs, none).
+
+guard(Gs, Hook) ->
+ frmt(lguard(Gs, Hook)).
+
+exprs(Es) ->
+ exprs(Es, 0, none).
+
+exprs(Es, Hook) ->
+ exprs(Es, 0, Hook).
+
+exprs(Es, I, Hook) ->
+ frmt({seq,[],[],[$,],lexprs(Es, Hook)}, I).
+
+expr(E) ->
+ frmt(lexpr(E, 0, none)).
+
+expr(E, Hook) ->
+ frmt(lexpr(E, 0, Hook)).
+
+expr(E, I, Hook) ->
+ frmt(lexpr(E, 0, Hook), I).
+
+expr(E, I, P, Hook) ->
+ frmt(lexpr(E, P, Hook), I).
+
+%%%
+%%% Local functions
+%%%
+
+lform({attribute,Line,Name,Arg}, Hook) ->
+ lattribute({attribute,Line,Name,Arg}, Hook);
+lform({function,Line,Name,Arity,Clauses}, Hook) ->
+ lfunction({function,Line,Name,Arity,Clauses}, Hook);
+lform({rule,Line,Name,Arity,Clauses}, Hook) ->
+ lrule({rule,Line,Name,Arity,Clauses}, Hook);
+%% These are specials to make it easier for the compiler.
+lform({error,E}, _Hook) ->
+ leaf(format("~p\n", [{error,E}]));
+lform({warning,W}, _Hook) ->
+ leaf(format("~p\n", [{warning,W}]));
+lform({eof,_Line}, _Hook) ->
+ $\n.
+
+lattribute({attribute,_Line,type,Type}, Hook) ->
+ [typeattr(type, Type, Hook),leaf(".\n")];
+lattribute({attribute,_Line,opaque,Type}, Hook) ->
+ [typeattr(opaque, Type, Hook),leaf(".\n")];
+lattribute({attribute,_Line,spec,Arg}, _Hook) ->
+ [specattr(Arg),leaf(".\n")];
+lattribute({attribute,_Line,Name,Arg}, Hook) ->
+ [lattribute(Name, Arg, Hook),leaf(".\n")].
+
+lattribute(module, {M,Vs}, _Hook) ->
+ attr("module",[{var,0,pname(M)},
+ foldr(fun(V, C) -> {cons,0,{var,0,V},C}
+ end, {nil,0}, Vs)]);
+lattribute(module, M, _Hook) ->
+ attr("module", [{var,0,pname(M)}]);
+lattribute(export, Falist, _Hook) ->
+ call({var,0,"-export"}, [falist(Falist)], 0, none);
+lattribute(import, Name, _Hook) when is_list(Name) ->
+ attr("import", [{var,0,pname(Name)}]);
+lattribute(import, {From,Falist}, _Hook) ->
+ attr("import",[{var,0,pname(From)},falist(Falist)]);
+lattribute(file, {Name,Line}, _Hook) ->
+ attr("file", [{var,0,format("~p", [Name])},{integer,0,Line}]);
+lattribute(record, {Name,Is}, Hook) ->
+ Nl = leaf(format("-record(~w,", [Name])),
+ [{first,Nl,record_fields(Is, Hook)},$)];
+lattribute(Name, Arg, _Hook) ->
+ attr(write(Name), [erl_parse:abstract(Arg)]).
+
+typeattr(Tag, {TypeName,Type,Args}, _Hook) ->
+ {first,leaf("-"++atom_to_list(Tag)++" "),
+ typed(call({atom,0,TypeName}, Args, 0, none), Type)}.
+
+ltype({ann_type,_Line,[V,T]}) ->
+ typed(lexpr(V, none), T);
+ltype({paren_type,_Line,[T]}) ->
+ [$(,ltype(T),$)];
+ltype({type,_Line,union,Ts}) ->
+ {seq,[],[],[' |'],ltypes(Ts)};
+ltype({type,_Line,list,[T]}) ->
+ {seq,$[,$],$,,[ltype(T)]};
+ltype({type,_Line,nonempty_list,[T]}) ->
+ {seq,$[,$],[$,],[ltype(T),leaf("...")]};
+ltype({type,Line,nil,[]}) ->
+ lexpr({nil,Line}, 0, none);
+ltype({type,Line,tuple,any}) ->
+ simple_type({atom,Line,tuple}, []);
+ltype({type,_Line,tuple,Ts}) ->
+ tuple_type(Ts, fun ltype/1);
+ltype({type,_Line,record,[N|Fs]}) ->
+ record_type(N, Fs);
+ltype({type,_Line,range,[_I1,_I2]=Es}) ->
+ expr_list(Es, '..', fun lexpr/2, none);
+ltype({type,_Line,binary,[I1,I2]}) ->
+ binary_type(I1, I2); % except binary()
+ltype({type,_Line,'fun',[]}) ->
+ leaf("fun()");
+ltype({type,_Line,'fun',_}=FunType) ->
+ [fun_type(['fun',$(], FunType),$)];
+ltype({type,Line,T,Ts}) ->
+ simple_type({atom,Line,T}, Ts);
+ltype({remote_type,Line,[M,F,Ts]}) ->
+ simple_type({remote,Line,M,F}, Ts);
+ltype({atom,_,T}) ->
+ %% Follow the convention to always quote atoms (in types):
+ leaf([$',atom_to_list(T),$']);
+ltype(E) ->
+ lexpr(E, 0, none).
+
+binary_type({integer,_,Int1}=I1, {integer,_,Int2}=I2) ->
+ E1 = [[leaf("_:"),lexpr(I1, 0, none)] || Int1 =/= 0],
+ E2 = [[leaf("_:_*"),lexpr(I2, 0, none)] || Int2 =/= 0],
+ {seq,'<<','>>',[$,],E1++E2}.
+
+record_type({atom,_,Name}, Fields) ->
+ {first,[record_name(Name)],field_types(Fields)}.
+
+field_types(Fs) ->
+ tuple_type(Fs, fun field_type/1).
+
+field_type({type,_Line,field_type,[Name,Type]}) ->
+ typed(lexpr(Name, none), Type).
+
+typed(B, {type,_,union,Ts}) ->
+ %% Special layout for :: followed by union.
+ {first,[B,$\s],{seq,[],[],[],union_type(Ts)}};
+typed(B, Type) ->
+ {list,[{cstep,[B,' ::'],ltype(Type)}]}.
+
+union_type([T|Ts]) ->
+ [[leaf(":: "),ltype(T)] | ltypes(Ts, fun union_elem/1)].
+
+union_elem(T) ->
+ [leaf(" | "),ltype(T)].
+
+tuple_type(Ts, F) ->
+ {seq,${,$},[$,],ltypes(Ts, F)}.
+
+specattr({FuncSpec,TypeSpecs}) ->
+ Func = case FuncSpec of
+ {F,_A} ->
+ format("~w", [F]);
+ {M,F,_A} ->
+ format("~w:~w", [M, F])
+ end,
+ {first,leaf("-spec "),
+ {list,[{first,leaf(Func),spec_clauses(TypeSpecs)}]}}.
+
+spec_clauses(TypeSpecs) ->
+ {prefer_nl,[$;],[sig_type(T) || T <- TypeSpecs]}.
+
+sig_type({type,_Line,bounded_fun,[T,Gs]}) ->
+ guard_type(fun_type([], T), Gs);
+sig_type(FunType) ->
+ fun_type([], FunType).
+
+guard_type(Before, Gs) ->
+ Gl = {list,[{step,'when',expr_list(Gs, [$,], fun constraint/2, none)}]},
+ {list,[{step,Before,Gl}]}.
+
+constraint({type,_Line,constraint,[Tag,As]}, _Hook) ->
+ simple_type(Tag, As).
+
+fun_type(Before, {type,_,'fun',[FType,Ret]}) ->
+ {first,Before,{step,[type_args(FType),' ->'],ltype(Ret)}}.
+
+type_args({type,_Line,any}) ->
+ leaf("(...)");
+type_args({type,_line,product,Ts}) ->
+ targs(Ts).
+
+simple_type(Tag, Types) ->
+ {first,lexpr(Tag, 0, none),targs(Types)}.
+
+targs(Ts) ->
+ {seq,$(,$),[$,],ltypes(Ts)}.
+
+ltypes(Ts) ->
+ ltypes(Ts, fun ltype/1).
+
+ltypes(Ts, F) ->
+ [F(T) || T <- Ts].
+
+attr(Name, Args) ->
+ call({var,0,format("-~s", [Name])}, Args, 0, none).
+
+pname(['' | As]) ->
+ [$. | pname(As)];
+pname([A]) ->
+ write(A);
+pname([A | As]) ->
+ [write(A),$.|pname(As)];
+pname(A) when is_atom(A) ->
+ write(A).
+
+falist([]) ->
+ {nil,0};
+falist([{Name,Arity}|Falist]) ->
+ {cons,0,{var,0,format("~w/~w", [Name,Arity])},falist(Falist)}.
+
+lfunction({function,_Line,Name,_Arity,Cs}, Hook) ->
+ Cll = nl_clauses(fun (C, H) -> func_clause(Name, C, H) end, $;, Hook, Cs),
+ [Cll,leaf(".\n")].
+
+func_clause(Name, {clause,Line,Head,Guard,Body}, Hook) ->
+ Hl = call({atom,Line,Name}, Head, 0, Hook),
+ Gl = guard_when(Hl, Guard, Hook),
+ Bl = body(Body, Hook),
+ {step,Gl,Bl}.
+
+lrule({rule,_Line,Name,_Arity,Cs}, Hook) ->
+ Cll = nl_clauses(fun (C, H) -> rule_clause(Name, C, H) end, $;, Hook, Cs),
+ [Cll,leaf(".\n")].
+
+rule_clause(Name, {clause,Line,Head,Guard,Body}, Hook) ->
+ Hl = call({atom,Line,Name}, Head, 0, Hook),
+ Gl = guard_when(Hl, Guard, Hook, leaf(" :-")),
+ Bl = rule_body(Body, Hook),
+ {step,Gl,Bl}.
+
+rule_body(Es, Hook) ->
+ lc_quals(Es, Hook).
+
+guard_when(Before, Guard, Hook) ->
+ guard_when(Before, Guard, Hook, ' ->').
+
+guard_when(Before, Guard, Hook, After) ->
+ Gl = lguard(Guard, Hook),
+ [{list,[{step,Before,Gl}]},After].
+
+lguard([E|Es], Hook) when is_list(E) ->
+ {list,[{step,'when',expr_list([E|Es], [$;], fun guard0/2, Hook)}]};
+lguard([E|Es], Hook) -> % before R6
+ lguard([[E|Es]], Hook);
+lguard([], _) ->
+ [].
+
+guard0(Es, Hook) ->
+ expr_list(Es, [$,], fun lexpr/2, Hook).
+
+%% body(Before, Es, Hook) -> [Char].
+
+body([E], Hook) ->
+ lexpr(E, Hook);
+body(Es, Hook) ->
+ {prefer_nl,[$,],lexprs(Es, Hook)}.
+
+lexpr(E, Hook) ->
+ lexpr(E, 0, Hook).
+
+lexpr({var,_,V}, _, _) when is_integer(V) -> %Special hack for Robert
+ leaf(format("_~w", [V]));
+lexpr({var,_,V}, _, _) -> leaf(format("~s", [V]));
+lexpr({char,_,C}, _, _) -> leaf(write_char(C));
+lexpr({integer,_,N}, _, _) -> leaf(write(N));
+lexpr({float,_,F}, _, _) -> leaf(write(F));
+lexpr({atom,_,A}, _, _) -> leaf(write(A));
+lexpr({string,_,S}, _, _) -> {string,S};
+lexpr({nil,_}, _, _) -> '[]';
+lexpr({cons,_,H,T}, _, Hook) ->
+ list(T, [H], Hook);
+lexpr({lc,_,E,Qs}, _Prec, Hook) ->
+ Lcl = {list,[{step,[lexpr(E, Hook),leaf(" ||")],lc_quals(Qs, Hook)}]},
+ {list,[{seq,$[,[],[[]],[{force_nl,leaf(" "),[Lcl]}]},$]]};
+ %% {list,[{step,$[,Lcl},$]]};
+lexpr({bc,_,E,Qs}, _Prec, Hook) ->
+ Lcl = {list,[{step,[lexpr(E, Hook),leaf(" ||")],lc_quals(Qs, Hook)}]},
+ {list,[{seq,'<<',[],[[]],[{force_nl,leaf(" "),[Lcl]}]},'>>']};
+ %% {list,[{step,'<<',Lcl},'>>']};
+lexpr({tuple,_,Elts}, _, Hook) ->
+ tuple(Elts, Hook);
+%%lexpr({struct,_,Tag,Elts}, _, Hook) ->
+%% {first,format("~w", [Tag]),tuple(Elts, Hook)};
+lexpr({record_index, _, Name, F}, Prec, Hook) ->
+ {P,R} = preop_prec('#'),
+ Nl = record_name(Name),
+ El = [Nl,$.,lexpr(F, R, Hook)],
+ maybe_paren(P, Prec, El);
+lexpr({record, _, Name, Fs}, Prec, Hook) ->
+ {P,_R} = preop_prec('#'),
+ Nl = record_name(Name),
+ El = {first,Nl,record_fields(Fs, Hook)},
+ maybe_paren(P, Prec, El);
+lexpr({record_field, _, Rec, Name, F}, Prec, Hook) ->
+ {L,P,R} = inop_prec('#'),
+ Rl = lexpr(Rec, L, Hook),
+ Nl = leaf(format("#~w.", [Name])),
+ El = [Rl,Nl,lexpr(F, R, Hook)],
+ maybe_paren(P, Prec, El);
+lexpr({record, _, Rec, Name, Fs}, Prec, Hook) ->
+ {L,P,_R} = inop_prec('#'),
+ Rl = lexpr(Rec, L, Hook),
+ Nl = record_name(Name),
+ El = {first,[Rl,Nl],record_fields(Fs, Hook)},
+ maybe_paren(P, Prec, El);
+lexpr({record_field, _, {atom,_,''}, F}, Prec, Hook) ->
+ {_L,P,R} = inop_prec('.'),
+ El = [$.,lexpr(F, R, Hook)],
+ maybe_paren(P, Prec, El);
+lexpr({record_field, _, Rec, F}, Prec, Hook) ->
+ {L,P,R} = inop_prec('.'),
+ El = [lexpr(Rec, L, Hook),$.,lexpr(F, R, Hook)],
+ maybe_paren(P, Prec, El);
+lexpr({block,_,Es}, _, Hook) ->
+ {list,[{step,'begin',body(Es, Hook)},'end']};
+lexpr({'if',_,Cs}, _, Hook) ->
+ {list,[{step,'if',if_clauses(Cs, Hook)},'end']};
+lexpr({'case',_,Expr,Cs}, _, Hook) ->
+ {list,[{step,{list,[{step,'case',lexpr(Expr, Hook)},'of']},
+ cr_clauses(Cs, Hook)},
+ 'end']};
+lexpr({'cond',_,Cs}, _, Hook) ->
+ {list,[{step,leaf("cond"),cond_clauses(Cs, Hook)},'end']};
+lexpr({'receive',_,Cs}, _, Hook) ->
+ {list,[{step,'receive',cr_clauses(Cs, Hook)},'end']};
+lexpr({'receive',_,Cs,To,ToOpt}, _, Hook) ->
+ Al = {list,[{step,[lexpr(To, Hook),' ->'],body(ToOpt, Hook)}]},
+ {list,[{step,'receive',cr_clauses(Cs, Hook)},
+ {step,'after',Al},
+ 'end']};
+lexpr({'fun',_,{function,F,A}}, _Prec, _Hook) ->
+ leaf(format("fun ~w/~w", [F,A]));
+lexpr({'fun',_,{function,F,A},Extra}, _Prec, _Hook) ->
+ {force_nl,fun_info(Extra),leaf(format("fun ~w/~w", [F,A]))};
+lexpr({'fun',_,{function,M,F,A}}, _Prec, _Hook) ->
+ leaf(format("fun ~w:~w/~w", [M,F,A]));
+lexpr({'fun',_,{clauses,Cs}}, _Prec, Hook) ->
+ {list,[{first,'fun',fun_clauses(Cs, Hook)},'end']};
+lexpr({'fun',_,{clauses,Cs},Extra}, _Prec, Hook) ->
+ {force_nl,fun_info(Extra),
+ {list,[{first,'fun',fun_clauses(Cs, Hook)},'end']}};
+lexpr({'query',_,Lc}, _Prec, Hook) ->
+ {list,[{step,leaf("query"),lexpr(Lc, 0, Hook)},'end']};
+lexpr({call,_,{remote,_,{atom,_,M},{atom,_,F}=N}=Name,Args}, Prec, Hook) ->
+ case erl_internal:bif(M, F, length(Args)) of
+ true ->
+ call(N, Args, Prec, Hook);
+ false ->
+ call(Name, Args, Prec, Hook)
+ end;
+lexpr({call,_,Name,Args}, Prec, Hook) ->
+ call(Name, Args, Prec, Hook);
+lexpr({'try',_,Es,Scs,Ccs,As}, _, Hook) ->
+ {list,[if
+ Scs =:= [] ->
+ {step,'try',body(Es, Hook)};
+ true ->
+ {step,{list,[{step,'try',body(Es, Hook)},'of']},
+ cr_clauses(Scs, Hook)}
+ end,
+ if
+ Ccs =:= [] ->
+ [];
+ true ->
+ {step,'catch',try_clauses(Ccs, Hook)}
+ end,
+ if
+ As =:= [] ->
+ [];
+ true ->
+ {step,'after',body(As, Hook)}
+ end,
+ 'end']};
+lexpr({'catch',_,Expr}, Prec, Hook) ->
+ {P,R} = preop_prec('catch'),
+ El = {list,[{step,'catch',lexpr(Expr, R, Hook)}]},
+ maybe_paren(P, Prec, El);
+lexpr({match,_,Lhs,Rhs}, Prec, Hook) ->
+ {L,P,R} = inop_prec('='),
+ Pl = lexpr(Lhs, L, Hook),
+ Rl = lexpr(Rhs, R, Hook),
+ El = {list,[{cstep,[Pl,' ='],Rl}]},
+ maybe_paren(P, Prec, El);
+lexpr({op,_,Op,Arg}, Prec, Hook) ->
+ {P,R} = preop_prec(Op),
+ Ol = leaf(format("~s ", [Op])),
+ El = [Ol,lexpr(Arg, R, Hook)],
+ maybe_paren(P, Prec, El);
+lexpr({op,_,Op,Larg,Rarg}, Prec, Hook) when Op =:= 'orelse';
+ Op =:= 'andalso' ->
+ %% Breaks lines since R12B.
+ {L,P,R} = inop_prec(Op),
+ Ll = lexpr(Larg, L, Hook),
+ Ol = leaf(format("~s", [Op])),
+ Lr = lexpr(Rarg, R, Hook),
+ El = {prefer_nl,[[]],[Ll,Ol,Lr]},
+ maybe_paren(P, Prec, El);
+lexpr({op,_,Op,Larg,Rarg}, Prec, Hook) ->
+ {L,P,R} = inop_prec(Op),
+ Ll = lexpr(Larg, L, Hook),
+ Ol = leaf(format("~s", [Op])),
+ Lr = lexpr(Rarg, R, Hook),
+ El = {list,[Ll,Ol,Lr]},
+ maybe_paren(P, Prec, El);
+%% Special expressions which are not really legal everywhere.
+lexpr({remote,_,M,F}, Prec, Hook) ->
+ {L,P,R} = inop_prec(':'),
+ NameItem = lexpr(M, L, Hook),
+ CallItem = lexpr(F, R, Hook),
+ maybe_paren(P, Prec, [NameItem,$:,CallItem]);
+%% BIT SYNTAX:
+lexpr({bin,_,Fs}, _, Hook) ->
+ bit_grp(Fs, Hook);
+%% Special case for straight values.
+lexpr({value,_,Val}, _,_) ->
+ leaf(write(Val));
+%% Now do the hook.
+lexpr(Other, _Precedence, none) ->
+ leaf(format("INVALID-FORM:~w:",[Other]));
+lexpr(HookExpr, Precedence, {Mod,Func,Eas}) when Mod =/= 'fun' ->
+ {ehook,HookExpr,Precedence,{Mod,Func,Eas}};
+lexpr(HookExpr, Precedence, Func) ->
+ {hook,HookExpr,Precedence,Func}.
+
+call(Name, Args, Prec, Hook) ->
+ {F,P} = func_prec(),
+ Item = {first,lexpr(Name, F, Hook),args(Args, Hook)},
+ maybe_paren(P, Prec, Item).
+
+fun_info(Extra) ->
+ leaf(format("% fun-info: ~w", [Extra])).
+
+%% BITS:
+
+bit_grp(Fs, Hook) ->
+ append([['<<'],
+ [try
+ true = Fs =/= [],
+ S = bin_string(Fs),
+ true = io_lib:printable_list(S),
+ {string,S}
+ catch _:_ ->
+ bit_elems(Fs, Hook)
+ end],
+ ['>>']]).
+
+bin_string([]) ->
+ [];
+bin_string([{bin_element,_,{char,_,C},_,_}|Bin]) ->
+ [C | bin_string(Bin)].
+
+bit_elems(Es, Hook) ->
+ expr_list(Es, $,, fun bit_elem/2, Hook).
+
+bit_elem({bin_element,_,Expr,Sz,Types}, Hook) ->
+ P = max_prec(),
+ VChars = lexpr(Expr, P, Hook),
+ SChars = if
+ Sz =/= default ->
+ [VChars,$:,lexpr(Sz, P, Hook)];
+ true ->
+ VChars
+ end,
+ if
+ Types =/= default ->
+ [SChars,$/|bit_elem_types(Types)];
+ true ->
+ SChars
+ end.
+
+bit_elem_types([T]) ->
+ [bit_elem_type(T)];
+bit_elem_types([T | Rest]) ->
+ [bit_elem_type(T), $-|bit_elem_types(Rest)].
+
+bit_elem_type({A,B}) ->
+ [lexpr(erl_parse:abstract(A), none),
+ $:,
+ lexpr(erl_parse:abstract(B), none)];
+bit_elem_type(T) ->
+ lexpr(erl_parse:abstract(T), none).
+
+%% end of BITS
+
+record_name(Name) ->
+ leaf(format("#~w", [Name])).
+
+record_fields(Fs, Hook) ->
+ tuple(Fs, fun record_field/2, Hook).
+
+record_field({record_field,_,F,Val}, Hook) ->
+ {L,_P,R} = inop_prec('='),
+ Fl = lexpr(F, L, Hook),
+ Vl = lexpr(Val, R, Hook),
+ {list,[{cstep,[Fl,' ='],Vl}]};
+record_field({typed_record_field,{record_field,_,F,Val},Type}, Hook) ->
+ {L,_P,R} = inop_prec('='),
+ Fl = lexpr(F, L, Hook),
+ Vl = typed(lexpr(Val, R, Hook), Type),
+ {list,[{cstep,[Fl,' ='],Vl}]};
+record_field({typed_record_field,Field,Type0}, Hook) ->
+ Type = remove_undefined(Type0),
+ typed(record_field(Field, Hook), Type);
+record_field({record_field,_,F}, Hook) ->
+ lexpr(F, 0, Hook).
+
+remove_undefined({type,L,union,[{atom,_,undefined}|T]}) ->
+ {type,L,union,T};
+remove_undefined(T) -> % cannot happen
+ T.
+
+list({cons,_,H,T}, Es, Hook) ->
+ list(T, [H|Es], Hook);
+list({nil,_}, Es, Hook) ->
+ proper_list(reverse(Es), Hook);
+list(Other, Es, Hook) ->
+ improper_list(reverse(Es, [Other]), Hook).
+
+%% if_clauses(Clauses, Hook) -> [Char].
+%% Print 'if' clauses.
+
+if_clauses(Cs, Hook) ->
+ clauses(fun if_clause/2, Hook, Cs).
+
+if_clause({clause,_,[],G,B}, Hook) ->
+ Gl = [guard_no_when(G, Hook),' ->'],
+ {step,Gl,body(B, Hook)}.
+
+guard_no_when([E|Es], Hook) when is_list(E) ->
+ expr_list([E|Es], $;, fun guard0/2, Hook);
+guard_no_when([E|Es], Hook) -> % before R6
+ guard_no_when([[E|Es]], Hook);
+guard_no_when([], _) -> % cannot happen
+ leaf("true").
+
+%% cr_clauses(Clauses, Hook) -> [Char].
+%% Print 'case'/'receive' clauses.
+
+cr_clauses(Cs, Hook) ->
+ clauses(fun cr_clause/2, Hook, Cs).
+
+cr_clause({clause,_,[T],G,B}, Hook) ->
+ El = lexpr(T, 0, Hook),
+ Gl = guard_when(El, G, Hook),
+ Bl = body(B, Hook),
+ {step,Gl,Bl}.
+
+%% try_clauses(Clauses, Hook) -> [Char].
+%% Print 'try' clauses.
+
+try_clauses(Cs, Hook) ->
+ clauses(fun try_clause/2, Hook, Cs).
+
+try_clause({clause,_,[{tuple,_,[{atom,_,throw},V,S]}],G,B}, Hook) ->
+ El = lexpr(V, 0, Hook),
+ Sl = stack_backtrace(S, [El], Hook),
+ Gl = guard_when(Sl, G, Hook),
+ Bl = body(B, Hook),
+ {step,Gl,Bl};
+try_clause({clause,_,[{tuple,_,[C,V,S]}],G,B}, Hook) ->
+ Cs = lexpr(C, 0, Hook),
+ El = lexpr(V, 0, Hook),
+ CsEl = [Cs,$:,El],
+ Sl = stack_backtrace(S, CsEl, Hook),
+ Gl = guard_when(Sl, G, Hook),
+ Bl = body(B, Hook),
+ {step,Gl,Bl}.
+
+stack_backtrace({var,_,'_'}, El, _Hook) ->
+ El;
+stack_backtrace(S, El, Hook) ->
+ El++[$:,lexpr(S, 0, Hook)].
+
+%% fun_clauses(Clauses, Hook) -> [Char].
+%% Print 'fun' clauses.
+
+fun_clauses(Cs, Hook) ->
+ nl_clauses(fun fun_clause/2, [$;], Hook, Cs).
+
+fun_clause({clause,_,A,G,B}, Hook) ->
+ El = args(A, Hook),
+ Gl = guard_when(El, G, Hook),
+ Bl = body(B, Hook),
+ {step,Gl,Bl}.
+
+%% cond_clauses(Clauses, Hook) -> [Char].
+%% Print 'cond' clauses.
+
+cond_clauses(Cs, Hook) ->
+ clauses(fun cond_clause/2, Hook, Cs).
+
+cond_clause({clause,_,[],[[E]],B}, Hook) ->
+ {step,[lexpr(E, Hook),' ->'],body(B, Hook)}.
+
+%% nl_clauses(Type, Hook, Clauses) -> [Char].
+%% Generic clause printing function (always breaks lines).
+
+nl_clauses(Type, Sep, Hook, Cs) ->
+ {prefer_nl,Sep,lexprs(Cs, Type, Hook)}.
+
+%% clauses(Type, Hook, Clauses) -> [Char].
+%% Generic clause printing function (breaks lines since R12B).
+
+clauses(Type, Hook, Cs) ->
+ {prefer_nl,[$;],lexprs(Cs, Type, Hook)}.
+
+%% lc_quals(Qualifiers, After, Hook)
+%% List comprehension qualifiers (breaks lines since R12B).
+
+lc_quals(Qs, Hook) ->
+ {prefer_nl,[$,],lexprs(Qs, fun lc_qual/2, Hook)}.
+
+lc_qual({b_generate,_,Pat,E}, Hook) ->
+ Pl = lexpr(Pat, 0, Hook),
+ {list,[{step,[Pl,leaf(" <=")],lexpr(E, 0, Hook)}]};
+lc_qual({generate,_,Pat,E}, Hook) ->
+ Pl = lexpr(Pat, 0, Hook),
+ {list,[{step,[Pl,leaf(" <-")],lexpr(E, 0, Hook)}]};
+lc_qual(Q, Hook) ->
+ lexpr(Q, 0, Hook).
+
+proper_list(Es, Hook) ->
+ {seq,$[,$],$,,lexprs(Es, Hook)}.
+
+improper_list(Es, Hook) ->
+ {seq,$[,$],{$,,$|},lexprs(Es, Hook)}.
+
+tuple(L, Hook) ->
+ tuple(L, fun lexpr/2, Hook).
+
+tuple(Es, F, Hook) ->
+ {seq,${,$},$,,lexprs(Es, F, Hook)}.
+
+args(As, Hook) ->
+ {seq,$(,$),[$,],lexprs(As, Hook)}.
+
+expr_list(Es, Sep, F, Hook) ->
+ {seq,[],[],Sep,lexprs(Es, F, Hook)}.
+
+lexprs(Es, Hook) ->
+ lexprs(Es, fun lexpr/2, Hook).
+
+lexprs(Es, F, Hook) ->
+ [F(E, Hook) || E <- Es].
+
+maybe_paren(P, Prec, Expr) when P < Prec ->
+ [$(,Expr,$)];
+maybe_paren(_P, _Prec, Expr) ->
+ Expr.
+
+leaf(S) ->
+ {leaf,iolist_size(S),S}.
+
+%%% Do the formatting. Currently nothing fancy. Could probably have
+%%% done it in one single pass.
+
+frmt(Item) ->
+ frmt(Item, 0).
+
+frmt(Item, I) ->
+ ST = spacetab(),
+ WT = wordtable(),
+ {Chars,_Length} = f(Item, I, ST, WT),
+ [Chars].
+
+%%% What the tags mean:
+%%% - C: a character
+%%% - [I|Is]: Is follow after I without newline or space
+%%% - {list,IPs}: try to put all IPs on one line, if that fails newlines
+%%% and indentation are inserted between IPs.
+%%% - {first,I,IP2}: IP2 follows after I, and is output with an indentation
+%%% updated with the width of I.
+%%% - {seq,Before,After,Separator,IPs}: a sequence of Is separated by
+%%% Separator. Before is output before IPs, and the indentation of IPs
+%%% is updated with the width of Before. After follows after IPs.
+%%% - {force_nl,ExtraInfo,I}: fun-info (a comment) forces linebreak before I.
+%%% - {prefer_nl,Sep,IPs}: forces linebreak between Is unlesss negative
+%%% indentation.
+%%% - {string,S}: a string.
+%%% - {hook,...}, {ehook,...}: hook expressions.
+%%%
+%%% list, first, seq, force_nl, and prefer_nl all accept IPs, where each
+%%% element is either an item or a tuple {step|cstep,I1,I2}. step means
+%%% that I2 is output after linebreak and an incremented indentation.
+%%% cstep works similarly, but no linebreak if the width of I1 is less
+%%% than the indentation (this is for "A = <expression over several lines>).
+
+f([]=Nil, _I0, _ST, _WT) ->
+ {Nil,0};
+f(C, _I0, _ST, _WT) when is_integer(C) ->
+ {C,1};
+f({leaf,Length,Chars}, _I0, _ST, _WT) ->
+ {Chars,Length};
+f([Item|Items], I0, ST, WT) ->
+ consecutive(Items, f(Item, I0, ST, WT), I0, ST, WT);
+f({list,Items}, I0, ST, WT) ->
+ f({seq,[],[],[[]],Items}, I0, ST, WT);
+f({first,E,Item}, I0, ST, WT) ->
+ f({seq,E,[],[[]],[Item]}, I0, ST, WT);
+f({seq,Before,After,Sep,LItems}, I0, ST, WT) ->
+ BCharsSize = f(Before, I0, ST, WT),
+ I = indent(BCharsSize, I0),
+ CharsSizeL = fl(LItems, Sep, I, After, ST, WT),
+ {CharsL,SizeL} = unz(CharsSizeL),
+ {BCharsL,BSizeL} = unz1([BCharsSize]),
+ Sizes = BSizeL ++ SizeL,
+ NSepChars = if
+ is_list(Sep), Sep =/= [] ->
+ erlang:max(0, length(CharsL)-1);
+ true ->
+ 0
+ end,
+ case same_line(I0, Sizes, NSepChars) of
+ {yes,Size} ->
+ Chars = if
+ NSepChars > 0 -> insert_sep(CharsL, $\s);
+ true -> CharsL
+ end,
+ {BCharsL++Chars,Size};
+ no ->
+ {BCharsL++insert_newlines(CharsSizeL, I, ST),
+ nsz(lists:last(Sizes), I0)}
+ end;
+f({force_nl,_ExtraInfoItem,Item}, I, ST, WT) when I < 0 ->
+ %% Extra info is a comment; cannot have that on the same line
+ f(Item, I, ST, WT);
+f({force_nl,ExtraInfoItem,Item}, I, ST, WT) ->
+ f({prefer_nl,[],[ExtraInfoItem,Item]}, I, ST, WT);
+f({prefer_nl,Sep,LItems}, I, ST, WT) when I < 0 ->
+ f({seq,[],[],Sep,LItems}, I, ST, WT);
+f({prefer_nl,Sep,LItems}, I0, ST, WT) ->
+ CharsSize2L = fl(LItems, Sep, I0, [], ST, WT),
+ {_CharsL,Sizes} = unz(CharsSize2L),
+ if
+ Sizes =:= [] ->
+ {[], 0};
+ true ->
+ {insert_newlines(CharsSize2L, I0, ST),nsz(lists:last(Sizes), I0)}
+ end;
+f({string,S}, I, ST, WT) ->
+ f(write_a_string(S, I), I, ST, WT);
+f({hook,HookExpr,Precedence,Func}, I, _ST, _WT) ->
+ Chars = Func(HookExpr, I, Precedence, Func),
+ {Chars,indentation(Chars, I)};
+f({ehook,HookExpr,Precedence,{Mod,Func,Eas}=ModFuncEas}, I, _ST, _WT) ->
+ Chars = apply(Mod, Func, [HookExpr,I,Precedence,ModFuncEas|Eas]),
+ {Chars,indentation(Chars, I)};
+f(WordName, _I, _ST, WT) -> % when is_atom(WordName)
+ word(WordName, WT).
+
+-define(IND, 4).
+
+%% fl(ListItems, I0, ST, WT) -> [[CharsSize1,CharsSize2]]
+%% ListItems = [{Item,Items}|Item]
+fl([], _Sep, I0, After, ST, WT) ->
+ [[f(After, I0, ST, WT),{[],0}]];
+fl(CItems, Sep0, I0, After, ST, WT) ->
+ F = fun({step,Item1,Item2}, S) ->
+ [f(Item1, I0, ST, WT),f([Item2,S], incr(I0, ?IND), ST, WT)];
+ ({cstep,Item1,Item2}, S) ->
+ {_,Sz1} = CharSize1 = f(Item1, I0, ST, WT),
+ if
+ is_integer(Sz1), Sz1 < ?IND ->
+ Item2p = [leaf("\s"),Item2,S],
+ [consecutive(Item2p, CharSize1, I0, ST, WT),{[],0}];
+ true ->
+ [CharSize1,f([Item2,S], incr(I0, ?IND), ST, WT)]
+ end;
+ (Item, S) ->
+ [f([Item,S], I0, ST, WT),{[],0}]
+ end,
+ {Sep,LastSep} = case Sep0 of {_,_} -> Sep0; _ -> {Sep0,Sep0} end,
+ fl1(CItems, F, Sep, LastSep, After).
+
+fl1([CItem], F, _Sep, _LastSep, After) ->
+ [F(CItem,After)];
+fl1([CItem1,CItem2], F, _Sep, LastSep, After) ->
+ [F(CItem1, LastSep),F(CItem2, After)];
+fl1([CItem|CItems], F, Sep, LastSep, After) ->
+ [F(CItem, Sep)|fl1(CItems, F, Sep, LastSep, After)].
+
+consecutive(Items, CharSize1, I0, ST, WT) ->
+ {CharsSizes,_Length} =
+ mapfoldl(fun(Item, Len) ->
+ CharsSize = f(Item, Len, ST, WT),
+ {CharsSize,indent(CharsSize, Len)}
+ end, indent(CharSize1, I0), Items),
+ {CharsL,SizeL} = unz1([CharSize1|CharsSizes]),
+ {CharsL,line_size(SizeL)}.
+
+unz(CharsSizesL) ->
+ unz1(append(CharsSizesL)).
+
+unz1(CharSizes) ->
+ lists:unzip(nonzero(CharSizes)).
+
+nonzero(CharSizes) ->
+ lists:filter(fun({_,Sz}) -> Sz =/= 0 end, CharSizes).
+
+insert_newlines(CharsSizesL, I, ST) when I >= 0 ->
+ insert_nl(foldr(fun([{_C1,0},{_C2,0}], A) ->
+ A;
+ ([{C1,_Sz1},{_C2,0}], A) ->
+ [C1|A];
+ ([{C1,_Sz1},{C2,Sz2}], A) when Sz2 > 0 ->
+ [insert_nl([C1,C2], I+?IND, ST)|A]
+ end, [], CharsSizesL), I, ST).
+
+
+insert_nl(CharsL, I, ST) ->
+ insert_sep(CharsL, nl_indent(I, ST)).
+
+insert_sep([Chars1 | CharsL], Sep) ->
+ [Chars1 | [[Sep,Chars] || Chars <- CharsL]].
+
+nl_indent(0, _T) ->
+ $\n;
+nl_indent(I, T) when I > 0 ->
+ [$\n|spaces(I, T)].
+
+same_line(I0, SizeL, NSepChars) ->
+ try
+ Size = lists:sum(SizeL) + NSepChars,
+ true = incr(I0, Size) =< ?MAXLINE,
+ {yes,Size}
+ catch _:_ ->
+ no
+ end.
+
+line_size(SizeL) ->
+ line_size(SizeL, 0, false).
+
+line_size([], Size, false) ->
+ Size;
+line_size([], Size, true) ->
+ {line,Size};
+line_size([{line,Len}|SizeL], _, _) ->
+ line_size(SizeL, Len, true);
+line_size([Sz|SizeL], SizeSoFar, LF) ->
+ line_size(SizeL, SizeSoFar+Sz, LF).
+
+nsz({line,_Len}=Sz, _I) ->
+ Sz;
+nsz(Size, I) when I >= 0 ->
+ {line,Size+I}.
+
+indent({_Chars,{line,Len}}, _I) ->
+ Len;
+indent({_Chars,Size}, I) ->
+ incr(I, Size).
+
+incr(I, _Incr) when I < 0 ->
+ I;
+incr(I, Incr) ->
+ I+Incr.
+
+indentation(E, I) when I < 0 ->
+ iolist_size(E);
+indentation(E, I0) ->
+ I = io_lib_format:indentation(E, I0),
+ case has_nl(E) of
+ true -> {line,I};
+ false -> I
+ end.
+
+has_nl([$\n|_]) ->
+ true;
+has_nl([C|Cs]) when is_integer(C) ->
+ has_nl(Cs);
+has_nl([C|Cs]) ->
+ has_nl(C) orelse has_nl(Cs);
+has_nl([]) ->
+ false.
+
+-define(MIN_SUBSTRING, 5).
+
+write_a_string(S, I) when I < 0; S =:= [] ->
+ leaf(write_string(S));
+write_a_string(S, I) ->
+ Len = erlang:max(?MAXLINE-I, ?MIN_SUBSTRING),
+ {list,write_a_string(S, Len, Len)}.
+
+write_a_string([], _N, _Len) ->
+ [];
+write_a_string(S, N, Len) ->
+ SS = string:sub_string(S, 1, N),
+ Sl = write_string(SS),
+ case (iolist_size(Sl) > Len) and (N > ?MIN_SUBSTRING) of
+ true ->
+ write_a_string(S, N-1, Len);
+ false ->
+ [leaf(Sl)|write_a_string(lists:nthtail(length(SS), S), Len, Len)]
+ end.
+
+%%
+%% Utilities
+%%
+
+-define(N_SPACES, 30).
+
+spacetab() ->
+ {[_|L],_} = mapfoldl(fun(_, A) -> {A,[$\s|A]}
+ end, [], lists:seq(0, ?N_SPACES)),
+ list_to_tuple(L).
+
+spaces(N, T) when N =< ?N_SPACES ->
+ element(N, T);
+spaces(N, T) ->
+ [element(?N_SPACES, T)|spaces(N-?N_SPACES, T)].
+
+wordtable() ->
+ L = [begin {leaf,Sz,S} = leaf(W), {S,Sz} end ||
+ W <- [" ->"," =","<<",">>","[]","after","begin","case","catch",
+ "end","fun","if","of","receive","try","when"," ::","..",
+ " |"]],
+ list_to_tuple(L).
+
+word(' ->', WT) -> element(1, WT);
+word(' =', WT) -> element(2, WT);
+word('<<', WT) -> element(3, WT);
+word('>>', WT) -> element(4, WT);
+word('[]', WT) -> element(5, WT);
+word('after', WT) -> element(6, WT);
+word('begin', WT) -> element(7, WT);
+word('case', WT) -> element(8, WT);
+word('catch', WT) -> element(9, WT);
+word('end', WT) -> element(10, WT);
+word('fun', WT) -> element(11, WT);
+word('if', WT) -> element(12, WT);
+word('of', WT) -> element(13, WT);
+word('receive', WT) -> element(14, WT);
+word('try', WT) -> element(15, WT);
+word('when', WT) -> element(16, WT);
+word(' ::', WT) -> element(17, WT);
+word('..', WT) -> element(18, WT);
+word(' |', WT) -> element(19, WT).
diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl
new file mode 100644
index 0000000000..52ec81a78b
--- /dev/null
+++ b/lib/stdlib/src/erl_scan.erl
@@ -0,0 +1,1307 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% Erlang token scanning functions of io library.
+
+%% For handling ISO 8859-1 (Latin-1) we use the following type
+%% information:
+%%
+%% 000 - 037 NUL - US control
+%% 040 - 057 SPC - / punctuation
+%% 060 - 071 0 - 9 digit
+%% 072 - 100 : - @ punctuation
+%% 101 - 132 A - Z uppercase
+%% 133 - 140 [ - ` punctuation
+%% 141 - 172 a - z lowercase
+%% 173 - 176 { - ~ punctuation
+%% 177 DEL control
+%% 200 - 237 control
+%% 240 - 277 NBSP - � punctuation
+%% 300 - 326 � - � uppercase
+%% 327 � punctuation
+%% 330 - 336 � - � uppercase
+%% 337 - 366 � - � lowercase
+%% 367 � punctuation
+%% 370 - 377 � - � lowercase
+%%
+%% Many punctuation characters have special meaning:
+%% $\s, $_, $", $$, $%, $', $.
+%% DEL is a punctuation.
+%%
+%% Must watch using � \327, very close to x \170.
+
+-module(erl_scan).
+
+%%% External exports
+
+-export([string/1,string/2,string/3,tokens/3,tokens/4,
+ format_error/1,reserved_word/1,
+ token_info/1,token_info/2,
+ attributes_info/1,attributes_info/2,set_attribute/3]).
+
+%%% Local record.
+-record(erl_scan,
+ {resword_fun=fun reserved_word/1,
+ ws=false,
+ comment=false,
+ text=false}).
+
+%%%
+%%% Exported functions
+%%%
+
+-define(COLUMN(C), is_integer(C), C >= 1).
+%% Line numbers less than zero have always been allowed:
+-define(ALINE(L), is_integer(L)).
+-define(STRING(S), is_list(S)).
+-define(RESWORDFUN(F), is_function(F, 1)).
+-define(SETATTRFUN(F), is_function(F, 1)).
+
+-type category() :: atom().
+-type column() :: pos_integer().
+-type line() :: integer().
+-type location() :: line() | {line(),column()}.
+-type resword_fun() :: fun((atom()) -> boolean()).
+-type option() :: 'return' | 'return_white_spaces' | 'return_comments'
+ | 'text' | {'reserved_word_fun', resword_fun()}.
+-type options() :: option() | [option()].
+-type symbol() :: atom() | float() | integer() | string().
+-type info_line() :: integer() | term().
+-type attributes_data()
+ :: [{'column', column()} | {'line', info_line()} | {'text', string()}]
+ | {line(), column()}.
+%% The fact that {line(),column()} is a possible attributes() type
+%% is hidden.
+-type attributes() :: line() | attributes_data().
+-type token() :: {category(), attributes(), symbol()}
+ | {category(), attributes()}.
+-type tokens() :: [token()].
+-type error_description() :: term().
+-type error_info() :: {location(), module(), error_description()}.
+
+-spec format_error(Error :: term()) -> string().
+format_error({string,Quote,Head}) ->
+ lists:flatten(["unterminated " ++ string_thing(Quote) ++
+ " starting with " ++
+ io_lib:write_unicode_string(Head, Quote)]);
+format_error({illegal,Type}) ->
+ lists:flatten(io_lib:fwrite("illegal ~w", [Type]));
+format_error(char) -> "unterminated character";
+format_error({base,Base}) ->
+ lists:flatten(io_lib:fwrite("illegal base '~w'", [Base]));
+format_error(Other) ->
+ lists:flatten(io_lib:write(Other)).
+
+-type string_return() :: {'ok', tokens(), location()}
+ | {'error', error_info(), location()}.
+
+-spec string(String :: string()) -> string_return().
+string(String) ->
+ string(String, 1, []).
+
+-spec string(String :: string(), StartLocation :: location()) ->
+ string_return().
+string(String, StartLocation) ->
+ string(String, StartLocation, []).
+
+-spec string(String :: string(), StartLocation :: location(),
+ Options :: options()) -> string_return().
+string(String, Line, Options) when ?STRING(String), ?ALINE(Line) ->
+ string1(String, options(Options), Line, no_col, []);
+string(String, {Line,Column}, Options) when ?STRING(String),
+ ?ALINE(Line),
+ ?COLUMN(Column) ->
+ string1(String, options(Options), Line, Column, []).
+
+-type char_spec() :: string() | 'eof'.
+-type cont_fun() :: fun((char_spec(), #erl_scan{}, line(), column(),
+ tokens(), any()) -> any()).
+-opaque return_cont() :: {string(), column(), tokens(), line(),
+ #erl_scan{}, cont_fun(), any()}.
+-type cont() :: return_cont() | [].
+-type tokens_result() :: {'ok', tokens(), location()}
+ | {'eof', location()}
+ | {'error', error_info(), location()}.
+-type tokens_return() :: {'done', tokens_result(), char_spec()}
+ | {'more', return_cont()}.
+
+-spec tokens(Cont :: cont(), CharSpec :: char_spec(),
+ StartLocation :: location()) -> tokens_return().
+tokens(Cont, CharSpec, StartLocation) ->
+ tokens(Cont, CharSpec, StartLocation, []).
+
+-spec tokens(Cont :: cont(), CharSpec :: char_spec(),
+ StartLocation :: location(), Options :: options()) ->
+ tokens_return().
+tokens([], CharSpec, Line, Options) when ?ALINE(Line) ->
+ tokens1(CharSpec, options(Options), Line, no_col, [], fun scan/6, []);
+tokens([], CharSpec, {Line,Column}, Options) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ tokens1(CharSpec, options(Options), Line, Column, [], fun scan/6, []);
+tokens({Cs,Col,Toks,Line,St,Any,Fun}, CharSpec, _Loc, _Opts) ->
+ tokens1(Cs++CharSpec, St, Line, Col, Toks, Fun, Any).
+
+-type attribute_item() :: 'column' | 'length' | 'line'
+ | 'location' | 'text'.
+-type info_location() :: location() | term().
+-type attribute_info() :: {'column', column()}| {'length', pos_integer()}
+ | {'line', info_line()}
+ | {'location', info_location()}
+ | {'text', string()}.
+-type token_item() :: 'category' | 'symbol' | attribute_item().
+-type token_info() :: {'category', category()} | {'symbol', symbol()}
+ | attribute_info().
+
+-spec token_info(token()) -> [token_info()].
+token_info(Token) ->
+ Items = [category,column,length,line,symbol,text], % undefined order
+ token_info(Token, Items).
+
+-spec token_info(token(), token_item()) -> token_info() | 'undefined';
+ (token(), [token_item()]) -> [token_info()].
+token_info(_Token, []) ->
+ [];
+token_info(Token, [Item|Items]) when is_atom(Item) ->
+ case token_info(Token, Item) of
+ undefined ->
+ token_info(Token, Items);
+ TokenInfo when is_tuple(TokenInfo) ->
+ [TokenInfo|token_info(Token, Items)]
+ end;
+token_info({Category,_Attrs}, category=Item) ->
+ {Item,Category};
+token_info({Category,_Attrs,_Symbol}, category=Item) ->
+ {Item,Category};
+token_info({Category,_Attrs}, symbol=Item) ->
+ {Item,Category};
+token_info({_Category,_Attrs,Symbol}, symbol=Item) ->
+ {Item,Symbol};
+token_info({_Category,Attrs}, Item) ->
+ attributes_info(Attrs, Item);
+token_info({_Category,Attrs,_Symbol}, Item) ->
+ attributes_info(Attrs, Item).
+
+-spec attributes_info(attributes()) -> [attribute_info()].
+attributes_info(Attributes) ->
+ Items = [column,length,line,text], % undefined order
+ attributes_info(Attributes, Items).
+
+-spec attributes_info(attributes(), attribute_item()) ->
+ attribute_info() | 'undefined';
+ (attributes(), [attribute_item()]) -> [attribute_info()].
+attributes_info(_Attrs, []) ->
+ [];
+attributes_info(Attrs, [A|As]) when is_atom(A) ->
+ case attributes_info(Attrs, A) of
+ undefined ->
+ attributes_info(Attrs, As);
+ AttributeInfo when is_tuple(AttributeInfo) ->
+ [AttributeInfo|attributes_info(Attrs, As)]
+ end;
+attributes_info({Line,Column}, column=Item) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ {Item,Column};
+attributes_info(Line, column) when ?ALINE(Line) ->
+ undefined;
+attributes_info(Attrs, column=Item) ->
+ attr_info(Attrs, Item);
+attributes_info(Attrs, length=Item) ->
+ case attributes_info(Attrs, text) of
+ undefined ->
+ undefined;
+ {text,Text} ->
+ {Item,length(Text)}
+ end;
+attributes_info(Line, line=Item) when ?ALINE(Line) ->
+ {Item,Line};
+attributes_info({Line,Column}, line=Item) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ {Item,Line};
+attributes_info(Attrs, line=Item) ->
+ attr_info(Attrs, Item);
+attributes_info({Line,Column}=Location, location=Item) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ {Item,Location};
+attributes_info(Line, location=Item) when ?ALINE(Line) ->
+ {Item,Line};
+attributes_info(Attrs, location=Item) ->
+ {line,Line} = attributes_info(Attrs, line), % assume line is present
+ case attributes_info(Attrs, column) of
+ undefined ->
+ %% If set_attribute() has assigned a term such as {17,42}
+ %% to 'line', then Line will look like {Line,Column}. One
+ %% should not use 'location' but 'line' and 'column' in
+ %% such special cases.
+ {Item,Line};
+ {column,Column} ->
+ {Item,{Line,Column}}
+ end;
+attributes_info({Line,Column}, text) when ?ALINE(Line), ?COLUMN(Column) ->
+ undefined;
+attributes_info(Line, text) when ?ALINE(Line) ->
+ undefined;
+attributes_info(Attrs, text=Item) ->
+ attr_info(Attrs, Item);
+attributes_info(T1, T2) ->
+ erlang:error(badarg, [T1,T2]).
+
+-type setlineattr_fun() :: fun((info_line()) -> info_line()).
+
+-spec set_attribute('line', attributes(), setlineattr_fun()) -> attributes().
+set_attribute(Tag, Attributes, Fun) when ?SETATTRFUN(Fun) ->
+ set_attr(Tag, Attributes, Fun).
+
+%%%
+%%% Local functions
+%%%
+
+string_thing($') -> "atom"; %' Stupid Emacs
+string_thing(_) -> "string".
+
+-define(WHITE_SPACE(C),
+ is_integer(C) andalso
+ (C >= $\000 andalso C =< $\s orelse C >= $\200 andalso C =< $\240)).
+-define(DIGIT(C), C >= $0, C =< $9).
+-define(CHAR(C), is_integer(C), C >= 0).
+
+%% A workaround: Unicode strings are not returned as strings, but as
+%% lists of integers. For instance, "b\x{aaa}c" => [98,2730,99]. This
+%% is to protect the system from character codes greater than 255. To
+%% be removed. Search for UNI to find workaround code.
+-define(NO_UNICODE, 0).
+-define(UNI255(C), (C) =< 16#ff).
+
+options(Opts0) when is_list(Opts0) ->
+ Opts = lists:foldr(fun expand_opt/2, [], Opts0),
+ [RW_fun] =
+ case opts(Opts, [reserved_word_fun], []) of
+ badarg ->
+ erlang:error(badarg, [Opts0]);
+ R ->
+ R
+ end,
+ Comment = proplists:get_bool(return_comments, Opts),
+ WS = proplists:get_bool(return_white_spaces, Opts),
+ Txt = proplists:get_bool(text, Opts),
+ #erl_scan{resword_fun = RW_fun,
+ comment = Comment,
+ ws = WS,
+ text = Txt};
+options(Opt) ->
+ options([Opt]).
+
+opts(Options, [Key|Keys], L) ->
+ V = case lists:keysearch(Key, 1, Options) of
+ {value,{reserved_word_fun,F}} when ?RESWORDFUN(F) ->
+ {ok,F};
+ {value,{Key,_}} ->
+ badarg;
+ false ->
+ {ok,default_option(Key)}
+ end,
+ case V of
+ badarg ->
+ badarg;
+ {ok,Value} ->
+ opts(Options, Keys, [Value|L])
+ end;
+opts(_Options, [], L) ->
+ lists:reverse(L).
+
+default_option(reserved_word_fun) ->
+ fun reserved_word/1.
+
+expand_opt(return, Os) ->
+ [return_comments,return_white_spaces|Os];
+expand_opt(O, Os) ->
+ [O|Os].
+
+attr_info(Attrs, Item) ->
+ case catch lists:keysearch(Item, 1, Attrs) of
+ {value,{Item,Value}} ->
+ {Item,Value};
+ false ->
+ undefined;
+ _ ->
+ erlang:error(badarg, [Attrs, Item])
+ end.
+
+-spec set_attr('line', attributes(), fun((line()) -> line())) -> attributes().
+
+set_attr(line, Line, Fun) when ?ALINE(Line) ->
+ Ln = Fun(Line),
+ if
+ ?ALINE(Ln) ->
+ Ln;
+ true ->
+ [{line,Ln}]
+ end;
+set_attr(line, {Line,Column}, Fun) when ?ALINE(Line), ?COLUMN(Column) ->
+ Ln = Fun(Line),
+ if
+ ?ALINE(Ln) ->
+ {Ln,Column};
+ true ->
+ [{line,Ln},{column,Column}]
+ end;
+set_attr(line=Tag, Attrs, Fun) when is_list(Attrs) ->
+ {line,Line} = lists:keyfind(Tag, 1, Attrs),
+ lists:keyreplace(Tag, 1, Attrs, {line,Fun(Line)});
+set_attr(T1, T2, T3) ->
+ erlang:error(badarg, [T1,T2,T3]).
+
+tokens1(Cs, St, Line, Col, Toks, Fun, Any) when ?STRING(Cs); Cs =:= eof ->
+ case Fun(Cs, St, Line, Col, Toks, Any) of
+ {more,{Cs0,Ncol,Ntoks,Nline,Nany,Nfun}} ->
+ {more,{Cs0,Ncol,Ntoks,Nline,St,Nany,Nfun}};
+ {ok,Toks0,eof,Nline,Ncol} ->
+ Res = case Toks0 of
+ [] ->
+ {eof,location(Nline, Ncol)};
+ _ ->
+ {ok,lists:reverse(Toks0),location(Nline,Ncol)}
+ end,
+ {done,Res,eof};
+ {ok,Toks0,Rest,Nline,Ncol} ->
+ {done,{ok,lists:reverse(Toks0),location(Nline, Ncol)},Rest};
+ {{error,_,_}=Error,Rest} ->
+ {done,Error,Rest}
+ end.
+
+string1(Cs, St, Line, Col, Toks) ->
+ case scan1(Cs, St, Line, Col, Toks) of
+ {more,{Cs0,Ncol,Ntoks,Nline,Any,Fun}} ->
+ case Fun(Cs0++eof, St, Nline, Ncol, Ntoks, Any) of
+ {ok,Toks1,_Rest,Line2,Col2} ->
+ {ok,lists:reverse(Toks1),location(Line2, Col2)};
+ {{error,_,_}=Error,_Rest} ->
+ Error
+ end;
+ {ok,Ntoks,[_|_]=Rest,Nline,Ncol} ->
+ string1(Rest, St, Nline, Ncol, Ntoks);
+ {ok,Ntoks,_,Nline,Ncol} ->
+ {ok,lists:reverse(Ntoks),location(Nline, Ncol)};
+ {{error,_,_}=Error,_Rest} ->
+ Error
+ end.
+
+scan(Cs, St, Line, Col, Toks, _) ->
+ scan1(Cs, St, Line, Col, Toks).
+
+scan1([$\s|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ scan_spcs(Cs, St, Line, Col, Toks, 1);
+scan1([$\s|Cs], St, Line, Col, Toks) ->
+ skip_white_space(Cs, St, Line, Col, Toks, 1);
+scan1([$\n|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ scan_newline(Cs, St, Line, Col, Toks);
+scan1([$\n|Cs], St, Line, Col, Toks) ->
+ skip_white_space(Cs, St, Line+1, new_column(Col, 1), Toks, 0);
+scan1([C|Cs], St, Line, Col, Toks) when C >= $A, C =< $Z ->
+ scan_variable(Cs, St, Line, Col, Toks, [C]);
+scan1([C|Cs], St, Line, Col, Toks) when C >= $a, C =< $z ->
+ scan_atom(Cs, St, Line, Col, Toks, [C]);
+%% Optimization: some very common punctuation characters:
+scan1([$,|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ",", ',', 1);
+scan1([$(|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "(", '(', 1);
+scan1([$)|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ")", ')', 1);
+scan1([${|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "{", '{', 1);
+scan1([$}|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "}", '}', 1);
+scan1([$[|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "[", '[', 1);
+scan1([$]|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "]", ']', 1);
+scan1([$;|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ";", ';', 1);
+scan1([$_=C|Cs], St, Line, Col, Toks) ->
+ scan_variable(Cs, St, Line, Col, Toks, [C]);
+%% More punctuation characters below.
+scan1([$\%|Cs], St, Line, Col, Toks) when not St#erl_scan.comment ->
+ skip_comment(Cs, St, Line, Col, Toks, 1);
+scan1([$\%=C|Cs], St, Line, Col, Toks) ->
+ scan_comment(Cs, St, Line, Col, Toks, [C]);
+scan1([C|Cs], St, Line, Col, Toks) when ?DIGIT(C) ->
+ scan_number(Cs, St, Line, Col, Toks, [C]);
+scan1([$.=C|Cs], St, Line, Col, Toks) ->
+ scan_dot(Cs, St, Line, Col, Toks, [C]);
+scan1([$"|Cs], St, Line, Col, Toks) -> %" Emacs
+ State0 = {[],[],Line,Col,?NO_UNICODE},
+ scan_string(Cs, St, Line, incr_column(Col, 1), Toks, State0);
+scan1([$'|Cs], St, Line, Col, Toks) -> %' Emacs
+ State0 = {[],[],Line,Col,?NO_UNICODE},
+ scan_qatom(Cs, St, Line, incr_column(Col, 1), Toks, State0);
+scan1([$$|Cs], St, Line, Col, Toks) ->
+ scan_char(Cs, St, Line, Col, Toks);
+scan1([$\r|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ white_space_end(Cs, St, Line, Col, Toks, 1, "\r");
+scan1([C|Cs], St, Line, Col, Toks) when C >= $�, C =< $�, C =/= $� ->
+ scan_atom(Cs, St, Line, Col, Toks, [C]);
+scan1([C|Cs], St, Line, Col, Toks) when C >= $�, C =< $�, C /= $� ->
+ scan_variable(Cs, St, Line, Col, Toks, [C]);
+scan1([$\t|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ scan_tabs(Cs, St, Line, Col, Toks, 1);
+scan1([$\t|Cs], St, Line, Col, Toks) ->
+ skip_white_space(Cs, St, Line, Col, Toks, 1);
+scan1([C|Cs], St, Line, Col, Toks) when ?WHITE_SPACE(C) ->
+ case St#erl_scan.ws of
+ true ->
+ scan_white_space(Cs, St, Line, Col, Toks, [C]);
+ false ->
+ skip_white_space(Cs, St, Line, Col, Toks, 1)
+ end;
+%% Punctuation characters and operators, first recognise multiples.
+%% << <- <=
+scan1("<<"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<<", '<<', 2);
+scan1("<-"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<-", '<-', 2);
+scan1("<="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<=", '<=', 2);
+scan1("<"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% >> >=
+scan1(">>"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ">>", '>>', 2);
+scan1(">="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ">=", '>=', 2);
+scan1(">"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% -> --
+scan1("->"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "->", '->', 2);
+scan1("--"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "--", '--', 2);
+scan1("-"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% ++
+scan1("++"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "++", '++', 2);
+scan1("+"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% =:= =/= =< ==
+scan1("=:="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=:=", '=:=', 3);
+scan1("=:"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("=/="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=/=", '=/=', 3);
+scan1("=/"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("=<"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=<", '=<', 2);
+scan1("=="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "==", '==', 2);
+scan1("="=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% /=
+scan1("/="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "/=", '/=', 2);
+scan1("/"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% ||
+scan1("||"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "||", '||', 2);
+scan1("|"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% :-
+scan1(":-"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ":-", ':-', 2);
+%% :: for typed records
+scan1("::"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "::", '::', 2);
+scan1(":"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% Optimization: punctuation characters less than 127:
+scan1([$=|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=", '=', 1);
+scan1([$:|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ":", ':', 1);
+scan1([$||Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "|", '|', 1);
+scan1([$#|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "#", '#', 1);
+scan1([$/|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "/", '/', 1);
+scan1([$?|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "?", '?', 1);
+scan1([$-|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "-", '-', 1);
+scan1([$+|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "+", '+', 1);
+scan1([$*|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "*", '*', 1);
+scan1([$<|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<", '<', 1);
+scan1([$>|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ">", '>', 1);
+scan1([$!|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "!", '!', 1);
+scan1([$@|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "@", '@', 1);
+scan1([$\\|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "\\", '\\', 1);
+scan1([$^|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "^", '^', 1);
+scan1([$`|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "`", '`', 1);
+scan1([$~|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "~", '~', 1);
+scan1([$&|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "&", '&', 1);
+%% End of optimization.
+scan1([C|Cs], St, Line, Col, Toks) when ?CHAR(C) ->
+ Str = [C],
+ case catch list_to_atom(Str) of
+ Sym when is_atom(Sym) ->
+ tok2(Cs, St, Line, Col, Toks, Str, Sym, 1);
+ _ ->
+ Ncol = incr_column(Col, 1),
+ scan_error({illegal,character}, Line, Col, Line, Ncol, Cs)
+ end;
+scan1([]=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1(eof=Cs, _St, Line, Col, Toks) ->
+ {ok,Toks,Cs,Line,Col}.
+
+scan_atom(Cs0, St, Line, Col, Toks, Ncs0) ->
+ case scan_name(Cs0, Ncs0) of
+ {more,Ncs} ->
+ {more,{[],Col,Toks,Line,Ncs,fun scan_atom/6}};
+ {Wcs,Cs} ->
+ case catch list_to_atom(Wcs) of
+ Name when is_atom(Name) ->
+ case (St#erl_scan.resword_fun)(Name) of
+ true ->
+ tok2(Cs, St, Line, Col, Toks, Wcs, Name);
+ false ->
+ tok3(Cs, St, Line, Col, Toks, atom, Wcs, Name)
+ end;
+ _Error ->
+ Ncol = incr_column(Col, length(Wcs)),
+ scan_error({illegal,atom}, Line, Col, Line, Ncol, Cs)
+ end
+ end.
+
+scan_variable(Cs0, St, Line, Col, Toks, Ncs0) ->
+ case scan_name(Cs0, Ncs0) of
+ {more,Ncs} ->
+ {more,{[],Col,Toks,Line,Ncs,fun scan_variable/6}};
+ {Wcs,Cs} ->
+ case catch list_to_atom(Wcs) of
+ Name when is_atom(Name) ->
+ tok3(Cs, St, Line, Col, Toks, var, Wcs, Name);
+ _Error ->
+ Ncol = incr_column(Col, length(Wcs)),
+ scan_error({illegal,var}, Line, Col, Line, Ncol, Cs)
+ end
+ end.
+
+scan_name([C|Cs], Ncs) when C >= $a, C =< $z ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when C >= $A, C =< $Z ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([$_=C|Cs], Ncs) ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when ?DIGIT(C) ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([$@=C|Cs], Ncs) ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when C >= $�, C =< $�, C =/= $� ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when C >= $�, C =< $�, C =/= $� ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([], Ncs) ->
+ {more,Ncs};
+scan_name(Cs, Ncs) ->
+ {lists:reverse(Ncs),Cs}.
+
+scan_dot([$%|_]=Cs, St, Line, Col, Toks, Ncs) ->
+ Attrs = attributes(Line, Col, St, Ncs),
+ {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 1)};
+scan_dot([$\n=C|Cs], St, Line, Col, Toks, Ncs) ->
+ Attrs = attributes(Line, Col, St, Ncs++[C]),
+ {ok,[{dot,Attrs}|Toks],Cs,Line+1,new_column(Col, 1)};
+scan_dot([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
+ Attrs = attributes(Line, Col, St, Ncs++[C]),
+ {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 2)};
+scan_dot([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_dot/6}};
+scan_dot(eof=Cs, St, Line, Col, Toks, Ncs) ->
+ Attrs = attributes(Line, Col, St, Ncs),
+ {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 1)};
+scan_dot(Cs, St, Line, Col, Toks, Ncs) ->
+ tok2(Cs, St, Line, Col, Toks, Ncs, '.', 1).
+
+%%% White space characters are very common, so it is worthwhile to
+%%% scan them fast and store them compactly. (The words "whitespace"
+%%% and "white space" usually mean the same thing. The Erlang
+%%% specification denotes the characters with ASCII code in the
+%%% interval 0 to 32 as "white space".)
+%%%
+%%% Convention: if there is a white newline ($\n) it will always be
+%%% the first character in the text string. As a consequence, there
+%%% cannot be more than one newline in a white_space token string.
+%%%
+%%% Some common combinations are recognized, some are not. Examples
+%%% of the latter are tab(s) followed by space(s), like "\t ".
+%%% (They will be represented by two (or more) tokens.)
+%%%
+%%% Note: the character sequence "\r\n" is *not* recognized since it
+%%% would violate the property that $\n will always be the first
+%%% character. (But since "\r\n\r\n" is common, it pays off to
+%%% recognize "\n\r".)
+
+scan_newline([$\s|Cs], St, Line, Col, Toks) ->
+ scan_nl_spcs(Cs, St, Line, Col, Toks, 2);
+scan_newline([$\t|Cs], St, Line, Col, Toks) ->
+ scan_nl_tabs(Cs, St, Line, Col, Toks, 2);
+scan_newline([$\r|Cs], St, Line, Col, Toks) ->
+ newline_end(Cs, St, Line, Col, Toks, 2, "\n\r");
+scan_newline([$\f|Cs], St, Line, Col, Toks) ->
+ newline_end(Cs, St, Line, Col, Toks, 2, "\n\f");
+scan_newline([], _St, Line, Col, Toks) ->
+ {more,{[$\n],Col,Toks,Line,[],fun scan/6}};
+scan_newline(Cs, St, Line, Col, Toks) ->
+ scan_nl_white_space(Cs, St, Line, Col, Toks, "\n").
+
+scan_nl_spcs([$\s|Cs], St, Line, Col, Toks, N) when N < 17 ->
+ scan_nl_spcs(Cs, St, Line, Col, Toks, N+1);
+scan_nl_spcs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_nl_spcs/6}};
+scan_nl_spcs(Cs, St, Line, Col, Toks, N) ->
+ newline_end(Cs, St, Line, Col, Toks, N, nl_spcs(N)).
+
+scan_nl_tabs([$\t|Cs], St, Line, Col, Toks, N) when N < 11 ->
+ scan_nl_tabs(Cs, St, Line, Col, Toks, N+1);
+scan_nl_tabs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_nl_tabs/6}};
+scan_nl_tabs(Cs, St, Line, Col, Toks, N) ->
+ newline_end(Cs, St, Line, Col, Toks, N, nl_tabs(N)).
+
+%% Note: returning {more,Cont} is meaningless here; one could just as
+%% well return several tokens. But since tokens() scans up to a full
+%% stop anyway, nothing is gained by not collecting all white spaces.
+scan_nl_white_space([$\n|Cs], #erl_scan{text = false}=St, Line, no_col=Col,
+ Toks0, Ncs) ->
+ Toks = [{white_space,Line,lists:reverse(Ncs)}|Toks0],
+ scan_newline(Cs, St, Line+1, Col, Toks);
+scan_nl_white_space([$\n|Cs], St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ Attrs = attributes(Line, Col, St, Ncs),
+ Token = {white_space,Attrs,Ncs},
+ scan_newline(Cs, St, Line+1, new_column(Col, length(Ncs)), [Token|Toks]);
+scan_nl_white_space([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
+ scan_nl_white_space(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_nl_white_space([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_nl_white_space/6}};
+scan_nl_white_space(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
+ Toks, Ncs) ->
+ scan1(Cs, St, Line+1, Col, [{white_space,Line,lists:reverse(Ncs)}|Toks]);
+scan_nl_white_space(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ Attrs = attributes(Line, Col, St, Ncs),
+ Token = {white_space,Attrs,Ncs},
+ scan1(Cs, St, Line+1, new_column(Col, length(Ncs)), [Token|Toks]).
+
+newline_end(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
+ Toks, _N, Ncs) ->
+ scan1(Cs, St, Line+1, Col, [{white_space,Line,Ncs}|Toks]);
+newline_end(Cs, St, Line, Col, Toks, N, Ncs) ->
+ Attrs = attributes(Line, Col, St, Ncs),
+ scan1(Cs, St, Line+1, new_column(Col, N), [{white_space,Attrs,Ncs}|Toks]).
+
+scan_spcs([$\s|Cs], St, Line, Col, Toks, N) when N < 16 ->
+ scan_spcs(Cs, St, Line, Col, Toks, N+1);
+scan_spcs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_spcs/6}};
+scan_spcs(Cs, St, Line, Col, Toks, N) ->
+ white_space_end(Cs, St, Line, Col, Toks, N, spcs(N)).
+
+scan_tabs([$\t|Cs], St, Line, Col, Toks, N) when N < 10 ->
+ scan_tabs(Cs, St, Line, Col, Toks, N+1);
+scan_tabs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_tabs/6}};
+scan_tabs(Cs, St, Line, Col, Toks, N) ->
+ white_space_end(Cs, St, Line, Col, Toks, N, tabs(N)).
+
+skip_white_space([$\n|Cs], St, Line, Col, Toks, _N) ->
+ skip_white_space(Cs, St, Line+1, new_column(Col, 1), Toks, 0);
+skip_white_space([C|Cs], St, Line, Col, Toks, N) when ?WHITE_SPACE(C) ->
+ skip_white_space(Cs, St, Line, Col, Toks, N+1);
+skip_white_space([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun skip_white_space/6}};
+skip_white_space(Cs, St, Line, Col, Toks, N) ->
+ scan1(Cs, St, Line, incr_column(Col, N), Toks).
+
+%% Maybe \t and \s should break the loop.
+scan_white_space([$\n|_]=Cs, St, Line, Col, Toks, Ncs) ->
+ white_space_end(Cs, St, Line, Col, Toks, length(Ncs), lists:reverse(Ncs));
+scan_white_space([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
+ scan_white_space(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_white_space([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_white_space/6}};
+scan_white_space(Cs, St, Line, Col, Toks, Ncs) ->
+ white_space_end(Cs, St, Line, Col, Toks, length(Ncs), lists:reverse(Ncs)).
+
+-compile({inline,[white_space_end/7]}).
+
+white_space_end(Cs, St, Line, Col, Toks, N, Ncs) ->
+ tok3(Cs, St, Line, Col, Toks, white_space, Ncs, Ncs, N).
+
+scan_char([$\\|Cs]=Cs0, St, Line, Col, Toks) ->
+ case scan_escape(Cs, incr_column(Col, 2)) of
+ more ->
+ {more,{[$$|Cs0],Col,Toks,Line,[],fun scan/6}};
+ {error,Ncs,Error,Ncol} ->
+ scan_error(Error, Line, Col, Line, Ncol, Ncs);
+ {eof,Ncol} ->
+ scan_error(char, Line, Col, Line, Ncol, eof);
+ {nl,Val,Str,Ncs,Ncol} ->
+ Attrs = attributes(Line, Col, St, "$\\"++Str),
+ Ntoks = [{char,Attrs,Val}|Toks],
+ scan1(Ncs, St, Line+1, Ncol, Ntoks);
+ {unicode,Val,Str,Ncs,Ncol} ->
+ Attrs = attributes(Line, Col, St, "$\\"++Str),
+ Ntoks = [{integer,Attrs,Val}|Toks], % UNI
+ scan1(Ncs, St, Line, Ncol, Ntoks);
+ {Val,Str,Ncs,Ncol} ->
+ Attrs = attributes(Line, Col, St, "$\\"++Str),
+ Ntoks = [{char,Attrs,Val}|Toks],
+ scan1(Ncs, St, Line, Ncol, Ntoks)
+ end;
+scan_char([$\n=C|Cs], St, Line, Col, Toks) ->
+ Attrs = attributes(Line, Col, St, [$$,C]),
+ scan1(Cs, St, Line+1, new_column(Col, 1), [{char,Attrs,C}|Toks]);
+scan_char([C|Cs], St, Line, Col, Toks) when ?CHAR(C) ->
+ Tag = if ?UNI255(C) -> char; true -> integer end, % UNI
+ Attrs = attributes(Line, Col, St, [$$,C]),
+ scan1(Cs, St, Line, incr_column(Col, 2), [{Tag,Attrs,C}|Toks]);
+scan_char([], _St, Line, Col, Toks) ->
+ {more,{[$$],Col,Toks,Line,[],fun scan/6}};
+scan_char(eof, _St, Line, Col, _Toks) ->
+ scan_error(char, Line, Col, Line, incr_column(Col, 1), eof).
+
+scan_string(Cs, St, Line, Col, Toks, {Wcs,Str,Line0,Col0,Uni0}) ->
+ case scan_string0(Cs, St, Line, Col, $\", Str, Wcs, Uni0) of
+ {more,Ncs,Nline,Ncol,Nstr,Nwcs,Uni} ->
+ State = {Nwcs,Nstr,Line0,Col0,Uni},
+ {more,{Ncs,Ncol,Toks,Nline,State,fun scan_string/6}};
+ {char_error,Ncs,Error,Nline,Ncol,EndCol} ->
+ scan_error(Error, Nline, Ncol, Nline, EndCol, Ncs);
+ {error,Nline,Ncol,Nwcs,Ncs} ->
+ Estr = string:substr(Nwcs, 1, 16), % Expanded escape chars.
+ scan_error({string,$\",Estr}, Line0, Col0, Nline, Ncol, Ncs);
+ {Ncs,Nline,Ncol,Nstr,Nwcs,?NO_UNICODE} ->
+ Attrs = attributes(Line0, Col0, St, Nstr),
+ scan1(Ncs, St, Nline, Ncol, [{string,Attrs,Nwcs}|Toks]);
+ {Ncs,Nline,Ncol,Nstr,_Nwcs,_Uni} ->
+ Ntoks = unicode_string_to_list(Line0, Col0, St, Nstr, Toks),
+ scan1(Ncs, St, Nline, Ncol, Ntoks)
+ end.
+
+%% UNI
+unicode_string_to_list(Line, Col, St, [$"=C|Nstr], Toks) -> %" Emacs
+ Paren = {'[',attributes(Line, Col, St, [C])},
+ u2l(Nstr, Line, incr_column(Col, 1), St, [Paren|Toks]).
+
+u2l([$"]=Cs, Line, Col, St, Toks) -> %" Emacs
+ [{']',attributes(Line, Col, St, Cs)}|Toks];
+u2l([$\n=C|Cs], Line, Col, St, Toks) ->
+ Ntoks = unicode_nl_tokens(Line, Col, [C], C, St, Toks, Cs),
+ u2l(Cs, Line+1, new_column(Col, 1), St, Ntoks);
+u2l([$\\|Cs], Line, Col, St, Toks) ->
+ case scan_escape(Cs, Col) of
+ {nl,Val,ValStr,Ncs,Ncol} ->
+ Nstr = [$\\|ValStr],
+ Ntoks = unicode_nl_tokens(Line, Col, Nstr, Val, St, Toks, Ncs),
+ u2l(Ncs, Line+1, Ncol, St, Ntoks);
+ {unicode,Val,ValStr,Ncs,Ncol} ->
+ Nstr = [$\\|ValStr],
+ Ntoks = unicode_tokens(Line, Col, Nstr, Val, St, Toks, Ncs),
+ u2l(Ncs, Line, incr_column(Ncol, 1), St, Ntoks);
+ {Val,ValStr,Ncs,Ncol} ->
+ Nstr = [$\\|ValStr],
+ Ntoks = unicode_tokens(Line, Col, Nstr, Val, St, Toks, Ncs),
+ u2l(Ncs, Line, incr_column(Ncol, 1), St, Ntoks)
+ end;
+u2l([C|Cs], Line, Col, St, Toks) ->
+ Ntoks = unicode_tokens(Line, Col, [C], C, St, Toks, Cs),
+ u2l(Cs, Line, incr_column(Col, 1), St, Ntoks).
+
+unicode_nl_tokens(Line, Col, Str, Val, St, Toks, Cs) ->
+ Ccol = new_column(Col, 1),
+ unicode_tokens(Line, Col, Str, Val, St, Toks, Cs, Line+1, Ccol).
+
+unicode_tokens(Line, Col, Str, Val, St, Toks, Cs) ->
+ Ccol = incr_column(Col, length(Str)),
+ unicode_tokens(Line, Col, Str, Val, St, Toks, Cs, Line, Ccol).
+
+unicode_tokens(Line, Col, Str, Val, St, Toks, Cs, Cline, Ccol) ->
+ Attrs = attributes(Line, Col, St, Str),
+ Tag = if ?UNI255(Val) -> char; true -> integer end,
+ Token = {Tag,Attrs,Val},
+ [{',',attributes(Cline, Ccol, St, "")} || Cs =/= "\""] ++ [Token|Toks].
+
+scan_qatom(Cs, St, Line, Col, Toks, {Wcs,Str,Line0,Col0,Uni0}) ->
+ case scan_string0(Cs, St, Line, Col, $\', Str, Wcs, Uni0) of
+ {more,Ncs,Nline,Ncol,Nstr,Nwcs,Uni} ->
+ State = {Nwcs,Nstr,Line0,Col0,Uni},
+ {more,{Ncs,Ncol,Toks,Nline,State,fun scan_qatom/6}};
+ {char_error,Ncs,Error,Nline,Ncol,EndCol} ->
+ scan_error(Error, Nline, Ncol, Nline, EndCol, Ncs);
+ {error,Nline,Ncol,Nwcs,Ncs} ->
+ Estr = string:substr(Nwcs, 1, 16), % Expanded escape chars.
+ scan_error({string,$\',Estr}, Line0, Col0, Nline, Ncol, Ncs);
+ {Ncs,Nline,Ncol,Nstr,Nwcs,?NO_UNICODE} ->
+ case catch list_to_atom(Nwcs) of
+ A when is_atom(A) ->
+ Attrs = attributes(Line0, Col0, St, Nstr),
+ scan1(Ncs, St, Nline, Ncol, [{atom,Attrs,A}|Toks]);
+ _ ->
+ scan_error({illegal,atom}, Line0, Col0, Nline, Ncol, Ncs)
+ end
+ end.
+
+scan_string0(Cs, #erl_scan{text=false}, Line, no_col=Col, Q, [], Wcs, Uni) ->
+ scan_string_no_col(Cs, Line, Col, Q, Wcs, Uni);
+scan_string0(Cs, #erl_scan{text=true}, Line, no_col=Col, Q, Str, Wcs, Uni) ->
+ scan_string1(Cs, Line, Col, Q, Str, Wcs, Uni);
+scan_string0(Cs, _St, Line, Col, Q, [], Wcs, Uni) ->
+ scan_string_col(Cs, Line, Col, Q, Wcs, Uni);
+scan_string0(Cs, _St, Line, Col, Q, Str, Wcs, Uni) ->
+ scan_string1(Cs, Line, Col, Q, Str, Wcs, Uni).
+
+%% Optimization. Col =:= no_col.
+scan_string_no_col([Q|Cs], Line, Col, Q, Wcs, Uni) ->
+ {Cs,Line,Col,_DontCare=[],lists:reverse(Wcs),Uni};
+scan_string_no_col([$\n=C|Cs], Line, Col, Q, Wcs, Uni) ->
+ scan_string_no_col(Cs, Line+1, Col, Q, [C|Wcs], Uni);
+scan_string_no_col([C|Cs], Line, Col, Q, Wcs, Uni) when C =/= $\\,
+ ?CHAR(C), ?UNI255(C) ->
+ scan_string_no_col(Cs, Line, Col, Q, [C|Wcs], Uni);
+scan_string_no_col(Cs, Line, Col, Q, Wcs, Uni) ->
+ scan_string1(Cs, Line, Col, Q, Wcs, Wcs, Uni).
+
+%% Optimization. Col =/= no_col.
+scan_string_col([Q|Cs], Line, Col, Q, Wcs0, Uni) ->
+ Wcs = lists:reverse(Wcs0),
+ Str = [Q|Wcs++[Q]],
+ {Cs,Line,Col+1,Str,Wcs,Uni};
+scan_string_col([$\n=C|Cs], Line, _xCol, Q, Wcs, Uni) ->
+ scan_string_col(Cs, Line+1, 1, Q, [C|Wcs], Uni);
+scan_string_col([C|Cs], Line, Col, Q, Wcs, Uni) when C =/= $\\,
+ ?CHAR(C), ?UNI255(C) ->
+ scan_string_col(Cs, Line, Col+1, Q, [C|Wcs], Uni);
+scan_string_col(Cs, Line, Col, Q, Wcs, Uni) ->
+ scan_string1(Cs, Line, Col, Q, Wcs, Wcs, Uni).
+
+%% UNI_STR is to be replaced by STR when the Unicode-string-to-list
+%% workaround is eventually removed.
+-define(UNI_STR(Col, S), S).
+
+%% Note: in those cases when a 'char_error' tuple is returned below it
+%% is tempting to skip over characters up to the first Q character,
+%% but then the end location of the error tuple would not correspond
+%% to the start location of the returned Rest string. (Maybe the end
+%% location could be modified, but that too is ugly.)
+scan_string1([Q|Cs], Line, Col, Q, Str0, Wcs0, Uni) ->
+ Wcs = lists:reverse(Wcs0),
+ Str = ?UNI_STR(Col, [Q|lists:reverse(Str0, [Q])]),
+ {Cs,Line,incr_column(Col, 1),Str,Wcs,Uni};
+scan_string1([$\n=C|Cs], Line, Col, Q, Str, Wcs, Uni) ->
+ Ncol = new_column(Col, 1),
+ scan_string1(Cs, Line+1, Ncol, Q, ?UNI_STR(Col, [C|Str]), [C|Wcs], Uni);
+scan_string1([$\\|Cs]=Cs0, Line, Col, Q, Str, Wcs, Uni) ->
+ case scan_escape(Cs, Col) of
+ more ->
+ {more,Cs0,Line,Col,Str,Wcs,Uni};
+ {error,Ncs,Error,Ncol} ->
+ {char_error,Ncs,Error,Line,Col,incr_column(Ncol, 1)};
+ {eof,Ncol} ->
+ {error,Line,incr_column(Ncol, 1),lists:reverse(Wcs),eof};
+ {nl,Val,ValStr,Ncs,Ncol} ->
+ Nstr = ?UNI_STR(Ncol, lists:reverse(ValStr, [$\\|Str])),
+ Nwcs = [Val|Wcs],
+ scan_string1(Ncs, Line+1, Ncol, Q, Nstr, Nwcs, Uni);
+ {unicode,_Val,_ValStr,Ncs,Ncol} when Q =:= $' -> %' Emacs
+ {char_error,Ncs,{illegal,character},Line,Col,incr_column(Ncol, 1)};
+ {unicode,Val,ValStr,Ncs,Ncol} -> % UNI. Uni is set to Val.
+ Nstr = ?UNI_STR(Ncol, lists:reverse(ValStr, [$\\|Str])),
+ Nwcs = [Val|Wcs], % not used
+ scan_string1(Ncs, Line, incr_column(Ncol, 1), Q, Nstr, Nwcs, Val);
+ {Val,ValStr,Ncs,Ncol} ->
+ Nstr = ?UNI_STR(Ncol, lists:reverse(ValStr, [$\\|Str])),
+ Nwcs = [Val|Wcs],
+ scan_string1(Ncs, Line, incr_column(Ncol, 1), Q, Nstr, Nwcs, Uni)
+ end;
+scan_string1([C|Cs], Line, no_col=Col, Q, Str, Wcs, Uni) when ?CHAR(C),
+ ?UNI255(C) ->
+ %% scan_string1(Cs, Line, Col, Q, Str, [C|Wcs], Uni);
+ scan_string1(Cs, Line, Col, Q, [C|Str], [C|Wcs], Uni); % UNI
+scan_string1([C|Cs], Line, Col, Q, Str, Wcs, Uni) when ?CHAR(C), ?UNI255(C) ->
+ scan_string1(Cs, Line, Col+1, Q, [C|Str], [C|Wcs], Uni);
+scan_string1([C|Cs], Line, Col, $', _Str, _Wcs, _Uni) when ?CHAR(C) -> %' UNI
+ {char_error,Cs,{illegal,character},Line,Col,incr_column(Col, 1)};
+scan_string1([C|Cs], Line, Col, Q, Str, Wcs, _Uni) when ?CHAR(C) -> % UNI
+ scan_string1(Cs, Line, incr_column(Col, 1), Q, [C|Str], [C|Wcs], C);
+scan_string1([]=Cs, Line, Col, _Q, Str, Wcs, Uni) ->
+ {more,Cs,Line,Col,Str,Wcs,Uni};
+scan_string1(eof, Line, Col, _Q, _Str, Wcs, _Uni) ->
+ {error,Line,Col,lists:reverse(Wcs),eof}.
+
+-define(OCT(C), C >= $0, C =< $7).
+-define(HEX(C), C >= $0 andalso C =< $9 orelse
+ C >= $A andalso C =< $F orelse
+ C >= $a andalso C =< $f).
+
+%% \<1-3> octal digits
+scan_escape([O1,O2,O3|Cs], Col) when ?OCT(O1), ?OCT(O2), ?OCT(O3) ->
+ Val = (O1*8 + O2)*8 + O3 - 73*$0,
+ {Val,?UNI_STR(Col, [O1,O2,O3]),Cs,incr_column(Col, 3)};
+scan_escape([O1,O2], _Col) when ?OCT(O1), ?OCT(O2) ->
+ more;
+scan_escape([O1,O2|Cs], Col) when ?OCT(O1), ?OCT(O2) ->
+ Val = (O1*8 + O2) - 9*$0,
+ {Val,?UNI_STR(Col, [O1,O2]),Cs,incr_column(Col, 2)};
+scan_escape([O1], _Col) when ?OCT(O1) ->
+ more;
+scan_escape([O1|Cs], Col) when ?OCT(O1) ->
+ {O1 - $0,?UNI_STR(Col, [O1]),Cs,incr_column(Col, 1)};
+%% \x{<hex digits>}
+scan_escape([$x,${|Cs], Col) ->
+ scan_hex(Cs, incr_column(Col, 2), []);
+scan_escape([$x], _Col) ->
+ more;
+scan_escape([$x|eof], Col) ->
+ {eof,incr_column(Col, 1)};
+%% \x<2> hexadecimal digits
+scan_escape([$x,H1,H2|Cs], Col) when ?HEX(H1), ?HEX(H2) ->
+ Val = erlang:list_to_integer([H1,H2], 16),
+ {Val,?UNI_STR(Col, [$x,H1,H2]),Cs,incr_column(Col, 3)};
+scan_escape([$x,H1], _Col) when ?HEX(H1) ->
+ more;
+scan_escape([$x|Cs], Col) ->
+ {error,Cs,{illegal,character},incr_column(Col, 1)};
+%% \^X -> CTL-X
+scan_escape([$^=C0,$\n=C|Cs], Col) ->
+ {nl,C,?UNI_STR(Col, [C0,C]),Cs,new_column(Col, 1)};
+scan_escape([$^=C0,C|Cs], Col) when ?CHAR(C) ->
+ Val = C band 31,
+ {Val,?UNI_STR(Col, [C0,C]),Cs,incr_column(Col, 2)};
+scan_escape([$^], _Col) ->
+ more;
+scan_escape([$^|eof], Col) ->
+ {eof,incr_column(Col, 1)};
+scan_escape([$\n=C|Cs], Col) ->
+ {nl,C,?UNI_STR(Col, [C]),Cs,new_column(Col, 1)};
+scan_escape([C0|Cs], Col) when ?CHAR(C0), ?UNI255(C0) ->
+ C = escape_char(C0),
+ {C,?UNI_STR(Col, [C0]),Cs,incr_column(Col, 1)};
+scan_escape([C|Cs], Col) when ?CHAR(C) -> % UNI
+ {unicode,C,?UNI_STR(Col, [C]),Cs,incr_column(Col, 1)};
+scan_escape([], _Col) ->
+ more;
+scan_escape(eof, Col) ->
+ {eof,Col}.
+
+scan_hex([C|Cs], no_col=Col, Wcs) when ?HEX(C) ->
+ scan_hex(Cs, Col, [C|Wcs]);
+scan_hex([C|Cs], Col, Wcs) when ?HEX(C) ->
+ scan_hex(Cs, Col+1, [C|Wcs]);
+scan_hex(Cs, Col, Wcs) ->
+ scan_esc_end(Cs, Col, Wcs, 16, "x{").
+
+scan_esc_end([$}|Cs], Col, Wcs0, B, Str0) ->
+ Wcs = lists:reverse(Wcs0),
+ case catch erlang:list_to_integer(Wcs, B) of
+ Val when Val =< 16#FF ->
+ {Val,?UNI_STR(Col, Str0++Wcs++[$}]),Cs,incr_column(Col, 1)};
+ Val when Val =< 16#10FFFF ->
+ {unicode,Val,?UNI_STR(Col, Str0++Wcs++[$}]),Cs,incr_column(Col,1)};
+ _ ->
+ {error,Cs,{illegal,character},incr_column(Col, 1)}
+ end;
+scan_esc_end([], _Col, _Wcs, _B, _Str0) ->
+ more;
+scan_esc_end(eof, Col, _Wcs, _B, _Str0) ->
+ {eof,Col};
+scan_esc_end(Cs, Col, _Wcs, _B, _Str0) ->
+ {error,Cs,{illegal,character},Col}.
+
+escape_char($n) -> $\n; % \n = LF
+escape_char($r) -> $\r; % \r = CR
+escape_char($t) -> $\t; % \t = TAB
+escape_char($v) -> $\v; % \v = VT
+escape_char($b) -> $\b; % \b = BS
+escape_char($f) -> $\f; % \f = FF
+escape_char($e) -> $\e; % \e = ESC
+escape_char($s) -> $\s; % \s = SPC
+escape_char($d) -> $\d; % \d = DEL
+escape_char(C) -> C.
+
+scan_number([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_number(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_number([$.,C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_fraction(Cs, St, Line, Col, Toks, [C,$.|Ncs]);
+scan_number([$.]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_number/6}};
+scan_number([$#|Cs]=Cs0, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch list_to_integer(Ncs) of
+ B when B >= 2, B =< 1+$Z-$A+10 ->
+ Bcs = Ncs++[$#],
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[],Bcs});
+ B ->
+ Len = length(Ncs),
+ scan_error({base,B}, Line, Col, Line, incr_column(Col, Len), Cs0)
+ end;
+scan_number([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_number/6}};
+scan_number(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch list_to_integer(Ncs) of
+ N when is_integer(N) ->
+ tok3(Cs, St, Line, Col, Toks, integer, Ncs, N);
+ _ ->
+ Ncol = incr_column(Col, length(Ncs)),
+ scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
+ end.
+
+scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
+ when ?DIGIT(C), C < $0+B ->
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
+scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
+ when C >= $A, B > 10, C < $A+B-10 ->
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
+scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
+ when C >= $a, B > 10, C < $a+B-10 ->
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
+scan_based_int([]=Cs, _St, Line, Col, Toks, State) ->
+ {more,{Cs,Col,Toks,Line,State,fun scan_based_int/6}};
+scan_based_int(Cs, St, Line, Col, Toks, {B,Ncs0,Bcs}) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch erlang:list_to_integer(Ncs, B) of
+ N when is_integer(N) ->
+ tok3(Cs, St, Line, Col, Toks, integer, Bcs++Ncs, N);
+ _ ->
+ Len = length(Bcs)+length(Ncs),
+ Ncol = incr_column(Col, Len),
+ scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
+ end.
+
+scan_fraction([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_fraction(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_fraction([E|Cs], St, Line, Col, Toks, Ncs) when E =:= $e; E =:= $E ->
+ scan_exponent_sign(Cs, St, Line, Col, Toks, [E|Ncs]);
+scan_fraction([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_fraction/6}};
+scan_fraction(Cs, St, Line, Col, Toks, Ncs) ->
+ float_end(Cs, St, Line, Col, Toks, Ncs).
+
+scan_exponent_sign([C|Cs], St, Line, Col, Toks, Ncs) when C =:= $+; C =:= $- ->
+ scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_exponent_sign([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_exponent_sign/6}};
+scan_exponent_sign(Cs, St, Line, Col, Toks, Ncs) ->
+ scan_exponent(Cs, St, Line, Col, Toks, Ncs).
+
+scan_exponent([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_exponent([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_exponent/6}};
+scan_exponent(Cs, St, Line, Col, Toks, Ncs) ->
+ float_end(Cs, St, Line, Col, Toks, Ncs).
+
+float_end(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch list_to_float(Ncs) of
+ F when is_float(F) ->
+ tok3(Cs, St, Line, Col, Toks, float, Ncs, F);
+ _ ->
+ Ncol = incr_column(Col, length(Ncs)),
+ scan_error({illegal,float}, Line, Col, Line, Ncol, Cs)
+ end.
+
+skip_comment([C|Cs], St, Line, Col, Toks, N) when C =/= $\n, ?CHAR(C) ->
+ skip_comment(Cs, St, Line, Col, Toks, N+1);
+skip_comment([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun skip_comment/6}};
+skip_comment(Cs, St, Line, Col, Toks, N) ->
+ scan1(Cs, St, Line, incr_column(Col, N), Toks).
+
+scan_comment([C|Cs], St, Line, Col, Toks, Ncs) when C =/= $\n, ?CHAR(C) ->
+ scan_comment(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_comment([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_comment/6}};
+scan_comment(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ tok3(Cs, St, Line, Col, Toks, comment, Ncs, Ncs).
+
+tok2(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, _Wcs, P) ->
+ scan1(Cs, St, Line, Col, [{P,Line}|Toks]);
+tok2(Cs, St, Line, Col, Toks, Wcs, P) ->
+ Attrs = attributes(Line, Col, St, Wcs),
+ scan1(Cs, St, Line, incr_column(Col, length(Wcs)), [{P,Attrs}|Toks]).
+
+tok2(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, _Wcs, P, _N) ->
+ scan1(Cs, St, Line, Col, [{P,Line}|Toks]);
+tok2(Cs, St, Line, Col, Toks, Wcs, P, N) ->
+ Attrs = attributes(Line, Col, St, Wcs),
+ scan1(Cs, St, Line, incr_column(Col, N), [{P,Attrs}|Toks]).
+
+tok3(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, Item, _S, Sym) ->
+ scan1(Cs, St, Line, Col, [{Item,Line,Sym}|Toks]);
+tok3(Cs, St, Line, Col, Toks, Item, String, Sym) ->
+ Token = {Item,attributes(Line, Col, St, String),Sym},
+ scan1(Cs, St, Line, incr_column(Col, length(String)), [Token|Toks]).
+
+tok3(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, Item,
+ _String, Sym, _Length) ->
+ scan1(Cs, St, Line, Col, [{Item,Line,Sym}|Toks]);
+tok3(Cs, St, Line, Col, Toks, Item, String, Sym, Length) ->
+ Token = {Item,attributes(Line, Col, St, String),Sym},
+ scan1(Cs, St, Line, incr_column(Col, Length), [Token|Toks]).
+
+scan_error(Error, Line, Col, EndLine, EndCol, Rest) ->
+ Loc = location(Line, Col),
+ EndLoc = location(EndLine, EndCol),
+ scan_error(Error, Loc, EndLoc, Rest).
+
+scan_error(Error, ErrorLoc, EndLoc, Rest) ->
+ {{error,{ErrorLoc,?MODULE,Error},EndLoc},Rest}.
+
+-compile({inline,[attributes/4]}).
+
+attributes(Line, no_col, #erl_scan{text = false}, _String) ->
+ Line;
+attributes(Line, no_col, #erl_scan{text = true}, String) ->
+ [{line,Line},{text,String}];
+attributes(Line, Col, #erl_scan{text = false}, _String) ->
+ {Line,Col};
+attributes(Line, Col, #erl_scan{text = true}, String) ->
+ [{line,Line},{column,Col},{text,String}].
+
+location(Line, no_col) ->
+ Line;
+location(Line, Col) when is_integer(Col) ->
+ {Line,Col}.
+
+-compile({inline,[incr_column/2,new_column/2]}).
+
+incr_column(no_col=Col, _N) ->
+ Col;
+incr_column(Col, N) when is_integer(Col) ->
+ Col + N.
+
+new_column(no_col=Col, _Ncol) ->
+ Col;
+new_column(Col, Ncol) when is_integer(Col) ->
+ Ncol.
+
+nl_spcs(2) -> "\n ";
+nl_spcs(3) -> "\n ";
+nl_spcs(4) -> "\n ";
+nl_spcs(5) -> "\n ";
+nl_spcs(6) -> "\n ";
+nl_spcs(7) -> "\n ";
+nl_spcs(8) -> "\n ";
+nl_spcs(9) -> "\n ";
+nl_spcs(10) -> "\n ";
+nl_spcs(11) -> "\n ";
+nl_spcs(12) -> "\n ";
+nl_spcs(13) -> "\n ";
+nl_spcs(14) -> "\n ";
+nl_spcs(15) -> "\n ";
+nl_spcs(16) -> "\n ";
+nl_spcs(17) -> "\n ".
+
+spcs(1) -> " ";
+spcs(2) -> " ";
+spcs(3) -> " ";
+spcs(4) -> " ";
+spcs(5) -> " ";
+spcs(6) -> " ";
+spcs(7) -> " ";
+spcs(8) -> " ";
+spcs(9) -> " ";
+spcs(10) -> " ";
+spcs(11) -> " ";
+spcs(12) -> " ";
+spcs(13) -> " ";
+spcs(14) -> " ";
+spcs(15) -> " ";
+spcs(16) -> " ".
+
+nl_tabs(2) -> "\n\t";
+nl_tabs(3) -> "\n\t\t";
+nl_tabs(4) -> "\n\t\t\t";
+nl_tabs(5) -> "\n\t\t\t\t";
+nl_tabs(6) -> "\n\t\t\t\t\t";
+nl_tabs(7) -> "\n\t\t\t\t\t\t";
+nl_tabs(8) -> "\n\t\t\t\t\t\t\t";
+nl_tabs(9) -> "\n\t\t\t\t\t\t\t\t";
+nl_tabs(10) -> "\n\t\t\t\t\t\t\t\t\t";
+nl_tabs(11) -> "\n\t\t\t\t\t\t\t\t\t\t".
+
+tabs(1) -> "\t";
+tabs(2) -> "\t\t";
+tabs(3) -> "\t\t\t";
+tabs(4) -> "\t\t\t\t";
+tabs(5) -> "\t\t\t\t\t";
+tabs(6) -> "\t\t\t\t\t\t";
+tabs(7) -> "\t\t\t\t\t\t\t";
+tabs(8) -> "\t\t\t\t\t\t\t\t";
+tabs(9) -> "\t\t\t\t\t\t\t\t\t";
+tabs(10) -> "\t\t\t\t\t\t\t\t\t\t".
+
+-spec reserved_word(atom()) -> boolean().
+reserved_word('after') -> true;
+reserved_word('begin') -> true;
+reserved_word('case') -> true;
+reserved_word('try') -> true;
+reserved_word('cond') -> true;
+reserved_word('catch') -> true;
+reserved_word('andalso') -> true;
+reserved_word('orelse') -> true;
+reserved_word('end') -> true;
+reserved_word('fun') -> true;
+reserved_word('if') -> true;
+reserved_word('let') -> true;
+reserved_word('of') -> true;
+reserved_word('query') -> true;
+reserved_word('receive') -> true;
+reserved_word('when') -> true;
+reserved_word('bnot') -> true;
+reserved_word('not') -> true;
+reserved_word('div') -> true;
+reserved_word('rem') -> true;
+reserved_word('band') -> true;
+reserved_word('and') -> true;
+reserved_word('bor') -> true;
+reserved_word('bxor') -> true;
+reserved_word('bsl') -> true;
+reserved_word('bsr') -> true;
+reserved_word('or') -> true;
+reserved_word('xor') -> true;
+reserved_word('spec') -> true;
+reserved_word(_) -> false.
diff --git a/lib/stdlib/src/erl_tar.erl b/lib/stdlib/src/erl_tar.erl
new file mode 100644
index 0000000000..fd85c7aef5
--- /dev/null
+++ b/lib/stdlib/src/erl_tar.erl
@@ -0,0 +1,959 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_tar).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Purpose: Unix tar (tape archive) utility.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-export([create/2, create/3, extract/1, extract/2, table/1, table/2,
+ open/2, close/1, add/3, add/4,
+ t/1, tt/1, format_error/1]).
+
+-include_lib("kernel/include/file.hrl").
+
+-record(add_opts,
+ {read_info, % Fun to use for read file/link info.
+ verbose = false :: boolean()}). % Verbose on/off.
+
+%% Opens a tar archive.
+
+open(Name, Mode) ->
+ case open_mode(Mode) of
+ {ok, Access, Raw, Opts} ->
+ open1(Name, Access, Raw, Opts);
+ {error, Reason} ->
+ {error, {Name, Reason}}
+ end.
+
+open1({binary,Bin}, read, _Raw, Opts) ->
+ case file:open(Bin, [ram,binary,read]) of
+ {ok,File} ->
+ case Opts of
+ [compressed] -> ram_file:uncompress(File);
+ [] -> ok
+ end,
+ {ok,{read,File}};
+ Error ->
+ Error
+ end;
+open1({file, Fd}, read, _Raw, _Opts) ->
+ {ok, {read, Fd}};
+open1(Name, Access, Raw, Opts) ->
+ case file:open(Name, Raw ++ [binary, Access|Opts]) of
+ {ok, File} ->
+ {ok, {Access, File}};
+ {error, Reason} ->
+ {error, {Name, Reason}}
+ end.
+
+%% Closes a tar archive.
+
+close({read, File}) ->
+ ok = file:close(File);
+close({write, File}) ->
+ PadResult = pad_file(File),
+ ok = file:close(File),
+ PadResult;
+close(_) ->
+ {error, einval}.
+
+%% Adds a file to a tape archive.
+
+add(File, Name, Options) ->
+ add(File, Name, Name, Options).
+
+add({write, File}, Name, NameInArchive, Options) ->
+ Opts = #add_opts{read_info=fun(F) -> file:read_link_info(F) end},
+ add1(File, Name, NameInArchive, add_opts(Options, Opts));
+add({read, _File}, _, _, _) ->
+ {error, eacces};
+add(_, _, _, _) ->
+ {error, einval}.
+
+add_opts([dereference|T], Opts) ->
+ add_opts(T, Opts#add_opts{read_info=fun(F) -> file:read_file_info(F) end});
+add_opts([verbose|T], Opts) ->
+ add_opts(T, Opts#add_opts{verbose=true});
+add_opts([_|T], Opts) ->
+ add_opts(T, Opts);
+add_opts([], Opts) ->
+ Opts.
+
+%% Creates a tar file Name containing the given files.
+
+create(Name, Filenames) ->
+ create(Name, Filenames, []).
+
+%% Creates a tar archive Name containing the given files.
+%% Accepted options: verbose, compressed, cooked
+
+create(Name, FileList, Options) ->
+ Mode = lists:filter(fun(X) -> (X=:=compressed) or (X=:=cooked)
+ end, Options),
+ case open(Name, [write|Mode]) of
+ {ok, TarFile} ->
+ Add = fun({NmInA, NmOrBin}) ->
+ add(TarFile, NmOrBin, NmInA, Options);
+ (Nm) ->
+ add(TarFile, Nm, Nm, Options)
+ end,
+ Result = foreach_while_ok(Add, FileList),
+ case {Result, close(TarFile)} of
+ {ok, Res} -> Res;
+ {Res, _} -> Res
+ end;
+ Reason ->
+ Reason
+ end.
+
+%% Extracts all files from the tar file Name.
+
+extract(Name) ->
+ extract(Name, []).
+
+%% Extracts (all) files from the tar file Name.
+%% Options accepted: keep_old_files, {files, ListOfFilesToExtract}, verbose,
+%% {cwd, AbsoluteDirectory}
+
+extract(Name, Opts) ->
+ foldl_read(Name, fun extract1/4, ok, extract_opts(Opts)).
+
+%% Returns a list of names of the files in the tar file Name.
+%% Options accepted: verbose
+
+table(Name) ->
+ table(Name, []).
+
+%% Returns a list of names of the files in the tar file Name.
+%% Options accepted: compressed, verbose, cooked.
+
+table(Name, Opts) ->
+ foldl_read(Name, fun table1/4, [], table_opts(Opts)).
+
+
+%% Comments for printing the contents of a tape archive,
+%% meant to be invoked from the shell.
+
+t(Name) ->
+ case table(Name) of
+ {ok, List} ->
+ lists:foreach(fun(N) -> ok = io:format("~s\n", [N]) end, List);
+ Error ->
+ Error
+ end.
+
+tt(Name) ->
+ case table(Name, [verbose]) of
+ {ok, List} ->
+ lists:foreach(fun print_header/1, List);
+ Error ->
+ Error
+ end.
+
+print_header({Name, Type, Size, Mtime, Mode, Uid, Gid}) ->
+ io:format("~s~s ~4w/~-4w ~7w ~s ~s\n",
+ [type_to_string(Type), mode_to_string(Mode),
+ Uid, Gid, Size, time_to_string(Mtime), Name]).
+
+type_to_string(regular) -> "-";
+type_to_string(directory) -> "d";
+type_to_string(link) -> "l";
+type_to_string(symlink) -> "s";
+type_to_string(char) -> "c";
+type_to_string(block) -> "b";
+type_to_string(fifo) -> "f";
+type_to_string(_) -> "?".
+
+mode_to_string(Mode) ->
+ mode_to_string(Mode, "xwrxwrxwr", []).
+
+mode_to_string(Mode, [C|T], Acc) when Mode band 1 =:= 1 ->
+ mode_to_string(Mode bsr 1, T, [C|Acc]);
+mode_to_string(Mode, [_|T], Acc) ->
+ mode_to_string(Mode bsr 1, T, [$-|Acc]);
+mode_to_string(_, [], Acc) ->
+ Acc.
+
+time_to_string({{Y, Mon, Day}, {H, Min, _}}) ->
+ io_lib:format("~s ~2w ~s:~s ~w", [month(Mon), Day, two_d(H), two_d(Min), Y]).
+
+two_d(N) ->
+ tl(integer_to_list(N + 100)).
+
+month(1) -> "Jan";
+month(2) -> "Feb";
+month(3) -> "Mar";
+month(4) -> "Apr";
+month(5) -> "May";
+month(6) -> "Jun";
+month(7) -> "Jul";
+month(8) -> "Aug";
+month(9) -> "Sep";
+month(10) -> "Oct";
+month(11) -> "Nov";
+month(12) -> "Dec".
+
+%% Converts the short error reason to a descriptive string.
+
+format_error(bad_header) -> "Bad directory header";
+format_error(eof) -> "Unexpected end of file";
+format_error(symbolic_link_too_long) -> "Symbolic link too long";
+format_error({Name,Reason}) ->
+ lists:flatten(io_lib:format("~s: ~s", [Name,format_error(Reason)]));
+format_error(Atom) when is_atom(Atom) ->
+ file:format_error(Atom);
+format_error(Term) ->
+ lists:flatten(io_lib:format("~p", [Term])).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Useful definitions (also start of implementation).
+%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Offset for fields in the tar header.
+%% Note that these offsets are ZERO-based as in the POSIX standard
+%% document, while binaries use ONE-base offset. Caveat Programmer.
+
+-define(th_name, 0).
+-define(th_mode, 100).
+-define(th_uid, 108).
+-define(th_gid, 116).
+-define(th_size, 124).
+-define(th_mtime, 136).
+-define(th_chksum, 148).
+-define(th_typeflag, 156).
+-define(th_linkname, 157).
+-define(th_magic, 257).
+-define(th_version, 263).
+-define(th_prefix, 345).
+
+%% Length of these fields.
+
+-define(th_name_len, 100).
+-define(th_mode_len, 8).
+-define(th_uid_len, 8).
+-define(th_gid_len, 8).
+-define(th_size_len, 12).
+-define(th_mtime_len, 12).
+-define(th_chksum_len, 8).
+-define(th_linkname_len, 100).
+-define(th_magic_len, 6).
+-define(th_version_len, 2).
+-define(th_prefix_len, 167).
+
+-record(tar_header,
+ {name, % Name of file.
+ mode, % Mode bits.
+ uid, % User id.
+ gid, % Group id.
+ size, % Size of file
+ mtime, % Last modified (seconds since
+ % Jan 1, 1970).
+ chksum, % Checksum of header.
+ typeflag = [], % Type of file.
+ linkname = [], % Name of link.
+ filler = [],
+ prefix}). % Filename prefix.
+
+-define(record_size, 512).
+-define(block_size, (512*20)).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Adding members to a tar archive.
+%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+add1(TarFile, Bin, NameInArchive, Opts) when is_binary(Bin) ->
+ Now = calendar:now_to_local_time(now()),
+ Info = #file_info{size = byte_size(Bin),
+ type = regular,
+ access = read_write,
+ atime = Now,
+ mtime = Now,
+ ctime = Now,
+ mode = 8#100644,
+ links = 1,
+ major_device = 0,
+ minor_device = 0,
+ inode = 0,
+ uid = 0,
+ gid = 0},
+ Header = create_header(NameInArchive, Info),
+ add1(TarFile, NameInArchive, Header, Bin, Opts);
+add1(TarFile, Name, NameInArchive, Opts) ->
+ case read_file_and_info(Name, Opts) of
+ {ok, Bin, Info} when Info#file_info.type =:= regular ->
+ Header = create_header(NameInArchive, Info),
+ add1(TarFile, Name, Header, Bin, Opts);
+ {ok, PointsTo, Info} when Info#file_info.type =:= symlink ->
+ if
+ length(PointsTo) > 100 ->
+ {error,{PointsTo,symbolic_link_too_long}};
+ true ->
+ Info2 = Info#file_info{size=0},
+ Header = create_header(NameInArchive, Info2, PointsTo),
+ add1(TarFile, Name, Header, list_to_binary([]), Opts)
+ end;
+ {ok, _, Info} when Info#file_info.type =:= directory ->
+ add_directory(TarFile, Name, NameInArchive, Info, Opts);
+ {ok, _, #file_info{type=Type}} ->
+ {error, {bad_file_type, Name, Type}};
+ {error, Reason} ->
+ {error, {Name, Reason}}
+ end.
+
+add1(Tar, Name, Header, Bin, Options) ->
+ add_verbose(Options, "a ~s~n", [Name]),
+ file:write(Tar, [Header, Bin, padding(byte_size(Bin), ?record_size)]).
+
+add_directory(TarFile, DirName, NameInArchive, Info, Options) ->
+ case file:list_dir(DirName) of
+ {ok, []} ->
+ add_verbose(Options, "a ~s~n", [DirName]),
+ Header = create_header(NameInArchive, Info),
+ file:write(TarFile, Header);
+ {ok, Files} ->
+ Add = fun (File) ->
+ add1(TarFile,
+ filename:join(DirName, File),
+ filename:join(NameInArchive, File),
+ Options) end,
+ foreach_while_ok(Add, Files);
+ {error, Reason} ->
+ {error, {DirName, Reason}}
+ end.
+
+%% Creates a header for file in a tar file.
+
+create_header(Name, Info) ->
+ create_header(Name, Info, []).
+create_header(Name, #file_info {mode=Mode, uid=Uid, gid=Gid,
+ size=Size, mtime=Mtime0, type=Type}, Linkname) ->
+ Mtime = posix_time(erlang:localtime_to_universaltime(Mtime0)),
+ {Prefix,Suffix} = split_filename(Name),
+ H0 = [to_string(Suffix, 100),
+ to_octal(Mode, 8),
+ to_octal(Uid, 8),
+ to_octal(Gid, 8),
+ to_octal(Size, ?th_size_len),
+ to_octal(Mtime, ?th_mtime_len),
+ <<" ">>,
+ file_type(Type),
+ to_string(Linkname, ?th_linkname_len),
+ "ustar",0,
+ "00",
+ zeroes(?th_prefix-?th_version-?th_version_len),
+ to_string(Prefix, ?th_prefix_len)],
+ H = list_to_binary(H0),
+ 512 = byte_size(H), %Assertion.
+ ChksumString = to_octal(checksum(H), 6, [0,$\s]),
+ <<Before:?th_chksum/binary,_:?th_chksum_len/binary,After/binary>> = H,
+ [Before,ChksumString,After].
+
+file_type(regular) -> $0;
+file_type(symlink) -> $2;
+file_type(directory) -> $5.
+
+to_octal(Int, Count) when Count > 1 ->
+ to_octal(Int, Count-1, [0]).
+
+to_octal(_, 0, Result) -> Result;
+to_octal(Int, Count, Result) ->
+ to_octal(Int div 8, Count-1, [Int rem 8 + $0|Result]).
+
+to_string(Str0, Count) ->
+ Str = list_to_binary(Str0),
+ case byte_size(Str) of
+ Size when Size < Count ->
+ [Str|zeroes(Count-Size)];
+ _ -> Str
+ end.
+
+%% Pads out end of file.
+
+pad_file(File) ->
+ {ok,Position} = file:position(File, {cur,0}),
+ %% There must be at least one empty record at the end of the file.
+ Zeros = zeroes(?block_size - (Position rem ?block_size)),
+ file:write(File, Zeros).
+
+split_filename(Name) when length(Name) =< ?th_name_len ->
+ {"", Name};
+split_filename(Name0) ->
+ split_filename(lists:reverse(filename:split(Name0)), [], [], 0).
+
+split_filename([Comp|Rest], Prefix, Suffix, Len)
+ when Len+length(Comp) < ?th_name_len ->
+ split_filename(Rest, Prefix, [Comp|Suffix], Len+length(Comp)+1);
+split_filename([Comp|Rest], Prefix, Suffix, Len) ->
+ split_filename(Rest, [Comp|Prefix], Suffix, Len+length(Comp)+1);
+split_filename([], Prefix, Suffix, _) ->
+ {filename:join(Prefix),filename:join(Suffix)}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Retrieving files from a tape archive.
+%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Options used when reading a tar archive.
+
+-record(read_opts,
+ {cwd :: string(), % Current working directory.
+ keep_old_files = false :: boolean(), % Owerwrite or not.
+ files = all, % Set of files to extract
+ % (or all).
+ output = file :: 'file' | 'memory',
+ open_mode = [], % Open mode options.
+ verbose = false :: boolean()}). % Verbose on/off.
+
+extract_opts(List) ->
+ extract_opts(List, default_options()).
+
+table_opts(List) ->
+ read_opts(List, default_options()).
+
+default_options() ->
+ {ok, Cwd} = file:get_cwd(),
+ #read_opts{cwd=Cwd}.
+
+%% Parse options for extract.
+
+extract_opts([keep_old_files|Rest], Opts) ->
+ extract_opts(Rest, Opts#read_opts{keep_old_files=true});
+extract_opts([{cwd, Cwd}|Rest], Opts) ->
+ extract_opts(Rest, Opts#read_opts{cwd=Cwd});
+extract_opts([{files, Files}|Rest], Opts) ->
+ Set = ordsets:from_list(Files),
+ extract_opts(Rest, Opts#read_opts{files=Set});
+extract_opts([memory|Rest], Opts) ->
+ extract_opts(Rest, Opts#read_opts{output=memory});
+extract_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
+ extract_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]});
+extract_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
+ extract_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]});
+extract_opts([verbose|Rest], Opts) ->
+ extract_opts(Rest, Opts#read_opts{verbose=true});
+extract_opts([Other|Rest], Opts) ->
+ extract_opts(Rest, read_opts([Other], Opts));
+extract_opts([], Opts) ->
+ Opts.
+
+%% Common options for all read operations.
+
+read_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
+ read_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]});
+read_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
+ read_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]});
+read_opts([verbose|Rest], Opts) ->
+ read_opts(Rest, Opts#read_opts{verbose=true});
+read_opts([_|Rest], Opts) ->
+ read_opts(Rest, Opts);
+read_opts([], Opts) ->
+ Opts.
+
+foldl_read(TarName, Fun, Accu, Opts) ->
+ case open(TarName, [read|Opts#read_opts.open_mode]) of
+ {ok, {read, File}} ->
+ Result =
+ case catch foldl_read1(Fun, Accu, File, Opts) of
+ {'EXIT', Reason} ->
+ exit(Reason);
+ {error, {Reason, Format, Args}} ->
+ read_verbose(Opts, Format, Args),
+ {error, Reason};
+ {error, Reason} ->
+ {error, Reason};
+ Ok ->
+ Ok
+ end,
+ ok = file:close(File),
+ Result;
+ Error ->
+ Error
+ end.
+
+foldl_read1(Fun, Accu0, File, Opts) ->
+ case get_header(File) of
+ eof ->
+ Fun(eof, File, Opts, Accu0);
+ Header ->
+ {ok, NewAccu} = Fun(Header, File, Opts, Accu0),
+ foldl_read1(Fun, NewAccu, File, Opts)
+ end.
+
+table1(eof, _, _, Result) ->
+ {ok, lists:reverse(Result)};
+table1(Header = #tar_header{}, File, #read_opts{verbose=true}, Result) ->
+ #tar_header{name=Name, size=Size, mtime=Mtime, typeflag=Type,
+ mode=Mode, uid=Uid, gid=Gid} = Header,
+ skip(File, Size),
+ {ok, [{Name, Type, Size, posix_to_erlang_time(Mtime), Mode, Uid, Gid}|Result]};
+table1(#tar_header{name=Name, size=Size}, File, _, Result) ->
+ skip(File, Size),
+ {ok, [Name|Result]}.
+
+extract1(eof, _, _, Acc) ->
+ if
+ is_list(Acc) ->
+ {ok, lists:reverse(Acc)};
+ true ->
+ Acc
+ end;
+extract1(Header, File, Opts, Acc) ->
+ Name = Header#tar_header.name,
+ case check_extract(Name, Opts) of
+ true ->
+ {ok, Bin} = get_element(File, Header),
+ case write_extracted_element(Header, Bin, Opts) of
+ ok ->
+ {ok, Acc};
+ {ok, NameBin} when is_list(Acc) ->
+ {ok, [NameBin | Acc]};
+ {ok, NameBin} when Acc =:= ok ->
+ {ok, [NameBin]}
+ end;
+ false ->
+ ok = skip(File, Header#tar_header.size),
+ {ok, Acc}
+ end.
+
+%% Checks if the file Name should be extracted.
+
+check_extract(_, #read_opts{files=all}) ->
+ true;
+check_extract(Name, #read_opts{files=Files}) ->
+ ordsets:is_element(Name, Files).
+
+get_header(File) ->
+ case file:read(File, ?record_size) of
+ eof ->
+ throw({error,eof});
+ {ok, Bin} when is_binary(Bin) ->
+ convert_header(Bin);
+ {ok, List} ->
+ convert_header(list_to_binary(List));
+ {error, Reason} ->
+ throw({error, Reason})
+ end.
+
+%% Converts the tar header to a record.
+
+convert_header(Bin) when byte_size(Bin) =:= ?record_size ->
+ case verify_checksum(Bin) of
+ ok ->
+ Hd = #tar_header{name=get_name(Bin),
+ mode=from_octal(Bin, ?th_mode, ?th_mode_len),
+ uid=from_octal(Bin, ?th_uid, ?th_uid_len),
+ gid=from_octal(Bin, ?th_gid, ?th_gid_len),
+ size=from_octal(Bin, ?th_size, ?th_size_len),
+ mtime=from_octal(Bin, ?th_mtime, ?th_mtime_len),
+ linkname=from_string(Bin,
+ ?th_linkname, ?th_linkname_len),
+ typeflag=typeflag(Bin)},
+ convert_header1(Hd);
+ eof ->
+ eof
+ end;
+convert_header(Bin) when byte_size(Bin) =:= 0 ->
+ eof;
+convert_header(_Bin) ->
+ throw({error, eof}).
+
+%% Basic sanity. Better set the element size to zero here if the type
+%% always is of zero length.
+
+convert_header1(H) when H#tar_header.typeflag =:= symlink, H#tar_header.size =/= 0 ->
+ convert_header1(H#tar_header{size=0});
+convert_header1(H) when H#tar_header.typeflag =:= directory, H#tar_header.size =/= 0 ->
+ convert_header1(H#tar_header{size=0});
+convert_header1(Header) ->
+ Header.
+
+typeflag(Bin) ->
+ [T] = binary_to_list(Bin, ?th_typeflag+1, ?th_typeflag+1),
+ case T of
+ 0 -> regular;
+ $0 -> regular;
+ $1 -> link;
+ $2 -> symlink;
+ $3 -> char;
+ $4 -> block;
+ $5 -> directory;
+ $6 -> fifo;
+ $7 -> regular;
+ _ -> unknown
+ end.
+
+%% Get the name of the file from the prefix and name fields of the
+%% tar header.
+
+get_name(Bin) ->
+ Name = from_string(Bin, ?th_name, ?th_name_len),
+ case binary_to_list(Bin, ?th_prefix+1, ?th_prefix+1) of
+ [0] ->
+ Name;
+ [_] ->
+ Prefix = binary_to_list(Bin, ?th_prefix+1, byte_size(Bin)),
+ lists:reverse(remove_nulls(Prefix), [$/|Name])
+ end.
+
+from_string(Bin, Pos, Len) ->
+ lists:reverse(remove_nulls(binary_to_list(Bin, Pos+1, Pos+Len))).
+
+%% Returns all characters up to (but not including) the first null
+%% character, in REVERSE order.
+
+remove_nulls(List) ->
+ remove_nulls(List, []).
+
+remove_nulls([0|_], Result) ->
+ remove_nulls([], Result);
+remove_nulls([C|Rest], Result) ->
+ remove_nulls(Rest, [C|Result]);
+remove_nulls([], Result) ->
+ Result.
+
+from_octal(Bin, Pos, Len) ->
+ from_octal(binary_to_list(Bin, Pos+1, Pos+Len)).
+
+from_octal([$\s|Rest]) ->
+ from_octal(Rest);
+from_octal([Digit|Rest]) when $0 =< Digit, Digit =< $7 ->
+ from_octal(Rest, Digit-$0);
+from_octal(Bin) when is_binary(Bin) ->
+ from_octal(binary_to_list(Bin));
+from_octal(Other) ->
+ throw({error, {bad_header, "Bad octal number: ~p", [Other]}}).
+
+from_octal([Digit|Rest], Result) when $0 =< Digit, Digit =< $7 ->
+ from_octal(Rest, Result*8+Digit-$0);
+from_octal([$\s|_], Result) ->
+ Result;
+from_octal([0|_], Result) ->
+ Result;
+from_octal(Other, _) ->
+ throw({error, {bad_header, "Bad contents in octal field: ~p", [Other]}}).
+
+%% Retrieves the next element from the archive.
+%% Returns {ok, Bin} | eof | {error, Reason}
+
+get_element(File, #tar_header{size = 0}) ->
+ skip_to_next(File),
+ {ok,<<>>};
+get_element(File, #tar_header{size = Size}) ->
+ case file:read(File, Size) of
+ {ok,Bin}=Res when byte_size(Bin) =:= Size ->
+ skip_to_next(File),
+ Res;
+ {ok,List} when length(List) =:= Size ->
+ skip_to_next(File),
+ {ok,list_to_binary(List)};
+ {ok,_} -> throw({error,eof});
+ {error, Reason} -> throw({error, Reason});
+ eof -> throw({error,eof})
+ end.
+
+%% Verify the checksum in the header. First try an unsigned addition
+%% of all bytes in the header (as it should be according to Posix).
+
+verify_checksum(Bin) ->
+ <<H1:?th_chksum/binary,CheckStr:?th_chksum_len/binary,H2/binary>> = Bin,
+ case checksum(H1) + checksum(H2) of
+ 0 -> eof;
+ Checksum0 ->
+ Csum = from_octal(CheckStr),
+ CsumInit = ?th_chksum_len * $\s,
+ case Checksum0 + CsumInit of
+ Csum -> ok;
+ Unsigned ->
+ verify_checksum(H1, H2, CsumInit, Csum, Unsigned)
+ end
+ end.
+
+%% The checksums didn't match. Now try a signed addition.
+
+verify_checksum(H1, H2, Csum, ShouldBe, Unsigned) ->
+ case signed_sum(binary_to_list(H1), signed_sum(binary_to_list(H2), Csum)) of
+ ShouldBe -> ok;
+ Signed ->
+ throw({error,
+ {bad_header,
+ "Incorrect directory checksum ~w (~w), should be ~w",
+ [Signed, Unsigned, ShouldBe]}})
+ end.
+
+signed_sum([C|Rest], Sum) when C < 128 ->
+ signed_sum(Rest, Sum+C);
+signed_sum([C|Rest], Sum) ->
+ signed_sum(Rest, Sum+C-256);
+signed_sum([], Sum) -> Sum.
+
+write_extracted_element(Header, Bin, Opts)
+ when Opts#read_opts.output =:= memory ->
+ case Header#tar_header.typeflag of
+ regular ->
+ {ok, {Header#tar_header.name, Bin}};
+ _ ->
+ ok
+ end;
+write_extracted_element(Header, Bin, Opts) ->
+ Name = filename:absname(Header#tar_header.name, Opts#read_opts.cwd),
+ Created =
+ case Header#tar_header.typeflag of
+ regular ->
+ write_extracted_file(Name, Bin, Opts);
+ directory ->
+ create_extracted_dir(Name, Opts);
+ symlink ->
+ create_symlink(Name, Header, Opts);
+ Other -> % Ignore.
+ read_verbose(Opts, "x ~s - unsupported type ~p~n",
+ [Name, Other]),
+ not_written
+ end,
+ case Created of
+ ok -> set_extracted_file_info(Name, Header);
+ not_written -> ok
+ end.
+
+create_extracted_dir(Name, _Opts) ->
+ case file:make_dir(Name) of
+ ok -> ok;
+ {error,enotsup} -> not_written;
+ {error,eexist} -> not_written;
+ {error,enoent} -> make_dirs(Name, dir);
+ {error,Reason} -> throw({error, Reason})
+ end.
+
+create_symlink(Name, #tar_header{linkname=Linkname}=Header, Opts) ->
+ case file:make_symlink(Linkname, Name) of
+ ok -> ok;
+ {error,enoent} ->
+ ok = make_dirs(Name, file),
+ create_symlink(Name, Header, Opts);
+ {error,eexist} -> not_written;
+ {error,enotsup} ->
+ read_verbose(Opts, "x ~s - symbolic links not supported~n", [Name]),
+ not_written;
+ {error,Reason} -> throw({error, Reason})
+ end.
+
+write_extracted_file(Name, Bin, Opts) ->
+ Write =
+ case Opts#read_opts.keep_old_files of
+ true ->
+ case file:read_file_info(Name) of
+ {ok, _} -> false;
+ _ -> true
+ end;
+ false -> true
+ end,
+ case Write of
+ true ->
+ read_verbose(Opts, "x ~s~n", [Name]),
+ write_file(Name, Bin);
+ false ->
+ read_verbose(Opts, "x ~s - exists, not created~n", [Name]),
+ not_written
+ end.
+
+write_file(Name, Bin) ->
+ case file:write_file(Name, Bin) of
+ ok -> ok;
+ {error,enoent} ->
+ ok = make_dirs(Name, file),
+ write_file(Name, Bin);
+ {error,Reason} ->
+ throw({error, Reason})
+ end.
+
+set_extracted_file_info(_, #tar_header{typeflag = symlink}) -> ok;
+set_extracted_file_info(Name, #tar_header{mode=Mode, mtime=Mtime}) ->
+ Info = #file_info{mode=Mode, mtime=posix_to_erlang_time(Mtime)},
+ file:write_file_info(Name, Info).
+
+%% Makes all directories leading up to the file.
+
+make_dirs(Name, Type) ->
+ make_dirs1(filename:split(Name), Type).
+
+make_dirs1([Dir, Next|Rest], Type) ->
+ case file:read_file_info(Dir) of
+ {ok, #file_info{type=directory}} ->
+ make_dirs1([filename:join(Dir, Next)|Rest], Type);
+ {ok, #file_info{}} ->
+ throw({error, enotdir});
+ {error, _} ->
+ case file:make_dir(Dir) of
+ ok ->
+ make_dirs1([filename:join(Dir, Next)|Rest], Type);
+ {error, Reason} ->
+ throw({error, Reason})
+ end
+ end;
+make_dirs1([_], file) -> ok;
+make_dirs1([Dir], dir) ->
+ file:make_dir(Dir);
+make_dirs1([], _) ->
+ %% There must be something wrong here. The list was not supposed
+ %% to be empty.
+ throw({error, enoent}).
+
+%% Prints the message on if the verbose option is given (for reading).
+
+read_verbose(#read_opts{verbose=true}, Format, Args) ->
+ io:format(Format, Args),
+ io:nl();
+read_verbose(_, _, _) ->
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Utility functions.
+%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Returns the checksum of a binary.
+
+checksum(Bin) -> checksum(Bin, 0).
+
+checksum(<<A,B,C,D,E,F,G,H,T/binary>>, Sum) ->
+ checksum(T, Sum+A+B+C+D+E+F+G+H);
+checksum(<<A,T/binary>>, Sum) ->
+ checksum(T, Sum+A);
+checksum(<<>>, Sum) -> Sum.
+
+%% Returns a list of zeroes to pad out to the given block size.
+
+padding(Size, BlockSize) ->
+ zeroes(pad_size(Size, BlockSize)).
+
+pad_size(Size, BlockSize) ->
+ case Size rem BlockSize of
+ 0 -> 0;
+ Rem -> BlockSize-Rem
+ end.
+
+zeroes(0) -> [];
+zeroes(1) -> [0];
+zeroes(2) -> [0,0];
+zeroes(Number) ->
+ Half = zeroes(Number div 2),
+ case Number rem 2 of
+ 0 -> [Half|Half];
+ 1 -> [Half|[0|Half]]
+ end.
+
+%% Skips the given number of bytes rounded up to an even record.
+
+skip(File, Size) ->
+ %% Note: There is no point in handling failure to get the current position
+ %% in the file. If it doesn't work, something serious is wrong.
+ Amount = ((Size + ?record_size - 1) div ?record_size) * ?record_size,
+ {ok,_} = file:position(File, {cur, Amount}),
+ ok.
+
+%% Skips to the next record in the file.
+
+skip_to_next(File) ->
+ %% Note: There is no point in handling failure to get the current position
+ %% in the file. If it doesn't work, something serious is wrong.
+ {ok, Position} = file:position(File, {cur, 0}),
+ NewPosition = ((Position + ?record_size - 1) div ?record_size) * ?record_size,
+ {ok,NewPosition} = file:position(File, NewPosition),
+ ok.
+
+%% Prints the message on if the verbose option is given.
+
+add_verbose(#add_opts{verbose=true}, Format, Args) ->
+ io:format(Format, Args);
+add_verbose(_, _, _) ->
+ ok.
+
+%% Converts a tuple containing the time to a Posix time (seconds
+%% since Jan 1, 1970).
+
+posix_time(Time) ->
+ EpochStart = {{1970,1,1},{0,0,0}},
+ {Days,{Hour,Min,Sec}} = calendar:time_difference(EpochStart, Time),
+ 86400*Days + 3600*Hour + 60*Min + Sec.
+
+posix_to_erlang_time(Sec) ->
+ OneMillion = 1000000,
+ Time = calendar:now_to_datetime({Sec div OneMillion, Sec rem OneMillion, 0}),
+ erlang:universaltime_to_localtime(Time).
+
+read_file_and_info(Name, Opts) ->
+ ReadInfo = Opts#add_opts.read_info,
+ case ReadInfo(Name) of
+ {ok,Info} when Info#file_info.type =:= regular ->
+ case file:read_file(Name) of
+ {ok,Bin} ->
+ {ok,Bin,Info};
+ Error ->
+ Error
+ end;
+ {ok,Info} when Info#file_info.type =:= symlink ->
+ case file:read_link(Name) of
+ {ok,PointsTo} ->
+ {ok,PointsTo,Info};
+ Error ->
+ Error
+ end;
+ {ok, Info} ->
+ {ok,[],Info};
+ Error ->
+ Error
+ end.
+
+foreach_while_ok(Fun, [First|Rest]) ->
+ case Fun(First) of
+ ok -> foreach_while_ok(Fun, Rest);
+ Other -> Other
+ end;
+foreach_while_ok(_, []) -> ok.
+
+open_mode(Mode) ->
+ open_mode(Mode, false, [raw], []).
+
+open_mode(read, _, Raw, _) ->
+ {ok, read, Raw, []};
+open_mode(write, _, Raw, _) ->
+ {ok, write, Raw, []};
+open_mode([read|Rest], false, Raw, Opts) ->
+ open_mode(Rest, read, Raw, Opts);
+open_mode([write|Rest], false, Raw, Opts) ->
+ open_mode(Rest, write, Raw, Opts);
+open_mode([compressed|Rest], Access, Raw, Opts) ->
+ open_mode(Rest, Access, Raw, [compressed|Opts]);
+open_mode([cooked|Rest], Access, _Raw, Opts) ->
+ open_mode(Rest, Access, [], Opts);
+open_mode([], Access, Raw, Opts) ->
+ {ok, Access, Raw, Opts};
+open_mode(_, _, _, _) ->
+ {error, einval}.
diff --git a/lib/stdlib/src/error_logger_file_h.erl b/lib/stdlib/src/error_logger_file_h.erl
new file mode 100644
index 0000000000..ee4f0b3a51
--- /dev/null
+++ b/lib/stdlib/src/error_logger_file_h.erl
@@ -0,0 +1,265 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(error_logger_file_h).
+
+-behaviour(gen_event).
+
+
+%%%
+%%% A handler that can be connected to the error_logger
+%%% event handler.
+%%% Writes all events formatted to file.
+%%% Handles events tagged error, emulator and info.
+%%%
+%%% It can only be started from error_logger:swap_handler({logfile, File})
+%%% or error_logger:logfile(File)
+%%%
+
+-export([init/1,
+ handle_event/2, handle_call/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% This one is used when we takeover from the simple error_logger.
+init({File, {error_logger, Buf}}) ->
+ case init(File, error_logger) of
+ {ok, {Fd, File, PrevHandler}} ->
+ write_events(Fd, Buf),
+ {ok, {Fd, File, PrevHandler}};
+ Error ->
+ Error
+ end;
+%% This one is used when we are started directly.
+init(File) ->
+ init(File, []).
+
+init(File, PrevHandler) ->
+ process_flag(trap_exit, true),
+ case file:open(File, [write]) of
+ {ok,Fd} ->
+ {ok, {Fd, File, PrevHandler}};
+ Error ->
+ Error
+ end.
+
+handle_event({_Type, GL, _Msg}, State) when node(GL) =/= node() ->
+ {ok, State};
+handle_event(Event, {Fd, File, PrevHandler}) ->
+ write_event(Fd, tag_event(Event)),
+ {ok, {Fd, File, PrevHandler}};
+handle_event(_, State) ->
+ {ok, State}.
+
+handle_info({'EXIT', Fd, _Reason}, {Fd, _File, PrevHandler}) ->
+ case PrevHandler of
+ [] ->
+ remove_handler;
+ _ ->
+ {swap_handler, install_prev, [], PrevHandler, go_back}
+ end;
+handle_info({emulator, GL, Chars}, {Fd, File, PrevHandler})
+ when node(GL) == node() ->
+ write_event(Fd, tag_event({emulator, GL, Chars})),
+ {ok, {Fd, File, PrevHandler}};
+handle_info({emulator, noproc, Chars}, {Fd, File, PrevHandler}) ->
+ write_event(Fd, tag_event({emulator, noproc, Chars})),
+ {ok, {Fd, File, PrevHandler}};
+handle_info(_, State) ->
+ {ok, State}.
+
+handle_call(filename, {Fd, File, Prev}) ->
+ {ok, File, {Fd, File, Prev}};
+handle_call(_Query, State) ->
+ {ok, {error, bad_query}, State}.
+
+terminate(_Reason, State) ->
+ case State of
+ {Fd, _File, _Prev} ->
+ ok = file:close(Fd);
+ _ ->
+ ok
+ end,
+ [].
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%% ------------------------------------------------------
+%%% Misc. functions.
+%%% ------------------------------------------------------
+
+tag_event(Event) ->
+ {erlang:localtime(), Event}.
+
+write_events(Fd, Events) -> write_events1(Fd, lists:reverse(Events)).
+
+write_events1(Fd, [Event|Es]) ->
+ write_event(Fd, Event),
+ write_events1(Fd, Es);
+write_events1(_, []) ->
+ ok.
+
+write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) ->
+ T = write_time(maybe_utc(Time)),
+ case catch io_lib:format(add_node(Format,Pid), Args) of
+ S when is_list(S) ->
+ io:format(Fd, T ++ S, []);
+ _ ->
+ F = add_node("ERROR: ~p - ~p~n", Pid),
+ io:format(Fd, T ++ F, [Format,Args])
+ end;
+write_event(Fd, {Time, {emulator, _GL, Chars}}) ->
+ T = write_time(maybe_utc(Time)),
+ case catch io_lib:format(Chars, []) of
+ S when is_list(S) ->
+ io:format(Fd, T ++ S, []);
+ _ ->
+ io:format(Fd, T ++ "ERROR: ~p ~n", [Chars])
+ end;
+write_event(Fd, {Time, {info, _GL, {Pid, Info, _}}}) ->
+ T = write_time(maybe_utc(Time)),
+ io:format(Fd, T ++ add_node("~p~n",Pid),[Info]);
+write_event(Fd, {Time, {error_report, _GL, {Pid, std_error, Rep}}}) ->
+ T = write_time(maybe_utc(Time)),
+ S = format_report(Rep),
+ io:format(Fd, T ++ S ++ add_node("", Pid), []);
+write_event(Fd, {Time, {info_report, _GL, {Pid, std_info, Rep}}}) ->
+ T = write_time(maybe_utc(Time), "INFO REPORT"),
+ S = format_report(Rep),
+ io:format(Fd, T ++ S ++ add_node("", Pid), []);
+write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
+ T = write_time(maybe_utc(Time), "INFO REPORT"),
+ case catch io_lib:format(add_node(Format,Pid), Args) of
+ S when is_list(S) ->
+ io:format(Fd, T ++ S, []);
+ _ ->
+ F = add_node("ERROR: ~p - ~p~n", Pid),
+ io:format(Fd, T ++ F, [Format,Args])
+ end;
+write_event(Fd, {Time, {warning_report, _GL, {Pid, std_warning, Rep}}}) ->
+ T = write_time(maybe_utc(Time), "WARNING REPORT"),
+ S = format_report(Rep),
+ io:format(Fd, T ++ S ++ add_node("", Pid), []);
+write_event(Fd, {Time, {warning_msg, _GL, {Pid, Format, Args}}}) ->
+ T = write_time(maybe_utc(Time), "WARNING REPORT"),
+ case catch io_lib:format(add_node(Format,Pid), Args) of
+ S when is_list(S) ->
+ io:format(Fd, T ++ S, []);
+ _ ->
+ F = add_node("ERROR: ~p - ~p~n", Pid),
+ io:format(Fd, T ++ F, [Format,Args])
+ end;
+write_event(_, _) ->
+ ok.
+
+maybe_utc(Time) ->
+ UTC = case application:get_env(sasl, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ %% Backwards compatible:
+ case application:get_env(stdlib, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ false
+ end
+ end,
+ if
+ UTC =:= true ->
+ {utc, calendar:local_time_to_universal_time(Time)};
+ true ->
+ Time
+ end.
+
+format_report(Rep) when is_list(Rep) ->
+ case string_p(Rep) of
+ true ->
+ io_lib:format("~s~n",[Rep]);
+ _ ->
+ format_rep(Rep)
+ end;
+format_report(Rep) ->
+ io_lib:format("~p~n",[Rep]).
+
+format_rep([{Tag,Data}|Rep]) ->
+ io_lib:format(" ~p: ~p~n",[Tag,Data]) ++ format_rep(Rep);
+format_rep([Other|Rep]) ->
+ io_lib:format(" ~p~n",[Other]) ++ format_rep(Rep);
+format_rep(_) ->
+ [].
+
+add_node(X, Pid) when is_atom(X) ->
+ add_node(atom_to_list(X), Pid);
+add_node(X, Pid) when node(Pid) =/= node() ->
+ lists:concat([X,"** at node ",node(Pid)," **~n"]);
+add_node(X, _) ->
+ X.
+
+string_p([]) ->
+ false;
+string_p(Term) ->
+ string_p1(Term).
+
+string_p1([H|T]) when is_integer(H), H >= $\s, H < 255 ->
+ string_p1(T);
+string_p1([$\n|T]) -> string_p1(T);
+string_p1([$\r|T]) -> string_p1(T);
+string_p1([$\t|T]) -> string_p1(T);
+string_p1([$\v|T]) -> string_p1(T);
+string_p1([$\b|T]) -> string_p1(T);
+string_p1([$\f|T]) -> string_p1(T);
+string_p1([$\e|T]) -> string_p1(T);
+string_p1([H|T]) when is_list(H) ->
+ case string_p1(H) of
+ true -> string_p1(T);
+ _ -> false
+ end;
+string_p1([]) -> true;
+string_p1(_) -> false.
+
+write_time(Time) -> write_time(Time, "ERROR REPORT").
+
+write_time({utc,{{Y,Mo,D},{H,Mi,S}}}, Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]);
+write_time({{Y,Mo,D},{H,Mi,S}}, Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]).
+
+t(X) when is_integer(X) ->
+ t1(integer_to_list(X));
+t(_) ->
+ "".
+t1([X]) -> [$0,X];
+t1(X) -> X.
+
+month(1) -> "Jan";
+month(2) -> "Feb";
+month(3) -> "Mar";
+month(4) -> "Apr";
+month(5) -> "May";
+month(6) -> "Jun";
+month(7) -> "Jul";
+month(8) -> "Aug";
+month(9) -> "Sep";
+month(10) -> "Oct";
+month(11) -> "Nov";
+month(12) -> "Dec".
+
+
diff --git a/lib/stdlib/src/error_logger_tty_h.erl b/lib/stdlib/src/error_logger_tty_h.erl
new file mode 100644
index 0000000000..435e57aa0e
--- /dev/null
+++ b/lib/stdlib/src/error_logger_tty_h.erl
@@ -0,0 +1,261 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(error_logger_tty_h).
+
+-behaviour(gen_event).
+
+%%%
+%%% A handler that can be connected to the error_logger
+%%% event handler.
+%%% Writes all events formatted to stdout.
+%%% Handles events tagged error, emulator and info.
+%%%
+%%% It can only be started from error_logger:swap_handler(tty)
+%%% or error_logger:tty(true)
+%%%
+
+-export([init/1,
+ handle_event/2, handle_call/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% This one is used when we takeover from the simple error_logger.
+init({[], {error_logger, Buf}}) ->
+ User = set_group_leader(),
+ write_events(Buf),
+ {ok, {User, error_logger}};
+%% This one is used if someone took over from us, and now wants to
+%% go back.
+init({[], {error_logger_tty_h, PrevHandler}}) ->
+ User = set_group_leader(),
+ {ok, {User, PrevHandler}};
+%% This one is used when we are started directly.
+init([]) ->
+ User = set_group_leader(),
+ {ok, {User, []}}.
+
+handle_event({_Type, GL, _Msg}, State) when node(GL) =/= node() ->
+ {ok, State};
+handle_event(Event, State) ->
+ write_event(tag_event(Event)),
+ {ok, State}.
+
+handle_info({'EXIT', User, _Reason}, {User, PrevHandler}) ->
+ case PrevHandler of
+ [] ->
+ remove_handler;
+ _ ->
+ {swap_handler, install_prev, {User, PrevHandler},
+ PrevHandler, go_back}
+ end;
+handle_info({emulator, GL, Chars}, State) when node(GL) == node() ->
+ write_event(tag_event({emulator, GL, Chars})),
+ {ok, State};
+handle_info({emulator, noproc, Chars}, State) ->
+ write_event(tag_event({emulator, noproc, Chars})),
+ {ok, State};
+handle_info(_, State) ->
+ {ok, State}.
+
+handle_call(_Query, State) -> {ok, {error, bad_query}, State}.
+
+% unfortunately, we can't unlink from User - links are not counted!
+% if pid(User) -> unlink(User); true -> ok end,
+terminate(install_prev, _State) ->
+ [];
+terminate(_Reason, {_User, PrevHandler}) ->
+ {error_logger_tty_h, PrevHandler}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%% ------------------------------------------------------
+%%% Misc. functions.
+%%% ------------------------------------------------------
+
+set_group_leader() ->
+ case whereis(user) of
+ User when is_pid(User) -> link(User), group_leader(User,self()), User;
+ _ -> false
+ end.
+
+tag_event(Event) ->
+ {erlang:localtime(), Event}.
+
+write_events(Events) -> write_events1(lists:reverse(Events)).
+
+write_events1([Event|Es]) ->
+ write_event(Event),
+ write_events1(Es);
+write_events1([]) ->
+ ok.
+
+write_event({Time, {error, _GL, {Pid, Format, Args}}}) ->
+ T = write_time(maybe_utc(Time)),
+ case catch io_lib:format(add_node(Format,Pid), Args) of
+ S when is_list(S) ->
+ format(T ++ S);
+ _ ->
+ F = add_node("ERROR: ~p - ~p~n", Pid),
+ format(T ++ F, [Format,Args])
+ end;
+write_event({Time, {emulator, _GL, Chars}}) ->
+ T = write_time(maybe_utc(Time)),
+ case catch io_lib:format(Chars, []) of
+ S when is_list(S) ->
+ format(T ++ S);
+ _ ->
+ format(T ++ "ERROR: ~p ~n", [Chars])
+ end;
+write_event({Time, {info, _GL, {Pid, Info, _}}}) ->
+ T = write_time(maybe_utc(Time)),
+ format(T ++ add_node("~p~n",Pid),[Info]);
+write_event({Time, {error_report, _GL, {Pid, std_error, Rep}}}) ->
+ T = write_time(maybe_utc(Time)),
+ S = format_report(Rep),
+ format(T ++ S ++ add_node("", Pid));
+write_event({Time, {info_report, _GL, {Pid, std_info, Rep}}}) ->
+ T = write_time(maybe_utc(Time), "INFO REPORT"),
+ S = format_report(Rep),
+ format(T ++ S ++ add_node("", Pid));
+write_event({Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
+ T = write_time(maybe_utc(Time), "INFO REPORT"),
+ case catch io_lib:format(add_node(Format,Pid), Args) of
+ S when is_list(S) ->
+ format(T ++ S);
+ _ ->
+ F = add_node("ERROR: ~p - ~p~n", Pid),
+ format(T ++ F, [Format,Args])
+ end;
+write_event({Time, {warning_report, _GL, {Pid, std_warning, Rep}}}) ->
+ T = write_time(maybe_utc(Time), "WARNING REPORT"),
+ S = format_report(Rep),
+ format(T ++ S ++ add_node("", Pid));
+write_event({Time, {warning_msg, _GL, {Pid, Format, Args}}}) ->
+ T = write_time(maybe_utc(Time), "WARNING REPORT"),
+ case catch io_lib:format(add_node(Format,Pid), Args) of
+ S when is_list(S) ->
+ format(T ++ S);
+ _ ->
+ F = add_node("ERROR: ~p - ~p~n", Pid),
+ format(T ++ F, [Format,Args])
+ end;
+write_event({_Time, _Error}) ->
+ ok.
+
+maybe_utc(Time) ->
+ UTC = case application:get_env(sasl, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ %% Backwards compatible:
+ case application:get_env(stdlib, utc_log) of
+ {ok, Val} ->
+ Val;
+ undefined ->
+ false
+ end
+ end,
+ if
+ UTC =:= true ->
+ {utc, calendar:local_time_to_universal_time(Time)};
+ true ->
+ Time
+ end.
+
+format(String) -> io:format(user, String, []).
+format(String, Args) -> io:format(user, String, Args).
+
+format_report(Rep) when is_list(Rep) ->
+ case string_p(Rep) of
+ true ->
+ io_lib:format("~s~n",[Rep]);
+ _ ->
+ format_rep(Rep)
+ end;
+format_report(Rep) ->
+ io_lib:format("~p~n",[Rep]).
+
+format_rep([{Tag,Data}|Rep]) ->
+ io_lib:format(" ~p: ~p~n",[Tag,Data]) ++ format_rep(Rep);
+format_rep([Other|Rep]) ->
+ io_lib:format(" ~p~n",[Other]) ++ format_rep(Rep);
+format_rep(_) ->
+ [].
+
+add_node(X, Pid) when is_atom(X) ->
+ add_node(atom_to_list(X), Pid);
+add_node(X, Pid) when node(Pid) =/= node() ->
+ lists:concat([X,"** at node ",node(Pid)," **~n"]);
+add_node(X, _) ->
+ X.
+
+string_p([]) ->
+ false;
+string_p(Term) ->
+ string_p1(Term).
+
+string_p1([H|T]) when is_integer(H), H >= $\s, H < 255 ->
+ string_p1(T);
+string_p1([$\n|T]) -> string_p1(T);
+string_p1([$\r|T]) -> string_p1(T);
+string_p1([$\t|T]) -> string_p1(T);
+string_p1([$\v|T]) -> string_p1(T);
+string_p1([$\b|T]) -> string_p1(T);
+string_p1([$\f|T]) -> string_p1(T);
+string_p1([$\e|T]) -> string_p1(T);
+string_p1([H|T]) when is_list(H) ->
+ case string_p1(H) of
+ true -> string_p1(T);
+ _ -> false
+ end;
+string_p1([]) -> true;
+string_p1(_) -> false.
+
+write_time(Time) -> write_time(Time, "ERROR REPORT").
+write_time({utc,{{Y,Mo,D},{H,Mi,S}}},Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]);
+write_time({{Y,Mo,D},{H,Mi,S}},Type) ->
+ io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n",
+ [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]).
+
+t(X) when is_integer(X) ->
+ t1(integer_to_list(X));
+t(_) ->
+ "".
+t1([X]) -> [$0,X];
+t1(X) -> X.
+
+month(1) -> "Jan";
+month(2) -> "Feb";
+month(3) -> "Mar";
+month(4) -> "Apr";
+month(5) -> "May";
+month(6) -> "Jun";
+month(7) -> "Jul";
+month(8) -> "Aug";
+month(9) -> "Sep";
+month(10) -> "Oct";
+month(11) -> "Nov";
+month(12) -> "Dec".
+
+
+
+
+
diff --git a/lib/stdlib/src/escript.erl b/lib/stdlib/src/escript.erl
new file mode 100644
index 0000000000..697a69b801
--- /dev/null
+++ b/lib/stdlib/src/escript.erl
@@ -0,0 +1,694 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(escript).
+
+%% Useful functions that can be called from scripts.
+-export([script_name/0, foldl/3]).
+
+%% Internal API.
+-export([start/0, start/1]).
+
+-record(state, {file,
+ module,
+ forms_or_bin,
+ source,
+ n_errors,
+ mode,
+ exports_main,
+ has_records}).
+
+script_name() ->
+ [ScriptName|_] = init:get_plain_arguments(),
+ ScriptName.
+
+%% Apply Fun(Name, GetInfo, GetBin, Acc) for each file in the escript.
+%%
+%% Fun/2 must return a new accumulator which is passed to the next call.
+%% The function returns the final value of the accumulator. Acc0 is
+%% returned if the escript contain an empty archive.
+%%
+%% GetInfo/0 is a fun that returns a #file_info{} record for the file.
+%% GetBin/0 is a fun that returns a the contents of the file as a binary.
+%%
+%% An escript may contain erlang code, beam code or an archive:
+%%
+%% archive - the Fun/2 will be applied for each file in the archive
+%% beam - the Fun/2 will be applied once and GetInfo/0 returns the file
+%% info for the (entire) escript file
+%% erl - the Fun/2 will be applied once, GetInfo/0 returns the file
+%% info for the (entire) escript file and the GetBin returns
+%% the compiled beam code
+
+%%-spec foldl(fun((string(),
+%% fun(() -> #file_info()),
+%% fun(() -> binary() -> term()),
+%% term()) -> term()),
+%% term(),
+%% string()).
+foldl(Fun, Acc0, File) when is_function(Fun, 4) ->
+ case parse_file(File, false) of
+ {text, _, Forms, _Mode} when is_list(Forms) ->
+ GetInfo = fun() -> file:read_file_info(File) end,
+ GetBin =
+ fun() ->
+ case compile:forms(Forms, [return_errors, debug_info]) of
+ {ok, _, BeamBin} ->
+ BeamBin;
+ {error, _Errors, _Warnings} ->
+ fatal("There were compilation errors.")
+ end
+ end,
+ try
+ {ok, Fun(".", GetInfo, GetBin, Acc0)}
+ catch
+ throw:Reason ->
+ {error, Reason}
+ end;
+ {beam, _, BeamBin, _Mode} when is_binary(BeamBin) ->
+ GetInfo = fun() -> file:read_file_info(File) end,
+ GetBin = fun() -> BeamBin end,
+ try
+ {ok, Fun(".", GetInfo, GetBin, Acc0)}
+ catch
+ throw:Reason ->
+ {error, Reason}
+ end;
+ {archive, _, ArchiveBin, _Mode} when is_binary(ArchiveBin) ->
+ ZipFun =
+ fun({Name, GetInfo, GetBin}, A) ->
+ A2 = Fun(Name, GetInfo, GetBin, A),
+ {true, false, A2}
+ end,
+ case prim_zip:open(ZipFun, Acc0, {File, ArchiveBin}) of
+ {ok, PrimZip, Res} ->
+ ok = prim_zip:close(PrimZip),
+ {ok, Res};
+ {error, bad_eocd} ->
+ {error, "Not an archive file"};
+ {error, Reason} ->
+ {error, Reason}
+ end
+ end.
+
+%%
+%% Internal API.
+%%
+
+start() ->
+ start([]).
+
+start(EscriptOptions) ->
+ try
+ %% Commands run using -run or -s are run in a process
+ %% trap_exit set to false. Because this behaviour is
+ %% surprising for users of escript, make sure to reset
+ %% trap_exit to false.
+ process_flag(trap_exit, false),
+ case init:get_plain_arguments() of
+ [File|Args] ->
+ parse_and_run(File, Args, EscriptOptions);
+ [] ->
+ io:format("escript: Missing filename\n", []),
+ my_halt(127)
+ end
+ catch
+ throw:Str ->
+ io:format("escript: ~s\n", [Str]),
+ my_halt(127);
+ _:Reason ->
+ io:format("escript: Internal error: ~p\n", [Reason]),
+ io:format("~p\n", [erlang:get_stacktrace()]),
+ my_halt(127)
+ end.
+
+parse_and_run(File, Args, Options) ->
+ CheckOnly = lists:member("s", Options),
+ {Source, Module, FormsOrBin, Mode} = parse_file(File, CheckOnly),
+ Mode2 =
+ case lists:member("d", Options) of
+ true ->
+ debug;
+ false ->
+ case lists:member("c", Options) of
+ true ->
+ compile;
+ false ->
+ case lists:member("i", Options) of
+ true -> interpret;
+ false -> Mode
+ end
+ end
+ end,
+ if
+ is_list(FormsOrBin) ->
+ case Mode2 of
+ interpret ->
+ interpret(FormsOrBin, File, Args);
+ compile ->
+ case compile:forms(FormsOrBin, [report]) of
+ {ok, Module, BeamBin} ->
+ {module, Module} = code:load_binary(Module, File, BeamBin),
+ run(Module, Args);
+ _Other ->
+ fatal("There were compilation errors.")
+ end;
+ debug ->
+ case compile:forms(FormsOrBin, [report, debug_info]) of
+ {ok,Module,BeamBin} ->
+ {module, Module} = code:load_binary(Module, File, BeamBin),
+ debug(Module, {Module, File, File, BeamBin}, Args);
+ _Other ->
+ fatal("There were compilation errors.")
+ end
+ end;
+ is_binary(FormsOrBin) ->
+ case Source of
+ archive ->
+ case code:set_primary_archive(File, FormsOrBin) of
+ ok when CheckOnly ->
+ case code:load_file(Module) of
+ {module, _} ->
+ case erlang:function_exported(Module, main, 1) of
+ true ->
+ my_halt(0);
+ false ->
+ Text = lists:concat(["Function ", Module, ":main/1 is not exported"]),
+ fatal(Text)
+ end;
+ _ ->
+ Text = lists:concat(["Cannot load module ", Module, " from archive"]),
+ fatal(Text)
+ end;
+ ok ->
+ case Mode2 of
+ run -> run(Module, Args);
+ debug -> debug(Module, Module, Args)
+ end;
+ {error, bad_eocd} ->
+ fatal("Not an archive file");
+ {error, Reason} ->
+ fatal(Reason)
+ end;
+ beam ->
+ case Mode2 of
+ run ->
+ {module, Module} = code:load_binary(Module, File, FormsOrBin),
+ run(Module, Args);
+ debug ->
+ [Base | Rest] = lists:reverse(filename:split(File)),
+ Base2 = filename:basename(Base, code:objfile_extension()),
+ Rest2 =
+ case Rest of
+ ["ebin" | Top] -> ["src" | Top];
+ _ -> Rest
+ end,
+ SrcFile = filename:join(lists:reverse([Base2 ++ ".erl" | Rest2])),
+ debug(Module, {Module, SrcFile, File, FormsOrBin}, Args)
+ end
+ end
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Parse script
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+parse_file(File, CheckOnly) ->
+ S = #state{file = File,
+ n_errors = 0,
+ mode = interpret,
+ exports_main = false,
+ has_records = false},
+ {ok, Fd} =
+ case file:open(File, [read]) of
+ {ok, Fd0} ->
+ {ok, Fd0};
+ {error, R} ->
+ fatal(lists:concat([file:format_error(R), ": '", File, "'"]))
+ end,
+ {HeaderSz, StartLine, ScriptType} = skip_header(Fd, 1),
+ #state{mode = Mode,
+ source = Source,
+ module = Module,
+ forms_or_bin = FormsOrBin} =
+ case ScriptType of
+ archive ->
+ %% Archive file
+ ok = file:close(Fd),
+ parse_archive(S, File, HeaderSz);
+ beam ->
+ %% Beam file
+ ok = file:close(Fd),
+ parse_beam(S, File, HeaderSz, CheckOnly);
+ source ->
+ %% Source code
+ parse_source(S, File, Fd, StartLine, HeaderSz, CheckOnly)
+ end,
+ {Source, Module, FormsOrBin, Mode}.
+
+%% Skip header and make a heuristic guess about the script type
+skip_header(P, LineNo) ->
+ %% Skip shebang on first line
+ {ok, HeaderSz0} = file:position(P, cur),
+ Line1 = get_line(P),
+ case classify_line(Line1) of
+ shebang ->
+ find_first_body_line(P, LineNo);
+ archive ->
+ {HeaderSz0, LineNo, archive};
+ beam ->
+ {HeaderSz0, LineNo, beam};
+ _ ->
+ find_first_body_line(P, LineNo)
+ end.
+
+find_first_body_line(P, LineNo) ->
+ {ok, HeaderSz1} = file:position(P, cur),
+ %% Look for special comment on second line
+ Line2 = get_line(P),
+ {ok, HeaderSz2} = file:position(P, cur),
+ case classify_line(Line2) of
+ emu_args ->
+ %% Skip special comment on second line
+ Line3 = get_line(P),
+ {HeaderSz2, LineNo + 2, guess_type(Line3)};
+ _ ->
+ %% Look for special comment on third line
+ Line3 = get_line(P),
+ {ok, HeaderSz3} = file:position(P, cur),
+ case classify_line(Line3) of
+ emu_args ->
+ %% Skip special comment on third line
+ Line4 = get_line(P),
+ {HeaderSz3, LineNo + 3, guess_type(Line4)};
+ _ ->
+ %% Just skip shebang on first line
+ {HeaderSz1, LineNo + 1, guess_type(Line2)}
+ end
+ end.
+
+classify_line(Line) ->
+ case Line of
+ [$\#, $\! | _] ->
+ shebang;
+ [$P, $K | _] ->
+ archive;
+ [$F, $O, $R, $1 | _] ->
+ beam;
+ [$\%, $\%, $\! | _] ->
+ emu_args;
+ _ ->
+ undefined
+ end.
+
+guess_type(Line) ->
+ case classify_line(Line) of
+ archive -> archive;
+ beam -> beam;
+ _ -> source
+ end.
+
+get_line(P) ->
+ case io:get_line(P, '') of
+ eof ->
+ fatal("Premature end of file reached");
+ Line ->
+ Line
+ end.
+
+parse_archive(S, File, HeaderSz) ->
+ case file:read_file(File) of
+ {ok, <<_FirstLine:HeaderSz/binary, Bin/binary>>} ->
+ Mod =
+ case init:get_argument(escript) of
+ {ok, [["main", M]]} ->
+ %% Use explicit module name
+ list_to_atom(M);
+ _ ->
+ %% Use escript name without extension as module name
+ RevBase = lists:reverse(filename:basename(File)),
+ RevBase2 =
+ case lists:dropwhile(fun(X) -> X =/= $. end, RevBase) of
+ [$. | Rest] -> Rest;
+ [] -> RevBase
+ end,
+ list_to_atom(lists:reverse(RevBase2))
+ end,
+
+ S#state{source = archive,
+ mode = run,
+ module = Mod,
+ forms_or_bin = Bin};
+ {ok, _} ->
+ fatal("Illegal archive format");
+ {error, Reason} ->
+ fatal(file:format_error(Reason))
+ end.
+
+
+parse_beam(S, File, HeaderSz, CheckOnly) ->
+ {ok, <<_FirstLine:HeaderSz/binary, Bin/binary>>} =
+ file:read_file(File),
+ case beam_lib:chunks(Bin, [exports]) of
+ {ok, {Module, [{exports, Exports}]}} ->
+ case CheckOnly of
+ true ->
+ case lists:member({main, 1}, Exports) of
+ true ->
+ my_halt(0);
+ false ->
+ Text = lists:concat(["Function ", Module, ":main/1 is not exported"]),
+ fatal(Text)
+ end;
+ false ->
+ S#state{source = beam,
+ mode = run,
+ module = Module,
+ forms_or_bin = Bin}
+ end;
+ {error, beam_lib, Reason} when is_tuple(Reason) ->
+ fatal(element(1, Reason));
+ {error, beam_lib, Reason} ->
+ fatal(Reason)
+ end.
+
+parse_source(S, File, Fd, StartLine, HeaderSz, CheckOnly) ->
+ {PreDefMacros, Module} = pre_def_macros(File),
+ IncludePath = [],
+ {ok, _} = file:position(Fd, {bof, HeaderSz}),
+ case epp:open(File, Fd, StartLine, IncludePath, PreDefMacros) of
+ {ok, Epp} ->
+ {ok, FileForm} = epp:parse_erl_form(Epp),
+ OptModRes = epp:parse_erl_form(Epp),
+ S2 = S#state{source = text, module = Module},
+ S3 =
+ case OptModRes of
+ {ok, {attribute,_, module, M} = Form} ->
+ epp_parse_file(Epp, S2#state{module = M}, [Form, FileForm]);
+ {ok, _} ->
+ ModForm = {attribute,1,module, Module},
+ epp_parse_file2(Epp, S2, [ModForm, FileForm], OptModRes);
+ {error, _} ->
+ epp_parse_file2(Epp, S2, [FileForm], OptModRes);
+ {eof,LastLine} ->
+ S#state{forms_or_bin = [FileForm, {eof,LastLine}]}
+ end,
+ ok = epp:close(Epp),
+ ok = file:close(Fd),
+ check_source(S3, CheckOnly);
+ {error, Reason} ->
+ io:format("escript: ~p\n", [Reason]),
+ fatal("Preprocessor error")
+ end.
+
+check_source(S, CheckOnly) ->
+ case S of
+ #state{n_errors = Nerrs} when Nerrs =/= 0 ->
+ fatal("There were compilation errors.");
+ #state{exports_main = ExpMain,
+ has_records = HasRecs,
+ forms_or_bin = [FileForm2, ModForm2 | Forms]} ->
+ %% Optionally add export of main/1
+ Forms2 =
+ case ExpMain of
+ false -> [{attribute,0,export, [{main,1}]} | Forms];
+ true -> Forms
+ end,
+ Forms3 = [FileForm2, ModForm2 | Forms2],
+ case CheckOnly of
+ true ->
+ %% Optionally expand records
+ Forms4 =
+ case HasRecs of
+ false -> Forms3;
+ true -> erl_expand_records:module(Forms3, [])
+ end,
+ %% Strong validation and halt
+ case compile:forms(Forms4, [report,strong_validation]) of
+ {ok,_} ->
+ my_halt(0);
+ _Other ->
+ fatal("There were compilation errors.")
+ end;
+ false ->
+ %% Basic validation before execution
+ case erl_lint:module(Forms3) of
+ {ok,Ws} ->
+ report_warnings(Ws);
+ {error,Es,Ws} ->
+ report_errors(Es),
+ report_warnings(Ws),
+ fatal("There were compilation errors.")
+ end,
+ %% Optionally expand records
+ Forms4 =
+ case HasRecs of
+ false -> Forms3;
+ true -> erl_expand_records:module(Forms3, [])
+ end,
+ S#state{forms_or_bin = Forms4}
+ end
+ end.
+
+pre_def_macros(File) ->
+ {MegaSecs, Secs, MicroSecs} = erlang:now(),
+ Replace = fun(Char) ->
+ case Char of
+ $\. -> $\_;
+ _ -> Char
+ end
+ end,
+ CleanBase = lists:map(Replace, filename:basename(File)),
+ ModuleStr =
+ CleanBase ++ "__" ++
+ "escript__" ++
+ integer_to_list(MegaSecs) ++ "__" ++
+ integer_to_list(Secs) ++ "__" ++
+ integer_to_list(MicroSecs),
+ Module = list_to_atom(ModuleStr),
+ PreDefMacros = [{'MODULE', Module, redefine},
+ {'MODULE_STRING', ModuleStr, redefine}],
+ {PreDefMacros, Module}.
+
+epp_parse_file(Epp, S, Forms) ->
+ Parsed = epp:parse_erl_form(Epp),
+ epp_parse_file2(Epp, S, Forms, Parsed).
+
+epp_parse_file2(Epp, S, Forms, Parsed) ->
+ %% io:format("~p\n", [Parsed]),
+ case Parsed of
+ {ok, Form} ->
+ case Form of
+ {attribute,Ln,record,{Record,Fields}} ->
+ S2 = S#state{has_records = true},
+ case epp:normalize_typed_record_fields(Fields) of
+ {typed, NewFields} ->
+ epp_parse_file(Epp, S2,
+ [{attribute, Ln, record, {Record, NewFields}},
+ {attribute, Ln, type,
+ {{record, Record}, Fields, []}} | Forms]);
+ not_typed ->
+ epp_parse_file(Epp, S2, [Form | Forms])
+ end;
+ {attribute,Ln,mode,NewMode} ->
+ S2 = S#state{mode = NewMode},
+ if
+ NewMode =:= compile; NewMode =:= interpret; NewMode =:= debug ->
+ epp_parse_file(Epp, S2, [Form | Forms]);
+ true ->
+ Args = lists:flatten(io_lib:format("illegal mode attribute: ~p", [NewMode])),
+ io:format("~s:~w ~s\n", [S#state.file,Ln,Args]),
+ Error = {error,{Ln,erl_parse,Args}},
+ Nerrs= S#state.n_errors + 1,
+ epp_parse_file(Epp, S2#state{n_errors = Nerrs}, [Error | Forms])
+ end;
+ {attribute,_,export,Fs} ->
+ case lists:member({main,1}, Fs) of
+ false ->
+ epp_parse_file(Epp, S, [Form | Forms]);
+ true ->
+ epp_parse_file(Epp, S#state{exports_main = true}, [Form | Forms])
+ end;
+ _ ->
+ epp_parse_file(Epp, S, [Form | Forms])
+ end;
+ {error,{Ln,Mod,Args}} = Form ->
+ io:format("~s:~w: ~s\n",
+ [S#state.file,Ln,Mod:format_error(Args)]),
+ epp_parse_file(Epp, S#state{n_errors = S#state.n_errors + 1}, [Form | Forms]);
+ {eof,LastLine} ->
+ S#state{forms_or_bin = lists:reverse([{eof, LastLine} | Forms])}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Evaluate script
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+debug(Module, AbsMod, Args) ->
+ case hidden_apply(debugger, debugger, start, []) of
+ {ok, _} ->
+ case hidden_apply(debugger, int, i, [AbsMod]) of
+ {module, _} ->
+ hidden_apply(debugger, debugger, auto_attach, [[init]]),
+ run(Module, Args);
+ error ->
+ Text = lists:concat(["Cannot load the code for ", Module, " into the debugger"]),
+ fatal(Text)
+ end;
+ _ ->
+ fatal("Cannot start the debugger")
+ end.
+
+run(Module, Args) ->
+ try
+ Module:main(Args),
+ my_halt(0)
+ catch
+ Class:Reason ->
+ fatal(format_exception(Class, Reason))
+ end.
+
+interpret(Forms, File, Args) ->
+ Dict = parse_to_dict(Forms),
+ ArgsA = erl_parse:abstract(Args, 0),
+ Call = {call,0,{atom,0,main},[ArgsA]},
+ try
+ erl_eval:expr(Call,
+ erl_eval:new_bindings(),
+ {value,fun(I, J) -> code_handler(I, J, Dict, File) end}),
+ my_halt(0)
+ catch
+ Class:Reason ->
+ fatal(format_exception(Class, Reason))
+ end.
+
+report_errors(Errors) ->
+ lists:foreach(fun ({{F,_L},Eds}) -> list_errors(F, Eds);
+ ({F,Eds}) -> list_errors(F, Eds) end,
+ Errors).
+
+list_errors(F, [{Line,Mod,E}|Es]) ->
+ io:fwrite("~s:~w: ~s\n", [F,Line,Mod:format_error(E)]),
+ list_errors(F, Es);
+list_errors(F, [{Mod,E}|Es]) ->
+ io:fwrite("~s: ~s\n", [F,Mod:format_error(E)]),
+ list_errors(F, Es);
+list_errors(_F, []) -> ok.
+
+report_warnings(Ws0) ->
+ Ws1 = lists:flatmap(fun({{F,_L},Eds}) -> format_message(F, Eds);
+ ({F,Eds}) -> format_message(F, Eds) end,
+ Ws0),
+ Ws = ordsets:from_list(Ws1),
+ lists:foreach(fun({_,Str}) -> io:put_chars(Str) end, Ws).
+
+format_message(F, [{Line,Mod,E}|Es]) ->
+ M = {{F,Line},io_lib:format("~s:~w: Warning: ~s\n", [F,Line,Mod:format_error(E)])},
+ [M|format_message(F, Es)];
+format_message(F, [{Mod,E}|Es]) ->
+ M = {none,io_lib:format("~s: Warning: ~s\n", [F,Mod:format_error(E)])},
+ [M|format_message(F, Es)];
+format_message(_, []) -> [].
+
+parse_to_dict(L) -> parse_to_dict(L, dict:new()).
+
+parse_to_dict([{function,_,Name,Arity,Clauses}|T], Dict0) ->
+ Dict = dict:store({local, Name,Arity}, Clauses, Dict0),
+ parse_to_dict(T, Dict);
+parse_to_dict([{attribute,_,import,{Mod,Funcs}}|T], Dict0) ->
+ Dict = lists:foldl(fun(I, D) ->
+ dict:store({remote,I}, Mod, D)
+ end, Dict0, Funcs),
+ parse_to_dict(T, Dict);
+parse_to_dict([_|T], Dict) ->
+ parse_to_dict(T, Dict);
+parse_to_dict([], Dict) ->
+ Dict.
+
+code_handler(local, [file], _, File) ->
+ File;
+code_handler(Name, Args, Dict, File) ->
+ %%io:format("code handler=~p~n",[{Name, Args}]),
+ Arity = length(Args),
+ case dict:find({local,Name,Arity}, Dict) of
+ {ok, Cs} ->
+ LF = {value,fun(I, J) -> code_handler(I, J, Dict, File) end},
+ case erl_eval:match_clause(Cs, Args,erl_eval:new_bindings(),LF) of
+ {Body, Bs} ->
+ eval_exprs(Body, Bs, LF, none, none);
+ nomatch ->
+ erlang:error({function_clause,[{local,Name,Args}]})
+ end;
+ error ->
+ case dict:find({remote,{Name,Arity}}, Dict) of
+ {ok, Mod} ->
+ %% io:format("Calling:~p~n",[{Mod,Name,Args}]),
+ apply(Mod, Name, Args);
+ error ->
+ io:format("Script does not export ~w/~w\n", [Name,Arity]),
+ my_halt(127)
+ end
+ end.
+
+eval_exprs([E], Bs0, Lf, Ef, _RBs) ->
+ RBs1 = value,
+ erl_eval:expr(E, Bs0, Lf, Ef, RBs1);
+eval_exprs([E|Es], Bs0, Lf, Ef, RBs) ->
+ RBs1 = none,
+ {value,_V,Bs} = erl_eval:expr(E, Bs0, Lf, Ef, RBs1),
+ eval_exprs(Es, Bs, Lf, Ef, RBs).
+
+format_exception(Class, Reason) ->
+ PF = fun(Term, I) ->
+ io_lib:format("~." ++ integer_to_list(I) ++ "P", [Term, 50])
+ end,
+ StackTrace = erlang:get_stacktrace(),
+ StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
+ lib:format_exception(1, Class, Reason, StackTrace, StackFun, PF).
+
+fatal(Str) ->
+ throw(Str).
+
+my_halt(Reason) ->
+ case process_info(group_leader(), status) of
+ {_,waiting} ->
+ %% Now all output data is down in the driver.
+ %% Give the driver some extra time before halting.
+ receive after 1 -> ok end,
+ halt(Reason);
+ _ ->
+ %% Probably still processing I/O requests.
+ erlang:yield(),
+ my_halt(Reason)
+ end.
+
+hidden_apply(App, M, F, Args) ->
+ try
+ apply(fun() -> M end(), F, Args)
+ catch
+ error:undef ->
+ case erlang:get_stacktrace() of
+ [{M,F,Args} | _] ->
+ Arity = length(Args),
+ Text = io_lib:format("Call to ~w:~w/~w in application ~w failed.\n",
+ [M, F, Arity, App]),
+ fatal(Text);
+ Stk ->
+ erlang:raise(error, undef, Stk)
+ end
+ end.
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
new file mode 100644
index 0000000000..9f84e3639f
--- /dev/null
+++ b/lib/stdlib/src/ets.erl
@@ -0,0 +1,1269 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ets).
+
+%% Interface to the Term store BIF's
+%% ets == Erlang Term Store
+
+-export([file2tab/1,
+ file2tab/2,
+ filter/3,
+ foldl/3, foldr/3,
+ match_delete/2,
+ tab2file/2,
+ tab2file/3,
+ tabfile_info/1,
+ from_dets/2,
+ to_dets/2,
+ init_table/2,
+ test_ms/2,
+ tab2list/1,
+ table/1,
+ table/2,
+ fun2ms/1,
+ match_spec_run/2,
+ repair_continuation/2]).
+
+-export([i/0, i/1, i/2, i/3]).
+
+%%------------------------------------------------------------------------------
+
+-type tab() :: atom() | tid().
+
+-type ext_info() :: 'md5sum' | 'object_count'.
+-type protection() :: 'private' | 'protected' | 'public'.
+-type type() :: 'bag' | 'duplicate_bag' | 'ordered_set' | 'set'.
+
+-type table_info() :: {'name', atom()}
+ | {'type', type()}
+ | {'protection', protection()}
+ | {'named_table', boolean()}
+ | {'keypos', non_neg_integer()}
+ | {'size', non_neg_integer()}
+ | {'extended_info', [ext_info()]}
+ | {'version', {non_neg_integer(), non_neg_integer()}}.
+
+%% these ones are also defined in erl_bif_types
+-type match_pattern() :: atom() | tuple().
+-type match_specs() :: [{match_pattern(), [_], [_]}].
+
+%%------------------------------------------------------------------------------
+
+%% The following functions used to be found in this module, but
+%% are now BIFs (i.e. implemented in C).
+%%
+%% all/0
+%% new/2
+%% delete/1
+%% delete/2
+%% first/1
+%% info/1
+%% info/2
+%% safe_fixtable/2
+%% lookup/2
+%% lookup_element/3
+%% insert/2
+%% is_compiled_ms/1
+%% last/1
+%% next/2
+%% prev/2
+%% rename/2
+%% slot/2
+%% match/1
+%% match/2
+%% match/3
+%% match_object/1
+%% match_object/2
+%% match_object/3
+%% match_spec_compile/1
+%% match_spec_run_r/3
+%% select/1
+%% select/2
+%% select/3
+%% select_reverse/1
+%% select_reverse/2
+%% select_reverse/3
+%% select_delete/2
+%% update_counter/3
+%%
+
+-opaque comp_match_spec() :: any(). %% this one is REALLY opaque
+
+-spec match_spec_run([tuple()], comp_match_spec()) -> [term()].
+
+match_spec_run(List, CompiledMS) ->
+ lists:reverse(ets:match_spec_run_r(List, CompiledMS, [])).
+
+-type continuation() :: '$end_of_table'
+ | {tab(),integer(),integer(),binary(),list(),integer()}
+ | {tab(),_,_,integer(),binary(),list(),integer(),integer()}.
+
+-spec repair_continuation(continuation(), match_specs()) -> continuation().
+
+%% $end_of_table is an allowed continuation in ets...
+repair_continuation('$end_of_table', _) ->
+ '$end_of_table';
+%% ordered_set
+repair_continuation(Untouched = {Table,Lastkey,EndCondition,N2,Bin,L2,N3,N4}, MS)
+ when %% (is_atom(Table) or is_integer(Table)),
+ is_integer(N2),
+ byte_size(Bin) =:= 0,
+ is_list(L2),
+ is_integer(N3),
+ is_integer(N4) ->
+ case ets:is_compiled_ms(Bin) of
+ true ->
+ Untouched;
+ false ->
+ {Table,Lastkey,EndCondition,N2,ets:match_spec_compile(MS),L2,N3,N4}
+ end;
+%% set/bag/duplicate_bag
+repair_continuation(Untouched = {Table,N1,N2,Bin,L,N3}, MS)
+ when %% (is_atom(Table) or is_integer(Table)),
+ is_integer(N1),
+ is_integer(N2),
+ byte_size(Bin) =:= 0,
+ is_list(L),
+ is_integer(N3) ->
+ case ets:is_compiled_ms(Bin) of
+ true ->
+ Untouched;
+ false ->
+ {Table,N1,N2,ets:match_spec_compile(MS),L,N3}
+ end.
+
+-spec fun2ms(function()) -> match_specs().
+
+fun2ms(ShellFun) when is_function(ShellFun) ->
+ %% Check that this is really a shell fun...
+ case erl_eval:fun_data(ShellFun) of
+ {fun_data,ImportList,Clauses} ->
+ case ms_transform:transform_from_shell(
+ ?MODULE,Clauses,ImportList) of
+ {error,[{_,[{_,_,Code}|_]}|_],_} ->
+ io:format("Error: ~s~n",
+ [ms_transform:format_error(Code)]),
+ {error,transform_error};
+ Else ->
+ Else
+ end;
+ false ->
+ exit({badarg,{?MODULE,fun2ms,
+ [function,called,with,real,'fun',
+ should,be,transformed,with,
+ parse_transform,'or',called,with,
+ a,'fun',generated,in,the,
+ shell]}})
+ end.
+
+-spec foldl(fun((_, term()) -> term()), term(), tab()) -> term().
+
+foldl(F, Accu, T) ->
+ ets:safe_fixtable(T, true),
+ First = ets:first(T),
+ try
+ do_foldl(F, Accu, First, T)
+ after
+ ets:safe_fixtable(T, false)
+ end.
+
+do_foldl(F, Accu0, Key, T) ->
+ case Key of
+ '$end_of_table' ->
+ Accu0;
+ _ ->
+ do_foldl(F,
+ lists:foldl(F, Accu0, ets:lookup(T, Key)),
+ ets:next(T, Key), T)
+ end.
+
+-spec foldr(fun((_, term()) -> term()), term(), tab()) -> term().
+
+foldr(F, Accu, T) ->
+ ets:safe_fixtable(T, true),
+ Last = ets:last(T),
+ try
+ do_foldr(F, Accu, Last, T)
+ after
+ ets:safe_fixtable(T, false)
+ end.
+
+do_foldr(F, Accu0, Key, T) ->
+ case Key of
+ '$end_of_table' ->
+ Accu0;
+ _ ->
+ do_foldr(F,
+ lists:foldr(F, Accu0, ets:lookup(T, Key)),
+ ets:prev(T, Key), T)
+ end.
+
+-spec from_dets(tab(), dets:tab_name()) -> 'true'.
+
+from_dets(EtsTable, DetsTable) ->
+ case (catch dets:to_ets(DetsTable, EtsTable)) of
+ {error, Reason} ->
+ erlang:error(Reason, [EtsTable,DetsTable]);
+ {'EXIT', {Reason1, _Stack1}} ->
+ erlang:error(Reason1,[EtsTable,DetsTable]);
+ {'EXIT', EReason} ->
+ erlang:error(EReason,[EtsTable,DetsTable]);
+ EtsTable ->
+ true;
+ Unexpected -> %% Dets bug?
+ erlang:error(Unexpected,[EtsTable,DetsTable])
+ end.
+
+-spec to_dets(tab(), dets:tab_name()) -> tab().
+
+to_dets(EtsTable, DetsTable) ->
+ case (catch dets:from_ets(DetsTable, EtsTable)) of
+ {error, Reason} ->
+ erlang:error(Reason, [EtsTable,DetsTable]);
+ {'EXIT', {Reason1, _Stack1}} ->
+ erlang:error(Reason1,[EtsTable,DetsTable]);
+ {'EXIT', EReason} ->
+ erlang:error(EReason,[EtsTable,DetsTable]);
+ ok ->
+ DetsTable;
+ Unexpected -> %% Dets bug?
+ erlang:error(Unexpected,[EtsTable,DetsTable])
+ end.
+
+-spec test_ms(tuple(), match_specs()) ->
+ {'ok', term()} | {'error', [{'warning'|'error', string()}]}.
+
+test_ms(Term, MS) ->
+ case erlang:match_spec_test(Term, MS, table) of
+ {ok, Result, _Flags, _Messages} ->
+ {ok, Result};
+ {error, _Errors} = Error ->
+ Error
+ end.
+
+-spec init_table(tab(), fun(('read' | 'close') -> term())) -> 'true'.
+
+init_table(Table, Fun) ->
+ ets:delete_all_objects(Table),
+ init_table_continue(Table, Fun(read)).
+
+init_table_continue(_Table, end_of_input) ->
+ true;
+init_table_continue(Table, {List, Fun}) when is_list(List), is_function(Fun) ->
+ case (catch init_table_sub(Table, List)) of
+ {'EXIT', Reason} ->
+ (catch Fun(close)),
+ exit(Reason);
+ true ->
+ init_table_continue(Table, Fun(read))
+ end;
+init_table_continue(_Table, Error) ->
+ exit(Error).
+
+init_table_sub(_Table, []) ->
+ true;
+init_table_sub(Table, [H|T]) ->
+ ets:insert(Table, H),
+ init_table_sub(Table, T).
+
+-spec match_delete(tab(), match_pattern()) -> 'true'.
+
+match_delete(Table, Pattern) ->
+ ets:select_delete(Table, [{Pattern,[],[true]}]),
+ true.
+
+%% Produce a list of tuples from a table
+
+-spec tab2list(tab()) -> [tuple()].
+
+tab2list(T) ->
+ ets:match_object(T, '_').
+
+-spec filter(tab(), function(), [term()]) -> [term()].
+
+filter(Tn, F, A) when is_atom(Tn) ; is_integer(Tn) ->
+ do_filter(Tn, ets:first(Tn), F, A, []).
+
+do_filter(_Tab, '$end_of_table', _, _, Ack) ->
+ Ack;
+do_filter(Tab, Key, F, A, Ack) ->
+ case apply(F, [ets:lookup(Tab, Key)|A]) of
+ false ->
+ do_filter(Tab, ets:next(Tab, Key), F, A, Ack);
+ true ->
+ Ack2 = ets:lookup(Tab, Key) ++ Ack,
+ do_filter(Tab, ets:next(Tab, Key), F, A, Ack2);
+ {true, Value} ->
+ do_filter(Tab, ets:next(Tab, Key), F, A, [Value|Ack])
+ end.
+
+
+%% Dump a table to a file using the disk_log facility
+
+%% Options := [Option]
+%% Option := {extended_info,[ExtInfo]}
+%% ExtInfo := object_count | md5sum
+
+-define(MAJOR_F2T_VERSION,1).
+-define(MINOR_F2T_VERSION,0).
+
+-record(filetab_options,
+ {
+ object_count = false :: boolean(),
+ md5sum = false :: boolean()
+ }).
+
+-type fname() :: string() | atom().
+-type t2f_option() :: {'extended_info', [ext_info()]}.
+
+-spec tab2file(tab(), fname()) -> 'ok' | {'error', term()}.
+
+tab2file(Tab, File) ->
+ tab2file(Tab, File, []).
+
+-spec tab2file(tab(), fname(), [t2f_option()]) -> 'ok' | {'error', term()}.
+
+tab2file(Tab, File, Options) ->
+ try
+ {ok, FtOptions} = parse_ft_options(Options),
+ file:delete(File),
+ case file:read_file_info(File) of
+ {error, enoent} -> ok;
+ _ -> throw(eaccess)
+ end,
+ Name = make_ref(),
+ case disk_log:open([{name, Name}, {file, File}]) of
+ {ok, Name} ->
+ ok;
+ {error, Reason} ->
+ throw(Reason)
+ end,
+ try
+ Info0 = case ets:info(Tab) of
+ undefined ->
+ %% erlang:error(badarg, [Tab, File, Options]);
+ throw(badtab);
+ I ->
+ I
+ end,
+ Info = [list_to_tuple(Info0 ++
+ [{major_version,?MAJOR_F2T_VERSION},
+ {minor_version,?MINOR_F2T_VERSION},
+ {extended_info,
+ ft_options_to_list(FtOptions)}])],
+ {LogFun, InitState} =
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ {fun(Oldstate,Termlist) ->
+ {NewState,BinList} =
+ md5terms(Oldstate,Termlist),
+ disk_log:blog_terms(Name,BinList),
+ NewState
+ end,
+ erlang:md5_init()};
+ false ->
+ {fun(_,Termlist) ->
+ disk_log:log_terms(Name,Termlist),
+ true
+ end,
+ true}
+ end,
+ ets:safe_fixtable(Tab,true),
+ {NewState1,Num} = try
+ NewState = LogFun(InitState,Info),
+ dump_file(
+ ets:select(Tab,[{'_',[],['$_']}],100),
+ LogFun, NewState, 0)
+ after
+ (catch ets:safe_fixtable(Tab,false))
+ end,
+ EndInfo =
+ case FtOptions#filetab_options.object_count of
+ true ->
+ [{count,Num}];
+ false ->
+ []
+ end ++
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ [{md5,erlang:md5_final(NewState1)}];
+ false ->
+ []
+ end,
+ case EndInfo of
+ [] ->
+ ok;
+ List ->
+ LogFun(NewState1,[['$end_of_table',List]])
+ end,
+ disk_log:close(Name)
+ catch
+ throw:TReason ->
+ disk_log:close(Name),
+ file:delete(File),
+ throw(TReason);
+ exit:ExReason ->
+ disk_log:close(Name),
+ file:delete(File),
+ exit(ExReason);
+ error:ErReason ->
+ disk_log:close(Name),
+ file:delete(File),
+ erlang:raise(error,ErReason,erlang:get_stacktrace())
+ end
+ catch
+ throw:TReason2 ->
+ {error,TReason2};
+ exit:ExReason2 ->
+ {error,ExReason2}
+ end.
+
+dump_file('$end_of_table', _LogFun, State, Num) ->
+ {State,Num};
+dump_file({Terms, Context}, LogFun, State, Num) ->
+ Count = length(Terms),
+ NewState = LogFun(State, Terms),
+ dump_file(ets:select(Context), LogFun, NewState, Num + Count).
+
+ft_options_to_list(#filetab_options{md5sum = MD5, object_count = PS}) ->
+ case PS of
+ true ->
+ [object_count];
+ _ ->
+ []
+ end ++
+ case MD5 of
+ true ->
+ [md5sum];
+ _ ->
+ []
+ end.
+
+md5terms(State, []) ->
+ {State, []};
+md5terms(State, [H|T]) ->
+ B = term_to_binary(H),
+ NewState = erlang:md5_update(State, B),
+ {FinState, TL} = md5terms(NewState, T),
+ {FinState, [B|TL]}.
+
+parse_ft_options(Options) when is_list(Options) ->
+ {Opt,Rest} = case (catch lists:keytake(extended_info,1,Options)) of
+ false ->
+ {[],Options};
+ {value,{extended_info,L},R} when is_list(L) ->
+ {L,R}
+ end,
+ case Rest of
+ [] ->
+ parse_ft_info_options(#filetab_options{}, Opt);
+ Other ->
+ throw({unknown_option, Other})
+ end;
+parse_ft_options(Malformed) ->
+ throw({malformed_option, Malformed}).
+
+parse_ft_info_options(FtOpt,[]) ->
+ {ok,FtOpt};
+parse_ft_info_options(FtOpt,[object_count | T]) ->
+ parse_ft_info_options(FtOpt#filetab_options{object_count = true}, T);
+parse_ft_info_options(FtOpt,[md5sum | T]) ->
+ parse_ft_info_options(FtOpt#filetab_options{md5sum = true}, T);
+parse_ft_info_options(_,[Unexpected | _]) ->
+ throw({unknown_option,[{extended_info,[Unexpected]}]});
+parse_ft_info_options(_,Malformed) ->
+ throw({malformed_option,Malformed}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Read a dumped file from disk and create a corresponding table
+%% Opts := [Opt]
+%% Opt := {verify,boolean()}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-type f2t_option() :: {'verify', boolean()}.
+
+-spec file2tab(fname()) -> {'ok', tab()} | {'error', term()}.
+
+file2tab(File) ->
+ file2tab(File, []).
+
+-spec file2tab(fname(), [f2t_option()]) -> {'ok', tab()} | {'error', term()}.
+
+file2tab(File, Opts) ->
+ try
+ {ok,Verify} = parse_f2t_opts(Opts,false),
+ Name = make_ref(),
+ {ok, Major, Minor, FtOptions, MD5State, FullHeader, DLContext} =
+ case disk_log:open([{name, Name},
+ {file, File},
+ {mode, read_only}]) of
+ {ok, Name} ->
+ get_header_data(Name,Verify);
+ {repaired, Name, _,_} -> %Uh? cannot happen?
+ case Verify of
+ true ->
+ disk_log:close(Name),
+ throw(badfile);
+ false ->
+ get_header_data(Name,Verify)
+ end;
+ {error, Other1} ->
+ throw({read_error, Other1});
+ Other2 ->
+ throw(Other2)
+ end,
+ try
+ if
+ Major > ?MAJOR_F2T_VERSION ->
+ throw({unsupported_file_version,{Major,Minor}});
+ true ->
+ ok
+ end,
+ {ok, Tab, HeadCount} = create_tab(FullHeader),
+ StrippedOptions =
+ case Verify of
+ true ->
+ FtOptions;
+ false ->
+ #filetab_options{}
+ end,
+ {ReadFun,InitState} =
+ case StrippedOptions#filetab_options.md5sum of
+ true ->
+ {fun({OldMD5State,OldCount,_OL,ODLContext} = OS) ->
+ case wrap_bchunk(Name,ODLContext,100,Verify) of
+ eof ->
+ {OS,[]};
+ {NDLContext,Blist} ->
+ {Termlist, NewMD5State,
+ NewCount,NewLast} =
+ md5_and_convert(Blist,
+ OldMD5State,
+ OldCount),
+ {{NewMD5State, NewCount,
+ NewLast,NDLContext},
+ Termlist}
+ end
+ end,
+ {MD5State,0,[],DLContext}};
+ false ->
+ {fun({_,OldCount,_OL,ODLContext} = OS) ->
+ case wrap_chunk(Name,ODLContext,100,Verify) of
+ eof ->
+ {OS,[]};
+ {NDLContext,List} ->
+ {NewLast,NewCount,NewList} =
+ scan_for_endinfo(List, OldCount),
+ {{false,NewCount,NewLast,NDLContext},
+ NewList}
+ end
+ end,
+ {false,0,[],DLContext}}
+ end,
+ try
+ do_read_and_verify(ReadFun,InitState,Tab,
+ StrippedOptions,HeadCount,Verify)
+ catch
+ throw:TReason ->
+ ets:delete(Tab),
+ throw(TReason);
+ exit:ExReason ->
+ ets:delete(Tab),
+ exit(ExReason);
+ error:ErReason ->
+ ets:delete(Tab),
+ erlang:raise(error,ErReason,erlang:get_stacktrace())
+ end
+ after
+ disk_log:close(Name)
+ end
+ catch
+ throw:TReason2 ->
+ {error,TReason2};
+ exit:ExReason2 ->
+ {error,ExReason2}
+ end.
+
+do_read_and_verify(ReadFun,InitState,Tab,FtOptions,HeadCount,Verify) ->
+ case load_table(ReadFun,InitState,Tab) of
+ {ok,{_,FinalCount,[],_}} ->
+ case {FtOptions#filetab_options.md5sum,
+ FtOptions#filetab_options.object_count} of
+ {false,false} ->
+ case Verify of
+ false ->
+ ok;
+ true ->
+ case FinalCount of
+ HeadCount ->
+ ok;
+ _ ->
+ throw(invalid_object_count)
+ end
+ end;
+ _ ->
+ throw(badfile)
+ end,
+ {ok,Tab};
+ {ok,{FinalMD5State,FinalCount,['$end_of_table',LastInfo],_}} ->
+ ECount = case lists:keysearch(count,1,LastInfo) of
+ {value,{count,N}} ->
+ N;
+ _ ->
+ false
+ end,
+ EMD5 = case lists:keysearch(md5,1,LastInfo) of
+ {value,{md5,M}} ->
+ M;
+ _ ->
+ false
+ end,
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ case erlang:md5_final(FinalMD5State) of
+ EMD5 ->
+ ok;
+ _MD5MisM ->
+ throw(checksum_error)
+ end;
+ false ->
+ ok
+ end,
+ case FtOptions#filetab_options.object_count of
+ true ->
+ case FinalCount of
+ ECount ->
+ ok;
+ _Other ->
+ throw(invalid_object_count)
+ end;
+ false ->
+ %% Only use header count if no extended info
+ %% at all is present and verification is requested.
+ case {Verify,FtOptions#filetab_options.md5sum} of
+ {true,false} ->
+ case FinalCount of
+ HeadCount ->
+ ok;
+ _Other2 ->
+ throw(invalid_object_count)
+ end;
+ _ ->
+ ok
+ end
+ end,
+ {ok,Tab}
+ end.
+
+parse_f2t_opts([],Verify) ->
+ {ok,Verify};
+parse_f2t_opts([{verify, true}|T],_OV) ->
+ parse_f2t_opts(T,true);
+parse_f2t_opts([{verify,false}|T],OV) ->
+ parse_f2t_opts(T,OV);
+parse_f2t_opts([Unexpected|_],_) ->
+ throw({unknown_option,Unexpected});
+parse_f2t_opts(Malformed,_) ->
+ throw({malformed_option,Malformed}).
+
+count_mandatory([]) ->
+ 0;
+count_mandatory([{Tag,_}|T]) when Tag =:= name;
+ Tag =:= type;
+ Tag =:= protection;
+ Tag =:= named_table;
+ Tag =:= keypos;
+ Tag =:= size ->
+ 1+count_mandatory(T);
+count_mandatory([_|T]) ->
+ count_mandatory(T).
+
+verify_header_mandatory(L) ->
+ count_mandatory(L) =:= 6.
+
+wrap_bchunk(Name,C,N,true) ->
+ case disk_log:bchunk(Name,C,N) of
+ {_,_,X} when X > 0 ->
+ throw(badfile);
+ {NC,Bin,_} ->
+ {NC,Bin};
+ Y ->
+ Y
+ end;
+wrap_bchunk(Name,C,N,false) ->
+ case disk_log:bchunk(Name,C,N) of
+ {NC,Bin,_} ->
+ {NC,Bin};
+ Y ->
+ Y
+ end.
+
+wrap_chunk(Name,C,N,true) ->
+ case disk_log:chunk(Name,C,N) of
+ {_,_,X} when X > 0 ->
+ throw(badfile);
+ {NC,TL,_} ->
+ {NC,TL};
+ Y ->
+ Y
+ end;
+wrap_chunk(Name,C,N,false) ->
+ case disk_log:chunk(Name,C,N) of
+ {NC,TL,_} ->
+ {NC,TL};
+ Y ->
+ Y
+ end.
+
+get_header_data(Name,true) ->
+ case wrap_bchunk(Name,start,1,true) of
+ {C,[Bin]} when is_binary(Bin) ->
+ T = binary_to_term(Bin),
+ case T of
+ Tup when is_tuple(Tup) ->
+ L = tuple_to_list(Tup),
+ case verify_header_mandatory(L) of
+ false ->
+ throw(badfile);
+ true ->
+ Major = case lists:keysearch(major,1,L) of
+ {value,{major,Maj}} ->
+ Maj;
+ _ ->
+ 0
+ end,
+ Minor = case lists:keysearch(minor,1,L) of
+ {value,{minor,Min}} ->
+ Min;
+ _ ->
+ 0
+ end,
+ FtOptions =
+ case lists:keysearch(extended_info,1,L) of
+ {value,{extended_info,I}}
+ when is_list(I) ->
+ #filetab_options
+ {
+ object_count =
+ lists:member(object_count,I),
+ md5sum =
+ lists:member(md5sum,I)
+ };
+ _ ->
+ #filetab_options{}
+ end,
+ MD5Initial =
+ case FtOptions#filetab_options.md5sum of
+ true ->
+ X = erlang:md5_init(),
+ erlang:md5_update(X,Bin);
+ false ->
+ false
+ end,
+ {ok, Major, Minor, FtOptions, MD5Initial, L, C}
+ end;
+ _X ->
+ throw(badfile)
+ end;
+ _Y ->
+ throw(badfile)
+ end;
+
+get_header_data(Name, false) ->
+ case wrap_chunk(Name,start,1,false) of
+ {C,[Tup]} when is_tuple(Tup) ->
+ L = tuple_to_list(Tup),
+ case verify_header_mandatory(L) of
+ false ->
+ throw(badfile);
+ true ->
+ Major = case lists:keysearch(major_version,1,L) of
+ {value,{major_version,Maj}} ->
+ Maj;
+ _ ->
+ 0
+ end,
+ Minor = case lists:keysearch(minor_version,1,L) of
+ {value,{minor_version,Min}} ->
+ Min;
+ _ ->
+ 0
+ end,
+ FtOptions =
+ case lists:keysearch(extended_info,1,L) of
+ {value,{extended_info,I}}
+ when is_list(I) ->
+ #filetab_options
+ {
+ object_count =
+ lists:member(object_count,I),
+ md5sum =
+ lists:member(md5sum,I)
+ };
+ _ ->
+ #filetab_options{}
+ end,
+ {ok, Major, Minor, FtOptions, false, L, C}
+ end;
+ _ ->
+ throw(badfile)
+ end.
+
+md5_and_convert([],MD5State,Count) ->
+ {[],MD5State,Count,[]};
+md5_and_convert([H|T],MD5State,Count) when is_binary(H) ->
+ case (catch binary_to_term(H)) of
+ {'EXIT', _} ->
+ md5_and_convert(T,MD5State,Count);
+ ['$end_of_table',Dat] ->
+ {[],MD5State,Count,['$end_of_table',Dat]};
+ Term ->
+ X = erlang:md5_update(MD5State,H),
+ {Rest,NewMD5,NewCount,NewLast} = md5_and_convert(T,X,Count+1),
+ {[Term | Rest],NewMD5,NewCount,NewLast}
+ end.
+scan_for_endinfo([],Count) ->
+ {[],Count,[]};
+scan_for_endinfo([['$end_of_table',Dat]],Count) ->
+ {['$end_of_table',Dat],Count,[]};
+scan_for_endinfo([Term|T],Count) ->
+ {NewLast,NCount,Rest} = scan_for_endinfo(T,Count+1),
+ {NewLast,NCount,[Term | Rest]}.
+
+load_table(ReadFun, State, Tab) ->
+ {NewState,NewData} = ReadFun(State),
+ case NewData of
+ [] ->
+ {ok,NewState};
+ List ->
+ ets:insert(Tab,List),
+ load_table(ReadFun,NewState,Tab)
+ end.
+
+create_tab(I) ->
+ {value, {name, Name}} = lists:keysearch(name, 1, I),
+ {value, {type, Type}} = lists:keysearch(type, 1, I),
+ {value, {protection, P}} = lists:keysearch(protection, 1, I),
+ {value, {named_table, Val}} = lists:keysearch(named_table, 1, I),
+ {value, {keypos, Kp}} = lists:keysearch(keypos, 1, I),
+ {value, {size, Sz}} = lists:keysearch(size, 1, I),
+ try
+ Tab = ets:new(Name, [Type, P, {keypos, Kp} | named_table(Val)]),
+ {ok, Tab, Sz}
+ catch
+ _:_ ->
+ throw(cannot_create_table)
+ end.
+
+named_table(true) -> [named_table];
+named_table(false) -> [].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% tabfile_info/1 reads the head information in an ets table dumped to
+%% disk by means of file2tab and returns a list of the relevant table
+%% information
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec tabfile_info(fname()) -> {'ok', [table_info()]} | {'error', term()}.
+
+tabfile_info(File) when is_list(File) ; is_atom(File) ->
+ try
+ Name = make_ref(),
+ {ok, Major, Minor, _FtOptions, _MD5State, FullHeader, _DLContext} =
+ case disk_log:open([{name, Name},
+ {file, File},
+ {mode, read_only}]) of
+ {ok, Name} ->
+ get_header_data(Name,false);
+ {repaired, Name, _,_} -> %Uh? cannot happen?
+ get_header_data(Name,false);
+ {error, Other1} ->
+ throw({read_error, Other1});
+ Other2 ->
+ throw(Other2)
+ end,
+ disk_log:close(Name),
+ {value, N} = lists:keysearch(name, 1, FullHeader),
+ {value, Type} = lists:keysearch(type, 1, FullHeader),
+ {value, P} = lists:keysearch(protection, 1, FullHeader),
+ {value, Val} = lists:keysearch(named_table, 1, FullHeader),
+ {value, Kp} = lists:keysearch(keypos, 1, FullHeader),
+ {value, Sz} = lists:keysearch(size, 1, FullHeader),
+ Ei = case lists:keysearch(extended_info, 1, FullHeader) of
+ {value, Ei0} -> Ei0;
+ _ -> {extended_info, []}
+ end,
+ {ok, [N,Type,P,Val,Kp,Sz,Ei,{version,{Major,Minor}}]}
+ catch
+ throw:TReason ->
+ {error,TReason};
+ exit:ExReason ->
+ {error,ExReason}
+ end.
+
+-type qlc__query_handle() :: term(). %% XXX: belongs in 'qlc'
+
+-type num_objects() :: 'default' | pos_integer().
+-type trav_method() :: 'first_next' | 'last_prev'
+ | 'select' | {'select', match_specs()}.
+-type table_option() :: {'n_objects', num_objects()}
+ | {'traverse', trav_method()}.
+
+-spec table(tab()) -> qlc__query_handle().
+
+table(Tab) ->
+ table(Tab, []).
+
+-spec table(tab(), table_option() | [table_option()]) -> qlc__query_handle().
+
+table(Tab, Opts) ->
+ case options(Opts, [traverse, n_objects]) of
+ {badarg,_} ->
+ erlang:error(badarg, [Tab, Opts]);
+ [[Traverse, NObjs], QlcOptions] ->
+ TF = case Traverse of
+ first_next ->
+ fun() -> qlc_next(Tab, ets:first(Tab)) end;
+ last_prev ->
+ fun() -> qlc_prev(Tab, ets:last(Tab)) end;
+ select ->
+ fun(MS) -> qlc_select(ets:select(Tab, MS, NObjs)) end;
+ {select, MS} ->
+ fun() -> qlc_select(ets:select(Tab, MS, NObjs)) end
+ end,
+ PreFun = fun(_) -> ets:safe_fixtable(Tab, true) end,
+ PostFun = fun() -> ets:safe_fixtable(Tab, false) end,
+ InfoFun = fun(Tag) -> table_info(Tab, Tag) end,
+ KeyEquality = case ets:info(Tab, type) of
+ ordered_set -> '==';
+ _ -> '=:='
+ end,
+ LookupFun =
+ case Traverse of
+ {select, _MS} ->
+ undefined;
+ _ ->
+ fun(_Pos, [K]) ->
+ ets:lookup(Tab, K);
+ (_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K)
+ end, Ks)
+ end
+ end,
+ FormatFun =
+ fun({all, _NElements, _ElementFun}) ->
+ As = [Tab | [Opts || _ <- [[]], Opts =/= []]],
+ {?MODULE, table, As};
+ ({match_spec, MS}) ->
+ {?MODULE, table,
+ [Tab, [{traverse, {select, MS}} |
+ listify(Opts)]]};
+ ({lookup, _KeyPos, [Value], _NElements, ElementFun}) ->
+ io_lib:format("~w:lookup(~w, ~w)",
+ [?MODULE, Tab, ElementFun(Value)]);
+ ({lookup, _KeyPos, Values, _NElements, ElementFun}) ->
+ Vals = [ElementFun(V) || V <- Values],
+ io_lib:format("lists:flatmap(fun(V) -> "
+ "~w:lookup(~w, V) end, ~w)",
+ [?MODULE, Tab, Vals])
+ end,
+ qlc:table(TF, [{pre_fun, PreFun}, {post_fun, PostFun},
+ {info_fun, InfoFun}, {format_fun, FormatFun},
+ {key_equality, KeyEquality},
+ {lookup_fun, LookupFun}] ++ QlcOptions)
+ end.
+
+table_info(Tab, num_of_objects) ->
+ ets:info(Tab, size);
+table_info(Tab, keypos) ->
+ ets:info(Tab, keypos);
+table_info(Tab, is_unique_objects) ->
+ ets:info(Tab, type) =/= duplicate_bag;
+table_info(Tab, is_sorted_key) ->
+ ets:info(Tab, type) =:= ordered_set;
+table_info(_Tab, _) ->
+ undefined.
+
+qlc_next(_Tab, '$end_of_table') ->
+ [];
+qlc_next(Tab, Key) ->
+ ets:lookup(Tab, Key) ++ fun() -> qlc_next(Tab, ets:next(Tab, Key)) end.
+
+qlc_prev(_Tab, '$end_of_table') ->
+ [];
+qlc_prev(Tab, Key) ->
+ ets:lookup(Tab, Key) ++ fun() -> qlc_prev(Tab, ets:prev(Tab, Key)) end.
+
+qlc_select('$end_of_table') ->
+ [];
+qlc_select({Objects, Cont}) ->
+ Objects ++ fun() -> qlc_select(ets:select(Cont)) end.
+
+options(Options, Keys) when is_list(Options) ->
+ options(Options, Keys, []);
+options(Option, Keys) ->
+ options([Option], Keys, []).
+
+options(Options, [Key | Keys], L) when is_list(Options) ->
+ V = case lists:keysearch(Key, 1, Options) of
+ {value, {n_objects, default}} ->
+ {ok, default_option(Key)};
+ {value, {n_objects, NObjs}} when is_integer(NObjs),
+ NObjs >= 1 ->
+ {ok, NObjs};
+ {value, {traverse, select}} ->
+ {ok, select};
+ {value, {traverse, {select, MS}}} ->
+ {ok, {select, MS}};
+ {value, {traverse, first_next}} ->
+ {ok, first_next};
+ {value, {traverse, last_prev}} ->
+ {ok, last_prev};
+ {value, {Key, _}} ->
+ badarg;
+ false ->
+ Default = default_option(Key),
+ {ok, Default}
+ end,
+ case V of
+ badarg ->
+ {badarg, Key};
+ {ok,Value} ->
+ NewOptions = lists:keydelete(Key, 1, Options),
+ options(NewOptions, Keys, [Value | L])
+ end;
+options(Options, [], L) ->
+ [lists:reverse(L), Options].
+
+default_option(traverse) -> select;
+default_option(n_objects) -> 100.
+
+listify(L) when is_list(L) ->
+ L;
+listify(T) ->
+ [T].
+
+%% End of table/2.
+
+%% Print info about all tabs on the tty
+-spec i() -> 'ok'.
+
+i() ->
+ hform('id', 'name', 'type', 'size', 'mem', 'owner'),
+ io:format(" -------------------------------------"
+ "---------------------------------------\n"),
+ lists:foreach(fun prinfo/1, tabs()),
+ ok.
+
+tabs() ->
+ lists:sort(ets:all()).
+
+prinfo(Tab) ->
+ case catch prinfo2(Tab) of
+ {'EXIT', _} ->
+ io:format("~-10s ... unreadable \n", [to_string(Tab)]);
+ ok ->
+ ok
+ end.
+prinfo2(Tab) ->
+ Name = ets:info(Tab, name),
+ Type = ets:info(Tab, type),
+ Size = ets:info(Tab, size),
+ Mem = ets:info(Tab, memory),
+ Owner = ets:info(Tab, owner),
+ hform(Tab, Name, Type, Size, Mem, is_reg(Owner)).
+
+is_reg(Owner) ->
+ case process_info(Owner, registered_name) of
+ {registered_name, Name} -> Name;
+ _ -> Owner
+ end.
+
+%%% Arndt: this code used to truncate over-sized fields. Now it
+%%% pushes the remaining entries to the right instead, rather than
+%%% losing information.
+hform(A0, B0, C0, D0, E0, F0) ->
+ [A,B,C,D,E,F] = [to_string(T) || T <- [A0,B0,C0,D0,E0,F0]],
+ A1 = pad_right(A, 15),
+ B1 = pad_right(B, 17),
+ C1 = pad_right(C, 5),
+ D1 = pad_right(D, 6),
+ E1 = pad_right(E, 8),
+ %% no need to pad the last entry on the line
+ io:format(" ~s ~s ~s ~s ~s ~s\n", [A1,B1,C1,D1,E1,F]).
+
+pad_right(String, Len) ->
+ if
+ length(String) >= Len ->
+ String;
+ true ->
+ [Space] = " ",
+ String ++ lists:duplicate(Len - length(String), Space)
+ end.
+
+to_string(X) ->
+ lists:flatten(io_lib:format("~p", [X])).
+
+%% view a specific table
+-spec i(tab()) -> 'ok'.
+
+i(Tab) ->
+ i(Tab, 40).
+
+-spec i(tab(), pos_integer()) -> 'ok'.
+
+i(Tab, Height) ->
+ i(Tab, Height, 80).
+
+-spec i(tab(), pos_integer(), pos_integer()) -> 'ok'.
+
+i(Tab, Height, Width) ->
+ First = ets:first(Tab),
+ display_items(Height, Width, Tab, First, 1, 1).
+
+display_items(Height, Width, Tab, '$end_of_table', Turn, Opos) ->
+ P = 'EOT (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, eot, Tab, '$end_of_table', Turn, Opos);
+display_items(Height, Width, Tab, Key, Turn, Opos) when Turn < Height ->
+ do_display(Height, Width, Tab, Key, Turn, Opos);
+display_items(Height, Width, Tab, Key, Turn, Opos) when Turn >= Height ->
+ P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, normal, Tab, Key, Turn, Opos).
+
+choice(Height, Width, P, Mode, Tab, Key, Turn, Opos) ->
+ case get_line(P, "c\n") of
+ "c\n" when Mode =:= normal ->
+ do_display(Height, Width, Tab, Key, 1, Opos);
+ "c\n" when is_tuple(Mode), element(1, Mode) =:= re ->
+ {re, Re} = Mode,
+ re_search(Height, Width, Tab, Key, Re, 1, Opos);
+ "q\n" ->
+ ok;
+ "k\n" ->
+ ets:delete(Tab),
+ ok;
+ [$p|Digs] ->
+ catch case catch list_to_integer(nonl(Digs)) of
+ {'EXIT', _} ->
+ io:put_chars("Bad digits\n");
+ Number when Mode =:= normal ->
+ print_number(Tab, ets:first(Tab), Number);
+ Number when Mode =:= eot ->
+ print_number(Tab, ets:first(Tab), Number);
+ Number -> %% regexp
+ {re, Re} = Mode,
+ print_re_num(Tab, ets:first(Tab), Number, Re)
+ end,
+ choice(Height, Width, P, Mode, Tab, Key, Turn, Opos);
+ [$/|Regexp] -> %% from regexp
+ case re:compile(nonl(Regexp)) of
+ {ok,Re} ->
+ re_search(Height, Width, Tab, ets:first(Tab), Re, 1, 1);
+ {error,{ErrorString,_Pos}} ->
+ io:format("~s\n", [ErrorString]),
+ choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
+ end;
+ _ ->
+ choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
+ end.
+
+get_line(P, Default) ->
+ case io:get_line(P) of
+ "\n" ->
+ Default;
+ L ->
+ L
+ end.
+
+nonl(S) -> string:strip(S, right, $\n).
+
+print_number(Tab, Key, Num) ->
+ Os = ets:lookup(Tab, Key),
+ Len = length(Os),
+ if
+ (Num - Len) < 1 ->
+ O = lists:nth(Num, Os),
+ io:format("~p~n", [O]); %% use ppterm here instead
+ true ->
+ print_number(Tab, ets:next(Tab, Key), Num - Len)
+ end.
+
+do_display(Height, Width, Tab, Key, Turn, Opos) ->
+ Objs = ets:lookup(Tab, Key),
+ do_display_items(Height, Width, Objs, Opos),
+ Len = length(Objs),
+ display_items(Height, Width, Tab, ets:next(Tab, Key), Turn+Len, Opos+Len).
+
+do_display_items(Height, Width, [Obj|Tail], Opos) ->
+ do_display_item(Height, Width, Obj, Opos),
+ do_display_items(Height, Width, Tail, Opos+1);
+do_display_items(_Height, _Width, [], Opos) ->
+ Opos.
+
+do_display_item(_Height, Width, I, Opos) ->
+ L = to_string(I),
+ L2 = if
+ length(L) > Width - 8 ->
+ string:substr(L, 1, Width-13) ++ " ...";
+ true ->
+ L
+ end,
+ io:format("<~-4w> ~s~n", [Opos,L2]).
+
+re_search(Height, Width, Tab, '$end_of_table', Re, Turn, Opos) ->
+ P = 'EOT (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, {re, Re}, Tab, '$end_of_table', Turn, Opos);
+re_search(Height, Width, Tab, Key, Re, Turn, Opos) when Turn < Height ->
+ re_display(Height, Width, Tab, Key, ets:lookup(Tab, Key), Re, Turn, Opos);
+re_search(Height, Width, Tab, Key, Re, Turn, Opos) ->
+ P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
+ choice(Height, Width, P, {re, Re}, Tab, Key, Turn, Opos).
+
+re_display(Height, Width, Tab, Key, [], Re, Turn, Opos) ->
+ re_search(Height, Width, Tab, ets:next(Tab, Key), Re, Turn, Opos);
+re_display(Height, Width, Tab, Key, [H|T], Re, Turn, Opos) ->
+ Str = to_string(H),
+ case re:run(Str, Re, [{capture,none}]) of
+ match ->
+ do_display_item(Height, Width, H, Opos),
+ re_display(Height, Width, Tab, Key, T, Re, Turn+1, Opos+1);
+ nomatch ->
+ re_display(Height, Width, Tab, Key, T, Re, Turn, Opos)
+ end.
+
+print_re_num(_,'$end_of_table',_,_) -> ok;
+print_re_num(Tab, Key, Num, Re) ->
+ Os = re_match(ets:lookup(Tab, Key), Re),
+ Len = length(Os),
+ if
+ (Num - Len) < 1 ->
+ O = lists:nth(Num, Os),
+ io:format("~p~n", [O]); %% use ppterm here instead
+ true ->
+ print_re_num(Tab, ets:next(Tab, Key), Num - Len, Re)
+ end.
+
+re_match([], _) -> [];
+re_match([H|T], Re) ->
+ case re:run(to_string(H), Re, [{capture,none}]) of
+ match ->
+ [H|re_match(T,Re)];
+ nomatch ->
+ re_match(T, Re)
+ end.
diff --git a/lib/stdlib/src/eval_bits.erl b/lib/stdlib/src/eval_bits.erl
new file mode 100644
index 0000000000..3671aecdcb
--- /dev/null
+++ b/lib/stdlib/src/eval_bits.erl
@@ -0,0 +1,348 @@
+%% -*- erlang-indent-level: 4 -*-
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(eval_bits).
+
+-export([expr_grp/3,expr_grp/5,match_bits/6,
+ match_bits/7,bin_gen/6]).
+
+%% Types used in this module:
+%% @type bindings(). An abstract structure for bindings between
+%% variables and values (the environment)
+%%
+%% @type evalfun(). A closure which evaluates an expression given an
+%% environment
+%%
+%% @type matchfun(). A closure which performs a match given a value, a
+%% pattern and an environment
+%%
+%% @type field() represents a field in a "bin"
+
+%%% Part 1: expression evaluation (binary construction)
+
+%% @spec expr_grp(Fields::[field()], Bindings::bindings(),
+%% EvalFun::evalfun()) ->
+%% {value, binary(), bindings()}
+%%
+%% @doc Returns a tuple with {value,Bin,Bs} where Bin is the binary
+%% constructed from form the Fields under the current Bindings. Bs
+%% contains the present bindings. This function can also throw an
+%% exception if the construction fails.
+
+expr_grp(Fields, Bindings, EvalFun, [], _) ->
+ expr_grp(Fields, Bindings, EvalFun, <<>>);
+expr_grp(Fields, Bindings, EvalFun, ListOfBits, _) ->
+ Bin = convert_list(ListOfBits),
+ expr_grp(Fields, Bindings, EvalFun, Bin).
+
+convert_list(List) ->
+ << <<X:1>> || X <- List >>.
+
+expr_grp(Fields, Bindings, EvalFun) ->
+ expr_grp(Fields, Bindings, EvalFun, <<>>).
+
+expr_grp([Field | FS], Bs0, Lf, Acc) ->
+ {Bin,Bs} = eval_field(Field, Bs0, Lf),
+ expr_grp(FS, Bs, Lf, <<Acc/binary-unit:1,Bin/binary-unit:1>>);
+expr_grp([], Bs0, _Lf, Acc) ->
+ {value,Acc,Bs0}.
+
+eval_field({bin_element, _, {string, _, S}, default, default}, Bs0, _Fun) ->
+ {list_to_binary(S),Bs0};
+eval_field({bin_element, Line, {string, _, S}, Size0, Options0}, Bs, _Fun) ->
+ {_Size,[Type,_Unit,_Sign,Endian]} =
+ make_bit_type(Line, Size0, Options0),
+ Res = << <<(eval_exp_field1(C, no_size, no_unit,
+ Type, Endian, no_sign))/binary>> ||
+ C <- S >>,
+ {Res,Bs};
+eval_field({bin_element,Line,E,Size0,Options0}, Bs0, Fun) ->
+ {value,V,Bs1} = Fun(E, Bs0),
+ {Size1,[Type,{unit,Unit},Sign,Endian]} =
+ make_bit_type(Line, Size0, Options0),
+ {value,Size,Bs} = Fun(Size1, Bs1),
+ {eval_exp_field1(V, Size, Unit, Type, Endian, Sign),Bs}.
+
+eval_exp_field1(V, Size, Unit, Type, Endian, Sign) ->
+ try
+ eval_exp_field(V, Size, Unit, Type, Endian, Sign)
+ catch
+ error:system_limit ->
+ error(system_limit);
+ error:_ ->
+ error(badarg)
+ end.
+
+eval_exp_field(Val, Size, Unit, integer, little, signed) ->
+ <<Val:(Size*Unit)/little-signed>>;
+eval_exp_field(Val, Size, Unit, integer, little, unsigned) ->
+ <<Val:(Size*Unit)/little>>;
+eval_exp_field(Val, Size, Unit, integer, native, signed) ->
+ <<Val:(Size*Unit)/native-signed>>;
+eval_exp_field(Val, Size, Unit, integer, native, unsigned) ->
+ <<Val:(Size*Unit)/native>>;
+eval_exp_field(Val, Size, Unit, integer, big, signed) ->
+ <<Val:(Size*Unit)/signed>>;
+eval_exp_field(Val, Size, Unit, integer, big, unsigned) ->
+ <<Val:(Size*Unit)>>;
+eval_exp_field(Val, _Size, _Unit, utf8, _, _) ->
+ <<Val/utf8>>;
+eval_exp_field(Val, _Size, _Unit, utf16, big, _) ->
+ <<Val/big-utf16>>;
+eval_exp_field(Val, _Size, _Unit, utf16, little, _) ->
+ <<Val/little-utf16>>;
+eval_exp_field(Val, _Size, _Unit, utf32, big, _) ->
+ <<Val/big-utf32>>;
+eval_exp_field(Val, _Size, _Unit, utf32, little, _) ->
+ <<Val/little-utf32>>;
+eval_exp_field(Val, Size, Unit, float, little, _) ->
+ <<Val:(Size*Unit)/float-little>>;
+eval_exp_field(Val, Size, Unit, float, native, _) ->
+ <<Val:(Size*Unit)/float-native>>;
+eval_exp_field(Val, Size, Unit, float, big, _) ->
+ <<Val:(Size*Unit)/float>>;
+eval_exp_field(Val, all, Unit, binary, _, _) ->
+ case bit_size(Val) of
+ Size when Size rem Unit =:= 0 ->
+ <<Val:Size/binary-unit:1>>;
+ _ ->
+ error(badarg)
+ end;
+eval_exp_field(Val, Size, Unit, binary, _, _) ->
+ <<Val:(Size*Unit)/binary-unit:1>>.
+
+
+%%% Part 2: matching in binary comprehensions
+%% @spec bin_gen(BinPattern::{bin,integer(),[field()]}, Bin::binary(),
+%% GlobalEnv::bindings(), LocalEnv::bindings(),
+%% MatchFun::matchfun(), EvalFun::evalfun()) ->
+%% {match, binary(), bindings()} | {nomatch, binary()} | done
+%%
+%% @doc Used to perform matching in a comprehension. If the match
+%% succeeds a new environment and what remains of the binary is
+%% returned. If the match fails what remains of the binary is returned.
+%% If nothing remains of the binary the atom 'done' is returned.
+
+bin_gen({bin,_,Fs}, Bin, Bs0, BBs0, Mfun, Efun) ->
+ bin_gen(Fs, Bin, Bs0, BBs0, Mfun, Efun, true).
+
+bin_gen([F|Fs], Bin, Bs0, BBs0, Mfun, Efun, Flag) ->
+ case bin_gen_field(F, Bin, Bs0, BBs0, Mfun, Efun) of
+ {match,Bs,BBs,Rest} ->
+ bin_gen(Fs, Rest, Bs, BBs, Mfun, Efun, Flag);
+ {nomatch,Rest} ->
+ bin_gen(Fs, Rest, Bs0, BBs0, Mfun, Efun, false);
+ done ->
+ done
+ end;
+bin_gen([], Bin, Bs0, _BBs0, _Mfun, _Efun, true) ->
+ {match, Bin, Bs0};
+bin_gen([], Bin, _Bs0, _BBs0, _Mfun, _Efun, false) ->
+ {nomatch, Bin}.
+
+bin_gen_field({bin_element,_,{string,_,S},default,default},
+ Bin, Bs, BBs, _Mfun, _Efun) ->
+ Bits = list_to_binary(S),
+ Size = byte_size(Bits),
+ case Bin of
+ <<Bits:Size/binary,Rest/bitstring>> ->
+ {match,Bs,BBs,Rest};
+ <<_:Size/binary,Rest/bitstring>> ->
+ {nomatch,Rest};
+ _ ->
+ done
+ end;
+bin_gen_field({bin_element,Line,VE,Size0,Options0},
+ Bin, Bs0, BBs0, Mfun, Efun) ->
+ {Size1, [Type,{unit,Unit},Sign,Endian]} =
+ make_bit_type(Line, Size0, Options0),
+ V = erl_eval:partial_eval(VE),
+ match_check_size(Size1, BBs0),
+ {value, Size, _BBs} = Efun(Size1, BBs0),
+ case catch get_value(Bin, Type, Size, Unit, Sign, Endian) of
+ {Val,<<_/bitstring>>=Rest} ->
+ NewV = coerce_to_float(V, Type),
+ case catch Mfun(NewV, Val, Bs0) of
+ {match,Bs} ->
+ BBs = add_bin_binding(NewV, Bs, BBs0),
+ {match,Bs,BBs,Rest};
+ _ ->
+ {nomatch,Rest}
+ end;
+ _ ->
+ done
+ end.
+
+%%% Part 3: binary pattern matching
+%% @spec match_bits(Fields::[field()], Bin::binary()
+%% GlobalEnv::bindings(), LocalEnv::bindings(),
+%% MatchFun::matchfun(),EvalFun::evalfun()) ->
+%% {match, bindings()}
+%% @doc Used to perform matching. If the match succeeds a new
+%% environment is returned. If the match have some syntactic or
+%% semantic problem which would have been caught at compile time this
+%% function throws 'invalid', if the matching fails for other reasons
+%% the function throws 'nomatch'
+
+match_bits(Fs, Bin, Bs0, BBs, Mfun, Efun, _) ->
+ match_bits(Fs, Bin, Bs0, BBs, Mfun, Efun).
+
+match_bits(Fs, Bin, Bs0, BBs, Mfun, Efun) ->
+ case catch match_bits_1(Fs, Bin, Bs0, BBs, Mfun, Efun) of
+ {match,Bs} -> {match,Bs};
+ invalid -> throw(invalid);
+ _Error -> throw(nomatch)
+ end.
+
+match_bits_1([], <<>>, Bs, _BBs, _Mfun, _Efun) ->
+ {match,Bs};
+match_bits_1([F|Fs], Bits0, Bs0, BBs0, Mfun, Efun) ->
+ {Bs,BBs,Bits} = match_field_1(F, Bits0, Bs0, BBs0, Mfun, Efun),
+ match_bits_1(Fs, Bits, Bs, BBs, Mfun, Efun).
+
+match_field_1({bin_element,_,{string,_,S},default,default},
+ Bin, Bs, BBs, _Mfun, _Efun) ->
+ Bits = list_to_binary(S),
+ Size = byte_size(Bits),
+ <<Bits:Size/binary,Rest/binary-unit:1>> = Bin,
+ {Bs,BBs,Rest};
+match_field_1({bin_element,Line,VE,Size0,Options0},
+ Bin, Bs0, BBs0, Mfun, Efun) ->
+ {Size1, [Type,{unit,Unit},Sign,Endian]} =
+ make_bit_type(Line, Size0, Options0),
+ V = erl_eval:partial_eval(VE),
+ Size2 = erl_eval:partial_eval(Size1),
+ match_check_size(Size2, BBs0),
+ {value, Size, _BBs} = Efun(Size2, BBs0),
+ {Val,Rest} = get_value(Bin, Type, Size, Unit, Sign, Endian),
+ NewV = coerce_to_float(V, Type),
+ {match,Bs} = Mfun(NewV, Val, Bs0),
+ BBs = add_bin_binding(NewV, Bs, BBs0),
+ {Bs,BBs,Rest}.
+
+%% Almost identical to the one in sys_pre_expand.
+coerce_to_float({integer,L,I}=E, float) ->
+ try
+ {float,L,float(I)}
+ catch
+ error:badarg -> E;
+ error:badarith -> E
+ end;
+coerce_to_float(E, _Type) ->
+ E.
+
+add_bin_binding({var,_,'_'}, _Bs, BBs) ->
+ BBs;
+add_bin_binding({var,_,Name}, Bs, BBs) ->
+ {value,Value} = erl_eval:binding(Name, Bs),
+ erl_eval:add_binding(Name, Value, BBs);
+add_bin_binding(_, _Bs, BBs) ->
+ BBs.
+
+get_value(Bin, integer, Size, Unit, Sign, Endian) ->
+ get_integer(Bin, Size*Unit, Sign, Endian);
+get_value(Bin, float, Size, Unit, _Sign, Endian) ->
+ get_float(Bin, Size*Unit, Endian);
+get_value(Bin, utf8, undefined, _Unit, _Sign, _Endian) ->
+ <<I/utf8,Rest/bits>> = Bin,
+ {I,Rest};
+get_value(Bin, utf16, undefined, _Unit, _Sign, big) ->
+ <<I/big-utf16,Rest/bits>> = Bin,
+ {I,Rest};
+get_value(Bin, utf16, undefined, _Unit, _Sign, little) ->
+ <<I/little-utf16,Rest/bits>> = Bin,
+ {I,Rest};
+get_value(Bin, utf32, undefined, _Unit, _Sign, big) ->
+ <<Val/big-utf32,Rest/bits>> = Bin,
+ {Val,Rest};
+get_value(Bin, utf32, undefined, _Unit, _Sign, little) ->
+ <<Val/little-utf32,Rest/bits>> = Bin,
+ {Val,Rest};
+get_value(Bin, binary, all, Unit, _Sign, _Endian) ->
+ 0 = (bit_size(Bin) rem Unit),
+ {Bin,<<>>};
+get_value(Bin, binary, Size, Unit, _Sign, _Endian) ->
+ TotSize = Size*Unit,
+ <<Val:TotSize/bitstring,Rest/bits>> = Bin,
+ {Val,Rest}.
+
+get_integer(Bin, Size, signed, little) ->
+ <<Val:Size/little-signed,Rest/binary-unit:1>> = Bin,
+ {Val,Rest};
+get_integer(Bin, Size, unsigned, little) ->
+ <<Val:Size/little,Rest/binary-unit:1>> = Bin,
+ {Val,Rest};
+get_integer(Bin, Size, signed, native) ->
+ <<Val:Size/native-signed,Rest/binary-unit:1>> = Bin,
+ {Val,Rest};
+get_integer(Bin, Size, unsigned, native) ->
+ <<Val:Size/native,Rest/binary-unit:1>> = Bin,
+ {Val,Rest};
+get_integer(Bin, Size, signed, big) ->
+ <<Val:Size/signed,Rest/binary-unit:1>> = Bin,
+ {Val,Rest};
+get_integer(Bin, Size, unsigned, big) ->
+ <<Val:Size,Rest/binary-unit:1>> = Bin,
+ {Val,Rest}.
+
+get_float(Bin, Size, little) ->
+ <<Val:Size/float-little,Rest/binary-unit:1>> = Bin,
+ {Val,Rest};
+get_float(Bin, Size, native) ->
+ <<Val:Size/float-native,Rest/binary-unit:1>> = Bin,
+ {Val,Rest};
+get_float(Bin, Size, big) ->
+ <<Val:Size/float,Rest/binary-unit:1>> = Bin,
+ {Val,Rest}.
+
+%% Identical to the one in sys_pre_expand.
+make_bit_type(Line, default, Type0) ->
+ case erl_bits:set_bit_type(default, Type0) of
+ {ok,all,Bt} -> {{atom,Line,all},erl_bits:as_list(Bt)};
+ {ok,undefined,Bt} -> {{atom,Line,undefined},erl_bits:as_list(Bt)};
+ {ok,Size,Bt} -> {{integer,Line,Size},erl_bits:as_list(Bt)};
+ {error,Reason} -> error(Reason)
+ end;
+make_bit_type(_Line, Size, Type0) -> %Size evaluates to an integer or 'all'
+ case erl_bits:set_bit_type(Size, Type0) of
+ {ok,Size,Bt} -> {Size,erl_bits:as_list(Bt)};
+ {error,Reason} -> error(Reason)
+ end.
+
+match_check_size({var,_,V}, Bs) ->
+ case erl_eval:binding(V, Bs) of
+ {value,_} -> ok;
+ unbound -> throw(invalid) % or, rather, error({unbound,V})
+ end;
+match_check_size({atom,_,all}, _Bs) ->
+ ok;
+match_check_size({atom,_,undefined}, _Bs) ->
+ ok;
+match_check_size({integer,_,_}, _Bs) ->
+ ok;
+match_check_size({value,_,_}, _Bs) ->
+ ok; %From the debugger.
+match_check_size(_, _Bs) ->
+ throw(invalid).
+
+%% error(Reason) -> exception thrown
+%% Throw a nice-looking exception, similar to exceptions from erl_eval.
+error(Reason) ->
+ erlang:raise(error, Reason, [{erl_eval,expr,3}]).
+
diff --git a/lib/stdlib/src/file_sorter.erl b/lib/stdlib/src/file_sorter.erl
new file mode 100644
index 0000000000..de9e628e22
--- /dev/null
+++ b/lib/stdlib/src/file_sorter.erl
@@ -0,0 +1,1500 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(file_sorter).
+
+-export([sort/1, sort/2, sort/3,
+ keysort/2, keysort/3, keysort/4,
+ merge/2, merge/3,
+ keymerge/3, keymerge/4,
+ check/1, check/2,
+ keycheck/2, keycheck/3]).
+
+-include_lib("kernel/include/file.hrl").
+
+-define(CHUNKSIZE, 16384).
+-define(RUNSIZE, 524288).
+-define(NOMERGE, 16).
+-define(MERGESIZE, ?CHUNKSIZE).
+
+-define(MAXSIZE, (1 bsl 31)).
+
+-record(w, {keypos, runs = [[]], seq = 1, in, out, fun_out, prefix, temp = [],
+ format, runsize, no_files, order, chunksize, wfd, ref, z, unique,
+ hdlen, inout_value}).
+
+-record(opts, {format = binary_term_fun(), size = ?RUNSIZE,
+ no_files = ?NOMERGE, tmpdir = default, order = ascending,
+ compressed = false, unique = false, header = 4}).
+
+-compile({inline, [{badarg, 2}, {make_key,2}, {make_stable_key,3}, {cfun,3}]}).
+
+%%%
+%%% Exported functions
+%%%
+
+sort(FileName) ->
+ sort([FileName], FileName).
+
+sort(Input, Output) ->
+ sort(Input, Output, []).
+
+sort(Input0, Output0, Options) ->
+ case {is_input(Input0), maybe_output(Output0), options(Options)} of
+ {{true,Input}, {true,Output}, #opts{}=Opts} ->
+ do_sort(0, Input, Output, Opts, sort);
+ T ->
+ badarg(culprit(tuple_to_list(T)), [Input0, Output0, Options])
+ end.
+
+keysort(KeyPos, FileName) ->
+ keysort(KeyPos, [FileName], FileName).
+
+keysort(KeyPos, Input, Output) ->
+ keysort(KeyPos, Input, Output, []).
+
+keysort(KeyPos, Input0, Output0, Options) ->
+ R = case {is_keypos(KeyPos), is_input(Input0),
+ maybe_output(Output0), options(Options)} of
+ {_, _, _, #opts{format = binary}} ->
+ {Input0,Output0,[{badarg,format}]};
+ {_, _, _, #opts{order = Order}} when is_function(Order) ->
+ {Input0,Output0,[{badarg,order}]};
+ {true, {true,In}, {true,Out}, #opts{}=Opts} ->
+ {In,Out,Opts};
+ T ->
+ {Input0,Output0,tuple_to_list(T)}
+ end,
+ case R of
+ {Input,Output,#opts{}=O} ->
+ do_sort(KeyPos, Input, Output, O, sort);
+ {_,_,O} ->
+ badarg(culprit(O), [KeyPos, Input0, Output0, Options])
+ end.
+
+merge(Files, Output) ->
+ merge(Files, Output, []).
+
+merge(Files0, Output0, Options) ->
+ case {is_files(Files0), maybe_output(Output0), options(Options)} of
+ %% size not used
+ {{true,Files}, {true,Output}, #opts{}=Opts} ->
+ do_sort(0, Files, Output, Opts, merge);
+ T ->
+ badarg(culprit(tuple_to_list(T)), [Files0, Output0, Options])
+ end.
+
+keymerge(KeyPos, Files, Output) ->
+ keymerge(KeyPos, Files, Output, []).
+
+keymerge(KeyPos, Files0, Output0, Options) ->
+ R = case {is_keypos(KeyPos), is_files(Files0),
+ maybe_output(Output0), options(Options)} of
+ {_, _, _, #opts{format = binary}} ->
+ {Files0,Output0,[{badarg,format}]};
+ {_, _, _, #opts{order = Order}} when is_function(Order) ->
+ {Files0,Output0,[{badarg,order}]};
+ {true, {true,Fs}, {true,Out}, #opts{}=Opts} ->
+ {Fs,Out,Opts};
+ T ->
+ {Files0,Output0,tuple_to_list(T)}
+ end,
+ case R of
+ {Files,Output,#opts{}=O} ->
+ do_sort(KeyPos, Files, Output, O, merge);
+ {_,_,O} ->
+ badarg(culprit(O), [KeyPos, Files0, Output0, Options])
+ end.
+
+check(FileName) ->
+ check([FileName], []).
+
+check(Files0, Options) ->
+ case {is_files(Files0), options(Options)} of
+ {{true,Files}, #opts{}=Opts} ->
+ do_sort(0, Files, undefined, Opts, check);
+ T ->
+ badarg(culprit(tuple_to_list(T)), [Files0, Options])
+ end.
+
+keycheck(KeyPos, FileName) ->
+ keycheck(KeyPos, [FileName], []).
+
+keycheck(KeyPos, Files0, Options) ->
+ R = case {is_keypos(KeyPos), is_files(Files0), options(Options)} of
+ {_, _, #opts{format = binary}} ->
+ {Files0,[{badarg,format}]};
+ {_, _, #opts{order = Order}} when is_function(Order) ->
+ {Files0,[{badarg,order}]};
+ {true, {true,Fs}, #opts{}=Opts} ->
+ {Fs,Opts};
+ T ->
+ {Files0,tuple_to_list(T)}
+ end,
+ case R of
+ {Files,#opts{}=O} ->
+ do_sort(KeyPos, Files, undefined, O, check);
+ {_,O} ->
+ badarg(culprit(O), [KeyPos, Files0, Options])
+ end.
+
+%%%
+%%% Local functions
+%%%
+
+%%-define(debug, true).
+
+-ifdef(debug).
+-define(DEBUG(S, A), io:format(S, A)).
+-else.
+-define(DEBUG(S, A), ok).
+-endif.
+
+culprit([{error, _} = E | _]) ->
+ E;
+culprit([{badarg, _} = B | _]) ->
+ B;
+culprit([_ | B]) ->
+ culprit(B).
+
+%% Inlined.
+badarg({error, _} = E, _Args) ->
+ E;
+badarg({badarg, _} = B, Args) ->
+ erlang:error(B, Args).
+
+options(Options) when is_list(Options) ->
+ options(Options, #opts{});
+options(Option) ->
+ options([Option]).
+
+options([{format, Format} | L], Opts) when Format =:= binary;
+ Format =:= term;
+ is_function(Format),
+ is_function(Format, 1) ->
+ options(L, Opts#opts{format = Format});
+options([{format, binary_term} | L], Opts) ->
+ options(L, Opts#opts{format = binary_term_fun()});
+options([{size, Size} | L], Opts) when is_integer(Size), Size >= 0 ->
+ options(L, Opts#opts{size = max(Size, 1)});
+options([{no_files, NoFiles} | L], Opts) when is_integer(NoFiles),
+ NoFiles > 1 ->
+ options(L, Opts#opts{no_files = NoFiles});
+options([{tmpdir, ""} | L], Opts) ->
+ options(L, Opts#opts{tmpdir = default});
+options([{tmpdir, Dir} | L], Opts) ->
+ case catch filename:absname(Dir) of
+ {'EXIT', _} ->
+ {badarg, Dir};
+ FileName ->
+ options(L, Opts#opts{tmpdir = {dir, FileName}})
+ end;
+options([{order, Fun} | L], Opts) when is_function(Fun), is_function(Fun, 2) ->
+ options(L, Opts#opts{order = Fun});
+options([{order, Order} | L], Opts) when Order =:= ascending;
+ Order =:= descending ->
+ options(L, Opts#opts{order = Order});
+options([{compressed, Bool} | L], Opts) when is_boolean(Bool) ->
+ options(L, Opts#opts{compressed = Bool});
+options([{unique, Bool} | L], Opts) when is_boolean(Bool) ->
+ options(L, Opts#opts{unique = Bool});
+options([{header, Len} | L], Opts)
+ when is_integer(Len), Len > 0, Len < ?MAXSIZE ->
+ options(L, Opts#opts{header = Len});
+options([], Opts) ->
+ if
+ Opts#opts.format =:= term, Opts#opts.header =/= 4 ->
+ {badarg, header};
+ true ->
+ Opts
+ end;
+options([Bad | _], _Opts) ->
+ {badarg, Bad};
+options(Bad, _Opts) ->
+ {badarg, Bad}.
+
+-define(OBJ(X, Y), {X, Y}).
+-define(SK(T, I), [T | I]). % stable key
+
+do_sort(KeyPos0, Input0, Output0, Opts, Do) ->
+ #opts{format = Format0, size = Size, no_files = NoFiles,
+ tmpdir = TmpDir, order = Order, compressed = Compressed,
+ unique = Unique, header = HdLen} = Opts,
+ Prefix = tmp_prefix(Output0, TmpDir),
+ ChunkSize = ?CHUNKSIZE,
+ Ref = make_ref(),
+ KeyPos = case KeyPos0 of [Kp] -> Kp; _ -> KeyPos0 end,
+ {Format, Input} = wrap_input(Format0, Do, Input0),
+ Z = if Compressed -> [compressed]; true -> [] end,
+ {Output, FunOut} = wrap_output_terms(Format0, Output0, Z),
+ W = #w{keypos = KeyPos, out = Output, fun_out = FunOut,
+ prefix = Prefix, format = Format, runsize = Size,
+ no_files = NoFiles, order = Order, chunksize = ChunkSize,
+ ref = Ref, z = Z, unique = Unique, hdlen = HdLen,
+ inout_value = no_value},
+ try
+ doit(Do, Input, W)
+ catch {Ref,Error} ->
+ Error
+ end.
+
+doit(sort, Input, W) ->
+ files(1, [], 0, W, Input);
+doit(merge, Input, W) ->
+ last_merge(Input, W);
+doit(check, Input, W) ->
+ check_files(Input, W, []).
+
+wrap_input(term, check, Files) ->
+ Fun = fun(File) ->
+ Fn = merge_terms_fun(file_rterms(no_file, [File])),
+ {fn, Fn, File}
+ end,
+ {binary_term_fun(), [Fun(F) || F <- Files]};
+wrap_input(Format, check, Files) ->
+ {Format, Files};
+wrap_input(term, merge, Files) ->
+ Fun = fun(File) -> merge_terms_fun(file_rterms(no_file, [File])) end,
+ Input = lists:reverse([Fun(F) || F <- Files]),
+ {binary_term_fun(), Input};
+wrap_input(Format, merge, Files) ->
+ Input = lists:reverse([merge_bins_fun(F) || F <- Files]),
+ {Format, Input};
+wrap_input(term, sort, InFun) when is_function(InFun, 1) ->
+ {binary_term_fun(), fun_rterms(InFun)};
+wrap_input(term, sort, Files) ->
+ {binary_term_fun(), file_rterms(no_file, Files)};
+wrap_input(Format, sort, Input) ->
+ {Format, Input}.
+
+merge_terms_fun(RFun) ->
+ fun(close) ->
+ RFun(close);
+ ({I, [], _LSz, W}) ->
+ case RFun(read) of
+ end_of_input ->
+ eof;
+ {Objs, NRFun} when is_function(NRFun), is_function(NRFun, 1) ->
+ {_, [], Ts, _} = fun_objs(Objs, [], 0, ?MAXSIZE, I, W),
+ {{I, Ts, ?CHUNKSIZE}, merge_terms_fun(NRFun)};
+ Error ->
+ error(Error, W)
+ end
+ end.
+
+merge_bins_fun(FileName) ->
+ fun(close) ->
+ ok;
+ ({_I, _L, _LSz, W} = A) ->
+ Fun = read_fun(FileName, user, W),
+ Fun(A)
+ end.
+
+wrap_output_terms(term, OutFun, _Z) when is_function(OutFun),
+ is_function(OutFun, 1) ->
+ {fun_wterms(OutFun), true};
+wrap_output_terms(term, File, Z) when File =/= undefined ->
+ {file_wterms(name, File, Z++[write]), false};
+wrap_output_terms(_Format, Output, _Z) ->
+ {Output, is_function(Output) and is_function(Output, 1)}.
+
+binary_term_fun() ->
+ fun binary_to_term/1.
+
+check_files([], _W, L) ->
+ {ok, lists:reverse(L)};
+check_files([FN | FNs], W, L) ->
+ {IFun, FileName} =
+ case FN of
+ {fn, Fun, File} ->
+ {Fun, File};
+ File ->
+ {read_fun(File, user, W), File}
+ end,
+ NW = W#w{in = IFun},
+ check_run(IFun, FileName, FNs, NW, L, 2, nolast).
+
+check_run(IFun, F, FNs, W, L, I, Last) ->
+ case IFun({{merge,I}, [], 0, W}) of
+ {{_I, Objs, _LSz}, IFun1} ->
+ NW = W#w{in = IFun1},
+ check_objs0(IFun1, F, FNs, NW, L, I, Last, lists:reverse(Objs));
+ eof ->
+ NW = W#w{in = undefined},
+ check_files(FNs, NW, L)
+ end.
+
+check_objs0(IFun, F, FNs, W, L, I, nolast, [?OBJ(T,_BT) | Os]) ->
+ check_objs1(IFun, F, FNs, W, L, I, T, Os);
+check_objs0(IFun, F, FNs, W, L, I, Last, []) ->
+ check_run(IFun, F, FNs, W, L, I, Last);
+check_objs0(IFun, F, FNs, W, L, I, {last, Last}, Os) ->
+ check_objs1(IFun, F, FNs, W, L, I, Last, Os).
+
+check_objs1(IFun, F, FNs, W, L, I, LastT, Os) ->
+ case W of
+ #w{order = ascending, unique = true} ->
+ ucheck_objs(IFun, F, FNs, W, L, I, LastT, Os);
+ #w{order = ascending, unique = false} ->
+ check_objs(IFun, F, FNs, W, L, I, LastT, Os);
+ #w{order = descending, unique = true} ->
+ rucheck_objs(IFun, F, FNs, W, L, I, LastT, Os);
+ #w{order = descending, unique = false} ->
+ rcheck_objs(IFun, F, FNs, W, L, I, LastT, Os);
+ #w{order = CF, unique = true} ->
+ uccheck_objs(IFun, F, FNs, W, L, I, LastT, Os, CF);
+ #w{order = CF, unique = false} ->
+ ccheck_objs(IFun, F, FNs, W, L, I, LastT, Os, CF)
+ end.
+
+check_objs(IFun, F, FNs, W, L, I, Last, [?OBJ(T,_BT) | Os]) when T >= Last ->
+ check_objs(IFun, F, FNs, W, L, I+1, T, Os);
+check_objs(IFun, F, FNs, W, L, I, _Last, [?OBJ(_T,BT) | _]) ->
+ culprit_found(IFun, F, FNs, W, L, I, BT);
+check_objs(IFun, F, FNs, W, L, I, Last, []) ->
+ check_run(IFun, F, FNs, W, L, I, {last, Last}).
+
+rcheck_objs(IFun, F, FNs, W, L, I, Last, [?OBJ(T,_BT) | Os]) when T =< Last ->
+ rcheck_objs(IFun, F, FNs, W, L, I+1, T, Os);
+rcheck_objs(IFun, F, FNs, W, L, I, _Last, [?OBJ(_T,BT) | _]) ->
+ culprit_found(IFun, F, FNs, W, L, I, BT);
+rcheck_objs(IFun, F, FNs, W, L, I, Last, []) ->
+ check_run(IFun, F, FNs, W, L, I, {last, Last}).
+
+ucheck_objs(IFun, F, FNs, W, L, I, LT, [?OBJ(T,_BT) | Os]) when T > LT ->
+ ucheck_objs(IFun, F, FNs, W, L, I+1, T, Os);
+ucheck_objs(IFun, F, FNs, W, L, I, _LT, [?OBJ(_T,BT) | _]) ->
+ culprit_found(IFun, F, FNs, W, L, I, BT);
+ucheck_objs(IFun, F, FNs, W, L, I, LT, []) ->
+ check_run(IFun, F, FNs, W, L, I, {last, LT}).
+
+rucheck_objs(IFun, F, FNs, W, L, I, LT, [?OBJ(T,_BT) | Os]) when T < LT ->
+ rucheck_objs(IFun, F, FNs, W, L, I+1, T, Os);
+rucheck_objs(IFun, F, FNs, W, L, I, _LT, [?OBJ(_T,BT) | _]) ->
+ culprit_found(IFun, F, FNs, W, L, I, BT);
+rucheck_objs(IFun, F, FNs, W, L, I, LT, []) ->
+ check_run(IFun, F, FNs, W, L, I, {last, LT}).
+
+ccheck_objs(IFun, F, FNs, W, L, I, LT, [?OBJ(T,BT) | Os], CF) ->
+ case CF(LT, T) of
+ true -> % LT =< T
+ ccheck_objs(IFun, F, FNs, W, L, I+1, T, Os, CF);
+ false -> % LT > T
+ culprit_found(IFun, F, FNs, W, L, I, BT)
+ end;
+ccheck_objs(IFun, F, FNs, W, L, I, LT, [], _CF) ->
+ check_run(IFun, F, FNs, W, L, I, {last, LT}).
+
+uccheck_objs(IFun, F, FNs, W, L, I, LT, [?OBJ(T,BT) | Os], CF) ->
+ case CF(LT, T) of
+ true -> % LT =< T
+ case CF(T, LT) of
+ true -> % T equal to LT
+ culprit_found(IFun, F, FNs, W, L, I, BT);
+ false -> % LT < T
+ uccheck_objs(IFun, F, FNs, W, L, I+1, T, Os, CF)
+ end;
+ false -> % LT > T
+ culprit_found(IFun, F, FNs, W, L, I, BT)
+ end;
+uccheck_objs(IFun, F, FNs, W, L, I, LT, [], _CF) ->
+ check_run(IFun, F, FNs, W, L, I, {last, LT}).
+
+culprit_found(IFun, F, FNs, W, L, I, [_Size | BT]) ->
+ IFun(close),
+ check_files(FNs, W, [{F,I,binary_to_term(BT)} | L]).
+
+files(_I, L, _LSz, #w{seq = 1}=W, []) ->
+ %% No temporary files created, everything in L.
+ case W#w.out of
+ Fun when is_function(Fun) ->
+ SL = internal_sort(L, W),
+ W1 = outfun(binterm_objects(SL, []), W),
+ NW = close_input(W1),
+ outfun(close, NW);
+ Out ->
+ write_run(L, W, Out),
+ ok
+ end;
+files(_I, L, _LSz, W, []) ->
+ W1 = write_run(L, W),
+ last_merge(lists:append(W1#w.runs), W1);
+files(I, L, LSz, W, Fun) when is_function(Fun) ->
+ NW = W#w{in = Fun},
+ fun_run(I, L, LSz, NW, []);
+files(I, L, LSz, W, [FileName | FileNames]) ->
+ InFun = read_fun(FileName, user, W),
+ NW = W#w{in = InFun},
+ file_run(InFun, FileNames, I, L, LSz, NW).
+
+file_run(InFun, FileNames, I, L, LSz, W) when LSz < W#w.runsize ->
+ case InFun({I, L, LSz, W}) of
+ {{I1, L1, LSz1}, InFun1} ->
+ NW = W#w{in = InFun1},
+ file_run(InFun1, FileNames, I1, L1, LSz1, NW);
+ eof ->
+ NW = W#w{in = undefined},
+ files(I, L, LSz, NW, FileNames)
+ end;
+file_run(InFun, FileNames, I, L, _LSz, W) ->
+ NW = write_run(L, W),
+ file_run(InFun, FileNames, I, [], 0, NW).
+
+fun_run(I, L, LSz, W, []) ->
+ case infun(W) of
+ {end_of_input, NW} ->
+ files(I, L, LSz, NW, []);
+ {cont, NW, Objs} ->
+ fun_run(I, L, LSz, NW, Objs)
+ end;
+fun_run(I, L, LSz, W, Objs) when LSz < W#w.runsize ->
+ {NI, NObjs, NL, NLSz} = fun_objs(Objs, L, LSz, W#w.runsize, I, W),
+ fun_run(NI, NL, NLSz, W, NObjs);
+fun_run(I, L, _LSz, W, Objs) ->
+ NW = write_run(L, W),
+ fun_run(I, [], 0, NW, Objs).
+
+write_run([], W) ->
+ W;
+write_run(L, W) ->
+ {W1, Temp} = next_temp(W),
+ NW = write_run(L, W1, Temp),
+ [R | Rs] = NW#w.runs,
+ merge_runs([[Temp | R] | Rs], [], NW).
+
+write_run(L, W, FileName) ->
+ SL = internal_sort(L, W),
+ BTs = binterms(SL, []),
+ {Fd, W1} = open_file(FileName, W),
+ write(Fd, FileName, BTs, W1),
+ close_file(Fd, W1).
+
+%% Returns a list in reversed order.
+internal_sort([]=L, _W) ->
+ L;
+internal_sort(L, #w{order = CFun, unique = Unique}) when is_function(CFun) ->
+ Fun = fun(?OBJ(T1, _), ?OBJ(T2, _)) -> CFun(T1, T2) end,
+ RL = lists:reverse(L),
+ lists:reverse(if
+ Unique ->
+ lists:usort(Fun, RL);
+ true ->
+ lists:sort(Fun, RL)
+ end);
+internal_sort(L, #w{unique = true, keypos = 0}=W) ->
+ rev(lists:usort(L), W);
+internal_sort(L, #w{unique = false, keypos = 0}=W) ->
+ rev(lists:sort(L), W);
+internal_sort(L, #w{unique = true}=W) ->
+ rev(lists:ukeysort(1, lists:reverse(L)), W);
+internal_sort(L, #w{unique = false}=W) ->
+ rev(lists:keysort(1, lists:reverse(L)), W).
+
+rev(L, #w{order = ascending}) ->
+ lists:reverse(L);
+rev(L, #w{order = descending}) ->
+ L.
+
+last_merge(R, W) when length(R) =< W#w.no_files ->
+ case W#w.out of
+ Fun when is_function(Fun) ->
+ {Fs, W1} = init_merge(lists:reverse(R), 1, [], W),
+ ?DEBUG("merging ~p~n", [lists:reverse(R)]),
+ W2 = merge_files(Fs, [], 0, nolast, W1),
+ NW = close_input(W2),
+ outfun(close, NW);
+ Out ->
+ merge_files(R, W, Out),
+ ok
+ end;
+last_merge(R, W) ->
+ L = lists:sublist(R, W#w.no_files),
+ {M, NW} = merge_files(L, W),
+ last_merge([M | lists:nthtail(W#w.no_files, R)], NW).
+
+merge_runs([R | Rs], NRs0, W) when length(R) < W#w.no_files ->
+ NRs = lists:reverse(NRs0) ++ [R | Rs],
+ W#w{runs = NRs};
+merge_runs([R], NRs0, W) ->
+ {M, NW} = merge_files(R, W),
+ NRs = [[] | lists:reverse([[M] | NRs0])],
+ NW#w{runs = NRs};
+merge_runs([R, R1 | Rs], NRs0, W) ->
+ {M, NW} = merge_files(R, W),
+ merge_runs([[M | R1] | Rs], [[] | NRs0], NW).
+
+merge_files(R, W) ->
+ {W1, Temp} = next_temp(W),
+ ?DEBUG("merging ~p~nto ~p~n", [lists:reverse(R), Temp]),
+ {Temp, merge_files(R, W1, Temp)}.
+
+merge_files(R, W, FileName) ->
+ {Fs, W1} = init_merge(lists:reverse(R), 1, [], W),
+ {Fd, W2} = open_file(FileName, W1),
+ W3 = W2#w{wfd = {Fd, FileName}},
+ W4 = merge_files(Fs, [], 0, nolast, W3),
+ NW = W4#w{wfd = undefined},
+ close_file(Fd, NW).
+
+%% A file number, I, is used for making the merge phase stable.
+init_merge([FN | FNs], I, Fs, W) ->
+ IFun = case FN of
+ _ when is_function(FN) ->
+ %% When and only when merge/2,3 or keymerge/3,4 was called.
+ FN;
+ _ ->
+ read_fun(FN, fsort, W)
+ end,
+ W1 = W#w{temp = [IFun | lists:delete(FN, W#w.temp)]},
+ case read_more(IFun, I, 0, W1) of
+ {Ts, _LSz, NIFun, NW} ->
+ InEtc = {I, NIFun},
+ init_merge(FNs, I+1, [[Ts | InEtc] | Fs], NW);
+ {eof, NW} -> % can only happen when merging files
+ init_merge(FNs, I+1, Fs, NW)
+ end;
+init_merge([], _I, Fs0, #w{order = ascending}=W) ->
+ {lists:sort(Fs0), W};
+init_merge([], _I, Fs0, #w{order = descending}=W) ->
+ {lists:reverse(lists:sort(Fs0)), W};
+init_merge([], _I, Fs0, #w{order = Order}=W) when is_function(Order) ->
+ {lists:sort(cfun_files(W#w.order), lists:reverse(Fs0)), W}.
+
+cfun_files(CFun) ->
+ fun(F1, F2) ->
+ [[?OBJ(T1,_) | _] | _] = F1,
+ [[?OBJ(T2,_) | _] | _] = F2,
+ CFun(T1, T2)
+ end.
+
+%% The argument Last is used when unique = true. It is the last kept
+%% element.
+%% LSz is not the sum of the sizes of objects in L. Instead it is
+%% the number of bytes read. After init_merge it is set to 0, which
+%% means that the first chunk written may be quite large (it may take
+%% a while before buffers are exhausted).
+merge_files([F1, F2 | Fs], L0, LSz, Last0, W) when LSz < ?MERGESIZE ->
+ [Ts0 | InEtc] = F1,
+ Kind = merge_kind(W),
+ {Last, L, Ts} = case {Last0, Kind} of
+ {{last, Lst}, Kind} ->
+ {Lst, L0, Ts0};
+ {nolast, {ukmerge, _Kp}} ->
+ [?OBJ(?SK(T, _I), BT) | Ts1] = Ts0,
+ {T, [BT], Ts1};
+ {nolast, {rukmerge, _Kp}} ->
+ [?OBJ(?SK(T, _I), BT) | Ts1] = Ts0,
+ {{T, BT}, [], Ts1};
+ {nolast, _} ->
+ [?OBJ(T, BT) | Ts1] = Ts0,
+ {T, [BT], Ts1}
+ end,
+ [[?OBJ(T2, BT2) | Ts2T] = Ts2 | InEtc2] = F2,
+ {NInEtc, NFs, NL, NLast} =
+ case Kind of
+ umerge ->
+ umerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Last);
+ {ukmerge, Kp} ->
+ ukmerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Kp, Last);
+ merge ->
+ merge_files(L, F2, Fs, InEtc2, BT2, Ts2T, Ts, InEtc, T2);
+ rumerge ->
+ rumerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Last);
+ {rukmerge, Kp} ->
+ {Lt, LtBT} = Last,
+ rukmerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Kp,
+ Lt, LtBT);
+ rmerge ->
+ rmerge_files(L, F2, Fs, InEtc2, BT2, Ts2T, Ts, InEtc, T2);
+ {ucmerge, CF} ->
+ {I2, _} = InEtc2,
+ {I, _} = InEtc,
+ ucmerge_files(L, F2, Fs, InEtc2, Ts2, I2, Ts, I, InEtc, T2, CF,
+ Last);
+ {cmerge, CF} ->
+ {I2, _} = InEtc2,
+ {I, _} = InEtc,
+ cmerge_files(L, F2, Fs, InEtc2, BT2, Ts2T, I2, Ts, I, InEtc, T2,
+ CF)
+ end,
+ read_chunk(NInEtc, NFs, NL, LSz, NLast, W);
+merge_files([F1], L, LSz, Last, W) when LSz < ?MERGESIZE ->
+ [Ts | InEtc] = F1,
+ NL = last_file(Ts, L, Last, merge_kind(W), W),
+ read_chunk(InEtc, [], NL, LSz, nolast, W);
+merge_files([], [], 0, nolast, W) ->
+ %% When merging files, ensure that the output fun (if there is
+ %% one) is called at least once before closing.
+ merge_write(W, []);
+merge_files([], L, _LSz, Last, W) ->
+ Last = nolast,
+ merge_write(W, L);
+merge_files(Fs, L, _LSz, Last, W) ->
+ NW = merge_write(W, L),
+ merge_files(Fs, [], 0, Last, NW).
+
+merge_kind(#w{order = ascending, unique = true, keypos = 0}) ->
+ umerge;
+merge_kind(#w{order = ascending, unique = true, keypos = Kp}) ->
+ {ukmerge, Kp};
+merge_kind(#w{order = ascending, unique = false}) ->
+ merge;
+merge_kind(#w{order = descending, unique = true, keypos = 0}) ->
+ rumerge;
+merge_kind(#w{order = descending, unique = true, keypos = Kp}) ->
+ {rukmerge, Kp};
+merge_kind(#w{order = descending, unique = false}) ->
+ rmerge;
+merge_kind(#w{order = CF, unique = true}) ->
+ {ucmerge, CF};
+merge_kind(#w{order = CF, unique = false}) ->
+ {cmerge, CF}.
+
+merge_write(W, L) ->
+ case {W#w.wfd, W#w.out} of
+ {undefined, Fun} when is_function(Fun) ->
+ outfun(objects(L, []), W);
+ {{Fd, FileName}, _} ->
+ write(Fd, FileName, lists:reverse(L), W),
+ W
+ end.
+
+umerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(T, _BT) | Ts], InEtc, T2, Last)
+ when T == Last ->
+ umerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Last);
+umerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(T, BT) | Ts], InEtc, T2, _Last)
+ when T =< T2 ->
+ umerge_files([BT | L], F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, T);
+umerge_files(L, F2, Fs, _InEtc2, _Ts2, [], InEtc, _T2, Last) ->
+ {InEtc, [F2 | Fs], L, {last, Last}};
+umerge_files(L, _F2, Fs, InEtc2, Ts2, Ts, InEtc, _T2, Last) ->
+ [F3 | NFs] = insert([Ts | InEtc], Fs),
+ [[?OBJ(T3,_BT3) | _] = Ts3 | InEtc3] = F3,
+ umerge_files(L, F3, NFs, InEtc3, Ts3, Ts2, InEtc2, T3, Last).
+
+rumerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(T, _BT) | Ts], InEtc, T2, Last)
+ when T == Last ->
+ rumerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Last);
+rumerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(T, BT) | Ts], InEtc, T2, _Last)
+ when T >= T2 ->
+ rumerge_files([BT | L], F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, T);
+rumerge_files(L, F2, Fs, _InEtc2, _Ts2, [], InEtc, _T2, Last) ->
+ {InEtc, [F2 | Fs], L, {last, Last}};
+rumerge_files(L, _F2, Fs, InEtc2, Ts2, Ts, InEtc, _T2, Last) ->
+ [F3 | NFs] = rinsert([Ts | InEtc], Fs),
+ [[?OBJ(T3,_BT3) | _] = Ts3 | InEtc3] = F3,
+ rumerge_files(L, F3, NFs, InEtc3, Ts3, Ts2, InEtc2, T3, Last).
+
+merge_files(L, F2, Fs, InEtc2, BT2, Ts2, [?OBJ(T, BT) | Ts], InEtc, T2)
+ when T =< T2 ->
+ merge_files([BT | L], F2, Fs, InEtc2, BT2, Ts2, Ts, InEtc, T2);
+merge_files(L, F2, Fs, _InEtc2, _BT2, _Ts2, [], InEtc, _T2) ->
+ {InEtc, [F2 | Fs], L, {last, foo}};
+merge_files(L, _F2, Fs, InEtc2, BT2, Ts2, Ts, InEtc, _T2) ->
+ L1 = [BT2 | L],
+ [F3 | NFs] = insert([Ts | InEtc], Fs),
+ [[?OBJ(T3,BT3) | Ts3] | InEtc3] = F3,
+ merge_files(L1, F3, NFs, InEtc3, BT3, Ts3, Ts2, InEtc2, T3).
+
+rmerge_files(L, F2, Fs, InEtc2, BT2, Ts2, [?OBJ(T, BT) | Ts], InEtc, T2)
+ when T >= T2 ->
+ rmerge_files([BT | L], F2, Fs, InEtc2, BT2, Ts2, Ts, InEtc, T2);
+rmerge_files(L, F2, Fs, _InEtc2, _BT2, _Ts2, [], InEtc, _T2) ->
+ {InEtc, [F2 | Fs], L, {last, foo}};
+rmerge_files(L, _F2, Fs, InEtc2, BT2, Ts2, Ts, InEtc, _T2) ->
+ L1 = [BT2 | L],
+ [F3 | NFs] = rinsert([Ts | InEtc], Fs),
+ [[?OBJ(T3,BT3) | Ts3] | InEtc3] = F3,
+ rmerge_files(L1, F3, NFs, InEtc3, BT3, Ts3, Ts2, InEtc2, T3).
+
+ukmerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(?SK(T, _I),_BT) | Ts], InEtc,
+ T2, Kp, Last) when T == Last ->
+ ukmerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Kp, Last);
+ukmerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(?SK(T0,_I)=T,BT) | Ts], InEtc,
+ T2, Kp, _Last) when T =< T2 ->
+ ukmerge_files([BT | L], F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Kp, T0);
+ukmerge_files(L, F2, Fs, _InEtc2, _Ts2, [], InEtc, _T2, _Kp, Last) ->
+ {InEtc, [F2 | Fs], L, {last, Last}};
+ukmerge_files(L, _F2, Fs, InEtc2, Ts2, Ts, InEtc, _T2, Kp, Last) ->
+ [F3 | NFs] = insert([Ts | InEtc], Fs),
+ [[?OBJ(T3,_BT3) | _] = Ts3 | InEtc3] = F3,
+ ukmerge_files(L, F3, NFs, InEtc3, Ts3, Ts2, InEtc2, T3, Kp, Last).
+
+rukmerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(?SK(T, _I), BT) | Ts], InEtc,
+ T2, Kp, Last, _LastBT) when T == Last ->
+ rukmerge_files(L, F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Kp, T, BT);
+rukmerge_files(L, F2, Fs, InEtc2, Ts2, [?OBJ(?SK(T0, _I)=T, BT) | Ts], InEtc,
+ T2, Kp, _Last, LastBT) when T >= T2 ->
+ rukmerge_files([LastBT|L], F2, Fs, InEtc2, Ts2, Ts, InEtc, T2, Kp, T0,BT);
+rukmerge_files(L, F2, Fs, _InEtc2, _Ts2, [], InEtc, _T2, _Kp, Last, LastBT) ->
+ {InEtc, [F2 | Fs], L, {last, {Last, LastBT}}};
+rukmerge_files(L, _F2, Fs, InEtc2, Ts2, Ts, InEtc, _T2, Kp, Last, LastBT) ->
+ [F3 | NFs] = rinsert([Ts | InEtc], Fs),
+ [[?OBJ(T3,_BT3) | _] = Ts3 | InEtc3] = F3,
+ rukmerge_files(L, F3, NFs, InEtc3, Ts3, Ts2, InEtc2, T3, Kp, Last,LastBT).
+
+ucmerge_files(L, F2, Fs, InEtc2, Ts2, I2, [?OBJ(T, BT) | Ts] = Ts0, I,
+ InEtc, T2, CF, Last) when I < I2 ->
+ case CF(T, T2) of
+ true -> % T =< T2
+ case CF(T, Last) of
+ true ->
+ ucmerge_files(L, F2, Fs, InEtc2, Ts2, I2, Ts, I, InEtc, T2,
+ CF, Last);
+ false ->
+ ucmerge_files([BT | L], F2, Fs, InEtc2, Ts2, I2, Ts, I,
+ InEtc, T2, CF, T)
+ end;
+ false -> % T > T2
+ [F3 | NFs] = cinsert([Ts0 | InEtc], Fs, CF),
+ [[?OBJ(T3,_BT3) | _] = Ts3 | {I3,_} = InEtc3] = F3,
+ ucmerge_files(L, F3, NFs, InEtc3, Ts3, I3, Ts2, I2, InEtc2, T3, CF, Last)
+ end;
+ucmerge_files(L, F2, Fs, InEtc2, Ts2, I2, [?OBJ(T, BT) | Ts] = Ts0, I,
+ InEtc, T2, CF, Last) -> % when I2 < I
+ case CF(T2, T) of
+ true -> % T2 =< T
+ [F3 | NFs] = cinsert([Ts0 | InEtc], Fs, CF),
+ [[?OBJ(T3,_BT3) | _] = Ts3 | {I3,_} = InEtc3] = F3,
+ ucmerge_files(L, F3, NFs, InEtc3, Ts3, I3, Ts2, I2, InEtc2, T3,
+ CF, Last);
+ false -> % T < T2
+ case CF(T, Last) of
+ true ->
+ ucmerge_files(L, F2, Fs, InEtc2, Ts2, I2, Ts, I, InEtc, T2,
+ CF, Last);
+ false ->
+ ucmerge_files([BT | L], F2, Fs, InEtc2, Ts2, I2, Ts, I,
+ InEtc, T2, CF, T)
+ end
+ end;
+ucmerge_files(L, F2, Fs, _InEtc2, _Ts2, _I2, [], _I, InEtc, _T2, _CF, Last) ->
+ {InEtc, [F2 | Fs], L, {last, Last}}.
+
+cmerge_files(L, F2, Fs, InEtc2, BT2, Ts2, I2, [?OBJ(T, BT) | Ts] = Ts0, I,
+ InEtc, T2, CF) when I < I2 ->
+ case CF(T, T2) of
+ true -> % T =< T2
+ cmerge_files([BT|L], F2, Fs, InEtc2, BT2, Ts2, I2, Ts, I, InEtc, T2, CF);
+ false -> % T > T2
+ L1 = [BT2 | L],
+ [F3 | NFs] = cinsert([Ts0 | InEtc], Fs, CF),
+ [[?OBJ(T3,BT3) | Ts3] | {I3,_} = InEtc3] = F3,
+ cmerge_files(L1, F3, NFs, InEtc3, BT3, Ts3, I3, Ts2, I2, InEtc2, T3, CF)
+ end;
+cmerge_files(L, F2, Fs, InEtc2, BT2, Ts2, I2, [?OBJ(T, BT) | Ts] = Ts0, I,
+ InEtc, T2, CF) -> % when I2 < I
+ case CF(T2, T) of
+ true -> % T2 =< T
+ L1 = [BT2 | L],
+ [F3 | NFs] = cinsert([Ts0 | InEtc], Fs, CF),
+ [[?OBJ(T3,BT3) | Ts3] | {I3,_} = InEtc3] = F3,
+ cmerge_files(L1, F3, NFs, InEtc3, BT3, Ts3, I3, Ts2, I2, InEtc2, T3, CF);
+ false -> % T < T2
+ cmerge_files([BT|L], F2, Fs, InEtc2, BT2, Ts2, I2, Ts, I, InEtc, T2, CF)
+ end;
+cmerge_files(L, F2, Fs, _InEtc2, _BT2, _Ts2, _I2, [], _I, InEtc, _T2, _CF) ->
+ {InEtc, [F2 | Fs], L, {last, foo}}.
+
+last_file(Ts, L, {last, T}, {ukmerge,_}, _W) ->
+ kulast_file(Ts, T, L);
+last_file(Ts, L, {last, {T,BT}}, {rukmerge,_}, _W) ->
+ ruklast_file(Ts, T, BT, L);
+last_file(Ts, L, {last, T}, {ucmerge,CF}, _W) ->
+ uclast_file(Ts, T, CF, L);
+last_file(Ts, L, {last, T}, _Kind, #w{unique = true}) ->
+ ulast_file(Ts, T, L);
+last_file(Ts, L, _Last, _Kind, _W) ->
+ last_file(Ts, L).
+
+ulast_file([?OBJ(T, _BT) | Ts], Last, L) when Last == T ->
+ last_file(Ts, L);
+ulast_file(Ts, _Last, L) ->
+ last_file(Ts, L).
+
+kulast_file([?OBJ(?SK(T, _I), _BT) | Ts], Last, L) when Last == T ->
+ last_file(Ts, L);
+kulast_file(Ts, _Last, L) ->
+ last_file(Ts, L).
+
+ruklast_file([?OBJ(?SK(T, _I), BT) | Ts], Last, _LastBT, L) when Last == T ->
+ last_file(Ts, [BT | L]);
+ruklast_file(Ts, _Last, LastBT, L) ->
+ last_file(Ts, [LastBT | L]).
+
+uclast_file([?OBJ(T, BT) | Ts], Last, CF, L) ->
+ case CF(T, Last) of
+ true ->
+ last_file(Ts, L);
+ false ->
+ last_file(Ts, [BT | L])
+ end.
+
+last_file([?OBJ(_Ta, BTa), ?OBJ(_Tb, BTb) | Ts], L) ->
+ last_file(Ts, [BTb, BTa | L]);
+last_file([?OBJ(_T, BT) | Ts], L) ->
+ last_file(Ts, [BT | L]);
+last_file([], L) ->
+ L.
+
+%% OK for 16 files.
+insert(A, [X1, X2, X3, X4 | Xs]) when A > X4 ->
+ [X1, X2, X3, X4 | insert(A, Xs)];
+insert(A, [X1, X2, X3 | T]) when A > X3 ->
+ [X1, X2, X3, A | T];
+insert(A, [X1, X2 | Xs]) when A > X2 ->
+ [X1, X2, A | Xs];
+insert(A, [X1 | T]) when A > X1 ->
+ [X1, A | T];
+insert(A, Xs) ->
+ [A | Xs].
+
+rinsert(A, [X1, X2, X3, X4 | Xs]) when A < X4 ->
+ [X1, X2, X3, X4 | rinsert(A, Xs)];
+rinsert(A, [X1, X2, X3 | T]) when A < X3 ->
+ [X1, X2, X3, A | T];
+rinsert(A, [X1, X2 | Xs]) when A < X2 ->
+ [X1, X2, A | Xs];
+rinsert(A, [X1 | T]) when A < X1 ->
+ [X1, A | T];
+rinsert(A, Xs) ->
+ [A | Xs].
+
+-define(CINSERT(F, A, T1, T2),
+ case cfun(CF, F, A) of
+ true -> [F, A | T2];
+ false -> [A | T1]
+ end).
+
+cinsert(A, [F1 | [F2 | [F3 | [F4 | Fs]=T4]=T3]=T2]=T1, CF) ->
+ case cfun(CF, F4, A) of
+ true -> [F1, F2, F3, F4 | cinsert(A, Fs, CF)];
+ false ->
+ case cfun(CF, F2, A) of
+ true -> [F1, F2 | ?CINSERT(F3, A, T3, T4)];
+ false -> ?CINSERT(F1, A, T1, T2)
+ end
+ end;
+cinsert(A, [F1 | [F2 | Fs]=T2]=T1, CF) ->
+ case cfun(CF, F2, A) of
+ true -> [F1, F2 | cinsert(A, Fs, CF)];
+ false -> ?CINSERT(F1, A, T1, T2)
+ end;
+cinsert(A, [F | Fs]=T, CF) ->
+ ?CINSERT(F, A, T, Fs);
+cinsert(A, _, _CF) ->
+ [A].
+
+%% Inlined.
+cfun(CF, F1, F2) ->
+ [[?OBJ(T1,_) | _] | {I1,_}] = F1,
+ [[?OBJ(T2,_) | _] | {I2,_}] = F2,
+ if
+ I1 < I2 ->
+ CF(T1, T2);
+ true -> % I2 < I1
+ not CF(T2, T1)
+ end.
+
+binterm_objects([?OBJ(_T, [_Sz | BT]) | Ts], L) ->
+ binterm_objects(Ts, [BT | L]);
+binterm_objects([], L) ->
+ L.
+
+objects([[_Sz | BT] | Ts], L) ->
+ objects(Ts, [BT | L]);
+objects([], L) ->
+ L.
+
+binterms([?OBJ(_T1, BT1), ?OBJ(_T2, BT2) | Ts], L) ->
+ binterms(Ts, [BT2, BT1 | L]);
+binterms([?OBJ(_T, BT) | Ts], L) ->
+ binterms(Ts, [BT | L]);
+binterms([], L) ->
+ L.
+
+read_chunk(InEtc, Fs, L, LSz, Last, W) ->
+ {I, IFun} = InEtc,
+ case read_more(IFun, I, LSz, W) of
+ {Ts, NLSz, NIFun, #w{order = ascending}=NW} ->
+ NInEtc = {I, NIFun},
+ NFs = insert([Ts | NInEtc], Fs),
+ merge_files(NFs, L, NLSz, Last, NW);
+ {Ts, NLSz, NIFun, #w{order = descending}=NW} ->
+ NInEtc = {I, NIFun},
+ NFs = rinsert([Ts | NInEtc], Fs),
+ merge_files(NFs, L, NLSz, Last, NW);
+ {Ts, NLSz, NIFun, NW} ->
+ NInEtc = {I, NIFun},
+ NFs = cinsert([Ts | NInEtc], Fs, NW#w.order),
+ merge_files(NFs, L, NLSz, Last, NW);
+ {eof, NW} ->
+ merge_files(Fs, L, LSz, Last, NW)
+ end.
+
+%% -> {[{term() | binary()}], NewLSz, NewIFun, NewW} | eof | throw(Error)
+read_more(IFun, I, LSz, W) ->
+ case IFun({{merge, I}, [], LSz, W}) of
+ {{_, [], NLSz}, NIFun} ->
+ read_more(NIFun, I, NLSz, W);
+ {{_, L, NLSz}, NInFun} ->
+ NW = case lists:member(IFun, W#w.temp) of
+ true ->
+ %% temporary file
+ W#w{temp = [NInFun | lists:delete(IFun, W#w.temp)]};
+ false ->
+ %% input file
+ W
+ end,
+ {lists:reverse(L), NLSz, NInFun, NW};
+ eof ->
+ %% already closed.
+ NW = W#w{temp = lists:delete(IFun, W#w.temp)},
+ {eof, NW}
+ end.
+
+read_fun(FileName, Owner, W) ->
+ case file:open(FileName, [raw, binary, read, compressed]) of
+ {ok, Fd} ->
+ read_fun2(Fd, <<>>, 0, FileName, Owner);
+ Error ->
+ file_error(FileName, Error, W)
+ end.
+
+read_fun2(Fd, Bin, Size, FileName, Owner) ->
+ fun(close) ->
+ close_read_fun(Fd, FileName, Owner);
+ ({I, L, LSz, W}) ->
+ case read_objs(Fd, FileName, I, L, Bin, Size, LSz, W) of
+ {{I1, L1, Bin1, Size1}, LSz1} ->
+ NIFun = read_fun2(Fd, Bin1, Size1, FileName, Owner),
+ {{I1, L1, LSz1}, NIFun};
+ eof ->
+ close_read_fun(Fd, FileName, Owner),
+ eof
+ end
+ end.
+
+close_read_fun(Fd, _FileName, user) ->
+ file:close(Fd);
+close_read_fun(Fd, FileName, fsort) ->
+ file:close(Fd),
+ file:delete(FileName).
+
+read_objs(Fd, FileName, I, L, Bin0, Size0, LSz, W) ->
+ Max = max(Size0, ?CHUNKSIZE),
+ BSz0 = byte_size(Bin0),
+ Min = Size0 - BSz0 + W#w.hdlen, % Min > 0
+ NoBytes = max(Min, Max),
+ case read(Fd, FileName, NoBytes, W) of
+ {ok, Bin} ->
+ BSz = byte_size(Bin),
+ NLSz = LSz + BSz,
+ case catch file_loop(L, I, Bin0, Bin, Size0, BSz0, BSz, Min, W)
+ of
+ {'EXIT', _R} ->
+ error({error, {bad_object, FileName}}, W);
+ Reply ->
+ {Reply, NLSz}
+ end;
+ eof when byte_size(Bin0) =:= 0 ->
+ eof;
+ eof ->
+ error({error, {premature_eof, FileName}}, W)
+ end.
+
+file_loop(L, I, _B1, B2, Sz, 0, _B2Sz, _Min, W) ->
+ file_loop(L, I, B2, Sz, W);
+file_loop(L, I, B1, B2, Sz, _B1Sz, B2Sz, Min, W) when B2Sz > Min ->
+ {B3, B4} = split_binary(B2, Min),
+ {I1, L1, <<>>, Sz1} = file_loop(L, I, list_to_binary([B1, B3]), Sz, W),
+ file_loop(L1, I1, B4, Sz1, W);
+file_loop(L, I, B1, B2, Sz, _B1Sz, _B2Sz, _Min, W) ->
+ file_loop(L, I, list_to_binary([B1, B2]), Sz, W).
+
+file_loop(L, I, B, Sz, W) ->
+ #w{keypos = Kp, format = Format, hdlen = HdLen} = W,
+ file_loop1(L, I, B, Sz, Kp, Format, HdLen).
+
+file_loop1(L, I, HB, 0, Kp, F, HdLen) ->
+ <<Size:HdLen/unit:8, B/binary>> = HB,
+ file_loop2(L, I, B, Size, <<Size:HdLen/unit:8>>, Kp, F, HdLen);
+file_loop1(L, I, B, Sz, Kp, F, HdLen) ->
+ file_loop2(L, I, B, Sz, <<Sz:HdLen/unit:8>>, Kp, F, HdLen).
+
+file_loop2(L, _I, B, Sz, SzB, 0, binary, HdLen) ->
+ {NL, NB, NSz, NSzB} = file_binloop(L, Sz, SzB, B, HdLen),
+ if
+ byte_size(NB) =:= NSz ->
+ <<Bin:NSz/binary>> = NB,
+ {0, [?OBJ(Bin, [NSzB | Bin]) | NL], <<>>, 0};
+ true ->
+ {0, NL, NB, NSz}
+ end;
+file_loop2(L, _I, B, Sz, SzB, 0, Fun, HdLen) ->
+ file_binterm_loop(L, Sz, SzB, B, Fun, HdLen);
+file_loop2(L, {merge, I}, B, Sz, SzB, Kp, Fun, HdLen) -> % when Kp =/= 0
+ merge_loop(Kp, I, L, Sz, SzB, B, Fun, HdLen);
+file_loop2(L, I, B, Sz, SzB, Kp, Fun, HdLen) when is_integer(I) ->
+ key_loop(Kp, I, L, Sz, SzB, B, Fun, HdLen).
+
+file_binloop(L, Size, SizeB, B, HL) ->
+ case B of
+ <<Bin:Size/binary, NSizeB:HL/binary, R/binary>> ->
+ <<NSize:HL/unit:8>> = NSizeB,
+ file_binloop([?OBJ(Bin, [SizeB | Bin]) | L], NSize, NSizeB, R, HL);
+ _ ->
+ {L, B, Size, SizeB}
+ end.
+
+file_binterm_loop(L, Size, SizeB, B, Fun, HL) ->
+ case B of
+ <<BinTerm:Size/binary, NSizeB:HL/binary, R/binary>> ->
+ <<NSize:HL/unit:8>> = NSizeB,
+ BT = [SizeB | BinTerm],
+ Term = Fun(BinTerm),
+ file_binterm_loop([?OBJ(Term, BT) | L], NSize, NSizeB, R, Fun, HL);
+ <<BinTerm:Size/binary>> ->
+ Term = Fun(BinTerm),
+ NL = [?OBJ(Term, [SizeB | BinTerm]) | L],
+ {0, NL, <<>>, 0};
+ _ ->
+ {0, L, B, Size}
+ end.
+
+key_loop(KeyPos, I, L, Size, SizeB, B, Fun, HL) ->
+ case B of
+ <<BinTerm:Size/binary, NSizeB:HL/binary, R/binary>> ->
+ <<NSize:HL/unit:8>> = NSizeB,
+ BT = [SizeB | BinTerm],
+ UniqueKey = make_key(KeyPos, Fun(BinTerm)),
+ E = ?OBJ(UniqueKey, BT),
+ key_loop(KeyPos, I+1, [E | L], NSize, NSizeB, R, Fun, HL);
+ <<BinTerm:Size/binary>> ->
+ UniqueKey = make_key(KeyPos, Fun(BinTerm)),
+ NL = [?OBJ(UniqueKey, [SizeB | BinTerm]) | L],
+ {I+1, NL, <<>>, 0};
+ _ ->
+ {I, L, B, Size}
+ end.
+
+merge_loop(KeyPos, I, L, Size, SizeB, B, Fun, HL) ->
+ case B of
+ <<BinTerm:Size/binary, NSizeB:HL/binary, R/binary>> ->
+ <<NSize:HL/unit:8>> = NSizeB,
+ BT = [SizeB | BinTerm],
+ UniqueKey = make_stable_key(KeyPos, I, Fun(BinTerm)),
+ E = ?OBJ(UniqueKey, BT),
+ merge_loop(KeyPos, I, [E | L], NSize, NSizeB, R, Fun, HL);
+ <<BinTerm:Size/binary>> ->
+ UniqueKey = make_stable_key(KeyPos, I, Fun(BinTerm)),
+ NL = [?OBJ(UniqueKey, [SizeB | BinTerm]) | L],
+ {{merge, I}, NL, <<>>, 0};
+ _ ->
+ {{merge, I}, L, B, Size}
+ end.
+
+fun_objs(Objs, L, LSz, NoBytes, I, W) ->
+ #w{keypos = Keypos, format = Format, hdlen = HL} = W,
+ case catch fun_loop(Objs, L, LSz, NoBytes, I, Keypos, Format, HL) of
+ {'EXIT', _R} ->
+ error({error, bad_object}, W);
+ Reply ->
+ Reply
+ end.
+
+fun_loop(Objs, L, LSz, RunSize, _I, 0, binary, HdLen) ->
+ fun_binloop(Objs, L, LSz, RunSize, HdLen);
+fun_loop(Objs, L, LSz, RunSize, _I, 0, Fun, HdLen) ->
+ fun_loop(Objs, L, LSz, RunSize, Fun, HdLen);
+fun_loop(Objs, L, LSz, RunSize, {merge, I}, Keypos, Fun, HdLen) ->
+ fun_mergeloop(Objs, L, LSz, RunSize, I, Keypos, Fun, HdLen);
+fun_loop(Objs, L, LSz, RunSize, I, Keypos, Fun, HdLen) when is_integer(I) ->
+ fun_keyloop(Objs, L, LSz, RunSize, I, Keypos, Fun, HdLen).
+
+fun_binloop([B | Bs], L, LSz, RunSize, HL) when LSz < RunSize ->
+ Size = byte_size(B),
+ Obj = ?OBJ(B, [<<Size:HL/unit:8>> | B]),
+ fun_binloop(Bs, [Obj | L], LSz+Size, RunSize, HL);
+fun_binloop(Bs, L, LSz, _RunSize, _HL) ->
+ {0, Bs, L, LSz}.
+
+fun_loop([B | Bs], L, LSz, RunSize, Fun, HL) when LSz < RunSize ->
+ Size = byte_size(B),
+ Obj = ?OBJ(Fun(B), [<<Size:HL/unit:8>> | B]),
+ fun_loop(Bs, [Obj | L], LSz+Size, RunSize, Fun, HL);
+fun_loop(Bs, L, LSz, _RunSize, _Fun, _HL) ->
+ {0, Bs, L, LSz}.
+
+fun_keyloop([B | Bs], L, LSz, RunSize, I, Kp, Fun, HL) when LSz < RunSize ->
+ Size = byte_size(B),
+ UniqueKey = make_key(Kp, Fun(B)),
+ E = ?OBJ(UniqueKey, [<<Size:HL/unit:8>> | B]),
+ fun_keyloop(Bs, [E | L], LSz+Size, RunSize, I+1, Kp, Fun, HL);
+fun_keyloop(Bs, L, LSz, _RunSize, I, _Kp, _Fun, _HL) ->
+ {I, Bs, L, LSz}.
+
+fun_mergeloop([B | Bs], L, LSz, RunSize, I, Kp, Fun, HL) when LSz < RunSize ->
+ Size = byte_size(B),
+ UniqueKey = make_stable_key(Kp, I, Fun(B)),
+ E = ?OBJ(UniqueKey, [<<Size:HL/unit:8>> | B]),
+ fun_mergeloop(Bs, [E | L], LSz+Size, RunSize, I, Kp, Fun, HL);
+fun_mergeloop(Bs, L, LSz, _RunSize, I, _Kp, _Fun, _HL) ->
+ {{merge, I}, Bs, L, LSz}. % any I would do
+
+%% Inlined.
+make_key(Kp, T) when is_integer(Kp) ->
+ element(Kp, T);
+make_key([Kp1, Kp2], T) ->
+ [element(Kp1, T), element(Kp2, T)];
+make_key([Kp1, Kp2 | Kps], T) ->
+ [element(Kp1, T), element(Kp2, T) | make_key2(Kps, T)].
+
+%% Inlined.
+%% A sequence number (I) is used for making the internal sort stable.
+%% I is ordering number of the file from which T was read.
+make_stable_key(Kp, I, T) when is_integer(Kp) ->
+ ?SK(element(Kp, T), I);
+make_stable_key([Kp1, Kp2], I, T) ->
+ ?SK([element(Kp1, T) | element(Kp2, T)], I);
+make_stable_key([Kp1, Kp2 | Kps], I, T) ->
+ ?SK([element(Kp1, T), element(Kp2, T) | make_key2(Kps, T)], I).
+
+make_key2([Kp], T) ->
+ [element(Kp, T)];
+make_key2([Kp | Kps], T) ->
+ [element(Kp, T) | make_key2(Kps, T)].
+
+max(A, B) when A < B -> B;
+max(A, _) -> A.
+
+infun(W) ->
+ W1 = W#w{in = undefined},
+ try (W#w.in)(read) of
+ end_of_input ->
+ {end_of_input, W1};
+ {end_of_input, Value} ->
+ {end_of_input, W1#w{inout_value = {value, Value}}};
+ {Objs, NFun} when is_function(NFun),
+ is_function(NFun, 1),
+ is_list(Objs) ->
+ {cont, W#w{in = NFun}, Objs};
+ Error ->
+ error(Error, W1)
+ catch Class:Reason ->
+ cleanup(W1),
+ erlang:raise(Class, Reason, erlang:get_stacktrace())
+ end.
+
+outfun(A, W) when W#w.inout_value =/= no_value ->
+ W1 = W#w{inout_value = no_value},
+ W2 = if
+ W1#w.fun_out ->
+ outfun(W#w.inout_value, W1);
+ true -> W1
+ end,
+ outfun(A, W2);
+outfun(A, W) ->
+ W1 = W#w{out = undefined},
+ try (W#w.out)(A) of
+ Reply when A =:= close ->
+ Reply;
+ NF when is_function(NF), is_function(NF, 1) ->
+ W#w{out = NF};
+ Error ->
+ error(Error, W1)
+ catch Class:Reason ->
+ cleanup(W1),
+ erlang:raise(Class, Reason, erlang:get_stacktrace())
+ end.
+
+is_keypos(Keypos) when is_integer(Keypos), Keypos > 0 ->
+ true;
+is_keypos([]) ->
+ {badarg, []};
+is_keypos(L) ->
+ is_keyposs(L).
+
+is_keyposs([Kp | Kps]) when is_integer(Kp), Kp > 0 ->
+ is_keyposs(Kps);
+is_keyposs([]) ->
+ true;
+is_keyposs([Bad | _]) ->
+ {badarg, Bad};
+is_keyposs(Bad) ->
+ {badarg, Bad}.
+
+is_input(Fun) when is_function(Fun), is_function(Fun, 1) ->
+ {true, Fun};
+is_input(Files) ->
+ is_files(Files).
+
+is_files(Fs) ->
+ is_files(Fs, []).
+
+is_files([F | Fs], L) ->
+ case read_file_info(F) of
+ {ok, File, _FI} ->
+ is_files(Fs, [File | L]);
+ Error ->
+ Error
+ end;
+is_files([], L) ->
+ {true, lists:reverse(L)};
+is_files(Bad, _L) ->
+ {badarg, Bad}.
+
+maybe_output(Fun) when is_function(Fun), is_function(Fun, 1) ->
+ {true, Fun};
+maybe_output(File) ->
+ case read_file_info(File) of
+ {badarg, _File} = Badarg ->
+ Badarg;
+ {ok, FileName, _FileInfo} ->
+ {true, FileName};
+ {error, {file_error, FileName, _Reason}} ->
+ {true, FileName}
+ end.
+
+read_file_info(File) ->
+ %% Absolute names in case some process should call file:set_cwd/1.
+ case catch filename:absname(File) of
+ {'EXIT', _} ->
+ {badarg, File};
+ FileName ->
+ case file:read_file_info(FileName) of
+ {ok, FileInfo} ->
+ {ok, FileName, FileInfo};
+ {error, einval} ->
+ {badarg, File};
+ {error, Reason} ->
+ {error, {file_error, FileName, Reason}}
+ end
+ end.
+
+%% No attempt is made to avoid overwriting existing files.
+next_temp(W) ->
+ Seq = W#w.seq,
+ NW = W#w{seq = Seq + 1},
+ Temp = lists:concat([W#w.prefix, Seq]),
+ {NW, Temp}.
+
+%% Would use the temporary directory (TMP|TEMP|TMPDIR), were it
+%% readily accessible.
+tmp_prefix(F, TmpDirOpt) when is_function(F); F =:= undefined ->
+ {ok, CurDir} = file:get_cwd(),
+ tmp_prefix1(CurDir, TmpDirOpt);
+tmp_prefix(OutFile, TmpDirOpt) ->
+ Dir = filename:dirname(OutFile),
+ tmp_prefix1(Dir, TmpDirOpt).
+
+tmp_prefix1(Dir, TmpDirOpt) ->
+ U = "_",
+ Node = node(),
+ Pid = os:getpid(),
+ {MSecs,Secs,MySecs} = now(),
+ F = lists:concat(["fs_",Node,U,Pid,U,MSecs,U,Secs,U,MySecs,"."]),
+ TmpDir = case TmpDirOpt of
+ default ->
+ Dir;
+ {dir, TDir} ->
+ TDir
+ end,
+ filename:join(filename:absname(TmpDir), F).
+
+%% -> {Fd, NewW} | throw(Error)
+open_file(FileName, W) ->
+ case file:open(FileName, W#w.z ++ [raw, binary, write]) of
+ {ok, Fd} ->
+ {Fd, W#w{temp = [{Fd,FileName} | W#w.temp]}};
+ Error ->
+ file_error(FileName, Error, W)
+ end.
+
+read(Fd, FileName, N, W) ->
+ case file:read(Fd, N) of
+ {ok, Bin} ->
+ {ok, Bin};
+ eof ->
+ eof;
+ {error, enomem} ->
+ %% Bad N
+ error({error, {bad_object, FileName}}, W);
+ {error, einval} ->
+ %% Bad N
+ error({error, {bad_object, FileName}}, W);
+ Error ->
+ file_error(FileName, Error, W)
+ end.
+
+write(Fd, FileName, B, W) ->
+ case file:write(Fd, B) of
+ ok ->
+ ok;
+ Error ->
+ file_error(FileName, Error, W)
+ end.
+
+-spec file_error(_, {'error',atom()}, #w{}) -> no_return().
+
+file_error(File, {error, Reason}, W) ->
+ error({error, {file_error, File, Reason}}, W).
+
+error(Error, W) ->
+ cleanup(W),
+ throw({W#w.ref, Error}).
+
+cleanup(W) ->
+ close_out(W),
+ W1 = close_input(W),
+ F = fun(IFun) when is_function(IFun) ->
+ IFun(close);
+ ({Fd,FileName}) ->
+ file:close(Fd),
+ file:delete(FileName);
+ (FileName) ->
+ file:delete(FileName)
+ end,
+ lists:foreach(F, W1#w.temp).
+
+close_input(W) when is_function(W#w.in) ->
+ catch (W#w.in)(close),
+ W#w{in = undefined};
+close_input(#w{in = undefined}=W) ->
+ W.
+
+close_out(W) when is_function(W#w.out) ->
+ catch (W#w.out)(close);
+close_out(_) ->
+ ok.
+
+close_file(Fd, W) ->
+ {value, {Fd, FileName}} = lists:keysearch(Fd, 1, W#w.temp),
+ ?DEBUG("closing ~p~n", [FileName]),
+ file:close(Fd),
+ W#w{temp = [FileName | lists:keydelete(Fd, 1, W#w.temp)]}.
+
+%%%
+%%% Format 'term'.
+%%%
+
+file_rterms(no_file, Files) ->
+ fun(close) ->
+ ok;
+ (read) when Files =:= [] ->
+ end_of_input;
+ (read) ->
+ [F | Fs] = Files,
+ case file:open(F, [read, compressed]) of
+ {ok, Fd} ->
+ file_rterms2(Fd, [], 0, F, Fs);
+ {error, Reason} ->
+ {error, {file_error, F, Reason}}
+ end
+ end;
+file_rterms({Fd, FileName}, Files) ->
+ fun(close) ->
+ file:close(Fd);
+ (read) ->
+ file_rterms2(Fd, [], 0, FileName, Files)
+ end.
+
+file_rterms2(Fd, L, LSz, FileName, Files) when LSz < ?CHUNKSIZE ->
+ case io:read(Fd, '') of
+ {ok, Term} ->
+ B = term_to_binary(Term),
+ file_rterms2(Fd, [B | L], LSz + byte_size(B), FileName, Files);
+ eof ->
+ file:close(Fd),
+ {lists:reverse(L), file_rterms(no_file, Files)};
+ _Error ->
+ file:close(Fd),
+ {error, {bad_term, FileName}}
+ end;
+file_rterms2(Fd, L, _LSz, FileName, Files) ->
+ {lists:reverse(L), file_rterms({Fd, FileName}, Files)}.
+
+file_wterms(W, F, Args) ->
+ fun(close) when W =:= name ->
+ ok;
+ (close) ->
+ {fd, Fd} = W,
+ file:close(Fd);
+ (L) when W =:= name ->
+ case file:open(F, Args) of
+ {ok, Fd} ->
+ write_terms(Fd, F, L, Args);
+ {error, Reason} ->
+ {error, {file_error, F, Reason}}
+ end;
+ (L) ->
+ {fd, Fd} = W,
+ write_terms(Fd, F, L, Args)
+ end.
+
+write_terms(Fd, F, [B | Bs], Args) ->
+ case io:request(Fd, {format, "~p.~n", [binary_to_term(B)]}) of
+ ok ->
+ write_terms(Fd, F, Bs, Args);
+ {error, Reason} ->
+ file:close(Fd),
+ {error, {file_error, F, Reason}}
+ end;
+write_terms(Fd, F, [], Args) ->
+ file_wterms({fd, Fd}, F, Args).
+
+fun_rterms(InFun) ->
+ fun(close) ->
+ InFun(close);
+ (read) ->
+ case InFun(read) of
+ {Ts, NInFun} when is_list(Ts),
+ is_function(NInFun),
+ is_function(NInFun, 1) ->
+ {to_bin(Ts, []), fun_rterms(NInFun)};
+ Else ->
+ Else
+ end
+ end.
+
+fun_wterms(OutFun) ->
+ fun(close) ->
+ OutFun(close);
+ (L) ->
+ case OutFun(wterms_arg(L)) of
+ NOutFun when is_function(NOutFun), is_function(NOutFun, 1) ->
+ fun_wterms(NOutFun);
+ Else ->
+ Else
+ end
+ end.
+
+to_bin([E | Es], L) ->
+ to_bin(Es, [term_to_binary(E) | L]);
+to_bin([], L) ->
+ lists:reverse(L).
+
+wterms_arg(L) when is_list(L) ->
+ to_term(L, []);
+wterms_arg(Value) ->
+ Value.
+
+to_term([B | Bs], L) ->
+ to_term(Bs, [binary_to_term(B) | L]);
+to_term([], L) ->
+ lists:reverse(L).
diff --git a/lib/stdlib/src/filelib.erl b/lib/stdlib/src/filelib.erl
new file mode 100644
index 0000000000..d65588f0d1
--- /dev/null
+++ b/lib/stdlib/src/filelib.erl
@@ -0,0 +1,443 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(filelib).
+
+%% File utilities.
+
+-export([wildcard/1, wildcard/2, is_dir/1, is_file/1, is_regular/1,
+ compile_wildcard/1]).
+-export([fold_files/5, last_modified/1, file_size/1, ensure_dir/1]).
+
+-export([wildcard/3, is_dir/2, is_file/2, is_regular/2]).
+-export([fold_files/6, last_modified/2, file_size/2]).
+
+-include_lib("kernel/include/file.hrl").
+
+-define(HANDLE_ERROR(Expr),
+ try
+ Expr
+ catch
+ error:{badpattern,_}=UnUsUalVaRiAbLeNaMe ->
+ %% Get the stack backtrace correct.
+ erlang:error(UnUsUalVaRiAbLeNaMe)
+ end).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec wildcard(name()) -> [file:filename()].
+wildcard(Pattern) when is_list(Pattern) ->
+ ?HANDLE_ERROR(do_wildcard(Pattern, file)).
+
+-spec wildcard(name(), name() | atom()) -> [file:filename()].
+wildcard(Pattern, Cwd) when is_list(Pattern), is_list(Cwd) ->
+ ?HANDLE_ERROR(do_wildcard(Pattern, Cwd, file));
+wildcard(Pattern, Mod) when is_list(Pattern), is_atom(Mod) ->
+ ?HANDLE_ERROR(do_wildcard(Pattern, Mod)).
+
+-spec wildcard(name(), name(), atom()) -> [file:filename()].
+wildcard(Pattern, Cwd, Mod)
+ when is_list(Pattern), is_list(Cwd), is_atom(Mod) ->
+ ?HANDLE_ERROR(do_wildcard(Pattern, Cwd, Mod)).
+
+-spec is_dir(name()) -> boolean().
+is_dir(Dir) ->
+ do_is_dir(Dir, file).
+
+-spec is_dir(name(), atom()) -> boolean().
+is_dir(Dir, Mod) when is_atom(Mod) ->
+ do_is_dir(Dir, Mod).
+
+-spec is_file(name()) -> boolean().
+is_file(File) ->
+ do_is_file(File, file).
+
+-spec is_file(name(), atom()) -> boolean().
+is_file(File, Mod) when is_atom(Mod) ->
+ do_is_file(File, Mod).
+
+-spec is_regular(name()) -> boolean().
+is_regular(File) ->
+ do_is_regular(File, file).
+
+-spec is_regular(name(), atom()) -> boolean().
+is_regular(File, Mod) when is_atom(Mod) ->
+ do_is_regular(File, Mod).
+
+-spec fold_files(name(), string(), boolean(), fun((_,_) -> _), _) -> _.
+fold_files(Dir, RegExp, Recursive, Fun, Acc) ->
+ do_fold_files(Dir, RegExp, Recursive, Fun, Acc, file).
+
+-spec fold_files(name(), string(), boolean(), fun((_,_) -> _), _, atom()) -> _.
+fold_files(Dir, RegExp, Recursive, Fun, Acc, Mod) when is_atom(Mod) ->
+ do_fold_files(Dir, RegExp, Recursive, Fun, Acc, Mod).
+
+-spec last_modified(name()) -> date_time() | 0.
+last_modified(File) ->
+ do_last_modified(File, file).
+
+-spec last_modified(name(), atom()) -> date_time() | 0.
+last_modified(File, Mod) when is_atom(Mod) ->
+ do_last_modified(File, Mod).
+
+-spec file_size(name()) -> non_neg_integer().
+file_size(File) ->
+ do_file_size(File, file).
+
+-spec file_size(name(), atom()) -> non_neg_integer().
+file_size(File, Mod) when is_atom(Mod) ->
+ do_file_size(File, Mod).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_wildcard(Pattern, Mod) when is_list(Pattern) ->
+ do_wildcard_comp(do_compile_wildcard(Pattern), Mod).
+
+do_wildcard_comp({compiled_wildcard,{exists,File}}, Mod) ->
+ case eval_read_file_info(File, Mod) of
+ {ok,_} -> [File];
+ _ -> []
+ end;
+do_wildcard_comp({compiled_wildcard,[Base|Rest]}, Mod) ->
+ do_wildcard_1([Base], Rest, Mod).
+
+do_wildcard(Pattern, Cwd, Mod) when is_list(Pattern), is_list(Cwd) ->
+ do_wildcard_comp(do_compile_wildcard(Pattern), Cwd, Mod).
+
+do_wildcard_comp({compiled_wildcard,{exists,File}}, Cwd, Mod) ->
+ case eval_read_file_info(filename:absname(File, Cwd), Mod) of
+ {ok,_} -> [File];
+ _ -> []
+ end;
+do_wildcard_comp({compiled_wildcard,[current|Rest]}, Cwd0, Mod) ->
+ Cwd = filename:join([Cwd0]), %Slash away redundant slashes.
+ PrefixLen = length(Cwd)+1,
+ [lists:nthtail(PrefixLen, N) || N <- do_wildcard_1([Cwd], Rest, Mod)];
+do_wildcard_comp({compiled_wildcard,[Base|Rest]}, _Cwd, Mod) ->
+ do_wildcard_1([Base], Rest, Mod).
+
+do_is_dir(Dir, Mod) ->
+ case eval_read_file_info(Dir, Mod) of
+ {ok, #file_info{type=directory}} ->
+ true;
+ _ ->
+ false
+ end.
+
+do_is_file(File, Mod) ->
+ case eval_read_file_info(File, Mod) of
+ {ok, #file_info{type=regular}} ->
+ true;
+ {ok, #file_info{type=directory}} ->
+ true;
+ _ ->
+ false
+ end.
+
+do_is_regular(File, Mod) ->
+ case eval_read_file_info(File, Mod) of
+ {ok, #file_info{type=regular}} ->
+ true;
+ _ ->
+ false
+ end.
+
+%% fold_files(Dir, RegExp, Recursive, Fun, AccIn).
+
+%% folds the function Fun(F, Acc) -> Acc1 over
+%% all files <F> in <Dir> that match the regular expression <RegExp>
+%% If <Recursive> is true all sub-directories to <Dir> are processed
+
+do_fold_files(Dir, RegExp, Recursive, Fun, Acc, Mod) ->
+ {ok, Re1} = re:compile(RegExp),
+ do_fold_files1(Dir, Re1, Recursive, Fun, Acc, Mod).
+
+do_fold_files1(Dir, RegExp, Recursive, Fun, Acc, Mod) ->
+ case eval_list_dir(Dir, Mod) of
+ {ok, Files} -> do_fold_files2(Files, Dir, RegExp, Recursive, Fun, Acc, Mod);
+ {error, _} -> Acc
+ end.
+
+do_fold_files2([], _Dir, _RegExp, _Recursive, _Fun, Acc, _Mod) ->
+ Acc;
+do_fold_files2([File|T], Dir, RegExp, Recursive, Fun, Acc0, Mod) ->
+ FullName = filename:join(Dir, File),
+ case do_is_regular(FullName, Mod) of
+ true ->
+ case re:run(File, RegExp, [{capture,none}]) of
+ match ->
+ Acc = Fun(FullName, Acc0),
+ do_fold_files2(T, Dir, RegExp, Recursive, Fun, Acc, Mod);
+ nomatch ->
+ do_fold_files2(T, Dir, RegExp, Recursive, Fun, Acc0, Mod)
+ end;
+ false ->
+ case Recursive andalso do_is_dir(FullName, Mod) of
+ true ->
+ Acc1 = do_fold_files1(FullName, RegExp, Recursive,
+ Fun, Acc0, Mod),
+ do_fold_files2(T, Dir, RegExp, Recursive, Fun, Acc1, Mod);
+ false ->
+ do_fold_files2(T, Dir, RegExp, Recursive, Fun, Acc0, Mod)
+ end
+ end.
+
+do_last_modified(File, Mod) ->
+ case eval_read_file_info(File, Mod) of
+ {ok, Info} ->
+ Info#file_info.mtime;
+ _ ->
+ 0
+ end.
+
+do_file_size(File, Mod) ->
+ case eval_read_file_info(File, Mod) of
+ {ok, Info} ->
+ Info#file_info.size;
+ _ ->
+ 0
+ end.
+
+%%----------------------------------------------------------------------
+%% +type ensure_dir(X) -> ok | {error, Reason}.
+%% +type X = filename() | dirname()
+%% ensures that the directory name required to create D exists
+
+-spec ensure_dir(name()) -> 'ok' | {'error', posix()}.
+ensure_dir("/") ->
+ ok;
+ensure_dir(F) ->
+ Dir = filename:dirname(F),
+ case do_is_dir(Dir, file) of
+ true ->
+ ok;
+ false ->
+ ensure_dir(Dir),
+ file:make_dir(Dir)
+ end.
+
+
+%%%
+%%% Pattern matching using a compiled wildcard.
+%%%
+
+do_wildcard_1(Files, Pattern, Mod) ->
+ do_wildcard_2(Files, Pattern, [], Mod).
+
+do_wildcard_2([File|Rest], Pattern, Result, Mod) ->
+ do_wildcard_2(Rest, Pattern, do_wildcard_3(File, Pattern, Result, Mod), Mod);
+do_wildcard_2([], _, Result, _Mod) ->
+ Result.
+
+do_wildcard_3(Base, [Pattern|Rest], Result, Mod) ->
+ case do_list_dir(Base, Mod) of
+ {ok, Files0} ->
+ Files = lists:sort(Files0),
+ Matches = wildcard_4(Pattern, Files, Base, []),
+ do_wildcard_2(Matches, Rest, Result, Mod);
+ _ ->
+ Result
+ end;
+do_wildcard_3(Base, [], Result, _Mod) ->
+ [Base|Result].
+
+wildcard_4(Pattern, [File|Rest], Base, Result) ->
+ case wildcard_5(Pattern, File) of
+ true ->
+ wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
+ false ->
+ wildcard_4(Pattern, Rest, Base, Result)
+ end;
+wildcard_4(_Patt, [], _Base, Result) ->
+ Result.
+
+wildcard_5([question|Rest1], [_|Rest2]) ->
+ wildcard_5(Rest1, Rest2);
+wildcard_5([accept], _) ->
+ true;
+wildcard_5([star|Rest], File) ->
+ do_star(Rest, File);
+wildcard_5([{one_of, Ordset}|Rest], [C|File]) ->
+ case ordsets:is_element(C, Ordset) of
+ true -> wildcard_5(Rest, File);
+ false -> false
+ end;
+wildcard_5([{alt, Alts}], File) ->
+ do_alt(Alts, File);
+wildcard_5([C|Rest1], [C|Rest2]) when is_integer(C) ->
+ wildcard_5(Rest1, Rest2);
+wildcard_5([X|_], [Y|_]) when is_integer(X), is_integer(Y) ->
+ false;
+wildcard_5([], []) ->
+ true;
+wildcard_5([], [_|_]) ->
+ false;
+wildcard_5([_|_], []) ->
+ false.
+
+do_star(Pattern, [X|Rest]) ->
+ case wildcard_5(Pattern, [X|Rest]) of
+ true -> true;
+ false -> do_star(Pattern, Rest)
+ end;
+do_star(Pattern, []) ->
+ wildcard_5(Pattern, []).
+
+do_alt([Alt|Rest], File) ->
+ case wildcard_5(Alt, File) of
+ true -> true;
+ false -> do_alt(Rest, File)
+ end;
+do_alt([], _File) ->
+ false.
+
+do_list_dir(current, Mod) -> eval_list_dir(".", Mod);
+do_list_dir(Dir, Mod) -> eval_list_dir(Dir, Mod).
+
+join(current, File) -> File;
+join(Base, File) -> filename:join(Base, File).
+
+
+%%% Compiling a wildcard.
+
+compile_wildcard(Pattern) ->
+ ?HANDLE_ERROR(do_compile_wildcard(Pattern)).
+
+do_compile_wildcard(Pattern) ->
+ {compiled_wildcard,compile_wildcard_1(Pattern)}.
+
+compile_wildcard_1(Pattern) ->
+ [Root|Rest] = filename:split(Pattern),
+ case filename:pathtype(Root) of
+ relative ->
+ compile_wildcard_2([Root|Rest], current);
+ _ ->
+ compile_wildcard_2(Rest, [Root])
+ end.
+
+compile_wildcard_2([Part|Rest], Root) ->
+ case compile_part(Part) of
+ Part ->
+ compile_wildcard_2(Rest, join(Root, Part));
+ Pattern ->
+ compile_wildcard_3(Rest, [Pattern,Root])
+ end;
+compile_wildcard_2([], Root) -> {exists,Root}.
+
+compile_wildcard_3([Part|Rest], Result) ->
+ compile_wildcard_3(Rest, [compile_part(Part)|Result]);
+compile_wildcard_3([], Result) ->
+ lists:reverse(Result).
+
+compile_part(Part) ->
+ compile_part(Part, false, []).
+
+compile_part_to_sep(Part) ->
+ compile_part(Part, true, []).
+
+compile_part([], true, _) ->
+ error(missing_delimiter);
+compile_part([$,|Rest], true, Result) ->
+ {ok, $,, lists:reverse(Result), Rest};
+compile_part([$}|Rest], true, Result) ->
+ {ok, $}, lists:reverse(Result), Rest};
+compile_part([$?|Rest], Upto, Result) ->
+ compile_part(Rest, Upto, [question|Result]);
+compile_part([$*], Upto, Result) ->
+ compile_part([], Upto, [accept|Result]);
+compile_part([$*|Rest], Upto, Result) ->
+ compile_part(Rest, Upto, [star|Result]);
+compile_part([$[|Rest], Upto, Result) ->
+ case compile_charset(Rest, ordsets:new()) of
+ {ok, Charset, Rest1} ->
+ compile_part(Rest1, Upto, [Charset|Result]);
+ error ->
+ compile_part(Rest, Upto, [$[|Result])
+ end;
+compile_part([${|Rest], Upto, Result) ->
+ case compile_alt(Rest) of
+ {ok, Alt} ->
+ lists:reverse(Result, [Alt]);
+ error ->
+ compile_part(Rest, Upto, [${|Result])
+ end;
+compile_part([X|Rest], Upto, Result) ->
+ compile_part(Rest, Upto, [X|Result]);
+compile_part([], _Upto, Result) ->
+ lists:reverse(Result).
+
+compile_charset([$]|Rest], Ordset) ->
+ compile_charset1(Rest, ordsets:add_element($], Ordset));
+compile_charset([$-|Rest], Ordset) ->
+ compile_charset1(Rest, ordsets:add_element($-, Ordset));
+compile_charset([], _Ordset) ->
+ error;
+compile_charset(List, Ordset) ->
+ compile_charset1(List, Ordset).
+
+compile_charset1([Lower, $-, Upper|Rest], Ordset) when Lower =< Upper ->
+ compile_charset1(Rest, compile_range(Lower, Upper, Ordset));
+compile_charset1([$]|Rest], Ordset) ->
+ {ok, {one_of, Ordset}, Rest};
+compile_charset1([X|Rest], Ordset) ->
+ compile_charset1(Rest, ordsets:add_element(X, Ordset));
+compile_charset1([], _Ordset) ->
+ error.
+
+compile_range(Lower, Current, Ordset) when Lower =< Current ->
+ compile_range(Lower, Current-1, ordsets:add_element(Current, Ordset));
+compile_range(_, _, Ordset) ->
+ Ordset.
+
+compile_alt(Pattern) ->
+ compile_alt(Pattern, []).
+
+compile_alt(Pattern, Result) ->
+ case compile_part_to_sep(Pattern) of
+ {ok, $,, AltPattern, Rest} ->
+ compile_alt(Rest, [AltPattern|Result]);
+ {ok, $}, AltPattern, Rest} ->
+ NewResult = [AltPattern|Result],
+ RestPattern = compile_part(Rest),
+ {ok, {alt, [Alt++RestPattern || Alt <- NewResult]}};
+ Pattern ->
+ error
+ end.
+
+error(Reason) ->
+ erlang:error({badpattern,Reason}).
+
+eval_read_file_info(File, file) ->
+ file:read_file_info(File);
+eval_read_file_info(File, erl_prim_loader) ->
+ case erl_prim_loader:read_file_info(File) of
+ error -> {error, erl_prim_loader};
+ Res-> Res
+ end;
+eval_read_file_info(File, Mod) ->
+ Mod:read_file_info(File).
+
+eval_list_dir(Dir, file) ->
+ file:list_dir(Dir);
+eval_list_dir(Dir, erl_prim_loader) ->
+ case erl_prim_loader:list_dir(Dir) of
+ error -> {error, erl_prim_loader};
+ Res-> Res
+ end;
+eval_list_dir(Dir, Mod) ->
+ Mod:list_dir(Dir).
diff --git a/lib/stdlib/src/filename.erl b/lib/stdlib/src/filename.erl
new file mode 100644
index 0000000000..cd26b2e219
--- /dev/null
+++ b/lib/stdlib/src/filename.erl
@@ -0,0 +1,787 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(filename).
+
+%% Purpose: Provides generic manipulation of filenames.
+%%
+%% Generally, these functions accept filenames in the native format
+%% for the current operating system (Unix or Windows).
+%% Deep characters lists (as returned by io_lib:format()) are accepted;
+%% resulting strings will always be flat.
+%%
+%% Implementation note: We used to only flatten if the list turned out
+%% to be deep. Now that atoms are allowed in deep lists, in most cases
+%% we flatten the arguments immediately on function entry as that makes
+%% it easier to ensure that the code works.
+
+-export([absname/1, absname/2, absname_join/2,
+ basename/1, basename/2, dirname/1,
+ extension/1, join/1, join/2, pathtype/1,
+ rootname/1, rootname/2, split/1, nativename/1]).
+-export([find_src/1, find_src/2, flatten/1]).
+
+%% Undocumented and unsupported exports.
+-export([append/2]).
+
+-include_lib("kernel/include/file.hrl").
+
+%% Converts a relative filename to an absolute filename
+%% or the filename itself if it already is an absolute filename
+%% Note that no attempt is made to create the most beatiful
+%% absolute name since this can give incorrect results on
+%% file systems which allows links.
+%% Examples:
+%% Assume (for UNIX) current directory "/usr/local"
+%% Assume (for WIN32) current directory "D:/usr/local"
+%%
+%% (for Unix) : absname("foo") -> "/usr/local/foo"
+%% (for WIN32): absname("foo") -> "D:/usr/local/foo"
+%% (for Unix) : absname("../x") -> "/usr/local/../x"
+%% (for WIN32): absname("../x") -> "D:/usr/local/../x"
+%% (for Unix) : absname("/") -> "/"
+%% (for WIN32): absname("/") -> "D:/"
+
+-spec absname(name()) -> string().
+absname(Name) ->
+ {ok, Cwd} = file:get_cwd(),
+ absname(Name, Cwd).
+
+-spec absname(name(), string()) -> string().
+absname(Name, AbsBase) ->
+ case pathtype(Name) of
+ relative ->
+ absname_join(AbsBase, Name);
+ absolute ->
+ %% We must flatten the filename before passing it into join/1,
+ %% or we will get slashes inserted into the wrong places.
+ join([flatten(Name)]);
+ volumerelative ->
+ absname_vr(split(Name), split(AbsBase), AbsBase)
+ end.
+
+%% Handles volumerelative names (on Windows only).
+
+absname_vr(["/"|Rest1], [Volume|_], _AbsBase) ->
+ %% Absolute path on current drive.
+ join([Volume|Rest1]);
+absname_vr([[X, $:]|Rest1], [[X|_]|_], AbsBase) ->
+ %% Relative to current directory on current drive.
+ absname(join(Rest1), AbsBase);
+absname_vr([[X, $:]|Name], _, _AbsBase) ->
+ %% Relative to current directory on another drive.
+ Dcwd =
+ case file:get_cwd([X, $:]) of
+ {ok, Dir} -> Dir;
+ {error, _} -> [X, $:, $/]
+ end,
+ absname(join(Name), Dcwd).
+
+%% Joins a relative filename to an absolute base. For VxWorks the
+%% resulting name is fixed to minimize the length by collapsing
+%% ".." directories.
+%% For other systems this is just a join/2, but assumes that
+%% AbsBase must be absolute and Name must be relative.
+
+-spec absname_join(string(), name()) -> string().
+absname_join(AbsBase, Name) ->
+ case major_os_type() of
+ vxworks ->
+ absname_pretty(AbsBase, split(Name), lists:reverse(split(AbsBase)));
+ _Else ->
+ join(AbsBase, flatten(Name))
+ end.
+
+%% Handles absolute filenames for VxWorks - these are 'pretty-printed',
+%% since a C function call chdir("/erlang/lib/../bin") really sets
+%% cwd to '/erlang/lib/../bin' which also works, but the long term
+%% effect is potentially not so good ...
+%%
+%% absname_pretty("../bin", "/erlang/lib") -> "/erlang/bin"
+%% absname_pretty("../../../..", "/erlang") -> "/erlang"
+
+absname_pretty(Abspath, Relpath, []) ->
+ %% AbsBase _must_ begin with a vxworks device name
+ {device, _Rest, Dev} = vxworks_first(Abspath),
+ absname_pretty(Abspath, Relpath, [lists:reverse(Dev)]);
+absname_pretty(_Abspath, [], AbsBase) ->
+ join(lists:reverse(AbsBase));
+absname_pretty(Abspath, [[$.]|Rest], AbsBase) ->
+ absname_pretty(Abspath, Rest, AbsBase);
+absname_pretty(Abspath, [[$.,$.]|Rest], [_|AbsRest]) ->
+ absname_pretty(Abspath, Rest, AbsRest);
+absname_pretty(Abspath, [First|Rest], AbsBase) ->
+ absname_pretty(Abspath, Rest, [First|AbsBase]).
+
+%% Returns the part of the filename after the last directory separator,
+%% or the filename itself if it has no separators.
+%%
+%% Examples: basename("foo") -> "foo"
+%% basename("/usr/foo") -> "foo"
+%% basename("/usr/foo/") -> "foo" (trailing slashes ignored)
+%% basename("/") -> []
+
+-spec basename(name()) -> string().
+basename(Name0) ->
+ Name = flatten(Name0),
+ {DirSep2, DrvSep} = separators(),
+ basename1(skip_prefix(Name, DrvSep), [], DirSep2).
+
+basename1([$/|[]], Tail, DirSep2) ->
+ basename1([], Tail, DirSep2);
+basename1([$/|Rest], _Tail, DirSep2) ->
+ basename1(Rest, [], DirSep2);
+basename1([[_|_]=List|Rest], Tail, DirSep2) ->
+ basename1(List++Rest, Tail, DirSep2);
+basename1([DirSep2|Rest], Tail, DirSep2) when is_integer(DirSep2) ->
+ basename1([$/|Rest], Tail, DirSep2);
+basename1([Char|Rest], Tail, DirSep2) when is_integer(Char) ->
+ basename1(Rest, [Char|Tail], DirSep2);
+basename1([], Tail, _DirSep2) ->
+ lists:reverse(Tail).
+
+skip_prefix(Name, false) -> % No prefix for unix, but for VxWorks.
+ case major_os_type() of
+ vxworks ->
+ case vxworks_first(Name) of
+ {device, Rest, _Device} ->
+ Rest;
+ {not_device, _Rest, _First} ->
+ Name
+ end;
+ _Else ->
+ Name
+ end;
+skip_prefix(Name, DrvSep) ->
+ skip_prefix1(Name, DrvSep).
+
+skip_prefix1([L, DrvSep|Name], DrvSep) when is_integer(L) ->
+ Name;
+skip_prefix1([L], _) when is_integer(L) ->
+ [L];
+skip_prefix1(Name, _) ->
+ Name.
+
+%% Returns the last component of the filename, with the given
+%% extension stripped. Use this function if you want
+%% to remove an extension that might or might not be there.
+%% Use rootname(basename(File)) if you want to remove an extension
+%% that you know exists, but you are not sure which one it is.
+%%
+%% Example: basename("~/src/kalle.erl", ".erl") -> "kalle"
+%% basename("~/src/kalle.jam", ".erl") -> "kalle.jam"
+%% basename("~/src/kalle.old.erl", ".erl") -> "kalle.old"
+%%
+%% rootname(basename("xxx.jam")) -> "xxx"
+%% rootname(basename("xxx.erl")) -> "xxx"
+
+-spec basename(name(), name()) -> string().
+basename(Name0, Ext0) ->
+ Name = flatten(Name0),
+ Ext = flatten(Ext0),
+ {DirSep2,DrvSep} = separators(),
+ NoPrefix = skip_prefix(Name, DrvSep),
+ basename(NoPrefix, Ext, [], DirSep2).
+
+basename(Ext, Ext, Tail, _DrvSep2) ->
+ lists:reverse(Tail);
+basename([$/|[]], Ext, Tail, DrvSep2) ->
+ basename([], Ext, Tail, DrvSep2);
+basename([$/|Rest], Ext, _Tail, DrvSep2) ->
+ basename(Rest, Ext, [], DrvSep2);
+basename([$\\|Rest], Ext, Tail, DirSep2) when is_integer(DirSep2) ->
+ basename([$/|Rest], Ext, Tail, DirSep2);
+basename([Char|Rest], Ext, Tail, DrvSep2) when is_integer(Char) ->
+ basename(Rest, Ext, [Char|Tail], DrvSep2);
+basename([], _Ext, Tail, _DrvSep2) ->
+ lists:reverse(Tail).
+
+%% Returns the directory part of a pathname.
+%%
+%% Example: dirname("/usr/src/kalle.erl") -> "/usr/src",
+%% dirname("kalle.erl") -> "."
+
+-spec dirname(name()) -> string().
+dirname(Name0) ->
+ Name = flatten(Name0),
+ case os:type() of
+ vxworks ->
+ {Devicep, Restname, FirstComp} = vxworks_first(Name),
+ case Devicep of
+ device ->
+ dirname(Restname, FirstComp, [], separators());
+ _ ->
+ dirname(Name, [], [], separators())
+ end;
+ _ ->
+ dirname(Name, [], [], separators())
+ end.
+
+dirname([[_|_]=List|Rest], Dir, File, Seps) ->
+ dirname(List++Rest, Dir, File, Seps);
+dirname([$/|Rest], Dir, File, Seps) ->
+ dirname(Rest, File++Dir, [$/], Seps);
+dirname([DirSep|Rest], Dir, File, {DirSep,_}=Seps) when is_integer(DirSep) ->
+ dirname(Rest, File++Dir, [$/], Seps);
+dirname([Dl,DrvSep|Rest], [], [], {_,DrvSep}=Seps)
+ when is_integer(DrvSep), ((($a =< Dl) and (Dl =< $z)) or
+ (($A =< Dl) and (Dl =< $Z))) ->
+ dirname(Rest, [DrvSep,Dl], [], Seps);
+dirname([Char|Rest], Dir, File, Seps) when is_integer(Char) ->
+ dirname(Rest, Dir, [Char|File], Seps);
+dirname([], [], File, _Seps) ->
+ case lists:reverse(File) of
+ [$/|_] -> [$/];
+ _ -> "."
+ end;
+dirname([], [$/|Rest], File, Seps) ->
+ dirname([], Rest, File, Seps);
+dirname([], [DrvSep,Dl], File, {_,DrvSep}) ->
+ case lists:reverse(File) of
+ [$/|_] -> [Dl,DrvSep,$/];
+ _ -> [Dl,DrvSep]
+ end;
+dirname([], Dir, _, _) ->
+ lists:reverse(Dir).
+
+%% Given a filename string, returns the file extension,
+%% including the period. Returns an empty list if there
+%% is no extension.
+%%
+%% Example: extension("foo.erl") -> ".erl"
+%% extension("jam.src/kalle") -> ""
+%%
+%% On Windows: fn:dirname("\\usr\\src/kalle.erl") -> "/usr/src"
+
+-spec extension(name()) -> string().
+extension(Name0) ->
+ Name = flatten(Name0),
+ extension(Name, [], major_os_type()).
+
+extension([$.|Rest], _Result, OsType) ->
+ extension(Rest, [$.], OsType);
+extension([Char|Rest], [], OsType) when is_integer(Char) ->
+ extension(Rest, [], OsType);
+extension([$/|Rest], _Result, OsType) ->
+ extension(Rest, [], OsType);
+extension([$\\|Rest], _Result, win32) ->
+ extension(Rest, [], win32);
+extension([$\\|Rest], _Result, vxworks) ->
+ extension(Rest, [], vxworks);
+extension([Char|Rest], Result, OsType) when is_integer(Char) ->
+ extension(Rest, [Char|Result], OsType);
+extension([], Result, _OsType) ->
+ lists:reverse(Result).
+
+%% Joins a list of filenames with directory separators.
+
+-spec join([string()]) -> string().
+join([Name1, Name2|Rest]) ->
+ join([join(Name1, Name2)|Rest]);
+join([Name]) when is_list(Name) ->
+ join1(Name, [], [], major_os_type());
+join([Name]) when is_atom(Name) ->
+ join([atom_to_list(Name)]).
+
+%% Joins two filenames with directory separators.
+
+-spec join(string(), string()) -> string().
+join(Name1, Name2) when is_list(Name1), is_list(Name2) ->
+ OsType = major_os_type(),
+ case pathtype(Name2) of
+ relative -> join1(Name1, Name2, [], OsType);
+ _Other -> join1(Name2, [], [], OsType)
+ end;
+join(Name1, Name2) when is_atom(Name1) ->
+ join(atom_to_list(Name1), Name2);
+join(Name1, Name2) when is_atom(Name2) ->
+ join(Name1, atom_to_list(Name2)).
+
+%% Internal function to join an absolute name and a relative name.
+%% It is the responsibility of the caller to ensure that RelativeName
+%% is relative.
+
+join1([UcLetter, $:|Rest], RelativeName, [], win32)
+when is_integer(UcLetter), UcLetter >= $A, UcLetter =< $Z ->
+ join1(Rest, RelativeName, [$:, UcLetter+$a-$A], win32);
+join1([$\\|Rest], RelativeName, Result, win32) ->
+ join1([$/|Rest], RelativeName, Result, win32);
+join1([$\\|Rest], RelativeName, Result, vxworks) ->
+ join1([$/|Rest], RelativeName, Result, vxworks);
+join1([$/|Rest], RelativeName, [$., $/|Result], OsType) ->
+ join1(Rest, RelativeName, [$/|Result], OsType);
+join1([$/|Rest], RelativeName, [$/|Result], OsType) ->
+ join1(Rest, RelativeName, [$/|Result], OsType);
+join1([], [], Result, OsType) ->
+ maybe_remove_dirsep(Result, OsType);
+join1([], RelativeName, [$:|Rest], win32) ->
+ join1(RelativeName, [], [$:|Rest], win32);
+join1([], RelativeName, [$/|Result], OsType) ->
+ join1(RelativeName, [], [$/|Result], OsType);
+join1([], RelativeName, Result, OsType) ->
+ join1(RelativeName, [], [$/|Result], OsType);
+join1([[_|_]=List|Rest], RelativeName, Result, OsType) ->
+ join1(List++Rest, RelativeName, Result, OsType);
+join1([[]|Rest], RelativeName, Result, OsType) ->
+ join1(Rest, RelativeName, Result, OsType);
+join1([Char|Rest], RelativeName, Result, OsType) when is_integer(Char) ->
+ join1(Rest, RelativeName, [Char|Result], OsType);
+join1([Atom|Rest], RelativeName, Result, OsType) when is_atom(Atom) ->
+ join1(atom_to_list(Atom)++Rest, RelativeName, Result, OsType).
+
+maybe_remove_dirsep([$/, $:, Letter], win32) ->
+ [Letter, $:, $/];
+maybe_remove_dirsep([$/], _) ->
+ [$/];
+maybe_remove_dirsep([$/|Name], _) ->
+ lists:reverse(Name);
+maybe_remove_dirsep(Name, _) ->
+ lists:reverse(Name).
+
+%% Appends a directory separator and a pathname component to
+%% a given base directory, which is is assumed to be normalised
+%% by a previous call to join/{1,2}.
+
+-spec append(string(), name()) -> string().
+append(Dir, Name) ->
+ Dir ++ [$/|Name].
+
+%% Returns one of absolute, relative or volumerelative.
+%%
+%% absolute The pathname refers to a specific file on a specific
+%% volume. Example: /usr/local/bin/ (on Unix),
+%% h:/port_test (on Windows).
+%% relative The pathname is relative to the current working directory
+%% on the current volume. Example: foo/bar, ../src
+%% volumerelative The pathname is relative to the current working directory
+%% on the specified volume, or is a specific file on the
+%% current working volume. (Windows only)
+%% Example: a:bar.erl, /temp/foo.erl
+
+-spec pathtype(name()) -> 'absolute' | 'relative' | 'volumerelative'.
+pathtype(Atom) when is_atom(Atom) ->
+ pathtype(atom_to_list(Atom));
+pathtype(Name) when is_list(Name) ->
+ case os:type() of
+ {unix, _} -> unix_pathtype(Name);
+ {win32, _} -> win32_pathtype(Name);
+ vxworks -> case vxworks_first(Name) of
+ {device, _Rest, _Dev} ->
+ absolute;
+ _ ->
+ relative
+ end;
+ {ose,_} -> unix_pathtype(Name)
+ end.
+
+unix_pathtype([$/|_]) ->
+ absolute;
+unix_pathtype([List|Rest]) when is_list(List) ->
+ unix_pathtype(List++Rest);
+unix_pathtype([Atom|Rest]) when is_atom(Atom) ->
+ unix_pathtype(atom_to_list(Atom)++Rest);
+unix_pathtype(_) ->
+ relative.
+
+win32_pathtype([List|Rest]) when is_list(List) ->
+ win32_pathtype(List++Rest);
+win32_pathtype([Atom|Rest]) when is_atom(Atom) ->
+ win32_pathtype(atom_to_list(Atom)++Rest);
+win32_pathtype([Char, List|Rest]) when is_list(List) ->
+ win32_pathtype([Char|List++Rest]);
+win32_pathtype([$/, $/|_]) -> absolute;
+win32_pathtype([$\\, $/|_]) -> absolute;
+win32_pathtype([$/, $\\|_]) -> absolute;
+win32_pathtype([$\\, $\\|_]) -> absolute;
+win32_pathtype([$/|_]) -> volumerelative;
+win32_pathtype([$\\|_]) -> volumerelative;
+win32_pathtype([C1, C2, List|Rest]) when is_list(List) ->
+ pathtype([C1, C2|List++Rest]);
+win32_pathtype([_Letter, $:, $/|_]) -> absolute;
+win32_pathtype([_Letter, $:, $\\|_]) -> absolute;
+win32_pathtype([_Letter, $:|_]) -> volumerelative;
+win32_pathtype(_) -> relative.
+
+%% Returns all characters in the filename, except the extension.
+%%
+%% Examples: rootname("/jam.src/kalle") -> "/jam.src/kalle"
+%% rootname("/jam.src/foo.erl") -> "/jam.src/foo"
+
+-spec rootname(name()) -> string().
+rootname(Name0) ->
+ Name = flatten(Name0),
+ rootname(Name, [], [], major_os_type()).
+
+rootname([$/|Rest], Root, Ext, OsType) ->
+ rootname(Rest, [$/]++Ext++Root, [], OsType);
+rootname([$\\|Rest], Root, Ext, win32) ->
+ rootname(Rest, [$/]++Ext++Root, [], win32);
+rootname([$\\|Rest], Root, Ext, vxworks) ->
+ rootname(Rest, [$/]++Ext++Root, [], vxworks);
+rootname([$.|Rest], Root, [], OsType) ->
+ rootname(Rest, Root, ".", OsType);
+rootname([$.|Rest], Root, Ext, OsType) ->
+ rootname(Rest, Ext++Root, ".", OsType);
+rootname([Char|Rest], Root, [], OsType) when is_integer(Char) ->
+ rootname(Rest, [Char|Root], [], OsType);
+rootname([Char|Rest], Root, Ext, OsType) when is_integer(Char) ->
+ rootname(Rest, Root, [Char|Ext], OsType);
+rootname([], Root, _Ext, _OsType) ->
+ lists:reverse(Root).
+
+%% Returns all characters in the filename, except the given extension.
+%% If the filename has another extension, the complete filename is
+%% returned.
+%%
+%% Examples: rootname("/jam.src/kalle.jam", ".erl") -> "/jam.src/kalle.jam"
+%% rootname("/jam.src/foo.erl", ".erl") -> "/jam.src/foo"
+
+-spec rootname(name(), name()) -> string().
+rootname(Name0, Ext0) ->
+ Name = flatten(Name0),
+ Ext = flatten(Ext0),
+ rootname2(Name, Ext, []).
+
+rootname2(Ext, Ext, Result) ->
+ lists:reverse(Result);
+rootname2([], _Ext, Result) ->
+ lists:reverse(Result);
+rootname2([Char|Rest], Ext, Result) when is_integer(Char) ->
+ rootname2(Rest, Ext, [Char|Result]).
+
+%% Returns a list whose elements are the path components in the filename.
+%%
+%% Examples:
+%% split("/usr/local/bin") -> ["/", "usr", "local", "bin"]
+%% split("foo/bar") -> ["foo", "bar"]
+%% split("a:\\msdev\\include") -> ["a:/", "msdev", "include"]
+
+-spec split(name()) -> [string()].
+split(Name0) ->
+ Name = flatten(Name0),
+ case os:type() of
+ {unix, _} -> unix_split(Name);
+ {win32, _} -> win32_split(Name);
+ vxworks -> vxworks_split(Name);
+ {ose,_} -> unix_split(Name)
+ end.
+
+%% If a VxWorks filename starts with '[/\].*[^/\]' '[/\].*:' or '.*:'
+%% that part of the filename is considered a device.
+%% The rest of the name is interpreted exactly as for win32.
+
+%% XXX - dirty solution to make filename:split([]) return the same thing on
+%% VxWorks as on unix and win32.
+vxworks_split([]) ->
+ [];
+vxworks_split(L) ->
+ {_Devicep, Rest, FirstComp} = vxworks_first(L),
+ split(Rest, [], [lists:reverse(FirstComp)], win32).
+
+unix_split(Name) ->
+ split(Name, [], unix).
+
+win32_split([$\\|Rest]) ->
+ win32_split([$/|Rest]);
+win32_split([X, $\\|Rest]) when is_integer(X) ->
+ win32_split([X, $/|Rest]);
+win32_split([X, Y, $\\|Rest]) when is_integer(X), is_integer(Y) ->
+ win32_split([X, Y, $/|Rest]);
+win32_split([$/, $/|Rest]) ->
+ split(Rest, [], [[$/, $/]]);
+win32_split([UcLetter, $:|Rest]) when UcLetter >= $A, UcLetter =< $Z ->
+ win32_split([UcLetter+$a-$A, $:|Rest]);
+win32_split([Letter, $:, $/|Rest]) ->
+ split(Rest, [], [[Letter, $:, $/]], win32);
+win32_split([Letter, $:|Rest]) ->
+ split(Rest, [], [[Letter, $:]], win32);
+win32_split(Name) ->
+ split(Name, [], win32).
+
+split([$/|Rest], Components, OsType) ->
+ split(Rest, [], [[$/]|Components], OsType);
+split([$\\|Rest], Components, win32) ->
+ split(Rest, [], [[$/]|Components], win32);
+split(RelativeName, Components, OsType) ->
+ split(RelativeName, [], Components, OsType).
+
+split([$\\|Rest], Comp, Components, win32) ->
+ split([$/|Rest], Comp, Components, win32);
+split([$/|Rest], [], Components, OsType) ->
+ split(Rest, [], Components, OsType);
+split([$/|Rest], Comp, Components, OsType) ->
+ split(Rest, [], [lists:reverse(Comp)|Components], OsType);
+split([Char|Rest], Comp, Components, OsType) when is_integer(Char) ->
+ split(Rest, [Char|Comp], Components, OsType);
+split([List|Rest], Comp, Components, OsType) when is_list(List) ->
+ split(List++Rest, Comp, Components, OsType);
+split([], [], Components, _OsType) ->
+ lists:reverse(Components);
+split([], Comp, Components, OsType) ->
+ split([], [], [lists:reverse(Comp)|Components], OsType).
+
+%% Converts a filename to a form accepedt by the command shell and native
+%% applications on the current platform. On Windows, forward slashes
+%% will be converted to backslashes. On all platforms, the
+%% name will be normalized as done by join/1.
+
+-spec nativename(string()) -> string().
+nativename(Name0) ->
+ Name = join([Name0]), %Normalize.
+ case os:type() of
+ {win32, _} -> win32_nativename(Name);
+ _ -> Name
+ end.
+
+win32_nativename([$/|Rest]) ->
+ [$\\|win32_nativename(Rest)];
+win32_nativename([C|Rest]) ->
+ [C|win32_nativename(Rest)];
+win32_nativename([]) ->
+ [].
+
+separators() ->
+ case os:type() of
+ {unix, _} -> {false, false};
+ {win32, _} -> {$\\, $:};
+ vxworks -> {$\\, false};
+ {ose,_} -> {false, false}
+ end.
+
+
+%% find_src(Module) --
+%% find_src(Module, Rules) --
+%%
+%% Finds the source file name and compilation options for a compiled
+%% module. The result can be fed to compile:file/2 to compile the
+%% file again.
+%%
+%% The Module argument (which can be a string or an atom) specifies
+%% either the module name or the path to the source code, with or
+%% without the ".erl" extension. In either case the module must be
+%% known by the code manager, i.e. code:which/1 should succeed.
+%%
+%% Rules describes how the source directory should be found given
+%% the directory for the object code. Each rule is on the form
+%% {BinSuffix, SourceSuffix}, and is interpreted like this:
+%% If the end of directory name where the object is located matches
+%% BinSuffix, then the suffix will be replaced with SourceSuffix
+%% in the directory name. If the source file in the resulting
+%% directory, the next rule will be tried.
+%%
+%% Returns: {SourceFile, Options}
+%%
+%% SourceFile is the absolute path to the source file (but without the ".erl"
+%% extension) and Options are the necessary options to compile the file
+%% with compile:file/2, but doesn't include options like 'report' or
+%% 'verbose' that doesn't change the way code is generated.
+%% The paths in the {outdir, Path} and {i, Path} options are guaranteed
+%% to be absolute.
+
+-type rule() :: {string(), string()}.
+-type ecode() :: 'non_existing' | 'preloaded' | 'interpreted'.
+-type option() :: {'i', string()} | {'outdir', string()} | {'d', atom()}.
+
+-spec find_src(atom() | string()) ->
+ {string(), [option()]} | {'error', {ecode(), atom()}}.
+find_src(Mod) ->
+ Default = [{"", ""}, {"ebin", "src"}, {"ebin", "esrc"}],
+ Rules =
+ case application:get_env(kernel, source_search_rules) of
+ undefined -> Default;
+ {ok, []} -> Default;
+ {ok, R} when is_list(R) -> R
+ end,
+ find_src(Mod, Rules).
+
+-spec find_src(atom() | string(), [rule()]) ->
+ {string(), [option()]} | {'error', {ecode(), atom()}}.
+find_src(Mod, Rules) when is_atom(Mod) ->
+ find_src(atom_to_list(Mod), Rules);
+find_src(File0, Rules) when is_list(File0) ->
+ Mod = list_to_atom(basename(File0, ".erl")),
+ File = rootname(File0, ".erl"),
+ case readable_file(File++".erl") of
+ true ->
+ try_file(File, Mod, Rules);
+ false ->
+ try_file(undefined, Mod, Rules)
+ end.
+
+try_file(File, Mod, Rules) ->
+ case code:which(Mod) of
+ Possibly_Rel_Path when is_list(Possibly_Rel_Path) ->
+ {ok, Cwd} = file:get_cwd(),
+ Path = join(Cwd, Possibly_Rel_Path),
+ try_file(File, Path, Mod, Rules);
+ Ecode when is_atom(Ecode) -> % Ecode :: ecode()
+ {error, {Ecode, Mod}}
+ end.
+
+%% At this point, the Mod is known to be valid.
+%% If the source name is not known, find it.
+%% Then get the compilation options.
+%% Returns: {SrcFile, Options}
+
+try_file(undefined, ObjFilename, Mod, Rules) ->
+ case get_source_file(ObjFilename, Mod, Rules) of
+ {ok, File} -> try_file(File, ObjFilename, Mod, Rules);
+ Error -> Error
+ end;
+try_file(Src, _ObjFilename, Mod, _Rules) ->
+ List = Mod:module_info(compile),
+ {options, Options} = lists:keyfind(options, 1, List),
+ {ok, Cwd} = file:get_cwd(),
+ AbsPath = make_abs_path(Cwd, Src),
+ {AbsPath, filter_options(dirname(AbsPath), Options, [])}.
+
+%% Filters the options.
+%%
+%% 1) Remove options that have no effect on the generated code,
+%% such as report and verbose.
+%%
+%% 2) The paths found in {i, Path} and {outdir, Path} are converted
+%% to absolute paths. When doing this, it is assumed that relatives
+%% paths are relative to directory where the source code is located.
+%% This is not necessarily true. It would be safer if the compiler
+%% would emit absolute paths in the first place.
+
+filter_options(Base, [{outdir, Path}|Rest], Result) ->
+ filter_options(Base, Rest, [{outdir, make_abs_path(Base, Path)}|Result]);
+filter_options(Base, [{i, Path}|Rest], Result) ->
+ filter_options(Base, Rest, [{i, make_abs_path(Base, Path)}|Result]);
+filter_options(Base, [Option|Rest], Result) when Option =:= trace ->
+ filter_options(Base, Rest, [Option|Result]);
+filter_options(Base, [Option|Rest], Result) when Option =:= export_all ->
+ filter_options(Base, Rest, [Option|Result]);
+filter_options(Base, [Option|Rest], Result) when Option =:= binary ->
+ filter_options(Base, Rest, [Option|Result]);
+filter_options(Base, [Option|Rest], Result) when Option =:= fast ->
+ filter_options(Base, Rest, [Option|Result]);
+filter_options(Base, [Tuple|Rest], Result) when element(1, Tuple) =:= d ->
+ filter_options(Base, Rest, [Tuple|Result]);
+filter_options(Base, [Tuple|Rest], Result)
+when element(1, Tuple) =:= parse_transform ->
+ filter_options(Base, Rest, [Tuple|Result]);
+filter_options(Base, [_|Rest], Result) ->
+ filter_options(Base, Rest, Result);
+filter_options(_Base, [], Result) ->
+ Result.
+
+%% Gets the source file given path of object code and module name.
+
+get_source_file(Obj, Mod, Rules) ->
+ case catch Mod:module_info(source_file) of
+ {'EXIT', _Reason} ->
+ source_by_rules(dirname(Obj), packages:last(Mod), Rules);
+ File ->
+ {ok, File}
+ end.
+
+source_by_rules(Dir, Base, [{From, To}|Rest]) ->
+ case try_rule(Dir, Base, From, To) of
+ {ok, File} -> {ok, File};
+ error -> source_by_rules(Dir, Base, Rest)
+ end;
+source_by_rules(_Dir, _Base, []) ->
+ {error, source_file_not_found}.
+
+try_rule(Dir, Base, From, To) ->
+ case lists:suffix(From, Dir) of
+ true ->
+ NewDir = lists:sublist(Dir, 1, length(Dir)-length(From))++To,
+ Src = join(NewDir, Base),
+ case readable_file(Src++".erl") of
+ true -> {ok, Src};
+ false -> error
+ end;
+ false ->
+ error
+ end.
+
+readable_file(File) ->
+ case file:read_file_info(File) of
+ {ok, #file_info{type=regular, access=read}} ->
+ true;
+ {ok, #file_info{type=regular, access=read_write}} ->
+ true;
+ _Other ->
+ false
+ end.
+
+make_abs_path(BasePath, Path) ->
+ join(BasePath, Path).
+
+major_os_type() ->
+ case os:type() of
+ {OsT, _} -> OsT;
+ OsT -> OsT
+ end.
+
+%% Need to take care of the first pathname component separately
+%% due to VxWorks less than good device naming rules.
+%% (i.e. this is VxWorks specific ...)
+%% The following four all starts with device names
+%% elrond:/foo -> elrond:
+%% elrond:\\foo.bar -> elrond:
+%% /DISK1:foo -> /DISK1:
+%% /usr/include -> /usr
+%% This one doesn't:
+%% foo/bar
+
+vxworks_first([]) ->
+ {not_device, [], []};
+vxworks_first([$/|T]) ->
+ vxworks_first2(device, T, [$/]);
+vxworks_first([$\\|T]) ->
+ vxworks_first2(device, T, [$/]);
+vxworks_first([H|T]) when is_list(H) ->
+ vxworks_first(H++T);
+vxworks_first([H|T]) ->
+ vxworks_first2(not_device, T, [H]).
+
+vxworks_first2(Devicep, [], FirstComp) ->
+ {Devicep, [], FirstComp};
+vxworks_first2(Devicep, [$/|T], FirstComp) ->
+ {Devicep, [$/|T], FirstComp};
+vxworks_first2(Devicep, [$\\|T], FirstComp) ->
+ {Devicep, [$/|T], FirstComp};
+vxworks_first2(_Devicep, [$:|T], FirstComp)->
+ {device, T, [$:|FirstComp]};
+vxworks_first2(Devicep, [H|T], FirstComp) when is_list(H) ->
+ vxworks_first2(Devicep, H++T, FirstComp);
+vxworks_first2(Devicep, [H|T], FirstComp) ->
+ vxworks_first2(Devicep, T, [H|FirstComp]).
+
+%% flatten(List)
+%% Flatten a list, also accepting atoms.
+
+-spec flatten(name()) -> string().
+flatten(List) ->
+ do_flatten(List, []).
+
+do_flatten([H|T], Tail) when is_list(H) ->
+ do_flatten(H, do_flatten(T, Tail));
+do_flatten([H|T], Tail) when is_atom(H) ->
+ atom_to_list(H) ++ do_flatten(T, Tail);
+do_flatten([H|T], Tail) ->
+ [H|do_flatten(T, Tail)];
+do_flatten([], Tail) ->
+ Tail;
+do_flatten(Atom, Tail) when is_atom(Atom) ->
+ atom_to_list(Atom) ++ flatten(Tail).
diff --git a/lib/stdlib/src/gb_sets.erl b/lib/stdlib/src/gb_sets.erl
new file mode 100644
index 0000000000..086dc79b46
--- /dev/null
+++ b/lib/stdlib/src/gb_sets.erl
@@ -0,0 +1,812 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% =====================================================================
+%% Ordered Sets implemented as General Balanced Trees
+%%
+%% Copyright (C) 1999-2001 Richard Carlsson
+%%
+%% An implementation of ordered sets using Prof. Arne Andersson's
+%% General Balanced Trees. This can be much more efficient than using
+%% ordered lists, for larger sets, but depends on the application. See
+%% notes below for details.
+%% ---------------------------------------------------------------------
+%% Notes:
+%%
+%% The complexity on set operations is bounded by either O(|S|) or O(|T|
+%% * log(|S|)), where S is the largest given set, depending on which is
+%% fastest for any particular function call. For operating on sets of
+%% almost equal size, this implementation is about 3 times slower than
+%% using ordered-list sets directly. For sets of very different sizes,
+%% however, this solution can be arbitrarily much faster; in practical
+%% cases, often between 10 and 100 times. This implementation is
+%% particularly suited for ackumulating elements a few at a time,
+%% building up a large set (more than 100-200 elements), and repeatedly
+%% testing for membership in the current set.
+%%
+%% As with normal tree structures, lookup (membership testing),
+%% insertion and deletion have logarithmic complexity.
+%%
+%% Operations:
+%%
+%% - empty(): returns empty set.
+%%
+%% Alias: new(), for compatibility with `sets'.
+%%
+%% - is_empty(S): returns 'true' if S is an empty set, and 'false'
+%% otherwise.
+%%
+%% - size(S): returns the number of nodes in the set as an integer.
+%% Returns 0 (zero) if the set is empty.
+%%
+%% - singleton(X): returns a set containing only the element X.
+%%
+%% - is_member(X, S): returns `true' if element X is a member of set S,
+%% and `false' otherwise.
+%%
+%% Alias: is_element(), for compatibility with `sets'.
+%%
+%% - insert(X, S): inserts element X into set S; returns the new set.
+%% *Assumes that the element is not present in S.*
+%%
+%% - add(X, S): adds element X to set S; returns the new set. If X is
+%% already an element in S, nothing is changed.
+%%
+%% Alias: add_element(), for compatibility with `sets'.
+%%
+%% - delete(X, S): removes element X from set S; returns new set.
+%% Assumes that the element exists in the set.
+%%
+%% - delete_any(X, S): removes key X from set S if the key is present
+%% in the set, otherwise does nothing; returns new set.
+%%
+%% Alias: del_element(), for compatibility with `sets'.
+%%
+%% - balance(S): rebalances the tree representation of S. Note that this
+%% is rarely necessary, but may be motivated when a large number of
+%% elements have been deleted from the tree without further
+%% insertions. Rebalancing could then be forced in order to minimise
+%% lookup times, since deletion only does not rebalance the tree.
+%%
+%% - union(S1, S2): returns a new set that contains each element that is
+%% in either S1 or S2 or both, and no other elements.
+%%
+%% - union(Ss): returns a new set that contains each element that is in
+%% at least one of the sets in the list Ss, and no other elements.
+%%
+%% - intersection(S1, S2): returns a new set that contains each element
+%% that is in both S1 and S2, and no other elements.
+%%
+%% - intersection(Ss): returns a new set that contains each element that
+%% is in all of the sets in the list Ss, and no other elements.
+%%
+%% - is_disjoint(S1, S2): returns `true' if none of the elements in S1
+%% occurs in S2.
+%%
+%% - difference(S1, S2): returns a new set that contains each element in
+%% S1 that is not also in S2, and no other elements.
+%%
+%% Alias: subtract(), for compatibility with `sets'.
+%%
+%% - is_subset(S1, S2): returns `true' if each element in S1 is also a
+%% member of S2, and `false' otherwise.
+%%
+%% - to_list(S): returns an ordered list of all elements in set S. The
+%% list never contains duplicates.
+%%
+%% - from_list(List): creates a set containing all elements in List,
+%% where List may be unordered and contain duplicates.
+%%
+%% - from_ordset(L): turns an ordered-set list L into a set. The list
+%% must not contain duplicates.
+%%
+%% - smallest(S): returns the smallest element in set S. Assumes that
+%% the set S is nonempty.
+%%
+%% - largest(S): returns the largest element in set S. Assumes that the
+%% set S is nonempty.
+%%
+%% - take_smallest(S): returns {X, S1}, where X is the smallest element
+%% in set S, and S1 is the set S with element X deleted. Assumes that
+%% the set S is nonempty.
+%%
+%% - take_largest(S): returns {X, S1}, where X is the largest element in
+%% set S, and S1 is the set S with element X deleted. Assumes that the
+%% set S is nonempty.
+%%
+%% - iterator(S): returns an iterator that can be used for traversing
+%% the entries of set S; see `next'. The implementation of this is
+%% very efficient; traversing the whole set using `next' is only
+%% slightly slower than getting the list of all elements using
+%% `to_list' and traversing that. The main advantage of the iterator
+%% approach is that it does not require the complete list of all
+%% elements to be built in memory at one time.
+%%
+%% - next(T): returns {X, T1} where X is the smallest element referred
+%% to by the iterator T, and T1 is the new iterator to be used for
+%% traversing the remaining elements, or the atom `none' if no
+%% elements remain.
+%%
+%% - filter(P, S): Filters set S using predicate function P. Included
+%% for compatibility with `sets'.
+%%
+%% - fold(F, A, S): Folds function F over set S with A as the initial
+%% ackumulator. Included for compatibility with `sets'.
+%%
+%% - is_set(S): returns 'true' if S appears to be a set, and 'false'
+%% otherwise. Not recommended; included for compatibility with `sets'.
+
+-module(gb_sets).
+
+-export([empty/0, is_empty/1, size/1, singleton/1, is_member/2,
+ insert/2, add/2, delete/2, delete_any/2, balance/1, union/2,
+ union/1, intersection/2, intersection/1, is_disjoint/2, difference/2,
+ is_subset/2, to_list/1, from_list/1, from_ordset/1, smallest/1,
+ largest/1, take_smallest/1, take_largest/1, iterator/1, next/1,
+ filter/2, fold/3, is_set/1]).
+
+%% `sets' compatibility aliases:
+
+-export([new/0, is_element/2, add_element/2, del_element/2,
+ subtract/2]).
+
+%% GB-trees adapted from Sven-Olof Nystr�m's implementation for
+%% representation of sets.
+%%
+%% Data structures:
+%% - {Size, Tree}, where `Tree' is composed of nodes of the form:
+%% - {Key, Smaller, Bigger}, and the "empty tree" node:
+%% - nil.
+%%
+%% No attempt is made to balance trees after deletions. Since deletions
+%% don't increase the height of a tree, this should be OK.
+%%
+%% Original balance condition h(T) <= ceil(c * log(|T|)) has been
+%% changed to the similar (but not quite equivalent) condition 2 ^ h(T)
+%% <= |T| ^ c. This should also be OK.
+%%
+%% Behaviour is logarithmic (as it should be).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Some macros.
+
+-define(p, 2). % It seems that p = 2 is optimal for sorted keys
+
+-define(pow(A, _), A * A). % correct with exponent as defined above.
+
+-define(div2(X), X bsr 1).
+
+-define(mul2(X), X bsl 1).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Some types.
+
+-type gb_set_node() :: 'nil' | {term(), _, _}.
+
+%% A declaration equivalent to the following is currently hard-coded
+%% in erl_types.erl
+%%
+%% -opaque gb_set() :: {non_neg_integer(), gb_set_node()}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec empty() -> gb_set().
+
+empty() ->
+ {0, nil}.
+
+-spec new() -> gb_set().
+
+new() -> empty().
+
+-spec is_empty(gb_set()) -> boolean().
+
+is_empty({0, nil}) ->
+ true;
+is_empty(_) ->
+ false.
+
+-spec size(gb_set()) -> non_neg_integer().
+
+size({Size, _}) ->
+ Size.
+
+-spec singleton(term()) -> gb_set().
+
+singleton(Key) ->
+ {1, {Key, nil, nil}}.
+
+-spec is_element(term(), gb_set()) -> boolean().
+
+is_element(Key, S) ->
+ is_member(Key, S).
+
+-spec is_member(term(), gb_set()) -> boolean().
+
+is_member(Key, {_, T}) ->
+ is_member_1(Key, T).
+
+is_member_1(Key, {Key1, Smaller, _}) when Key < Key1 ->
+ is_member_1(Key, Smaller);
+is_member_1(Key, {Key1, _, Bigger}) when Key > Key1 ->
+ is_member_1(Key, Bigger);
+is_member_1(_, {_, _, _}) ->
+ true;
+is_member_1(_, nil) ->
+ false.
+
+-spec insert(term(), gb_set()) -> gb_set().
+
+insert(Key, {S, T}) ->
+ S1 = S + 1,
+ {S1, insert_1(Key, T, ?pow(S1, ?p))}.
+
+insert_1(Key, {Key1, Smaller, Bigger}, S) when Key < Key1 ->
+ case insert_1(Key, Smaller, ?div2(S)) of
+ {T1, H1, S1} when is_integer(H1) ->
+ T = {Key1, T1, Bigger},
+ {H2, S2} = count(Bigger),
+ H = ?mul2(erlang:max(H1, H2)),
+ SS = S1 + S2 + 1,
+ P = ?pow(SS, ?p),
+ if
+ H > P ->
+ balance(T, SS);
+ true ->
+ {T, H, SS}
+ end;
+ T1 ->
+ {Key1, T1, Bigger}
+ end;
+insert_1(Key, {Key1, Smaller, Bigger}, S) when Key > Key1 ->
+ case insert_1(Key, Bigger, ?div2(S)) of
+ {T1, H1, S1} when is_integer(H1) ->
+ T = {Key1, Smaller, T1},
+ {H2, S2} = count(Smaller),
+ H = ?mul2(erlang:max(H1, H2)),
+ SS = S1 + S2 + 1,
+ P = ?pow(SS, ?p),
+ if
+ H > P ->
+ balance(T, SS);
+ true ->
+ {T, H, SS}
+ end;
+ T1 ->
+ {Key1, Smaller, T1}
+ end;
+insert_1(Key, nil, 0) ->
+ {{Key, nil, nil}, 1, 1};
+insert_1(Key, nil, _) ->
+ {Key, nil, nil};
+insert_1(Key, _, _) ->
+ erlang:error({key_exists, Key}).
+
+count({_, nil, nil}) ->
+ {1, 1};
+count({_, Sm, Bi}) ->
+ {H1, S1} = count(Sm),
+ {H2, S2} = count(Bi),
+ {?mul2(erlang:max(H1, H2)), S1 + S2 + 1};
+count(nil) ->
+ {1, 0}.
+
+-spec balance(gb_set()) -> gb_set().
+
+balance({S, T}) ->
+ {S, balance(T, S)}.
+
+balance(T, S) ->
+ balance_list(to_list_1(T), S).
+
+balance_list(L, S) ->
+ {T, _} = balance_list_1(L, S),
+ T.
+
+balance_list_1(L, S) when S > 1 ->
+ Sm = S - 1,
+ S2 = Sm div 2,
+ S1 = Sm - S2,
+ {T1, [K | L1]} = balance_list_1(L, S1),
+ {T2, L2} = balance_list_1(L1, S2),
+ T = {K, T1, T2},
+ {T, L2};
+balance_list_1([Key | L], 1) ->
+ {{Key, nil, nil}, L};
+balance_list_1(L, 0) ->
+ {nil, L}.
+
+-spec add_element(term(), gb_set()) -> gb_set().
+
+add_element(X, S) ->
+ add(X, S).
+
+-spec add(term(), gb_set()) -> gb_set().
+
+add(X, S) ->
+ case is_member(X, S) of
+ true ->
+ S; % we don't have to do anything here
+ false ->
+ insert(X, S)
+ end.
+
+-spec from_list([term()]) -> gb_set().
+
+from_list(L) ->
+ from_ordset(ordsets:from_list(L)).
+
+-spec from_ordset([term()]) -> gb_set().
+
+from_ordset(L) ->
+ S = length(L),
+ {S, balance_list(L, S)}.
+
+-spec del_element(term(), gb_set()) -> gb_set().
+
+del_element(Key, S) ->
+ delete_any(Key, S).
+
+-spec delete_any(term(), gb_set()) -> gb_set().
+
+delete_any(Key, S) ->
+ case is_member(Key, S) of
+ true ->
+ delete(Key, S);
+ false ->
+ S
+ end.
+
+-spec delete(term(), gb_set()) -> gb_set().
+
+delete(Key, {S, T}) ->
+ {S - 1, delete_1(Key, T)}.
+
+delete_1(Key, {Key1, Smaller, Larger}) when Key < Key1 ->
+ Smaller1 = delete_1(Key, Smaller),
+ {Key1, Smaller1, Larger};
+delete_1(Key, {Key1, Smaller, Bigger}) when Key > Key1 ->
+ Bigger1 = delete_1(Key, Bigger),
+ {Key1, Smaller, Bigger1};
+delete_1(_, {_, Smaller, Larger}) ->
+ merge(Smaller, Larger).
+
+merge(Smaller, nil) ->
+ Smaller;
+merge(nil, Larger) ->
+ Larger;
+merge(Smaller, Larger) ->
+ {Key, Larger1} = take_smallest1(Larger),
+ {Key, Smaller, Larger1}.
+
+-spec take_smallest(gb_set()) -> {term(), gb_set()}.
+
+take_smallest({S, T}) ->
+ {Key, Larger} = take_smallest1(T),
+ {Key, {S - 1, Larger}}.
+
+take_smallest1({Key, nil, Larger}) ->
+ {Key, Larger};
+take_smallest1({Key, Smaller, Larger}) ->
+ {Key1, Smaller1} = take_smallest1(Smaller),
+ {Key1, {Key, Smaller1, Larger}}.
+
+-spec smallest(gb_set()) -> term().
+
+smallest({_, T}) ->
+ smallest_1(T).
+
+smallest_1({Key, nil, _Larger}) ->
+ Key;
+smallest_1({_Key, Smaller, _Larger}) ->
+ smallest_1(Smaller).
+
+-spec take_largest(gb_set()) -> {term(), gb_set()}.
+
+take_largest({S, T}) ->
+ {Key, Smaller} = take_largest1(T),
+ {Key, {S - 1, Smaller}}.
+
+take_largest1({Key, Smaller, nil}) ->
+ {Key, Smaller};
+take_largest1({Key, Smaller, Larger}) ->
+ {Key1, Larger1} = take_largest1(Larger),
+ {Key1, {Key, Smaller, Larger1}}.
+
+-spec largest(gb_set()) -> term().
+
+largest({_, T}) ->
+ largest_1(T).
+
+largest_1({Key, _Smaller, nil}) ->
+ Key;
+largest_1({_Key, _Smaller, Larger}) ->
+ largest_1(Larger).
+
+-spec to_list(gb_set()) -> [term()].
+
+to_list({_, T}) ->
+ to_list(T, []).
+
+to_list_1(T) -> to_list(T, []).
+
+to_list({Key, Small, Big}, L) ->
+ to_list(Small, [Key | to_list(Big, L)]);
+to_list(nil, L) -> L.
+
+-spec iterator(gb_set()) -> [term()].
+
+iterator({_, T}) ->
+ iterator(T, []).
+
+%% The iterator structure is really just a list corresponding to the
+%% call stack of an in-order traversal. This is quite fast.
+
+iterator({_, nil, _} = T, As) ->
+ [T | As];
+iterator({_, L, _} = T, As) ->
+ iterator(L, [T | As]);
+iterator(nil, As) ->
+ As.
+
+-spec next([term()]) -> {term(), [term()]} | 'none'.
+
+next([{X, _, T} | As]) ->
+ {X, iterator(T, As)};
+next([]) ->
+ none.
+
+
+%% Set operations:
+
+
+%% If |X| < |Y|, then we traverse the elements of X. The cost for
+%% testing a single random element for membership in a tree S is
+%% proportional to log(|S|); thus, if |Y| / |X| < c * log(|Y|), for some
+%% c, it is more efficient to scan the ordered sequence of elements of Y
+%% while traversing X (under the same ordering) in order to test whether
+%% elements of X are already in Y. Since the `math' module does not have
+%% a `log2'-function, we rewrite the condition to |X| < |Y| * c1 *
+%% ln(|X|), where c1 = c / ln 2.
+
+-define(c, 1.46). % 1 / ln 2; this appears to be best
+
+%% If the sets are not very different in size, i.e., if |Y| / |X| >= c *
+%% log(|Y|), then the fastest way to do union (and the other similar set
+%% operations) is to build the lists of elements, traverse these lists
+%% in parallel while building a reversed ackumulator list, and finally
+%% rebuild the tree directly from the ackumulator. Other methods of
+%% traversing the elements can be devised, but they all have higher
+%% overhead.
+
+-spec union(gb_set(), gb_set()) -> gb_set().
+
+union({N1, T1}, {N2, T2}) when N2 < N1 ->
+ union(to_list_1(T2), N2, T1, N1);
+union({N1, T1}, {N2, T2}) ->
+ union(to_list_1(T1), N1, T2, N2).
+
+%% We avoid the expensive mathematical computations if there is little
+%% chance at saving at least the same amount of time by making the right
+%% choice of strategy. Recall that N1 < N2 here.
+
+union(L, N1, T2, N2) when N2 < 10 ->
+ %% Break even is about 7 for N1 = 1 and 10 for N1 = 2
+ union_2(L, to_list_1(T2), N1 + N2);
+union(L, N1, T2, N2) ->
+ X = N1 * round(?c * math:log(N2)),
+ if N2 < X ->
+ union_2(L, to_list_1(T2), N1 + N2);
+ true ->
+ union_1(L, mk_set(N2, T2))
+ end.
+
+-spec mk_set(non_neg_integer(), gb_set_node()) -> gb_set().
+
+mk_set(N, T) ->
+ {N, T}.
+
+%% If the length of the list is in proportion with the size of the
+%% target set, this version spends too much time doing lookups, compared
+%% to the below version.
+
+union_1([X | Xs], S) ->
+ union_1(Xs, add(X, S));
+union_1([], S) ->
+ S.
+
+
+%% If the length of the first list is too small in comparison with the
+%% size of the target set, this version spends too much time scanning
+%% the element list of the target set for possible membership, compared
+%% with the above version.
+
+%% Some notes on sequential scanning of ordered lists
+%%
+%% 1) We want to put the equality case last, if we can assume that the
+%% probability for overlapping elements is relatively low on average.
+%% Doing this also allows us to completely skip the (arithmetic)
+%% equality test, since the term order is arithmetically total.
+%%
+%% 2) We always test for `smaller than' first, i.e., whether the head of
+%% the left list is smaller than the head of the right list, and if the
+%% `greater than' test should instead turn out to be true, we switch
+%% left and right arguments in the recursive call under the assumption
+%% that the same is likely to apply to the next element also,
+%% statistically reducing the number of failed tests and automatically
+%% adapting to cases of lists having very different lengths. This saves
+%% 10-40% of the traversation time compared to a "fixed" strategy,
+%% depending on the sizes and contents of the lists.
+%%
+%% 3) A tail recursive version using `lists:reverse/2' is about 5-10%
+%% faster than a plain recursive version using the stack, for lists of
+%% more than about 20 elements and small stack frames. For very short
+%% lists, however (length < 10), the stack version can be several times
+%% faster. As stack frames grow larger, the advantages of using
+%% `reverse' could get greater.
+
+union_2(Xs, Ys, S) ->
+ union_2(Xs, Ys, [], S). % S is the sum of the sizes here
+
+union_2([X | Xs1], [Y | _] = Ys, As, S) when X < Y ->
+ union_2(Xs1, Ys, [X | As], S);
+union_2([X | _] = Xs, [Y | Ys1], As, S) when X > Y ->
+ union_2(Ys1, Xs, [Y | As], S);
+union_2([X | Xs1], [_ | Ys1], As, S) ->
+ union_2(Xs1, Ys1, [X | As], S - 1);
+union_2([], Ys, As, S) ->
+ {S, balance_revlist(push(Ys, As), S)};
+union_2(Xs, [], As, S) ->
+ {S, balance_revlist(push(Xs, As), S)}.
+
+push([X | Xs], As) ->
+ push(Xs, [X | As]);
+push([], As) ->
+ As.
+
+balance_revlist(L, S) ->
+ {T, _} = balance_revlist_1(L, S),
+ T.
+
+balance_revlist_1(L, S) when S > 1 ->
+ Sm = S - 1,
+ S2 = Sm div 2,
+ S1 = Sm - S2,
+ {T2, [K | L1]} = balance_revlist_1(L, S1),
+ {T1, L2} = balance_revlist_1(L1, S2),
+ T = {K, T1, T2},
+ {T, L2};
+balance_revlist_1([Key | L], 1) ->
+ {{Key, nil, nil}, L};
+balance_revlist_1(L, 0) ->
+ {nil, L}.
+
+-spec union([gb_set()]) -> gb_set().
+
+union([S | Ss]) ->
+ union_list(S, Ss);
+union([]) -> empty().
+
+union_list(S, [S1 | Ss]) ->
+ union_list(union(S, S1), Ss);
+union_list(S, []) -> S.
+
+
+%% The rest is modelled on the above.
+
+-spec intersection(gb_set(), gb_set()) -> gb_set().
+
+intersection({N1, T1}, {N2, T2}) when N2 < N1 ->
+ intersection(to_list_1(T2), N2, T1, N1);
+intersection({N1, T1}, {N2, T2}) ->
+ intersection(to_list_1(T1), N1, T2, N2).
+
+intersection(L, _N1, T2, N2) when N2 < 10 ->
+ intersection_2(L, to_list_1(T2));
+intersection(L, N1, T2, N2) ->
+ X = N1 * round(?c * math:log(N2)),
+ if N2 < X ->
+ intersection_2(L, to_list_1(T2));
+ true ->
+ intersection_1(L, T2)
+ end.
+
+%% We collect the intersecting elements in an accumulator list and count
+%% them at the same time so we can balance the list afterwards.
+
+intersection_1(Xs, T) ->
+ intersection_1(Xs, T, [], 0).
+
+intersection_1([X | Xs], T, As, N) ->
+ case is_member_1(X, T) of
+ true ->
+ intersection_1(Xs, T, [X | As], N + 1);
+ false ->
+ intersection_1(Xs, T, As, N)
+ end;
+intersection_1([], _, As, N) ->
+ {N, balance_revlist(As, N)}.
+
+
+intersection_2(Xs, Ys) ->
+ intersection_2(Xs, Ys, [], 0).
+
+intersection_2([X | Xs1], [Y | _] = Ys, As, S) when X < Y ->
+ intersection_2(Xs1, Ys, As, S);
+intersection_2([X | _] = Xs, [Y | Ys1], As, S) when X > Y ->
+ intersection_2(Ys1, Xs, As, S);
+intersection_2([X | Xs1], [_ | Ys1], As, S) ->
+ intersection_2(Xs1, Ys1, [X | As], S + 1);
+intersection_2([], _, As, S) ->
+ {S, balance_revlist(As, S)};
+intersection_2(_, [], As, S) ->
+ {S, balance_revlist(As, S)}.
+
+-spec intersection([gb_set()]) -> gb_set().
+
+intersection([S | Ss]) ->
+ intersection_list(S, Ss).
+
+intersection_list(S, [S1 | Ss]) ->
+ intersection_list(intersection(S, S1), Ss);
+intersection_list(S, []) -> S.
+
+-spec is_disjoint(gb_set(), gb_set()) -> boolean().
+
+is_disjoint({N1, T1}, {N2, T2}) when N1 < N2 ->
+ is_disjoint_1(T1, T2);
+is_disjoint({_, T1}, {_, T2}) ->
+ is_disjoint_1(T2, T1).
+
+is_disjoint_1({K1, Smaller1, Bigger}, {K2, Smaller2, _}=Tree) when K1 < K2 ->
+ not is_member_1(K1, Smaller2) andalso
+ is_disjoint_1(Smaller1, Smaller2) andalso
+ is_disjoint_1(Bigger, Tree);
+is_disjoint_1({K1, Smaller, Bigger1}, {K2, _, Bigger2}=Tree) when K1 > K2 ->
+ not is_member_1(K1, Bigger2) andalso
+ is_disjoint_1(Bigger1, Bigger2) andalso
+ is_disjoint_1(Smaller, Tree);
+is_disjoint_1({_K1, _, _}, {_K2, _, _}) -> %K1 == K2
+ false;
+is_disjoint_1(nil, _) ->
+ true;
+is_disjoint_1(_, nil) ->
+ true.
+
+%% Note that difference is not symmetric. We don't use `delete' here,
+%% since the GB-trees implementation does not rebalance after deletion
+%% and so we could end up with very unbalanced trees indeed depending on
+%% the sets. Therefore, we always build a new tree, and thus we need to
+%% traverse the whole element list of the left operand.
+
+-spec subtract(gb_set(), gb_set()) -> gb_set().
+
+subtract(S1, S2) ->
+ difference(S1, S2).
+
+-spec difference(gb_set(), gb_set()) -> gb_set().
+
+difference({N1, T1}, {N2, T2}) ->
+ difference(to_list_1(T1), N1, T2, N2).
+
+difference(L, N1, T2, N2) when N2 < 10 ->
+ difference_2(L, to_list_1(T2), N1);
+difference(L, N1, T2, N2) ->
+ X = N1 * round(?c * math:log(N2)),
+ if N2 < X ->
+ difference_2(L, to_list_1(T2), N1);
+ true ->
+ difference_1(L, T2)
+ end.
+
+
+difference_1(Xs, T) ->
+ difference_1(Xs, T, [], 0).
+
+difference_1([X | Xs], T, As, N) ->
+ case is_member_1(X, T) of
+ true ->
+ difference_1(Xs, T, As, N);
+ false ->
+ difference_1(Xs, T, [X | As], N + 1)
+ end;
+difference_1([], _, As, N) ->
+ {N, balance_revlist(As, N)}.
+
+
+difference_2(Xs, Ys, S) ->
+ difference_2(Xs, Ys, [], S). % S is the size of the left set
+
+difference_2([X | Xs1], [Y | _] = Ys, As, S) when X < Y ->
+ difference_2(Xs1, Ys, [X | As], S);
+difference_2([X | _] = Xs, [Y | Ys1], As, S) when X > Y ->
+ difference_2(Xs, Ys1, As, S);
+difference_2([_X | Xs1], [_Y | Ys1], As, S) ->
+ difference_2(Xs1, Ys1, As, S - 1);
+difference_2([], _Ys, As, S) ->
+ {S, balance_revlist(As, S)};
+difference_2(Xs, [], As, S) ->
+ {S, balance_revlist(push(Xs, As), S)}.
+
+
+%% Subset testing is much the same thing as set difference, but
+%% without the construction of a new set.
+
+-spec is_subset(gb_set(), gb_set()) -> boolean().
+
+is_subset({N1, T1}, {N2, T2}) ->
+ is_subset(to_list_1(T1), N1, T2, N2).
+
+is_subset(L, _N1, T2, N2) when N2 < 10 ->
+ is_subset_2(L, to_list_1(T2));
+is_subset(L, N1, T2, N2) ->
+ X = N1 * round(?c * math:log(N2)),
+ if N2 < X ->
+ is_subset_2(L, to_list_1(T2));
+ true ->
+ is_subset_1(L, T2)
+ end.
+
+
+is_subset_1([X | Xs], T) ->
+ case is_member_1(X, T) of
+ true ->
+ is_subset_1(Xs, T);
+ false ->
+ false
+ end;
+is_subset_1([], _) ->
+ true.
+
+
+is_subset_2([X | _], [Y | _]) when X < Y ->
+ false;
+is_subset_2([X | _] = Xs, [Y | Ys1]) when X > Y ->
+ is_subset_2(Xs, Ys1);
+is_subset_2([_ | Xs1], [_ | Ys1]) ->
+ is_subset_2(Xs1, Ys1);
+is_subset_2([], _) ->
+ true;
+is_subset_2(_, []) ->
+ false.
+
+
+%% For compatibility with `sets':
+
+-spec is_set(term()) -> boolean().
+
+is_set({0, nil}) -> true;
+is_set({N, {_, _, _}}) when is_integer(N), N >= 0 -> true;
+is_set(_) -> false.
+
+-spec filter(fun((term()) -> boolean()), gb_set()) -> gb_set().
+
+filter(F, S) ->
+ from_ordset([X || X <- to_list(S), F(X)]).
+
+-spec fold(fun((term(), term()) -> term()), term(), gb_set()) -> term().
+
+fold(F, A, {_, T}) when is_function(F, 2) ->
+ fold_1(F, A, T).
+
+fold_1(F, Acc0, {Key, Small, Big}) ->
+ Acc1 = fold_1(F, Acc0, Small),
+ Acc = F(Key, Acc1),
+ fold_1(F, Acc, Big);
+fold_1(_, Acc, _) ->
+ Acc.
diff --git a/lib/stdlib/src/gb_trees.erl b/lib/stdlib/src/gb_trees.erl
new file mode 100644
index 0000000000..d37786a100
--- /dev/null
+++ b/lib/stdlib/src/gb_trees.erl
@@ -0,0 +1,515 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% =====================================================================
+%% General Balanced Trees - highly efficient dictionaries.
+%%
+%% Copyright (C) 1999-2001 Sven-Olof Nystr�m, Richard Carlsson
+%%
+%% An efficient implementation of Prof. Arne Andersson's General
+%% Balanced Trees. These have no storage overhead compared to plain
+%% unbalanced binary trees, and their performance is in general better
+%% than AVL trees.
+%% ---------------------------------------------------------------------
+%% Operations:
+%%
+%% - empty(): returns empty tree.
+%%
+%% - is_empty(T): returns 'true' if T is an empty tree, and 'false'
+%% otherwise.
+%%
+%% - size(T): returns the number of nodes in the tree as an integer.
+%% Returns 0 (zero) if the tree is empty.
+%%
+%% - lookup(X, T): looks up key X in tree T; returns {value, V}, or
+%% `none' if the key is not present.
+%%
+%% - get(X, T): retreives the value stored with key X in tree T. Assumes
+%% that the key is present in the tree.
+%%
+%% - insert(X, V, T): inserts key X with value V into tree T; returns
+%% the new tree. Assumes that the key is *not* present in the tree.
+%%
+%% - update(X, V, T): updates key X to value V in tree T; returns the
+%% new tree. Assumes that the key is present in the tree.
+%%
+%% - enter(X, V, T): inserts key X with value V into tree T if the key
+%% is not present in the tree, otherwise updates key X to value V in
+%% T. Returns the new tree.
+%%
+%% - delete(X, T): removes key X from tree T; returns new tree. Assumes
+%% that the key is present in the tree.
+%%
+%% - delete_any(X, T): removes key X from tree T if the key is present
+%% in the tree, otherwise does nothing; returns new tree.
+%%
+%% - balance(T): rebalances tree T. Note that this is rarely necessary,
+%% but may be motivated when a large number of entries have been
+%% deleted from the tree without further insertions. Rebalancing could
+%% then be forced in order to minimise lookup times, since deletion
+%% only does not rebalance the tree.
+%%
+%% - is_defined(X, T): returns `true' if key X is present in tree T, and
+%% `false' otherwise.
+%%
+%% - keys(T): returns an ordered list of all keys in tree T.
+%%
+%% - values(T): returns the list of values for all keys in tree T,
+%% sorted by their corresponding keys. Duplicates are not removed.
+%%
+%% - to_list(T): returns an ordered list of {Key, Value} pairs for all
+%% keys in tree T.
+%%
+%% - from_orddict(L): turns an ordered list L of {Key, Value} pairs into
+%% a tree. The list must not contain duplicate keys.
+%%
+%% - smallest(T): returns {X, V}, where X is the smallest key in tree T,
+%% and V is the value associated with X in T. Assumes that the tree T
+%% is nonempty.
+%%
+%% - largest(T): returns {X, V}, where X is the largest key in tree T,
+%% and V is the value associated with X in T. Assumes that the tree T
+%% is nonempty.
+%%
+%% - take_smallest(T): returns {X, V, T1}, where X is the smallest key
+%% in tree T, V is the value associated with X in T, and T1 is the
+%% tree T with key X deleted. Assumes that the tree T is nonempty.
+%%
+%% - take_largest(T): returns {X, V, T1}, where X is the largest key
+%% in tree T, V is the value associated with X in T, and T1 is the
+%% tree T with key X deleted. Assumes that the tree T is nonempty.
+%%
+%% - iterator(T): returns an iterator that can be used for traversing
+%% the entries of tree T; see `next'. The implementation of this is
+%% very efficient; traversing the whole tree using `next' is only
+%% slightly slower than getting the list of all elements using
+%% `to_list' and traversing that. The main advantage of the iterator
+%% approach is that it does not require the complete list of all
+%% elements to be built in memory at one time.
+%%
+%% - next(S): returns {X, V, S1} where X is the smallest key referred to
+%% by the iterator S, and S1 is the new iterator to be used for
+%% traversing the remaining entries, or the atom `none' if no entries
+%% remain.
+%%
+%% - map(F, T): maps the function F(K, V) -> V' to all key-value pairs
+%% of the tree T and returns a new tree T' with the same set of keys
+%% as T and the new set of values V'.
+
+-module(gb_trees).
+
+-export([empty/0, is_empty/1, size/1, lookup/2, get/2, insert/3,
+ update/3, enter/3, delete/2, delete_any/2, balance/1,
+ is_defined/2, keys/1, values/1, to_list/1, from_orddict/1,
+ smallest/1, largest/1, take_smallest/1, take_largest/1,
+ iterator/1, next/1, map/2]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Data structure:
+%% - {Size, Tree}, where `Tree' is composed of nodes of the form:
+%% - {Key, Value, Smaller, Bigger}, and the "empty tree" node:
+%% - nil.
+%%
+%% I make no attempt to balance trees after deletions. Since deletions
+%% don't increase the height of a tree, I figure this is OK.
+%%
+%% Original balance condition h(T) <= ceil(c * log(|T|)) has been
+%% changed to the similar (but not quite equivalent) condition 2 ^ h(T)
+%% <= |T| ^ c. I figure this should also be OK.
+%%
+%% Performance is comparable to the AVL trees in the Erlang book (and
+%% faster in general due to less overhead); the difference is that
+%% deletion works for my trees, but not for the book's trees. Behaviour
+%% is logaritmic (as it should be).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Some macros.
+
+-define(p, 2). % It seems that p = 2 is optimal for sorted keys
+
+-define(pow(A, _), A * A). % correct with exponent as defined above.
+
+-define(div2(X), X bsr 1).
+
+-define(mul2(X), X bsl 1).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Some types.
+
+-type gb_tree_node() :: 'nil' | {_, _, _, _}.
+
+%% A declaration equivalent to the following is currently hard-coded
+%% in erl_types.erl
+%%
+%% -opaque gb_tree() :: {non_neg_integer(), gb_tree_node()}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec empty() -> gb_tree().
+
+empty() ->
+ {0, nil}.
+
+-spec is_empty(gb_tree()) -> boolean().
+
+is_empty({0, nil}) ->
+ true;
+is_empty(_) ->
+ false.
+
+-spec size(gb_tree()) -> non_neg_integer().
+
+size({Size, _}) when is_integer(Size), Size >= 0 ->
+ Size.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec lookup(term(), gb_tree()) -> 'none' | {'value', term()}.
+
+lookup(Key, {_, T}) ->
+ lookup_1(Key, T).
+
+%% The term order is an arithmetic total order, so we should not
+%% test exact equality for the keys. (If we do, then it becomes
+%% possible that neither `>', `<', nor `=:=' matches.) Testing '<'
+%% and '>' first is statistically better than testing for
+%% equality, and also allows us to skip the test completely in the
+%% remaining case.
+
+lookup_1(Key, {Key1, _, Smaller, _}) when Key < Key1 ->
+ lookup_1(Key, Smaller);
+lookup_1(Key, {Key1, _, _, Bigger}) when Key > Key1 ->
+ lookup_1(Key, Bigger);
+lookup_1(_, {_, Value, _, _}) ->
+ {value, Value};
+lookup_1(_, nil) ->
+ none.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is a specialized version of `lookup'.
+
+-spec is_defined(term(), gb_tree()) -> boolean().
+
+is_defined(Key, {_, T}) ->
+ is_defined_1(Key, T).
+
+is_defined_1(Key, {Key1, _, Smaller, _}) when Key < Key1 ->
+ is_defined_1(Key, Smaller);
+is_defined_1(Key, {Key1, _, _, Bigger}) when Key > Key1 ->
+ is_defined_1(Key, Bigger);
+is_defined_1(_, {_, _, _, _}) ->
+ true;
+is_defined_1(_, nil) ->
+ false.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% This is a specialized version of `lookup'.
+
+-spec get(term(), gb_tree()) -> term().
+
+get(Key, {_, T}) ->
+ get_1(Key, T).
+
+get_1(Key, {Key1, _, Smaller, _}) when Key < Key1 ->
+ get_1(Key, Smaller);
+get_1(Key, {Key1, _, _, Bigger}) when Key > Key1 ->
+ get_1(Key, Bigger);
+get_1(_, {_, Value, _, _}) ->
+ Value.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec update(term(), term(), gb_tree()) -> gb_tree().
+
+update(Key, Val, {S, T}) ->
+ T1 = update_1(Key, Val, T),
+ {S, T1}.
+
+%% See `lookup' for notes on the term comparison order.
+
+update_1(Key, Value, {Key1, V, Smaller, Bigger}) when Key < Key1 ->
+ {Key1, V, update_1(Key, Value, Smaller), Bigger};
+update_1(Key, Value, {Key1, V, Smaller, Bigger}) when Key > Key1 ->
+ {Key1, V, Smaller, update_1(Key, Value, Bigger)};
+update_1(Key, Value, {_, _, Smaller, Bigger}) ->
+ {Key, Value, Smaller, Bigger}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec insert(term(), term(), gb_tree()) -> gb_tree().
+
+insert(Key, Val, {S, T}) when is_integer(S) ->
+ S1 = S+1,
+ {S1, insert_1(Key, Val, T, ?pow(S1, ?p))}.
+
+insert_1(Key, Value, {Key1, V, Smaller, Bigger}, S) when Key < Key1 ->
+ case insert_1(Key, Value, Smaller, ?div2(S)) of
+ {T1, H1, S1} ->
+ T = {Key1, V, T1, Bigger},
+ {H2, S2} = count(Bigger),
+ H = ?mul2(erlang:max(H1, H2)),
+ SS = S1 + S2 + 1,
+ P = ?pow(SS, ?p),
+ if
+ H > P ->
+ balance(T, SS);
+ true ->
+ {T, H, SS}
+ end;
+ T1 ->
+ {Key1, V, T1, Bigger}
+ end;
+insert_1(Key, Value, {Key1, V, Smaller, Bigger}, S) when Key > Key1 ->
+ case insert_1(Key, Value, Bigger, ?div2(S)) of
+ {T1, H1, S1} ->
+ T = {Key1, V, Smaller, T1},
+ {H2, S2} = count(Smaller),
+ H = ?mul2(erlang:max(H1, H2)),
+ SS = S1 + S2 + 1,
+ P = ?pow(SS, ?p),
+ if
+ H > P ->
+ balance(T, SS);
+ true ->
+ {T, H, SS}
+ end;
+ T1 ->
+ {Key1, V, Smaller, T1}
+ end;
+insert_1(Key, Value, nil, S) when S =:= 0 ->
+ {{Key, Value, nil, nil}, 1, 1};
+insert_1(Key, Value, nil, _S) ->
+ {Key, Value, nil, nil};
+insert_1(Key, _, _, _) ->
+ erlang:error({key_exists, Key}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec enter(term(), term(), gb_tree()) -> gb_tree().
+
+enter(Key, Val, T) ->
+ case is_defined(Key, T) of
+ true ->
+ update(Key, Val, T);
+ false ->
+ insert(Key, Val, T)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+count({_, _, nil, nil}) ->
+ {1, 1};
+count({_, _, Sm, Bi}) ->
+ {H1, S1} = count(Sm),
+ {H2, S2} = count(Bi),
+ {?mul2(erlang:max(H1, H2)), S1 + S2 + 1};
+count(nil) ->
+ {1, 0}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec balance(gb_tree()) -> gb_tree().
+
+balance({S, T}) ->
+ {S, balance(T, S)}.
+
+balance(T, S) ->
+ balance_list(to_list_1(T), S).
+
+balance_list(L, S) ->
+ {T, []} = balance_list_1(L, S),
+ T.
+
+balance_list_1(L, S) when S > 1 ->
+ Sm = S - 1,
+ S2 = Sm div 2,
+ S1 = Sm - S2,
+ {T1, [{K, V} | L1]} = balance_list_1(L, S1),
+ {T2, L2} = balance_list_1(L1, S2),
+ T = {K, V, T1, T2},
+ {T, L2};
+balance_list_1([{Key, Val} | L], 1) ->
+ {{Key, Val, nil, nil}, L};
+balance_list_1(L, 0) ->
+ {nil, L}.
+
+-spec from_orddict([{_,_}]) -> gb_tree().
+
+from_orddict(L) ->
+ S = length(L),
+ {S, balance_list(L, S)}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec delete_any(term(), gb_tree()) -> gb_tree().
+
+delete_any(Key, T) ->
+ case is_defined(Key, T) of
+ true ->
+ delete(Key, T);
+ false ->
+ T
+ end.
+
+%%% delete. Assumes that key is present.
+
+-spec delete(term(), gb_tree()) -> gb_tree().
+
+delete(Key, {S, T}) when is_integer(S), S >= 0 ->
+ {S - 1, delete_1(Key, T)}.
+
+%% See `lookup' for notes on the term comparison order.
+
+delete_1(Key, {Key1, Value, Smaller, Larger}) when Key < Key1 ->
+ Smaller1 = delete_1(Key, Smaller),
+ {Key1, Value, Smaller1, Larger};
+delete_1(Key, {Key1, Value, Smaller, Bigger}) when Key > Key1 ->
+ Bigger1 = delete_1(Key, Bigger),
+ {Key1, Value, Smaller, Bigger1};
+delete_1(_, {_, _, Smaller, Larger}) ->
+ merge(Smaller, Larger).
+
+merge(Smaller, nil) ->
+ Smaller;
+merge(nil, Larger) ->
+ Larger;
+merge(Smaller, Larger) ->
+ {Key, Value, Larger1} = take_smallest1(Larger),
+ {Key, Value, Smaller, Larger1}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec take_smallest(gb_tree()) -> {term(), term(), gb_tree()}.
+
+take_smallest({Size, Tree}) when is_integer(Size), Size >= 0 ->
+ {Key, Value, Larger} = take_smallest1(Tree),
+ {Key, Value, {Size - 1, Larger}}.
+
+take_smallest1({Key, Value, nil, Larger}) ->
+ {Key, Value, Larger};
+take_smallest1({Key, Value, Smaller, Larger}) ->
+ {Key1, Value1, Smaller1} = take_smallest1(Smaller),
+ {Key1, Value1, {Key, Value, Smaller1, Larger}}.
+
+-spec smallest(gb_tree()) -> {term(), term()}.
+
+smallest({_, Tree}) ->
+ smallest_1(Tree).
+
+smallest_1({Key, Value, nil, _Larger}) ->
+ {Key, Value};
+smallest_1({_Key, _Value, Smaller, _Larger}) ->
+ smallest_1(Smaller).
+
+-spec take_largest(gb_tree()) -> {term(), term(), gb_tree()}.
+
+take_largest({Size, Tree}) when is_integer(Size), Size >= 0 ->
+ {Key, Value, Smaller} = take_largest1(Tree),
+ {Key, Value, {Size - 1, Smaller}}.
+
+take_largest1({Key, Value, Smaller, nil}) ->
+ {Key, Value, Smaller};
+take_largest1({Key, Value, Smaller, Larger}) ->
+ {Key1, Value1, Larger1} = take_largest1(Larger),
+ {Key1, Value1, {Key, Value, Smaller, Larger1}}.
+
+-spec largest(gb_tree()) -> {term(), term()}.
+
+largest({_, Tree}) ->
+ largest_1(Tree).
+
+largest_1({Key, Value, _Smaller, nil}) ->
+ {Key, Value};
+largest_1({_Key, _Value, _Smaller, Larger}) ->
+ largest_1(Larger).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec to_list(gb_tree()) -> [{term(), term()}].
+
+to_list({_, T}) ->
+ to_list(T, []).
+
+to_list_1(T) -> to_list(T, []).
+
+to_list({Key, Value, Small, Big}, L) ->
+ to_list(Small, [{Key, Value} | to_list(Big, L)]);
+to_list(nil, L) -> L.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec keys(gb_tree()) -> [term()].
+
+keys({_, T}) ->
+ keys(T, []).
+
+keys({Key, _Value, Small, Big}, L) ->
+ keys(Small, [Key | keys(Big, L)]);
+keys(nil, L) -> L.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec values(gb_tree()) -> [term()].
+
+values({_, T}) ->
+ values(T, []).
+
+values({_Key, Value, Small, Big}, L) ->
+ values(Small, [Value | values(Big, L)]);
+values(nil, L) -> L.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec iterator(gb_tree()) -> [gb_tree_node()].
+
+iterator({_, T}) ->
+ iterator_1(T).
+
+iterator_1(T) ->
+ iterator(T, []).
+
+%% The iterator structure is really just a list corresponding to
+%% the call stack of an in-order traversal. This is quite fast.
+
+iterator({_, _, nil, _} = T, As) ->
+ [T | As];
+iterator({_, _, L, _} = T, As) ->
+ iterator(L, [T | As]);
+iterator(nil, As) ->
+ As.
+
+-spec next([gb_tree_node()]) -> 'none' | {term(), term(), [gb_tree_node()]}.
+
+next([{X, V, _, T} | As]) ->
+ {X, V, iterator(T, As)};
+next([]) ->
+ none.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec map(fun((term(), term()) -> term()), gb_tree()) -> gb_tree().
+
+map(F, {Size, Tree}) when is_function(F, 2) ->
+ {Size, map_1(F, Tree)}.
+
+map_1(_, nil) -> nil;
+map_1(F, {K, V, Smaller, Larger}) ->
+ {K, F(K, V), map_1(F, Smaller), map_1(F, Larger)}.
diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl
new file mode 100644
index 0000000000..5aab547644
--- /dev/null
+++ b/lib/stdlib/src/gen.erl
@@ -0,0 +1,320 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen).
+
+%%%-----------------------------------------------------------------
+%%% This module implements the really generic stuff of the generic
+%%% standard behaviours (e.g. gen_server, gen_fsm).
+%%%
+%%% The standard behaviour should export init_it/6.
+%%%-----------------------------------------------------------------
+-export([start/5, start/6, debug_options/1,
+ call/3, call/4, reply/2]).
+
+-export([init_it/6, init_it/7]).
+
+-define(default_timeout, 5000).
+
+%%-----------------------------------------------------------------
+
+-type linkage() :: 'link' | 'nolink'.
+-type emgr_name() :: {'local', atom()} | {'global', term()}.
+
+-type start_ret() :: {'ok', pid()} | 'ignore' | {'error', term()}.
+
+-type debug_flag() :: 'trace' | 'log' | 'statistics' | 'debug'
+ | {'logfile', string()}.
+-type option() :: {'timeout', timeout()}
+ | {'debug', [debug_flag()]}
+ | {'spawn_opt', [proc_lib:spawn_option()]}.
+-type options() :: [option()].
+
+%%-----------------------------------------------------------------
+%% Starts a generic process.
+%% start(GenMod, LinkP, Mod, Args, Options)
+%% start(GenMod, LinkP, Name, Mod, Args, Options)
+%% GenMod = atom(), callback module implementing the 'real' fsm
+%% LinkP = link | nolink
+%% Name = {local, atom()} | {global, term()}
+%% Args = term(), init arguments (to Mod:init/1)
+%% Options = [{timeout, Timeout} | {debug, [Flag]} | {spawn_opt, OptionList}]
+%% Flag = trace | log | {logfile, File} | statistics | debug
+%% (debug == log && statistics)
+%% Returns: {ok, Pid} | ignore |{error, Reason} |
+%% {error, {already_started, Pid}} |
+%% The 'already_started' is returned only if Name is given
+%%-----------------------------------------------------------------
+
+-spec start(module(), linkage(), emgr_name(), module(), term(), options()) ->
+ start_ret().
+
+start(GenMod, LinkP, Name, Mod, Args, Options) ->
+ case where(Name) of
+ undefined ->
+ do_spawn(GenMod, LinkP, Name, Mod, Args, Options);
+ Pid ->
+ {error, {already_started, Pid}}
+ end.
+
+-spec start(module(), linkage(), module(), term(), options()) -> start_ret().
+
+start(GenMod, LinkP, Mod, Args, Options) ->
+ do_spawn(GenMod, LinkP, Mod, Args, Options).
+
+%%-----------------------------------------------------------------
+%% Spawn the process (and link) maybe at another node.
+%% If spawn without link, set parent to ourselves 'self'!!!
+%%-----------------------------------------------------------------
+do_spawn(GenMod, link, Mod, Args, Options) ->
+ Time = timeout(Options),
+ proc_lib:start_link(?MODULE, init_it,
+ [GenMod, self(), self(), Mod, Args, Options],
+ Time,
+ spawn_opts(Options));
+do_spawn(GenMod, _, Mod, Args, Options) ->
+ Time = timeout(Options),
+ proc_lib:start(?MODULE, init_it,
+ [GenMod, self(), self, Mod, Args, Options],
+ Time,
+ spawn_opts(Options)).
+
+do_spawn(GenMod, link, Name, Mod, Args, Options) ->
+ Time = timeout(Options),
+ proc_lib:start_link(?MODULE, init_it,
+ [GenMod, self(), self(), Name, Mod, Args, Options],
+ Time,
+ spawn_opts(Options));
+do_spawn(GenMod, _, Name, Mod, Args, Options) ->
+ Time = timeout(Options),
+ proc_lib:start(?MODULE, init_it,
+ [GenMod, self(), self, Name, Mod, Args, Options],
+ Time,
+ spawn_opts(Options)).
+
+%%-----------------------------------------------------------------
+%% Initiate the new process.
+%% Register the name using the Rfunc function
+%% Calls the Mod:init/Args function.
+%% Finally an acknowledge is sent to Parent and the main
+%% loop is entered.
+%%-----------------------------------------------------------------
+init_it(GenMod, Starter, Parent, Mod, Args, Options) ->
+ init_it2(GenMod, Starter, Parent, self(), Mod, Args, Options).
+
+init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
+ case name_register(Name) of
+ true ->
+ init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options);
+ {false, Pid} ->
+ proc_lib:init_ack(Starter, {error, {already_started, Pid}})
+ end.
+
+init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
+ GenMod:init_it(Starter, Parent, Name, Mod, Args, Options).
+
+%%-----------------------------------------------------------------
+%% Makes a synchronous call to a generic process.
+%% Request is sent to the Pid, and the response must be
+%% {Tag, _, Reply}.
+%%-----------------------------------------------------------------
+
+%%% New call function which uses the new monitor BIF
+%%% call(ServerId, Label, Request)
+
+call(Process, Label, Request) ->
+ call(Process, Label, Request, ?default_timeout).
+
+%% Local or remote by pid
+call(Pid, Label, Request, Timeout)
+ when is_pid(Pid), Timeout =:= infinity;
+ is_pid(Pid), is_integer(Timeout), Timeout >= 0 ->
+ do_call(Pid, Label, Request, Timeout);
+%% Local by name
+call(Name, Label, Request, Timeout)
+ when is_atom(Name), Timeout =:= infinity;
+ is_atom(Name), is_integer(Timeout), Timeout >= 0 ->
+ case whereis(Name) of
+ Pid when is_pid(Pid) ->
+ do_call(Pid, Label, Request, Timeout);
+ undefined ->
+ exit(noproc)
+ end;
+%% Global by name
+call({global, _Name}=Process, Label, Request, Timeout)
+ when Timeout =:= infinity;
+ is_integer(Timeout), Timeout >= 0 ->
+ case where(Process) of
+ Pid when is_pid(Pid) ->
+ Node = node(Pid),
+ try do_call(Pid, Label, Request, Timeout)
+ catch
+ exit:{nodedown, Node} ->
+ %% A nodedown not yet detected by global,
+ %% pretend that it was.
+ exit(noproc)
+ end;
+ undefined ->
+ exit(noproc)
+ end;
+%% Local by name in disguise
+call({Name, Node}, Label, Request, Timeout)
+ when Node =:= node(), Timeout =:= infinity;
+ Node =:= node(), is_integer(Timeout), Timeout >= 0 ->
+ call(Name, Label, Request, Timeout);
+%% Remote by name
+call({_Name, Node}=Process, Label, Request, Timeout)
+ when is_atom(Node), Timeout =:= infinity;
+ is_atom(Node), is_integer(Timeout), Timeout >= 0 ->
+ if
+ node() =:= nonode@nohost ->
+ exit({nodedown, Node});
+ true ->
+ do_call(Process, Label, Request, Timeout)
+ end.
+
+do_call(Process, Label, Request, Timeout) ->
+ %% We trust the arguments to be correct, i.e
+ %% Process is either a local or remote pid,
+ %% or a {Name, Node} tuple (of atoms) and in this
+ %% case this node (node()) _is_ distributed and Node =/= node().
+ Node = case Process of
+ {_S, N} when is_atom(N) ->
+ N;
+ _ when is_pid(Process) ->
+ node(Process)
+ end,
+ try erlang:monitor(process, Process) of
+ Mref ->
+ %% If the monitor/2 call failed to set up a connection to a
+ %% remote node, we don't want the '!' operator to attempt
+ %% to set up the connection again. (If the monitor/2 call
+ %% failed due to an expired timeout, '!' too would probably
+ %% have to wait for the timeout to expire.) Therefore,
+ %% use erlang:send/3 with the 'noconnect' option so that it
+ %% will fail immediately if there is no connection to the
+ %% remote node.
+
+ catch erlang:send(Process, {Label, {self(), Mref}, Request},
+ [noconnect]),
+ wait_resp_mon(Node, Mref, Timeout)
+ catch
+ error:_ ->
+ %% Node (C/Java?) is not supporting the monitor.
+ %% The other possible case -- this node is not distributed
+ %% -- should have been handled earlier.
+ %% Do the best possible with monitor_node/2.
+ %% This code may hang indefinitely if the Process
+ %% does not exist. It is only used for featureweak remote nodes.
+ monitor_node(Node, true),
+ receive
+ {nodedown, Node} ->
+ monitor_node(Node, false),
+ exit({nodedown, Node})
+ after 0 ->
+ Tag = make_ref(),
+ Process ! {Label, {self(), Tag}, Request},
+ wait_resp(Node, Tag, Timeout)
+ end
+ end.
+
+wait_resp_mon(Node, Mref, Timeout) ->
+ receive
+ {Mref, Reply} ->
+ erlang:demonitor(Mref, [flush]),
+ {ok, Reply};
+ {'DOWN', Mref, _, _, noconnection} ->
+ exit({nodedown, Node});
+ {'DOWN', Mref, _, _, Reason} ->
+ exit(Reason)
+ after Timeout ->
+ erlang:demonitor(Mref),
+ receive
+ {'DOWN', Mref, _, _, _} -> true
+ after 0 -> true
+ end,
+ exit(timeout)
+ end.
+
+wait_resp(Node, Tag, Timeout) ->
+ receive
+ {Tag, Reply} ->
+ monitor_node(Node, false),
+ {ok, Reply};
+ {nodedown, Node} ->
+ monitor_node(Node, false),
+ exit({nodedown, Node})
+ after Timeout ->
+ monitor_node(Node, false),
+ exit(timeout)
+ end.
+
+%%
+%% Send a reply to the client.
+%%
+reply({To, Tag}, Reply) ->
+ Msg = {Tag, Reply},
+ try To ! Msg catch _:_ -> Msg end.
+
+%%%-----------------------------------------------------------------
+%%% Misc. functions.
+%%%-----------------------------------------------------------------
+where({global, Name}) -> global:safe_whereis_name(Name);
+where({local, Name}) -> whereis(Name).
+
+name_register({local, Name} = LN) ->
+ try register(Name, self()) of
+ true -> true
+ catch
+ error:_ ->
+ {false, where(LN)}
+ end;
+name_register({global, Name} = GN) ->
+ case global:register_name(Name, self()) of
+ yes -> true;
+ no -> {false, where(GN)}
+ end.
+
+timeout(Options) ->
+ case opt(timeout, Options) of
+ {ok, Time} ->
+ Time;
+ _ ->
+ infinity
+ end.
+
+spawn_opts(Options) ->
+ case opt(spawn_opt, Options) of
+ {ok, Opts} ->
+ Opts;
+ _ ->
+ []
+ end.
+
+opt(Op, [{Op, Value}|_]) ->
+ {ok, Value};
+opt(Op, [_|Options]) ->
+ opt(Op, Options);
+opt(_, []) ->
+ false.
+
+debug_options(Opts) ->
+ case opt(debug, Opts) of
+ {ok, Options} -> sys:debug_options(Options);
+ _ -> []
+ end.
diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl
new file mode 100644
index 0000000000..1b30aaf5eb
--- /dev/null
+++ b/lib/stdlib/src/gen_event.erl
@@ -0,0 +1,721 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen_event).
+
+%%%
+%%% A general event handler.
+%%% Several handlers (functions) can be added.
+%%% Each handler holds a state and will be called
+%%% for every event received of the handler.
+%%%
+
+%%% Modified by Magnus.
+%%% Take care of fault situations and made notify asynchronous.
+%%% Re-written by Joe with new functional interface !
+%%% Modified by Martin - uses proc_lib, sys and gen!
+
+
+-export([start/0, start/1, start_link/0, start_link/1, stop/1, notify/2,
+ sync_notify/2,
+ add_handler/3, add_sup_handler/3, delete_handler/3, swap_handler/3,
+ swap_sup_handler/3, which_handlers/1, call/3, call/4, wake_hib/4]).
+
+-export([behaviour_info/1]).
+
+-export([init_it/6,
+ system_continue/3,
+ system_terminate/4,
+ system_code_change/4,
+ print_event/3,
+ format_status/2]).
+
+-import(error_logger, [error_msg/2]).
+
+-define(reply(X), From ! {element(2,Tag), X}).
+
+-record(handler, {module :: atom(),
+ id = false,
+ state,
+ supervised = false :: 'false' | pid()}).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec behaviour_info(atom()) -> 'undefined' | [{atom(), arity()}].
+
+behaviour_info(callbacks) ->
+ [{init,1},{handle_event,2},{handle_call,2},{handle_info,2},
+ {terminate,2},{code_change,3}];
+behaviour_info(_Other) ->
+ undefined.
+
+%% gen_event:start(Handler) -> {ok, Pid} | {error, What}
+%% gen_event:add_handler(Handler, Mod, Args) -> ok | Other
+%% gen_event:notify(Handler, Event) -> ok
+%% gen_event:call(Handler, Mod, Query) -> {ok, Val} | {error, Why}
+%% gen_event:call(Handler, Mod, Query, Timeout) -> {ok, Val} | {error, Why}
+%% gen_event:delete_handler(Handler, Mod, Args) -> Val
+%% gen_event:swap_handler(Handler, {OldMod, Args1}, {NewMod, Args2}) -> ok
+%% gen_event:which_handler(Handler) -> [Mod]
+%% gen_event:stop(Handler) -> ok
+
+
+%% handlers must export
+%% Mod:init(Args) -> {ok, State} | Other
+%% Mod:handle_event(Event, State) ->
+%% {ok, State'} | remove_handler | {swap_handler,Args1,State1,Mod2,Args2}
+%% Mod:handle_info(Info, State) ->
+%% {ok, State'} | remove_handler | {swap_handler,Args1,State1,Mod2,Args2}
+%% Mod:handle_call(Query, State) ->
+%% {ok, Reply, State'} | {remove_handler, Reply} |
+%% {swap_handler, Reply, Args1,State1,Mod2,Args2}
+%% Mod:terminate(Args, State) -> Val
+
+
+%% add_handler(H, Mod, Args) -> ok | Other
+%% Mod:init(Args) -> {ok, State} | Other
+
+%% delete_handler(H, Mod, Args) -> Val
+%% Mod:terminate(Args, State) -> Val
+
+%% notify(H, Event)
+%% Mod:handle_event(Event, State) ->
+%% {ok, State1}
+%% remove_handler
+%% Mod:terminate(remove_handler, State) is called
+%% the return value is ignored
+%% {swap_handler, Args1, State1, Mod2, Args2}
+%% State2 = Mod:terminate(Args1, State1) is called
+%% the return value is chained into the new module and
+%% Mod2:init({Args2, State2}) is called
+%% Other
+%% Mod:terminate({error, Other}, State) is called
+%% The return value is ignored
+%% call(H, Mod, Query) -> Val
+%% call(H, Mod, Query, Timeout) -> Val
+%% Mod:handle_call(Query, State) -> as above
+
+%%---------------------------------------------------------------------------
+
+-type handler() :: atom() | {atom(), term()}.
+-type emgr_name() :: {'local', atom()} | {'global', atom()}.
+-type emgr_ref() :: atom() | {atom(), atom()} | {'global', atom()} | pid().
+-type start_ret() :: {'ok', pid()} | {'error', term()}.
+
+%%---------------------------------------------------------------------------
+
+-define(NO_CALLBACK, 'no callback module').
+
+-spec start() -> start_ret().
+start() ->
+ gen:start(?MODULE, nolink, ?NO_CALLBACK, [], []).
+
+-spec start(emgr_name()) -> start_ret().
+start(Name) ->
+ gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], []).
+
+-spec start_link() -> start_ret().
+start_link() ->
+ gen:start(?MODULE, link, ?NO_CALLBACK, [], []).
+
+-spec start_link(emgr_name()) -> start_ret().
+start_link(Name) ->
+ gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], []).
+
+%% -spec init_it(pid(), 'self' | pid(), emgr_name(), module(), [term()], [_]) ->
+init_it(Starter, self, Name, Mod, Args, Options) ->
+ init_it(Starter, self(), Name, Mod, Args, Options);
+init_it(Starter, Parent, Name0, _, _, Options) ->
+ process_flag(trap_exit, true),
+ Debug = gen:debug_options(Options),
+ proc_lib:init_ack(Starter, {ok, self()}),
+ Name = name(Name0),
+ loop(Parent, Name, [], Debug, false).
+
+name({local,Name}) -> Name;
+name({global,Name}) -> Name;
+name(Pid) when is_pid(Pid) -> Pid.
+
+-spec add_handler(emgr_ref(), handler(), term()) -> term().
+add_handler(M, Handler, Args) -> rpc(M, {add_handler, Handler, Args}).
+
+-spec add_sup_handler(emgr_ref(), handler(), term()) -> term().
+add_sup_handler(M, Handler, Args) ->
+ rpc(M, {add_sup_handler, Handler, Args, self()}).
+
+-spec notify(emgr_ref(), term()) -> 'ok'.
+notify(M, Event) -> send(M, {notify, Event}).
+
+-spec sync_notify(emgr_ref(), term()) -> 'ok'.
+sync_notify(M, Event) -> rpc(M, {sync_notify, Event}).
+
+-spec call(emgr_ref(), handler(), term()) -> term().
+call(M, Handler, Query) -> call1(M, Handler, Query).
+
+-spec call(emgr_ref(), handler(), term(), timeout()) -> term().
+call(M, Handler, Query, Timeout) -> call1(M, Handler, Query, Timeout).
+
+-spec delete_handler(emgr_ref(), handler(), term()) -> term().
+delete_handler(M, Handler, Args) -> rpc(M, {delete_handler, Handler, Args}).
+
+-spec swap_handler(emgr_ref(), {handler(), term()}, {handler(), term()}) ->
+ 'ok' | {'error', term()}.
+swap_handler(M, {H1, A1}, {H2, A2}) -> rpc(M, {swap_handler, H1, A1, H2, A2}).
+
+-spec swap_sup_handler(emgr_ref(), {handler(), term()}, {handler(), term()}) ->
+ 'ok' | {'error', term()}.
+swap_sup_handler(M, {H1, A1}, {H2, A2}) ->
+ rpc(M, {swap_sup_handler, H1, A1, H2, A2, self()}).
+
+-spec which_handlers(emgr_ref()) -> [handler()].
+which_handlers(M) -> rpc(M, which_handlers).
+
+-spec stop(emgr_ref()) -> 'ok'.
+stop(M) -> rpc(M, stop).
+
+rpc(M, Cmd) ->
+ {ok, Reply} = gen:call(M, self(), Cmd, infinity),
+ Reply.
+
+call1(M, Handler, Query) ->
+ Cmd = {call, Handler, Query},
+ try gen:call(M, self(), Cmd) of
+ {ok, Res} ->
+ Res
+ catch
+ exit:Reason ->
+ exit({Reason, {?MODULE, call, [M, Handler, Query]}})
+ end.
+
+call1(M, Handler, Query, Timeout) ->
+ Cmd = {call, Handler, Query},
+ try gen:call(M, self(), Cmd, Timeout) of
+ {ok, Res} ->
+ Res
+ catch
+ exit:Reason ->
+ exit({Reason, {?MODULE, call, [M, Handler, Query, Timeout]}})
+ end.
+
+send({global, Name}, Cmd) ->
+ catch global:send(Name, Cmd),
+ ok;
+send(M, Cmd) ->
+ M ! Cmd,
+ ok.
+
+loop(Parent, ServerName, MSL, Debug, true) ->
+ proc_lib:hibernate(?MODULE, wake_hib, [Parent, ServerName, MSL, Debug]);
+loop(Parent, ServerName, MSL, Debug, _) ->
+ fetch_msg(Parent, ServerName, MSL, Debug, false).
+
+wake_hib(Parent, ServerName, MSL, Debug) ->
+ fetch_msg(Parent, ServerName, MSL, Debug, true).
+
+fetch_msg(Parent, ServerName, MSL, Debug, Hib) ->
+ receive
+ {system, From, Req} ->
+ sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
+ [ServerName, MSL, Hib],Hib);
+ {'EXIT', Parent, Reason} ->
+ terminate_server(Reason, Parent, MSL, ServerName);
+ Msg when Debug =:= [] ->
+ handle_msg(Msg, Parent, ServerName, MSL, []);
+ Msg ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ ServerName, {in, Msg}),
+ handle_msg(Msg, Parent, ServerName, MSL, Debug1)
+ end.
+
+handle_msg(Msg, Parent, ServerName, MSL, Debug) ->
+ case Msg of
+ {notify, Event} ->
+ {Hib,MSL1} = server_notify(Event, handle_event, MSL, ServerName),
+ loop(Parent, ServerName, MSL1, Debug, Hib);
+ {From, Tag, {sync_notify, Event}} ->
+ {Hib, MSL1} = server_notify(Event, handle_event, MSL, ServerName),
+ ?reply(ok),
+ loop(Parent, ServerName, MSL1, Debug, Hib);
+ {'EXIT', From, Reason} ->
+ MSL1 = handle_exit(From, Reason, MSL, ServerName),
+ loop(Parent, ServerName, MSL1, Debug, false);
+ {From, Tag, {call, Handler, Query}} ->
+ {Hib, Reply, MSL1} = server_call(Handler, Query, MSL, ServerName),
+ ?reply(Reply),
+ loop(Parent, ServerName, MSL1, Debug, Hib);
+ {From, Tag, {add_handler, Handler, Args}} ->
+ {Hib, Reply, MSL1} = server_add_handler(Handler, Args, MSL),
+ ?reply(Reply),
+ loop(Parent, ServerName, MSL1, Debug, Hib);
+ {From, Tag, {add_sup_handler, Handler, Args, SupP}} ->
+ {Hib, Reply, MSL1} = server_add_sup_handler(Handler, Args, MSL, SupP),
+ ?reply(Reply),
+ loop(Parent, ServerName, MSL1, Debug, Hib);
+ {From, Tag, {delete_handler, Handler, Args}} ->
+ {Reply, MSL1} = server_delete_handler(Handler, Args, MSL,
+ ServerName),
+ ?reply(Reply),
+ loop(Parent, ServerName, MSL1, Debug, false);
+ {From, Tag, {swap_handler, Handler1, Args1, Handler2, Args2}} ->
+ {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2,
+ Args2, MSL, ServerName),
+ ?reply(Reply),
+ loop(Parent, ServerName, MSL1, Debug, Hib);
+ {From, Tag, {swap_sup_handler, Handler1, Args1, Handler2, Args2,
+ Sup}} ->
+ {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2,
+ Args2, MSL, Sup, ServerName),
+ ?reply(Reply),
+ loop(Parent, ServerName, MSL1, Debug, Hib);
+ {From, Tag, stop} ->
+ catch terminate_server(normal, Parent, MSL, ServerName),
+ ?reply(ok);
+ {From, Tag, which_handlers} ->
+ ?reply(the_handlers(MSL)),
+ loop(Parent, ServerName, MSL, Debug, false);
+ {From, Tag, get_modules} ->
+ ?reply(get_modules(MSL)),
+ loop(Parent, ServerName, MSL, Debug, false);
+ Other ->
+ {Hib, MSL1} = server_notify(Other, handle_info, MSL, ServerName),
+ loop(Parent, ServerName, MSL1, Debug, Hib)
+ end.
+
+terminate_server(Reason, Parent, MSL, ServerName) ->
+ stop_handlers(MSL, ServerName),
+ do_unlink(Parent, MSL),
+ exit(Reason).
+
+%% unlink the supervisor process of all supervised handlers.
+%% We do not want a handler supervisor to EXIT due to the
+%% termination of the event manager (server).
+%% Do not unlink Parent !
+do_unlink(Parent, MSL) ->
+ lists:foreach(fun(Handler) when Handler#handler.supervised =:= Parent ->
+ true;
+ (Handler) when is_pid(Handler#handler.supervised) ->
+ unlink(Handler#handler.supervised),
+ true;
+ (_) ->
+ true
+ end,
+ MSL).
+
+%% First terminate the supervised (if exists) handlers and
+%% then inform other handlers.
+%% We do not know if any handler really is interested but it
+%% may be so !
+handle_exit(From, Reason, MSL, SName) ->
+ MSL1 = terminate_supervised(From, Reason, MSL, SName),
+ {_,MSL2}=server_notify({'EXIT', From, Reason}, handle_info, MSL1, SName),
+ MSL2.
+
+terminate_supervised(Pid, Reason, MSL, SName) ->
+ F = fun(Ha) when Ha#handler.supervised =:= Pid ->
+ do_terminate(Ha#handler.module,
+ Ha,
+ {stop,Reason},
+ Ha#handler.state,
+ {parent_terminated, {Pid,Reason}},
+ SName,
+ shutdown),
+ false;
+ (_) ->
+ true
+ end,
+ lists:filter(F, MSL).
+
+%%-----------------------------------------------------------------
+%% Callback functions for system messages handling.
+%%-----------------------------------------------------------------
+system_continue(Parent, Debug, [ServerName, MSL, Hib]) ->
+ loop(Parent, ServerName, MSL, Debug, Hib).
+
+-spec system_terminate(_, _, _, [_]) -> no_return().
+system_terminate(Reason, Parent, _Debug, [ServerName, MSL, _Hib]) ->
+ terminate_server(Reason, Parent, MSL, ServerName).
+
+%%-----------------------------------------------------------------
+%% Module here is sent in the system msg change_code. It specifies
+%% which module should be changed.
+%%-----------------------------------------------------------------
+system_code_change([ServerName, MSL, Hib], Module, OldVsn, Extra) ->
+ MSL1 = lists:zf(fun(H) when H#handler.module =:= Module ->
+ {ok, NewState} =
+ Module:code_change(OldVsn,
+ H#handler.state, Extra),
+ {true, H#handler{state = NewState}};
+ (_) -> true
+ end,
+ MSL),
+ {ok, [ServerName, MSL1, Hib]}.
+
+%%-----------------------------------------------------------------
+%% Format debug messages. Print them as the call-back module sees
+%% them, not as the real erlang messages. Use trace for that.
+%%-----------------------------------------------------------------
+print_event(Dev, {in, Msg}, Name) ->
+ case Msg of
+ {notify, Event} ->
+ io:format(Dev, "*DBG* ~p got event ~p~n", [Name, Event]);
+ {_,_,{call, Handler, Query}} ->
+ io:format(Dev, "*DBG* ~p(~p) got call ~p~n",
+ [Name, Handler, Query]);
+ _ ->
+ io:format(Dev, "*DBG* ~p got ~p~n", [Name, Msg])
+ end;
+print_event(Dev, Dbg, Name) ->
+ io:format(Dev, "*DBG* ~p : ~p~n", [Name, Dbg]).
+
+
+%% server_add_handler(Handler, Args, MSL) -> {Ret, MSL'}.
+%% where MSL = [#handler{}]
+%% Ret goes to the top level MSL' is the new internal state of the
+%% event handler
+
+server_add_handler({Mod,Id}, Args, MSL) ->
+ Handler = #handler{module = Mod,
+ id = Id},
+ server_add_handler(Mod, Handler, Args, MSL);
+server_add_handler(Mod, Args, MSL) ->
+ Handler = #handler{module = Mod},
+ server_add_handler(Mod, Handler, Args, MSL).
+
+server_add_handler(Mod, Handler, Args, MSL) ->
+ case catch Mod:init(Args) of
+ {ok, State} ->
+ {false, ok, [Handler#handler{state = State}|MSL]};
+ {ok, State, hibernate} ->
+ {true, ok, [Handler#handler{state = State}|MSL]};
+ Other ->
+ {false, Other, MSL}
+ end.
+
+%% Set up a link to the supervising process.
+%% (Ought to be unidirected links here, Erl5.0 !!)
+%% NOTE: This link will not be removed then the
+%% handler is removed in case another handler has
+%% own link to this process.
+server_add_sup_handler({Mod,Id}, Args, MSL, Parent) ->
+ link(Parent),
+ Handler = #handler{module = Mod,
+ id = Id,
+ supervised = Parent},
+ server_add_handler(Mod, Handler, Args, MSL);
+server_add_sup_handler(Mod, Args, MSL, Parent) ->
+ link(Parent),
+ Handler = #handler{module = Mod,
+ supervised = Parent},
+ server_add_handler(Mod, Handler, Args, MSL).
+
+%% server_delete_handler(HandlerId, Args, MSL) -> {Ret, MSL'}
+
+server_delete_handler(HandlerId, Args, MSL, SName) ->
+ case split(HandlerId, MSL) of
+ {Mod, Handler, MSL1} ->
+ {do_terminate(Mod, Handler, Args,
+ Handler#handler.state, delete, SName, normal),
+ MSL1};
+ error ->
+ {{error, module_not_found}, MSL}
+ end.
+
+%% server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, SN) -> MSL'
+%% server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, Sup, SN) -> MSL'
+
+server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, SName) ->
+ {State2, Sup, MSL1} = split_and_terminate(Handler1, Args1, MSL,
+ SName, Handler2, false),
+ case s_s_h(Sup, Handler2, {Args2, State2}, MSL1) of
+ {Hib, ok, MSL2} ->
+ {Hib, ok, MSL2};
+ {Hib, What, MSL2} ->
+ {Hib, {error, What}, MSL2}
+ end.
+
+server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, Sup, SName) ->
+ {State2, _, MSL1} = split_and_terminate(Handler1, Args1, MSL,
+ SName, Handler2, Sup),
+ case s_s_h(Sup, Handler2, {Args2, State2}, MSL1) of
+ {Hib, ok, MSL2} ->
+ {Hib, ok, MSL2};
+ {Hib, What, MSL2} ->
+ {Hib, {error, What}, MSL2}
+ end.
+
+s_s_h(false, Handler, Args, MSL) ->
+ server_add_handler(Handler, Args, MSL);
+s_s_h(Pid, Handler, Args, MSL) ->
+ server_add_sup_handler(Handler, Args, MSL, Pid).
+
+split_and_terminate(HandlerId, Args, MSL, SName, Handler2, Sup) ->
+ case split(HandlerId, MSL) of
+ {Mod, Handler, MSL1} ->
+ OldSup = Handler#handler.supervised,
+ NewSup = if
+ not Sup -> OldSup;
+ true -> Sup
+ end,
+ {do_terminate(Mod, Handler, Args,
+ Handler#handler.state, swapped, SName,
+ {swapped, Handler2, NewSup}),
+ OldSup,
+ MSL1};
+ error ->
+ {error, false, MSL}
+ end.
+
+%% server_notify(Event, Func, MSL, SName) -> MSL'
+
+server_notify(Event, Func, [Handler|T], SName) ->
+ case server_update(Handler, Func, Event, SName) of
+ {ok, Handler1} ->
+ {Hib, NewHandlers} = server_notify(Event, Func, T, SName),
+ {Hib, [Handler1|NewHandlers]};
+ {hibernate, Handler1} ->
+ {_Hib, NewHandlers} = server_notify(Event, Func, T, SName),
+ {true, [Handler1|NewHandlers]};
+ no ->
+ server_notify(Event, Func, T, SName)
+ end;
+server_notify(_, _, [], _) ->
+ {false, []}.
+
+%% server_update(Handler, Func, Event, ServerName) -> Handler1 | no
+
+server_update(Handler1, Func, Event, SName) ->
+ Mod1 = Handler1#handler.module,
+ State = Handler1#handler.state,
+ case catch Mod1:Func(Event, State) of
+ {ok, State1} ->
+ {ok, Handler1#handler{state = State1}};
+ {ok, State1, hibernate} ->
+ {hibernate, Handler1#handler{state = State1}};
+ {swap_handler, Args1, State1, Handler2, Args2} ->
+ do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName);
+ remove_handler ->
+ do_terminate(Mod1, Handler1, remove_handler, State,
+ remove, SName, normal),
+ no;
+ Other ->
+ do_terminate(Mod1, Handler1, {error, Other}, State,
+ Event, SName, crash),
+ no
+ end.
+
+do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName) ->
+ %% finalise the existing handler
+ State2 = do_terminate(Mod1, Handler1, Args1, State1,
+ swapped, SName,
+ {swapped, Handler2, Handler1#handler.supervised}),
+ {Mod2, Handler} = new_handler(Handler2, Handler1),
+ case catch Mod2:init({Args2, State2}) of
+ {ok, State2a} ->
+ {ok, Handler#handler{state = State2a}};
+ Other ->
+ report_terminate(Handler, crash, {error, Other}, SName, false),
+ no
+ end.
+
+new_handler({Mod,Id}, Handler1) ->
+ {Mod, #handler{module = Mod,
+ id = Id,
+ supervised = Handler1#handler.supervised}};
+new_handler(Mod, Handler1) ->
+ {Mod, #handler{module = Mod,
+ supervised = Handler1#handler.supervised}}.
+
+
+-spec split(handler(), [#handler{}]) ->
+ {atom(), #handler{}, [#handler{}]} | 'error'.
+
+split(Ha, MSL) -> split(Ha, MSL, []).
+
+split({Mod,Id}, [Ha|T], L) when Ha#handler.module =:= Mod,
+ Ha#handler.id =:= Id ->
+ {Mod, Ha, lists:reverse(L, T)};
+split(Mod, [Ha|T], L) when Ha#handler.module =:= Mod,
+ not Ha#handler.id ->
+ {Mod, Ha, lists:reverse(L, T)};
+split(Ha, [H|T], L) ->
+ split(Ha, T, [H|L]);
+split(_, [], _) ->
+ error.
+
+%% server_call(Handler, Query, MSL, ServerName) ->
+%% {Reply, MSL1}
+
+server_call(Handler, Query, MSL, SName) ->
+ case search(Handler, MSL) of
+ {ok, Ha} ->
+ case server_call_update(Ha, Query, SName) of
+ {no, Reply} ->
+ {false, Reply, delete(Handler, MSL)};
+ {{ok, Ha1}, Reply} ->
+ {false, Reply, replace(Handler, MSL, Ha1)};
+ {{hibernate, Ha1}, Reply} ->
+ {true, Reply, replace(Handler, MSL, Ha1)}
+ end;
+ false ->
+ {false, {error, bad_module}, MSL}
+ end.
+
+search({Mod, Id}, [Ha|_MSL]) when Ha#handler.module =:= Mod,
+ Ha#handler.id =:= Id ->
+ {ok, Ha};
+search(Mod, [Ha|_MSL]) when Ha#handler.module =:= Mod,
+ not Ha#handler.id ->
+ {ok, Ha};
+search(Handler, [_|MSL]) ->
+ search(Handler, MSL);
+search(_, []) ->
+ false.
+
+delete({Mod, Id}, [Ha|MSL]) when Ha#handler.module =:= Mod,
+ Ha#handler.id =:= Id ->
+ MSL;
+delete(Mod, [Ha|MSL]) when Ha#handler.module =:= Mod,
+ not Ha#handler.id ->
+ MSL;
+delete(Handler, [Ha|MSL]) ->
+ [Ha|delete(Handler, MSL)];
+delete(_, []) ->
+ [].
+
+replace({Mod, Id}, [Ha|MSL], NewHa) when Ha#handler.module =:= Mod,
+ Ha#handler.id =:= Id ->
+ [NewHa|MSL];
+replace(Mod, [Ha|MSL], NewHa) when Ha#handler.module =:= Mod,
+ not Ha#handler.id ->
+ [NewHa|MSL];
+replace(Handler, [Ha|MSL], NewHa) ->
+ [Ha|replace(Handler, MSL, NewHa)];
+replace(_, [], NewHa) ->
+ [NewHa].
+
+%% server_call_update(Handler, Query, ServerName) ->
+%% {{Handler1, State1} | 'no', Reply}
+
+server_call_update(Handler1, Query, SName) ->
+ Mod1 = Handler1#handler.module,
+ State = Handler1#handler.state,
+ case catch Mod1:handle_call(Query, State) of
+ {ok, Reply, State1} ->
+ {{ok, Handler1#handler{state = State1}}, Reply};
+ {ok, Reply, State1, hibernate} ->
+ {{hibernate, Handler1#handler{state = State1}},
+ Reply};
+ {swap_handler, Reply, Args1, State1, Handler2, Args2} ->
+ {do_swap(Mod1,Handler1,Args1,State1,Handler2,Args2,SName), Reply};
+ {remove_handler, Reply} ->
+ do_terminate(Mod1, Handler1, remove_handler, State,
+ remove, SName, normal),
+ {no, Reply};
+ Other ->
+ do_terminate(Mod1, Handler1, {error, Other}, State,
+ Query, SName, crash),
+ {no, {error, Other}}
+ end.
+
+do_terminate(Mod, Handler, Args, State, LastIn, SName, Reason) ->
+ Res = (catch Mod:terminate(Args, State)),
+ report_terminate(Handler, Reason, Args, State, LastIn, SName, Res),
+ Res.
+
+report_terminate(Handler, crash, {error, Why}, State, LastIn, SName, _) ->
+ report_terminate(Handler, Why, State, LastIn, SName);
+report_terminate(Handler, How, _, State, LastIn, SName, _) ->
+ %% How == normal | shutdown | {swapped, NewHandler, NewSupervisor}
+ report_terminate(Handler, How, State, LastIn, SName).
+
+report_terminate(Handler, Reason, State, LastIn, SName) ->
+ report_error(Handler, Reason, State, LastIn, SName),
+ case Handler#handler.supervised of
+ false ->
+ ok;
+ Pid ->
+ Pid ! {gen_event_EXIT,handler(Handler),Reason},
+ ok
+ end.
+
+report_error(_Handler, normal, _, _, _) -> ok;
+report_error(_Handler, shutdown, _, _, _) -> ok;
+report_error(_Handler, {swapped,_,_}, _, _, _) -> ok;
+report_error(Handler, Reason, State, LastIn, SName) ->
+ Reason1 =
+ case Reason of
+ {'EXIT',{undef,[{M,F,A}|MFAs]}} ->
+ case code:is_loaded(M) of
+ false ->
+ {'module could not be loaded',[{M,F,A}|MFAs]};
+ _ ->
+ case erlang:function_exported(M, F, length(A)) of
+ true ->
+ {undef,[{M,F,A}|MFAs]};
+ false ->
+ {'function not exported',[{M,F,A}|MFAs]}
+ end
+ end;
+ {'EXIT',Why} ->
+ Why;
+ _ ->
+ Reason
+ end,
+ error_msg("** gen_event handler ~p crashed.~n"
+ "** Was installed in ~p~n"
+ "** Last event was: ~p~n"
+ "** When handler state == ~p~n"
+ "** Reason == ~p~n",
+ [handler(Handler),SName,LastIn,State,Reason1]).
+
+handler(Handler) when not Handler#handler.id ->
+ Handler#handler.module;
+handler(Handler) ->
+ {Handler#handler.module, Handler#handler.id}.
+
+the_handlers(MSL) ->
+ [handler(Handler) || Handler <- MSL].
+
+%% stop_handlers(MSL, ServerName) -> []
+
+stop_handlers([Handler|T], SName) ->
+ Mod = Handler#handler.module,
+ do_terminate(Mod, Handler, stop, Handler#handler.state,
+ stop, SName, shutdown),
+ stop_handlers(T, SName);
+stop_handlers([], _) ->
+ [].
+
+%% Message from the release_handler.
+%% The list of modules got to be a set !
+get_modules(MSL) ->
+ Mods = [Handler#handler.module || Handler <- MSL],
+ ordsets:to_list(ordsets:from_list(Mods)).
+
+%%-----------------------------------------------------------------
+%% Status information
+%%-----------------------------------------------------------------
+format_status(_Opt, StatusData) ->
+ [_PDict, SysState, Parent, _Debug, [ServerName, MSL, _Hib]] = StatusData,
+ Header = lists:concat(["Status for event handler ", ServerName]),
+ [{header, Header},
+ {data, [{"Status", SysState},
+ {"Parent", Parent}]},
+ {items, {"Installed handlers", MSL}}].
diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl
new file mode 100644
index 0000000000..f3775f967a
--- /dev/null
+++ b/lib/stdlib/src/gen_fsm.erl
@@ -0,0 +1,623 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen_fsm).
+
+%%%-----------------------------------------------------------------
+%%%
+%%% This state machine is somewhat more pure than state_lib. It is
+%%% still based on State dispatching (one function per state), but
+%%% allows a function handle_event to take care of events in all states.
+%%% It's not that pure anymore :( We also allow synchronized event sending.
+%%%
+%%% If the Parent process terminates the Module:terminate/2
+%%% function is called.
+%%%
+%%% The user module should export:
+%%%
+%%% init(Args)
+%%% ==> {ok, StateName, StateData}
+%%% {ok, StateName, StateData, Timeout}
+%%% ignore
+%%% {stop, Reason}
+%%%
+%%% StateName(Msg, StateData)
+%%%
+%%% ==> {next_state, NewStateName, NewStateData}
+%%% {next_state, NewStateName, NewStateData, Timeout}
+%%% {stop, Reason, NewStateData}
+%%% Reason = normal | shutdown | Term terminate(State) is called
+%%%
+%%% StateName(Msg, From, StateData)
+%%%
+%%% ==> {next_state, NewStateName, NewStateData}
+%%% {next_state, NewStateName, NewStateData, Timeout}
+%%% {reply, Reply, NewStateName, NewStateData}
+%%% {reply, Reply, NewStateName, NewStateData, Timeout}
+%%% {stop, Reason, NewStateData}
+%%% Reason = normal | shutdown | Term terminate(State) is called
+%%%
+%%% handle_event(Msg, StateName, StateData)
+%%%
+%%% ==> {next_state, NewStateName, NewStateData}
+%%% {next_state, NewStateName, NewStateData, Timeout}
+%%% {stop, Reason, Reply, NewStateData}
+%%% {stop, Reason, NewStateData}
+%%% Reason = normal | shutdown | Term terminate(State) is called
+%%%
+%%% handle_sync_event(Msg, From, StateName, StateData)
+%%%
+%%% ==> {next_state, NewStateName, NewStateData}
+%%% {next_state, NewStateName, NewStateData, Timeout}
+%%% {reply, Reply, NewStateName, NewStateData}
+%%% {reply, Reply, NewStateName, NewStateData, Timeout}
+%%% {stop, Reason, Reply, NewStateData}
+%%% {stop, Reason, NewStateData}
+%%% Reason = normal | shutdown | Term terminate(State) is called
+%%%
+%%% handle_info(Info, StateName) (e.g. {'EXIT', P, R}, {nodedown, N}, ...
+%%%
+%%% ==> {next_state, NewStateName, NewStateData}
+%%% {next_state, NewStateName, NewStateData, Timeout}
+%%% {stop, Reason, NewStateData}
+%%% Reason = normal | shutdown | Term terminate(State) is called
+%%%
+%%% terminate(Reason, StateName, StateData) Let the user module clean up
+%%% always called when server terminates
+%%%
+%%% ==> the return value is ignored
+%%%
+%%%
+%%% The work flow (of the fsm) can be described as follows:
+%%%
+%%% User module fsm
+%%% ----------- -------
+%%% start -----> start
+%%% init <----- .
+%%%
+%%% loop
+%%% StateName <----- .
+%%%
+%%% handle_event <----- .
+%%%
+%%% handle__sunc_event <----- .
+%%%
+%%% handle_info <----- .
+%%%
+%%% terminate <----- .
+%%%
+%%%
+%%% ---------------------------------------------------
+
+-export([start/3, start/4,
+ start_link/3, start_link/4,
+ send_event/2, sync_send_event/2, sync_send_event/3,
+ send_all_state_event/2,
+ sync_send_all_state_event/2, sync_send_all_state_event/3,
+ reply/2,
+ start_timer/2,send_event_after/2,cancel_timer/1,
+ enter_loop/4, enter_loop/5, enter_loop/6, wake_hib/6]).
+
+-export([behaviour_info/1]).
+
+%% Internal exports
+-export([init_it/6, print_event/3,
+ system_continue/3,
+ system_terminate/4,
+ system_code_change/4,
+ format_status/2]).
+
+-import(error_logger, [format/2]).
+
+%%% ---------------------------------------------------
+%%% Interface functions.
+%%% ---------------------------------------------------
+
+-spec behaviour_info(atom()) -> 'undefined' | [{atom(), arity()}].
+
+behaviour_info(callbacks) ->
+ [{init,1},{handle_event,3},{handle_sync_event,4},{handle_info,3},
+ {terminate,3},{code_change,4}];
+behaviour_info(_Other) ->
+ undefined.
+
+%%% ---------------------------------------------------
+%%% Starts a generic state machine.
+%%% start(Mod, Args, Options)
+%%% start(Name, Mod, Args, Options)
+%%% start_link(Mod, Args, Options)
+%%% start_link(Name, Mod, Args, Options) where:
+%%% Name ::= {local, atom()} | {global, atom()}
+%%% Mod ::= atom(), callback module implementing the 'real' fsm
+%%% Args ::= term(), init arguments (to Mod:init/1)
+%%% Options ::= [{debug, [Flag]}]
+%%% Flag ::= trace | log | {logfile, File} | statistics | debug
+%%% (debug == log && statistics)
+%%% Returns: {ok, Pid} |
+%%% {error, {already_started, Pid}} |
+%%% {error, Reason}
+%%% ---------------------------------------------------
+start(Mod, Args, Options) ->
+ gen:start(?MODULE, nolink, Mod, Args, Options).
+
+start(Name, Mod, Args, Options) ->
+ gen:start(?MODULE, nolink, Name, Mod, Args, Options).
+
+start_link(Mod, Args, Options) ->
+ gen:start(?MODULE, link, Mod, Args, Options).
+
+start_link(Name, Mod, Args, Options) ->
+ gen:start(?MODULE, link, Name, Mod, Args, Options).
+
+
+send_event({global, Name}, Event) ->
+ catch global:send(Name, {'$gen_event', Event}),
+ ok;
+send_event(Name, Event) ->
+ Name ! {'$gen_event', Event},
+ ok.
+
+sync_send_event(Name, Event) ->
+ case catch gen:call(Name, '$gen_sync_event', Event) of
+ {ok,Res} ->
+ Res;
+ {'EXIT',Reason} ->
+ exit({Reason, {?MODULE, sync_send_event, [Name, Event]}})
+ end.
+
+sync_send_event(Name, Event, Timeout) ->
+ case catch gen:call(Name, '$gen_sync_event', Event, Timeout) of
+ {ok,Res} ->
+ Res;
+ {'EXIT',Reason} ->
+ exit({Reason, {?MODULE, sync_send_event, [Name, Event, Timeout]}})
+ end.
+
+send_all_state_event({global, Name}, Event) ->
+ catch global:send(Name, {'$gen_all_state_event', Event}),
+ ok;
+send_all_state_event(Name, Event) ->
+ Name ! {'$gen_all_state_event', Event},
+ ok.
+
+sync_send_all_state_event(Name, Event) ->
+ case catch gen:call(Name, '$gen_sync_all_state_event', Event) of
+ {ok,Res} ->
+ Res;
+ {'EXIT',Reason} ->
+ exit({Reason, {?MODULE, sync_send_all_state_event, [Name, Event]}})
+ end.
+
+sync_send_all_state_event(Name, Event, Timeout) ->
+ case catch gen:call(Name, '$gen_sync_all_state_event', Event, Timeout) of
+ {ok,Res} ->
+ Res;
+ {'EXIT',Reason} ->
+ exit({Reason, {?MODULE, sync_send_all_state_event,
+ [Name, Event, Timeout]}})
+ end.
+
+%% Designed to be only callable within one of the callbacks
+%% hence using the self() of this instance of the process.
+%% This is to ensure that timers don't go astray in global
+%% e.g. when straddling a failover, or turn up in a restarted
+%% instance of the process.
+
+%% Returns Ref, sends event {timeout,Ref,Msg} after Time
+%% to the (then) current state.
+start_timer(Time, Msg) ->
+ erlang:start_timer(Time, self(), {'$gen_timer', Msg}).
+
+%% Returns Ref, sends Event after Time to the (then) current state.
+send_event_after(Time, Event) ->
+ erlang:start_timer(Time, self(), {'$gen_event', Event}).
+
+%% Returns the remaing time for the timer if Ref referred to
+%% an active timer/send_event_after, false otherwise.
+cancel_timer(Ref) ->
+ case erlang:cancel_timer(Ref) of
+ false ->
+ receive {timeout, Ref, _} -> 0
+ after 0 -> false
+ end;
+ RemainingTime ->
+ RemainingTime
+ end.
+
+%% enter_loop/4,5,6
+%% Makes an existing process into a gen_fsm.
+%% The calling process will enter the gen_fsm receive loop and become a
+%% gen_fsm process.
+%% The process *must* have been started using one of the start functions
+%% in proc_lib, see proc_lib(3).
+%% The user is responsible for any initialization of the process,
+%% including registering a name for it.
+enter_loop(Mod, Options, StateName, StateData) ->
+ enter_loop(Mod, Options, StateName, StateData, self(), infinity).
+
+enter_loop(Mod, Options, StateName, StateData, ServerName = {_,_}) ->
+ enter_loop(Mod, Options, StateName, StateData, ServerName,infinity);
+enter_loop(Mod, Options, StateName, StateData, Timeout) ->
+ enter_loop(Mod, Options, StateName, StateData, self(), Timeout).
+
+enter_loop(Mod, Options, StateName, StateData, ServerName, Timeout) ->
+ Name = get_proc_name(ServerName),
+ Parent = get_parent(),
+ Debug = gen:debug_options(Options),
+ loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug).
+
+get_proc_name(Pid) when is_pid(Pid) ->
+ Pid;
+get_proc_name({local, Name}) ->
+ case process_info(self(), registered_name) of
+ {registered_name, Name} ->
+ Name;
+ {registered_name, _Name} ->
+ exit(process_not_registered);
+ [] ->
+ exit(process_not_registered)
+ end;
+get_proc_name({global, Name}) ->
+ case global:safe_whereis_name(Name) of
+ undefined ->
+ exit(process_not_registered_globally);
+ Pid when Pid =:= self() ->
+ Name;
+ _Pid ->
+ exit(process_not_registered_globally)
+ end.
+
+get_parent() ->
+ case get('$ancestors') of
+ [Parent | _] when is_pid(Parent) ->
+ Parent;
+ [Parent | _] when is_atom(Parent) ->
+ name_to_pid(Parent);
+ _ ->
+ exit(process_was_not_started_by_proc_lib)
+ end.
+
+name_to_pid(Name) ->
+ case whereis(Name) of
+ undefined ->
+ case global:safe_whereis_name(Name) of
+ undefined ->
+ exit(could_not_find_registerd_name);
+ Pid ->
+ Pid
+ end;
+ Pid ->
+ Pid
+ end.
+
+%%% ---------------------------------------------------
+%%% Initiate the new process.
+%%% Register the name using the Rfunc function
+%%% Calls the Mod:init/Args function.
+%%% Finally an acknowledge is sent to Parent and the main
+%%% loop is entered.
+%%% ---------------------------------------------------
+init_it(Starter, self, Name, Mod, Args, Options) ->
+ init_it(Starter, self(), Name, Mod, Args, Options);
+init_it(Starter, Parent, Name0, Mod, Args, Options) ->
+ Name = name(Name0),
+ Debug = gen:debug_options(Options),
+ case catch Mod:init(Args) of
+ {ok, StateName, StateData} ->
+ proc_lib:init_ack(Starter, {ok, self()}),
+ loop(Parent, Name, StateName, StateData, Mod, infinity, Debug);
+ {ok, StateName, StateData, Timeout} ->
+ proc_lib:init_ack(Starter, {ok, self()}),
+ loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug);
+ {stop, Reason} ->
+ proc_lib:init_ack(Starter, {error, Reason}),
+ exit(Reason);
+ ignore ->
+ proc_lib:init_ack(Starter, ignore),
+ exit(normal);
+ {'EXIT', Reason} ->
+ proc_lib:init_ack(Starter, {error, Reason}),
+ exit(Reason);
+ Else ->
+ Error = {bad_return_value, Else},
+ proc_lib:init_ack(Starter, {error, Error}),
+ exit(Error)
+ end.
+
+name({local,Name}) -> Name;
+name({global,Name}) -> Name;
+name(Pid) when is_pid(Pid) -> Pid.
+
+%%-----------------------------------------------------------------
+%% The MAIN loop
+%%-----------------------------------------------------------------
+loop(Parent, Name, StateName, StateData, Mod, hibernate, Debug) ->
+ proc_lib:hibernate(?MODULE,wake_hib,
+ [Parent, Name, StateName, StateData, Mod,
+ Debug]);
+loop(Parent, Name, StateName, StateData, Mod, Time, Debug) ->
+ Msg = receive
+ Input ->
+ Input
+ after Time ->
+ {'$gen_event', timeout}
+ end,
+ decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug, false).
+
+wake_hib(Parent, Name, StateName, StateData, Mod, Debug) ->
+ Msg = receive
+ Input ->
+ Input
+ end,
+ decode_msg(Msg, Parent, Name, StateName, StateData, Mod, hibernate, Debug, true).
+
+decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug, Hib) ->
+ case Msg of
+ {system, From, Req} ->
+ sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
+ [Name, StateName, StateData, Mod, Time], Hib);
+ {'EXIT', Parent, Reason} ->
+ terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug);
+ _Msg when Debug =:= [] ->
+ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time);
+ _Msg ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ {Name, StateName}, {in, Msg}),
+ handle_msg(Msg, Parent, Name, StateName, StateData,
+ Mod, Time, Debug1)
+ end.
+
+%%-----------------------------------------------------------------
+%% Callback functions for system messages handling.
+%%-----------------------------------------------------------------
+system_continue(Parent, Debug, [Name, StateName, StateData, Mod, Time]) ->
+ loop(Parent, Name, StateName, StateData, Mod, Time, Debug).
+
+-spec system_terminate(term(), _, _, [term(),...]) -> no_return().
+
+system_terminate(Reason, _Parent, Debug,
+ [Name, StateName, StateData, Mod, _Time]) ->
+ terminate(Reason, Name, [], Mod, StateName, StateData, Debug).
+
+system_code_change([Name, StateName, StateData, Mod, Time],
+ _Module, OldVsn, Extra) ->
+ case catch Mod:code_change(OldVsn, StateName, StateData, Extra) of
+ {ok, NewStateName, NewStateData} ->
+ {ok, [Name, NewStateName, NewStateData, Mod, Time]};
+ Else -> Else
+ end.
+
+%%-----------------------------------------------------------------
+%% Format debug messages. Print them as the call-back module sees
+%% them, not as the real erlang messages. Use trace for that.
+%%-----------------------------------------------------------------
+print_event(Dev, {in, Msg}, {Name, StateName}) ->
+ case Msg of
+ {'$gen_event', Event} ->
+ io:format(Dev, "*DBG* ~p got event ~p in state ~w~n",
+ [Name, Event, StateName]);
+ {'$gen_all_state_event', Event} ->
+ io:format(Dev,
+ "*DBG* ~p got all_state_event ~p in state ~w~n",
+ [Name, Event, StateName]);
+ {timeout, Ref, {'$gen_timer', Message}} ->
+ io:format(Dev,
+ "*DBG* ~p got timer ~p in state ~w~n",
+ [Name, {timeout, Ref, Message}, StateName]);
+ {timeout, _Ref, {'$gen_event', Event}} ->
+ io:format(Dev,
+ "*DBG* ~p got timer ~p in state ~w~n",
+ [Name, Event, StateName]);
+ _ ->
+ io:format(Dev, "*DBG* ~p got ~p in state ~w~n",
+ [Name, Msg, StateName])
+ end;
+print_event(Dev, {out, Msg, To, StateName}, Name) ->
+ io:format(Dev, "*DBG* ~p sent ~p to ~w~n"
+ " and switched to state ~w~n",
+ [Name, Msg, To, StateName]);
+print_event(Dev, return, {Name, StateName}) ->
+ io:format(Dev, "*DBG* ~p switched to state ~w~n",
+ [Name, StateName]).
+
+handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time) -> %No debug here
+ From = from(Msg),
+ case catch dispatch(Msg, Mod, StateName, StateData) of
+ {next_state, NStateName, NStateData} ->
+ loop(Parent, Name, NStateName, NStateData, Mod, infinity, []);
+ {next_state, NStateName, NStateData, Time1} ->
+ loop(Parent, Name, NStateName, NStateData, Mod, Time1, []);
+ {reply, Reply, NStateName, NStateData} when From =/= undefined ->
+ reply(From, Reply),
+ loop(Parent, Name, NStateName, NStateData, Mod, infinity, []);
+ {reply, Reply, NStateName, NStateData, Time1} when From =/= undefined ->
+ reply(From, Reply),
+ loop(Parent, Name, NStateName, NStateData, Mod, Time1, []);
+ {stop, Reason, NStateData} ->
+ terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
+ {stop, Reason, Reply, NStateData} when From =/= undefined ->
+ {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
+ StateName, NStateData, [])),
+ reply(From, Reply),
+ exit(R);
+ {'EXIT', What} ->
+ terminate(What, Name, Msg, Mod, StateName, StateData, []);
+ Reply ->
+ terminate({bad_return_value, Reply},
+ Name, Msg, Mod, StateName, StateData, [])
+ end.
+
+handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, Debug) ->
+ From = from(Msg),
+ case catch dispatch(Msg, Mod, StateName, StateData) of
+ {next_state, NStateName, NStateData} ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ {Name, NStateName}, return),
+ loop(Parent, Name, NStateName, NStateData, Mod, infinity, Debug1);
+ {next_state, NStateName, NStateData, Time1} ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ {Name, NStateName}, return),
+ loop(Parent, Name, NStateName, NStateData, Mod, Time1, Debug1);
+ {reply, Reply, NStateName, NStateData} when From =/= undefined ->
+ Debug1 = reply(Name, From, Reply, Debug, NStateName),
+ loop(Parent, Name, NStateName, NStateData, Mod, infinity, Debug1);
+ {reply, Reply, NStateName, NStateData, Time1} when From =/= undefined ->
+ Debug1 = reply(Name, From, Reply, Debug, NStateName),
+ loop(Parent, Name, NStateName, NStateData, Mod, Time1, Debug1);
+ {stop, Reason, NStateData} ->
+ terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug);
+ {stop, Reason, Reply, NStateData} when From =/= undefined ->
+ {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
+ StateName, NStateData, Debug)),
+ reply(Name, From, Reply, Debug, StateName),
+ exit(R);
+ {'EXIT', What} ->
+ terminate(What, Name, Msg, Mod, StateName, StateData, Debug);
+ Reply ->
+ terminate({bad_return_value, Reply},
+ Name, Msg, Mod, StateName, StateData, Debug)
+ end.
+
+dispatch({'$gen_event', Event}, Mod, StateName, StateData) ->
+ Mod:StateName(Event, StateData);
+dispatch({'$gen_all_state_event', Event}, Mod, StateName, StateData) ->
+ Mod:handle_event(Event, StateName, StateData);
+dispatch({'$gen_sync_event', From, Event}, Mod, StateName, StateData) ->
+ Mod:StateName(Event, From, StateData);
+dispatch({'$gen_sync_all_state_event', From, Event},
+ Mod, StateName, StateData) ->
+ Mod:handle_sync_event(Event, From, StateName, StateData);
+dispatch({timeout, Ref, {'$gen_timer', Msg}}, Mod, StateName, StateData) ->
+ Mod:StateName({timeout, Ref, Msg}, StateData);
+dispatch({timeout, _Ref, {'$gen_event', Event}}, Mod, StateName, StateData) ->
+ Mod:StateName(Event, StateData);
+dispatch(Info, Mod, StateName, StateData) ->
+ Mod:handle_info(Info, StateName, StateData).
+
+from({'$gen_sync_event', From, _Event}) -> From;
+from({'$gen_sync_all_state_event', From, _Event}) -> From;
+from(_) -> undefined.
+
+%% Send a reply to the client.
+reply({To, Tag}, Reply) ->
+ catch To ! {Tag, Reply}.
+
+reply(Name, {To, Tag}, Reply, Debug, StateName) ->
+ reply({To, Tag}, Reply),
+ sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ {out, Reply, To, StateName}).
+
+%%% ---------------------------------------------------
+%%% Terminate the server.
+%%% ---------------------------------------------------
+
+-spec terminate(term(), _, _, atom(), _, _, _) -> no_return().
+
+terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
+ case catch Mod:terminate(Reason, StateName, StateData) of
+ {'EXIT', R} ->
+ error_info(R, Name, Msg, StateName, StateData, Debug),
+ exit(R);
+ _ ->
+ case Reason of
+ normal ->
+ exit(normal);
+ shutdown ->
+ exit(shutdown);
+ {shutdown,_}=Shutdown ->
+ exit(Shutdown);
+ _ ->
+ error_info(Reason, Name, Msg, StateName, StateData, Debug),
+ exit(Reason)
+ end
+ end.
+
+error_info(Reason, Name, Msg, StateName, StateData, Debug) ->
+ Reason1 =
+ case Reason of
+ {undef,[{M,F,A}|MFAs]} ->
+ case code:is_loaded(M) of
+ false ->
+ {'module could not be loaded',[{M,F,A}|MFAs]};
+ _ ->
+ case erlang:function_exported(M, F, length(A)) of
+ true ->
+ Reason;
+ false ->
+ {'function not exported',[{M,F,A}|MFAs]}
+ end
+ end;
+ _ ->
+ Reason
+ end,
+ Str = "** State machine ~p terminating \n" ++
+ get_msg_str(Msg) ++
+ "** When State == ~p~n"
+ "** Data == ~p~n"
+ "** Reason for termination = ~n** ~p~n",
+ format(Str, [Name, get_msg(Msg), StateName, StateData, Reason1]),
+ sys:print_log(Debug),
+ ok.
+
+get_msg_str({'$gen_event', _Event}) ->
+ "** Last event in was ~p~n";
+get_msg_str({'$gen_sync_event', _Event}) ->
+ "** Last sync event in was ~p~n";
+get_msg_str({'$gen_all_state_event', _Event}) ->
+ "** Last event in was ~p (for all states)~n";
+get_msg_str({'$gen_sync_all_state_event', _Event}) ->
+ "** Last sync event in was ~p (for all states)~n";
+get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) ->
+ "** Last timer event in was ~p~n";
+get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
+ "** Last timer event in was ~p~n";
+get_msg_str(_Msg) ->
+ "** Last message in was ~p~n".
+
+get_msg({'$gen_event', Event}) -> Event;
+get_msg({'$gen_sync_event', Event}) -> Event;
+get_msg({'$gen_all_state_event', Event}) -> Event;
+get_msg({'$gen_sync_all_state_event', Event}) -> Event;
+get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> {timeout, Ref, Msg};
+get_msg({timeout, _Ref, {'$gen_event', Event}}) -> Event;
+get_msg(Msg) -> Msg.
+
+%%-----------------------------------------------------------------
+%% Status information
+%%-----------------------------------------------------------------
+format_status(Opt, StatusData) ->
+ [PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time]] =
+ StatusData,
+ Header = lists:concat(["Status for state machine ", Name]),
+ Log = sys:get_debug(log, Debug, []),
+ Specfic =
+ case erlang:function_exported(Mod, format_status, 2) of
+ true ->
+ case catch Mod:format_status(Opt,[PDict,StateData]) of
+ {'EXIT', _} -> [{data, [{"StateData", StateData}]}];
+ Else -> Else
+ end;
+ _ ->
+ [{data, [{"StateData", StateData}]}]
+ end,
+ [{header, Header},
+ {data, [{"Status", SysState},
+ {"Parent", Parent},
+ {"Logged events", Log},
+ {"StateName", StateName}]} |
+ Specfic].
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
new file mode 100644
index 0000000000..f1a9a31c63
--- /dev/null
+++ b/lib/stdlib/src/gen_server.erl
@@ -0,0 +1,853 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen_server).
+
+%%% ---------------------------------------------------
+%%%
+%%% The idea behind THIS server is that the user module
+%%% provides (different) functions to handle different
+%%% kind of inputs.
+%%% If the Parent process terminates the Module:terminate/2
+%%% function is called.
+%%%
+%%% The user module should export:
+%%%
+%%% init(Args)
+%%% ==> {ok, State}
+%%% {ok, State, Timeout}
+%%% ignore
+%%% {stop, Reason}
+%%%
+%%% handle_call(Msg, {From, Tag}, State)
+%%%
+%%% ==> {reply, Reply, State}
+%%% {reply, Reply, State, Timeout}
+%%% {noreply, State}
+%%% {noreply, State, Timeout}
+%%% {stop, Reason, Reply, State}
+%%% Reason = normal | shutdown | Term terminate(State) is called
+%%%
+%%% handle_cast(Msg, State)
+%%%
+%%% ==> {noreply, State}
+%%% {noreply, State, Timeout}
+%%% {stop, Reason, State}
+%%% Reason = normal | shutdown | Term terminate(State) is called
+%%%
+%%% handle_info(Info, State) Info is e.g. {'EXIT', P, R}, {nodedown, N}, ...
+%%%
+%%% ==> {noreply, State}
+%%% {noreply, State, Timeout}
+%%% {stop, Reason, State}
+%%% Reason = normal | shutdown | Term, terminate(State) is called
+%%%
+%%% terminate(Reason, State) Let the user module clean up
+%%% always called when server terminates
+%%%
+%%% ==> ok
+%%%
+%%%
+%%% The work flow (of the server) can be described as follows:
+%%%
+%%% User module Generic
+%%% ----------- -------
+%%% start -----> start
+%%% init <----- .
+%%%
+%%% loop
+%%% handle_call <----- .
+%%% -----> reply
+%%%
+%%% handle_cast <----- .
+%%%
+%%% handle_info <----- .
+%%%
+%%% terminate <----- .
+%%%
+%%% -----> reply
+%%%
+%%%
+%%% ---------------------------------------------------
+
+%% API
+-export([start/3, start/4,
+ start_link/3, start_link/4,
+ call/2, call/3,
+ cast/2, reply/2,
+ abcast/2, abcast/3,
+ multi_call/2, multi_call/3, multi_call/4,
+ enter_loop/3, enter_loop/4, enter_loop/5, wake_hib/5]).
+
+-export([behaviour_info/1]).
+
+%% System exports
+-export([system_continue/3,
+ system_terminate/4,
+ system_code_change/4,
+ format_status/2]).
+
+%% Internal exports
+-export([init_it/6, print_event/3]).
+
+-import(error_logger, [format/2]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec behaviour_info(atom()) -> 'undefined' | [{atom(), arity()}].
+
+behaviour_info(callbacks) ->
+ [{init,1},{handle_call,3},{handle_cast,2},{handle_info,2},
+ {terminate,2},{code_change,3}];
+behaviour_info(_Other) ->
+ undefined.
+
+%%% -----------------------------------------------------------------
+%%% Starts a generic server.
+%%% start(Mod, Args, Options)
+%%% start(Name, Mod, Args, Options)
+%%% start_link(Mod, Args, Options)
+%%% start_link(Name, Mod, Args, Options) where:
+%%% Name ::= {local, atom()} | {global, atom()}
+%%% Mod ::= atom(), callback module implementing the 'real' server
+%%% Args ::= term(), init arguments (to Mod:init/1)
+%%% Options ::= [{timeout, Timeout} | {debug, [Flag]}]
+%%% Flag ::= trace | log | {logfile, File} | statistics | debug
+%%% (debug == log && statistics)
+%%% Returns: {ok, Pid} |
+%%% {error, {already_started, Pid}} |
+%%% {error, Reason}
+%%% -----------------------------------------------------------------
+start(Mod, Args, Options) ->
+ gen:start(?MODULE, nolink, Mod, Args, Options).
+
+start(Name, Mod, Args, Options) ->
+ gen:start(?MODULE, nolink, Name, Mod, Args, Options).
+
+start_link(Mod, Args, Options) ->
+ gen:start(?MODULE, link, Mod, Args, Options).
+
+start_link(Name, Mod, Args, Options) ->
+ gen:start(?MODULE, link, Name, Mod, Args, Options).
+
+
+%% -----------------------------------------------------------------
+%% Make a call to a generic server.
+%% If the server is located at another node, that node will
+%% be monitored.
+%% If the client is trapping exits and is linked server termination
+%% is handled here (? Shall we do that here (or rely on timeouts) ?).
+%% -----------------------------------------------------------------
+call(Name, Request) ->
+ case catch gen:call(Name, '$gen_call', Request) of
+ {ok,Res} ->
+ Res;
+ {'EXIT',Reason} ->
+ exit({Reason, {?MODULE, call, [Name, Request]}})
+ end.
+
+call(Name, Request, Timeout) ->
+ case catch gen:call(Name, '$gen_call', Request, Timeout) of
+ {ok,Res} ->
+ Res;
+ {'EXIT',Reason} ->
+ exit({Reason, {?MODULE, call, [Name, Request, Timeout]}})
+ end.
+
+%% -----------------------------------------------------------------
+%% Make a cast to a generic server.
+%% -----------------------------------------------------------------
+cast({global,Name}, Request) ->
+ catch global:send(Name, cast_msg(Request)),
+ ok;
+cast({Name,Node}=Dest, Request) when is_atom(Name), is_atom(Node) ->
+ do_cast(Dest, Request);
+cast(Dest, Request) when is_atom(Dest) ->
+ do_cast(Dest, Request);
+cast(Dest, Request) when is_pid(Dest) ->
+ do_cast(Dest, Request).
+
+do_cast(Dest, Request) ->
+ do_send(Dest, cast_msg(Request)),
+ ok.
+
+cast_msg(Request) -> {'$gen_cast',Request}.
+
+%% -----------------------------------------------------------------
+%% Send a reply to the client.
+%% -----------------------------------------------------------------
+reply({To, Tag}, Reply) ->
+ catch To ! {Tag, Reply}.
+
+%% -----------------------------------------------------------------
+%% Asyncronous broadcast, returns nothing, it's just send'n prey
+%%-----------------------------------------------------------------
+abcast(Name, Request) when is_atom(Name) ->
+ do_abcast([node() | nodes()], Name, cast_msg(Request)).
+
+abcast(Nodes, Name, Request) when is_list(Nodes), is_atom(Name) ->
+ do_abcast(Nodes, Name, cast_msg(Request)).
+
+do_abcast([Node|Nodes], Name, Msg) when is_atom(Node) ->
+ do_send({Name,Node},Msg),
+ do_abcast(Nodes, Name, Msg);
+do_abcast([], _,_) -> abcast.
+
+%%% -----------------------------------------------------------------
+%%% Make a call to servers at several nodes.
+%%% Returns: {[Replies],[BadNodes]}
+%%% A Timeout can be given
+%%%
+%%% A middleman process is used in case late answers arrives after
+%%% the timeout. If they would be allowed to glog the callers message
+%%% queue, it would probably become confused. Late answers will
+%%% now arrive to the terminated middleman and so be discarded.
+%%% -----------------------------------------------------------------
+multi_call(Name, Req)
+ when is_atom(Name) ->
+ do_multi_call([node() | nodes()], Name, Req, infinity).
+
+multi_call(Nodes, Name, Req)
+ when is_list(Nodes), is_atom(Name) ->
+ do_multi_call(Nodes, Name, Req, infinity).
+
+multi_call(Nodes, Name, Req, infinity) ->
+ do_multi_call(Nodes, Name, Req, infinity);
+multi_call(Nodes, Name, Req, Timeout)
+ when is_list(Nodes), is_atom(Name), is_integer(Timeout), Timeout >= 0 ->
+ do_multi_call(Nodes, Name, Req, Timeout).
+
+
+%%-----------------------------------------------------------------
+%% enter_loop(Mod, Options, State, <ServerName>, <TimeOut>) ->_
+%%
+%% Description: Makes an existing process into a gen_server.
+%% The calling process will enter the gen_server receive
+%% loop and become a gen_server process.
+%% The process *must* have been started using one of the
+%% start functions in proc_lib, see proc_lib(3).
+%% The user is responsible for any initialization of the
+%% process, including registering a name for it.
+%%-----------------------------------------------------------------
+enter_loop(Mod, Options, State) ->
+ enter_loop(Mod, Options, State, self(), infinity).
+
+enter_loop(Mod, Options, State, ServerName = {_, _}) ->
+ enter_loop(Mod, Options, State, ServerName, infinity);
+
+enter_loop(Mod, Options, State, Timeout) ->
+ enter_loop(Mod, Options, State, self(), Timeout).
+
+enter_loop(Mod, Options, State, ServerName, Timeout) ->
+ Name = get_proc_name(ServerName),
+ Parent = get_parent(),
+ Debug = debug_options(Name, Options),
+ loop(Parent, Name, State, Mod, Timeout, Debug).
+
+%%%========================================================================
+%%% Gen-callback functions
+%%%========================================================================
+
+%%% ---------------------------------------------------
+%%% Initiate the new process.
+%%% Register the name using the Rfunc function
+%%% Calls the Mod:init/Args function.
+%%% Finally an acknowledge is sent to Parent and the main
+%%% loop is entered.
+%%% ---------------------------------------------------
+init_it(Starter, self, Name, Mod, Args, Options) ->
+ init_it(Starter, self(), Name, Mod, Args, Options);
+init_it(Starter, Parent, Name0, Mod, Args, Options) ->
+ Name = name(Name0),
+ Debug = debug_options(Name, Options),
+ case catch Mod:init(Args) of
+ {ok, State} ->
+ proc_lib:init_ack(Starter, {ok, self()}),
+ loop(Parent, Name, State, Mod, infinity, Debug);
+ {ok, State, Timeout} ->
+ proc_lib:init_ack(Starter, {ok, self()}),
+ loop(Parent, Name, State, Mod, Timeout, Debug);
+ {stop, Reason} ->
+ %% For consistency, we must make sure that the
+ %% registered name (if any) is unregistered before
+ %% the parent process is notified about the failure.
+ %% (Otherwise, the parent process could get
+ %% an 'already_started' error if it immediately
+ %% tried starting the process again.)
+ unregister_name(Name0),
+ proc_lib:init_ack(Starter, {error, Reason}),
+ exit(Reason);
+ ignore ->
+ unregister_name(Name0),
+ proc_lib:init_ack(Starter, ignore),
+ exit(normal);
+ {'EXIT', Reason} ->
+ unregister_name(Name0),
+ proc_lib:init_ack(Starter, {error, Reason}),
+ exit(Reason);
+ Else ->
+ Error = {bad_return_value, Else},
+ proc_lib:init_ack(Starter, {error, Error}),
+ exit(Error)
+ end.
+
+name({local,Name}) -> Name;
+name({global,Name}) -> Name;
+name(Pid) when is_pid(Pid) -> Pid.
+
+unregister_name({local,Name}) ->
+ _ = (catch unregister(Name));
+unregister_name({global,Name}) ->
+ _ = global:unregister_name(Name);
+unregister_name(Pid) when is_pid(Pid) ->
+ Pid.
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+%%% ---------------------------------------------------
+%%% The MAIN loop.
+%%% ---------------------------------------------------
+loop(Parent, Name, State, Mod, hibernate, Debug) ->
+ proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, Debug]);
+loop(Parent, Name, State, Mod, Time, Debug) ->
+ Msg = receive
+ Input ->
+ Input
+ after Time ->
+ timeout
+ end,
+ decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, false).
+
+wake_hib(Parent, Name, State, Mod, Debug) ->
+ Msg = receive
+ Input ->
+ Input
+ end,
+ decode_msg(Msg, Parent, Name, State, Mod, hibernate, Debug, true).
+
+decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
+ case Msg of
+ {system, From, Req} ->
+ sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
+ [Name, State, Mod, Time], Hib);
+ {'EXIT', Parent, Reason} ->
+ terminate(Reason, Name, Msg, Mod, State, Debug);
+ _Msg when Debug =:= [] ->
+ handle_msg(Msg, Parent, Name, State, Mod);
+ _Msg ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ Name, {in, Msg}),
+ handle_msg(Msg, Parent, Name, State, Mod, Debug1)
+ end.
+
+%%% ---------------------------------------------------
+%%% Send/recive functions
+%%% ---------------------------------------------------
+do_send(Dest, Msg) ->
+ case catch erlang:send(Dest, Msg, [noconnect]) of
+ noconnect ->
+ spawn(erlang, send, [Dest,Msg]);
+ Other ->
+ Other
+ end.
+
+do_multi_call(Nodes, Name, Req, infinity) ->
+ Tag = make_ref(),
+ Monitors = send_nodes(Nodes, Name, Tag, Req),
+ rec_nodes(Tag, Monitors, Name, undefined);
+do_multi_call(Nodes, Name, Req, Timeout) ->
+ Tag = make_ref(),
+ Caller = self(),
+ Receiver =
+ spawn(
+ fun() ->
+ %% Middleman process. Should be unsensitive to regular
+ %% exit signals. The sychronization is needed in case
+ %% the receiver would exit before the caller started
+ %% the monitor.
+ process_flag(trap_exit, true),
+ Mref = erlang:monitor(process, Caller),
+ receive
+ {Caller,Tag} ->
+ Monitors = send_nodes(Nodes, Name, Tag, Req),
+ TimerId = erlang:start_timer(Timeout, self(), ok),
+ Result = rec_nodes(Tag, Monitors, Name, TimerId),
+ exit({self(),Tag,Result});
+ {'DOWN',Mref,_,_,_} ->
+ %% Caller died before sending us the go-ahead.
+ %% Give up silently.
+ exit(normal)
+ end
+ end),
+ Mref = erlang:monitor(process, Receiver),
+ Receiver ! {self(),Tag},
+ receive
+ {'DOWN',Mref,_,_,{Receiver,Tag,Result}} ->
+ Result;
+ {'DOWN',Mref,_,_,Reason} ->
+ %% The middleman code failed. Or someone did
+ %% exit(_, kill) on the middleman process => Reason==killed
+ exit(Reason)
+ end.
+
+send_nodes(Nodes, Name, Tag, Req) ->
+ send_nodes(Nodes, Name, Tag, Req, []).
+
+send_nodes([Node|Tail], Name, Tag, Req, Monitors)
+ when is_atom(Node) ->
+ Monitor = start_monitor(Node, Name),
+ %% Handle non-existing names in rec_nodes.
+ catch {Name, Node} ! {'$gen_call', {self(), {Tag, Node}}, Req},
+ send_nodes(Tail, Name, Tag, Req, [Monitor | Monitors]);
+send_nodes([_Node|Tail], Name, Tag, Req, Monitors) ->
+ %% Skip non-atom Node
+ send_nodes(Tail, Name, Tag, Req, Monitors);
+send_nodes([], _Name, _Tag, _Req, Monitors) ->
+ Monitors.
+
+%% Against old nodes:
+%% If no reply has been delivered within 2 secs. (per node) check that
+%% the server really exists and wait for ever for the answer.
+%%
+%% Against contemporary nodes:
+%% Wait for reply, server 'DOWN', or timeout from TimerId.
+
+rec_nodes(Tag, Nodes, Name, TimerId) ->
+ rec_nodes(Tag, Nodes, Name, [], [], 2000, TimerId).
+
+rec_nodes(Tag, [{N,R}|Tail], Name, Badnodes, Replies, Time, TimerId ) ->
+ receive
+ {'DOWN', R, _, _, _} ->
+ rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, Time, TimerId);
+ {{Tag, N}, Reply} -> %% Tag is bound !!!
+ unmonitor(R),
+ rec_nodes(Tag, Tail, Name, Badnodes,
+ [{N,Reply}|Replies], Time, TimerId);
+ {timeout, TimerId, _} ->
+ unmonitor(R),
+ %% Collect all replies that already have arrived
+ rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies)
+ end;
+rec_nodes(Tag, [N|Tail], Name, Badnodes, Replies, Time, TimerId) ->
+ %% R6 node
+ receive
+ {nodedown, N} ->
+ monitor_node(N, false),
+ rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, 2000, TimerId);
+ {{Tag, N}, Reply} -> %% Tag is bound !!!
+ receive {nodedown, N} -> ok after 0 -> ok end,
+ monitor_node(N, false),
+ rec_nodes(Tag, Tail, Name, Badnodes,
+ [{N,Reply}|Replies], 2000, TimerId);
+ {timeout, TimerId, _} ->
+ receive {nodedown, N} -> ok after 0 -> ok end,
+ monitor_node(N, false),
+ %% Collect all replies that already have arrived
+ rec_nodes_rest(Tag, Tail, Name, [N | Badnodes], Replies)
+ after Time ->
+ case rpc:call(N, erlang, whereis, [Name]) of
+ Pid when is_pid(Pid) -> % It exists try again.
+ rec_nodes(Tag, [N|Tail], Name, Badnodes,
+ Replies, infinity, TimerId);
+ _ -> % badnode
+ receive {nodedown, N} -> ok after 0 -> ok end,
+ monitor_node(N, false),
+ rec_nodes(Tag, Tail, Name, [N|Badnodes],
+ Replies, 2000, TimerId)
+ end
+ end;
+rec_nodes(_, [], _, Badnodes, Replies, _, TimerId) ->
+ case catch erlang:cancel_timer(TimerId) of
+ false -> % It has already sent it's message
+ receive
+ {timeout, TimerId, _} -> ok
+ after 0 ->
+ ok
+ end;
+ _ -> % Timer was cancelled, or TimerId was 'undefined'
+ ok
+ end,
+ {Replies, Badnodes}.
+
+%% Collect all replies that already have arrived
+rec_nodes_rest(Tag, [{N,R}|Tail], Name, Badnodes, Replies) ->
+ receive
+ {'DOWN', R, _, _, _} ->
+ rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies);
+ {{Tag, N}, Reply} -> %% Tag is bound !!!
+ unmonitor(R),
+ rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies])
+ after 0 ->
+ unmonitor(R),
+ rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies)
+ end;
+rec_nodes_rest(Tag, [N|Tail], Name, Badnodes, Replies) ->
+ %% R6 node
+ receive
+ {nodedown, N} ->
+ monitor_node(N, false),
+ rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies);
+ {{Tag, N}, Reply} -> %% Tag is bound !!!
+ receive {nodedown, N} -> ok after 0 -> ok end,
+ monitor_node(N, false),
+ rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies])
+ after 0 ->
+ receive {nodedown, N} -> ok after 0 -> ok end,
+ monitor_node(N, false),
+ rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies)
+ end;
+rec_nodes_rest(_Tag, [], _Name, Badnodes, Replies) ->
+ {Replies, Badnodes}.
+
+
+%%% ---------------------------------------------------
+%%% Monitor functions
+%%% ---------------------------------------------------
+
+start_monitor(Node, Name) when is_atom(Node), is_atom(Name) ->
+ if node() =:= nonode@nohost, Node =/= nonode@nohost ->
+ Ref = make_ref(),
+ self() ! {'DOWN', Ref, process, {Name, Node}, noconnection},
+ {Node, Ref};
+ true ->
+ case catch erlang:monitor(process, {Name, Node}) of
+ {'EXIT', _} ->
+ %% Remote node is R6
+ monitor_node(Node, true),
+ Node;
+ Ref when is_reference(Ref) ->
+ {Node, Ref}
+ end
+ end.
+
+%% Cancels a monitor started with Ref=erlang:monitor(_, _).
+unmonitor(Ref) when is_reference(Ref) ->
+ erlang:demonitor(Ref),
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ true
+ after 0 ->
+ true
+ end.
+
+%%% ---------------------------------------------------
+%%% Message handling functions
+%%% ---------------------------------------------------
+
+dispatch({'$gen_cast', Msg}, Mod, State) ->
+ Mod:handle_cast(Msg, State);
+dispatch(Info, Mod, State) ->
+ Mod:handle_info(Info, State).
+
+handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) ->
+ case catch Mod:handle_call(Msg, From, State) of
+ {reply, Reply, NState} ->
+ reply(From, Reply),
+ loop(Parent, Name, NState, Mod, infinity, []);
+ {reply, Reply, NState, Time1} ->
+ reply(From, Reply),
+ loop(Parent, Name, NState, Mod, Time1, []);
+ {noreply, NState} ->
+ loop(Parent, Name, NState, Mod, infinity, []);
+ {noreply, NState, Time1} ->
+ loop(Parent, Name, NState, Mod, Time1, []);
+ {stop, Reason, Reply, NState} ->
+ {'EXIT', R} =
+ (catch terminate(Reason, Name, Msg, Mod, NState, [])),
+ reply(From, Reply),
+ exit(R);
+ Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State)
+ end;
+handle_msg(Msg, Parent, Name, State, Mod) ->
+ Reply = (catch dispatch(Msg, Mod, State)),
+ handle_common_reply(Reply, Parent, Name, Msg, Mod, State).
+
+handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) ->
+ case catch Mod:handle_call(Msg, From, State) of
+ {reply, Reply, NState} ->
+ Debug1 = reply(Name, From, Reply, NState, Debug),
+ loop(Parent, Name, NState, Mod, infinity, Debug1);
+ {reply, Reply, NState, Time1} ->
+ Debug1 = reply(Name, From, Reply, NState, Debug),
+ loop(Parent, Name, NState, Mod, Time1, Debug1);
+ {noreply, NState} ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ {noreply, NState}),
+ loop(Parent, Name, NState, Mod, infinity, Debug1);
+ {noreply, NState, Time1} ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ {noreply, NState}),
+ loop(Parent, Name, NState, Mod, Time1, Debug1);
+ {stop, Reason, Reply, NState} ->
+ {'EXIT', R} =
+ (catch terminate(Reason, Name, Msg, Mod, NState, Debug)),
+ reply(Name, From, Reply, NState, Debug),
+ exit(R);
+ Other ->
+ handle_common_reply(Other, Parent, Name, Msg, Mod, State, Debug)
+ end;
+handle_msg(Msg, Parent, Name, State, Mod, Debug) ->
+ Reply = (catch dispatch(Msg, Mod, State)),
+ handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug).
+
+handle_common_reply(Reply, Parent, Name, Msg, Mod, State) ->
+ case Reply of
+ {noreply, NState} ->
+ loop(Parent, Name, NState, Mod, infinity, []);
+ {noreply, NState, Time1} ->
+ loop(Parent, Name, NState, Mod, Time1, []);
+ {stop, Reason, NState} ->
+ terminate(Reason, Name, Msg, Mod, NState, []);
+ {'EXIT', What} ->
+ terminate(What, Name, Msg, Mod, State, []);
+ _ ->
+ terminate({bad_return_value, Reply}, Name, Msg, Mod, State, [])
+ end.
+
+handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) ->
+ case Reply of
+ {noreply, NState} ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ {noreply, NState}),
+ loop(Parent, Name, NState, Mod, infinity, Debug1);
+ {noreply, NState, Time1} ->
+ Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ {noreply, NState}),
+ loop(Parent, Name, NState, Mod, Time1, Debug1);
+ {stop, Reason, NState} ->
+ terminate(Reason, Name, Msg, Mod, NState, Debug);
+ {'EXIT', What} ->
+ terminate(What, Name, Msg, Mod, State, Debug);
+ _ ->
+ terminate({bad_return_value, Reply}, Name, Msg, Mod, State, Debug)
+ end.
+
+reply(Name, {To, Tag}, Reply, State, Debug) ->
+ reply({To, Tag}, Reply),
+ sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ {out, Reply, To, State} ).
+
+
+%%-----------------------------------------------------------------
+%% Callback functions for system messages handling.
+%%-----------------------------------------------------------------
+system_continue(Parent, Debug, [Name, State, Mod, Time]) ->
+ loop(Parent, Name, State, Mod, Time, Debug).
+
+-spec system_terminate(_, _, _, [_]) -> no_return().
+
+system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time]) ->
+ terminate(Reason, Name, [], Mod, State, Debug).
+
+system_code_change([Name, State, Mod, Time], _Module, OldVsn, Extra) ->
+ case catch Mod:code_change(OldVsn, State, Extra) of
+ {ok, NewState} -> {ok, [Name, NewState, Mod, Time]};
+ Else -> Else
+ end.
+
+%%-----------------------------------------------------------------
+%% Format debug messages. Print them as the call-back module sees
+%% them, not as the real erlang messages. Use trace for that.
+%%-----------------------------------------------------------------
+print_event(Dev, {in, Msg}, Name) ->
+ case Msg of
+ {'$gen_call', {From, _Tag}, Call} ->
+ io:format(Dev, "*DBG* ~p got call ~p from ~w~n",
+ [Name, Call, From]);
+ {'$gen_cast', Cast} ->
+ io:format(Dev, "*DBG* ~p got cast ~p~n",
+ [Name, Cast]);
+ _ ->
+ io:format(Dev, "*DBG* ~p got ~p~n", [Name, Msg])
+ end;
+print_event(Dev, {out, Msg, To, State}, Name) ->
+ io:format(Dev, "*DBG* ~p sent ~p to ~w, new state ~w~n",
+ [Name, Msg, To, State]);
+print_event(Dev, {noreply, State}, Name) ->
+ io:format(Dev, "*DBG* ~p new state ~w~n", [Name, State]);
+print_event(Dev, Event, Name) ->
+ io:format(Dev, "*DBG* ~p dbg ~p~n", [Name, Event]).
+
+
+%%% ---------------------------------------------------
+%%% Terminate the server.
+%%% ---------------------------------------------------
+
+terminate(Reason, Name, Msg, Mod, State, Debug) ->
+ case catch Mod:terminate(Reason, State) of
+ {'EXIT', R} ->
+ error_info(R, Name, Msg, State, Debug),
+ exit(R);
+ _ ->
+ case Reason of
+ normal ->
+ exit(normal);
+ shutdown ->
+ exit(shutdown);
+ {shutdown,_}=Shutdown ->
+ exit(Shutdown);
+ _ ->
+ error_info(Reason, Name, Msg, State, Debug),
+ exit(Reason)
+ end
+ end.
+
+error_info(_Reason, application_controller, _Msg, _State, _Debug) ->
+ %% OTP-5811 Don't send an error report if it's the system process
+ %% application_controller which is terminating - let init take care
+ %% of it instead
+ ok;
+error_info(Reason, Name, Msg, State, Debug) ->
+ Reason1 =
+ case Reason of
+ {undef,[{M,F,A}|MFAs]} ->
+ case code:is_loaded(M) of
+ false ->
+ {'module could not be loaded',[{M,F,A}|MFAs]};
+ _ ->
+ case erlang:function_exported(M, F, length(A)) of
+ true ->
+ Reason;
+ false ->
+ {'function not exported',[{M,F,A}|MFAs]}
+ end
+ end;
+ _ ->
+ Reason
+ end,
+ format("** Generic server ~p terminating \n"
+ "** Last message in was ~p~n"
+ "** When Server state == ~p~n"
+ "** Reason for termination == ~n** ~p~n",
+ [Name, Msg, State, Reason1]),
+ sys:print_log(Debug),
+ ok.
+
+%%% ---------------------------------------------------
+%%% Misc. functions.
+%%% ---------------------------------------------------
+
+opt(Op, [{Op, Value}|_]) ->
+ {ok, Value};
+opt(Op, [_|Options]) ->
+ opt(Op, Options);
+opt(_, []) ->
+ false.
+
+debug_options(Name, Opts) ->
+ case opt(debug, Opts) of
+ {ok, Options} -> dbg_options(Name, Options);
+ _ -> dbg_options(Name, [])
+ end.
+
+dbg_options(Name, []) ->
+ Opts =
+ case init:get_argument(generic_debug) of
+ error ->
+ [];
+ _ ->
+ [log, statistics]
+ end,
+ dbg_opts(Name, Opts);
+dbg_options(Name, Opts) ->
+ dbg_opts(Name, Opts).
+
+dbg_opts(Name, Opts) ->
+ case catch sys:debug_options(Opts) of
+ {'EXIT',_} ->
+ format("~p: ignoring erroneous debug options - ~p~n",
+ [Name, Opts]),
+ [];
+ Dbg ->
+ Dbg
+ end.
+
+get_proc_name(Pid) when is_pid(Pid) ->
+ Pid;
+get_proc_name({local, Name}) ->
+ case process_info(self(), registered_name) of
+ {registered_name, Name} ->
+ Name;
+ {registered_name, _Name} ->
+ exit(process_not_registered);
+ [] ->
+ exit(process_not_registered)
+ end;
+get_proc_name({global, Name}) ->
+ case global:safe_whereis_name(Name) of
+ undefined ->
+ exit(process_not_registered_globally);
+ Pid when Pid =:= self() ->
+ Name;
+ _Pid ->
+ exit(process_not_registered_globally)
+ end.
+
+get_parent() ->
+ case get('$ancestors') of
+ [Parent | _] when is_pid(Parent)->
+ Parent;
+ [Parent | _] when is_atom(Parent)->
+ name_to_pid(Parent);
+ _ ->
+ exit(process_was_not_started_by_proc_lib)
+ end.
+
+name_to_pid(Name) ->
+ case whereis(Name) of
+ undefined ->
+ case global:safe_whereis_name(Name) of
+ undefined ->
+ exit(could_not_find_registerd_name);
+ Pid ->
+ Pid
+ end;
+ Pid ->
+ Pid
+ end.
+
+%%-----------------------------------------------------------------
+%% Status information
+%%-----------------------------------------------------------------
+format_status(Opt, StatusData) ->
+ [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time]] = StatusData,
+ NameTag = if is_pid(Name) ->
+ pid_to_list(Name);
+ is_atom(Name) ->
+ Name
+ end,
+ Header = lists:concat(["Status for generic server ", NameTag]),
+ Log = sys:get_debug(log, Debug, []),
+ Specfic =
+ case erlang:function_exported(Mod, format_status, 2) of
+ true ->
+ case catch Mod:format_status(Opt, [PDict, State]) of
+ {'EXIT', _} -> [{data, [{"State", State}]}];
+ Else -> Else
+ end;
+ _ ->
+ [{data, [{"State", State}]}]
+ end,
+ [{header, Header},
+ {data, [{"Status", SysState},
+ {"Parent", Parent},
+ {"Logged events", Log}]} |
+ Specfic].
diff --git a/lib/stdlib/src/io.erl b/lib/stdlib/src/io.erl
new file mode 100644
index 0000000000..1f8076e864
--- /dev/null
+++ b/lib/stdlib/src/io.erl
@@ -0,0 +1,578 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(io).
+
+-export([put_chars/1,put_chars/2,nl/0,nl/1,
+ get_chars/2,get_chars/3,get_line/1,get_line/2,
+ get_password/0, get_password/1,
+ setopts/1, setopts/2, getopts/0, getopts/1]).
+-export([write/1,write/2,read/1,read/2,read/3]).
+-export([columns/0,columns/1,rows/0,rows/1]).
+-export([fwrite/1,fwrite/2,fwrite/3,fread/2,fread/3,
+ format/1,format/2,format/3]).
+-export([scan_erl_exprs/1,scan_erl_exprs/2,scan_erl_exprs/3,
+ scan_erl_form/1,scan_erl_form/2,scan_erl_form/3,
+ parse_erl_exprs/1,parse_erl_exprs/2,parse_erl_exprs/3,
+ parse_erl_form/1,parse_erl_form/2,parse_erl_form/3]).
+-export([request/1,request/2,requests/1,requests/2]).
+
+
+%%-------------------------------------------------------------------------
+
+-type device() :: atom() | pid().
+-type prompt() :: atom() | string().
+
+%% XXX: Some uses of line() in this file may need to read erl_scan:location()
+-type line() :: pos_integer().
+
+%%-------------------------------------------------------------------------
+
+%%
+%% User interface.
+%%
+
+%% Writing and reading characters.
+
+to_tuple(T) when is_tuple(T) -> T;
+to_tuple(T) -> {T}.
+
+%% Problem: the variables Other, Name and Args may collide with surrounding
+%% ones.
+%% Give extra args to macro, being the variables to use.
+-define(O_REQUEST(Io, Request),
+ case request(Io, Request) of
+ {error, Reason} ->
+ [Name | Args] = tuple_to_list(to_tuple(Request)),
+ erlang:error(conv_reason(Name, Reason), [Name, Io | Args]);
+ Other ->
+ Other
+ end).
+
+o_request(Io, Request, Func) ->
+ case request(Io, Request) of
+ {error, Reason} ->
+ [_Name | Args] = tuple_to_list(to_tuple(Request)),
+ {'EXIT',{undef,[_Current|Mfas]}} = (catch erlang:error(undef)),
+ MFA = {io, Func, [Io | Args]},
+ exit({conv_reason(Func, Reason),[MFA|Mfas]});
+% erlang:error(conv_reason(Name, Reason), [Name, Io | Args]);
+ Other ->
+ Other
+ end.
+
+%% Put chars takes mixed *unicode* list from R13 onwards.
+-spec put_chars(iodata()) -> 'ok'.
+
+put_chars(Chars) ->
+ put_chars(default_output(), Chars).
+
+-spec put_chars(device(), iodata()) -> 'ok'.
+
+put_chars(Io, Chars) ->
+ o_request(Io, {put_chars,unicode,Chars}, put_chars).
+
+-spec nl() -> 'ok'.
+
+nl() ->
+ nl(default_output()).
+
+-spec nl(device()) -> 'ok'.
+
+nl(Io) ->
+% o_request(Io, {put_chars,io_lib:nl()}).
+ o_request(Io, nl, nl).
+
+-spec columns() -> {'ok', pos_integer()} | {'error', 'enotsup'}.
+
+columns() ->
+ columns(default_output()).
+
+-spec columns(device()) -> {'ok', pos_integer()} | {'error', 'enotsup'}.
+
+columns(Io) ->
+ case request(Io, {get_geometry,columns}) of
+ N when is_integer(N), N > 0 ->
+ {ok,N};
+ _ ->
+ {error,enotsup}
+ end.
+
+-spec rows() -> {'ok', pos_integer()} | {'error', 'enotsup'}.
+
+rows() ->
+ rows(default_output()).
+
+-spec rows(device()) -> {'ok', pos_integer()} | {'error', 'enotsup'}.
+
+rows(Io) ->
+ case request(Io,{get_geometry,rows}) of
+ N when is_integer(N), N > 0 ->
+ {ok,N};
+ _ ->
+ {error,enotsup}
+ end.
+
+-spec get_chars(prompt(), non_neg_integer()) -> iodata() | 'eof'.
+
+get_chars(Prompt, N) ->
+ get_chars(default_input(), Prompt, N).
+
+-spec get_chars(device(), prompt(), non_neg_integer()) -> iodata() | 'eof'.
+
+get_chars(Io, Prompt, N) when is_integer(N), N >= 0 ->
+ request(Io, {get_chars,unicode,Prompt,N}).
+
+-spec get_line(prompt()) -> iodata() | 'eof' | {'error', term()}.
+
+get_line(Prompt) ->
+ get_line(default_input(), Prompt).
+
+-spec get_line(device(), prompt()) -> iodata() | 'eof' | {'error', term()}.
+
+get_line(Io, Prompt) ->
+ request(Io, {get_line,unicode,Prompt}).
+
+get_password() ->
+ get_password(default_input()).
+
+get_password(Io) ->
+ request(Io, {get_password,unicode}).
+
+-type encoding() :: 'latin1' | 'unicode' | 'utf8' | 'utf16' | 'utf32'
+ | {'utf16', 'big' | 'little'} | {'utf32','big' | 'little'}.
+-type expand_fun() :: fun((term()) -> {'yes'|'no', string(), [string(), ...]}).
+-type opt_pair() :: {'binary', boolean()}
+ | {'echo', boolean()}
+ | {'expand_fun', expand_fun()}
+ | {'encoding', encoding()}.
+
+-spec getopts() -> [opt_pair()].
+
+getopts() ->
+ getopts(default_input()).
+
+-spec getopts(device()) -> [opt_pair()].
+
+getopts(Io) ->
+ request(Io, getopts).
+
+-type setopt() :: 'binary' | 'list' | opt_pair().
+
+-spec setopts([setopt()]) -> 'ok' | {'error', term()}.
+
+setopts(Opts) ->
+ setopts(default_input(), Opts).
+
+-spec setopts(device(), [setopt()]) -> 'ok' | {'error', term()}.
+
+setopts(Io, Opts) ->
+ request(Io, {setopts, Opts}).
+
+%% Writing and reading Erlang terms.
+
+-spec write(term()) -> 'ok'.
+
+write(Term) ->
+ write(default_output(), Term).
+
+-spec write(device(), term()) -> 'ok'.
+
+write(Io, Term) ->
+ o_request(Io, {write,Term}, write).
+
+
+-spec read(prompt()) ->
+ {'ok', term()} | 'eof' | {'error', erl_scan:error_info()}.
+
+% Read does not use get_until as erl_scan does not work with unicode
+% XXX:PaN fixme?
+read(Prompt) ->
+ read(default_input(), Prompt).
+
+-spec read(device(), prompt()) ->
+ {'ok', term()} | 'eof' | {'error', erl_scan:error_info()}.
+
+read(Io, Prompt) ->
+ case request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[1]}) of
+ {ok,Toks,_EndLine} ->
+ erl_parse:parse_term(Toks);
+% {error, Reason} when atom(Reason) ->
+% erlang:error(conv_reason(read, Reason), [Io, Prompt]);
+ {error,E,_EndLine} ->
+ {error,E};
+ {eof,_EndLine} ->
+ eof;
+ Other ->
+ Other
+ end.
+
+-spec read(device(), prompt(), line()) ->
+ {'ok', term(), line()} | {'eof', line()} |
+ {'error', erl_scan:error_info(), line()}.
+
+read(Io, Prompt, StartLine) when is_integer(StartLine) ->
+ case request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[StartLine]}) of
+ {ok,Toks,EndLine} ->
+ case erl_parse:parse_term(Toks) of
+ {ok,Term} -> {ok,Term,EndLine};
+ {error,ErrorInfo} -> {error,ErrorInfo,EndLine}
+ end;
+ {error,_E,_EndLine} = Error ->
+ Error;
+ {eof,_EndLine} = Eof ->
+ Eof;
+ Other ->
+ Other
+ end.
+
+%% Formatted writing and reading.
+
+conv_reason(_, arguments) -> badarg;
+conv_reason(_, terminated) -> terminated;
+conv_reason(_, {no_translation,_,_}) -> no_translation;
+conv_reason(_, _Reason) -> badarg.
+
+-type format() :: atom() | string() | binary().
+
+-spec fwrite(format()) -> 'ok'.
+
+fwrite(Format) ->
+ format(Format).
+
+-spec fwrite(format(), [term()]) -> 'ok'.
+
+fwrite(Format, Args) ->
+ format(Format, Args).
+
+-spec fwrite(device(), format(), [term()]) -> 'ok'.
+
+fwrite(Io, Format, Args) ->
+ format(Io, Format, Args).
+
+-spec fread(prompt(), format()) -> {'ok', [term()]} | 'eof' | {'error',term()}.
+
+fread(Prompt, Format) ->
+ fread(default_input(), Prompt, Format).
+
+-spec fread(device(), prompt(), format()) ->
+ {'ok', [term()]} | 'eof' | {'error',term()}.
+
+fread(Io, Prompt, Format) ->
+ case request(Io, {fread,Prompt,Format}) of
+% {error, Reason} when atom(Reason) ->
+% erlang:error(conv_reason(fread, Reason), [Io, Prompt, Format]);
+ Other ->
+ Other
+ end.
+
+-spec format(format()) -> 'ok'.
+
+format(Format) ->
+ format(Format, []).
+
+-spec format(format(), [term()]) -> 'ok'.
+
+format(Format, Args) ->
+ format(default_output(), Format, Args).
+
+-spec format(device(), format(), [term()]) -> 'ok'.
+
+format(Io, Format, Args) ->
+ o_request(Io, {format,Format,Args}, format).
+
+%% Scanning Erlang code.
+
+-spec scan_erl_exprs(prompt()) -> erl_scan:tokens_result().
+
+scan_erl_exprs(Prompt) ->
+ scan_erl_exprs(default_input(), Prompt, 1).
+
+-spec scan_erl_exprs(device(), prompt()) -> erl_scan:tokens_result().
+
+scan_erl_exprs(Io, Prompt) ->
+ scan_erl_exprs(Io, Prompt, 1).
+
+-spec scan_erl_exprs(device(), prompt(), line()) -> erl_scan:tokens_result().
+
+scan_erl_exprs(Io, Prompt, Pos0) ->
+ request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[Pos0]}).
+
+-spec scan_erl_form(prompt()) -> erl_scan:tokens_result().
+
+scan_erl_form(Prompt) ->
+ scan_erl_form(default_input(), Prompt, 1).
+
+-spec scan_erl_form(device(), prompt()) -> erl_scan:tokens_result().
+
+scan_erl_form(Io, Prompt) ->
+ scan_erl_form(Io, Prompt, 1).
+
+-spec scan_erl_form(device(), prompt(), line()) -> erl_scan:tokens_result().
+
+scan_erl_form(Io, Prompt, Pos0) ->
+ request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[Pos0]}).
+
+%% Parsing Erlang code.
+
+-type erl_parse_expr_list() :: [_]. %% XXX: should be imported from erl_parse
+
+-type parse_ret() :: {'ok', erl_parse_expr_list(), line()}
+ | {'eof', line()}
+ | {'error', erl_scan:error_info(), line()}.
+
+-spec parse_erl_exprs(prompt()) -> parse_ret().
+
+parse_erl_exprs(Prompt) ->
+ parse_erl_exprs(default_input(), Prompt, 1).
+
+-spec parse_erl_exprs(device(), prompt()) -> parse_ret().
+
+parse_erl_exprs(Io, Prompt) ->
+ parse_erl_exprs(Io, Prompt, 1).
+
+-spec parse_erl_exprs(device(), prompt(), line()) -> parse_ret().
+
+parse_erl_exprs(Io, Prompt, Pos0) ->
+ case request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[Pos0]}) of
+ {ok,Toks,EndPos} ->
+ case erl_parse:parse_exprs(Toks) of
+ {ok,Exprs} -> {ok,Exprs,EndPos};
+ {error,E} -> {error,E,EndPos}
+ end;
+ Other ->
+ Other
+ end.
+
+-type erl_parse_absform() :: _. %% XXX: should be imported from erl_parse
+
+-type parse_form_ret() :: {'ok', erl_parse_absform(), line()}
+ | {'eof', line()}
+ | {'error', erl_scan:error_info(), line()}.
+
+-spec parse_erl_form(prompt()) -> parse_form_ret().
+
+parse_erl_form(Prompt) ->
+ parse_erl_form(default_input(), Prompt, 1).
+
+-spec parse_erl_form(device(), prompt()) -> parse_form_ret().
+
+parse_erl_form(Io, Prompt) ->
+ parse_erl_form(Io, Prompt, 1).
+
+-spec parse_erl_form(device(), prompt(), line()) -> parse_form_ret().
+
+parse_erl_form(Io, Prompt, Pos0) ->
+ case request(Io, {get_until,unicode,Prompt,erl_scan,tokens,[Pos0]}) of
+ {ok,Toks,EndPos} ->
+ case erl_parse:parse_form(Toks) of
+ {ok,Exprs} -> {ok,Exprs,EndPos};
+ {error,E} -> {error,E,EndPos}
+ end;
+ Other ->
+ Other
+ end.
+
+%% Miscellaneous functions.
+
+request(Request) ->
+ request(default_output(), Request).
+
+request(standard_io, Request) ->
+ request(group_leader(), Request);
+request(Pid, Request) when is_pid(Pid) ->
+ execute_request(Pid, io_request(Pid, Request));
+request(Name, Request) when is_atom(Name) ->
+ case whereis(Name) of
+ undefined ->
+ {error, arguments};
+ Pid ->
+ request(Pid, Request)
+ end.
+
+execute_request(Pid, {Convert,Converted}) ->
+ Mref = erlang:monitor(process, Pid),
+ Pid ! {io_request,self(),Pid,Converted},
+ if
+ Convert ->
+ convert_binaries(wait_io_mon_reply(Pid, Mref));
+ true ->
+ wait_io_mon_reply(Pid, Mref)
+ end.
+
+requests(Requests) -> %Requests as atomic action
+ requests(default_output(), Requests).
+
+requests(standard_io, Requests) -> %Requests as atomic action
+ requests(group_leader(), Requests);
+requests(Pid, Requests) when is_pid(Pid) ->
+ {Convert, Converted} = io_requests(Pid, Requests),
+ execute_request(Pid,{Convert,{requests,Converted}});
+requests(Name, Requests) when is_atom(Name) ->
+ case whereis(Name) of
+ undefined ->
+ {error, arguments};
+ Pid ->
+ requests(Pid, Requests)
+ end.
+
+
+default_input() ->
+ group_leader().
+
+default_output() ->
+ group_leader().
+
+wait_io_mon_reply(From, Mref) ->
+ receive
+ {io_reply, From, Reply} ->
+ erlang:demonitor(Mref),
+ receive
+ {'DOWN', Mref, _, _, _} -> true
+ after 0 -> true
+ end,
+ Reply;
+ {'EXIT', From, _What} ->
+ receive
+ {'DOWN', Mref, _, _, _} -> true
+ after 0 -> true
+ end,
+ {error,terminated};
+ {'DOWN', Mref, _, _, _} ->
+ receive
+ {'EXIT', From, _What} -> true
+ after 0 -> true
+ end,
+ {error,terminated}
+ end.
+
+
+%% io_requests(Requests)
+%% Transform requests into correct i/o server messages. Only handle the
+%% one we KNOW must be changed, others, including incorrect ones, are
+%% passed straight through. Perform a flatten on the request list.
+
+io_requests(Pid, Rs) ->
+ io_requests(Pid, Rs, [], []).
+
+io_requests(Pid, [{requests,Rs1}|Rs], Cont, Tail) ->
+ io_requests(Pid, Rs1, [Rs|Cont], Tail);
+io_requests(Pid, [R], [], _Tail) ->
+ {Conv,Request} = io_request(Pid, R),
+ {Conv,[Request]};
+io_requests(Pid, [R|Rs], Cont, Tail) ->
+ {_,Request} = io_request(Pid, R),
+ {Conv,Requests} = io_requests(Pid, Rs, Cont, Tail),
+ {Conv,[Request|Requests]};
+io_requests(Pid, [], [Rs|Cont], Tail) ->
+ io_requests(Pid, Rs, Cont, Tail);
+io_requests(_Pid, [], [], _Tail) ->
+ {false,[]}.
+
+
+bc_req(Pid,{Op,Enc,Param},MaybeConvert) ->
+ case net_kernel:dflag_unicode_io(Pid) of
+ true ->
+ {false,{Op,Enc,Param}};
+ false ->
+ {MaybeConvert,{Op,Param}}
+ end;
+bc_req(Pid,{Op,Enc,P,F},MaybeConvert) ->
+ case net_kernel:dflag_unicode_io(Pid) of
+ true ->
+ {false,{Op,Enc,P,F}};
+ false ->
+ {MaybeConvert,{Op,P,F}}
+ end;
+bc_req(Pid, {Op,Enc,M,F,A},MaybeConvert) ->
+ case net_kernel:dflag_unicode_io(Pid) of
+ true ->
+ {false,{Op,Enc,M,F,A}};
+ false ->
+ {MaybeConvert,{Op,M,F,A}}
+ end;
+bc_req(Pid, {Op,Enc,P,M,F,A},MaybeConvert) ->
+ case net_kernel:dflag_unicode_io(Pid) of
+ true ->
+ {false,{Op,Enc,P,M,F,A}};
+ false ->
+ {MaybeConvert,{Op,P,M,F,A}}
+ end;
+bc_req(Pid,{Op,Enc},MaybeConvert) ->
+ case net_kernel:dflag_unicode_io(Pid) of
+ true ->
+ {false,{Op, Enc}};
+ false ->
+ {MaybeConvert,Op}
+ end.
+
+io_request(Pid, {write,Term}) ->
+ bc_req(Pid,{put_chars,unicode,io_lib,write,[Term]},false);
+io_request(Pid, {format,Format,Args}) ->
+ bc_req(Pid,{put_chars,unicode,io_lib,format,[Format,Args]},false);
+io_request(Pid, {fwrite,Format,Args}) ->
+ bc_req(Pid,{put_chars,unicode,io_lib,fwrite,[Format,Args]},false);
+io_request(Pid, nl) ->
+ bc_req(Pid,{put_chars,unicode,io_lib:nl()},false);
+io_request(Pid, {put_chars,Enc,Chars}=Request0)
+ when is_list(Chars), node(Pid) =:= node() ->
+ %% Convert to binary data if the I/O server is guaranteed to be new
+ Request =
+ case catch unicode:characters_to_binary(Chars,Enc) of
+ Binary when is_binary(Binary) ->
+ {put_chars,Enc,Binary};
+ _ ->
+ Request0
+ end,
+ {false,Request};
+io_request(Pid, {put_chars,Enc,Chars}=Request0)
+ when is_list(Chars) ->
+ case net_kernel:dflag_unicode_io(Pid) of
+ true ->
+ case catch unicode:characters_to_binary(Chars,Enc,unicode) of
+ Binary when is_binary(Binary) ->
+ {false,{put_chars,unicode,Binary}};
+ _ ->
+ {false,Request0}
+ end;
+ false ->
+ %% Convert back to old style put_chars message...
+ case catch unicode:characters_to_binary(Chars,Enc,latin1) of
+ Binary when is_binary(Binary) ->
+ {false,{put_chars,Binary}};
+ _ ->
+ {false,{put_chars,Chars}}
+ end
+ end;
+io_request(Pid, {fread,Prompt,Format}) ->
+ bc_req(Pid,{get_until,unicode,Prompt,io_lib,fread,[Format]},true);
+io_request(Pid, {get_until,Enc,Prompt,M,F,A}) ->
+ bc_req(Pid,{get_until,Enc,Prompt,M,F,A},true);
+io_request(Pid, {get_chars,Enc,Prompt,N}) ->
+ bc_req(Pid,{get_chars,Enc,Prompt,N},true);
+io_request(Pid, {get_line,Enc,Prompt}) ->
+ bc_req(Pid,{get_line,Enc,Prompt},true);
+io_request(Pid, {get_password,Enc}) ->
+ bc_req(Pid,{get_password, Enc},true);
+io_request(_Pid, R) -> %Pass this straight through
+ {false,R}.
+
+convert_binaries(Bin) when is_binary(Bin) ->
+ unicode:characters_to_binary(Bin,latin1,unicode);
+convert_binaries(Else) ->
+ Else.
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
new file mode 100644
index 0000000000..2d3c86e4ea
--- /dev/null
+++ b/lib/stdlib/src/io_lib.erl
@@ -0,0 +1,688 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% This module is a library of useful i/o functions. It is hoped that the
+%% functions defined in it are basic enough to be used without modification
+%% as components of more complex utilities.
+%%
+%% It is completely self-contained and uses no other modules. Its own
+%% utilities are exported.
+%%
+%% Most of the code here is derived from the original prolog versions and
+%% from similar code written by Joe Armstrong and myself.
+%%
+%% This module has been split into seperate modules:
+%% io_lib - basic write and utilities
+%% io_lib_format - formatted output
+%% io_lib_fread - formatted input
+%% io_lib_pretty - term prettyprinter
+
+%% For handling ISO 8859-1 (Latin-1) we use the following type
+%% information:
+%%
+%% 000 - 037 NUL - US control
+%% 040 - 057 SPC - / punctuation
+%% 060 - 071 0 - 9 digit
+%% 072 - 100 : - @ punctuation
+%% 101 - 132 A - Z uppercase
+%% 133 - 140 [ - ` punctuation
+%% 141 - 172 a - z lowercase
+%% 173 - 176 { - ~ punctuation
+%% 177 DEL control
+%% 200 - 237 control
+%% 240 - 277 NBSP - � punctuation
+%% 300 - 326 � - � uppercase
+%% 327 � punctuation
+%% 330 - 336 � - � uppercase
+%% 337 - 366 � - � lowercase
+%% 367 � punctuation
+%% 370 - 377 � - � lowercase
+%%
+%% Many punctuation characters region have special meaning. Must
+%% watch using � \327, very close to x \170
+
+-module(io_lib).
+
+-export([fwrite/2,fread/2,fread/3,format/2]).
+-export([print/1,print/4,indentation/2]).
+
+-export([write/1,write/2,write/3,nl/0,format_prompt/1]).
+-export([write_atom/1,write_string/1,write_string/2,write_unicode_string/1,
+ write_unicode_string/2, write_char/1, write_unicode_char/1]).
+
+-export([quote_atom/2, char_list/1, unicode_char_list/1,
+ deep_char_list/1, deep_unicode_char_list/1,
+ printable_list/1, printable_unicode_list/1]).
+
+%% Utilities for collecting characters.
+-export([collect_chars/3, collect_chars/4,
+ collect_line/2, collect_line/3, collect_line/4,
+ get_until/3, get_until/4]).
+
+%%----------------------------------------------------------------------
+
+ %% XXX: overapproximates a deep list of (unicode) characters
+-type chars() :: [_].
+-type depth() :: -1 | non_neg_integer().
+
+%%----------------------------------------------------------------------
+
+%% Interface calls to sub-modules.
+
+-spec fwrite(io:format(), [term()]) -> chars().
+
+fwrite(Format, Args) ->
+ format(Format, Args).
+
+-spec fread(string(), string()) -> io_lib_fread:fread_2_ret().
+
+fread(Chars, Format) ->
+ io_lib_fread:fread(Chars, Format).
+
+-spec fread(io_lib_fread:continuation(), string(), string()) ->
+ io_lib_fread:fread_3_ret().
+
+fread(Cont, Chars, Format) ->
+ io_lib_fread:fread(Cont, Chars, Format).
+
+-spec format(io:format(), [term()]) -> chars().
+
+format(Format, Args) ->
+ case catch io_lib_format:fwrite(Format, Args) of
+ {'EXIT',_} ->
+ erlang:error(badarg, [Format, Args]);
+ Other ->
+ Other
+ end.
+
+-spec print(term()) -> chars().
+
+print(Term) ->
+ io_lib_pretty:print(Term).
+
+-spec print(term(), non_neg_integer(), non_neg_integer(), depth()) -> chars().
+
+print(Term, Column, LineLength, Depth) ->
+ io_lib_pretty:print(Term, Column, LineLength, Depth).
+
+-spec indentation(string(), integer()) -> integer().
+
+indentation(Chars, Current) ->
+ io_lib_format:indentation(Chars, Current).
+
+
+%% Format an IO-request prompt (handles formatting errors safely).
+%% Atoms, binaries, and iolists can be used as-is, and will be
+%% printed without any additional quotes.
+%% Note that the output is a deep string, and not an iolist (i.e.,
+%% it may be deep, but never contains binaries, due to the "~s").
+
+-spec format_prompt(term()) -> chars().
+
+format_prompt({format,Format,Args}) ->
+ format_prompt(Format,Args);
+format_prompt(Prompt)
+ when is_list(Prompt); is_atom(Prompt); is_binary(Prompt) ->
+ format_prompt("~s", [Prompt]);
+format_prompt(Prompt) ->
+ format_prompt("~p", [Prompt]).
+
+format_prompt(Format, Args) ->
+ case catch io_lib:format(Format, Args) of
+ {'EXIT',_} -> "???";
+ List -> List
+ end.
+
+
+%% write(Term)
+%% write(Term, Depth)
+%% write(Term, Depth, Pretty)
+%% Return a (non-flattened) list of characters giving a printed
+%% representation of the term. write/3 is for backward compatibility.
+
+-spec write(term()) -> chars().
+
+write(Term) -> write(Term, -1).
+
+-spec write(term(), depth(), boolean()) -> chars().
+
+write(Term, D, true) ->
+ io_lib_pretty:print(Term, 1, 80, D);
+write(Term, D, false) ->
+ write(Term, D).
+
+-spec write(term(), depth()) -> chars().
+
+write(_Term, 0) -> "...";
+write(Term, _D) when is_integer(Term) -> integer_to_list(Term);
+write(Term, _D) when is_float(Term) -> io_lib_format:fwrite_g(Term);
+write(Atom, _D) when is_atom(Atom) -> write_atom(Atom);
+write(Term, _D) when is_port(Term) -> write_port(Term);
+write(Term, _D) when is_pid(Term) -> pid_to_list(Term);
+write(Term, _D) when is_reference(Term) -> write_ref(Term);
+write(<<_/bitstring>>=Term, D) -> write_binary(Term, D);
+write([], _D) -> "[]";
+write({}, _D) -> "{}";
+write([H|T], D) ->
+ if
+ D =:= 1 -> "[...]";
+ true ->
+ [$[,[write(H, D-1)|write_tail(T, D-1, $|)],$]]
+ end;
+write(F, _D) when is_function(F) ->
+ erlang:fun_to_list(F);
+write(T, D) when is_tuple(T) ->
+ if
+ D =:= 1 -> "{...}";
+ true ->
+ [${,
+ [write(element(1, T), D-1)|
+ write_tail(tl(tuple_to_list(T)), D-1, $,)],
+ $}]
+ end.
+
+%% write_tail(List, Depth, CharacterBeforeDots)
+%% Test the terminating case first as this looks better with depth.
+
+write_tail([], _D, _S) -> "";
+write_tail(_, 1, S) -> [S | "..."];
+write_tail([H|T], D, S) ->
+ [$,,write(H, D-1)|write_tail(T, D-1, S)];
+write_tail(Other, D, S) ->
+ [S,write(Other, D-1)].
+
+write_port(Port) ->
+ erlang:port_to_list(Port).
+
+write_ref(Ref) ->
+ erlang:ref_to_list(Ref).
+
+write_binary(B, D) when is_integer(D) ->
+ [$<,$<,write_binary_body(B, D),$>,$>].
+
+write_binary_body(_B, 1) ->
+ "...";
+write_binary_body(<<>>, _D) ->
+ "";
+write_binary_body(<<X:8>>, _D) ->
+ [integer_to_list(X)];
+write_binary_body(<<X:8,Rest/bitstring>>, D) ->
+ [integer_to_list(X),$,|write_binary_body(Rest, D-1)];
+write_binary_body(B, _D) ->
+ L = bit_size(B),
+ <<X:L>> = B,
+ [integer_to_list(X),$:,integer_to_list(L)].
+
+%% write_atom(Atom) -> [Char]
+%% Generate the list of characters needed to print an atom.
+
+-spec write_atom(atom()) -> chars().
+
+write_atom(Atom) ->
+ Chars = atom_to_list(Atom),
+ case quote_atom(Atom, Chars) of
+ true ->
+ write_string(Chars, $'); %'
+ false ->
+ Chars
+ end.
+
+%% quote_atom(Atom, CharList)
+%% Return 'true' if atom with chars in CharList needs to be quoted, else
+%% return 'false'.
+
+-spec quote_atom(atom(), chars()) -> boolean().
+
+quote_atom(Atom, Cs0) ->
+ case erl_scan:reserved_word(Atom) of
+ true -> true;
+ false ->
+ case Cs0 of
+ [C|Cs] when C >= $a, C =< $z ->
+ not name_chars(Cs);
+ [C|Cs] when C >= $�, C =< $�, C =/= $� ->
+ not name_chars(Cs);
+ _ -> true
+ end
+ end.
+
+name_chars([C|Cs]) ->
+ case name_char(C) of
+ true -> name_chars(Cs);
+ false -> false
+ end;
+name_chars([]) -> true.
+
+name_char(C) when C >= $a, C =< $z -> true;
+name_char(C) when C >= $�, C =< $�, C =/= $� -> true;
+name_char(C) when C >= $A, C =< $Z -> true;
+name_char(C) when C >= $�, C =< $�, C =/= $� -> true;
+name_char(C) when C >= $0, C =< $9 -> true;
+name_char($_) -> true;
+name_char($@) -> true;
+name_char(_) -> false.
+
+%% write_string([Char]) -> [Char]
+%% Generate the list of characters needed to print a string.
+
+-spec write_string(string()) -> chars().
+
+write_string(S) ->
+ write_string(S, $"). %"
+
+-spec write_string(string(), char()) -> chars().
+
+write_string(S, Q) ->
+ [Q|write_string1(latin1, S, Q)].
+
+write_unicode_string(S) ->
+ write_unicode_string(S, $"). %"
+
+write_unicode_string(S, Q) ->
+ [Q|write_string1(unicode, S, Q)].
+
+write_string1(_,[], Q) ->
+ [Q];
+write_string1(Enc,[C|Cs], Q) ->
+ string_char(Enc,C, Q, write_string1(Enc,Cs, Q)).
+
+string_char(_,Q, Q, Tail) -> [$\\,Q|Tail]; %Must check these first!
+string_char(_,$\\, _, Tail) -> [$\\,$\\|Tail];
+string_char(_,C, _, Tail) when C >= $\s, C =< $~ ->
+ [C|Tail];
+string_char(latin1,C, _, Tail) when C >= $\240, C =< $\377 ->
+ [C|Tail];
+string_char(unicode,C, _, Tail) when C >= $\240 ->
+ "\\x{"++erlang:integer_to_list(C, 16)++"}"++Tail;
+string_char(_,$\n, _, Tail) -> [$\\,$n|Tail]; %\n = LF
+string_char(_,$\r, _, Tail) -> [$\\,$r|Tail]; %\r = CR
+string_char(_,$\t, _, Tail) -> [$\\,$t|Tail]; %\t = TAB
+string_char(_,$\v, _, Tail) -> [$\\,$v|Tail]; %\v = VT
+string_char(_,$\b, _, Tail) -> [$\\,$b|Tail]; %\b = BS
+string_char(_,$\f, _, Tail) -> [$\\,$f|Tail]; %\f = FF
+string_char(_,$\e, _, Tail) -> [$\\,$e|Tail]; %\e = ESC
+string_char(_,$\d, _, Tail) -> [$\\,$d|Tail]; %\d = DEL
+string_char(_,C, _, Tail) when C < $\240-> %Other control characters.
+ C1 = (C bsr 6) + $0,
+ C2 = ((C bsr 3) band 7) + $0,
+ C3 = (C band 7) + $0,
+ [$\\,C1,C2,C3|Tail].
+
+%% write_char(Char) -> [char()].
+%% Generate the list of characters needed to print a character constant.
+%% Must special case SPACE, $\s, here.
+
+-spec write_char(char()) -> chars().
+
+write_char($\s) -> "$\\s"; %Must special case this.
+write_char(C) when is_integer(C), C >= $\000, C =< $\377 ->
+ [$$|string_char(latin1,C, -1, [])].
+
+write_unicode_char(Ch) when Ch =< 255 ->
+ write_char(Ch);
+write_unicode_char(Uni) ->
+ [$$|string_char(unicode,Uni, -1, [])].
+
+%% char_list(CharList)
+%% deep_char_list(CharList)
+%% Return true if CharList is a (possibly deep) list of characters, else
+%% false.
+
+-spec char_list(term()) -> boolean().
+
+char_list([C|Cs]) when is_integer(C), C >= $\000, C =< $\377 ->
+ char_list(Cs);
+char_list([]) -> true;
+char_list(_) -> false. %Everything else is false
+
+-spec unicode_char_list(term()) -> boolean().
+
+unicode_char_list([C|Cs]) when is_integer(C), C >= 0, C < 16#D800;
+ is_integer(C), C > 16#DFFF, C < 16#FFFE;
+ is_integer(C), C > 16#FFFF, C =< 16#10FFFF ->
+ unicode_char_list(Cs);
+unicode_char_list([]) -> true;
+unicode_char_list(_) -> false. %Everything else is false
+
+-spec deep_char_list(term()) -> boolean().
+
+deep_char_list(Cs) ->
+ deep_char_list(Cs, []).
+
+deep_char_list([C|Cs], More) when is_list(C) ->
+ deep_char_list(C, [Cs|More]);
+deep_char_list([C|Cs], More) when is_integer(C), C >= $\000, C =< $\377 ->
+ deep_char_list(Cs, More);
+deep_char_list([], [Cs|More]) ->
+ deep_char_list(Cs, More);
+deep_char_list([], []) -> true;
+deep_char_list(_, _More) -> %Everything else is false
+ false.
+
+-spec deep_unicode_char_list(term()) -> boolean().
+
+deep_unicode_char_list(Cs) ->
+ deep_unicode_char_list(Cs, []).
+
+deep_unicode_char_list([C|Cs], More) when is_list(C) ->
+ deep_unicode_char_list(C, [Cs|More]);
+deep_unicode_char_list([C|Cs], More)
+ when is_integer(C), C >= 0, C < 16#D800;
+ is_integer(C), C > 16#DFFF, C < 16#FFFE;
+ is_integer(C), C > 16#FFFF, C =< 16#10FFFF ->
+ deep_unicode_char_list(Cs, More);
+deep_unicode_char_list([], [Cs|More]) ->
+ deep_unicode_char_list(Cs, More);
+deep_unicode_char_list([], []) -> true;
+deep_unicode_char_list(_, _More) -> %Everything else is false
+ false.
+
+%% printable_list([Char]) -> boolean()
+%% Return true if CharList is a list of printable characters, else
+%% false.
+
+-spec printable_list(term()) -> boolean().
+
+printable_list([C|Cs]) when is_integer(C), C >= $\040, C =< $\176 ->
+ printable_list(Cs);
+printable_list([C|Cs]) when is_integer(C), C >= $\240, C =< $\377 ->
+ printable_list(Cs);
+printable_list([$\n|Cs]) -> printable_list(Cs);
+printable_list([$\r|Cs]) -> printable_list(Cs);
+printable_list([$\t|Cs]) -> printable_list(Cs);
+printable_list([$\v|Cs]) -> printable_list(Cs);
+printable_list([$\b|Cs]) -> printable_list(Cs);
+printable_list([$\f|Cs]) -> printable_list(Cs);
+printable_list([$\e|Cs]) -> printable_list(Cs);
+printable_list([]) -> true;
+printable_list(_) -> false. %Everything else is false
+
+%% printable_unicode_list([Char]) -> boolean()
+%% Return true if CharList is a list of printable characters, else
+%% false. The notion of printable in Unicode terms is somewhat floating.
+%% Everything that is not a control character and not invalid unicode
+%% will be considered printable.
+
+-spec printable_unicode_list(term()) -> boolean().
+
+printable_unicode_list([C|Cs]) when is_integer(C), C >= $\040, C =< $\176 ->
+ printable_unicode_list(Cs);
+printable_unicode_list([C|Cs])
+ when is_integer(C), C >= 16#A0, C < 16#D800;
+ is_integer(C), C > 16#DFFF, C < 16#FFFE;
+ is_integer(C), C > 16#FFFF, C =< 16#10FFFF ->
+ printable_unicode_list(Cs);
+printable_unicode_list([$\n|Cs]) -> printable_unicode_list(Cs);
+printable_unicode_list([$\r|Cs]) -> printable_unicode_list(Cs);
+printable_unicode_list([$\t|Cs]) -> printable_unicode_list(Cs);
+printable_unicode_list([$\v|Cs]) -> printable_unicode_list(Cs);
+printable_unicode_list([$\b|Cs]) -> printable_unicode_list(Cs);
+printable_unicode_list([$\f|Cs]) -> printable_unicode_list(Cs);
+printable_unicode_list([$\e|Cs]) -> printable_unicode_list(Cs);
+printable_unicode_list([]) -> true;
+printable_unicode_list(_) -> false. %Everything else is false
+
+%% List = nl()
+%% Return a list of characters to generate a newline.
+
+-spec nl() -> string().
+
+nl() ->
+ "\n".
+
+%%
+%% Utilities for collecting characters in input files
+%%
+
+count_and_find_utf8(Bin,N) ->
+ cafu(Bin,N,0,0,none).
+
+cafu(<<>>,_N,Count,_ByteCount,SavePos) ->
+ {Count,SavePos};
+cafu(<<_/utf8,Rest/binary>>, 0, Count, ByteCount, _SavePos) ->
+ cafu(Rest,-1,Count+1,0,ByteCount);
+cafu(<<_/utf8,Rest/binary>>, N, Count, _ByteCount, SavePos) when N < 0 ->
+ cafu(Rest,-1,Count+1,0,SavePos);
+cafu(<<_/utf8,Rest/binary>> = Whole, N, Count, ByteCount, SavePos) ->
+ Delta = byte_size(Whole) - byte_size(Rest),
+ cafu(Rest,N-1,Count+1,ByteCount+Delta,SavePos);
+cafu(_Other,_N,Count,_ByteCount,SavePos) -> % Non Utf8 character at end
+ {Count,SavePos}.
+
+%% collect_chars(State, Data, Count). New in R9C.
+%% Returns:
+%% {stop,Result,RestData}
+%% NewState
+%%% BC (with pre-R13).
+collect_chars(Tag, Data, N) ->
+ collect_chars(Tag, Data, latin1, N).
+
+%% Now we are aware of encoding...
+collect_chars(start, Data, unicode, N) when is_binary(Data) ->
+ {Size,Npos} = count_and_find_utf8(Data,N),
+ if Size > N ->
+ {B1,B2} = split_binary(Data, Npos),
+ {stop,B1,B2};
+ Size < N ->
+ {binary,[Data],N-Size};
+ true ->
+ {stop,Data,eof}
+ end;
+collect_chars(start, Data, latin1, N) when is_binary(Data) ->
+ Size = byte_size(Data),
+ if Size > N ->
+ {B1,B2} = split_binary(Data, N),
+ {stop,B1,B2};
+ Size < N ->
+ {binary,[Data],N-Size};
+ true ->
+ {stop,Data,eof}
+ end;
+collect_chars(start,Data,_,N) when is_list(Data) ->
+ collect_chars_list([], N, Data);
+collect_chars(start, eof, _,_) ->
+ {stop,eof,eof};
+collect_chars({binary,Stack,_N}, eof, _,_) ->
+ {stop,binrev(Stack),eof};
+collect_chars({binary,Stack,N}, Data,unicode, _) ->
+ {Size,Npos} = count_and_find_utf8(Data,N),
+ if Size > N ->
+ {B1,B2} = split_binary(Data, Npos),
+ {stop,binrev(Stack, [B1]),B2};
+ Size < N ->
+ {binary,[Data|Stack],N-Size};
+ true ->
+ {stop,binrev(Stack, [Data]),eof}
+ end;
+collect_chars({binary,Stack,N}, Data,latin1, _) ->
+ Size = byte_size(Data),
+ if Size > N ->
+ {B1,B2} = split_binary(Data, N),
+ {stop,binrev(Stack, [B1]),B2};
+ Size < N ->
+ {binary,[Data|Stack],N-Size};
+ true ->
+ {stop,binrev(Stack, [Data]),eof}
+ end;
+collect_chars({list,Stack,N}, Data, _,_) ->
+ collect_chars_list(Stack, N, Data);
+%% collect_chars(Continuation, MoreChars, Count)
+%% Returns:
+%% {done,Result,RestChars}
+%% {more,Continuation}
+
+collect_chars([], Chars, _, N) ->
+ collect_chars1(N, Chars, []);
+collect_chars({Left,Sofar}, Chars, _, _N) ->
+ collect_chars1(Left, Chars, Sofar).
+
+collect_chars1(N, Chars, Stack) when N =< 0 ->
+ {done,lists:reverse(Stack, []),Chars};
+collect_chars1(N, [C|Rest], Stack) ->
+ collect_chars1(N-1, Rest, [C|Stack]);
+collect_chars1(_N, eof, []) ->
+ {done,eof,[]};
+collect_chars1(_N, eof, Stack) ->
+ {done,lists:reverse(Stack, []),[]};
+collect_chars1(N, [], Stack) ->
+ {more,{N,Stack}}.
+
+collect_chars_list(Stack, 0, Data) ->
+ {stop,lists:reverse(Stack, []),Data};
+collect_chars_list(Stack, _N, eof) ->
+ {stop,lists:reverse(Stack, []),eof};
+collect_chars_list(Stack, N, []) ->
+ {list,Stack,N};
+collect_chars_list(Stack,N, [H|T]) ->
+ collect_chars_list([H|Stack], N-1, T).
+
+%% collect_line(Continuation, MoreChars)
+%% Returns:
+%% {done,Result,RestChars}
+%% {more,Continuation}
+%%
+%% XXX Can be removed when compatibility with pre-R12B-5 nodes
+%% is no longer required.
+%%
+collect_line([], Chars) ->
+ collect_line1(Chars, []);
+collect_line({SoFar}, More) ->
+ collect_line1(More, SoFar).
+
+collect_line1([$\r, $\n|Rest], Stack) ->
+ collect_line1([$\n|Rest], Stack);
+collect_line1([$\n|Rest], Stack) ->
+ {done,lists:reverse([$\n|Stack], []),Rest};
+collect_line1([C|Rest], Stack) ->
+ collect_line1(Rest, [C|Stack]);
+collect_line1(eof, []) ->
+ {done,eof,[]};
+collect_line1(eof, Stack) ->
+ {done,lists:reverse(Stack, []),[]};
+collect_line1([], Stack) ->
+ {more,{Stack}}.
+
+%% collect_line(State, Data, _). New in R9C.
+%% Returns:
+%% {stop,Result,RestData}
+%% NewState
+%%% BC (with pre-R13).
+collect_line(Tag, Data, Any) ->
+ collect_line(Tag, Data, latin1, Any).
+
+%% Now we are aware of encoding...
+collect_line(start, Data, Encoding, _) when is_binary(Data) ->
+ collect_line_bin(Data, Data, [], Encoding);
+collect_line(start, Data, _, _) when is_list(Data) ->
+ collect_line_list(Data, []);
+collect_line(start, eof, _, _) ->
+ {stop,eof,eof};
+collect_line(Stack, Data, Encoding, _) when is_binary(Data) ->
+ collect_line_bin(Data, Data, Stack, Encoding);
+collect_line(Stack, Data, _, _) when is_list(Data) ->
+ collect_line_list(Data, Stack);
+collect_line([B|_]=Stack, eof, _, _) when is_binary(B) ->
+ {stop,binrev(Stack),eof};
+collect_line(Stack, eof, _, _) ->
+ {stop,lists:reverse(Stack, []),eof}.
+
+
+collect_line_bin(<<$\n,T/binary>>, Data, Stack0, _) ->
+ N = byte_size(Data) - byte_size(T),
+ <<Line:N/binary,_/binary>> = Data,
+ case Stack0 of
+ [] ->
+ {stop,Line,T};
+ [<<$\r>>|Stack] when N =:= 1 ->
+ {stop,binrev(Stack, [$\n]),T};
+ _ ->
+ {stop,binrev(Stack0, [Line]),T}
+ end;
+collect_line_bin(<<$\r,$\n,T/binary>>, Data, Stack, _) ->
+ N = byte_size(Data) - byte_size(T) - 2,
+ <<Line:N/binary,_/binary>> = Data,
+ {stop,binrev(Stack, [Line,$\n]),T};
+collect_line_bin(<<$\r>>, Data0, Stack, _) ->
+ N = byte_size(Data0) - 1,
+ <<Data:N/binary,_/binary>> = Data0,
+ [<<$\r>>,Data|Stack];
+collect_line_bin(<<_,T/binary>>, Data, Stack, Enc) ->
+ collect_line_bin(T, Data, Stack, Enc);
+collect_line_bin(<<>>, Data, Stack, _) ->
+ %% Need more data here.
+ [Data|Stack].
+
+collect_line_list([$\n|T], [$\r|Stack]) ->
+ {stop,lists:reverse(Stack, [$\n]),T};
+collect_line_list([$\n|T], Stack) ->
+ {stop,lists:reverse(Stack, [$\n]),T};
+collect_line_list([H|T], Stack) ->
+ collect_line_list(T, [H|Stack]);
+collect_line_list([], Stack) ->
+ Stack.
+
+%% Translator function to emulate a new (R9C and later)
+%% I/O client when you have an old one.
+%%
+%% Implements a middleman that is get_until server and get_chars client.
+
+%%% BC (with pre-R13).
+get_until(Any,Data,Arg) ->
+ get_until(Any,Data,latin1,Arg).
+
+%% Now we are aware of encoding...
+get_until(start, Data, Encoding, XtraArg) ->
+ get_until([], Data, Encoding, XtraArg);
+get_until(Cont, Data, Encoding, {Mod, Func, XtraArgs}) ->
+ Chars = if is_binary(Data), Encoding =:= unicode ->
+ unicode:characters_to_list(Data,utf8);
+ is_binary(Data) ->
+ binary_to_list(Data);
+ true ->
+ Data
+ end,
+ case apply(Mod, Func, [Cont,Chars|XtraArgs]) of
+ {done,Result,Buf} ->
+ {stop,if is_binary(Data),
+ is_list(Result),
+ Encoding =:= unicode ->
+ unicode:characters_to_binary(Result,unicode,unicode);
+ is_binary(Data),
+ is_list(Result) ->
+ erlang:iolist_to_binary(Result);
+%% is_list(Data),
+%% is_list(Result),
+%% Encoding =:= latin1 ->
+%% % Should check for only latin1, but skip that for
+%% % efficiency reasons.
+%% [ exit({cannot_convert, unicode, latin1}) ||
+%% X <- List, X > 255 ];
+ true ->
+ Result
+ end,
+ Buf};
+ {more,NewCont} ->
+ NewCont
+ end.
+
+binrev(L) ->
+ list_to_binary(lists:reverse(L, [])).
+
+binrev(L, T) ->
+ list_to_binary(lists:reverse(L, T)).
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
new file mode 100644
index 0000000000..eb1885021d
--- /dev/null
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -0,0 +1,678 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(io_lib_format).
+
+%% Formatting functions of io library.
+
+-export([fwrite/2,fwrite_g/1,indentation/2]).
+
+%% fwrite(Format, ArgList) -> [Char].
+%% Format the arguments in ArgList after string Format. Just generate
+%% an error if there is an error in the arguments.
+%%
+%% To do the printing command correctly we need to calculate the
+%% current indentation for everything before it. This may be very
+%% expensive, especially when it is not needed, so we first determine
+%% if, and for how long, we need to calculate the indentations. We do
+%% this by first collecting all the control sequences and
+%% corresponding arguments, then counting the print sequences and
+%% then building the output. This method has some drawbacks, it does
+%% two passes over the format string and creates more temporary data,
+%% and it also splits the handling of the control characters into two
+%% parts.
+
+fwrite(Format, Args) when is_atom(Format) ->
+ fwrite(atom_to_list(Format), Args);
+fwrite(Format, Args) when is_binary(Format) ->
+ fwrite(binary_to_list(Format), Args);
+fwrite(Format, Args) ->
+ Cs = collect(Format, Args),
+ Pc = pcount(Cs),
+ build(Cs, Pc, 0).
+
+collect([$~|Fmt0], Args0) ->
+ {C,Fmt1,Args1} = collect_cseq(Fmt0, Args0),
+ [C|collect(Fmt1, Args1)];
+collect([C|Fmt], Args) ->
+ [C|collect(Fmt, Args)];
+collect([], []) -> [].
+
+collect_cseq(Fmt0, Args0) ->
+ {F,Ad,Fmt1,Args1} = field_width(Fmt0, Args0),
+ {P,Fmt2,Args2} = precision(Fmt1, Args1),
+ {Pad,Fmt3,Args3} = pad_char(Fmt2, Args2),
+ {Encoding,Fmt4,Args4} = encoding(Fmt3, Args3),
+ {C,As,Fmt5,Args5} = collect_cc(Fmt4, Args4),
+ {{C,As,F,Ad,P,Pad,Encoding},Fmt5,Args5}.
+
+encoding([$t|Fmt],Args) ->
+ {unicode,Fmt,Args};
+encoding(Fmt,Args) ->
+ {latin1,Fmt,Args}.
+
+field_width([$-|Fmt0], Args0) ->
+ {F,Fmt,Args} = field_value(Fmt0, Args0),
+ field_width(-F, Fmt, Args);
+field_width(Fmt0, Args0) ->
+ {F,Fmt,Args} = field_value(Fmt0, Args0),
+ field_width(F, Fmt, Args).
+
+field_width(F, Fmt, Args) when F < 0 ->
+ {-F,left,Fmt,Args};
+field_width(F, Fmt, Args) when F >= 0 ->
+ {F,right,Fmt,Args}.
+
+precision([$.|Fmt], Args) ->
+ field_value(Fmt, Args);
+precision(Fmt, Args) ->
+ {none,Fmt,Args}.
+
+field_value([$*|Fmt], [A|Args]) when is_integer(A) ->
+ {A,Fmt,Args};
+field_value([C|Fmt], Args) when is_integer(C), C >= $0, C =< $9 ->
+ field_value([C|Fmt], Args, 0);
+field_value(Fmt, Args) ->
+ {none,Fmt,Args}.
+
+field_value([C|Fmt], Args, F) when is_integer(C), C >= $0, C =< $9 ->
+ field_value(Fmt, Args, 10*F + (C - $0));
+field_value(Fmt, Args, F) -> %Default case
+ {F,Fmt,Args}.
+
+pad_char([$.,$*|Fmt], [Pad|Args]) -> {Pad,Fmt,Args};
+pad_char([$.,Pad|Fmt], Args) -> {Pad,Fmt,Args};
+pad_char(Fmt, Args) -> {$\s,Fmt,Args}.
+
+%% collect_cc([FormatChar], [Argument]) ->
+%% {Control,[ControlArg],[FormatChar],[Arg]}.
+%% Here we collect the argments for each control character.
+%% Be explicit to cause failure early.
+
+collect_cc([$w|Fmt], [A|Args]) -> {$w,[A],Fmt,Args};
+collect_cc([$p|Fmt], [A|Args]) -> {$p,[A],Fmt,Args};
+collect_cc([$W|Fmt], [A,Depth|Args]) -> {$W,[A,Depth],Fmt,Args};
+collect_cc([$P|Fmt], [A,Depth|Args]) -> {$P,[A,Depth],Fmt,Args};
+collect_cc([$s|Fmt], [A|Args]) -> {$s,[A],Fmt,Args};
+collect_cc([$e|Fmt], [A|Args]) -> {$e,[A],Fmt,Args};
+collect_cc([$f|Fmt], [A|Args]) -> {$f,[A],Fmt,Args};
+collect_cc([$g|Fmt], [A|Args]) -> {$g,[A],Fmt,Args};
+collect_cc([$b|Fmt], [A|Args]) -> {$b,[A],Fmt,Args};
+collect_cc([$B|Fmt], [A|Args]) -> {$B,[A],Fmt,Args};
+collect_cc([$x|Fmt], [A,Prefix|Args]) -> {$x,[A,Prefix],Fmt,Args};
+collect_cc([$X|Fmt], [A,Prefix|Args]) -> {$X,[A,Prefix],Fmt,Args};
+collect_cc([$+|Fmt], [A|Args]) -> {$+,[A],Fmt,Args};
+collect_cc([$#|Fmt], [A|Args]) -> {$#,[A],Fmt,Args};
+collect_cc([$c|Fmt], [A|Args]) -> {$c,[A],Fmt,Args};
+collect_cc([$~|Fmt], Args) when is_list(Args) -> {$~,[],Fmt,Args};
+collect_cc([$n|Fmt], Args) when is_list(Args) -> {$n,[],Fmt,Args};
+collect_cc([$i|Fmt], [A|Args]) -> {$i,[A],Fmt,Args}.
+
+%% pcount([ControlC]) -> Count.
+%% Count the number of print requests.
+
+pcount(Cs) -> pcount(Cs, 0).
+
+pcount([{$p,_As,_F,_Ad,_P,_Pad,_Enc}|Cs], Acc) -> pcount(Cs, Acc+1);
+pcount([{$P,_As,_F,_Ad,_P,_Pad,_Enc}|Cs], Acc) -> pcount(Cs, Acc+1);
+pcount([_|Cs], Acc) -> pcount(Cs, Acc);
+pcount([], Acc) -> Acc.
+
+%% build([Control], Pc, Indentation) -> [Char].
+%% Interpret the control structures. Count the number of print
+%% remaining and only calculate indentation when necessary. Must also
+%% be smart when calculating indentation for characters in format.
+
+build([{C,As,F,Ad,P,Pad,Enc}|Cs], Pc0, I) ->
+ S = control(C, As, F, Ad, P, Pad, Enc, I),
+ Pc1 = decr_pc(C, Pc0),
+ if
+ Pc1 > 0 -> [S|build(Cs, Pc1, indentation(S, I))];
+ true -> [S|build(Cs, Pc1, I)]
+ end;
+build([$\n|Cs], Pc, _I) -> [$\n|build(Cs, Pc, 0)];
+build([$\t|Cs], Pc, I) -> [$\t|build(Cs, Pc, ((I + 8) div 8) * 8)];
+build([C|Cs], Pc, I) -> [C|build(Cs, Pc, I+1)];
+build([], _Pc, _I) -> [].
+
+decr_pc($p, Pc) -> Pc - 1;
+decr_pc($P, Pc) -> Pc - 1;
+decr_pc(_, Pc) -> Pc.
+
+%% indentation([Char], Indentation) -> Indentation.
+%% Calculate the indentation of the end of a string given its start
+%% indentation. We assume tabs at 8 cols.
+
+indentation([$\n|Cs], _I) -> indentation(Cs, 0);
+indentation([$\t|Cs], I) -> indentation(Cs, ((I + 8) div 8) * 8);
+indentation([C|Cs], I) when is_integer(C) ->
+ indentation(Cs, I+1);
+indentation([C|Cs], I) ->
+ indentation(Cs, indentation(C, I));
+indentation([], I) -> I.
+
+%% control(FormatChar, [Argument], FieldWidth, Adjust, Precision, PadChar,
+%% Indentation) ->
+%% [Char]
+%% This is the main dispatch function for the various formatting commands.
+%% Field widths and precisions have already been calculated.
+
+control($w, [A], F, Adj, P, Pad, _Enc,_I) ->
+ term(io_lib:write(A, -1), F, Adj, P, Pad);
+control($p, [A], F, Adj, P, Pad, _Enc, I) ->
+ print(A, -1, F, Adj, P, Pad, I);
+control($W, [A,Depth], F, Adj, P, Pad, _Enc, _I) when is_integer(Depth) ->
+ term(io_lib:write(A, Depth), F, Adj, P, Pad);
+control($P, [A,Depth], F, Adj, P, Pad, _Enc, I) when is_integer(Depth) ->
+ print(A, Depth, F, Adj, P, Pad, I);
+control($s, [A], F, Adj, P, Pad, _Enc, _I) when is_atom(A) ->
+ string(atom_to_list(A), F, Adj, P, Pad);
+control($s, [L0], F, Adj, P, Pad, latin1, _I) ->
+ L = iolist_to_chars(L0),
+ string(L, F, Adj, P, Pad);
+control($s, [L0], F, Adj, P, Pad, unicode, _I) ->
+ L = unicode:characters_to_list(L0),
+ uniconv(string(L, F, Adj, P, Pad));
+control($e, [A], F, Adj, P, Pad, _Enc, _I) when is_float(A) ->
+ fwrite_e(A, F, Adj, P, Pad);
+control($f, [A], F, Adj, P, Pad, _Enc, _I) when is_float(A) ->
+ fwrite_f(A, F, Adj, P, Pad);
+control($g, [A], F, Adj, P, Pad, _Enc, _I) when is_float(A) ->
+ fwrite_g(A, F, Adj, P, Pad);
+control($b, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+ unprefixed_integer(A, F, Adj, base(P), Pad, true);
+control($B, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+ unprefixed_integer(A, F, Adj, base(P), Pad, false);
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A),
+ is_atom(Prefix) ->
+ prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), true);
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+ true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
+ prefixed_integer(A, F, Adj, base(P), Pad, Prefix, true);
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A),
+ is_atom(Prefix) ->
+ prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), false);
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+ true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
+ prefixed_integer(A, F, Adj, base(P), Pad, Prefix, false);
+control($+, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+ Base = base(P),
+ Prefix = [integer_to_list(Base), $#],
+ prefixed_integer(A, F, Adj, Base, Pad, Prefix, true);
+control($#, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+ Base = base(P),
+ Prefix = [integer_to_list(Base), $#],
+ prefixed_integer(A, F, Adj, Base, Pad, Prefix, false);
+control($c, [A], F, Adj, P, Pad, unicode, _I) when is_integer(A) ->
+ char(A, F, Adj, P, Pad);
+control($c, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+ char(A band 255, F, Adj, P, Pad);
+control($~, [], F, Adj, P, Pad, _Enc, _I) -> char($~, F, Adj, P, Pad);
+control($n, [], F, Adj, P, Pad, _Enc, _I) -> newline(F, Adj, P, Pad);
+control($i, [_A], _F, _Adj, _P, _Pad, _Enc, _I) -> [].
+
+-ifdef(UNICODE_AS_BINARIES).
+uniconv(C) ->
+ unicode:characters_to_binary(C,unicode).
+-else.
+uniconv(C) ->
+ C.
+-endif.
+%% Default integer base
+base(none) ->
+ 10;
+base(B) when is_integer(B) ->
+ B.
+
+%% term(TermList, Field, Adjust, Precision, PadChar)
+%% Output the characters in a term.
+%% Adjust the characters within the field if length less than Max padding
+%% with PadChar.
+
+term(T, none, _Adj, none, _Pad) -> T;
+term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
+term(T, F, Adj, P0, Pad) ->
+ L = lists:flatlength(T),
+ P = case P0 of none -> erlang:min(L, F); _ -> P0 end,
+ if
+ L > P ->
+ adjust(chars($*, P), chars(Pad, F-P), Adj);
+ F >= P ->
+ adjust(T, chars(Pad, F-L), Adj)
+ end.
+
+%% print(Term, Depth, Field, Adjust, Precision, PadChar, Indentation)
+%% Print a term.
+
+print(T, D, none, Adj, P, Pad, I) -> print(T, D, 80, Adj, P, Pad, I);
+print(T, D, F, Adj, none, Pad, I) -> print(T, D, F, Adj, I+1, Pad, I);
+print(T, D, F, right, P, _Pad, _I) ->
+ io_lib_pretty:print(T, P, F, D).
+
+%% fwrite_e(Float, Field, Adjust, Precision, PadChar)
+
+fwrite_e(Fl, none, Adj, none, Pad) -> %Default values
+ fwrite_e(Fl, none, Adj, 6, Pad);
+fwrite_e(Fl, none, _Adj, P, _Pad) when P >= 2 ->
+ float_e(Fl, float_data(Fl), P);
+fwrite_e(Fl, F, Adj, none, Pad) ->
+ fwrite_e(Fl, F, Adj, 6, Pad);
+fwrite_e(Fl, F, Adj, P, Pad) when P >= 2 ->
+ term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad).
+
+float_e(Fl, Fd, P) when Fl < 0.0 -> %Negative numbers
+ [$-|float_e(-Fl, Fd, P)];
+float_e(_Fl, {Ds,E}, P) ->
+ case float_man(Ds, 1, P-1) of
+ {[$0|Fs],true} -> [[$1|Fs]|float_exp(E)];
+ {Fs,false} -> [Fs|float_exp(E-1)]
+ end.
+
+%% float_man([Digit], Icount, Dcount) -> {[Chars],CarryFlag}.
+%% Generate the characters in the mantissa from the digits with Icount
+%% characters before the '.' and Dcount decimals. Handle carry and let
+%% caller decide what to do at top.
+
+float_man(Ds, 0, Dc) ->
+ {Cs,C} = float_man(Ds, Dc),
+ {[$.|Cs],C};
+float_man([D|Ds], I, Dc) ->
+ case float_man(Ds, I-1, Dc) of
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
+ end;
+float_man([], I, Dc) -> %Pad with 0's
+ {string:chars($0, I, [$.|string:chars($0, Dc)]),false}.
+
+float_man([D|_], 0) when D >= $5 -> {[],true};
+float_man([_|_], 0) -> {[],false};
+float_man([D|Ds], Dc) ->
+ case float_man(Ds, Dc-1) of
+ {Cs,true} when D =:= $9 -> {[$0|Cs],true};
+ {Cs,true} -> {[D+1|Cs],false};
+ {Cs,false} -> {[D|Cs],false}
+ end;
+float_man([], Dc) -> {string:chars($0, Dc),false}. %Pad with 0's
+
+%% float_exp(Exponent) -> [Char].
+%% Generate the exponent of a floating point number. Always include sign.
+
+float_exp(E) when E >= 0 ->
+ [$e,$+|integer_to_list(E)];
+float_exp(E) ->
+ [$e|integer_to_list(E)].
+
+%% fwrite_f(FloatData, Field, Adjust, Precision, PadChar)
+
+fwrite_f(Fl, none, Adj, none, Pad) -> %Default values
+ fwrite_f(Fl, none, Adj, 6, Pad);
+fwrite_f(Fl, none, _Adj, P, _Pad) when P >= 1 ->
+ float_f(Fl, float_data(Fl), P);
+fwrite_f(Fl, F, Adj, none, Pad) ->
+ fwrite_f(Fl, F, Adj, 6, Pad);
+fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 ->
+ term(float_f(Fl, float_data(Fl), P), F, Adj, F, Pad).
+
+float_f(Fl, Fd, P) when Fl < 0.0 ->
+ [$-|float_f(-Fl, Fd, P)];
+float_f(Fl, {Ds,E}, P) when E =< 0 ->
+ float_f(Fl, {string:chars($0, -E+1, Ds),1}, P); %Prepend enough 0's
+float_f(_Fl, {Ds,E}, P) ->
+ case float_man(Ds, E, P) of
+ {Fs,true} -> "1" ++ Fs; %Handle carry
+ {Fs,false} -> Fs
+ end.
+
+%% float_data([FloatChar]) -> {[Digit],Exponent}
+
+float_data(Fl) ->
+ float_data(float_to_list(Fl), []).
+
+float_data([$e|E], Ds) ->
+ {lists:reverse(Ds),list_to_integer(E)+1};
+float_data([D|Cs], Ds) when D >= $0, D =< $9 ->
+ float_data(Cs, [D|Ds]);
+float_data([_|Cs], Ds) ->
+ float_data(Cs, Ds).
+
+%% fwrite_g(Float)
+%% Writes the shortest, correctly rounded string that converts
+%% to Float when read back with list_to_float/1.
+%%
+%% See also "Printing Floating-Point Numbers Quickly and Accurately"
+%% in Proceedings of the SIGPLAN '96 Conference on Programming
+%% Language Design and Implementation.
+
+fwrite_g(0.0) ->
+ "0.0";
+fwrite_g(Float) when is_float(Float) ->
+ {Frac, Exp} = mantissa_exponent(Float),
+ {Place, Digits} = fwrite_g_1(Float, Exp, Frac),
+ R = insert_decimal(Place, [$0 + D || D <- Digits]),
+ [$- || true <- [Float < 0.0]] ++ R.
+
+-define(BIG_POW, (1 bsl 52)).
+-define(MIN_EXP, (-1074)).
+
+mantissa_exponent(F) ->
+ case <<F:64/float>> of
+ <<_S:1, 0:11, M:52>> -> % denormalized
+ E = log2floor(M),
+ {M bsl (53 - E), E - 52 - 1075};
+ <<_S:1, BE:11, M:52>> when BE < 2047 ->
+ {M + ?BIG_POW, BE - 1075}
+ end.
+
+fwrite_g_1(Float, Exp, Frac) ->
+ Round = (Frac band 1) =:= 0,
+ if
+ Exp >= 0 ->
+ BExp = 1 bsl Exp,
+ if
+ Frac =:= ?BIG_POW ->
+ scale(Frac * BExp * 4, 4, BExp * 2, BExp,
+ Round, Round, Float);
+ true ->
+ scale(Frac * BExp * 2, 2, BExp, BExp,
+ Round, Round, Float)
+ end;
+ Exp < ?MIN_EXP ->
+ BExp = 1 bsl (?MIN_EXP - Exp),
+ scale(Frac * 2, 1 bsl (1 - Exp), BExp, BExp,
+ Round, Round, Float);
+ Exp > ?MIN_EXP, Frac =:= ?BIG_POW ->
+ scale(Frac * 4, 1 bsl (2 - Exp), 2, 1,
+ Round, Round, Float);
+ true ->
+ scale(Frac * 2, 1 bsl (1 - Exp), 1, 1,
+ Round, Round, Float)
+ end.
+
+scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) ->
+ Est = int_ceil(math:log10(abs(Float)) - 1.0e-10),
+ %% Note that the scheme implementation uses a 326 element look-up
+ %% table for int_pow(10, N) where we do not.
+ if
+ Est >= 0 ->
+ fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est,
+ LowOk, HighOk);
+ true ->
+ Scale = int_pow(10, -Est),
+ fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est,
+ LowOk, HighOk)
+ end.
+
+fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) ->
+ TooLow = if
+ HighOk -> R + MPlus >= S;
+ true -> R + MPlus > S
+ end,
+ case TooLow of
+ true ->
+ {K + 1, generate(R, S, MPlus, MMinus, LowOk, HighOk)};
+ false ->
+ {K, generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)}
+ end.
+
+generate(R0, S, MPlus, MMinus, LowOk, HighOk) ->
+ D = R0 div S,
+ R = R0 rem S,
+ TC1 = if
+ LowOk -> R =< MMinus;
+ true -> R < MMinus
+ end,
+ TC2 = if
+ HighOk -> R + MPlus >= S;
+ true -> R + MPlus > S
+ end,
+ case {TC1, TC2} of
+ {false, false} ->
+ [D | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)];
+ {false, true} ->
+ [D + 1];
+ {true, false} ->
+ [D];
+ {true, true} when R * 2 < S ->
+ [D];
+ {true, true} ->
+ [D + 1]
+ end.
+
+insert_decimal(0, S) ->
+ "0." ++ S;
+insert_decimal(Place, S) ->
+ L = length(S),
+ if
+ Place < 0;
+ Place >= L ->
+ ExpL = integer_to_list(Place - 1),
+ ExpDot = if L =:= 1 -> 2; true -> 1 end,
+ ExpCost = length(ExpL) + 1 + ExpDot,
+ if
+ Place < 0 ->
+ if
+ 2 - Place =< ExpCost ->
+ "0." ++ lists:duplicate(-Place, $0) ++ S;
+ true ->
+ insert_exp(ExpL, S)
+ end;
+ true ->
+ if
+ Place - L + 2 =< ExpCost ->
+ S ++ lists:duplicate(Place - L, $0) ++ ".0";
+ true ->
+ insert_exp(ExpL, S)
+ end
+ end;
+ true ->
+ {S0, S1} = lists:split(Place, S),
+ S0 ++ "." ++ S1
+ end.
+
+insert_exp(ExpL, [C]) ->
+ [C] ++ ".0e" ++ ExpL;
+insert_exp(ExpL, [C | S]) ->
+ [C] ++ "." ++ S ++ "e" ++ ExpL.
+
+int_ceil(X) when is_float(X) ->
+ T = trunc(X),
+ case (X - T) of
+ Neg when Neg < 0 -> T;
+ Pos when Pos > 0 -> T + 1;
+ _ -> T
+ end.
+
+int_pow(X, 0) when is_integer(X) ->
+ 1;
+int_pow(X, N) when is_integer(X), is_integer(N), N > 0 ->
+ int_pow(X, N, 1).
+
+int_pow(X, N, R) when N < 2 ->
+ R * X;
+int_pow(X, N, R) ->
+ int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end).
+
+log2floor(Int) when is_integer(Int), Int > 0 ->
+ log2floor(Int, 0).
+
+log2floor(0, N) ->
+ N;
+log2floor(Int, N) ->
+ log2floor(Int bsr 1, 1 + N).
+
+%% fwrite_g(Float, Field, Adjust, Precision, PadChar)
+%% Use the f form if Float is >= 0.1 and < 1.0e4,
+%% and the prints correctly in the f form, else the e form.
+%% Precision always means the # of significant digits.
+
+fwrite_g(Fl, F, Adj, none, Pad) ->
+ fwrite_g(Fl, F, Adj, 6, Pad);
+fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 ->
+ A = abs(Fl),
+ E = if A < 1.0e-1 -> -2;
+ A < 1.0e0 -> -1;
+ A < 1.0e1 -> 0;
+ A < 1.0e2 -> 1;
+ A < 1.0e3 -> 2;
+ A < 1.0e4 -> 3;
+ true -> fwrite_f
+ end,
+ if P =< 1, E =:= -1;
+ P-1 > E, E >= -1 ->
+ fwrite_f(Fl, F, Adj, P-1-E, Pad);
+ P =< 1 ->
+ fwrite_e(Fl, F, Adj, 2, Pad);
+ true ->
+ fwrite_e(Fl, F, Adj, P, Pad)
+ end.
+
+
+%% iolist_to_chars(iolist()) -> deep_char_list()
+
+iolist_to_chars([C|Cs]) when is_integer(C), C >= $\000, C =< $\377 ->
+ [C | iolist_to_chars(Cs)];
+iolist_to_chars([I|Cs]) ->
+ [iolist_to_chars(I) | iolist_to_chars(Cs)];
+iolist_to_chars([]) ->
+ [];
+iolist_to_chars(B) when is_binary(B) ->
+ binary_to_list(B).
+
+%% string(String, Field, Adjust, Precision, PadChar)
+
+string(S, none, _Adj, none, _Pad) -> S;
+string(S, F, Adj, none, Pad) ->
+ N = lists:flatlength(S),
+ if N > F -> flat_trunc(S, F);
+ N =:= F -> S;
+ true -> adjust(S, chars(Pad, F-N), Adj)
+ end;
+string(S, none, _Adj, P, Pad) ->
+ N = lists:flatlength(S),
+ if N > P -> flat_trunc(S, P);
+ N =:= P -> S;
+ true -> [S|chars(Pad, P-N)]
+ end;
+string(S, F, Adj, F, Pad) ->
+ string(S, none, Adj, F, Pad);
+string(S, F, Adj, P, Pad) when F > P ->
+ N = lists:flatlength(S),
+ if N > F -> flat_trunc(S, F);
+ N =:= F -> S;
+ N > P -> adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
+ N =:= P -> adjust(S, chars(Pad, F-P), Adj);
+ true -> adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj)
+ end.
+
+%% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase)
+%% -> [Char].
+
+unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
+ when Base >= 2, Base =< 1+$Z-$A+10 ->
+ if Int < 0 ->
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-|S], F, Adj, none, Pad);
+ true ->
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term(S, F, Adj, none, Pad)
+ end.
+
+%% prefixed_integer(Int, Field, Adjust, Base, PadChar, Prefix, Lowercase)
+%% -> [Char].
+
+prefixed_integer(Int, F, Adj, Base, Pad, Prefix, Lowercase)
+ when Base >= 2, Base =< 1+$Z-$A+10 ->
+ if Int < 0 ->
+ S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
+ term([$-,Prefix|S], F, Adj, none, Pad);
+ true ->
+ S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
+ term([Prefix|S], F, Adj, none, Pad)
+ end.
+
+%% char(Char, Field, Adjust, Precision, PadChar) -> [Char].
+
+char(C, none, _Adj, none, _Pad) -> [C];
+char(C, F, _Adj, none, _Pad) -> chars(C, F);
+char(C, none, _Adj, P, _Pad) -> chars(C, P);
+char(C, F, Adj, P, Pad) when F >= P ->
+ adjust(chars(C, P), chars(Pad, F - P), Adj).
+
+%% newline(Field, Adjust, Precision, PadChar) -> [Char].
+
+newline(none, _Adj, _P, _Pad) -> "\n";
+newline(F, right, _P, _Pad) -> chars($\n, F).
+
+%%
+%% Utilities
+%%
+
+adjust(Data, [], _) -> Data;
+adjust(Data, Pad, left) -> [Data,Pad];
+adjust(Data, Pad, right) -> [Pad,Data].
+
+%% Flatten and truncate a deep list to at most N elements.
+
+flat_trunc(List, N) when is_integer(N), N >= 0 ->
+ flat_trunc(List, N, [], []).
+
+flat_trunc(L, 0, _, R) when is_list(L) ->
+ lists:reverse(R);
+flat_trunc([H|T], N, S, R) when is_list(H) ->
+ flat_trunc(H, N, [T|S], R);
+flat_trunc([H|T], N, S, R) ->
+ flat_trunc(T, N-1, S, [H|R]);
+flat_trunc([], N, [H|S], R) ->
+ flat_trunc(H, N, S, R);
+flat_trunc([], _, [], R) ->
+ lists:reverse(R).
+
+%% A deep version of string:chars/2,3
+
+chars(_C, 0) ->
+ [];
+chars(C, 1) ->
+ [C];
+chars(C, 2) ->
+ [C,C];
+chars(C, 3) ->
+ [C,C,C];
+chars(C, N) when is_integer(N), (N band 1) =:= 0 ->
+ S = chars(C, N bsr 1),
+ [S|S];
+chars(C, N) when is_integer(N) ->
+ S = chars(C, N bsr 1),
+ [C,S|S].
+
+%chars(C, N, Tail) ->
+% [chars(C, N)|Tail].
+
+%% Lowercase conversion
+
+cond_lowercase(String, true) ->
+ lowercase(String);
+cond_lowercase(String,false) ->
+ String.
+
+lowercase([H|T]) when is_integer(H), H >= $A, H =< $Z ->
+ [(H-$A+$a)|lowercase(T)];
+lowercase([H|T]) ->
+ [H|lowercase(T)];
+lowercase([]) ->
+ [].
diff --git a/lib/stdlib/src/io_lib_fread.erl b/lib/stdlib/src/io_lib_fread.erl
new file mode 100644
index 0000000000..74316dc730
--- /dev/null
+++ b/lib/stdlib/src/io_lib_fread.erl
@@ -0,0 +1,466 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(io_lib_fread).
+
+%% Formatted input functions of io library.
+
+-export([fread/2,fread/3]).
+
+-import(lists, [reverse/1,reverse/2]).
+
+%%-----------------------------------------------------------------------
+%% Local types
+%%-----------------------------------------------------------------------
+
+-type done_arg2() :: {'ok', io_lib:chars()} | 'eof' | {'error', term()}.
+
+%%-----------------------------------------------------------------------
+%% Types also used in other files
+%%-----------------------------------------------------------------------
+
+-type continuation() :: [] | {_, _, _, _}. % XXX: refine
+
+-type fread_2_ret() :: {'ok', io_lib:chars(), string()}
+ | {'more', string(), non_neg_integer(), io_lib:chars()}
+ | {'error', term()}.
+-type fread_3_ret() :: {'more', continuation()}
+ | {'done', done_arg2(), string()}.
+
+%%-----------------------------------------------------------------------
+
+%% fread(Continuation, CharList, FormatString)
+%% This is the main function into the re-entrant formatted reader. It
+%% repeatedly collects lines and calls fread/2 to format the input until
+%% all the format string has been used. And it counts the characters.
+
+-spec fread(io_lib_fread:continuation(), string(), string()) -> fread_3_ret().
+
+fread([], Chars, Format) ->
+ %%io:format("FREAD: ~w `~s'~n", [Format,Chars]),
+ fread_collect(Format, [], 0, [], Chars);
+fread({Format,Stack,N,Results}=_Continuation, Chars, _) ->
+ %%io:format("FREAD: ~w `~s'~n", [_Continuation,Chars]),
+ fread_collect(Format, Stack, N, Results, Chars).
+
+fread_collect(Format, [$\r|Stack], N, Results, [$\n|Chars]) ->
+ fread_line(Format, reverse(Stack), N, Results, Chars, [$\r,$\n]);
+fread_collect(Format, Stack, N, Results, [$\n|Chars]) ->
+ fread_line(Format, reverse(Stack), N, Results, Chars, [$\n]);
+fread_collect(Format, Stack, N, Results, []) ->
+ Continuation = {Format,Stack,N,Results},
+ {more,Continuation};
+fread_collect(Format, [$\r|Stack], N, Results, Chars) -> % Maybe eof
+ fread_line(Format, reverse(Stack), N, Results, Chars, [$\r]);
+fread_collect(Format, Stack, N, Results, [C|Chars]) ->
+ fread_collect(Format, [C|Stack], N, Results, Chars);
+fread_collect(Format, Stack, N, Results, Chars) -> % eof
+ fread_line(Format, reverse(Stack), N, Results, Chars, []).
+
+fread_line(Format0, Line, N0, Results0, More, Newline) ->
+ %%io:format("FREAD1: `~s' `~s'~n", [Format0,Line]),
+ Chars = if is_list(More) -> More; true -> [] end,
+ case fread(Format0, Line, N0, Results0) of
+ {ok,Results,[]} ->
+ {done,{ok,Results},Chars};
+ {ok,Results,Rest} ->
+ %% Don't lose the whitespace
+ {done,{ok,Results},Rest++(Newline++Chars)};
+ %% fread/4 should not return {more,...} on eof; guard just in case...
+ %% Count newline characters here since fread/4 does not get them.
+ {more,Format,N,Results} when is_list(Line), is_list(More) ->
+ fread_collect(Format, [], N+length(Newline), Results, More);
+ {more,Format,N,Results} when is_list(Line) -> % eof
+ fread_line(Format, eof, N+length(Newline), Results, More, []);
+ Other -> %An error has occurred
+ {done,Other,More}
+ end.
+
+
+%% Conventions
+%% ~s String White terminated
+%% ~d Integer terminated by ~[0-9]
+%% ~u Unsigned integer in base 2..36, no leading whitespace
+%% ~- Optional sign character, no leading whitespace
+%% ~f Float
+%% ~a as ~s but converted to an atom
+%% ~c characters without any stripping
+%% ~n number of characters scanned
+%% WHITE Skip white space
+%% X Literal X
+
+-spec fread(string(), string()) -> fread_2_ret().
+
+fread(Format, Line) ->
+ fread(Format, Line, 0, []).
+
+fread([$~|Format0], Line, N, Results) ->
+ {Format,F,Sup,Unicode} = fread_field(Format0),
+ fread1(Format, F, Sup, Unicode, Line, N, Results, Format0);
+fread([$\s|Format], Line, N, Results) ->
+ fread_skip_white(Format, Line, N, Results);
+fread([$\t|Format], Line, N, Results) ->
+ fread_skip_white(Format, Line, N, Results);
+fread([$\r|Format], Line, N, Results) ->
+ fread_skip_white(Format, Line, N, Results);
+fread([$\n|Format], Line, N, Results) ->
+ fread_skip_white(Format, Line, N, Results);
+fread([C|Format], [C|Line], N, Results) ->
+ fread(Format, Line, N+1, Results);
+fread([_F|_Format], [_C|_Line], _N, _Results) ->
+ fread_error(input);
+fread([], Line, _N, Results) ->
+ {ok,reverse(Results),Line}.
+
+fread_skip_white(Format, [$\s|Line], N, Results) ->
+ fread_skip_white(Format, Line, N+1, Results);
+fread_skip_white(Format, [$\t|Line], N, Results) ->
+ fread_skip_white(Format, Line, N+1, Results);
+fread_skip_white(Format, [$\r|Line], N, Results) ->
+ fread_skip_white(Format, Line, N+1, Results);
+fread_skip_white(Format, [$\n|Line], N, Results) ->
+ fread_skip_white(Format, Line, N+1, Results);
+fread_skip_white(Format, Line, N, Results) ->
+ fread(Format, Line, N, Results).
+
+%% fread_field(Format)
+%% Reads the field specification paramters. Returns:
+%%
+%% {RestFormat,FieldWidth,Suppress}
+
+fread_field([$*|Format]) -> fread_field(Format, true, false);
+fread_field(Format) -> fread_field(Format, false, false).
+
+fread_field([C|Format], Sup, Unic) when C >= $0, C =< $9 ->
+ fread_field(Format, C - $0, Sup, Unic);
+fread_field([$t|Format], Sup, _Unic) ->
+ {Format,none,Sup,true};
+fread_field(Format, Sup, Unic) ->
+ {Format,none,Sup,Unic}.
+
+fread_field([C|Format], F, Sup, Unic) when C >= $0, C =< $9 ->
+ fread_field(Format, 10*F + C - $0, Sup, Unic);
+fread_field([$t|Format], F, Sup, _Unic) ->
+ {Format,F,Sup,true};
+fread_field(Format, F, Sup, Unic) ->
+ {Format,F,Sup,Unic}.
+
+%% fread1(Format, FieldWidth, Suppress, Line, N, Results, AllFormat)
+%% fread1(Format, FieldWidth, Suppress, Line, N, Results)
+%% The main dispatch function for the formatting commands. Done in two
+%% stages so format commands that need no input can always be processed.
+
+fread1([$l|Format], _F, Sup, _U, Line, N, Res, _AllFormat) ->
+ fread(Format, Line, N, fread_result(Sup, N, Res));
+fread1(_Format, _F, _Sup, _U, [], N, Res, AllFormat) ->
+ %% Need more input here.
+ {more,[$~|AllFormat],N,Res};
+fread1(_Format, _F, _Sup, _U, eof, _N, [], _AllFormat) ->
+ %% This is at start of format string so no error.
+ eof;
+fread1(_Format, _F, _Sup, _U, eof, _N, _Res, _AllFormat) ->
+ %% This is an error as there is no more input.
+ fread_error(input);
+fread1(Format, F, Sup, U, Line, N, Res, _AllFormat) ->
+ fread1(Format, F, Sup, U, Line, N, Res).
+
+fread1([$f|Format], none, Sup, false, Line0, N0, Res) ->
+ {Line,N,Cs} = fread_float_cs(Line0, N0),
+ fread_float(Cs, Sup, Format, Line, N, Res);
+fread1([$f|Format], F, Sup, false, Line0, N, Res) ->
+ {Line,Cs} = fread_chars(Line0, F, false),
+ fread_float(Cs, Sup, Format, Line, N+F, Res);
+fread1([$d|Format], none, Sup, false, Line0, N0, Res) ->
+ {Line,N,Cs} = fread_int_cs(Line0, N0),
+ fread_integer(Cs, 10, Sup, Format, Line, N, Res);
+fread1([$d|Format], F, Sup, false, Line0, N, Res) ->
+ {Line,Cs} = fread_chars(Line0, F, false),
+ fread_integer(Cs, 10, Sup, Format, Line, N+F, Res);
+fread1([$u|Format], none, Sup, false, Line0, N0, Res) ->
+ {Line,N,Cs} = fread_digits(Line0, N0, 10, []),
+ fread_unsigned(Cs, 10, Sup, Format, Line, N, Res);
+fread1([$u|Format], F, Sup, false, Line0, N0, Res) when F >= 2, F =< 1+$Z-$A+10 ->
+ {Line,N,Cs} = fread_digits(Line0, N0, F, []),
+ fread_unsigned(Cs, F, Sup, Format, Line, N, Res);
+fread1([$-|Format], _F, Sup, false, Line, N, Res) ->
+ fread_sign_char(Sup, Format, Line, N, Res);
+fread1([$#|Format], none, Sup, false, Line0, N0, Res) ->
+ case catch
+ begin
+ {Line1,N1,B1} = fread_base(Line0, N0),
+ B = abs(B1),
+ true = (B >= 2) and (B =< 1+$Z-$A+10),
+ {Line2,N2,Cs2} = fread_digits(Line1, N1, B, []),
+ fread_based(reverse(Cs2), B1, Sup, Format, Line2, N2, Res)
+ end of
+ {'EXIT',_} ->
+ fread_error(based);
+ Other ->
+ Other
+ end;
+fread1([$#|Format], F, Sup, false, Line0, N, Res) ->
+ case catch
+ begin
+ {Line1,Cs1} = fread_chars(Line0, F, false),
+ {Line2,_,B2} = fread_base(reverse(Cs1), N),
+ true = ((B2 >= 2) and (B2 =< 1+$Z-$A+10)),
+ fread_based(Line2, B2, Sup, Format, Line1, N+F, Res)
+ end of
+ {'EXIT',_} ->
+ fread_error(based);
+ Other ->
+ Other
+ end;
+fread1([$s|Format], none, Sup, U, Line0, N0, Res) ->
+ {Line,N,Cs} = fread_string_cs(Line0, N0, U),
+ fread_string(Cs, Sup, U, Format, Line, N, Res);
+fread1([$s|Format], F, Sup, U, Line0, N, Res) ->
+ {Line,Cs} = fread_chars(Line0, F, U),
+ fread_string(Cs, Sup, U, Format, Line, N+F, Res);
+%% XXX:PaN Atoms still only latin1...
+fread1([$a|Format], none, Sup, false, Line0, N0, Res) ->
+ {Line,N,Cs} = fread_string_cs(Line0, N0, false),
+ fread_atom(Cs, Sup, Format, Line, N, Res);
+fread1([$a|Format], F, Sup, false, Line0, N, Res) ->
+ {Line,Cs} = fread_chars(Line0, F, false),
+ fread_atom(Cs, Sup, Format, Line, N+F, Res);
+fread1([$c|Format], none, Sup, U, Line0, N, Res) ->
+ {Line,Cs} = fread_chars(Line0, 1, U),
+ fread_chars(Cs, Sup, U, Format, Line, N+1, Res);
+fread1([$c|Format], F, Sup, U, Line0, N, Res) ->
+ {Line,Cs} = fread_chars(Line0, F, U),
+ fread_chars(Cs, Sup, U, Format, Line, N+F, Res);
+fread1([$~|Format], _F, _Sup, _U, [$~|Line], N, Res) ->
+ fread(Format, Line, N+1, Res);
+fread1(_Format, _F, _Sup, _U, _Line, _N, _Res) ->
+ fread_error(format).
+
+%% fread_float(FloatChars, Suppress, Format, Line, N, Results)
+
+fread_float(Cs, Sup, Format, Line, N, Res) ->
+ case catch list_to_float(fread_skip_white(reverse(Cs))) of
+ {'EXIT',_} ->
+ fread_error(float);
+ Float ->
+ fread(Format, Line, N, fread_result(Sup, Float, Res))
+ end.
+
+%% fread_integer(IntegerChars, Base, Suppress, Format, Line, N, Results)
+
+fread_integer(Cs, Base, Sup, Format, Line, N, Res) ->
+ case catch erlang:list_to_integer(fread_skip_white(reverse(Cs)), Base) of
+ {'EXIT',_} ->
+ fread_error(integer);
+ Integer ->
+ fread(Format, Line, N, fread_result(Sup, Integer, Res))
+ end.
+
+
+%% fread_unsigned(IntegerChars, Base, Suppress, Format, Line, N, Results)
+
+fread_unsigned(Cs, Base, Sup, Format, Line, N, Res) ->
+ case catch erlang:list_to_integer(fread_skip_white(reverse(Cs)), Base) of
+ {'EXIT',_} ->
+ fread_error(unsigned);
+ Integer ->
+ fread(Format, Line, N, fread_result(Sup, Integer, Res))
+ end.
+
+
+%% fread_based(IntegerChars, Base, Suppress, Format, Line, N, Results)
+
+fread_based(Cs0, B, Sup, Format, Line, N, Res) ->
+ {Cs,Base} = if B < 0 -> {[$-|Cs0],-B};
+ true -> {Cs0,B}
+ end,
+ I = erlang:list_to_integer(Cs, Base),
+ fread(Format, Line, N, fread_result(Sup, I, Res)).
+
+
+%% fread_sign_char(Suppress, Format, Line, N, Results)
+
+fread_sign_char(Sup, Format, [$-|Line], N, Res) ->
+ fread(Format, Line, N+1, fread_result(Sup, -1, Res));
+fread_sign_char(Sup, Format, [$+|Line], N, Res) ->
+ fread(Format, Line, N+1, fread_result(Sup, +1, Res));
+fread_sign_char(Sup, Format, Line, N, Res) ->
+ fread(Format, Line, N, fread_result(Sup, 1, Res)).
+
+
+%% fread_string(StringChars, Suppress, Format, Line, N, Results)
+
+fread_string(error, _Sup, _U, _Format, _Line, _N, _Res) ->
+ fread_error(string);
+fread_string(Cs0, Sup, U, Format, Line, N, Res) ->
+ Cs = fread_skip_white(reverse(fread_skip_white(Cs0))),
+ fread(Format, Line, N, fread_convert(fread_result(Sup, Cs, Res),U)).
+
+%% fread_atom(AtomChars, Suppress, Format, Line, N, Results)
+
+fread_atom(error, _Sup, _Format, _Line, _N, _Res) ->
+ fread_error(atom);
+fread_atom(Cs0, Sup, Format, Line, N, Res) ->
+ Cs = fread_skip_white(reverse(fread_skip_white(Cs0))),
+ fread(Format, Line, N, fread_result(Sup, list_to_atom(Cs), Res)).
+
+%% fread_chars(Characters, Suppress, Format, Line, N, Results)
+
+fread_chars(error, _Sup, _U, _Format, _Line, _N, _Res) ->
+ fread_error(character);
+fread_chars(Cs, Sup, U, Format, Line, N, Res) ->
+ fread(Format, Line, N, fread_convert(fread_result(Sup, reverse(Cs), Res),U)).
+
+%% fread_chars(Line, Count)
+
+fread_chars(Line, C, U) ->
+ fread_chars(C, Line, U, []).
+
+fread_chars(0, Line, _U, Cs) -> {Line,Cs};
+fread_chars(_N, [$\n|Line], _U, _Cs) -> {[$\n|Line],error};
+fread_chars(N, [C|Line], true, Cs) ->
+ fread_chars(N-1, Line, true, [C|Cs]);
+fread_chars(N, [C|Line], false, Cs) when C >= 0, C =< 255 ->
+ fread_chars(N-1, Line, false, [C|Cs]);
+fread_chars(_N, L, _U, _Cs) ->
+ {L,error}.
+%%fread_chars(_N, [], _U,_Cs) ->
+%% {[],error}.
+
+%% fread_int_cs(Line, N)
+
+fread_int_cs(Line0, N0) ->
+ {Line1,N1} = fread_skip_white(Line0, N0),
+ {Line,N,Cs} = fread_sign(Line1, N1, []),
+ fread_digits(Line, N, Cs).
+
+%% fread_float_cs(Line, N)
+%% A float is "[+|-][0-9]+.[0-9]+[[E|e][+|-][09-]+]
+
+fread_float_cs(Line0, N0) ->
+ {Line1,N1} = fread_skip_white(Line0, N0),
+ {Line2,N2,Cs2} = fread_sign(Line1, N1, []),
+ {Line,N,Cs} = fread_digits(Line2, N2, Cs2),
+ fread_float_cs_1(Line, N, Cs).
+
+fread_float_cs_1([$.|Line0], N0, Cs0) ->
+ {Line,N,Cs} = fread_digits(Line0, N0+1, [$.|Cs0]),
+ fread_float_cs_2(Line, N, Cs);
+fread_float_cs_1(Line, N, Cs) ->
+ {Line,N,Cs}.
+
+fread_float_cs_2([$e|Line0], N0, Cs0) ->
+ {Line,N,Cs} = fread_sign(Line0, N0+1, [$e|Cs0]),
+ fread_digits(Line, N, Cs);
+fread_float_cs_2([$E|Line0], N0, Cs0) ->
+ {Line,N,Cs} = fread_sign(Line0, N0+1, [$E|Cs0]),
+ fread_digits(Line, N, Cs);
+fread_float_cs_2(Line, N, Cs) ->
+ {Line,N,Cs}.
+
+%% fread_string_cs(Line, N, Unicode)
+
+fread_string_cs(Line0, N0, false) ->
+ {Line,N} = fread_skip_white(Line0, N0),
+ fread_skip_latin1_nonwhite(Line, N, []);
+fread_string_cs(Line0, N0, true) ->
+ {Line,N} = fread_skip_white(Line0, N0),
+ fread_skip_nonwhite(Line, N, []).
+
+%% fread_skip_white(Line)
+%% fread_skip_white(Line, N)
+%% fread_skip_nonwhite(Line, N, Characters)
+%% fread_sign(Line, N, Characters)
+%% fread_digits(Line, N, Characters)
+%% fread_digits(Line, N, Base, Characters)
+%% Read segments of things, return "thing" characters in reverse order.
+
+fread_skip_white([$\s|Line]) -> fread_skip_white(Line);
+fread_skip_white([$\t|Line]) -> fread_skip_white(Line);
+fread_skip_white([$\r|Line]) -> fread_skip_white(Line);
+fread_skip_white([$\n|Line]) -> fread_skip_white(Line);
+fread_skip_white(Line) -> Line.
+
+fread_skip_white([$\s|Line], N) ->
+ fread_skip_white(Line, N+1);
+fread_skip_white([$\t|Line], N) ->
+ fread_skip_white(Line, N+1);
+fread_skip_white([$\r|Line], N) ->
+ fread_skip_white(Line, N+1);
+fread_skip_white([$\n|Line], N) ->
+ fread_skip_white(Line, N+1);
+fread_skip_white(Line, N) -> {Line,N}.
+
+fread_skip_latin1_nonwhite([$\s|Line], N, Cs) -> {[$\s|Line],N,Cs};
+fread_skip_latin1_nonwhite([$\t|Line], N, Cs) -> {[$\t|Line],N,Cs};
+fread_skip_latin1_nonwhite([$\r|Line], N, Cs) -> {[$\r|Line],N,Cs};
+fread_skip_latin1_nonwhite([$\n|Line], N, Cs) -> {[$\n|Line],N,Cs};
+fread_skip_latin1_nonwhite([C|Line], N, []) when C > 255 ->
+ {[C|Line],N,error};
+fread_skip_latin1_nonwhite([C|Line], N, Cs) when C > 255 ->
+ {[C|Line],N,Cs};
+fread_skip_latin1_nonwhite([C|Line], N, Cs) ->
+ fread_skip_latin1_nonwhite(Line, N+1, [C|Cs]);
+fread_skip_latin1_nonwhite([], N, Cs) -> {[],N,Cs}.
+
+fread_skip_nonwhite([$\s|Line], N, Cs) -> {[$\s|Line],N,Cs};
+fread_skip_nonwhite([$\t|Line], N, Cs) -> {[$\t|Line],N,Cs};
+fread_skip_nonwhite([$\r|Line], N, Cs) -> {[$\r|Line],N,Cs};
+fread_skip_nonwhite([$\n|Line], N, Cs) -> {[$\n|Line],N,Cs};
+fread_skip_nonwhite([C|Line], N, Cs) ->
+ fread_skip_nonwhite(Line, N+1, [C|Cs]);
+fread_skip_nonwhite([], N, Cs) -> {[],N,Cs}.
+
+fread_sign([$+|Line], N, Cs) -> {Line,N+1,[$+|Cs]};
+fread_sign([$-|Line], N, Cs) -> {Line,N+1,[$-|Cs]};
+fread_sign(Line, N, Cs) -> {Line,N,Cs}.
+
+fread_base(Line0, N0) ->
+ {[$#|Line1],N1,Cs1} = fread_int_cs(Line0, N0),
+ B = list_to_integer(reverse(Cs1)),
+ {Line1,N1+1,B}.
+
+fread_digits([C|Line], N, Cs) when C >= $0, C =< $9 ->
+ fread_digits(Line, N+1, [C|Cs]);
+fread_digits(Line, N, Cs) -> {Line,N,Cs}.
+
+fread_digits([C|Line], N, Base, Cs) when C >= $0, C =< $9 ->
+ fread_digits(Line, N+1, Base, [C|Cs]);
+fread_digits([C|Line], N, Base, Cs) when C >= $A, C < $A+Base-10 ->
+ fread_digits(Line, N+1, Base, [C|Cs]);
+fread_digits([C|Line], N, Base, Cs) when C >= $a, C < $a+Base-10 ->
+ fread_digits(Line, N+1, Base, [C|Cs]);
+fread_digits(Line, N, _Base, Cs) -> {Line,N,Cs}.
+
+
+
+%% fread_result(Suppress, Value, Results)
+
+fread_result(true, _V, Res) -> Res;
+fread_result(false, V, Res) -> [V|Res].
+
+-ifdef(UNICODE_AS_BINARIES).
+fread_convert([L|R],true) when is_list(L) ->
+ [unicode:characters_to_binary(L) | R];
+fread_convert(Any,_) ->
+ Any.
+-else.
+fread_convert(Any,_) ->
+ Any.
+-endif.
+fread_error(In) ->
+ {error,{fread,In}}.
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
new file mode 100644
index 0000000000..169410796b
--- /dev/null
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -0,0 +1,646 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(io_lib_pretty).
+
+%%% Pretty printing Erlang terms
+%%%
+%%% In this module "print" means the formatted printing while "write"
+%%% means just writing out onto one line.
+
+-export([print/1,print/2,print/3,print/4,print/5,print/6]).
+
+%%%
+%%% Exported functions
+%%%
+
+%% print(Term) -> [Chars]
+%% print(Term, Column, LineLength, Depth) -> [Chars]
+%% Depth = -1 gives unlimited print depth. Use io_lib:write for atomic terms.
+
+print(Term) ->
+ print(Term, 1, 80, -1).
+
+%% print(Term, RecDefFun) -> [Chars]
+%% print(Term, Depth, RecDefFun) -> [Chars]
+%% RecDefFun = fun(Tag, NoFields) -> [FieldTag] | no
+%% Used by the shell for printing records.
+print(Term, RecDefFun) ->
+ print(Term, -1, RecDefFun).
+
+print(Term, Depth, RecDefFun) ->
+ print(Term, 1, 80, Depth, RecDefFun).
+
+print(Term, Col, Ll, D) ->
+ print(Term, Col, Ll, D, _M=-1, no_fun).
+
+print(Term, Col, Ll, D, RecDefFun) ->
+ print(Term, Col, Ll, D, _M=-1, RecDefFun).
+
+print(_, _, _, 0, _M, _RF) -> "...";
+print(Term, Col, Ll, D, M, RecDefFun) when Col =< 0 ->
+ print(Term, 1, Ll, D, M, RecDefFun);
+print(Term, Col, Ll, D, M0, RecDefFun) when is_tuple(Term);
+ is_list(Term) ->
+ If = {_S, Len} = print_length(Term, D, RecDefFun),
+ M = max_cs(M0, Len),
+ if
+ Len < Ll - Col, Len =< M ->
+ write(If);
+ true ->
+ TInd = while_fail([-1, 4],
+ fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,
+ 1),
+ pp(If, Col, Ll, M, TInd, indent(Col), 0, 0)
+ end;
+print(<<_/bitstring>>=Term, Col, Ll, D, M0, RecDefFun) ->
+ If = {_S, Len} = print_length(Term, D, RecDefFun),
+ M = max_cs(M0, Len),
+ if
+ Len < Ll - Col, Len =< M ->
+ write(If);
+ true ->
+ TInd = while_fail([-1, 4],
+ fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,
+ 1),
+ pp(If, Col, Ll, M, TInd, indent(Col), 0, 0)
+ end;
+print(Term, _Col, _Ll, _D, _M, _RF) ->
+ io_lib:write(Term).
+
+%%%
+%%% Local functions
+%%%
+
+max_cs(M, Len) when M < 0 ->
+ Len;
+max_cs(M, _Len) ->
+ M.
+
+-define(ATM(T), is_list(element(1, T))).
+-define(ATM_FLD(Field), ?ATM(element(4, element(1, Field)))).
+
+pp({_S, Len} = If, Col, Ll, M, _TInd, _Ind, LD, W)
+ when Len < Ll - Col - LD, Len + W + LD =< M ->
+ write(If);
+pp({{list,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) ->
+ [$[, pp_list(L, Col + 1, Ll, M, TInd, indent(1, Ind), LD, $|, W + 1), $]];
+pp({{tuple,true,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) ->
+ [${, pp_tag_tuple(L, Col, Ll, M, TInd, Ind, LD, W + 1), $}];
+pp({{tuple,false,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) ->
+ [${, pp_list(L, Col + 1, Ll, M, TInd, indent(1, Ind), LD, $,, W + 1), $}];
+pp({{record,[{Name,NLen} | L]}, _Len}, Col, Ll, M, TInd, Ind, LD, W) ->
+ [Name, ${, pp_record(L, NLen, Col, Ll, M, TInd, Ind, LD, W + NLen+1), $}];
+pp({{bin,S}, _Len}, Col, Ll, M, _TInd, Ind, LD, W) ->
+ pp_binary(S, Col + 2, Ll, M, indent(2, Ind), LD, W);
+pp({S, _Len}, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
+ S.
+
+%% Print a tagged tuple by indenting the rest of the elements
+%% differently to the tag. Tuple has size >= 2.
+pp_tag_tuple([{Tag,Tlen} | L], Col, Ll, M, TInd, Ind, LD, W) ->
+ TagInd = Tlen + 2,
+ Tcol = Col + TagInd,
+ S = $,,
+ if
+ TInd > 0, TagInd > TInd ->
+ Col1 = Col + TInd,
+ Indent = indent(TInd, Ind),
+ [Tag|pp_tail(L, Col1, Tcol, Ll, M, TInd, Indent, LD, S, W+Tlen)];
+ true ->
+ Indent = indent(TagInd, Ind),
+ [Tag, S | pp_list(L, Tcol, Ll, M, TInd, Indent, LD, S, W+Tlen+1)]
+ end.
+
+pp_record([], _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
+ "";
+pp_record({dots, _}, _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
+ "...";
+pp_record([F | Fs], Nlen, Col0, Ll, M, TInd, Ind0, LD, W0) ->
+ Nind = Nlen + 1,
+ {Col, Ind, S, W} = rec_indent(Nind, TInd, Col0, Ind0, W0),
+ {FS, FW} = pp_field(F, Col, Ll, M, TInd, Ind, last_depth(Fs, LD), W),
+ [S, FS | pp_fields_tail(Fs, Col, Col + FW, Ll, M, TInd, Ind, LD, W + FW)].
+
+pp_fields_tail([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
+ "";
+pp_fields_tail({dots, _}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _LD, _W) ->
+ ",...";
+pp_fields_tail([{_, Len}=F | Fs], Col0, Col, Ll, M, TInd, Ind, LD, W) ->
+ LD1 = last_depth(Fs, LD),
+ ELen = 1 + Len,
+ if
+ LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_FLD(F);
+ LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_FLD(F) ->
+ [$,, write_field(F) |
+ pp_fields_tail(Fs, Col0, Col+ELen, Ll, M, TInd, Ind, LD, W+ELen)];
+ true ->
+ {FS, FW} = pp_field(F, Col0, Ll, M, TInd, Ind, LD1, 0),
+ [$,, $\n, Ind, FS |
+ pp_fields_tail(Fs, Col0, Col0 + FW, Ll, M, TInd, Ind, LD, FW)]
+ end.
+
+pp_field({_, Len}=Fl, Col, Ll, M, _TInd, _Ind, LD, W)
+ when Len < Ll - Col - LD, Len + W + LD =< M ->
+ {write_field(Fl), if
+ ?ATM_FLD(Fl) ->
+ Len;
+ true ->
+ Ll % force nl
+ end};
+pp_field({{field, Name, NameL, F}, _Len}, Col0, Ll, M, TInd, Ind0, LD, W0) ->
+ {Col, Ind, S, W} = rec_indent(NameL, TInd, Col0, Ind0, W0 + NameL),
+ {[Name, " = ", S | pp(F, Col, Ll, M, TInd, Ind, LD, W)], Ll}. % force nl
+
+rec_indent(RInd, TInd, Col0, Ind0, W0) ->
+ Nl = (TInd > 0) and (RInd > TInd),
+ DCol = case Nl of
+ true -> TInd;
+ false -> RInd
+ end,
+ Col = Col0 + DCol,
+ Ind = indent(DCol, Ind0),
+ S = case Nl of
+ true -> [$\n | Ind];
+ false -> ""
+ end,
+ W = case Nl of
+ true -> 0;
+ false -> W0
+ end,
+ {Col, Ind, S, W}.
+
+pp_list({dots, _}, _Col0, _Ll, _M, _TInd, _Ind, _LD, _S, _W) ->
+ "...";
+pp_list([E | Es], Col0, Ll, M, TInd, Ind, LD, S, W) ->
+ {ES, WE} = pp_element(E, Col0, Ll, M, TInd, Ind, last_depth(Es, LD), W),
+ [ES | pp_tail(Es, Col0, Col0 + WE, Ll, M, TInd, Ind, LD, S, W + WE)].
+
+pp_tail([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _S, _W) ->
+ "";
+pp_tail([{_, Len}=E | Es], Col0, Col, Ll, M, TInd, Ind, LD, S, W) ->
+ LD1 = last_depth(Es, LD),
+ ELen = 1 + Len,
+ if
+ LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM(E);
+ LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM(E) ->
+ [$,, write(E) |
+ pp_tail(Es, Col0, Col + ELen, Ll, M, TInd, Ind, LD, S, W+ELen)];
+ true ->
+ {ES, WE} = pp_element(E, Col0, Ll, M, TInd, Ind, LD1, 0),
+ [$,, $\n, Ind, ES |
+ pp_tail(Es, Col0, Col0 + WE, Ll, M, TInd, Ind, LD, S, WE)]
+ end;
+pp_tail({dots, _}, _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, S, _W) ->
+ [S | "..."];
+pp_tail({_, Len}=E, _Col0, Col, Ll, M, _TInd, _Ind, LD, S, W)
+ when Len + 1 < Ll - Col - (LD + 1),
+ Len + 1 + W + (LD + 1) =< M,
+ ?ATM(E) ->
+ [S | write(E)];
+pp_tail(E, Col0, _Col, Ll, M, TInd, Ind, LD, S, _W) ->
+ [S, $\n, Ind | pp(E, Col0, Ll, M, TInd, Ind, LD + 1, 0)].
+
+pp_element({_, Len}=E, Col, Ll, M, _TInd, _Ind, LD, W)
+ when Len < Ll - Col - LD, Len + W + LD =< M, ?ATM(E) ->
+ {write(E), Len};
+pp_element(E, Col, Ll, M, TInd, Ind, LD, W) ->
+ {pp(E, Col, Ll, M, TInd, Ind, LD, W), Ll}. % force nl
+
+%% Reuse the list created by io_lib:write_binary()...
+pp_binary([LT,LT,S,GT,GT], Col, Ll, M, Ind, LD, W) ->
+ N = erlang:max(8, erlang:min(Ll - Col, M - 4 - W) - LD),
+ [LT,LT,pp_binary(S, N, N, Ind),GT,GT].
+
+pp_binary([BS, $, | S], N, N0, Ind) ->
+ Len = length(BS) + 1,
+ case N - Len of
+ N1 when N1 < 0 ->
+ [$\n, Ind, BS, $, | pp_binary(S, N0 - Len, N0, Ind)];
+ N1 ->
+ [BS, $, | pp_binary(S, N1, N0, Ind)]
+ end;
+pp_binary([BS1, $:, BS2]=S, N, _N0, Ind)
+ when length(BS1) + length(BS2) + 1 > N ->
+ [$\n, Ind, S];
+pp_binary(S, N, _N0, Ind) ->
+ case iolist_size(S) > N of
+ true ->
+ [$\n, Ind, S];
+ false ->
+ S
+ end.
+
+write({{tuple, _IsTagged, L}, _}) ->
+ [${, write_list(L, $,), $}];
+write({{list, L}, _}) ->
+ [$[, write_list(L, $|), $]];
+write({{record, [{Name,_} | L]}, _}) ->
+ [Name, ${, write_fields(L), $}];
+write({{bin, S}, _}) ->
+ S;
+write({S, _}) ->
+ S.
+
+write_fields([]) ->
+ "";
+write_fields({dots, _}) ->
+ "...";
+write_fields([F | Fs]) ->
+ [write_field(F) | write_fields_tail(Fs)].
+
+write_fields_tail([]) ->
+ "";
+write_fields_tail({dots, _}) ->
+ ",...";
+write_fields_tail([F | Fs]) ->
+ [$,, write_field(F) | write_fields_tail(Fs)].
+
+write_field({{field, Name, _NameL, F}, _}) ->
+ [Name, " = " | write(F)].
+
+write_list({dots, _}, _S) ->
+ "...";
+write_list([E | Es], S) ->
+ [write(E) | write_tail(Es, S)].
+
+write_tail([], _S) ->
+ [];
+write_tail([E | Es], S) ->
+ [$,, write(E) | write_tail(Es, S)];
+write_tail({dots, _}, S) ->
+ [S | "..."];
+write_tail(E, S) ->
+ [S | write(E)].
+
+%% The depth (D) is used for extracting and counting the characters to
+%% print. The structure is kept so that the returned intermediate
+%% format can be formatted. The separators (list, tuple, record) are
+%% counted but need to be added later.
+
+%% D =/= 0
+print_length([], _D, _RF) ->
+ {"[]", 2};
+print_length({}, _D, _RF) ->
+ {"{}", 2};
+print_length(List, D, RF) when is_list(List) ->
+ case printable_list(List, D) of
+ true ->
+ S = io_lib:write_string(List, $"), %"
+ {S, length(S)};
+ %% Truncated lists could break some existing code.
+ % {true, Prefix} ->
+ % S = io_lib:write_string(Prefix, $"), %"
+ % {[S | "..."], 3 + length(S)};
+ false ->
+ print_length_list(List, D, RF)
+ end;
+print_length(Fun, _D, _RF) when is_function(Fun) ->
+ S = io_lib:write(Fun),
+ {S, iolist_size(S)};
+print_length(R, D, RF) when is_atom(element(1, R)),
+ is_function(RF) ->
+ case RF(element(1, R), tuple_size(R) - 1) of
+ no ->
+ print_length_tuple(R, D, RF);
+ RDefs ->
+ print_length_record(R, D, RF, RDefs)
+ end;
+print_length(Tuple, D, RF) when is_tuple(Tuple) ->
+ print_length_tuple(Tuple, D, RF);
+print_length(<<>>, _D, _RF) ->
+ {"<<>>", 4};
+print_length(<<_/bitstring>>, 1, _RF) ->
+ {"<<...>>", 7};
+print_length(<<_/bitstring>>=Bin, D, _RF) ->
+ case bit_size(Bin) rem 8 of
+ 0 ->
+ D1 = D - 1,
+ case printable_bin(Bin, D1) of
+ List when is_list(List) ->
+ S = io_lib:write_string(List, $"),
+ {[$<,$<,S,$>,$>], 4 + length(S)};
+ {true, Prefix} ->
+ S = io_lib:write_string(Prefix, $"),
+ {[$<,$<, S | "...>>"], 4 + length(S)};
+ false ->
+ S = io_lib:write(Bin, D),
+ {{bin,S}, iolist_size(S)}
+ end;
+ _ ->
+ S = io_lib:write(Bin, D),
+ {{bin,S}, iolist_size(S)}
+ end;
+print_length(Term, _D, _RF) ->
+ S = io_lib:write(Term),
+ {S, iolist_size(S)}.
+
+print_length_tuple(_Tuple, 1, _RF) ->
+ {"{...}", 5};
+print_length_tuple(Tuple, D, RF) ->
+ L = print_length_list1(tuple_to_list(Tuple), D, RF),
+ IsTagged = is_atom(element(1, Tuple)) and (tuple_size(Tuple) > 1),
+ {{tuple,IsTagged,L}, list_length(L, 2)}.
+
+print_length_record(_Tuple, 1, _RF, _RDefs) ->
+ {"{...}", 5};
+print_length_record(Tuple, D, RF, RDefs) ->
+ Name = [$# | io_lib:write_atom(element(1, Tuple))],
+ NameL = length(Name),
+ L = print_length_fields(RDefs, D - 1, tl(tuple_to_list(Tuple)), RF),
+ {{record, [{Name,NameL} | L]}, list_length(L, NameL + 2)}.
+
+print_length_fields([], _D, [], _RF) ->
+ [];
+print_length_fields(_, 1, _, _RF) ->
+ {dots, 3};
+print_length_fields([Def | Defs], D, [E | Es], RF) ->
+ [print_length_field(Def, D - 1, E, RF) |
+ print_length_fields(Defs, D - 1, Es, RF)].
+
+print_length_field(Def, D, E, RF) ->
+ Name = io_lib:write_atom(Def),
+ {S, L} = print_length(E, D, RF),
+ NameL = length(Name) + 3,
+ {{field, Name, NameL, {S, L}}, NameL + L}.
+
+print_length_list(List, D, RF) ->
+ L = print_length_list1(List, D, RF),
+ {{list, L}, list_length(L, 2)}.
+
+print_length_list1([], _D, _RF) ->
+ [];
+print_length_list1(_, 1, _RF) ->
+ {dots, 3};
+print_length_list1([E | Es], D, RF) ->
+ [print_length(E, D - 1, RF) | print_length_list1(Es, D - 1, RF)];
+print_length_list1(E, D, RF) ->
+ print_length(E, D - 1, RF).
+
+list_length([], Acc) ->
+ Acc;
+list_length([{_, Len} | Es], Acc) ->
+ list_length_tail(Es, Acc + Len);
+list_length({_, Len}, Acc) ->
+ Acc + Len.
+
+list_length_tail([], Acc) ->
+ Acc;
+list_length_tail([{_,Len} | Es], Acc) ->
+ list_length_tail(Es, Acc + 1 + Len);
+list_length_tail({_, Len}, Acc) ->
+ Acc + 1 + Len.
+
+%% ?CHARS printable characters has depth 1.
+-define(CHARS, 4).
+
+printable_list(L, D) when D < 0 ->
+ io_lib:printable_list(L);
+printable_list(_L, 1) ->
+ false;
+printable_list(L, _D) ->
+ io_lib:printable_list(L).
+%% Truncated lists could break some existing code.
+% printable_list(L, D) ->
+% Len = ?CHARS * (D - 1),
+% case printable_list1(L, Len) of
+% all ->
+% true;
+% N when is_integer(N), Len - N >= D - 1 ->
+% {L1, _} = lists:split(Len - N, L),
+% {true, L1};
+% N when is_integer(N) ->
+% false
+% end.
+
+printable_bin(Bin, D) when D >= 0, ?CHARS * D =< byte_size(Bin) ->
+ printable_bin(Bin, erlang:min(?CHARS * D, byte_size(Bin)), D);
+printable_bin(Bin, D) ->
+ printable_bin(Bin, byte_size(Bin), D).
+
+printable_bin(Bin, Len, D) ->
+ N = erlang:min(20, Len),
+ L = binary_to_list(Bin, 1, N),
+ case printable_list1(L, N) of
+ all when N =:= byte_size(Bin) ->
+ L;
+ all when N =:= Len -> % N < byte_size(Bin)
+ {true, L};
+ all ->
+ case printable_bin1(Bin, 1 + N, Len - N) of
+ 0 when byte_size(Bin) =:= Len ->
+ binary_to_list(Bin);
+ NC when D > 0, Len - NC >= D ->
+ {true, binary_to_list(Bin, 1, Len - NC)};
+ NC when is_integer(NC) ->
+ false
+ end;
+ NC when is_integer(NC), D > 0, N - NC >= D ->
+ {true, binary_to_list(Bin, 1, N - NC)};
+ NC when is_integer(NC) ->
+ false
+ end.
+
+printable_bin1(_Bin, _Start, 0) ->
+ 0;
+printable_bin1(Bin, Start, Len) ->
+ N = erlang:min(10000, Len),
+ L = binary_to_list(Bin, Start, Start + N - 1),
+ case printable_list1(L, N) of
+ all ->
+ printable_bin1(Bin, Start + N, Len - N);
+ NC when is_integer(NC) ->
+ Len - (N - NC)
+ end.
+
+%% -> all | integer() >=0. Adopted from io_lib.erl.
+% printable_list1([_ | _], 0) -> 0;
+printable_list1([C | Cs], N) when is_integer(C), C >= $\s, C =< $~ ->
+ printable_list1(Cs, N - 1);
+printable_list1([C | Cs], N) when is_integer(C), C >= $\240, C =< $\377 ->
+ printable_list1(Cs, N - 1);
+printable_list1([$\n | Cs], N) -> printable_list1(Cs, N - 1);
+printable_list1([$\r | Cs], N) -> printable_list1(Cs, N - 1);
+printable_list1([$\t | Cs], N) -> printable_list1(Cs, N - 1);
+printable_list1([$\v | Cs], N) -> printable_list1(Cs, N - 1);
+printable_list1([$\b | Cs], N) -> printable_list1(Cs, N - 1);
+printable_list1([$\f | Cs], N) -> printable_list1(Cs, N - 1);
+printable_list1([$\e | Cs], N) -> printable_list1(Cs, N - 1);
+printable_list1([], _) -> all;
+printable_list1(_, N) -> N.
+
+%% Throw 'no_good' if the indentation exceeds half the line length
+%% unless there is room for M characters on the line.
+
+cind({_S, Len}, Col, Ll, M, Ind, LD, W) when Len < Ll - Col - LD,
+ Len + W + LD =< M ->
+ Ind;
+cind({{list,L}, _Len}, Col, Ll, M, Ind, LD, W) ->
+ cind_list(L, Col + 1, Ll, M, Ind, LD, W + 1);
+cind({{tuple,true,L}, _Len}, Col, Ll, M, Ind, LD, W) ->
+ cind_tag_tuple(L, Col, Ll, M, Ind, LD, W + 1);
+cind({{tuple,false,L}, _Len}, Col, Ll, M, Ind, LD, W) ->
+ cind_list(L, Col + 1, Ll, M, Ind, LD, W + 1);
+cind({{record,[{_Name,NLen} | L]}, _Len}, Col, Ll, M, Ind, LD, W) ->
+ cind_record(L, NLen, Col, Ll, M, Ind, LD, W + NLen + 1);
+cind({{bin,_S}, _Len}, _Col, _Ll, _M, Ind, _LD, _W) ->
+ Ind;
+cind({_S, _Len}, _Col, _Ll, _M, Ind, _LD, _W) ->
+ Ind.
+
+cind_tag_tuple([{_Tag,Tlen} | L], Col, Ll, M, Ind, LD, W) ->
+ TagInd = Tlen + 2,
+ Tcol = Col + TagInd,
+ if
+ Ind > 0, TagInd > Ind ->
+ Col1 = Col + Ind,
+ if
+ M + Col1 =< Ll; Col1 =< Ll div 2 ->
+ cind_tail(L, Col1, Tcol, Ll, M, Ind, LD, W + Tlen);
+ true ->
+ throw(no_good)
+ end;
+ M + Tcol < Ll; Tcol < Ll div 2 ->
+ cind_list(L, Tcol, Ll, M, Ind, LD, W + Tlen + 1);
+ true ->
+ throw(no_good)
+ end.
+
+cind_record([F | Fs], Nlen, Col0, Ll, M, Ind, LD, W0) ->
+ Nind = Nlen + 1,
+ {Col, W} = cind_rec(Nind, Col0, Ll, M, Ind, W0),
+ FW = cind_field(F, Col, Ll, M, Ind, last_depth(Fs, LD), W),
+ cind_fields_tail(Fs, Col, Col + FW, Ll, M, Ind, LD, W + FW);
+cind_record(_, _Nlen, _Col, _Ll, _M, Ind, _LD, _W) ->
+ Ind.
+
+cind_fields_tail([{_, Len}=F | Fs], Col0, Col, Ll, M, Ind, LD, W) ->
+ LD1 = last_depth(Fs, LD),
+ ELen = 1 + Len,
+ if
+ LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_FLD(F);
+ LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_FLD(F) ->
+ cind_fields_tail(Fs, Col0, Col + ELen, Ll, M, Ind, LD, W + ELen);
+ true ->
+ FW = cind_field(F, Col0, Ll, M, Ind, LD1, 0),
+ cind_fields_tail(Fs, Col0, Col + FW, Ll, M, Ind, LD, FW)
+ end;
+cind_fields_tail(_, _Col0, _Col, _Ll, _M, Ind, _LD, _W) ->
+ Ind.
+
+cind_field({{field, _N, _NL, _F}, Len}=Fl, Col, Ll, M, _Ind, LD, W)
+ when Len < Ll - Col - LD, Len + W + LD =< M ->
+ if
+ ?ATM_FLD(Fl) ->
+ Len;
+ true ->
+ Ll
+ end;
+cind_field({{field, _Name, NameL, F}, _Len}, Col0, Ll, M, Ind, LD, W0) ->
+ {Col, W} = cind_rec(NameL, Col0, Ll, M, Ind, W0 + NameL),
+ cind(F, Col, Ll, M, Ind, LD, W),
+ Ll.
+
+cind_rec(RInd, Col0, Ll, M, Ind, W0) ->
+ Nl = (Ind > 0) and (RInd > Ind),
+ DCol = case Nl of
+ true -> Ind;
+ false -> RInd
+ end,
+ Col = Col0 + DCol,
+ if
+ M + Col =< Ll; Col =< Ll div 2 ->
+ W = case Nl of
+ true -> 0;
+ false -> W0
+ end,
+ {Col, W};
+ true ->
+ throw(no_good)
+ end.
+
+cind_list({dots, _}, _Col0, _Ll, _M, Ind, _LD, _W) ->
+ Ind;
+cind_list([E | Es], Col0, Ll, M, Ind, LD, W) ->
+ WE = cind_element(E, Col0, Ll, M, Ind, last_depth(Es, LD), W),
+ cind_tail(Es, Col0, Col0 + WE, Ll, M, Ind, LD, W + WE).
+
+cind_tail([], _Col0, _Col, _Ll, _M, Ind, _LD, _W) ->
+ Ind;
+cind_tail([{_, Len}=E | Es], Col0, Col, Ll, M, Ind, LD, W) ->
+ LD1 = last_depth(Es, LD),
+ ELen = 1 + Len,
+ if
+ LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM(E);
+ LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM(E) ->
+ cind_tail(Es, Col0, Col + ELen, Ll, M, Ind, LD, W + ELen);
+ true ->
+ WE = cind_element(E, Col0, Ll, M, Ind, LD1, 0),
+ cind_tail(Es, Col0, Col0 + WE, Ll, M, Ind, LD, WE)
+ end;
+cind_tail({dots, _}, _Col0, _Col, _Ll, _M, Ind, _LD, _W) ->
+ Ind;
+cind_tail({_, Len}=E, _Col0, Col, Ll, M, Ind, LD, W)
+ when Len + 1 < Ll - Col - (LD + 1),
+ Len + 1 + W + (LD + 1) =< M,
+ ?ATM(E) ->
+ Ind;
+cind_tail(E, _Col0, Col, Ll, M, Ind, LD, _W) ->
+ cind(E, Col, Ll, M, Ind, LD + 1, 0).
+
+cind_element({_, Len}=E, Col, Ll, M, _Ind, LD, W)
+ when Len < Ll - Col - LD, Len + W + LD =< M, ?ATM(E) ->
+ Len;
+cind_element(E, Col, Ll, M, Ind, LD, W) ->
+ cind(E, Col, Ll, M, Ind, LD, W),
+ Ll.
+
+last_depth([_ | _], _LD) ->
+ 0;
+last_depth(_, LD) ->
+ LD + 1.
+
+while_fail([], _F, V) ->
+ V;
+while_fail([A | As], F, V) ->
+ try F(A) catch _ -> while_fail(As, F, V) end.
+
+indent(N) when is_integer(N), N > 0 ->
+ chars($\s, N-1).
+
+indent(1, Ind) -> % Optimization of common case
+ [$\s | Ind];
+indent(4, Ind) -> % Optimization of common case
+ S2 = [$\s, $\s],
+ [S2, S2 | Ind];
+indent(N, Ind) when is_integer(N), N > 0 ->
+ [chars($\s, N) | Ind].
+
+%% A deep version of string:chars/2
+chars(_C, 0) ->
+ [];
+chars(C, 2) ->
+ [C, C];
+chars(C, 3) ->
+ [C, C, C];
+chars(C, N) when (N band 1) =:= 0 ->
+ S = chars(C, N bsr 1),
+ [S | S];
+chars(C, N) ->
+ S = chars(C, N bsr 1),
+ [C, S | S].
diff --git a/lib/stdlib/src/lib.erl b/lib/stdlib/src/lib.erl
new file mode 100644
index 0000000000..b2cfb00de9
--- /dev/null
+++ b/lib/stdlib/src/lib.erl
@@ -0,0 +1,452 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(lib).
+
+-export([flush_receive/0, error_message/2, progname/0, nonl/1, send/2,
+ sendw/2, eval_str/1]).
+
+-export([format_exception/6, format_stacktrace/4,
+ format_call/4, format_fun/1]).
+
+-spec flush_receive() -> 'ok'.
+
+flush_receive() ->
+ receive
+ _Any ->
+ flush_receive()
+ after
+ 0 ->
+ ok
+ end.
+
+%%
+%% Functions for doing standard system format i/o.
+%%
+-spec error_message(atom() | string() | binary(), [term()]) -> 'ok'.
+
+error_message(Format, Args) ->
+ io:format(<<"** ~s **\n">>, [io_lib:format(Format, Args)]).
+
+%% Return the name of the script that starts (this) erlang
+%%
+-spec progname() -> atom().
+
+progname() ->
+ case init:get_argument(progname) of
+ {ok, [[Prog]]} ->
+ list_to_atom(Prog);
+ _Other ->
+ no_prog_name
+ end.
+
+-spec nonl(string()) -> string().
+
+nonl([10]) -> [];
+nonl([]) -> [];
+nonl([H|T]) -> [H|nonl(T)].
+
+-spec send(pid() | atom() | {atom(), node()}, term()) -> term().
+
+send(To, Msg) -> To ! Msg.
+
+-spec sendw(pid() | atom() | {atom(), node()}, term()) -> term().
+
+sendw(To, Msg) ->
+ To ! {self(), Msg},
+ receive
+ Reply -> Reply
+ end.
+
+%% eval_str(InStr) -> {ok, OutStr} | {error, ErrStr'}
+%% InStr must represent a body
+
+-define(result(F,D), lists:flatten(io_lib:format(F, D))).
+
+-spec eval_str(string() | binary()) -> {'ok', string()} | {'error', string()}.
+
+eval_str(Str) when is_list(Str) ->
+ case erl_scan:tokens([], Str, 0) of
+ {more, _} ->
+ {error, "Incomplete form (missing .<cr>)??"};
+ {done, {ok, Toks, _}, Rest} ->
+ case all_white(Rest) of
+ true ->
+ case erl_parse:parse_exprs(Toks) of
+ {ok, Exprs} ->
+ case catch erl_eval:exprs(Exprs, []) of
+ {value, Val, _} ->
+ {ok, Val};
+ Other ->
+ {error, ?result("*** eval: ~p", [Other])}
+ end;
+ {error, {_Line, Mod, Args}} ->
+ Msg = ?result("*** ~s",[Mod:format_error(Args)]),
+ {error, Msg}
+ end;
+ false ->
+ {error, ?result("Non-white space found after "
+ "end-of-form :~s", [Rest])}
+ end
+ end;
+eval_str(Bin) when is_binary(Bin) ->
+ eval_str(binary_to_list(Bin)).
+
+all_white([$\s|T]) -> all_white(T);
+all_white([$\n|T]) -> all_white(T);
+all_white([$\t|T]) -> all_white(T);
+all_white([]) -> true;
+all_white(_) -> false.
+
+%%% Formatting of exceptions, mfa:s and funs.
+
+%% -> iolist() (no \n at end)
+%% I is the current column, starting from 1 (it will be used
+%% as indentation whenever newline has been inserted);
+%% Class, Reason and StackTrace are the exception;
+%% FormatFun = fun(Term, I) -> iolist() formats terms;
+%% StackFun = fun(Mod, Fun, Arity) -> bool() is used for trimming the
+%% end of the stack (typically calls to erl_eval are skipped).
+format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun)
+ when is_integer(I), I >= 1, is_function(StackFun, 3),
+ is_function(FormatFun, 2) ->
+ S = n_spaces(I-1),
+ {Term,Trace1,Trace} = analyze_exception(Class, Reason, StackTrace),
+ Expl0 = explain_reason(Term, Class, Trace1, FormatFun, S),
+ Expl = io_lib:fwrite(<<"~s~s">>, [exited(Class), Expl0]),
+ case format_stacktrace1(S, Trace, FormatFun, StackFun) of
+ [] -> Expl;
+ Stack -> [Expl, $\n, Stack]
+ end.
+
+%% -> iolist() (no \n at end)
+format_stacktrace(I, StackTrace, StackFun, FormatFun)
+ when is_integer(I), I >= 1, is_function(StackFun, 3),
+ is_function(FormatFun, 2) ->
+ S = n_spaces(I-1),
+ format_stacktrace1(S, StackTrace, FormatFun, StackFun).
+
+%% -> iolist() (no \n at end)
+format_call(I, ForMForFun, As, FormatFun) when is_integer(I), I >= 1,
+ is_list(As),
+ is_function(FormatFun, 2) ->
+ format_call("", n_spaces(I-1), ForMForFun, As, FormatFun).
+
+%% -> iolist() (no \n at end)
+format_fun(Fun) when is_function(Fun) ->
+ {module, M} = erlang:fun_info(Fun, module),
+ {name, F} = erlang:fun_info(Fun, name),
+ {arity, A} = erlang:fun_info(Fun, arity),
+ case erlang:fun_info(Fun, type) of
+ {type, local} when F =:= "" ->
+ io_lib:fwrite(<<"~w">>, [Fun]);
+ {type, local} when M =:= erl_eval ->
+ io_lib:fwrite(<<"interpreted function with arity ~w">>, [A]);
+ {type, local} ->
+ mfa_to_string(M, F, A);
+ {type, external} ->
+ mfa_to_string(M, F, A)
+ end.
+
+analyze_exception(error, Term, Stack) ->
+ case {is_stacktrace(Stack), Stack, Term} of
+ {true, [{_M,_F,As}=MFA|MFAs], function_clause} when is_list(As) ->
+ {Term,[MFA],MFAs};
+ {true, [{shell,F,A}], function_clause} when is_integer(A) ->
+ {Term, [{F,A}], []};
+ {true, [{_M,_F,_AorAs}=MFA|MFAs], undef} ->
+ {Term,[MFA],MFAs};
+ {true, _, _} ->
+ {Term,[],Stack};
+ {false, _, _} ->
+ {{Term,Stack},[],[]}
+ end;
+analyze_exception(_Class, Term, Stack) ->
+ case is_stacktrace(Stack) of
+ true ->
+ {Term,[],Stack};
+ false ->
+ {{Term,Stack},[],[]}
+ end.
+
+is_stacktrace([]) ->
+ true;
+is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) ->
+ is_stacktrace(Fs);
+is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), length(As) >= 0 ->
+ is_stacktrace(Fs);
+is_stacktrace(_) ->
+ false.
+
+%% ERTS exit codes (some of them are also returned by erl_eval):
+explain_reason(badarg, error, [], _PF, _S) ->
+ <<"bad argument">>;
+explain_reason({badarg,V}, error=Cl, [], PF, S) -> % orelse, andalso
+ format_value(V, <<"bad argument: ">>, Cl, PF, S);
+explain_reason(badarith, error, [], _PF, _S) ->
+ <<"bad argument in an arithmetic expression">>;
+explain_reason({badarity,{Fun,As}}, error, [], _PF, _S)
+ when is_function(Fun) ->
+ %% Only the arity is displayed, not the arguments As.
+ io_lib:fwrite(<<"~s called with ~s">>,
+ [format_fun(Fun), argss(length(As))]);
+explain_reason({badfun,Term}, error=Cl, [], PF, S) ->
+ format_value(Term, <<"bad function ">>, Cl, PF, S);
+explain_reason({badmatch,Term}, error=Cl, [], PF, S) ->
+ format_value(Term, <<"no match of right hand side value ">>, Cl, PF, S);
+explain_reason({case_clause,V}, error=Cl, [], PF, S) ->
+ %% "there is no case clause with a true guard sequence and a
+ %% pattern matching..."
+ format_value(V, <<"no case clause matching ">>, Cl, PF, S);
+explain_reason(function_clause, error, [{F,A}], _PF, _S) ->
+ %% Shell commands
+ FAs = io_lib:fwrite(<<"~w/~w">>, [F, A]),
+ [<<"no function clause matching call to ">> | FAs];
+explain_reason(function_clause, error=Cl, [{M,F,As}], PF, S) ->
+ Str = <<"no function clause matching ">>,
+ format_errstr_call(Str, Cl, {M,F}, As, PF, S);
+explain_reason(if_clause, error, [], _PF, _S) ->
+ <<"no true branch found when evaluating an if expression">>;
+explain_reason(noproc, error, [], _PF, _S) ->
+ <<"no such process or port">>;
+explain_reason(notalive, error, [], _PF, _S) ->
+ <<"the node cannot be part of a distributed system">>;
+explain_reason(system_limit, error, [], _PF, _S) ->
+ <<"a system limit has been reached">>;
+explain_reason(timeout_value, error, [], _PF, _S) ->
+ <<"bad receive timeout value">>;
+explain_reason({try_clause,V}, error=Cl, [], PF, S) ->
+ %% "there is no try clause with a true guard sequence and a
+ %% pattern matching..."
+ format_value(V, <<"no try clause matching ">>, Cl, PF, S);
+explain_reason(undef, error, [{M,F,A}], _PF, _S) ->
+ %% Only the arity is displayed, not the arguments, if there are any.
+ io_lib:fwrite(<<"undefined function ~s">>,
+ [mfa_to_string(M, F, n_args(A))]);
+explain_reason({shell_undef,F,A}, error, [], _PF, _S) ->
+ %% Give nicer reports for undefined shell functions
+ %% (but not when the user actively calls shell_default:F(...)).
+ io_lib:fwrite(<<"undefined shell command ~s/~w">>, [F, n_args(A)]);
+%% Exit codes returned by erl_eval only:
+explain_reason({argument_limit,_Fun}, error, [], _PF, _S) ->
+ io_lib:fwrite(<<"limit of number of arguments to interpreted function"
+ " exceeded">>, []);
+explain_reason({bad_filter,V}, error=Cl, [], PF, S) ->
+ format_value(V, <<"bad filter ">>, Cl, PF, S);
+explain_reason({bad_generator,V}, error=Cl, [], PF, S) ->
+ format_value(V, <<"bad generator ">>, Cl, PF, S);
+explain_reason({unbound,V}, error, [], _PF, _S) ->
+ io_lib:fwrite(<<"variable ~w is unbound">>, [V]);
+%% Exit codes local to the shell module (restricted shell):
+explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, S) ->
+ Str = <<"restricted shell module returned bad value ">>,
+ format_value(V, Str, Cl, PF, S);
+explain_reason({restricted_shell_disallowed,{ForMF,As}},
+ exit=Cl, [], PF, S) ->
+ %% ForMF can be a fun, but not a shell fun.
+ Str = <<"restricted shell does not allow ">>,
+ format_errstr_call(Str, Cl, ForMF, As, PF, S);
+explain_reason(restricted_shell_started, exit, [], _PF, _S) ->
+ <<"restricted shell starts now">>;
+explain_reason(restricted_shell_stopped, exit, [], _PF, _S) ->
+ <<"restricted shell stopped">>;
+%% Other exit code:
+explain_reason(Reason, Class, [], PF, S) ->
+ PF(Reason, (iolist_size(S)+1) + exited_size(Class)).
+
+n_args(A) when is_integer(A) ->
+ A;
+n_args(As) when is_list(As) ->
+ length(As).
+
+argss(0) ->
+ <<"no arguments">>;
+argss(1) ->
+ <<"one argument">>;
+argss(2) ->
+ <<"two arguments">>;
+argss(I) ->
+ io_lib:fwrite(<<"~w arguments">>, [I]).
+
+format_stacktrace1(S0, Stack0, PF, SF) ->
+ Stack1 = lists:dropwhile(fun({M,F,A}) -> SF(M, F, A)
+ end, lists:reverse(Stack0)),
+ S = [" " | S0],
+ Stack = lists:reverse(Stack1),
+ format_stacktrace2(S, Stack, 1, PF).
+
+format_stacktrace2(S, [{M,F,A}|Fs], N, PF) when is_integer(A) ->
+ [io_lib:fwrite(<<"~s~s ~s">>,
+ [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A)])
+ | format_stacktrace2(S, Fs, N + 1, PF)];
+format_stacktrace2(S, [{M,F,As}|Fs], N, PF) when is_list(As) ->
+ A = length(As),
+ CalledAs = [S,<<" called as ">>],
+ C = format_call("", CalledAs, {M,F}, As, PF),
+ [io_lib:fwrite(<<"~s~s ~s\n~s~s">>,
+ [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A),
+ CalledAs, C])
+ | format_stacktrace2(S, Fs, N + 1, PF)];
+format_stacktrace2(_S, [], _N, _PF) ->
+ "".
+
+sep(1, S) -> S;
+sep(_, S) -> [$\n | S].
+
+origin(1, M, F, A) ->
+ case is_op({M, F}, n_args(A)) of
+ {yes, F} -> <<"in operator ">>;
+ no -> <<"in function ">>
+ end;
+origin(_N, _M, _F, _A) ->
+ <<"in call from">>.
+
+format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0) ->
+ Pre1 = [Pre0 | n_spaces(exited_size(Class))],
+ format_call(ErrStr, Pre1, ForMForFun, As, PF).
+
+format_call(ErrStr, Pre1, ForMForFun, As, PF) ->
+ Arity = length(As),
+ [ErrStr |
+ case is_op(ForMForFun, Arity) of
+ {yes,Op} ->
+ format_op(ErrStr, Pre1, Op, As, PF);
+ no ->
+ MFs = mf_to_string(ForMForFun, Arity),
+ I1 = iolist_size([Pre1,ErrStr|MFs]),
+ S1 = pp_arguments(PF, As, I1),
+ S2 = pp_arguments(PF, As, iolist_size([Pre1|MFs])),
+ Long = count_nl(pp_arguments(PF, [a2345,b2345], I1)) > 0,
+ case Long or (count_nl(S2) < count_nl(S1)) of
+ true ->
+ [$\n, Pre1, MFs, S2];
+ false ->
+ [MFs, S1]
+ end
+ end].
+
+format_op(ErrStr, Pre, Op, [A1], PF) ->
+ OpS = io_lib:fwrite(<<"~s ">>, [Op]),
+ I1 = iolist_size([ErrStr,Pre,OpS]),
+ [OpS | PF(A1, I1+1)];
+format_op(ErrStr, Pre, Op, [A1, A2], PF) ->
+ I1 = iolist_size([ErrStr,Pre]),
+ S1 = PF(A1, I1+1),
+ S2 = PF(A2, I1+1),
+ OpS = atom_to_list(Op),
+ Pre1 = [$\n | n_spaces(I1)],
+ case count_nl(S1) > 0 of
+ true ->
+ [S1,Pre1,OpS,Pre1|S2];
+ false ->
+ OpS2 = io_lib:fwrite(<<" ~s ">>, [Op]),
+ S2_2 = PF(A2, iolist_size([ErrStr,Pre,S1|OpS2])+1),
+ case count_nl(S2) < count_nl(S2_2) of
+ true ->
+ [S1,Pre1,OpS,Pre1|S2];
+ false ->
+ [S1,OpS2|S2_2]
+ end
+ end.
+
+pp_arguments(PF, As, I) ->
+ case {As, io_lib:printable_list(As)} of
+ {[Int | T], true} ->
+ L = integer_to_list(Int),
+ Ll = length(L),
+ A = list_to_atom(lists:duplicate(Ll, $a)),
+ S0 = binary_to_list(iolist_to_binary(PF([A | T], I+1))),
+ brackets_to_parens([$[,L,string:sub_string(S0, 2+Ll)]);
+ _ ->
+ brackets_to_parens(PF(As, I+1))
+ end.
+
+brackets_to_parens(S) ->
+ B = iolist_to_binary(S),
+ Sz = byte_size(B) - 2,
+ <<$[,R:Sz/binary,$]>> = B,
+ [$(,R,$)].
+
+mfa_to_string(M, F, A) ->
+ io_lib:fwrite(<<"~s/~w">>, [mf_to_string({M, F}, A), A]).
+
+mf_to_string({M, F}, A) ->
+ case erl_internal:bif(M, F, A) of
+ true ->
+ io_lib:fwrite(<<"~w">>, [F]);
+ false ->
+ case is_op({M, F}, A) of
+ {yes, '/'} ->
+ io_lib:fwrite(<<"~w">>, [F]);
+ {yes, F} ->
+ atom_to_list(F);
+ no ->
+ io_lib:fwrite(<<"~w:~w">>, [M, F])
+ end
+ end;
+mf_to_string(Fun, _A) when is_function(Fun) ->
+ format_fun(Fun);
+mf_to_string(F, _A) ->
+ io_lib:fwrite(<<"~w">>, [F]).
+
+format_value(V, ErrStr, Class, PF, S) ->
+ Pre1Sz = exited_size(Class),
+ S1 = PF(V, Pre1Sz + iolist_size([S, ErrStr])+1),
+ [ErrStr | case count_nl(S1) of
+ N1 when N1 > 1 ->
+ S2 = PF(V, iolist_size(S) + 1 + Pre1Sz),
+ case count_nl(S2) < N1 of
+ true ->
+ [$\n, S, n_spaces(Pre1Sz) | S2];
+ false ->
+ S1
+ end;
+ _ ->
+ S1
+ end].
+
+%% Handles deep lists, but not all iolists.
+count_nl([E | Es]) ->
+ count_nl(E) + count_nl(Es);
+count_nl($\n) ->
+ 1;
+count_nl(Bin) when is_binary(Bin) ->
+ count_nl(binary_to_list(Bin));
+count_nl(_) ->
+ 0.
+
+n_spaces(N) ->
+ lists:duplicate(N, $\s).
+
+is_op(ForMForFun, A) ->
+ try
+ {erlang,F} = ForMForFun,
+ _ = erl_internal:op_type(F, A),
+ {yes,F}
+ catch error:_ -> no
+ end.
+
+exited_size(Class) ->
+ iolist_size(exited(Class)).
+
+exited(error) ->
+ <<"exception error: ">>;
+exited(exit) ->
+ <<"exception exit: ">>;
+exited(throw) ->
+ <<"exception throw: ">>.
diff --git a/lib/stdlib/src/lists.erl b/lib/stdlib/src/lists.erl
new file mode 100644
index 0000000000..e1f8d1c200
--- /dev/null
+++ b/lib/stdlib/src/lists.erl
@@ -0,0 +1,2462 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(lists).
+
+-export([append/2, append/1, subtract/2, reverse/1,
+ nth/2, nthtail/2, prefix/2, suffix/2, last/1,
+ seq/2, seq/3, sum/1, duplicate/2, min/1, max/1, sublist/2, sublist/3,
+ delete/2,
+ unzip/1, unzip3/1, zip/2, zip3/3, zipwith/3, zipwith3/4,
+ sort/1, merge/1, merge/2, rmerge/2, merge3/3, rmerge3/3,
+ usort/1, umerge/1, umerge3/3, umerge/2, rumerge3/3, rumerge/2,
+ concat/1, flatten/1, flatten/2, flat_length/1, flatlength/1,
+ keydelete/3, keyreplace/4, keytake/3, keystore/4,
+ keysort/2, keymerge/3, rkeymerge/3, rukeymerge/3,
+ ukeysort/2, ukeymerge/3, keymap/3]).
+
+%% Bifs: member/2, reverse/2
+%% Bifs: keymember/3, keysearch/3, keyfind/3
+
+-export([merge/3, rmerge/3, sort/2, umerge/3, rumerge/3, usort/2]).
+
+-export([all/2,any/2,map/2,flatmap/2,foldl/3,foldr/3,filter/2,
+ partition/2,zf/2,
+ mapfoldl/3,mapfoldr/3,foreach/2,takewhile/2,dropwhile/2,splitwith/2,
+ split/2]).
+
+-deprecated([flat_length/1]).
+
+%% member(X, L) -> (true | false)
+%% test if X is a member of the list L
+%% Now a BIF!
+
+%member(X, [X|_]) -> true;
+%member(X, [_|Y]) ->
+% member(X, Y);
+%member(X, []) -> false.
+
+%% append(X, Y) appends lists X and Y
+
+-spec append([T], [T]) -> [T].
+
+append(L1, L2) -> L1 ++ L2.
+
+%% append(L) appends the list of lists L
+
+-spec append([[T]]) -> [T].
+
+append([E]) -> E;
+append([H|T]) -> H ++ append(T);
+append([]) -> [].
+
+%% subtract(List1, List2) subtract elements in List2 form List1.
+
+-spec subtract([T], [T]) -> [T].
+
+subtract(L1, L2) -> L1 -- L2.
+
+%% reverse(L) reverse all elements in the list L. Is now a BIF!
+
+-spec reverse([T]) -> [T].
+
+reverse([] = L) ->
+ L;
+reverse([_] = L) ->
+ L;
+reverse([A, B]) ->
+ [B, A];
+reverse([A, B | L]) ->
+ lists:reverse(L, [B, A]).
+
+%reverse([H|T], Y) ->
+% reverse(T, [H|Y]);
+%reverse([], X) -> X.
+
+
+%% nth(N, L) returns the N`th element of the list L
+%% nthtail(N, L) returns the N`th tail of the list L
+
+-spec nth(pos_integer(), [T,...]) -> T.
+
+nth(1, [H|_]) -> H;
+nth(N, [_|T]) when N > 1 ->
+ nth(N - 1, T).
+
+-spec nthtail(non_neg_integer(), [T,...]) -> [T].
+
+nthtail(1, [_|T]) -> T;
+nthtail(N, [_|T]) when N > 1 ->
+ nthtail(N - 1, T);
+nthtail(0, L) when is_list(L) -> L.
+
+%% prefix(Prefix, List) -> (true | false)
+
+-spec prefix([T], [T]) -> boolean().
+
+prefix([X|PreTail], [X|Tail]) ->
+ prefix(PreTail, Tail);
+prefix([], List) when is_list(List) -> true;
+prefix([_|_], List) when is_list(List) -> false.
+
+%% suffix(Suffix, List) -> (true | false)
+
+-spec suffix([T], [T]) -> boolean().
+
+suffix(Suffix, List) ->
+ Delta = length(List) - length(Suffix),
+ Delta >= 0 andalso nthtail(Delta, List) =:= Suffix.
+
+%% last(List) returns the last element in a list.
+
+-spec last([T,...]) -> T.
+
+last([E|Es]) -> last(E, Es).
+
+last(_, [E|Es]) -> last(E, Es);
+last(E, []) -> E.
+
+%% seq(Min, Max) -> [Min,Min+1, ..., Max]
+%% seq(Min, Max, Incr) -> [Min,Min+Incr, ..., Max]
+%% returns the sequence Min..Max
+%% Min <= Max and Min and Max must be integers
+
+-spec seq(integer(), integer()) -> [integer()].
+
+seq(First, Last)
+ when is_integer(First), is_integer(Last), First-1 =< Last ->
+ seq_loop(Last-First+1, Last, []).
+
+seq_loop(N, X, L) when N >= 4 ->
+ seq_loop(N-4, X-4, [X-3,X-2,X-1,X|L]);
+seq_loop(N, X, L) when N >= 2 ->
+ seq_loop(N-2, X-2, [X-1,X|L]);
+seq_loop(1, X, L) ->
+ [X|L];
+seq_loop(0, _, L) ->
+ L.
+
+-spec seq(integer(), integer(), integer()) -> [integer()].
+
+seq(First, Last, Inc)
+ when is_integer(First), is_integer(Last), is_integer(Inc) ->
+ if
+ Inc > 0, First - Inc =< Last;
+ Inc < 0, First - Inc >= Last ->
+ N = (Last - First + Inc) div Inc,
+ seq_loop(N, Inc*(N-1)+First, Inc, []);
+ Inc =:= 0, First =:= Last ->
+ seq_loop(1, First, Inc, [])
+ end.
+
+seq_loop(N, X, D, L) when N >= 4 ->
+ Y = X-D, Z = Y-D, W = Z-D,
+ seq_loop(N-4, W-D, D, [W,Z,Y,X|L]);
+seq_loop(N, X, D, L) when N >= 2 ->
+ Y = X-D,
+ seq_loop(N-2, Y-D, D, [Y,X|L]);
+seq_loop(1, X, _, L) ->
+ [X|L];
+seq_loop(0, _, _, L) ->
+ L.
+
+%% sum(L) returns the sum of the elements in L
+
+-spec sum([number()]) -> number().
+
+sum(L) -> sum(L, 0).
+
+sum([H|T], Sum) -> sum(T, Sum + H);
+sum([], Sum) -> Sum.
+
+%% duplicate(N, X) -> [X,X,X,.....,X] (N times)
+%% return N copies of X
+
+-spec duplicate(non_neg_integer(), T) -> [T].
+
+duplicate(N, X) when is_integer(N), N >= 0 -> duplicate(N, X, []).
+
+duplicate(0, _, L) -> L;
+duplicate(N, X, L) -> duplicate(N-1, X, [X|L]).
+
+%% min(L) -> returns the minimum element of the list L
+
+-spec min([T,...]) -> T.
+
+min([H|T]) -> min(T, H).
+
+min([H|T], Min) when H < Min -> min(T, H);
+min([_|T], Min) -> min(T, Min);
+min([], Min) -> Min.
+
+%% max(L) -> returns the maximum element of the list L
+
+-spec max([T,...]) -> T.
+
+max([H|T]) -> max(T, H).
+
+max([H|T], Max) when H > Max -> max(T, H);
+max([_|T], Max) -> max(T, Max);
+max([], Max) -> Max.
+
+%% sublist(List, Start, Length)
+%% Returns the sub-list starting at Start of length Length.
+
+-spec sublist([T], pos_integer(), non_neg_integer()) -> [T].
+
+sublist(List, S, L) when is_integer(L), L >= 0 ->
+ sublist(nthtail(S-1, List), L).
+
+-spec sublist([T], non_neg_integer()) -> [T].
+
+sublist(List, L) when is_integer(L), is_list(List) ->
+ sublist_2(List, L).
+
+sublist_2([H|T], L) when L > 0 ->
+ [H|sublist_2(T, L-1)];
+sublist_2(_, 0) ->
+ [];
+sublist_2(List, L) when is_list(List), L > 0 ->
+ [].
+
+%% delete(Item, List) -> List'
+%% Delete the first occurrence of Item from the list L.
+
+-spec delete(T, [T]) -> [T].
+
+delete(Item, [Item|Rest]) -> Rest;
+delete(Item, [H|Rest]) ->
+ [H|delete(Item, Rest)];
+delete(_, []) -> [].
+
+%% Return [{X0, Y0}, {X1, Y1}, ..., {Xn, Yn}] for lists [X0, X1, ...,
+%% Xn] and [Y0, Y1, ..., Yn].
+
+-spec zip([A], [B]) -> [{A, B}].
+
+zip([X | Xs], [Y | Ys]) -> [{X, Y} | zip(Xs, Ys)];
+zip([], []) -> [].
+
+%% Return {[X0, X1, ..., Xn], [Y0, Y1, ..., Yn]}, for a list [{X0, Y0},
+%% {X1, Y1}, ..., {Xn, Yn}].
+
+-spec unzip([{A, B}]) -> {[A], [B]}.
+
+unzip(Ts) -> unzip(Ts, [], []).
+
+unzip([{X, Y} | Ts], Xs, Ys) -> unzip(Ts, [X | Xs], [Y | Ys]);
+unzip([], Xs, Ys) -> {reverse(Xs), reverse(Ys)}.
+
+%% Return [{X0, Y0, Z0}, {X1, Y1, Z1}, ..., {Xn, Yn, Zn}] for lists [X0,
+%% X1, ..., Xn], [Y0, Y1, ..., Yn] and [Z0, Z1, ..., Zn].
+
+-spec zip3([A], [B], [C]) -> [{A, B, C}].
+
+zip3([X | Xs], [Y | Ys], [Z | Zs]) -> [{X, Y, Z} | zip3(Xs, Ys, Zs)];
+zip3([], [], []) -> [].
+
+%% Return {[X0, X1, ..., Xn], [Y0, Y1, ..., Yn], [Z0, Z1, ..., Zn]}, for
+%% a list [{X0, Y0, Z0}, {X1, Y1, Z1}, ..., {Xn, Yn, Zn}].
+
+-spec unzip3([{A, B, C}]) -> {[A], [B], [C]}.
+
+unzip3(Ts) -> unzip3(Ts, [], [], []).
+
+unzip3([{X, Y, Z} | Ts], Xs, Ys, Zs) ->
+ unzip3(Ts, [X | Xs], [Y | Ys], [Z | Zs]);
+unzip3([], Xs, Ys, Zs) ->
+ {reverse(Xs), reverse(Ys), reverse(Zs)}.
+
+%% Return [F(X0, Y0), F(X1, Y1), ..., F(Xn, Yn)] for lists [X0, X1, ...,
+%% Xn] and [Y0, Y1, ..., Yn].
+
+-spec zipwith(fun((X, Y) -> R), [X], [Y]) -> [R].
+
+zipwith(F, [X | Xs], [Y | Ys]) -> [F(X, Y) | zipwith(F, Xs, Ys)];
+zipwith(F, [], []) when is_function(F, 2) -> [].
+
+%% Return [F(X0, Y0, Z0), F(X1, Y1, Z1), ..., F(Xn, Yn, Zn)] for lists
+%% [X0, X1, ..., Xn], [Y0, Y1, ..., Yn] and [Z0, Z1, ..., Zn].
+
+-spec zipwith3(fun((X, Y, Z) -> R), [X], [Y], [Z]) -> [R].
+
+zipwith3(F, [X | Xs], [Y | Ys], [Z | Zs]) ->
+ [F(X, Y, Z) | zipwith3(F, Xs, Ys, Zs)];
+zipwith3(F, [], [], []) when is_function(F, 3) -> [].
+
+%% sort(List) -> L
+%% sorts the list L
+
+-spec sort([T]) -> [T].
+
+sort([X, Y | L] = L0) when X =< Y ->
+ case L of
+ [] ->
+ L0;
+ [Z] when Y =< Z ->
+ L0;
+ [Z] when X =< Z ->
+ [X, Z, Y];
+ [Z] ->
+ [Z, X, Y];
+ _ when X == Y ->
+ sort_1(Y, L, [X]);
+ _ ->
+ split_1(X, Y, L, [], [])
+ end;
+sort([X, Y | L]) ->
+ case L of
+ [] ->
+ [Y, X];
+ [Z] when X =< Z ->
+ [Y, X | L];
+ [Z] when Y =< Z ->
+ [Y, Z, X];
+ [Z] ->
+ [Z, Y, X];
+ _ ->
+ split_2(X, Y, L, [], [])
+ end;
+sort([_] = L) ->
+ L;
+sort([] = L) ->
+ L.
+
+sort_1(X, [Y | L], R) when X == Y ->
+ sort_1(Y, L, [X | R]);
+sort_1(X, [Y | L], R) when X < Y ->
+ split_1(X, Y, L, R, []);
+sort_1(X, [Y | L], R) ->
+ split_2(X, Y, L, R, []);
+sort_1(X, [], R) ->
+ lists:reverse(R, [X]).
+
+%% merge(List) -> L
+%% merges a list of sorted lists
+
+-spec merge([T]) -> [T].
+
+merge(L) ->
+ mergel(L, []).
+
+%% merge3(X, Y, Z) -> L
+%% merges three sorted lists X, Y and Z
+
+-spec merge3([_], [_], [_]) -> [_].
+
+merge3(L1, [], L3) ->
+ merge(L1, L3);
+merge3(L1, L2, []) ->
+ merge(L1, L2);
+merge3(L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(merge3_1(L1, [], H2, T2, H3, T3), []).
+
+%% rmerge3(X, Y, Z) -> L
+%% merges three reversed sorted lists X, Y and Z
+
+-spec rmerge3([_], [_], [_]) -> [_].
+
+rmerge3(L1, [], L3) ->
+ rmerge(L1, L3);
+rmerge3(L1, L2, []) ->
+ rmerge(L1, L2);
+rmerge3(L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(rmerge3_1(L1, [], H2, T2, H3, T3), []).
+
+%% merge(X, Y) -> L
+%% merges two sorted lists X and Y
+
+-spec merge([_], [_]) -> [_].
+
+merge(T1, []) ->
+ T1;
+merge(T1, [H2 | T2]) ->
+ lists:reverse(merge2_1(T1, H2, T2, []), []).
+
+%% rmerge(X, Y) -> L
+%% merges two reversed sorted lists X and Y
+
+%% reverse(rmerge(reverse(A),reverse(B))) is equal to merge(I,A,B).
+
+-spec rmerge([_], [_]) -> [_].
+
+rmerge(T1, []) ->
+ T1;
+rmerge(T1, [H2 | T2]) ->
+ lists:reverse(rmerge2_1(T1, H2, T2, []), []).
+
+%% concat(L) concatenate the list representation of the elements
+%% in L - the elements in L can be atoms, numbers of strings.
+%% Returns a list of characters.
+
+-type concat_thing() :: atom() | integer() | float() | string().
+-spec concat([concat_thing()]) -> string().
+
+concat(List) ->
+ flatmap(fun thing_to_list/1, List).
+
+thing_to_list(X) when is_integer(X) -> integer_to_list(X);
+thing_to_list(X) when is_float(X) -> float_to_list(X);
+thing_to_list(X) when is_atom(X) -> atom_to_list(X);
+thing_to_list(X) when is_list(X) -> X. %Assumed to be a string
+
+%% flatten(List)
+%% flatten(List, Tail)
+%% Flatten a list, adding optional tail.
+
+-spec flatten([_]) -> [_].
+
+flatten(List) when is_list(List) ->
+ do_flatten(List, []).
+
+-spec flatten([_], [_]) -> [_].
+
+flatten(List, Tail) when is_list(List), is_list(Tail) ->
+ do_flatten(List, Tail).
+
+do_flatten([H|T], Tail) when is_list(H) ->
+ do_flatten(H, do_flatten(T, Tail));
+do_flatten([H|T], Tail) ->
+ [H|do_flatten(T, Tail)];
+do_flatten([], Tail) ->
+ Tail.
+
+%% flat_length(List) (undocumented can be removed later)
+%% Calculate the length of a list of lists.
+
+-spec flat_length([_]) -> non_neg_integer().
+
+flat_length(List) -> flatlength(List).
+
+%% flatlength(List)
+%% Calculate the length of a list of lists.
+
+-spec flatlength([_]) -> non_neg_integer().
+
+flatlength(List) ->
+ flatlength(List, 0).
+
+flatlength([H|T], L) when is_list(H) ->
+ flatlength(H, flatlength(T, L));
+flatlength([_|T], L) ->
+ flatlength(T, L + 1);
+flatlength([], L) -> L.
+
+%% keymember(Key, Index, [Tuple]) Now a BIF!
+%% keysearch(Key, Index, [Tuple]) Now a BIF!
+%% keydelete(Key, Index, [Tuple])
+%% keyreplace(Key, Index, [Tuple], NewTuple)
+%% keytake(Key, Index, [Tuple])
+%% keystore(Key, Index, [Tuple], NewTuple)
+%% keysort(Index, [Tuple])
+%% keymerge(Index, [Tuple], [Tuple])
+%% ukeysort(Index, [Tuple])
+%% ukeymerge(Index, [Tuple], [Tuple])
+%% keymap(Function, Index, [Tuple])
+%% keymap(Function, ExtraArgs, Index, [Tuple])
+
+%keymember(K,N,L) when is_integer(N), N > 0 ->
+% keymember3(K,N,L).
+
+%keymember3(Key, N, [T|Ts]) when element(N, T) == Key -> true;
+%keymember3(Key, N, [T|Ts]) ->
+% keymember3(Key, N, Ts);
+%keymember3(Key, N, []) -> false.
+
+%keysearch(K, N, L) when is_integer(N), N > 0 ->
+% keysearch3(K, N, L).
+
+%keysearch3(Key, N, [H|T]) when element(N, H) == Key ->
+% {value, H};
+%keysearch3(Key, N, [H|T]) ->
+% keysearch3(Key, N, T);
+%keysearch3(Key, N, []) -> false.
+
+-spec keydelete(_, pos_integer(), [T]) -> [T].
+
+keydelete(K, N, L) when is_integer(N), N > 0 ->
+ keydelete3(K, N, L).
+
+keydelete3(Key, N, [H|T]) when element(N, H) == Key -> T;
+keydelete3(Key, N, [H|T]) ->
+ [H|keydelete3(Key, N, T)];
+keydelete3(_, _, []) -> [].
+
+-spec keyreplace(_, pos_integer(), [_], tuple()) -> [_].
+
+keyreplace(K, N, L, New) when is_integer(N), N > 0, is_tuple(New) ->
+ keyreplace3(K, N, L, New).
+
+keyreplace3(Key, Pos, [Tup|Tail], New) when element(Pos, Tup) == Key ->
+ [New|Tail];
+keyreplace3(Key, Pos, [H|T], New) ->
+ [H|keyreplace3(Key, Pos, T, New)];
+keyreplace3(_, _, [], _) -> [].
+
+-spec keytake(_, pos_integer(), [_]) -> {'value', tuple(), [_]} | 'false'.
+
+keytake(Key, N, L) when is_integer(N), N > 0 ->
+ keytake(Key, N, L, []).
+
+keytake(Key, N, [H|T], L) when element(N, H) == Key ->
+ {value, H, lists:reverse(L, T)};
+keytake(Key, N, [H|T], L) ->
+ keytake(Key, N, T, [H|L]);
+keytake(_K, _N, [], _L) -> false.
+
+-spec keystore(_, pos_integer(), [_], tuple()) -> [_].
+keystore(K, N, L, New) when is_integer(N), N > 0, is_tuple(New) ->
+ keystore2(K, N, L, New).
+
+keystore2(Key, N, [H|T], New) when element(N, H) == Key ->
+ [New|T];
+keystore2(Key, N, [H|T], New) ->
+ [H|keystore2(Key, N, T, New)];
+keystore2(_Key, _N, [], New) ->
+ [New].
+
+-spec keysort(pos_integer(), [T]) -> [T] when is_subtype(T, tuple()).
+
+keysort(I, L) when is_integer(I), I > 0 ->
+ case L of
+ [] -> L;
+ [_] -> L;
+ [X, Y | T] ->
+ case {element(I, X), element(I, Y)} of
+ {EX, EY} when EX =< EY ->
+ case T of
+ [] ->
+ L;
+ [Z] ->
+ case element(I, Z) of
+ EZ when EY =< EZ ->
+ L;
+ EZ when EX =< EZ ->
+ [X, Z, Y];
+ _EZ ->
+ [Z, X, Y]
+ end;
+ _ when X == Y ->
+ keysort_1(I, Y, EY, T, [X]);
+ _ ->
+ keysplit_1(I, X, EX, Y, EY, T, [], [])
+ end;
+ {EX, EY} ->
+ case T of
+ [] ->
+ [Y, X];
+ [Z] ->
+ case element(I, Z) of
+ EZ when EX =< EZ ->
+ [Y, X | T];
+ EZ when EY =< EZ ->
+ [Y, Z, X];
+ _EZ ->
+ [Z, Y, X]
+ end;
+ _ ->
+ keysplit_2(I, X, EX, Y, EY, T, [], [])
+ end
+ end
+ end.
+
+keysort_1(I, X, EX, [Y | L], R) when X == Y ->
+ keysort_1(I, Y, EX, L, [X | R]);
+keysort_1(I, X, EX, [Y | L], R) ->
+ case element(I, Y) of
+ EY when EX =< EY ->
+ keysplit_1(I, X, EX, Y, EY, L, R, []);
+ EY ->
+ keysplit_2(I, X, EX, Y, EY, L, R, [])
+ end;
+keysort_1(_I, X, _EX, [], R) ->
+ lists:reverse(R, [X]).
+
+-spec keymerge(pos_integer(), [X], [Y]) ->
+ [R] when is_subtype(X, tuple()), is_subtype(Y, tuple()), is_subtype(R, tuple()).
+
+keymerge(Index, T1, L2) when is_integer(Index), Index > 0 ->
+ case L2 of
+ [] ->
+ T1;
+ [H2 | T2] ->
+ E2 = element(Index, H2),
+ M = keymerge2_1(Index, T1, E2, H2, T2, []),
+ lists:reverse(M, [])
+ end.
+
+%% reverse(rkeymerge(I,reverse(A),reverse(B))) is equal to keymerge(I,A,B).
+
+-spec rkeymerge(pos_integer(), [X], [Y]) ->
+ [R] when is_subtype(X, tuple()), is_subtype(Y, tuple()), is_subtype(R, tuple()).
+
+rkeymerge(Index, T1, L2) when is_integer(Index), Index > 0 ->
+ case L2 of
+ [] ->
+ T1;
+ [H2 | T2] ->
+ E2 = element(Index, H2),
+ M = rkeymerge2_1(Index, T1, E2, H2, T2, []),
+ lists:reverse(M, [])
+ end.
+
+-spec ukeysort(pos_integer(), [T]) -> [T] when is_subtype(T, tuple()).
+
+ukeysort(I, L) when is_integer(I), I > 0 ->
+ case L of
+ [] -> L;
+ [_] -> L;
+ [X, Y | T] ->
+ case {element(I, X), element(I, Y)} of
+ {EX, EY} when EX == EY ->
+ ukeysort_1(I, X, EX, T);
+ {EX, EY} when EX < EY ->
+ case T of
+ [] ->
+ L;
+ [Z] ->
+ case element(I, Z) of
+ EZ when EY == EZ ->
+ [X, Y];
+ EZ when EY < EZ ->
+ [X, Y, Z];
+ EZ when EZ == EX ->
+ [X, Y];
+ EZ when EX =< EZ ->
+ [X, Z, Y];
+ _EZ ->
+ [Z, X, Y]
+ end;
+ _ ->
+ ukeysplit_1(I, X, EX, Y, EY, T, [], [])
+ end;
+ {EX, EY} ->
+ case T of
+ [] ->
+ [Y, X];
+ [Z] ->
+ case element(I, Z) of
+ EZ when EX == EZ ->
+ [Y, X];
+ EZ when EX < EZ ->
+ [Y, X, Z];
+ EZ when EY == EZ ->
+ [Y, X];
+ EZ when EY =< EZ ->
+ [Y, Z, X];
+ _EZ ->
+ [Z, Y, X]
+ end;
+ _ ->
+ ukeysplit_2(I, Y, EY, T, [X])
+ end
+ end
+ end.
+
+ukeysort_1(I, X, EX, [Y | L]) ->
+ case element(I, Y) of
+ EY when EX == EY ->
+ ukeysort_1(I, X, EX, L);
+ EY when EX < EY ->
+ ukeysplit_1(I, X, EX, Y, EY, L, [], []);
+ EY ->
+ ukeysplit_2(I, Y, EY, L, [X])
+ end;
+ukeysort_1(_I, X, _EX, []) ->
+ [X].
+
+-spec ukeymerge(pos_integer(), [X], [Y]) ->
+ [(X | Y)] when is_subtype(X, tuple()), is_subtype(Y, tuple()).
+
+ukeymerge(Index, L1, T2) when is_integer(Index), Index > 0 ->
+ case L1 of
+ [] ->
+ T2;
+ [H1 | T1] ->
+ E1 = element(Index, H1),
+ M = ukeymerge2_2(Index, T1, E1, H1, T2, []),
+ lists:reverse(M, [])
+ end.
+
+%% reverse(rukeymerge(I,reverse(A),reverse(B))) is equal to ukeymerge(I,A,B).
+
+-spec rukeymerge(pos_integer(), [X], [Y]) ->
+ [(X | Y)] when is_subtype(X, tuple()), is_subtype(Y, tuple()).
+
+rukeymerge(Index, T1, L2) when is_integer(Index), Index > 0 ->
+ case L2 of
+ [] ->
+ T1;
+ [H2 | T2] ->
+ E2 = element(Index, H2),
+ M = rukeymerge2_1(Index, T1, E2, T2, [], H2),
+ lists:reverse(M, [])
+ end.
+
+-spec keymap(fun((_) -> _), pos_integer(), [tuple()]) -> [tuple()].
+
+keymap(Fun, Index, [Tup|Tail]) ->
+ [setelement(Index, Tup, Fun(element(Index, Tup)))|keymap(Fun, Index, Tail)];
+keymap(Fun, Index, []) when is_integer(Index), Index >= 1,
+ is_function(Fun, 1) -> [].
+
+%%% Suggestion from OTP-2948: sort and merge with Fun.
+
+-spec sort(fun((T, T) -> boolean()), [T]) -> [T].
+
+sort(Fun, []) when is_function(Fun, 2) ->
+ [];
+sort(Fun, [_] = L) when is_function(Fun, 2) ->
+ L;
+sort(Fun, [X, Y | T]) ->
+ case Fun(X, Y) of
+ true ->
+ fsplit_1(Y, X, Fun, T, [], []);
+ false ->
+ fsplit_2(Y, X, Fun, T, [], [])
+ end.
+
+-spec merge(fun((X, Y) -> boolean()), [X], [Y]) -> [_].
+
+merge(Fun, T1, [H2 | T2]) when is_function(Fun, 2) ->
+ lists:reverse(fmerge2_1(T1, H2, Fun, T2, []), []);
+merge(Fun, T1, []) when is_function(Fun, 2) ->
+ T1.
+
+%% reverse(rmerge(F,reverse(A),reverse(B))) is equal to merge(F,A,B).
+
+-spec rmerge(fun((X, Y) -> boolean()), [X], [Y]) -> [_].
+
+rmerge(Fun, T1, [H2 | T2]) when is_function(Fun, 2) ->
+ lists:reverse(rfmerge2_1(T1, H2, Fun, T2, []), []);
+rmerge(Fun, T1, []) when is_function(Fun, 2) ->
+ T1.
+
+-spec usort(fun((T, T) -> boolean()), [T]) -> [T].
+
+usort(Fun, [_] = L) when is_function(Fun, 2) ->
+ L;
+usort(Fun, [] = L) when is_function(Fun, 2) ->
+ L;
+usort(Fun, [X | L]) when is_function(Fun, 2) ->
+ usort_1(Fun, X, L).
+
+usort_1(Fun, X, [Y | L]) ->
+ case Fun(X, Y) of
+ true ->
+ case Fun(Y, X) of
+ true -> % X equal to Y
+ case L of
+ [] ->
+ [X];
+ _ ->
+ usort_1(Fun, X, L)
+ end;
+ false ->
+ ufsplit_1(Y, X, Fun, L, [], [])
+ end;
+ false ->
+ ufsplit_2(Y, L, Fun, [X])
+ end.
+
+-spec umerge(fun((X, Y) -> boolean()), [X], [Y]) -> [_].
+
+umerge(Fun, [], T2) when is_function(Fun, 2) ->
+ T2;
+umerge(Fun, [H1 | T1], T2) when is_function(Fun, 2) ->
+ lists:reverse(ufmerge2_2(H1, T1, Fun, T2, []), []).
+
+%% reverse(rumerge(F,reverse(A),reverse(B))) is equal to umerge(F,A,B).
+
+-spec rumerge(fun((X, Y) -> boolean()), [X], [Y]) -> [_].
+
+rumerge(Fun, T1, []) when is_function(Fun, 2) ->
+ T1;
+rumerge(Fun, T1, [H2 | T2]) when is_function(Fun, 2) ->
+ lists:reverse(rufmerge2_1(T1, H2, Fun, T2, []), []).
+
+%% usort(List) -> L
+%% sorts the list L, removes duplicates
+
+-spec usort([T]) -> [T].
+
+usort([X, Y | L] = L0) when X < Y ->
+ case L of
+ [] ->
+ L0;
+ [Z] when Y < Z ->
+ L0;
+ [Z] when Y == Z ->
+ [X, Y];
+ [Z] when Z < X ->
+ [Z, X, Y];
+ [Z] when Z == X ->
+ [X, Y];
+ [Z] ->
+ [X, Z, Y];
+ _ ->
+ usplit_1(X, Y, L, [], [])
+ end;
+usort([X, Y | L]) when X > Y ->
+ case L of
+ [] ->
+ [Y, X];
+ [Z] when X < Z ->
+ [Y, X | L];
+ [Z] when X == Z ->
+ [Y, X];
+ [Z] when Z < Y ->
+ [Z, Y, X];
+ [Z] when Z == Y ->
+ [Y, X];
+ [Z] ->
+ [Y, Z, X];
+ _ ->
+ usplit_2(X, Y, L, [], [])
+ end;
+usort([X, _Y | L]) ->
+ usort_1(X, L);
+usort([_] = L) ->
+ L;
+usort([]) ->
+ [].
+
+usort_1(X, [Y | L]) when X == Y ->
+ usort_1(X, L);
+usort_1(X, [Y | L]) when X < Y ->
+ usplit_1(X, Y, L, [], []);
+usort_1(X, [Y | L]) ->
+ usplit_2(X, Y, L, [], []);
+usort_1(X, []) ->
+ [X].
+
+%% umerge(List) -> L
+%% merges a list of sorted lists without duplicates, removes duplicates
+
+-spec umerge([T]) -> [T].
+
+umerge(L) ->
+ umergel(L).
+
+%% umerge3(X, Y, Z) -> L
+%% merges three sorted lists X, Y and Z without duplicates,
+%% removes duplicates
+
+-spec umerge3([_], [_], [_]) -> [_].
+
+umerge3(L1, [], L3) ->
+ umerge(L1, L3);
+umerge3(L1, L2, []) ->
+ umerge(L1, L2);
+umerge3(L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(umerge3_1(L1, [H2 | H3], T2, H2, [], T3, H3), []).
+
+%% rumerge3(X, Y, Z) -> L
+%% merges three reversed sorted lists X, Y and Z without duplicates,
+%% removes duplicates
+
+-spec rumerge3([_], [_], [_]) -> [_].
+
+rumerge3(L1, [], L3) ->
+ rumerge(L1, L3);
+rumerge3(L1, L2, []) ->
+ rumerge(L1, L2);
+rumerge3(L1, [H2 | T2], [H3 | T3]) ->
+ lists:reverse(rumerge3_1(L1, T2, H2, [], T3, H3),[]).
+
+%% umerge(X, Y) -> L
+%% merges two sorted lists X and Y without duplicates, removes duplicates
+
+-spec umerge([_], [_]) -> [_].
+
+umerge([], T2) ->
+ T2;
+umerge([H1 | T1], T2) ->
+ lists:reverse(umerge2_2(T1, T2, [], H1), []).
+
+%% rumerge(X, Y) -> L
+%% merges two reversed sorted lists X and Y without duplicates,
+%% removes duplicates
+
+%% reverse(rumerge(reverse(A),reverse(B))) is equal to umerge(I,A,B).
+
+-spec rumerge([_], [_]) -> [_].
+
+rumerge(T1, []) ->
+ T1;
+rumerge(T1, [H2 | T2]) ->
+ lists:reverse(rumerge2_1(T1, T2, [], H2), []).
+
+%% all(Predicate, List)
+%% any(Predicate, List)
+%% map(Function, List)
+%% flatmap(Function, List)
+%% foldl(Function, First, List)
+%% foldr(Function, Last, List)
+%% filter(Predicate, List)
+%% zf(Function, List)
+%% mapfoldl(Function, First, List)
+%% mapfoldr(Function, Last, List)
+%% foreach(Function, List)
+%% takewhile(Predicate, List)
+%% dropwhile(Predicate, List)
+%% splitwith(Predicate, List)
+%% for list programming. Function here is a 'fun'. For backward compatibility,
+%% {Module,Function} is still accepted.
+%%
+%% The name zf is a joke!
+%%
+%% N.B. Unless where the functions actually needs it only foreach/2/3,
+%% which is meant to be used for its side effects, has a defined order
+%% of evaluation.
+%%
+%% There are also versions with an extra argument, ExtraArgs, which is a
+%% list of extra arguments to each call.
+
+-spec all(fun((T) -> boolean()), [T]) -> boolean().
+
+all(Pred, [Hd|Tail]) ->
+ case Pred(Hd) of
+ true -> all(Pred, Tail);
+ false -> false
+ end;
+all(Pred, []) when is_function(Pred, 1) -> true.
+
+-spec any(fun((T) -> boolean()), [T]) -> boolean().
+
+any(Pred, [Hd|Tail]) ->
+ case Pred(Hd) of
+ true -> true;
+ false -> any(Pred, Tail)
+ end;
+any(Pred, []) when is_function(Pred, 1) -> false.
+
+-spec map(fun((D) -> R), [D]) -> [R].
+
+map(F, [H|T]) ->
+ [F(H)|map(F, T)];
+map(F, []) when is_function(F, 1) -> [].
+
+-spec flatmap(fun((D) -> [R]), [D]) -> [R].
+
+flatmap(F, [Hd|Tail]) ->
+ F(Hd) ++ flatmap(F, Tail);
+flatmap(F, []) when is_function(F, 1) -> [].
+
+-spec foldl(fun((T, _) -> _), _, [T]) -> _.
+
+foldl(F, Accu, [Hd|Tail]) ->
+ foldl(F, F(Hd, Accu), Tail);
+foldl(F, Accu, []) when is_function(F, 2) -> Accu.
+
+-spec foldr(fun((T, _) -> _), _, [T]) -> _.
+
+foldr(F, Accu, [Hd|Tail]) ->
+ F(Hd, foldr(F, Accu, Tail));
+foldr(F, Accu, []) when is_function(F, 2) -> Accu.
+
+-spec filter(Pred :: fun((T) -> boolean()), List :: [T]) -> [T].
+
+filter(Pred, List) when is_function(Pred, 1) ->
+ [ E || E <- List, Pred(E) ].
+
+%% Equivalent to {filter(F, L), filter(NotF, L)}, if NotF = 'fun(X) ->
+%% not F(X) end'.
+
+-spec partition(Pred :: fun((T) -> boolean()), List :: [T]) -> {[T], [T]}.
+
+partition(Pred, L) ->
+ partition(Pred, L, [], []).
+
+partition(Pred, [H | T], As, Bs) ->
+ case Pred(H) of
+ true -> partition(Pred, T, [H | As], Bs);
+ false -> partition(Pred, T, As, [H | Bs])
+ end;
+partition(Pred, [], As, Bs) when is_function(Pred, 1) ->
+ {reverse(As), reverse(Bs)}.
+
+-spec zf(fun((T) -> boolean() | {'true', X}), [T]) -> [(T | X)].
+
+zf(F, [Hd|Tail]) ->
+ case F(Hd) of
+ true ->
+ [Hd|zf(F, Tail)];
+ {true,Val} ->
+ [Val|zf(F, Tail)];
+ false ->
+ zf(F, Tail)
+ end;
+zf(F, []) when is_function(F, 1) -> [].
+
+-spec foreach(F :: fun((T) -> _), List :: [T]) -> 'ok'.
+
+foreach(F, [Hd|Tail]) ->
+ F(Hd),
+ foreach(F, Tail);
+foreach(F, []) when is_function(F, 1) -> ok.
+
+-spec mapfoldl(fun((T, _) -> {_, _}), _, [T]) -> {[_], _}.
+
+mapfoldl(F, Accu0, [Hd|Tail]) ->
+ {R,Accu1} = F(Hd, Accu0),
+ {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
+ {[R|Rs],Accu2};
+mapfoldl(F, Accu, []) when is_function(F, 2) -> {[],Accu}.
+
+-spec mapfoldr(fun((T, _) -> {_, _}), _, [T]) -> {[_], _}.
+
+mapfoldr(F, Accu0, [Hd|Tail]) ->
+ {Rs,Accu1} = mapfoldr(F, Accu0, Tail),
+ {R,Accu2} = F(Hd, Accu1),
+ {[R|Rs],Accu2};
+mapfoldr(F, Accu, []) when is_function(F, 2) -> {[],Accu}.
+
+-spec takewhile(fun((T) -> boolean()), [T]) -> [T].
+
+takewhile(Pred, [Hd|Tail]) ->
+ case Pred(Hd) of
+ true -> [Hd|takewhile(Pred, Tail)];
+ false -> []
+ end;
+takewhile(Pred, []) when is_function(Pred, 1) -> [].
+
+-spec dropwhile(fun((T) -> boolean()), [T]) -> [T].
+
+dropwhile(Pred, [Hd|Tail]=Rest) ->
+ case Pred(Hd) of
+ true -> dropwhile(Pred, Tail);
+ false -> Rest
+ end;
+dropwhile(Pred, []) when is_function(Pred, 1) -> [].
+
+-spec splitwith(fun((T) -> boolean()), [T]) -> {[T], [T]}.
+
+splitwith(Pred, List) when is_function(Pred, 1) ->
+ splitwith(Pred, List, []).
+
+splitwith(Pred, [Hd|Tail], Taken) ->
+ case Pred(Hd) of
+ true -> splitwith(Pred, Tail, [Hd|Taken]);
+ false -> {reverse(Taken), [Hd|Tail]}
+ end;
+splitwith(Pred, [], Taken) when is_function(Pred, 1) ->
+ {reverse(Taken),[]}.
+
+-spec split(non_neg_integer(), [T]) -> {[T], [T]}.
+
+split(N, List) when is_integer(N), N >= 0, is_list(List) ->
+ case split(N, List, []) of
+ {_, _} = Result -> Result;
+ Fault when is_atom(Fault) ->
+ erlang:error(Fault, [N,List])
+ end;
+split(N, List) ->
+ erlang:error(badarg, [N,List]).
+
+split(0, L, R) ->
+ {lists:reverse(R, []), L};
+split(N, [H|T], R) ->
+ split(N-1, T, [H|R]);
+split(_, [], _) ->
+ badarg.
+
+%%% =================================================================
+%%% Here follows the implementation of the sort functions.
+%%%
+%%% These functions used to be in their own module (lists_sort),
+%%% but have now been placed here to allow Dialyzer to produce better
+%%% type information.
+%%% =================================================================
+
+-compile({inline,
+ [{merge3_12,7}, {merge3_21,7}, {rmerge3_12,7}, {rmerge3_21,7}]}).
+
+-compile({inline,
+ [{umerge3_12,8}, {umerge3_21,8},
+ {rumerge3_12a,7}, {rumerge3_12b,8}]}).
+
+-compile({inline,
+ [{keymerge3_12,12}, {keymerge3_21,12},
+ {rkeymerge3_12,12}, {rkeymerge3_21,12}]}).
+
+-compile({inline,
+ [{ukeymerge3_12,13}, {ukeymerge3_21,13},
+ {rukeymerge3_12a,11}, {rukeymerge3_21a,13},
+ {rukeymerge3_12b,12}, {rukeymerge3_21b,12}]}).
+
+%% sort/1
+
+%% Ascending.
+split_1(X, Y, [Z | L], R, Rs) when Z >= Y ->
+ split_1(Y, Z, L, [X | R], Rs);
+split_1(X, Y, [Z | L], R, Rs) when Z >= X ->
+ split_1(Z, Y, L, [X | R], Rs);
+split_1(X, Y, [Z | L], [], Rs) ->
+ split_1(X, Y, L, [Z], Rs);
+split_1(X, Y, [Z | L], R, Rs) ->
+ split_1_1(X, Y, L, R, Rs, Z);
+split_1(X, Y, [], R, Rs) ->
+ rmergel([[Y, X | R] | Rs], []).
+
+split_1_1(X, Y, [Z | L], R, Rs, S) when Z >= Y ->
+ split_1_1(Y, Z, L, [X | R], Rs, S);
+split_1_1(X, Y, [Z | L], R, Rs, S) when Z >= X ->
+ split_1_1(Z, Y, L, [X | R], Rs, S);
+split_1_1(X, Y, [Z | L], R, Rs, S) when S =< Z ->
+ split_1(S, Z, L, [], [[Y, X | R] | Rs]);
+split_1_1(X, Y, [Z | L], R, Rs, S) ->
+ split_1(Z, S, L, [], [[Y, X | R] | Rs]);
+split_1_1(X, Y, [], R, Rs, S) ->
+ rmergel([[S], [Y, X | R] | Rs], []).
+
+%% Descending.
+split_2(X, Y, [Z | L], R, Rs) when Z =< Y ->
+ split_2(Y, Z, L, [X | R], Rs);
+split_2(X, Y, [Z | L], R, Rs) when Z =< X ->
+ split_2(Z, Y, L, [X | R], Rs);
+split_2(X, Y, [Z | L], [], Rs) ->
+ split_2(X, Y, L, [Z], Rs);
+split_2(X, Y, [Z | L], R, Rs) ->
+ split_2_1(X, Y, L, R, Rs, Z);
+split_2(X, Y, [], R, Rs) ->
+ mergel([[Y, X | R] | Rs], []).
+
+split_2_1(X, Y, [Z | L], R, Rs, S) when Z =< Y ->
+ split_2_1(Y, Z, L, [X | R], Rs, S);
+split_2_1(X, Y, [Z | L], R, Rs, S) when Z =< X ->
+ split_2_1(Z, Y, L, [X | R], Rs, S);
+split_2_1(X, Y, [Z | L], R, Rs, S) when S > Z ->
+ split_2(S, Z, L, [], [[Y, X | R] | Rs]);
+split_2_1(X, Y, [Z | L], R, Rs, S) ->
+ split_2(Z, S, L, [], [[Y, X | R] | Rs]);
+split_2_1(X, Y, [], R, Rs, S) ->
+ mergel([[S], [Y, X | R] | Rs], []).
+
+%% merge/1
+
+mergel([[] | L], Acc) ->
+ mergel(L, Acc);
+mergel([T1, [H2 | T2], [H3 | T3] | L], Acc) ->
+ mergel(L, [merge3_1(T1, [], H2, T2, H3, T3) | Acc]);
+mergel([T1, [H2 | T2]], Acc) ->
+ rmergel([merge2_1(T1, H2, T2, []) | Acc], []);
+mergel([L], []) ->
+ L;
+mergel([L], Acc) ->
+ rmergel([lists:reverse(L, []) | Acc], []);
+mergel([], []) ->
+ [];
+mergel([], Acc) ->
+ rmergel(Acc, []);
+mergel([A, [] | L], Acc) ->
+ mergel([A | L], Acc);
+mergel([A, B, [] | L], Acc) ->
+ mergel([A, B | L], Acc).
+
+rmergel([[H3 | T3], [H2 | T2], T1 | L], Acc) ->
+ rmergel(L, [rmerge3_1(T1, [], H2, T2, H3, T3) | Acc]);
+rmergel([[H2 | T2], T1], Acc) ->
+ mergel([rmerge2_1(T1, H2, T2, []) | Acc], []);
+rmergel([L], Acc) ->
+ mergel([lists:reverse(L, []) | Acc], []);
+rmergel([], Acc) ->
+ mergel(Acc, []).
+
+%% merge3/3
+
+%% Take L1 apart.
+merge3_1([H1 | T1], M, H2, T2, H3, T3) when H1 =< H2 ->
+ merge3_12(T1, H1, H2, T2, H3, T3, M);
+merge3_1([H1 | T1], M, H2, T2, H3, T3) ->
+ merge3_21(T1, H1, H2, T2, H3, T3, M);
+merge3_1([], M, H2, T2, H3, T3) when H2 =< H3 ->
+ merge2_1(T2, H3, T3, [H2 | M]);
+merge3_1([], M, H2, T2, H3, T3) ->
+ merge2_2(T2, H3, T3, M, H2).
+
+%% Take L2 apart.
+merge3_2(T1, H1, M, [H2 | T2], H3, T3) when H1 =< H2 ->
+ merge3_12(T1, H1, H2, T2, H3, T3, M);
+merge3_2(T1, H1, M, [H2 | T2], H3, T3) ->
+ merge3_21(T1, H1, H2, T2, H3, T3, M);
+merge3_2(T1, H1, M, [], H3, T3) when H1 =< H3 ->
+ merge2_1(T1, H3, T3, [H1 | M]);
+merge3_2(T1, H1, M, [], H3, T3) ->
+ merge2_2(T1, H3, T3, M, H1).
+
+% H1 =< H2. Inlined.
+merge3_12(T1, H1, H2, T2, H3, T3, M) when H1 =< H3 ->
+ merge3_1(T1, [H1 | M], H2, T2, H3, T3);
+merge3_12(T1, H1, H2, T2, H3, T3, M) ->
+ merge3_12_3(T1, H1, H2, T2, [H3 | M], T3).
+
+% H1 =< H2, take L3 apart.
+merge3_12_3(T1, H1, H2, T2, M, [H3 | T3]) when H1 =< H3 ->
+ merge3_1(T1, [H1 | M], H2, T2, H3, T3);
+merge3_12_3(T1, H1, H2, T2, M, [H3 | T3]) ->
+ merge3_12_3(T1, H1, H2, T2, [H3 | M], T3);
+merge3_12_3(T1, H1, H2, T2, M, []) ->
+ merge2_1(T1, H2, T2, [H1 | M]).
+
+% H1 > H2. Inlined.
+merge3_21(T1, H1, H2, T2, H3, T3, M) when H2 =< H3 ->
+ merge3_2(T1, H1, [H2 | M], T2, H3, T3);
+merge3_21(T1, H1, H2, T2, H3, T3, M) ->
+ merge3_21_3(T1, H1, H2, T2, [H3 | M], T3).
+
+% H1 > H2, take L3 apart.
+merge3_21_3(T1, H1, H2, T2, M, [H3 | T3]) when H2 =< H3 ->
+ merge3_2(T1, H1, [H2 | M], T2, H3, T3);
+merge3_21_3(T1, H1, H2, T2, M, [H3 | T3]) ->
+ merge3_21_3(T1, H1, H2, T2, [H3 | M], T3);
+merge3_21_3(T1, H1, H2, T2, M, []) ->
+ merge2_2(T1, H2, T2, M, H1).
+
+%% rmerge/3
+
+%% Take L1 apart.
+rmerge3_1([H1 | T1], M, H2, T2, H3, T3) when H1 =< H2 ->
+ rmerge3_12(T1, H1, H2, T2, H3, T3, M);
+rmerge3_1([H1 | T1], M, H2, T2, H3, T3) ->
+ rmerge3_21(T1, H1, H2, T2, H3, T3, M);
+rmerge3_1([], M, H2, T2, H3, T3) when H2 =< H3 ->
+ rmerge2_2(T2, H3, T3, M, H2);
+rmerge3_1([], M, H2, T2, H3, T3) ->
+ rmerge2_1(T2, H3, T3, [H2 | M]).
+
+%% Take L2 apart.
+rmerge3_2(T1, H1, M, [H2 | T2], H3, T3) when H1 =< H2 ->
+ rmerge3_12(T1, H1, H2, T2, H3, T3, M);
+rmerge3_2(T1, H1, M, [H2 | T2], H3, T3) ->
+ rmerge3_21(T1, H1, H2, T2, H3, T3, M);
+rmerge3_2(T1, H1, M, [], H3, T3) when H1 =< H3 ->
+ rmerge2_2(T1, H3, T3, M, H1);
+rmerge3_2(T1, H1, M, [], H3, T3) ->
+ rmerge2_1(T1, H3, T3, [H1 | M]).
+
+% H1 =< H2. Inlined.
+rmerge3_12(T1, H1, H2, T2, H3, T3, M) when H2 =< H3 ->
+ rmerge3_12_3(T1, H1, H2, T2, [H3 | M], T3);
+rmerge3_12(T1, H1, H2, T2, H3, T3, M) ->
+ rmerge3_2(T1, H1, [H2 | M], T2, H3, T3).
+
+% H1 =< H2, take L3 apart.
+rmerge3_12_3(T1, H1, H2, T2, M, [H3 | T3]) when H2 =< H3 ->
+ rmerge3_12_3(T1, H1, H2, T2, [H3 | M], T3);
+rmerge3_12_3(T1, H1, H2, T2, M, [H3 | T3]) ->
+ rmerge3_2(T1, H1, [H2 | M], T2, H3, T3);
+rmerge3_12_3(T1, H1, H2, T2, M, []) ->
+ rmerge2_2(T1, H2, T2, M, H1).
+
+% H1 > H2. Inlined.
+rmerge3_21(T1, H1, H2, T2, H3, T3, M) when H1 =< H3 ->
+ rmerge3_21_3(T1, H1, H2, T2, [H3 | M], T3);
+rmerge3_21(T1, H1, H2, T2, H3, T3, M) ->
+ rmerge3_1(T1, [H1 | M], H2, T2, H3, T3).
+
+% H1 > H2, take L3 apart.
+rmerge3_21_3(T1, H1, H2, T2, M, [H3 | T3]) when H1 =< H3 ->
+ rmerge3_21_3(T1, H1, H2, T2, [H3 | M], T3);
+rmerge3_21_3(T1, H1, H2, T2, M, [H3 | T3]) ->
+ rmerge3_1(T1, [H1 | M], H2, T2, H3, T3);
+rmerge3_21_3(T1, H1, H2, T2, M, []) ->
+ rmerge2_1(T1, H2, T2, [H1 | M]).
+
+%% merge/2
+
+merge2_1([H1 | T1], H2, T2, M) when H1 =< H2 ->
+ merge2_1(T1, H2, T2, [H1 | M]);
+merge2_1([H1 | T1], H2, T2, M) ->
+ merge2_2(T1, H2, T2, M, H1);
+merge2_1([], H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+merge2_2(T1, HdM, [H2 | T2], M, H1) when H1 =< H2 ->
+ merge2_1(T1, H2, T2, [H1, HdM | M]);
+merge2_2(T1, HdM, [H2 | T2], M, H1) ->
+ merge2_2(T1, H2, T2, [HdM | M], H1);
+merge2_2(T1, HdM, [], M, H1) ->
+ lists:reverse(T1, [H1, HdM | M]).
+
+%% rmerge/2
+
+rmerge2_1([H1 | T1], H2, T2, M) when H1 =< H2 ->
+ rmerge2_2(T1, H2, T2, M, H1);
+rmerge2_1([H1 | T1], H2, T2, M) ->
+ rmerge2_1(T1, H2, T2, [H1 | M]);
+rmerge2_1([], H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+rmerge2_2(T1, HdM, [H2 | T2], M, H1) when H1 =< H2 ->
+ rmerge2_2(T1, H2, T2, [HdM | M], H1);
+rmerge2_2(T1, HdM, [H2 | T2], M, H1) ->
+ rmerge2_1(T1, H2, T2, [H1, HdM | M]);
+rmerge2_2(T1, HdM, [], M, H1) ->
+ lists:reverse(T1, [H1, HdM | M]).
+
+%% usort/1
+
+%% Ascending.
+usplit_1(X, Y, [Z | L], R, Rs) when Z > Y ->
+ usplit_1(Y, Z, L, [X | R], Rs);
+usplit_1(X, Y, [Z | L], R, Rs) when Z == Y ->
+ usplit_1(X, Y, L, R, Rs);
+usplit_1(X, Y, [Z | L], R, Rs) when Z > X ->
+ usplit_1(Z, Y, L, [X | R], Rs);
+usplit_1(X, Y, [Z | L], R, Rs) when Z == X ->
+ usplit_1(X, Y, L, R, Rs);
+usplit_1(X, Y, [Z | L], [], Rs) ->
+ usplit_1(X, Y, L, [Z], Rs);
+usplit_1(X, Y, [Z | L], R, Rs) ->
+ usplit_1_1(X, Y, L, R, Rs, Z);
+usplit_1(X, Y, [], R, Rs) ->
+ rumergel([[Y, X | R] | Rs], [], asc).
+
+usplit_1_1(X, Y, [Z | L], R, Rs, S) when Z > Y ->
+ usplit_1_1(Y, Z, L, [X | R], Rs, S);
+usplit_1_1(X, Y, [Z | L], R, Rs, S) when Z == Y ->
+ usplit_1_1(X, Y, L, R, Rs, S);
+usplit_1_1(X, Y, [Z | L], R, Rs, S) when Z > X ->
+ usplit_1_1(Z, Y, L, [X | R], Rs, S);
+usplit_1_1(X, Y, [Z | L], R, Rs, S) when Z == X ->
+ usplit_1_1(X, Y, L, R, Rs, S);
+usplit_1_1(X, Y, [Z | L], R, Rs, S) when Z > S ->
+ usplit_1(S, Z, L, [], [[Y, X | R] | Rs]);
+usplit_1_1(X, Y, [Z | L], R, Rs, S) when Z == S ->
+ usplit_1_1(X, Y, L, R, Rs, S);
+usplit_1_1(X, Y, [Z | L], R, Rs, S) ->
+ usplit_1(Z, S, L, [], [[Y, X | R] | Rs]);
+usplit_1_1(X, Y, [], R, Rs, S) ->
+ rumergel([[S], [Y, X | R] | Rs], [], asc).
+
+%% Descending.
+usplit_2(X, Y, [Z | L], R, Rs) when Z < Y ->
+ usplit_2(Y, Z, L, [X | R], Rs);
+usplit_2(X, Y, [Z | L], R, Rs) when Z == Y ->
+ usplit_2(X, Y, L, R, Rs);
+usplit_2(X, Y, [Z | L], R, Rs) when Z < X ->
+ usplit_2(Z, Y, L, [X | R], Rs);
+usplit_2(X, Y, [Z | L], R, Rs) when Z == X ->
+ usplit_2(X, Y, L, R, Rs);
+usplit_2(X, Y, [Z | L], [], Rs) ->
+ usplit_2(X, Y, L, [Z], Rs);
+usplit_2(X, Y, [Z | L], R, Rs) ->
+ usplit_2_1(X, Y, L, R, Rs, Z);
+usplit_2(X, Y, [], R, Rs) ->
+ umergel([[Y, X | R] | Rs], [], desc).
+
+usplit_2_1(X, Y, [Z | L], R, Rs, S) when Z < Y ->
+ usplit_2_1(Y, Z, L, [X | R], Rs, S);
+usplit_2_1(X, Y, [Z | L], R, Rs, S) when Z == Y ->
+ usplit_2_1(X, Y, L, R, Rs, S);
+usplit_2_1(X, Y, [Z | L], R, Rs, S) when Z < X ->
+ usplit_2_1(Z, Y, L, [X | R], Rs, S);
+usplit_2_1(X, Y, [Z | L], R, Rs, S) when Z == X ->
+ usplit_2_1(X, Y, L, R, Rs, S);
+usplit_2_1(X, Y, [Z | L], R, Rs, S) when Z < S ->
+ usplit_2(S, Z, L, [], [[Y, X | R] | Rs]);
+usplit_2_1(X, Y, [Z | L], R, Rs, S) when Z == S ->
+ usplit_2_1(X, Y, L, R, Rs, S);
+usplit_2_1(X, Y, [Z | L], R, Rs, S) ->
+ usplit_2(Z, S, L, [], [[Y, X | R] | Rs]);
+usplit_2_1(X, Y, [], R, Rs, S) ->
+ umergel([[S], [Y, X | R] | Rs], [], desc).
+
+%% umerge/1
+
+umergel(L) ->
+ umergel(L, [], asc).
+
+umergel([[] | L], Acc, O) ->
+ umergel(L, Acc, O);
+umergel([T1, [H2 | T2], [H3 | T3] | L], Acc, asc) ->
+ umergel(L, [umerge3_1(T1, [H2 | H3], T2, H2, [], T3, H3) | Acc], asc);
+umergel([[H3 | T3], [H2 | T2], T1 | L], Acc, desc) ->
+ umergel(L, [umerge3_1(T1, [H2 | H3], T2, H2, [], T3, H3) | Acc], desc);
+umergel([A, [] | L], Acc, O) ->
+ umergel([A | L], Acc, O);
+umergel([A, B, [] | L], Acc, O) ->
+ umergel([A, B | L], Acc, O);
+umergel([[H1 | T1], T2 | L], Acc, asc) ->
+ umergel(L, [umerge2_2(T1, T2, [], H1) | Acc], asc);
+umergel([T2, [H1 | T1] | L], Acc, desc) ->
+ umergel(L, [umerge2_2(T1, T2, [], H1) | Acc], desc);
+umergel([L], [], _O) ->
+ L;
+umergel([L], Acc, O) ->
+ rumergel([lists:reverse(L, []) | Acc], [], O);
+umergel([], [], _O) ->
+ [];
+umergel([], Acc, O) ->
+ rumergel(Acc, [], O).
+
+rumergel([[H3 | T3], [H2 | T2], T1 | L], Acc, asc) ->
+ rumergel(L, [rumerge3_1(T1, T2, H2, [], T3, H3) | Acc], asc);
+rumergel([T1, [H2 | T2], [H3 | T3] | L], Acc, desc) ->
+ rumergel(L, [rumerge3_1(T1, T2, H2, [], T3, H3) | Acc], desc);
+rumergel([[H2 | T2], T1 | L], Acc, asc) ->
+ rumergel(L, [rumerge2_1(T1, T2, [], H2) | Acc], asc);
+rumergel([T1, [H2 | T2] | L], Acc, desc) ->
+ rumergel(L, [rumerge2_1(T1, T2, [], H2) | Acc], desc);
+rumergel([L], Acc, O) ->
+ umergel([lists:reverse(L, []) | Acc], [], O);
+rumergel([], Acc, O) ->
+ umergel(Acc, [], O).
+
+%% umerge3/3
+
+%% Take L1 apart.
+umerge3_1([H1 | T1], HdM, T2, H2, M, T3, H3) when H1 =< H2 ->
+ umerge3_12(T1, H1, T2, H2, M, T3, H3, HdM);
+umerge3_1([H1 | T1], HdM, T2, H2, M, T3, H3) when H2 == HdM ->
+ umerge3_2(T1, H1, T2, H2, M, T3, H3);
+umerge3_1([H1 | T1], HdM, T2, H2, M, T3, H3) ->
+ umerge3_21(T1, H1, T2, H2, M, T3, H3, HdM);
+umerge3_1([], HdM, T2, H2, M, T3, H3) when H2 == HdM ->
+ umerge2_1(T2, T3, M, HdM, H3);
+umerge3_1([], _HdM, T2, H2, M, T3, H3) when H2 =< H3 ->
+ umerge2_1(T2, T3, [H2 | M], H2, H3);
+umerge3_1([], HdM, T2, H2, M, T3, H3) when H3 == HdM ->
+ umerge2_2(T2, T3, M, H2);
+umerge3_1([], _HdM, T2, H2, M, T3, H3) ->
+ umerge2_2(T2, T3, [H3 | M], H2).
+
+%% Take L2 apart.
+umerge3_2(T1, H1, [H2 | T2], HdM, M, T3, H3) when H1 =< H2 ->
+ umerge3_12(T1, H1, T2, H2, M, T3, H3, HdM);
+umerge3_2(T1, H1, [H2 | T2], HdM, M, T3, H3) ->
+ umerge3_21(T1, H1, T2, H2, M, T3, H3, HdM);
+umerge3_2(T1, H1, [], _HdM, M, T3, H3) when H1 =< H3 ->
+ umerge2_1(T1, T3, [H1 | M], H1, H3);
+umerge3_2(T1, H1, [], HdM, M, T3, H3) when H3 == HdM ->
+ umerge2_2(T1, T3, M, H1);
+umerge3_2(T1, H1, [], _HdM, M, T3, H3) ->
+ umerge2_2(T1, T3, [H3 | M], H1).
+
+% H1 =< H2. Inlined.
+umerge3_12(T1, H1, T2, H2, M, T3, H3, _HdM) when H1 =< H3 ->
+ umerge3_1(T1, H1, T2, H2, [H1 | M], T3, H3);
+umerge3_12(T1, H1, T2, H2, M, T3, H3, HdM) when H3 == HdM ->
+ umerge3_12_3(T1, H1, T2, H2, M, T3);
+umerge3_12(T1, H1, T2, H2, M, T3, H3, _HdM) ->
+ umerge3_12_3(T1, H1, T2, H2, [H3 | M], T3).
+
+% H1 =< H2, take L3 apart.
+umerge3_12_3(T1, H1, T2, H2, M, [H3 | T3]) when H1 =< H3 ->
+ umerge3_1(T1, H1, T2, H2, [H1 | M], T3, H3);
+umerge3_12_3(T1, H1, T2, H2, M, [H3 | T3]) ->
+ umerge3_12_3(T1, H1, T2, H2, [H3 | M], T3);
+umerge3_12_3(T1, H1, T2, H2, M, []) ->
+ umerge2_1(T1, T2, [H1 | M], H1, H2).
+
+% H1 > H2. Inlined.
+umerge3_21(T1, H1, T2, H2, M, T3, H3, _HdM) when H2 =< H3 ->
+ umerge3_2(T1, H1, T2, H2, [H2 | M], T3, H3);
+umerge3_21(T1, H1, T2, H2, M, T3, H3, HdM) when H3 == HdM ->
+ umerge3_21_3(T1, H1, T2, H2, M, T3);
+umerge3_21(T1, H1, T2, H2, M, T3, H3, _HdM) ->
+ umerge3_21_3(T1, H1, T2, H2, [H3 | M], T3).
+
+% H1 > H2, take L3 apart.
+umerge3_21_3(T1, H1, T2, H2, M, [H3 | T3]) when H2 =< H3 ->
+ umerge3_2(T1, H1, T2, H2, [H2 | M], T3, H3);
+umerge3_21_3(T1, H1, T2, H2, M, [H3 | T3]) ->
+ umerge3_21_3(T1, H1, T2, H2, [H3 | M], T3);
+umerge3_21_3(T1, H1, T2, H2, M, []) ->
+ umerge2_2(T1, T2, [H2 | M], H1).
+
+%% Take L1 apart.
+rumerge3_1([H1 | T1], T2, H2, M, T3, H3) when H1 =< H2 ->
+ rumerge3_12a(T1, H1, T2, H2, M, T3, H3);
+rumerge3_1([H1 | T1], T2, H2, M, T3, H3) when H1 =< H3 ->
+ rumerge3_21_3(T1, T2, H2, M, T3, H3, H1);
+rumerge3_1([H1 | T1], T2, H2, M, T3, H3) ->
+ rumerge3_1(T1, T2, H2, [H1 | M], T3, H3);
+rumerge3_1([], T2, H2, M, T3, H3) when H2 =< H3 ->
+ rumerge2_2(T2, T3, M, H3, H2);
+rumerge3_1([], T2, H2, M, T3, H3) ->
+ rumerge2_1(T2, T3, [H2 | M], H3).
+
+% H1 =< H2. Inlined.
+rumerge3_12a(T1, H1, T2, H2, M, T3, H3) when H2 =< H3 ->
+ rumerge3_12_3(T1, T2, H2, M, T3, H3, H1);
+rumerge3_12a(T1, H1, T2, H2, M, T3, H3) ->
+ rumerge3_2(T1, T2, H2, M, T3, H3, H1).
+
+%% Take L2 apart. H2M > H3. H2M > H2.
+rumerge3_2(T1, [H2 | T2], H2M, M, T3, H3, H1) when H1 =< H2 ->
+ % H2M > H1.
+ rumerge3_12b(T1, H1, T2, H2, M, T3, H3, H2M);
+rumerge3_2(T1, [H2 | T2], H2M, M, T3, H3, H1) when H1 == H2M ->
+ rumerge3_1(T1, T2, H2, [H1 | M], T3, H3);
+rumerge3_2(T1, [H2 | T2], H2M, M, T3, H3, H1) when H1 =< H3 ->
+ % H2M > H1.
+ rumerge3_21_3(T1, T2, H2, [H2M | M], T3, H3, H1);
+rumerge3_2(T1, [H2 | T2], H2M, M, T3, H3, H1) ->
+ % H2M > H1.
+ rumerge3_1(T1, T2, H2, [H1, H2M | M], T3, H3);
+rumerge3_2(T1, [], H2M, M, T3, H3, H1) when H1 == H2M ->
+ rumerge2_1(T1, T3, [H1 | M], H3);
+rumerge3_2(T1, [], H2M, M, T3, H3, H1) when H1 =< H3 ->
+ rumerge2_2(T1, T3, [H2M | M], H3, H1);
+rumerge3_2(T1, [], H2M, M, T3, H3, H1) ->
+ rumerge2_1(T1, T3, [H1, H2M | M], H3).
+
+% H1 =< H2. Inlined.
+rumerge3_12b(T1, H1, T2, H2, M, T3, H3, H2M) when H2 =< H3 ->
+ rumerge3_12_3(T1, T2, H2, [H2M | M], T3, H3, H1);
+rumerge3_12b(T1, H1, T2, H2, M, T3, H3, H2M) ->
+ rumerge3_2(T1, T2, H2, [H2M | M], T3, H3, H1).
+
+% H1 =< H2, take L3 apart.
+rumerge3_12_3(T1, T2, H2, M, [H3 | T3], H3M, H1) when H2 =< H3 ->
+ rumerge3_12_3(T1, T2, H2, [H3M | M], T3, H3, H1);
+rumerge3_12_3(T1, T2, H2, M, [H3 | T3], H3M, H1) when H2 == H3M ->
+ rumerge3_2(T1, T2, H2, M, T3, H3, H1);
+rumerge3_12_3(T1, T2, H2, M, [H3 | T3], H3M, H1) ->
+ rumerge3_2(T1, T2, H2, [H3M | M], T3, H3, H1);
+rumerge3_12_3(T1, T2, H2, M, [], H3M, H1) when H2 == H3M ->
+ rumerge2_2(T1, T2, M, H2, H1);
+rumerge3_12_3(T1, T2, H2, M, [], H3M, H1) ->
+ rumerge2_2(T1, T2, [H3M | M], H2, H1).
+
+% H1 > H2, take L3 apart.
+rumerge3_21_3(T1, T2, H2, M, [H3 | T3], H3M, H1) when H1 =< H3 ->
+ rumerge3_21_3(T1, T2, H2, [H3M | M], T3, H3, H1);
+rumerge3_21_3(T1, T2, H2, M, [H3 | T3], H3M, H1) when H1 == H3M ->
+ rumerge3_1(T1, T2, H2, [H1 | M], T3, H3);
+rumerge3_21_3(T1, T2, H2, M, [H3 | T3], H3M, H1) ->
+ rumerge3_1(T1, T2, H2, [H1, H3M | M], T3, H3);
+rumerge3_21_3(T1, T2, H2, M, [], H3M, H1) when H1 == H3M ->
+ rumerge2_1(T1, T2, [H1 | M], H2);
+rumerge3_21_3(T1, T2, H2, M, [], H3M, H1) ->
+ rumerge2_1(T1, T2, [H1, H3M | M], H2).
+
+%% umerge/2
+
+%% Elements from the first list are kept and prioritized.
+umerge2_1([H1 | T1], T2, M, _HdM, H2) when H1 =< H2 ->
+ umerge2_1(T1, T2, [H1 | M], H1, H2);
+umerge2_1([H1 | T1], T2, M, HdM, H2) when H2 == HdM ->
+ umerge2_2(T1, T2, M, H1);
+umerge2_1([H1 | T1], T2, M, _HdM, H2) ->
+ umerge2_2(T1, T2, [H2 | M], H1);
+umerge2_1([], T2, M, HdM, H2) when H2 == HdM ->
+ lists:reverse(T2, M);
+umerge2_1([], T2, M, _HdM, H2) ->
+ lists:reverse(T2, [H2 | M]).
+
+umerge2_2(T1, [H2 | T2], M, H1) when H1 =< H2 ->
+ umerge2_1(T1, T2, [H1 | M], H1, H2);
+umerge2_2(T1, [H2 | T2], M, H1) ->
+ umerge2_2(T1, T2, [H2 | M], H1);
+umerge2_2(T1, [], M, H1) ->
+ lists:reverse(T1, [H1 | M]).
+
+%% rumerge/2
+
+%% Elements from the first list are kept and prioritized.
+rumerge2_1([H1 | T1], T2, M, H2) when H1 =< H2 ->
+ rumerge2_2(T1, T2, M, H2, H1);
+rumerge2_1([H1 | T1], T2, M, H2) ->
+ rumerge2_1(T1, T2, [H1 | M], H2);
+rumerge2_1([], T2, M, H2) ->
+ lists:reverse(T2, [H2 | M]).
+
+% H1 =< H2M.
+rumerge2_2(T1, [H2 | T2], M, H2M, H1) when H1 =< H2 ->
+ rumerge2_2(T1, T2, [H2M | M], H2, H1);
+rumerge2_2(T1, [H2 | T2], M, H2M, H1) when H1 == H2M ->
+ rumerge2_1(T1, T2, [H1 | M], H2);
+rumerge2_2(T1, [H2 | T2], M, H2M, H1) ->
+ rumerge2_1(T1, T2, [H1, H2M | M], H2);
+rumerge2_2(T1, [], M, H2M, H1) when H1 == H2M ->
+ lists:reverse(T1, [H1 | M]);
+rumerge2_2(T1, [], M, H2M, H1) ->
+ lists:reverse(T1, [H1, H2M | M]).
+
+%% keysort/2
+
+%% Ascending.
+keysplit_1(I, X, EX, Y, EY, [Z | L], R, Rs) ->
+ case element(I, Z) of
+ EZ when EY =< EZ ->
+ keysplit_1(I, Y, EY, Z, EZ, L, [X | R], Rs);
+ EZ when EX =< EZ ->
+ keysplit_1(I, Z, EZ, Y, EY, L, [X | R], Rs);
+ _EZ when R == [] ->
+ keysplit_1(I, X, EX, Y, EY, L, [Z], Rs);
+ EZ ->
+ keysplit_1_1(I, X, EX, Y, EY, EZ, R, Rs, Z, L)
+ end;
+keysplit_1(I, X, _EX, Y, _EY, [], R, Rs) ->
+ rkeymergel(I, [[Y, X | R] | Rs], [], asc).
+
+keysplit_1_1(I, X, EX, Y, EY, ES, R, Rs, S, [Z | L]) ->
+ case element(I, Z) of
+ EZ when EY =< EZ ->
+ keysplit_1_1(I, Y, EY, Z, EZ, ES, [X | R], Rs, S, L);
+ EZ when EX =< EZ ->
+ keysplit_1_1(I, Z, EZ, Y, EY, ES, [X | R], Rs, S, L);
+ EZ when ES =< EZ ->
+ keysplit_1(I, S, ES, Z, EZ, L, [], [[Y, X | R] | Rs]);
+ EZ ->
+ keysplit_1(I, Z, EZ, S, ES, L, [], [[Y, X | R] | Rs])
+ end;
+keysplit_1_1(I, X, _EX, Y, _EY, _ES, R, Rs, S, []) ->
+ rkeymergel(I, [[S], [Y, X | R] | Rs], [], asc).
+
+%% Descending.
+keysplit_2(I, X, EX, Y, EY, [Z | L], R, Rs) ->
+ case element(I, Z) of
+ EZ when EY > EZ ->
+ keysplit_2(I, Y, EY, Z, EZ, L, [X | R], Rs);
+ EZ when EX > EZ ->
+ keysplit_2(I, Z, EZ, Y, EY, L, [X | R], Rs);
+ _EZ when R == [] ->
+ keysplit_2(I, X, EX, Y, EY, L, [Z], Rs);
+ EZ ->
+ keysplit_2_1(I, X, EX, Y, EY, EZ, R, Rs, Z, L)
+ end;
+keysplit_2(I, X, _EX, Y, _EY, [], R, Rs) ->
+ keymergel(I, [[Y, X | R] | Rs], [], desc).
+
+keysplit_2_1(I, X, EX, Y, EY, ES, R, Rs, S, [Z | L]) ->
+ case element(I, Z) of
+ EZ when EY > EZ ->
+ keysplit_2_1(I, Y, EY, Z, EZ, ES, [X | R], Rs, S, L);
+ EZ when EX > EZ ->
+ keysplit_2_1(I, Z, EZ, Y, EY, ES, [X | R], Rs, S, L);
+ EZ when ES > EZ ->
+ keysplit_2(I, S, ES, Z, EZ, L, [], [[Y, X | R] | Rs]);
+ EZ ->
+ keysplit_2(I, Z, EZ, S, ES, L, [], [[Y, X | R] | Rs])
+ end;
+keysplit_2_1(I, X, _EX, Y, _EY, _ES, R, Rs, S, []) ->
+ keymergel(I, [[S], [Y, X | R] | Rs], [], desc).
+
+keymergel(I, [T1, [H2 | T2], [H3 | T3] | L], Acc, O) when O == asc ->
+ M = keymerge3_1(I, T1, [],O,element(I,H2), H2, T2, element(I,H3), H3, T3),
+ keymergel(I, L, [M | Acc], O);
+keymergel(I, [[H3 | T3], [H2 | T2], T1 | L], Acc, O) when O == desc ->
+ M = keymerge3_1(I, T1, [],O,element(I,H2), H2, T2, element(I,H3), H3, T3),
+ keymergel(I, L, [M | Acc], O);
+keymergel(I, [T1, [H2 | T2] | L], Acc, asc) ->
+ keymergel(I, L, [keymerge2_1(I, T1, element(I,H2),H2,T2,[]) | Acc], asc);
+keymergel(I, [[H2 | T2], T1 | L], Acc, desc) ->
+ keymergel(I, L, [keymerge2_1(I, T1, element(I,H2),H2,T2,[]) | Acc], desc);
+keymergel(_I, [L], [], _O) ->
+ L;
+keymergel(I, [L], Acc, O) ->
+ rkeymergel(I, [lists:reverse(L, []) | Acc], [], O);
+keymergel(I, [], Acc, O) ->
+ rkeymergel(I, Acc, [], O).
+
+rkeymergel(I, [[H3 | T3], [H2 | T2], T1 | L], Acc, O) when O == asc ->
+ M = rkeymerge3_1(I, T1, [],O,element(I,H2), H2, T2, element(I,H3), H3,T3),
+ rkeymergel(I, L, [M | Acc], O);
+rkeymergel(I, [T1, [H2 | T2], [H3 | T3] | L], Acc, O) when O == desc ->
+ M = rkeymerge3_1(I, T1, [],O,element(I,H2), H2, T2, element(I,H3), H3,T3),
+ rkeymergel(I, L, [M | Acc], O);
+rkeymergel(I, [[H2 | T2], T1 | L], Acc, asc) ->
+ rkeymergel(I, L, [rkeymerge2_1(I, T1, element(I,H2),H2,T2,[]) | Acc],asc);
+rkeymergel(I, [T1, [H2 | T2] | L], Acc, desc) ->
+ rkeymergel(I, L, [rkeymerge2_1(I,T1, element(I,H2),H2,T2,[]) | Acc],desc);
+rkeymergel(I, [L], Acc, O) ->
+ keymergel(I, [lists:reverse(L, []) | Acc], [], O);
+rkeymergel(I, [], Acc, O) ->
+ keymergel(I, Acc, [], O).
+
+%%% An extra argument, D, just to avoid some move instructions.
+
+%% Take L1 apart.
+keymerge3_1(I, [H1 | T1], M, D, E2, H2, T2, E3, H3, T3) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ keymerge3_12(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D);
+ E1 ->
+ keymerge3_21(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, T2)
+ end;
+keymerge3_1(I, [], M, _D, E2, H2, T2, E3, H3, T3) when E2 =< E3 ->
+ keymerge2_1(I, T2, E3, H3, T3, [H2 | M]);
+keymerge3_1(I, [], M, _D, E2, H2, T2, _E3, H3, T3) ->
+ keymerge2_2(I, T2, E2, H3, T3, M, H2).
+
+%% Take L2 apart.
+keymerge3_2(I, E1, H1, T1, [H2 | T2], M, D, E3, H3, T3) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ keymerge3_12(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, T1);
+ E2 ->
+ keymerge3_21(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D)
+ end;
+keymerge3_2(I, E1, H1, T1, [], M, _D, E3, H3, T3) when E1 =< E3 ->
+ keymerge2_1(I, T1, E3, H3, T3, [H1 | M]);
+keymerge3_2(I, E1, H1, T1, [], M, _D, _E3, H3, T3) ->
+ keymerge2_2(I, T1, E1, H3, T3, M, H1).
+
+% E1 =< E2. Inlined.
+keymerge3_12(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D) when E1 =< E3 ->
+ keymerge3_1(I, T1, [H1 | M], D, E2, H2, T2, E3, H3, T3);
+keymerge3_12(I, E1, H1, T1, E2, H2, T2, _E3, H3, T3, M, _D) ->
+ keymerge3_12_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M]).
+
+% E1 =< E2, take L3 apart.
+keymerge3_12_3(I, E1, H1, T1, E2, H2, T2, [H3 | T3], M) ->
+ case element(I, H3) of
+ E3 when E1 =< E3 ->
+ keymerge3_1(I, T1, [H1 | M], T1, E2, H2, T2, E3, H3, T3);
+ _E3 ->
+ keymerge3_12_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M])
+ end;
+keymerge3_12_3(I, _E1, H1, T1, E2, H2, T2, [], M) ->
+ keymerge2_1(I, T1, E2, H2, T2, [H1 | M]).
+
+% E1 > E2. Inlined.
+keymerge3_21(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D) when E2 =< E3 ->
+ keymerge3_2(I, E1, H1, T1, T2, [H2 | M], D, E3, H3, T3);
+keymerge3_21(I, E1, H1, T1, E2, H2, T2, _E3, H3, T3, M, _D) ->
+ keymerge3_21_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M]).
+
+% E1 > E2, take L3 apart.
+keymerge3_21_3(I, E1, H1, T1, E2, H2, T2, [H3 | T3], M) ->
+ case element(I, H3) of
+ E3 when E2 =< E3 ->
+ keymerge3_2(I, E1, H1, T1, T2, [H2 | M], T2, E3, H3, T3);
+ _E3 ->
+ keymerge3_21_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M])
+ end;
+keymerge3_21_3(I, E1, H1, T1, _E2, H2, T2, [], M) ->
+ keymerge2_2(I, T1, E1, H2, T2, M, H1).
+
+%% Take L1 apart.
+rkeymerge3_1(I, [H1 | T1], M, D, E2, H2, T2, E3, H3, T3) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ rkeymerge3_12(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, T2);
+ E1 ->
+ rkeymerge3_21(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D)
+ end;
+rkeymerge3_1(I, [], M, _D, E2, H2, T2, E3, H3, T3) when E2 =< E3 ->
+ rkeymerge2_2(I, E2, T2, H3, T3, M, H2);
+rkeymerge3_1(I, [], M, _D, _E2, H2, T2, E3, H3, T3) ->
+ rkeymerge2_1(I, T2, E3, H3, T3, [H2 | M]).
+
+%% Take L2 apart.
+rkeymerge3_2(I, E1, H1, T1, [H2 | T2], M, D, E3, H3, T3) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ rkeymerge3_12(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D);
+ E2 ->
+ rkeymerge3_21(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, T1)
+ end;
+rkeymerge3_2(I, E1, H1, T1, [], M, _D, E3, H3, T3) when E1 =< E3 ->
+ rkeymerge2_2(I, E1, T1, H3, T3, M, H1);
+rkeymerge3_2(I, _E1, H1, T1, [], M, _D, E3, H3, T3) ->
+ rkeymerge2_1(I, T1, E3, H3, T3, [H1 | M]).
+
+% E1 =< E2. Inlined.
+rkeymerge3_12(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, _D) when E2 =< E3 ->
+ rkeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M]);
+rkeymerge3_12(I, E1, H1, T1, _E2, H2, T2, E3, H3, T3, M, D) ->
+ rkeymerge3_2(I, E1, H1, T1, T2, [H2 | M], D, E3, H3, T3).
+
+% E1 =< E2, take L3 apart.
+rkeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, [H3 | T3], M) ->
+ case element(I, H3) of
+ E3 when E2 =< E3 ->
+ rkeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M]);
+ E3 ->
+ rkeymerge3_2(I, E1, H1, T1, T2, [H2 | M], T2, E3, H3, T3)
+ end;
+rkeymerge3_12_3(I, E1, H1, T1, _E2, H2, T2, [], M) ->
+ rkeymerge2_2(I, E1, T1, H2, T2, M, H1).
+
+% E1 > E2. Inlined.
+rkeymerge3_21(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, _D) when E1 =< E3 ->
+ rkeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M]);
+rkeymerge3_21(I, _E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D) ->
+ rkeymerge3_1(I, T1, [H1 | M], D, E2, H2, T2, E3, H3, T3).
+
+% E1 > E2, take L3 apart.
+rkeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, [H3 | T3], M) ->
+ case element(I, H3) of
+ E3 when E1 =< E3 ->
+ rkeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, T3, [H3 | M]);
+ E3 ->
+ rkeymerge3_1(I, T1, [H1 | M], T1, E2, H2, T2, E3, H3, T3)
+ end;
+rkeymerge3_21_3(I, _E1, H1, T1, E2, H2, T2, [], M) ->
+ rkeymerge2_1(I, T1, E2, H2, T2, [H1 | M]).
+
+%% keymerge/3
+
+%% Elements from the first list are prioritized.
+keymerge2_1(I, [H1 | T1], E2, H2, T2, M) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ keymerge2_1(I, T1, E2, H2, T2, [H1 | M]);
+ E1 ->
+ keymerge2_2(I, T1, E1, H2, T2, M, H1)
+ end;
+keymerge2_1(_I, [], _E2, H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+keymerge2_2(I, T1, E1, HdM, [H2 | T2], M, H1) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ keymerge2_1(I, T1, E2, H2, T2, [H1, HdM | M]);
+ _E2 ->
+ keymerge2_2(I, T1, E1, H2, T2, [HdM | M], H1)
+ end;
+keymerge2_2(_I, T1, _E1, HdM, [], M, H1) ->
+ lists:reverse(T1, [H1, HdM | M]).
+
+%% rkeymerge/3
+
+rkeymerge2_1(I, [H1 | T1], E2, H2, T2, M) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ rkeymerge2_2(I, E1, T1, H2, T2, M, H1);
+ _E1 ->
+ rkeymerge2_1(I, T1, E2, H2, T2, [H1 | M])
+ end;
+rkeymerge2_1(_I, [], _E2, H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+rkeymerge2_2(I, E1, T1, HdM, [H2 | T2], M, H1) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ rkeymerge2_2(I, E1, T1, H2, T2, [HdM | M], H1);
+ E2 ->
+ rkeymerge2_1(I, T1, E2, H2, T2, [H1, HdM | M])
+ end;
+rkeymerge2_2(_I, _E1, T1, HdM, [], M, H1) ->
+ lists:reverse(T1, [H1, HdM | M]).
+
+%% ukeysort/2
+
+%% Ascending.
+ukeysplit_1(I, X, EX, Y, EY, [Z | L], R, Rs) ->
+ case element(I, Z) of
+ EZ when EY == EZ ->
+ ukeysplit_1(I, X, EX, Y, EY, L, R, Rs);
+ EZ when EY < EZ ->
+ ukeysplit_1(I, Y, EY, Z, EZ, L, [X | R], Rs);
+ EZ when EX == EZ ->
+ ukeysplit_1(I, X, EX, Y, EY, L, R, Rs);
+ EZ when EX < EZ ->
+ ukeysplit_1(I, Z, EZ, Y, EY, L, [X | R], Rs);
+ _EZ when R == [] ->
+ ukeysplit_1(I, X, EX, Y, EY, L, [Z], Rs);
+ EZ ->
+ ukeysplit_1_1(I, X, EX, Y, EY, L, R, Rs, Z, EZ)
+ end;
+ukeysplit_1(I, X, _EX, Y, _EY, [], R, Rs) ->
+ rukeymergel(I, [[Y, X | R] | Rs], []).
+
+ukeysplit_1_1(I, X, EX, Y, EY, [Z | L], R, Rs, S, ES) ->
+ case element(I, Z) of
+ EZ when EY == EZ ->
+ ukeysplit_1_1(I, X, EX, Y, EY, L, R, Rs, S, ES);
+ EZ when EY < EZ ->
+ ukeysplit_1_1(I, Y, EY, Z, EZ, L, [X | R], Rs, S, ES);
+ EZ when EX == EZ ->
+ ukeysplit_1_1(I, X, EX, Y, EY, L, R, Rs, S, ES);
+ EZ when EX < EZ ->
+ ukeysplit_1_1(I, Z, EZ, Y, EY, L, [X | R], Rs, S, ES);
+ EZ when ES == EZ ->
+ ukeysplit_1_1(I, X, EX, Y, EY, L, R, Rs, S, ES);
+ EZ when ES < EZ ->
+ ukeysplit_1(I, S, ES, Z, EZ, L, [], [[Y, X | R] | Rs]);
+ EZ ->
+ ukeysplit_1(I, Z, EZ, S, ES, L, [], [[Y, X | R] | Rs])
+ end;
+ukeysplit_1_1(I, X, _EX, Y, _EY, [], R, Rs, S, _ES) ->
+ rukeymergel(I, [[S], [Y, X | R] | Rs], []).
+
+%% Descending.
+ukeysplit_2(I, Y, EY, [Z | L], R) ->
+ case element(I, Z) of
+ EZ when EY == EZ ->
+ ukeysplit_2(I, Y, EY, L, R);
+ EZ when EY < EZ ->
+ ukeysplit_1(I, Y, EY, Z, EZ, L, [], [lists:reverse(R, [])]);
+ EZ ->
+ ukeysplit_2(I, Z, EZ, L, [Y | R])
+ end;
+ukeysplit_2(_I, Y, _EY, [], R) ->
+ [Y | R].
+
+ukeymergel(I, [T1, [H2 | T2], [H3 | T3] | L], Acc) ->
+ %% The fourth argument, [H2 | H3] (=HdM), may confuse type
+ %% checkers. Its purpose is to ensure that the tests H2 == HdM
+ %% and H3 == HdM in ukeymerge3_1 will always fail as long as M == [].
+ M = ukeymerge3_1(I, T1, Acc, [H2 | H3], element(I, H2), H2, T2, [],
+ element(I, H3), H3, T3),
+ ukeymergel(I, L, [M | Acc]);
+ukeymergel(I, [[H1 | T1], T2 | L], Acc) ->
+ ukeymergel(I, L, [ukeymerge2_2(I, T1, element(I, H1), H1, T2, []) | Acc]);
+ukeymergel(_I, [L], []) ->
+ L;
+ukeymergel(I, [L], Acc) ->
+ rukeymergel(I, [lists:reverse(L, []) | Acc], []);
+ukeymergel(I, [], Acc) ->
+ rukeymergel(I, Acc, []).
+
+rukeymergel(I, [[H3 | T3], [H2 | T2], T1 | L], Acc) ->
+ M = rukeymerge3_1(I, T1, Acc, [], element(I, H2), H2, T2, [],
+ element(I, H3), H3, T3),
+ rukeymergel(I, L, [M | Acc]);
+rukeymergel(I, [[H2 | T2], T1 | L], Acc) ->
+ rukeymergel(I, L, [rukeymerge2_1(I, T1, element(I,H2), T2, [], H2)|Acc]);
+rukeymergel(I, [L], Acc) ->
+ ukeymergel(I, [lists:reverse(L, []) | Acc], []);
+rukeymergel(I, [], Acc) ->
+ ukeymergel(I, Acc, []).
+
+%%% An extra argument, D, just to avoid some move instructions.
+
+%% Take L1 apart.
+ukeymerge3_1(I, [H1 | T1], D, HdM, E2, H2, T2, M, E3, H3, T3) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ ukeymerge3_12(I, E1, T1, H1, E2, H2, T2, E3, H3, T3, M, HdM, D);
+ E1 when E2 == HdM ->
+ ukeymerge3_2(I, E1, T1, H1, T2, HdM, T2, M, E3, H3, T3);
+ E1 ->
+ ukeymerge3_21(I, E1, T1, H1, E2, H2, T2, E3, H3, T3, M, HdM, T2)
+ end;
+ukeymerge3_1(I, [], _D, HdM, E2, _H2, T2, M, E3, H3, T3) when E2 == HdM ->
+ ukeymerge2_1(I, T2, E3, HdM, T3, M, H3);
+ukeymerge3_1(I, [], _D, _HdM, E2, H2, T2, M, E3, H3, T3) when E2 =< E3 ->
+ ukeymerge2_1(I, T2, E3, E2, T3, [H2 | M], H3);
+ukeymerge3_1(I, [], _D, HdM, E2, H2, T2, M, E3, _H3, T3) when E3 == HdM ->
+ ukeymerge2_2(I, T2, E2, H2, T3, M);
+ukeymerge3_1(I, [], _D, _HdM, E2, H2, T2, M, _E3, H3, T3) ->
+ ukeymerge2_2(I, T2, E2, H2, T3, [H3 | M]).
+
+%% Take L2 apart.
+ukeymerge3_2(I, E1, T1, H1, [H2 | T2], HdM, D, M, E3, H3, T3) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ ukeymerge3_12(I, E1, T1, H1, E2, H2, T2, E3, H3, T3, M, HdM, T1);
+ E2 ->
+ ukeymerge3_21(I, E1, T1, H1, E2, H2, T2, E3, H3, T3, M, HdM, D)
+ end;
+ukeymerge3_2(I, E1, T1, H1, [], _HdM, _D, M, E3, H3, T3) when E1 =< E3 ->
+ ukeymerge2_1(I, T1, E3, E1, T3, [H1 | M], H3);
+ukeymerge3_2(I, E1, T1, H1, [], HdM, _D, M, E3, _H3, T3) when E3 == HdM ->
+ ukeymerge2_2(I, T1, E1, H1, T3, M);
+ukeymerge3_2(I, E1, T1, H1, [], _HdM, _D, M, _E3, H3, T3) ->
+ ukeymerge2_2(I, T1, E1, H1, T3, [H3 | M]).
+
+% E1 =< E2. Inlined.
+ukeymerge3_12(I, E1, T1, H1, E2, H2, T2, E3, H3, T3, M, _HdM, D)
+ when E1 =< E3 ->
+ ukeymerge3_1(I, T1, D, E1, E2, H2, T2, [H1 | M], E3, H3, T3);
+ukeymerge3_12(I, E1, T1, H1, E2, H2, T2, E3, _H3, T3, M, HdM, _D)
+ when E3 == HdM ->
+ ukeymerge3_12_3(I, E1, T1, H1, E2, H2, T2, M, T3);
+ukeymerge3_12(I, E1, T1, H1, E2, H2, T2, _E3, H3, T3, M, _HdM, _D) ->
+ ukeymerge3_12_3(I, E1, T1, H1, E2, H2, T2, [H3 | M], T3).
+
+% E1 =< E2, take L3 apart.
+ukeymerge3_12_3(I, E1, T1, H1, E2, H2, T2, M, [H3 | T3]) ->
+ case element(I, H3) of
+ E3 when E1 =< E3 ->
+ ukeymerge3_1(I, T1, T1, E1, E2, H2, T2, [H1 | M], E3, H3, T3);
+ _E3 ->
+ ukeymerge3_12_3(I, E1, T1, H1, E2, H2, T2, [H3 | M], T3)
+ end;
+ukeymerge3_12_3(I, E1, T1, H1, E2, H2, T2, M, []) ->
+ ukeymerge2_1(I, T1, E2, E1, T2, [H1 | M], H2).
+
+% E1 > E2. Inlined.
+ukeymerge3_21(I, E1, T1, H1, E2, H2, T2, E3, H3, T3, M, _HdM, D)
+ when E2 =< E3 ->
+ ukeymerge3_2(I, E1, T1, H1, T2, E2, D, [H2 | M], E3, H3, T3);
+ukeymerge3_21(I, E1, T1, H1, E2, H2, T2, E3, _H3, T3, M, HdM, _D)
+ when E3 == HdM ->
+ ukeymerge3_21_3(I, E1, T1, H1, E2, H2, T2, M, T3);
+ukeymerge3_21(I, E1, T1, H1, E2, H2, T2, _E3, H3, T3, M, _HdM, _D) ->
+ ukeymerge3_21_3(I, E1, T1, H1, E2, H2, T2, [H3 | M], T3).
+
+% E1 > E2, take L3 apart.
+ukeymerge3_21_3(I, E1, T1, H1, E2, H2, T2, M, [H3 | T3]) ->
+ case element(I, H3) of
+ E3 when E2 =< E3 ->
+ ukeymerge3_2(I, E1, T1, H1, T2, E2, T2, [H2 | M], E3, H3, T3);
+ _E3 ->
+ ukeymerge3_21_3(I, E1, T1, H1, E2, H2, T2, [H3 | M], T3)
+ end;
+ukeymerge3_21_3(I, E1, T1, H1, _E2, H2, T2, M, []) ->
+ ukeymerge2_2(I, T1, E1, H1, T2, [H2 | M]).
+
+%%% Two extra arguments, D1 and D2, just to avoid some move instructions.
+
+%% Take L1 apart.
+rukeymerge3_1(I, [H1 | T1], D1, D2, E2, H2, T2, M, E3, H3, T3) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ rukeymerge3_12a(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M);
+ E1 ->
+ rukeymerge3_21a(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D1, D2)
+ end;
+rukeymerge3_1(I, [], _D1, _D2, E2, H2, T2, M, E3, H3, T3) when E2 =< E3 ->
+ rukeymerge2_2(I, T2, E2, T3, M, E3, H3, H2);
+rukeymerge3_1(I, [], _D1, _D2, _E2, H2, T2, M, E3, H3, T3) ->
+ rukeymerge2_1(I, T2, E3, T3, [H2 | M], H3).
+
+% E1 =< E2. Inlined.
+rukeymerge3_12a(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M) when E2 =< E3 ->
+ rukeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, M, E3, H3, T3);
+rukeymerge3_12a(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M) ->
+ rukeymerge3_2(I, E1, H1, T1, T2, H2, E2, M, E3, H3, T3).
+
+% E1 > E2. Inlined
+rukeymerge3_21a(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, _D1, _D2)
+ when E1 =< E3 ->
+ rukeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, M, E3, H3, T3);
+rukeymerge3_21a(I, _E1, H1, T1, E2, H2, T2, E3, H3, T3, M, D1, D2) ->
+ rukeymerge3_1(I, T1, D1, D2, E2, H2, T2, [H1 | M], E3, H3, T3).
+
+%% Take L2 apart. E2M > E3. E2M > E2.
+rukeymerge3_2(I, E1, H1, T1, [H2 | T2], H2M, E2M, M, E3, H3, T3) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ % E2M > E1.
+ rukeymerge3_12b(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, H2M);
+ E2 when E1 == E2M ->
+ rukeymerge3_1(I, T1, H1, T1, E2, H2, T2, [H1 | M], E3, H3, T3);
+ E2 ->
+ % E2M > E1.
+ rukeymerge3_21b(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, H2M)
+ end;
+rukeymerge3_2(I, E1, H1, T1, [], _H2M, E2M, M, E3, H3, T3) when E1 == E2M ->
+ rukeymerge2_1(I, T1, E3, T3, [H1 | M], H3);
+rukeymerge3_2(I, E1, H1, T1, [], H2M, _E2M, M, E3, H3, T3) when E1 =< E3 ->
+ rukeymerge2_2(I, T1, E1, T3, [H2M | M], E3, H3, H1);
+rukeymerge3_2(I, _E1, H1, T1, [], H2M, _E2M, M, E3, H3, T3) ->
+ rukeymerge2_1(I, T1, E3, T3, [H1, H2M | M], H3).
+
+% E1 =< E2. Inlined.
+rukeymerge3_12b(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, H2M)
+ when E2 =< E3 ->
+ rukeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, [H2M | M], E3, H3, T3);
+rukeymerge3_12b(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M, H2M) ->
+ rukeymerge3_2(I, E1, H1, T1, T2, H2, E2, [H2M | M], E3, H3, T3).
+
+% E1 > E2. Inlined
+rukeymerge3_21b(I, E1, H1, T1, E2, H2, T2, E3, H3, T3, M,H2M) when E1 =< E3 ->
+ rukeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, [H2M | M], E3, H3, T3);
+rukeymerge3_21b(I, _E1, H1, T1, E2, H2, T2, E3, H3, T3, M, H2M) ->
+ rukeymerge3_1(I, T1, H1, T1, E2, H2, T2, [H1, H2M | M], E3, H3, T3).
+
+% E1 =< E2, take L3 apart.
+rukeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, M, E3M, H3M, [H3 | T3]) ->
+ case element(I, H3) of
+ E3 when E2 =< E3 ->
+ rukeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, [H3M | M], E3, H3, T3);
+ E3 when E2 == E3M ->
+ rukeymerge3_2(I, E1, H1, T1, T2, H2, E2, M, E3, H3, T3);
+ E3 ->
+ rukeymerge3_2(I, E1, H1, T1, T2, H2, E2, [H3M | M], E3, H3, T3)
+ end;
+rukeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, M, E3M, _H3M, []) when E2 == E3M ->
+ rukeymerge2_2(I, T1, E1, T2, M, E2, H2, H1);
+rukeymerge3_12_3(I, E1, H1, T1, E2, H2, T2, M, _E3M, H3M, []) ->
+ rukeymerge2_2(I, T1, E1, T2, [H3M | M], E2, H2, H1).
+
+% E1 > E2, take L3 apart.
+rukeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, M, E3M, H3M, [H3 | T3]) ->
+ case element(I, H3) of
+ E3 when E1 =< E3 ->
+ rukeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, [H3M | M], E3, H3, T3);
+ E3 when E1 == E3M ->
+ rukeymerge3_1(I, T1, H1, T1, E2, H2, T2, [H1 | M], E3, H3, T3);
+ E3 ->
+ rukeymerge3_1(I, T1, H1, T1, E2, H2, T2, [H1,H3M | M], E3, H3, T3)
+ end;
+rukeymerge3_21_3(I, E1, H1, T1, E2, H2, T2, M, E3M, _H3M, []) when E1 == E3M ->
+ rukeymerge2_1(I, T1, E2, T2, [H1 | M], H2);
+rukeymerge3_21_3(I, _E1, H1, T1, E2, H2, T2, M, _E3M, H3M, []) ->
+ rukeymerge2_1(I, T1, E2, T2, [H1, H3M | M], H2).
+
+%% ukeymerge/3
+
+%% Elements from the first list are kept and prioritized.
+ukeymerge2_1(I, [H1 | T1], E2, HdM, T2, M, H2) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ ukeymerge2_1(I, T1, E2, E1, T2, [H1 | M], H2);
+ E1 when E2 == HdM ->
+ ukeymerge2_2(I, T1, E1, H1, T2, M);
+ E1 ->
+ ukeymerge2_2(I, T1, E1, H1, T2, [H2 | M])
+ end;
+ukeymerge2_1(_I, [], E2, HdM, T2, M, _H2) when E2 == HdM ->
+ lists:reverse(T2, M);
+ukeymerge2_1(_I, [], _E2, _HdM, T2, M, H2) ->
+ lists:reverse(T2, [H2 | M]).
+
+ukeymerge2_2(I, T1, E1, H1, [H2 | T2], M) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ ukeymerge2_1(I, T1, E2, E1, T2, [H1 | M], H2);
+ _E2 ->
+ ukeymerge2_2(I, T1, E1, H1, T2, [H2 | M])
+ end;
+ukeymerge2_2(_I, T1, _E1, H1, [], M) ->
+ lists:reverse(T1, [H1 | M]).
+
+%% rukeymerge/3
+
+rukeymerge2_1(I, [H1 | T1], E2, T2, M, H2) ->
+ case element(I, H1) of
+ E1 when E1 =< E2 ->
+ rukeymerge2_2(I, T1, E1, T2, M, E2, H2, H1);
+ _E1 ->
+ rukeymerge2_1(I, T1, E2, T2, [H1 | M], H2)
+ end;
+rukeymerge2_1(_I, [], _E2, T2, M, H2) ->
+ lists:reverse(T2, [H2 | M]).
+
+rukeymerge2_2(I, T1, E1, [H2 | T2], M, E2M, H2M, H1) ->
+ case element(I, H2) of
+ E2 when E1 =< E2 ->
+ rukeymerge2_2(I, T1, E1, T2, [H2M | M], E2, H2, H1);
+ E2 when E1 == E2M ->
+ rukeymerge2_1(I, T1, E2, T2, [H1 | M], H2);
+ E2 ->
+ rukeymerge2_1(I, T1, E2, T2, [H1, H2M | M], H2)
+ end;
+rukeymerge2_2(_I, T1, E1, [], M, E2M, _H2M, H1) when E1 == E2M ->
+ lists:reverse(T1, [H1 | M]);
+rukeymerge2_2(_I, T1, _E1, [], M, _E2M, H2M, H1) ->
+ lists:reverse(T1, [H1, H2M | M]).
+
+%% sort/2
+
+%% Ascending.
+fsplit_1(Y, X, Fun, [Z | L], R, Rs) ->
+ case Fun(Y, Z) of
+ true ->
+ fsplit_1(Z, Y, Fun, L, [X | R], Rs);
+ false ->
+ case Fun(X, Z) of
+ true ->
+ fsplit_1(Y, Z, Fun, L, [X | R], Rs);
+ false when R == [] ->
+ fsplit_1(Y, X, Fun, L, [Z], Rs);
+ false ->
+ fsplit_1_1(Y, X, Fun, L, R, Rs, Z)
+ end
+ end;
+fsplit_1(Y, X, Fun, [], R, Rs) ->
+ rfmergel([[Y, X | R] | Rs], [], Fun, asc).
+
+fsplit_1_1(Y, X, Fun, [Z | L], R, Rs, S) ->
+ case Fun(Y, Z) of
+ true ->
+ fsplit_1_1(Z, Y, Fun, L, [X | R], Rs, S);
+ false ->
+ case Fun(X, Z) of
+ true ->
+ fsplit_1_1(Y, Z, Fun, L, [X | R], Rs, S);
+ false ->
+ case Fun(S, Z) of
+ true ->
+ fsplit_1(Z, S, Fun, L, [], [[Y, X | R] | Rs]);
+ false ->
+ fsplit_1(S, Z, Fun, L, [], [[Y, X | R] | Rs])
+ end
+ end
+ end;
+fsplit_1_1(Y, X, Fun, [], R, Rs, S) ->
+ rfmergel([[S], [Y, X | R] | Rs], [], Fun, asc).
+
+%% Descending.
+fsplit_2(Y, X, Fun, [Z | L], R, Rs) ->
+ case Fun(Y, Z) of
+ false ->
+ fsplit_2(Z, Y, Fun, L, [X | R], Rs);
+ true ->
+ case Fun(X, Z) of
+ false ->
+ fsplit_2(Y, Z, Fun, L, [X | R], Rs);
+ true when R == [] ->
+ fsplit_2(Y, X, Fun, L, [Z], Rs);
+ true ->
+ fsplit_2_1(Y, X, Fun, L, R, Rs, Z)
+ end
+ end;
+fsplit_2(Y, X, Fun, [], R, Rs) ->
+ fmergel([[Y, X | R] | Rs], [], Fun, desc).
+
+fsplit_2_1(Y, X, Fun, [Z | L], R, Rs, S) ->
+ case Fun(Y, Z) of
+ false ->
+ fsplit_2_1(Z, Y, Fun, L, [X | R], Rs, S);
+ true ->
+ case Fun(X, Z) of
+ false ->
+ fsplit_2_1(Y, Z, Fun, L, [X | R], Rs, S);
+ true ->
+ case Fun(S, Z) of
+ false ->
+ fsplit_2(Z, S, Fun, L, [], [[Y, X | R] | Rs]);
+ true ->
+ fsplit_2(S, Z, Fun, L, [], [[Y, X | R] | Rs])
+ end
+ end
+ end;
+fsplit_2_1(Y, X, Fun, [], R, Rs, S) ->
+ fmergel([[S], [Y, X | R] | Rs], [], Fun, desc).
+
+fmergel([T1, [H2 | T2] | L], Acc, Fun, asc) ->
+ fmergel(L, [fmerge2_1(T1, H2, Fun, T2, []) | Acc], Fun, asc);
+fmergel([[H2 | T2], T1 | L], Acc, Fun, desc) ->
+ fmergel(L, [fmerge2_1(T1, H2, Fun, T2, []) | Acc], Fun, desc);
+fmergel([L], [], _Fun, _O) ->
+ L;
+fmergel([L], Acc, Fun, O) ->
+ rfmergel([lists:reverse(L, []) | Acc], [], Fun, O);
+fmergel([], Acc, Fun, O) ->
+ rfmergel(Acc, [], Fun, O).
+
+rfmergel([[H2 | T2], T1 | L], Acc, Fun, asc) ->
+ rfmergel(L, [rfmerge2_1(T1, H2, Fun, T2, []) | Acc], Fun, asc);
+rfmergel([T1, [H2 | T2] | L], Acc, Fun, desc) ->
+ rfmergel(L, [rfmerge2_1(T1, H2, Fun, T2, []) | Acc], Fun, desc);
+rfmergel([L], Acc, Fun, O) ->
+ fmergel([lists:reverse(L, []) | Acc], [], Fun, O);
+rfmergel([], Acc, Fun, O) ->
+ fmergel(Acc, [], Fun, O).
+
+%% merge/3
+
+%% Elements from the first list are prioritized.
+fmerge2_1([H1 | T1], H2, Fun, T2, M) ->
+ case Fun(H1, H2) of
+ true ->
+ fmerge2_1(T1, H2, Fun, T2, [H1 | M]);
+ false ->
+ fmerge2_2(H1, T1, Fun, T2, [H2 | M])
+ end;
+fmerge2_1([], H2, _Fun, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+fmerge2_2(H1, T1, Fun, [H2 | T2], M) ->
+ case Fun(H1, H2) of
+ true ->
+ fmerge2_1(T1, H2, Fun, T2, [H1 | M]);
+ false ->
+ fmerge2_2(H1, T1, Fun, T2, [H2 | M])
+ end;
+fmerge2_2(H1, T1, _Fun, [], M) ->
+ lists:reverse(T1, [H1 | M]).
+
+%% rmerge/3
+
+rfmerge2_1([H1 | T1], H2, Fun, T2, M) ->
+ case Fun(H1, H2) of
+ true ->
+ rfmerge2_2(H1, T1, Fun, T2, [H2 | M]);
+ false ->
+ rfmerge2_1(T1, H2, Fun, T2, [H1 | M])
+ end;
+rfmerge2_1([], H2, _Fun, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+rfmerge2_2(H1, T1, Fun, [H2 | T2], M) ->
+ case Fun(H1, H2) of
+ true ->
+ rfmerge2_2(H1, T1, Fun, T2, [H2 | M]);
+ false ->
+ rfmerge2_1(T1, H2, Fun, T2, [H1 | M])
+ end;
+rfmerge2_2(H1, T1, _Fun, [], M) ->
+ lists:reverse(T1, [H1 | M]).
+
+%% usort/2
+
+%% Ascending. X < Y
+ufsplit_1(Y, X, Fun, [Z | L], R, Rs) ->
+ case Fun(Y, Z) of
+ true ->
+ case Fun(Z, Y) of
+ true -> % Z equal to Y
+ ufsplit_1(Y, X, Fun, L, R, Rs);
+ false ->
+ ufsplit_1(Z, Y, Fun, L, [X | R], Rs)
+ end;
+ false ->
+ case Fun(X, Z) of
+ true ->
+ case Fun(Z, X) of
+ true -> % Z equal to X
+ ufsplit_1(Y, X, Fun, L, R, Rs);
+ false ->
+ ufsplit_1(Y, Z, Fun, L, [X | R], Rs)
+ end;
+ false when R == [] ->
+ ufsplit_1(Y, X, Fun, L, [Z], Rs);
+ false ->
+ ufsplit_1_1(Y, X, Fun, L, R, Rs, Z)
+ end
+ end;
+ufsplit_1(Y, X, Fun, [], R, Rs) ->
+ rufmergel([[Y, X | R] | Rs], [], Fun).
+
+%% X < Y
+ufsplit_1_1(Y, X, Fun, [Z | L], R, Rs, S) ->
+ case Fun(Y, Z) of
+ true ->
+ case Fun(Z, Y) of
+ true -> % Z equal to Y
+ ufsplit_1_1(Y, X, Fun, L, R, Rs, S);
+ false ->
+ ufsplit_1_1(Z, Y, Fun, L, [X | R], Rs, S)
+ end;
+ false ->
+ case Fun(X, Z) of
+ true ->
+ case Fun(Z, X) of
+ true -> % Z equal to X
+ ufsplit_1_1(Y, X, Fun, L, R, Rs, S);
+ false ->
+ ufsplit_1_1(Y, Z, Fun, L, [X | R], Rs, S)
+ end;
+ false ->
+ case Fun(S, Z) of
+ true ->
+ case Fun(Z, S) of
+ true -> % Z equal to S
+ ufsplit_1_1(Y, X, Fun, L, R, Rs, S);
+ false ->
+ ufsplit_1(Z, S, Fun, L, [], [[Y, X | R] | Rs])
+ end;
+ false ->
+ ufsplit_1(S, Z, Fun, L, [], [[Y, X | R] | Rs])
+ end
+ end
+ end;
+ufsplit_1_1(Y, X, Fun, [], R, Rs, S) ->
+ rufmergel([[S], [Y, X | R] | Rs], [], Fun).
+
+%% Descending.
+ufsplit_2(Y, [Z | L], Fun, R) ->
+ case Fun(Y, Z) of
+ true ->
+ case Fun(Z, Y) of
+ true -> % Z equal to Y
+ ufsplit_2(Y, L, Fun, R);
+ false ->
+ ufsplit_1(Z, Y, Fun, L, [], [lists:reverse(R, [])])
+ end;
+ false ->
+ ufsplit_2(Z, L, Fun, [Y | R])
+ end;
+ufsplit_2(Y, [], _Fun, R) ->
+ [Y | R].
+
+ufmergel([[H1 | T1], T2 | L], Acc, Fun) ->
+ ufmergel(L, [ufmerge2_2(H1, T1, Fun, T2, []) | Acc], Fun);
+ufmergel([L], [], _Fun) ->
+ L;
+ufmergel([L], Acc, Fun) ->
+ rufmergel([lists:reverse(L, []) | Acc], [], Fun);
+ufmergel([], Acc, Fun) ->
+ rufmergel(Acc, [], Fun).
+
+rufmergel([[H2 | T2], T1 | L], Acc, Fun) ->
+ rufmergel(L, [rufmerge2_1(T1, H2, Fun, T2, []) | Acc], Fun);
+rufmergel([L], Acc, Fun) ->
+ ufmergel([lists:reverse(L, []) | Acc], [], Fun);
+rufmergel([], Acc, Fun) ->
+ ufmergel(Acc, [], Fun).
+
+%% umerge/3
+
+%% Elements from the first list are kept and prioritized.
+%% HdM before H2.
+ufmerge2_1([H1 | T1], H2, Fun, T2, M, HdM) ->
+ case Fun(H1, H2) of
+ true ->
+ ufmerge2_1(T1, H2, Fun, T2, [H1 | M], H1);
+ false ->
+ case Fun(H2, HdM) of
+ true -> % HdM equal to H2
+ ufmerge2_2(H1, T1, Fun, T2, M);
+ false ->
+ ufmerge2_2(H1, T1, Fun, T2, [H2 | M])
+ end
+ end;
+ufmerge2_1([], H2, Fun, T2, M, HdM) ->
+ case Fun(H2, HdM) of
+ true -> % HdM equal to H2
+ lists:reverse(T2, M);
+ false ->
+ lists:reverse(T2, [H2 | M])
+ end.
+
+ufmerge2_2(H1, T1, Fun, [H2 | T2], M) ->
+ case Fun(H1, H2) of
+ true ->
+ ufmerge2_1(T1, H2, Fun, T2, [H1 | M], H1);
+ false ->
+ ufmerge2_2(H1, T1, Fun, T2, [H2 | M])
+ end;
+ufmerge2_2(H1, T1, _Fun, [], M) ->
+ lists:reverse(T1, [H1 | M]).
+
+%% rumerge/3
+
+rufmerge2_1([H1 | T1], H2, Fun, T2, M) ->
+ case Fun(H1, H2) of
+ true ->
+ rufmerge2_2(H1, T1, Fun, T2, M, H2);
+ false ->
+ rufmerge2_1(T1, H2, Fun, T2, [H1 | M])
+ end;
+rufmerge2_1([], H2, _Fun, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+%% H1 before H2M
+rufmerge2_2(H1, T1, Fun, [H2 | T2], M, H2M) ->
+ case Fun(H1, H2) of
+ true ->
+ rufmerge2_2(H1, T1, Fun, T2, [H2M | M], H2);
+ false ->
+ case Fun(H2M, H1) of
+ true -> % H2M equal to H1
+ rufmerge2_1(T1, H2, Fun, T2, [H1 | M]);
+ false ->
+ rufmerge2_1(T1, H2, Fun, T2, [H1, H2M | M])
+ end
+ end;
+rufmerge2_2(H1, T1, Fun, [], M, H2M) ->
+ case Fun(H2M, H1) of
+ true ->
+ lists:reverse(T1, [H1 | M]);
+ false ->
+ lists:reverse(T1, [H1, H2M | M])
+ end.
+
diff --git a/lib/stdlib/src/log_mf_h.erl b/lib/stdlib/src/log_mf_h.erl
new file mode 100644
index 0000000000..2729f27e51
--- /dev/null
+++ b/lib/stdlib/src/log_mf_h.erl
@@ -0,0 +1,202 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(log_mf_h).
+
+-behaviour(gen_event).
+
+-export([init/3, init/4]).
+
+-export([init/1, handle_event/2, handle_info/2, terminate/2]).
+-export([handle_call/2, code_change/3]).
+
+%%-----------------------------------------------------------------
+
+-type dir() :: file:filename().
+-type b() :: non_neg_integer().
+-type f() :: 1..255.
+-type pred() :: fun((term()) -> boolean()).
+
+%%-----------------------------------------------------------------
+
+-record(state, {dir :: dir(),
+ maxB :: b(),
+ maxF :: f(),
+ curB :: b(),
+ curF :: f(),
+ cur_fd :: file:fd(),
+ index = [], %% Seems unused - take out??
+ pred :: pred()}).
+
+%%%-----------------------------------------------------------------
+%%% This module implements an event handler that writes events
+%%% to multiple files (configurable).
+%%%-----------------------------------------------------------------
+%% Func: init/3, init/4
+%% Args: Dir = string()
+%% MaxB = integer()
+%% MaxF = byte()
+%% Pred = fun(Event) -> boolean()
+%% Purpose: An event handler. Writes binary events
+%% to files in the directory Dir. Each file is called
+%% 1, 2, 3, ..., MaxF. Writes MaxB bytes on each file.
+%% Creates a file called 'index' in the Dir.
+%% This file contains the last written FileName.
+%% On startup, this file is read, and the next available
+%% filename is used as first logfile.
+%% Each event is filtered with the predicate function Pred.
+%% Reports can be browsed with Report Browser Tool (rb).
+%% Returns: Args = term()
+%% The Args term should be used in a call to
+%% gen_event:add_handler(EventMgr, log_mf_h, Args)
+%% EventMgr = pid() | atom().
+%%-----------------------------------------------------------------
+
+-spec init(dir(), b(), f()) -> {dir(), b(), f(), pred()}.
+
+init(Dir, MaxB, MaxF) -> init(Dir, MaxB, MaxF, fun(_) -> true end).
+
+-spec init(dir(), b(), f(), pred()) -> {dir(), b(), f(), pred()}.
+
+init(Dir, MaxB, MaxF, Pred) -> {Dir, MaxB, MaxF, Pred}.
+
+%%-----------------------------------------------------------------
+%% Call-back functions from gen_event
+%%-----------------------------------------------------------------
+
+-spec init({dir(), b(), f(), pred()}) -> {'ok', #state{}} | {'error', term()}.
+
+init({Dir, MaxB, MaxF, Pred}) when is_integer(MaxF), MaxF > 0, MaxF < 256 ->
+ First =
+ case read_index_file(Dir) of
+ {ok, LastWritten} -> inc(LastWritten, MaxF);
+ _ -> 1
+ end,
+ case catch file_open(Dir, First) of
+ {ok, Fd} ->
+ {ok, #state{dir = Dir, maxB = MaxB, maxF = MaxF, pred = Pred,
+ curF = First, cur_fd = Fd, curB = 0}};
+ Error -> Error
+ end.
+
+%%-----------------------------------------------------------------
+%% The handle_event/2 function may crash! In this case, this
+%% handler is removed by gen_event from the event handlers.
+%% Fails: 'file_open' if file:open failed for a log file.
+%% 'write_index_file' if file:write_file failed for the
+%% index file.
+%% {file_exit, Reason} if the current Fd crashes.
+%%-----------------------------------------------------------------
+
+-spec handle_event(term(), #state{}) -> {'ok', #state{}}.
+
+handle_event(Event, State) ->
+ #state{curB = CurB, maxB = MaxB, curF = CurF, maxF = MaxF,
+ dir = Dir, cur_fd = CurFd, pred = Pred} = State,
+ case catch Pred(Event) of
+ true ->
+ Bin = term_to_binary(tag_event(Event)),
+ Size = byte_size(Bin),
+ NewState =
+ if
+ CurB + Size < MaxB -> State;
+ true ->
+ ok = file:close(CurFd),
+ NewF = inc(CurF, MaxF),
+ {ok, NewFd} = file_open(Dir, NewF),
+ State#state{cur_fd = NewFd, curF = NewF, curB = 0}
+ end,
+ [Hi,Lo] = put_int16(Size),
+ file:write(NewState#state.cur_fd, [Hi, Lo, Bin]),
+ {ok, NewState#state{curB = NewState#state.curB + Size + 2}};
+ _ ->
+ {ok, State}
+ end.
+
+-spec handle_info(term(), #state{}) -> {'ok', #state{}}.
+
+handle_info({emulator, GL, Chars}, State) ->
+ handle_event({emulator, GL, Chars}, State);
+handle_info(_, State) ->
+ {ok, State}.
+
+-spec terminate(term(), #state{}) -> #state{}.
+
+terminate(_, State) ->
+ ok = file:close(State#state.cur_fd),
+ State.
+
+-spec handle_call('null', #state{}) -> {'ok', 'null', #state{}}.
+
+handle_call(null, State) ->
+ {ok, null, State}.
+
+-spec code_change(term(), #state{}, term()) -> {'ok', #state{}}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%-----------------------------------------------------------------
+%% Misc local functions
+%%-----------------------------------------------------------------
+
+file_open(Dir, FileNo) ->
+ case file:open(Dir ++ [$/ | integer_to_list(FileNo)], [raw, write]) of
+ {ok, Fd} ->
+ write_index_file(Dir, FileNo),
+ {ok, Fd};
+ _ ->
+ exit({file, open})
+ end.
+
+put_int16(I) ->
+ [((I band 16#ff00) bsr 8),I band 16#ff].
+
+tag_event(Event) ->
+ {erlang:localtime(), Event}.
+
+read_index_file(Dir) ->
+ case file:open(Dir ++ "/index", [raw, read]) of
+ {ok, Fd} ->
+ Res = case catch file:read(Fd, 1) of
+ {ok, [Index]} -> {ok, Index};
+ _ -> error
+ end,
+ ok = file:close(Fd),
+ Res;
+ _ -> error
+ end.
+
+%%-----------------------------------------------------------------
+%% Write the index file. This file contains one binary with
+%% the last used filename (an integer).
+%%-----------------------------------------------------------------
+
+write_index_file(Dir, Index) ->
+ case file:open(Dir ++ "/index", [raw, write]) of
+ {ok, Fd} ->
+ file:write(Fd, [Index]),
+ ok = file:close(Fd);
+ _ -> exit(open_index_file)
+ end.
+
+inc(N, Max) ->
+ if
+ N < Max -> N + 1;
+ true -> 1
+ end.
diff --git a/lib/stdlib/src/math.erl b/lib/stdlib/src/math.erl
new file mode 100644
index 0000000000..b2ea6195c5
--- /dev/null
+++ b/lib/stdlib/src/math.erl
@@ -0,0 +1,25 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(math).
+
+-export([pi/0]).
+
+-spec pi() -> float().
+
+pi() -> 3.1415926535897932.
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
new file mode 100644
index 0000000000..78b1de6e16
--- /dev/null
+++ b/lib/stdlib/src/ms_transform.erl
@@ -0,0 +1,992 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ms_transform).
+
+-export([format_error/1,transform_from_shell/3,parse_transform/2]).
+
+%% Error codes.
+-define(ERROR_BASE_GUARD,0).
+-define(ERROR_BASE_BODY,100).
+-define(ERR_NOFUN,1).
+-define(ERR_ETS_HEAD,2).
+-define(ERR_DBG_HEAD,3).
+-define(ERR_HEADMATCH,4).
+-define(ERR_SEMI_GUARD,5).
+-define(ERR_UNBOUND_VARIABLE,6).
+-define(ERR_HEADBADREC,7).
+-define(ERR_HEADBADFIELD,8).
+-define(ERR_HEADMULTIFIELD,9).
+-define(ERR_HEADDOLLARATOM,10).
+-define(ERR_HEADBINMATCH,11).
+-define(ERR_GENMATCH,16).
+-define(ERR_GENLOCALCALL,17).
+-define(ERR_GENELEMENT,18).
+-define(ERR_GENBADFIELD,19).
+-define(ERR_GENBADREC,20).
+-define(ERR_GENMULTIFIELD,21).
+-define(ERR_GENREMOTECALL,22).
+-define(ERR_GENBINCONSTRUCT,23).
+-define(ERR_GENDISALLOWEDOP,24).
+-define(ERR_GUARDMATCH,?ERR_GENMATCH+?ERROR_BASE_GUARD).
+-define(ERR_BODYMATCH,?ERR_GENMATCH+?ERROR_BASE_BODY).
+-define(ERR_GUARDLOCALCALL,?ERR_GENLOCALCALL+?ERROR_BASE_GUARD).
+-define(ERR_BODYLOCALCALL,?ERR_GENLOCALCALL+?ERROR_BASE_BODY).
+-define(ERR_GUARDELEMENT,?ERR_GENELEMENT+?ERROR_BASE_GUARD).
+-define(ERR_BODYELEMENT,?ERR_GENELEMENT+?ERROR_BASE_BODY).
+-define(ERR_GUARDBADFIELD,?ERR_GENBADFIELD+?ERROR_BASE_GUARD).
+-define(ERR_BODYBADFIELD,?ERR_GENBADFIELD+?ERROR_BASE_BODY).
+-define(ERR_GUARDBADREC,?ERR_GENBADREC+?ERROR_BASE_GUARD).
+-define(ERR_BODYBADREC,?ERR_GENBADREC+?ERROR_BASE_BODY).
+-define(ERR_GUARDMULTIFIELD,?ERR_GENMULTIFIELD+?ERROR_BASE_GUARD).
+-define(ERR_BODYMULTIFIELD,?ERR_GENMULTIFIELD+?ERROR_BASE_BODY).
+-define(ERR_GUARDREMOTECALL,?ERR_GENREMOTECALL+?ERROR_BASE_GUARD).
+-define(ERR_BODYREMOTECALL,?ERR_GENREMOTECALL+?ERROR_BASE_BODY).
+-define(ERR_GUARDBINCONSTRUCT,?ERR_GENBINCONSTRUCT+?ERROR_BASE_GUARD).
+-define(ERR_BODYBINCONSTRUCT,?ERR_GENBINCONSTRUCT+?ERROR_BASE_BODY).
+-define(ERR_GUARDDISALLOWEDOP,?ERR_GENDISALLOWEDOP+?ERROR_BASE_GUARD).
+-define(ERR_BODYDISALLOWEDOP,?ERR_GENDISALLOWEDOP+?ERROR_BASE_BODY).
+
+%%
+%% Called by compiler or ets/dbg:fun2ms when errors occur
+%%
+format_error(?ERR_NOFUN) ->
+ "Parameter of ets/dbg:fun2ms/1 is not a literal fun";
+format_error(?ERR_ETS_HEAD) ->
+ "ets:fun2ms requires fun with single variable or tuple parameter";
+format_error(?ERR_DBG_HEAD) ->
+ "dbg:fun2ms requires fun with single variable or list parameter";
+format_error(?ERR_HEADMATCH) ->
+ "in fun head, only matching (=) on toplevel can be translated into match_spec";
+format_error(?ERR_SEMI_GUARD) ->
+ "fun with semicolon (;) in guard cannot be translated into match_spec";
+format_error(?ERR_GUARDMATCH) ->
+ "fun with guard matching ('=' in guard) is illegal as match_spec as well";
+format_error({?ERR_GUARDLOCALCALL, Name, Arithy}) ->
+ lists:flatten(io_lib:format("fun containing the local function call "
+ "'~w/~w' (called in guard) "
+ "cannot be translated into match_spec",
+ [Name, Arithy]));
+format_error({?ERR_GUARDREMOTECALL, Module, Name, Arithy}) ->
+ lists:flatten(io_lib:format("fun containing the remote function call "
+ "'~w:~w/~w' (called in guard) "
+ "cannot be translated into match_spec",
+ [Module,Name,Arithy]));
+format_error({?ERR_GUARDELEMENT, Str}) ->
+ lists:flatten(
+ io_lib:format("the language element ~s (in guard) cannot be translated "
+ "into match_spec", [Str]));
+format_error({?ERR_GUARDBINCONSTRUCT, Var}) ->
+ lists:flatten(
+ io_lib:format("bit syntax construction with variable ~w (in guard) "
+ "cannot be translated "
+ "into match_spec", [Var]));
+format_error({?ERR_GUARDDISALLOWEDOP, Operator}) ->
+ %% There is presently no operators that are allowed in bodies but
+ %% not in guards.
+ lists:flatten(
+ io_lib:format("the operator ~w is not allowed in guards", [Operator]));
+format_error(?ERR_BODYMATCH) ->
+ "fun with body matching ('=' in body) is illegal as match_spec";
+format_error({?ERR_BODYLOCALCALL, Name, Arithy}) ->
+ lists:flatten(io_lib:format("fun containing the local function "
+ "call '~w/~w' (called in body) "
+ "cannot be translated into match_spec",
+ [Name,Arithy]));
+format_error({?ERR_BODYREMOTECALL, Module, Name, Arithy}) ->
+ lists:flatten(io_lib:format("fun containing the remote function call "
+ "'~w:~w/~w' (called in body) "
+ "cannot be translated into match_spec",
+ [Module,Name,Arithy]));
+format_error({?ERR_BODYELEMENT, Str}) ->
+ lists:flatten(
+ io_lib:format("the language element ~s (in body) cannot be translated "
+ "into match_spec", [Str]));
+format_error({?ERR_BODYBINCONSTRUCT, Var}) ->
+ lists:flatten(
+ io_lib:format("bit syntax construction with variable ~w (in body) "
+ "cannot be translated "
+ "into match_spec", [Var]));
+format_error({?ERR_BODYDISALLOWEDOP, Operator}) ->
+ %% This will probably never happen, Are there op's that are allowed in
+ %% guards but not in bodies? Not at time of writing anyway...
+ lists:flatten(
+ io_lib:format("the operator ~w is not allowed in function bodies",
+ [Operator]));
+
+format_error({?ERR_UNBOUND_VARIABLE, Str}) ->
+ lists:flatten(
+ io_lib:format("the variable ~s is unbound, cannot translate "
+ "into match_spec", [Str]));
+format_error({?ERR_HEADBADREC,Name}) ->
+ lists:flatten(
+ io_lib:format("fun head contains unknown record type ~w",[Name]));
+format_error({?ERR_HEADBADFIELD,RName,FName}) ->
+ lists:flatten(
+ io_lib:format("fun head contains reference to unknown field ~w in "
+ "record type ~w",[FName, RName]));
+format_error({?ERR_HEADMULTIFIELD,RName,FName}) ->
+ lists:flatten(
+ io_lib:format("fun head contains already defined field ~w in "
+ "record type ~w",[FName, RName]));
+format_error({?ERR_HEADDOLLARATOM,Atom}) ->
+ lists:flatten(
+ io_lib:format("fun head contains atom ~w, which conflics with reserved "
+ "atoms in match_spec heads",[Atom]));
+format_error({?ERR_HEADBINMATCH,Atom}) ->
+ lists:flatten(
+ io_lib:format("fun head contains bit syntax matching of variable ~w, "
+ "which cannot be translated into match_spec", [Atom]));
+format_error({?ERR_GUARDBADREC,Name}) ->
+ lists:flatten(
+ io_lib:format("fun guard contains unknown record type ~w",[Name]));
+format_error({?ERR_GUARDBADFIELD,RName,FName}) ->
+ lists:flatten(
+ io_lib:format("fun guard contains reference to unknown field ~w in "
+ "record type ~w",[FName, RName]));
+format_error({?ERR_GUARDMULTIFIELD,RName,FName}) ->
+ lists:flatten(
+ io_lib:format("fun guard contains already defined field ~w in "
+ "record type ~w",[FName, RName]));
+format_error({?ERR_BODYBADREC,Name}) ->
+ lists:flatten(
+ io_lib:format("fun body contains unknown record type ~w",[Name]));
+format_error({?ERR_BODYBADFIELD,RName,FName}) ->
+ lists:flatten(
+ io_lib:format("fun body contains reference to unknown field ~w in "
+ "record type ~w",[FName, RName]));
+format_error({?ERR_BODYMULTIFIELD,RName,FName}) ->
+ lists:flatten(
+ io_lib:format("fun body contains already defined field ~w in "
+ "record type ~w",[FName, RName]));
+format_error(Else) ->
+ lists:flatten(io_lib:format("Unknown error code ~w",[Else])).
+
+%%
+%% Called when translating in shell
+%%
+transform_from_shell(Dialect, Clauses, BoundEnvironment) ->
+ SaveFilename = setup_filename(),
+ case catch ms_clause_list(1,Clauses,Dialect) of
+ {'EXIT',Reason} ->
+ cleanup_filename(SaveFilename),
+ exit(Reason);
+ {error,Line,R} ->
+ {error, [{cleanup_filename(SaveFilename),
+ [{Line, ?MODULE, R}]}], []};
+ Else ->
+ case (catch fixup_environment(Else,BoundEnvironment)) of
+ {error,Line1,R1} ->
+ {error, [{cleanup_filename(SaveFilename),
+ [{Line1, ?MODULE, R1}]}], []};
+ Else1 ->
+ Ret = normalise(Else1),
+ cleanup_filename(SaveFilename),
+ Ret
+ end
+ end.
+
+
+%%
+%% Called when translating during compiling
+%%
+parse_transform(Forms, _Options) ->
+ SaveFilename = setup_filename(),
+ case catch forms(Forms) of
+ {'EXIT',Reason} ->
+ cleanup_filename(SaveFilename),
+ exit(Reason);
+ {error,Line,R} ->
+ {error, [{cleanup_filename(SaveFilename),
+ [{Line, ?MODULE, R}]}], []};
+ Else ->
+ cleanup_filename(SaveFilename),
+ Else
+ end.
+
+setup_filename() ->
+ {erase(filename),erase(records)}.
+
+put_filename(Name) ->
+ put(filename,Name).
+
+put_records(R) ->
+ put(records,R),
+ ok.
+get_records() ->
+ case get(records) of
+ undefined ->
+ [];
+ Else ->
+ Else
+ end.
+cleanup_filename({Old,OldRec}) ->
+ Ret = case erase(filename) of
+ undefined ->
+ "TOP_LEVEL";
+ X ->
+ X
+ end,
+ case OldRec of
+ undefined ->
+ erase(records);
+ Rec ->
+ put(records,Rec)
+ end,
+ case Old of
+ undefined ->
+ Ret;
+ Y ->
+ put(filename,Y),
+ Ret
+ end.
+
+add_record_definition({Name,FieldList}) ->
+ {KeyList,_} = lists:foldl(
+ fun({record_field,_,{atom,Line0,FieldName}},{L,C}) ->
+ {[{FieldName,C,{atom,Line0,undefined}}|L],C+1};
+ ({record_field,_,{atom,_,FieldName},Def},{L,C}) ->
+ {[{FieldName,C,Def}|L],C+1}
+ end,
+ {[],2},
+ FieldList),
+ put_records([{Name,KeyList}|get_records()]).
+
+forms([F0|Fs0]) ->
+ F1 = form(F0),
+ Fs1 = forms(Fs0),
+ [F1|Fs1];
+forms([]) -> [].
+
+form({attribute,_,file,{Filename,_}}=Form) ->
+ put_filename(Filename),
+ Form;
+form({attribute,_,record,Definition}=Form) ->
+ add_record_definition(Definition),
+ Form;
+form({function,Line,Name0,Arity0,Clauses0}) ->
+ {Name,Arity,Clauses} = function(Name0, Arity0, Clauses0),
+ {function,Line,Name,Arity,Clauses};
+form(AnyOther) ->
+ AnyOther.
+function(Name, Arity, Clauses0) ->
+ Clauses1 = clauses(Clauses0),
+ {Name,Arity,Clauses1}.
+clauses([C0|Cs]) ->
+ C1 = clause(C0),
+ [C1|clauses(Cs)];
+clauses([]) -> [].
+clause({clause,Line,H0,G0,B0}) ->
+ B1 = copy(B0),
+ {clause,Line,H0,G0,B1}.
+
+copy({call,Line,{remote,_Line2,{atom,_Line3,ets},{atom,_Line4,fun2ms}},
+ As0}) ->
+ transform_call(ets,Line,As0);
+copy({call,Line,{remote,_Line2,{record_field,_Line3,
+ {atom,_Line4,''},{atom,_Line5,ets}},
+ {atom,_Line6,fun2ms}}, As0}) ->
+ %% Packages...
+ transform_call(ets,Line,As0);
+copy({call,Line,{remote,_Line2,{atom,_Line3,dbg},{atom,_Line4,fun2ms}},
+ As0}) ->
+ transform_call(dbg,Line,As0);
+copy(T) when is_tuple(T) ->
+ list_to_tuple(copy_list(tuple_to_list(T)));
+copy(L) when is_list(L) ->
+ copy_list(L);
+copy(AnyOther) ->
+ AnyOther.
+
+copy_list([H|T]) ->
+ [copy(H)|copy_list(T)];
+copy_list([]) ->
+ [].
+
+transform_call(Type,_Line,[{'fun',Line2,{clauses, ClauseList}}]) ->
+ ms_clause_list(Line2, ClauseList,Type);
+transform_call(_Type,Line,_NoAbstractFun) ->
+ throw({error,Line,?ERR_NOFUN}).
+
+% Fixup semicolons in guards
+ms_clause_expand({clause, Line, Parameters, Guard = [_,_|_], Body}) ->
+ [ {clause, Line, Parameters, [X], Body} || X <- Guard ];
+ms_clause_expand(_Other) ->
+ false.
+
+ms_clause_list(Line,[H|T],Type) ->
+ case ms_clause_expand(H) of
+ NewHead when is_list(NewHead) ->
+ ms_clause_list(Line,NewHead ++ T, Type);
+ false ->
+ {cons, Line, ms_clause(H,Type), ms_clause_list(Line, T,Type)}
+ end;
+ms_clause_list(Line,[],_) ->
+ {nil,Line}.
+ms_clause({clause, Line, Parameters, Guards, Body},Type) ->
+ check_type(Line,Parameters,Type),
+ {MSHead,Bindings} = transform_head(Parameters),
+ MSGuards = transform_guards(Line, Guards, Bindings),
+ MSBody = transform_body(Line,Body,Bindings),
+ {tuple, Line, [MSHead,MSGuards,MSBody]}.
+
+
+check_type(_,[{var,_,_}],_) ->
+ ok;
+check_type(_,[{tuple,_,_}],ets) ->
+ ok;
+check_type(_,[{record,_,_,_}],ets) ->
+ ok;
+check_type(_,[{cons,_,_,_}],dbg) ->
+ ok;
+check_type(Line0,[{match,_,{var,_,_},X}],Any) ->
+ check_type(Line0,[X],Any);
+check_type(Line0,[{match,_,X,{var,_,_}}],Any) ->
+ check_type(Line0,[X],Any);
+check_type(Line,_Type,ets) ->
+ throw({error,Line,?ERR_ETS_HEAD});
+check_type(Line,_,dbg) ->
+ throw({error,Line,?ERR_DBG_HEAD}).
+
+-record(tgd,{ b, %Bindings
+ p, %Part of spec
+ eb %Error code base, 0 for guards, 100 for bodies
+ }).
+
+transform_guards(Line,[],_Bindings) ->
+ {nil,Line};
+transform_guards(Line,[G],Bindings) ->
+ B = #tgd{b = Bindings, p = guard, eb = ?ERROR_BASE_GUARD},
+ tg0(Line,G,B);
+transform_guards(Line,_,_) ->
+ throw({error,Line,?ERR_SEMI_GUARD}).
+
+transform_body(Line,Body,Bindings) ->
+ B = #tgd{b = Bindings, p = body, eb = ?ERROR_BASE_BODY},
+ tg0(Line,Body,B).
+
+
+guard_top_trans({call,Line0,{atom,Line1,OldTest},Params}) ->
+ case old_bool_test(OldTest,length(Params)) of
+ undefined ->
+ {call,Line0,{atom,Line1,OldTest},Params};
+ Trans ->
+ {call,Line0,{atom,Line1,Trans},Params}
+ end;
+guard_top_trans(Else) ->
+ Else.
+
+tg0(Line,[],_) ->
+ {nil,Line};
+tg0(Line,[H0|T],B) when B#tgd.p =:= guard ->
+ H = guard_top_trans(H0),
+ {cons,Line, tg(H,B), tg0(Line,T,B)};
+tg0(Line,[H|T],B) ->
+ {cons,Line, tg(H,B), tg0(Line,T,B)}.
+
+
+tg({match,Line,_,_},B) ->
+ throw({error,Line,?ERR_GENMATCH+B#tgd.eb});
+tg({op, Line, Operator, O1, O2}, B) ->
+ {tuple, Line, [{atom, Line, Operator}, tg(O1,B), tg(O2,B)]};
+tg({op, Line, Operator, O1}, B) ->
+ {tuple, Line, [{atom, Line, Operator}, tg(O1,B)]};
+tg({call, _Line, {atom, Line2, bindings},[]},_B) ->
+ {atom, Line2, '$*'};
+tg({call, _Line, {atom, Line2, object},[]},_B) ->
+ {atom, Line2, '$_'};
+tg({call, Line, {atom, _, is_record}=Call,[Object, {atom,Line3,RName}=R]},B) ->
+ MSObject = tg(Object,B),
+ RDefs = get_records(),
+ case lists:keysearch(RName,1,RDefs) of
+ {value, {RName, FieldList}} ->
+ RSize = length(FieldList)+1,
+ {tuple, Line, [Call, MSObject, R, {integer, Line3, RSize}]};
+ _ ->
+ throw({error,Line3,{?ERR_GENBADREC+B#tgd.eb,RName}})
+ end;
+tg({call, Line, {atom, Line2, FunName},ParaList},B) ->
+ case is_ms_function(FunName,length(ParaList), B#tgd.p) of
+ true ->
+ {tuple, Line, [{atom, Line2, FunName} |
+ lists:map(fun(X) -> tg(X,B) end, ParaList)]};
+ _ ->
+ throw({error,Line,{?ERR_GENLOCALCALL+B#tgd.eb,
+ FunName,length(ParaList)}})
+ end;
+tg({call, Line, {remote,_,{atom,_,erlang},{atom, Line2, FunName}},ParaList},
+ B) ->
+ L = length(ParaList),
+ case is_imported_from_erlang(FunName,L,B#tgd.p) of
+ true ->
+ case is_operator(FunName,L,B#tgd.p) of
+ false ->
+ tg({call, Line, {atom, Line2, FunName},ParaList},B);
+ true ->
+ tg(list_to_tuple([op,Line2,FunName | ParaList]),B)
+ end;
+ _ ->
+ throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,erlang,
+ FunName,length(ParaList)}})
+ end;
+tg({call, Line, {remote,_,{atom,_,ModuleName},
+ {atom, _, FunName}},_ParaList},B) ->
+ throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName}});
+tg({cons,Line, H, T},B) ->
+ {cons, Line, tg(H,B), tg(T,B)};
+tg({nil, Line},_B) ->
+ {nil, Line};
+tg({tuple,Line,L},B) ->
+ {tuple,Line,[{tuple,Line,lists:map(fun(X) -> tg(X,B) end, L)}]};
+tg({integer,Line,I},_) ->
+ {integer,Line,I};
+tg({char,Line,C},_) ->
+ {char,Line,C};
+tg({float, Line,F},_) ->
+ {float,Line,F};
+tg({atom,Line,A},_) ->
+ case atom_to_list(A) of
+ [$$|_] ->
+ {tuple, Line,[{atom, Line, 'const'},{atom,Line,A}]};
+ _ ->
+ {atom,Line,A}
+ end;
+tg({string,Line,S},_) ->
+ {string,Line,S};
+tg({var,Line,VarName},B) ->
+ case lkup_bind(VarName, B#tgd.b) of
+ undefined ->
+ {tuple, Line,[{atom, Line, 'const'},{var,Line,VarName}]};
+ AtomName ->
+ {atom, Line, AtomName}
+ end;
+tg({record_field,Line,Object,RName,{atom,_Line1,KeyName}},B) ->
+ RDefs = get_records(),
+ case lists:keysearch(RName,1,RDefs) of
+ {value, {RName, FieldList}} ->
+ case lists:keysearch(KeyName,1, FieldList) of
+ {value, {KeyName,Position,_}} ->
+ NewObject = tg(Object,B),
+ {tuple, Line, [{atom, Line, 'element'},
+ {integer, Line, Position}, NewObject]};
+ _ ->
+ throw({error,Line,{?ERR_GENBADFIELD+B#tgd.eb, RName,
+ KeyName}})
+ end;
+ _ ->
+ throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}})
+ end;
+
+tg({record,Line,RName,RFields},B) ->
+ RDefs = get_records(),
+ KeyList0 = lists:foldl(fun({record_field,_,{atom,_,Key},Value},
+ L) ->
+ NV = tg(Value,B),
+ [{Key,NV}|L];
+ ({record_field,_,{var,_,'_'},Value},
+ L) ->
+ NV = tg(Value,B),
+ [{{default},NV}|L];
+ (_,_) ->
+ throw({error,Line,
+ {?ERR_GENBADREC+B#tgd.eb,
+ RName}})
+ end,
+ [],
+ RFields),
+ DefValue = case lists:keysearch({default},1,KeyList0) of
+ {value,{{default},OverriddenDefValue}} ->
+ {true,OverriddenDefValue};
+ _ ->
+ false
+ end,
+ KeyList = lists:keydelete({default},1,KeyList0),
+ case lists:keysearch({default},1,KeyList) of
+ {value,{{default},_}} ->
+ throw({error,Line,{?ERR_GENMULTIFIELD+B#tgd.eb,RName,'_'}});
+ _ ->
+ ok
+ end,
+ case lists:keysearch(RName,1,RDefs) of
+ {value, {RName, FieldList0}} ->
+ FieldList1 = lists:foldl(
+ fun({FN,_,Def},Acc) ->
+ El = case lists:keysearch(FN,1,KeyList) of
+ {value, {FN, X0}} ->
+ X0;
+ _ ->
+ case DefValue of
+ {true,Overridden} ->
+ Overridden;
+ false ->
+ Def
+ end
+ end,
+ [El | Acc]
+ end,
+ [],
+ FieldList0),
+ check_multi_field(RName,Line,KeyList,
+ ?ERR_GENMULTIFIELD+B#tgd.eb),
+ check_undef_field(RName,Line,KeyList,FieldList0,
+ ?ERR_GENBADFIELD+B#tgd.eb),
+ {tuple,Line,[{tuple,Line,[{atom,Line,RName}|FieldList1]}]};
+ _ ->
+ throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}})
+ end;
+
+tg({record_index,Line,RName,{atom,Line2,KeyName}},B) ->
+ RDefs = get_records(),
+ case lists:keysearch(RName,1,RDefs) of
+ {value, {RName, FieldList}} ->
+ case lists:keysearch(KeyName,1, FieldList) of
+ {value, {KeyName,Position,_}} ->
+ {integer, Line2, Position};
+ _ ->
+ throw({error,Line2,{?ERR_GENBADFIELD+B#tgd.eb, RName,
+ KeyName}})
+ end;
+ _ ->
+ throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}})
+ end;
+
+tg({record,Line,{var,Line2,_VName}=AVName, RName,RFields},B) ->
+ RDefs = get_records(),
+ MSVName = tg(AVName,B),
+ KeyList = lists:foldl(fun({record_field,_,{atom,_,Key},Value},
+ L) ->
+ NV = tg(Value,B),
+ [{Key,NV}|L];
+ (_,_) ->
+ throw({error,Line,?ERR_HEADBADREC})
+ end,
+ [],
+ RFields),
+ case lists:keysearch(RName,1,RDefs) of
+ {value, {RName, FieldList0}} ->
+ FieldList1 = lists:foldl(
+ fun({FN,Pos,_},Acc) ->
+ El = case lists:keysearch(FN,1,KeyList) of
+ {value, {FN, X0}} ->
+ X0;
+ _ ->
+ {tuple, Line2,
+ [{atom, Line2, element},
+ {integer, Line2, Pos},
+ MSVName]}
+ end,
+ [El | Acc]
+ end,
+ [],
+ FieldList0),
+ check_multi_field(RName,Line,KeyList,
+ ?ERR_GENMULTIFIELD+B#tgd.eb),
+ check_undef_field(RName,Line,KeyList,FieldList0,
+ ?ERR_GENBADFIELD+B#tgd.eb),
+ {tuple,Line,[{tuple,Line,[{atom,Line,RName}|FieldList1]}]};
+ _ ->
+ throw({error,Line,{?ERR_GENBADREC+B#tgd.eb,RName}})
+ end;
+
+tg({bin_element,_Line0,{var, Line, A},_,_} = Whole,B) ->
+ case lkup_bind(A, B#tgd.b) of
+ undefined ->
+ Whole; % exists in environment hopefully
+ _AtomName ->
+ throw({error,Line,{?ERR_GENBINCONSTRUCT+B#tgd.eb,A}})
+ end;
+tg(default,_B) ->
+ default;
+tg({bin_element,Line,X,Y,Z},B) ->
+ {bin_element, Line, tg(X,B), tg(Y,B), Z};
+
+tg({bin,Line,List},B) ->
+ {bin,Line,[tg(X,B) || X <- List]};
+
+tg(T,B) when is_tuple(T), tuple_size(T) >= 2 ->
+ Element = element(1,T),
+ Line = element(2,T),
+ throw({error,Line,{?ERR_GENELEMENT+B#tgd.eb,
+ translate_language_element(Element)}});
+tg(Other,B) ->
+ Element = io_lib:format("unknown element ~w", [Other]),
+ throw({error,unknown,{?ERR_GENELEMENT+B#tgd.eb,Element}}).
+
+transform_head([V]) ->
+ Bind = cre_bind(),
+ {NewV,NewBind} = toplevel_head_match(V,Bind),
+ th(NewV,NewBind).
+
+
+toplevel_head_match({match,_,{var,_,VName},Expr},B) ->
+ {Expr,new_bind({VName,'$_'},B)};
+toplevel_head_match({match,_,Expr,{var,_,VName}},B) ->
+ {Expr,new_bind({VName,'$_'},B)};
+toplevel_head_match(Other,B) ->
+ {Other,B}.
+
+th({record,Line,RName,RFields},B) ->
+ % youch...
+ RDefs = get_records(),
+ {KeyList0,NewB} = lists:foldl(fun({record_field,_,{atom,_,Key},Value},
+ {L,B0}) ->
+ {NV,B1} = th(Value,B0),
+ {[{Key,NV}|L],B1};
+ ({record_field,_,{var,_,'_'},Value},
+ {L,B0}) ->
+ {NV,B1} = th(Value,B0),
+ {[{{default},NV}|L],B1};
+ (_,_) ->
+ throw({error,Line,{?ERR_HEADBADREC,
+ RName}})
+ end,
+ {[],B},
+ RFields),
+ DefValue = case lists:keysearch({default},1,KeyList0) of
+ {value,{{default},OverriddenDefValue}} ->
+ OverriddenDefValue;
+ _ ->
+ {atom,Line,'_'}
+ end,
+ KeyList = lists:keydelete({default},1,KeyList0),
+ case lists:keysearch({default},1,KeyList) of
+ {value,{{default},_}} ->
+ throw({error,Line,{?ERR_HEADMULTIFIELD,RName,'_'}});
+ _ ->
+ ok
+ end,
+ case lists:keysearch(RName,1,RDefs) of
+ {value, {RName, FieldList0}} ->
+ FieldList1 = lists:foldl(
+ fun({FN,_,_},Acc) ->
+ El = case lists:keysearch(FN,1,KeyList) of
+ {value, {FN, X0}} ->
+ X0;
+ _ ->
+ DefValue
+ end,
+ [El | Acc]
+ end,
+ [],
+ FieldList0),
+ check_multi_field(RName,Line,KeyList,
+ ?ERR_HEADMULTIFIELD),
+ check_undef_field(RName,Line,KeyList,FieldList0,
+ ?ERR_HEADBADFIELD),
+ {{tuple,Line,[{atom,Line,RName}|FieldList1]},NewB};
+ _ ->
+ throw({error,Line,{?ERR_HEADBADREC,RName}})
+ end;
+th({match,Line,_,_},_) ->
+ throw({error,Line,?ERR_HEADMATCH});
+th({atom,Line,A},B) ->
+ case atom_to_list(A) of
+ [$$|NL] ->
+ case (catch list_to_integer(NL)) of
+ N when is_integer(N) ->
+ throw({error,Line,{?ERR_HEADDOLLARATOM,A}});
+ _ ->
+ {{atom,Line,A},B}
+ end;
+ _ ->
+ {{atom,Line,A},B}
+ end;
+th({bin_element,_Line0,{var, Line, A},_,_},_) ->
+ throw({error,Line,{?ERR_HEADBINMATCH,A}});
+
+th({var,Line,Name},B) ->
+ case lkup_bind(Name,B) of
+ undefined ->
+ NewB = new_bind(Name,B),
+ {{atom,Line,lkup_bind(Name,NewB)},NewB};
+ Trans ->
+ {{atom,Line,Trans},B}
+ end;
+th([H|T],B) ->
+ {NH,NB} = th(H,B),
+ {NT,NNB} = th(T,NB),
+ {[NH|NT],NNB};
+th(T,B) when is_tuple(T) ->
+ {L,NB} = th(tuple_to_list(T),B),
+ {list_to_tuple(L),NB};
+th(Nonstruct,B) ->
+ {Nonstruct,B}.
+
+%% Could be more efficient...
+check_multi_field(_, _, [], _) ->
+ ok;
+check_multi_field(RName, Line, [{Key,_}|T], ErrCode) ->
+ case lists:keymember(Key,1,T) of
+ true ->
+ throw({error,Line,{ErrCode,RName,Key}});
+ false ->
+ check_multi_field(RName, Line, T, ErrCode)
+ end.
+check_undef_field(_, _, [], _, _) ->
+ ok;
+check_undef_field(RName, Line, [{Key,_}|T], FieldList, ErrCode) ->
+ case lists:keymember(Key, 1, FieldList) of
+ true ->
+ check_undef_field(RName, Line, T, FieldList, ErrCode);
+ false ->
+ throw({error,Line,{ErrCode,RName,Key}})
+ end.
+
+cre_bind() ->
+ {1,[{'_','_'}]}.
+
+lkup_bind(Name,{_,List}) ->
+ case lists:keysearch(Name,1,List) of
+ {value, {Name, Trans}} ->
+ Trans;
+ _ ->
+ undefined
+ end.
+
+new_bind({Name,Trans},{Next,L}) ->
+ {Next,[{Name,Trans}|L]};
+new_bind(Name,{Next,L}) ->
+ Trans = list_to_atom([$$|integer_to_list(Next)]),
+ {Next+1,[{Name,Trans}|L]}.
+
+translate_language_element(Atom) ->
+ Transtab = [
+ {lc,"list comprehension"},
+ {bc,"binary comprehension"},
+ {block, "begin/end block"},
+ {'if', "if"},
+ {'case', "case"},
+ {'receive', "receive"},
+ {'try', "try"},
+ {'catch', "catch"},
+ {'match', "match (=)"},
+ {remote, "external function call"}
+ ],
+ case lists:keysearch(Atom,1,Transtab) of
+ {value,{Atom, String}} ->
+ String;
+ _ ->
+ atom_to_list(Atom)
+ end.
+
+old_bool_test(atom,1) -> is_atom;
+old_bool_test(constant,1) -> is_constant;
+old_bool_test(float,1) -> is_float;
+old_bool_test(integer,1) -> is_integer;
+old_bool_test(list,1) -> is_list;
+old_bool_test(number,1) -> is_number;
+old_bool_test(pid,1) -> is_pid;
+old_bool_test(port,1) -> is_port;
+old_bool_test(reference,1) -> is_reference;
+old_bool_test(tuple,1) -> is_tuple;
+old_bool_test(binary,1) -> is_binary;
+old_bool_test(function,1) -> is_function;
+old_bool_test(record,2) -> is_record;
+old_bool_test(_,_) -> undefined.
+
+bool_test(is_atom,1) -> true;
+bool_test(is_constant,1) -> true;
+bool_test(is_float,1) -> true;
+bool_test(is_integer,1) -> true;
+bool_test(is_list,1) -> true;
+bool_test(is_number,1) -> true;
+bool_test(is_pid,1) -> true;
+bool_test(is_port,1) -> true;
+bool_test(is_reference,1) -> true;
+bool_test(is_tuple,1) -> true;
+bool_test(is_binary,1) -> true;
+bool_test(is_function,1) -> true;
+bool_test(is_record,2) -> true;
+bool_test(is_seq_trace,0) -> true;
+bool_test(_,_) -> false.
+
+real_guard_function(abs,1) -> true;
+real_guard_function(element,2) -> true;
+real_guard_function(hd,1) -> true;
+real_guard_function(length,1) -> true;
+real_guard_function(node,0) -> true;
+real_guard_function(node,1) -> true;
+real_guard_function(round,1) -> true;
+real_guard_function(size,1) -> true;
+real_guard_function(tl,1) -> true;
+real_guard_function(trunc,1) -> true;
+real_guard_function(self,0) -> true;
+real_guard_function(float,1) -> true;
+real_guard_function(_,_) -> false.
+
+pseudo_guard_function(get_tcw,0) -> true;
+pseudo_guard_function(_,_) -> false.
+
+guard_function(X,A) ->
+ real_guard_function(X,A) or pseudo_guard_function(X,A).
+
+action_function(set_seq_token,2) -> true;
+action_function(get_seq_token,0) -> true;
+action_function(message,1) -> true;
+action_function(return_trace,0) -> true;
+action_function(exception_trace,0) -> true;
+action_function(process_dump,0) -> true;
+action_function(enable_trace,1) -> true;
+action_function(enable_trace,2) -> true;
+action_function(disable_trace,1) -> true;
+action_function(disable_trace,2) -> true;
+action_function(display,1) -> true;
+action_function(caller,0) -> true;
+action_function(set_tcw,1) -> true;
+action_function(silent,1) -> true;
+action_function(trace,2) -> true;
+action_function(trace,3) -> true;
+action_function(_,_) -> false.
+
+bool_operator('and',2) ->
+ true;
+bool_operator('or',2) ->
+ true;
+bool_operator('xor',2) ->
+ true;
+bool_operator('not',1) ->
+ true;
+bool_operator('andalso',2) ->
+ true;
+bool_operator('orelse',2) ->
+ true;
+bool_operator(_,_) ->
+ false.
+
+arith_operator('+',1) ->
+ true;
+arith_operator('+',2) ->
+ true;
+arith_operator('-',1) ->
+ true;
+arith_operator('-',2) ->
+ true;
+arith_operator('*',2) ->
+ true;
+arith_operator('/',2) ->
+ true;
+arith_operator('div',2) ->
+ true;
+arith_operator('rem',2) ->
+ true;
+arith_operator('band',2) ->
+ true;
+arith_operator('bor',2) ->
+ true;
+arith_operator('bxor',2) ->
+ true;
+arith_operator('bnot',1) ->
+ true;
+arith_operator('bsl',2) ->
+ true;
+arith_operator('bsr',2) ->
+ true;
+arith_operator(_,_) ->
+ false.
+
+cmp_operator('>',2) ->
+ true;
+cmp_operator('>=',2) ->
+ true;
+cmp_operator('<',2) ->
+ true;
+cmp_operator('=<',2) ->
+ true;
+cmp_operator('==',2) ->
+ true;
+cmp_operator('=:=',2) ->
+ true;
+cmp_operator('/=',2) ->
+ true;
+cmp_operator('=/=',2) ->
+ true;
+cmp_operator(_,_) ->
+ false.
+
+is_operator(X,A,_) ->
+ bool_operator(X,A) or arith_operator(X,A) or cmp_operator(X,A).
+
+is_imported_from_erlang(X,A,_) ->
+ real_guard_function(X,A) or bool_test(X,A) or bool_operator(X,A) or
+ arith_operator(X,A) or cmp_operator(X,A).
+
+is_ms_function(X,A,body) ->
+ action_function(X,A) or guard_function(X,A) or bool_test(X,A);
+
+is_ms_function(X,A,guard) ->
+ guard_function(X,A) or bool_test(X,A).
+
+fixup_environment(L,B) when is_list(L) ->
+ lists:map(fun(X) ->
+ fixup_environment(X,B)
+ end,
+ L);
+fixup_environment({var,Line,Name},B) ->
+ case lists:keysearch(Name,1,B) of
+ {value,{Name,Value}} ->
+ freeze(Line,Value);
+ _ ->
+ throw({error,Line,{?ERR_UNBOUND_VARIABLE,atom_to_list(Name)}})
+ end;
+fixup_environment(T,B) when is_tuple(T) ->
+ list_to_tuple(
+ lists:map(fun(X) ->
+ fixup_environment(X,B)
+ end,
+ tuple_to_list(T)));
+fixup_environment(Other,_B) ->
+ Other.
+
+freeze(Line,Term) ->
+ {frozen,Line,Term}.
+
+%% Most of this is bluntly stolen from erl_parse.
+
+normalise({frozen,_,Term}) ->
+ Term;
+normalise({char,_,C}) -> C;
+normalise({integer,_,I}) -> I;
+normalise({float,_,F}) -> F;
+normalise({atom,_,A}) -> A;
+normalise({string,_,S}) -> S;
+normalise({nil,_}) -> [];
+normalise({bin,_,Fs}) ->
+ {value, B, _} =
+ eval_bits:expr_grp(Fs, [],
+ fun(E, _) ->
+ {value, normalise(E), []}
+ end, [], true),
+ B;
+normalise({cons,_,Head,Tail}) ->
+ [normalise(Head)|normalise(Tail)];
+normalise({tuple,_,Args}) ->
+ list_to_tuple(normalise_list(Args));
+%% Special case for unary +/-.
+normalise({op,_,'+',{char,_,I}}) -> I;
+normalise({op,_,'+',{integer,_,I}}) -> I;
+normalise({op,_,'+',{float,_,F}}) -> F;
+normalise({op,_,'-',{char,_,I}}) -> -I; % Weird, but compatible!
+normalise({op,_,'-',{integer,_,I}}) -> -I;
+normalise({op,_,'-',{float,_,F}}) -> -F.
+
+normalise_list([H|T]) ->
+ [normalise(H)|normalise_list(T)];
+normalise_list([]) ->
+ [].
+
+
diff --git a/lib/stdlib/src/orddict.erl b/lib/stdlib/src/orddict.erl
new file mode 100644
index 0000000000..c7b52b933e
--- /dev/null
+++ b/lib/stdlib/src/orddict.erl
@@ -0,0 +1,173 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(orddict).
+
+%% Standard interface.
+-export([new/0,is_key/2,to_list/1,from_list/1,size/1]).
+-export([fetch/2,find/2,fetch_keys/1,erase/2]).
+-export([store/3,append/3,append_list/3,update/3,update/4,update_counter/3]).
+-export([fold/3,map/2,filter/2,merge/3]).
+
+%%---------------------------------------------------------------------------
+
+-type orddict() :: [{term(), term()}].
+
+%%---------------------------------------------------------------------------
+
+-spec new() -> orddict().
+
+new() -> [].
+
+-spec is_key(Key::term(), Dictionary::orddict()) -> boolean().
+
+is_key(Key, [{K,_}|_]) when Key < K -> false;
+is_key(Key, [{K,_}|Dict]) when Key > K -> is_key(Key, Dict);
+is_key(_Key, [{_K,_Val}|_]) -> true; %Key == K
+is_key(_, []) -> false.
+
+-spec to_list(orddict()) -> [{term(), term()}].
+
+to_list(Dict) -> Dict.
+
+-spec from_list([{term(), term()}]) -> orddict().
+
+from_list(Pairs) ->
+ lists:foldl(fun ({K,V}, D) -> store(K, V, D) end, [], Pairs).
+
+-spec size(orddict()) -> non_neg_integer().
+
+size(D) -> length(D).
+
+-spec fetch(Key::term(), Dictionary::orddict()) -> term().
+
+fetch(Key, [{K,_}|D]) when Key > K -> fetch(Key, D);
+fetch(Key, [{K,Value}|_]) when Key == K -> Value.
+
+-spec find(Key::term(), Dictionary::orddict()) -> {'ok', term()} | 'error'.
+
+find(Key, [{K,_}|_]) when Key < K -> error;
+find(Key, [{K,_}|D]) when Key > K -> find(Key, D);
+find(_Key, [{_K,Value}|_]) -> {ok,Value}; %Key == K
+find(_, []) -> error.
+
+-spec fetch_keys(Dictionary::orddict()) -> [term()].
+
+fetch_keys([{Key,_}|Dict]) ->
+ [Key|fetch_keys(Dict)];
+fetch_keys([]) -> [].
+
+-spec erase(Key::term(), Dictionary::orddict()) -> orddict().
+
+erase(Key, [{K,_}=E|Dict]) when Key < K -> [E|Dict];
+erase(Key, [{K,_}=E|Dict]) when Key > K ->
+ [E|erase(Key, Dict)];
+erase(_Key, [{_K,_Val}|Dict]) -> Dict; %Key == K
+erase(_, []) -> [].
+
+-spec store(Key::term(), Value::term(), Dictionary::orddict()) -> orddict().
+
+store(Key, New, [{K,_}=E|Dict]) when Key < K ->
+ [{Key,New},E|Dict];
+store(Key, New, [{K,_}=E|Dict]) when Key > K ->
+ [E|store(Key, New, Dict)];
+store(Key, New, [{_K,_Old}|Dict]) -> %Key == K
+ [{Key,New}|Dict];
+store(Key, New, []) -> [{Key,New}].
+
+-spec append(Key::term(), Value::term(), Dictionary::orddict()) -> orddict().
+
+append(Key, New, [{K,_}=E|Dict]) when Key < K ->
+ [{Key,[New]},E|Dict];
+append(Key, New, [{K,_}=E|Dict]) when Key > K ->
+ [E|append(Key, New, Dict)];
+append(Key, New, [{_K,Old}|Dict]) -> %Key == K
+ [{Key,Old ++ [New]}|Dict];
+append(Key, New, []) -> [{Key,[New]}].
+
+-spec append_list(Key::term(), ValueList::[term()], orddict()) -> orddict().
+
+append_list(Key, NewList, [{K,_}=E|Dict]) when Key < K ->
+ [{Key,NewList},E|Dict];
+append_list(Key, NewList, [{K,_}=E|Dict]) when Key > K ->
+ [E|append_list(Key, NewList, Dict)];
+append_list(Key, NewList, [{_K,Old}|Dict]) -> %Key == K
+ [{Key,Old ++ NewList}|Dict];
+append_list(Key, NewList, []) ->
+ [{Key,NewList}].
+
+-spec update(Key::term(), Fun::fun((term()) -> term()), orddict()) -> orddict().
+
+update(Key, Fun, [{K,_}=E|Dict]) when Key > K ->
+ [E|update(Key, Fun, Dict)];
+update(Key, Fun, [{K,Val}|Dict]) when Key == K ->
+ [{Key,Fun(Val)}|Dict].
+
+-spec update(term(), fun((term()) -> term()), term(), orddict()) -> orddict().
+
+update(Key, _, Init, [{K,_}=E|Dict]) when Key < K ->
+ [{Key,Init},E|Dict];
+update(Key, Fun, Init, [{K,_}=E|Dict]) when Key > K ->
+ [E|update(Key, Fun, Init, Dict)];
+update(Key, Fun, _Init, [{_K,Val}|Dict]) -> %Key == K
+ [{Key,Fun(Val)}|Dict];
+update(Key, _, Init, []) -> [{Key,Init}].
+
+-spec update_counter(Key::term(), Incr::number(), orddict()) -> orddict().
+
+update_counter(Key, Incr, [{K,_}=E|Dict]) when Key < K ->
+ [{Key,Incr},E|Dict];
+update_counter(Key, Incr, [{K,_}=E|Dict]) when Key > K ->
+ [E|update_counter(Key, Incr, Dict)];
+update_counter(Key, Incr, [{_K,Val}|Dict]) -> %Key == K
+ [{Key,Val+Incr}|Dict];
+update_counter(Key, Incr, []) -> [{Key,Incr}].
+
+-spec fold(fun((term(),term(),term()) -> term()), term(), orddict()) -> term().
+
+fold(F, Acc, [{Key,Val}|D]) ->
+ fold(F, F(Key, Val, Acc), D);
+fold(F, Acc, []) when is_function(F, 3) -> Acc.
+
+-spec map(fun((term(), term()) -> term()), orddict()) -> orddict().
+
+map(F, [{Key,Val}|D]) ->
+ [{Key,F(Key, Val)}|map(F, D)];
+map(F, []) when is_function(F, 2) -> [].
+
+-spec filter(fun((term(), term()) -> term()), orddict()) -> orddict().
+
+filter(F, [{Key,Val}=E|D]) ->
+ case F(Key, Val) of
+ true -> [E|filter(F, D)];
+ false -> filter(F, D)
+ end;
+filter(F, []) when is_function(F, 2) -> [].
+
+-spec merge(fun((term(), term(), term()) -> term()), orddict(), orddict()) ->
+ orddict().
+
+merge(F, [{K1,_}=E1|D1], [{K2,_}=E2|D2]) when K1 < K2 ->
+ [E1|merge(F, D1, [E2|D2])];
+merge(F, [{K1,_}=E1|D1], [{K2,_}=E2|D2]) when K1 > K2 ->
+ [E2|merge(F, [E1|D1], D2)];
+merge(F, [{K1,V1}|D1], [{_K2,V2}|D2]) -> %K1 == K2
+ [{K1,F(K1, V1, V2)}|merge(F, D1, D2)];
+merge(F, [], D2) when is_function(F, 3) -> D2;
+merge(F, D1, []) when is_function(F, 3) -> D1.
diff --git a/lib/stdlib/src/ordsets.erl b/lib/stdlib/src/ordsets.erl
new file mode 100644
index 0000000000..05041c15f1
--- /dev/null
+++ b/lib/stdlib/src/ordsets.erl
@@ -0,0 +1,220 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(ordsets).
+
+-export([new/0,is_set/1,size/1,to_list/1,from_list/1]).
+-export([is_element/2,add_element/2,del_element/2]).
+-export([union/2,union/1,intersection/2,intersection/1]).
+-export([is_disjoint/2]).
+-export([subtract/2,is_subset/2]).
+-export([fold/3,filter/2]).
+
+-type ordset(T) :: [T].
+
+%% new() -> Set.
+%% Return a new empty ordered set.
+
+-spec new() -> ordset(term()).
+
+new() -> [].
+
+%% is_set(Term) -> boolean().
+%% Return 'true' if Set is an ordered set of elements, else 'false'.
+
+-spec is_set(term()) -> boolean().
+
+is_set([E|Es]) -> is_set(Es, E);
+is_set([]) -> true;
+is_set(_) -> false.
+
+is_set([E2|Es], E1) when E1 < E2 ->
+ is_set(Es, E2);
+is_set([_|_], _) -> false;
+is_set([], _) -> true.
+
+%% size(OrdSet) -> int().
+%% Return the number of elements in OrdSet.
+
+-spec size(ordset(_)) -> non_neg_integer().
+
+size(S) -> length(S).
+
+%% to_list(OrdSet) -> [Elem].
+%% Return the elements in OrdSet as a list.
+
+-spec to_list(ordset(T)) -> [T].
+
+to_list(S) -> S.
+
+%% from_list([Elem]) -> Set.
+%% Build an ordered set from the elements in List.
+
+-spec from_list([T]) -> ordset(T).
+
+from_list(L) ->
+ lists:usort(L).
+
+%% is_element(Element, OrdSet) -> boolean().
+%% Return 'true' if Element is an element of OrdSet, else 'false'.
+
+-spec is_element(term(), ordset(_)) -> boolean().
+
+is_element(E, [H|Es]) when E > H -> is_element(E, Es);
+is_element(E, [H|_]) when E < H -> false;
+is_element(_E, [_H|_]) -> true; %E == H
+is_element(_, []) -> false.
+
+%% add_element(Element, OrdSet) -> OrdSet.
+%% Return OrdSet with Element inserted in it.
+
+-spec add_element(term(), ordset(_)) -> ordset(_).
+
+add_element(E, [H|Es]) when E > H -> [H|add_element(E, Es)];
+add_element(E, [H|_]=Set) when E < H -> [E|Set];
+add_element(_E, [_H|_]=Set) -> Set; %E == H
+add_element(E, []) -> [E].
+
+%% del_element(Element, OrdSet) -> OrdSet.
+%% Return OrdSet but with Element removed.
+
+-spec del_element(term(), ordset(_)) -> ordset(_).
+
+del_element(E, [H|Es]) when E > H -> [H|del_element(E, Es)];
+del_element(E, [H|_]=Set) when E < H -> Set;
+del_element(_E, [_H|Es]) -> Es; %E == H
+del_element(_, []) -> [].
+
+%% union(OrdSet1, OrdSet2) -> OrdSet
+%% Return the union of OrdSet1 and OrdSet2.
+
+-spec union(ordset(_), ordset(_)) -> ordset(_).
+
+union([E1|Es1], [E2|_]=Set2) when E1 < E2 ->
+ [E1|union(Es1, Set2)];
+union([E1|_]=Set1, [E2|Es2]) when E1 > E2 ->
+ [E2|union(Es2, Set1)]; % switch arguments!
+union([E1|Es1], [_E2|Es2]) -> %E1 == E2
+ [E1|union(Es1, Es2)];
+union([], Es2) -> Es2;
+union(Es1, []) -> Es1.
+
+%% union([OrdSet]) -> OrdSet
+%% Return the union of the list of ordered sets.
+
+-spec union([ordset(_)]) -> ordset(_).
+
+union([S1,S2|Ss]) ->
+ union1(union(S1, S2), Ss);
+union([S]) -> S;
+union([]) -> [].
+
+union1(S1, [S2|Ss]) -> union1(union(S1, S2), Ss);
+union1(S1, []) -> S1.
+
+%% intersection(OrdSet1, OrdSet2) -> OrdSet.
+%% Return the intersection of OrdSet1 and OrdSet2.
+
+-spec intersection(ordset(_), ordset(_)) -> ordset(_).
+
+intersection([E1|Es1], [E2|_]=Set2) when E1 < E2 ->
+ intersection(Es1, Set2);
+intersection([E1|_]=Set1, [E2|Es2]) when E1 > E2 ->
+ intersection(Es2, Set1); % switch arguments!
+intersection([E1|Es1], [_E2|Es2]) -> %E1 == E2
+ [E1|intersection(Es1, Es2)];
+intersection([], _) ->
+ [];
+intersection(_, []) ->
+ [].
+
+%% intersection([OrdSet]) -> OrdSet.
+%% Return the intersection of the list of ordered sets.
+
+-spec intersection([ordset(_)]) -> ordset(_).
+
+intersection([S1,S2|Ss]) ->
+ intersection1(intersection(S1, S2), Ss);
+intersection([S]) -> S.
+
+intersection1(S1, [S2|Ss]) ->
+ intersection1(intersection(S1, S2), Ss);
+intersection1(S1, []) -> S1.
+
+%% is_disjoint(OrdSet1, OrdSet2) -> boolean().
+%% Check whether OrdSet1 and OrdSet2 are disjoint.
+
+-spec is_disjoint(ordset(_), ordset(_)) -> boolean().
+
+is_disjoint([E1|Es1], [E2|_]=Set2) when E1 < E2 ->
+ is_disjoint(Es1, Set2);
+is_disjoint([E1|_]=Set1, [E2|Es2]) when E1 > E2 ->
+ is_disjoint(Es2, Set1); % switch arguments!
+is_disjoint([_E1|_Es1], [_E2|_Es2]) -> %E1 == E2
+ false;
+is_disjoint([], _) ->
+ true;
+is_disjoint(_, []) ->
+ true.
+
+%% subtract(OrdSet1, OrdSet2) -> OrdSet.
+%% Return all and only the elements of OrdSet1 which are not also in
+%% OrdSet2.
+
+-spec subtract(ordset(_), ordset(_)) -> ordset(_).
+
+subtract([E1|Es1], [E2|_]=Set2) when E1 < E2 ->
+ [E1|subtract(Es1, Set2)];
+subtract([E1|_]=Set1, [E2|Es2]) when E1 > E2 ->
+ subtract(Set1, Es2);
+subtract([_E1|Es1], [_E2|Es2]) -> %E1 == E2
+ subtract(Es1, Es2);
+subtract([], _) -> [];
+subtract(Es1, []) -> Es1.
+
+%% is_subset(OrdSet1, OrdSet2) -> boolean().
+%% Return 'true' when every element of OrdSet1 is also a member of
+%% OrdSet2, else 'false'.
+
+-spec is_subset(ordset(_), ordset(_)) -> boolean().
+
+is_subset([E1|_], [E2|_]) when E1 < E2 -> %E1 not in Set2
+ false;
+is_subset([E1|_]=Set1, [E2|Es2]) when E1 > E2 ->
+ is_subset(Set1, Es2);
+is_subset([_E1|Es1], [_E2|Es2]) -> %E1 == E2
+ is_subset(Es1, Es2);
+is_subset([], _) -> true;
+is_subset(_, []) -> false.
+
+%% fold(Fun, Accumulator, OrdSet) -> Accumulator.
+%% Fold function Fun over all elements in OrdSet and return Accumulator.
+
+-spec fold(fun((_, _) -> _), _, ordset(_)) -> _.
+
+fold(F, Acc, Set) ->
+ lists:foldl(F, Acc, Set).
+
+%% filter(Fun, OrdSet) -> OrdSet.
+%% Filter OrdSet with Fun.
+
+-spec filter(fun((_) -> boolean()), ordset(_)) -> ordset(_).
+
+filter(F, Set) ->
+ lists:filter(F, Set).
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
new file mode 100644
index 0000000000..3df6f4bb90
--- /dev/null
+++ b/lib/stdlib/src/otp_internal.erl
@@ -0,0 +1,384 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(otp_internal).
+
+-export([obsolete/3]).
+
+%%----------------------------------------------------------------------
+
+-type tag() :: 'deprecated' | 'removed'. %% | 'experimental'.
+-type mfas() :: mfa() | {atom(), atom(), [byte()]}.
+-type release() :: string().
+
+-spec obsolete(atom(), atom(), byte()) ->
+ 'no' | {tag(), string()} | {tag(), mfas(), release()}.
+
+obsolete(Module, Name, Arity) ->
+ case obsolete_1(Module, Name, Arity) of
+ {deprecated=Tag,{_,_,_}=Replacement} ->
+ {Tag,Replacement,"in a future release"};
+ {_,String}=Ret when is_list(String) ->
+ Ret;
+ {_,_,_}=Ret ->
+ Ret;
+ no ->
+ no
+ end.
+
+obsolete_1(init, get_flag, 1) ->
+ {removed, {init, get_argument, 1}, "R12B"};
+obsolete_1(init, get_flags, 0) ->
+ {removed, {init, get_arguments, 0}, "R12B"};
+obsolete_1(init, get_args, 0) ->
+ {removed, {init, get_plain_arguments, 0}, "R12B"};
+obsolete_1(unix, cmd, 1) ->
+ {removed, {os,cmd,1}, "R9B"};
+
+obsolete_1(net, _, _) ->
+ {deprecated, "module 'net' obsolete; use 'net_adm'"};
+
+obsolete_1(erl_internal, builtins, 0) ->
+ {deprecated, {erl_internal, bif, 2}};
+
+obsolete_1(string, re_sh_to_awk, 1) ->
+ {removed, {regexp, sh_to_awk, 1}, "R12B"};
+obsolete_1(string, re_parse, 1) ->
+ {removed, {regexp, parse, 1}, "R12B"};
+obsolete_1(string, re_match, 2) ->
+ {removed, {regexp, match, 2}, "R12B"};
+obsolete_1(string, re_sub, 3) ->
+ {removed, {regexp, sub, 3}, "R12B"};
+obsolete_1(string, re_gsub, 3) ->
+ {removed, {regexp, gsub, 3}, "R12B"};
+obsolete_1(string, re_split, 2) ->
+ {removed, {regexp, split, 2}, "R12B"};
+
+obsolete_1(string, index, 2) ->
+ {removed, {string, str, 2}, "R12B"};
+
+obsolete_1(erl_eval, seq, 2) ->
+ {deprecated, {erl_eval, exprs, 2}};
+obsolete_1(erl_eval, seq, 3) ->
+ {deprecated, {erl_eval, exprs, 3}};
+obsolete_1(erl_eval, arg_list, 2) ->
+ {deprecated, {erl_eval, expr_list, 2}};
+obsolete_1(erl_eval, arg_list, 3) ->
+ {deprecated, {erl_eval, expr_list, 3}};
+
+obsolete_1(erl_pp, seq, 1) ->
+ {removed, {erl_pp, exprs, 1}, "R12B"};
+obsolete_1(erl_pp, seq, 2) ->
+ {removed, {erl_pp, exprs, 2}, "R12B"};
+
+obsolete_1(io, scan_erl_seq, 1) ->
+ {removed, {io, scan_erl_exprs, 1}, "R12B"};
+obsolete_1(io, scan_erl_seq, 2) ->
+ {removed, {io, scan_erl_exprs, 2}, "R12B"};
+obsolete_1(io, scan_erl_seq, 3) ->
+ {removed, {io, scan_erl_exprs, 3}, "R12B"};
+obsolete_1(io, parse_erl_seq, 1) ->
+ {removed, {io, parse_erl_exprs, 1}, "R12B"};
+obsolete_1(io, parse_erl_seq, 2) ->
+ {removed, {io, parse_erl_exprs, 2}, "R12B"};
+obsolete_1(io, parse_erl_seq, 3) ->
+ {removed, {io, parse_erl_exprs, 3}, "R12B"};
+obsolete_1(io, parse_exprs, 2) ->
+ {removed, {io, parse_erl_exprs, 2}, "R12B"};
+
+obsolete_1(io_lib, scan, 1) ->
+ {removed, {erl_scan, string, 1}, "R12B"};
+obsolete_1(io_lib, scan, 2) ->
+ {removed, {erl_scan, string, 2}, "R12B"};
+obsolete_1(io_lib, scan, 3) ->
+ {removed, {erl_scan, tokens, 3}, "R12B"};
+obsolete_1(io_lib, reserved_word, 1) ->
+ {removed, {erl_scan, reserved_word, 1}, "R12B"};
+
+obsolete_1(lists, keymap, 4) ->
+ {removed, {lists, keymap, 3}, "R12B"};
+obsolete_1(lists, all, 3) ->
+ {removed, {lists, all, 2}, "R12B"};
+obsolete_1(lists, any, 3) ->
+ {removed, {lists, any, 2}, "R12B"};
+obsolete_1(lists, map, 3) ->
+ {removed, {lists, map, 2}, "R12B"};
+obsolete_1(lists, flatmap, 3) ->
+ {removed, {lists, flatmap, 2}, "R12B"};
+obsolete_1(lists, foldl, 4) ->
+ {removed, {lists, foldl, 3}, "R12B"};
+obsolete_1(lists, foldr, 4) ->
+ {removed, {lists, foldr, 3}, "R12B"};
+obsolete_1(lists, mapfoldl, 4) ->
+ {removed, {lists, mapfoldl, 3}, "R12B"};
+obsolete_1(lists, mapfoldr, 4) ->
+ {removed, {lists, mapfoldr, 3}, "R12B"};
+obsolete_1(lists, filter, 3) ->
+ {removed, {lists, filter, 2}, "R12B"};
+obsolete_1(lists, foreach, 3) ->
+ {removed, {lists, foreach, 2}, "R12B"};
+obsolete_1(lists, zf, 3) ->
+ {removed, {lists, zf, 2}, "R12B"};
+
+obsolete_1(ets, fixtable, 2) ->
+ {removed, {ets, safe_fixtable, 2}, "R12B"};
+
+obsolete_1(erlang, old_binary_to_term, 1) ->
+ {removed, {erlang, binary_to_term, 1}, "R12B"};
+obsolete_1(erlang, info, 1) ->
+ {removed, {erlang, system_info, 1}, "R12B"};
+obsolete_1(erlang, hash, 2) ->
+ {deprecated, {erlang, phash2, 2}};
+
+obsolete_1(file, file_info, 1) ->
+ {removed, {file, read_file_info, 1}, "R12B"};
+
+obsolete_1(dict, dict_to_list, 1) ->
+ {removed, {dict,to_list,1}, "R12B"};
+obsolete_1(dict, list_to_dict, 1) ->
+ {removed, {dict,from_list,1}, "R12B"};
+obsolete_1(orddict, dict_to_list, 1) ->
+ {removed, {orddict,to_list,1}, "R12B"};
+obsolete_1(orddict, list_to_dict, 1) ->
+ {removed, {orddict,from_list,1}, "R12B"};
+
+obsolete_1(sets, new_set, 0) ->
+ {removed, {sets, new, 0}, "R12B"};
+obsolete_1(sets, set_to_list, 1) ->
+ {removed, {sets, to_list, 1}, "R12B"};
+obsolete_1(sets, list_to_set, 1) ->
+ {removed, {sets, from_list, 1}, "R12B"};
+obsolete_1(sets, subset, 2) ->
+ {removed, {sets, is_subset, 2}, "R12B"};
+obsolete_1(ordsets, new_set, 0) ->
+ {removed, {ordsets, new, 0}, "R12B"};
+obsolete_1(ordsets, set_to_list, 1) ->
+ {removed, {ordsets, to_list, 1}, "R12B"};
+obsolete_1(ordsets, list_to_set, 1) ->
+ {removed, {ordsets, from_list, 1}, "R12B"};
+obsolete_1(ordsets, subset, 2) ->
+ {removed, {ordsets, is_subset, 2}, "R12B"};
+
+obsolete_1(calendar, local_time_to_universal_time, 1) ->
+ {deprecated, {calendar, local_time_to_universal_time_dst, 1}};
+
+obsolete_1(rpc, safe_multi_server_call, A) when A =:= 2; A =:= 3 ->
+ {deprecated, {rpc, multi_server_call, A}};
+
+obsolete_1(snmp, N, A) ->
+ case is_snmp_agent_function(N, A) of
+ false ->
+ no;
+ true ->
+ {deprecated,"Deprecated; use snmpa:"++atom_to_list(N)++"/"++
+ integer_to_list(A)++" instead"}
+ end;
+
+obsolete_1(megaco, format_versions, 1) ->
+ {deprecated, "Deprecated; use megaco:print_version_info/0,1 instead"};
+
+obsolete_1(os_mon_mib, init, 1) ->
+ {deprecated, {os_mon_mib, load, 1}};
+obsolete_1(os_mon_mib, stop, 1) ->
+ {deprecated, {os_mon_mib, unload, 1}};
+
+obsolete_1(auth, is_auth, 1) ->
+ {deprecated, {net_adm, ping, 1}};
+obsolete_1(auth, cookie, 0) ->
+ {deprecated, {erlang, get_cookie, 0}};
+obsolete_1(auth, cookie, 1) ->
+ {deprecated, {erlang, set_cookie, 2}};
+obsolete_1(auth, node_cookie, 1) ->
+ {deprecated, "Deprecated; use erlang:set_cookie/2 and net_adm:ping/1 instead"};
+obsolete_1(auth, node_cookie, 2) ->
+ {deprecated, "Deprecated; use erlang:set_cookie/2 and net_adm:ping/1 instead"};
+
+%% Added in R11B-5.
+obsolete_1(http_base_64, _, _) ->
+ {removed, "The http_base_64 module was removed in R12B; use the base64 module instead"};
+obsolete_1(httpd_util, encode_base64, 1) ->
+ {removed, "Removed in R12B; use one of the encode functions in the base64 module instead"};
+obsolete_1(httpd_util, decode_base64, 1) ->
+ {removed, "Removed in R12B; use one of the decode functions in the base64 module instead"};
+obsolete_1(httpd_util, to_upper, 1) ->
+ {removed, {string, to_upper, 1}, "R12B"};
+obsolete_1(httpd_util, to_lower, 1) ->
+ {removed, {string, to_lower, 1}, "R12B"};
+obsolete_1(erlang, is_constant, 1) ->
+ {removed, "Removed in R13B"};
+
+%% Added in R12B-0.
+obsolete_1(ssl, port, 1) ->
+ {removed, {ssl, sockname, 1}, "R13B"};
+obsolete_1(ssl, accept, A) when A =:= 1; A =:= 2 ->
+ {removed, "deprecated; use ssl:transport_accept/1,2 and ssl:ssl_accept/1,2"};
+obsolete_1(erlang, fault, 1) ->
+ {removed, {erlang,error,1}, "R13B"};
+obsolete_1(erlang, fault, 2) ->
+ {removed, {erlang,error,2}, "R13B"};
+
+%% Added in R12B-2.
+obsolete_1(file, rawopen, 2) ->
+ {removed, "deprecated (will be removed in R13B); use file:open/2 with the raw option"};
+
+obsolete_1(httpd, start, 0) -> {deprecated,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start_link, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start_child, 0) -> {deprecated,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start_child, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, stop, 0) -> {deprecated,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop, 1) -> {deprecated,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop, 2) -> {deprecated,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop_child, 0) -> {deprecated,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop_child, 1) -> {deprecated,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop_child, 2) -> {deprecated,{inets,stop,2},"R14B"};
+obsolete_1(httpd, restart, 0) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, restart, 1) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, restart, 2) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 0) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 1) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 2) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 3) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 4) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, unblock, 0) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, unblock, 1) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, unblock, 2) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd_util, key1search, 2) -> {removed,{proplists,get_value,2},"R13B"};
+obsolete_1(httpd_util, key1search, 3) -> {removed,{proplists,get_value,3},"R13B"};
+obsolete_1(ftp, open, 3) -> {deprecated,{inets,start,[2,3]},"R14B"};
+obsolete_1(ftp, force_active, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
+
+%% Added in R12B-4.
+obsolete_1(ssh_cm, connect, A) when 1 =< A, A =< 3 ->
+ {deprecated,{ssh,connect,A},"R14B"};
+obsolete_1(ssh_cm, listen, A) when 2 =< A, A =< 4 ->
+ {deprecated,{ssh,daemon,A},"R14B"};
+obsolete_1(ssh_cm, stop_listener, 1) ->
+ {deprecated,{ssh,stop_listener,[1,2]},"R14B"};
+obsolete_1(ssh_cm, session_open, A) when A =:= 2; A =:= 4 ->
+ {deprecated,{ssh_connection,session_channel,A},"R14B"};
+obsolete_1(ssh_cm, direct_tcpip, A) when A =:= 6; A =:= 8 ->
+ {deprecated,{ssh_connection,direct_tcpip,A}};
+obsolete_1(ssh_cm, tcpip_forward, 3) ->
+ {deprecated,{ssh_connection,tcpip_forward,3},"R14B"};
+obsolete_1(ssh_cm, cancel_tcpip_forward, 3) ->
+ {deprecated,{ssh_connection,cancel_tcpip_forward,3},"R14B"};
+obsolete_1(ssh_cm, open_pty, A) when A =:= 3; A =:= 7; A =:= 9 ->
+ {deprecated,{ssh_connection,open_pty,A},"R14"};
+obsolete_1(ssh_cm, setenv, 5) ->
+ {deprecated,{ssh_connection,setenv,5},"R14B"};
+obsolete_1(ssh_cm, shell, 2) ->
+ {deprecated,{ssh_connection,shell,2},"R14B"};
+obsolete_1(ssh_cm, exec, 4) ->
+ {deprecated,{ssh_connection,exec,4},"R14B"};
+obsolete_1(ssh_cm, subsystem, 4) ->
+ {deprecated,{ssh_connection,subsystem,4},"R14B"};
+obsolete_1(ssh_cm, winch, A) when A =:= 4; A =:= 6 ->
+ {deprecated,{ssh_connection,window_change,A},"R14B"};
+obsolete_1(ssh_cm, signal, 3) ->
+ {deprecated,{ssh_connection,signal,3},"R14B"};
+obsolete_1(ssh_cm, attach, A) when A =:= 2; A =:= 3 ->
+ {deprecated,{ssh,attach,A}};
+obsolete_1(ssh_cm, detach, 2) ->
+ {deprecated,"no longer useful; will be removed in R14B"};
+obsolete_1(ssh_cm, set_user_ack, 4) ->
+ {deprecated,"no longer useful; will be removed in R14B"};
+obsolete_1(ssh_cm, adjust_window, 3) ->
+ {deprecated,{ssh_connection,adjust_window,3},"R14B"};
+obsolete_1(ssh_cm, close, 2) ->
+ {deprecated,{ssh_connection,close,2},"R14B"};
+obsolete_1(ssh_cm, stop, 1) ->
+ {deprecated,{ssh,close,1},"R14B"};
+obsolete_1(ssh_cm, send_eof, 2) ->
+ {deprecated,{ssh_connection,send_eof,2},"R14B"};
+obsolete_1(ssh_cm, send, A) when A =:= 3; A =:= 4 ->
+ {deprecated,{ssh_connection,send,A},"R14B"};
+obsolete_1(ssh_cm, send_ack, A) when 3 =< A, A =< 5 ->
+ {deprecated,{ssh_connection,send,[3,4]},"R14B"};
+obsolete_1(ssh_ssh, connect, A) when 1 =< A, A =< 3 ->
+ {deprecated,{ssh,shell,A},"R14B"};
+obsolete_1(ssh_sshd, listen, A) when 0 =< A, A =< 3 ->
+ {deprecated,{ssh,daemon,[1,2,3]},"R14"};
+obsolete_1(ssh_sshd, stop, 1) ->
+ {deprecated,{ssh,stop_listener,1}};
+
+%% Added in R13A.
+obsolete_1(regexp, _, _) ->
+ {deprecated, "the regexp module is deprecated (will be removed in R15A); use the re module instead"};
+
+obsolete_1(lists, flat_length, 1) ->
+ {deprecated,{lists,flatlength,1},"R14"};
+
+obsolete_1(ssh_sftp, connect, A) when 1 =< A, A =< 3 ->
+ {deprecated,{ssh_sftp,start_channel,A},"R14B"};
+obsolete_1(ssh_sftp, stop, 1) ->
+ {deprecated,{ssh_sftp,stop_channel,1},"R14B"};
+
+%% Added in R13B01.
+obsolete_1(ssl_pkix, decode_cert_file, A) when A =:= 1; A =:= 2 ->
+ {deprecated,"deprecated (will be removed in R14B); use public_key:pem_to_der/1 and public_key:pkix_decode_cert/2 instead"};
+obsolete_1(ssl_pkix, decode_cert, A) when A =:= 1; A =:= 2 ->
+ {deprecated,{public_key,pkix_decode_cert,2},"R14B"};
+
+obsolete_1(_, _, _) ->
+ no.
+
+
+-spec is_snmp_agent_function(atom(), byte()) -> boolean().
+
+is_snmp_agent_function(c, 1) -> true;
+is_snmp_agent_function(c, 2) -> true;
+is_snmp_agent_function(compile, 3) -> true;
+is_snmp_agent_function(is_consistent, 1) -> true;
+is_snmp_agent_function(mib_to_hrl, 1) -> true;
+is_snmp_agent_function(change_log_size, 1) -> true;
+is_snmp_agent_function(log_to_txt, 2) -> true;
+is_snmp_agent_function(log_to_txt, 3) -> true;
+is_snmp_agent_function(log_to_txt, 4) -> true;
+is_snmp_agent_function(current_request_id, 0) -> true;
+is_snmp_agent_function(current_community, 0) -> true;
+is_snmp_agent_function(current_address, 0) -> true;
+is_snmp_agent_function(current_context, 0) -> true;
+is_snmp_agent_function(current_net_if_data, 0) -> true;
+is_snmp_agent_function(get_symbolic_store_db, 0) -> true;
+is_snmp_agent_function(name_to_oid, 1) -> true;
+is_snmp_agent_function(name_to_oid, 2) -> true;
+is_snmp_agent_function(oid_to_name, 1) -> true;
+is_snmp_agent_function(oid_to_name, 2) -> true;
+is_snmp_agent_function(int_to_enum, 2) -> true;
+is_snmp_agent_function(int_to_enum, 3) -> true;
+is_snmp_agent_function(enum_to_int, 2) -> true;
+is_snmp_agent_function(enum_to_int, 3) -> true;
+is_snmp_agent_function(get, 2) -> true;
+is_snmp_agent_function(info, 1) -> true;
+is_snmp_agent_function(load_mibs, 2) -> true;
+is_snmp_agent_function(unload_mibs, 2) -> true;
+is_snmp_agent_function(dump_mibs, 0) -> true;
+is_snmp_agent_function(dump_mibs, 1) -> true;
+is_snmp_agent_function(register_subagent, 3) -> true;
+is_snmp_agent_function(unregister_subagent, 2) -> true;
+is_snmp_agent_function(send_notification, 3) -> true;
+is_snmp_agent_function(send_notification, 4) -> true;
+is_snmp_agent_function(send_notification, 5) -> true;
+is_snmp_agent_function(send_notification, 6) -> true;
+is_snmp_agent_function(send_trap, 3) -> true;
+is_snmp_agent_function(send_trap, 4) -> true;
+is_snmp_agent_function(add_agent_caps, 2) -> true;
+is_snmp_agent_function(del_agent_caps, 1) -> true;
+is_snmp_agent_function(get_agent_caps, 0) -> true;
+is_snmp_agent_function(_, _) -> false.
diff --git a/lib/stdlib/src/pg.erl b/lib/stdlib/src/pg.erl
new file mode 100644
index 0000000000..503654e706
--- /dev/null
+++ b/lib/stdlib/src/pg.erl
@@ -0,0 +1,172 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(pg).
+
+%% pg provides a process group facility. Messages
+%% can be multicasted to all members in the group
+
+-export([create/1,
+ create/2,
+ standby/2,
+ join/2,
+ send/2,
+ esend/2,
+ members/1,
+ name_to_pid/1,
+ master/1]).
+
+
+%% Create a brand new empty process group with the master residing
+%% at the local node
+
+-spec create(term()) -> 'ok' | {'error', term()}.
+
+create(PgName) ->
+ catch begin check(PgName),
+ Pid = spawn(pg,master,[PgName]),
+ global:register_name(PgName,Pid),
+ ok end.
+
+%% Create a brand new empty process group with the master
+%% residing at Node
+
+-spec create(term(), node()) -> 'ok' | {'error', term()}.
+
+create(PgName, Node) ->
+ catch begin check(PgName),
+ Pid = spawn(Node,pg,master,[PgName]),
+ global:register_name(PgName,Pid),
+ ok end.
+
+%% Have a process on Node that will act as a standby for the process
+%% group manager. So if the node where the manager runs fails, the
+%% process group will continue to function.
+
+-spec standby(term(), node()) -> 'ok'.
+
+standby(_PgName, _Node) ->
+ ok.
+
+%% Tell process group PgName that Pid is a new member of the group
+%% synchronously return a list of all old members in the group
+
+-spec join(atom(), pid()) -> [pid()].
+
+join(PgName, Pid) when is_atom(PgName) ->
+ global:send(PgName, {join,self(),Pid}),
+ receive
+ {_P,{members,Members}} ->
+ Members
+ end.
+
+%% Multi cast Mess to all members in the group
+
+-spec send(atom() | pid(), term()) -> 'ok'.
+
+send(PgName, Mess) when is_atom(PgName) ->
+ global:send(PgName, {send, self(), Mess}),
+ ok;
+send(Pg, Mess) when is_pid(Pg) ->
+ Pg ! {send,self(),Mess},
+ ok.
+
+%% multi cast a message to all members in the group but ourselves
+%% If we are a member
+
+-spec esend(atom() | pid(), term()) -> 'ok'.
+
+esend(PgName, Mess) when is_atom(PgName) ->
+ global:send(PgName, {esend,self(),Mess}),
+ ok;
+esend(Pg, Mess) when is_pid(Pg) ->
+ Pg ! {esend,self(),Mess},
+ ok.
+
+%% Return the members of the group
+
+-spec members(atom() | pid()) -> [pid()].
+
+members(PgName) when is_atom(PgName) ->
+ global:send(PgName, {self() ,members}),
+ receive
+ {_P,{members,Members}} ->
+ Members
+ end;
+members(Pg) when is_pid(Pg) ->
+ Pg ! {self,members},
+ receive
+ {_P,{members,Members}} ->
+ Members
+ end.
+
+-spec name_to_pid(atom()) -> pid() | 'undefined'.
+
+name_to_pid(PgName) when is_atom(PgName) ->
+ global:whereis_name(PgName).
+
+-spec master(term()) -> no_return().
+
+master(PgName) ->
+ process_flag(trap_exit, true),
+ master_loop(PgName, []).
+
+master_loop(PgName,Members) ->
+ receive
+ {send,From,Message} ->
+ send_all(Members,{pg_message,From,PgName,Message}),
+ master_loop(PgName,Members);
+ {esend,From,Message} ->
+ send_all(lists:delete(From,Members),
+ {pg_message,From,PgName,Message}),
+ master_loop(PgName,Members);
+ {join,From,Pid} ->
+ link(Pid),
+ send_all(Members,{new_member,PgName,Pid}),
+ From ! {self(),{members,Members}},
+ master_loop(PgName,[Pid|Members]);
+ {From,members} ->
+ From ! {self(),{members,Members}},
+ master_loop(PgName,Members);
+ {'EXIT',From,_} ->
+ L =
+ case lists:member(From,Members) of
+ true ->
+ NewMembers = lists:delete(From,Members),
+ send_all(NewMembers, {crashed_member,PgName,From}),
+ NewMembers;
+ false ->
+ Members
+ end,
+ master_loop(PgName,L)
+ end.
+
+send_all([], _) -> ok;
+send_all([P|Ps], M) ->
+ P ! M,
+ send_all(Ps, M).
+
+%% Check if the process group already exists
+
+check(PgName) ->
+ case global:whereis_name(PgName) of
+ Pid when is_pid(Pid) ->
+ throw({error,already_created});
+ undefined ->
+ ok
+ end.
diff --git a/lib/stdlib/src/pool.erl b/lib/stdlib/src/pool.erl
new file mode 100644
index 0000000000..7f5f23e26d
--- /dev/null
+++ b/lib/stdlib/src/pool.erl
@@ -0,0 +1,212 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(pool).
+
+%% Supplies a computational pool of processors.
+%% The chief user interface function here is get_node()
+%% Which returns the name of the nodes in the pool
+%% with the least load !!!!
+%% This function is callable from any node including the master
+%% That is part of the pool
+%% nodes are scheduled on a per usgae basis and per load basis,
+%% Whenever we use a node, we put at the end of the queue, and whenever
+%% a node report a change in load, we insert it accordingly
+
+% User interface Exports ...
+-export([start/1,
+ start/2,
+ stop/0,
+ get_nodes/0,
+ get_nodes_and_load/0,
+ get_node/0,
+ pspawn/3,
+ attach/1,
+ pspawn_link/3]).
+
+%% Internal Exports
+-export([statistic_collector/0,
+ do_spawn/4,
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2]).
+
+%% User interface
+
+%% Start up using the .hosts.erlang file
+
+-spec start(atom()) -> [node()].
+start(Name) ->
+ start(Name,[]).
+
+-spec start(atom(), string()) -> [node()].
+start(Name, Args) when is_atom(Name) ->
+ gen_server:start({global, pool_master}, pool, [], []),
+ Hosts = net_adm:host_file(),
+ Nodes = start_nodes(Hosts, Name, Args),
+ lists:foreach(fun attach/1, Nodes),
+ Nodes.
+
+%%
+%% Interface functions ...
+%%
+-spec get_nodes() -> [node()].
+get_nodes() ->
+ get_elements(2, get_nodes_and_load()).
+
+-spec attach(node()) -> 'already_attached' | 'attached'.
+attach(Node) ->
+ gen_server:call({global, pool_master}, {attach, Node}).
+
+get_nodes_and_load() ->
+ gen_server:call({global, pool_master}, get_nodes).
+
+-spec get_node() -> node().
+get_node() ->
+ gen_server:call({global, pool_master}, get_node).
+
+-spec pspawn(module(), atom(), [term()]) -> pid().
+pspawn(M, F, A) ->
+ gen_server:call({global, pool_master}, {spawn, group_leader(), M, F, A}).
+
+-spec pspawn_link(module(), atom(), [term()]) -> pid().
+pspawn_link(M, F, A) ->
+ P = pspawn(M, F, A),
+ link(P),
+ P.
+
+start_nodes([], _, _) -> [];
+start_nodes([Host|Tail], Name, Args) ->
+ case slave:start(Host, Name, Args) of
+ {error, R} ->
+ io:format("Can't start node on host ~w due to ~w~n",[Host, R]),
+ start_nodes(Tail, Name, Args);
+ {ok, Node} ->
+ [Node | start_nodes(Tail, Name, Args)]
+ end.
+
+-spec stop() -> 'stopped'.
+stop() ->
+ gen_server:call({global, pool_master}, stop).
+
+get_elements(_Pos,[]) -> [];
+get_elements(Pos,[E|T]) -> [element(Pos,E) | get_elements(Pos,T)].
+
+stop_em([]) -> stopped;
+stop_em([N|Tail]) ->
+ rpc:cast(N, erlang, halt, []),
+ stop_em(Tail).
+
+init([]) ->
+ process_flag(trap_exit, true),
+ spawn_link(pool, statistic_collector, []),
+ {ok,[{0,node()}]}.
+
+handle_call(get_nodes, _From, Nodes)->
+ {reply, Nodes, Nodes};
+handle_call(get_node, _From, [{Load,N}|Tail]) ->
+ {reply, N, Tail++[{Load+1, N}]};
+handle_call({attach, Node}, _From, Nodes) ->
+ case lists:keymember(Node, 2, Nodes) of
+ true ->
+ {reply, already_attached, Nodes};
+ false ->
+ erlang:monitor_node(Node, true),
+ spawn_link(Node, pool, statistic_collector, []),
+ {reply, attached, Nodes++[{999999,Node}]}
+ end;
+handle_call({spawn, Gl, M, F, A}, _From, Nodes) ->
+ [{Load,N}|Tail] = Nodes,
+ Pid = spawn(N, pool, do_spawn, [Gl, M, F, A]),
+ {reply, Pid, Tail++[{Load+1, N}]};
+handle_call(stop, _From, Nodes) ->
+ %% clean up in terminate/2
+ {stop, normal, stopped, Nodes}.
+
+handle_cast(_, Nodes) ->
+ {noreply, Nodes}.
+
+handle_info({Node,load,Load}, Nodes) ->
+ Nodes2 = insert_node({Load,Node}, Nodes),
+ {noreply, Nodes2};
+handle_info({nodedown, Node}, Nodes) ->
+ {noreply, lists:keydelete(Node, 2, Nodes)};
+handle_info(_, Nodes) -> %% The EXIT signals etc.etc
+ {noreply, Nodes}.
+
+terminate(_Reason, Nodes) ->
+ N = lists:delete(node(), get_elements(2, Nodes)),
+ stop_em(N),
+ ok.
+
+-spec do_spawn(pid(), module(), atom(), [term()]) -> term().
+do_spawn(Gl, M, F, A) ->
+ group_leader(Gl, self()),
+ apply(M, F, A).
+
+insert_node({Load,Node},[{L,Node}|Tail]) when Load > L ->
+ %% We have a raised load here
+ pure_insert({Load,Node},Tail);
+insert_node({Load,Node},[{L,N}|Tail]) when Load =< L ->
+ %% Move forward in the list
+ T = lists:keydelete(Node,2,[{L,N}|Tail]),
+ [{Load,Node} | T];
+insert_node(Ln,[H|T]) ->
+ [H | insert_node(Ln,T)];
+insert_node(X,[]) -> % Can't happen
+ error_logger:error_msg("Pool_master: Bad node list X=~w\n", [X]),
+ exit(crash).
+
+pure_insert({Load,Node},[]) ->
+ [{Load,Node}];
+pure_insert({Load,Node},[{L,N}|Tail]) when Load < L ->
+ [{Load,Node}, {L,N} | Tail];
+pure_insert(L,[H|T]) -> [H|pure_insert(L,T)].
+
+%% Really should not measure the contributions from
+%% the back ground processes here .... which we do :-(
+%% We don't have to monitor the master, since we're slaves anyway
+
+statistic_collector() ->
+ statistic_collector(5).
+
+statistic_collector(0) -> exit(normal);
+statistic_collector(I) ->
+ sleep(300),
+ case global:whereis_name(pool_master) of
+ undefined ->
+ statistic_collector(I-1);
+ M ->
+ stat_loop(M, 999999)
+ end.
+
+%% Do not tell the master about our load if it has not changed
+
+stat_loop(M, Old) ->
+ sleep(2000),
+ case statistics(run_queue) of
+ Old ->
+ stat_loop(M, Old);
+ NewLoad ->
+ M ! {node(), load, NewLoad}, %% async
+ stat_loop(M, NewLoad)
+ end.
+
+sleep(I) -> receive after I -> ok end.
diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl
new file mode 100644
index 0000000000..9aa5e0a71e
--- /dev/null
+++ b/lib/stdlib/src/proc_lib.erl
@@ -0,0 +1,624 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(proc_lib).
+
+%% This module is used to set some initial information
+%% in each created process.
+%% Then a process terminates the Reason is checked and
+%% a crash report is generated if the Reason was not expected.
+
+-export([spawn/1, spawn_link/1, spawn/2, spawn_link/2,
+ spawn/3, spawn_link/3, spawn/4, spawn_link/4,
+ spawn_opt/2, spawn_opt/3, spawn_opt/4, spawn_opt/5,
+ start/3, start/4, start/5, start_link/3, start_link/4, start_link/5,
+ hibernate/3,
+ init_ack/1, init_ack/2,
+ init_p/3,init_p/5,format/1,initial_call/1,translate_initial_call/1]).
+
+%% Internal exports.
+-export([wake_up/3]).
+
+%%-----------------------------------------------------------------------------
+
+-type priority_level() :: 'high' | 'low' | 'max' | 'normal'.
+-type spawn_option() :: 'link'
+ | {'priority', priority_level()}
+ | {'min_heap_size', non_neg_integer()}
+ | {'fullsweep_after', non_neg_integer()}.
+
+-type dict_or_pid() :: pid() | [_] | {integer(), integer(), integer()}.
+
+%%-----------------------------------------------------------------------------
+
+-spec spawn(function()) -> pid().
+
+spawn(F) when is_function(F) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn(?MODULE, init_p, [Parent,Ancestors,F]).
+
+-spec spawn(atom(), atom(), [term()]) -> pid().
+
+spawn(M,F,A) when is_atom(M), is_atom(F), is_list(A) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn(?MODULE, init_p, [Parent,Ancestors,M,F,A]).
+
+-spec spawn_link(function()) -> pid().
+
+spawn_link(F) when is_function(F) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn_link(?MODULE, init_p, [Parent,Ancestors,F]).
+
+-spec spawn_link(atom(), atom(), [term()]) -> pid().
+
+spawn_link(M,F,A) when is_atom(M), is_atom(F), is_list(A) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn_link(?MODULE, init_p, [Parent,Ancestors,M,F,A]).
+
+-spec spawn(node(), function()) -> pid().
+
+spawn(Node, F) when is_function(F) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn(Node, ?MODULE, init_p, [Parent,Ancestors,F]).
+
+-spec spawn(node(), atom(), atom(), [term()]) -> pid().
+spawn(Node, M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn(Node, ?MODULE, init_p, [Parent,Ancestors,M,F,A]).
+
+-spec spawn_link(node(), function()) -> pid().
+
+spawn_link(Node, F) when is_function(F) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn_link(Node, ?MODULE, init_p, [Parent,Ancestors,F]).
+
+-spec spawn_link(node(), atom(), atom(), [term()]) -> pid().
+
+spawn_link(Node, M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ erlang:spawn_link(Node, ?MODULE, init_p, [Parent,Ancestors,M,F,A]).
+
+-spec spawn_opt(function(), [spawn_option()]) -> pid().
+spawn_opt(F, Opts) when is_function(F) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ check_for_monitor(Opts),
+ erlang:spawn_opt(?MODULE, init_p, [Parent,Ancestors,F],Opts).
+
+-spec spawn_opt(node(), function(), [spawn_option()]) -> pid().
+
+spawn_opt(Node, F, Opts) when is_function(F) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ check_for_monitor(Opts),
+ erlang:spawn_opt(Node, ?MODULE, init_p, [Parent,Ancestors,F], Opts).
+
+-spec spawn_opt(atom(), atom(), [term()], [spawn_option()]) -> pid().
+
+spawn_opt(M, F, A, Opts) when is_atom(M), is_atom(F), is_list(A) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ check_for_monitor(Opts),
+ erlang:spawn_opt(?MODULE, init_p, [Parent,Ancestors,M,F,A], Opts).
+
+-spec spawn_opt(node(), atom(), atom(), [term()], [spawn_option()]) -> pid().
+
+spawn_opt(Node, M, F, A, Opts) when is_atom(M), is_atom(F), is_list(A) ->
+ Parent = get_my_name(),
+ Ancestors = get_ancestors(),
+ check_for_monitor(Opts),
+ erlang:spawn_opt(Node, ?MODULE, init_p, [Parent,Ancestors,M,F,A], Opts).
+
+%% OTP-6345
+%% monitor spawn_opt option is currently not possible to use
+check_for_monitor(SpawnOpts) ->
+ case lists:member(monitor, SpawnOpts) of
+ true ->
+ erlang:error(badarg);
+ false ->
+ false
+ end.
+
+-spec hibernate(module(), atom(), [term()]) -> no_return().
+
+hibernate(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ erlang:hibernate(?MODULE, wake_up, [M, F, A]).
+
+ensure_link(SpawnOpts) ->
+ case lists:member(link, SpawnOpts) of
+ true ->
+ SpawnOpts;
+ false ->
+ [link|SpawnOpts]
+ end.
+
+-spec init_p(pid(), [pid()], function()) -> term().
+
+init_p(Parent, Ancestors, Fun) when is_function(Fun) ->
+ put('$ancestors', [Parent|Ancestors]),
+ {module,Mod} = erlang:fun_info(Fun, module),
+ {name,Name} = erlang:fun_info(Fun, name),
+ {arity,Arity} = erlang:fun_info(Fun, arity),
+ put('$initial_call', {Mod,Name,Arity}),
+ try
+ Fun()
+ catch
+ Class:Reason ->
+ exit_p(Class, Reason)
+ end.
+
+-spec init_p(pid(), [pid()], atom(), atom(), [term()]) -> term().
+
+init_p(Parent, Ancestors, M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ put('$ancestors', [Parent|Ancestors]),
+ put('$initial_call', trans_init(M, F, A)),
+ init_p_do_apply(M, F, A).
+
+init_p_do_apply(M, F, A) ->
+ try
+ apply(M, F, A)
+ catch
+ Class:Reason ->
+ exit_p(Class, Reason)
+ end.
+
+-spec wake_up(atom(), atom(), [term()]) -> term().
+
+wake_up(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ try
+ apply(M, F, A)
+ catch
+ Class:Reason ->
+ exit_p(Class, Reason)
+ end.
+
+exit_p(Class, Reason) ->
+ case get('$initial_call') of
+ {M,F,A} when is_atom(M), is_atom(F), is_integer(A) ->
+ MFA = {M,F,make_dummy_args(A, [])},
+ crash_report(Class, Reason, MFA),
+ exit(Reason);
+ _ ->
+ %% The process dictionary has been cleared or
+ %% possibly modified.
+ crash_report(Class, Reason, []),
+ exit(Reason)
+ end.
+
+-spec start(atom(), atom(), [term()]) -> term().
+
+start(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ start(M, F, A, infinity).
+
+-spec start(atom(), atom(), [term()], timeout()) -> term().
+
+start(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) ->
+ Pid = ?MODULE:spawn(M, F, A),
+ sync_wait(Pid, Timeout).
+
+-spec start(atom(), atom(), [term()], timeout(), [spawn_option()]) -> term().
+
+start(M, F, A, Timeout, SpawnOpts) when is_atom(M), is_atom(F), is_list(A) ->
+ Pid = ?MODULE:spawn_opt(M, F, A, SpawnOpts),
+ sync_wait(Pid, Timeout).
+
+-spec start_link(atom(), atom(), [term()]) -> term().
+
+start_link(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ start_link(M, F, A, infinity).
+
+-spec start_link(atom(), atom(), [term()], timeout()) -> term().
+
+start_link(M, F, A, Timeout) when is_atom(M), is_atom(F), is_list(A) ->
+ Pid = ?MODULE:spawn_link(M, F, A),
+ sync_wait(Pid, Timeout).
+
+-spec start_link(atom(),atom(),[term()],timeout(),[spawn_option()]) -> term().
+
+start_link(M,F,A,Timeout,SpawnOpts) when is_atom(M), is_atom(F), is_list(A) ->
+ Pid = ?MODULE:spawn_opt(M, F, A, ensure_link(SpawnOpts)),
+ sync_wait(Pid, Timeout).
+
+sync_wait(Pid, Timeout) ->
+ receive
+ {ack, Pid, Return} ->
+ Return;
+ {'EXIT', Pid, Reason} ->
+ {error, Reason}
+ after Timeout ->
+ unlink(Pid),
+ exit(Pid, kill),
+ flush(Pid),
+ {error, timeout}
+ end.
+
+-spec flush(pid()) -> 'true'.
+
+flush(Pid) ->
+ receive
+ {'EXIT', Pid, _} ->
+ true
+ after 0 ->
+ true
+ end.
+
+-spec init_ack(pid(), term()) -> 'ok'.
+
+init_ack(Parent, Return) ->
+ Parent ! {ack, self(), Return},
+ ok.
+
+-spec init_ack(term()) -> 'ok'.
+init_ack(Return) ->
+ [Parent|_] = get('$ancestors'),
+ init_ack(Parent, Return).
+
+%% -----------------------------------------------------
+%% Fetch the initial call of a proc_lib spawned process.
+%% -----------------------------------------------------
+
+-spec initial_call(dict_or_pid()) -> {atom(), atom(), [atom()]} | 'false'.
+
+initial_call(DictOrPid) ->
+ case raw_initial_call(DictOrPid) of
+ {M,F,A} ->
+ {M,F,make_dummy_args(A, [])};
+ false ->
+ false
+ end.
+
+make_dummy_args(0, Acc) ->
+ Acc;
+make_dummy_args(N, Acc) ->
+ Arg = list_to_atom("Argument__" ++ integer_to_list(N)),
+ make_dummy_args(N-1, [Arg|Acc]).
+
+%% -----------------------------------------------------
+%% Translate the '$initial_call' to some useful information.
+%% However, the arguments are not returned here; only the
+%% arity of the initial function.
+%% This function is typically called from c:i() and c:regs().
+%% -----------------------------------------------------
+
+-spec translate_initial_call(dict_or_pid()) -> mfa().
+
+translate_initial_call(DictOrPid) ->
+ case raw_initial_call(DictOrPid) of
+ {_,_,_}=MFA ->
+ MFA;
+ false ->
+ {?MODULE,init_p,5}
+ end.
+
+%% -----------------------------------------------------
+%% Fetch the initial call information exactly as stored
+%% in the process dictionary.
+%% -----------------------------------------------------
+
+raw_initial_call({X,Y,Z}) when is_integer(X), is_integer(Y), is_integer(Z) ->
+ raw_initial_call(c:pid(X,Y,Z));
+raw_initial_call(Pid) when is_pid(Pid) ->
+ case get_process_info(Pid, dictionary) of
+ {dictionary,Dict} ->
+ raw_init_call(Dict);
+ _ ->
+ false
+ end;
+raw_initial_call(ProcInfo) when is_list(ProcInfo) ->
+ case lists:keyfind(dictionary, 1, ProcInfo) of
+ {dictionary,Dict} ->
+ raw_init_call(Dict);
+ _ ->
+ false
+ end.
+
+raw_init_call(Dict) ->
+ case lists:keyfind('$initial_call', 1, Dict) of
+ {_,{_,_,_}=MFA} ->
+ MFA;
+ _ ->
+ false
+ end.
+
+%% -----------------------------------------------------
+%% Translate the initial call to some useful information.
+%% -----------------------------------------------------
+
+trans_init(gen,init_it,[gen_server,_,_,supervisor,{_,Module,_},_]) ->
+ {supervisor,Module,1};
+trans_init(gen,init_it,[gen_server,_,_,_,supervisor,{_,Module,_},_]) ->
+ {supervisor,Module,1};
+trans_init(gen,init_it,[gen_server,_,_,supervisor_bridge,[Module|_],_]) ->
+ {supervisor_bridge,Module,1};
+trans_init(gen,init_it,[gen_server,_,_,_,supervisor_bridge,[Module|_],_]) ->
+ {supervisor_bridge,Module,1};
+trans_init(gen,init_it,[gen_server,_,_,Module,_,_]) ->
+ {Module,init,1};
+trans_init(gen,init_it,[gen_server,_,_,_,Module|_]) ->
+ {Module,init,1};
+trans_init(gen,init_it,[gen_fsm,_,_,Module,_,_]) ->
+ {Module,init,1};
+trans_init(gen,init_it,[gen_fsm,_,_,_,Module|_]) ->
+ {Module,init,1};
+trans_init(gen,init_it,[gen_event|_]) ->
+ {gen_event,init_it,6};
+trans_init(M, F, A) when is_atom(M), is_atom(F) ->
+ {M,F,length(A)}.
+
+%% -----------------------------------------------------
+%% Generate a crash report.
+%% -----------------------------------------------------
+
+crash_report(exit, normal, _) -> ok;
+crash_report(exit, shutdown, _) -> ok;
+crash_report(exit, {shutdown,_}, _) -> ok;
+crash_report(Class, Reason, StartF) ->
+ OwnReport = my_info(Class, Reason, StartF),
+ LinkReport = linked_info(self()),
+ Rep = [OwnReport,LinkReport],
+ error_logger:error_report(crash_report, Rep).
+
+my_info(Class, Reason, []) ->
+ my_info_1(Class, Reason);
+my_info(Class, Reason, StartF) ->
+ [{initial_call, StartF}|my_info_1(Class, Reason)].
+
+my_info_1(Class, Reason) ->
+ [{pid, self()},
+ get_process_info(self(), registered_name),
+ {error_info, {Class,Reason,erlang:get_stacktrace()}},
+ get_ancestors(self()),
+ get_process_info(self(), messages),
+ get_process_info(self(), links),
+ get_cleaned_dictionary(self()),
+ get_process_info(self(), trap_exit),
+ get_process_info(self(), status),
+ get_process_info(self(), heap_size),
+ get_process_info(self(), stack_size),
+ get_process_info(self(), reductions)
+ ].
+
+-spec get_ancestors(pid()) -> {'ancestors', [pid()]}.
+
+get_ancestors(Pid) ->
+ case get_dictionary(Pid,'$ancestors') of
+ {'$ancestors',Ancestors} ->
+ {ancestors,Ancestors};
+ _ ->
+ {ancestors,[]}
+ end.
+
+get_cleaned_dictionary(Pid) ->
+ case get_process_info(Pid,dictionary) of
+ {dictionary,Dict} -> {dictionary,clean_dict(Dict)};
+ _ -> {dictionary,[]}
+ end.
+
+clean_dict([{'$ancestors',_}|Dict]) ->
+ clean_dict(Dict);
+clean_dict([{'$initial_call',_}|Dict]) ->
+ clean_dict(Dict);
+clean_dict([E|Dict]) ->
+ [E|clean_dict(Dict)];
+clean_dict([]) ->
+ [].
+
+get_dictionary(Pid,Tag) ->
+ case get_process_info(Pid,dictionary) of
+ {dictionary,Dict} ->
+ case lists:keysearch(Tag,1,Dict) of
+ {value,Value} -> Value;
+ _ -> undefined
+ end;
+ _ ->
+ undefined
+ end.
+
+linked_info(Pid) ->
+ make_neighbour_reports1(neighbours(Pid)).
+
+make_neighbour_reports1([P|Ps]) ->
+ ReportBody = make_neighbour_report(P),
+ %%
+ %% Process P might have been deleted.
+ %%
+ case lists:member(undefined, ReportBody) of
+ true ->
+ make_neighbour_reports1(Ps);
+ false ->
+ [{neighbour, ReportBody}|make_neighbour_reports1(Ps)]
+ end;
+make_neighbour_reports1([]) ->
+ [].
+
+make_neighbour_report(Pid) ->
+ [{pid, Pid},
+ get_process_info(Pid, registered_name),
+ get_initial_call(Pid),
+ get_process_info(Pid, current_function),
+ get_ancestors(Pid),
+ get_process_info(Pid, messages),
+ get_process_info(Pid, links),
+ get_cleaned_dictionary(Pid),
+ get_process_info(Pid, trap_exit),
+ get_process_info(Pid, status),
+ get_process_info(Pid, heap_size),
+ get_process_info(Pid, stack_size),
+ get_process_info(Pid, reductions)
+ ].
+
+get_initial_call(Pid) ->
+ case get_dictionary(Pid, '$initial_call') of
+ {'$initial_call', {M, F, A}} ->
+ {initial_call, {M, F, make_dummy_args(A, [])}};
+ _ ->
+ get_process_info(Pid, initial_call)
+ end.
+
+%% neighbours(Pid) = list of Pids
+%%
+%% Get the neighbours of Pid. A neighbour is a process which is
+%% linked to Pid and does not trap exit; or a neigbour of a
+%% neighbour etc.
+%%
+%% A breadth-first search is performed.
+
+-spec neighbours(pid()) -> [pid()].
+
+neighbours(Pid) ->
+ {_, Visited} = visit(adjacents(Pid), {max_neighbours(), [Pid]}),
+ lists:delete(Pid, Visited).
+
+max_neighbours() -> 15.
+
+%%
+%% visit(Ps, {N, Vs}) = {N0, V0s}
+%%
+%% A breadth-first search of neighbours.
+%% Ps processes,
+%% Vs visited processes,
+%% N max number to visit.
+%%
+visit([P|Ps], {N, Vs} = NVs) when N > 0 ->
+ case lists:member(P, Vs) of
+ false -> visit(adjacents(P), visit(Ps, {N-1, [P|Vs]}));
+ true -> visit(Ps, NVs)
+ end;
+visit(_, {_N, _Vs} = NVs) ->
+ NVs.
+
+%%
+%% adjacents(Pid) = AdjacencyList
+%%
+-spec adjacents(pid()) -> [pid()].
+
+adjacents(Pid) ->
+ case catch proc_info(Pid, links) of
+ {links, Links} -> no_trap(Links);
+ _ -> []
+ end.
+
+no_trap([P|Ps]) ->
+ case catch proc_info(P, trap_exit) of
+ {trap_exit, false} -> [P|no_trap(Ps)];
+ _ -> no_trap(Ps)
+ end;
+no_trap([]) ->
+ [].
+
+get_process_info(Pid, Tag) ->
+ translate_process_info(Tag, catch proc_info(Pid, Tag)).
+
+translate_process_info(registered_name, []) ->
+ {registered_name, []};
+translate_process_info(_ , {'EXIT', _}) ->
+ undefined;
+translate_process_info(_, Result) ->
+ Result.
+
+%%% -----------------------------------------------------------
+%%% Misc. functions
+%%% -----------------------------------------------------------
+
+get_my_name() ->
+ case proc_info(self(),registered_name) of
+ {registered_name,Name} -> Name;
+ _ -> self()
+ end.
+
+-spec get_ancestors() -> [pid()].
+
+get_ancestors() ->
+ case get('$ancestors') of
+ A when is_list(A) -> A;
+ _ -> []
+ end.
+
+proc_info(Pid,Item) when node(Pid) =:= node() ->
+ process_info(Pid,Item);
+proc_info(Pid,Item) ->
+ case lists:member(node(Pid),nodes()) of
+ true ->
+ check(rpc:call(node(Pid), erlang, process_info, [Pid, Item]));
+ _ ->
+ hidden
+ end.
+
+check({badrpc,nodedown}) -> undefined;
+check({badrpc,Error}) -> Error;
+check(Res) -> Res.
+
+%%% -----------------------------------------------------------
+%%% Format (and write) a generated crash info structure.
+%%% -----------------------------------------------------------
+
+-spec format([term()]) -> string().
+
+format([OwnReport,LinkReport]) ->
+ OwnFormat = format_report(OwnReport),
+ LinkFormat = format_report(LinkReport),
+ S = io_lib:format(" crasher:~n~s neighbours:~n~s",[OwnFormat,LinkFormat]),
+ lists:flatten(S).
+
+format_report(Rep) when is_list(Rep) ->
+ format_rep(Rep);
+format_report(Rep) ->
+ io_lib:format("~p~n", [Rep]).
+
+format_rep([{initial_call,InitialCall}|Rep]) ->
+ [format_mfa(InitialCall)|format_rep(Rep)];
+format_rep([{error_info,{Class,Reason,StackTrace}}|Rep]) ->
+ [format_exception(Class, Reason, StackTrace)|format_rep(Rep)];
+format_rep([{Tag,Data}|Rep]) ->
+ [format_tag(Tag, Data)|format_rep(Rep)];
+format_rep(_) ->
+ [].
+
+format_exception(Class, Reason, StackTrace) ->
+ PF = pp_fun(),
+ StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
+ %% EI = " exception: ",
+ EI = " ",
+ [EI, lib:format_exception(1+length(EI), Class, Reason,
+ StackTrace, StackFun, PF), "\n"].
+
+format_mfa({M,F,Args}=StartF) ->
+ try
+ A = length(Args),
+ [" initial call: ",atom_to_list(M),$:,atom_to_list(F),$/,
+ integer_to_list(A),"\n"]
+ catch
+ error:_ ->
+ format_tag(initial_call, StartF)
+ end.
+
+pp_fun() ->
+ fun(Term, I) ->
+ io_lib:format("~." ++ integer_to_list(I) ++ "p", [Term])
+ end.
+
+format_tag(Tag, Data) ->
+ io_lib:format(" ~p: ~80.18p~n", [Tag, Data]).
diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl
new file mode 100644
index 0000000000..35d14891f1
--- /dev/null
+++ b/lib/stdlib/src/proplists.erl
@@ -0,0 +1,686 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% =====================================================================
+%% Support functions for property lists
+%%
+%% Copyright (C) 2000-2003 Richard Carlsson
+%% ---------------------------------------------------------------------
+%%
+%% @doc Support functions for property lists.
+%%
+%% <p>Property lists are ordinary lists containing entries in the form
+%% of either tuples, whose first elements are keys used for lookup and
+%% insertion, or atoms, which work as shorthand for tuples <code>{Atom,
+%% true}</code>. (Other terms are allowed in the lists, but are ignored
+%% by this module.) If there is more than one entry in a list for a
+%% certain key, the first occurrence normally overrides any later
+%% (irrespective of the arity of the tuples).</p>
+%%
+%% <p>Property lists are useful for representing inherited properties,
+%% such as options passed to a function where a user may specify options
+%% overriding the default settings, object properties, annotations,
+%% etc.</p>
+%%
+%% @type property() = atom() | tuple()
+
+-module(proplists).
+
+-export([property/1, property/2, unfold/1, compact/1, lookup/2,
+ lookup_all/2, is_defined/2, get_value/2, get_value/3,
+ get_all_values/2, append_values/2, get_bool/2, get_keys/1,
+ delete/2, substitute_aliases/2, substitute_negations/2,
+ expand/2, normalize/2, split/2]).
+
+%% ---------------------------------------------------------------------
+
+-type property() :: atom() | tuple().
+
+-type aliases() :: [{any(), any()}].
+-type negations() :: [{any(), any()}].
+-type expansions() :: [{property(), [any()]}].
+
+%% ---------------------------------------------------------------------
+
+%% @spec property(P::property()) -> property()
+%%
+%% @doc Creates a normal form (minimal) representation of a property. If
+%% <code>P</code> is <code>{Key, true}</code> where <code>Key</code> is
+%% an atom, this returns <code>Key</code>, otherwise the whole term
+%% <code>P</code> is returned.
+%%
+%% @see property/2
+
+-spec property(property()) -> property().
+
+property({Key, true}) when is_atom(Key) ->
+ Key;
+property(Property) ->
+ Property.
+
+
+%% @spec property(Key::term(), Value::term()) -> property()
+%%
+%% @doc Creates a normal form (minimal) representation of a simple
+%% key/value property. Returns <code>Key</code> if <code>Value</code> is
+%% <code>true</code> and <code>Key</code> is an atom, otherwise a tuple
+%% <code>{Key, Value}</code> is returned.
+%%
+%% @see property/1
+
+-spec property(Key::term(), Value::term()) -> atom() | {term(), term()}.
+
+property(Key, true) when is_atom(Key) ->
+ Key;
+property(Key, Value) ->
+ {Key, Value}.
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec unfold(List::[term()]) -> [term()]
+%%
+%% @doc Unfolds all occurences of atoms in <code>List</code> to tuples
+%% <code>{Atom, true}</code>.
+%%
+%% @see compact/1
+
+-spec unfold(List::[term()]) -> [term()].
+
+unfold([P | Ps]) ->
+ if is_atom(P) ->
+ [{P, true} | unfold(Ps)];
+ true ->
+ [P | unfold(Ps)]
+ end;
+unfold([]) ->
+ [].
+
+%% @spec compact(List::[term()]) -> [term()]
+%%
+%% @doc Minimizes the representation of all entries in the list. This is
+%% equivalent to <code>[property(P) || P &lt;- List]</code>.
+%%
+%% @see unfold/1
+%% @see property/1
+
+-spec compact(List::[property()]) -> [property()].
+
+compact(List) ->
+ [property(P) || P <- List].
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec lookup(Key::term(), List::[term()]) -> none | tuple()
+%%
+%% @doc Returns the first entry associated with <code>Key</code> in
+%% <code>List</code>, if one exists, otherwise returns
+%% <code>none</code>. For an atom <code>A</code> in the list, the tuple
+%% <code>{A, true}</code> is the entry associated with <code>A</code>.
+%%
+%% @see lookup_all/2
+%% @see get_value/2
+%% @see get_bool/2
+
+-spec lookup(Key::term(), List::[term()]) -> 'none' | tuple().
+
+lookup(Key, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ {Key, true};
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ %% Note that <code>Key</code> does not have to be an atom in this case.
+ P;
+ true ->
+ lookup(Key, Ps)
+ end;
+lookup(_Key, []) ->
+ none.
+
+%% @spec lookup_all(Key::term(), List::[term()]) -> [tuple()]
+%%
+%% @doc Returns the list of all entries associated with <code>Key</code>
+%% in <code>List</code>. If no such entry exists, the result is the
+%% empty list.
+%%
+%% @see lookup/2
+
+-spec lookup_all(Key::term(), List::[term()]) -> [tuple()].
+
+lookup_all(Key, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ [{Key, true} | lookup_all(Key, Ps)];
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ [P | lookup_all(Key, Ps)];
+ true ->
+ lookup_all(Key, Ps)
+ end;
+lookup_all(_Key, []) ->
+ [].
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec is_defined(Key::term(), List::[term()]) -> boolean()
+%%
+%% @doc Returns <code>true</code> if <code>List</code> contains at least
+%% one entry associated with <code>Key</code>, otherwise
+%% <code>false</code> is returned.
+
+-spec is_defined(Key::term(), List::[term()]) -> boolean().
+
+is_defined(Key, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ true;
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ true;
+ true ->
+ is_defined(Key, Ps)
+ end;
+is_defined(_Key, []) ->
+ false.
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec get_value(Key::term(), List::[term()]) -> term()
+%% @equiv get_value(Key, List, undefined)
+
+-spec get_value(Key::term(), List::[term()]) -> term().
+
+get_value(Key, List) ->
+ get_value(Key, List, undefined).
+
+%% @spec get_value(Key::term(), List::[term()], Default::term()) ->
+%% term()
+%%
+%% @doc Returns the value of a simple key/value property in
+%% <code>List</code>. If <code>lookup(Key, List)</code> would yield
+%% <code>{Key, Value}</code>, this function returns the corresponding
+%% <code>Value</code>, otherwise <code>Default</code> is returned.
+%%
+%% @see lookup/2
+%% @see get_value/2
+%% @see get_all_values/2
+%% @see get_bool/2
+
+-spec get_value(Key::term(), List::[term()], Default::term()) -> term().
+
+get_value(Key, [P | Ps], Default) ->
+ if is_atom(P), P =:= Key ->
+ true;
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ case P of
+ {_, Value} ->
+ Value;
+ _ ->
+ %% Don</code>t continue the search!
+ Default
+ end;
+ true ->
+ get_value(Key, Ps, Default)
+ end;
+get_value(_Key, [], Default) ->
+ Default.
+
+%% @spec get_all_values(Key, List) -> [term()]
+%%
+%% @doc Similar to <code>get_value/2</code>, but returns the list of
+%% values for <em>all</em> entries <code>{Key, Value}</code> in
+%% <code>List</code>. If no such entry exists, the result is the empty
+%% list.
+%%
+%% @see get_value/2
+
+-spec get_all_values(Key::term(), List::[term()]) -> [term()].
+
+get_all_values(Key, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ [true | get_all_values(Key, Ps)];
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ case P of
+ {_, Value} ->
+ [Value | get_all_values(Key, Ps)];
+ _ ->
+ get_all_values(Key, Ps)
+ end;
+ true ->
+ get_all_values(Key, Ps)
+ end;
+get_all_values(_Key, []) ->
+ [].
+
+%% @spec append_values(Key::term(), List::[term()]) -> [term()]
+%%
+%% @doc Similar to <code>get_all_values/2</code>, but each value is
+%% wrapped in a list unless it is already itself a list, and the
+%% resulting list of lists is concatenated. This is often useful for
+%% "incremental" options; e.g., <code>append_values(a, [{a, [1,2]}, {b,
+%% 0}, {a, 3}, {c, -1}, {a, [4]}])</code> will return the list
+%% <code>[1,2,3,4]</code>.
+%%
+%% @see get_all_values/2
+
+-spec append_values(Key::term(), List::[term()]) -> [term()].
+
+append_values(Key, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ [true | append_values(Key, Ps)];
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ case P of
+ {_, Value} when is_list(Value) ->
+ Value ++ append_values(Key, Ps);
+ {_, Value} ->
+ [Value | append_values(Key, Ps)];
+ _ ->
+ append_values(Key, Ps)
+ end;
+ true ->
+ append_values(Key, Ps)
+ end;
+append_values(_Key, []) ->
+ [].
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec get_bool(Key::term(), List::[term()]) -> boolean()
+%%
+%% @doc Returns the value of a boolean key/value option. If
+%% <code>lookup(Key, List)</code> would yield <code>{Key, true}</code>,
+%% this function returns <code>true</code>; otherwise <code>false</code>
+%% is returned.
+%%
+%% @see lookup/2
+%% @see get_value/2
+
+-spec get_bool(Key::term(), List::[term()]) -> boolean().
+
+get_bool(Key, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ true;
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ case P of
+ {_, true} ->
+ true;
+ _ ->
+ %% Don't continue the search!
+ false
+ end;
+ true ->
+ get_bool(Key, Ps)
+ end;
+get_bool(_Key, []) ->
+ false.
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec get_keys(List::[term()]) -> [term()]
+%%
+%% @doc Returns an unordered list of the keys used in <code>List</code>,
+%% not containing duplicates.
+
+-spec get_keys(List::[term()]) -> [term()].
+
+get_keys(Ps) ->
+ sets:to_list(get_keys(Ps, sets:new())).
+
+get_keys([P | Ps], Keys) ->
+ if is_atom(P) ->
+ get_keys(Ps, sets:add_element(P, Keys));
+ tuple_size(P) >= 1 ->
+ get_keys(Ps, sets:add_element(element(1, P), Keys));
+ true ->
+ get_keys(Ps, Keys)
+ end;
+get_keys([], Keys) ->
+ Keys.
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec delete(Key::term(), List::[term()]) -> [term()]
+%%
+%% @doc Deletes all entries associated with <code>Key</code> from
+%% <code>List</code>.
+
+-spec delete(Key::term(), List::[term()]) -> [term()].
+
+delete(Key, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ delete(Key, Ps);
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ delete(Key, Ps);
+ true ->
+ [P | delete(Key, Ps)]
+ end;
+delete(_, []) ->
+ [].
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec substitute_aliases(Aliases, List::[term()]) -> [term()]
+%%
+%% Aliases = [{Key, Key}]
+%% Key = term()
+%%
+%% @doc Substitutes keys of properties. For each entry in
+%% <code>List</code>, if it is associated with some key <code>K1</code>
+%% such that <code>{K1, K2}</code> occurs in <code>Aliases</code>, the
+%% key of the entry is changed to <code>Key2</code>. If the same
+%% <code>K1</code> occurs more than once in <code>Aliases</code>, only
+%% the first occurrence is used.
+%%
+%% <p>Example: <code>substitute_aliases([{color, colour}], L)</code>
+%% will replace all tuples <code>{color, ...}</code> in <code>L</code>
+%% with <code>{colour, ...}</code>, and all atoms <code>color</code>
+%% with <code>colour</code>.</p>
+%%
+%% @see substitute_negations/2
+%% @see normalize/2
+
+-spec substitute_aliases(aliases(), List::[term()]) -> [term()].
+
+substitute_aliases(As, Props) ->
+ [substitute_aliases_1(As, P) || P <- Props].
+
+substitute_aliases_1([{Key, Key1} | As], P) ->
+ if is_atom(P), P =:= Key ->
+ property(Key1, true);
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ property(setelement(1, P, Key1));
+ true ->
+ substitute_aliases_1(As, P)
+ end;
+substitute_aliases_1([], P) ->
+ P.
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec substitute_negations(Negations, List::[term()]) -> [term()]
+%%
+%% Negations = [{Key, Key}]
+%% Key = term()
+%%
+%% @doc Substitutes keys of boolean-valued properties and simultaneously
+%% negates their values. For each entry in <code>List</code>, if it is
+%% associated with some key <code>K1</code> such that <code>{K1,
+%% K2}</code> occurs in <code>Negations</code>, then if the entry was
+%% <code>{K1, true}</code> it will be replaced with <code>{K2,
+%% false}</code>, otherwise it will be replaced with <code>{K2,
+%% true}</code>, thus changing the name of the option and simultaneously
+%% negating the value given by <code>get_bool(List)</code>. If the same
+%% <code>K1</code> occurs more than once in <code>Negations</code>, only
+%% the first occurrence is used.
+%%
+%% <p>Example: <code>substitute_negations([{no_foo, foo}], L)</code>
+%% will replace any atom <code>no_foo</code> or tuple <code>{no_foo,
+%% true}</code> in <code>L</code> with <code>{foo, false}</code>, and
+%% any other tuple <code>{no_foo, ...}</code> with <code>{foo,
+%% true}</code>.</p>
+%%
+%% @see get_bool/2
+%% @see substitute_aliases/2
+%% @see normalize/2
+
+-spec substitute_negations(negations(), List::[term()]) -> [term()].
+
+substitute_negations(As, Props) ->
+ [substitute_negations_1(As, P) || P <- Props].
+
+substitute_negations_1([{Key, Key1} | As], P) ->
+ if is_atom(P), P =:= Key ->
+ property(Key1, false);
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ case P of
+ {_, true} ->
+ property(Key1, false);
+ {_, false} ->
+ property(Key1, true);
+ _ ->
+ %% The property is supposed to be a boolean, so any
+ %% other tuple is interpreted as `false', as done in
+ %% `get_bool'.
+ property(Key1, true)
+ end;
+ true ->
+ substitute_negations_1(As, P)
+ end;
+substitute_negations_1([], P) ->
+ P.
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec expand(Expansions, List::[term()]) -> [term()]
+%%
+%% Expansions = [{property(), [term()]}]
+%%
+%% @doc Expands particular properties to corresponding sets of
+%% properties (or other terms). For each pair <code>{Property,
+%% Expansion}</code> in <code>Expansions</code>, if <code>E</code> is
+%% the first entry in <code>List</code> with the same key as
+%% <code>Property</code>, and <code>E</code> and <code>Property</code>
+%% have equivalent normal forms, then <code>E</code> is replaced with
+%% the terms in <code>Expansion</code>, and any following entries with
+%% the same key are deleted from <code>List</code>.
+%%
+%% <p>For example, the following expressions all return <code>[fie, bar,
+%% baz, fum]</code>:
+%% <ul>
+%% <li><code>expand([{foo, [bar, baz]}],
+%% [fie, foo, fum])</code></li>
+%% <li><code>expand([{{foo, true}, [bar, baz]}],
+%% [fie, foo, fum])</code></li>
+%% <li><code>expand([{{foo, false}, [bar, baz]}],
+%% [fie, {foo, false}, fum])</code></li>
+%% </ul>
+%% However, no expansion is done in the following call:
+%% <ul>
+%% <li><code>expand([{{foo, true}, [bar, baz]}],
+%% [{foo, false}, fie, foo, fum])</code></li>
+%% </ul>
+%% because <code>{foo, false}</code> shadows <code>foo</code>.</p>
+%%
+%% <p>Note that if the original property term is to be preserved in the
+%% result when expanded, it must be included in the expansion list. The
+%% inserted terms are not expanded recursively. If
+%% <code>Expansions</code> contains more than one property with the same
+%% key, only the first occurrance is used.</p>
+%%
+%% @see normalize/2
+
+-spec expand(Expansions::expansions(), [term()]) -> [term()].
+
+expand(Es, Ps) when is_list(Ps) ->
+ Es1 = [{property(P), V} || {P, V} <- Es],
+ flatten(expand_0(key_uniq(Es1), Ps)).
+
+%% Here, all key properties are normalized and there are no multiple
+%% entries in the list of expansions for any specific key property. We
+%% insert the expansions one at a time - this is quadratic, but gives
+%% the desired behaviour in a simple way.
+
+expand_0([{P, L} | Es], Ps) ->
+ expand_0(Es, expand_1(P, L, Ps));
+expand_0([], Ps) ->
+ Ps.
+
+expand_1(P, L, Ps) ->
+ %% First, we must find out what key to look for.
+ %% P has a minimal representation here.
+ if is_atom(P) ->
+ expand_2(P, P, L, Ps);
+ tuple_size(P) >= 1 ->
+ expand_2(element(1, P), P, L, Ps);
+ true ->
+ Ps % refuse to expand non-property
+ end.
+
+expand_2(Key, P1, L, [P | Ps]) ->
+ if is_atom(P), P =:= Key ->
+ expand_3(Key, P1, P, L, Ps);
+ tuple_size(P) >= 1, element(1, P) =:= Key ->
+ expand_3(Key, P1, property(P), L, Ps);
+ true ->
+ %% This case handles non-property entries, and thus
+ %% any already inserted expansions (lists), by simply
+ %% ignoring them.
+ [P | expand_2(Key, P1, L, Ps)]
+ end;
+expand_2(_, _, _, []) ->
+ [].
+
+expand_3(Key, P1, P, L, Ps) ->
+ %% Here, we have found the first entry with a matching key. Both P
+ %% and P1 have minimal representations here. The inserted list will
+ %% be flattened afterwards. If the expansion is done, we drop the
+ %% found entry and alao delete any later entries with the same key.
+ if P1 =:= P ->
+ [L | delete(Key, Ps)];
+ true ->
+ %% The existing entry does not match - keep it.
+ [P | Ps]
+ end.
+
+key_uniq([{K, V} | Ps]) ->
+ [{K, V} | key_uniq_1(K, Ps)];
+key_uniq([]) ->
+ [].
+
+key_uniq_1(K, [{K1, V} | Ps]) ->
+ if K =:= K1 ->
+ key_uniq_1(K, Ps);
+ true ->
+ [{K1, V} | key_uniq_1(K1, Ps)]
+ end;
+key_uniq_1(_, []) ->
+ [].
+
+%% This does top-level flattening only.
+
+flatten([E | Es]) when is_list(E) ->
+ E ++ flatten(Es);
+flatten([E | Es]) ->
+ [E | flatten(Es)];
+flatten([]) ->
+ [].
+
+
+%% ---------------------------------------------------------------------
+
+%% @spec normalize(List::[term()], Stages::[Operation]) -> [term()]
+%%
+%% Operation = {aliases, Aliases} | {negations, Negations}
+%% | {expand, Expansions}
+%% Aliases = [{Key, Key}]
+%% Negations = [{Key, Key}]
+%% Key = term()
+%% Expansions = [{property(), [term()]}]
+%%
+%% @doc Passes <code>List</code> through a sequence of
+%% substitution/expansion stages. For an <code>aliases</code> operation,
+%% the function <code>substitute_aliases/2</code> is applied using the
+%% given list of aliases; for a <code>negations</code> operation,
+%% <code>substitute_negations/2</code> is applied using the given
+%% negation list; for an <code>expand</code> operation, the function
+%% <code>expand/2</code> is applied using the given list of expansions.
+%% The final result is automatically compacted (cf.
+%% <code>compact/1</code>).
+%%
+%% <p>Typically you want to substitute negations first, then aliases,
+%% then perform one or more expansions (sometimes you want to pre-expand
+%% particular entries before doing the main expansion). You might want
+%% to substitute negations and/or aliases repeatedly, to allow such
+%% forms in the right-hand side of aliases and expansion lists.</p>
+%%
+%% @see substitute_aliases/2
+%% @see substitute_negations/2
+%% @see expand/2
+%% @see compact/1
+
+-type operation() :: {'aliases', aliases()}
+ | {'negations', negations()}
+ | {'expand', expansions()}.
+
+-spec normalize(List::[term()], Stages::[operation()]) -> [term()].
+
+normalize(L, [{aliases, As} | Xs]) ->
+ normalize(substitute_aliases(As, L), Xs);
+normalize(L, [{expand, Es} | Xs]) ->
+ normalize(expand(Es, L), Xs);
+normalize(L, [{negations, Ns} | Xs]) ->
+ normalize(substitute_negations(Ns, L), Xs);
+normalize(L, []) ->
+ compact(L).
+
+%% ---------------------------------------------------------------------
+
+%% @spec split(List::[term()], Keys::[term()]) -> {Lists, Rest}
+%% Lists = [[term()]]
+%% Rest = [term()]
+%%
+%% @doc Partitions <code>List</code> into a list of sublists and a
+%% remainder. <code>Lists</code> contains one sublist for each key in
+%% <code>Keys</code>, in the corresponding order. The relative order of
+%% the elements in each sublist is preserved from the original
+%% <code>List</code>. <code>Rest</code> contains the elements in
+%% <code>List</code> that are not associated with any of the given keys,
+%% also with their original relative order preserved.
+%%
+%% <p>Example:<pre>
+%% split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</pre>
+%% returns<pre>
+%% {[[a], [{b, 5}, b],[{c, 2}, {c, 3, 4}]], [{e, 1}, d]}</pre>
+%% </p>
+
+-spec split(List::[term()], Keys::[term()]) -> {[[term()]], [term()]}.
+
+split(List, Keys) ->
+ {Store, Rest} = split(List, dict:from_list([{K, []} || K <- Keys]), []),
+ {[lists:reverse(dict:fetch(K, Store)) || K <- Keys],
+ lists:reverse(Rest)}.
+
+split([P | Ps], Store, Rest) ->
+ if is_atom(P) ->
+ case dict:is_key(P, Store) of
+ true ->
+ split(Ps, dict_prepend(P, P, Store), Rest);
+ false ->
+ split(Ps, Store, [P | Rest])
+ end;
+ tuple_size(P) >= 1 ->
+ %% Note that Key does not have to be an atom in this case.
+ Key = element(1, P),
+ case dict:is_key(Key, Store) of
+ true ->
+ split(Ps, dict_prepend(Key, P, Store), Rest);
+ false ->
+ split(Ps, Store, [P | Rest])
+ end;
+ true ->
+ split(Ps, Store, [P | Rest])
+ end;
+split([], Store, Rest) ->
+ {Store, Rest}.
+
+dict_prepend(Key, Val, Dict) ->
+ dict:store(Key, [Val | dict:fetch(Key, Dict)], Dict).
diff --git a/lib/stdlib/src/qlc.erl b/lib/stdlib/src/qlc.erl
new file mode 100644
index 0000000000..ef142e1c8a
--- /dev/null
+++ b/lib/stdlib/src/qlc.erl
@@ -0,0 +1,3540 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(qlc).
+
+%%% Purpose: Main API module qlc. Functions for evaluation.
+%%% Other files:
+%%% qlc_pt. Implements the parse transform.
+
+%% External exports
+
+-export([parse_transform/2, transform_from_evaluator/2]).
+
+-export([q/1, q/2]).
+
+-export([eval/1, e/1, eval/2, e/2, fold/3, fold/4]).
+-export([cursor/1, cursor/2,
+ next_answers/1, next_answers/2,
+ delete_cursor/1]).
+-export([append/1, append/2]).
+
+-export([sort/1, sort/2, keysort/2, keysort/3]).
+
+-export([table/2]).
+
+-export([info/1, info/2]).
+
+-export([string_to_handle/1, string_to_handle/2, string_to_handle/3]).
+
+-export([format_error/1]).
+
+%% Exported to qlc_pt.erl only:
+-export([template_state/0, aux_name/3, name_suffix/2, vars/1,
+ var_ufold/2, var_fold/3, all_selections/1]).
+
+%% When cache=list lists bigger than ?MAX_LIST_SIZE bytes are put on
+%% file. Also used when merge join finds big equivalence classes.
+-define(MAX_LIST_SIZE, 512*1024).
+
+-record(qlc_append, % qlc:append/1,2
+ {hl
+ }).
+
+-record(qlc_table, % qlc:table/2
+ {trav_fun, % traverse fun
+ trav_MS, % bool(); true iff traverse fun takes a match spec
+ pre_fun,
+ post_fun,
+ info_fun,
+ format_fun,
+ lookup_fun,
+ parent_fun,
+ key_equality, % '==' | '=:=' | undefined (--R12B-5)
+ lu_vals, % undefined | {Position,Values}; values to be looked up
+ ms = no_match_spec
+ % match specification; [T || P <- Tab, Fs]
+ }).
+
+-record(qlc_sort, % qlc:sort/1,2 and qlc:keysort/2,3
+ {h,
+ keypos, % sort | {keysort, KeyPos}
+ unique,
+ compressed, % [] | [compressed]
+ order,
+ fs_opts, % file_sorter options
+ tmpdir_usage = allowed, % allowed | not_allowed
+ % | warning_msg | error_msg | info_msg
+ tmpdir
+ }).
+
+%% Also in qlc_pt.erl.
+-record(qlc_lc, % qlc:q/1,2
+ {lc,
+ opt % #qlc_opt
+ }).
+
+-record(qlc_list, % a prepared list
+ {l,
+ ms = no_match_spec
+ }).
+
+-record(qlc_join, % a prepared join
+ {kind, % {merge, KeyEquality} |
+ % {lookup, KeyEquality, LookupFun}
+ opt, % #qlc_opt from q/2.
+ h1, q1, c1, % to be traversed by "lookup join"
+ h2, q2, c2 % to be looked up by "lookup join"
+ }).
+
+%%% A query cursor is a tuple {qlc_cursor, Cursor} where Cursor is a pair
+%%% {CursorPid, OwnerPid}.
+
+-record(qlc_cursor, {c}).
+
+-record(qlc_opt,
+ {unique = false, % bool()
+ cache = false, % bool() | list (true~ets, false~no)
+ max_lookup = -1, % int() >= 0 | -1 (represents infinity)
+ join = any, % any | nested_loop | merge | lookup
+ tmpdir = "", % global tmpdir
+ lookup = any, % any | bool()
+ max_list = ?MAX_LIST_SIZE, % int() >= 0
+ tmpdir_usage = allowed % allowed | not_allowed
+ % | warning_msg | error_msg | info_msg
+ }).
+
+-record(setup, {parent}).
+
+-define(THROWN_ERROR, {?MODULE, throw_error, _}).
+
+%%% A query handle is a tuple {qlc_handle, Handle} where Handle is one
+%%% of #qlc_append, #qlc_table, #qlc_sort, and #qlc_lc.
+
+-record(qlc_handle, {h}).
+
+get_handle(#qlc_handle{h = #qlc_lc{opt = {qlc_opt, U, C, M}}=H}) ->
+ %% R11B-0.
+ H#qlc_lc{opt = #qlc_opt{unique = U, cache = C, max_lookup = M}};
+get_handle(#qlc_handle{h = H}) ->
+ H;
+get_handle(L) when is_list(L) ->
+ L;
+get_handle(_) ->
+ badarg.
+
+%%%
+%%% Exported functions
+%%%
+
+append(QHs) ->
+ Hs = [case get_handle(QH) of
+ badarg -> erlang:error(badarg, [QHs]);
+ H -> H
+ end || QH <- QHs],
+ #qlc_handle{h = #qlc_append{hl = Hs}}.
+
+append(QH1, QH2) ->
+ Hs = [case get_handle(QH) of
+ badarg -> erlang:error(badarg, [QH1, QH2]);
+ H -> H
+ end || QH <- [QH1, QH2]],
+ #qlc_handle{h = #qlc_append{hl = Hs}}.
+
+cursor(QH) ->
+ cursor(QH, []).
+
+cursor(QH, Options) ->
+ case {options(Options, [unique_all, cache_all, tmpdir,
+ spawn_options, max_list_size,
+ tmpdir_usage]),
+ get_handle(QH)} of
+ {B1, B2} when B1 =:= badarg; B2 =:= badarg ->
+ erlang:error(badarg, [QH, Options]);
+ {[GUnique, GCache, TmpDir, SpawnOptions0, MaxList, TmpUsage], H} ->
+ SpawnOptions = spawn_options(SpawnOptions0),
+ case cursor_process(H, GUnique, GCache, TmpDir,
+ SpawnOptions, MaxList, TmpUsage) of
+ Pid when is_pid(Pid) ->
+ #qlc_cursor{c = {Pid, self()}};
+ Error ->
+ Error
+ end
+ end.
+
+delete_cursor(#qlc_cursor{c = {_, Owner}}=C) when Owner =/= self() ->
+ erlang:error(not_cursor_owner, [C]);
+delete_cursor(#qlc_cursor{c = {Pid, _}}) ->
+ stop_cursor(Pid);
+delete_cursor(T) ->
+ erlang:error(badarg, [T]).
+
+e(QH) ->
+ eval(QH, []).
+
+e(QH, Options) ->
+ eval(QH, Options).
+
+eval(QH) ->
+ eval(QH, []).
+
+eval(QH, Options) ->
+ case {options(Options, [unique_all, cache_all, tmpdir, max_list_size,
+ tmpdir_usage]),
+ get_handle(QH)} of
+ {B1, B2} when B1 =:= badarg; B2 =:= badarg ->
+ erlang:error(badarg, [QH, Options]);
+ {[GUnique, GCache, TmpDir, MaxList, TmpUsage], Handle} ->
+ try
+ Prep = prepare_qlc(Handle, [], GUnique, GCache,
+ TmpDir, MaxList, TmpUsage),
+ case setup_qlc(Prep, #setup{parent = self()}) of
+ {L, Post, _LocalPost} when is_list(L) ->
+ post_funs(Post),
+ L;
+ {Objs, Post, _LocalPost} when is_function(Objs) ->
+ try
+ collect(Objs)
+ after
+ post_funs(Post)
+ end
+ end
+ catch Term ->
+ case erlang:get_stacktrace() of
+ [?THROWN_ERROR | _] ->
+ Term;
+ Stacktrace ->
+ erlang:raise(throw, Term, Stacktrace)
+ end
+ end
+ end.
+
+fold(Fun, Acc0, QH) ->
+ fold(Fun, Acc0, QH, []).
+
+fold(Fun, Acc0, QH, Options) ->
+ case {options(Options, [unique_all, cache_all, tmpdir, max_list_size,
+ tmpdir_usage]),
+ get_handle(QH)} of
+ {B1, B2} when B1 =:= badarg; B2 =:= badarg ->
+ erlang:error(badarg, [Fun, Acc0, QH, Options]);
+ {[GUnique, GCache, TmpDir, MaxList, TmpUsage], Handle} ->
+ try
+ Prep = prepare_qlc(Handle, not_a_list, GUnique, GCache,
+ TmpDir, MaxList, TmpUsage),
+ case setup_qlc(Prep, #setup{parent = self()}) of
+ {Objs, Post, _LocalPost} when is_function(Objs);
+ is_list(Objs) ->
+ try
+ fold_loop(Fun, Objs, Acc0)
+ after
+ post_funs(Post)
+ end
+ end
+ catch Term ->
+ case erlang:get_stacktrace() of
+ [?THROWN_ERROR | _] ->
+ Term;
+ Stacktrace ->
+ erlang:raise(throw, Term, Stacktrace)
+ end
+ end
+ end.
+
+format_error(not_a_query_list_comprehension) ->
+ io_lib:format("argument is not a query list comprehension", []);
+format_error({used_generator_variable, V}) ->
+ io_lib:format("generated variable ~w must not be used in list expression",
+ [V]);
+format_error(binary_generator) ->
+ io_lib:format("cannot handle binary generators", []);
+format_error(too_complex_join) ->
+ io_lib:format("cannot handle join of three or more generators efficiently",
+ []);
+format_error(too_many_joins) ->
+ io_lib:format("cannot handle more than one join efficiently", []);
+format_error(nomatch_pattern) ->
+ io_lib:format("pattern cannot possibly match", []);
+format_error(nomatch_filter) ->
+ io_lib:format("filter evaluates to 'false'", []);
+format_error({Line, Mod, Reason}) when is_integer(Line) ->
+ io_lib:format("~p: ~s~n",
+ [Line, lists:flatten(Mod:format_error(Reason))]);
+%% file_sorter errors
+format_error({bad_object, FileName}) ->
+ io_lib:format("the temporary file \"~s\" holding answers is corrupt",
+ [FileName]);
+format_error(bad_object) ->
+ io_lib:format("the keys could not be extracted from some term", []);
+format_error({file_error, FileName, Reason}) ->
+ io_lib:format("\"~s\": ~p~n",[FileName, file:format_error(Reason)]);
+format_error({premature_eof, FileName}) ->
+ io_lib:format("\"~s\": end-of-file was encountered inside some binary term",
+ [FileName]);
+format_error({tmpdir_usage, Why}) ->
+ io_lib:format("temporary file was needed for ~w~n", [Why]);
+format_error({error, Module, Reason}) ->
+ Module:format_error(Reason);
+format_error(E) ->
+ io_lib:format("~p~n", [E]).
+
+info(QH) ->
+ info(QH, []).
+
+info(QH, Options) ->
+ case {options(Options, [unique_all, cache_all, flat, format, n_elements,
+ depth, tmpdir, max_list_size, tmpdir_usage]),
+ get_handle(QH)} of
+ {B1, B2} when B1 =:= badarg; B2 =:= badarg ->
+ erlang:error(badarg, [QH, Options]);
+ {[GUnique, GCache, Flat, Format, NElements,
+ Depth, TmpDir, MaxList, TmpUsage],
+ H} ->
+ try
+ Prep = prepare_qlc(H, [], GUnique, GCache,
+ TmpDir, MaxList, TmpUsage),
+ Info = le_info(Prep, {NElements,Depth}),
+ AbstractCode = abstract(Info, Flat, NElements, Depth),
+ case Format of
+ abstract_code ->
+ abstract_code(AbstractCode);
+ string ->
+ Hook = fun({special, _Line, String}, _I, _P, _F) ->
+ String
+ end,
+ lists:flatten(erl_pp:expr(AbstractCode, 0, Hook));
+ debug -> % Not documented. Intended for testing only.
+ Info
+ end
+ catch Term ->
+ case erlang:get_stacktrace() of
+ [?THROWN_ERROR | _] ->
+ Term;
+ Stacktrace ->
+ erlang:raise(throw, Term, Stacktrace)
+ end
+ end
+ end.
+
+keysort(KeyPos, QH) ->
+ keysort(KeyPos, QH, []).
+
+keysort(KeyPos, QH, Options) ->
+ case {is_keypos(KeyPos),
+ options(Options, [tmpdir, order, unique, compressed,
+ size, no_files]),
+ get_handle(QH)} of
+ {true, [TmpDir, Order, Unique,Compressed | _], H} when H =/= badarg ->
+ #qlc_handle{h = #qlc_sort{h = H, keypos = {keysort,KeyPos},
+ unique = Unique,
+ compressed = Compressed,
+ order = Order,
+ fs_opts = listify(Options),
+ tmpdir = TmpDir}};
+ _ ->
+ erlang:error(badarg, [KeyPos, QH, Options])
+ end.
+
+-define(DEFAULT_NUM_OF_ANSWERS, 10).
+
+next_answers(C) ->
+ next_answers(C, ?DEFAULT_NUM_OF_ANSWERS).
+
+next_answers(#qlc_cursor{c = {_, Owner}}=C,
+ NumOfAnswers) when Owner =/= self() ->
+ erlang:error(not_cursor_owner, [C, NumOfAnswers]);
+next_answers(#qlc_cursor{c = {Pid, _}}=C, NumOfAnswers) ->
+ N = case NumOfAnswers of
+ all_remaining -> -1;
+ _ when is_integer(NumOfAnswers), NumOfAnswers > 0 -> NumOfAnswers;
+ _ -> erlang:error(badarg, [C, NumOfAnswers])
+ end,
+ next_loop(Pid, [], N);
+next_answers(T1, T2) ->
+ erlang:error(badarg, [T1, T2]).
+
+parse_transform(Forms, Options) ->
+ qlc_pt:parse_transform(Forms, Options).
+
+%% The funcspecs qlc:q/1 and qlc:q/2 are known by erl_eval.erl and
+%% erl_lint.erl.
+q(QLC_lc) ->
+ q(QLC_lc, []).
+
+q(#qlc_lc{}=QLC_lc, Options) ->
+ case options(Options, [unique, cache, max_lookup, join, lookup]) of
+ [Unique, Cache, Max, Join, Lookup] ->
+ Opt = #qlc_opt{unique = Unique, cache = Cache,
+ max_lookup = Max, join = Join, lookup = Lookup},
+ #qlc_handle{h = QLC_lc#qlc_lc{opt = Opt}};
+ _ ->
+ erlang:error(badarg, [QLC_lc, Options])
+ end;
+q(T1, T2) ->
+ erlang:error(badarg, [T1, T2]).
+
+sort(QH) ->
+ sort(QH, []).
+
+sort(QH, Options) ->
+ case {options(Options, [tmpdir, order, unique, compressed,
+ size, no_files]), get_handle(QH)} of
+ {B1, B2} when B1 =:= badarg; B2 =:= badarg ->
+ erlang:error(badarg, [QH, Options]);
+ {[TD, Order, Unique, Compressed | _], H} ->
+ #qlc_handle{h = #qlc_sort{h = H, keypos = sort, unique = Unique,
+ compressed = Compressed, order = Order,
+ fs_opts = listify(Options),
+ tmpdir = TD}}
+ end.
+
+%% Note that the generated code is evaluated by (the slow) erl_eval.
+string_to_handle(Str) ->
+ string_to_handle(Str, []).
+
+string_to_handle(Str, Options) ->
+ string_to_handle(Str, Options, []).
+
+string_to_handle(Str, Options, Bindings) when is_list(Str),
+ is_list(Bindings) ->
+ case options(Options, [unique, cache, max_lookup, join, lookup]) of
+ badarg ->
+ erlang:error(badarg, [Str, Options, Bindings]);
+ [Unique, Cache, MaxLookup, Join, Lookup] ->
+ case erl_scan:string(Str) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_exprs(Tokens) of
+ {ok, [Expr]} ->
+ case qlc_pt:transform_expression(Expr, Bindings) of
+ {ok, {call, _, _QlcQ, Handle}} ->
+ {value, QLC_lc, _} =
+ erl_eval:exprs(Handle, Bindings),
+ O = #qlc_opt{unique = Unique,
+ cache = Cache,
+ max_lookup = MaxLookup,
+ join = Join,
+ lookup = Lookup},
+ #qlc_handle{h = QLC_lc#qlc_lc{opt = O}};
+ {not_ok, [{error, Error} | _]} ->
+ error(Error)
+ end;
+ {ok, _ExprList} ->
+ erlang:error(badarg, [Str, Options, Bindings]);
+ {error, ErrorInfo} ->
+ error(ErrorInfo)
+ end;
+ {error, ErrorInfo, _EndLine} ->
+ error(ErrorInfo)
+ end
+ end;
+string_to_handle(T1, T2, T3) ->
+ erlang:error(badarg, [T1, T2, T3]).
+
+table(TraverseFun, Options) when is_function(TraverseFun) ->
+ case {is_function(TraverseFun, 0),
+ IsFun1 = is_function(TraverseFun, 1)} of
+ {false, false} ->
+ erlang:error(badarg, [TraverseFun, Options]);
+ _ ->
+ case options(Options, [pre_fun, post_fun, info_fun, format_fun,
+ lookup_fun, parent_fun, key_equality]) of
+ [PreFun, PostFun, InfoFun, FormatFun, LookupFun, ParentFun,
+ KeyEquality] ->
+ T = #qlc_table{trav_fun = TraverseFun, pre_fun = PreFun,
+ post_fun = PostFun, info_fun = InfoFun,
+ parent_fun = ParentFun,
+ trav_MS = IsFun1,
+ format_fun = FormatFun,
+ lookup_fun = LookupFun,
+ key_equality = KeyEquality},
+ #qlc_handle{h = T};
+ badarg ->
+ erlang:error(badarg, [TraverseFun, Options])
+ end
+ end;
+table(T1, T2) ->
+ erlang:error(badarg, [T1, T2]).
+
+transform_from_evaluator(LC, Bs0) ->
+ qlc_pt:transform_from_evaluator(LC, Bs0).
+
+-define(TEMPLATE_STATE, 1).
+
+template_state() ->
+ ?TEMPLATE_STATE.
+
+aux_name(Name, N, AllNames) ->
+ {VN, _} = aux_name1(Name, N, AllNames),
+ VN.
+
+name_suffix(A, Suff) ->
+ list_to_atom(lists:concat([A, Suff])).
+
+vars(E) ->
+ var_ufold(fun({var,_L,V}) -> V end, E).
+
+var_ufold(F, E) ->
+ ordsets:from_list(var_fold(F, [], E)).
+
+all_selections([]) ->
+ [[]];
+all_selections([{I,Cs} | ICs]) ->
+ [[{I,C} | L] || C <- Cs, L <- all_selections(ICs)].
+
+%%%
+%%% Local functions
+%%%
+
+aux_name1(Name, N, AllNames) ->
+ SN = name_suffix(Name, N),
+ case sets:is_element(SN, AllNames) of
+ true -> aux_name1(Name, N + 1, AllNames);
+ false -> {SN, N}
+ end.
+
+var_fold(F, A, {var,_,V}=Var) when V =/= '_' ->
+ [F(Var) | A];
+var_fold(F, A, T) when is_tuple(T) ->
+ var_fold(F, A, tuple_to_list(T));
+var_fold(F, A, [E | Es]) ->
+ var_fold(F, var_fold(F, A, E), Es);
+var_fold(_F, A, _T) ->
+ A.
+
+options(Options, Keys) when is_list(Options) ->
+ options(Options, Keys, []);
+options(Option, Keys) ->
+ options([Option], Keys, []).
+
+options(Options0, [Key | Keys], L) when is_list(Options0) ->
+ Options = case lists:member(Key, Options0) of
+ true ->
+ [atom_option(Key) | lists:delete(Key, Options0)];
+ false ->
+ Options0
+ end,
+ V = case lists:keysearch(Key, 1, Options) of
+ {value, {format_fun, U=undefined}} ->
+ {ok, U};
+ {value, {info_fun, U=undefined}} ->
+ {ok, U};
+ {value, {lookup_fun, U=undefined}} ->
+ {ok, U};
+ {value, {parent_fun, U=undefined}} ->
+ {ok, U};
+ {value, {post_fun, U=undefined}} ->
+ {ok, U};
+ {value, {pre_fun, U=undefined}} ->
+ {ok, U};
+ {value, {info_fun, Fun}} when is_function(Fun),
+ is_function(Fun, 1) ->
+ {ok, Fun};
+ {value, {pre_fun, Fun}} when is_function(Fun),
+ is_function(Fun, 1) ->
+ {ok, Fun};
+ {value, {post_fun, Fun}} when is_function(Fun),
+ is_function(Fun, 0) ->
+ {ok, Fun};
+ {value, {lookup_fun, Fun}} when is_function(Fun),
+ is_function(Fun, 2) ->
+ {ok, Fun};
+ {value, {max_lookup, Max}} when is_integer(Max), Max >= 0 ->
+ {ok, Max};
+ {value, {max_lookup, infinity}} ->
+ {ok, -1};
+ {value, {format_fun, Fun}} when is_function(Fun),
+ is_function(Fun, 1) ->
+ {ok, Fun};
+ {value, {parent_fun, Fun}} when is_function(Fun),
+ is_function(Fun, 0) ->
+ {ok, Fun};
+ {value, {key_equality, KE='=='}}->
+ {ok, KE};
+ {value, {key_equality, KE='=:='}}->
+ {ok, KE};
+ {value, {join, J=any}} ->
+ {ok, J};
+ {value, {join, J=nested_loop}} ->
+ {ok, J};
+ {value, {join, J=merge}} ->
+ {ok, J};
+ {value, {join, J=lookup}} ->
+ {ok, J};
+ {value, {lookup, LookUp}} when LookUp;
+ not LookUp;
+ LookUp =:= any ->
+ {ok, LookUp};
+ {value, {max_list_size, Max}} when is_integer(Max), Max >= 0 ->
+ {ok, Max};
+ {value, {tmpdir_usage, TmpUsage}} when TmpUsage =:= allowed;
+ TmpUsage =:= not_allowed;
+ TmpUsage =:= info_msg;
+ TmpUsage =:= warning_msg;
+ TmpUsage =:= error_msg ->
+ {ok, TmpUsage};
+ {value, {unique, Unique}} when Unique; not Unique ->
+ {ok, Unique};
+ {value, {cache, Cache}} when Cache; not Cache; Cache =:= list ->
+ {ok, Cache};
+ {value, {cache, ets}} ->
+ {ok, true};
+ {value, {cache, no}} ->
+ {ok, false};
+ {value, {unique_all, UniqueAll}} when UniqueAll; not UniqueAll ->
+ {ok, UniqueAll};
+ {value, {cache_all, CacheAll}} when CacheAll;
+ not CacheAll;
+ CacheAll =:= list ->
+ {ok, CacheAll};
+ {value, {cache_all, ets}} ->
+ {ok, true};
+ {value, {cache_all, no}} ->
+ {ok, false};
+ {value, {spawn_options, default}} ->
+ {ok, default};
+ {value, {spawn_options, SpawnOptions}} ->
+ case is_proper_list(SpawnOptions) of
+ true ->
+ {ok, SpawnOptions};
+ false ->
+ badarg
+ end;
+ {value, {flat, Flat}} when Flat; not Flat ->
+ {ok, Flat};
+ {value, {format, Format}} when Format =:= string;
+ Format =:= abstract_code;
+ Format =:= debug ->
+ {ok, Format};
+ {value, {n_elements, NElements}} when NElements =:= infinity;
+ is_integer(NElements),
+ NElements > 0 ->
+ {ok, NElements};
+ {value, {depth, Depth}} when Depth =:= infinity;
+ is_integer(Depth), Depth >= 0 ->
+ {ok, Depth};
+ {value, {order, Order}} when is_function(Order),
+ is_function(Order, 2);
+ (Order =:= ascending);
+ (Order =:= descending) ->
+ {ok, Order};
+ {value, {compressed, Comp}} when Comp ->
+ {ok, [compressed]};
+ {value, {compressed, Comp}} when not Comp ->
+ {ok, []};
+ {value, {tmpdir, T}} ->
+ {ok, T};
+ {value, {size, Size}} when is_integer(Size), Size > 0 ->
+ {ok, Size};
+ {value, {no_files, NoFiles}} when is_integer(NoFiles),
+ NoFiles > 1 ->
+ {ok, NoFiles};
+ {value, {Key, _}} ->
+ badarg;
+ false ->
+ Default = default_option(Key),
+ {ok, Default}
+ end,
+ case V of
+ badarg ->
+ badarg;
+ {ok, Value} ->
+ NewOptions = lists:keydelete(Key, 1, Options),
+ options(NewOptions, Keys, [Value | L])
+ end;
+options([], [], L) ->
+ lists:reverse(L);
+options(_Options, _, _L) ->
+ badarg.
+
+default_option(pre_fun) -> undefined;
+default_option(post_fun) -> undefined;
+default_option(info_fun) -> undefined;
+default_option(format_fun) -> undefined;
+default_option(lookup_fun) -> undefined;
+default_option(max_lookup) -> -1;
+default_option(join) -> any;
+default_option(lookup) -> any;
+default_option(parent_fun) -> undefined;
+default_option(key_equality) -> '=:=';
+default_option(spawn_options) -> default;
+default_option(flat) -> true;
+default_option(format) -> string;
+default_option(n_elements) -> infinity;
+default_option(depth) -> infinity;
+default_option(max_list_size) -> ?MAX_LIST_SIZE;
+default_option(tmpdir_usage) -> allowed;
+default_option(cache) -> false;
+default_option(cache_all) -> false;
+default_option(unique) -> false;
+default_option(unique_all) -> false;
+default_option(order) -> ascending; % default values from file_sorter.erl
+default_option(compressed) -> [];
+default_option(tmpdir) -> "";
+default_option(size) -> 524288;
+default_option(no_files) -> 16.
+
+atom_option(cache) -> {cache, true};
+atom_option(unique) -> {unique, true};
+atom_option(cache_all) -> {cache_all, true};
+atom_option(unique_all) -> {unique_all, true};
+atom_option(lookup) -> {lookup, true};
+atom_option(flat) -> {flat, true};
+atom_option(Key) -> Key.
+
+is_proper_list([_ | L]) ->
+ is_proper_list(L);
+is_proper_list(L) ->
+ L =:= [].
+
+spawn_options(default) ->
+ [link];
+spawn_options(SpawnOptions) ->
+ lists:delete(monitor,
+ case lists:member(link, SpawnOptions) of
+ true ->
+ SpawnOptions;
+ false ->
+ [link | SpawnOptions]
+ end).
+
+is_keypos(Keypos) when is_integer(Keypos), Keypos > 0 ->
+ true;
+is_keypos([]) ->
+ false;
+is_keypos(L) ->
+ is_keyposs(L).
+
+is_keyposs([Kp | Kps]) when is_integer(Kp), Kp > 0 ->
+ is_keyposs(Kps);
+is_keyposs(Kps) ->
+ Kps =:= [].
+
+listify(L) when is_list(L) ->
+ L;
+listify(T) ->
+ [T].
+
+%% Optimizations to be carried out.
+-record(optz,
+ {unique = false, % bool()
+ cache = false, % bool() | list
+ join_option = any, % constraint set by the 'join' option
+ fast_join = no, % no | #qlc_join. 'no' means nested loop.
+ opt % #qlc_opt
+ }).
+
+%% Prepared #qlc_lc.
+-record(qlc,
+ {lcf, % fun() -> Val
+ codef,
+ qdata, % with evaluated list expressions
+ init_value,
+ optz % #optz
+ }).
+
+%% Prepared simple #qlc_lc.
+-record(simple_qlc,
+ {p, % atom(), pattern variable
+ le,
+ line,
+ init_value,
+ optz % #optz
+ }).
+
+-record(prepared,
+ {qh, % #qlc_append | #qlc_table | #qlc | #simple_qlc |
+ % #qlc_sort | list()
+ sorted = no, % yes | no | ascending | descending
+ sort_info = [], %
+ sort_info2 = [], % 'sort_info' updated with pattern info; qh is LE
+ lu_skip_quals = [], % qualifiers to skip due to lookup
+ join = {[],[]}, % {Lookup, Merge}
+ n_objs = undefined, % for join (not used yet)
+ is_unique_objects = false, % bool()
+ is_cached = false % bool() (true means 'ets' or 'list')
+ }).
+
+%%% Cursor process functions.
+
+cursor_process(H, GUnique, GCache, TmpDir, SpawnOptions, MaxList, TmpUsage) ->
+ Parent = self(),
+ Setup = #setup{parent = Parent},
+ CF = fun() ->
+ %% Unless exit/2 is trapped no cleanup can be done.
+ %% The user is assumed not to set the flag to false.
+ process_flag(trap_exit, true),
+ MonRef = erlang:monitor(process, Parent),
+ {Objs, Post, _LocalPost} =
+ try
+ Prep = prepare_qlc(H, not_a_list, GUnique, GCache,
+ TmpDir, MaxList, TmpUsage),
+ setup_qlc(Prep, Setup)
+ catch Class:Reason ->
+ Parent ! {self(), {caught, Class, Reason,
+ erlang:get_stacktrace()}},
+ exit(normal)
+ end,
+ Parent ! {self(), ok},
+ wait_for_request(Parent, MonRef, Post),
+ reply(Parent, MonRef, Post, Objs)
+ end,
+ Pid = spawn_opt(CF, SpawnOptions),
+ parent_fun(Pid, Parent).
+
+%% Expect calls from tables calling the parent_fun and finally an 'ok'.
+parent_fun(Pid, Parent) ->
+ receive
+ {Pid, ok} -> Pid;
+ {TPid, {parent_fun, Fun}} ->
+ V = try
+ {value, Fun()}
+ catch Class:Reason ->
+ {parent_fun_caught, Class, Reason, erlang:get_stacktrace()}
+ end,
+ TPid ! {Parent, V},
+ parent_fun(Pid, Parent);
+ {Pid, {caught, throw, Error, [?THROWN_ERROR | _]}} ->
+ Error;
+ {Pid, {caught, Class, Reason, Stacktrace}} ->
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+reply(Parent, MonRef, Post, []) ->
+ no_more(Parent, MonRef, Post);
+reply(Parent, MonRef, Post, [Answer | Cont]) ->
+ Parent ! {self(), {answer, Answer}},
+ wait_for_request(Parent, MonRef, Post),
+ reply(Parent, MonRef, Post, Cont);
+reply(Parent, MonRef, Post, Cont) ->
+ Reply = try
+ if
+ is_function(Cont) ->
+ Cont();
+ true ->
+ throw_error(Cont)
+ end
+ catch
+ Class:Reason ->
+ post_funs(Post),
+ Message = {caught, Class, Reason, erlang:get_stacktrace()},
+ Parent ! {self(), Message},
+ exit(normal)
+ end,
+ reply(Parent, MonRef, Post, Reply).
+
+no_more(Parent, MonRef, Post) ->
+ Parent ! {self(), no_more},
+ wait_for_request(Parent, MonRef, Post),
+ no_more(Parent, MonRef, Post).
+
+wait_for_request(Parent, MonRef, Post) ->
+ receive
+ {Parent, stop} ->
+ post_funs(Post),
+ exit(normal);
+ {Parent, more} ->
+ ok;
+ {'EXIT', Parent, _Reason} ->
+ post_funs(Post),
+ exit(normal);
+ {'DOWN', MonRef, process, Parent, _Info} ->
+ post_funs(Post),
+ exit(normal);
+ {'EXIT', Pid, _Reason} when Pid =:= self() ->
+ %% Trapped signal. The cursor ignores it...
+ wait_for_request(Parent, MonRef, Post);
+ Other ->
+ error_logger:error_msg(
+ "The qlc cursor ~w received an unexpected message:\n~p\n",
+ [self(), Other]),
+ wait_for_request(Parent, MonRef, Post)
+ end.
+
+%%% End of cursor process functions.
+
+abstract_code({special, Line, String}) ->
+ {string, Line, String};
+abstract_code(Tuple) when is_tuple(Tuple) ->
+ list_to_tuple(abstract_code(tuple_to_list(Tuple)));
+abstract_code([H | T]) ->
+ [abstract_code(H) | abstract_code(T)];
+abstract_code(Term) ->
+ Term.
+
+%% Also in qlc_pt.erl.
+-define(Q, q).
+-define(QLC_Q(L1, L2, L3, L4, LC, Os),
+ {call,L1,{remote,L2,{atom,L3,?MODULE},{atom,L4,?Q}},[LC | Os]}).
+
+abstract(Info, false=_Flat, NElements, Depth) ->
+ abstract(Info, NElements, Depth);
+abstract(Info, true=_Flat, NElements, Depth) ->
+ Abstract = abstract(Info, NElements, Depth),
+ Vars = abstract_vars(Abstract),
+ {_, Body0, Expr} = flatten_abstr(Abstract, 1, Vars, []),
+ case Body0 of
+ [] ->
+ Expr;
+ [{match,_,Expr,Q}] ->
+ Q;
+ [{match,_,Expr,Q} | Body] ->
+ {block, 0, lists:reverse(Body, [Q])};
+ _ ->
+ {block, 0, lists:reverse(Body0, [Expr])}
+ end.
+
+abstract({qlc, E0, Qs0, Opt}, NElements, Depth) ->
+ Qs = lists:map(fun({generate, P, LE}) ->
+ {generate, 1, binary_to_term(P),
+ abstract(LE, NElements, Depth)};
+ (F) ->
+ binary_to_term(F)
+ end, Qs0),
+ E = binary_to_term(E0),
+ Os = case Opt of
+ [] -> [];
+ _ -> [abstract_term(Opt, 1)]
+ end,
+ ?QLC_Q(1, 1, 1, 1, {lc,1,E,Qs}, Os);
+abstract({table, {M, F, As0}}, _NElements, _Depth)
+ when is_atom(M), is_atom(F), is_list(As0) ->
+ As = [abstract_term(A, 1) || A <- As0],
+ {call, 1, {remote, 1, {atom, 1, M}, {atom, 1, F}}, As};
+abstract({table, TableDesc}, _NElements, _Depth) ->
+ case io_lib:deep_char_list(TableDesc) of
+ true ->
+ {ok, Tokens, _} = erl_scan:string(lists:flatten(TableDesc++".")),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ Expr;
+ false -> % abstract expression
+ TableDesc
+ end;
+abstract({append, Infos}, NElements, Depth) ->
+ As = lists:foldr(fun(Info, As0) ->
+ {cons,1,abstract(Info, NElements, Depth),As0}
+ end, {nil, 1}, Infos),
+ {call, 1, {remote, 1, {atom, 1, ?MODULE}, {atom, 1, append}}, [As]};
+abstract({sort, Info, SortOptions}, NElements, Depth) ->
+ {call, 1, {remote, 1, {atom, 1, ?MODULE}, {atom, 1, sort}},
+ [abstract(Info, NElements, Depth), abstract_term(SortOptions, 1)]};
+abstract({keysort, Info, Kp, SortOptions}, NElements, Depth) ->
+ {call, 1, {remote, 1, {atom, 1, ?MODULE}, {atom, 1, keysort}},
+ [abstract_term(Kp, 1), abstract(Info, NElements, Depth),
+ abstract_term(SortOptions, 1)]};
+abstract({list,L,MS}, NElements, Depth) ->
+ {call, 1, {remote, 1, {atom, 1, ets}, {atom, 1, match_spec_run}},
+ [abstract(L, NElements, Depth),
+ {call, 1, {remote, 1, {atom, 1, ets}, {atom, 1, match_spec_compile}},
+ [abstract_term(depth(MS, Depth), 1)]}]};
+abstract({list, L}, NElements, Depth) when NElements =:= infinity;
+ NElements >= length(L) ->
+ abstract_term(depth(L, Depth), 1);
+abstract({list, L}, NElements, Depth) ->
+ abstract_term(depth(lists:sublist(L, NElements), Depth) ++ '...', 1).
+
+depth(List, infinity) ->
+ List;
+depth(List, Depth) ->
+ [depth1(E, Depth) || E <- List].
+
+depth_fun(infinity = _Depth) ->
+ fun(E) -> E end;
+depth_fun(Depth) ->
+ fun(E) -> depth1(E, Depth) end.
+
+depth1([]=L, _D) ->
+ L;
+depth1(_Term, 0) ->
+ '...';
+depth1(Tuple, D) when is_tuple(Tuple) ->
+ depth_tuple(Tuple, tuple_size(Tuple), 1, D - 1, []);
+depth1(List, D) when is_list(List) ->
+ if
+ D =:= 1 ->
+ ['...'];
+ true ->
+ depth_list(List, D - 1)
+ end;
+depth1(Binary, D) when byte_size(Binary) > D - 1 ->
+ D1 = D - 1,
+ <<Bin:D1/bytes,_/bytes>> = Binary,
+ <<Bin/bytes,"...">>;
+depth1(T, _Depth) ->
+ T.
+
+depth_list([]=L, _D) ->
+ L;
+depth_list(_L, 0) ->
+ '...';
+depth_list([E | Es], D) ->
+ [depth1(E, D) | depth_list(Es, D - 1)].
+
+depth_tuple(_Tuple, Sz, I, _D, L) when I > Sz ->
+ list_to_tuple(lists:reverse(L));
+depth_tuple(_L, _Sz, _I, 0, L) ->
+ list_to_tuple(lists:reverse(L, ['...']));
+depth_tuple(Tuple, Sz, I, D, L) ->
+ E = depth1(element(I, Tuple), D),
+ depth_tuple(Tuple, Sz, I + 1, D - 1, [E | L]).
+
+abstract_term(Term) ->
+ abstract_term(Term, 0).
+
+abstract_term(Term, Line) ->
+ abstr_term(Term, Line).
+
+abstr_term(Tuple, Line) when is_tuple(Tuple) ->
+ {tuple,Line,[abstr_term(E, Line) || E <- tuple_to_list(Tuple)]};
+abstr_term([_ | _]=L, Line) ->
+ case io_lib:char_list(L) of
+ true ->
+ erl_parse:abstract(L, Line);
+ false ->
+ abstr_list(L, Line)
+ end;
+abstr_term(Fun, Line) when is_function(Fun) ->
+ case erl_eval:fun_data(Fun) of
+ {fun_data, _Bs, Cs} ->
+ {'fun', Line, {clauses, Cs}};
+ false ->
+ {name, Name} = erlang:fun_info(Fun, name),
+ {arity, Arity} = erlang:fun_info(Fun, arity),
+ case erlang:fun_info(Fun, type) of
+ {type, external} ->
+ {module, Module} = erlang:fun_info(Fun, module),
+ {'fun', Line, {function,Module,Name,Arity}};
+ {type, local} ->
+ {'fun', Line, {function,Name,Arity}}
+ end
+ end;
+abstr_term(PPR, Line) when is_pid(PPR); is_port(PPR); is_reference(PPR) ->
+ {special, Line, lists:flatten(io_lib:write(PPR))};
+abstr_term(Simple, Line) ->
+ erl_parse:abstract(Simple, Line).
+
+abstr_list([H | T], Line) ->
+ {cons, Line, abstr_term(H, Line), abstr_list(T, Line)};
+abstr_list(T, Line) ->
+ abstr_term(T, Line).
+
+%% Since generator pattern variables cannot be used in list
+%% expressions, it is OK to flatten out QLCs using temporary
+%% variables.
+flatten_abstr(?QLC_Q(L1, L2, L3, L4, LC0, Os), VN0, Vars, Body0) ->
+ {lc,L,E,Qs0} = LC0,
+ F = fun({generate,Ln,P,LE0}, {VN1,Body1}) ->
+ {VN2,Body2,LE} = flatten_abstr(LE0, VN1, Vars, Body1),
+ {{generate,Ln,P,LE}, {VN2,Body2}};
+ (Fil, VN_Body) ->
+ {Fil, VN_Body}
+ end,
+ {Qs, {VN3,Body}} = lists:mapfoldl(F, {VN0,Body0}, Qs0),
+ LC = {lc,L,E,Qs},
+ {V, VN} = aux_name1('V', VN3, Vars),
+ Var = {var, L1, V},
+ QLC = ?QLC_Q(L1, L2, L3, L4, LC, Os),
+ {VN + 1, [{match, L1, Var, QLC} | Body], Var};
+flatten_abstr(T0, VN0, Vars, Body0) when is_tuple(T0) ->
+ {VN, Body, L} = flatten_abstr(tuple_to_list(T0), VN0, Vars, Body0),
+ {VN, Body, list_to_tuple(L)};
+flatten_abstr([E0 | Es0], VN0, Vars, Body0) ->
+ {VN1, Body1, E} = flatten_abstr(E0, VN0, Vars, Body0),
+ {VN, Body, Es} = flatten_abstr(Es0, VN1, Vars, Body1),
+ {VN, Body, [E | Es]};
+flatten_abstr(E, VN, _Vars, Body) ->
+ {VN, Body, E}.
+
+abstract_vars(Abstract) ->
+ sets:from_list(ordsets:to_list(vars(Abstract))).
+
+collect([]=L) ->
+ L;
+collect([Answer | Cont]) ->
+ [Answer | collect(Cont)];
+collect(Cont) ->
+ case Cont() of
+ Answers when is_list(Answers) ->
+ collect(Answers);
+ Term ->
+ throw_error(Term)
+ end.
+
+fold_loop(Fun, [Obj | Cont], Acc) ->
+ fold_loop(Fun, Cont, Fun(Obj, Acc));
+fold_loop(_Fun, [], Acc) ->
+ Acc;
+fold_loop(Fun, Cont, Acc) ->
+ case Cont() of
+ Objects when is_list(Objects) ->
+ fold_loop(Fun, Objects, Acc);
+ Term ->
+ Term
+ end.
+
+next_loop(Pid, L, N) when N =/= 0 ->
+ case monitor_request(Pid, more) of
+ no_more ->
+ lists:reverse(L);
+ {answer, Answer} ->
+ next_loop(Pid, [Answer | L], N - 1);
+ {caught, throw, Error, [?THROWN_ERROR | _]} ->
+ Error;
+ {caught, Class, Reason, Stacktrace} ->
+ _ = (catch erlang:error(foo)),
+ erlang:raise(Class, Reason, Stacktrace ++ erlang:get_stacktrace());
+ error ->
+ erlang:error({qlc_cursor_pid_no_longer_exists, Pid})
+ end;
+next_loop(_Pid, L, _N) ->
+ lists:reverse(L).
+
+stop_cursor(Pid) ->
+ erlang:monitor(process, Pid),
+ unlink(Pid),
+ receive
+ {'EXIT',Pid,_Reason} -> % Simply ignore the error.
+ receive
+ {'DOWN',_,process,Pid,_} -> ok
+ end
+ after 0 ->
+ Pid ! {self(),stop},
+ receive
+ {'DOWN',_,process,Pid,_} -> ok
+ end
+ end.
+
+monitor_request(Pid, Req) ->
+ Ref = erlang:monitor(process, Pid),
+ Pid ! {self(), Req},
+ receive
+ {'DOWN', Ref, process, Pid, _Info} ->
+ receive
+ {'EXIT', Pid, _Reason} -> ok
+ after 1 -> ok end,
+ error;
+ {'EXIT', Pid, _Reason} ->
+ receive
+ {'DOWN', _, process, Pid, _} -> error
+ end;
+ {Pid, Reply} ->
+ erlang:demonitor(Ref, [flush]),
+ Reply
+ end.
+
+%% Marker for skipped filter or unused generator.
+-define(SKIP, (-1)).
+
+%% Qual = {gen, LE} | fil
+-define(qual_data(QNum, GoToIndex, State, Qual),
+ {QNum, GoToIndex, State, Qual}).
+
+-record(join, % generated by qlc_pt
+ {op, q1, q2, wh1, wh2, cs_fun}). % op is unused
+
+%% le_info/1 returns an intermediate information format only used for
+%% testing purposes. Changes will happen without notice.
+%%
+%% QueryDesc = {qlc, TemplateDesc, [QualDesc], [QueryOpt]}
+%% | {table, TableDesc}
+%% | {append, [QueryDesc]}
+%% | {sort, QueryDesc, [SortOption]}
+%% | {keysort, KeyPos, QueryDesc, [SortOption]}
+%% | {list, list()}
+%% | {list, QueryDesc, MatchExpression}
+%% TableDesc = {Mod, Fun, Args}
+%% | AbstractExpression
+%% | character_list()
+%% Mod = module()
+%% Fun = atom()
+%% Args = [term()]
+%% QualDesc = FilterDesc
+%% | {generate, PatternDesc, QueryDesc}
+%% QueryOpt = {cache, bool()} | cache
+%% | {unique, bool()} | unique
+%% FilterDesc = PatternDesc = TemplateDesc = binary()
+
+le_info(#prepared{qh = #simple_qlc{le = LE, p = P, line = L, optz = Optz}},
+ InfOpt) ->
+ QVar = term_to_binary({var, L, P}),
+ {qlc, QVar, [{generate, QVar, le_info(LE, InfOpt)}], opt_info(Optz)};
+le_info(#prepared{qh = #qlc{codef = CodeF, qdata = Qdata, optz = Optz}},
+ InfOpt) ->
+ Code = CodeF(),
+ TemplateState = template_state(),
+ E = element(TemplateState, Code),
+ QualInfo0 = qual_info(Qdata, Code, InfOpt),
+ QualInfo1 = case Optz#optz.fast_join of
+ #qlc_join{} = Join ->
+ join_info(Join, QualInfo0, Qdata, Code);
+ no ->
+ QualInfo0
+ end,
+ QualInfo = [I || I <- QualInfo1, I =/= skip],
+ {qlc, E, QualInfo, opt_info(Optz)};
+le_info(#prepared{qh = #qlc_table{format_fun = FormatFun, trav_MS = TravMS,
+ ms = MS, lu_vals = LuVals}}, InfOpt) ->
+ {NElements, Depth} = InfOpt,
+ %% The 'depth' option applies to match specifications as well.
+ %% This is for limiting imported variables (parameters).
+ DepthFun = depth_fun(Depth),
+ case LuVals of
+ _ when FormatFun =:= undefined ->
+ {table, {'$MOD', '$FUN', []}};
+ {Pos, Vals} ->
+ Formated = try FormatFun({lookup, Pos, Vals, NElements, DepthFun})
+ catch _:_ -> FormatFun({lookup, Pos, Vals})
+ end,
+ if
+ MS =:= no_match_spec ->
+ {table, Formated};
+ true ->
+ {list, {table, Formated}, depth(MS, Depth)}
+ end;
+ _ when TravMS, is_list(MS) ->
+ {table, FormatFun({match_spec, depth(MS, Depth)})};
+ _ when MS =:= no_match_spec ->
+ try {table, FormatFun({all, NElements, DepthFun})}
+ catch _:_ -> {table, FormatFun(all)}
+ end
+ end;
+le_info(#prepared{qh = #qlc_append{hl = HL}}, InfOpt) ->
+ {append, [le_info(H, InfOpt) || H <- HL]};
+le_info(#prepared{qh = #qlc_sort{h = H, keypos = sort,
+ fs_opts = SortOptions0, tmpdir = TmpDir}},
+ InfOpt) ->
+ SortOptions = sort_options_global_tmp(SortOptions0, TmpDir),
+ {sort, le_info(H, InfOpt), SortOptions};
+le_info(#prepared{qh = #qlc_sort{h = H, keypos = {keysort, Kp},
+ fs_opts = SortOptions0, tmpdir = TmpDir}},
+ InfOpt) ->
+ SortOptions = sort_options_global_tmp(SortOptions0, TmpDir),
+ {keysort, le_info(H, InfOpt), Kp, SortOptions};
+le_info(#prepared{qh = #qlc_list{l = L, ms = no_match_spec}}, _InfOpt) ->
+ {list, L};
+le_info(#prepared{qh = #qlc_list{l = L, ms = MS}},_InfOpt) when is_list(L) ->
+ {list, {list, L}, MS};
+le_info(#prepared{qh = #qlc_list{l = L, ms = MS}}, InfOpt) ->
+ {list, le_info(L, InfOpt), MS}.
+
+qual_info([?qual_data(_QNum, _GoI, ?SKIP, fil) | Qdata], Code, InfOpt) ->
+ %% see skip_lookup_filters()
+ [skip | qual_info(Qdata, Code, InfOpt)];
+qual_info([?qual_data(QNum, _GoI, _SI, fil) | Qdata], Code, InfOpt) ->
+ [element(QNum + 1, Code) | qual_info(Qdata, Code, InfOpt)];
+qual_info([?qual_data(_QNum, _GoI, _SI, {gen,#join{}}) | Qdata],
+ Code, InfOpt) ->
+ [skip | qual_info(Qdata, Code, InfOpt)];
+qual_info([?qual_data(QNum, _GoI, _SI, {gen,LE}) | Qdata], Code, InfOpt) ->
+ [{generate,element(QNum + 1, Code),le_info(LE, InfOpt)} |
+ qual_info(Qdata, Code, InfOpt)];
+qual_info([], _Code, _InfOpt) ->
+ [].
+
+join_info(Join, QInfo, Qdata, Code) ->
+ #qlc_join{kind = Kind, q1 = QNum1a, c1 = C1, q2 = QNum2a, c2 = C2,
+ opt = Opt} = Join,
+ {?qual_data(JQNum,_,_,_), Rev, QNum1, QNum2, _WH1, _WH2, CsFun} =
+ find_join_data(Qdata, QNum1a, QNum2a),
+ {Cs1_0, Cs2_0, Compat} = CsFun(),
+ [Cs1, Cs2] = case Compat of
+ [] -> % --R12B-5
+ [[{C,[{V,'=:='} || V <- Vs]} || {C,Vs} <- CVs] ||
+ CVs <- [Cs1_0, Cs2_0]];
+ _ -> % 'v1', R13A --
+ %% Only compared constants (==).
+ [Cs1_0, Cs2_0]
+ end,
+ L = 0,
+ G1_0 = {var,L,'G1'}, G2_0 = {var,L,'G2'},
+ JP = element(JQNum + 1, Code),
+ %% Create code for wh1 and wh2 in #join{}:
+ {{I1,G1}, {I2,G2}, QInfoL} =
+ case Kind of
+ {merge, _} ->
+ {JG1,QInfo1} = join_merge_info(QNum1, QInfo, Code, G1_0, Cs1),
+ {JG2,QInfo2} = join_merge_info(QNum2, QInfo, Code, G2_0, Cs2),
+ {JG1, JG2, QInfo1 ++ QInfo2};
+ _ when Rev ->
+ {JG2,QInfo2} = join_merge_info(QNum2, QInfo, Code, G2_0, Cs2),
+ {J1, QInfo1} = join_lookup_info(QNum1, QInfo, G1_0),
+ {{J1,G1_0}, JG2, QInfo2 ++ [QInfo1]};
+ _ ->
+ {JG1,QInfo1} = join_merge_info(QNum1, QInfo, Code, G1_0, Cs1),
+ {J2, QInfo2} = join_lookup_info(QNum2, QInfo, G2_0),
+ {JG1, {J2,G2_0}, QInfo1 ++ [QInfo2]}
+ end,
+ {JOptVal, JOp} = kind2op(Kind),
+ JOpt = [{join, JOptVal}] ++ opt_info(join_unique_cache(Opt)),
+ JFil = term_to_binary({op,L,JOp,
+ {call,L,{atom,L,element},[{integer,L,C1},G1]},
+ {call,L,{atom,L,element},[{integer,L,C2},G2]}}),
+ P = term_to_binary({cons, L, G1, G2}),
+ JInfo = {generate, JP, {qlc, P, QInfoL ++ [JFil], JOpt}},
+ {Before, [I1 | After]} = lists:split(QNum1 - 1, QInfo),
+ Before ++ [JInfo] ++ lists:delete(I2, After).
+
+kind2op({merge, _KE}) -> {merge, '=='};
+kind2op({lookup, KE, _LU_fun}) -> {lookup, KE}.
+
+%% qlc:q(P0 || P0 = Pattern <- H1, ConstFilters),
+%% where "P0" is a fresh variable and ConstFilters are filters that
+%% test constant values of pattern columns.
+join_merge_info(QNum, QInfo, Code, G, ExtraConstants) ->
+ {generate, _, LEInfo}=I = lists:nth(QNum, QInfo),
+ P = binary_to_term(element(QNum + 1, Code)),
+ case {P, ExtraConstants} of
+ {{var, _, _}, []} ->
+ %% No need to introduce a QLC.
+ TP = term_to_binary(G),
+ I2 = {generate, TP, LEInfo},
+ {{I,G}, [I2]};
+ _ ->
+ {EPV, M} =
+ case P of
+ {var, _, _} ->
+ %% No need to introduce a pattern variable.
+ {P, P};
+ _ ->
+ {PV, _} = aux_name1('P', 0, abstract_vars(P)),
+ L = 0,
+ V = {var, L, PV},
+ {V, {match, L, V, P}}
+ end,
+ DQP = term_to_binary(EPV),
+ LEI = {generate, term_to_binary(M), LEInfo},
+ TP = term_to_binary(G),
+ CFs = [begin
+ Call = {call,0,{atom,0,element},[{integer,0,Col},EPV]},
+ F = list2op([{op,0,Op,abstract_term(Con),Call}
+ || {Con,Op} <- ConstOps], 'or'),
+ term_to_binary(F)
+ end ||
+ {Col,ConstOps} <- ExtraConstants],
+ {{I,G}, [{generate, TP, {qlc, DQP, [LEI | CFs], []}}]}
+ end.
+
+list2op([E], _Op) ->
+ E;
+list2op([E | Es], Op) ->
+ {op,0,Op,E,list2op(Es, Op)}.
+
+join_lookup_info(QNum, QInfo, G) ->
+ {generate, _, LEInfo}=I = lists:nth(QNum, QInfo),
+ TP = term_to_binary(G),
+ {I, {generate, TP, LEInfo}}.
+
+opt_info(#optz{unique = Unique, cache = Cache0, join_option = JoinOption}) ->
+ %% No 'nested_loop' options are added here, even if there are
+ %% nested loops to carry out, unless a 'nested_loop' was given as
+ %% option. The reason is that the qlc module does not know about
+ %% all instances of nested loops.
+ Cache = if
+ Cache0 -> ets;
+ true -> Cache0
+ end,
+ [{T,V} || {T,V} <- [{cache,Cache},{unique,Unique}],
+ V =/= default_option(T)] ++
+ [{T,V} || {T,V} <- [{join,JoinOption}], V =:= nested_loop].
+
+prepare_qlc(H, InitialValue, GUnique, GCache, TmpDir, MaxList, TmpUsage) ->
+ GOpt = #qlc_opt{unique = GUnique, cache = GCache,
+ tmpdir = TmpDir, max_list = MaxList,
+ tmpdir_usage = TmpUsage},
+ case opt_le(prep_le(H, GOpt), 1) of
+ #prepared{qh = #qlc{} = QLC}=Prep ->
+ Prep#prepared{qh = QLC#qlc{init_value = InitialValue}};
+ #prepared{qh = #simple_qlc{}=SimpleQLC}=Prep ->
+ Prep#prepared{qh = SimpleQLC#simple_qlc{init_value = InitialValue}};
+ Prep ->
+ Prep
+ end.
+
+%%% The options given to append, q and table (unique and cache) as well
+%%% as the type of expression (list, table, append, qlc...) are
+%%% analyzed by prep_le. The results are is_unique_objects and
+%%% is_cached. It is checked that the evaluation (in the Erlang sense)
+%%% of list expressions yields qlc handles.
+
+prep_le(#qlc_lc{lc = LC_fun, opt = #qlc_opt{} = Opt0}=H, GOpt) ->
+ #qlc_opt{unique = GUnique, cache = GCache,
+ tmpdir = TmpDir, max_list = MaxList,
+ tmpdir_usage = TmpUsage} = GOpt,
+ Unique = Opt0#qlc_opt.unique or GUnique,
+ Cache = if
+ not GCache -> Opt0#qlc_opt.cache;
+ true -> GCache
+ end,
+ Opt = Opt0#qlc_opt{unique = Unique, cache = Cache,
+ tmpdir = TmpDir, max_list = MaxList,
+ tmpdir_usage = TmpUsage},
+ prep_qlc_lc(LC_fun(), Opt, GOpt, H);
+prep_le(#qlc_table{info_fun = IF}=T, GOpt) ->
+ {SortInfo, Sorted} = table_sort_info(T),
+ IsUnique = grd(IF, is_unique_objects),
+ Prep = #prepared{qh = T, sort_info = SortInfo, sorted = Sorted,
+ is_unique_objects = IsUnique},
+ Opt = if
+ IsUnique or not GOpt#qlc_opt.unique,
+ T#qlc_table.ms =:= no_match_spec ->
+ GOpt#qlc_opt{cache = false};
+ true ->
+ GOpt
+ end,
+ may_create_simple(Opt, Prep);
+prep_le(#qlc_append{hl = HL}, GOpt) ->
+ case lists:flatmap(fun(#prepared{qh = #qlc_list{l = []}}) -> [];
+ (#prepared{qh = #qlc_append{hl = HL1}}) -> HL1;
+ (H) -> [H] end,
+ [prep_le(H, GOpt) || H <- HL]) of
+ []=Nil ->
+ short_list(Nil);
+ [Prep] ->
+ Prep;
+ PrepL ->
+ Cache = lists:all(fun(#prepared{is_cached = IsC}) -> IsC =/= false
+ end, PrepL),
+ %% The handles in hl are replaced by prepared handles:
+ Prep = #prepared{qh = #qlc_append{hl = PrepL}, is_cached = Cache},
+ may_create_simple(GOpt, Prep)
+ end;
+prep_le(#qlc_sort{h = H0}=Q0, GOpt) ->
+ %% The handle h is replaced by a prepared handle:
+ Q = Q0#qlc_sort{h = prep_le(H0, GOpt)},
+ prep_sort(Q, GOpt);
+prep_le([_, _ | _]=L, GOpt) ->
+ Prep = #prepared{qh = #qlc_list{l = L}, is_cached = true},
+ Opt = if
+ not GOpt#qlc_opt.unique ->
+ GOpt#qlc_opt{cache = false};
+ true -> GOpt
+ end,
+ may_create_simple(Opt, Prep);
+prep_le(L, _GOpt) when is_list(L) ->
+ short_list(L);
+prep_le(T, _GOpt) ->
+ erlang:error({unsupported_qlc_handle, #qlc_handle{h = T}}).
+
+eval_le(LE_fun, GOpt) ->
+ case LE_fun() of
+ {error, ?MODULE, _} = Error ->
+ throw_error(Error);
+ R ->
+ case get_handle(R) of
+ badarg ->
+ erlang:error(badarg, [R]);
+ H ->
+ prep_le(H, GOpt)
+ end
+ end.
+
+prep_qlc_lc({simple_v1, PVar, LE_fun, L}, Opt, GOpt, _H) ->
+ check_lookup_option(Opt, false),
+ prep_simple_qlc(PVar, L, eval_le(LE_fun, GOpt), Opt);
+prep_qlc_lc({qlc_v1, QFun, CodeF, Qdata0, QOpt}, Opt, GOpt, _H) ->
+ F = fun(?qual_data(_QNum, _GoI, _SI, fil)=QualData, ModGens) ->
+ {QualData, ModGens};
+ (?qual_data(_QNum, _GoI, _SI, {gen, #join{}})=QualData, ModGens) ->
+ {QualData, ModGens};
+ (?qual_data(QNum, GoI, SI, {gen, LE_fun}), ModGens0) ->
+ Prep1 = eval_le(LE_fun, GOpt),
+ {Prep, ModGens} =
+ prep_generator(QNum, Prep1, QOpt, Opt, ModGens0),
+ {?qual_data(QNum, GoI, SI, {gen, Prep}), ModGens}
+ end,
+ {Qdata, ModGens} = lists:mapfoldl(F, [], Qdata0),
+ SomeLookUp = lists:keymember(true, 2, ModGens) =/= false,
+ check_lookup_option(Opt, SomeLookUp),
+ case ModGens of
+ [{_QNum, _LookUp, all, OnePrep}] ->
+ check_join_option(Opt),
+ OnePrep;
+ _ ->
+ Prep0 = prep_qlc(QFun, CodeF, Qdata, QOpt, Opt),
+ LU_SkipQuals =
+ lists:flatmap(fun({QNum,_LookUp,Fs,_Prep}) -> [{QNum,Fs}]
+ end, ModGens),
+ Prep1 = Prep0#prepared{lu_skip_quals = LU_SkipQuals},
+ prep_join(Prep1, QOpt, Opt)
+ end;
+prep_qlc_lc(_, _Opt, _GOpt, H) ->
+ erlang:error({unsupported_qlc_handle, #qlc_handle{h = H}}).
+
+prep_generator(QNum, Prep0, QOpt, Opt, ModGens) ->
+ PosFun = fun(KeyEquality) -> pos_fun(KeyEquality, QOpt, QNum) end,
+ MSFs = case match_specs(QOpt, QNum) of
+ undefined ->
+ {no_match_spec, []};
+ {_, _}=MSFs0 ->
+ MSFs0
+ end,
+ #prepared{qh = LE} = Prep0,
+ case prep_gen(LE, Prep0, PosFun, MSFs, Opt) of
+ {replace, Fs, LookUp, Prep} ->
+ {Prep, [{QNum,LookUp,Fs,Prep} | ModGens]};
+ {skip, SkipFils, LookUp, Prep} ->
+ {Prep, [{QNum,LookUp,SkipFils,Prep} | ModGens]};
+ {no, _Fs, _LookUp, Prep} ->
+ {Prep, ModGens}
+ end.
+
+pos_fun(undefined, QOpt, QNum) ->
+ {'=:=', constants(QOpt, QNum)}; %% --R12B-5
+pos_fun('=:=', QOpt, QNum) ->
+ {'=:=', constants(QOpt, QNum)};
+pos_fun('==', QOpt, QNum) ->
+ try {'==', equal_constants(QOpt, QNum)} % R13A--
+ catch _:_ -> {'=:=', constants(QOpt, QNum)}
+ end.
+
+prep_gen(#qlc_table{lu_vals = LuV0, ms = MS0, trav_MS = TravMS,
+ info_fun = IF, lookup_fun = LU_fun,
+ key_equality = KeyEquality}=LE0,
+ Prep0, PosFun0, {MS, Fs}, Opt) ->
+ PosFun = PosFun0(KeyEquality),
+ {LuV, {STag,SkipFils}} = find_const_positions(IF, LU_fun, PosFun, Opt),
+ LU = LuV =/= false,
+ if
+ LuV0 =/= undefined; MS0 =/= no_match_spec ->
+ {no, [], false, Prep0};
+ MS =/= no_match_spec, LU ->
+ MS1 = if
+ Fs =:= SkipFils; STag =:= Fs ->
+ %% The guard of the match specification
+ %% is covered by the lookup.
+ case MS of
+ [{'$1',_Guard,['$1']}] -> % no transformation
+ no_match_spec;
+ [{Head,_Guard,Body}] ->
+ [{Head,[],Body}] % true guard
+ end;
+ true ->
+ MS
+ end,
+ Prep = Prep0#prepared{qh = LE0#qlc_table{lu_vals = LuV,ms = MS1}},
+ {replace, Fs, LU, Prep};
+ LU ->
+ Prep = Prep0#prepared{qh = LE0#qlc_table{lu_vals = LuV}},
+ {skip, SkipFils, LU, Prep};
+ TravMS, MS =/= no_match_spec ->
+ Prep = Prep0#prepared{qh = LE0#qlc_table{ms = MS},
+ is_unique_objects = false},
+ {replace, Fs, false, may_create_simple(Opt, Prep)};
+ true ->
+ {no, [], false, Prep0}
+ end;
+prep_gen(#qlc_list{l = []}, Prep0, _PosFun, {_MS, Fs}, _Opt) ->
+ %% unique and cached
+ {replace, Fs, false, Prep0};
+prep_gen(#qlc_list{ms = no_match_spec}=LE0, Prep0, _PosFun, {MS, Fs}, Opt)
+ when MS =/= no_match_spec ->
+ Prep = Prep0#prepared{qh = LE0#qlc_list{ms = MS},
+ is_cached = false},
+ {replace, Fs, false, may_create_simple(Opt, Prep)};
+prep_gen(#qlc_list{}, Prep0, _PosFun, {MS, Fs}, Opt)
+ when MS =/= no_match_spec ->
+ ListMS = #qlc_list{l = Prep0, ms = MS},
+ LE = #prepared{qh = ListMS, is_cached = false},
+ {replace, Fs, false, may_create_simple(Opt, LE)};
+prep_gen(_LE0, Prep0, _PosFun, _MSFs, _Opt) ->
+ {no, [], false, Prep0}.
+
+-define(SIMPLE_QVAR, 'SQV').
+
+may_create_simple(#qlc_opt{unique = Unique, cache = Cache} = Opt,
+ #prepared{is_cached = IsCached,
+ is_unique_objects = IsUnique} = Prep) ->
+ if
+ Unique and not IsUnique;
+ (Cache =/= false) and not IsCached ->
+ prep_simple_qlc(?SIMPLE_QVAR, 1, Prep, Opt);
+ true ->
+ Prep
+ end.
+
+prep_simple_qlc(PVar, Line, LE, Opt) ->
+ check_join_option(Opt),
+ #prepared{is_cached = IsCached,
+ sort_info = SortInfo, sorted = Sorted,
+ is_unique_objects = IsUnique} = LE,
+ #qlc_opt{unique = Unique, cache = Cache} = Opt,
+ Cachez = if
+ Unique -> Cache;
+ not IsCached -> Cache;
+ true -> false
+ end,
+ Optz = #optz{unique = Unique and not IsUnique,
+ cache = Cachez, opt = Opt},
+ QLC = #simple_qlc{p = PVar, le = LE, line = Line,
+ init_value = not_a_list, optz = Optz},
+ %% LE#prepared.join is not copied
+ #prepared{qh = QLC, is_unique_objects = IsUnique or Unique,
+ sort_info = SortInfo, sorted = Sorted,
+ is_cached = IsCached or (Cachez =/= false)}.
+
+prep_sort(#qlc_sort{h = #prepared{sorted = yes}=Prep}, _GOpt) ->
+ Prep;
+prep_sort(#qlc_sort{h = #prepared{is_unique_objects = IsUniqueObjs}}=Q,
+ GOpt) ->
+ S1 = sort_unique(IsUniqueObjs, Q),
+ S2 = sort_tmpdir(S1, GOpt),
+ S = S2#qlc_sort{tmpdir_usage = GOpt#qlc_opt.tmpdir_usage},
+ {SortInfo, Sorted} = sort_sort_info(S),
+ #prepared{qh = S, is_cached = true, sort_info = SortInfo,
+ sorted = Sorted,
+ is_unique_objects = S#qlc_sort.unique or IsUniqueObjs}.
+
+prep_qlc(QFun, CodeF, Qdata0, QOpt, Opt) ->
+ #qlc_opt{unique = Unique, cache = Cache, join = Join} = Opt,
+ Optz = #optz{unique = Unique, cache = Cache,
+ join_option = Join, opt = Opt},
+ {Qdata, SortInfo} = qlc_sort_info(Qdata0, QOpt),
+ QLC = #qlc{lcf = QFun, codef = CodeF, qdata = Qdata,
+ init_value = not_a_list, optz = Optz},
+ #prepared{qh = QLC, sort_info = SortInfo,
+ is_unique_objects = Unique,
+ is_cached = Cache =/= false}.
+
+%% 'sorted', 'sorted_info', and 'sorted_info2' are used to avoid
+%% sorting on a key when there is no need to sort on the key. 'sorted'
+%% is set by qlc:sort() only; its purpose is to assure that if columns
+%% 1 to i are constant, then column i+1 is key-sorted (always true if
+%% the tuples are sorted). Note: the implementation is (too?) simple.
+%% For instance, each column is annotated with 'ascending' or
+%% 'descending' (not yet). More exact would be, as examples, 'always
+%% ascending' and 'ascending if all preceding columns are constant'.
+%%
+%% The 'size' of the template is not used (size_of_qualifier(QOpt, 0)).
+
+qlc_sort_info(Qdata0, QOpt) ->
+ F = fun(?qual_data(_QNum, _GoI, _SI, fil)=Qd, Info) ->
+ {Qd, Info};
+ (?qual_data(_QNum, _GoI, _SI, {gen, #join{}})=Qd, Info) ->
+ {Qd, Info};
+ (?qual_data(QNum, GoI, SI, {gen, PrepLE0}), Info) ->
+ PrepLE = sort_info(PrepLE0, QNum, QOpt),
+ Qd = ?qual_data(QNum, GoI, SI, {gen, PrepLE}),
+ I = [{{Column,Order}, [{traverse,QNum,C}]} ||
+ {{C,Order},What} <- PrepLE#prepared.sort_info2,
+ What =:= [], % Something else later...
+ Column <- equal_template_columns(QOpt, {QNum,C})],
+ {Qd, [I | Info]}
+ end,
+ {Qdata, SortInfoL} = lists:mapfoldl(F, [], Qdata0),
+ SortInfo0 = [{{Pos,Ord}, [template]} ||
+ Pos <- constant_columns(QOpt, 0),
+ Ord <- orders(yes)]
+ ++ lists:append(SortInfoL),
+ SortInfo = family_union(SortInfo0),
+ {Qdata, SortInfo}.
+
+sort_info(#prepared{sort_info = SI, sorted = S} = Prep, QNum, QOpt) ->
+ SI1 = [{{C,Ord},[]} ||
+ S =/= no,
+ is_integer(Sz = size_of_qualifier(QOpt, QNum)),
+ Sz > 0, % the size of the pattern
+ (NConstCols = size_of_constant_prefix(QOpt, QNum)) < Sz,
+ C <- [NConstCols+1],
+ Ord <- orders(S)]
+ ++ [{{Pos,Ord},[]} || Pos <- constant_columns(QOpt, QNum),
+ Ord <- orders(yes)]
+ ++ [{PosOrd,[]} || {PosOrd,_} <- SI],
+ SI2 = lists:usort(SI1),
+ Prep#prepared{sort_info2 = SI2}.
+
+%orders(descending=O) ->
+% [O];
+orders(ascending=O) ->
+ [O];
+orders(yes) ->
+ [ascending
+% ,descending
+ ].
+
+sort_unique(true, #qlc_sort{fs_opts = SortOptions, keypos = sort}=Sort) ->
+ Sort#qlc_sort{unique = false,
+ fs_opts =
+ lists:keydelete(unique, 1,
+ lists:delete(unique, SortOptions))};
+sort_unique(_, Sort) ->
+ Sort.
+
+sort_tmpdir(S, #qlc_opt{tmpdir = ""}) ->
+ S;
+sort_tmpdir(S, Opt) ->
+ S#qlc_sort{tmpdir = Opt#qlc_opt.tmpdir}.
+
+short_list(L) ->
+ %% length(L) < 2: all elements are known be equal
+ #prepared{qh = #qlc_list{l = L}, sorted = yes, is_unique_objects = true,
+ is_cached = true}.
+
+find_const_positions(IF, LU_fun, {KeyEquality, PosFun},
+ #qlc_opt{max_lookup = Max, lookup = Lookup})
+ when is_function(LU_fun), is_function(PosFun), is_function(IF),
+ Lookup =/= false ->
+ case call(IF, keypos, undefined, []) of
+ undefined ->
+ Indices = call(IF, indices, undefined, []),
+ find_const_position_idx(Indices, KeyEquality, PosFun, Max, []);
+ KeyPos ->
+ case pos_vals(KeyPos, KeyEquality, PosFun(KeyPos), Max) of
+ false ->
+ find_const_position_idx(IF(indices), KeyEquality,
+ PosFun, Max, []);
+ PosValuesSkip ->
+ PosValuesSkip
+ end
+ end;
+find_const_positions(_IF, _LU_fun, _KE_PosFun, _Opt0) ->
+ {false, {some,[]}}.
+
+find_const_position_idx([I | Is], KeyEquality, PosFun, Max, L0) ->
+ case pos_vals(I, KeyEquality, PosFun(I), Max) of
+ false ->
+ find_const_position_idx(Is, KeyEquality, PosFun, Max, L0);
+ {{_Pos, Values}, _SkipFils}=PosValuesFils ->
+ L = [{length(Values), PosValuesFils} | L0],
+ find_const_position_idx(Is, KeyEquality, PosFun, Max, L)
+ end;
+find_const_position_idx(_, _KeyEquality, _PosFun, _Max, []) ->
+ {false, {some,[]}};
+find_const_position_idx(_, _KeyEquality, _PosFun, _Max, L) ->
+ [{_,PVF} | _] = lists:sort(L),
+ PVF.
+
+pos_vals(Pos, '==', {usort_needed, Values, SkipFils}, Max) ->
+ pos_vals_max(Pos, lists:usort(Values), SkipFils, Max);
+pos_vals(Pos, '=:=', {usort_needed, Values, SkipFils}, Max) ->
+ pos_vals_max(Pos, lists:sort(nub(Values)), SkipFils, Max);
+pos_vals(Pos, _KeyEquality, {values, Values, SkipFils}, Max) ->
+ pos_vals_max(Pos, Values, SkipFils, Max);
+pos_vals(_Pos, _KeyEquality, _T, _Max) ->
+ false.
+
+nub([]) ->
+ [];
+nub([E | L]) ->
+ case lists:member(E, Es=nub(L)) of
+ true ->
+ Es;
+ false ->
+ [E | Es]
+ end.
+
+%% length(Values) >= 1
+pos_vals_max(Pos, Values, Skip, Max) when Max =:= -1; Max >= length(Values) ->
+ {{Pos, Values}, Skip};
+pos_vals_max(_Pos, _Value, _Skip, _Max) ->
+ false.
+
+prep_join(Prep, QOpt, Opt) ->
+ case join_opt(QOpt) of
+ undefined ->
+ check_join_option(Opt),
+ Prep;
+ EqualMatch ->
+ {Ix, M} = case EqualMatch of
+ {NEqual, NMatch} ->
+ pref_join(NEqual, NMatch, Prep, QOpt, Opt);
+ EM ->
+ pref_join(EM, EM, Prep, QOpt, Opt)
+ end,
+ SI = family_union(Prep#prepared.sort_info ++ M),
+ Prep#prepared{join = {Ix, M}, sort_info = SI}
+ end.
+
+%% The parse transform ensures that only two tables are involved.
+pref_join(Equal, Match, Prep, QOpt, #qlc_opt{join = JoinOpt}) ->
+ JQs = [{KeyEquality, QCs} ||
+ {KeyEquality, QCsL} <- [{'==',Equal}, {'=:=',Match}],
+ QCs <- QCsL],
+ IxL = [pref_lookup_join(KE, QCs, Prep, QOpt) ||
+ JoinOpt =:= any orelse JoinOpt =:= lookup,
+ {KE, QCs} <- JQs],
+ ML = [pref_merge_join(KE, QCs, Prep, QOpt) ||
+ JoinOpt =:= any orelse JoinOpt =:= merge,
+ {KE, QCs} <- JQs],
+ {lists:usort(lists:append(IxL)), lists:usort(lists:append(ML))}.
+
+pref_lookup_join(KeyEquality, {[{Q1,C1},{Q2,C2}],Skip}, Prep, QOpt)
+ when is_integer(C1), is_integer(C2) ->
+ #prepared{qh = #qlc{qdata = QData}} = Prep,
+ Is1 = lookup_qual_data(QData, Q1, KeyEquality),
+ Lu2 = [pref_lookup_join2(Q2, C2, Q1, C1, Skip, QOpt, KeyEquality) ||
+ IC1 <- Is1, IC1 =:= C1],
+ Is2 = lookup_qual_data(QData, Q2, KeyEquality),
+ Lu1 = [pref_lookup_join2(Q1, C1, Q2, C2, Skip, QOpt, KeyEquality) ||
+ IC2 <- Is2, IC2 =:= C2],
+ family(Lu1 ++ Lu2);
+pref_lookup_join(KE, [{_,Cs1},{_,Cs2}]=L, Prep, QOpt) when is_list(Cs1),
+ is_list(Cs2) ->
+ %% --R12B-5
+ lists:append([pref_lookup_join(KE, QC,Prep,QOpt) ||
+ QC <- selections_no_skip(L)]).
+
+lookup_qual_data(QData, QNum, KeyEquality) ->
+ case lists:keysearch(QNum, 1, QData) of
+ {value, ?qual_data(QNum, _, _, {gen, PrepLE})} ->
+ join_indices(PrepLE, KeyEquality)
+ end.
+
+%% If the table has a match specification (ms =/= no_match_spec) that
+%% has _not_ been derived from a filter but from a query handle then
+%% the lookup join cannot be done. This particular case has not been
+%% excluded here but is taken care of in opt_join().
+join_indices(#prepared{qh = #qlc_table{info_fun = IF,
+ lookup_fun = LU_fun,
+ key_equality = KeyEquality,
+ lu_vals = undefined}},
+ KE) when is_function(LU_fun),
+ KE =:= KeyEquality orelse
+ KE =:= '=:=' andalso
+ KeyEquality =:= undefined -> % --R12B-5
+ KpL = case call(IF, keypos, undefined, []) of
+ undefined -> [];
+ Kp -> [Kp]
+ end,
+ case call(IF, indices, undefined, []) of
+ undefined -> KpL;
+ Is0 -> lists:usort(KpL ++ Is0)
+ end;
+join_indices(_Prep, _KeyEquality) ->
+ [].
+
+pref_lookup_join2(Q1, C1, Q2, C2, Skip, QOpt, KeyEquality) ->
+ TemplCols = compared_template_columns(QOpt, {Q1,C1}, KeyEquality),
+ {{Q1,C1,Q2,C2},{lookup_join,TemplCols,KeyEquality,Skip}}.
+
+pref_merge_join(KE, {[{Q1,C1},{Q2,C2}],Skip}, Prep, QOpt)
+ when is_integer(C1), is_integer(C2) ->
+ #prepared{qh = #qlc{qdata = QData}} = Prep,
+ Sort1 = merge_qual_data(QData, Q1),
+ Sort2 = merge_qual_data(QData, Q2),
+ Merge = pref_merge(KE, Q1, C1, Q2, C2, Skip, Sort1, Sort2, QOpt),
+ family_union(Merge);
+pref_merge_join(KE, [{_,Cs1},{_,Cs2}]=L, Prep, QOpt) when is_list(Cs1),
+ is_list(Cs2) ->
+ %% --R12B-5
+ lists:append([pref_merge_join(KE, QC, Prep, QOpt) ||
+ QC <- selections_no_skip(L)]).
+
+selections_no_skip(L) ->
+ [{C,{some,[]}} || C <- all_selections(L)].
+
+merge_qual_data(QData, QNum) ->
+ case lists:keysearch(QNum, 1, QData) of
+ {value, ?qual_data(QNum, _, _, {gen, PrepLE})} ->
+ #prepared{sort_info2 = SortInfo} = PrepLE,
+ SortInfo
+ end.
+
+pref_merge(KE, Q1, C1, Q2, C2, Skip, Sort1, Sort2, QOpt) ->
+ Col1 = {Q1,C1},
+ Col2 = {Q2,C2},
+ DoSort = [QC || {{_QNum,Col}=QC,SortL} <- [{Col1,Sort1}, {Col2,Sort2}],
+ lists:keymember({Col, ascending}, 1, SortL) =:= false],
+ J = [{{Q1,C1,Q2,C2}, {merge_join,DoSort,KE,Skip}}],
+ %% true = (QOpt(template))(Col1, '==') =:= (QOpt(template))(Col2, '==')
+ [{{Column, ascending}, J} ||
+ Column <- equal_template_columns(QOpt, Col1)] ++ [{other, J}].
+
+table_sort_info(#qlc_table{info_fun = IF}) ->
+ case call(IF, is_sorted_key, undefined, []) of
+ undefined ->
+ {[], no};
+ false ->
+ {[], no};
+ true ->
+ case call(IF, keypos, undefined, []) of
+ undefined -> % strange
+ {[], no};
+ KeyPos ->
+ {[{{KeyPos,ascending},[]}], no}
+ end
+ end.
+
+sort_sort_info(#qlc_sort{keypos = sort, order = Ord0}) ->
+ {[], sort_order(Ord0)};
+sort_sort_info(#qlc_sort{keypos = {keysort,Kp0}, order = Ord0}) ->
+ Kp = case Kp0 of
+ [Pos | _] -> Pos;
+ _ -> Kp0
+ end,
+ {[{{Kp,sort_order(Ord0)},[]}], no}.
+
+sort_order(F) when is_function(F) ->
+ no;
+sort_order(Order) ->
+ Order.
+
+check_join_option(#qlc_opt{join = any}) ->
+ ok;
+check_join_option(#qlc_opt{join = Join}) ->
+ erlang:error(no_join_to_carry_out, [{join,Join}]).
+
+check_lookup_option(#qlc_opt{lookup = true}, false) ->
+ erlang:error(no_lookup_to_carry_out, [{lookup,true}]);
+check_lookup_option(_QOpt, _LuV) ->
+ ok.
+
+compared_template_columns(QOpt, QNumColumn, KeyEquality) ->
+ (QOpt(template))(QNumColumn, KeyEquality).
+
+equal_template_columns(QOpt, QNumColumn) ->
+ (QOpt(template))(QNumColumn, '==').
+
+%eq_template_columns(QOpt, QNumColumn) ->
+% (QOpt(template))(QNumColumn, '=:=').
+
+size_of_constant_prefix(QOpt, QNum) ->
+ (QOpt(n_leading_constant_columns))(QNum).
+
+constants(QOpt, QNum) ->
+ (QOpt(constants))(QNum).
+
+equal_constants(QOpt, QNum) ->
+ (QOpt(equal_constants))(QNum).
+
+join_opt(QOpt) ->
+ QOpt(join).
+
+match_specs(QOpt, QNum) ->
+ (QOpt(match_specs))(QNum).
+
+constant_columns(QOpt, QNum) ->
+ (QOpt(constant_columns))(QNum).
+
+size_of_qualifier(QOpt, QNum) ->
+ (QOpt(size))(QNum).
+
+%% Two optimizations are carried out:
+%% 1. The first generator is never cached if the QLC itself is cached.
+%% Since the answers do not need to be cached, the top-most QLC is
+%% never cached either. Simple QLCs not holding any options are
+%% removed. Simple QLCs are coalesced when possible.
+%% 2. Merge join and lookup join is done if possible.
+
+opt_le(#prepared{qh = #simple_qlc{le = LE0, optz = Optz0}=QLC}=Prep0,
+ GenNum) ->
+ case LE0 of
+ #prepared{qh = #simple_qlc{p = LE_Pvar, le = LE2, optz = Optz2}} ->
+ %% Coalesce two simple QLCs.
+ Cachez = case Optz2#optz.cache of
+ false -> Optz0#optz.cache;
+ Cache2 -> Cache2
+ end,
+ Optz = Optz0#optz{cache = Cachez,
+ unique = Optz0#optz.unique or Optz2#optz.unique},
+ PVar = if
+ LE_Pvar =:= ?SIMPLE_QVAR -> QLC#simple_qlc.p;
+ true -> LE_Pvar
+ end,
+ Prep = Prep0#prepared{qh = QLC#simple_qlc{p = PVar, le = LE2,
+ optz = Optz}},
+ opt_le(Prep, GenNum);
+ _ ->
+ Optz1 = no_cache_of_first_generator(Optz0, GenNum),
+ case {opt_le(LE0, 1), Optz1} of
+ {LE, #optz{unique = false, cache = false}} ->
+ LE;
+ {LE, _} ->
+ Prep0#prepared{qh = QLC#simple_qlc{le = LE, optz = Optz1}}
+ end
+ end;
+opt_le(#prepared{qh = #qlc{}, lu_skip_quals = LU_SkipQuals0}=Prep0, GenNum) ->
+ #prepared{qh = #qlc{qdata = Qdata0, optz = Optz0}=QLC} = Prep0,
+ #optz{join_option = JoinOption, opt = Opt} = Optz0,
+ JoinOption = Optz0#optz.join_option,
+ {LU_QNum, Join, JoinSkipFs, DoSort} =
+ opt_join(Prep0#prepared.join, JoinOption, Qdata0, Opt, LU_SkipQuals0),
+ {LU_Skip, LU_SkipQuals} =
+ lists:partition(fun({QNum,_Fs}) -> QNum =:= LU_QNum end,
+ LU_SkipQuals0),
+ LU_SkipFs = lists:flatmap(fun({_QNum,Fs}) -> Fs end, LU_SkipQuals),
+ %% If LU_QNum has a match spec it must be applied _after_ the
+ %% lookup join (the filter must not be skipped!).
+ Qdata1 = if
+ LU_Skip =:= [] -> Qdata0;
+ true -> activate_join_lookup_filter(LU_QNum, Qdata0)
+ end,
+ Qdata2 = skip_lookup_filters(Qdata1, LU_SkipFs ++ JoinSkipFs),
+ F = fun(?qual_data(QNum, GoI, SI, {gen, #prepared{}=PrepLE}), GenNum1) ->
+ NewPrepLE = maybe_sort(PrepLE, QNum, DoSort, Opt),
+ {?qual_data(QNum, GoI, SI, {gen, opt_le(NewPrepLE, GenNum1)}),
+ GenNum1 + 1};
+ (Qd, GenNum1) ->
+ {Qd, GenNum1}
+ end,
+ {Qdata, _} = lists:mapfoldl(F, 1, Qdata2),
+ Optz1 = no_cache_of_first_generator(Optz0, GenNum),
+ Optz = Optz1#optz{fast_join = Join},
+ Prep0#prepared{qh = QLC#qlc{qdata = Qdata, optz = Optz}};
+opt_le(#prepared{qh = #qlc_append{hl = HL}}=Prep, GenNum) ->
+ Hs = [opt_le(H, GenNum) || H <- HL],
+ Prep#prepared{qh = #qlc_append{hl = Hs}};
+opt_le(#prepared{qh = #qlc_sort{h = H}=Sort}=Prep, GenNum) ->
+ Prep#prepared{qh = Sort#qlc_sort{h = opt_le(H, GenNum)}};
+opt_le(Prep, _GenNum) ->
+ Prep.
+
+no_cache_of_first_generator(Optz, GenNum) when GenNum > 1 ->
+ Optz;
+no_cache_of_first_generator(Optz, 1) ->
+ Optz#optz{cache = false}.
+
+maybe_sort(LE, QNum, DoSort, Opt) ->
+ case lists:keysearch(QNum, 1, DoSort) of
+ {value, {QNum, Col}} ->
+ #qlc_opt{tmpdir = TmpDir, tmpdir_usage = TmpUsage} = Opt,
+ SortOpts = [{tmpdir,Dir} || Dir <- [TmpDir], Dir =/= ""],
+ Sort = #qlc_sort{h = LE, keypos = {keysort, Col}, unique = false,
+ compressed = [], order = ascending,
+ fs_opts = SortOpts, tmpdir_usage = TmpUsage,
+ tmpdir = TmpDir},
+ #prepared{qh = Sort, sorted = no, join = no};
+ false ->
+ LE
+ end.
+
+skip_lookup_filters(Qdata, []) ->
+ Qdata;
+skip_lookup_filters(Qdata0, LU_SkipFs) ->
+ [case lists:member(QNum, LU_SkipFs) of
+ true ->
+ ?qual_data(QNum, GoI, ?SKIP, fil);
+ false ->
+ Qd
+ end || ?qual_data(QNum, GoI, _, _)=Qd <- Qdata0].
+
+%% If the qualifier used for lookup by the join (QNum) has a match
+%% specification it must be applied _after_ the lookup join (the
+%% filter must not be skipped!).
+activate_join_lookup_filter(QNum, Qdata) ->
+ {value, {_,GoI2,SI2,{gen,Prep2}}} = lists:keysearch(QNum, 1, Qdata),
+ Table2 = Prep2#prepared.qh,
+ NPrep2 = Prep2#prepared{qh = Table2#qlc_table{ms = no_match_spec}},
+ %% Table2#qlc_table.ms has been reset; the filter will be run.
+ lists:keyreplace(QNum, 1, Qdata, ?qual_data(QNum,GoI2,SI2,{gen,NPrep2})).
+
+opt_join(Join, JoinOption, Qdata, Opt, LU_SkipQuals) ->
+ %% prep_qlc_lc() assures that no unwanted join is carried out
+ {Ix0, M0} = Join,
+ Ix1 = opt_join_lu(Ix0, Qdata, LU_SkipQuals),
+ Ix = lists:reverse(lists:keysort(2, Ix1)), % prefer to skip
+ case Ix of
+ [{{Q1,C1,Q2,C2},Skip,KE,LU_fun} | _] ->
+ J = #qlc_join{kind = {lookup, KE, LU_fun}, q1 = Q1,
+ c1 = C1, q2 = Q2, c2 = C2, opt = Opt},
+ {Q2, J, Skip, []};
+ [] ->
+ M = opt_join_merge(M0),
+ case M of
+ [{{Q1,C1,Q2,C2},{merge_join,DoSort,KE,Skip}}|_] ->
+ J = #qlc_join{kind = {merge, KE}, opt = Opt,
+ q1 = Q1, c1 = C1, q2 = Q2, c2 = C2},
+ {not_a_qnum, J, Skip, DoSort};
+ [] when JoinOption =:= nested_loop ->
+ {not_a_qnum, no, [], []};
+ _ when JoinOption =/= any ->
+ erlang:error(cannot_carry_out_join, [JoinOption]);
+ _ ->
+ {not_a_qnum, no, [], []}
+ end
+ end.
+
+opt_join_lu([{{_Q1,_C1,Q2,_C2}=J,[{lookup_join,_KEols,JKE,Skip0} | _]} | LJ],
+ Qdata, LU_SkipQuals) ->
+ {value, {Q2,_,_,{gen,Prep2}}} = lists:keysearch(Q2, 1, Qdata),
+ #qlc_table{ms = MS, key_equality = KE,
+ lookup_fun = LU_fun} = Prep2#prepared.qh,
+ %% If there is no filter to skip (the match spec was derived
+ %% from a query handle) then the lookup join cannot be done.
+ case
+ MS =/= no_match_spec andalso
+ lists:keymember(Q2, 1, LU_SkipQuals) =:= false
+ of
+ true ->
+ opt_join_lu(LJ, Qdata, LU_SkipQuals);
+ false ->
+ %% The join is preferred before evaluating the match spec
+ %% (if there is one).
+ Skip = skip_if_possible(JKE, KE, Skip0),
+ [{J,Skip,KE,LU_fun} | opt_join_lu(LJ, Qdata, LU_SkipQuals)]
+ end;
+opt_join_lu([], _Qdata, _LU_SkipQuals) ->
+ [].
+
+opt_join_merge(M) ->
+ %% Prefer not to sort arguments. Prefer to skip join filter.
+ L = [{-length(DoSort),length(Skip),
+ {QCs,{merge_join,DoSort,KE,Skip}}} ||
+ {_KpOrder_or_other,MJ} <- M,
+ {QCs,{merge_join,DoSort,KE,Skip0}} <- MJ,
+ Skip <- [skip_if_possible(KE, '==', Skip0)]],
+ lists:reverse([J || {_,_,J} <- lists:sort(L)]).
+
+%% Cannot skip the join filter the join operator is '=:=' and the join
+%% is performed using '=='. Note: the tag 'some'/'all' is not used.
+skip_if_possible('=:=', '==', _) ->
+ [];
+skip_if_possible(_, _, {_SkipTag, Skip}) ->
+ Skip.
+
+%% -> {Objects, Post, LocalPost} | throw()
+%% Post is a list of funs (closures) to run afterwards.
+%% LocalPost should be run when all objects have been found (optimization).
+%% LocalPost will always be a subset of Post.
+%% List expressions are evaluated, resulting in lists of objects kept in
+%% RAM or on disk.
+%% An error term is thrown as soon as cleanup according Post has been
+%% done. (This is opposed to errors during evaluation; such errors are
+%% returned as terms.)
+setup_qlc(Prep, Setup) ->
+ Post0 = [],
+ setup_le(Prep, Post0, Setup).
+
+setup_le(#prepared{qh = #simple_qlc{le = LE, optz = Optz}}, Post0, Setup) ->
+ {Objs, Post, LocalPost} = setup_le(LE, Post0, Setup),
+ unique_cache(Objs, Post, LocalPost, Optz);
+setup_le(#prepared{qh = #qlc{lcf = QFun, qdata = Qdata, init_value = V,
+ optz = Optz}}, Post0, Setup) ->
+ {GoTo, FirstState, Post, LocalPost} =
+ setup_quals(Qdata, Post0, Setup, Optz),
+ Objs = fun() -> QFun(FirstState, V, GoTo) end,
+ unique_cache(Objs, Post, LocalPost, Optz);
+setup_le(#prepared{qh = #qlc_table{post_fun = PostFun}=Table}, Post, Setup) ->
+ H = table_handle(Table, Post, Setup),
+ %% The pre fun has been called from table_handle():
+ {H, [PostFun | Post], []};
+setup_le(#prepared{qh = #qlc_append{hl = PrepL}}, Post0, Setup) ->
+ F = fun(Prep, {Post1, LPost1}) ->
+ {Objs, Post2, LPost2} = setup_le(Prep, Post1, Setup),
+ {Objs, {Post2, LPost1++LPost2}}
+ end,
+ {ObjsL, {Post, LocalPost}} = lists:mapfoldl(F, {Post0,[]}, PrepL),
+ {fun() -> append_loop(ObjsL, 0) end, Post, LocalPost};
+setup_le(#prepared{qh = #qlc_sort{h = Prep, keypos = Kp,
+ unique = Unique, compressed = Compressed,
+ order = Order, fs_opts = SortOptions0,
+ tmpdir_usage = TmpUsage,tmpdir = TmpDir}},
+ Post0, Setup) ->
+ SortOptions = sort_options_global_tmp(SortOptions0, TmpDir),
+ LF = fun(Objs) ->
+ sort_list(Objs, Order, Unique, Kp, SortOptions, Post0)
+ end,
+ case setup_le(Prep, Post0, Setup) of
+ {L, Post, LocalPost} when is_list(L) ->
+ {LF(L), Post, LocalPost};
+ {Objs, Post, LocalPost} ->
+ FF = fun(Objs1) ->
+ file_sort_handle(Objs1, Kp, SortOptions, TmpDir,
+ Compressed, Post, LocalPost)
+ end,
+ sort_handle(Objs, LF, FF, SortOptions, Post, LocalPost,
+ {TmpUsage, sorting})
+ end;
+setup_le(#prepared{qh = #qlc_list{l = L, ms = MS}}, Post, _Setup)
+ when (no_match_spec =:= MS); L =:= [] ->
+ {L, Post, []};
+setup_le(#prepared{qh = #qlc_list{l = L, ms = MS}}, Post, _Setup)
+ when is_list(L) ->
+ {ets:match_spec_run(L, ets:match_spec_compile(MS)), Post, []};
+setup_le(#prepared{qh = #qlc_list{l = H0, ms = MS}}, Post0, Setup) ->
+ {Objs0, Post, LocalPost} = setup_le(H0, Post0, Setup),
+ Objs = ets:match_spec_run(Objs0, ets:match_spec_compile(MS)),
+ {Objs, Post, LocalPost}.
+
+%% The goto table (a tuple) is created at runtime. It is accessed by
+%% the generated code in order to find next clause to execute. For
+%% generators there is also a fun; calling the fun runs the list
+%% expression of the generator. There are two elements for a filter:
+%% the first one is the state to go when the filter is false; the
+%% other the state when the filter is true. There are three elements
+%% for a generator G: the first one is the state of the generator
+%% before G (or the stop state if there is no generator); the second
+%% one is the state of the qualifier following the generator (or the
+%% template if there is no next generator); the third one is the list
+%% expression fun.
+%% There are also join generators which are "activated" when it is
+%% possbible to do a join.
+
+setup_quals(Qdata, Post0, Setup, Optz) ->
+ {GoTo0, Post1, LocalPost0} =
+ setup_quals(0, Qdata, [], Post0, [], Setup),
+ GoTo1 = lists:keysort(1, GoTo0),
+ FirstState0 = next_state(Qdata),
+ {GoTo2, FirstState, Post, LocalPost1} =
+ case Optz#optz.fast_join of
+ #qlc_join{kind = {merge,_KE}, c1 = C1, c2 = C2, opt = Opt} = MJ ->
+ MF = fun(_Rev, {H1, WH1}, {H2, WH2}) ->
+ fun() ->
+ merge_join(WH1(H1), C1, WH2(H2), C2, Opt)
+ end
+ end,
+ setup_join(MJ, Qdata, GoTo1, FirstState0, MF, Post1);
+ #qlc_join{kind = {lookup,_KE,LuF}, c1 = C1, c2 = C2} = LJ ->
+ LF = fun(Rev, {H1, WH1}, {H2, WH2}) ->
+ {H, W} = if
+ Rev -> {H2, WH2};
+ true -> {H1, WH1}
+ end,
+ fun() ->
+ lookup_join(W(H), C1, LuF, C2, Rev)
+ end
+ end,
+ setup_join(LJ, Qdata, GoTo1, FirstState0, LF, Post1);
+ no ->
+ {flat_goto(GoTo1), FirstState0, Post1, []}
+ end,
+ GoTo = list_to_tuple(GoTo2),
+ {GoTo, FirstState, Post, LocalPost0 ++ LocalPost1}.
+
+setup_quals(GenLoopS, [?qual_data(_QNum,GoI,?SKIP,fil) | Qdata],
+ Gs, P, LP, Setup) ->
+ %% ?SKIP causes runtime error. See also skip_lookup_filters().
+ setup_quals(GenLoopS, Qdata, [{GoI,[?SKIP,?SKIP]} | Gs], P, LP, Setup);
+setup_quals(GenLoopS, [?qual_data(_QNum,GoI,_SI,fil) | Qdata],
+ Gs, P, LP, Setup) ->
+ setup_quals(GenLoopS, Qdata, [{GoI,[GenLoopS,next_state(Qdata)]} | Gs],
+ P, LP, Setup);
+setup_quals(GenLoopS, [?qual_data(_QNum,GoI,_SI, {gen,#join{}}) | Qdata],
+ Gs, P, LP, Setup) ->
+ setup_quals(GenLoopS, Qdata, [{GoI,[?SKIP,?SKIP,?SKIP]} | Gs],P,LP,Setup);
+setup_quals(GenLoopS, [?qual_data(_QNum,GoI,SI,{gen,LE}) | Qdata],
+ Gs, P, LP, Setup) ->
+ {V, NP, LP1} = setup_le(LE, P, Setup),
+ setup_quals(SI + 1, Qdata, [{GoI, [GenLoopS,next_state(Qdata),V]} | Gs],
+ NP, LP ++ LP1, Setup);
+setup_quals(GenLoopS, [], Gs, P, LP, _Setup) ->
+ {[{1,[GenLoopS]} | Gs], P, LP}.
+
+%% Finds the qualifier in Qdata that performs the join between Q1 and
+%% Q2, and sets it up using the handles already set up for Q1 and Q2.
+%% Removes Q1 and Q2 from GoTo0 and updates the join qualifier in GoTo0.
+%% Note: the parse transform has given each generator three slots
+%% in the GoTo table. The position of these slots within the GoTo table
+%% is fixed (at runtime).
+%% (Assumes there is only one join-generator in Qdata.)
+setup_join(J, Qdata, GoTo0, FirstState0, JoinFun, Post0) ->
+ #qlc_join{q1 = QNum1a, q2 = QNum2a, opt = Opt} = J,
+ {?qual_data(_QN,JGoI,JSI,_), Rev, QNum1, QNum2, WH1, WH2, _CsFun} =
+ find_join_data(Qdata, QNum1a, QNum2a),
+ [{GoI1,SI1}] = [{GoI,SI} ||
+ ?qual_data(QNum,GoI,SI,_) <- Qdata, QNum =:= QNum1],
+ [{GoI2,SI2}] = [{GoI,SI} ||
+ ?qual_data(QNum,GoI,SI,_) <- Qdata, QNum =:= QNum2],
+
+ [H1] = [H || {GoI,[_Back,_Forth,H]} <- GoTo0, GoI =:= GoI1],
+ [{BackH2,H2}] =
+ [{Back,H} || {GoI,[Back,_Forth,H]} <- GoTo0, GoI =:= GoI2],
+ H0 = JoinFun(Rev, {H1,WH1}, {H2,WH2}),
+ %% The qlc expression options apply to the introduced qlc expr as well.
+ {H, Post, LocalPost} =
+ unique_cache(H0, Post0, [], join_unique_cache(Opt)),
+ [JBack] = [Back || {GoI,[Back,_,_]} <- GoTo0, GoI =:= GoI1],
+ JForth = next_after(Qdata, SI1, QNum2),
+ GoTo1 = lists:map(fun({GoI,_}) when GoI =:= JGoI ->
+ {JGoI, [JBack, JForth, H]};
+ ({GoI,_}) when GoI =:= GoI1; GoI =:= GoI2 ->
+ {GoI, [?SKIP,?SKIP,?SKIP]}; % not necessary
+ (Go) ->
+ Go
+ end, GoTo0),
+ GoTo = lists:map(fun(S) when S =:= SI1 ->
+ JSI;
+ (S) when S =:= SI2 ->
+ next_after(Qdata, S, QNum2);
+ (S) when S =:= SI1+1 ->
+ JSI+1;
+ (S) when S =:= SI2+1, SI1 + 1 =:= BackH2 ->
+ JSI+1;
+ (S) when S =:= SI2+1 ->
+ BackH2;
+ (S) -> S
+ end, flat_goto(GoTo1)),
+ FirstState = if
+ SI1 =:= FirstState0 -> JSI;
+ true -> FirstState0
+ end,
+ {GoTo, FirstState, Post, LocalPost}.
+
+join_unique_cache(#qlc_opt{cache = Cache, unique = Unique}=Opt) ->
+ #optz{cache = Cache, unique = Unique, opt = Opt}.
+
+flat_goto(GoTo) ->
+ lists:flatmap(fun({_,L}) -> L end, GoTo).
+
+next_after([?qual_data(_, _, S, _) | Qdata], S, QNum2) ->
+ case Qdata of
+ [?qual_data(QNum2, _, _, _) | Qdata1] ->
+ next_state(Qdata1);
+ _ ->
+ next_state(Qdata)
+ end;
+next_after([_ | Qdata], S, QNum2) ->
+ next_after(Qdata, S, QNum2).
+
+next_state([?qual_data(_,_,_,{gen,#join{}}) | Qdata]) ->
+ next_state(Qdata);
+next_state([?qual_data(_,_,?SKIP,fil) | Qdata]) ->
+ %% see skip_lookup_filters()
+ next_state(Qdata);
+next_state([?qual_data(_,_,S,_) | _]) ->
+ S;
+next_state([]) ->
+ template_state().
+
+find_join_data(Qdata, QNum1, QNum2) ->
+ [QRev] = [{Q,Rev,QN1,QN2,H1,H2,CsF} ||
+ ?qual_data(_QN,_GoI,_SI,
+ {gen,#join{q1 = QN1,q2 = QN2,
+ wh1 = H1, wh2 = H2,
+ cs_fun = CsF}})= Q <- Qdata,
+ if
+ QN1 =:= QNum1, QN2 =:= QNum2 ->
+ not (Rev = false);
+ QN1 =:= QNum2, QN2 =:= QNum1 ->
+ Rev = true;
+ true ->
+ Rev = false
+ end],
+ QRev.
+
+table_handle(#qlc_table{trav_fun = TraverseFun, trav_MS = TravMS,
+ pre_fun = PreFun, lookup_fun = LuF,
+ parent_fun = ParentFun, lu_vals = LuVals, ms = MS},
+ Post, Setup) ->
+ #setup{parent = Parent} = Setup,
+ ParentValue =
+ if
+ ParentFun =:= undefined ->
+ undefined;
+ Parent =:= self() ->
+ try
+ ParentFun()
+ catch Class:Reason ->
+ post_funs(Post),
+ erlang:raise(Class, Reason, erlang:get_stacktrace())
+ end;
+ true ->
+ case monitor_request(Parent, {parent_fun, ParentFun}) of
+ error -> % parent has died
+ post_funs(Post),
+ exit(normal);
+ {value, Value} ->
+ Value;
+ {parent_fun_caught, Class, Reason, Stacktrace} ->
+ %% No use augmenting Stacktrace here.
+ post_funs(Post),
+ erlang:raise(Class, Reason, Stacktrace)
+ end
+ end,
+ StopFun =
+ if
+ Parent =:= self() ->
+ undefined;
+ true ->
+ Cursor = #qlc_cursor{c = {self(), Parent}},
+ fun() -> delete_cursor(Cursor) end
+ end,
+ PreFunArgs = [{parent_value, ParentValue}, {stop_fun, StopFun}],
+ _ = call(PreFun, PreFunArgs, ok, Post),
+ case LuVals of
+ {Pos, Vals} when MS =:= no_match_spec ->
+ LuF(Pos, Vals);
+ {Pos, Vals} ->
+ case LuF(Pos, Vals) of
+ [] ->
+ [];
+ Objs when is_list(Objs) ->
+ ets:match_spec_run(Objs,
+ ets:match_spec_compile(MS));
+ Error ->
+ post_funs(Post),
+ throw_error(Error)
+ end;
+ _ when not TravMS ->
+ MS = no_match_spec, % assertion
+ TraverseFun;
+ _ when MS =:= no_match_spec ->
+ fun() -> TraverseFun([{'$1',[],['$1']}]) end;
+ _ ->
+ fun() -> TraverseFun(MS) end
+ end.
+
+-define(CHUNK_SIZE, 64*1024).
+
+open_file(FileName, Extra, Post) ->
+ case file:open(FileName, [read, raw, binary | Extra]) of
+ {ok, Fd} ->
+ {fun() ->
+ case file:position(Fd, bof) of
+ {ok, 0} ->
+ TF = fun([], _) ->
+ [];
+ (Ts, C) when is_list(Ts) ->
+ lists:reverse(Ts, C)
+ end,
+ file_loop_read(<<>>, ?CHUNK_SIZE, {Fd,FileName}, TF);
+ Error ->
+ file_error(FileName, Error)
+ end
+ end, Fd};
+ Error ->
+ post_funs(Post),
+ throw_file_error(FileName, Error)
+ end.
+
+file_loop(Bin0, Fd_FName, Ts0, TF) ->
+ case
+ try file_loop2(Bin0, Ts0)
+ catch _:_ ->
+ {_Fd, FileName} = Fd_FName,
+ error({bad_object, FileName})
+ end
+ of
+ {terms, <<Size:4/unit:8, B/bytes>>=Bin, []} ->
+ file_loop_read(Bin, Size - byte_size(B) + 4, Fd_FName, TF);
+ {terms, <<Size:4/unit:8, _/bytes>>=Bin, Ts} ->
+ C = fun() -> file_loop_read(Bin, Size+4, Fd_FName, TF) end,
+ TF(Ts, C);
+ {terms, B, Ts} ->
+ C = fun() -> file_loop_read(B, ?CHUNK_SIZE, Fd_FName, TF) end,
+ TF(Ts, C);
+ Error ->
+ Error
+ end.
+
+file_loop2(<<Size:4/unit:8, B:Size/bytes, Bin/bytes>>, Ts) ->
+ file_loop2(Bin, [binary_to_term(B) | Ts]);
+file_loop2(Bin, Ts) ->
+ {terms, Bin, Ts}.
+
+%% After power failures (and only then) files with corrupted Size
+%% fields have been observed in a disk_log file. If file:read/2 is
+%% asked to read a huge amount of data the emulator may crash. Nothing
+%% has been done here to prevent such crashes (by inspecting
+%% BytesToRead in some way) since temporary files will never be read
+%% after a power failure.
+file_loop_read(B, MinBytesToRead, {Fd, FileName}=Fd_FName, TF) ->
+ BytesToRead = erlang:max(?CHUNK_SIZE, MinBytesToRead),
+ case file:read(Fd, BytesToRead) of
+ {ok, Bin} when byte_size(B) =:= 0 ->
+ file_loop(Bin, Fd_FName, [], TF);
+ {ok, Bin} ->
+ case B of
+ <<Size:4/unit:8, Tl/bytes>>
+ when byte_size(Bin) + byte_size(Tl) >= Size ->
+ {B1, B2} = split_binary(Bin, Size - byte_size(Tl)),
+ Foo = fun([T], Fun) -> [T | Fun] end,
+ %% TF should be applied exactly once.
+ case
+ file_loop(list_to_binary([B, B1]), Fd_FName, [], Foo)
+ of
+ [T | Fun] ->
+ true = is_function(Fun),
+ file_loop(B2, Fd_FName, [T], TF);
+ Error ->
+ Error
+ end;
+ _ ->
+ file_loop(list_to_binary([B, Bin]), Fd_FName, [], TF)
+ end;
+ eof when byte_size(B) =:= 0 ->
+ TF([], foo);
+ eof ->
+ error({bad_object, FileName});
+ Error ->
+ file_error(FileName, Error)
+ end.
+
+sort_cursor_input(H, NoObjects) ->
+ fun(close) ->
+ ok;
+ (read) ->
+ sort_cursor_input_read(H, NoObjects)
+ end.
+
+sort_cursor_list_output(TmpDir, Z, Unique) ->
+ fun(close) ->
+ {terms, []};
+ ({value, NoObjects}) ->
+ fun(BTerms) when Unique; length(BTerms) =:= NoObjects ->
+ fun(close) ->
+ {terms, BTerms};
+ (BTerms1) ->
+ sort_cursor_file(BTerms ++ BTerms1, TmpDir, Z)
+ end;
+ (BTerms) ->
+ sort_cursor_file(BTerms, TmpDir, Z)
+ end
+ end.
+
+sort_cursor_file(BTerms, TmpDir, Z) ->
+ FName = tmp_filename(TmpDir),
+ case file:open(FName, [write, raw, binary | Z]) of
+ {ok, Fd} ->
+ WFun = write_terms(FName, Fd),
+ WFun(BTerms);
+ Error ->
+ throw_file_error(FName, Error)
+ end.
+
+sort_options_global_tmp(S, "") ->
+ S;
+sort_options_global_tmp(S, TmpDir) ->
+ [{tmpdir,TmpDir} | lists:keydelete(tmpdir, 1, S)].
+
+tmp_filename(TmpDirOpt) ->
+ U = "_",
+ Node = node(),
+ Pid = os:getpid(),
+ {MSecs,Secs,MySecs} = erlang:now(),
+ F = lists:concat([?MODULE,U,Node,U,Pid,U,MSecs,U,Secs,U,MySecs]),
+ TmpDir = case TmpDirOpt of
+ "" ->
+ {ok, CurDir} = file:get_cwd(),
+ CurDir;
+ TDir ->
+ TDir
+ end,
+ filename:join(filename:absname(TmpDir), F).
+
+write_terms(FileName, Fd) ->
+ fun(close) ->
+ _ = file:close(Fd),
+ {file, FileName};
+ (BTerms) ->
+ case file:write(Fd, size_bin(BTerms, [])) of
+ ok ->
+ write_terms(FileName, Fd);
+ Error ->
+ _ = file:close(Fd),
+ throw_file_error(FileName, Error)
+ end
+ end.
+
+size_bin([], L) ->
+ L;
+size_bin([BinTerm | BinTerms], L) ->
+ size_bin(BinTerms, [L, <<(byte_size(BinTerm)):4/unit:8>> | BinTerm]).
+
+sort_cursor_input_read([], NoObjects) ->
+ {end_of_input, NoObjects};
+sort_cursor_input_read([Object | Cont], NoObjects) ->
+ {[term_to_binary(Object)], sort_cursor_input(Cont, NoObjects + 1)};
+sort_cursor_input_read(F, NoObjects) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ sort_cursor_input_read(Objects, NoObjects);
+ Term ->
+ throw_error(Term)
+ end.
+
+unique_cache(L, Post, LocalPost, Optz) when is_list(L) ->
+ case Optz#optz.unique of
+ true ->
+ {unique_sort_list(L), Post, LocalPost};
+ false ->
+ %% If Optz#optz.cache then an ETS table could be used.
+ {L, Post, LocalPost}
+ end;
+unique_cache(H, Post, LocalPost, #optz{unique = false, cache = false}) ->
+ {H, Post, LocalPost};
+unique_cache(H, Post, LocalPost, #optz{unique = true, cache = false}) ->
+ E = ets:new(qlc, [set, private]),
+ {fun() -> no_dups(H, E) end, [del_table(E) | Post], LocalPost};
+unique_cache(H, Post, LocalPost, #optz{unique = false, cache = true}) ->
+ E = ets:new(qlc, [set, private]),
+ {L, P} = unique_cache_post(E),
+ {fun() -> cache(H, E, LocalPost) end, [P | Post], [L]};
+unique_cache(H, Post, LocalPost, #optz{unique = true, cache = true}) ->
+ UT = ets:new(qlc, [bag, private]),
+ MT = ets:new(qlc, [set, private]),
+ {L1, P1} = unique_cache_post(UT),
+ {L2, P2} = unique_cache_post(MT),
+ {fun() -> ucache(H, UT, MT, LocalPost) end, [P1, P2 | Post], [L1, L2]};
+unique_cache(H, Post, LocalPost, #optz{unique = false, cache = list}=Optz) ->
+ Ref = make_ref(),
+ F = del_lcache(Ref),
+ #qlc_opt{tmpdir = TmpDir, max_list = MaxList, tmpdir_usage = TmpUsage} =
+ Optz#optz.opt,
+ {fun() -> lcache(H, Ref, LocalPost, TmpDir, MaxList, TmpUsage) end,
+ [F | Post], [F]};
+unique_cache(H, Post0, LocalPost0, #optz{unique = true, cache = list}=Optz) ->
+ #qlc_opt{tmpdir = TmpDir, max_list = MaxList, tmpdir_usage = TmpUsage} =
+ Optz#optz.opt,
+ Size = if
+ MaxList >= 1 bsl 31 -> (1 bsl 31) - 1;
+ MaxList =:= 0 -> 1;
+ true -> MaxList
+ end,
+ SortOptions = [{size, Size}, {tmpdir, TmpDir}],
+ USortOptions = [{unique, true} | SortOptions],
+ TmpUsageM = {TmpUsage, caching},
+ LF1 = fun(Objs) -> lists:ukeysort(1, Objs) end,
+ FF1 = fun(Objs) ->
+ file_sort_handle(Objs, {keysort, 1}, USortOptions,
+ TmpDir, [], Post0, LocalPost0)
+ end,
+ {UH, Post1, LocalPost1} = sort_handle(tag_objects(H, 1), LF1, FF1,
+ USortOptions, Post0, LocalPost0,
+ TmpUsageM),
+ LF2 = fun(Objs) -> lists:keysort(2, Objs) end,
+ FF2 = fun(Objs) ->
+ file_sort_handle(Objs, {keysort, 2}, SortOptions, TmpDir,
+ [], Post1, LocalPost1)
+ end,
+ {SH, Post, LocalPost} =
+ sort_handle(UH, LF2, FF2, SortOptions, Post1, LocalPost1, TmpUsageM),
+ if
+ is_list(SH) ->
+ %% Remove the tag once and for all.
+ {untag_objects2(SH), Post, LocalPost};
+ true ->
+ %% Every traversal untags the objects...
+ {fun() -> untag_objects(SH) end, Post, LocalPost}
+ end.
+
+unique_cache_post(E) ->
+ {empty_table(E), del_table(E)}.
+
+unique_sort_list(L) ->
+ E = ets:new(qlc, [set, private]),
+ unique_list(L, E).
+
+unique_list([], E) ->
+ true = ets:delete(E),
+ [];
+unique_list([Object | Objects], E) ->
+ case ets:member(E, Object) of
+ false ->
+ true = ets:insert(E, {Object}),
+ [Object | unique_list(Objects, E)];
+ true ->
+ unique_list(Objects, E)
+ end.
+
+sort_list(L, CFun, true, sort, _SortOptions, _Post) when is_function(CFun) ->
+ lists:usort(CFun, L);
+sort_list(L, CFun, false, sort, _SortOptions, _Post) when is_function(CFun) ->
+ lists:sort(CFun, L);
+sort_list(L, ascending, true, sort, _SortOptions, _Post) ->
+ lists:usort(L);
+sort_list(L, descending, true, sort, _SortOptions, _Post) ->
+ lists:reverse(lists:usort(L));
+sort_list(L, ascending, false, sort, _SortOptions, _Post) ->
+ lists:sort(L);
+sort_list(L, descending, false, sort, _SortOptions, _Post) ->
+ lists:reverse(lists:sort(L));
+sort_list(L, Order, Unique, {keysort, Kp}, _SortOptions, _Post)
+ when is_integer(Kp), is_atom(Order) ->
+ case {Order, Unique} of
+ {ascending, true} ->
+ lists:ukeysort(Kp, L);
+ {ascending, false} ->
+ lists:keysort(Kp, L);
+ {descending, true} ->
+ lists:reverse(lists:ukeysort(Kp, L));
+ {descending, false} ->
+ lists:reverse(lists:keysort(Kp, L))
+ end;
+sort_list(L, _Order, _Unique, Sort, SortOptions, Post) ->
+ In = fun(_) -> {L, fun(_) -> end_of_input end} end,
+ Out = sort_list_output([]),
+ TSortOptions = [{format,term} | SortOptions],
+ do_sort(In, Out, Sort, TSortOptions, Post).
+
+sort_list_output(L) ->
+ fun(close) ->
+ lists:append(lists:reverse(L));
+ (Terms) when is_list(Terms) ->
+ sort_list_output([Terms | L])
+ end.
+
+%% Don't use the file_sorter unless it is known that objects will be
+%% put on a temporary file (optimization).
+sort_handle(H, ListFun, FileFun, SortOptions, Post, LocalPost, TmpUsageM) ->
+ Size = case lists:keysearch(size, 1, SortOptions) of
+ {value, {size, Size0}} -> Size0;
+ false -> default_option(size)
+ end,
+ sort_cache(H, [], Size, {ListFun, FileFun, Post, LocalPost, TmpUsageM}).
+
+sort_cache([], CL, _Sz, {LF, _FF, Post, LocalPost, _TmpUsageM}) ->
+ {LF(lists:reverse(CL)), Post, LocalPost};
+sort_cache(Objs, CL, Sz, C) when Sz < 0 ->
+ sort_cache2(Objs, CL, false, C);
+sort_cache([Object | Cont], CL, Sz0, C) ->
+ Sz = decr_list_size(Sz0, Object),
+ sort_cache(Cont, [Object | CL], Sz, C);
+sort_cache(F, CL, Sz, C) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ sort_cache(Objects, CL, Sz, C);
+ Term ->
+ {_LF, _FF, Post, _LocalPost, _TmpUsageM} = C,
+ post_funs(Post),
+ throw_error(Term)
+ end.
+
+sort_cache2([], CL, _X, {LF, _FF, Post, LocalPost, _TmpUsageM}) ->
+ {LF(lists:reverse(CL)), Post, LocalPost};
+sort_cache2([Object | Cont], CL, _, C) ->
+ sort_cache2(Cont, [Object | CL], true, C);
+sort_cache2(F, CL, false, C) ->
+ %% Find one extra object to be sure that temporary file(s) will be
+ %% used when calling the file_sorter. This works even if
+ %% duplicates are removed.
+ case F() of
+ Objects when is_list(Objects) ->
+ sort_cache2(Objects, CL, true, C);
+ Term ->
+ {_LF, _FF, Post, _LocalPost, _TmpUsageM} = C,
+ post_funs(Post),
+ throw_error(Term)
+ end;
+sort_cache2(_Cont, _CL, true, {_LF,_FF,Post,_LocalPost, {not_allowed,M}}) ->
+ post_funs(Post),
+ throw_reason({tmpdir_usage, M});
+sort_cache2(Cont, CL, true, {_LF, FF, _Post, _LocalPost, {TmpUsage, M}}) ->
+ maybe_error_logger(TmpUsage, M),
+ FF(lists:reverse(CL, Cont)).
+
+file_sort_handle(H, Kp, SortOptions, TmpDir, Compressed, Post, LocalPost) ->
+ In = sort_cursor_input(H, 0),
+ Unique = lists:member(unique, SortOptions)
+ orelse
+ lists:keymember(unique, 1, SortOptions),
+ Out = sort_cursor_list_output(TmpDir, Compressed, Unique),
+ Reply = do_sort(In, Out, Kp, SortOptions, Post),
+ case Reply of
+ {file, FileName} ->
+ {F, Fd} = open_file(FileName, Compressed, Post),
+ P = fun() -> _ = file:close(Fd),
+ _ = file:delete(FileName)
+ end,
+ {F, [P | Post], LocalPost};
+ {terms, BTerms} ->
+ try
+ {[binary_to_term(B) || B <- BTerms], Post, LocalPost}
+ catch Class:Reason ->
+ post_funs(Post),
+ erlang:raise(Class, Reason, erlang:get_stacktrace())
+ end
+ end.
+
+do_sort(In, Out, Sort, SortOptions, Post) ->
+ try
+ case do_sort(In, Out, Sort, SortOptions) of
+ {error, Reason} -> throw_reason(Reason);
+ Reply -> Reply
+ end
+ catch Class:Term ->
+ post_funs(Post),
+ erlang:raise(Class, Term, erlang:get_stacktrace())
+ end.
+
+do_sort(In, Out, sort, SortOptions) ->
+ file_sorter:sort(In, Out, SortOptions);
+do_sort(In, Out, {keysort, KeyPos}, SortOptions) ->
+ file_sorter:keysort(KeyPos, In, Out, SortOptions).
+
+del_table(Ets) ->
+ fun() -> true = ets:delete(Ets) end.
+
+empty_table(Ets) ->
+ fun() -> true = ets:delete_all_objects(Ets) end.
+
+append_loop([[_ | _]=L], _N) ->
+ L;
+append_loop([F], _N) ->
+ F();
+append_loop([L | Hs], N) ->
+ append_loop(L, N, Hs).
+
+append_loop([], N, Hs) ->
+ append_loop(Hs, N);
+append_loop([Object | Cont], N, Hs) ->
+ [Object | append_loop(Cont, N + 1, Hs)];
+append_loop(F, 0, Hs) ->
+ case F() of
+ [] ->
+ append_loop(Hs, 0);
+ [Object | Cont] ->
+ [Object | append_loop(Cont, 1, Hs)];
+ Term ->
+ Term
+ end;
+append_loop(F, _N, Hs) -> % when _N > 0
+ fun() -> append_loop(F, 0, Hs) end.
+
+no_dups([]=Cont, UTab) ->
+ true = ets:delete_all_objects(UTab),
+ Cont;
+no_dups([Object | Cont], UTab) ->
+ case ets:member(UTab, Object) of
+ false ->
+ true = ets:insert(UTab, {Object}),
+ %% A fun is created here, even if Cont is a list; objects
+ %% will not be copied to the ETS table unless requested.
+ [Object | fun() -> no_dups(Cont, UTab) end];
+ true ->
+ no_dups(Cont, UTab)
+ end;
+no_dups(F, UTab) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ no_dups(Objects, UTab);
+ Term ->
+ Term
+ end.
+
+%% When all objects have been returned from a cached QLC, the
+%% generators of the expression will never be called again, and so the
+%% tables used by the generators (LocalPost) can be emptied.
+
+cache(H, MTab, LocalPost) ->
+ case ets:member(MTab, 0) of
+ false ->
+ true = ets:insert(MTab, {0}),
+ cache(H, MTab, 1, LocalPost);
+ true ->
+ cache_recall(MTab, 1)
+ end.
+
+cache([]=Cont, _MTab, _SeqNo, LocalPost) ->
+ local_post(LocalPost),
+ Cont;
+cache([Object | Cont], MTab, SeqNo, LocalPost) ->
+ true = ets:insert(MTab, {SeqNo, Object}),
+ %% A fun is created here, even if Cont is a list; objects
+ %% will not be copied to the ETS table unless requested.
+ [Object | fun() -> cache(Cont, MTab, SeqNo + 1, LocalPost) end];
+cache(F, MTab, SeqNo, LocalPost) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ cache(Objects, MTab, SeqNo, LocalPost);
+ Term ->
+ Term
+ end.
+
+cache_recall(MTab, SeqNo) ->
+ case ets:lookup(MTab, SeqNo) of
+ []=Cont ->
+ Cont;
+ [{SeqNo, Object}] ->
+ [Object | fun() -> cache_recall(MTab, SeqNo + 1) end]
+ end.
+
+ucache(H, UTab, MTab, LocalPost) ->
+ case ets:member(MTab, 0) of
+ false ->
+ true = ets:insert(MTab, {0}),
+ ucache(H, UTab, MTab, 1, LocalPost);
+ true ->
+ ucache_recall(UTab, MTab, 1)
+ end.
+
+ucache([]=Cont, _UTab, _MTab, _SeqNo, LocalPost) ->
+ local_post(LocalPost),
+ Cont;
+ucache([Object | Cont], UTab, MTab, SeqNo, LocalPost) ->
+ %% Always using 28 bits hash value...
+ Hash = erlang:phash2(Object),
+ case ets:lookup(UTab, Hash) of
+ [] ->
+ ucache3(Object, Cont, Hash, UTab, MTab, SeqNo, LocalPost);
+ HashSeqObjects ->
+ case lists:keymember(Object, 3, HashSeqObjects) of
+ true ->
+ ucache(Cont, UTab, MTab, SeqNo, LocalPost);
+ false ->
+ ucache3(Object, Cont, Hash, UTab, MTab, SeqNo, LocalPost)
+ end
+ end;
+ucache(F, UTab, MTab, SeqNo, LocalPost) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ ucache(Objects, UTab, MTab, SeqNo, LocalPost);
+ Term ->
+ Term
+ end.
+
+ucache3(Object, Cont, Hash, UTab, MTab, SeqNo, LocalPost) ->
+ true = ets:insert(UTab, {Hash, SeqNo, Object}),
+ true = ets:insert(MTab, {SeqNo, Hash}),
+ %% A fun is created here, even if Cont is a list; objects
+ %% will not be copied to the ETS table unless requested.
+ [Object | fun() -> ucache(Cont, UTab, MTab, SeqNo+1, LocalPost) end].
+
+ucache_recall(UTab, MTab, SeqNo) ->
+ case ets:lookup(MTab, SeqNo) of
+ []=Cont ->
+ Cont;
+ [{SeqNo, Hash}] ->
+ Object = case ets:lookup(UTab, Hash) of
+ [{Hash, SeqNo, Object0}] -> Object0;
+ HashSeqObjects ->
+ {value, {Hash, SeqNo, Object0}} =
+ lists:keysearch(SeqNo, 2, HashSeqObjects),
+ Object0
+ end,
+ [Object | fun() -> ucache_recall(UTab, MTab, SeqNo + 1) end]
+ end.
+
+-define(LCACHE_FILE(Ref), {Ref, '$_qlc_cache_tmpfiles_'}).
+
+lcache(H, Ref, LocalPost, TmpDir, MaxList, TmpUsage) ->
+ Key = ?LCACHE_FILE(Ref),
+ case get(Key) of
+ undefined ->
+ lcache1(H, {Key, LocalPost, TmpDir, MaxList, TmpUsage},
+ MaxList, []);
+ {file, _Fd, _TmpFile, F} ->
+ F();
+ L when is_list(L) ->
+ L
+ end.
+
+lcache1([]=Cont, {Key, LocalPost, _TmpDir, _MaxList, _TmpUsage}, _Sz, Acc) ->
+ local_post(LocalPost),
+ case get(Key) of
+ undefined ->
+ put(Key, lists:reverse(Acc)),
+ Cont;
+ {file, Fd, TmpFile, _F} ->
+ case lcache_write(Fd, TmpFile, Acc) of
+ ok ->
+ Cont;
+ Error ->
+ Error
+ end
+ end;
+lcache1(H, State, Sz, Acc) when Sz < 0 ->
+ {Key, LocalPost, TmpDir, MaxList, TmpUsage} = State,
+ GetFile =
+ case get(Key) of
+ {file, Fd0, TmpFile, _F} ->
+ {TmpFile, Fd0};
+ undefined when TmpUsage =:= not_allowed ->
+ error({tmpdir_usage, caching});
+ undefined ->
+ maybe_error_logger(TmpUsage, caching),
+ FName = tmp_filename(TmpDir),
+ {F, Fd0} = open_file(FName, [write], LocalPost),
+ put(Key, {file, Fd0, FName, F}),
+ {FName, Fd0}
+ end,
+ case GetFile of
+ {FileName, Fd} ->
+ case lcache_write(Fd, FileName, Acc) of
+ ok ->
+ lcache1(H, State, MaxList, []);
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end;
+lcache1([Object | Cont], State, Sz0, Acc) ->
+ Sz = decr_list_size(Sz0, Object),
+ [Object | lcache2(Cont, State, Sz, [Object | Acc])];
+lcache1(F, State, Sz, Acc) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ lcache1(Objects, State, Sz, Acc);
+ Term ->
+ Term
+ end.
+
+lcache2([Object | Cont], State, Sz0, Acc) when Sz0 >= 0 ->
+ Sz = decr_list_size(Sz0, Object),
+ [Object | lcache2(Cont, State, Sz, [Object | Acc])];
+lcache2(Cont, State, Sz, Acc) ->
+ fun() -> lcache1(Cont, State, Sz, Acc) end.
+
+lcache_write(Fd, FileName, L) ->
+ write_binary_terms(t2b(L, []), Fd, FileName).
+
+t2b([], Bs) ->
+ Bs;
+t2b([T | Ts], Bs) ->
+ t2b(Ts, [term_to_binary(T) | Bs]).
+
+del_lcache(Ref) ->
+ fun() ->
+ Key = ?LCACHE_FILE(Ref),
+ case get(Key) of
+ undefined ->
+ ok;
+ {file, Fd, TmpFile, _F} ->
+ _ = file:close(Fd),
+ _ = file:delete(TmpFile),
+ erase(Key);
+ _L ->
+ erase(Key)
+ end
+ end.
+
+tag_objects([Object | Cont], T) ->
+ [{Object, T} | tag_objects2(Cont, T + 1)];
+tag_objects([]=Cont, _T) ->
+ Cont;
+tag_objects(F, T) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ tag_objects(Objects, T);
+ Term ->
+ Term
+ end.
+
+tag_objects2([Object | Cont], T) ->
+ [{Object, T} | tag_objects2(Cont, T + 1)];
+tag_objects2(Objects, T) ->
+ fun() -> tag_objects(Objects, T) end.
+
+untag_objects([]=Objs) ->
+ Objs;
+untag_objects([{Object, _N} | Cont]) ->
+ [Object | untag_objects2(Cont)];
+untag_objects(F) ->
+ case F() of
+ Objects when is_list(Objects) ->
+ untag_objects(Objects);
+ Term -> % Cannot happen
+ Term
+ end.
+
+untag_objects2([{Object, _N} | Cont]) ->
+ [Object | untag_objects2(Cont)];
+untag_objects2([]=Cont) ->
+ Cont;
+untag_objects2(Objects) ->
+ fun() -> untag_objects(Objects) end.
+
+%%% Merge join.
+%%% Temporary files are used when many objects have the same key.
+
+-define(JWRAP(E1, E2), [E1 | E2]).
+
+-record(m, {id, tmpdir, max_list, tmp_usage}).
+
+merge_join([]=Cont, _C1, _T2, _C2, _Opt) ->
+ Cont;
+merge_join([E1 | L1], C1, L2, C2, Opt) ->
+ #qlc_opt{tmpdir = TmpDir, max_list = MaxList,
+ tmpdir_usage = TmpUsage} = Opt,
+ M = #m{id = merge_join_id(), tmpdir = TmpDir, max_list = MaxList,
+ tmp_usage = TmpUsage},
+ merge_join2(E1, element(C1, E1), L1, C1, L2, C2, M);
+merge_join(F1, C1, L2, C2, Opt) ->
+ case F1() of
+ L1 when is_list(L1) ->
+ merge_join(L1, C1, L2, C2, Opt);
+ T1 ->
+ T1
+ end.
+
+merge_join1(_E2, _K2, []=Cont, _C1, _L2, _C2, M) ->
+ end_merge_join(Cont, M);
+merge_join1(E2, K2, [E1 | L1], C1, L2, C2, M) ->
+ K1 = element(C1, E1),
+ if
+ K1 == K2 ->
+ same_keys2(E1, K1, L1, C1, L2, C2, E2, M);
+ K1 > K2 ->
+ merge_join2(E1, K1, L1, C1, L2, C2, M);
+ true -> % K1 < K2
+ merge_join1(E2, K2, L1, C1, L2, C2, M)
+ end;
+merge_join1(E2, K2, F1, C1, L2, C2, M) ->
+ case F1() of
+ L1 when is_list(L1) ->
+ merge_join1(E2, K2, L1, C1, L2, C2, M);
+ T1 ->
+ T1
+ end.
+
+merge_join2(_E1, _K1, _L1, _C1, []=Cont, _C2, M) ->
+ end_merge_join(Cont, M);
+merge_join2(E1, K1, L1, C1, [E2 | L2], C2, M) ->
+ K2 = element(C2, E2),
+ if
+ K1 == K2 ->
+ same_keys2(E1, K1, L1, C1, L2, C2, E2, M);
+ K1 > K2 ->
+ merge_join2(E1, K1, L1, C1, L2, C2, M);
+ true -> % K1 < K2
+ merge_join1(E2, K2, L1, C1, L2, C2, M)
+ end;
+merge_join2(E1, K1, L1, C1, F2, C2, M) ->
+ case F2() of
+ L2 when is_list(L2) ->
+ merge_join2(E1, K1, L1, C1, L2, C2, M);
+ T2 ->
+ T2
+ end.
+
+%% element(C2, E2_0) == K1
+same_keys2(E1, K1, L1, C1, [], _C2, E2_0, M) ->
+ Cont = fun(_L1b) -> end_merge_join([], M) end,
+ loop_same_keys(E1, K1, L1, C1, [E2_0], Cont, M);
+same_keys2(E1, K1, L1, C1, [E2 | L2]=L2_0, C2, E2_0, M) ->
+ K2 = element(C2, E2),
+ if
+ K1 == K2 ->
+ same_keys1(E1, K1, L1, C1, E2, C2, E2_0, L2, M);
+ K1 < K2 ->
+ [?JWRAP(E1, E2_0) |
+ fun() -> same_loop1(L1, K1, C1, E2_0, L2_0, C2, M) end]
+ end;
+same_keys2(E1, K1, L1, C1, F2, C2, E2_0, M) ->
+ case F2() of
+ L2 when is_list(L2) ->
+ same_keys2(E1, K1, L1, C1, L2, C2, E2_0, M);
+ T2 ->
+ Cont = fun(_L1b) -> T2 end,
+ loop_same_keys(E1, K1, L1, C1, [E2_0], Cont, M)
+ end.
+
+same_loop1([], _K1_0, _C1, _E2_0, _L2, _C2, M) ->
+ end_merge_join([], M);
+same_loop1([E1 | L1], K1_0, C1, E2_0, L2, C2, M) ->
+ K1 = element(C1, E1),
+ if
+ K1 == K1_0 ->
+ [?JWRAP(E1, E2_0) |
+ fun() -> same_loop1(L1, K1_0, C1, E2_0, L2, C2, M) end];
+ K1_0 < K1 ->
+ merge_join2(E1, K1, L1, C1, L2, C2, M)
+ end;
+same_loop1(F1, K1_0, C1, E2_0, L2, C2, M) ->
+ case F1() of
+ L1 when is_list(L1) ->
+ same_loop1(L1, K1_0, C1, E2_0, L2, C2, M);
+ T1 ->
+ T1
+ end.
+
+%% element(C2, E2_0) == K1, element(C2, E2) == K1_0
+same_keys1(E1_0, K1_0, []=L1, C1, E2, C2, E2_0, L2, M) ->
+ [?JWRAP(E1_0, E2_0), ?JWRAP(E1_0, E2) |
+ fun() -> same_keys(K1_0, E1_0, L1, C1, L2, C2, M) end];
+same_keys1(E1_0, K1_0, [E1 | _]=L1, C1, E2, C2, E2_0, L2, M) ->
+ K1 = element(C1, E1),
+ if
+ K1_0 == K1 ->
+ E2s = [E2, E2_0],
+ Sz0 = decr_list_size(M#m.max_list, E2s),
+ same_keys_cache(E1_0, K1_0, L1, C1, L2, C2, E2s, Sz0, M);
+ K1_0 < K1 ->
+ [?JWRAP(E1_0, E2_0), ?JWRAP(E1_0, E2) |
+ fun() -> same_keys(K1_0, E1_0, L1, C1, L2, C2, M) end]
+ end;
+same_keys1(E1_0, K1_0, F1, C1, E2, C2, E2_0, L2, M) ->
+ case F1() of
+ L1 when is_list(L1) ->
+ same_keys1(E1_0, K1_0, L1, C1, E2, C2, E2_0, L2, M);
+ T1 ->
+ Cont = fun() -> T1 end,
+ loop_same(E1_0, [E2, E2_0], Cont)
+ end.
+
+%% There is no such element E in L1 such that element(C1, E) == K1.
+same_keys(_K1, _E1, _L1, _C1, []=Cont, _C2, M) ->
+ end_merge_join(Cont, M);
+same_keys(K1, E1, L1, C1, [E2 | L2], C2, M) ->
+ K2 = element(C2, E2),
+ if
+ K1 == K2 ->
+ [?JWRAP(E1, E2) |
+ fun() -> same_keys(K1, E1, L1, C1, L2, C2, M) end];
+ K1 < K2 ->
+ merge_join1(E2, K2, L1, C1, L2, C2, M)
+ end;
+same_keys(K1, E1, L1, C1, F2, C2, M) ->
+ case F2() of
+ L2 when is_list(L2) ->
+ same_keys(K1, E1, L1, C1, L2, C2, M);
+ T2 ->
+ T2
+ end.
+
+%% There are at least two elements in [E1 | L1] that are to be combined
+%% with the elements in E2s (length(E2s) > 1). This loop covers the case
+%% when all elements in E2 with key K1 can be kept in RAM.
+same_keys_cache(E1, K1, L1, C1, [], _C2, E2s, _Sz, M) ->
+ Cont = fun(_L1b) -> end_merge_join([], M) end,
+ loop_same_keys(E1, K1, L1, C1, E2s, Cont, M);
+same_keys_cache(E1, K1, L1, C1, L2, C2, E2s, Sz0, M) when Sz0 < 0 ->
+ case init_merge_join(M) of
+ ok ->
+ Sz = M#m.max_list,
+ C = fun() ->
+ same_keys_file(E1, K1, L1, C1, L2, C2, [], Sz, M)
+ end,
+ write_same_keys(E1, E2s, M, C);
+ Error ->
+ Error
+ end;
+same_keys_cache(E1, K1, L1, C1, [E2 | L2], C2, E2s, Sz0, M) ->
+ K2 = element(C2, E2),
+ if
+ K1 == K2 ->
+ Sz = decr_list_size(Sz0, E2),
+ same_keys_cache(E1, K1, L1, C1, L2, C2, [E2 | E2s], Sz, M);
+ K1 < K2 ->
+ Cont = fun(L1b) -> merge_join1(E2, K2, L1b, C1, L2, C2, M) end,
+ loop_same_keys(E1, K1, L1, C1, E2s, Cont, M)
+ end;
+same_keys_cache(E1, K1, L1, C1, F2, C2, E2s, Sz, M) ->
+ case F2() of
+ L2 when is_list(L2) ->
+ same_keys_cache(E1, K1, L1, C1, L2, C2, E2s, Sz, M);
+ T2 ->
+ Cont = fun(_L1b) -> T2 end,
+ loop_same_keys(E1, K1, L1, C1, E2s, Cont, M)
+ end.
+
+%% E2s holds all elements E2 in L2 such that element(E2, C2) == K1.
+loop_same_keys(E1, _K1, [], _C1, E2s, _Cont, M) ->
+ end_merge_join(loop_same(E1, E2s, []), M);
+loop_same_keys(E1, K1, L1, C1, E2s, Cont, M) ->
+ loop_same(E1, E2s, fun() -> loop_keys(K1, L1, C1, E2s, Cont, M) end).
+
+loop_same(_E1, [], L) ->
+ L;
+loop_same(E1, [E2 | E2s], L) ->
+ loop_same(E1, E2s, [?JWRAP(E1, E2) | L]).
+
+loop_keys(K, [E1 | L1]=L1_0, C1, E2s, Cont, M) ->
+ K1 = element(C1, E1),
+ if
+ K1 == K ->
+ loop_same_keys(E1, K1, L1, C1, E2s, Cont, M);
+ K1 > K ->
+ Cont(L1_0)
+ end;
+loop_keys(_K, []=L1, _C1, _Es2, Cont, _M) ->
+ Cont(L1);
+loop_keys(K, F1, C1, E2s, Cont, M) ->
+ case F1() of
+ L1 when is_list(L1) ->
+ loop_keys(K, L1, C1, E2s, Cont, M);
+ T1 ->
+ T1
+ end.
+
+%% This is for the case when a temporary file has to be used.
+same_keys_file(E1, K1, L1, C1, [], _C2, E2s, _Sz, M) ->
+ Cont = fun(_L1b) -> end_merge_join([], M) end,
+ same_keys_file_write(E1, K1, L1, C1, E2s, M, Cont);
+same_keys_file(E1, K1, L1, C1, L2, C2, E2s, Sz0, M) when Sz0 < 0 ->
+ Sz = M#m.max_list,
+ C = fun() -> same_keys_file(E1, K1, L1, C1, L2, C2, [], Sz, M) end,
+ write_same_keys(E1, E2s, M, C);
+same_keys_file(E1, K1, L1, C1, [E2 | L2], C2, E2s, Sz0, M) ->
+ K2 = element(C2, E2),
+ if
+ K1 == K2 ->
+ Sz = decr_list_size(Sz0, E2),
+ same_keys_file(E1, K1, L1, C1, L2, C2, [E2 | E2s], Sz, M);
+ K1 < K2 ->
+ Cont = fun(L1b) ->
+ %% The temporary file could be truncated here.
+ merge_join1(E2, K2, L1b, C1, L2, C2, M)
+ end,
+ same_keys_file_write(E1, K1, L1, C1, E2s, M, Cont)
+ end;
+same_keys_file(E1, K1, L1, C1, F2, C2, E2s, Sz, M) ->
+ case F2() of
+ L2 when is_list(L2) ->
+ same_keys_file(E1, K1, L1, C1, L2, C2, E2s, Sz, M);
+ T2 ->
+ Cont = fun(_L1b) -> T2 end,
+ same_keys_file_write(E1, K1, L1, C1, E2s, M, Cont)
+ end.
+
+same_keys_file_write(E1, K1, L1, C1, E2s, M, Cont) ->
+ C = fun() -> loop_keys_file(K1, L1, C1, Cont, M) end,
+ write_same_keys(E1, E2s, M, C).
+
+write_same_keys(_E1, [], _M, Cont) ->
+ Cont();
+write_same_keys(E1, Es2, M, Cont) ->
+ write_same_keys(E1, Es2, M, [], Cont).
+
+%% Avoids one (the first) traversal of the temporary file.
+write_same_keys(_E1, [], M, E2s, Objs) ->
+ case write_merge_join(M, E2s) of
+ ok -> Objs;
+ Error -> Error
+ end;
+write_same_keys(E1, [E2 | E2s0], M, E2s, Objs) ->
+ BE2 = term_to_binary(E2),
+ write_same_keys(E1, E2s0, M, [BE2 | E2s], [?JWRAP(E1, E2) | Objs]).
+
+loop_keys_file(K, [E1 | L1]=L1_0, C1, Cont, M) ->
+ K1 = element(C1, E1),
+ if
+ K1 == K ->
+ C = fun() -> loop_keys_file(K1, L1, C1, Cont, M) end,
+ read_merge_join(M, E1, C);
+ K1 > K ->
+ Cont(L1_0)
+ end;
+loop_keys_file(_K, []=L1, _C1, Cont, _M) ->
+ Cont(L1);
+loop_keys_file(K, F1, C1, Cont, M) ->
+ case F1() of
+ L1 when is_list(L1) ->
+ loop_keys_file(K, L1, C1, Cont, M);
+ T1 ->
+ T1
+ end.
+
+end_merge_join(Reply, M) ->
+ end_merge_join(M),
+ Reply.
+
+%% Normally post_funs() cleans up temporary files by calling funs in
+%% Post. It seems impossible to do that with the temporary file(s)
+%% used when many objects have the same key--such a file is created
+%% after the setup when Post is prepared. There seems to be no real
+%% alternative to using the process dictionary, at least as things
+%% have been implemented so far. Probably all of Post could have been
+%% put in the process dictionary...
+
+-define(MERGE_JOIN_FILE, '$_qlc_merge_join_tmpfiles_').
+
+init_merge_join(#m{id = MergeId, tmpdir = TmpDir, tmp_usage = TmpUsage}) ->
+ case tmp_merge_file(MergeId) of
+ {Fd, FileName} ->
+ case file:position(Fd, bof) of
+ {ok, 0} ->
+ case file:truncate(Fd) of
+ ok ->
+ ok;
+ Error ->
+ file_error(FileName, Error)
+ end;
+ Error ->
+ file_error(FileName, Error)
+ end;
+ none when TmpUsage =:= not_allowed ->
+ error({tmpdir_usage, joining});
+ none ->
+ maybe_error_logger(TmpUsage, joining),
+ FName = tmp_filename(TmpDir),
+ case file:open(FName, [raw, binary, read, write]) of
+ {ok, Fd} ->
+ TmpFiles = get(?MERGE_JOIN_FILE),
+ put(?MERGE_JOIN_FILE, [{MergeId, Fd, FName} | TmpFiles]),
+ ok;
+ Error ->
+ file_error(FName, Error)
+ end
+ end.
+
+write_merge_join(#m{id = MergeId}, BTerms) ->
+ {Fd, FileName} = tmp_merge_file(MergeId),
+ write_binary_terms(BTerms, Fd, FileName).
+
+read_merge_join(#m{id = MergeId}, E1, Cont) ->
+ {Fd, FileName} = tmp_merge_file(MergeId),
+ case file:position(Fd, bof) of
+ {ok, 0} ->
+ Fun = fun([], _) ->
+ Cont();
+ (Ts, C) when is_list(Ts) ->
+ join_read_terms(E1, Ts, C)
+ end,
+ file_loop_read(<<>>, ?CHUNK_SIZE, {Fd, FileName}, Fun);
+ Error ->
+ file_error(FileName, Error)
+ end.
+
+join_read_terms(_E1, [], Objs) ->
+ Objs;
+join_read_terms(E1, [E2 | E2s], Objs) ->
+ join_read_terms(E1, E2s, [?JWRAP(E1, E2) | Objs]).
+
+end_merge_join(#m{id = MergeId}) ->
+ case tmp_merge_file(MergeId) of
+ none ->
+ ok;
+ {Fd, FileName} ->
+ _ = file:close(Fd),
+ _ = file:delete(FileName),
+ put(?MERGE_JOIN_FILE,
+ lists:keydelete(MergeId, 1, get(?MERGE_JOIN_FILE)))
+ end.
+
+end_all_merge_joins() ->
+ lists:foreach(
+ fun(Id) -> end_merge_join(#m{id = Id}) end,
+ [Id || {Id, _Fd, _FileName} <- lists:flatten([get(?MERGE_JOIN_FILE)])]),
+ erase(?MERGE_JOIN_FILE).
+
+merge_join_id() ->
+ case get(?MERGE_JOIN_FILE) of
+ undefined ->
+ put(?MERGE_JOIN_FILE, []);
+ _ ->
+ ok
+ end,
+ make_ref().
+
+tmp_merge_file(MergeId) ->
+ TmpFiles = get(?MERGE_JOIN_FILE),
+ case lists:keysearch(MergeId, 1, TmpFiles) of
+ {value, {MergeId, Fd, FileName}} ->
+ {Fd, FileName};
+ false ->
+ none
+ end.
+
+decr_list_size(Sz0, E) when is_integer(Sz0) ->
+ Sz0 - erlang:external_size(E).
+
+%%% End of merge join.
+
+lookup_join([E1 | L1], C1, LuF, C2, Rev) ->
+ K1 = element(C1, E1),
+ case LuF(C2, [K1]) of
+ [] ->
+ lookup_join(L1, C1, LuF, C2, Rev);
+ [E2] when Rev ->
+ [?JWRAP(E2, E1) | fun() -> lookup_join(L1, C1, LuF, C2, Rev) end];
+ [E2] ->
+ [?JWRAP(E1, E2) | fun() -> lookup_join(L1, C1, LuF, C2, Rev) end];
+ E2s when is_list(E2s), Rev ->
+ [?JWRAP(E2, E1) || E2 <- E2s] ++
+ fun() -> lookup_join(L1, C1, LuF, C2, Rev) end;
+ E2s when is_list(E2s) ->
+ [?JWRAP(E1, E2) || E2 <- E2s] ++
+ fun() -> lookup_join(L1, C1, LuF, C2, Rev) end;
+ Term ->
+ Term
+ end;
+lookup_join([]=Cont, _C1, _LuF, _C2, _Rev) ->
+ Cont;
+lookup_join(F1, C1, LuF, C2, Rev) ->
+ case F1() of
+ L1 when is_list(L1) ->
+ lookup_join(L1, C1, LuF, C2, Rev);
+ T1 ->
+ T1
+ end.
+
+maybe_error_logger(allowed, _) ->
+ ok;
+maybe_error_logger(Name, Why) ->
+ [_, _, {?MODULE,maybe_error_logger,_} | Stacktrace] = expand_stacktrace(),
+ Trimmer = fun(M, _F, _A) -> M =:= erl_eval end,
+ Formater = fun(Term, I) -> io_lib:print(Term, I, 80, -1) end,
+ X = lib:format_stacktrace(1, Stacktrace, Trimmer, Formater),
+ error_logger:Name("qlc: temporary file was needed for ~w\n~s\n",
+ [Why, lists:flatten(X)]).
+
+expand_stacktrace() ->
+ D = erlang:system_flag(backtrace_depth, 8),
+ try
+ %% Compensate for a bug in erlang:system_flag/2:
+ expand_stacktrace(erlang:max(1, D))
+ after
+ erlang:system_flag(backtrace_depth, D)
+ end.
+
+expand_stacktrace(D) ->
+ _ = erlang:system_flag(backtrace_depth, D),
+ {'EXIT', {foo, Stacktrace}} = (catch erlang:error(foo)),
+ L = lists:takewhile(fun({M,_,_}) -> M =/= ?MODULE
+ end, lists:reverse(Stacktrace)),
+ if
+ length(L) < 3 andalso length(Stacktrace) =:= D ->
+ expand_stacktrace(D + 5);
+ true ->
+ Stacktrace
+ end.
+
+write_binary_terms(BTerms, Fd, FileName) ->
+ case file:write(Fd, size_bin(BTerms, [])) of
+ ok ->
+ ok;
+ Error ->
+ file_error(FileName, Error)
+ end.
+
+post_funs(L) ->
+ end_all_merge_joins(),
+ local_post(L).
+
+local_post(L) ->
+ lists:foreach(fun(undefined) -> ok;
+ (F) -> catch (F)()
+ end, L).
+
+call(undefined, _Arg, Default, _Post) ->
+ Default;
+call(Fun, Arg, _Default, Post) ->
+ try
+ Fun(Arg)
+ catch Class:Reason ->
+ post_funs(Post),
+ erlang:raise(Class, Reason, erlang:get_stacktrace())
+ end.
+
+grd(undefined, _Arg) ->
+ false;
+grd(Fun, Arg) ->
+ case Fun(Arg) of
+ true ->
+ true;
+ _ ->
+ false
+ end.
+
+family(L) ->
+ sofs:to_external(sofs:relation_to_family(sofs:relation(L))).
+
+family_union(L) ->
+ R = sofs:relation(L,[{atom,[atom]}]),
+ sofs:to_external(sofs:family_union(sofs:relation_to_family(R))).
+
+file_error(File, {error, Reason}) ->
+ error({file_error, File, Reason}).
+
+-spec throw_file_error(string(), {'error',atom()}) -> no_return().
+
+throw_file_error(File, {error, Reason}) ->
+ throw_reason({file_error, File, Reason}).
+
+-spec throw_reason(term()) -> no_return().
+
+throw_reason(Reason) ->
+ throw_error(error(Reason)).
+
+-spec throw_error(term()) -> no_return().
+
+throw_error(Error) ->
+ throw(Error).
+
+error(Reason) ->
+ {error, ?MODULE, Reason}.
diff --git a/lib/stdlib/src/qlc_pt.erl b/lib/stdlib/src/qlc_pt.erl
new file mode 100644
index 0000000000..2d7874d99f
--- /dev/null
+++ b/lib/stdlib/src/qlc_pt.erl
@@ -0,0 +1,2746 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(qlc_pt).
+
+%%% Purpose: Implements the qlc Parse Transform.
+
+-export([parse_transform/2, transform_from_evaluator/2,
+ transform_expression/2]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-define(APIMOD, qlc).
+-define(Q, q).
+
+%% Also in qlc.erl.
+-define(QLC_Q(L1, L2, L3, L4, LC, Os),
+ {call,L1,{remote,L2,{atom,L3,?APIMOD},{atom,L4,?Q}},[LC | Os]}).
+-define(QLC_QQ(L1, L2, L3, L4, L5, L6, LC, Os), % packages...
+ {call,L1,{remote,L2,{record_field,L3,{atom,L4,''},
+ {atom,L5,?APIMOD}},{atom,L6,?Q}},[LC | Os]}).
+-define(IMP_Q(L1, L2, LC, Os), {call,L,{atom,L2,?Q},[LC | Os]}).
+
+%% Also in qlc.erl.
+-record(qlc_lc, % qlc:q/1,2, a query handle
+ {lc,
+ opt % #qlc_opt
+ }).
+
+-record(state, {imp, maxargs, records, xwarnings = []}).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(DEBUG(S, A), io:format(S, A)).
+-else.
+-define(DEBUG(S, A), ok).
+-endif.
+
+%% erl_eval cannot interpret funs with more than 20 arguments:
+-define(EVAL_MAX_NUM_OF_ARGS, 20).
+%% Currently the compiler can handle at most 255 arguments.
+-define(COMPILE_MAX_NUM_OF_ARGS, 250).
+
+-define(QLC_FILE, qlc_current_file).
+
+%%%
+%%% Exported functions
+%%%
+
+parse_transform(Forms, Options) ->
+ ?DEBUG("qlc Parse Transform~n", []),
+ State = #state{imp = is_qlc_q_imported(Forms),
+ maxargs = ?COMPILE_MAX_NUM_OF_ARGS,
+ records = record_attributes(Forms)},
+ case called_from_type_checker(Options) of
+ true ->
+ %% The returned value should conform to the types, but
+ %% need not evaluate to anything meaningful.
+ L = 0,
+ {tuple,_,Fs0} = abstr(#qlc_lc{}, L),
+ F = fun(_Id, LC, A) ->
+ Init = simple(L, 'V', LC, L),
+ {{tuple,L,set_field(#qlc_lc.lc, Fs0, Init)}, A}
+ end,
+ {Forms1,ok} = qlc_mapfold(F, ok, Forms, State),
+ Forms1;
+ false ->
+ FormsNoShadows = no_shadows(Forms, State),
+ case compile_messages(Forms, FormsNoShadows, Options, State) of
+ {[],[],Warnings} ->
+ {NewForms, State1} = transform(FormsNoShadows, State),
+ ExtraWs = State1#state.xwarnings,
+ {[],WForms} = no_duplicates(NewForms, [], Warnings,
+ ExtraWs, Options),
+ WForms ++ NewForms;
+ {E0,Errors,Warnings} ->
+ {EForms,WForms} = no_duplicates(Forms, E0++Errors,
+ Warnings, [], Options),
+ EForms ++ WForms ++ Forms
+ end
+ end.
+
+transform_from_evaluator(LC, Bindings) ->
+ ?DEBUG("qlc Parse Transform (Evaluator Version)~n", []),
+ transform_expression(LC, Bindings, false).
+
+transform_expression(LC, Bindings) ->
+ transform_expression(LC, Bindings, true).
+
+%%%
+%%% Local functions
+%%%
+
+called_from_type_checker(Options) ->
+ lists:member(type_checker, Options).
+
+transform_expression(LC, Bs0, WithLintErrors) ->
+ L = 1,
+ As = [{var,L,V} || {V,_Val} <- Bs0],
+ Ar = length(As),
+ F = {function,L,bar,Ar,[{clause,L,As,[],[?QLC_Q(L, L, L, L, LC, [])]}]},
+ Forms = [{attribute,L,file,{"foo",L}},
+ {attribute,L,module,foo}, F],
+ State = #state{imp = false,
+ maxargs = ?EVAL_MAX_NUM_OF_ARGS,
+ records = record_attributes(Forms)},
+ Options = [],
+ FormsNoShadows = no_shadows(Forms, State),
+ case compile_messages(Forms, FormsNoShadows, Options, State) of
+ {[],[],_Warnings} ->
+ {NewForms,_State1} = transform(FormsNoShadows, State),
+ {function,L,bar,Ar,[{clause,L,As,[],[NF]}]} =
+ lists:last(NewForms),
+ {ok,NF};
+ {E0,Errors,_Warnings} when WithLintErrors ->
+ {not_ok,mforms(error, E0 ++ Errors)};
+ {E0,Errors0,_Warnings} ->
+ [{error,Reason} | _] = mforms(error, E0++Errors0),
+ {not_ok, {error, ?APIMOD, Reason}}
+ end.
+
+-define(I(I), {integer, L, I}).
+-define(A(A), {atom, L, A}).
+-define(V(V), {var, L, V}).
+-define(ABST_NO_MORE, {nil, L}).
+-define(ABST_MORE(Obj, Cont), {cons, L, Obj, Cont}).
+
+%% Qualifier identifier.
+%% The first one encountered in a QLC has no=1.
+-record(qid, {lcid,no}).
+
+mforms(Tag, L) ->
+ lists:sort([{Tag,M} || {_File,Ms} <- L, M <- Ms]).
+
+%% Avoid duplicated lint warnings and lint errors. Care has been taken
+%% not to introduce unused variables in the transformed code.
+%%
+no_duplicates(Forms, Errors, Warnings0, ExtraWarnings, Options) ->
+ %% Some mistakes such as "{X} =:= {}" are found by strong
+ %% validation as well as by qlc. Prefer the warnings from qlc:
+ Warnings1 = mforms(Warnings0) --
+ ([{File,[{L,v3_core,nomatch}]} ||
+ {File,[{L,qlc,M}]} <- mforms(ExtraWarnings),
+ lists:member(M, [nomatch_pattern,nomatch_filter])]
+ ++
+ [{File,[{L,sys_core_fold,nomatch_guard}]} ||
+ {File,[{L,qlc,M}]} <- mforms(ExtraWarnings),
+ M =:= nomatch_filter]),
+ Warnings = Warnings1 ++ ExtraWarnings,
+ {Es1,Ws1} = compile_forms(Forms, Options),
+ Es = mforms(Errors) -- mforms(Es1),
+ Ws = mforms(Warnings) -- mforms(Ws1),
+ {mforms2(error, Es),mforms2(warning, Ws)}.
+
+mforms(L) ->
+ lists:sort([{File,[M]} || {File,Ms} <- L, M <- Ms]).
+
+mforms2(Tag, L) ->
+ Line = 0,
+ ML = lists:flatmap(fun({File,Ms}) ->
+ [[{attribute,Line,file,{File,Line}}, {Tag,M}] ||
+ M <- Ms]
+ end, lists:sort(L)),
+ lists:flatten(lists:sort(ML)).
+
+is_qlc_q_imported(Forms) ->
+ [[] || {attribute,_,import,{?APIMOD,FAs}} <- Forms, {?Q,1} <- FAs] =/= [].
+
+record_attributes(Forms) ->
+ [A || A = {attribute, _, record, _D} <- Forms].
+
+%% Get the compile errors and warnings for the QLC as well as messages
+%% for introduced variables used in list expressions and messages for
+%% badargs. Since the QLCs will be replaced by some terms, the
+%% compiler cannot find the errors and warnings after the parse
+%% transformation.
+%%
+compile_messages(Forms, FormsNoShadows, Options, State) ->
+ %% The qlc module cannot handle binary generators.
+ BGenF = fun(_QId,{b_generate,Line,_P,_LE}=BGen, GA, A) ->
+ M = {loc(Line),?APIMOD,binary_generator},
+ {BGen,[{get(?QLC_FILE),[M]}|GA],A};
+ (_QId, Q, GA, A) ->
+ {Q,GA,A}
+ end,
+ {_,BGens} = qual_fold(BGenF, [], [], FormsNoShadows, State),
+ GenForm = used_genvar_check(FormsNoShadows, State),
+ ?DEBUG("GenForm = ~s~n", [catch erl_pp:form(GenForm)]),
+ WarnFun = fun(Id, LC, A) -> {tag_lines(LC, get_lcid_no(Id)), A} end,
+ {WForms,ok} = qlc_mapfold(WarnFun, ok, Forms, State),
+ {Es,Ws} = compile_forms(WForms ++ [GenForm], Options),
+ {badarg(Forms, State),tagged_messages(Es)++BGens,tagged_messages(Ws)}.
+
+badarg(Forms, State) ->
+ F = fun(_Id, {lc,_L,_E,_Qs}=LC, Es) ->
+ {LC,Es};
+ (Id, A, Es) ->
+ E = {get_lcid_line(Id),?APIMOD,not_a_query_list_comprehension},
+ {A,[{get(?QLC_FILE), [E]} | Es]}
+ end,
+ {_,E0} = qlc_mapfold(F, [], Forms, State),
+ E0.
+
+tag_lines(E, No) ->
+ map_lines(fun(Id) ->
+ case is_lcid(Id) of
+ true -> Id;
+ false -> make_lcid(Id, No)
+ end
+ end, E).
+
+map_lines(F, E) ->
+ erl_lint:modify_line(E, F).
+
+tagged_messages(MsL) ->
+ [{File,
+ [{Loc,Mod,untag(T)} || {Loc0,Mod,T} <- Ms,
+ {true,Loc} <- [tloc(Loc0)]]}
+ || {File,Ms} <- MsL]
+ ++
+ [{File,[{Loc,?APIMOD,{used_generator_variable,V}}]}
+ || {_, Ms} <- MsL,
+ {XLoc,erl_lint,{unbound_var,_}} <- Ms,
+ {Loc,File,V} <- [extra(XLoc)]].
+
+tloc({Id,Column}) ->
+ {IsLcid,T} = tloc(Id),
+ {IsLcid,{T,Column}};
+tloc(Id) ->
+ IsLcid = is_lcid(Id),
+ {IsLcid,case IsLcid of
+ true -> get_lcid_line(Id);
+ false -> any
+ end}.
+
+extra({extra,Line,File,V}) ->
+ {Line,File,V};
+extra({Line,Column}) ->
+ case extra(Line) of
+ {L,File,V} -> {{L,Column},File,V};
+ Else -> Else
+ end;
+extra(Else) ->
+ Else.
+
+untag([E | Es]) -> [untag(E) | untag(Es)];
+untag(T) when is_tuple(T) -> list_to_tuple(untag(tuple_to_list(T)));
+untag(E) ->
+ case is_lcid(E) of
+ true -> get_lcid_line(E);
+ false -> E
+ end.
+
+%% -> [{Qid,[variable()]}].
+%%
+%% For each qualifier the introduced variables are found. The
+%% variables introduced in filters are very much like the variables
+%% introduced in generator patterns. If no variables are introduced in
+%% a qualifier, [variable()] is empty.
+%%
+%% Generator: all variables occurring in the pattern are introduced
+%% variables.
+%% Filter: all variables bound inside the filter are introduced
+%% variables (unless they are unsafe).
+%%
+intro_variables(FormsNoShadows, State) ->
+ Fun = fun(QId, {T,_L,P0,_E0}=Q, {GVs,QIds}, Foo) when T =:= b_generate;
+ T =:= generate ->
+ PVs = qlc:var_ufold(fun({var,_,V}) -> {QId,V} end, P0),
+ {Q,{ordsets:to_list(PVs) ++ GVs,[{QId,[]} | QIds]},Foo};
+ (QId, Filter0, {GVs,QIds}, Foo) ->
+ %% The filter F is replaced by begin E, F, E end,
+ %% where E is an LC expression consisting of a
+ %% template mentioning all variables occurring in F.
+ Vs = ordsets:to_list(qlc:vars(Filter0)),
+ Id = QId#qid.lcid,
+ LC1 = embed_vars(intro_set_line({QId,f1}, Vs), Id),
+ LC2 = embed_vars(intro_set_line({QId,f2}, Vs), Id),
+ AnyLine = -1,
+ Filter = {block,AnyLine,[LC1,Filter0,LC2]},
+ {Filter,{GVs,[{QId,[]} | QIds]},Foo}
+ end,
+ Acc0 = {[],[]},
+ {FForms,{GenVars,QIds}} =
+ qual_fold(Fun, Acc0, [], FormsNoShadows, State),
+ %% Note: the linter messages are the ones we are looking for.
+ %% If there are no linter messages, the compiler will crash (ignored).
+ Es0 = compile_errors(FForms),
+ %% A variable is bound inside the filter if it is not bound before
+ %% the filter, but it is bound after the filter (obviously).
+ Before = [{QId,V} || {{QId,f1},erl_lint,{unbound_var,V}} <- Es0],
+ After = [{QId,V} || {{QId,f2},erl_lint,{unbound_var,V}} <- Es0],
+ Unsafe = [{QId,V} || {{QId,f2},erl_lint,{unsafe_var,V,_Where}} <- Es0],
+ ?DEBUG("Before = ~p~n", [Before]),
+ ?DEBUG("After = ~p~n", [After]),
+ ?DEBUG("Unsafe = ~p~n", [Unsafe]),
+ ?DEBUG("Filter ~p~n", [(Before -- After) -- Unsafe]),
+ IV = (Before -- After) -- Unsafe,
+ I1 = family(IV ++ GenVars),
+ sofs:to_external(sofs:family_union(sofs:family(QIds), I1)).
+
+intro_set_line(Tag, Vars) ->
+ L = erl_parse:set_line(1, fun(_) -> Tag end),
+ [{var,L,V} || V <- Vars].
+
+compile_errors(FormsNoShadows) ->
+ case compile_forms(FormsNoShadows, []) of
+ {[], _Warnings} ->
+ [];
+ {Errors, _Warnings} ->
+ ?DEBUG("got errors ~p~n", [Errors]),
+ lists:flatmap(fun({_File,Es}) -> Es end, Errors)
+ end.
+
+-define(MAX_NUM_OF_LINES, 23). % assume max 1^23 lines (> 8 millions)
+
+compile_forms(Forms0, Options) ->
+ Forms = [F || F <- Forms0, element(1, F) =/= eof] ++
+ [{eof,1 bsl ?MAX_NUM_OF_LINES}],
+ try
+ case compile:noenv_forms(Forms, compile_options(Options)) of
+ {ok, _ModName, Ws0} ->
+ {[], Ws0};
+ {error, Es0, Ws0} ->
+ {Es0, Ws0}
+ end
+ catch _:_ ->
+ %% The compiler is not available. Use the linter instead.
+ case erl_lint:module(Forms, lint_options(Options)) of
+ {ok, Warnings} ->
+ {[], Warnings};
+ {error, Errors, Warnings} ->
+ {Errors, Warnings}
+ end
+ end.
+
+compile_options(Options) ->
+ No = [report,report_errors,report_warnings,'P','E' | bitstr_options()],
+ [strong_validation,return | skip_options(No, Options)].
+
+lint_options(Options) ->
+ skip_options(bitstr_options(), Options).
+
+skip_options(Skip, Options) ->
+ [O || O <- Options, not lists:member(O, Skip)].
+
+bitstr_options() ->
+ [binary_comprehension,bitlevel_binaries].
+
+%% In LCs it is possible to use variables introduced in filters and
+%% generator patterns in the right hand side of generators (ListExpr),
+%% but in QLCs this is not allowed.
+%%
+%% A brand new function is returned such that there is one expression
+%% for each ListExpr. The expression mentions all introduced variables
+%% occurring in ListExpr. Running the function through the compiler
+%% yields error messages for erroneous use of introduced variables.
+%% The messages have the form
+%% {{extra,LineNo,File,Var},Module,{unbound_var,V}}, where Var is the
+%% original variable name and V is the name invented by no_shadows/2.
+%%
+used_genvar_check(FormsNoShadows, State) ->
+ F = fun(QId, {T, Ln, _P, LE}=Q, {QsIVs0, Exprs0}, IVsSoFar0)
+ when T =:= b_generate; T =:= generate ->
+ F = fun({var, _, V}=Var) ->
+ {var, L, OrigVar} = undo_no_shadows(Var),
+ AF = fun(Line) ->
+ {extra, Line, get(?QLC_FILE), OrigVar}
+ end,
+ L2 = erl_parse:set_line(L, AF),
+ {var, L2, V}
+ end,
+ Vs = [Var || {var, _, V}=Var <- qlc:var_fold(F, [], LE),
+ lists:member(V, IVsSoFar0)],
+ Exprs = case Vs of
+ [] -> Exprs0;
+ _ -> [embed_vars(Vs, Ln) | Exprs0]
+ end,
+ {QsIVs,IVsSoFar} = q_intro_vars(QId, QsIVs0, IVsSoFar0),
+ {Q, {QsIVs, Exprs}, IVsSoFar};
+ (QId, Filter, {QsIVs0, Exprs}, IVsSoFar0) ->
+ {QsIVs, IVsSoFar} = q_intro_vars(QId, QsIVs0, IVsSoFar0),
+ {Filter, {QsIVs, Exprs}, IVsSoFar}
+ end,
+ IntroVars = intro_variables(FormsNoShadows, State),
+ Acc0 = {IntroVars, [{atom, 0, true}]},
+ {_, {[], Exprs}} = qual_fold(F, Acc0, [], FormsNoShadows, State),
+ FunctionNames = [Name || {function, _, Name, _, _} <- FormsNoShadows],
+ UniqueFName = qlc:aux_name(used_genvar, 1, sets:from_list(FunctionNames)),
+ {function,0,UniqueFName,0,[{clause,0,[],[],lists:reverse(Exprs)}]}.
+
+q_intro_vars(QId, [{QId, IVs} | QsIVs], IVsSoFar) -> {QsIVs, IVs ++ IVsSoFar}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% The transformed code has two major parts: a fun where each
+%% qualifier is represented by one or more clauses, and a table where
+%% list expressions (the right hand side of generators, LE) are
+%% represented by funs (the table is further processed at runtime).
+%% The separation into a fun and a table makes it possible to
+%% rearrange qualifiers while keeping the speed offered by compiled
+%% code, and to run the LEs before evaluation of the QLC (and possibly
+%% modify the LEs should that be necessary). Only when doing a fast
+%% join are qualifiers rearranged.
+%%
+%% Extra generators (and clauses) are inserted for possible fast join
+%% operations. The list expression for such a generator has the form
+%% {join, Op, QualifierNumber1, QualifierNumber2, PatternFilter1,
+%% PatternFilter2, PatternConstants1, PatternConstants2} (it is not a
+%% fun). Join generators are ignored at runtime unless a fast join is
+%% possible, in which case they replace other generators. See also
+%% qlc.erl.
+%%
+%% For each QLC, every filter is given a state number and every
+%% generator two state numbers (one for initialization, one for
+%% looping over values). State 1 is reserved for the template and
+%% state 0 is entered when there are no more values to try, so
+%% assuming no rearrangement of the qualifiers has taken place, the
+%% first qualifier is given state number 2. For every state except 0,
+%% the table tells which state to go to next. By modifying the table,
+%% the order of the qualifiers can be altered at runtime.
+%%
+%% The syntax of the value Val returned from the fun is:
+%% Val = [] | [term() | Val] | fun() -> Val
+%% Note: the fun must not return a fun if it is to be called by
+%% the function outlined below.
+%%
+%% An outline of the generated fun:
+%%
+%% fun(0, RL, ...) when is_list(RL) -> % the final state
+%% lists:reverse(RL); % eval, all answers collected in a list
+%% (0, ...) -> []; % cursor (or fold)
+%% (1, RL, ...) when is_list(RL) -> % the template state
+%% Fun(<last generator loop state>, [Template | RL], ...);
+%% (1, ....) -> % return the object and a continuation
+%% [Template | fun() -> Fun(<last generator loop state>, ...)];
+%% (2, ...) -> % an sample generator, initialization state
+%% Fun(3, ..., <initial value>, ...);
+%% (3, ..., [Pattern | Val], ...) -> % looping over values (a list)
+%% Fun(<next qualifier state>, ..., Val, ...); % arguments are bound
+%% (3, ..., [_ | Val], ...) -> % pattern does not match
+%% Fun(3, ..., Val, ...);
+%% (3, ..., [], ...) ->
+%% Fun(<last generator loop state>, ...);
+%% (3, ...., F, ...) -> % looping over values (using continuations)
+%% case F() of % get the next value by calling a continuation
+%% [Pattern | Val] ->
+%% Fun(<next qualifier state>..., Val, ...);
+%% [_ | Val] ->
+%% Fun(3, ..., Val, ...);
+%% [] ->
+%% Fun(<last generator loop state>, ...);
+%% T -> % returned immediately, typically an error tuple
+%% T
+%% end;
+%% (4, ...) -> % a sample filter
+%% case Filter of
+%% true -> Fun(<next qualifier state>, ...);
+%% false -> Fun(<last generator loop state>, ...)
+%% end;
+%% (5, ...) -> % a filter so simple that it could be used as a guard
+%% if
+%% Guard -> Fun(<next qualifier state>, ...);
+%% true -> Fun(<last generator loop state>, ...)
+%% end
+%%
+%% <last generator loop state> means state 0 if there is no last
+%% generator. <initial value> is the evaluated list expression
+%% (evaluated once only). Among the arguments indicated by ellipses
+%% are all variables introduced in patterns and filters.
+%%
+%% transform/2 replaces each QLC (call to qlc:q/1) with a qlc_lc
+%% record. The general case is that calling the fun stored in the 'lc'
+%% field returns {qlc_v1, QFun, CodeF, Qdata, QOpt} such that: QFun is
+%% the above mentioned fun; CodeF is a fun returning the original code
+%% for the template, every pattern, and every filter; Qdata is the
+%% above mentioned table; QOpt is a property list implemented as a fun
+%% of one argument - an atom - which returns information about such
+%% things as constant columns, match specifications, &c.
+%% There is one special case when calling the fun stored in the 'lc'
+%% field returns something else:
+%% - If the QLC has the form [Var || Var <- LE] and there are no
+%% options to qlc:q/2, a tuple {simple_v1, P, LEf, Line} is returned.
+%% The objects returned are the objects returned by the generator
+%% (calling LEf returns the objects generated by LE).
+
+transform(FormsNoShadows, State) ->
+ IntroVars = intro_variables(FormsNoShadows, State),
+ AllVars = sets:from_list(ordsets:to_list(qlc:vars(FormsNoShadows))),
+ ?DEBUG("AllVars = ~p~n", [sets:to_list(AllVars)]),
+ F1 = fun(QId, {generate,_,P,LE}, Foo, {GoI,SI}) ->
+ {{QId,GoI,SI,{gen,P,LE}},Foo,{GoI + 3, SI + 2}};
+ (QId, F, Foo, {GoI,SI}) ->
+ {{QId,GoI,SI,{fil,F}},Foo,{GoI + 2,SI + 1}}
+ end,
+ TemplS = qlc:template_state(),
+ GoState = {TemplS + 1, TemplS + 1},
+ {ModifiedForms1,_} =
+ qual_fold(F1, [], GoState, FormsNoShadows, State),
+
+ %% This is for info/2. QLCs in filters and the template are
+ %% translated before the expression itself is translated. info/2
+ %% must not display the result of the translation, but the source
+ %% code.
+ {_,Source0} = qual_fold(fun(_QId, {generate,_,_P,_E}=Q, Dict, Foo) ->
+ {Q,Dict,Foo};
+ (QId, F, Dict, Foo) ->
+ {F,dict:store(QId, F, Dict),Foo}
+ end, dict:new(), [], FormsNoShadows, State),
+ {_,Source} = qlc_mapfold(fun(Id, {lc,_L,E,_Qs}=LC, Dict) ->
+ {LC,dict:store(Id, E, Dict)}
+ end, Source0, FormsNoShadows, State),
+
+
+ %% Unused variables introduced in filters are not optimized away.
+ F2 = fun(Id, {lc,_L,E,Qs}, {IntroVs0,XWarn0}) ->
+ LcNo = get_lcid_no(Id),
+ LcL = get_lcid_line(Id),
+ [RL,Fun,Go,NGV,S0,RL0,Go0,AT,Err] =
+ aux_vars(['RL','Fun','Go','C','S0','RL0','Go0','AT','E'],
+ LcNo, AllVars),
+ ?DEBUG("RL = ~p, Fun = ~p, Go = ~p~n", [RL, Fun, Go]),
+ {IntroVs, RestIntroVs} = lists:split(length(Qs), IntroVs0),
+ IntroVs_Qs = lists:zip(IntroVs, Qs),
+ F = fun({{QId,IVs}, {QId,GoI,SI,{gen,P,LE}}}, AllIVs0) ->
+ GV = aux_var('C', LcNo, QId#qid.no, 1, AllVars),
+ GenIVs = [GV | IVs],
+ {{QId,{GenIVs,{{gen,P,LE,GV},GoI,SI}}},
+ GenIVs ++ AllIVs0};
+ ({{QId,IVs}, {QId,GoI,SI,{fil,F}}}, AllIVs0) ->
+ {{QId,{IVs,{{fil,F},GoI,SI}}},
+ IVs++AllIVs0}
+ end,
+ {QCs, AllIVs} = lists:mapfoldl(F, [], IntroVs_Qs),
+
+ Dependencies = qualifier_dependencies(Qs, IntroVs),
+ L = no_compiler_warning(LcL),
+ {EqColumnConstants, EqualColumnConstants,
+ ExtraConsts, SizeInfo} =
+ constants_and_sizes(Qs, E, Dependencies, AllIVs, State),
+ {JoinInfo, XWarn} =
+ join_kind(Qs, LcL, AllIVs, Dependencies, State),
+ %% Not at all sure it is a good idea to try and find
+ %% failing qualifiers; Dialyzer does it so much better.
+ %% But there are a few cases where qlc finds more... (r12b).
+ FWarn = warn_failing_qualifiers(Qs, AllIVs, Dependencies,
+ State),
+ JQs = join_quals(JoinInfo, QCs, L, LcNo, ExtraConsts, AllVars),
+ XQCs = QCs ++ JQs,
+ Cs0 = clauses(XQCs, RL, Fun, Go, NGV, Err, AllIVs, State),
+ Template = template(E, RL, Fun, Go, AT, L, AllIVs, State),
+ Fin = final(RL, AllIVs, L, State),
+ FunC = {'fun',L,{clauses,Fin ++ Template ++ Cs0}},
+ As0 = pack_args(abst_vars([S0, RL0, Fun, Go0
+ | replace(AllIVs, AllIVs, nil)],
+ L), L, State),
+ AsW = abst_vars([S0, RL0, Go0], L),
+ FunW = {'fun',L,{clauses,[{clause,L,AsW,[],
+ [{match,L,{var,L,Fun},FunC},
+ {call,L,{var,L,Fun},As0}]}]}},
+ {ok, OrigE0} = dict:find(Id, Source),
+ OrigE = undo_no_shadows(OrigE0),
+ QCode = qcode(OrigE, XQCs, Source, L),
+ Qdata = qdata(XQCs, L),
+ TemplateInfo =
+ template_columns(Qs, E, AllIVs, Dependencies, State),
+ %% ExtraConsts should be used by match_spec_quals.
+ MSQs = match_spec_quals(E, Dependencies, Qs, State),
+ Opt = opt_info(TemplateInfo, SizeInfo, JoinInfo, MSQs, L,
+ EqColumnConstants, EqualColumnConstants),
+ LCTuple =
+ case qlc_kind(OrigE, Qs) of
+ qlc ->
+ {tuple,L,[?A(qlc_v1),FunW,QCode,Qdata,Opt]};
+ {simple, PL, LE, V} ->
+ Init = closure(LE, L),
+ simple(L, V, Init, PL)
+ end,
+ LCFun = {'fun',L,{clauses,[{clause,L,[],[],[LCTuple]}]}},
+ {tuple,_,Fs0} = abstr(#qlc_lc{}, L),
+ Fs = set_field(#qlc_lc.lc, Fs0, LCFun),
+ {{tuple,L,Fs},{RestIntroVs,FWarn++XWarn++XWarn0}}
+ end,
+ {NForms,{[],XW}} = qlc_mapfold(F2, {IntroVars,[]}, ModifiedForms1, State),
+ display_forms(NForms),
+ {restore_line_numbers(NForms), State#state{xwarnings = XW}}.
+
+join_kind(Qs, LcL, AllIVs, Dependencies, State) ->
+ {EqualCols2, EqualColsN} = equal_columns(Qs, AllIVs, Dependencies, State),
+ {MatchCols2, MatchColsN} = eq_columns(Qs, AllIVs, Dependencies, State),
+ Tables = lists:usort
+ ([T || {C,_Skip} <- EqualCols2, {T,_} <- C]
+ ++ [T || {C,_Skip} <- EqualCols2, T <- C, is_integer(T)]),
+ if
+ EqualColsN =/= []; MatchColsN =/= [] ->
+ {[],
+ [{get(?QLC_FILE),[{abs(LcL),?APIMOD,too_complex_join}]}]};
+ EqualCols2 =:= [], MatchCols2 =:= [] ->
+ {[], []};
+ length(Tables) > 2 ->
+ {[],
+ [{get(?QLC_FILE),[{abs(LcL),?APIMOD,too_many_joins}]}]};
+ EqualCols2 =:= MatchCols2 ->
+ {EqualCols2, []};
+ true ->
+ {{EqualCols2, MatchCols2}, []}
+ end.
+
+qlc_kind(OrigE, Qs) ->
+ {OrigFilterData, OrigGeneratorData} = qual_data(undo_no_shadows(Qs)),
+ OrigAllFilters = filters_as_one(OrigFilterData),
+ {_FilterData, GeneratorData} = qual_data(Qs),
+ case {OrigE, OrigAllFilters, OrigGeneratorData} of
+ {{var,_,V}, {atom,_,true}, [{_,{gen,{var,PatternL,V},_LE}}]} ->
+ [{_,{gen,_,LE}}] = GeneratorData,
+ {simple, PatternL, LE, V}; % V is for info()
+ _ ->
+ qlc
+ end.
+
+%% Finds filters and patterns that cannot match any values at all.
+%% Nothing but the patterns and the filters themselves is analyzed.
+%% A much weaker analysis than the one of Dialyzer's.
+warn_failing_qualifiers(Qualifiers, AllIVs, Dependencies, State) ->
+ {FilterData, GeneratorData} = qual_data(Qualifiers),
+ Anon = 1,
+ BindFun = fun(_Op, Value) -> is_bindable(Value) end,
+ {PFrame, _PatternVars} =
+ pattern_frame(GeneratorData, BindFun, Anon, State),
+ {_, _, Imported} =
+ filter_info(FilterData, AllIVs, Dependencies, State),
+ PFrames = frame2frames(PFrame),
+ {_, Warnings} =
+ lists:foldl(fun({_QId,{fil,_Filter}}, {[]=Frames,Warnings}) ->
+ {Frames,Warnings};
+ ({_QId,{fil,Filter}}, {Frames,Warnings}) ->
+ case filter(set_line(Filter, 0), Frames, BindFun,
+ State, Imported) of
+ [] ->
+ {[],
+ [{get(?QLC_FILE),
+ [{abs_loc(element(2, Filter)),?APIMOD,
+ nomatch_filter}]} | Warnings]};
+ Frames1 ->
+ {Frames1,Warnings}
+ end;
+ ({_QId,{gen,Pattern,_}}, {Frames,Warnings}) ->
+ case pattern(Pattern, Anon, [], BindFun, State) of
+ {failed, _, _} ->
+ {Frames,
+ [{get(?QLC_FILE),
+ [{abs_loc(element(2, Pattern)),?APIMOD,
+ nomatch_pattern}]} | Warnings]};
+ _ ->
+ {Frames,Warnings}
+ end
+ end, {PFrames,[]}, FilterData++GeneratorData),
+ Warnings.
+
+-define(TNO, 0).
+-define(TID, #qid{lcid = template, no = ?TNO}).
+
+opt_info(TemplateInfo, Sizes, JoinInfo, MSQs, L,
+ EqColumnConstants0, EqualColumnConstants0) ->
+ SzCls = [{clause,L,[?I(C)],[],[?I(Sz)]} || {C,Sz} <- lists:sort(Sizes)]
+ ++ [{clause,L,[?V('_')],[],[?A(undefined)]}],
+ S = [{size, {'fun', L, {clauses, SzCls}}}],
+ J = case JoinInfo of [] -> []; _ -> [{join, abstr(JoinInfo, L)}] end,
+ %% Superfluous clauses may be emitted:
+ TCls0 = lists:append(
+ [[{clause,L,[abstr(Col, L),EqType],[],
+ [abstr(TemplCols, L)]} ||
+ {Col,TemplCols} <- TemplateColumns]
+ || {EqType, TemplateColumns} <- TemplateInfo]),
+ TCls = lists:sort(TCls0) ++ [{clause,L,[?V('_'),?V('_')],[],[{nil,L}]}],
+ T = [{template, {'fun', L, {clauses, TCls}}}],
+
+ %% The template may also have a constant function (IdNo = 0).
+ %% Only constant template columns are interesting.
+ EqColumnConstants = opt_column_constants(EqColumnConstants0),
+ CCs = opt_constants(L, EqColumnConstants),
+ EqC = {constants,{'fun',L,{clauses,CCs}}},
+
+ EqualColumnConstants = opt_column_constants(EqualColumnConstants0),
+ ECCs = opt_constants(L, EqualColumnConstants),
+ EqualC = {equal_constants,{'fun',L,{clauses,ECCs}}},
+ C = [EqC | [EqualC || true <- [CCs =/= ECCs]]],
+
+ %% Comparisons yield more constant columns than matchings.
+ ConstCols = [{IdNo,Col} ||
+ {{IdNo,Col},[_],_FilNs} <- EqualColumnConstants],
+ ConstColsFamily = family_list(ConstCols),
+ NSortedCols0 = [{IdNo,hd(lists:seq(1, length(Cols)+1)--Cols)} ||
+ {IdNo,Cols} <- ConstColsFamily],
+ NCls = [{clause,L,[?I(IdNo)],[],[?I(N-1)]} ||
+ {IdNo,N} <- NSortedCols0, N > 0]
+ ++ [{clause,L,[?V('_')],[],[?I(0)]}],
+ N = [{n_leading_constant_columns,{'fun',L,{clauses,NCls}}}],
+
+ ConstCls = [{clause,L,[?I(IdNo)],[],[abstr(Cols,L)]} ||
+ {IdNo,Cols} <- ConstColsFamily]
+ ++ [{clause,L,[?V('_')],[],[{nil,L}]}],
+ CC = [{constant_columns,{'fun',L,{clauses,ConstCls}}}],
+
+ MSCls = [{clause,L,[?I(G)],[],[{tuple,L,[MS,abstr(Fs,L)]}]} ||
+ {G,MS,Fs} <- MSQs]
+ ++ [{clause,L,[?V('_')],[],[?A(undefined)]}],
+ MS = [{match_specs, {'fun',L,{clauses,MSCls}}}],
+
+ Cls = [{clause,L,[?A(Tag)],[],[V]} ||
+ {Tag,V} <- lists:append([J, S, T, C, N, CC, MS])]
+ ++ [{clause,L,[?V('_')],[],[?A(undefined)]}],
+ {'fun', L, {clauses, Cls}}.
+
+opt_column_constants(ColumnConstants0) ->
+ [CC || {{IdNo,_Col},Const,_FilNs}=CC <- ColumnConstants0,
+ (IdNo =/= ?TNO) or (length(Const) =:= 1)].
+
+opt_constants(L, ColumnConstants) ->
+ Ns = lists:usort([IdNo || {{IdNo,_Col},_Const,_FilNs} <- ColumnConstants]),
+ [{clause,L,[?I(IdNo)],[],[column_fun(ColumnConstants, IdNo, L)]}
+ || IdNo <- Ns]
+ ++ [{clause,L,[?V('_')],[],[?A(no_column_fun)]}].
+
+abstr(Term, Line) ->
+ erl_parse:abstract(Term, Line).
+
+%% Extra generators are introduced for join.
+join_quals(JoinInfo, QCs, L, LcNo, ExtraConstants, AllVars) ->
+ {LastGoI, LastSI} =
+ lists:foldl(fun({_QId,{_QIVs,{{fil,_},GoI,SI}}},
+ {GoI0, _SI0}) when GoI >= GoI0 ->
+ {GoI + 2, SI + 1};
+ ({_QId,{_QIVs,{{gen,_,_,_},GoI,SI}}},
+ {GoI0, _SI0}) when GoI >= GoI0 ->
+ {GoI + 3, SI + 2};
+ (_, A) ->
+ A
+ end, {0, 0}, QCs),
+ LastQId = lists:max([QId || {QId,{_QIVs,{_Q,_GoI,_SI}}} <- QCs]),
+ %% Only two tables for the time being.
+ %% The join generator re-uses the generator variable assigned to
+ %% the first of the two joined generators. Its introduced variables
+ %% are the variables introduced by any of the two joined generators.
+ %% Its abstract code is a pair of the joined generators' patterns.
+ QNums = case JoinInfo of
+ {EqualCols, MatchCols} ->
+ EQs = join_qnums(EqualCols),
+ MQs = join_qnums(MatchCols),
+ [{Q1,Q2,'=:='} || {Q1,Q2} <- MQs] ++
+ [{Q1,Q2,'=='} || {Q1,Q2} <- EQs -- MQs];
+ EqualCols ->
+ [{Q1,Q2,'=='} || {Q1,Q2} <- join_qnums(EqualCols)]
+ end,
+ LD = [begin
+ [{QId1,P1,GV1,QIVs1}] =
+ [{QId,P,GV,QIVs} ||
+ {QId,{QIVs,{{gen,P,_,GV},_GoI,_SI}}} <- QCs,
+ QId#qid.no =:= Q1],
+ [{QId2,P2,QIVs2}] =
+ [{QId,P,QIVs--[GV]} ||
+ {QId,{QIVs,{{gen,P,_,GV},_,_}}} <- QCs,
+ QId#qid.no =:= Q2],
+ {QId1,Op,P1,GV1,QIVs1++QIVs2,QId2,P2}
+ end || {Q1, Q2, Op} <- lists:usort(QNums)],
+ Aux = abst_vars(aux_vars(['F','H','O','C'], LcNo, AllVars), L),
+ F = fun({QId1,Op,P1,GV1,QIVs,QId2,P2}, {QId,GoI,SI}) ->
+ AP1 = anon_pattern(P1),
+ AP2 = anon_pattern(P2),
+ Cs1 = join_handle_constants(QId1, ExtraConstants),
+ Cs2 = join_handle_constants(QId2, ExtraConstants),
+ H1 = join_handle(AP1, L, Aux, Cs1),
+ H2 = join_handle(AP2, L, Aux, Cs2),
+ %% Op is not used.
+ Join = {join,Op,QId1#qid.no,QId2#qid.no,H1,H2,Cs1,Cs2},
+ G = {NQId=QId#qid{no = QId#qid.no + 1},
+ {QIVs,{{gen,{cons,L,P1,P2},Join,GV1},GoI,SI}}},
+ A = {NQId, GoI + 3, SI + 2},
+ {G, A}
+ end,
+ {Qs, _} = lists:mapfoldl(F, {LastQId, LastGoI, LastSI}, LD),
+ Qs.
+
+join_qnums(Cols) ->
+ lists:usort([{Q1, Q2} || {[{Q1,_C1}, {Q2,_C2}], _Skip} <- Cols]).
+
+%% Variables occurring only once are replaced by '_'.
+anon_pattern(P) ->
+ MoreThanOnce = lists:usort(occ_vars(P) -- qlc:vars(P)),
+ {AP, foo} = var_mapfold(fun({var, L, V}, A) ->
+ case lists:member(V, MoreThanOnce) of
+ true ->
+ {{var, L, V}, A};
+ false ->
+ {{var, L, '_'}, A}
+ end
+ end, foo, P),
+ AP.
+
+%% Creates a handle that filters the operands of merge join using the
+%% pattern. It is important that objects that do not pass the pattern
+%% are filtered out because the columns of the pattern are inspected
+%% in order to determine if key-sorting the operands can be avoided.
+%%
+%% No objects will be filtered out if the pattern is just a variable.
+join_handle(AP, L, [F, H, O, C], Constants) ->
+ case {AP, Constants} of
+ {{var, _, _}, []} ->
+ {'fun',L,{clauses,[{clause,L,[H],[],[H]}]}};
+ _ ->
+ G0 = [begin
+ Call = {call,0,{atom,0,element},[{integer,0,Col},O]},
+ list2op([{op,0,Op,Con,Call} || {Con,Op} <- Cs], 'or')
+ end || {Col,Cs} <- Constants],
+ G = if G0 =:= [] -> G0; true -> [G0] end,
+ CC1 = {clause,L,[AP],G,[{cons,L,O,closure({call,L,F,[F,C]},L)}]},
+ CC2 = {clause,L,[?V('_')],[],[{call,L,F,[F,C]}]},
+ Case = {'case',L,O,[CC1,CC2]},
+ Cls = [{clause,L,[?V('_'),{nil,L}],[],[{nil,L}]},
+ {clause,L,[F,{cons,L,O,C}],[],[Case]},
+ {clause,L,[F,C],[[{call,L,?A(is_function),[C]}]],
+ [{call,L,F,[F,{call,L,C,[]}]}]},
+ {clause,L,[?V('_'),C],[],[C]}],
+ Fun = {'fun', L, {clauses, Cls}},
+ {'fun',L,{clauses,[{clause,L,[H],[],[{match,L,F,Fun},
+ closure({call,L,F,[F,H]},
+ L)]}]}}
+ end.
+
+join_handle_constants(QId, ExtraConstants) ->
+ IdNo = QId#qid.no,
+ case lists:keysearch(IdNo, 1, ExtraConstants) of
+ {value, {IdNo, ConstOps}} ->
+ ConstOps;
+ false ->
+ []
+ end.
+
+%%% By the term "imported variable" is meant a variable that is bound
+%%% outside (before) the QLC. Perhaps "parameter" would be a more
+%%% suitable name.
+
+%% The column fun is to be used when there is a known key column or
+%% indices. The argument is a column number and the return value is a
+%% list of the values to look up to get all objects needed to evaluate
+%% the filter. The order of the objects need not be the same as the
+%% order the traverse fun would return them.
+
+column_fun(Columns, QualifierNumber, LcL) ->
+ ColCls0 =
+ [begin
+ true = Vs0 =/= [], % at least one value to look up
+ Vs1 = list2cons(Vs0),
+ Fils1 = {tuple,0,[{atom,0,FTag},
+ lists:foldr
+ (fun(F, A) -> {cons,0,{integer,0,F},A}
+ end, {nil,0}, Fils)]},
+ Tag = case ordsets:to_list(qlc:vars(Vs1)) of
+ Imp when length(Imp) > 0, % imported vars
+ length(Vs0) > 1 ->
+ usort_needed;
+ _ ->
+ values
+ end,
+ Vs = {tuple,0,[{atom,0,Tag},Vs1,Fils1]},
+ {clause,0,[erl_parse:abstract(Col)],[],[Vs]}
+ end ||
+ {{CIdNo,Col}, Vs0, {FTag,Fils}} <- Columns,
+ CIdNo =:= QualifierNumber]
+ ++ [{clause,0,[{var,0,'_'}],[],[{atom,0,false}]}],
+ ColCls = set_line(ColCls0, LcL),
+ {'fun', LcL, {clauses, ColCls}}.
+
+%% Tries to find columns of the template that (1) are equal to (or
+%% match) or (2) match columns of the patterns of the generators. The
+%% results are to be used only for determining which columns are
+%% sorted. The template can be handled very much like a generator
+%% pattern (the variables are not fresh, though). As in filters calls
+%% like element(I, T) are recognized.
+%% -> [{EqType,Equal | Match}]
+%% Equal = Match = TemplateColumns
+%% EqType = abstract code for {_ | '==' | '=:='}
+%% TemplateColumns = [{Column,Integers}] % integer is position in template
+%% Column = {QualifierNumber,ColumnNumber}} % column is position in pattern
+
+template_columns(Qs0, E0, AllIVs, Dependencies, State) ->
+ E = expand_expr_records(pre_expand(E0), State),
+ TemplateAsPattern = template_as_pattern(E),
+ Qs = [TemplateAsPattern | Qs0],
+ EqualColumns = equal_columns2(Qs, AllIVs, Dependencies, State),
+ MatchColumns = eq_columns2(Qs, AllIVs, Dependencies, State),
+ Equal = template_cols(EqualColumns),
+ Match = template_cols(MatchColumns),
+ L = 0,
+ if
+ Match =:= Equal ->
+ [{?V('_'), Match}];
+ true ->
+ [{?A('=='), Equal}, {?A('=:='), Match}]
+ end.
+
+equal_columns2(Qualifiers, AllIVs, Dependencies, State) ->
+ {JI, _Skip} =
+ join_info(Qualifiers, AllIVs, Dependencies, State,_JoinOp = '=='),
+ JI.
+
+eq_columns2(Qualifiers, AllIVs, Dependencies, State) ->
+ {JI, _SKip} =
+ join_info(Qualifiers, AllIVs, Dependencies, State, _JoinOp = '=:='),
+ JI.
+
+template_cols(ColumnClasses) ->
+ lists:sort([{{IdNo,Col}, lists:usort(Cs)} ||
+ Class <- ColumnClasses,
+ {IdNo,Col} <- Class,
+ IdNo =/= ?TNO,
+ [] =/= (Cs = [C || {?TNO,C} <- Class])]).
+
+template_as_pattern(E) ->
+ P = simple_template(E),
+ {?TID,foo,foo,{gen,P,{nil,0}}}.
+
+simple_template({call,L,{remote,_,{atom,_,erlang},{atom,_,element}}=Call,
+ [{integer,_,I}=A1,A2]}) when I > 0 ->
+ %% This kludge is known by pattern/5 below.
+ {call, L, Call, [A1, simple_template(A2)]};
+simple_template({var, _, _}=E) ->
+ E;
+simple_template({tuple, L, Es}) ->
+ {tuple, L, [simple_template(E) || E <- Es]};
+simple_template({cons, L, H, T}) ->
+ {cons, L, simple_template(H), simple_template(T)};
+simple_template(E) ->
+ case catch erl_parse:normalise(E) of
+ {'EXIT', _} -> unique_var();
+ _ -> E
+ end.
+
+%% -> [{QId,[QId']}].
+%% Qualifier QId (a filter) uses variables introduced in QId'.
+qualifier_dependencies(Qualifiers, IntroVs) ->
+ Intro = sofs:relation([{IV,QId} || {QId,IVs} <- IntroVs, IV <- IVs]),
+ {FilterData, _} = qual_data(Qualifiers),
+ Used = sofs:relation([{QId,UV} ||
+ {QId,{fil,F}} <- FilterData,
+ UV <- qlc:vars(F)]),
+ Depend = sofs:strict_relation(sofs:relative_product(Used, Intro)),
+ G = sofs:family_to_digraph(sofs:relation_to_family(Depend)),
+ Dep0 = [{V,digraph_utils:reachable_neighbours([V], G)} ||
+ V <- digraph:vertices(G)],
+ true = digraph:delete(G),
+ FilterIds = sofs:set(filter_ids(Qualifiers)),
+ Dep1 = sofs:restriction(sofs:family(Dep0), FilterIds),
+ NoDep = sofs:constant_function(FilterIds, sofs:empty_set()),
+ sofs:to_external(sofs:family_union(Dep1, NoDep)).
+
+filter_ids(Qualifiers) ->
+ {FilterData, _} = qual_data(Qualifiers),
+ [QId || {QId,_} <- FilterData].
+
+%% -> [{QualifierNumber,MatchSpec,[QualifierNumber']}
+%% The qualifiers [QualifierNumber'] are filters (F1, ..., Fn) that
+%% depend on QualifierNumber (a generator Pattern <- LE) only.
+%% MatchSpec is the match specification for [Pattern' || Pattern <- LE,
+%% F1, ..., Fn], where Pattern' is Template if all qualifiers can be
+%% replaced by one match specification, otherwise a modified Pattern.
+match_spec_quals(Template, Dependencies, Qualifiers, State) ->
+ {FilterData, GeneratorData} = qual_data(Qualifiers),
+ NoFilterGIds = [GId || {GId,_} <- GeneratorData]
+ -- lists:flatmap(fun({_,GIds}) -> GIds end, Dependencies),
+ Filters = filter_list(FilterData, Dependencies, State),
+ Candidates = [{QId2#qid.no,Pattern,[Filter],F} ||
+ {QId,[QId2]} <- Dependencies,
+ {GQId,{gen,Pattern,_}} <- GeneratorData,
+ GQId =:= QId2,
+ {FQId,{fil,F}}=Filter <- Filters, % guard filters only
+ FQId =:= QId]
+ ++ [{GId#qid.no,Pattern,[],{atom,0,true}} ||
+ {GId,{gen,Pattern,_}} <- GeneratorData,
+ lists:member(GId, NoFilterGIds)],
+ E = {nil, 0},
+ GF = [{{GNum,Pattern},Filter} ||
+ {GNum,Pattern,Filter,F} <- Candidates,
+ no =/= try_ms(E, Pattern, F, State)],
+ GFF = sofs:relation_to_family(sofs:relation(GF,
+ [{gnum_pattern,[filter]}])),
+ GFFL = sofs:to_external(sofs:family_union(GFF)),
+ try
+ [{{GNum,Pattern}, GFilterData}] = GFFL,
+ true = length(GFilterData) =:= length(FilterData),
+ [_] = GeneratorData,
+ AbstrMS = gen_ms(Template, Pattern, GFilterData, State),
+ %% There is one generator and every filter uses some of the
+ %% variables introduced by the generator. The whole qlc
+ %% expressione can be replaced by a match specification.
+ [{GNum, AbstrMS, all}]
+ catch _:_ ->
+ {TemplVar, _} = anon_var({var,0,'_'}, 0),
+ [one_gen_match_spec(GNum, Pattern, GFilterData, State, TemplVar) ||
+ {{GNum,Pattern},GFilterData} <- GFFL]
+ end.
+
+one_gen_match_spec(GNum, Pattern0, GFilterData, State, TemplVar) ->
+ {E, Pattern} = pattern_as_template(Pattern0, TemplVar),
+ AbstrMS = gen_ms(E, Pattern, GFilterData, State),
+ {GNum, AbstrMS, [FId#qid.no || {FId,_} <- GFilterData]}.
+
+gen_ms(E, Pattern, GFilterData, State) ->
+ {ok, MS, AMS} = try_ms(E, Pattern, filters_as_one(GFilterData), State),
+ case MS of
+ [{'$1',[true],['$1']}] ->
+ {atom, 0, no_match_spec};
+ _ ->
+ AMS
+ end.
+
+%% -> {Template, Pattern'}
+%% The pattern is accepted by ets:fun2ms/1, that is, =/2 can only
+%% occur at top level. Introduce or reuse a top-level variable as
+%% template
+pattern_as_template({var,_,'_'}, TemplVar) ->
+ {TemplVar, TemplVar};
+pattern_as_template({var,_,_}=V, _TemplVar) ->
+ {V, V};
+pattern_as_template({match,L,E,{var,_,'_'}}, TemplVar) ->
+ {TemplVar, {match,L,E,TemplVar}};
+pattern_as_template({match,L,{var,_,'_'},E}, TemplVar) ->
+ {TemplVar, {match,L,E,TemplVar}};
+pattern_as_template({match,_,_E,{var,_,_}=V}=P, _TemplVar) ->
+ {V, P};
+pattern_as_template({match,_,{var,_,_}=V,_E}=P, _TemplVar) ->
+ {V, P};
+pattern_as_template(E, TemplVar) ->
+ L = 0,
+ {TemplVar, {match, L, E, TemplVar}}.
+
+%% Tries to find columns which are compared or matched against
+%% constant values or other columns. To that end unification is used.
+%% A frame is a list of bindings created by unification.
+%% Also tries to find the number of columns of patterns.
+%% Note that the template is handled more or less as a pattern.
+%% -> {ColumnConstants, SizeInfo, ExtraConstants}
+%% ColumnConstants = [{Column,[Constant],[FilterNo]}]
+%% SizeInfo = [{QualifierNumber,NumberOfColumns}]
+%% Column = {QualifierNumber,ColumnNumber}}
+%% FilterNo is a filter that can be skipped at runtime provided constants
+%% are looked up.
+%% ExtraConstants =
+%% [{GeneratorNumber,[{ColumnNumber,
+%% [{AbstractConstant,AbstractOperator}]}]}]
+%% For every generator such that the unification binds value(s) to
+%% some column(s), extra constants are returned. These constants are
+%% the results of the unification, and do not occur in the pattern of
+%% the generator.
+constants_and_sizes(Qualifiers0, E, Dependencies, AllIVs, State) ->
+ TemplateAsPattern = template_as_pattern(E),
+ Qualifiers = [TemplateAsPattern | Qualifiers0],
+ {FilterData, GeneratorData} = qual_data(Qualifiers),
+ {Filter, Anon1, Imported} =
+ filter_info(FilterData, AllIVs, Dependencies, State),
+ PatBindFun = fun(_Op, Value) -> is_bindable(Value) end,
+ {PatternFrame, PatternVars} =
+ pattern_frame(GeneratorData, PatBindFun, Anon1, State),
+ PatternFrames = frame2frames(PatternFrame),
+ FilterFun =
+ fun(BindFun) ->
+ filter(Filter, PatternFrames, BindFun, State, Imported)
+ end,
+ SzFs = FilterFun(PatBindFun),
+
+ SizeInfo = pattern_sizes(PatternVars, SzFs),
+ SelectorFun = const_selector(Imported),
+ PatternConstants =
+ lists:flatten(frames_to_columns(PatternFrames, PatternVars,
+ deref_pattern(Imported),
+ SelectorFun, Imported,
+ '=:=')),
+
+ {EqColumnConstants, _EqExtraConsts} =
+ constants(FilterFun, PatternVars, PatternConstants, PatternFrame,
+ FilterData, Dependencies, _LookupOp1 = '=:=',
+ Imported, State),
+ {EqualColumnConstants, EqualExtraConsts} =
+ constants(FilterFun, PatternVars, PatternConstants, PatternFrame,
+ FilterData, Dependencies, _LookupOp2 = '==',
+ Imported, State),
+
+ %% Use compared extra constants only because:
+ %% - merge join compares terms;
+ %% - the constants from the matching unification is a subset of the
+ %% constants from the comparing unification.
+ %% Using constants from the matching unification would make it
+ %% possible to skip some (more) objects when joining.
+ ExtraCon1 =
+ [{{GId,Col},{Val,Op}} ||
+ {Consts,Op} <- [{EqualExtraConsts,'=='}],
+ {{GId,Col},Val} <- Consts],
+ ExtraConstants =
+ family_list([{GId, {Col,ValOps}} ||
+ {{GId,Col},ValOps} <- family_list(ExtraCon1)]),
+ {EqColumnConstants, EqualColumnConstants, ExtraConstants, SizeInfo}.
+
+constants(FilterFun, PatternVars, PatternConstants, PatternFrame,
+ FilterData, Dependencies, LookupOp, Imported, State) ->
+ BindFun = fun(_Op, Value) -> is_bindable(Value) end,
+ Fs = FilterFun(BindFun),
+ SelectorFun = const_selector(Imported),
+ ColumnConstants0 = frames_to_columns(Fs, PatternVars,
+ deref_lookup(Imported, LookupOp),
+ SelectorFun, Imported, LookupOp),
+ ColumnConstants1 = lists:flatten(ColumnConstants0),
+ ExtraConstants =
+ [{{GId,Col},Val} ||
+ {{GId,Col},Vals} <- ColumnConstants1 -- PatternConstants,
+ GId =/= ?TNO,
+ Val <- Vals],
+ ColumnConstants = lu_skip(ColumnConstants1, FilterData, PatternFrame,
+ PatternVars, Dependencies, State,
+ Imported, LookupOp),
+ {ColumnConstants, ExtraConstants}.
+
+%%% ** Comparing Terms **
+%%% When comparing the key against a term where some integer (or float
+%%% comparing equal to an integer) occurs, one has to be careful if the
+%%% table matches keys. One way would be to look up the term both with
+%%% the integer and with the float comparing equal to the integer--then
+%%% all objects that could possibly be answers are filtered (with
+%%% reasonable assumptions). But if integers occur several times in the
+%%% term all combinations have to be looked up, and that could be just
+%%% too many.
+%%% If imported variables occur in the term one could assume at compile
+%%% time that they are not integers and check that assumption at
+%%% runtime. However, this would probably be bad design since some keys
+%%% can be looked up, but others cannot.
+%%% However, the current implementation is simple: do not bind a
+%%% variable to a term if imported variables or integers occur in the
+%%% term.
+
+deref_lookup(Imported, '==') ->
+ %% Comparing table. Every value can be looked up.
+ fun(PV, F) -> deref_values(PV, F, Imported) end;
+deref_lookup(Imported, '=:=') ->
+ %% Matching table. Ignore comparisons unless the value is free of
+ %% integers. See also Comparing Terms.
+ BFun = fun(DV, Op) ->
+ Op =:= '=:=' orelse free_of_integers(DV, Imported)
+ end,
+ fun(PV, F) -> deref_values(PV, F, BFun, Imported) end.
+
+%% Augment ColConstants with filters that do not need to be run
+%% provided that constants are looked up.
+%% Does not find all filters that can be removed.
+lu_skip(ColConstants, FilterData, PatternFrame, PatternVars,
+ Dependencies, State, Imported, LookupOp) ->
+ %% If there is a test that does not compare or match, then the
+ %% filter cannot be skipped.
+ FailSelector = fun(_Frame) -> fun(Value) -> {yes, Value} end end,
+ %% In runtime, constants are looked up and matched against a pattern
+ %% (the pattern acts like a filter), then the filters are run.
+ PatternFrames = frame2frames(PatternFrame),
+ PatternColumns =
+ lists:flatten(frames_to_columns(PatternFrames, PatternVars,
+ deref_pattern(Imported), FailSelector,
+ Imported, LookupOp)),
+
+ %% Note: ColFil can contain filters for columns that cannot be
+ %% looked up. Such (possibly bogus) elements are however not used.
+ %% Note: one filter at a time is tested; only the pattern is
+ %% assumed to have been run when the filter is run. Sometimes it
+ %% would be advantageously to assume some filter(s) occurring
+ %% before the filter had been run as well
+ %% (an example: {{X,Y}} <- LE, X =:= 1, Y =:= a).
+ BindFun = fun(_Op, Value) -> is_bindable(Value) end,
+ ColFil = [{Column, FId#qid.no} ||
+ {FId,{fil,Fil}} <-
+ filter_list(FilterData, Dependencies, State),
+ [] =/= (SFs = safe_filter(set_line(Fil, 0), PatternFrames,
+ BindFun, State, Imported)),
+ {GId,PV} <- PatternVars,
+ [] =/=
+ (Cols = hd(frames_to_columns(SFs, [{GId, PV}],
+ deref_lu_skip(LookupOp,
+ Imported),
+ const_selector(Imported),
+ Imported, LookupOp))),
+ %% The filter must not test more than one column (unless the
+ %% pattern has already done the test):
+ %% Note: if the pattern and the filter test the same
+ %% column, the filter will not be skipped.
+ %% (an example: {X=1} <- ..., X =:= 1).
+ length(D = Cols -- PatternColumns) =:= 1,
+ Frame <- SFs,
+ begin
+ %% The column is compared/matched against a constant.
+ %% If there are no more comparisons/matches then
+ %% the filter can be replaced by the lookup of
+ %% the constant.
+ [{{_,Col} = Column, Constants}] = D,
+ {VarI, FrameI} = unify_column(Frame, PV, Col, BindFun,
+ Imported),
+ VarValues = deref_skip(VarI, FrameI, LookupOp, Imported),
+
+ {NV, F1} = unify_column(PatternFrame, PV, Col, BindFun,
+ Imported),
+ F2 = unify_var_bindings(VarValues, '=:=', NV, F1,
+ BindFun, Imported, false),
+ %% F2: the pattern has been matched and the
+ %% constant has been looked up. If Frame has no
+ %% more bindings than F2 (modulo unique
+ %% variables), then the filter can be skipped.
+ %%
+ %% Under rare circumstances (for instance:
+ %% "X =:= 1, X =:= U", U imported; only 1 is looked up),
+ %% not all constants mentioned in a filter are looked up.
+ %% The filter can only be skipped if all constants
+ %% are looked up.
+ LookedUpConstants =
+ case lists:keysearch(Column, 1, ColConstants) of
+ false -> [];
+ {value, {Column,LUCs}} -> LUCs
+ end,
+ %% Don't try to handle filters that compare several
+ %% values equal. See also frames_to_columns().
+ length(VarValues) =< 1 andalso
+ (Constants -- LookedUpConstants =:= []) andalso
+ bindings_is_subset(Frame, F2, Imported)
+ end],
+ ColFils = family_list(ColFil),
+ %% The skip tag 'all' means that all filters are covered by the lookup.
+ %% It does not imply that there is only one generator as is the case
+ %% for match specifications (see match_spec_quals above).
+ [{Col, Constants, skip_tag(Col, ColFils, FilterData)} ||
+ {Col,Constants} <- ColConstants].
+
+deref_skip(E, F, _LookupOp, Imported) ->
+ deref(E, F, Imported).
+
+deref_lu_skip('==', Imported) ->
+ %% Comparing table. Cannot skip filters that match integers.
+ BFun = fun(DV, Op) ->
+ Op =:= '==' orelse free_of_integers(DV, Imported)
+ end,
+ fun(PV, F) -> deref_values(PV, F, BFun, Imported) end;
+deref_lu_skip('=:=', Imported) ->
+ %% Matching table. Skip filters regardless of operator.
+ fun(PV, F) -> deref_values(PV, F, Imported) end.
+
+equal_columns(Qualifiers, AllIVs, Dependencies, State) ->
+ {Cs, Skip} =
+ join_info(Qualifiers, AllIVs, Dependencies, State, _JoinOp = '=='),
+ join_gens(Cs, Qualifiers, Skip).
+
+eq_columns(Qualifiers, AllIVs, Dependencies, State) ->
+ {Cs, Skip} =
+ join_info(Qualifiers, AllIVs, Dependencies, State, _JoinOp = '=:='),
+ join_gens(Cs, Qualifiers, Skip).
+
+%% -> {TwoGens, ManyGens}
+join_gens(Cs0, Qs, Skip) ->
+ Cs = [family_list(C) || C <- Cs0],
+ {FD, _GeneratorData} = qual_data(Qs),
+ {join_gens2(lists:filter(fun(C) -> length(C) =:= 2 end, Cs), FD, Skip),
+ join_gens2(lists:filter(fun(C) -> length(C) > 2 end, Cs), FD, Skip)}.
+
+join_gens2(Cs0, FilterData, Skip) ->
+ [{J, skip_tag(case lists:keysearch(J, 1, Skip) of
+ {value, {J,FilL}} ->
+ FilL;
+ false ->
+ []
+ end, FilterData)} ||
+ J <- lists:append([qlc:all_selections(C) || C <- Cs0])].
+
+skip_tag(FilList, FilterData) ->
+ {if
+ length(FilterData) =:= length(FilList) ->
+ all;
+ true ->
+ some
+ end, FilList}.
+
+skip_tag(Col, ColFils, FilterData) ->
+ case lists:keysearch(Col, 1, ColFils) of
+ {value, {Col, FilL}} ->
+ Tag = if
+ length(FilterData) =:= length(FilL) ->
+ all;
+ true ->
+ some
+ end,
+ {Tag, FilL};
+ false ->
+ {some,[]}
+ end.
+
+%% Tries to find columns (possibly in the same table) that are equal.
+%% If LookupOp is '=:=' then "equal" means that the columns are matched;
+%% if LookupOp is '==' then "equal" means that the columns are matched or
+%% compared.
+%% -> [[{QualifierNumber,ColumnNumber}]] % Eq.classes.
+join_info(Qualifiers, AllIVs, Dependencies, State, JoinOp) ->
+ {FilterData, GeneratorData} = qual_data(Qualifiers),
+ {Filter, Anon1, Imported} =
+ filter_info(FilterData, AllIVs, Dependencies, State),
+ BindFun = fun(_Op, V) -> bind_no_const(V, Imported) end,
+ {PatternFrame, PatternVars} =
+ pattern_frame(GeneratorData, BindFun, Anon1, State),
+ PatternFrames = frame2frames(PatternFrame),
+ Fs = filter(Filter, PatternFrames, BindFun, State, Imported),
+ SelectorFun = no_const_selector(Imported),
+ Cols = frames_to_columns(Fs, PatternVars,
+ fun(PV1, F) -> deref_join(PV1, F, JoinOp) end,
+ SelectorFun, Imported, '=:='),
+ JC = join_classes(Cols),
+ Skip = join_skip(JC, FilterData, PatternFrame,
+ PatternVars, Dependencies, State, Imported, JoinOp),
+ {JC, Skip}.
+
+deref_join(E, Frame, '==') ->
+ deref_values(E, Frame, _Imp = []);
+deref_join(E, Frame, '=:=') ->
+ %% Matching table. It is possible that some objects read from the
+ %% other table (the one with the objects to look up) contain
+ %% integers. By making all variables imported it is ensured that
+ %% comparisons are kept. See also Comparing Terms.
+ deref_values(E, Frame, fun(_DV, Op) -> Op =:= '=:=' end, all).
+
+join_classes(Cols0) ->
+ ColVar = sofs:relation(lists:append(Cols0)),
+ Cols = sofs:partition(2, ColVar),
+ [[C || {C,_} <- Cs] || Cs <- sofs:to_external(Cols), length(Cs) > 1].
+
+join_skip(JoinClasses, FilterData, PatternFrame, PatternVars, Dependencies,
+ State, Imported, JoinOp) ->
+ PatternFrames = frame2frames(PatternFrame),
+ ColFil = [{JoinClass,FId#qid.no} ||
+ [{Q1,C1}, {Q2,C2}]=JoinClass <- JoinClasses,
+ {GId1, PV1} <- PatternVars,
+ GId1#qid.no =:= Q1,
+ {GId2, PV2} <- PatternVars,
+ GId2#qid.no =:= Q2,
+
+ %% Select a filter that depends on the two generators:
+ {FId,{fil,Fil}} <-
+ filter_list(FilterData, Dependencies, State),
+ {value,{_,GIds}} <-
+ [lists:keysearch(FId, 1, Dependencies)],
+ GIds =:= lists:sort([GId1,GId2]),
+
+ begin
+ %% Do what the join does:
+ %% element(C1, G1) JoinOp element(C2, G2).
+ %% As for lu_skip: sometimes it would be
+ %% advantageously to assume some filter(s)
+ %% occurring before the join filter had been run
+ %% as well.
+ BindFun = fun(_Op, V) -> is_bindable(V) end,
+ {V1, JF1} =
+ unify_column(PatternFrame, PV1, C1, BindFun, Imported),
+ {V2, JF2} =
+ unify_column(JF1, PV2, C2, BindFun, Imported),
+ JF = unify(JoinOp, V1, V2, JF2, BindFun, Imported),
+
+ %% "Run" the filter:
+ SFs = safe_filter(set_line(Fil, 0), PatternFrames,
+ BindFun, State, Imported),
+ JImp = qlc:vars([SFs, JF]), % kludge
+ lists:all(fun(Frame) ->
+ bindings_is_subset(Frame, JF, JImp)
+ end, SFs) andalso SFs =/= []
+ end],
+ family_list(ColFil).
+
+filter_info(FilterData, AllIVs, Dependencies, State) ->
+ FilterList = filter_list(FilterData, Dependencies, State),
+ Filter0 = set_line(filters_as_one(FilterList), 0),
+ Anon0 = 0,
+ {Filter, Anon1} = anon_var(Filter0, Anon0),
+ Imported = ordsets:subtract(qlc:vars(Filter), % anonymous too
+ ordsets:from_list(AllIVs)),
+ {Filter, Anon1, Imported}.
+
+%% Selects the guard filters. Other filters than guard filters are
+%% ignored when trying to find constants and join columns. Note: there
+%% must not occur any non-guard filter between a guard filter and the
+%% generator(s) the guard filter depends on. The reason is that such a
+%% filter could fail for some object(s) excluded by lookup or join. If
+%% the failing filter is placed _after_ the guard filter, the failing
+%% objects have already been filtered out by the guard filter.
+%% Note: guard filters using variables from one generator are allowed
+%% to be placed after further generators (the docs states otherwise, but
+%% this seems to be common practice).
+filter_list(FilterData, Dependencies, State) ->
+ RDs = State#state.records,
+ sel_gf(FilterData, 1, Dependencies, RDs, [], []).
+
+sel_gf([], _N, _Deps, _RDs, _Gens, _Gens1) ->
+ [];
+sel_gf([{#qid{no = N}=Id,{fil,F}}=Fil | FData], N, Deps, RDs, Gens, Gens1) ->
+ case erl_lint:is_guard_test(F, RDs) of
+ true ->
+ {value, {Id,GIds}} = lists:keysearch(Id, 1, Deps),
+ case length(GIds) =< 1 of
+ true ->
+ case generators_in_scope(GIds, Gens1) of
+ true ->
+ [Fil|sel_gf(FData, N+1, Deps, RDs, Gens, Gens1)];
+ false ->
+ sel_gf(FData, N + 1, Deps, RDs, [], [])
+ end;
+ false ->
+ case generators_in_scope(GIds, Gens) of
+ true ->
+ [Fil | sel_gf(FData, N + 1, Deps, RDs, Gens, [])];
+ false ->
+ sel_gf(FData, N + 1, Deps, RDs, [], [])
+ end
+ end;
+ false ->
+ sel_gf(FData, N + 1, Deps, RDs, [], [])
+ end;
+sel_gf(FData, N, Deps, RDs, Gens, Gens1) ->
+ sel_gf(FData, N + 1, Deps, RDs, [N | Gens], [N | Gens1]).
+
+generators_in_scope(GenIds, GenNumbers) ->
+ lists:all(fun(#qid{no=N}) -> lists:member(N, GenNumbers) end, GenIds).
+
+pattern_frame(GeneratorData, BindFun, Anon1, State) ->
+ Frame0 = [],
+ {PatternFrame, _Anon2, PatternVars} =
+ lists:foldl(fun({QId,{gen,Pattern,_}}, {F0,An0,PVs}) ->
+ {F1, An1, PV} =
+ pattern(Pattern, An0, F0, BindFun, State),
+ {F1, An1, [{QId,PV} | PVs]}
+ end, {Frame0, Anon1, []}, GeneratorData),
+ {PatternFrame, PatternVars}.
+
+const_selector(Imported) ->
+ selector(Imported, fun is_const/2).
+
+no_const_selector(Imported) ->
+ selector(Imported, fun(V, I) -> not is_const(V, I) end).
+
+selector(Imported, TestFun) ->
+ fun(_Frame) ->
+ fun(Value) ->
+ case TestFun(Value, Imported) of
+ true ->
+ {yes, Value};
+ false ->
+ no
+ end
+ end
+ end.
+
+bind_no_const(Value, Imported) ->
+ case is_const(Value, Imported) of
+ true ->
+ false;
+ false ->
+ is_bindable(Value)
+ end.
+
+%% Tuple tails are variables, never constants.
+is_const(Value, Imported) ->
+ %% is_bindable() has checked that E is normalisable.
+ [] =:= ordsets:to_list(ordsets:subtract(qlc:vars(Value), Imported)).
+
+is_bindable(Value) ->
+ case normalise(Value) of
+ {ok, _C} ->
+ true;
+ not_ok ->
+ false
+ end.
+
+pattern(P0, AnonI, Frame0, BindFun, State) ->
+ P1 = try
+ expand_pattern_records(P0, State)
+ catch _:_ -> P0 % template, records already expanded
+ end,
+ %% Makes test for equality simple:
+ P2 = set_line(P1, 0),
+ {P3, AnonN} = anon_var(P2, AnonI),
+ {P4, F1} = match_in_pattern(tuple2cons(P3), Frame0, BindFun),
+ {P, F2} = element_calls(P4, F1, BindFun, _Imp=[]), % kludge for templates
+ {var, _, PatternVar} = UniqueVar = unique_var(),
+ F = unify('=:=', UniqueVar, P, F2, BindFun, _Imported = []),
+ {F, AnonN, PatternVar}.
+
+frame2frames(failed) ->
+ [];
+frame2frames(F) ->
+ [F].
+
+match_in_pattern({match, _, E10, E20}, F0, BF) ->
+ {E1, F1} = match_in_pattern(E10, F0, BF),
+ {E2, F} = match_in_pattern(E20, F1, BF),
+ %% This is for join: chosing a constant could "hide" a variable.
+ E = case BF('=:=', E1) of
+ true -> E1;
+ false -> E2
+ end,
+ {E, unify('=:=', E1, E2, F, BF, _Imported = [])};
+match_in_pattern(T, F0, BF) when is_tuple(T) ->
+ {L, F} = match_in_pattern(tuple_to_list(T), F0, BF),
+ {list_to_tuple(L), F};
+match_in_pattern([E0 | Es0], F0, BF) ->
+ {E, F1} = match_in_pattern(E0, F0, BF),
+ {Es, F} = match_in_pattern(Es0, F1, BF),
+ {[E | Es], F};
+match_in_pattern(E, F, _BF) ->
+ {E, F}.
+
+-define(ANON_VAR(N), N).
+
+anon_var(E, AnonI) ->
+ var_mapfold(fun({var, L, '_'}, N) ->
+ {{var, L, ?ANON_VAR(N)}, N+1};
+ (Var, N) -> {Var, N}
+ end, AnonI, E).
+
+set_line(T, L) ->
+ map_lines(fun(_L) -> L end, T).
+
+-record(fstate, {state, bind_fun, imported}).
+
+filter(_E, []=Frames0, _BF, _State, _Imported) ->
+ Frames0;
+filter(E0, Frames0, BF, State, Imported) ->
+ E = pre_expand(E0),
+ FState = #fstate{state = State, bind_fun = BF, imported = Imported},
+ filter1(E, Frames0, FState).
+
+%% One frame for each path through the and/or expression.
+%%
+%% "A xor B" is equal to "(A and not B) or (not A and B)".
+%% Ignoring "not B" and "not A" this is the same as "A or B";
+%% "xor" can be handled just as "or".
+%%
+%% One must handle filters with care, both when joining and when
+%% looking up values. The reference is a nested loop: if the filter
+%% fails for some combination of values, it must fail also when
+%% looking up values or joining. In other words, the excluded
+%% combinations of values must not evaluate to anything but 'false'.
+%% Filters looking like guards are allowed to fail since for such
+%% filter the so called guard semantics ensures that the value is
+%% 'false' if it is not 'true'. This behavior was inherited from the
+%% ordinary list comprehension, where it has been considered a bug
+%% kept for backward compatibility. Now it has become part of the QLC
+%% semantics, and hard to change (at least in the qlc module).
+%%
+%% A special case is =/2. If there is a chance that the =/2 fails
+%% (badmatch) for some combination of values, that combination cannot
+%% be excluded. If the variable is bound only once, it is OK, but not
+%% twice (or more). The current implementation does not handle =/2 at
+%% all (except in generator patterns).
+
+filter1({op, _, Op, L0, R0}, Fs, FS) when Op =:= '=:='; Op =:= '==' ->
+ #fstate{state = S, bind_fun = BF, imported = Imported} = FS,
+ %% In the transformed code there are no records in lookup values
+ %% because records are expanded away in prep_expr.
+ lists:flatmap(fun(F0) ->
+ {L, F1} = prep_expr(L0, F0, S, BF, Imported),
+ {R, F2} = prep_expr(R0, F1, S, BF, Imported),
+ case unify(Op, L, R, F2, BF, Imported) of
+ failed -> [];
+ F -> [F]
+ end
+ end, Fs);
+filter1({op, _, Op, L, R}, Fs, FS) when Op =:= 'and'; Op =:= 'andalso' ->
+ filter1(R, filter1(L, Fs, FS), FS);
+filter1({op, _, Op, L, R}, Fs, FS) when Op =:= 'or';
+ Op =:= 'orelse';
+ Op =:= 'xor' ->
+ filter1(L, Fs, FS) ++ filter1(R, Fs, FS);
+filter1({atom,_,Atom}, _Fs, _FS) when Atom =/= true ->
+ [];
+filter1({call,L,{remote,_,{atom,_,erlang},{atom,_,is_record}},[T,R]},
+ Fs, FS) ->
+ filter1({op,L,'=:=',{call,L,{remote,L,{atom,L,erlang},{atom,L,element}},
+ [{integer,L,1},T]},R},
+ Fs, FS);
+%% erlang:is_record/3 (the size information is ignored):
+filter1({call,L,{remote,L1,{atom,_,erlang}=M,{atom,L2,is_record}},[T,R,_Sz]},
+ Fs, FS) ->
+ filter1({call,L,{remote,L1,M,{atom,L2,is_record}},[T,R]}, Fs, FS);
+filter1(_E, Fs, _FS) ->
+ Fs.
+
+%% filter() tries to extract as much information about constant
+%% columns as possible. It ignores those parts of the filter that are
+%% uninteresting. safe_filter() on the other hand ensures that the
+%% bindings returned capture _all_ aspects of the filter (wrt BF).
+safe_filter(_E, []=Frames0, _BF, _State, _Imported) ->
+ Frames0;
+safe_filter(E0, Frames0, BF, State, Imported) ->
+ E = pre_expand(E0),
+ FState = #fstate{state = State, bind_fun = BF, imported = Imported},
+ safe_filter1(E, Frames0, FState).
+
+safe_filter1({op, _, Op, L0, R0}, Fs, FS) when Op =:= '=:='; Op =:= '==' ->
+ #fstate{state = S, bind_fun = BF, imported = Imported} = FS,
+ lists:flatmap(fun(F0) ->
+ {L, F1} = prep_expr(L0, F0, S, BF, Imported),
+ {R, F2} = prep_expr(R0, F1, S, BF, Imported),
+ case safe_unify(Op, L, R, F2, BF, Imported) of
+ failed -> [];
+ F -> [F]
+ end
+ end, Fs);
+safe_filter1({op, _, Op, L, R}, Fs, FS) when Op =:= 'and'; Op =:= 'andalso' ->
+ safe_filter1(R, safe_filter1(L, Fs, FS), FS);
+safe_filter1({op, _, Op, L, R}, Fs, FS) when Op =:= 'or'; Op =:= 'orelse' ->
+ safe_filter1(L, Fs, FS) ++ safe_filter1(R, Fs, FS);
+safe_filter1({atom,_,true}, Fs, _FS) ->
+ Fs;
+safe_filter1(_E, _Fs, _FS) ->
+ [].
+
+%% Substitutions:
+%% M:F() for {M,F}(); erlang:F() for F(); is_record() for record().
+pre_expand({call,L1,{atom,L2,record},As}) ->
+ pre_expand({call,L1,{atom,L2,is_record},As});
+pre_expand({call,L,{atom,_,_}=F,As}) ->
+ pre_expand({call,L,{remote,L,{atom,L,erlang},F},As});
+pre_expand({call,L,{tuple,_,[M,F]},As}) ->
+ pre_expand({call,L,{remote,L,M,F},As});
+pre_expand(T) when is_tuple(T) ->
+ list_to_tuple(pre_expand(tuple_to_list(T)));
+pre_expand([E | Es]) ->
+ [pre_expand(E) | pre_expand(Es)];
+pre_expand(T) ->
+ T.
+
+%% -> [ [{{QualifierNumber,ColumnNumber}, [Value]}] ]
+frames_to_columns([], _PatternVars, _DerefFun, _SelectorFun, _Imp, _CompOp) ->
+ [];
+frames_to_columns(Fs, PatternVars, DerefFun, SelectorFun, Imp, CompOp) ->
+ %% It is important that *the same* variables are introduced for
+ %% columns in every frame. (When trying to find constant columns
+ %% it doesn't matter, but when trying to find joined columns, the
+ %% same variables have to be the representatives in every frame.)
+ SizesVarsL =
+ [begin
+ PatVar = {var,0,PV},
+ PatternSizes = [pattern_size([F], PatVar, false) ||
+ F <- Fs],
+ MaxPZ = lists:max([0 | PatternSizes -- [undefined]]),
+ Vars = pat_vars(MaxPZ),
+ {PatternId#qid.no, PatVar, PatternSizes, Vars}
+ end || {PatternId, PV} <- PatternVars],
+ BF = fun(_Op, Value) -> is_bindable(Value) end,
+ Fun = fun({_PatN, PatVar, PatSizes, Vars}, Frames) ->
+ [unify('=:=', pat_tuple(Sz, Vars), PatVar, Frame, BF, Imp) ||
+ {Sz, Frame} <- lists:zip(PatSizes, Frames)]
+ end,
+ NFs = lists:foldl(Fun, Fs, SizesVarsL),
+ [frames2cols(NFs, PatN, PatSizes, Vars, DerefFun, SelectorFun, CompOp) ||
+ {PatN, _PatVar, PatSizes, Vars} <- SizesVarsL].
+
+frames2cols(Fs, PatN, PatSizes, Vars, DerefFun, SelectorFun, CompOp) ->
+ Rs = [ begin
+ RL = [{{PatN,Col},cons2tuple(element(2, Const))} ||
+ {V, Col} <- lists:zip(sublist(Vars, PatSz),
+ seq(1, PatSz)),
+ %% Do not handle the case where several
+ %% values compare equal, e.g. "X =:= 1
+ %% andalso X == 1.0". Looking up both
+ %% values or one of them won't always do
+ %% because it is more or less undefined
+ %% whether the table returns the given key
+ %% or the one stored in the table. Or
+ %% rather, it would be strange if the table
+ %% did not return the stored key upon
+ %% request, but the 'lookup_fun' function
+ %% may have to add the given key (see also
+ %% gb_table in qlc(3)). (Not a very strong
+ %% argument. "X =:= 1" could (should?) be
+ %% seen as a bug.) Note: matching tables
+ %% cannot skip the filter, but looking up
+ %% one of the values should be OK.
+ tl(Consts = DerefFun(V, F)) =:= [],
+ (Const = (SelectorFun(F))(hd(Consts))) =/= no],
+ sofs:relation(RL) % possibly empty
+ end || {F,PatSz} <- lists:zip(Fs, PatSizes)],
+ Ss = sofs:from_sets(Rs),
+ %% D: columns occurring in every frame (path).
+ D = sofs:intersection(sofs:projection(fun(S) -> sofs:projection(1, S) end,
+ Ss)),
+ Cs = sofs:restriction(sofs:relation_to_family(sofs:union(Ss)), D),
+ [C || {_,Vs}=C <- sofs:to_external(Cs), not col_ignore(Vs, CompOp)].
+
+pat_vars(N) ->
+ [unique_var() || _ <- seq(1, N)].
+
+pat_tuple(Sz, Vars) when is_integer(Sz), Sz > 0 ->
+ TupleTail = unique_var(),
+ {cons_tuple, list2cons(sublist(Vars, Sz) ++ TupleTail)};
+pat_tuple(_, _Vars) ->
+ unique_var().
+
+%% Do not handle tests as "X =:= 1.0 orelse X == 1" either.
+%% Similar problems as described above.
+col_ignore(_Vs, '=:=') ->
+ false;
+col_ignore(Vs, '==') ->
+ length(Vs) =/= length(lists:usort([element(2, normalise(V)) || V <- Vs])).
+
+pattern_sizes(PatternVars, Fs) ->
+ [{QId#qid.no, Size} ||
+ {QId,PV} <- PatternVars,
+ undefined =/= (Size = pattern_size(Fs, {var,0,PV}, true))].
+
+pattern_size(Fs, PatternVar, Exact) ->
+ Fun = fun(F) -> (deref_pattern(_Imported = []))(PatternVar, F) end,
+ Derefs = lists:flatmap(Fun, Fs),
+ Szs = [pattern_sz(Cs, 0, Exact) || {cons_tuple, Cs} <- Derefs],
+ case lists:usort(Szs) of
+ [Sz] when is_integer(Sz), Sz >= 0 -> Sz;
+ [] when not Exact -> 0;
+ _ -> undefined
+ end.
+
+pattern_sz({cons,_,_C,E}, Col, Exact) ->
+ pattern_sz(E, Col+1, Exact);
+pattern_sz({nil,_}, Sz, _Exact) ->
+ Sz;
+pattern_sz(_, _Sz, true) ->
+ undefined;
+pattern_sz(_, Sz, false) ->
+ Sz.
+
+deref_pattern(Imported) ->
+ fun(PV, F) -> deref_values(PV, F, Imported) end.
+
+prep_expr(E, F, S, BF, Imported) ->
+ element_calls(tuple2cons(expand_expr_records(E, S)), F, BF, Imported).
+
+unify_column(Frame, Var, Col, BindFun, Imported) ->
+ Call = {call,0,{atom,0,element},[{integer,0,Col}, {var,0,Var}]},
+ element_calls(Call, Frame, BindFun, Imported).
+
+%% cons_tuple is used for representing {V1, ..., Vi | TupleTail}.
+%%
+%% Tests like "element(2, X) =:= a" are represented by "tuple tails":
+%% {_, a | _}. The tail may be unified later, when more information
+%% about the size of the tuple is known.
+element_calls({call,_,{remote,_,{atom,_,erlang},{atom,_,element}},
+ [{integer,_,I},Term0]}, F0, BF, Imported) when I > 0 ->
+ TupleTail = unique_var(),
+ VarsL = [unique_var() || _ <- lists:seq(1, I)],
+ Vars = VarsL ++ TupleTail,
+ Tuple = {cons_tuple, list2cons(Vars)},
+ VarI = lists:nth(I, VarsL),
+ {Term, F} = element_calls(Term0, F0, BF, Imported),
+ {VarI, unify('=:=', Tuple, Term, F, BF, Imported)};
+element_calls({call,L1,{atom,_,element}=E,As}, F0, BF, Imported) ->
+ %% erl_expand_records should add "erlang:"...
+ element_calls({call,L1,{remote,L1,{atom,L1,erlang},E}, As}, F0, BF,
+ Imported);
+element_calls(T, F0, BF, Imported) when is_tuple(T) ->
+ {L, F} = element_calls(tuple_to_list(T), F0, BF, Imported),
+ {list_to_tuple(L), F};
+element_calls([E0 | Es0], F0, BF, Imported) ->
+ {E, F1} = element_calls(E0, F0, BF, Imported),
+ {Es, F} = element_calls(Es0, F1, BF, Imported),
+ {[E | Es], F};
+element_calls(E, F, _BF, _Imported) ->
+ {E, F}.
+
+unique_var() ->
+ {var, 0, make_ref()}.
+
+is_unique_var({var, _L, V}) ->
+ is_reference(V).
+
+expand_pattern_records(P, State) ->
+ E = {'case',0,{atom,0,true},[{clause,0,[P],[],[{atom,0,true}]}]},
+ {'case',_,_,[{clause,0,[NP],_,_}]} = expand_expr_records(E, State),
+ NP.
+
+expand_expr_records(E, State) ->
+ RecordDefs = State#state.records,
+ Forms = RecordDefs ++ [{function,1,foo,0,[{clause,1,[],[],[pe(E)]}]}],
+ [{function,_,foo,0,[{clause,_,[],[],[NE]}]}] =
+ erl_expand_records:module(Forms, [no_strict_record_tests]),
+ NE.
+
+%% Partial evaluation.
+pe({op,Line,Op,A}) ->
+ erl_eval:partial_eval({op,Line,Op,pe(A)});
+pe({op,Line,Op,L,R}) ->
+ erl_eval:partial_eval({op,Line,Op,pe(L),pe(R)});
+pe(T) when is_tuple(T) ->
+ list_to_tuple(pe(tuple_to_list(T)));
+pe([E | Es]) ->
+ [pe(E) | pe(Es)];
+pe(E) ->
+ E.
+
+unify(Op, E1, E2, F, BF, Imported) ->
+ unify(Op, E1, E2, F, BF, Imported, false).
+
+safe_unify(Op, E1, E2, F, BF, Imported) ->
+ unify(Op, E1, E2, F, BF, Imported, true).
+
+unify(_Op, _E1, _E2, failed, _BF, _Imported, _Safe) -> % contradiction
+ failed;
+unify(_Op, E, E, F, _BF, _Imported, _Safe) ->
+ F;
+unify(Op, {var, _, _}=Var, E2, F, BF, Imported, Safe) ->
+ extend_frame(Op, Var, E2, F, BF, Imported, Safe);
+unify(Op, E1, {var, _, _}=Var, F, BF, Imported, Safe) ->
+ extend_frame(Op, Var, E1, F, BF, Imported, Safe);
+unify(Op, {cons_tuple, Es1}, {cons_tuple, Es2}, F, BF, Imported, Safe) ->
+ unify(Op, Es1, Es2, F, BF, Imported, Safe);
+unify(Op, {cons, _, L1, R1}, {cons, _, L2, R2}, F, BF, Imported, Safe) ->
+ E = unify(Op, L1, L2, F, BF, Imported, Safe),
+ unify(Op, R1, R2, E, BF, Imported, Safe);
+unify(Op, E1, E2, F, _BF, _Imported, Safe) ->
+ try
+ {ok, C1} = normalise(E1),
+ {ok, C2} = normalise(E2),
+ if
+ Op =:= '=:=', C1 =:= C2 ->
+ F;
+ Op =:= '==', C1 == C2 ->
+ F;
+ true ->
+ failed
+ end
+ catch error:_ when Safe -> failed;
+ error:_ when not Safe -> F % ignored
+ end.
+%% Binaries are not handled at all (by unify).
+
+%% Note that a variable can be bound to several values, for instance:
+%% X =:= 3, X == 3.0. As a consequence, deref() returns a list of
+%% values.
+
+%% Binding a variable to several values makes the unification and
+%% dereferencing more complicated. An alternative would be not to try
+%% to find lookup values for such QLCs at all. That might have been a
+%% better design decision.
+
+-record(bind, {var, value, op}).
+
+extend_frame(Op, Var, Value, F, BF, Imported, Safe) ->
+ case var_values(Var, F) of
+ [] ->
+ case Value of
+ {var, _, _} ->
+ case var_values(Value, F) of
+ [] ->
+ add_binding(Op, Value, Var, F, BF, Imported, Safe);
+ ValsOps ->
+ maybe_add_binding(ValsOps, Op, Value, Var, F,
+ BF, Imported, Safe)
+ end;
+ _ ->
+ add_binding(Op, Var, Value, F, BF, Imported, Safe)
+ end;
+ ValsOps ->
+ maybe_add_binding(ValsOps, Op, Var, Value, F, BF, Imported, Safe)
+ end.
+
+maybe_add_binding(ValsOps, Op, Var, Value, F0, BF, Imported, Safe) ->
+ case unify_var_bindings(ValsOps, Op, Value, F0, BF, Imported, Safe) of
+ failed ->
+ failed;
+ F ->
+ case already_bound(Op, Var, Value, F) of
+ true ->
+ F;
+ false ->
+ add_binding(Op, Var, Value, F, BF, Imported, Safe)
+ end
+ end.
+
+already_bound(Op, Var, Value, F) ->
+ %% Note: all variables are treated as imported. The dereferenced
+ %% values must not depend on Imported.
+ BFun = fun(_DV, BOp) -> Op =:= BOp end,
+ DerefValue = deref_value(Value, Op, F, BFun, all),
+ DerefVar = deref_var(Var, F, BFun, all),
+ DerefValue -- DerefVar =:= [].
+
+unify_var_bindings([], _Op, _Value, F, _BF, _Imported, _Safe) ->
+ F;
+unify_var_bindings([{VarValue, Op2} | Bindings],
+ Op1, Value, F0, BF, Imported, Safe) ->
+ Op = deref_op(Op1, Op2),
+ case unify(Op, VarValue, Value, F0, BF, Imported, Safe) of
+ failed ->
+ failed;
+ F ->
+ unify_var_bindings(Bindings, Op1, Value, F, BF, Imported, Safe)
+ end.
+
+deref_op('=:=', '=:=') ->
+ '=:=';
+deref_op(_, _) ->
+ '=='.
+
+%%% Note: usort works; {integer,L,3} does not match {float,L,3.0}.
+
+var_values(Var, Frame) ->
+ [{Value, Op} ||
+ #bind{value = Value, op = Op} <- var_bindings(Var, Frame)].
+
+deref_var(Var, Frame, Imported) ->
+ deref_var(Var, Frame, fun(_DV, _Op) -> true end, Imported).
+
+deref_var(Var, Frame, BFun, Imported) ->
+ lists:usort([ValOp ||
+ #bind{value = Value, op = Op} <- var_bindings(Var, Frame),
+ ValOp <- deref_value(Value, Op, Frame, BFun, Imported)]).
+
+deref_value(Value, Op, Frame, BFun, Imported) ->
+ lists:usort([{Val,value_op(ValOp, Op, Imported)} ||
+ {Val,_Op}=ValOp <- deref(Value, Frame, BFun, Imported)]).
+
+add_binding(Op, Var0, Value0, F, BF, Imported, Safe) ->
+ {Var, Value} = maybe_swap_var_value(Var0, Value0, F, Imported),
+ case BF(Op, Value) of
+ true ->
+ add_binding2(Var, Value, Op, F);
+ false when Safe ->
+ failed;
+ false when not Safe ->
+ F
+ end.
+
+add_binding2(Var, Value, Op, F) ->
+ case occurs(Var, Value, F) of
+ true ->
+ failed;
+ false ->
+ [#bind{var = Var, value = Value, op = Op} | F]
+ end.
+
+%% Ensure that imported variables are visible in the dereferenced
+%% value by pushing them to the end of the binding chain. Be careful
+%% not to introduce loops.
+maybe_swap_var_value(Var, Value, Frame, Imported) ->
+ case do_swap_var_value(Var, Value, Frame, Imported) of
+ true ->
+ {Value, Var};
+ false ->
+ {Var, Value}
+ end.
+
+do_swap_var_value({var, _, V1}=Var1, {var, _, V2}=Var2, F, Imported) ->
+ case swap_vv(Var1, Var2, F) of
+ [] ->
+ case swap_vv(Var2, Var1, F) of
+ [] ->
+ ordsets:is_element(V1, Imported) andalso
+ not ordsets:is_element(V2, Imported);
+ _Bs ->
+ true
+ end;
+ _Bs ->
+ false
+ end;
+do_swap_var_value(_, _, _F, _Imp) ->
+ false.
+
+swap_vv(V1, V2, F) ->
+ [V || #bind{value = V} <- var_bindings(V1, F), V =:= V2].
+
+normalise(E) ->
+ %% Tuple tails are OK.
+ case catch erl_parse:normalise(var2const(cons2tuple(E))) of
+ {'EXIT', _} ->
+ not_ok;
+ C ->
+ {ok, C}
+ end.
+
+occurs(V, V, _F) ->
+ true;
+occurs(V, {var, _, _} = Var, F) ->
+ lists:any(fun(B) -> occurs(V, B#bind.value, F) end, var_bindings(Var, F));
+occurs(V, T, F) when is_tuple(T) ->
+ lists:any(fun(E) -> occurs(V, E, F) end, tuple_to_list(T));
+occurs(V, [E | Es], F) ->
+ occurs(V, E, F) orelse occurs(V, Es, F);
+occurs(_V, _E, _F) ->
+ false.
+
+deref_values(E, Frame, Imported) ->
+ deref_values(E, Frame, fun(_DV, _Op) -> true end, Imported).
+
+deref_values(E, Frame, BFun, Imported) ->
+ lists:usort([V ||
+ {V, Op} <- deref(E, Frame, BFun, Imported),
+ BFun(V, Op)]).
+
+deref(E, F, Imp) ->
+ BFun = fun(_DV, _Op) -> true end,
+ deref(E, F, BFun, Imp).
+
+deref({var, _, _}=V, F, BFun, Imp) ->
+ DBs = lists:flatmap(fun(B) -> deref_binding(B, F, BFun, Imp)
+ end, var_bindings(V, F)),
+ case DBs of
+ [] ->
+ [{V, '=:='}];
+ _ ->
+ lists:usort(DBs)
+ end;
+deref(T, F, BFun, Imp) when is_tuple(T) ->
+ [{list_to_tuple(DL), Op} ||
+ {DL, Op} <- deref(tuple_to_list(T), F, BFun, Imp)];
+deref(Es, F, BFun, Imp) when is_list(Es) ->
+ L = [deref(C, F, BFun, Imp) || C <- Es],
+ lists:usort([deref_list(S) || S <- all_comb(L)]);
+deref(E, _F, _BFun, _Imp) ->
+ [{E, '=:='}].
+
+var_bindings(Var, F) ->
+ [B || #bind{var = V}=B <- F, V =:= Var].
+
+deref_binding(Bind, Frame, BFun, Imp) ->
+ #bind{value = Value, op = Op0} = Bind,
+ [{Val, Op} ||
+ {Val, _Op}=ValOp <- deref(Value, Frame, BFun, Imp),
+ BFun(Val, Op = value_op(ValOp, Op0, Imp))].
+
+deref_list(L) ->
+ Op = case lists:usort([Op || {_Val, Op} <- L]) of
+ ['=:='] ->
+ '=:=';
+ _ ->
+ '=='
+ end,
+ {[V || {V, _Op} <- L], Op}.
+
+value_op({_V, '=='}, _BindOp, _Imp) ->
+ '==';
+value_op({_V, '=:='}, _BindOp='=:=', _Imp) ->
+ '=:=';
+value_op({V, '=:='}, _BindOp='==', Imp) ->
+ case free_of_integers(V, Imp) of
+ true ->
+ '=:=';
+ false ->
+ '=='
+ end.
+
+all_comb([]) ->
+ [[]];
+all_comb([Cs | ICs]) ->
+ [[C | L] || C <- Cs, L <- all_comb(ICs)].
+
+%% "Free of integers" here means that there are not imported variables
+%% in V (which could take on integer values), but there may be other
+%% variables in V.
+free_of_integers(V, Imported) ->
+ not has_integer(V) andalso not has_imported_vars(V, Imported).
+
+%% Assumes that imported variables are representatives, if Value is a
+%% dereferenced value.
+has_imported_vars(Value, all) ->
+ qlc:vars(Value) =/= [];
+has_imported_vars(Value, Imported) ->
+ [Var || Var <- qlc:vars(Value), lists:member(Var, Imported)] =/= [].
+
+has_integer(Abstr) ->
+ try
+ has_int(Abstr)
+ catch throw:true -> true
+ end.
+
+has_int({integer,_,I}) when float(I) == I ->
+ throw(true);
+has_int({float,_,F}) when round(F) == F ->
+ throw(true);
+has_int(T) when is_tuple(T) ->
+ has_int(tuple_to_list(T));
+has_int([E | Es]) ->
+ has_int(E),
+ has_int(Es);
+has_int(_) ->
+ false.
+
+tuple2cons({tuple, _, Es}) ->
+ {cons_tuple, list2cons(tuple2cons(Es))};
+tuple2cons(T) when is_tuple(T) ->
+ list_to_tuple(tuple2cons(tuple_to_list(T)));
+tuple2cons([E | Es]) ->
+ [tuple2cons(E) | tuple2cons(Es)];
+tuple2cons(E) ->
+ E.
+
+list2cons([E | Es]) ->
+ {cons, 0, E, list2cons(Es)};
+list2cons([]) ->
+ {nil, 0};
+list2cons(E) ->
+ E.
+
+%% Returns {..., Variable} if Variable is a tuple tail.
+cons2tuple({cons_tuple, Es}) ->
+ {tuple, 0, cons2list(Es)};
+cons2tuple(T) when is_tuple(T) ->
+ list_to_tuple(cons2tuple(tuple_to_list(T)));
+cons2tuple([E | Es]) ->
+ [cons2tuple(E) | cons2tuple(Es)];
+cons2tuple(E) ->
+ E.
+
+cons2list({cons, _, L, R}) ->
+ [cons2tuple(L) | cons2list(R)];
+cons2list({nil, _}) ->
+ [];
+cons2list(E) -> % tuple tail (always a variable)
+ [cons2tuple(E)].
+
+%% Returns true if all bindings in F1 also occur in F2.
+%% Viewing F1 and F2 as sets, the fact that F1 is a subset of F2 iff
+%% F1 union F2 is equal to F2 is used. (This should take care of
+%% issues with anonymous variables.)
+bindings_is_subset(F1, F2, Imported) ->
+ BF = fun(_Op, _Value) -> true end, % don't need any test here
+ %% Extend F2 with the bindings in F1:
+ F = lists:foldl(fun(#bind{var = V, value = Value, op = Op}, Frame) ->
+ unify(Op, V, Value, Frame, BF, Imported)
+ end, F2, F1),
+ bindings_subset(F, F2, Imported) andalso bindings_subset(F2, F, Imported).
+
+bindings_subset(F1, F2, Imp) ->
+ Vars = lists:usort([V || #bind{var = V} <- F1, not is_unique_var(V)]),
+ lists:all(fun(V) ->
+ deref_var(V, F1, Imp) =:= deref_var(V, F2, Imp)
+ end, Vars).
+
+%% Recognizes all QLCs on the form [T || P <- LE, F] such that
+%% ets:fun2ms(fun(P) when F -> T end) is a match spec. This is OK with
+%% the guard semantics implemented in filter/_ below. If one chooses
+%% not to have guard semantics, affected filters will have to be
+%% recognized and excluded here as well.
+try_ms(E, P, Fltr, State) ->
+ L = 1,
+ Fun = {'fun',L,{clauses,[{clause,L,[P],[[Fltr]],[E]}]}},
+ Expr = {call,L,{remote,L,{atom,L,ets},{atom,L,fun2ms}},[Fun]},
+ Form0 = {function,L,foo,0,[{clause,L,[],[],[Expr]}]},
+ Form = restore_line_numbers(Form0),
+ X = ms_transform:parse_transform(State#state.records ++ [Form], []),
+ case catch
+ begin
+ {function,L,foo,0,[{clause,L,[],[],[MS0]}]} = lists:last(X),
+ MS = erl_parse:normalise(var2const(MS0)),
+ XMS = ets:match_spec_compile(MS),
+ true = is_binary(XMS),
+ {ok, MS, MS0}
+ end of
+ {'EXIT', _Reason} ->
+ no;
+ Reply ->
+ Reply
+ end.
+
+filters_as_one([]) ->
+ {atom, 0, true};
+filters_as_one(FilterData) ->
+ [{_,{fil,Filter1}} | Filters] = lists:reverse(FilterData),
+ lists:foldr(fun({_QId,{fil,Filter}}, AbstF) ->
+ {op,0,'andalso',Filter,AbstF}
+ end, Filter1, Filters).
+
+qual_data(Qualifiers) ->
+ F = fun(T) ->
+ [{QId,Q} || {QId,_,_,Q} <- Qualifiers, element(1,Q) =:= T]
+ end,
+ {F(fil), F(gen)}.
+
+set_field(Pos, Fs, Data) ->
+ lists:sublist(Fs, Pos-1) ++ [Data] ++ lists:nthtail(Pos, Fs).
+
+qdata([{#qid{no = QIdNo},{_QIVs,{{gen,_P,LE,_GV},GoI,SI}}} | QCs], L) ->
+ Init = case LE of
+ {join, Op, Q1, Q2, H1, H2, Cs1_0, Cs2_0} ->
+ Cs1 = qcon(Cs1_0),
+ Cs2 = qcon(Cs2_0),
+ %% -- R12B-3: {nil,L}
+ %% R12B-4 --: {atom,L,v1}
+ Compat = {atom,L,v1}, % meant for redundant match spec
+ CF = closure({tuple,L,[Cs1,Cs2,Compat]}, L),
+ {tuple,L,[?A(join),?A(Op),?I(Q1),?I(Q2),H1,H2,CF]};
+ _ ->
+ closure(LE, L)
+ end,
+ %% Create qual_data (see qlc.erl):
+ {cons,L,{tuple,L,[?I(QIdNo),?I(GoI),?I(SI),{tuple,L,[?A(gen),Init]}]},
+ qdata(QCs, L)};
+qdata([{#qid{no = QIdNo},{_QIVs,{{fil,_F},GoI,SI}}} | QCs], L) ->
+ %% Create qual_data (see qlc.erl):
+ {cons,L,{tuple,L,[?I(QIdNo),?I(GoI),?I(SI),?A(fil)]},qdata(QCs, L)};
+qdata([], L) ->
+ {nil,L}.
+
+qcon(Cs) ->
+ list2cons([{tuple,0,[{integer,0,Col},list2cons(qcon1(ConstOps))]} ||
+ {Col,ConstOps} <- Cs]).
+
+qcon1(ConstOps) ->
+ [{tuple,0,[Const,abstr(Op, 0)]} || {Const,Op} <- ConstOps].
+
+%% The original code (in Source) is used for filters and the template
+%% since the translated code can have QLCs and we don't want them to
+%% be visible.
+qcode(E, QCs, Source, L) ->
+ CL = [begin
+ Bin = term_to_binary(C, [compressed]),
+ {bin, L, [{bin_element, L,
+ {string, L, binary_to_list(Bin)},
+ default, default}]}
+ end || {_,C} <- lists:keysort(1, [{qlc:template_state(),E} |
+ qcode(QCs, Source)])],
+ {'fun', L, {clauses, [{clause, L, [], [], [{tuple, L, CL}]}]}}.
+
+qcode([{_QId, {_QIvs, {{gen,P,_LE,_GV}, GoI, _SI}}} | QCs], Source) ->
+ [{GoI,undo_no_shadows(P)} | qcode(QCs, Source)];
+qcode([{QId, {_QIVs, {{fil,_F}, GoI, _SI}}} | QCs], Source) ->
+ {ok,OrigF} = dict:find(QId, Source),
+ [{GoI,undo_no_shadows(OrigF)} | qcode(QCs, Source)];
+qcode([], _Source) ->
+ [].
+
+closure(Code, L) ->
+ {'fun',L,{clauses,[{clause,L,[],[],[Code]}]}}.
+
+simple(L, Var, Init, Line) ->
+ {tuple,L,[?A(simple_v1),?A(Var),Init,?I(Line)]}.
+
+clauses([{QId,{QIVs,{QualData,GoI,S}}} | QCs], RL, Fun, Go, NGV, E, IVs,St) ->
+ ?DEBUG("QIVs = ~p~n", [QIVs]),
+ ?DEBUG("IVs = ~p~n", [IVs]),
+ ?DEBUG("GoI = ~p, S = ~p~n", [GoI, S]),
+ L = no_compiler_warning(get_lcid_line(QId#qid.lcid)),
+ Cs = case QualData of
+ {gen,P,_LE,GV} ->
+ generator(S, QIVs, P, GV, NGV, E, IVs, RL, Fun, Go,GoI,L,St);
+ {fil,F} ->
+ filter(F, L, QIVs, S, RL, Fun, Go, GoI, IVs, St)
+ end,
+ Cs ++ clauses(QCs, RL, Fun, Go, NGV, E, IVs, St);
+clauses([], _RL, _Fun, _Go, _NGV, _IVs, _E, _St) ->
+ [].
+
+final(RL, IVs, L, State) ->
+ IAs = replace(IVs, IVs, '_'),
+ AsL = pack_args([?I(0) | abst_vars([RL, '_', '_'] ++ IAs, L)], L, State),
+ Grd = [is_list_c(RL, L)],
+ Rev = {call,L,{remote,L,?A(lists),?A(reverse)},[?V(RL)]},
+ CL = {clause,L,AsL,[Grd],[Rev]},
+ AsF = pack_args([?I(0) | abst_vars(['_', '_', '_'] ++ IAs, L)], L, State),
+ CF = {clause,L,AsF,[],[?ABST_NO_MORE]},
+ [CL, CF].
+
+template(E, RL, Fun, Go, AT, L, IVs, State) ->
+ I = qlc:template_state(), GoI = qlc:template_state(),
+ ARL = {cons,L,E,abst_vars(RL, L)},
+ Next = next(Go, GoI, L),
+ As0 = abst_vars([RL, Fun, Go] ++ IVs, L),
+ As = pack_args([?I(I) | As0], L, State),
+ NAs = pack_args([Next, ARL] ++ abst_vars([Fun, Go] ++ IVs, L), L, State),
+ Grd = [is_list_c(RL, L)],
+ CL = {clause,L,As,[Grd],[{call,L,?V(Fun),NAs}]},
+
+ %% Extra careful here or arguments will be lifted into a wide fun.
+ F = case split_args([Next | As0], L, State) of
+ {ArgsL, ArgsT} ->
+ Call = {call,L,?V(Fun),ArgsL++[{var,L,AT}]},
+ {block,L,
+ [{match,L,{var,L,AT},ArgsT},
+ {'fun',L,{clauses,[{clause,L,[],[],[Call]}]}}]};
+ FNAs ->
+ {'fun',L,{clauses,[{clause,L,[],[],[{call,L,?V(Fun),FNAs}]}]}}
+ end,
+ CF = {clause,L,As,[],[?ABST_MORE(E, F)]},
+ [CL,CF].
+
+generator(S, QIVs, P, GV, NGV, E, IVs, RL, Fun, Go, GoI, L, State) ->
+ ComAs = abst_vars([RL, Fun, Go], L),
+ InitC = generator_init(S, L, GV, RL, Fun, Go, GoI, IVs, State),
+ As = [?I(S + 1)| ComAs ++ abst_vars(replace(QIVs -- [GV], IVs, '_'), L)],
+
+ MatchS = next(Go, GoI + 1, L),
+ AsM0 = [MatchS | ComAs ++ abst_vars(replace([GV], IVs, NGV), L)],
+ AsM = pack_args(AsM0, L, State),
+
+ ContS = ?I(S + 1),
+ QIVs__GV = QIVs -- [GV],
+ Tmp = replace([GV], replace(QIVs__GV, IVs, nil), NGV),
+ AsC = pack_args([ContS | ComAs ++ abst_vars(Tmp, L)], L, State),
+
+ DoneS = next(Go, GoI, L),
+ AsD0 = [DoneS | ComAs ++ abst_vars(replace(QIVs, IVs, nil), L)],
+ AsD = pack_args(AsD0, L, State),
+
+ CsL = generator_list(P, GV, NGV, As, AsM, AsC, AsD, Fun, L, State),
+ CsF = generator_cont(P, GV, NGV, E, As, AsM, AsC, AsD, Fun, L, State),
+ [InitC | CsL ++ CsF].
+
+generator_init(S, L, GV, RL, Fun, Go, GoI, IVs, State) ->
+ As0 = abst_vars([RL, Fun, Go] ++ replace([GV], IVs, '_'), L),
+ As = pack_args([?I(S) | As0], L, State),
+ Next = next(Go, GoI + 2, L),
+ NAs = pack_args([?I(S + 1) | replace([?V('_')], As0, Next)], L, State),
+ {clause,L,As,[],[{call,L,?V(Fun),NAs}]}.
+
+generator_list(P, GV, NGV, As, AsM, AsC, AsD, Fun, L, State) ->
+ As1 = pack_args(replace([?V(GV)], As, {cons,L,P,?V(NGV)}), L, State),
+ As2 = pack_args(replace([?V(GV)], As, {cons,L,?V('_'),?V(NGV)}), L,State),
+ As3 = pack_args(replace([?V(GV)], As, {nil,L}), L, State),
+ CM = {clause,L,As1,[],[{call,L,?V(Fun),AsM}]},
+ CC = {clause,L,As2,[],[{call,L,?V(Fun),AsC}]},
+ CD = {clause,L,As3,[],[{call,L,?V(Fun),AsD}]},
+ [CM, CC, CD].
+
+%% The clause 'CE' was added in R11B. The version of the generated was
+%% however not incremented.
+generator_cont(P, GV, NGV, E, As0, AsM, AsC, AsD, Fun, L, State) ->
+ As = pack_args(As0, L, State),
+ CF1 = ?ABST_MORE(P, ?V(NGV)),
+ CF2 = ?ABST_MORE(?V('_'), ?V(NGV)),
+ CF3 = ?ABST_NO_MORE,
+ CF4 = ?V(E),
+ CM = {clause,L,[CF1],[],[{call,L,?V(Fun),AsM}]},
+ CC = {clause,L,[CF2],[],[{call,L,?V(Fun),AsC}]},
+ CD = {clause,L,[CF3],[],[{call,L,?V(Fun),AsD}]},
+ CE = {clause,L,[CF4],[],[CF4]},
+ Cls = [CM, CC, CD, CE],
+ B = {'case',L,{call,L,?V(GV),[]},Cls},
+ [{clause,L,As,[],[B]}].
+
+filter(E, L, QIVs, S, RL, Fun, Go, GoI, IVs, State) ->
+ IAs = replace(QIVs, IVs, '_'),
+ As = pack_args([?I(S) | abst_vars([RL, Fun, Go] ++ IAs, L)], L, State),
+ NAs = abst_vars([RL, Fun, Go] ++ IVs, L),
+ TNext = next(Go, GoI + 1, L),
+ FNext = next(Go, GoI, L),
+ NAsT = pack_args([TNext | NAs], L, State),
+ NAsF = pack_args([FNext | NAs], L, State),
+ %% This is the "guard semantics" used in ordinary list
+ %% comprehension: if a filter looks like a guard test, it returns
+ %% 'false' rather than fails.
+ Body = case erl_lint:is_guard_test(E, State#state.records) of
+ true ->
+ CT = {clause,L,[],[[E]],[{call,L,?V(Fun),NAsT}]},
+ CF = {clause,L,[],[[?A(true)]],[{call,L,?V(Fun),NAsF}]},
+ [{'if',L,[CT,CF]}];
+ false ->
+ CT = {clause,L,[?A(true)],[],[{call,L,?V(Fun),NAsT}]},
+ CF = {clause,L,[?A(false)],[],[{call,L,?V(Fun),NAsF}]},
+ [{'case',L,E,[CT,CF]}]
+ end,
+ [{clause,L,As,[],Body}].
+
+pack_args(Args, L, State) ->
+ case split_args(Args, L, State) of
+ {ArgsL, ArgsT} ->
+ ArgsL ++ [ArgsT];
+ _ ->
+ Args
+ end.
+
+split_args(Args, L, State) when length(Args) > State#state.maxargs ->
+ {lists:sublist(Args, State#state.maxargs-1),
+ {tuple,L,lists:nthtail(State#state.maxargs-1, Args)}};
+split_args(Args, _L, _State) ->
+ Args.
+
+%% Replace every element in IEs that is a member of Es by R, keep all
+%% other elements as they are.
+replace(Es, IEs, R) ->
+ [case lists:member(E, Es) of
+ true -> R;
+ false -> E
+ end || E <- IEs].
+
+is_list_c(V, L) ->
+ {call,L,?A(is_list),[?V(V)]}.
+
+next(Go, GoI, L) ->
+ {call,L,?A(element),[?I(GoI),?V(Go)]}.
+
+aux_vars(Vars, LcN, AllVars) ->
+ [aux_var(Name, LcN, 0, 1, AllVars) || Name <- Vars].
+
+aux_var(Name, LcN, QN, N, AllVars) ->
+ qlc:aux_name(lists:concat([Name, LcN, '_', QN, '_']), N, AllVars).
+
+no_compiler_warning(L) ->
+ erl_parse:set_line(L, fun(Line) -> -abs(Line) end).
+
+abs_loc(L) ->
+ loc(erl_parse:set_line(L, fun(Line) -> abs(Line) end)).
+
+loc(L) ->
+ {location,Location} = erl_parse:get_attribute(L, location),
+ Location.
+
+list2op([E], _Op) ->
+ E;
+list2op([E | Es], Op) ->
+ {op,0,Op,E,list2op(Es, Op)}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+qual_fold(Fun, GlobAcc0, Acc0, Forms, State) ->
+ F = fun(Id, {lc,L,E,Qs0}, GA0) ->
+ {Qs,GA,_NA} = qual_fold(Qs0, Fun, GA0, Acc0, Id, 1, []),
+ {{lc,L,E,Qs},GA};
+ (_Id, Expr, GA) ->
+ {Expr,GA}
+ end,
+ qlc_mapfold(F, GlobAcc0, Forms, State).
+
+qual_fold([Q0 | Qs], F, GA0, A0, Id, No, NQs) ->
+ QId = qid(Id, No),
+ {Q,GA,A} = F(QId, Q0, GA0, A0),
+ qual_fold(Qs, F, GA, A, Id, No + 1, [Q | NQs]);
+qual_fold([], _F, GA, A, _Id, _No, NQs) ->
+ {lists:reverse(NQs),GA,A}.
+
+qlc_mapfold(Fun, Acc0, Forms0, State) ->
+ {Forms, A, _NNo} = qlcmf(Forms0, Fun, State#state.imp, Acc0, 1),
+ erase(?QLC_FILE),
+ {Forms, A}.
+
+qlcmf([E0 | Es0], F, Imp, A0, No0) ->
+ {E, A1, No1} = qlcmf(E0, F, Imp, A0, No0),
+ {Es, A, No} = qlcmf(Es0, F, Imp, A1, No1),
+ {[E | Es], A, No};
+qlcmf(?QLC_Q(L1, L2, L3, L4, LC0, Os0), F, Imp, A0, No0) when length(Os0) < 2 ->
+ {Os, A1, No1} = qlcmf(Os0, F, Imp, A0, No0),
+ {LC, A2, No} = qlcmf(LC0, F, Imp, A1, No1), % nested...
+ NL = make_lcid(L1, No),
+ {T, A} = F(NL, LC, A2),
+ {?QLC_Q(L1, L2, L3, L4, T, Os), A, No + 1};
+qlcmf(?QLC_QQ(L1, L2, L3, L4, L5, L6, LC0, Os0),
+ F, Imp, A0, No0) when length(Os0) < 2 ->
+ {Os, A1, No1} = qlcmf(Os0, F, Imp, A0, No0),
+ {LC, A2, No} = qlcmf(LC0, F, Imp, A1, No1), % nested...
+ NL = make_lcid(L1, No),
+ {T, A} = F(NL, LC, A2),
+ {?QLC_QQ(L1, L2, L3, L4, L5, L6, T, Os), A, No + 1};
+qlcmf(?IMP_Q(L1, L2, LC0, Os0), F, Imp=true, A0, No0) when length(Os0) < 2 ->
+ {Os, A1, No1} = qlcmf(Os0, F, Imp, A0, No0),
+ {LC, A2, No} = qlcmf(LC0, F, Imp, A1, No1), % nested...
+ NL = make_lcid(L, No),
+ {T, A} = F(NL, LC, A2),
+ {?IMP_Q(L1, L2, T, Os), A, No + 1};
+qlcmf({attribute,_L,file,{File,_Line}}=Attr, _F, _Imp, A, No) ->
+ put(?QLC_FILE, File),
+ {Attr, A, No};
+qlcmf(T, F, Imp, A0, No0) when is_tuple(T) ->
+ {TL, A, No} = qlcmf(tuple_to_list(T), F, Imp, A0, No0),
+ {list_to_tuple(TL), A, No};
+qlcmf(T, _F, _Imp, A, No) ->
+ {T, A, No}.
+
+occ_vars(E) ->
+ qlc:var_fold(fun({var,_L,V}) -> V end, [], E).
+
+no_shadows(Forms0, State) ->
+ %% Variables that may shadow other variables are introduced in
+ %% LCs and Funs. Such variables (call them SV, Shadowing
+ %% Variables) are now renamed. Each (new) occurrence in a pattern
+ %% is assigned an index (integer), unique in the file.
+ %%
+ %% The state {LastIndex,ActiveVars,UsedVars,AllVars,Singletons}
+ %% holds the last index used for each SV (LastIndex), the SVs in
+ %% the current scope (ActiveVars), used SVs (UsedVars, the indexed
+ %% name is the key), all variables occurring in the file
+ %% (AllVars), and all singletons. If an SV is not used (that is,
+ %% is a member of Singletons), it is replaced by '_' (otherwise a
+ %% warning for unused variable would erroneously be emitted). If
+ %% the indexed name of an SV occurs in the file, next index is
+ %% tried (to avoid mixing up introduced names with existing ones).
+ %%
+ %% The original names of variables are kept in the line number
+ %% position of the abstract code: {var, {nos, OriginalName, L},
+ %% NewName}. undo_no_shadows/1 re-creates the original code.
+ AllVars = sets:from_list(ordsets:to_list(qlc:vars(Forms0))),
+ ?DEBUG("nos AllVars = ~p~n", [sets:to_list(AllVars)]),
+ VFun = fun(_Id, LC, Vs) -> nos(LC, Vs) end,
+ LI = ets:new(?APIMOD,[]),
+ UV = ets:new(?APIMOD,[]),
+ D0 = dict:new(),
+ S1 = {LI, D0, UV, AllVars, []},
+ _ = qlc_mapfold(VFun, S1, Forms0, State),
+ ?DEBUG("UsedIntroVars = ~p~n", [ets:match_object(UV, '_')]),
+ Singletons = ets:select(UV, ets:fun2ms(fun({K,0}) -> K end)),
+ ?DEBUG("Singletons: ~p~n", [Singletons]),
+ true = ets:delete_all_objects(LI),
+ true = ets:delete_all_objects(UV),
+ %% Do it again, this time we know which variables are singletons.
+ S2 = {LI, D0, UV, AllVars, Singletons},
+ {Forms,_} = qlc_mapfold(VFun, S2, Forms0, State),
+ true = ets:delete(LI),
+ true = ets:delete(UV),
+ Forms.
+
+nos([E0 | Es0], S0) ->
+ {E, S1} = nos(E0, S0),
+ {Es, S} = nos(Es0, S1),
+ {[E | Es], S};
+nos({'fun',L,{clauses,Cs}}, S) ->
+ NCs = [begin
+ {H, S1} = nos_pattern(H0, S),
+ {[G, B], _} = nos([G0, B0], S1),
+ {clause,Ln,H,G,B}
+ end || {clause,Ln,H0,G0,B0} <- Cs],
+ {{'fun',L,{clauses,NCs}}, S};
+nos({lc,L,E0,Qs0}, S) ->
+ %% QLCs as well as LCs. It is OK to modify LCs as long as they
+ %% occur within QLCs--the warning messages have already been found
+ %% by compile_errors.
+ F = fun({T,Ln,P0,LE0}, QS0) when T =:= b_generate; T =:= generate ->
+ {LE, _} = nos(LE0, QS0),
+ {P, QS} = nos_pattern(P0, QS0),
+ {{T,Ln,P,LE}, QS};
+ (Filter, QS) ->
+ nos(Filter, QS)
+ end,
+ {Qs, S1} = lists:mapfoldl(F, S, Qs0),
+ {E, _} = nos(E0, S1),
+ {{lc,L,E,Qs}, S};
+nos({var,L,V}=Var, {_LI,Vs,UV,_A,_Sg}=S) when V =/= '_' ->
+ case used_var(V, Vs, UV) of
+ {true, VN} ->
+ NL = nos_var(L, V),
+ {{var,NL,VN}, S};
+ false ->
+ {Var, S}
+ end;
+nos(T, S0) when is_tuple(T) ->
+ {TL, S} = nos(tuple_to_list(T), S0),
+ {list_to_tuple(TL), S};
+nos(T, S) ->
+ {T, S}.
+
+nos_pattern(P, S) ->
+ {T, NS, _} = nos_pattern(P, S, []),
+ {T, NS}.
+
+nos_pattern([P0 | Ps0], S0, PVs0) ->
+ {P, S1, PVs1} = nos_pattern(P0, S0, PVs0),
+ {Ps, S, PVs} = nos_pattern(Ps0, S1, PVs1),
+ {[P | Ps], S, PVs};
+nos_pattern({var,L,V}, {LI,Vs0,UV,A,Sg}, PVs0) when V =/= '_' ->
+ {Name, Vs, PVs} =
+ case lists:keysearch(V, 1, PVs0) of
+ {value, {V,VN}} ->
+ _ = used_var(V, Vs0, UV),
+ {VN, Vs0, PVs0};
+ false ->
+ {VN, Vs1} = next_var(V, Vs0, A, LI, UV),
+ N = case lists:member(VN, Sg) of
+ true -> '_';
+ false -> VN
+ end,
+ {N, Vs1, [{V,VN} | PVs0]}
+ end,
+ NL = nos_var(L, V),
+ {{var,NL,Name}, {LI,Vs,UV,A,Sg}, PVs};
+nos_pattern(T, S0, PVs0) when is_tuple(T) ->
+ {TL, S, PVs} = nos_pattern(tuple_to_list(T), S0, PVs0),
+ {list_to_tuple(TL), S, PVs};
+nos_pattern(T, S, PVs) ->
+ {T, S, PVs}.
+
+nos_var(L, Name) ->
+ erl_parse:set_line(L, fun(Line) -> {nos,Name,Line} end).
+
+used_var(V, Vs, UV) ->
+ case dict:find(V, Vs) of
+ {ok,Value} ->
+ VN = qlc:name_suffix(V, Value),
+ _ = ets:update_counter(UV, VN, 1),
+ {true, VN};
+ error -> false
+ end.
+
+next_var(V, Vs, AllVars, LI, UV) ->
+ NValue = case ets:lookup(LI, V) of
+ [{V, Value}] -> Value + 1;
+ [] -> 1
+ end,
+ true = ets:insert(LI, {V, NValue}),
+ VN = qlc:name_suffix(V, NValue),
+ case sets:is_element(VN, AllVars) of
+ true -> next_var(V, Vs, AllVars, LI, UV);
+ false -> true = ets:insert(UV, {VN, 0}),
+ NVs = dict:store(V, NValue, Vs),
+ {VN, NVs}
+ end.
+
+undo_no_shadows(E) ->
+ var_map(fun undo_no_shadows1/1, E).
+
+undo_no_shadows1({var, L, _}=Var) ->
+ case erl_parse:get_attribute(L, line) of
+ {line,{nos,V,_VL}} ->
+ NL = erl_parse:set_line(L, fun({nos,_V,VL}) -> VL end),
+ undo_no_shadows1({var, NL, V});
+ _Else ->
+ Var
+ end.
+
+restore_line_numbers(E) ->
+ var_map(fun restore_line_numbers1/1, E).
+
+restore_line_numbers1({var, L, V}=Var) ->
+ case erl_parse:get_attribute(L, line) of
+ {line,{nos,_,_}} ->
+ NL = erl_parse:set_line(L, fun({nos,_V,VL}) -> VL end),
+ restore_line_numbers1({var, NL, V});
+ _Else ->
+ Var
+ end.
+
+%% QLC identifier.
+%% The first one encountered in the file has No=1.
+
+make_lcid(Attrs, No) when is_integer(No), No > 0 ->
+ F = fun(Line) when is_integer(Line), Line < (1 bsl ?MAX_NUM_OF_LINES) ->
+ sgn(Line) * ((No bsl ?MAX_NUM_OF_LINES) + sgn(Line) * Line)
+ end,
+ erl_parse:set_line(Attrs, F).
+
+is_lcid(Attrs) ->
+ try
+ {line,Id} = erl_parse:get_attribute(Attrs, line),
+ is_integer(Id) andalso (abs(Id) > (1 bsl ?MAX_NUM_OF_LINES))
+ catch _:_ ->
+ false
+ end.
+
+get_lcid_no(IdAttrs) ->
+ {line,Id} = erl_parse:get_attribute(IdAttrs, line),
+ abs(Id) bsr ?MAX_NUM_OF_LINES.
+
+get_lcid_line(IdAttrs) ->
+ {line,Id} = erl_parse:get_attribute(IdAttrs, line),
+ sgn(Id) * (abs(Id) band ((1 bsl ?MAX_NUM_OF_LINES) - 1)).
+
+sgn(X) when X >= 0 ->
+ 1;
+sgn(X) when X < 0 ->
+ -1.
+
+seq(S, E) when S - E =:= 1 ->
+ [];
+seq(S, E) ->
+ lists:seq(S, E).
+
+sublist(_, 0) ->
+ [];
+sublist(L, N) ->
+ lists:sublist(L, N).
+
+qid(LCId, No) ->
+ #qid{no = No, lcid = LCId}.
+
+abst_vars([V | Vs], L) ->
+ [abst_vars(V, L) | abst_vars(Vs, L)];
+abst_vars([], _L) ->
+ [];
+abst_vars(nil, L) ->
+ {nil,L};
+abst_vars(V, L) ->
+ {var,L,V}.
+
+embed_vars(Vars, L) ->
+ embed_expr({tuple,L,Vars}, L).
+
+%% -> [Expr || _ <- []] on abstract format.
+embed_expr(Expr, L) ->
+ {lc,L,Expr,[{generate,L,{var,L,'_'},{nil,L}}]}.
+
+%% Doesn't handle binaries very well, but don't bother for now.
+var2const(E) ->
+ var_map(fun({var, L, V}) -> {atom, L, V} end, E).
+
+var_map(F, {var, _, _}=V) ->
+ F(V);
+var_map(F, T) when is_tuple(T) ->
+ list_to_tuple(var_map(F, tuple_to_list(T)));
+var_map(F, [E | Es]) ->
+ [var_map(F, E) | var_map(F, Es)];
+var_map(_F, E) ->
+ E.
+
+var_mapfold(F, A, {var, _, _}=V) ->
+ F(V, A);
+var_mapfold(F, A0, T) when is_tuple(T) ->
+ {L, A} = var_mapfold(F, A0, tuple_to_list(T)),
+ {list_to_tuple(L), A};
+var_mapfold(F, A0, [E0 | Es0]) ->
+ {E, A1} = var_mapfold(F, A0, E0),
+ {Es, A} = var_mapfold(F, A1, Es0),
+ {[E | Es], A};
+var_mapfold(_F, A, E) ->
+ {E, A}.
+
+family_list(L) ->
+ sofs:to_external(family(L)).
+
+family(L) ->
+ sofs:relation_to_family(sofs:relation(L)).
+
+-ifdef(debug).
+display_forms(Forms) ->
+ io:format("Forms ***~n"),
+ lists:foreach(fun(Form) ->
+ io:format("~s~n", [catch erl_pp:form(Form)])
+ end, Forms),
+ io:format("End Forms ***~n").
+-else.
+display_forms(_) ->
+ ok.
+-endif.
+
diff --git a/lib/stdlib/src/queue.erl b/lib/stdlib/src/queue.erl
new file mode 100644
index 0000000000..c09079e8d2
--- /dev/null
+++ b/lib/stdlib/src/queue.erl
@@ -0,0 +1,487 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(queue).
+
+%% Creation, inspection and conversion
+-export([new/0,is_queue/1,is_empty/1,len/1,to_list/1,from_list/1,member/2]).
+%% Original style API
+-export([in/2,in_r/2,out/1,out_r/1]).
+%% Less garbage style API
+-export([get/1,get_r/1,peek/1,peek_r/1,drop/1,drop_r/1]).
+
+%% Higher level API
+-export([reverse/1,join/2,split/2,filter/2]).
+
+%% Okasaki API from klacke
+-export([cons/2,head/1,tail/1,
+ snoc/2,last/1,daeh/1,init/1,liat/1,lait/1]).
+
+%%--------------------------------------------------------------------------
+%% Efficient implementation of double ended fifo queues
+%%
+%% Queue representation
+%%
+%% {RearList,FrontList}
+%%
+%% The first element in the queue is at the head of the FrontList
+%% The last element in the queue is at the head of the RearList,
+%% that is; the RearList is reversed.
+%%
+
+%% A declaration equivalent to the following is currently hard-coded
+%% in erl_types.erl
+%%
+%% -opaque queue() :: {list(), list()}.
+
+%% Creation, inspection and conversion
+
+%% O(1)
+-spec new() -> queue().
+new() -> {[],[]}. %{RearList,FrontList}
+
+%% O(1)
+-spec is_queue(term()) -> boolean().
+is_queue({R,F}) when is_list(R), is_list(F) ->
+ true;
+is_queue(_) ->
+ false.
+
+%% O(1)
+-spec is_empty(queue()) -> boolean().
+is_empty({[],[]}) ->
+ true;
+is_empty({In,Out}) when is_list(In), is_list(Out) ->
+ false;
+is_empty(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% O(len(Q))
+-spec len(queue()) -> non_neg_integer().
+len({R,F}) when is_list(R), is_list(F) ->
+ length(R)+length(F);
+len(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% O(len(Q))
+-spec to_list(queue()) -> list().
+to_list({In,Out}) when is_list(In), is_list(Out) ->
+ Out++lists:reverse(In, []);
+to_list(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Create queue from list
+%%
+%% O(length(L))
+-spec from_list(list()) -> queue().
+from_list(L) when is_list(L) ->
+ f2r(L);
+from_list(L) ->
+ erlang:error(badarg, [L]).
+
+%% Return true or false depending on if element is in queue
+%%
+%% O(length(Q)) worst case
+-spec member(term(), queue()) -> boolean().
+member(X, {R,F}) when is_list(R), is_list(F) ->
+ lists:member(X, R) orelse lists:member(X, F);
+member(X, Q) ->
+ erlang:error(badarg, [X,Q]).
+
+%%--------------------------------------------------------------------------
+%% Original style API
+
+%% Append to tail/rear
+%% Put at least one element in each list, if it is cheap
+%%
+%% O(1)
+-spec in(term(), queue()) -> queue().
+in(X, {[_]=In,[]}) ->
+ {[X], In};
+in(X, {In,Out}) when is_list(In), is_list(Out) ->
+ {[X|In],Out};
+in(X, Q) ->
+ erlang:error(badarg, [X,Q]).
+
+%% Prepend to head/front
+%% Put at least one element in each list, if it is cheap
+%%
+%% O(1)
+-spec in_r(term(), queue()) -> queue().
+in_r(X, {[],[_]=F}) ->
+ {F,[X]};
+in_r(X, {R,F}) when is_list(R), is_list(F) ->
+ {R,[X|F]};
+in_r(X, Q) ->
+ erlang:error(badarg, [X,Q]).
+
+%% Take from head/front
+%%
+%% O(1) amortized, O(len(Q)) worst case
+-spec out(queue()) -> {'empty' | {'value',term()}, queue()}.
+out({[],[]}=Q) ->
+ {empty,Q};
+out({[V],[]}) ->
+ {{value,V},{[],[]}};
+out({[Y|In],[]}) ->
+ [V|Out] = lists:reverse(In, []),
+ {{value,V},{[Y],Out}};
+out({In,[V]}) when is_list(In) ->
+ {{value,V},r2f(In)};
+out({In,[V|Out]}) when is_list(In) ->
+ {{value,V},{In,Out}};
+out(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Take from tail/rear
+%%
+%% O(1) amortized, O(len(Q)) worst case
+-spec out_r(queue()) -> {'empty' | {'value',term()}, queue()}.
+out_r({[],[]}=Q) ->
+ {empty,Q};
+out_r({[],[V]}) ->
+ {{value,V},{[],[]}};
+out_r({[],[Y|Out]}) ->
+ [V|In] = lists:reverse(Out, []),
+ {{value,V},{In,[Y]}};
+out_r({[V],Out}) when is_list(Out) ->
+ {{value,V},f2r(Out)};
+out_r({[V|In],Out}) when is_list(Out) ->
+ {{value,V},{In,Out}};
+out_r(Q) ->
+ erlang:error(badarg, [Q]).
+
+%%--------------------------------------------------------------------------
+%% Less garbage style API.
+
+%% Return the first element in the queue
+%%
+%% O(1) since the queue is supposed to be well formed
+-spec get(queue()) -> term().
+get({[],[]}=Q) ->
+ erlang:error(empty, [Q]);
+get({R,F}) when is_list(R), is_list(F) ->
+ get(R, F);
+get(Q) ->
+ erlang:error(badarg, [Q]).
+
+-spec get(list(), list()) -> term().
+get(R, [H|_]) when is_list(R) ->
+ H;
+get([H], []) ->
+ H;
+get([_|R], []) -> % malformed queue -> O(len(Q))
+ lists:last(R).
+
+%% Return the last element in the queue
+%%
+%% O(1) since the queue is supposed to be well formed
+-spec get_r(queue()) -> term().
+get_r({[],[]}=Q) ->
+ erlang:error(empty, [Q]);
+get_r({[H|_],F}) when is_list(F) ->
+ H;
+get_r({[],[H]}) ->
+ H;
+get_r({[],[_|F]}) -> % malformed queue -> O(len(Q))
+ lists:last(F);
+get_r(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Return the first element in the queue
+%%
+%% O(1) since the queue is supposed to be well formed
+-spec peek(queue()) -> 'empty' | {'value',term()}.
+peek({[],[]}) ->
+ empty;
+peek({R,[H|_]}) when is_list(R) ->
+ {value,H};
+peek({[H],[]}) ->
+ {value,H};
+peek({[_|R],[]}) -> % malformed queue -> O(len(Q))
+ {value,lists:last(R)};
+peek(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Return the last element in the queue
+%%
+%% O(1) since the queue is supposed to be well formed
+-spec peek_r(queue()) -> 'empty' | {'value',term()}.
+peek_r({[],[]}) ->
+ empty;
+peek_r({[H|_],F}) when is_list(F) ->
+ {value,H};
+peek_r({[],[H]}) ->
+ {value,H};
+peek_r({[],[_|R]}) -> % malformed queue -> O(len(Q))
+ {value,lists:last(R)};
+peek_r(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Remove the first element and return resulting queue
+%%
+%% O(1) amortized
+-spec drop(queue()) -> queue().
+drop({[],[]}=Q) ->
+ erlang:error(empty, [Q]);
+drop({[_],[]}) ->
+ {[],[]};
+drop({[Y|R],[]}) ->
+ [_|F] = lists:reverse(R, []),
+ {[Y],F};
+drop({R, [_]}) when is_list(R) ->
+ r2f(R);
+drop({R, [_|F]}) when is_list(R) ->
+ {R,F};
+drop(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Remove the last element and return resulting queue
+%%
+%% O(1) amortized
+-spec drop_r(queue()) -> queue().
+drop_r({[],[]}=Q) ->
+ erlang:error(empty, [Q]);
+drop_r({[],[_]}) ->
+ {[],[]};
+drop_r({[],[Y|F]}) ->
+ [_|R] = lists:reverse(F, []),
+ {R,[Y]};
+drop_r({[_], F}) when is_list(F) ->
+ f2r(F);
+drop_r({[_|R], F}) when is_list(F) ->
+ {R,F};
+drop_r(Q) ->
+ erlang:error(badarg, [Q]).
+
+%%--------------------------------------------------------------------------
+%% Higher level API
+
+%% Return reversed queue
+%%
+%% O(1)
+-spec reverse(queue()) -> queue().
+reverse({R,F}) when is_list(R), is_list(F) ->
+ {F,R};
+reverse(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Join two queues
+%%
+%% Q2 empty: O(1)
+%% else: O(len(Q1))
+-spec join(queue(), queue()) -> queue().
+join({R,F}=Q, {[],[]}) when is_list(R), is_list(F) ->
+ Q;
+join({[],[]}, {R,F}=Q) when is_list(R), is_list(F) ->
+ Q;
+join({R1,F1}, {R2,F2}) when is_list(R1), is_list(F1), is_list(R2), is_list(F2) ->
+ {R2,F1++lists:reverse(R1,F2)};
+join(Q1, Q2) ->
+ erlang:error(badarg, [Q1,Q2]).
+
+%% Split a queue in two
+%%
+%% N = 0..len(Q)
+%% O(max(N, len(Q)))
+-spec split(non_neg_integer(), queue()) -> {queue(),queue()}.
+split(0, {R,F}=Q) when is_list(R), is_list(F) ->
+ {{[],[]},Q};
+split(N, {R,F}=Q) when is_integer(N), N >= 1, is_list(R), is_list(F) ->
+ Lf = erlang:length(F),
+ if N < Lf -> % Lf >= 2
+ [X|F1] = F,
+ split_f1_to_r2(N-1, R, F1, [], [X]);
+ N > Lf ->
+ Lr = length(R),
+ M = Lr - (N-Lf),
+ if M < 0 ->
+ erlang:error(badarg, [N,Q]);
+ M > 0 ->
+ [X|R1] = R,
+ split_r1_to_f2(M-1, R1, F, [X], []);
+ true -> % M == 0
+ {Q,{[],[]}}
+ end;
+ true -> % N == Lf
+ {f2r(F),r2f(R)}
+ end;
+split(N, Q) ->
+ erlang:error(badarg, [N,Q]).
+
+%% Move N elements from F1 to R2
+split_f1_to_r2(0, R1, F1, R2, F2) ->
+ {{R2,F2},{R1,F1}};
+split_f1_to_r2(N, R1, [X|F1], R2, F2) ->
+ split_f1_to_r2(N-1, R1, F1, [X|R2], F2).
+
+%% Move N elements from R1 to F2
+split_r1_to_f2(0, R1, F1, R2, F2) ->
+ {{R1,F1},{R2,F2}};
+split_r1_to_f2(N, [X|R1], F1, R2, F2) ->
+ split_r1_to_f2(N-1, R1, F1, R2, [X|F2]).
+
+%% filter, or rather filtermap with insert, traverses in queue order
+%%
+%% Fun(_) -> List: O(length(List) * len(Q))
+%% else: O(len(Q)
+-spec filter(fun((term()) -> boolean() | list()), queue()) -> queue().
+filter(Fun, {R0,F0}) when is_function(Fun, 1), is_list(R0), is_list(F0) ->
+ F = filter_f(Fun, F0),
+ R = filter_r(Fun, R0),
+ if R =:= [] ->
+ f2r(F);
+ F =:= [] ->
+ r2f(R);
+ true ->
+ {R,F}
+ end;
+filter(Fun, Q) ->
+ erlang:error(badarg, [Fun,Q]).
+
+%% Call Fun in head to tail order
+filter_f(_, []) ->
+ [];
+filter_f(Fun, [X|F]) ->
+ case Fun(X) of
+ true ->
+ [X|filter_f(Fun, F)];
+ false ->
+ filter_f(Fun, F);
+ L when is_list(L) ->
+ L++filter_f(Fun, F)
+ end.
+
+%% Call Fun in reverse order, i.e tail to head
+%% and reverse list result from fun to match queue order
+filter_r(_, []) ->
+ [];
+filter_r(Fun, [X|R0]) ->
+ R = filter_r(Fun, R0),
+ case Fun(X) of
+ true ->
+ [X|R];
+ false ->
+ R;
+ L when is_list(L) ->
+ lists:reverse(L, R)
+ end.
+
+%%--------------------------------------------------------------------------
+%% Okasaki API inspired by an Erlang user contribution "deque.erl"
+%% by Claes Wikstrom <[email protected]> 1999.
+%%
+%% This implementation does not use the internal data format from Klacke's
+%% doubly ended queues that was "shamelessly stolen" from
+%% "Purely Functional Data structures" by Chris Okasaki, since the data
+%% format of this module must remain the same in case some application
+%% has saved a queue in external format or sends it to an old node.
+%%
+%% This implementation tries to do the best of the situation and should
+%% be almost as efficient as Okasaki's queues, except for len/1 that
+%% is O(n) in this implementation instead of O(1).
+%%
+%% The new representation in this module again adds length field and
+%% fixes this, but it is not yet default.
+%%
+%% The implementation keeps at least one element in both the forward
+%% and the reversed lists to ensure that i.e head/1 or last/1 will
+%% not have to reverse a list to find the element.
+%%
+%% To be compatible with the old version of this module, as much data as
+%% possible is moved to the receiving side using lists:reverse/2 when data
+%% is needed, except for two elements (when possible). These two elements
+%% are kept to prevent alternating tail/1 and init/1 operations from
+%% moving data back and forth between the sides.
+%%
+%% An alternative would be to balance for equal list length when one side
+%% is exhausted. Although this could be better for a general double
+%% ended queue, it would more han double the amortized cost for
+%% the normal case (one way queue).
+
+%% Cons to head
+%%
+-spec cons(term(), queue()) -> queue().
+cons(X, Q) ->
+ in_r(X, Q).
+
+%% Return head element
+%%
+%% Return the first element in the queue
+%%
+%% O(1) since the queue is supposed to be well formed
+-spec head(queue()) -> term().
+head({[],[]}=Q) ->
+ erlang:error(empty, [Q]);
+head({R,F}) when is_list(R), is_list(F) ->
+ get(R, F);
+head(Q) ->
+ erlang:error(badarg, [Q]).
+
+%% Remove head element and return resulting queue
+%%
+-spec tail(queue()) -> queue().
+tail(Q) ->
+ drop(Q).
+
+%% Functions operating on the other end of the queue
+
+%% Cons to tail
+%%
+-spec snoc(queue(), term()) -> queue().
+snoc(Q, X) ->
+ in(X, Q).
+
+%% Return last element
+-spec daeh(queue()) -> term().
+daeh(Q) -> get_r(Q).
+-spec last(queue()) -> term().
+last(Q) -> get_r(Q).
+
+%% Remove last element and return resulting queue
+-spec liat(queue()) -> queue().
+liat(Q) -> drop_r(Q).
+-spec lait(queue()) -> queue().
+lait(Q) -> drop_r(Q). %% Oops, mis-spelled 'tail' reversed. Forget this one.
+-spec init(queue()) -> queue().
+init(Q) -> drop_r(Q).
+
+%%--------------------------------------------------------------------------
+%% Internal workers
+
+-compile({inline, [{r2f,1},{f2r,1}]}).
+
+%% Move all but two from R to F, if there are at least three
+r2f([]) ->
+ {[],[]};
+r2f([_]=R) ->
+ {[],R};
+r2f([X,Y]) ->
+ {[X],[Y]};
+r2f([X,Y|R]) ->
+ {[X,Y],lists:reverse(R, [])}.
+
+%% Move all but two from F to R, if there are enough
+f2r([]) ->
+ {[],[]};
+f2r([_]=F) ->
+ {F,[]};
+f2r([X,Y]) ->
+ {[Y],[X]};
+f2r([X,Y|F]) ->
+ {lists:reverse(F, []),[X,Y]}.
diff --git a/lib/stdlib/src/random.erl b/lib/stdlib/src/random.erl
new file mode 100644
index 0000000000..01227c29b4
--- /dev/null
+++ b/lib/stdlib/src/random.erl
@@ -0,0 +1,124 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(random).
+
+%% Reasonable random number generator.
+%% The method is attributed to B. A. Wichmann and I. D. Hill
+%% See "An efficient and portable pseudo-random number generator",
+%% Journal of Applied Statistics. AS183. 1982. Also Byte March 1987.
+
+-export([seed/0, seed/1, seed/3, uniform/0, uniform/1,
+ uniform_s/1, uniform_s/2, seed0/0]).
+
+%%-----------------------------------------------------------------------
+%% The type of the state
+
+-type ran() :: {integer(), integer(), integer()}.
+
+%%-----------------------------------------------------------------------
+
+-spec seed0() -> ran().
+
+seed0() ->
+ {3172, 9814, 20125}.
+
+%% seed()
+%% Seed random number generation with default values
+
+-spec seed() -> ran().
+
+seed() ->
+ reseed(seed0()).
+
+%% seed({A1, A2, A3})
+%% Seed random number generation
+
+-spec seed({integer(), integer(), integer()}) -> 'undefined' | ran().
+
+seed({A1, A2, A3}) ->
+ seed(A1, A2, A3).
+
+%% seed(A1, A2, A3)
+%% Seed random number generation
+
+-spec seed(integer(), integer(), integer()) -> 'undefined' | ran().
+
+seed(A1, A2, A3) ->
+ put(random_seed,
+ {abs(A1) rem 30269, abs(A2) rem 30307, abs(A3) rem 30323}).
+
+
+-spec reseed(ran()) -> ran().
+
+reseed({A1, A2, A3}) ->
+ case seed(A1, A2, A3) of
+ undefined -> seed0();
+ {_,_,_} = Tuple -> Tuple
+ end.
+
+%% uniform()
+%% Returns a random float between 0 and 1.
+
+-spec uniform() -> float().
+
+uniform() ->
+ {A1, A2, A3} = case get(random_seed) of
+ undefined -> seed0();
+ Tuple -> Tuple
+ end,
+ B1 = (A1*171) rem 30269,
+ B2 = (A2*172) rem 30307,
+ B3 = (A3*170) rem 30323,
+ put(random_seed, {B1,B2,B3}),
+ R = A1/30269 + A2/30307 + A3/30323,
+ R - trunc(R).
+
+%% uniform(N) -> I
+%% Given an integer N >= 1, uniform(N) returns a random integer
+%% between 1 and N.
+
+-spec uniform(pos_integer()) -> pos_integer().
+
+uniform(N) when is_integer(N), N >= 1 ->
+ trunc(uniform() * N) + 1.
+
+
+%%% Functional versions
+
+%% uniform_s(State) -> {F, NewState}
+%% Returns a random float between 0 and 1.
+
+-spec uniform_s(ran()) -> {float(), ran()}.
+
+uniform_s({A1, A2, A3}) ->
+ B1 = (A1*171) rem 30269,
+ B2 = (A2*172) rem 30307,
+ B3 = (A3*170) rem 30323,
+ R = A1/30269 + A2/30307 + A3/30323,
+ {R - trunc(R), {B1,B2,B3}}.
+
+%% uniform_s(N, State) -> {I, NewState}
+%% Given an integer N >= 1, uniform(N) returns a random integer
+%% between 1 and N.
+
+-spec uniform_s(pos_integer(), ran()) -> {integer(), ran()}.
+
+uniform_s(N, State0) when is_integer(N), N >= 1 ->
+ {F, State1} = uniform_s(State0),
+ {trunc(F * N) + 1, State1}.
diff --git a/lib/stdlib/src/re.erl b/lib/stdlib/src/re.erl
new file mode 100644
index 0000000000..5417ac02e5
--- /dev/null
+++ b/lib/stdlib/src/re.erl
@@ -0,0 +1,751 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(re).
+-export([grun/3,urun/3,ucompile/2,replace/3,replace/4,split/2,split/3]).
+
+%% Emulator builtins in this module:
+%% re:compile/1
+%% re:compile/2
+%% re:run/2
+%% re:run/3
+
+split(Subject,RE) ->
+ split(Subject,RE,[]).
+
+split(Subject,RE,Options) ->
+ try
+ {NewOpt,Convert,Unicode,Limit,Strip,Group} =
+ process_split_params(Options,iodata,false,-1,false,false),
+ FlatSubject =
+ case is_binary(Subject) of
+ true ->
+ Subject;
+ false ->
+ case Unicode of
+ true ->
+ unicode:characters_to_binary(Subject,unicode);
+ false ->
+ iolist_to_binary(Subject)
+ end
+ end,
+ case compile_split(RE,NewOpt) of
+ {error,_Err} ->
+ throw(badre);
+ {PreCompiled, NumSub, RunOpt} ->
+ % OK, lets run
+ case re:run(FlatSubject,PreCompiled,RunOpt ++ [global]) of
+ nomatch ->
+ case Group of
+ true ->
+ convert_any_split_result([[FlatSubject]],
+ Convert, Unicode,true);
+ false ->
+ convert_any_split_result([FlatSubject],
+ Convert, Unicode,false)
+ end;
+ {match, Matches} ->
+ Res = do_split(FlatSubject, 0, Matches, NumSub,
+ Limit, Group),
+ Stripped = case Strip of
+ true ->
+ backstrip_empty(Res,Group);
+ false ->
+ Res
+ end,
+ convert_any_split_result(Stripped, Convert, Unicode, Group)
+ end
+ end
+ catch
+ throw:badopt ->
+ erlang:error(badarg,[Subject,RE,Options]);
+ throw:badre ->
+ erlang:error(badarg,[Subject,RE,Options]);
+ error:badarg ->
+ erlang:error(badarg,[Subject,RE,Options])
+ end.
+
+backstrip_empty(List,false) ->
+ do_backstrip_empty(List);
+backstrip_empty(List, true) ->
+ do_backstrip_empty_g(List).
+
+do_backstrip_empty_g([]) ->
+ [];
+do_backstrip_empty_g([H]) ->
+ case do_backstrip_empty(H) of
+ [] ->
+ [];
+ _ ->
+ [H]
+ end;
+do_backstrip_empty_g([H|T]) ->
+ case do_backstrip_empty_g(T) of
+ [] ->
+ case do_backstrip_empty(H) of
+ [] ->
+ [];
+ _ ->
+ [H]
+ end;
+ Other ->
+ [H|Other]
+ end.
+
+do_backstrip_empty([]) ->
+ [];
+do_backstrip_empty([<<>>]) ->
+ [];
+do_backstrip_empty([<<>>|T]) ->
+ case do_backstrip_empty(T) of
+ [] ->
+ [];
+ Other ->
+ [<<>>|Other]
+ end;
+do_backstrip_empty([H|T]) ->
+ [H|do_backstrip_empty(T)].
+
+convert_any_split_result(List,Type,Uni,true) ->
+ [ convert_split_result(Part,Type,Uni) || Part <- List ];
+convert_any_split_result(List,Type,Uni, false) ->
+ convert_split_result(List,Type,Uni).
+
+convert_split_result(List, iodata, _Unicode) ->
+ List;
+convert_split_result(List, binary, _Unicode) ->
+ %% As it happens, the iodata is actually binaries
+ List;
+convert_split_result(List, list, true) ->
+ [unicode:characters_to_list(Element,unicode) || Element <- List];
+convert_split_result(List, list, false) ->
+ [binary_to_list(Element) || Element <- List].
+
+do_split(Subj, Off, _, _, 0, false) ->
+ <<_:Off/binary,Rest/binary>> = Subj,
+ [Rest];
+do_split(Subj, Off, [], _, _, false) ->
+ <<_:Off/binary,Rest/binary>> = Subj,
+ [Rest];
+do_split(Subj, Off, _, _, _,false) when byte_size(Subj) =< Off ->
+ [<<>>];
+do_split(Subj, Off, _, _, 0, true) ->
+ <<_:Off/binary,Rest/binary>> = Subj,
+ [[Rest]];
+do_split(Subj, Off, [], _, _, true) ->
+ <<_:Off/binary,Rest/binary>> = Subj,
+ [[Rest]];
+do_split(Subj, Off, _, _, _,true) when byte_size(Subj) =< Off ->
+ [[<<>>]];
+do_split(Subj, Offset, [[{MainI,MainL}|Sub]|T], NumSub, Limit, Group) ->
+ NewOffset = MainI+MainL,
+ KeptLen = MainI - Offset,
+ case {KeptLen,empty_sub(Sub),MainL} of
+ {0,true,0} ->
+ do_split(Subj,NewOffset,T,NumSub,Limit,Group);
+ _ ->
+ <<_:Offset/binary,Keep:KeptLen/binary,_/binary>> = Subj,
+ ESub = extend_subpatterns(Sub,NumSub),
+ Tail = do_split(Subj, NewOffset, T, NumSub, Limit - 1,Group),
+ case Group of
+ false ->
+ [Keep | dig_subpatterns(Subj,lists:reverse(ESub),Tail)];
+ true ->
+ [[Keep | dig_subpatterns(Subj,lists:reverse(ESub),[])]|
+ Tail]
+ end
+ end.
+empty_sub([]) ->
+ true;
+empty_sub([{_,0}|T]) ->
+ empty_sub(T);
+empty_sub(_) ->
+ false.
+
+dig_subpatterns(_,[],Acc) ->
+ Acc;
+dig_subpatterns(Subj,[{-1,0}|T],Acc) ->
+ dig_subpatterns(Subj,T,[<<>>|Acc]);
+dig_subpatterns(Subj,[{I,L}|T],Acc) ->
+ <<_:I/binary,Part:L/binary,_/binary>> = Subj,
+ dig_subpatterns(Subj,T,[Part|Acc]).
+
+extend_subpatterns(_,0) ->
+ [];
+extend_subpatterns([],N) ->
+ [{0,0} | extend_subpatterns([],N-1)];
+extend_subpatterns([H|T],N) ->
+ [H | extend_subpatterns(T,N-1)].
+
+compile_split({re_pattern,N,_,_} = Comp, Options) ->
+ {Comp,N,Options};
+compile_split(Pat,Options0) when not is_tuple(Pat) ->
+ Options = lists:filter(fun(O) ->
+ (not runopt(O))
+ end, Options0),
+ case re:compile(Pat,Options) of
+ {error,Err} ->
+ {error,Err};
+ {ok, {re_pattern,N,_,_} = Comp} ->
+ NewOpt = lists:filter(fun(OO) -> (not copt(OO)) end, Options0),
+ {Comp,N,NewOpt}
+ end;
+compile_split(_,_) ->
+ throw(badre).
+
+
+
+
+replace(Subject,RE,Replacement) ->
+ replace(Subject,RE,Replacement,[]).
+replace(Subject,RE,Replacement,Options) ->
+ try
+ {NewOpt,Convert,Unicode} =
+ process_repl_params(Options,iodata,false),
+ FlatSubject =
+ case is_binary(Subject) of
+ true ->
+ Subject;
+ false ->
+ case Unicode of
+ true ->
+ unicode:characters_to_binary(Subject,unicode);
+ false ->
+ iolist_to_binary(Subject)
+ end
+ end,
+ case do_replace(FlatSubject,Subject,RE,Replacement,NewOpt) of
+ {error,_Err} ->
+ throw(badre);
+ IoList ->
+ case Convert of
+ iodata ->
+ IoList;
+ binary ->
+ iolist_to_binary(IoList);
+ list ->
+ case Unicode of
+ false ->
+ binary_to_list(iolist_to_binary(IoList));
+ true ->
+ unicode:characters_to_list(IoList,unicode)
+ end
+ end
+ end
+ catch
+ throw:badopt ->
+ erlang:error(badarg,[Subject,RE,Replacement,Options]);
+ throw:badre ->
+ erlang:error(badarg,[Subject,RE,Replacement,Options]);
+ error:badarg ->
+ erlang:error(badarg,[Subject,RE,Replacement,Options])
+ end.
+
+
+do_replace(FlatSubject,Subject,RE,Replacement,Options) ->
+ case re:run(FlatSubject,RE,Options) of
+ nomatch ->
+ Subject;
+ {match,[Mlist|T]} when is_list(Mlist) ->
+ apply_mlist(FlatSubject,Replacement,[Mlist|T]);
+ {match,Slist} ->
+ apply_mlist(FlatSubject,Replacement,[Slist])
+ end.
+
+process_repl_params([],Convert,Unicode) ->
+ {[],Convert,Unicode};
+process_repl_params([unicode|T],C,_U) ->
+ {NT,NC,NU} = process_repl_params(T,C,true),
+ {[unicode|NT],NC,NU};
+process_repl_params([{capture,_,_}|_],_,_) ->
+ throw(badopt);
+process_repl_params([{capture,_}|_],_,_) ->
+ throw(badopt);
+process_repl_params([{return,iodata}|T],_C,U) ->
+ process_repl_params(T,iodata,U);
+process_repl_params([{return,list}|T],_C,U) ->
+ process_repl_params(T,list,U);
+process_repl_params([{return,binary}|T],_C,U) ->
+ process_repl_params(T,binary,U);
+process_repl_params([{return,_}|_],_,_) ->
+ throw(badopt);
+process_repl_params([H|T],C,U) ->
+ {NT,NC,NU} = process_repl_params(T,C,U),
+ {[H|NT],NC,NU}.
+
+process_split_params([],Convert,Unicode,Limit,Strip,Group) ->
+ {[],Convert,Unicode,Limit,Strip,Group};
+process_split_params([unicode|T],C,_U,L,S,G) ->
+ {NT,NC,NU,NL,NS,NG} = process_split_params(T,C,true,L,S,G),
+ {[unicode|NT],NC,NU,NL,NS,NG};
+process_split_params([trim|T],C,U,_L,_S,G) ->
+ process_split_params(T,C,U,-1,true,G);
+process_split_params([{parts,0}|T],C,U,_L,_S,G) ->
+ process_split_params(T,C,U,-1,true,G);
+process_split_params([{parts,N}|T],C,U,_L,_S,G) when is_integer(N), N >= 1 ->
+ process_split_params(T,C,U,N-1,false,G);
+process_split_params([{parts,infinity}|T],C,U,_L,_S,G) ->
+ process_split_params(T,C,U,-1,false,G);
+process_split_params([{parts,_}|_],_,_,_,_,_) ->
+ throw(badopt);
+process_split_params([group|T],C,U,L,S,_G) ->
+ process_split_params(T,C,U,L,S,true);
+process_split_params([global|_],_,_,_,_,_) ->
+ throw(badopt);
+process_split_params([{capture,_,_}|_],_,_,_,_,_) ->
+ throw(badopt);
+process_split_params([{capture,_}|_],_,_,_,_,_) ->
+ throw(badopt);
+process_split_params([{return,iodata}|T],_C,U,L,S,G) ->
+ process_split_params(T,iodata,U,L,S,G);
+process_split_params([{return,list}|T],_C,U,L,S,G) ->
+ process_split_params(T,list,U,L,S,G);
+process_split_params([{return,binary}|T],_C,U,L,S,G) ->
+ process_split_params(T,binary,U,L,S,G);
+process_split_params([{return,_}|_],_,_,_,_,_) ->
+ throw(badopt);
+process_split_params([H|T],C,U,L,S,G) ->
+ {NT,NC,NU,NL,NS,NG} = process_split_params(T,C,U,L,S,G),
+ {[H|NT],NC,NU,NL,NS,NG}.
+
+apply_mlist(Subject,Replacement,Mlist) ->
+ do_mlist(Subject,Subject,0,precomp_repl(iolist_to_binary(Replacement)),
+ Mlist).
+
+
+precomp_repl(<<>>) ->
+ [];
+precomp_repl(<<$\\,X,Rest/binary>>) when X < $1 ; X > $9 ->
+ % Escaped character
+ case precomp_repl(Rest) of
+ [BHead | T0] when is_binary(BHead) ->
+ [<<X,BHead/binary>> | T0];
+ Other ->
+ [<<X>> | Other]
+ end;
+precomp_repl(<<$\\,Rest/binary>>) when byte_size(Rest) > 0->
+ {NS,NRest} = pick_int(Rest),
+ [list_to_integer(NS) | precomp_repl(NRest)];
+precomp_repl(<<$&,Rest/binary>>) ->
+ [0 | precomp_repl(Rest)];
+precomp_repl(<<X,Rest/binary>>) ->
+ case precomp_repl(Rest) of
+ [BHead | T0] when is_binary(BHead) ->
+ [<<X,BHead/binary>> | T0];
+ Other ->
+ [<<X>> | Other]
+ end.
+
+
+
+pick_int(<<X,R/binary>>) when X >= $0, X =< $9 ->
+ {Found,Rest} = pick_int(R),
+ {[X|Found],Rest};
+pick_int(Bin) ->
+ {[],Bin}.
+
+do_mlist(_,<<>>,_,_,[]) ->
+ []; %Avoid empty binary tail
+do_mlist(_,Subject,_,_,[]) ->
+ Subject;
+do_mlist(Whole,Subject,Pos,Repl,[[{MPos,Count} | Sub] | Tail])
+ when MPos > Pos ->
+ EatLength = MPos - Pos,
+ <<Untouched:EatLength/binary, Rest/binary>> = Subject,
+ [Untouched | do_mlist(Whole,Rest, MPos, Repl,
+ [[{MPos,Count} | Sub] | Tail])];
+do_mlist(Whole,Subject,Pos,Repl,[[{MPos,Count} | Sub] | Tail])
+ when MPos =:= Pos ->
+ EatLength = Count,
+ <<_:EatLength/binary,Rest/binary>> = Subject,
+ NewData = do_replace(Whole,Repl,[{MPos,Count} | Sub]),
+ [NewData | do_mlist(Whole,Rest,Pos+EatLength,Repl,Tail)].
+
+
+do_replace(_,[Bin],_) when is_binary(Bin) ->
+ Bin;
+do_replace(Subject,Repl,SubExprs0) ->
+ SubExprs = list_to_tuple(SubExprs0),
+ [ case Part of
+ N when is_integer(N) ->
+ if
+ tuple_size(SubExprs) =< N ->
+ <<>>;
+ true ->
+ {SPos,SLen} = element(N+1,SubExprs),
+ if
+ SPos < 0 ->
+ <<>>;
+ true ->
+ <<_:SPos/binary,Res:SLen/binary,_/binary>> =
+ Subject,
+ Res
+ end
+ end;
+ Other ->
+ Other
+ end || Part <- Repl ].
+
+
+check_for_unicode({re_pattern,_,1,_},_) ->
+ true;
+check_for_unicode({re_pattern,_,0,_},_) ->
+ false;
+check_for_unicode(_,L) ->
+ lists:member(unicode,L).
+
+% SelectReturn = false | all | stirpfirst | none
+% ConvertReturn = index | list | binary
+% {capture, all} -> all (untouchded)
+% {capture, first} -> kept in argumentt list and Select all
+% {capture, all_but_first} -> removed from argument list and selects stripfirst
+% {capture, none} -> removed from argument list and selects none
+% {capture, []} -> removed from argument list and selects none
+% {capture,[...]} -> 0 added to selection list and selects stripfirst
+% SelectReturn false is same as all in the end.
+
+% Call as process_parameters([],0,false,index,NeedClean)
+
+process_parameters([],InitialOffset, SelectReturn, ConvertReturn,_) ->
+ {[], InitialOffset, SelectReturn, ConvertReturn};
+process_parameters([{offset, N} | T],_Init0,Select0,Return0,CC) ->
+ process_parameters(T,N,Select0,Return0,CC);
+process_parameters([global | T],Init0,Select0,Return0,CC) ->
+ process_parameters(T,Init0,Select0,Return0,CC);
+process_parameters([{capture,Values,Type}|T],Init0,Select0,_Return0,CC) ->
+ process_parameters([{capture,Values}|T],Init0,Select0,Type,CC);
+process_parameters([{capture,Values}|T],Init0,Select0,Return0,CC) ->
+ % First process the rest to see if capture was already present
+ {NewTail, Init1, Select1, Return1} =
+ process_parameters(T,Init0,Select0,Return0,CC),
+ case Select1 of
+ false ->
+ case Values of
+ all ->
+ {[{capture,all} | NewTail], Init1, all, Return0};
+ first ->
+ {[{capture,first} | NewTail], Init1, all, Return0};
+ all_but_first ->
+ {[{capture,all} | NewTail], Init1, stripfirst, Return0};
+ none ->
+ {[{capture,first} | NewTail], Init1, none, Return0};
+ [] ->
+ {[{capture,first} | NewTail], Init1, none, Return0};
+ List when is_list(List) ->
+ {[{capture,[0|List]} | NewTail],
+ Init1, stripfirst, Return0};
+ _ ->
+ throw(badlist)
+ end;
+ _ ->
+ % Found overriding further down list, ignore this one
+ {NewTail, Init1, Select1, Return1}
+ end;
+process_parameters([H|T],Init0,Select0,Return0,true) ->
+ case copt(H) of
+ true ->
+ process_parameters(T,Init0,Select0,Return0,true);
+ false ->
+ {NewT,Init,Select,Return} =
+ process_parameters(T,Init0,Select0,Return0,true),
+ {[H|NewT],Init,Select,Return}
+ end;
+process_parameters([H|T],Init0,Select0,Return0,false) ->
+ {NewT,Init,Select,Return} =
+ process_parameters(T,Init0,Select0,Return0,false),
+ {[H|NewT],Init,Select,Return};
+process_parameters(_,_,_,_,_) ->
+ throw(badlist).
+
+postprocess({match,[]},_,_,_,_) ->
+ nomatch;
+postprocess({match,_},none,_,_,_) ->
+ match;
+postprocess({match,M},Any,binary,Flat,Uni) ->
+ binarify(postprocess({match,M},Any,index,Flat,Uni),Flat);
+postprocess({match,M},Any,list,Flat,Uni) ->
+ listify(postprocess({match,M},Any,index,Flat,Uni),Flat,Uni);
+postprocess({match,M},all,index,_,_) ->
+ {match,M};
+postprocess({match,M},false,index,_,_) ->
+ {match,M};
+postprocess({match,M},stripfirst,index,_,_) ->
+ {match, [ T || [_|T] <- M ]}.
+
+binarify({match,M},Flat) ->
+ {match, [ [ case {I,L} of
+ {-1,0} ->
+ <<>>;
+ {SPos,SLen} ->
+ <<_:SPos/binary,Res:SLen/binary,_/binary>> = Flat,
+ Res
+ end || {I,L} <- One ] || One <- M ]}.
+listify({match,M},Flat,Uni) ->
+ {match, [ [ case {I,L} of
+ {_,0} ->
+ [];
+ {SPos,SLen} ->
+ case Uni of
+ true ->
+ <<_:SPos/binary,Res:SLen/binary,_/binary>> = Flat,
+ unicode:characters_to_list(Res,unicode);
+ false ->
+ Start = SPos + 1,
+ End = SPos + SLen,
+ binary_to_list(Flat,Start,End)
+ end
+ end || {I,L} <- One ] || One <- M ]}.
+
+ubinarify({match,M},Flat) ->
+ {match, [ case {I,L} of
+ {-1,0} ->
+ <<>>;
+ {SPos,SLen} ->
+ <<_:SPos/binary,Res:SLen/binary,_/binary>> = Flat,
+ Res
+ end || {I,L} <- M ]};
+ubinarify(Else,_) ->
+ Else.
+ulistify({match,M},Flat) ->
+ {match, [ case {I,L} of
+ {_,0} ->
+ [];
+ {SPos,SLen} ->
+ <<_:SPos/binary,Res:SLen/binary,_/binary>> = Flat,
+ unicode:characters_to_list(Res,unicode)
+ end || {I,L} <- M ]};
+ulistify(Else,_) ->
+ Else.
+
+process_uparams([global|_T],_RetType) ->
+ throw(false);
+process_uparams([{capture,Values,Type}|T],_OldType) ->
+ process_uparams([{capture,Values}|T],Type);
+process_uparams([H|T],Type) ->
+ {NL,NType} = process_uparams(T,Type),
+ {[H|NL],NType};
+process_uparams([],Type) ->
+ {[],Type}.
+
+
+ucompile(RE,Options) ->
+ try
+ re:compile(unicode:characters_to_binary(RE,unicode))
+ catch
+ error:AnyError ->
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [RE,Options])),
+ erlang:raise(error,AnyError,[{Mod,compile,L}|Rest])
+ end.
+
+
+urun(Subject,RE,Options) ->
+ try
+ urun2(Subject,RE,Options)
+ catch
+ error:AnyError ->
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [Subject,RE,Options])),
+ erlang:raise(error,AnyError,[{Mod,run,L}|Rest])
+ end.
+urun2(Subject0,RE0,Options0) ->
+ {Options,RetType} = case (catch process_uparams(Options0,index)) of
+ {A,B} ->
+ {A,B};
+ _ ->
+ {Options0,false}
+ end,
+ Subject = unicode:characters_to_binary(Subject0,unicode),
+ RE = case RE0 of
+ BinRE when is_binary(BinRE) ->
+ BinRE;
+ {re_pattern,_,_,_} = ReCompiled ->
+ ReCompiled;
+ ListRE ->
+ unicode:characters_to_binary(ListRE,unicode)
+ end,
+ Ret = re:run(Subject,RE,Options),
+ case RetType of
+ binary ->
+ ubinarify(Ret,Subject);
+ list ->
+ ulistify(Ret,Subject);
+ _ ->
+ Ret
+ end.
+
+
+
+%% Might be called either with two-tuple (if regexp was already compiled)
+%% or with 3-tuple (saving original RE for exceptions
+grun(Subject,RE,{Options,NeedClean}) ->
+ try
+ grun2(Subject,RE,{Options,NeedClean})
+ catch
+ error:AnyError ->
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [Subject,RE,Options])),
+ erlang:raise(error,AnyError,[{Mod,run,L}|Rest])
+ end;
+grun(Subject,RE,{Options,NeedClean,OrigRE}) ->
+ try
+ grun2(Subject,RE,{Options,NeedClean})
+ catch
+ error:AnyError ->
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [Subject,OrigRE,Options])),
+ erlang:raise(error,AnyError,[{Mod,run,L}|Rest])
+ end.
+
+grun2(Subject,RE,{Options,NeedClean}) ->
+ Unicode = check_for_unicode(RE,Options),
+ FlatSubject =
+ case is_binary(Subject) of
+ true ->
+ Subject;
+ false ->
+ case Unicode of
+ true ->
+ unicode:characters_to_binary(Subject,unicode);
+ false ->
+ iolist_to_binary(Subject)
+ end
+ end,
+ do_grun(FlatSubject,Subject,Unicode,RE,{Options,NeedClean}).
+
+do_grun(FlatSubject,Subject,Unicode,RE,{Options0,NeedClean}) ->
+ {StrippedOptions, InitialOffset,
+ SelectReturn, ConvertReturn} =
+ case (catch
+ process_parameters(Options0, 0, false, index, NeedClean)) of
+ badlist ->
+ erlang:error(badarg,[Subject,RE,Options0]);
+ CorrectReturn ->
+ CorrectReturn
+ end,
+ postprocess(loopexec(FlatSubject,RE,InitialOffset,
+ byte_size(FlatSubject),
+ Unicode,StrippedOptions),
+ SelectReturn,ConvertReturn,FlatSubject,Unicode).
+
+loopexec(_,_,X,Y,_,_) when X > Y ->
+ {match,[]};
+loopexec(Subject,RE,X,Y,Unicode,Options) ->
+ case re:run(Subject,RE,[{offset,X}]++Options) of
+ nomatch ->
+ {match,[]};
+ {match,[{A,B}|More]} ->
+ {match,Rest} =
+ case B>0 of
+ true ->
+ loopexec(Subject,RE,A+B,Y,Unicode,Options);
+ false ->
+ {match,M} =
+ case re:run(Subject,RE,[{offset,X},notempty,
+ anchored]++Options) of
+ nomatch ->
+ {match,[]};
+ {match,Other} ->
+ {match,Other}
+ end,
+ NewA = case M of
+ [{_,NStep}|_] when NStep > 0 ->
+ A+NStep;
+ _ ->
+ forward(Subject,A,1,Unicode)
+ end,
+ {match,MM} = loopexec(Subject,RE,NewA,Y,
+ Unicode,Options),
+ case M of
+ [] ->
+ {match,MM};
+ _ ->
+ {match,[M | MM]}
+ end
+ end,
+ {match,[[{A,B}|More] | Rest]}
+ end.
+
+forward(_Chal,A,0,_) ->
+ A;
+forward(_Chal,A,N,false) ->
+ A+N;
+forward(Chal,A,N,true) ->
+ <<_:A/binary,Tl/binary>> = Chal,
+ Forw = case Tl of
+ <<1:1,1:1,0:1,_:5,_/binary>> ->
+ 2;
+ <<1:1,1:1,1:1,0:1,_:4,_/binary>> ->
+ 3;
+ <<1:1,1:1,1:1,1:1,0:1,_:3,_/binary>> ->
+ 4;
+ _ ->
+ 1
+ end,
+ forward(Chal,A+Forw,N-1,true).
+
+copt(caseless) ->
+ true;
+copt(dollar_endonly) ->
+ true;
+copt(dotall) ->
+ true;
+copt(extended) ->
+ true;
+copt(firstline) ->
+ true;
+copt(multiline) ->
+ true;
+copt(no_auto_capture) ->
+ true;
+copt(dupnames) ->
+ true;
+copt(ungreedy) ->
+ true;
+copt(unicode) ->
+ true;
+copt(_) ->
+ false.
+
+%bothopt({newline,_}) ->
+% true;
+%bothopt(anchored) ->
+% true;
+%bothopt(_) ->
+% false.
+
+runopt(notempty) ->
+ true;
+runopt(notbol) ->
+ true;
+runopt(noteol) ->
+ true;
+runopt({offset,_}) ->
+ true;
+runopt({capture,_,_}) ->
+ true;
+runopt({capture,_}) ->
+ true;
+runopt(global) ->
+ true;
+runopt(_) ->
+ false.
diff --git a/lib/stdlib/src/regexp.erl b/lib/stdlib/src/regexp.erl
new file mode 100644
index 0000000000..8f5994bbee
--- /dev/null
+++ b/lib/stdlib/src/regexp.erl
@@ -0,0 +1,490 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(regexp).
+
+%% This entire module is deprecated and will be removed in a future
+%% release. Use the 're' module instead.
+%%
+%% This module provides a basic set of regular expression functions
+%% for strings. The functions provided are taken from AWK.
+%%
+%% Note that we interpret the syntax tree of a regular expression
+%% directly instead of converting it to an NFA and then interpreting
+%% that. This method seems to go significantly faster.
+
+-export([sh_to_awk/1,parse/1,format_error/1,match/2,first_match/2,matches/2]).
+-export([sub/3,gsub/3,split/2]).
+
+-deprecated([sh_to_awk/1,parse/1,format_error/1,match/2,first_match/2,matches/2]).
+-deprecated([sub/3,gsub/3,split/2]).
+
+-import(string, [substr/2,substr/3]).
+-import(lists, [reverse/1]).
+
+%% -type matchres() = {match,Start,Length} | nomatch | {error,E}.
+%% -type subres() = {ok,RepString,RepCount} | {error,E}.
+%% -type splitres() = {ok,[SubString]} | {error,E}.
+
+%%-compile([export_all]).
+
+%% This is the regular expression grammar used. It is equivalent to the
+%% one used in AWK, except that we allow ^ $ to be used anywhere and fail
+%% in the matching.
+%%
+%% reg -> reg1 : '$1'.
+%% reg1 -> reg1 "|" reg2 : {'or','$1','$2'}.
+%% reg1 -> reg2 : '$1'.
+%% reg2 -> reg2 reg3 : {concat,'$1','$2'}.
+%% reg2 -> reg3 : '$1'.
+%% reg3 -> reg3 "*" : {kclosure,'$1'}.
+%% reg3 -> reg3 "+" : {pclosure,'$1'}.
+%% reg3 -> reg3 "?" : {optional,'$1'}.
+%% reg3 -> reg4 : '$1'.
+%% reg4 -> "(" reg ")" : '$2'.
+%% reg4 -> "\\" char : '$2'.
+%% reg4 -> "^" : bos.
+%% reg4 -> "$" : eos.
+%% reg4 -> "." : char.
+%% reg4 -> "[" class "]" : {char_class,char_class('$2')}
+%% reg4 -> "[" "^" class "]" : {comp_class,char_class('$3')}
+%% reg4 -> "\"" chars "\"" : char_string('$2')
+%% reg4 -> char : '$1'.
+%% reg4 -> empty : epsilon.
+%% The grammar of the current regular expressions. The actual parser
+%% is a recursive descent implementation of the grammar.
+
+reg(S) -> reg1(S).
+
+%% reg1 -> reg2 reg1'
+%% reg1' -> "|" reg2
+%% reg1' -> empty
+
+reg1(S0) ->
+ {L,S1} = reg2(S0),
+ reg1p(S1, L).
+
+reg1p([$||S0], L) ->
+ {R,S1} = reg2(S0),
+ reg1p(S1, {'or',L,R});
+reg1p(S, L) -> {L,S}.
+
+%% reg2 -> reg3 reg2'
+%% reg2' -> reg3
+%% reg2' -> empty
+
+reg2(S0) ->
+ {L,S1} = reg3(S0),
+ reg2p(S1, L).
+
+reg2p([C|S0], L) when C =/= $|, C =/= $) ->
+ {R,S1} = reg3([C|S0]),
+ reg2p(S1, {concat,L,R});
+reg2p(S, L) -> {L,S}.
+
+%% reg3 -> reg4 reg3'
+%% reg3' -> "*" reg3'
+%% reg3' -> "+" reg3'
+%% reg3' -> "?" reg3'
+%% reg3' -> empty
+
+reg3(S0) ->
+ {L,S1} = reg4(S0),
+ reg3p(S1, L).
+
+reg3p([$*|S], L) -> reg3p(S, {kclosure,L});
+reg3p([$+|S], L) -> reg3p(S, {pclosure,L});
+reg3p([$?|S], L) -> reg3p(S, {optional,L});
+reg3p(S, L) -> {L,S}.
+
+-define(HEX(C), C >= $0 andalso C =< $9 orelse
+ C >= $A andalso C =< $F orelse
+ C >= $a andalso C =< $f).
+
+reg4([$(|S0]) ->
+ case reg(S0) of
+ {R,[$)|S1]} -> {R,S1};
+ {_R,_S} -> throw({error,{unterminated,"("}})
+ end;
+reg4([$\\,O1,O2,O3|S]) when
+ O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 ->
+ {(O1*8 + O2)*8 + O3 - 73*$0,S};
+reg4([$\\,$x,H1,H2|S]) when ?HEX(H1), ?HEX(H2) ->
+ {erlang:list_to_integer([H1,H2], 16),S};
+reg4([$\\,$x,${|S]) ->
+ hex(S, []);
+reg4([$\\,$x|_]) ->
+ throw({error,{illegal,[$x]}});
+reg4([$\\,C|S]) -> {escape_char(C),S};
+reg4([$\\]) -> throw({error,{unterminated,"\\"}});
+reg4([$^|S]) -> {bos,S};
+reg4([$$|S]) -> {eos,S};
+reg4([$.|S]) -> {{comp_class,"\n"},S};
+reg4("[^" ++ S0) ->
+ case char_class(S0) of
+ {Cc,[$]|S1]} -> {{comp_class,Cc},S1};
+ {_Cc,_S} -> throw({error,{unterminated,"["}})
+ end;
+reg4([$[|S0]) ->
+ case char_class(S0) of
+ {Cc,[$]|S1]} -> {{char_class,Cc},S1};
+ {_Cc,_S1} -> throw({error,{unterminated,"["}})
+ end;
+%reg4([$"|S0]) ->
+% case char_string(S0) of
+% {St,[$"|S1]} -> {St,S1};
+% {St,S1} -> throw({error,{unterminated,"\""}})
+% end;
+reg4([C|S]) when C =/= $*, C =/= $+, C =/= $?, C =/= $] -> {C,S};
+reg4([C|_S]) -> throw({error,{illegal,[C]}});
+reg4([]) -> {epsilon,[]}.
+
+hex([C|Cs], L) when ?HEX(C) ->
+ hex(Cs, [C|L]);
+hex([$}|S], L) ->
+ case catch erlang:list_to_integer(lists:reverse(L), 16) of
+ V when V =< 16#FF ->
+ {V,S};
+ _ ->
+ throw({error,{illegal,[$}]}})
+ end;
+hex(_S, _) ->
+ throw({error,{unterminated,"\\x{"}}).
+
+escape_char($n) -> $\n; %\n = LF
+escape_char($r) -> $\r; %\r = CR
+escape_char($t) -> $\t; %\t = TAB
+escape_char($v) -> $\v; %\v = VT
+escape_char($b) -> $\b; %\b = BS
+escape_char($f) -> $\f; %\f = FF
+escape_char($e) -> $\e; %\e = ESC
+escape_char($s) -> $\s; %\s = SPACE
+escape_char($d) -> $\d; %\d = DEL
+escape_char(C) -> C.
+
+char_class([$]|S]) -> char_class(S, [$]]);
+char_class(S) -> char_class(S, []).
+
+char($\\, [O1,O2,O3|S]) when
+ O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 ->
+ {(O1*8 + O2)*8 + O3 - 73*$0,S};
+char($\\, [$x,H1,H2|S]) when ?HEX(H1), ?HEX(H2) ->
+ {erlang:list_to_integer([H1,H2], 16),S};
+char($\\,[$x,${|S]) ->
+ hex(S, []);
+char($\\,[$x|_]) ->
+ throw({error,{illegal,[$x]}});
+char($\\, [C|S]) -> {escape_char(C),S};
+char(C, S) -> {C,S}.
+
+char_class([C1|S0], Cc) when C1 =/= $] ->
+ case char(C1, S0) of
+ {Cf,[$-,C2|S1]} when C2 =/= $] ->
+ case char(C2, S1) of
+ {Cl,S2} when Cf < Cl -> char_class(S2, [{Cf,Cl}|Cc]);
+ {Cl,_S2} -> throw({error,{char_class,[Cf,$-,Cl]}})
+ end;
+ {C,S1} -> char_class(S1, [C|Cc])
+ end;
+char_class(S, Cc) -> {Cc,S}.
+
+%char_string([C|S]) when C =/= $" -> char_string(S, C);
+%char_string(S) -> {epsilon,S}.
+
+%char_string([C|S0], L) when C =/= $" ->
+% char_string(S0, {concat,L,C});
+%char_string(S, L) -> {L,S}.
+
+%% -deftype re_app_res() = {match,RestPos,Rest} | nomatch.
+
+%% re_apply(String, StartPos, RegExp) -> re_app_res().
+%%
+%% Apply the (parse of the) regular expression RegExp to String. If
+%% there is a match return the position of the remaining string and
+%% the string if else return 'nomatch'. BestMatch specifies if we want
+%% the longest match, or just a match.
+%%
+%% StartPos should be the real start position as it is used to decide
+%% if we ae at the beginning of the string.
+%%
+%% Pass two functions to re_apply_or so it can decide, on the basis
+%% of BestMatch, whether to just any take any match or try both to
+%% find the longest. This is slower but saves duplicatng code.
+
+re_apply(S, St, RE) -> re_apply(RE, [], S, St).
+
+re_apply(epsilon, More, S, P) -> %This always matches
+ re_apply_more(More, S, P);
+re_apply({'or',RE1,RE2}, More, S, P) ->
+ re_apply_or(re_apply(RE1, More, S, P),
+ re_apply(RE2, More, S, P));
+re_apply({concat,RE1,RE2}, More, S0, P) ->
+ re_apply(RE1, [RE2|More], S0, P);
+re_apply({kclosure,CE}, More, S, P) ->
+ %% Be careful with the recursion, explicitly do one call before
+ %% looping.
+ re_apply_or(re_apply_more(More, S, P),
+ re_apply(CE, [{kclosure,CE}|More], S, P));
+re_apply({pclosure,CE}, More, S, P) ->
+ re_apply(CE, [{kclosure,CE}|More], S, P);
+re_apply({optional,CE}, More, S, P) ->
+ re_apply_or(re_apply_more(More, S, P),
+ re_apply(CE, More, S, P));
+re_apply(bos, More, S, 1) -> re_apply_more(More, S, 1);
+re_apply(eos, More, [$\n|S], P) -> re_apply_more(More, S, P);
+re_apply(eos, More, [], P) -> re_apply_more(More, [], P);
+re_apply({char_class,Cc}, More, [C|S], P) ->
+ case in_char_class(C, Cc) of
+ true -> re_apply_more(More, S, P+1);
+ false -> nomatch
+ end;
+re_apply({comp_class,Cc}, More, [C|S], P) ->
+ case in_char_class(C, Cc) of
+ true -> nomatch;
+ false -> re_apply_more(More, S, P+1)
+ end;
+re_apply(C, More, [C|S], P) when is_integer(C) ->
+ re_apply_more(More, S, P+1);
+re_apply(_RE, _More, _S, _P) -> nomatch.
+
+%% re_apply_more([RegExp], String, Length) -> re_app_res().
+
+re_apply_more([RE|More], S, P) -> re_apply(RE, More, S, P);
+re_apply_more([], S, P) -> {match,P,S}.
+
+%% in_char_class(Char, Class) -> bool().
+
+in_char_class(C, [{C1,C2}|_Cc]) when C >= C1, C =< C2 -> true;
+in_char_class(C, [C|_Cc]) -> true;
+in_char_class(C, [_|Cc]) -> in_char_class(C, Cc);
+in_char_class(_C, []) -> false.
+
+%% re_apply_or(Match1, Match2) -> re_app_res().
+%% If we want the best match then choose the longest match, else just
+%% choose one by trying sequentially.
+
+re_apply_or({match,P1,S1}, {match,P2,_S2}) when P1 >= P2 -> {match,P1,S1};
+re_apply_or({match,_P1,_S1}, {match,P2,S2}) -> {match,P2,S2};
+re_apply_or(nomatch, R2) -> R2;
+re_apply_or(R1, nomatch) -> R1.
+
+%% sh_to_awk(ShellRegExp)
+%% Convert a sh style regexp into a full AWK one. The main difficulty is
+%% getting character sets right as the conventions are different.
+
+sh_to_awk(Sh) -> "^(" ++ sh_to_awk_1(Sh). %Fix the beginning
+
+sh_to_awk_1([$*|Sh]) -> %This matches any string
+ ".*" ++ sh_to_awk_1(Sh);
+sh_to_awk_1([$?|Sh]) -> %This matches any character
+ [$.|sh_to_awk_1(Sh)];
+sh_to_awk_1([$[,$^,$]|Sh]) -> %This takes careful handling
+ "\\^" ++ sh_to_awk_1(Sh);
+sh_to_awk_1("[^" ++ Sh) -> [$[|sh_to_awk_2(Sh, true)];
+sh_to_awk_1("[!" ++ Sh) -> "[^" ++ sh_to_awk_2(Sh, false);
+sh_to_awk_1([$[|Sh]) -> [$[|sh_to_awk_2(Sh, false)];
+sh_to_awk_1([C|Sh]) ->
+ %% Unspecialise everything else which is not an escape character.
+ case special_char(C) of
+ true -> [$\\,C|sh_to_awk_1(Sh)];
+ false -> [C|sh_to_awk_1(Sh)]
+ end;
+sh_to_awk_1([]) -> ")$". %Fix the end
+
+sh_to_awk_2([$]|Sh], UpArrow) -> [$]|sh_to_awk_3(Sh, UpArrow)];
+sh_to_awk_2(Sh, UpArrow) -> sh_to_awk_3(Sh, UpArrow).
+
+sh_to_awk_3([$]|Sh], true) -> "^]" ++ sh_to_awk_1(Sh);
+sh_to_awk_3([$]|Sh], false) -> [$]|sh_to_awk_1(Sh)];
+sh_to_awk_3([C|Sh], UpArrow) -> [C|sh_to_awk_3(Sh, UpArrow)];
+sh_to_awk_3([], true) -> [$^|sh_to_awk_1([])];
+sh_to_awk_3([], false) -> sh_to_awk_1([]).
+
+%% -type special_char(char()) -> bool().
+%% Test if a character is a special character.
+
+special_char($|) -> true;
+special_char($*) -> true;
+special_char($+) -> true;
+special_char($?) -> true;
+special_char($() -> true;
+special_char($)) -> true;
+special_char($\\) -> true;
+special_char($^) -> true;
+special_char($$) -> true;
+special_char($.) -> true;
+special_char($[) -> true;
+special_char($]) -> true;
+special_char($") -> true;
+special_char(_C) -> false.
+
+%% parse(RegExp) -> {ok,RE} | {error,E}.
+%% Parse the regexp described in the string RegExp.
+
+parse(S) ->
+ case catch reg(S) of
+ {R,[]} -> {ok,R};
+ {_R,[C|_]} -> {error,{illegal,[C]}};
+ {error,E} -> {error,E}
+ end.
+
+%% format_error(Error) -> String.
+
+format_error({illegal,What}) -> ["illegal character `",What,"'"];
+format_error({unterminated,What}) -> ["unterminated `",What,"'"];
+format_error({char_class,What}) ->
+ ["illegal character class ",io_lib:write_string(What)].
+
+%% -type match(String, RegExp) -> matchres().
+%% Find the longest match of RegExp in String.
+
+match(S, RegExp) when is_list(RegExp) ->
+ case parse(RegExp) of
+ {ok,RE} -> match(S, RE);
+ {error,E} -> {error,E}
+ end;
+match(S, RE) ->
+ case match(RE, S, 1, 0, -1) of
+ {Start,Len} when Len >= 0 ->
+ {match,Start,Len};
+ {_Start,_Len} -> nomatch
+ end.
+
+match(RE, S, St, Pos, L) ->
+ case first_match(RE, S, St) of
+ {St1,L1} ->
+ Nst = St1 + 1,
+ if L1 > L -> match(RE, lists:nthtail(Nst-St, S), Nst, St1, L1);
+ true -> match(RE, lists:nthtail(Nst-St, S), Nst, Pos, L)
+ end;
+ nomatch -> {Pos,L}
+ end.
+
+%% -type first_match(String, RegExp) -> matchres().
+%% Find the first match of RegExp in String.
+
+first_match(S, RegExp) when is_list(RegExp) ->
+ case parse(RegExp) of
+ {ok,RE} -> first_match(S, RE);
+ {error,E} -> {error,E}
+ end;
+first_match(S, RE) ->
+ case first_match(RE, S, 1) of
+ {Start,Len} when Len >= 0 ->
+ {match,Start,Len};
+ nomatch -> nomatch
+ end.
+
+first_match(RE, S, St) when S =/= [] ->
+ case re_apply(S, St, RE) of
+ {match,P,_Rest} -> {St,P-St};
+ nomatch -> first_match(RE, tl(S), St+1)
+ end;
+first_match(_RE, [], _St) -> nomatch.
+
+%% -type matches(String, RegExp) -> {match,[{Start,Length}]} | {error,E}.
+%% Return the all the non-overlapping matches of RegExp in String.
+
+matches(S, RegExp) when is_list(RegExp) ->
+ case parse(RegExp) of
+ {ok,RE} -> matches(S, RE);
+ {error,E} -> {error,E}
+ end;
+matches(S, RE) ->
+ {match,matches(S, RE, 1)}.
+
+matches(S, RE, St) ->
+ case first_match(RE, S, St) of
+ {St1,0} -> [{St1,0}|matches(substr(S, St1+2-St), RE, St1+1)];
+ {St1,L1} -> [{St1,L1}|matches(substr(S, St1+L1+1-St), RE, St1+L1)];
+ nomatch -> []
+ end.
+
+%% -type sub(String, RegExp, Replace) -> subsres().
+%% Substitute the first match of the regular expression RegExp with
+%% the string Replace in String. Accept pre-parsed regular
+%% expressions.
+
+sub(String, RegExp, Rep) when is_list(RegExp) ->
+ case parse(RegExp) of
+ {ok,RE} -> sub(String, RE, Rep);
+ {error,E} -> {error,E}
+ end;
+sub(String, RE, Rep) ->
+ Ss = sub_match(String, RE, 1),
+ {ok,sub_repl(Ss, Rep, String, 1),length(Ss)}.
+
+sub_match(S, RE, St) ->
+ case first_match(RE, S, St) of
+ {St1,L1} -> [{St1,L1}];
+ nomatch -> []
+ end.
+
+sub_repl([{St,L}|Ss], Rep, S, Pos) ->
+ Rs = sub_repl(Ss, Rep, S, St+L),
+ substr(S, Pos, St-Pos) ++ sub_repl(Rep, substr(S, St, L), Rs);
+sub_repl([], _Rep, S, Pos) -> substr(S, Pos).
+
+sub_repl([$&|Rep], M, Rest) -> M ++ sub_repl(Rep, M, Rest);
+sub_repl("\\&" ++ Rep, M, Rest) -> [$&|sub_repl(Rep, M, Rest)];
+sub_repl([C|Rep], M, Rest) -> [C|sub_repl(Rep, M, Rest)];
+sub_repl([], _M, Rest) -> Rest.
+
+%% -type gsub(String, RegExp, Replace) -> subres().
+%% Substitute every match of the regular expression RegExp with the
+%% string New in String. Accept pre-parsed regular expressions.
+
+gsub(String, RegExp, Rep) when is_list(RegExp) ->
+ case parse(RegExp) of
+ {ok,RE} -> gsub(String, RE, Rep);
+ {error,E} -> {error,E}
+ end;
+gsub(String, RE, Rep) ->
+ Ss = matches(String, RE, 1),
+ {ok,sub_repl(Ss, Rep, String, 1),length(Ss)}.
+
+%% -type split(String, RegExp) -> splitres().
+%% Split a string into substrings where the RegExp describes the
+%% field seperator. The RegExp " " is specially treated.
+
+split(String, " ") -> %This is really special
+ {ok,RE} = parse("[ \t]+"),
+ case split_apply(String, RE, true) of
+ [[]|Ss] -> {ok,Ss};
+ Ss -> {ok,Ss}
+ end;
+split(String, RegExp) when is_list(RegExp) ->
+ case parse(RegExp) of
+ {ok,RE} -> {ok,split_apply(String, RE, false)};
+ {error,E} -> {error,E}
+ end;
+split(String, RE) -> {ok,split_apply(String, RE, false)}.
+
+split_apply(S, RE, Trim) -> split_apply(S, 1, RE, Trim, []).
+
+split_apply([], _P, _RE, true, []) -> [];
+split_apply([], _P, _RE, _T, Sub) -> [reverse(Sub)];
+split_apply(S, P, RE, T, Sub) ->
+ case re_apply(S, P, RE) of
+ {match,P,_Rest} ->
+ split_apply(tl(S), P+1, RE, T, [hd(S)|Sub]);
+ {match,P1,Rest} ->
+ [reverse(Sub)|split_apply(Rest, P1, RE, T, [])];
+ nomatch ->
+ split_apply(tl(S), P+1, RE, T, [hd(S)|Sub])
+ end.
diff --git a/lib/stdlib/src/sets.erl b/lib/stdlib/src/sets.erl
new file mode 100644
index 0000000000..bcddca2567
--- /dev/null
+++ b/lib/stdlib/src/sets.erl
@@ -0,0 +1,417 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% We use the dynamic hashing techniques by Per-�ke Larsson as
+%% described in "The Design and Implementation of Dynamic Hashing for
+%% Sets and Tables in Icon" by Griswold and Townsend. Much of the
+%% terminology comes from that paper as well.
+
+%% The segments are all of the same fixed size and we just keep
+%% increasing the size of the top tuple as the table grows. At the
+%% end of the segments tuple we keep an empty segment which we use
+%% when we expand the segments. The segments are expanded by doubling
+%% every time n reaches maxn instead of increasing the tuple one
+%% element at a time. It is easier and does not seem detrimental to
+%% speed. The same applies when contracting the segments.
+%%
+%% Note that as the order of the keys is undefined we may freely
+%% reorder keys within in a bucket.
+
+-module(sets).
+
+%% Standard interface.
+-export([new/0,is_set/1,size/1,to_list/1,from_list/1]).
+-export([is_element/2,add_element/2,del_element/2]).
+-export([union/2,union/1,intersection/2,intersection/1]).
+-export([is_disjoint/2]).
+-export([subtract/2,is_subset/2]).
+-export([fold/3,filter/2]).
+
+%% Note: mk_seg/1 must be changed too if seg_size is changed.
+-define(seg_size, 16).
+-define(max_seg, 32).
+-define(expand_load, 5).
+-define(contract_load, 3).
+-define(exp_size, ?seg_size * ?expand_load).
+-define(con_size, ?seg_size * ?contract_load).
+
+%%------------------------------------------------------------------------------
+
+-type seg() :: tuple().
+-type segs() :: tuple().
+
+%% Define a hash set. The default values are the standard ones.
+-record(set,
+ {size=0 :: non_neg_integer(), % Number of elements
+ n=?seg_size :: non_neg_integer(), % Number of active slots
+ maxn=?seg_size :: pos_integer(), % Maximum slots
+ bso=?seg_size div 2 :: non_neg_integer(), % Buddy slot offset
+ exp_size=?exp_size :: non_neg_integer(), % Size to expand at
+ con_size=?con_size :: non_neg_integer(), % Size to contract at
+ empty :: seg(), % Empty segment
+ segs :: segs() % Segments
+ }).
+%% A declaration equivalent to the following one is hard-coded in erl_types.
+%% That declaration contains hard-coded information about the #set{}
+%% record and the types of its fields. So, please make sure that any
+%% changes to its structure are also propagated to erl_types.erl.
+%%
+%% -opaque set() :: #set{}.
+
+%%------------------------------------------------------------------------------
+
+%% new() -> Set
+-spec new() -> set().
+new() ->
+ Empty = mk_seg(?seg_size),
+ #set{empty = Empty, segs = {Empty}}.
+
+%% is_set(Set) -> boolean().
+%% Return 'true' if Set is a set of elements, else 'false'.
+-spec is_set(term()) -> boolean().
+is_set(#set{}) -> true;
+is_set(_) -> false.
+
+%% size(Set) -> int().
+%% Return the number of elements in Set.
+-spec size(set()) -> non_neg_integer().
+size(S) -> S#set.size.
+
+%% to_list(Set) -> [Elem].
+%% Return the elements in Set as a list.
+-spec to_list(set()) -> [term()].
+to_list(S) ->
+ fold(fun (Elem, List) -> [Elem|List] end, [], S).
+
+%% from_list([Elem]) -> Set.
+%% Build a set from the elements in List.
+-spec from_list([term()]) -> set().
+from_list(L) ->
+ lists:foldl(fun (E, S) -> add_element(E, S) end, new(), L).
+
+%% is_element(Element, Set) -> boolean().
+%% Return 'true' if Element is an element of Set, else 'false'.
+-spec is_element(term(), set()) -> boolean().
+is_element(E, S) ->
+ Slot = get_slot(S, E),
+ Bkt = get_bucket(S, Slot),
+ lists:member(E, Bkt).
+
+%% add_element(Element, Set) -> Set.
+%% Return Set with Element inserted in it.
+-spec add_element(term(), set()) -> set().
+add_element(E, S0) ->
+ Slot = get_slot(S0, E),
+ {S1,Ic} = on_bucket(fun (B0) -> add_bkt_el(E, B0, B0) end, S0, Slot),
+ maybe_expand(S1, Ic).
+
+-spec add_bkt_el(T, [T], [T]) -> {[T], 0 | 1}.
+add_bkt_el(E, [E|_], Bkt) -> {Bkt,0};
+add_bkt_el(E, [_|B], Bkt) ->
+ add_bkt_el(E, B, Bkt);
+add_bkt_el(E, [], Bkt) -> {[E|Bkt],1}.
+
+%% del_element(Element, Set) -> Set.
+%% Return Set but with Element removed.
+-spec del_element(term(), set()) -> set().
+del_element(E, S0) ->
+ Slot = get_slot(S0, E),
+ {S1,Dc} = on_bucket(fun (B0) -> del_bkt_el(E, B0) end, S0, Slot),
+ maybe_contract(S1, Dc).
+
+-spec del_bkt_el(T, [T]) -> {[T], 0 | 1}.
+del_bkt_el(E, [E|Bkt]) -> {Bkt,1};
+del_bkt_el(E, [Other|Bkt0]) ->
+ {Bkt1,Dc} = del_bkt_el(E, Bkt0),
+ {[Other|Bkt1],Dc};
+del_bkt_el(_, []) -> {[],0}.
+
+%% union(Set1, Set2) -> Set
+%% Return the union of Set1 and Set2.
+-spec union(set(), set()) -> set().
+union(S1, S2) when S1#set.size < S2#set.size ->
+ fold(fun (E, S) -> add_element(E, S) end, S2, S1);
+union(S1, S2) ->
+ fold(fun (E, S) -> add_element(E, S) end, S1, S2).
+
+%% union([Set]) -> Set
+%% Return the union of the list of sets.
+-spec union([set()]) -> set().
+union([S1,S2|Ss]) ->
+ union1(union(S1, S2), Ss);
+union([S]) -> S;
+union([]) -> new().
+
+-spec union1(set(), [set()]) -> set().
+union1(S1, [S2|Ss]) ->
+ union1(union(S1, S2), Ss);
+union1(S1, []) -> S1.
+
+%% intersection(Set1, Set2) -> Set.
+%% Return the intersection of Set1 and Set2.
+-spec intersection(set(), set()) -> set().
+intersection(S1, S2) when S1#set.size < S2#set.size ->
+ filter(fun (E) -> is_element(E, S2) end, S1);
+intersection(S1, S2) ->
+ filter(fun (E) -> is_element(E, S1) end, S2).
+
+%% intersection([Set]) -> Set.
+%% Return the intersection of the list of sets.
+-spec intersection([set(),...]) -> set().
+intersection([S1,S2|Ss]) ->
+ intersection1(intersection(S1, S2), Ss);
+intersection([S]) -> S.
+
+-spec intersection1(set(), [set()]) -> set().
+intersection1(S1, [S2|Ss]) ->
+ intersection1(intersection(S1, S2), Ss);
+intersection1(S1, []) -> S1.
+
+%% is_disjoint(Set1, Set2) -> boolean().
+%% Check whether Set1 and Set2 are disjoint.
+-spec is_disjoint(set(), set()) -> boolean().
+is_disjoint(S1, S2) when S1#set.size < S2#set.size ->
+ fold(fun (_, false) -> false;
+ (E, true) -> not is_element(E, S2)
+ end, true, S1);
+is_disjoint(S1, S2) ->
+ fold(fun (_, false) -> false;
+ (E, true) -> not is_element(E, S1)
+ end, true, S2).
+
+%% subtract(Set1, Set2) -> Set.
+%% Return all and only the elements of Set1 which are not also in
+%% Set2.
+-spec subtract(set(), set()) -> set().
+subtract(S1, S2) ->
+ filter(fun (E) -> not is_element(E, S2) end, S1).
+
+%% is_subset(Set1, Set2) -> boolean().
+%% Return 'true' when every element of Set1 is also a member of
+%% Set2, else 'false'.
+-spec is_subset(set(), set()) -> boolean().
+is_subset(S1, S2) ->
+ fold(fun (E, Sub) -> Sub andalso is_element(E, S2) end, true, S1).
+
+%% fold(Fun, Accumulator, Set) -> Accumulator.
+%% Fold function Fun over all elements in Set and return Accumulator.
+-spec fold(fun((_,_) -> _), T, set()) -> T.
+fold(F, Acc, D) -> fold_set(F, Acc, D).
+
+%% filter(Fun, Set) -> Set.
+%% Filter Set with Fun.
+-spec filter(fun((_) -> boolean()), set()) -> set().
+filter(F, D) -> filter_set(F, D).
+
+%% get_slot(Hashdb, Key) -> Slot.
+%% Get the slot. First hash on the new range, if we hit a bucket
+%% which has not been split use the unsplit buddy bucket.
+-spec get_slot(set(), term()) -> non_neg_integer().
+get_slot(T, Key) ->
+ H = erlang:phash(Key, T#set.maxn),
+ if
+ H > T#set.n -> H - T#set.bso;
+ true -> H
+ end.
+
+%% get_bucket(Hashdb, Slot) -> Bucket.
+-spec get_bucket(set(), non_neg_integer()) -> term().
+get_bucket(T, Slot) -> get_bucket_s(T#set.segs, Slot).
+
+%% on_bucket(Fun, Hashdb, Slot) -> {NewHashDb,Result}.
+%% Apply Fun to the bucket in Slot and replace the returned bucket.
+-spec on_bucket(fun((_) -> {[_], 0 | 1}), set(), non_neg_integer()) ->
+ {set(), 0 | 1}.
+on_bucket(F, T, Slot) ->
+ SegI = ((Slot-1) div ?seg_size) + 1,
+ BktI = ((Slot-1) rem ?seg_size) + 1,
+ Segs = T#set.segs,
+ Seg = element(SegI, Segs),
+ B0 = element(BktI, Seg),
+ {B1, Res} = F(B0), %Op on the bucket.
+ {T#set{segs = setelement(SegI, Segs, setelement(BktI, Seg, B1))},Res}.
+
+%% fold_set(Fun, Acc, Dictionary) -> Dictionary.
+%% filter_set(Fun, Dictionary) -> Dictionary.
+
+%% Work functions for fold and filter operations. These traverse the
+%% hash structure rebuilding as necessary. Note we could have
+%% implemented map and hash using fold but these should be faster.
+%% We hope!
+
+fold_set(F, Acc, D) when is_function(F, 2) ->
+ Segs = D#set.segs,
+ fold_segs(F, Acc, Segs, tuple_size(Segs)).
+
+fold_segs(F, Acc, Segs, I) when I >= 1 ->
+ Seg = element(I, Segs),
+ fold_segs(F, fold_seg(F, Acc, Seg, tuple_size(Seg)), Segs, I-1);
+fold_segs(_, Acc, _, _) -> Acc.
+
+fold_seg(F, Acc, Seg, I) when I >= 1 ->
+ fold_seg(F, fold_bucket(F, Acc, element(I, Seg)), Seg, I-1);
+fold_seg(_, Acc, _, _) -> Acc.
+
+fold_bucket(F, Acc, [E|Bkt]) ->
+ fold_bucket(F, F(E, Acc), Bkt);
+fold_bucket(_, Acc, []) -> Acc.
+
+filter_set(F, D) when is_function(F, 1) ->
+ Segs0 = tuple_to_list(D#set.segs),
+ {Segs1,Fc} = filter_seg_list(F, Segs0, [], 0),
+ maybe_contract(D#set{segs = list_to_tuple(Segs1)}, Fc).
+
+filter_seg_list(F, [Seg|Segs], Fss, Fc0) ->
+ Bkts0 = tuple_to_list(Seg),
+ {Bkts1,Fc1} = filter_bkt_list(F, Bkts0, [], Fc0),
+ filter_seg_list(F, Segs, [list_to_tuple(Bkts1)|Fss], Fc1);
+filter_seg_list(_, [], Fss, Fc) ->
+ {lists:reverse(Fss, []),Fc}.
+
+filter_bkt_list(F, [Bkt0|Bkts], Fbs, Fc0) ->
+ {Bkt1,Fc1} = filter_bucket(F, Bkt0, [], Fc0),
+ filter_bkt_list(F, Bkts, [Bkt1|Fbs], Fc1);
+filter_bkt_list(_, [], Fbs, Fc) ->
+ {lists:reverse(Fbs),Fc}.
+
+filter_bucket(F, [E|Bkt], Fb, Fc) ->
+ case F(E) of
+ true -> filter_bucket(F, Bkt, [E|Fb], Fc);
+ false -> filter_bucket(F, Bkt, Fb, Fc+1)
+ end;
+filter_bucket(_, [], Fb, Fc) -> {Fb,Fc}.
+
+%% get_bucket_s(Segments, Slot) -> Bucket.
+%% put_bucket_s(Segments, Slot, Bucket) -> NewSegments.
+
+get_bucket_s(Segs, Slot) ->
+ SegI = ((Slot-1) div ?seg_size) + 1,
+ BktI = ((Slot-1) rem ?seg_size) + 1,
+ element(BktI, element(SegI, Segs)).
+
+put_bucket_s(Segs, Slot, Bkt) ->
+ SegI = ((Slot-1) div ?seg_size) + 1,
+ BktI = ((Slot-1) rem ?seg_size) + 1,
+ Seg = setelement(BktI, element(SegI, Segs), Bkt),
+ setelement(SegI, Segs, Seg).
+
+-spec maybe_expand(set(), 0 | 1) -> set().
+maybe_expand(T0, Ic) when T0#set.size + Ic > T0#set.exp_size ->
+ T = maybe_expand_segs(T0), %Do we need more segments.
+ N = T#set.n + 1, %Next slot to expand into
+ Segs0 = T#set.segs,
+ Slot1 = N - T#set.bso,
+ B = get_bucket_s(Segs0, Slot1),
+ Slot2 = N,
+ {B1,B2} = rehash(B, Slot1, Slot2, T#set.maxn),
+ Segs1 = put_bucket_s(Segs0, Slot1, B1),
+ Segs2 = put_bucket_s(Segs1, Slot2, B2),
+ T#set{size = T#set.size + Ic,
+ n = N,
+ exp_size = N * ?expand_load,
+ con_size = N * ?contract_load,
+ segs = Segs2};
+maybe_expand(T, Ic) -> T#set{size = T#set.size + Ic}.
+
+-spec maybe_expand_segs(set()) -> set().
+maybe_expand_segs(T) when T#set.n =:= T#set.maxn ->
+ T#set{maxn = 2 * T#set.maxn,
+ bso = 2 * T#set.bso,
+ segs = expand_segs(T#set.segs, T#set.empty)};
+maybe_expand_segs(T) -> T.
+
+-spec maybe_contract(set(), non_neg_integer()) -> set().
+maybe_contract(T, Dc) when T#set.size - Dc < T#set.con_size,
+ T#set.n > ?seg_size ->
+ N = T#set.n,
+ Slot1 = N - T#set.bso,
+ Segs0 = T#set.segs,
+ B1 = get_bucket_s(Segs0, Slot1),
+ Slot2 = N,
+ B2 = get_bucket_s(Segs0, Slot2),
+ Segs1 = put_bucket_s(Segs0, Slot1, B1 ++ B2),
+ Segs2 = put_bucket_s(Segs1, Slot2, []), %Clear the upper bucket
+ N1 = N - 1,
+ maybe_contract_segs(T#set{size = T#set.size - Dc,
+ n = N1,
+ exp_size = N1 * ?expand_load,
+ con_size = N1 * ?contract_load,
+ segs = Segs2});
+maybe_contract(T, Dc) -> T#set{size = T#set.size - Dc}.
+
+-spec maybe_contract_segs(set()) -> set().
+maybe_contract_segs(T) when T#set.n =:= T#set.bso ->
+ T#set{maxn = T#set.maxn div 2,
+ bso = T#set.bso div 2,
+ segs = contract_segs(T#set.segs)};
+maybe_contract_segs(T) -> T.
+
+%% rehash(Bucket, Slot1, Slot2, MaxN) -> {Bucket1,Bucket2}.
+-spec rehash([T], integer(), pos_integer(), pos_integer()) -> {[T],[T]}.
+rehash([E|T], Slot1, Slot2, MaxN) ->
+ {L1,L2} = rehash(T, Slot1, Slot2, MaxN),
+ case erlang:phash(E, MaxN) of
+ Slot1 -> {[E|L1],L2};
+ Slot2 -> {L1,[E|L2]}
+ end;
+rehash([], _, _, _) -> {[],[]}.
+
+%% mk_seg(Size) -> Segment.
+-spec mk_seg(16) -> seg().
+mk_seg(16) -> {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}.
+
+%% expand_segs(Segs, EmptySeg) -> NewSegs.
+%% contract_segs(Segs) -> NewSegs.
+%% Expand/contract the segment tuple by doubling/halving the number
+%% of segments. We special case the powers of 2 upto 32, this should
+%% catch most case. N.B. the last element in the segments tuple is
+%% an extra element containing a default empty segment.
+-spec expand_segs(segs(), seg()) -> segs().
+expand_segs({B1}, Empty) ->
+ {B1,Empty};
+expand_segs({B1,B2}, Empty) ->
+ {B1,B2,Empty,Empty};
+expand_segs({B1,B2,B3,B4}, Empty) ->
+ {B1,B2,B3,B4,Empty,Empty,Empty,Empty};
+expand_segs({B1,B2,B3,B4,B5,B6,B7,B8}, Empty) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8,
+ Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty};
+expand_segs({B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16}, Empty) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16,
+ Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty,
+ Empty,Empty,Empty,Empty,Empty,Empty,Empty,Empty};
+expand_segs(Segs, Empty) ->
+ list_to_tuple(tuple_to_list(Segs)
+ ++ lists:duplicate(tuple_size(Segs), Empty)).
+
+-spec contract_segs(segs()) -> segs().
+contract_segs({B1,_}) ->
+ {B1};
+contract_segs({B1,B2,_,_}) ->
+ {B1,B2};
+contract_segs({B1,B2,B3,B4,_,_,_,_}) ->
+ {B1,B2,B3,B4};
+contract_segs({B1,B2,B3,B4,B5,B6,B7,B8,_,_,_,_,_,_,_,_}) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8};
+contract_segs({B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16,
+ _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_}) ->
+ {B1,B2,B3,B4,B5,B6,B7,B8,B9,B10,B11,B12,B13,B14,B15,B16};
+contract_segs(Segs) ->
+ Ss = tuple_size(Segs) div 2,
+ list_to_tuple(lists:sublist(tuple_to_list(Segs), 1, Ss)).
diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl
new file mode 100644
index 0000000000..a8d31b4e6b
--- /dev/null
+++ b/lib/stdlib/src/shell.erl
@@ -0,0 +1,1440 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(shell).
+
+-export([start/0, start/1, start/2, server/1, server/2, history/1, results/1]).
+-export([whereis_evaluator/0, whereis_evaluator/1]).
+-export([start_restricted/1, stop_restricted/0]).
+-export([local_allowed/3, non_local_allowed/3]).
+
+-define(LINEMAX, 30).
+-define(CHAR_MAX, 60).
+-define(DEF_HISTORY, 20).
+-define(DEF_RESULTS, 20).
+-define(DEF_CATCH_EXCEPTION, false).
+
+-define(RECORDS, shell_records).
+
+-define(MAXSIZE_HEAPBINARY, 64).
+
+%% When used as the fallback restricted shell callback module...
+local_allowed(q,[],State) ->
+ {true,State};
+local_allowed(_,_,State) ->
+ {false,State}.
+
+non_local_allowed({init,stop},[],State) ->
+ {true,State};
+non_local_allowed(_,_,State) ->
+ {false,State}.
+
+-spec start() -> pid().
+
+start() ->
+ start(false, false).
+
+start(init) ->
+ start(false, true);
+start(NoCtrlG) ->
+ start(NoCtrlG, false).
+
+start(NoCtrlG, StartSync) ->
+ code:ensure_loaded(user_default),
+ spawn(fun() -> server(NoCtrlG, StartSync) end).
+
+%% Find the pid of the current evaluator process.
+-spec whereis_evaluator() -> 'undefined' | pid().
+
+whereis_evaluator() ->
+ %% locate top group leader, always registered as user
+ %% can be implemented by group (normally) or user
+ %% (if oldshell or noshell)
+ case whereis(user) of
+ undefined ->
+ undefined;
+ User ->
+ %% get user_drv pid from group, or shell pid from user
+ case group:interfaces(User) of
+ [] -> % old- or noshell
+ case user:interfaces(User) of
+ [] ->
+ undefined;
+ [{shell,Shell}] ->
+ whereis_evaluator(Shell)
+ end;
+ [{user_drv,UserDrv}] ->
+ %% get current group pid from user_drv
+ case user_drv:interfaces(UserDrv) of
+ [] ->
+ undefined;
+ [{current_group,Group}] ->
+ %% get shell pid from group
+ GrIfs = group:interfaces(Group),
+ case lists:keyfind(shell, 1, GrIfs) of
+ {shell, Shell} ->
+ whereis_evaluator(Shell);
+ false ->
+ undefined
+ end
+ end
+ end
+ end.
+
+-spec whereis_evaluator(pid()) -> 'undefined' | pid().
+
+whereis_evaluator(Shell) ->
+ case process_info(Shell, dictionary) of
+ {dictionary,Dict} ->
+ case lists:keyfind(evaluator, 1, Dict) of
+ {_, Eval} when is_pid(Eval) ->
+ Eval;
+ _ ->
+ undefined
+ end;
+ _ ->
+ undefined
+ end.
+
+%% Call this function to start a user restricted shell
+%% from a normal shell session.
+-spec start_restricted(module()) -> {'error', code:load_error_rsn()}.
+
+start_restricted(RShMod) when is_atom(RShMod) ->
+ case code:ensure_loaded(RShMod) of
+ {module,RShMod} ->
+ application:set_env(stdlib, restricted_shell, RShMod),
+ exit(restricted_shell_started);
+ {error,What} = Error ->
+ error_logger:error_report(
+ lists:flatten(
+ io_lib:fwrite(
+ <<"Restricted shell module ~w not found: ~p\n">>,
+ [RShMod,What]))),
+ Error
+ end.
+
+-spec stop_restricted() -> no_return().
+
+stop_restricted() ->
+ application:unset_env(stdlib, restricted_shell),
+ exit(restricted_shell_stopped).
+
+default_packages() ->
+ [].
+%%% ['erl','erl.lang'].
+
+default_modules() ->
+ [].
+%%% [{pdict, 'erl.lang.proc.pdict'},
+%%% {keylist, 'erl.lang.list.keylist'},
+%%% {debug, 'erl.system.debug'}].
+
+-spec server(boolean(), boolean()) -> 'terminated'.
+
+server(NoCtrlG, StartSync) ->
+ put(no_control_g, NoCtrlG),
+ server(StartSync).
+
+
+%%% The shell should not start until the system is up and running.
+%%% We subscribe with init to get a notification of when.
+
+%%% In older releases we didn't syncronize the shell with init, but let it
+%%% start in parallell with other system processes. This was bad since
+%%% accessing the shell too early could interfere with the boot procedure.
+%%% Still, by means of a flag, we make it possible to start the shell the
+%%% old way (for backwards compatibility reasons). This should however not
+%%% be used unless for very special reasons necessary.
+
+-spec server(boolean()) -> 'terminated'.
+
+server(StartSync) ->
+ case init:get_argument(async_shell_start) of
+ {ok,_} ->
+ ok; % no sync with init
+ _ when not StartSync ->
+ ok;
+ _ ->
+ case init:notify_when_started(self()) of
+ started ->
+ ok;
+ _ ->
+ init:wait_until_started()
+ end
+ end,
+ %% Our spawner has fixed the process groups.
+ Bs0 = erl_eval:new_bindings(),
+ Bs = lists:foldl(fun ({K, V}, D) ->
+ erl_eval:add_binding({module,K}, V, D)
+ end,
+ lists:foldl(fun (P, D) ->
+ import_all(P, D)
+ end,
+ Bs0, default_packages()),
+ default_modules()),
+ %% io:fwrite("Imported modules: ~p.\n", [erl_eval:bindings(Bs)]),
+
+ %% Use an Ets table for record definitions. It takes too long to
+ %% send a huge term to and from the evaluator. Ets makes it
+ %% possible to have thousands of record definitions.
+ RT = ets:new(?RECORDS, [public,ordered_set]),
+ _ = initiate_records(Bs, RT),
+ process_flag(trap_exit, true),
+
+ %% Check if we're in user restricted mode.
+ RShErr =
+ case application:get_env(stdlib, restricted_shell) of
+ {ok,RShMod} ->
+ io:fwrite(<<"Restricted ">>, []),
+ case code:ensure_loaded(RShMod) of
+ {module,RShMod} ->
+ undefined;
+ {error,What} ->
+ {RShMod,What}
+ end;
+ undefined ->
+ undefined
+ end,
+
+ case get(no_control_g) of
+ true ->
+ io:fwrite(<<"Eshell V~s\n">>, [erlang:system_info(version)]);
+ _undefined_or_false ->
+ io:fwrite(<<"Eshell V~s (abort with ^G)\n">>,
+ [erlang:system_info(version)])
+ end,
+ erase(no_control_g),
+
+ case RShErr of
+ undefined ->
+ ok;
+ {RShMod2,What2} ->
+ io:fwrite(
+ <<"Warning! Restricted shell module ~w not found: ~p.\n"
+ "Only the commands q() and init:stop() will be allowed!\n">>,
+ [RShMod2,What2]),
+ application:set_env(stdlib, restricted_shell, ?MODULE)
+ end,
+
+ {History,Results} = check_and_get_history_and_results(),
+ server_loop(0, start_eval(Bs, RT, []), Bs, RT, [], History, Results).
+
+server_loop(N0, Eval_0, Bs0, RT, Ds0, History0, Results0) ->
+ N = N0 + 1,
+ {Res, Eval0} = get_command(prompt(N), Eval_0, Bs0, RT, Ds0),
+ case Res of
+ {ok,Es0,_EndLine} ->
+ case expand_hist(Es0, N) of
+ {ok,Es} ->
+ {V,Eval,Bs,Ds} = shell_cmd(Es, Eval0, Bs0, RT, Ds0),
+ {History,Results} = check_and_get_history_and_results(),
+ add_cmd(N, Es, V),
+ HB1 = del_cmd(command, N - History, N - History0, false),
+ HB = del_cmd(result, N - Results, N - Results0, HB1),
+ %% The following test makes sure that large binaries
+ %% (outside of the heap) are garbage collected as soon
+ %% as possible.
+ if
+ HB ->
+ garb(self());
+ true ->
+ ok
+ end,
+ server_loop(N, Eval, Bs, RT, Ds, History, Results);
+ {error,E} ->
+ fwrite_severity(benign, <<"~s">>, [E]),
+ server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0)
+ end;
+ {error,{Line,Mod,What},_EndLine} ->
+ fwrite_severity(benign, <<"~w: ~s">>,
+ [Line, Mod:format_error(What)]),
+ server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0);
+ {error,terminated} -> %Io process terminated
+ exit(Eval0, kill),
+ terminated;
+ {error,interrupted} -> %Io process interrupted us
+ exit(Eval0, kill),
+ {_,Eval,_,_} = shell_rep(Eval0, Bs0, RT, Ds0),
+ server_loop(N0, Eval, Bs0, RT, Ds0, History0, Results0);
+ {error,tokens} -> %Most probably unicode > 255
+ fwrite_severity(benign, <<"~w: Invalid tokens.">>,
+ [N]),
+ server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0);
+ {eof,_EndLine} ->
+ fwrite_severity(fatal, <<"Terminating erlang (~w)">>, [node()]),
+ halt();
+ eof ->
+ fwrite_severity(fatal, <<"Terminating erlang (~w)">>, [node()]),
+ halt()
+ end.
+
+get_command(Prompt, Eval, Bs, RT, Ds) ->
+ Parse = fun() -> exit(io:parse_erl_exprs(Prompt)) end,
+ Pid = spawn_link(Parse),
+ get_command1(Pid, Eval, Bs, RT, Ds).
+
+get_command1(Pid, Eval, Bs, RT, Ds) ->
+ receive
+ {'EXIT', Pid, Res} ->
+ {Res, Eval};
+ {'EXIT', Eval, {Reason,Stacktrace}} ->
+ report_exception(error, {Reason,Stacktrace}, RT),
+ get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds);
+ {'EXIT', Eval, Reason} ->
+ report_exception(error, {Reason,[]}, RT),
+ get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds)
+ end.
+
+prompt(N) ->
+ case is_alive() of
+ true -> io_lib:format(<<"(~s)~w> ">>, [node(), N]);
+ false -> io_lib:format(<<"~w> ">>, [N])
+ end.
+
+%% expand_hist(Expressions, CommandNumber)
+%% Preprocess the expression list replacing all history list commands
+%% with their expansions.
+
+expand_hist(Es, C) ->
+ catch {ok,expand_exprs(Es, C)}.
+
+expand_exprs([E|Es], C) ->
+ [expand_expr(E, C)|expand_exprs(Es, C)];
+expand_exprs([], _C) ->
+ [].
+
+expand_expr({cons,L,H,T}, C) ->
+ {cons,L,expand_expr(H, C),expand_expr(T, C)};
+expand_expr({lc,L,E,Qs}, C) ->
+ {lc,L,expand_expr(E, C),expand_quals(Qs, C)};
+expand_expr({bc,L,E,Qs}, C) ->
+ {bc,L,expand_expr(E, C),expand_quals(Qs, C)};
+expand_expr({tuple,L,Elts}, C) ->
+ {tuple,L,expand_exprs(Elts, C)};
+expand_expr({record_index,L,Name,F}, C) ->
+ {record_index,L,Name,expand_expr(F, C)};
+expand_expr({record,L,Name,Is}, C) ->
+ {record,L,Name,expand_fields(Is, C)};
+expand_expr({record_field,L,R,Name,F}, C) ->
+ {record_field,L,expand_expr(R, C),Name,expand_expr(F, C)};
+expand_expr({record,L,R,Name,Ups}, C) ->
+ {record,L,expand_expr(R, C),Name,expand_fields(Ups, C)};
+expand_expr({record_field,L,R,F}, C) -> %This is really illegal!
+ {record_field,L,expand_expr(R, C),expand_expr(F, C)};
+expand_expr({block,L,Es}, C) ->
+ {block,L,expand_exprs(Es, C)};
+expand_expr({'if',L,Cs}, C) ->
+ {'if',L,expand_cs(Cs, C)};
+expand_expr({'case',L,E,Cs}, C) ->
+ {'case',L,expand_expr(E, C),expand_cs(Cs, C)};
+expand_expr({'try',L,Es,Scs,Ccs,As}, C) ->
+ {'try',L,expand_exprs(Es, C),expand_cs(Scs, C),
+ expand_cs(Ccs, C),expand_exprs(As, C)};
+expand_expr({'receive',L,Cs}, C) ->
+ {'receive',L,expand_cs(Cs, C)};
+expand_expr({'receive',L,Cs,To,ToEs}, C) ->
+ {'receive',L,expand_cs(Cs, C), expand_expr(To, C), expand_exprs(ToEs, C)};
+expand_expr({call,L,{atom,_,e},[N]}, C) ->
+ case get_cmd(N, C) of
+ {undefined,_,_} ->
+ no_command(N);
+ {[Ce],_V,_CommandN} ->
+ Ce;
+ {Ces,_V,_CommandN} when is_list(Ces) ->
+ {block,L,Ces}
+ end;
+expand_expr({call,_L,{atom,_,v},[N]}, C) ->
+ case get_cmd(N, C) of
+ {_,undefined,_} ->
+ no_command(N);
+ {Ces,V,CommandN} when is_list(Ces) ->
+ {value,CommandN,V}
+ end;
+expand_expr({call,L,F,Args}, C) ->
+ {call,L,expand_expr(F, C),expand_exprs(Args, C)};
+expand_expr({'catch',L,E}, C) ->
+ {'catch',L,expand_expr(E, C)};
+expand_expr({match,L,Lhs,Rhs}, C) ->
+ {match,L,Lhs,expand_expr(Rhs, C)};
+expand_expr({op,L,Op,Arg}, C) ->
+ {op,L,Op,expand_expr(Arg, C)};
+expand_expr({op,L,Op,Larg,Rarg}, C) ->
+ {op,L,Op,expand_expr(Larg, C),expand_expr(Rarg, C)};
+expand_expr({remote,L,M,F}, C) ->
+ {remote,L,expand_expr(M, C),expand_expr(F, C)};
+expand_expr({'fun',L,{clauses,Cs}}, C) ->
+ {'fun',L,{clauses,expand_exprs(Cs, C)}};
+expand_expr({clause,L,H,G,B}, C) ->
+ %% Could expand H and G, but then erl_eval has to be changed as well.
+ {clause,L,H, G, expand_exprs(B, C)};
+expand_expr({bin,L,Fs}, C) ->
+ {bin,L,expand_bin_elements(Fs, C)};
+expand_expr(E, _C) -> % Constants.
+ E.
+
+expand_cs([{clause,L,P,G,B}|Cs], C) ->
+ [{clause,L,P,G,expand_exprs(B, C)}|expand_cs(Cs, C)];
+expand_cs([], _C) ->
+ [].
+
+expand_fields([{record_field,L,F,V}|Fs], C) ->
+ [{record_field,L,expand_expr(F, C),expand_expr(V, C)}|
+ expand_fields(Fs, C)];
+expand_fields([], _C) -> [].
+
+expand_quals([{generate,L,P,E}|Qs], C) ->
+ [{generate,L,P,expand_expr(E, C)}|expand_quals(Qs, C)];
+expand_quals([{b_generate,L,P,E}|Qs], C) ->
+ [{b_generate,L,P,expand_expr(E, C)}|expand_quals(Qs, C)];
+expand_quals([E|Qs], C) ->
+ [expand_expr(E, C)|expand_quals(Qs, C)];
+expand_quals([], _C) -> [].
+
+expand_bin_elements([], _C) ->
+ [];
+expand_bin_elements([{bin_element,L,E,Sz,Ts}|Fs], C) ->
+ [{bin_element,L,expand_expr(E, C),Sz,Ts}|expand_bin_elements(Fs, C)].
+
+no_command(N) ->
+ throw({error,
+ io_lib:fwrite(<<"~s: command not found">>, [erl_pp:expr(N)])}).
+
+%% add_cmd(Number, Expressions, Value)
+%% get_cmd(Number, CurrentCommand)
+%% del_cmd(Number, NewN, OldN, HasBin0) -> bool()
+
+add_cmd(N, Es, V) ->
+ put({command,N}, Es),
+ put({result,N}, V).
+
+getc(N) ->
+ {get({command,N}), get({result,N}), N}.
+
+get_cmd(Num, C) ->
+ case catch erl_eval:expr(Num, []) of
+ {value,N,_} when N < 0 -> getc(C+N);
+ {value,N,_} -> getc(N);
+ _Other -> {undefined,undefined,undefined}
+ end.
+
+del_cmd(_Type, N, N0, HasBin) when N < N0 ->
+ HasBin;
+del_cmd(Type, N, N0, HasBin0) ->
+ T = erase({Type,N}),
+ HasBin = HasBin0 orelse has_binary(T),
+ del_cmd(Type, N-1, N0, HasBin).
+
+has_binary(T) ->
+ try has_bin(T), false
+ catch true=Thrown -> Thrown
+ end.
+
+has_bin(T) when is_tuple(T) ->
+ has_bin(T, tuple_size(T));
+has_bin([E | Es]) ->
+ has_bin(E),
+ has_bin(Es);
+has_bin(B) when byte_size(B) > ?MAXSIZE_HEAPBINARY ->
+ throw(true);
+has_bin(T) ->
+ T.
+
+has_bin(T, 0) ->
+ T;
+has_bin(T, I) ->
+ has_bin(element(I, T)),
+ has_bin(T, I - 1).
+
+%% shell_cmd(Sequence, Evaluator, Bindings, RecordTable, Dictionary)
+%% shell_rep(Evaluator, Bindings, RecordTable, Dictionary) ->
+%% {Value,Evaluator,Bindings,Dictionary}
+%% Send a command to the evaluator and wait for the reply. Start a new
+%% evaluator if necessary.
+
+shell_cmd(Es, Eval, Bs, RT, Ds) ->
+ Eval ! {shell_cmd,self(),{eval,Es}},
+ shell_rep(Eval, Bs, RT, Ds).
+
+shell_rep(Ev, Bs0, RT, Ds0) ->
+ receive
+ {shell_rep,Ev,{value,V,Bs,Ds}} ->
+ {V,Ev,Bs,Ds};
+ {shell_rep,Ev,{command_error,{Line,M,Error}}} ->
+ fwrite_severity(benign, <<"~w: ~s">>,
+ [Line, M:format_error(Error)]),
+ {{'EXIT',Error},Ev,Bs0,Ds0};
+ {shell_req,Ev,get_cmd} ->
+ Ev ! {shell_rep,self(),get()},
+ shell_rep(Ev, Bs0, RT, Ds0);
+ {shell_req,Ev,exit} ->
+ Ev ! {shell_rep,self(),exit},
+ exit(normal);
+ {shell_req,Ev,{update_dict,Ds}} -> % Update dictionary
+ Ev ! {shell_rep,self(),ok},
+ shell_rep(Ev, Bs0, RT, Ds);
+ {ev_exit,{Ev,Class,Reason0}} -> % It has exited unnaturally
+ receive {'EXIT',Ev,normal} -> ok end,
+ report_exception(Class, Reason0, RT),
+ Reason = nocatch(Class, Reason0),
+ {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0};
+ {ev_caught,{Ev,Class,Reason0}} -> % catch_exception is in effect
+ report_exception(Class, benign, Reason0, RT),
+ Reason = nocatch(Class, Reason0),
+ {{'EXIT',Reason},Ev,Bs0,Ds0};
+ {'EXIT',_Id,interrupt} -> % Someone interrupted us
+ exit(Ev, kill),
+ shell_rep(Ev, Bs0, RT, Ds0);
+ {'EXIT',Ev,{Reason,Stacktrace}} ->
+ report_exception(exit, {Reason,Stacktrace}, RT),
+ {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0};
+ {'EXIT',Ev,Reason} ->
+ report_exception(exit, {Reason,[]}, RT),
+ {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0};
+ {'EXIT',_Id,R} ->
+ exit(Ev, R),
+ exit(R);
+ _Other -> % Ignore everything else
+ shell_rep(Ev, Bs0, RT, Ds0)
+ end.
+
+nocatch(throw, {Term,Stack}) ->
+ {{nocatch,Term},Stack};
+nocatch(error, Reason) ->
+ Reason;
+nocatch(exit, Reason) ->
+ Reason.
+
+report_exception(Class, Reason, RT) ->
+ report_exception(Class, serious, Reason, RT).
+
+report_exception(Class, Severity, {Reason,Stacktrace}, RT) ->
+ Tag = severity_tag(Severity),
+ I = iolist_size(Tag) + 1,
+ PF = fun(Term, I1) -> pp(Term, I1, RT) end,
+ SF = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end,
+ io:requests([{put_chars, Tag},
+ {put_chars,
+ lib:format_exception(I, Class, Reason, Stacktrace, SF, PF)},
+ nl]).
+
+start_eval(Bs, RT, Ds) ->
+ Self = self(),
+ Eval = spawn_link(fun() -> evaluator(Self, Bs, RT, Ds) end),
+ put(evaluator, Eval),
+ Eval.
+
+%% evaluator(Shell, Bindings, RecordTable, ProcessDictionary)
+%% Evaluate expressions from the shell. Use the "old" variable bindings
+%% and dictionary.
+
+evaluator(Shell, Bs, RT, Ds) ->
+ init_dict(Ds),
+ case application:get_env(stdlib, restricted_shell) of
+ undefined ->
+ eval_loop(Shell, Bs, RT);
+ {ok,RShMod} ->
+ case get(restricted_shell_state) of
+ undefined -> put(restricted_shell_state, []);
+ _ -> ok
+ end,
+ put(restricted_expr_state, []),
+ restricted_eval_loop(Shell, Bs, RT, RShMod)
+ end.
+
+eval_loop(Shell, Bs0, RT) ->
+ receive
+ {shell_cmd,Shell,{eval,Es}} ->
+ Ef = {value,
+ fun(MForFun, As) -> apply_fun(MForFun, As, Shell) end},
+ Lf = local_func_handler(Shell, RT, Ef),
+ Bs = eval_exprs(Es, Shell, Bs0, RT, Lf, Ef),
+ eval_loop(Shell, Bs, RT)
+ end.
+
+restricted_eval_loop(Shell, Bs0, RT, RShMod) ->
+ receive
+ {shell_cmd,Shell,{eval,Es}} ->
+ {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT),
+ put(restricted_expr_state, []),
+ Bs = eval_exprs(Es, Shell, Bs0, RT, {eval,LFH}, {value,NLFH}),
+ restricted_eval_loop(Shell, Bs, RT, RShMod)
+ end.
+
+eval_exprs(Es, Shell, Bs0, RT, Lf, Ef) ->
+ try
+ {R,Bs2} = exprs(Es, Bs0, RT, Lf, Ef),
+ Shell ! {shell_rep,self(),R},
+ Bs2
+ catch
+ exit:normal ->
+ exit(normal);
+ Class:Reason ->
+ Stacktrace = erlang:get_stacktrace(),
+ M = {self(),Class,{Reason,Stacktrace}},
+ case do_catch(Class, Reason) of
+ true ->
+ Shell ! {ev_caught,M},
+ Bs0;
+ false ->
+ %% We don't want the ERROR REPORT generated by the
+ %% emulator. Note: exit(kill) needs nothing special.
+ {links,LPs} = process_info(self(), links),
+ ER = nocatch(Class, {Reason,Stacktrace}),
+ lists:foreach(fun(P) -> exit(P, ER) end, LPs--[Shell]),
+ Shell ! {ev_exit,M},
+ exit(normal)
+ end
+ end.
+
+do_catch(exit, restricted_shell_stopped) ->
+ false;
+do_catch(exit, restricted_shell_started) ->
+ false;
+do_catch(_Class, _Reason) ->
+ case application:get_env(stdlib, shell_catch_exception) of
+ {ok, true} ->
+ true;
+ _ ->
+ false
+ end.
+
+exprs(Es, Bs0, RT, Lf, Ef) ->
+ exprs(Es, Bs0, RT, Lf, Ef, Bs0).
+
+exprs([E0|Es], Bs1, RT, Lf, Ef, Bs0) ->
+ UsedRecords = used_record_defs(E0, RT),
+ RBs = record_bindings(UsedRecords, Bs1),
+ case check_command(prep_check([E0]), RBs) of
+ ok ->
+ E1 = expand_records(UsedRecords, E0),
+ {value,V0,Bs2} = expr(E1, Bs1, Lf, Ef),
+ Bs = orddict:from_list([VV || {X,_}=VV <- erl_eval:bindings(Bs2),
+ not is_expand_variable(X)]),
+ if
+ Es =:= [] ->
+ VS = pp(V0, 1, RT),
+ io:requests([{put_chars, VS}, nl]),
+ %% Don't send the result back if it will be
+ %% discarded anyway.
+ V = case result_will_be_saved() of
+ true -> V0;
+ false -> ignored
+ end,
+ {{value,V,Bs,get()},Bs};
+ true ->
+ exprs(Es, Bs, RT, Lf, Ef, Bs0)
+ end;
+ {error,Error} ->
+ {{command_error,Error},Bs0}
+ end.
+
+is_expand_variable(V) ->
+ case catch atom_to_list(V) of
+ "rec" ++ _Integer -> true;
+ _ -> false
+ end.
+
+result_will_be_saved() ->
+ case get_history_and_results() of
+ {_, 0} -> false;
+ _ -> true
+ end.
+
+used_record_defs(E, RT) ->
+ %% Be careful to return a list where used records come before
+ %% records that use them. The linter wants them ordered that way.
+ UR = case used_records(E, [], RT) of
+ [] ->
+ [];
+ L0 ->
+ L1 = lists:zip(L0, lists:seq(1, length(L0))),
+ L2 = lists:keysort(2, lists:ukeysort(1, L1)),
+ [R || {R, _} <- L2]
+ end,
+ record_defs(RT, UR).
+
+used_records(E, U0, RT) ->
+ case used_records(E) of
+ {name,Name,E1} ->
+ U = used_records(ets:lookup(RT, Name), [Name | U0], RT),
+ used_records(E1, U, RT);
+ {expr,[E1 | Es]} ->
+ used_records(Es, used_records(E1, U0, RT), RT);
+ _ ->
+ U0
+ end.
+
+used_records({record_index,_,Name,F}) ->
+ {name, Name, F};
+used_records({record,_,Name,Is}) ->
+ {name, Name, Is};
+used_records({record_field,_,R,Name,F}) ->
+ {name, Name, [R | F]};
+used_records({record,_,R,Name,Ups}) ->
+ {name, Name, [R | Ups]};
+used_records({record_field,_,R,F}) -> % illegal
+ {expr, [R | F]};
+used_records({call,_,{atom,_,record},[A,{atom,_,Name}]}) ->
+ {name, Name, A};
+used_records({call,_,{atom,_,is_record},[A,{atom,_,Name}]}) ->
+ {name, Name, A};
+used_records({call,_,{remote,_,{atom,_,erlang},{atom,_,is_record}},
+ [A,{atom,_,Name}]}) ->
+ {name, Name, A};
+used_records({call,_,{atom,_,record_info},[A,{atom,_,Name}]}) ->
+ {name, Name, A};
+used_records({call,Line,{tuple,_,[M,F]},As}) ->
+ used_records({call,Line,{remote,Line,M,F},As});
+used_records(T) when is_tuple(T) ->
+ {expr, tuple_to_list(T)};
+used_records(E) ->
+ {expr, E}.
+
+fwrite_severity(Severity, S, As) ->
+ io:fwrite(<<"~s\n">>, [format_severity(Severity, S, As)]).
+
+format_severity(Severity, S, As) ->
+ add_severity(Severity, io_lib:fwrite(S, As)).
+
+add_severity(Severity, S) ->
+ [severity_tag(Severity), S].
+
+severity_tag(fatal) -> <<"*** ">>;
+severity_tag(serious) -> <<"** ">>;
+severity_tag(benign) -> <<"* ">>.
+
+restrict_handlers(RShMod, Shell, RT) ->
+ { fun(F,As,Binds) ->
+ local_allowed(F, As, RShMod, Binds, Shell, RT)
+ end,
+ fun(MF,As) ->
+ non_local_allowed(MF, As, RShMod, Shell)
+ end }.
+
+-define(BAD_RETURN(M, F, V),
+ try erlang:error(reason)
+ catch _:_ -> erlang:raise(exit, {restricted_shell_bad_return,V},
+ [{M,F,3} | erlang:get_stacktrace()])
+ end).
+
+local_allowed(F, As, RShMod, Bs, Shell, RT) when is_atom(F) ->
+ {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT),
+ case not_restricted(F, As) of % Not restricted is the same as builtin.
+ % variable and record manipulations local
+ % to the shell process. Those are never
+ % restricted.
+ true ->
+ local_func(F, As, Bs, Shell, RT, {eval,LFH}, {value,NLFH});
+ false ->
+ {AsEv,Bs1} = expr_list(As, Bs, {eval,LFH}, {value,NLFH}),
+ case RShMod:local_allowed(F, AsEv, {get(restricted_shell_state),
+ get(restricted_expr_state)}) of
+ {Result,{RShShSt,RShExprSt}} ->
+ put(restricted_shell_state, RShShSt),
+ put(restricted_expr_state, RShExprSt),
+ if not Result ->
+ shell_req(Shell, {update_dict,get()}),
+ exit({restricted_shell_disallowed,{F,AsEv}});
+ true -> % This is never a builtin,
+ % those are handled above.
+ non_builtin_local_func(F,AsEv,Bs1)
+ end;
+ Unexpected -> % The user supplied non conforming module
+ ?BAD_RETURN(RShMod, local_allowed, Unexpected)
+ end
+ end.
+
+non_local_allowed(MForFun, As, RShMod, Shell) ->
+ case RShMod:non_local_allowed(MForFun, As, {get(restricted_shell_state),
+ get(restricted_expr_state)}) of
+ {Result,{RShShSt,RShExprSt}} ->
+ put(restricted_shell_state, RShShSt),
+ put(restricted_expr_state, RShExprSt),
+ case Result of
+ false ->
+ shell_req(Shell, {update_dict,get()}),
+ exit({restricted_shell_disallowed,{MForFun,As}});
+ {redirect, NewMForFun, NewAs} ->
+ apply_fun(NewMForFun, NewAs, Shell);
+ _ ->
+ apply_fun(MForFun, As, Shell)
+ end;
+ Unexpected -> % The user supplied non conforming module
+ ?BAD_RETURN(RShMod, non_local_allowed, Unexpected)
+ end.
+
+%% The commands implemented in shell should not be checked if allowed
+%% This *has* to correspond to the function local_func/7!
+%% (especially true for f/1, the argument must not be evaluated).
+not_restricted(f, []) ->
+ true;
+not_restricted(f, [_]) ->
+ true;
+not_restricted(h, []) ->
+ true;
+not_restricted(b, []) ->
+ true;
+not_restricted(which, [_]) ->
+ true;
+not_restricted(import, [_]) ->
+ true;
+not_restricted(import_all, [_]) ->
+ true;
+not_restricted(use, [_]) ->
+ true;
+not_restricted(use_all, [_]) ->
+ true;
+not_restricted(history, [_]) ->
+ true;
+not_restricted(results, [_]) ->
+ true;
+not_restricted(catch_exception, [_]) ->
+ true;
+not_restricted(exit, []) ->
+ true;
+not_restricted(rd, [_,_]) ->
+ true;
+not_restricted(rf, []) ->
+ true;
+not_restricted(rf, [_]) ->
+ true;
+not_restricted(rl, []) ->
+ true;
+not_restricted(rl, [_]) ->
+ true;
+not_restricted(rp, [_]) ->
+ true;
+not_restricted(rr, [_]) ->
+ true;
+not_restricted(rr, [_,_]) ->
+ true;
+not_restricted(rr, [_,_,_]) ->
+ true;
+not_restricted(_, _) ->
+ false.
+
+%% When erlang:garbage_collect() is called from the shell,
+%% the shell process process that spawned the evaluating
+%% process is garbage collected as well.
+%% To garbage collect the evaluating process only the command
+%% garbage_collect(self()). can be used.
+apply_fun({erlang,garbage_collect}, [], Shell) ->
+ garb(Shell);
+apply_fun({M,F}, As, _Shell) ->
+ apply(M, F, As);
+apply_fun(MForFun, As, _Shell) ->
+ apply(MForFun, As).
+
+prep_check({call,Line,{atom,_,f},[{var,_,_Name}]}) ->
+ %% Do not emit a warning for f(V) when V is unbound.
+ {atom,Line,ok};
+prep_check({value,_CommandN,_Val}) ->
+ %% erl_lint cannot handle the history expansion {value,_,_}.
+ {atom,0,ok};
+prep_check(T) when is_tuple(T) ->
+ list_to_tuple(prep_check(tuple_to_list(T)));
+prep_check([E | Es]) ->
+ [prep_check(E) | prep_check(Es)];
+prep_check(E) ->
+ E.
+
+expand_records([], E0) ->
+ E0;
+expand_records(UsedRecords, E0) ->
+ RecordDefs = [Def || {_Name,Def} <- UsedRecords],
+ L = 1,
+ E = prep_rec(E0),
+ Forms = RecordDefs ++ [{function,L,foo,0,[{clause,L,[],[],[E]}]}],
+ [{function,L,foo,0,[{clause,L,[],[],[NE]}]}] =
+ erl_expand_records:module(Forms, [strict_record_tests]),
+ prep_rec(NE).
+
+prep_rec({value,_CommandN,_V}=Value) ->
+ %% erl_expand_records cannot handle the history expansion {value,_,_}.
+ {atom,Value,ok};
+prep_rec({atom,{value,_CommandN,_V}=Value,ok}) ->
+ %% Undo the effect of the previous clause...
+ Value;
+prep_rec(T) when is_tuple(T) -> list_to_tuple(prep_rec(tuple_to_list(T)));
+prep_rec([E | Es]) -> [prep_rec(E) | prep_rec(Es)];
+prep_rec(E) -> E.
+
+init_dict([{K,V}|Ds]) ->
+ put(K, V),
+ init_dict(Ds);
+init_dict([]) -> true.
+
+%% local_func(Function, Args, Bindings, Shell, RecordTable,
+%% LocalFuncHandler, ExternalFuncHandler) -> {value,Val,Bs}
+%% Evaluate local functions, including shell commands.
+%%
+%% Note that the predicate not_restricted/2 has to correspond to what's
+%% handled internally - it should return 'true' for all local functions
+%% handled in this module (i.e. those that are not eventually handled by
+%% non_builtin_local_func/3 (user_default/shell_default).
+
+local_func(h, [], Bs, Shell, RT, _Lf, _Ef) ->
+ Cs = shell_req(Shell, get_cmd),
+ Cs1 = lists:filter(fun({{command, _},_}) -> true;
+ ({{result, _},_}) -> true;
+ (_) -> false
+ end,
+ Cs),
+ Cs2 = lists:map(fun({{T, N}, V}) -> {{N, T}, V} end,
+ Cs1),
+ Cs3 = lists:keysort(1, Cs2),
+ {value,list_commands(Cs3, RT),Bs};
+local_func(b, [], Bs, _Shell, RT, _Lf, _Ef) ->
+ {value,list_bindings(erl_eval:bindings(Bs), RT),Bs};
+local_func(f, [], _Bs, _Shell, _RT, _Lf, _Ef) ->
+ {value,ok,erl_eval:new_bindings()};
+local_func(f, [{var,_,Name}], Bs, _Shell, _RT, _Lf, _Ef) ->
+ {value,ok,erl_eval:del_binding(Name, Bs)};
+local_func(f, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell,f,1}]);
+local_func(rd, [{atom,_,RecName},RecDef0], Bs, _Shell, RT, _Lf, _Ef) ->
+ RecDef = expand_value(RecDef0),
+ RDs = lists:flatten(erl_pp:expr(RecDef)),
+ Attr = lists:concat(["-record('", RecName, "',", RDs, ")."]),
+ {ok, Tokens, _} = erl_scan:string(Attr),
+ case erl_parse:parse_form(Tokens) of
+ {ok,AttrForm} ->
+ [RN] = add_records([AttrForm], Bs, RT),
+ {value,RN,Bs};
+ {error,{_Line,M,ErrDesc}} ->
+ ErrStr = io_lib:fwrite(<<"~s">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr))
+ end;
+local_func(rd, [_,_], _Bs, _Shell, _RT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell,rd,2}]);
+local_func(rf, [], Bs, _Shell, RT, _Lf, _Ef) ->
+ true = ets:delete_all_objects(RT),
+ {value,initiate_records(Bs, RT),Bs};
+local_func(rf, [A], Bs0, _Shell, RT, Lf, Ef) ->
+ {[Recs],Bs} = expr_list([A], Bs0, Lf, Ef),
+ if '_' =:= Recs ->
+ true = ets:delete_all_objects(RT);
+ true ->
+ lists:foreach(fun(Name) -> true = ets:delete(RT, Name)
+ end, listify(Recs))
+ end,
+ {value,ok,Bs};
+local_func(rl, [], Bs, _Shell, RT, _Lf, _Ef) ->
+ {value,list_records(ets:tab2list(RT)),Bs};
+local_func(rl, [A], Bs0, _Shell, RT, Lf, Ef) ->
+ {[Recs],Bs} = expr_list([A], Bs0, Lf, Ef),
+ {value,list_records(record_defs(RT, listify(Recs))),Bs};
+local_func(rp, [A], Bs0, _Shell, RT, Lf, Ef) ->
+ {[V],Bs} = expr_list([A], Bs0, Lf, Ef),
+ W = columns(),
+ io:requests([{put_chars,
+ io_lib_pretty:print(V, 1, W, -1, ?CHAR_MAX,
+ record_print_fun(RT))},
+ nl]),
+ {value,ok,Bs};
+local_func(rr, [A], Bs0, _Shell, RT, Lf, Ef) ->
+ {[File],Bs} = expr_list([A], Bs0, Lf, Ef),
+ {value,read_and_add_records(File, '_', [], Bs, RT),Bs};
+local_func(rr, [_,_]=As0, Bs0, _Shell, RT, Lf, Ef) ->
+ {[File,Sel],Bs} = expr_list(As0, Bs0, Lf, Ef),
+ {value,read_and_add_records(File, Sel, [], Bs, RT),Bs};
+local_func(rr, [_,_,_]=As0, Bs0, _Shell, RT, Lf, Ef) ->
+ {[File,Sel,Options],Bs} = expr_list(As0, Bs0, Lf, Ef),
+ {value,read_and_add_records(File, Sel, Options, Bs, RT),Bs};
+local_func(which, [{atom,_,M}], Bs, _Shell, _RT, _Lf, _Ef) ->
+ case erl_eval:binding({module,M}, Bs) of
+ {value, M1} ->
+ {value,M1,Bs};
+ unbound ->
+ {value,M,Bs}
+ end;
+local_func(which, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell,which,1}]);
+local_func(import, [M], Bs, _Shell, _RT, _Lf, _Ef) ->
+ case erl_parse:package_segments(M) of
+ error -> erlang:raise(error, function_clause, [{shell,import,1}]);
+ M1 ->
+ Mod = packages:concat(M1),
+ case packages:is_valid(Mod) of
+ true ->
+ Key = list_to_atom(packages:last(Mod)),
+ Mod1 = list_to_atom(Mod),
+ {value,ok,erl_eval:add_binding({module,Key}, Mod1, Bs)};
+ false ->
+ exit({{bad_module_name, Mod}, [{shell,import,1}]})
+ end
+ end;
+local_func(import_all, [P], Bs0, _Shell, _RT, _Lf, _Ef) ->
+ case erl_parse:package_segments(P) of
+ error -> erlang:raise(error, function_clause, [{shell,import_all,1}]);
+ P1 ->
+ Name = packages:concat(P1),
+ case packages:is_valid(Name) of
+ true ->
+ Bs1 = import_all(Name, Bs0),
+ {value,ok,Bs1};
+ false ->
+ exit({{bad_package_name, Name},
+ [{shell,import_all,1}]})
+ end
+ end;
+local_func(use, [M], Bs, Shell, RT, Lf, Ef) ->
+ local_func(import, [M], Bs, Shell, RT, Lf, Ef);
+local_func(use_all, [M], Bs, Shell, RT, Lf, Ef) ->
+ local_func(import_all, [M], Bs, Shell, RT, Lf, Ef);
+local_func(history, [{integer,_,N}], Bs, _Shell, _RT, _Lf, _Ef) ->
+ {value,history(N),Bs};
+local_func(history, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell,history,1}]);
+local_func(results, [{integer,_,N}], Bs, _Shell, _RT, _Lf, _Ef) ->
+ {value,results(N),Bs};
+local_func(results, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell,results,1}]);
+local_func(catch_exception, [{atom,_,Bool}], Bs, _Shell, _RT, _Lf, _Ef)
+ when Bool; not Bool ->
+ {value,catch_exception(Bool),Bs};
+local_func(catch_exception, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) ->
+ erlang:raise(error, function_clause, [{shell,catch_exception,1}]);
+local_func(exit, [], _Bs, Shell, _RT, _Lf, _Ef) ->
+ shell_req(Shell, exit), %This terminates us
+ exit(normal);
+local_func(F, As0, Bs0, _Shell, _RT, Lf, Ef) when is_atom(F) ->
+ {As,Bs} = expr_list(As0, Bs0, Lf, Ef),
+ non_builtin_local_func(F,As,Bs).
+
+non_builtin_local_func(F,As,Bs) ->
+ case erlang:function_exported(user_default, F, length(As)) of
+ true ->
+ {eval,{user_default,F},As,Bs};
+ false ->
+ shell_default(F,As,Bs)
+ end.
+
+shell_default(F,As,Bs) ->
+ M = shell_default,
+ A = length(As),
+ case code:ensure_loaded(M) of
+ {module, _} ->
+ case erlang:function_exported(M,F,A) of
+ true ->
+ {eval,{M,F},As,Bs};
+ false ->
+ shell_undef(F,A)
+ end;
+ {error, _} ->
+ shell_undef(F,A)
+ end.
+
+shell_undef(F,A) ->
+ erlang:error({shell_undef,F,A}).
+
+local_func_handler(Shell, RT, Ef) ->
+ H = fun(Lf) ->
+ fun(F, As, Bs) ->
+ local_func(F, As, Bs, Shell, RT, {eval,Lf(Lf)}, Ef)
+ end
+ end,
+ {eval,H(H)}.
+
+record_print_fun(RT) ->
+ fun(Tag, NoFields) ->
+ case ets:lookup(RT, Tag) of
+ [{_,{attribute,_,record,{Tag,Fields}}}]
+ when length(Fields) =:= NoFields ->
+ record_fields(Fields);
+ _ ->
+ no
+ end
+ end.
+
+record_fields([{record_field,_,{atom,_,Field}} | Fs]) ->
+ [Field | record_fields(Fs)];
+record_fields([{record_field,_,{atom,_,Field},_} | Fs]) ->
+ [Field | record_fields(Fs)];
+record_fields([]) ->
+ [].
+
+initiate_records(Bs, RT) ->
+ RNs1 = init_rec(shell_default, Bs, RT),
+ RNs2 = case code:is_loaded(user_default) of
+ {file,_File} ->
+ init_rec(user_default, Bs, RT);
+ false ->
+ []
+ end,
+ lists:usort(RNs1 ++ RNs2).
+
+init_rec(Module, Bs, RT) ->
+ case read_records(Module, []) of
+ RAs when is_list(RAs) ->
+ case catch add_records(RAs, Bs, RT) of
+ {'EXIT',_} ->
+ [];
+ RNs ->
+ RNs
+ end;
+ _Error ->
+ []
+ end.
+
+read_and_add_records(File, Selected, Options, Bs, RT) ->
+ case read_records(File, Selected, Options) of
+ RAs when is_list(RAs) ->
+ add_records(RAs, Bs, RT);
+ Error ->
+ Error
+ end.
+
+read_records(File, Selected, Options) ->
+ case read_records(File, listify(Options)) of
+ Error when is_tuple(Error) ->
+ Error;
+ RAs when Selected =:= '_' ->
+ RAs;
+ RAs ->
+ Sel = listify(Selected),
+ [RA || {attribute,_,_,{Name,_}}=RA <- RAs,
+ lists:member(Name, Sel)]
+ end.
+
+add_records(RAs, Bs0, RT) ->
+ Recs = [{Name,D} || {attribute,_,_,{Name,_}}=D <- RAs],
+ Bs1 = record_bindings(Recs, Bs0),
+ case check_command([], Bs1) of
+ {error,{_Line,M,ErrDesc}} ->
+ %% A source file that has not been compiled.
+ ErrStr = io_lib:fwrite(<<"~s">>, [M:format_error(ErrDesc)]),
+ exit(lists:flatten(ErrStr));
+ ok ->
+ true = ets:insert(RT, Recs),
+ lists:usort([Name || {Name,_} <- Recs])
+ end.
+
+listify(L) when is_list(L) ->
+ L;
+listify(E) ->
+ [E].
+
+check_command(Es, Bs) ->
+ erl_eval:check_command(Es, strip_bindings(Bs)).
+
+expr(E, Bs, Lf, Ef) ->
+ erl_eval:expr(E, strip_bindings(Bs), Lf, Ef).
+
+expr_list(Es, Bs, Lf, Ef) ->
+ erl_eval:expr_list(Es, strip_bindings(Bs), Lf, Ef).
+
+strip_bindings(Bs) ->
+ Bs -- [B || {{module,_},_}=B <- Bs].
+
+%% Note that a sequence number is used here to make sure that if a
+%% record is used by another record, then the first record is parsed
+%% before the second record. (erl_eval:check_command() calls the
+%% linter which needs the records in a proper order.)
+record_bindings([], Bs) ->
+ Bs;
+record_bindings(Recs0, Bs0) ->
+ {Recs1, _} = lists:mapfoldl(fun ({Name,Def}, I) -> {{Name,I,Def},I+1}
+ end, 0, Recs0),
+ Recs2 = lists:keysort(2, lists:ukeysort(1, Recs1)),
+ lists:foldl(fun ({Name,I,Def}, Bs) ->
+ erl_eval:add_binding({record,I,Name}, Def, Bs)
+ end, Bs0, Recs2).
+
+%%% Read record information from file(s)
+
+read_records(FileOrModule, Opts0) ->
+ Opts = lists:delete(report_warnings, Opts0),
+ case find_file(FileOrModule) of
+ {files,[File]} ->
+ read_file_records(File, Opts);
+ {files,Files} ->
+ lists:flatmap(fun(File) ->
+ case read_file_records(File, Opts) of
+ RAs when is_list(RAs) -> RAs;
+ _ -> []
+ end
+ end, Files);
+ Error ->
+ Error
+ end.
+
+-include_lib("kernel/include/file.hrl").
+
+find_file(Mod) when is_atom(Mod) ->
+ case code:which(Mod) of
+ File when is_list(File) ->
+ {files,[File]};
+ preloaded ->
+ {_M,_Bin,File} = code:get_object_code(Mod),
+ {files,[File]};
+ _Else -> % non_existing, interpreted, cover_compiled
+ {error,nofile}
+ end;
+find_file(File) ->
+ case catch filelib:wildcard(File) of
+ {'EXIT',_} ->
+ {error,invalid_filename};
+ Files ->
+ {files,Files}
+ end.
+
+read_file_records(File, Opts) ->
+ case filename:extension(File) of
+ ".beam" ->
+ case beam_lib:chunks(File, [abstract_code,"CInf"]) of
+ {ok,{_Mod,[{abstract_code,{Version,Forms}},{"CInf",CB}]}} ->
+ case record_attrs(Forms) of
+ [] when Version =:= raw_abstract_v1 ->
+ [];
+ [] ->
+ %% If the version is raw_X, then this test
+ %% is unnecessary.
+ try_source(File, CB);
+ Records ->
+ Records
+ end;
+ {ok,{_Mod,[{abstract_code,no_abstract_code},{"CInf",CB}]}} ->
+ try_source(File, CB);
+ Error ->
+ %% Could be that the "Abst" chunk is missing (pre R6).
+ Error
+ end;
+ _ ->
+ parse_file(File, Opts)
+ end.
+
+%% This is how the debugger searches for source files. See int.erl.
+try_source(Beam, CB) ->
+ Os = case lists:keyfind(options, 1, binary_to_term(CB)) of
+ false -> [];
+ {_, Os0} -> Os0
+ end,
+ Src0 = filename:rootname(Beam) ++ ".erl",
+ case is_file(Src0) of
+ true -> parse_file(Src0, Os);
+ false ->
+ EbinDir = filename:dirname(Beam),
+ Src = filename:join([filename:dirname(EbinDir), "src",
+ filename:basename(Src0)]),
+ case is_file(Src) of
+ true -> parse_file(Src, Os);
+ false -> {error, nofile}
+ end
+ end.
+
+is_file(Name) ->
+ case filelib:is_file(Name) of
+ true ->
+ not filelib:is_dir(Name);
+ false ->
+ false
+ end.
+
+parse_file(File, Opts) ->
+ Cwd = ".",
+ Dir = filename:dirname(File),
+ IncludePath = [Cwd,Dir|inc_paths(Opts)],
+ case epp:parse_file(File, IncludePath, pre_defs(Opts)) of
+ {ok,Forms} ->
+ record_attrs(Forms);
+ Error ->
+ Error
+ end.
+
+pre_defs([{d,M,V}|Opts]) ->
+ [{M,V}|pre_defs(Opts)];
+pre_defs([{d,M}|Opts]) ->
+ [M|pre_defs(Opts)];
+pre_defs([_|Opts]) ->
+ pre_defs(Opts);
+pre_defs([]) -> [].
+
+inc_paths(Opts) ->
+ [P || {i,P} <- Opts, is_list(P)].
+
+record_attrs(Forms) ->
+ [A || A = {attribute,_,record,_D} <- Forms].
+
+%%% End of reading record information from file(s)
+
+import_all(P, Bs0) ->
+ Ms = packages:find_modules(P),
+ lists:foldl(fun (M, Bs) ->
+ Key = list_to_atom(M),
+ M1 = list_to_atom(packages:concat(P, M)),
+ erl_eval:add_binding({module,Key}, M1, Bs)
+ end,
+ Bs0, Ms).
+
+shell_req(Shell, Req) ->
+ Shell ! {shell_req,self(),Req},
+ receive
+ {shell_rep,Shell,Rep} -> Rep
+ end.
+
+list_commands([{{N,command},Es0}, {{N,result}, V} |Ds], RT) ->
+ Es = prep_list_commands(Es0),
+ VS = pp(V, 4, RT),
+ Ns = io_lib:fwrite(<<"~w: ">>, [N]),
+ I = iolist_size(Ns),
+ io:requests([{put_chars, Ns},
+ {format,<<"~s\n">>,[erl_pp:exprs(Es, I, none)]},
+ {format,<<"-> ">>,[]},
+ {put_chars, VS},
+ nl]),
+ list_commands(Ds, RT);
+list_commands([{{N,command},Es0} |Ds], RT) ->
+ Es = prep_list_commands(Es0),
+ Ns = io_lib:fwrite(<<"~w: ">>, [N]),
+ I = iolist_size(Ns),
+ io:requests([{put_chars, Ns},
+ {format,<<"~s\n">>,[erl_pp:exprs(Es, I, none)]}]),
+ list_commands(Ds, RT);
+list_commands([_D|Ds], RT) ->
+ list_commands(Ds, RT);
+list_commands([], _RT) -> ok.
+
+list_bindings([{{module,M},Val}|Bs], RT) ->
+ io:fwrite(<<"~p is ~p\n">>, [M,Val]),
+ list_bindings(Bs, RT);
+list_bindings([{Name,Val}|Bs], RT) ->
+ case erl_eval:fun_data(Val) of
+ {fun_data,_FBs,FCs0} ->
+ FCs = expand_value(FCs0), % looks nicer
+ F = {'fun',0,{clauses,FCs}},
+ M = {match,0,{var,0,Name},F},
+ io:fwrite(<<"~s\n">>, [erl_pp:expr(M)]);
+ false ->
+ Namel = io_lib:fwrite(<<"~s = ">>, [Name]),
+ Nl = iolist_size(Namel)+1,
+ ValS = pp(Val, Nl, RT),
+ io:requests([{put_chars, Namel},
+ {put_chars, ValS},
+ nl])
+ end,
+ list_bindings(Bs, RT);
+list_bindings([], _RT) ->
+ ok.
+
+list_records(Records) ->
+ lists:foreach(fun({_Name,Attr}) ->
+ io:fwrite(<<"~s">>, [erl_pp:attribute(Attr)])
+ end, Records).
+
+record_defs(RT, Names) ->
+ lists:flatmap(fun(Name) -> ets:lookup(RT, Name)
+ end, Names).
+
+expand_value(E) ->
+ substitute_v1(fun({value,CommandN,V}) -> try_abstract(V, CommandN)
+ end, E).
+
+%% There is no abstract representation of funs.
+try_abstract(V, CommandN) ->
+ try erl_parse:abstract(V)
+ catch _:_ -> {call,0,{atom,0,v},[{integer,0,CommandN}]}
+ end.
+
+%% Rather than listing possibly huge results the calls to v/1 are shown.
+prep_list_commands(E) ->
+ substitute_v1(fun({value,CommandN,_V}) ->
+ {call,0,{atom,0,v},[{integer,0,CommandN}]}
+ end, E).
+
+substitute_v1(F, {value,_,_}=Value) ->
+ F(Value);
+substitute_v1(F, T) when is_tuple(T) ->
+ list_to_tuple(substitute_v1(F, tuple_to_list(T)));
+substitute_v1(F, [E | Es]) ->
+ [substitute_v1(F, E) | substitute_v1(F, Es)];
+substitute_v1(_F, E) ->
+ E.
+
+check_and_get_history_and_results() ->
+ check_env(shell_history_length),
+ check_env(shell_saved_results),
+ get_history_and_results().
+
+get_history_and_results() ->
+ History = get_env(shell_history_length, ?DEF_HISTORY),
+ Results = get_env(shell_saved_results, ?DEF_RESULTS),
+ {History, erlang:min(Results, History)}.
+
+pp(V, I, RT) ->
+ io_lib_pretty:print(V, I, columns(), ?LINEMAX, ?CHAR_MAX,
+ record_print_fun(RT)).
+
+columns() ->
+ case io:columns() of
+ {ok,N} -> N;
+ _ -> 80
+ end.
+
+garb(Shell) ->
+ erlang:garbage_collect(Shell),
+ catch erlang:garbage_collect(whereis(user)),
+ catch erlang:garbage_collect(group_leader()),
+ erlang:garbage_collect().
+
+get_env(V, Def) ->
+ case application:get_env(stdlib, V) of
+ {ok, Val} when is_integer(Val), Val >= 0 ->
+ Val;
+ _ ->
+ Def
+ end.
+
+check_env(V) ->
+ case application:get_env(stdlib, V) of
+ undefined ->
+ ok;
+ {ok, Val} when is_integer(Val), Val >= 0 ->
+ ok;
+ {ok, Val} ->
+ Txt = io_lib:fwrite(
+ <<"Invalid value of STDLIB configuration parameter ~p: ~p\n">>,
+ [V, Val]),
+ error_logger:info_report(lists:flatten(Txt))
+ end.
+
+set_env(App, Name, Val, Default) ->
+ Prev = case application:get_env(App, Name) of
+ undefined ->
+ Default;
+ {ok, Old} ->
+ Old
+ end,
+ application_controller:set_env(App, Name, Val),
+ Prev.
+
+-spec history(non_neg_integer()) -> non_neg_integer().
+
+history(L) when is_integer(L), L >= 0 ->
+ set_env(stdlib, shell_history_length, L, ?DEF_HISTORY).
+
+-spec results(non_neg_integer()) -> non_neg_integer().
+
+results(L) when is_integer(L), L >= 0 ->
+ set_env(stdlib, shell_saved_results, L, ?DEF_RESULTS).
+
+-spec catch_exception(boolean()) -> boolean().
+
+catch_exception(Bool) ->
+ set_env(stdlib, shell_catch_exception, Bool, ?DEF_CATCH_EXCEPTION).
diff --git a/lib/stdlib/src/shell_default.erl b/lib/stdlib/src/shell_default.erl
new file mode 100644
index 0000000000..670f8cdb44
--- /dev/null
+++ b/lib/stdlib/src/shell_default.erl
@@ -0,0 +1,131 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% This is just a empty template which calls routines in the module c
+%% to do all the work!
+
+-module(shell_default).
+
+-export([help/0,lc/1,c/1,c/2,nc/1,nl/1,l/1,i/0,pid/3,i/3,m/0,m/1,
+ memory/0,memory/1,
+ erlangrc/1,bi/1, regs/0, flush/0,pwd/0,ls/0,ls/1,cd/1,
+ y/1, y/2,
+ xm/1, bt/1, q/0,
+ ni/0, nregs/0]).
+
+-export([ih/0,iv/0,im/0,ii/1,ii/2,iq/1,ini/1,ini/2,inq/1,ib/2,ib/3,
+ ir/2,ir/3,ibd/2,ibe/2,iba/3,ibc/3,
+ ic/0,ir/1,ir/0,il/0,ipb/0,ipb/1,iaa/1,iaa/2,ist/1,ia/1,ia/2,ia/3,
+ ia/4,ip/0]).
+
+-import(io, [format/1]).
+
+help() ->
+ format("** shell internal commands **~n"),
+ format("b() -- display all variable bindings\n"),
+ format("e(N) -- repeat the expression in query <N>\n"),
+ format("f() -- forget all variable bindings\n"),
+ format("f(X) -- forget the binding of variable X\n"),
+ format("h() -- history\n"),
+ format("history(N) -- set how many previous commands to keep\n"),
+ format("results(N) -- set how many previous command results to keep\n"),
+ format("v(N) -- use the value of query <N>\n"),
+ format("rd(R,D) -- define a record\n"),
+ format("rf() -- remove all record information\n"),
+ format("rf(R) -- remove record information about R\n"),
+ format("rl() -- display all record information\n"),
+ format("rl(R) -- display record information about R\n"),
+ format("rp(Term) -- display Term using the shell's record information\n"),
+ format("rr(File) -- read record information from File (wildcards allowed)\n"),
+ format("rr(F,R) -- read selected record information from file(s)\n"),
+ format("rr(F,R,O) -- read selected record information with options\n"),
+ format("** commands in module c **\n"),
+ c:help(),
+ format("** commands in module i (interpreter interface) **\n"),
+ format("ih() -- print help for the i module\n"),
+ %% format("** private commands ** \n"),
+ %% format("myfunc() -- does my operation ...\n"),
+ true.
+
+%% these are in alphabetic order it would be nice if they
+%% were to *stay* so!
+
+bi(I) -> c:bi(I).
+bt(Pid) -> c:bt(Pid).
+c(File) -> c:c(File).
+c(File, Opt) -> c:c(File, Opt).
+cd(D) -> c:cd(D).
+erlangrc(X) -> c:erlangrc(X).
+flush() -> c:flush().
+i() -> c:i().
+i(X,Y,Z) -> c:i(X,Y,Z).
+l(Mod) -> c:l(Mod).
+lc(X) -> c:lc(X).
+ls() -> c:ls().
+ls(S) -> c:ls(S).
+m() -> c:m().
+m(Mod) -> c:m(Mod).
+memory() -> c:memory().
+memory(Type) -> c:memory(Type).
+nc(X) -> c:nc(X).
+ni() -> c:ni().
+nl(Mod) -> c:nl(Mod).
+nregs() -> c:nregs().
+pid(X,Y,Z) -> c:pid(X,Y,Z).
+pwd() -> c:pwd().
+q() -> c:q().
+regs() -> c:regs().
+xm(Mod) -> c:xm(Mod).
+y(File) -> c:y(File).
+y(File, Opts) -> c:y(File, Opts).
+
+iaa(Flag) -> calli(iaa, [Flag]).
+iaa(Flag,Fnk) -> calli(iaa, [Flag,Fnk]).
+ist(Flag) -> calli(ist, [Flag]).
+ia(Pid) -> calli(ia, [Pid]).
+ia(X,Y,Z) -> calli(ia, [X,Y,Z]).
+ia(Pid,Fnk) -> calli(ia, [Pid,Fnk]).
+ia(X,Y,Z,Fnk) -> calli(ia, [X,Y,Z,Fnk]).
+ib(Mod,Line) -> calli(ib, [Mod,Line]).
+ib(Mod,Fnk,Arity) -> calli(ib, [Mod,Fnk,Arity]).
+ibd(Mod,Line) -> calli(ibd, [Mod,Line]).
+ibe(Mod,Line) -> calli(ibe, [Mod,Line]).
+iba(M,L,Action) -> calli(iba, [M,L,Action]).
+ibc(M,L,Cond) -> calli(ibc, [M,L,Cond]).
+ic() -> calli(ic, []).
+ih() -> calli(help, []).
+ii(Mod) -> calli(ii, [Mod]).
+ii(Mod,Op) -> calli(ii, [Mod,Op]).
+il() -> calli(il, []).
+im() -> calli(im, []).
+ini(Mod) -> calli(ini, [Mod]).
+ini(Mod,Op) -> calli(ini, [Mod,Op]).
+inq(Mod) -> calli(inq, [Mod]).
+ip() -> calli(ip, []).
+ipb() -> calli(ipb, []).
+ipb(Mod) -> calli(ipb, [Mod]).
+iq(Mod) -> calli(iq, [Mod]).
+ir(Mod,Line) -> calli(ir, [Mod,Line]).
+ir(Mod,Fnk,Arity) -> calli(ir, [Mod,Fnk,Arity]).
+ir(Mod) -> calli(ir, [Mod]).
+ir() -> calli(ir, []).
+iv() -> calli(iv, []).
+
+calli(F, Args) ->
+ c:appcall(debugger, i, F, Args).
diff --git a/lib/stdlib/src/slave.erl b/lib/stdlib/src/slave.erl
new file mode 100644
index 0000000000..196b659938
--- /dev/null
+++ b/lib/stdlib/src/slave.erl
@@ -0,0 +1,332 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(slave).
+
+%% If the macro DEBUG is defined during compilation,
+%% debug printouts are done through erlang:display/1.
+%% Activate this feature by starting the compiler
+%% with> erlc -DDEBUG ...
+%% or by> setenv ERL_COMPILER_FLAGS DEBUG
+%% before running make (in the OTP make system)
+%% (the example is for tcsh)
+
+
+-export([pseudo/1,
+ pseudo/2,
+ start/1, start/2, start/3,
+ start/5,
+ start_link/1, start_link/2, start_link/3,
+ stop/1,
+ relay/1]).
+
+%% Internal exports
+-export([wait_for_slave/7, slave_start/1, wait_for_master_to_die/2]).
+
+-import(error_logger, [error_msg/2]).
+
+
+-ifdef(DEBUG).
+-define(dbg(Tag,Data), erlang:display({Tag,Data})).
+-else.
+-define(dbg(Tag,Data), true).
+-endif.
+
+
+%% Start a list of pseudo servers on the local node
+pseudo([Master | ServerList]) ->
+ pseudo(Master , ServerList);
+pseudo(_) ->
+ error_msg("No master node given to slave:pseudo/1~n",[]).
+
+pseudo(_, []) -> ok;
+pseudo(Master, [S|Tail]) ->
+ start_pseudo(S, whereis(S), Master),
+ pseudo(Master, Tail).
+
+start_pseudo(Name, undefined, Master) ->
+ X = rpc:call(Master,erlang, whereis,[Name]),
+ register(Name, spawn(slave, relay, [X]));
+
+start_pseudo(_,_,_) -> ok. %% It's already there
+
+
+%% This relay can be used to relay all messages directed to a process.
+
+relay({badrpc,Reason}) ->
+ error_msg(" ** exiting relay server ~w :~w **~n", [self(),Reason]),
+ exit(Reason);
+relay(undefined) ->
+ error_msg(" ** exiting relay server ~w **~n", [self()]),
+ exit(undefined);
+relay(Pid) when is_pid(Pid) ->
+ relay1(Pid).
+
+relay1(Pid) ->
+ receive
+ X ->
+ Pid ! X
+ end,
+ relay1(Pid).
+
+%% start/1,2,3 --
+%% start_link/1,2,3 --
+%%
+%% The start/1,2,3 functions are used to start a slave Erlang node.
+%% The node on which the start/N functions are used is called the
+%% master in the description below.
+%%
+%% If hostname is the same for the master and the slave,
+%% the Erlang node will simply be spawned. The only requirment for
+%% this to work is that the 'erl' program can be found in PATH.
+%%
+%% If the master and slave are on different hosts, start/N uses
+%% the 'rsh' program to spawn an Erlang node on the other host.
+%% Alternative, if the master was started as
+%% 'erl -sname xxx -rsh my_rsh...', then 'my_rsh' will be used instead
+%% of 'rsh' (this is useful for systems where the rsh program is named
+%% 'remsh').
+%%
+%% For this to work, the following conditions must be fulfilled:
+%%
+%% 1. There must be an Rsh program on computer; if not an error
+%% is returned.
+%%
+%% 2. The hosts must be configured to allowed 'rsh' access without
+%% prompts for password.
+%%
+%% The slave node will have its filer and user server redirected
+%% to the master. When the master node dies, the slave node will
+%% terminate. For the start_link functions, the slave node will
+%% terminate also if the process which called start_link terminates.
+%%
+%% Returns: {ok, Name@Host} |
+%% {error, timeout} |
+%% {error, no_rsh} |
+%% {error, {already_running, Name@Host}}
+
+start(Host) ->
+ L = atom_to_list(node()),
+ Name = upto($@, L),
+ start(Host, Name).
+
+start(Host, Name) ->
+ start(Host, Name, []).
+
+start(Host, Name, Args) ->
+ start(Host, Name, Args, no_link).
+
+start_link(Host) ->
+ L = atom_to_list(node()),
+ Name = upto($@, L),
+ start_link(Host, Name).
+
+start_link(Host, Name) ->
+ start_link(Host, Name, []).
+
+start_link(Host, Name, Args) ->
+ start(Host, Name, Args, self()).
+
+start(Host0, Name, Args, LinkTo) ->
+ Prog = lib:progname(),
+ start(Host0, Name, Args, LinkTo, Prog).
+
+start(Host0, Name, Args, LinkTo, Prog) ->
+ Host =
+ case net_kernel:longnames() of
+ true -> dns(Host0);
+ false -> strip_host_name(to_list(Host0));
+ ignored -> exit(not_alive)
+ end,
+ Node = list_to_atom(lists:concat([Name, "@", Host])),
+ case net_adm:ping(Node) of
+ pang ->
+ start_it(Host, Name, Node, Args, LinkTo, Prog);
+ pong ->
+ {error, {already_running, Node}}
+ end.
+
+%% Stops a running node.
+
+stop(Node) ->
+% io:format("stop(~p)~n", [Node]),
+ rpc:call(Node, erlang, halt, []),
+ ok.
+
+%% Starts a new slave node.
+
+start_it(Host, Name, Node, Args, LinkTo, Prog) ->
+ spawn(?MODULE, wait_for_slave, [self(), Host, Name, Node, Args, LinkTo,
+ Prog]),
+ receive
+ {result, Result} -> Result
+ end.
+
+%% Waits for the slave to start.
+
+wait_for_slave(Parent, Host, Name, Node, Args, LinkTo, Prog) ->
+ Waiter = register_unique_name(0),
+ case mk_cmd(Host, Name, Args, Waiter, Prog) of
+ {ok, Cmd} ->
+%% io:format("Command: ~s~n", [Cmd]),
+ open_port({spawn, Cmd}, [stream]),
+ receive
+ {SlavePid, slave_started} ->
+ unregister(Waiter),
+ slave_started(Parent, LinkTo, SlavePid)
+ after 32000 ->
+ %% If it seems that the node was partially started,
+ %% try to kill it.
+ Node = list_to_atom(lists:concat([Name, "@", Host])),
+ case net_adm:ping(Node) of
+ pong ->
+ spawn(Node, erlang, halt, []),
+ ok;
+ _ ->
+ ok
+ end,
+ Parent ! {result, {error, timeout}}
+ end;
+ Other ->
+ Parent ! {result, Other}
+ end.
+
+slave_started(ReplyTo, no_link, Slave) when is_pid(Slave) ->
+ ReplyTo ! {result, {ok, node(Slave)}};
+slave_started(ReplyTo, Master, Slave) when is_pid(Master), is_pid(Slave) ->
+ process_flag(trap_exit, true),
+ link(Master),
+ link(Slave),
+ ReplyTo ! {result, {ok, node(Slave)}},
+ one_way_link(Master, Slave).
+
+%% This function simulates a one-way link, so that the slave node
+%% will be killed if the master process terminates, but the master
+%% process will not be killed if the slave node terminates.
+
+one_way_link(Master, Slave) ->
+ receive
+ {'EXIT', Master, _Reason} ->
+ unlink(Slave),
+ Slave ! {nodedown, node()};
+ {'EXIT', Slave, _Reason} ->
+ unlink(Master);
+ _Other ->
+ one_way_link(Master, Slave)
+ end.
+
+register_unique_name(Number) ->
+ Name = list_to_atom(lists:concat(["slave_waiter_", Number])),
+ case catch register(Name, self()) of
+ true ->
+ Name;
+ {'EXIT', {badarg, _}} ->
+ register_unique_name(Number+1)
+ end.
+
+%% Makes up the command to start the nodes.
+%% If the node should run on the local host, there is
+%% no need to use rsh.
+
+mk_cmd(Host, Name, Args, Waiter, Prog) ->
+ BasicCmd = lists:concat([Prog,
+ " -detached -noinput -master ", node(),
+ " ", long_or_short(), Name, "@", Host,
+ " -s slave slave_start ", node(),
+ " ", Waiter,
+ " ", Args]),
+
+ case after_char($@, atom_to_list(node())) of
+ Host ->
+ {ok, BasicCmd};
+ _ ->
+ case rsh() of
+ {ok, Rsh} ->
+ {ok, lists:concat([Rsh, " ", Host, " ", BasicCmd])};
+ Other ->
+ Other
+ end
+ end.
+
+%% Give the user an opportunity to run another program,
+%% than the "rsh". On HP-UX rsh is called remsh; thus HP users
+%% must start erlang as erl -rsh remsh.
+%%
+%% Also checks that the given program exists.
+%%
+%% Returns: {ok, RshPath} | {error, Reason}
+
+rsh() ->
+ Rsh =
+ case init:get_argument(rsh) of
+ {ok, [[Prog]]} -> Prog;
+ _ -> "rsh"
+ end,
+ case os:find_executable(Rsh) of
+ false -> {error, no_rsh};
+ Path -> {ok, Path}
+ end.
+
+long_or_short() ->
+ case net_kernel:longnames() of
+ true -> " -name ";
+ false -> " -sname "
+ end.
+
+%% This function will be invoked on the slave, using the -s option of erl.
+%% It will wait for the master node to terminate.
+
+slave_start([Master, Waiter]) ->
+ ?dbg({?MODULE, slave_start}, [[Master, Waiter]]),
+ spawn(?MODULE, wait_for_master_to_die, [Master, Waiter]).
+
+wait_for_master_to_die(Master, Waiter) ->
+ ?dbg({?MODULE, wait_for_master_to_die}, [Master, Waiter]),
+ process_flag(trap_exit, true),
+ monitor_node(Master, true),
+ {Waiter, Master} ! {self(), slave_started},
+ wloop(Master).
+
+wloop(Master) ->
+ receive
+ {nodedown, Master} ->
+ ?dbg({?MODULE, wloop},
+ [[Master], {received, {nodedown, Master}}, halting_node] ),
+ halt();
+ _Other ->
+ wloop(Master)
+ end.
+
+%% Just the short hostname, not the qualified, for convenience.
+
+strip_host_name([]) -> [];
+strip_host_name([$.|_]) -> [];
+strip_host_name([H|T]) -> [H|strip_host_name(T)].
+
+dns(H) -> {ok, Host} = net_adm:dns_hostname(H), Host.
+
+to_list(X) when is_list(X) -> X;
+to_list(X) when is_atom(X) -> atom_to_list(X).
+
+upto(_, []) -> [];
+upto(Char, [Char|_]) -> [];
+upto(Char, [H|T]) -> [H|upto(Char, T)].
+
+after_char(_, []) -> [];
+after_char(Char, [Char|Rest]) -> Rest;
+after_char(Char, [_|Rest]) -> after_char(Char, Rest).
diff --git a/lib/stdlib/src/sofs.erl b/lib/stdlib/src/sofs.erl
new file mode 100644
index 0000000000..a83f803330
--- /dev/null
+++ b/lib/stdlib/src/sofs.erl
@@ -0,0 +1,2502 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(sofs).
+
+-export([from_term/1, from_term/2, from_external/2, empty_set/0,
+ is_type/1, set/1, set/2, from_sets/1, relation/1, relation/2,
+ a_function/1, a_function/2, family/1, family/2,
+ to_external/1, type/1, to_sets/1, no_elements/1,
+ specification/2, union/2, intersection/2, difference/2,
+ symdiff/2, symmetric_partition/2, product/1, product/2,
+ constant_function/2, is_equal/2, is_subset/2, is_sofs_set/1,
+ is_set/1, is_empty_set/1, is_disjoint/2]).
+
+-export([union/1, intersection/1, canonical_relation/1]).
+
+-export([relation_to_family/1, domain/1, range/1, field/1,
+ relative_product/1, relative_product/2, relative_product1/2,
+ converse/1, image/2, inverse_image/2, strict_relation/1,
+ weak_relation/1, extension/3, is_a_function/1]).
+
+-export([composite/2, inverse/1]).
+
+-export([restriction/2, restriction/3, drestriction/2, drestriction/3,
+ substitution/2, projection/2, partition/1, partition/2,
+ partition/3, multiple_relative_product/2, join/4]).
+
+-export([family_to_relation/1, family_specification/2,
+ union_of_family/1, intersection_of_family/1,
+ family_union/1, family_intersection/1,
+ family_domain/1, family_range/1, family_field/1,
+ family_union/2, family_intersection/2, family_difference/2,
+ partition_family/2, family_projection/2]).
+
+-export([family_to_digraph/1, family_to_digraph/2,
+ digraph_to_family/1, digraph_to_family/2]).
+
+%% Shorter names of some functions.
+-export([fam2rel/1, rel2fam/1]).
+
+-import(lists,
+ [any/2, append/1, flatten/1, foreach/2,
+ keysort/2, last/1, map/2, mapfoldl/3, member/2, merge/2,
+ reverse/1, reverse/2, sort/1, umerge/1, umerge/2, usort/1]).
+
+-compile({inline, [{family_to_relation,1}, {relation_to_family,1}]}).
+
+-compile({inline, [{rel,2},{a_func,2},{fam,2},{term2set,2}]}).
+
+-compile({inline, [{external_fun,1},{element_type,1}]}).
+
+-compile({inline,
+ [{unify_types,2}, {match_types,2},
+ {test_rel,3}, {symdiff,3},
+ {subst,3}]}).
+
+-compile({inline, [{fam_binop,3}]}).
+
+%% Nope, no is_member, del_member or add_member.
+%%
+%% See also "Naive Set Theory" by Paul R. Halmos.
+%%
+%% By convention, erlang:error/2 is called from exported functions.
+
+-define(TAG, 'Set').
+-define(ORDTAG, 'OrdSet').
+
+-record(?TAG, {data = [], type = type}).
+-record(?ORDTAG, {orddata = {}, ordtype = type}).
+
+-define(LIST(S), (S)#?TAG.data).
+-define(TYPE(S), (S)#?TAG.type).
+%%-define(SET(L, T),
+%% case is_type(T) of
+%% true -> #?TAG{data = L, type = T};
+%% false -> erlang:error(badtype, [T])
+%% end
+%% ).
+-define(SET(L, T), #?TAG{data = L, type = T}).
+-define(IS_SET(S), is_record(S, ?TAG)).
+-define(IS_UNTYPED_SET(S), ?TYPE(S) =:= ?ANYTYPE).
+
+%% Ordered sets and atoms:
+-define(ORDDATA(S), (S)#?ORDTAG.orddata).
+-define(ORDTYPE(S), (S)#?ORDTAG.ordtype).
+-define(ORDSET(L, T), #?ORDTAG{orddata = L, ordtype = T}).
+-define(IS_ORDSET(S), is_record(S, ?ORDTAG)).
+-define(ATOM_TYPE, atom).
+-define(IS_ATOM_TYPE(T), is_atom(T)). % true for ?ANYTYPE...
+
+%% When IS_SET is true:
+-define(ANYTYPE, '_').
+-define(BINREL(X, Y), {X, Y}).
+-define(IS_RELATION(R), is_tuple(R)).
+-define(REL_ARITY(R), tuple_size(R)).
+-define(REL_TYPE(I, R), element(I, R)).
+-define(SET_OF(X), [X]).
+-define(IS_SET_OF(X), is_list(X)).
+-define(FAMILY(X, Y), ?BINREL(X, ?SET_OF(Y))).
+
+%%
+%% Exported functions
+%%
+
+%%%
+%%% Create sets
+%%%
+
+from_term(T) ->
+ Type = case T of
+ _ when is_list(T) -> [?ANYTYPE];
+ _ -> ?ANYTYPE
+ end,
+ case catch setify(T, Type) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [T]);
+ Set ->
+ Set
+ end.
+
+from_term(L, T) ->
+ case is_type(T) of
+ true ->
+ case catch setify(L, T) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [L, T]);
+ Set ->
+ Set
+ end;
+ false ->
+ erlang:error(badarg, [L, T])
+ end.
+
+from_external(L, ?SET_OF(Type)) ->
+ ?SET(L, Type);
+from_external(T, Type) ->
+ ?ORDSET(T, Type).
+
+empty_set() ->
+ ?SET([], ?ANYTYPE).
+
+is_type(Atom) when ?IS_ATOM_TYPE(Atom), Atom =/= ?ANYTYPE ->
+ true;
+is_type(?SET_OF(T)) ->
+ is_element_type(T);
+is_type(T) when tuple_size(T) > 0 ->
+ is_types(tuple_size(T), T);
+is_type(_T) ->
+ false.
+
+set(L) ->
+ case catch usort(L) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [L]);
+ SL ->
+ ?SET(SL, ?ATOM_TYPE)
+ end.
+
+set(L, ?SET_OF(Type) = T) when ?IS_ATOM_TYPE(Type), Type =/= ?ANYTYPE ->
+ case catch usort(L) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [L, T]);
+ SL ->
+ ?SET(SL, Type)
+ end;
+set(L, ?SET_OF(_) = T) ->
+ case catch setify(L, T) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [L, T]);
+ Set ->
+ Set
+ end;
+set(L, T) ->
+ erlang:error(badarg, [L, T]).
+
+from_sets(Ss) when is_list(Ss) ->
+ case set_of_sets(Ss, [], ?ANYTYPE) of
+ {error, Error} ->
+ erlang:error(Error, [Ss]);
+ Set ->
+ Set
+ end;
+from_sets(Tuple) when is_tuple(Tuple) ->
+ case ordset_of_sets(tuple_to_list(Tuple), [], []) of
+ error ->
+ erlang:error(badarg, [Tuple]);
+ Set ->
+ Set
+ end;
+from_sets(T) ->
+ erlang:error(badarg, [T]).
+
+relation([]) ->
+ ?SET([], ?BINREL(?ATOM_TYPE, ?ATOM_TYPE));
+relation(Ts = [T | _]) when is_tuple(T) ->
+ case catch rel(Ts, tuple_size(T)) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [Ts]);
+ Set ->
+ Set
+ end;
+relation(E) ->
+ erlang:error(badarg, [E]).
+
+relation(Ts, TS) ->
+ case catch rel(Ts, TS) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [Ts, TS]);
+ Set ->
+ Set
+ end.
+
+a_function(Ts) ->
+ case catch func(Ts, ?BINREL(?ATOM_TYPE, ?ATOM_TYPE)) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [Ts]);
+ Bad when is_atom(Bad) ->
+ erlang:error(Bad, [Ts]);
+ Set ->
+ Set
+ end.
+
+a_function(Ts, T) ->
+ case catch a_func(Ts, T) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [Ts, T]);
+ Bad when is_atom(Bad) ->
+ erlang:error(Bad, [Ts, T]);
+ Set ->
+ Set
+ end.
+
+family(Ts) ->
+ case catch fam2(Ts, ?FAMILY(?ATOM_TYPE, ?ATOM_TYPE)) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [Ts]);
+ Bad when is_atom(Bad) ->
+ erlang:error(Bad, [Ts]);
+ Set ->
+ Set
+ end.
+
+family(Ts, T) ->
+ case catch fam(Ts, T) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [Ts, T]);
+ Bad when is_atom(Bad) ->
+ erlang:error(Bad, [Ts, T]);
+ Set ->
+ Set
+ end.
+
+%%%
+%%% Functions on sets.
+%%%
+
+to_external(S) when ?IS_SET(S) ->
+ ?LIST(S);
+to_external(S) when ?IS_ORDSET(S) ->
+ ?ORDDATA(S).
+
+type(S) when ?IS_SET(S) ->
+ ?SET_OF(?TYPE(S));
+type(S) when ?IS_ORDSET(S) ->
+ ?ORDTYPE(S).
+
+to_sets(S) when ?IS_SET(S) ->
+ case ?TYPE(S) of
+ ?SET_OF(Type) -> list_of_sets(?LIST(S), Type, []);
+ Type -> list_of_ordsets(?LIST(S), Type, [])
+ end;
+to_sets(S) when ?IS_ORDSET(S), is_tuple(?ORDTYPE(S)) ->
+ tuple_of_sets(tuple_to_list(?ORDDATA(S)), tuple_to_list(?ORDTYPE(S)), []);
+to_sets(S) when ?IS_ORDSET(S) ->
+ erlang:error(badarg, [S]).
+
+no_elements(S) when ?IS_SET(S) ->
+ length(?LIST(S));
+no_elements(S) when ?IS_ORDSET(S), is_tuple(?ORDTYPE(S)) ->
+ tuple_size(?ORDDATA(S));
+no_elements(S) when ?IS_ORDSET(S) ->
+ erlang:error(badarg, [S]).
+
+specification(Fun, S) when ?IS_SET(S) ->
+ Type = ?TYPE(S),
+ R = case external_fun(Fun) of
+ false ->
+ spec(?LIST(S), Fun, element_type(Type), []);
+ XFun ->
+ specification(?LIST(S), XFun, [])
+ end,
+ case R of
+ SL when is_list(SL) ->
+ ?SET(SL, Type);
+ Bad ->
+ erlang:error(Bad, [Fun, S])
+ end.
+
+union(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case unify_types(?TYPE(S1), ?TYPE(S2)) of
+ [] -> erlang:error(type_mismatch, [S1, S2]);
+ Type -> ?SET(umerge(?LIST(S1), ?LIST(S2)), Type)
+ end.
+
+intersection(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case unify_types(?TYPE(S1), ?TYPE(S2)) of
+ [] -> erlang:error(type_mismatch, [S1, S2]);
+ Type -> ?SET(intersection(?LIST(S1), ?LIST(S2), []), Type)
+ end.
+
+difference(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case unify_types(?TYPE(S1), ?TYPE(S2)) of
+ [] -> erlang:error(type_mismatch, [S1, S2]);
+ Type -> ?SET(difference(?LIST(S1), ?LIST(S2), []), Type)
+ end.
+
+symdiff(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case unify_types(?TYPE(S1), ?TYPE(S2)) of
+ [] -> erlang:error(type_mismatch, [S1, S2]);
+ Type -> ?SET(symdiff(?LIST(S1), ?LIST(S2), []), Type)
+ end.
+
+symmetric_partition(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case unify_types(?TYPE(S1), ?TYPE(S2)) of
+ [] -> erlang:error(type_mismatch, [S1, S2]);
+ Type -> sympart(?LIST(S1), ?LIST(S2), [], [], [], Type)
+ end.
+
+product(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ if
+ ?TYPE(S1) =:= ?ANYTYPE -> S1;
+ ?TYPE(S2) =:= ?ANYTYPE -> S2;
+ true ->
+ F = fun(E) -> {0, E} end,
+ T = ?BINREL(?TYPE(S1), ?TYPE(S2)),
+ ?SET(relprod(map(F, ?LIST(S1)), map(F, ?LIST(S2))), T)
+ end.
+
+product({S1, S2}) ->
+ product(S1, S2);
+product(T) when is_tuple(T) ->
+ Ss = tuple_to_list(T),
+ case catch sets_to_list(Ss) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [T]);
+ [] ->
+ erlang:error(badarg, [T]);
+ L ->
+ Type = types(Ss, []),
+ case member([], L) of
+ true ->
+ empty_set();
+ false ->
+ ?SET(reverse(prod(L, [], [])), Type)
+ end
+ end.
+
+constant_function(S, E) when ?IS_SET(S) ->
+ case {?TYPE(S), is_sofs_set(E)} of
+ {?ANYTYPE, true} -> S;
+ {Type, true} ->
+ NType = ?BINREL(Type, type(E)),
+ ?SET(constant_function(?LIST(S), to_external(E), []), NType);
+ _ -> erlang:error(badarg, [S, E])
+ end;
+constant_function(S, E) when ?IS_ORDSET(S) ->
+ erlang:error(badarg, [S, E]).
+
+is_equal(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case match_types(?TYPE(S1), ?TYPE(S2)) of
+ true -> ?LIST(S1) == ?LIST(S2);
+ false -> erlang:error(type_mismatch, [S1, S2])
+ end;
+is_equal(S1, S2) when ?IS_ORDSET(S1), ?IS_ORDSET(S2) ->
+ case match_types(?ORDTYPE(S1), ?ORDTYPE(S2)) of
+ true -> ?ORDDATA(S1) == ?ORDDATA(S2);
+ false -> erlang:error(type_mismatch, [S1, S2])
+ end;
+is_equal(S1, S2) when ?IS_SET(S1), ?IS_ORDSET(S2) ->
+ erlang:error(type_mismatch, [S1, S2]);
+is_equal(S1, S2) when ?IS_ORDSET(S1), ?IS_SET(S2) ->
+ erlang:error(type_mismatch, [S1, S2]).
+
+is_subset(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case match_types(?TYPE(S1), ?TYPE(S2)) of
+ true -> subset(?LIST(S1), ?LIST(S2));
+ false -> erlang:error(type_mismatch, [S1, S2])
+ end.
+
+is_sofs_set(S) when ?IS_SET(S) ->
+ true;
+is_sofs_set(S) when ?IS_ORDSET(S) ->
+ true;
+is_sofs_set(_S) ->
+ false.
+
+is_set(S) when ?IS_SET(S) ->
+ true;
+is_set(S) when ?IS_ORDSET(S) ->
+ false.
+
+is_empty_set(S) when ?IS_SET(S) ->
+ ?LIST(S) =:= [];
+is_empty_set(S) when ?IS_ORDSET(S) ->
+ false.
+
+is_disjoint(S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ case match_types(?TYPE(S1), ?TYPE(S2)) of
+ true ->
+ case ?LIST(S1) of
+ [] -> true;
+ [A | As] -> disjoint(?LIST(S2), A, As)
+ end;
+ false -> erlang:error(type_mismatch, [S1, S2])
+ end.
+
+%%%
+%%% Functions on set-of-sets.
+%%%
+
+union(Sets) when ?IS_SET(Sets) ->
+ case ?TYPE(Sets) of
+ ?SET_OF(Type) -> ?SET(lunion(?LIST(Sets)), Type);
+ ?ANYTYPE -> Sets;
+ _ -> erlang:error(badarg, [Sets])
+ end.
+
+intersection(Sets) when ?IS_SET(Sets) ->
+ case ?LIST(Sets) of
+ [] -> erlang:error(badarg, [Sets]);
+ [L | Ls] ->
+ case ?TYPE(Sets) of
+ ?SET_OF(Type) ->
+ ?SET(lintersection(Ls, L), Type);
+ _ -> erlang:error(badarg, [Sets])
+ end
+ end.
+
+canonical_relation(Sets) when ?IS_SET(Sets) ->
+ ST = ?TYPE(Sets),
+ case ST of
+ ?SET_OF(?ANYTYPE) -> empty_set();
+ ?SET_OF(Type) ->
+ ?SET(can_rel(?LIST(Sets), []), ?BINREL(Type, ST));
+ ?ANYTYPE -> Sets;
+ _ -> erlang:error(badarg, [Sets])
+ end.
+
+%%%
+%%% Functions on binary relations only.
+%%%
+
+rel2fam(R) ->
+ relation_to_family(R).
+
+%% Inlined.
+relation_to_family(R) when ?IS_SET(R) ->
+ case ?TYPE(R) of
+ ?BINREL(DT, RT) ->
+ ?SET(rel2family(?LIST(R)), ?FAMILY(DT, RT));
+ ?ANYTYPE -> R;
+ _Else -> erlang:error(badarg, [R])
+ end.
+
+domain(R) when ?IS_SET(R) ->
+ case ?TYPE(R) of
+ ?BINREL(DT, _) -> ?SET(dom(?LIST(R)), DT);
+ ?ANYTYPE -> R;
+ _Else -> erlang:error(badarg, [R])
+ end.
+
+range(R) when ?IS_SET(R) ->
+ case ?TYPE(R) of
+ ?BINREL(_, RT) -> ?SET(ran(?LIST(R), []), RT);
+ ?ANYTYPE -> R;
+ _ -> erlang:error(badarg, [R])
+ end.
+
+%% In "Introduction to LOGIC", Suppes defines the field of a binary
+%% relation to be the union of the domain and the range (or
+%% counterdomain).
+field(R) ->
+ union(domain(R), range(R)).
+
+relative_product(RT) when is_tuple(RT) ->
+ case relprod_n(RT, foo, false, false) of
+ {error, Reason} ->
+ erlang:error(Reason, [RT]);
+ Reply ->
+ Reply
+ end.
+
+relative_product(R1, R2) when ?IS_SET(R1), ?IS_SET(R2) ->
+ relative_product1(converse(R1), R2);
+relative_product(RT, R) when is_tuple(RT), ?IS_SET(R) ->
+ EmptyR = case ?TYPE(R) of
+ ?BINREL(_, _) -> ?LIST(R) =:= [];
+ ?ANYTYPE -> true;
+ _ -> erlang:error(badarg, [RT, R])
+ end,
+ case relprod_n(RT, R, EmptyR, true) of
+ {error, Reason} ->
+ erlang:error(Reason, [RT, R]);
+ Reply ->
+ Reply
+ end.
+
+relative_product1(R1, R2) when ?IS_SET(R1), ?IS_SET(R2) ->
+ {DTR1, RTR1} = case ?TYPE(R1) of
+ ?BINREL(_, _) = R1T -> R1T;
+ ?ANYTYPE -> {?ANYTYPE, ?ANYTYPE};
+ _ -> erlang:error(badarg, [R1, R2])
+ end,
+ {DTR2, RTR2} = case ?TYPE(R2) of
+ ?BINREL(_, _) = R2T -> R2T;
+ ?ANYTYPE -> {?ANYTYPE, ?ANYTYPE};
+ _ -> erlang:error(badarg, [R1, R2])
+ end,
+ case match_types(DTR1, DTR2) of
+ true when DTR1 =:= ?ANYTYPE -> R1;
+ true when DTR2 =:= ?ANYTYPE -> R2;
+ true -> ?SET(relprod(?LIST(R1), ?LIST(R2)), ?BINREL(RTR1, RTR2));
+ false -> erlang:error(type_mismatch, [R1, R2])
+ end.
+
+converse(R) when ?IS_SET(R) ->
+ case ?TYPE(R) of
+ ?BINREL(DT, RT) -> ?SET(converse(?LIST(R), []), ?BINREL(RT, DT));
+ ?ANYTYPE -> R;
+ _ -> erlang:error(badarg, [R])
+ end.
+
+image(R, S) when ?IS_SET(R), ?IS_SET(S) ->
+ case ?TYPE(R) of
+ ?BINREL(DT, RT) ->
+ case match_types(DT, ?TYPE(S)) of
+ true ->
+ ?SET(usort(restrict(?LIST(S), ?LIST(R))), RT);
+ false ->
+ erlang:error(type_mismatch, [R, S])
+ end;
+ ?ANYTYPE -> R;
+ _ -> erlang:error(badarg, [R, S])
+ end.
+
+inverse_image(R, S) when ?IS_SET(R), ?IS_SET(S) ->
+ case ?TYPE(R) of
+ ?BINREL(DT, RT) ->
+ case match_types(RT, ?TYPE(S)) of
+ true ->
+ NL = restrict(?LIST(S), converse(?LIST(R), [])),
+ ?SET(usort(NL), DT);
+ false ->
+ erlang:error(type_mismatch, [R, S])
+ end;
+ ?ANYTYPE -> R;
+ _ -> erlang:error(badarg, [R, S])
+ end.
+
+strict_relation(R) when ?IS_SET(R) ->
+ case ?TYPE(R) of
+ Type = ?BINREL(_, _) ->
+ ?SET(strict(?LIST(R), []), Type);
+ ?ANYTYPE -> R;
+ _ -> erlang:error(badarg, [R])
+ end.
+
+weak_relation(R) when ?IS_SET(R) ->
+ case ?TYPE(R) of
+ ?BINREL(DT, RT) ->
+ case unify_types(DT, RT) of
+ [] ->
+ erlang:error(badarg, [R]);
+ Type ->
+ ?SET(weak(?LIST(R)), ?BINREL(Type, Type))
+ end;
+ ?ANYTYPE -> R;
+ _ -> erlang:error(badarg, [R])
+ end.
+
+extension(R, S, E) when ?IS_SET(R), ?IS_SET(S) ->
+ case {?TYPE(R), ?TYPE(S), is_sofs_set(E)} of
+ {T=?BINREL(DT, RT), ST, true} ->
+ case match_types(DT, ST) and match_types(RT, type(E)) of
+ false ->
+ erlang:error(type_mismatch, [R, S, E]);
+ true ->
+ RL = ?LIST(R),
+ case extc([], ?LIST(S), to_external(E), RL) of
+ [] ->
+ R;
+ L ->
+ ?SET(merge(RL, reverse(L)), T)
+ end
+ end;
+ {?ANYTYPE, ?ANYTYPE, true} ->
+ R;
+ {?ANYTYPE, ST, true} ->
+ case type(E) of
+ ?SET_OF(?ANYTYPE) ->
+ R;
+ ET ->
+ ?SET([], ?BINREL(ST, ET))
+ end;
+ {_, _, true} ->
+ erlang:error(badarg, [R, S, E])
+ end.
+
+is_a_function(R) when ?IS_SET(R) ->
+ case ?TYPE(R) of
+ ?BINREL(_, _) ->
+ case ?LIST(R) of
+ [] -> true;
+ [{V,_} | Es] -> is_a_func(Es, V)
+ end;
+ ?ANYTYPE -> true;
+ _ -> erlang:error(badarg, [R])
+ end.
+
+restriction(Relation, Set) ->
+ restriction(1, Relation, Set).
+
+drestriction(Relation, Set) ->
+ drestriction(1, Relation, Set).
+
+%%%
+%%% Functions on functions only.
+%%%
+
+composite(Fn1, Fn2) when ?IS_SET(Fn1), ?IS_SET(Fn2) ->
+ ?BINREL(DTF1, RTF1) = case ?TYPE(Fn1)of
+ ?BINREL(_, _) = F1T -> F1T;
+ ?ANYTYPE -> {?ANYTYPE, ?ANYTYPE};
+ _ -> erlang:error(badarg, [Fn1, Fn2])
+ end,
+ ?BINREL(DTF2, RTF2) = case ?TYPE(Fn2) of
+ ?BINREL(_, _) = F2T -> F2T;
+ ?ANYTYPE -> {?ANYTYPE, ?ANYTYPE};
+ _ -> erlang:error(badarg, [Fn1, Fn2])
+ end,
+ case match_types(RTF1, DTF2) of
+ true when DTF1 =:= ?ANYTYPE -> Fn1;
+ true when DTF2 =:= ?ANYTYPE -> Fn2;
+ true ->
+ case comp(?LIST(Fn1), ?LIST(Fn2)) of
+ SL when is_list(SL) ->
+ ?SET(sort(SL), ?BINREL(DTF1, RTF2));
+ Bad ->
+ erlang:error(Bad, [Fn1, Fn2])
+ end;
+ false -> erlang:error(type_mismatch, [Fn1, Fn2])
+ end.
+
+inverse(Fn) when ?IS_SET(Fn) ->
+ case ?TYPE(Fn) of
+ ?BINREL(DT, RT) ->
+ case inverse1(?LIST(Fn)) of
+ SL when is_list(SL) ->
+ ?SET(SL, ?BINREL(RT, DT));
+ Bad ->
+ erlang:error(Bad, [Fn])
+ end;
+ ?ANYTYPE -> Fn;
+ _ -> erlang:error(badarg, [Fn])
+ end.
+
+%%%
+%%% Functions on relations (binary or other).
+%%%
+
+%% Equivalent to range(restriction(inverse(substitution(Fun, S1)), S2)).
+restriction(I, R, S) when is_integer(I), ?IS_SET(R), ?IS_SET(S) ->
+ RT = ?TYPE(R),
+ ST = ?TYPE(S),
+ case check_for_sort(RT, I) of
+ empty ->
+ R;
+ error ->
+ erlang:error(badarg, [I, R, S]);
+ Sort ->
+ RL = ?LIST(R),
+ case {match_types(?REL_TYPE(I, RT), ST), ?LIST(S)} of
+ {true, _SL} when RL =:= [] ->
+ R;
+ {true, []} ->
+ ?SET([], RT);
+ {true, [E | Es]} when Sort =:= false -> % I =:= 1
+ ?SET(reverse(restrict_n(I, RL, E, Es, [])), RT);
+ {true, [E | Es]} ->
+ ?SET(sort(restrict_n(I, keysort(I, RL), E, Es, [])), RT);
+ {false, _SL} ->
+ erlang:error(type_mismatch, [I, R, S])
+ end
+ end;
+restriction(SetFun, S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ Type1 = ?TYPE(S1),
+ Type2 = ?TYPE(S2),
+ SL1 = ?LIST(S1),
+ case external_fun(SetFun) of
+ false when Type2 =:= ?ANYTYPE ->
+ S2;
+ false ->
+ case subst(SL1, SetFun, element_type(Type1)) of
+ {NSL, NewType} -> % NewType can be ?ANYTYPE
+ case match_types(NewType, Type2) of
+ true ->
+ NL = sort(restrict(?LIST(S2), converse(NSL, []))),
+ ?SET(NL, Type1);
+ false ->
+ erlang:error(type_mismatch, [SetFun, S1, S2])
+ end;
+ Bad ->
+ erlang:error(Bad, [SetFun, S1, S2])
+ end;
+ _ when Type1 =:= ?ANYTYPE ->
+ S1;
+ _XFun when ?IS_SET_OF(Type1) ->
+ erlang:error(badarg, [SetFun, S1, S2]);
+ XFun ->
+ FunT = XFun(Type1),
+ case catch check_fun(Type1, XFun, FunT) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [SetFun, S1, S2]);
+ Sort ->
+ case match_types(FunT, Type2) of
+ true ->
+ R1 = inverse_substitution(SL1, XFun, Sort),
+ ?SET(sort(Sort, restrict(?LIST(S2), R1)), Type1);
+ false ->
+ erlang:error(type_mismatch, [SetFun, S1, S2])
+ end
+ end
+ end.
+
+drestriction(I, R, S) when is_integer(I), ?IS_SET(R), ?IS_SET(S) ->
+ RT = ?TYPE(R),
+ ST = ?TYPE(S),
+ case check_for_sort(RT, I) of
+ empty ->
+ R;
+ error ->
+ erlang:error(badarg, [I, R, S]);
+ Sort ->
+ RL = ?LIST(R),
+ case {match_types(?REL_TYPE(I, RT), ST), ?LIST(S)} of
+ {true, []} ->
+ R;
+ {true, _SL} when RL =:= [] ->
+ R;
+ {true, [E | Es]} when Sort =:= false -> % I =:= 1
+ ?SET(diff_restrict_n(I, RL, E, Es, []), RT);
+ {true, [E | Es]} ->
+ ?SET(diff_restrict_n(I, keysort(I, RL), E, Es, []), RT);
+ {false, _SL} ->
+ erlang:error(type_mismatch, [I, R, S])
+ end
+ end;
+drestriction(SetFun, S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ Type1 = ?TYPE(S1),
+ Type2 = ?TYPE(S2),
+ SL1 = ?LIST(S1),
+ case external_fun(SetFun) of
+ false when Type2 =:= ?ANYTYPE ->
+ S1;
+ false ->
+ case subst(SL1, SetFun, element_type(Type1)) of
+ {NSL, NewType} -> % NewType can be ?ANYTYPE
+ case match_types(NewType, Type2) of
+ true ->
+ SL2 = ?LIST(S2),
+ NL = sort(diff_restrict(SL2, converse(NSL, []))),
+ ?SET(NL, Type1);
+ false ->
+ erlang:error(type_mismatch, [SetFun, S1, S2])
+ end;
+ Bad ->
+ erlang:error(Bad, [SetFun, S1, S2])
+ end;
+ _ when Type1 =:= ?ANYTYPE ->
+ S1;
+ _XFun when ?IS_SET_OF(Type1) ->
+ erlang:error(badarg, [SetFun, S1, S2]);
+ XFun ->
+ FunT = XFun(Type1),
+ case catch check_fun(Type1, XFun, FunT) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [SetFun, S1, S2]);
+ Sort ->
+ case match_types(FunT, Type2) of
+ true ->
+ R1 = inverse_substitution(SL1, XFun, Sort),
+ SL2 = ?LIST(S2),
+ ?SET(sort(Sort, diff_restrict(SL2, R1)), Type1);
+ false ->
+ erlang:error(type_mismatch, [SetFun, S1, S2])
+ end
+ end
+ end.
+
+projection(I, Set) when is_integer(I), ?IS_SET(Set) ->
+ Type = ?TYPE(Set),
+ case check_for_sort(Type, I) of
+ empty ->
+ Set;
+ error ->
+ erlang:error(badarg, [I, Set]);
+ _ when I =:= 1 ->
+ ?SET(projection1(?LIST(Set)), ?REL_TYPE(I, Type));
+ _ ->
+ ?SET(projection_n(?LIST(Set), I, []), ?REL_TYPE(I, Type))
+ end;
+projection(Fun, Set) ->
+ range(substitution(Fun, Set)).
+
+substitution(I, Set) when is_integer(I), ?IS_SET(Set) ->
+ Type = ?TYPE(Set),
+ case check_for_sort(Type, I) of
+ empty ->
+ Set;
+ error ->
+ erlang:error(badarg, [I, Set]);
+ _Sort ->
+ NType = ?REL_TYPE(I, Type),
+ NSL = substitute_element(?LIST(Set), I, []),
+ ?SET(NSL, ?BINREL(Type, NType))
+ end;
+substitution(SetFun, Set) when ?IS_SET(Set) ->
+ Type = ?TYPE(Set),
+ L = ?LIST(Set),
+ case external_fun(SetFun) of
+ false when L =/= [] ->
+ case subst(L, SetFun, element_type(Type)) of
+ {SL, NewType} ->
+ ?SET(reverse(SL), ?BINREL(Type, NewType));
+ Bad ->
+ erlang:error(Bad, [SetFun, Set])
+ end;
+ false ->
+ empty_set();
+ _ when Type =:= ?ANYTYPE ->
+ empty_set();
+ _XFun when ?IS_SET_OF(Type) ->
+ erlang:error(badarg, [SetFun, Set]);
+ XFun ->
+ FunT = XFun(Type),
+ case catch check_fun(Type, XFun, FunT) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [SetFun, Set]);
+ _Sort ->
+ SL = substitute(L, XFun, []),
+ ?SET(SL, ?BINREL(Type, FunT))
+ end
+ end.
+
+partition(Sets) ->
+ F1 = relation_to_family(canonical_relation(Sets)),
+ F2 = relation_to_family(converse(F1)),
+ range(F2).
+
+partition(I, Set) when is_integer(I), ?IS_SET(Set) ->
+ Type = ?TYPE(Set),
+ case check_for_sort(Type, I) of
+ empty ->
+ Set;
+ error ->
+ erlang:error(badarg, [I, Set]);
+ false -> % I =:= 1
+ ?SET(partition_n(I, ?LIST(Set)), ?SET_OF(Type));
+ true ->
+ ?SET(partition_n(I, keysort(I, ?LIST(Set))), ?SET_OF(Type))
+ end;
+partition(Fun, Set) ->
+ range(partition_family(Fun, Set)).
+
+partition(I, R, S) when is_integer(I), ?IS_SET(R), ?IS_SET(S) ->
+ RT = ?TYPE(R),
+ ST = ?TYPE(S),
+ case check_for_sort(RT, I) of
+ empty ->
+ {R, R};
+ error ->
+ erlang:error(badarg, [I, R, S]);
+ Sort ->
+ RL = ?LIST(R),
+ case {match_types(?REL_TYPE(I, RT), ST), ?LIST(S)} of
+ {true, _SL} when RL =:= [] ->
+ {R, R};
+ {true, []} ->
+ {?SET([], RT), R};
+ {true, [E | Es]} when Sort =:= false -> % I =:= 1
+ [L1 | L2] = partition3_n(I, RL, E, Es, [], []),
+ {?SET(L1, RT), ?SET(L2, RT)};
+ {true, [E | Es]} ->
+ [L1 | L2] = partition3_n(I, keysort(I,RL), E, Es, [], []),
+ {?SET(L1, RT), ?SET(L2, RT)};
+ {false, _SL} ->
+ erlang:error(type_mismatch, [I, R, S])
+ end
+ end;
+partition(SetFun, S1, S2) when ?IS_SET(S1), ?IS_SET(S2) ->
+ Type1 = ?TYPE(S1),
+ Type2 = ?TYPE(S2),
+ SL1 = ?LIST(S1),
+ case external_fun(SetFun) of
+ false when Type2 =:= ?ANYTYPE ->
+ {S2, S1};
+ false ->
+ case subst(SL1, SetFun, element_type(Type1)) of
+ {NSL, NewType} -> % NewType can be ?ANYTYPE
+ case match_types(NewType, Type2) of
+ true ->
+ R1 = converse(NSL, []),
+ [L1 | L2] = partition3(?LIST(S2), R1),
+ {?SET(sort(L1), Type1), ?SET(sort(L2), Type1)};
+ false ->
+ erlang:error(type_mismatch, [SetFun, S1, S2])
+ end;
+ Bad ->
+ erlang:error(Bad, [SetFun, S1, S2])
+ end;
+ _ when Type1 =:= ?ANYTYPE ->
+ {S1, S1};
+ _XFun when ?IS_SET_OF(Type1) ->
+ erlang:error(badarg, [SetFun, S1, S2]);
+ XFun ->
+ FunT = XFun(Type1),
+ case catch check_fun(Type1, XFun, FunT) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [SetFun, S1, S2]);
+ Sort ->
+ case match_types(FunT, Type2) of
+ true ->
+ R1 = inverse_substitution(SL1, XFun, Sort),
+ [L1 | L2] = partition3(?LIST(S2), R1),
+ {?SET(sort(L1), Type1), ?SET(sort(L2), Type1)};
+ false ->
+ erlang:error(type_mismatch, [SetFun, S1, S2])
+ end
+ end
+ end.
+
+multiple_relative_product(T, R) when is_tuple(T), ?IS_SET(R) ->
+ case test_rel(R, tuple_size(T), eq) of
+ true when ?TYPE(R) =:= ?ANYTYPE ->
+ empty_set();
+ true ->
+ MProd = mul_relprod(tuple_to_list(T), 1, R),
+ relative_product(list_to_tuple(MProd));
+ false ->
+ erlang:error(badarg, [T, R])
+ end.
+
+join(R1, I1, R2, I2)
+ when ?IS_SET(R1), ?IS_SET(R2), is_integer(I1), is_integer(I2) ->
+ case test_rel(R1, I1, lte) and test_rel(R2, I2, lte) of
+ false ->
+ erlang:error(badarg, [R1, I1, R2, I2]);
+ true when ?TYPE(R1) =:= ?ANYTYPE -> R1;
+ true when ?TYPE(R2) =:= ?ANYTYPE -> R2;
+ true ->
+ L1 = ?LIST(raise_element(R1, I1)),
+ L2 = ?LIST(raise_element(R2, I2)),
+ T = relprod1(L1, L2),
+ F = case (I1 =:= 1) and (I2 =:= 1) of
+ true ->
+ fun({X,Y}) -> join_element(X, Y) end;
+ false ->
+ fun({X,Y}) ->
+ list_to_tuple(join_element(X, Y, I2))
+ end
+ end,
+ ?SET(replace(T, F, []), F({?TYPE(R1), ?TYPE(R2)}))
+ end.
+
+%% Inlined.
+test_rel(R, I, C) ->
+ case ?TYPE(R) of
+ Rel when ?IS_RELATION(Rel), C =:= eq, I =:= ?REL_ARITY(Rel) -> true;
+ Rel when ?IS_RELATION(Rel), C =:= lte, I>=1, I =< ?REL_ARITY(Rel) ->
+ true;
+ ?ANYTYPE -> true;
+ _ -> false
+ end.
+
+%%%
+%%% Family functions
+%%%
+
+fam2rel(F) ->
+ family_to_relation(F).
+
+%% Inlined.
+family_to_relation(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(DT, RT) ->
+ ?SET(family2rel(?LIST(F), []), ?BINREL(DT, RT));
+ ?ANYTYPE -> F;
+ _ -> erlang:error(badarg, [F])
+ end.
+
+family_specification(Fun, F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(_DT, Type) = FType ->
+ R = case external_fun(Fun) of
+ false ->
+ fam_spec(?LIST(F), Fun, Type, []);
+ XFun ->
+ fam_specification(?LIST(F), XFun, [])
+ end,
+ case R of
+ SL when is_list(SL) ->
+ ?SET(SL, FType);
+ Bad ->
+ erlang:error(Bad, [Fun, F])
+ end;
+ ?ANYTYPE -> F;
+ _ -> erlang:error(badarg, [Fun, F])
+ end.
+
+union_of_family(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(_DT, Type) ->
+ ?SET(un_of_fam(?LIST(F), []), Type);
+ ?ANYTYPE -> F;
+ _ -> erlang:error(badarg, [F])
+ end.
+
+intersection_of_family(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(_DT, Type) ->
+ case int_of_fam(?LIST(F)) of
+ FU when is_list(FU) ->
+ ?SET(FU, Type);
+ Bad ->
+ erlang:error(Bad, [F])
+ end;
+ _ -> erlang:error(badarg, [F])
+ end.
+
+family_union(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(DT, ?SET_OF(Type)) ->
+ ?SET(fam_un(?LIST(F), []), ?FAMILY(DT, Type));
+ ?ANYTYPE -> F;
+ _ -> erlang:error(badarg, [F])
+ end.
+
+family_intersection(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(DT, ?SET_OF(Type)) ->
+ case fam_int(?LIST(F), []) of
+ FU when is_list(FU) ->
+ ?SET(FU, ?FAMILY(DT, Type));
+ Bad ->
+ erlang:error(Bad, [F])
+ end;
+ ?ANYTYPE -> F;
+ _ -> erlang:error(badarg, [F])
+ end.
+
+family_domain(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(FDT, ?BINREL(DT, _)) ->
+ ?SET(fam_dom(?LIST(F), []), ?FAMILY(FDT, DT));
+ ?ANYTYPE -> F;
+ ?FAMILY(_, ?ANYTYPE) -> F;
+ _ -> erlang:error(badarg, [F])
+ end.
+
+family_range(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(DT, ?BINREL(_, RT)) ->
+ ?SET(fam_ran(?LIST(F), []), ?FAMILY(DT, RT));
+ ?ANYTYPE -> F;
+ ?FAMILY(_, ?ANYTYPE) -> F;
+ _ -> erlang:error(badarg, [F])
+ end.
+
+family_field(F) ->
+ family_union(family_domain(F), family_range(F)).
+
+family_union(F1, F2) ->
+ fam_binop(F1, F2, fun fam_union/3).
+
+family_intersection(F1, F2) ->
+ fam_binop(F1, F2, fun fam_intersect/3).
+
+family_difference(F1, F2) ->
+ fam_binop(F1, F2, fun fam_difference/3).
+
+%% Inlined.
+fam_binop(F1, F2, FF) when ?IS_SET(F1), ?IS_SET(F2) ->
+ case unify_types(?TYPE(F1), ?TYPE(F2)) of
+ [] ->
+ erlang:error(type_mismatch, [F1, F2]);
+ ?ANYTYPE ->
+ F1;
+ Type = ?FAMILY(_, _) ->
+ ?SET(FF(?LIST(F1), ?LIST(F2), []), Type);
+ _ -> erlang:error(badarg, [F1, F2])
+ end.
+
+partition_family(I, Set) when is_integer(I), ?IS_SET(Set) ->
+ Type = ?TYPE(Set),
+ case check_for_sort(Type, I) of
+ empty ->
+ Set;
+ error ->
+ erlang:error(badarg, [I, Set]);
+ false -> % when I =:= 1
+ ?SET(fam_partition_n(I, ?LIST(Set)),
+ ?BINREL(?REL_TYPE(I, Type), ?SET_OF(Type)));
+ true ->
+ ?SET(fam_partition_n(I, keysort(I, ?LIST(Set))),
+ ?BINREL(?REL_TYPE(I, Type), ?SET_OF(Type)))
+ end;
+partition_family(SetFun, Set) when ?IS_SET(Set) ->
+ Type = ?TYPE(Set),
+ SL = ?LIST(Set),
+ case external_fun(SetFun) of
+ false when SL =/= [] ->
+ case subst(SL, SetFun, element_type(Type)) of
+ {NSL, NewType} ->
+ P = fam_partition(converse(NSL, []), true),
+ ?SET(reverse(P), ?BINREL(NewType, ?SET_OF(Type)));
+ Bad ->
+ erlang:error(Bad, [SetFun, Set])
+ end;
+ false ->
+ empty_set();
+ _ when Type =:= ?ANYTYPE ->
+ empty_set();
+ _XFun when ?IS_SET_OF(Type) ->
+ erlang:error(badarg, [SetFun, Set]);
+ XFun ->
+ DType = XFun(Type),
+ case catch check_fun(Type, XFun, DType) of
+ {'EXIT', _} ->
+ erlang:error(badarg, [SetFun, Set]);
+ Sort ->
+ Ts = inverse_substitution(?LIST(Set), XFun, Sort),
+ P = fam_partition(Ts, Sort),
+ ?SET(reverse(P), ?BINREL(DType, ?SET_OF(Type)))
+ end
+ end.
+
+family_projection(SetFun, F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(_, _) when [] =:= ?LIST(F) ->
+ empty_set();
+ ?FAMILY(DT, Type) ->
+ case external_fun(SetFun) of
+ false ->
+ case fam_proj(?LIST(F), SetFun, Type, ?ANYTYPE, []) of
+ {SL, NewType} ->
+ ?SET(SL, ?BINREL(DT, NewType));
+ Bad ->
+ erlang:error(Bad, [SetFun, F])
+ end;
+ _ ->
+ erlang:error(badarg, [SetFun, F])
+ end;
+ ?ANYTYPE -> F;
+ _ -> erlang:error(badarg, [SetFun, F])
+ end.
+
+%%%
+%%% Digraph functions
+%%%
+
+family_to_digraph(F) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(_, _) -> fam2digraph(F, digraph:new());
+ ?ANYTYPE -> digraph:new();
+ _Else -> erlang:error(badarg, [F])
+ end.
+
+family_to_digraph(F, Type) when ?IS_SET(F) ->
+ case ?TYPE(F) of
+ ?FAMILY(_, _) -> ok;
+ ?ANYTYPE -> ok;
+ _Else -> erlang:error(badarg, [F, Type])
+ end,
+ try digraph:new(Type) of
+ G -> case catch fam2digraph(F, G) of
+ {error, Reason} ->
+ true = digraph:delete(G),
+ erlang:error(Reason, [F, Type]);
+ _ ->
+ G
+ end
+ catch
+ error:badarg -> erlang:error(badarg, [F, Type])
+ end.
+
+digraph_to_family(G) ->
+ case catch digraph_family(G) of
+ {'EXIT', _} -> erlang:error(badarg, [G]);
+ L -> ?SET(L, ?FAMILY(?ATOM_TYPE, ?ATOM_TYPE))
+ end.
+
+digraph_to_family(G, T) ->
+ case {is_type(T), T} of
+ {true, ?SET_OF(?FAMILY(_,_) = Type)} ->
+ case catch digraph_family(G) of
+ {'EXIT', _} -> erlang:error(badarg, [G, T]);
+ L -> ?SET(L, Type)
+ end;
+ _ ->
+ erlang:error(badarg, [G, T])
+ end.
+
+%%
+%% Local functions
+%%
+
+%% Type = OrderedSetType
+%% | SetType
+%% | atom() except '_'
+%% OrderedSetType = {Type, ..., Type}
+%% SetType = [ElementType] % list of exactly one element
+%% ElementType = '_' % any type (implies empty set)
+%% | Type
+
+is_types(0, _T) ->
+ true;
+is_types(I, T) ->
+ case is_type(?REL_TYPE(I, T)) of
+ true -> is_types(I-1, T);
+ false -> false
+ end.
+
+is_element_type(?ANYTYPE) ->
+ true;
+is_element_type(T) ->
+ is_type(T).
+
+set_of_sets([S | Ss], L, T0) when ?IS_SET(S) ->
+ case unify_types([?TYPE(S)], T0) of
+ [] -> {error, type_mismatch};
+ Type -> set_of_sets(Ss, [?LIST(S) | L], Type)
+ end;
+set_of_sets([S | Ss], L, T0) when ?IS_ORDSET(S) ->
+ case unify_types(?ORDTYPE(S), T0) of
+ [] -> {error, type_mismatch};
+ Type -> set_of_sets(Ss, [?ORDDATA(S) | L], Type)
+ end;
+set_of_sets([], L, T) ->
+ ?SET(usort(L), T);
+set_of_sets(_, _L, _T) ->
+ {error, badarg}.
+
+ordset_of_sets([S | Ss], L, T) when ?IS_SET(S) ->
+ ordset_of_sets(Ss, [?LIST(S) | L], [[?TYPE(S)] | T]);
+ordset_of_sets([S | Ss], L, T) when ?IS_ORDSET(S) ->
+ ordset_of_sets(Ss, [?ORDDATA(S) | L], [?ORDTYPE(S) | T]);
+ordset_of_sets([], L, T) ->
+ ?ORDSET(list_to_tuple(reverse(L)), list_to_tuple(reverse(T)));
+ordset_of_sets(_, _L, _T) ->
+ error.
+
+%% Inlined.
+rel(Ts, [Type]) ->
+ case is_type(Type) and atoms_only(Type, 1) of
+ true ->
+ rel(Ts, tuple_size(Type), Type);
+ false ->
+ rel_type(Ts, [], Type)
+ end;
+rel(Ts, Sz) ->
+ rel(Ts, Sz, erlang:make_tuple(Sz, ?ATOM_TYPE)).
+
+atoms_only(Type, I) when ?IS_ATOM_TYPE(?REL_TYPE(I, Type)) ->
+ atoms_only(Type, I+1);
+atoms_only(Type, I) when I > tuple_size(Type), ?IS_RELATION(Type) ->
+ true;
+atoms_only(_Type, _I) ->
+ false.
+
+rel(Ts, Sz, Type) when Sz >= 1 ->
+ SL = usort(Ts),
+ rel(SL, SL, Sz, Type).
+
+rel([T | Ts], L, Sz, Type) when tuple_size(T) =:= Sz ->
+ rel(Ts, L, Sz, Type);
+rel([], L, _Sz, Type) ->
+ ?SET(L, Type).
+
+rel_type([E | Ts], L, Type) ->
+ {NType, NE} = make_element(E, Type, Type),
+ rel_type(Ts, [NE | L], NType);
+rel_type([], [], ?ANYTYPE) ->
+ empty_set();
+rel_type([], SL, Type) when ?IS_RELATION(Type) ->
+ ?SET(usort(SL), Type).
+
+%% Inlined.
+a_func(Ts, T) ->
+ case {T, is_type(T)} of
+ {[?BINREL(DT, RT) = Type], true} when ?IS_ATOM_TYPE(DT),
+ ?IS_ATOM_TYPE(RT) ->
+ func(Ts, Type);
+ {[Type], true} ->
+ func_type(Ts, [], Type, fun(?BINREL(_,_)) -> true end)
+ end.
+
+func(L0, Type) ->
+ L = usort(L0),
+ func(L, L, L, Type).
+
+func([{X,_} | Ts], X0, L, Type) when X /= X0 ->
+ func(Ts, X, L, Type);
+func([{X,_} | _Ts], X0, _L, _Type) when X == X0 ->
+ bad_function;
+func([], _X0, L, Type) ->
+ ?SET(L, Type).
+
+%% Inlined.
+fam(Ts, T) ->
+ case {T, is_type(T)} of
+ {[?FAMILY(DT, RT) = Type], true} when ?IS_ATOM_TYPE(DT),
+ ?IS_ATOM_TYPE(RT) ->
+ fam2(Ts, Type);
+ {[Type], true} ->
+ func_type(Ts, [], Type, fun(?FAMILY(_,_)) -> true end)
+ end.
+
+fam2([], Type) ->
+ ?SET([], Type);
+fam2(Ts, Type) ->
+ fam2(sort(Ts), Ts, [], Type).
+
+fam2([{I,L} | T], I0, SL, Type) when I /= I0 ->
+ fam2(T, I, [{I,usort(L)} | SL], Type);
+fam2([{I,L} | T], I0, SL, Type) when I == I0 ->
+ case {usort(L), SL} of
+ {NL, [{_I,NL1} | _]} when NL == NL1 ->
+ fam2(T, I0, SL, Type);
+ _ ->
+ bad_function
+ end;
+fam2([], _I0, SL, Type) ->
+ ?SET(reverse(SL), Type).
+
+func_type([E | T], SL, Type, F) ->
+ {NType, NE} = make_element(E, Type, Type),
+ func_type(T, [NE | SL], NType, F);
+func_type([], [], ?ANYTYPE, _F) ->
+ empty_set();
+func_type([], SL, Type, F) ->
+ true = F(Type),
+ NL = usort(SL),
+ check_function(NL, ?SET(NL, Type)).
+
+setify(L, ?SET_OF(Atom)) when ?IS_ATOM_TYPE(Atom), Atom =/= ?ANYTYPE ->
+ ?SET(usort(L), Atom);
+setify(L, ?SET_OF(Type0)) ->
+ case catch is_no_lists(Type0) of
+ {'EXIT', _} ->
+ {?SET_OF(Type), Set} = create(L, Type0, Type0, []),
+ ?SET(Set, Type);
+ N when is_integer(N) ->
+ rel(L, N, Type0);
+ Sizes ->
+ make_oset(L, Sizes, L, Type0)
+ end;
+setify(E, Type0) ->
+ {Type, OrdSet} = make_element(E, Type0, Type0),
+ ?ORDSET(OrdSet, Type).
+
+is_no_lists(T) when is_tuple(T) ->
+ Sz = tuple_size(T),
+ is_no_lists(T, Sz, Sz, []).
+
+is_no_lists(_T, 0, Sz, []) ->
+ Sz;
+is_no_lists(_T, 0, Sz, L) ->
+ {Sz, L};
+is_no_lists(T, I, Sz, L) when ?IS_ATOM_TYPE(?REL_TYPE(I, T)) ->
+ is_no_lists(T, I-1, Sz, L);
+is_no_lists(T, I, Sz, L) ->
+ is_no_lists(T, I-1, Sz, [{I,is_no_lists(?REL_TYPE(I, T))} | L]).
+
+create([E | Es], T, T0, L) ->
+ {NT, S} = make_element(E, T, T0),
+ create(Es, NT, T0, [S | L]);
+create([], T, _T0, L) ->
+ {?SET_OF(T), usort(L)}.
+
+make_element(C, ?ANYTYPE, _T0) ->
+ make_element(C);
+make_element(C, Atom, ?ANYTYPE) when ?IS_ATOM_TYPE(Atom),
+ not is_list(C), not is_tuple(C) ->
+ {Atom, C};
+make_element(C, Atom, Atom) when ?IS_ATOM_TYPE(Atom) ->
+ {Atom, C};
+make_element(T, TT, ?ANYTYPE) when tuple_size(T) =:= tuple_size(TT) ->
+ make_tuple(tuple_to_list(T), tuple_to_list(TT), [], [], ?ANYTYPE);
+make_element(T, TT, T0) when tuple_size(T) =:= tuple_size(TT) ->
+ make_tuple(tuple_to_list(T), tuple_to_list(TT), [], [], tuple_to_list(T0));
+make_element(L, [LT], ?ANYTYPE) when is_list(L) ->
+ create(L, LT, ?ANYTYPE, []);
+make_element(L, [LT], [T0]) when is_list(L) ->
+ create(L, LT, T0, []).
+
+make_tuple([E | Es], [T | Ts], NT, L, T0) when T0 =:= ?ANYTYPE ->
+ {ET, ES} = make_element(E, T, T0),
+ make_tuple(Es, Ts, [ET | NT], [ES | L], T0);
+make_tuple([E | Es], [T | Ts], NT, L, [T0 | T0s]) ->
+ {ET, ES} = make_element(E, T, T0),
+ make_tuple(Es, Ts, [ET | NT], [ES | L], T0s);
+make_tuple([], [], NT, L, _T0s) when NT =/= [] ->
+ {list_to_tuple(reverse(NT)), list_to_tuple(reverse(L))}.
+
+%% Derive type.
+make_element(C) when not is_list(C), not is_tuple(C) ->
+ {?ATOM_TYPE, C};
+make_element(T) when is_tuple(T) ->
+ make_tuple(tuple_to_list(T), [], []);
+make_element(L) when is_list(L) ->
+ create(L, ?ANYTYPE, ?ANYTYPE, []).
+
+make_tuple([E | Es], T, L) ->
+ {ET, ES} = make_element(E),
+ make_tuple(Es, [ET | T], [ES | L]);
+make_tuple([], T, L) when T =/= [] ->
+ {list_to_tuple(reverse(T)), list_to_tuple(reverse(L))}.
+
+make_oset([T | Ts], Szs, L, Type) ->
+ true = test_oset(Szs, T, T),
+ make_oset(Ts, Szs, L, Type);
+make_oset([], _Szs, L, Type) ->
+ ?SET(usort(L), Type).
+
+%% Optimization. Avoid re-building (nested) tuples.
+test_oset({Sz,Args}, T, T0) when tuple_size(T) =:= Sz ->
+ test_oset_args(Args, T, T0);
+test_oset(Sz, T, _T0) when tuple_size(T) =:= Sz ->
+ true.
+
+test_oset_args([{Arg,Szs} | Ss], T, T0) ->
+ true = test_oset(Szs, ?REL_TYPE(Arg, T), T0),
+ test_oset_args(Ss, T, T0);
+test_oset_args([], _T, _T0) ->
+ true.
+
+list_of_sets([S | Ss], Type, L) ->
+ list_of_sets(Ss, Type, [?SET(S, Type) | L]);
+list_of_sets([], _Type, L) ->
+ reverse(L).
+
+list_of_ordsets([S | Ss], Type, L) ->
+ list_of_ordsets(Ss, Type, [?ORDSET(S, Type) | L]);
+list_of_ordsets([], _Type, L) ->
+ reverse(L).
+
+tuple_of_sets([S | Ss], [?SET_OF(Type) | Types], L) ->
+ tuple_of_sets(Ss, Types, [?SET(S, Type) | L]);
+tuple_of_sets([S | Ss], [Type | Types], L) ->
+ tuple_of_sets(Ss, Types, [?ORDSET(S, Type) | L]);
+tuple_of_sets([], [], L) ->
+ list_to_tuple(reverse(L)).
+
+spec([E | Es], Fun, Type, L) ->
+ case Fun(term2set(E, Type)) of
+ true ->
+ spec(Es, Fun, Type, [E | L]);
+ false ->
+ spec(Es, Fun, Type, L);
+ _ ->
+ badarg
+ end;
+spec([], _Fun, _Type, L) ->
+ reverse(L).
+
+specification([E | Es], Fun, L) ->
+ case Fun(E) of
+ true ->
+ specification(Es, Fun, [E | L]);
+ false ->
+ specification(Es, Fun, L);
+ _ ->
+ badarg
+ end;
+specification([], _Fun, L) ->
+ reverse(L).
+
+%% Elements from the first list are kept.
+intersection([H1 | T1], [H2 | T2], L) when H1 < H2 ->
+ intersection1(T1, T2, L, H2);
+intersection([H1 | T1], [H2 | T2], L) when H1 == H2 ->
+ intersection(T1, T2, [H1 | L]);
+intersection([H1 | T1], [_H2 | T2], L) ->
+ intersection2(T1, T2, L, H1);
+intersection(_, _, L) ->
+ reverse(L).
+
+intersection1([H1 | T1], T2, L, H2) when H1 < H2 ->
+ intersection1(T1, T2, L, H2);
+intersection1([H1 | T1], T2, L, H2) when H1 == H2 ->
+ intersection(T1, T2, [H1 | L]);
+intersection1([H1 | T1], T2, L, _H2) ->
+ intersection2(T1, T2, L, H1);
+intersection1(_, _, L, _) ->
+ reverse(L).
+
+intersection2(T1, [H2 | T2], L, H1) when H1 > H2 ->
+ intersection2(T1, T2, L, H1);
+intersection2(T1, [H2 | T2], L, H1) when H1 == H2 ->
+ intersection(T1, T2, [H1 | L]);
+intersection2(T1, [H2 | T2], L, _H1) ->
+ intersection1(T1, T2, L, H2);
+intersection2(_, _, L, _) ->
+ reverse(L).
+
+difference([H1 | T1], [H2 | T2], L) when H1 < H2 ->
+ diff(T1, T2, [H1 | L], H2);
+difference([H1 | T1], [H2 | T2], L) when H1 == H2 ->
+ difference(T1, T2, L);
+difference([H1 | T1], [_H2 | T2], L) ->
+ diff2(T1, T2, L, H1);
+difference(L1, _, L) ->
+ reverse(L, L1).
+
+diff([H1 | T1], T2, L, H2) when H1 < H2 ->
+ diff(T1, T2, [H1 | L], H2);
+diff([H1 | T1], T2, L, H2) when H1 == H2 ->
+ difference(T1, T2, L);
+diff([H1 | T1], T2, L, _H2) ->
+ diff2(T1, T2, L, H1);
+diff(_, _, L, _) ->
+ reverse(L).
+
+diff2(T1, [H2 | T2], L, H1) when H1 > H2 ->
+ diff2(T1, T2, L, H1);
+diff2(T1, [H2 | T2], L, H1) when H1 == H2 ->
+ difference(T1, T2, L);
+diff2(T1, [H2 | T2], L, H1) ->
+ diff(T1, T2, [H1 | L], H2);
+diff2(T1, _, L, H1) ->
+ reverse(L, [H1 | T1]).
+
+symdiff([H1 | T1], T2, L) ->
+ symdiff2(T1, T2, L, H1);
+symdiff(_, T2, L) ->
+ reverse(L, T2).
+
+symdiff1([H1 | T1], T2, L, H2) when H1 < H2 ->
+ symdiff1(T1, T2, [H1 | L], H2);
+symdiff1([H1 | T1], T2, L, H2) when H1 == H2 ->
+ symdiff(T1, T2, L);
+symdiff1([H1 | T1], T2, L, H2) ->
+ symdiff2(T1, T2, [H2 | L], H1);
+symdiff1(_, T2, L, H2) ->
+ reverse(L, [H2 | T2]).
+
+symdiff2(T1, [H2 | T2], L, H1) when H1 > H2 ->
+ symdiff2(T1, T2, [H2 | L], H1);
+symdiff2(T1, [H2 | T2], L, H1) when H1 == H2 ->
+ symdiff(T1, T2, L);
+symdiff2(T1, [H2 | T2], L, H1) ->
+ symdiff1(T1, T2, [H1 | L], H2);
+symdiff2(T1, _, L, H1) ->
+ reverse(L, [H1 | T1]).
+
+sympart([H1 | T1], [H2 | T2], L1, L12, L2, T) when H1 < H2 ->
+ sympart1(T1, T2, [H1 | L1], L12, L2, T, H2);
+sympart([H1 | T1], [H2 | T2], L1, L12, L2, T) when H1 == H2 ->
+ sympart(T1, T2, L1, [H1 | L12], L2, T);
+sympart([H1 | T1], [H2 | T2], L1, L12, L2, T) ->
+ sympart2(T1, T2, L1, L12, [H2 | L2], T, H1);
+sympart(S1, [], L1, L12, L2, T) ->
+ {?SET(reverse(L1, S1), T),
+ ?SET(reverse(L12), T),
+ ?SET(reverse(L2), T)};
+sympart(_, S2, L1, L12, L2, T) ->
+ {?SET(reverse(L1), T),
+ ?SET(reverse(L12), T),
+ ?SET(reverse(L2, S2), T)}.
+
+sympart1([H1 | T1], T2, L1, L12, L2, T, H2) when H1 < H2 ->
+ sympart1(T1, T2, [H1 | L1], L12, L2, T, H2);
+sympart1([H1 | T1], T2, L1, L12, L2, T, H2) when H1 == H2 ->
+ sympart(T1, T2, L1, [H1 | L12], L2, T);
+sympart1([H1 | T1], T2, L1, L12, L2, T, H2) ->
+ sympart2(T1, T2, L1, L12, [H2 | L2], T, H1);
+sympart1(_, T2, L1, L12, L2, T, H2) ->
+ {?SET(reverse(L1), T),
+ ?SET(reverse(L12), T),
+ ?SET(reverse(L2, [H2 | T2]), T)}.
+
+sympart2(T1, [H2 | T2], L1, L12, L2, T, H1) when H1 > H2 ->
+ sympart2(T1, T2, L1, L12, [H2 | L2], T, H1);
+sympart2(T1, [H2 | T2], L1, L12, L2, T, H1) when H1 == H2 ->
+ sympart(T1, T2, L1, [H1 | L12], L2, T);
+sympart2(T1, [H2 | T2], L1, L12, L2, T, H1) ->
+ sympart1(T1, T2, [H1 | L1], L12, L2, T, H2);
+sympart2(T1, _, L1, L12, L2, T, H1) ->
+ {?SET(reverse(L1, [H1 | T1]), T),
+ ?SET(reverse(L12), T),
+ ?SET(reverse(L2), T)}.
+
+prod([[E | Es] | Xs], T, L) ->
+ prod(Es, Xs, T, prod(Xs, [E | T], L));
+prod([], T, L) ->
+ [list_to_tuple(reverse(T)) | L].
+
+prod([E | Es], Xs, T, L) ->
+ prod(Es, Xs, T, prod(Xs, [E | T], L));
+prod([], _Xs, _E, L) ->
+ L.
+
+constant_function([E | Es], X, L) ->
+ constant_function(Es, X, [{E,X} | L]);
+constant_function([], _X, L) ->
+ reverse(L).
+
+subset([H1 | T1], [H2 | T2]) when H1 > H2 ->
+ subset(T1, T2, H1);
+subset([H1 | T1], [H2 | T2]) when H1 == H2 ->
+ subset(T1, T2);
+subset(L1, _) ->
+ L1 =:= [].
+
+subset(T1, [H2 | T2], H1) when H1 > H2 ->
+ subset(T1, T2, H1);
+subset(T1, [H2 | T2], H1) when H1 == H2 ->
+ subset(T1, T2);
+subset(_, _, _) ->
+ false.
+
+disjoint([B | Bs], A, As) when A < B ->
+ disjoint(As, B, Bs);
+disjoint([B | _Bs], A, _As) when A == B ->
+ false;
+disjoint([_B | Bs], A, As) ->
+ disjoint(Bs, A, As);
+disjoint(_Bs, _A, _As) ->
+ true.
+
+%% Append sets that come in order, then "merge".
+lunion([[_] = S]) -> % optimization
+ S;
+lunion([[] | Ls]) ->
+ lunion(Ls);
+lunion([S | Ss]) ->
+ umerge(lunion(Ss, last(S), [S], []));
+lunion([]) ->
+ [].
+
+lunion([[E] = S | Ss], Last, SL, Ls) when E > Last -> % optimization
+ lunion(Ss, E, [S | SL], Ls);
+lunion([S | Ss], Last, SL, Ls) when hd(S) > Last ->
+ lunion(Ss, last(S), [S | SL], Ls);
+lunion([S | Ss], _Last, SL, Ls) ->
+ lunion(Ss, last(S), [S], [append(reverse(SL)) | Ls]);
+lunion([], _Last, SL, Ls) ->
+ [append(reverse(SL)) | Ls].
+
+%% The empty list is always the first list, if present.
+lintersection(_, []) ->
+ [];
+lintersection([S | Ss], S0) ->
+ lintersection(Ss, intersection(S, S0, []));
+lintersection([], S) ->
+ S.
+
+can_rel([S | Ss], L) ->
+ can_rel(Ss, L, S, S);
+can_rel([], L) ->
+ sort(L).
+
+can_rel(Ss, L, [E | Es], S) ->
+ can_rel(Ss, [{E, S} | L], Es, S);
+can_rel(Ss, L, _, _S) ->
+ can_rel(Ss, L).
+
+rel2family([{X,Y} | S]) ->
+ rel2fam(S, X, [Y], []);
+rel2family([]) ->
+ [].
+
+rel2fam([{X,Y} | S], X0, YL, L) when X0 == X ->
+ rel2fam(S, X0, [Y | YL], L);
+rel2fam([{X,Y} | S], X0, [A,B | YL], L) -> % optimization
+ rel2fam(S, X, [Y], [{X0,reverse(YL,[B,A])} | L]);
+rel2fam([{X,Y} | S], X0, YL, L) ->
+ rel2fam(S, X, [Y], [{X0,YL} | L]);
+rel2fam([], X, YL, L) ->
+ reverse([{X,reverse(YL)} | L]).
+
+dom([{X,_} | Es]) ->
+ dom([], X, Es);
+dom([] = L) ->
+ L.
+
+dom(L, X, [{X1,_} | Es]) when X == X1 ->
+ dom(L, X, Es);
+dom(L, X, [{Y,_} | Es]) ->
+ dom([X | L], Y, Es);
+dom(L, X, []) ->
+ reverse(L, [X]).
+
+ran([{_,Y} | Es], L) ->
+ ran(Es, [Y | L]);
+ran([], L) ->
+ usort(L).
+
+relprod(A, B) ->
+ usort(relprod1(A, B)).
+
+relprod1([{Ay,Ax} | A], B) ->
+ relprod1(B, Ay, Ax, A, []);
+relprod1(_A, _B) ->
+ [].
+
+relprod1([{Bx,_By} | B], Ay, Ax, A, L) when Ay > Bx ->
+ relprod1(B, Ay, Ax, A, L);
+relprod1([{Bx,By} | B], Ay, Ax, A, L) when Ay == Bx ->
+ relprod(B, Bx, By, A, [{Ax,By} | L], Ax, B, Ay);
+relprod1([{Bx,By} | B], _Ay, _Ax, A, L) ->
+ relprod2(B, Bx, By, A, L);
+relprod1(_B, _Ay, _Ax, _A, L) ->
+ L.
+
+relprod2(B, Bx, By, [{Ay, _Ax} | A], L) when Ay < Bx ->
+ relprod2(B, Bx, By, A, L);
+relprod2(B, Bx, By, [{Ay, Ax} | A], L) when Ay == Bx ->
+ relprod(B, Bx, By, A, [{Ax,By} | L], Ax, B, Ay);
+relprod2(B, _Bx, _By, [{Ay, Ax} | A], L) ->
+ relprod1(B, Ay, Ax, A, L);
+relprod2(_, _, _, _, L) ->
+ L.
+
+relprod(B0, Bx0, By0, A0, L, Ax, [{Bx,By} | B], Ay) when Ay == Bx ->
+ relprod(B0, Bx0, By0, A0, [{Ax,By} | L], Ax, B, Ay);
+relprod(B0, Bx0, By0, A0, L, _Ax, _B, _Ay) ->
+ relprod2(B0, Bx0, By0, A0, L).
+
+relprod_n({}, _R, _EmptyG, _IsR) ->
+ {error, badarg};
+relprod_n(RT, R, EmptyR, IsR) ->
+ RL = tuple_to_list(RT),
+ case domain_type(RL, ?ANYTYPE) of
+ Error = {error, _Reason} ->
+ Error;
+ DType ->
+ Empty = any(fun is_empty_set/1, RL) or EmptyR,
+ RType = range_type(RL, []),
+ Type = ?BINREL(DType, RType),
+ Prod =
+ case Empty of
+ true when DType =:= ?ANYTYPE; RType =:= ?ANYTYPE ->
+ empty_set();
+ true ->
+ ?SET([], Type);
+ false ->
+ TL = ?LIST((relprod_n(RL))),
+ Sz = tuple_size(RT),
+ Fun = fun({X,A}) -> {X, flat(Sz, A, [])} end,
+ ?SET(map(Fun, TL), Type)
+ end,
+ case IsR of
+ true -> relative_product(Prod, R);
+ false -> Prod
+ end
+ end.
+
+relprod_n([R | Rs]) ->
+ relprod_n(Rs, R).
+
+relprod_n([], R) ->
+ R;
+relprod_n([R | Rs], R0) ->
+ T = raise_element(R0, 1),
+ R1 = relative_product1(T, R),
+ NR = projection({external, fun({{X,A},AS}) -> {X,{A,AS}} end}, R1),
+ relprod_n(Rs, NR).
+
+flat(1, A, L) ->
+ list_to_tuple([A | L]);
+flat(N, {T,A}, L) ->
+ flat(N-1, T, [A | L]).
+
+domain_type([T | Ts], T0) when ?IS_SET(T) ->
+ case ?TYPE(T) of
+ ?BINREL(DT, _RT) ->
+ case unify_types(DT, T0) of
+ [] -> {error, type_mismatch};
+ T1 -> domain_type(Ts, T1)
+ end;
+ ?ANYTYPE ->
+ domain_type(Ts, T0);
+ _ -> {error, badarg}
+ end;
+domain_type([], T0) ->
+ T0.
+
+range_type([T | Ts], L) ->
+ case ?TYPE(T) of
+ ?BINREL(_DT, RT) ->
+ range_type(Ts, [RT | L]);
+ ?ANYTYPE ->
+ ?ANYTYPE
+ end;
+range_type([], L) ->
+ list_to_tuple(reverse(L)).
+
+converse([{A,B} | X], L) ->
+ converse(X, [{B,A} | L]);
+converse([], L) ->
+ sort(L).
+
+strict([{E1,E2} | Es], L) when E1 == E2 ->
+ strict(Es, L);
+strict([E | Es], L) ->
+ strict(Es, [E | L]);
+strict([], L) ->
+ reverse(L).
+
+weak(Es) ->
+ %% Not very efficient...
+ weak(Es, ran(Es, []), []).
+
+weak(Es=[{X,_} | _], [Y | Ys], L) when X > Y ->
+ weak(Es, Ys, [{Y,Y} | L]);
+weak(Es=[{X,_} | _], [Y | Ys], L) when X == Y ->
+ weak(Es, Ys, L);
+weak([E={X,Y} | Es], Ys, L) when X > Y ->
+ weak1(Es, Ys, [E | L], X);
+weak([E={X,Y} | Es], Ys, L) when X == Y ->
+ weak2(Es, Ys, [E | L], X);
+weak([E={X,_Y} | Es], Ys, L) -> % when X < _Y
+ weak2(Es, Ys, [E, {X,X} | L], X);
+weak([], [Y | Ys], L) ->
+ weak([], Ys, [{Y,Y} | L]);
+weak([], [], L) ->
+ reverse(L).
+
+weak1([E={X,Y} | Es], Ys, L, X0) when X > Y, X == X0 ->
+ weak1(Es, Ys, [E | L], X);
+weak1([E={X,Y} | Es], Ys, L, X0) when X == Y, X == X0 ->
+ weak2(Es, Ys, [E | L], X);
+weak1([E={X,_Y} | Es], Ys, L, X0) when X == X0 -> % when X < Y
+ weak2(Es, Ys, [E, {X,X} | L], X);
+weak1(Es, Ys, L, X) ->
+ weak(Es, Ys, [{X,X} | L]).
+
+weak2([E={X,_Y} | Es], Ys, L, X0) when X == X0 -> % when X < _Y
+ weak2(Es, Ys, [E | L], X);
+weak2(Es, Ys, L, _X) ->
+ weak(Es, Ys, L).
+
+extc(L, [D | Ds], C, Ts) ->
+ extc(L, Ds, C, Ts, D);
+extc(L, [], _C, _Ts) ->
+ L.
+
+extc(L, Ds, C, [{X,_Y} | Ts], D) when X < D ->
+ extc(L, Ds, C, Ts, D);
+extc(L, Ds, C, [{X,_Y} | Ts], D) when X == D ->
+ extc(L, Ds, C, Ts);
+extc(L, Ds, C, [{X,_Y} | Ts], D) ->
+ extc2([{D,C} | L], Ds, C, Ts, X);
+extc(L, Ds, C, [], D) ->
+ extc_tail([{D,C} | L], Ds, C).
+
+extc2(L, [D | Ds], C, Ts, X) when X > D ->
+ extc2([{D,C} | L], Ds, C, Ts, X);
+extc2(L, [D | Ds], C, Ts, X) when X == D ->
+ extc(L, Ds, C, Ts);
+extc2(L, [D | Ds], C, Ts, _X) ->
+ extc(L, Ds, C, Ts, D);
+extc2(L, [], _C, _Ts, _X) ->
+ L.
+
+extc_tail(L, [D | Ds], C) ->
+ extc_tail([{D,C} | L], Ds, C);
+extc_tail(L, [], _C) ->
+ L.
+
+is_a_func([{E,_} | Es], E0) when E /= E0 ->
+ is_a_func(Es, E);
+is_a_func(L, _E) ->
+ L =:= [].
+
+restrict_n(I, [T | Ts], Key, Keys, L) ->
+ case element(I, T) of
+ K when K < Key ->
+ restrict_n(I, Ts, Key, Keys, L);
+ K when K == Key ->
+ restrict_n(I, Ts, Key, Keys, [T | L]);
+ K ->
+ restrict_n(I, K, Ts, Keys, L, T)
+ end;
+restrict_n(_I, _Ts, _Key, _Keys, L) ->
+ L.
+
+restrict_n(I, K, Ts, [Key | Keys], L, E) when K > Key ->
+ restrict_n(I, K, Ts, Keys, L, E);
+restrict_n(I, K, Ts, [Key | Keys], L, E) when K == Key ->
+ restrict_n(I, Ts, Key, Keys, [E | L]);
+restrict_n(I, _K, Ts, [Key | Keys], L, _E) ->
+ restrict_n(I, Ts, Key, Keys, L);
+restrict_n(_I, _K, _Ts, _Keys, L, _E) ->
+ L.
+
+restrict([Key | Keys], Tuples) ->
+ restrict(Tuples, Key, Keys, []);
+restrict(_Keys, _Tuples) ->
+ [].
+
+restrict([{K,_E} | Ts], Key, Keys, L) when K < Key ->
+ restrict(Ts, Key, Keys, L);
+restrict([{K,E} | Ts], Key, Keys, L) when K == Key ->
+ restrict(Ts, Key, Keys, [E | L]);
+restrict([{K,E} | Ts], _Key, Keys, L) ->
+ restrict(Ts, K, Keys, L, E);
+restrict(_Ts, _Key, _Keys, L) ->
+ L.
+
+restrict(Ts, K, [Key | Keys], L, E) when K > Key ->
+ restrict(Ts, K, Keys, L, E);
+restrict(Ts, K, [Key | Keys], L, E) when K == Key ->
+ restrict(Ts, Key, Keys, [E | L]);
+restrict(Ts, _K, [Key | Keys], L, _E) ->
+ restrict(Ts, Key, Keys, L);
+restrict(_Ts, _K, _Keys, L, _E) ->
+ L.
+
+diff_restrict_n(I, [T | Ts], Key, Keys, L) ->
+ case element(I, T) of
+ K when K < Key ->
+ diff_restrict_n(I, Ts, Key, Keys, [T | L]);
+ K when K == Key ->
+ diff_restrict_n(I, Ts, Key, Keys, L);
+ K ->
+ diff_restrict_n(I, K, Ts, Keys, L, T)
+ end;
+diff_restrict_n(I, _Ts, _Key, _Keys, L) when I =:= 1 ->
+ reverse(L);
+diff_restrict_n(_I, _Ts, _Key, _Keys, L) ->
+ sort(L).
+
+diff_restrict_n(I, K, Ts, [Key | Keys], L, T) when K > Key ->
+ diff_restrict_n(I, K, Ts, Keys, L, T);
+diff_restrict_n(I, K, Ts, [Key | Keys], L, _T) when K == Key ->
+ diff_restrict_n(I, Ts, Key, Keys, L);
+diff_restrict_n(I, _K, Ts, [Key | Keys], L, T) ->
+ diff_restrict_n(I, Ts, Key, Keys, [T | L]);
+diff_restrict_n(I, _K, Ts, _Keys, L, T) when I =:= 1 ->
+ reverse(L, [T | Ts]);
+diff_restrict_n(_I, _K, Ts, _Keys, L, T) ->
+ sort([T | Ts ++ L]).
+
+diff_restrict([Key | Keys], Tuples) ->
+ diff_restrict(Tuples, Key, Keys, []);
+diff_restrict(_Keys, Tuples) ->
+ diff_restrict_tail(Tuples, []).
+
+diff_restrict([{K,E} | Ts], Key, Keys, L) when K < Key ->
+ diff_restrict(Ts, Key, Keys, [E | L]);
+diff_restrict([{K,_E} | Ts], Key, Keys, L) when K == Key ->
+ diff_restrict(Ts, Key, Keys, L);
+diff_restrict([{K,E} | Ts], _Key, Keys, L) ->
+ diff_restrict(Ts, K, Keys, L, E);
+diff_restrict(_Ts, _Key, _Keys, L) ->
+ L.
+
+diff_restrict(Ts, K, [Key | Keys], L, E) when K > Key ->
+ diff_restrict(Ts, K, Keys, L, E);
+diff_restrict(Ts, K, [Key | Keys], L, _E) when K == Key ->
+ diff_restrict(Ts, Key, Keys, L);
+diff_restrict(Ts, _K, [Key | Keys], L, E) ->
+ diff_restrict(Ts, Key, Keys, [E | L]);
+diff_restrict(Ts, _K, _Keys, L, E) ->
+ diff_restrict_tail(Ts, [E | L]).
+
+diff_restrict_tail([{_K,E} | Ts], L) ->
+ diff_restrict_tail(Ts, [E | L]);
+diff_restrict_tail(_Ts, L) ->
+ L.
+
+comp([], B) ->
+ check_function(B, []);
+comp(_A, []) ->
+ bad_function;
+comp(A0, [{Bx,By} | B]) ->
+ A = converse(A0, []),
+ check_function(A0, comp1(A, B, [], Bx, By)).
+
+comp1([{Ay,Ax} | A], B, L, Bx, By) when Ay == Bx ->
+ comp1(A, B, [{Ax,By} | L], Bx, By);
+comp1([{Ay,Ax} | A], B, L, Bx, _By) when Ay > Bx ->
+ comp2(A, B, L, Bx, Ay, Ax);
+comp1([{Ay,_Ax} | _A], _B, _L, Bx, _By) when Ay < Bx ->
+ bad_function;
+comp1([], B, L, Bx, _By) ->
+ check_function(Bx, B, L).
+
+comp2(A, [{Bx,_By} | B], L, Bx0, Ay, Ax) when Ay > Bx, Bx /= Bx0 ->
+ comp2(A, B, L, Bx, Ay, Ax);
+comp2(A, [{Bx,By} | B], L, _Bx0, Ay, Ax) when Ay == Bx ->
+ comp1(A, B, [{Ax,By} | L], Bx, By);
+comp2(_A, _B, _L, _Bx0, _Ay, _Ax) ->
+ bad_function.
+
+inverse1([{A,B} | X]) ->
+ inverse(X, A, [{B,A}]);
+inverse1([]) ->
+ [].
+
+inverse([{A,B} | X], A0, L) when A0 /= A ->
+ inverse(X, A, [{B,A} | L]);
+inverse([{A,_B} | _X], A0, _L) when A0 == A ->
+ bad_function;
+inverse([], _A0, L) ->
+ SL = [{V,_} | Es] = sort(L),
+ case is_a_func(Es, V) of
+ true -> SL;
+ false -> bad_function
+ end.
+
+%% Inlined.
+external_fun({external, Function}) when is_atom(Function) ->
+ false;
+external_fun({external, Fun}) ->
+ Fun;
+external_fun(_) ->
+ false.
+
+%% Inlined.
+element_type(?SET_OF(Type)) -> Type;
+element_type(Type) -> Type.
+
+subst(Ts, Fun, Type) ->
+ subst(Ts, Fun, Type, ?ANYTYPE, []).
+
+subst([T | Ts], Fun, Type, NType, L) ->
+ case setfun(T, Fun, Type, NType) of
+ {SD, ST} -> subst(Ts, Fun, Type, ST, [{T, SD} | L]);
+ Bad -> Bad
+ end;
+subst([], _Fun, _Type, NType, L) ->
+ {L, NType}.
+
+projection1([E | Es]) ->
+ projection1([], element(1, E), Es);
+projection1([] = L) ->
+ L.
+
+projection1(L, X, [E | Es]) ->
+ case element(1, E) of
+ X1 when X == X1 -> projection1(L, X, Es);
+ X1 -> projection1([X | L], X1, Es)
+ end;
+projection1(L, X, []) ->
+ reverse(L, [X]).
+
+projection_n([E | Es], I, L) ->
+ projection_n(Es, I, [element(I, E) | L]);
+projection_n([], _I, L) ->
+ usort(L).
+
+substitute_element([T | Ts], I, L) ->
+ substitute_element(Ts, I, [{T, element(I, T)} | L]);
+substitute_element(_, _I, L) ->
+ reverse(L).
+
+substitute([T | Ts], Fun, L) ->
+ substitute(Ts, Fun, [{T, Fun(T)} | L]);
+substitute(_, _Fun, L) ->
+ reverse(L).
+
+partition_n(I, [E | Ts]) ->
+ partition_n(I, Ts, element(I, E), [E], []);
+partition_n(_I, []) ->
+ [].
+
+partition_n(I, [E | Ts], K, Es, P) ->
+ case {element(I, E), Es} of
+ {K1, _} when K == K1 ->
+ partition_n(I, Ts, K, [E | Es], P);
+ {K1, [_]} -> % optimization
+ partition_n(I, Ts, K1, [E], [Es | P]);
+ {K1, _} ->
+ partition_n(I, Ts, K1, [E], [reverse(Es) | P])
+ end;
+partition_n(I, [], _K, Es, P) when I > 1 ->
+ sort([reverse(Es) | P]);
+partition_n(_I, [], _K, [_] = Es, P) -> % optimization
+ reverse(P, [Es]);
+partition_n(_I, [], _K, Es, P) ->
+ reverse(P, [reverse(Es)]).
+
+partition3_n(I, [T | Ts], Key, Keys, L1, L2) ->
+ case element(I, T) of
+ K when K < Key ->
+ partition3_n(I, Ts, Key, Keys, L1, [T | L2]);
+ K when K == Key ->
+ partition3_n(I, Ts, Key, Keys, [T | L1], L2);
+ K ->
+ partition3_n(I, K, Ts, Keys, L1, L2, T)
+ end;
+partition3_n(I, _Ts, _Key, _Keys, L1, L2) when I =:= 1 ->
+ [reverse(L1) | reverse(L2)];
+partition3_n(_I, _Ts, _Key, _Keys, L1, L2) ->
+ [sort(L1) | sort(L2)].
+
+partition3_n(I, K, Ts, [Key | Keys], L1, L2, T) when K > Key ->
+ partition3_n(I, K, Ts, Keys, L1, L2, T);
+partition3_n(I, K, Ts, [Key | Keys], L1, L2, T) when K == Key ->
+ partition3_n(I, Ts, Key, Keys, [T | L1], L2);
+partition3_n(I, _K, Ts, [Key | Keys], L1, L2, T) ->
+ partition3_n(I, Ts, Key, Keys, L1, [T | L2]);
+partition3_n(I, _K, Ts, _Keys, L1, L2, T) when I =:= 1 ->
+ [reverse(L1) | reverse(L2, [T | Ts])];
+partition3_n(_I, _K, Ts, _Keys, L1, L2, T) ->
+ [sort(L1) | sort([T | Ts ++ L2])].
+
+partition3([Key | Keys], Tuples) ->
+ partition3(Tuples, Key, Keys, [], []);
+partition3(_Keys, Tuples) ->
+ partition3_tail(Tuples, [], []).
+
+partition3([{K,E} | Ts], Key, Keys, L1, L2) when K < Key ->
+ partition3(Ts, Key, Keys, L1, [E | L2]);
+partition3([{K,E} | Ts], Key, Keys, L1, L2) when K == Key ->
+ partition3(Ts, Key, Keys, [E | L1], L2);
+partition3([{K,E} | Ts], _Key, Keys, L1, L2) ->
+ partition3(Ts, K, Keys, L1, L2, E);
+partition3(_Ts, _Key, _Keys, L1, L2) ->
+ [L1 | L2].
+
+partition3(Ts, K, [Key | Keys], L1, L2, E) when K > Key ->
+ partition3(Ts, K, Keys, L1, L2, E);
+partition3(Ts, K, [Key | Keys], L1, L2, E) when K == Key ->
+ partition3(Ts, Key, Keys, [E | L1], L2);
+partition3(Ts, _K, [Key | Keys], L1, L2, E) ->
+ partition3(Ts, Key, Keys, L1, [E | L2]);
+partition3(Ts, _K, _Keys, L1, L2, E) ->
+ partition3_tail(Ts, L1, [E | L2]).
+
+partition3_tail([{_K,E} | Ts], L1, L2) ->
+ partition3_tail(Ts, L1, [E | L2]);
+partition3_tail(_Ts, L1, L2) ->
+ [L1 | L2].
+
+replace([E | Es], F, L) ->
+ replace(Es, F, [F(E) | L]);
+replace(_, _F, L) ->
+ sort(L).
+
+mul_relprod([T | Ts], I, R) when ?IS_SET(T) ->
+ P = raise_element(R, I),
+ F = relative_product1(P, T),
+ [F | mul_relprod(Ts, I+1, R)];
+mul_relprod([], _I, _R) ->
+ [].
+
+raise_element(R, I) ->
+ L = sort(I =/= 1, rearr(?LIST(R), I, [])),
+ Type = ?TYPE(R),
+ ?SET(L, ?BINREL(?REL_TYPE(I, Type), Type)).
+
+rearr([E | Es], I, L) ->
+ rearr(Es, I, [{element(I, E), E} | L]);
+rearr([], _I, L) ->
+ L.
+
+join_element(E1, E2) ->
+ [_ | L2] = tuple_to_list(E2),
+ list_to_tuple(tuple_to_list(E1) ++ L2).
+
+join_element(E1, E2, I2) ->
+ tuple_to_list(E1) ++ join_element2(tuple_to_list(E2), 1, I2).
+
+join_element2([B | Bs], C, I2) when C =/= I2 ->
+ [B | join_element2(Bs, C+1, I2)];
+join_element2([_ | Bs], _C, _I2) ->
+ Bs.
+
+family2rel([{X,S} | F], L) ->
+ fam2rel(F, L, X, S);
+family2rel([], L) ->
+ reverse(L).
+
+fam2rel(F, L, X, [Y | Ys]) ->
+ fam2rel(F, [{X,Y} | L], X, Ys);
+fam2rel(F, L, _X, _) ->
+ family2rel(F, L).
+
+fam_spec([{_,S}=E | F], Fun, Type, L) ->
+ case Fun(?SET(S, Type)) of
+ true ->
+ fam_spec(F, Fun, Type, [E | L]);
+ false ->
+ fam_spec(F, Fun, Type, L);
+ _ ->
+ badarg
+ end;
+fam_spec([], _Fun, _Type, L) ->
+ reverse(L).
+
+fam_specification([{_,S}=E | F], Fun, L) ->
+ case Fun(S) of
+ true ->
+ fam_specification(F, Fun, [E | L]);
+ false ->
+ fam_specification(F, Fun, L);
+ _ ->
+ badarg
+ end;
+fam_specification([], _Fun, L) ->
+ reverse(L).
+
+un_of_fam([{_X,S} | F], L) ->
+ un_of_fam(F, [S | L]);
+un_of_fam([], L) ->
+ lunion(sort(L)).
+
+int_of_fam([{_,S} | F]) ->
+ int_of_fam(F, [S]);
+int_of_fam([]) ->
+ badarg.
+
+int_of_fam([{_,S} | F], L) ->
+ int_of_fam(F, [S | L]);
+int_of_fam([], [L | Ls]) ->
+ lintersection(Ls, L).
+
+fam_un([{X,S} | F], L) ->
+ fam_un(F, [{X, lunion(S)} | L]);
+fam_un([], L) ->
+ reverse(L).
+
+fam_int([{X, [S | Ss]} | F], L) ->
+ fam_int(F, [{X, lintersection(Ss, S)} | L]);
+fam_int([{_X,[]} | _F], _L) ->
+ badarg;
+fam_int([], L) ->
+ reverse(L).
+
+fam_dom([{X,S} | F], L) ->
+ fam_dom(F, [{X, dom(S)} | L]);
+fam_dom([], L) ->
+ reverse(L).
+
+fam_ran([{X,S} | F], L) ->
+ fam_ran(F, [{X, ran(S, [])} | L]);
+fam_ran([], L) ->
+ reverse(L).
+
+fam_union(F1 = [{A,_AS} | _AL], [B1={B,_BS} | BL], L) when A > B ->
+ fam_union(F1, BL, [B1 | L]);
+fam_union([{A,AS} | AL], [{B,BS} | BL], L) when A == B ->
+ fam_union(AL, BL, [{A, umerge(AS, BS)} | L]);
+fam_union([A1 | AL], F2, L) ->
+ fam_union(AL, F2, [A1 | L]);
+fam_union(_, F2, L) ->
+ reverse(L, F2).
+
+fam_intersect(F1 = [{A,_AS} | _AL], [{B,_BS} | BL], L) when A > B ->
+ fam_intersect(F1, BL, L);
+fam_intersect([{A,AS} | AL], [{B,BS} | BL], L) when A == B ->
+ fam_intersect(AL, BL, [{A, intersection(AS, BS, [])} | L]);
+fam_intersect([_A1 | AL], F2, L) ->
+ fam_intersect(AL, F2, L);
+fam_intersect(_, _, L) ->
+ reverse(L).
+
+fam_difference(F1 = [{A,_AS} | _AL], [{B,_BS} | BL], L) when A > B ->
+ fam_difference(F1, BL, L);
+fam_difference([{A,AS} | AL], [{B,BS} | BL], L) when A == B ->
+ fam_difference(AL, BL, [{A, difference(AS, BS, [])} | L]);
+fam_difference([A1 | AL], F2, L) ->
+ fam_difference(AL, F2, [A1 | L]);
+fam_difference(F1, _, L) ->
+ reverse(L, F1).
+
+check_function([{X,_} | XL], R) ->
+ check_function(X, XL, R);
+check_function([], R) ->
+ R.
+
+check_function(X0, [{X,_} | XL], R) when X0 /= X ->
+ check_function(X, XL, R);
+check_function(X0, [{X,_} | _XL], _R) when X0 == X ->
+ bad_function;
+check_function(_X0, [], R) ->
+ R.
+
+fam_partition_n(I, [E | Ts]) ->
+ fam_partition_n(I, Ts, element(I, E), [E], []);
+fam_partition_n(_I, []) ->
+ [].
+
+fam_partition_n(I, [E | Ts], K, Es, P) ->
+ case {element(I, E), Es} of
+ {K1, _} when K == K1 ->
+ fam_partition_n(I, Ts, K, [E | Es], P);
+ {K1, [_]} -> % optimization
+ fam_partition_n(I, Ts, K1, [E], [{K,Es} | P]);
+ {K1, _} ->
+ fam_partition_n(I, Ts, K1, [E], [{K,reverse(Es)} | P])
+ end;
+fam_partition_n(_I, [], K, [_] = Es, P) -> % optimization
+ reverse(P, [{K,Es}]);
+fam_partition_n(_I, [], K, Es, P) ->
+ reverse(P, [{K,reverse(Es)}]).
+
+fam_partition([{K,Vs} | Ts], Sort) ->
+ fam_partition(Ts, K, [Vs], [], Sort);
+fam_partition([], _Sort) ->
+ [].
+
+fam_partition([{K1,V} | Ts], K, Vs, P, S) when K1 == K ->
+ fam_partition(Ts, K, [V | Vs], P, S);
+fam_partition([{K1,V} | Ts], K, [_] = Vs, P, S) -> % optimization
+ fam_partition(Ts, K1, [V], [{K, Vs} | P], S);
+fam_partition([{K1,V} | Ts], K, Vs, P, S) ->
+ fam_partition(Ts, K1, [V], [{K, sort(S, Vs)} | P], S);
+fam_partition([], K, [_] = Vs, P, _S) -> % optimization
+ [{K, Vs} | P];
+fam_partition([], K, Vs, P, S) ->
+ [{K, sort(S, Vs)} | P].
+
+fam_proj([{X,S} | F], Fun, Type, NType, L) ->
+ case setfun(S, Fun, Type, NType) of
+ {SD, ST} -> fam_proj(F, Fun, Type, ST, [{X, SD} | L]);
+ Bad -> Bad
+ end;
+fam_proj([], _Fun, _Type, NType, L) ->
+ {reverse(L), NType}.
+
+setfun(T, Fun, Type, NType) ->
+ case Fun(term2set(T, Type)) of
+ NS when ?IS_SET(NS) ->
+ case unify_types(NType, ?SET_OF(?TYPE(NS))) of
+ [] -> type_mismatch;
+ NT -> {?LIST(NS), NT}
+ end;
+ NS when ?IS_ORDSET(NS) ->
+ case unify_types(NType, NT = ?ORDTYPE(NS)) of
+ [] -> type_mismatch;
+ NT -> {?ORDDATA(NS), NT}
+ end;
+ _ ->
+ badarg
+ end.
+
+%% Inlined.
+term2set(L, Type) when is_list(L) ->
+ ?SET(L, Type);
+term2set(T, Type) ->
+ ?ORDSET(T, Type).
+
+fam2digraph(F, G) ->
+ Fun = fun({From, ToL}) ->
+ digraph:add_vertex(G, From),
+ Fun2 = fun(To) ->
+ digraph:add_vertex(G, To),
+ case digraph:add_edge(G, From, To) of
+ {error, {bad_edge, _}} ->
+ throw({error, cyclic});
+ _ ->
+ true
+ end
+ end,
+ foreach(Fun2, ToL)
+ end,
+ foreach(Fun, to_external(F)),
+ G.
+
+digraph_family(G) ->
+ Vs = sort(digraph:vertices(G)),
+ digraph_fam(Vs, Vs, G, []).
+
+digraph_fam([V | Vs], V0, G, L) when V /= V0 ->
+ Ns = sort(digraph:out_neighbours(G, V)),
+ digraph_fam(Vs, V, G, [{V,Ns} | L]);
+digraph_fam([], _V0, _G, L) ->
+ reverse(L).
+
+%% -> bool()
+check_fun(T, F, FunT) ->
+ true = is_type(FunT),
+ {NT, _MaxI} = number_tuples(T, 1),
+ L = flatten(tuple2list(F(NT))),
+ has_hole(L, 1).
+
+number_tuples(T, N) when is_tuple(T) ->
+ {L, NN} = mapfoldl(fun number_tuples/2, N, tuple_to_list(T)),
+ {list_to_tuple(L), NN};
+number_tuples(_, N) ->
+ {N, N+1}.
+
+tuple2list(T) when is_tuple(T) ->
+ map(fun tuple2list/1, tuple_to_list(T));
+tuple2list(C) ->
+ [C].
+
+has_hole([I | Is], I0) when I =< I0 -> has_hole(Is, erlang:max(I+1, I0));
+has_hole(Is, _I) -> Is =/= [].
+
+%% Optimization. Same as check_fun/3, but for integers.
+check_for_sort(T, _I) when T =:= ?ANYTYPE ->
+ empty;
+check_for_sort(T, I) when ?IS_RELATION(T), I =< ?REL_ARITY(T), I >= 1 ->
+ I > 1;
+check_for_sort(_T, _I) ->
+ error.
+
+inverse_substitution(L, Fun, Sort) ->
+ %% One easily sees that the inverse of the tuples created by
+ %% applying Fun need to be sorted iff the tuples created by Fun
+ %% need to be sorted.
+ sort(Sort, fun_rearr(L, Fun, [])).
+
+fun_rearr([E | Es], Fun, L) ->
+ fun_rearr(Es, Fun, [{Fun(E), E} | L]);
+fun_rearr([], _Fun, L) ->
+ L.
+
+sets_to_list(Ss) ->
+ map(fun(S) when ?IS_SET(S) -> ?LIST(S) end, Ss).
+
+types([], L) ->
+ list_to_tuple(reverse(L));
+types([S | _Ss], _L) when ?TYPE(S) =:= ?ANYTYPE ->
+ ?ANYTYPE;
+types([S | Ss], L) ->
+ types(Ss, [?TYPE(S) | L]).
+
+%% Inlined.
+unify_types(T, T) -> T;
+unify_types(Type1, Type2) ->
+ catch unify_types1(Type1, Type2).
+
+unify_types1(Atom, Atom) when ?IS_ATOM_TYPE(Atom) ->
+ Atom;
+unify_types1(?ANYTYPE, Type) ->
+ Type;
+unify_types1(Type, ?ANYTYPE) ->
+ Type;
+unify_types1(?SET_OF(Type1), ?SET_OF(Type2)) ->
+ [unify_types1(Type1, Type2)];
+unify_types1(T1, T2) when tuple_size(T1) =:= tuple_size(T2) ->
+ unify_typesl(tuple_size(T1), T1, T2, []);
+unify_types1(_T1, _T2) ->
+ throw([]).
+
+unify_typesl(0, _T1, _T2, L) ->
+ list_to_tuple(L);
+unify_typesl(N, T1, T2, L) ->
+ T = unify_types1(?REL_TYPE(N, T1), ?REL_TYPE(N, T2)),
+ unify_typesl(N-1, T1, T2, [T | L]).
+
+%% inlined.
+match_types(T, T) -> true;
+match_types(Type1, Type2) -> match_types1(Type1, Type2).
+
+match_types1(Atom, Atom) when ?IS_ATOM_TYPE(Atom) ->
+ true;
+match_types1(?ANYTYPE, _) ->
+ true;
+match_types1(_, ?ANYTYPE) ->
+ true;
+match_types1(?SET_OF(Type1), ?SET_OF(Type2)) ->
+ match_types1(Type1, Type2);
+match_types1(T1, T2) when tuple_size(T1) =:= tuple_size(T2) ->
+ match_typesl(tuple_size(T1), T1, T2);
+match_types1(_T1, _T2) ->
+ false.
+
+match_typesl(0, _T1, _T2) ->
+ true;
+match_typesl(N, T1, T2) ->
+ case match_types1(?REL_TYPE(N, T1), ?REL_TYPE(N, T2)) of
+ true -> match_typesl(N-1, T1, T2);
+ false -> false
+ end.
+
+sort(true, L) ->
+ sort(L);
+sort(false, L) ->
+ reverse(L).
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
new file mode 100644
index 0000000000..3e52c48e42
--- /dev/null
+++ b/lib/stdlib/src/stdlib.app.src
@@ -0,0 +1,105 @@
+%% This is an -*- erlang -*- file.
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+{application, stdlib,
+ [{description, "ERTS CXC 138 10"},
+ {vsn, "%VSN%"},
+ {modules, [array,
+ base64,
+ beam_lib,
+ c,
+ calendar,
+ dets,
+ dets_server,
+ dets_sup,
+ dets_utils,
+ dets_v8,
+ dets_v9,
+ dict,
+ digraph,
+ digraph_utils,
+ edlin,
+ edlin_expand,
+ epp,
+ eval_bits,
+ erl_bits,
+ erl_compile,
+ erl_eval,
+ erl_expand_records,
+ erl_internal,
+ erl_lint,
+ erl_parse,
+ erl_posix_msg,
+ erl_pp,
+ erl_scan,
+ erl_tar,
+ error_logger_file_h,
+ error_logger_tty_h,
+ escript,
+ ets,
+ file_sorter,
+ filelib,
+ filename,
+ gb_trees,
+ gb_sets,
+ gen,
+ gen_event,
+ gen_fsm,
+ gen_server,
+ io,
+ io_lib,
+ io_lib_format,
+ io_lib_fread,
+ io_lib_pretty,
+ lib,
+ lists,
+ log_mf_h,
+ math,
+ ms_transform,
+ orddict,
+ ordsets,
+ otp_internal,
+ pg,
+ pool,
+ proc_lib,
+ proplists,
+ qlc,
+ qlc_pt,
+ queue,
+ random,
+ re,
+ regexp,
+ sets,
+ shell,
+ shell_default,
+ slave,
+ sofs,
+ string,
+ supervisor,
+ supervisor_bridge,
+ sys,
+ timer,
+ unicode,
+ win32reg,
+ zip]},
+ {registered,[timer_server,rsh_starter,take_over_monitor,pool_master,
+ dets]},
+ {applications, [kernel]},
+ {env, []}]}.
+
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
new file mode 100644
index 0000000000..54a63833e6
--- /dev/null
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -0,0 +1 @@
+{"%VSN%",[],[]}.
diff --git a/lib/stdlib/src/string.erl b/lib/stdlib/src/string.erl
new file mode 100644
index 0000000000..6636a03f06
--- /dev/null
+++ b/lib/stdlib/src/string.erl
@@ -0,0 +1,394 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(string).
+
+-export([len/1,equal/2,concat/2,chr/2,rchr/2,str/2,rstr/2,
+ span/2,cspan/2,substr/2,substr/3,tokens/2,chars/2,chars/3]).
+-export([copies/2,words/1,words/2,strip/1,strip/2,strip/3,
+ sub_word/2,sub_word/3,left/2,left/3,right/2,right/3,
+ sub_string/2,sub_string/3,centre/2,centre/3, join/2]).
+-export([to_upper/1, to_lower/1]).
+
+-import(lists,[reverse/1,member/2]).
+
+%%---------------------------------------------------------------------------
+
+-type direction() :: 'left' | 'right' | 'both'.
+
+%%---------------------------------------------------------------------------
+
+%% Robert's bit
+
+%% len(String)
+%% Return the length of a string.
+
+-spec len(string()) -> non_neg_integer().
+
+len(S) -> length(S).
+
+%% equal(String1, String2)
+%% Test if 2 strings are equal.
+
+-spec equal(string(), string()) -> boolean().
+
+equal(S, S) -> true;
+equal(_, _) -> false.
+
+%% concat(String1, String2)
+%% Concatenate 2 strings.
+
+-spec concat(string(), string()) -> string().
+
+concat(S1, S2) -> S1 ++ S2.
+
+%% chr(String, Char)
+%% rchr(String, Char)
+%% Return the first/last index of the character in a string.
+
+-spec chr(string(), char()) -> non_neg_integer().
+
+chr(S, C) when is_integer(C) -> chr(S, C, 1).
+
+chr([C|_Cs], C, I) -> I;
+chr([_|Cs], C, I) -> chr(Cs, C, I+1);
+chr([], _C, _I) -> 0.
+
+-spec rchr(string(), char()) -> non_neg_integer().
+
+rchr(S, C) when is_integer(C) -> rchr(S, C, 1, 0).
+
+rchr([C|Cs], C, I, _L) -> %Found one, now find next!
+ rchr(Cs, C, I+1, I);
+rchr([_|Cs], C, I, L) ->
+ rchr(Cs, C, I+1, L);
+rchr([], _C, _I, L) -> L.
+
+%% str(String, SubString)
+%% rstr(String, SubString)
+%% index(String, SubString)
+%% Return the first/last index of the sub-string in a string.
+%% index/2 is kept for backwards compatibility.
+
+-spec str(string(), string()) -> non_neg_integer().
+
+str(S, Sub) when is_list(Sub) -> str(S, Sub, 1).
+
+str([C|S], [C|Sub], I) ->
+ case prefix(Sub, S) of
+ true -> I;
+ false -> str(S, [C|Sub], I+1)
+ end;
+str([_|S], Sub, I) -> str(S, Sub, I+1);
+str([], _Sub, _I) -> 0.
+
+-spec rstr(string(), string()) -> non_neg_integer().
+
+rstr(S, Sub) when is_list(Sub) -> rstr(S, Sub, 1, 0).
+
+rstr([C|S], [C|Sub], I, L) ->
+ case prefix(Sub, S) of
+ true -> rstr(S, [C|Sub], I+1, I);
+ false -> rstr(S, [C|Sub], I+1, L)
+ end;
+rstr([_|S], Sub, I, L) -> rstr(S, Sub, I+1, L);
+rstr([], _Sub, _I, L) -> L.
+
+prefix([C|Pre], [C|String]) -> prefix(Pre, String);
+prefix([], String) when is_list(String) -> true;
+prefix(Pre, String) when is_list(Pre), is_list(String) -> false.
+
+%% span(String, Chars) -> Length.
+%% cspan(String, Chars) -> Length.
+
+-spec span(string(), string()) -> non_neg_integer().
+
+span(S, Cs) when is_list(Cs) -> span(S, Cs, 0).
+
+span([C|S], Cs, I) ->
+ case member(C, Cs) of
+ true -> span(S, Cs, I+1);
+ false -> I
+ end;
+span([], _Cs, I) -> I.
+
+-spec cspan(string(), string()) -> non_neg_integer().
+
+cspan(S, Cs) when is_list(Cs) -> cspan(S, Cs, 0).
+
+cspan([C|S], Cs, I) ->
+ case member(C, Cs) of
+ true -> I;
+ false -> cspan(S, Cs, I+1)
+ end;
+cspan([], _Cs, I) -> I.
+
+%% substr(String, Start)
+%% substr(String, Start, Length)
+%% Extract a sub-string from String.
+
+-spec substr(string(), pos_integer()) -> string().
+
+substr(String, 1) when is_list(String) ->
+ String;
+substr(String, S) when is_integer(S), S > 1 ->
+ substr2(String, S).
+
+-spec substr(string(), pos_integer(), non_neg_integer()) -> string().
+
+substr(String, S, L) when is_integer(S), S >= 1, is_integer(L), L >= 0 ->
+ substr1(substr2(String, S), L).
+
+substr1([C|String], L) when L > 0 -> [C|substr1(String, L-1)];
+substr1(String, _L) when is_list(String) -> []. %Be nice!
+
+substr2(String, 1) when is_list(String) -> String;
+substr2([_|String], S) -> substr2(String, S-1).
+
+%% tokens(String, Seperators).
+%% Return a list of tokens seperated by characters in Seperators.
+
+-spec tokens(string(), string()) -> [[char(),...]].
+
+tokens(S, Seps) ->
+ tokens1(S, Seps, []).
+
+tokens1([C|S], Seps, Toks) ->
+ case member(C, Seps) of
+ true -> tokens1(S, Seps, Toks);
+ false -> tokens2(S, Seps, Toks, [C])
+ end;
+tokens1([], _Seps, Toks) ->
+ reverse(Toks).
+
+tokens2([C|S], Seps, Toks, Cs) ->
+ case member(C, Seps) of
+ true -> tokens1(S, Seps, [reverse(Cs)|Toks]);
+ false -> tokens2(S, Seps, Toks, [C|Cs])
+ end;
+tokens2([], _Seps, Toks, Cs) ->
+ reverse([reverse(Cs)|Toks]).
+
+-spec chars(char(), non_neg_integer()) -> string().
+
+chars(C, N) -> chars(C, N, []).
+
+-spec chars(char(), non_neg_integer(), string()) -> string().
+
+chars(C, N, Tail) when N > 0 ->
+ chars(C, N-1, [C|Tail]);
+chars(C, 0, Tail) when is_integer(C) ->
+ Tail.
+
+%% Torbj�rn's bit.
+
+%%% COPIES %%%
+
+-spec copies(string(), non_neg_integer()) -> string().
+
+copies(CharList, Num) when is_list(CharList), Num >= 0 ->
+ copies(CharList, Num, []).
+
+copies(_CharList, 0, R) ->
+ R;
+copies(CharList, Num, R) ->
+ copies(CharList, Num-1, CharList++R).
+
+%%% WORDS %%%
+
+-spec words(string()) -> pos_integer().
+
+words(String) -> words(String, $\s).
+
+-spec words(string(), char()) -> pos_integer().
+
+words(String, Char) when is_integer(Char) ->
+ w_count(strip(String, both, Char), Char, 0).
+
+w_count([], _, Num) -> Num+1;
+w_count([H|T], H, Num) -> w_count(strip(T, left, H), H, Num+1);
+w_count([_H|T], Char, Num) -> w_count(T, Char, Num).
+
+%%% SUB_WORDS %%%
+
+-spec sub_word(string(), integer()) -> string().
+
+sub_word(String, Index) -> sub_word(String, Index, $\s).
+
+-spec sub_word(string(), integer(), char()) -> string().
+
+sub_word(String, Index, Char) when is_integer(Index), is_integer(Char) ->
+ case words(String, Char) of
+ Num when Num < Index ->
+ [];
+ _Num ->
+ s_word(strip(String, left, Char), Index, Char, 1, [])
+ end.
+
+s_word([], _, _, _,Res) -> reverse(Res);
+s_word([Char|_],Index,Char,Index,Res) -> reverse(Res);
+s_word([H|T],Index,Char,Index,Res) -> s_word(T,Index,Char,Index,[H|Res]);
+s_word([Char|T],Stop,Char,Index,Res) when Index < Stop ->
+ s_word(strip(T,left,Char),Stop,Char,Index+1,Res);
+s_word([_|T],Stop,Char,Index,Res) when Index < Stop ->
+ s_word(T,Stop,Char,Index,Res).
+
+%%% STRIP %%%
+
+-spec strip(string()) -> string().
+
+strip(String) -> strip(String, both).
+
+-spec strip(string(), direction()) -> string().
+
+strip(String, left) -> strip_left(String, $\s);
+strip(String, right) -> strip_right(String, $\s);
+strip(String, both) ->
+ strip_right(strip_left(String, $\s), $\s).
+
+-spec strip(string(), direction(), char()) -> string().
+
+strip(String, right, Char) -> strip_right(String, Char);
+strip(String, left, Char) -> strip_left(String, Char);
+strip(String, both, Char) ->
+ strip_right(strip_left(String, Char), Char).
+
+strip_left([Sc|S], Sc) ->
+ strip_left(S, Sc);
+strip_left([_|_]=S, Sc) when is_integer(Sc) -> S;
+strip_left([], Sc) when is_integer(Sc) -> [].
+
+strip_right([Sc|S], Sc) ->
+ case strip_right(S, Sc) of
+ [] -> [];
+ T -> [Sc|T]
+ end;
+strip_right([C|S], Sc) ->
+ [C|strip_right(S, Sc)];
+strip_right([], Sc) when is_integer(Sc) ->
+ [].
+
+%%% LEFT %%%
+
+-spec left(string(), non_neg_integer()) -> string().
+
+left(String, Len) when is_integer(Len) -> left(String, Len, $\s).
+
+-spec left(string(), non_neg_integer(), char()) -> string().
+
+left(String, Len, Char) when is_integer(Char) ->
+ Slen = length(String),
+ if
+ Slen > Len -> substr(String, 1, Len);
+ Slen < Len -> l_pad(String, Len-Slen, Char);
+ Slen =:= Len -> String
+ end.
+
+l_pad(String, Num, Char) -> String ++ chars(Char, Num).
+
+%%% RIGHT %%%
+
+-spec right(string(), non_neg_integer()) -> string().
+
+right(String, Len) when is_integer(Len) -> right(String, Len, $\s).
+
+-spec right(string(), non_neg_integer(), char()) -> string().
+
+right(String, Len, Char) when is_integer(Char) ->
+ Slen = length(String),
+ if
+ Slen > Len -> substr(String, Slen-Len+1);
+ Slen < Len -> r_pad(String, Len-Slen, Char);
+ Slen =:= Len -> String
+ end.
+
+r_pad(String, Num, Char) -> chars(Char, Num, String).
+
+%%% CENTRE %%%
+
+-spec centre(string(), non_neg_integer()) -> string().
+
+centre(String, Len) when is_integer(Len) -> centre(String, Len, $\s).
+
+-spec centre(string(), non_neg_integer(), char()) -> string().
+
+centre(String, 0, Char) when is_list(String), is_integer(Char) ->
+ []; % Strange cases to centre string
+centre(String, Len, Char) when is_integer(Char) ->
+ Slen = length(String),
+ if
+ Slen > Len -> substr(String, (Slen-Len) div 2 + 1, Len);
+ Slen < Len ->
+ N = (Len-Slen) div 2,
+ r_pad(l_pad(String, Len-(Slen+N), Char), N, Char);
+ Slen =:= Len -> String
+ end.
+
+%%% SUB_STRING %%%
+
+-spec sub_string(string(), pos_integer()) -> string().
+
+sub_string(String, Start) -> substr(String, Start).
+
+-spec sub_string(string(), pos_integer(), pos_integer()) -> string().
+
+sub_string(String, Start, Stop) -> substr(String, Start, Stop - Start + 1).
+
+%% ISO/IEC 8859-1 (latin1) letters are converted, others are ignored
+%%
+
+to_lower_char(C) when is_integer(C), $A =< C, C =< $Z ->
+ C + 32;
+to_lower_char(C) when is_integer(C), 16#C0 =< C, C =< 16#D6 ->
+ C + 32;
+to_lower_char(C) when is_integer(C), 16#D8 =< C, C =< 16#DE ->
+ C + 32;
+to_lower_char(C) ->
+ C.
+
+to_upper_char(C) when is_integer(C), $a =< C, C =< $z ->
+ C - 32;
+to_upper_char(C) when is_integer(C), 16#E0 =< C, C =< 16#F6 ->
+ C - 32;
+to_upper_char(C) when is_integer(C), 16#F8 =< C, C =< 16#FE ->
+ C - 32;
+to_upper_char(C) ->
+ C.
+
+-spec to_lower(string()) -> string()
+ ; (char()) -> char().
+
+to_lower(S) when is_list(S) ->
+ [to_lower_char(C) || C <- S];
+to_lower(C) when is_integer(C) ->
+ to_lower_char(C).
+
+-spec to_upper(string()) -> string()
+ ; (char()) -> char().
+
+to_upper(S) when is_list(S) ->
+ [to_upper_char(C) || C <- S];
+to_upper(C) when is_integer(C) ->
+ to_upper_char(C).
+
+-spec join([string()], string()) -> string().
+
+join([], Sep) when is_list(Sep) ->
+ [];
+join([H|T], Sep) ->
+ H ++ lists:append([Sep ++ X || X <- T]).
diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl
new file mode 100644
index 0000000000..fb1303d1eb
--- /dev/null
+++ b/lib/stdlib/src/supervisor.erl
@@ -0,0 +1,889 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(supervisor).
+
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/2,start_link/3,
+ start_child/2, restart_child/2,
+ delete_child/2, terminate_child/2,
+ which_children/1,
+ check_childspecs/1]).
+
+-export([behaviour_info/1]).
+
+%% Internal exports
+-export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]).
+-export([handle_cast/2]).
+
+-define(DICT, dict).
+
+-record(state, {name,
+ strategy,
+ children = [],
+ dynamics = ?DICT:new(),
+ intensity,
+ period,
+ restarts = [],
+ module,
+ args}).
+
+-record(child, {pid = undefined, % pid is undefined when child is not running
+ name,
+ mfa,
+ restart_type,
+ shutdown,
+ child_type,
+ modules = []}).
+
+-define(is_simple(State), State#state.strategy =:= simple_one_for_one).
+
+behaviour_info(callbacks) ->
+ [{init,1}];
+behaviour_info(_Other) ->
+ undefined.
+
+%%% ---------------------------------------------------
+%%% This is a general process supervisor built upon gen_server.erl.
+%%% Servers/processes should/could also be built using gen_server.erl.
+%%% SupName = {local, atom()} | {global, atom()}.
+%%% ---------------------------------------------------
+start_link(Mod, Args) ->
+ gen_server:start_link(supervisor, {self, Mod, Args}, []).
+
+start_link(SupName, Mod, Args) ->
+ gen_server:start_link(SupName, supervisor, {SupName, Mod, Args}, []).
+
+%%% ---------------------------------------------------
+%%% Interface functions.
+%%% ---------------------------------------------------
+start_child(Supervisor, ChildSpec) ->
+ call(Supervisor, {start_child, ChildSpec}).
+
+restart_child(Supervisor, Name) ->
+ call(Supervisor, {restart_child, Name}).
+
+delete_child(Supervisor, Name) ->
+ call(Supervisor, {delete_child, Name}).
+
+%%-----------------------------------------------------------------
+%% Func: terminate_child/2
+%% Returns: ok | {error, Reason}
+%% Note that the child is *always* terminated in some
+%% way (maybe killed).
+%%-----------------------------------------------------------------
+terminate_child(Supervisor, Name) ->
+ call(Supervisor, {terminate_child, Name}).
+
+which_children(Supervisor) ->
+ call(Supervisor, which_children).
+
+call(Supervisor, Req) ->
+ gen_server:call(Supervisor, Req, infinity).
+
+check_childspecs(ChildSpecs) when is_list(ChildSpecs) ->
+ case check_startspec(ChildSpecs) of
+ {ok, _} -> ok;
+ Error -> {error, Error}
+ end;
+check_childspecs(X) -> {error, {badarg, X}}.
+
+%%% ---------------------------------------------------
+%%%
+%%% Initialize the supervisor.
+%%%
+%%% ---------------------------------------------------
+init({SupName, Mod, Args}) ->
+ process_flag(trap_exit, true),
+ case Mod:init(Args) of
+ {ok, {SupFlags, StartSpec}} ->
+ case init_state(SupName, SupFlags, Mod, Args) of
+ {ok, State} when ?is_simple(State) ->
+ init_dynamic(State, StartSpec);
+ {ok, State} ->
+ init_children(State, StartSpec);
+ Error ->
+ {stop, {supervisor_data, Error}}
+ end;
+ ignore ->
+ ignore;
+ Error ->
+ {stop, {bad_return, {Mod, init, Error}}}
+ end.
+
+init_children(State, StartSpec) ->
+ SupName = State#state.name,
+ case check_startspec(StartSpec) of
+ {ok, Children} ->
+ case start_children(Children, SupName) of
+ {ok, NChildren} ->
+ {ok, State#state{children = NChildren}};
+ {error, NChildren} ->
+ terminate_children(NChildren, SupName),
+ {stop, shutdown}
+ end;
+ Error ->
+ {stop, {start_spec, Error}}
+ end.
+
+init_dynamic(State, [StartSpec]) ->
+ case check_startspec([StartSpec]) of
+ {ok, Children} ->
+ {ok, State#state{children = Children}};
+ Error ->
+ {stop, {start_spec, Error}}
+ end;
+init_dynamic(_State, StartSpec) ->
+ {stop, {bad_start_spec, StartSpec}}.
+
+%%-----------------------------------------------------------------
+%% Func: start_children/2
+%% Args: Children = [#child] in start order
+%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod}
+%% Purpose: Start all children. The new list contains #child's
+%% with pids.
+%% Returns: {ok, NChildren} | {error, NChildren}
+%% NChildren = [#child] in termination order (reversed
+%% start order)
+%%-----------------------------------------------------------------
+start_children(Children, SupName) -> start_children(Children, [], SupName).
+
+start_children([Child|Chs], NChildren, SupName) ->
+ case do_start_child(SupName, Child) of
+ {ok, Pid} ->
+ start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName);
+ {ok, Pid, _Extra} ->
+ start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName);
+ {error, Reason} ->
+ report_error(start_error, Reason, Child, SupName),
+ {error, lists:reverse(Chs) ++ [Child | NChildren]}
+ end;
+start_children([], NChildren, _SupName) ->
+ {ok, NChildren}.
+
+do_start_child(SupName, Child) ->
+ #child{mfa = {M, F, A}} = Child,
+ case catch apply(M, F, A) of
+ {ok, Pid} when is_pid(Pid) ->
+ NChild = Child#child{pid = Pid},
+ report_progress(NChild, SupName),
+ {ok, Pid};
+ {ok, Pid, Extra} when is_pid(Pid) ->
+ NChild = Child#child{pid = Pid},
+ report_progress(NChild, SupName),
+ {ok, Pid, Extra};
+ ignore ->
+ {ok, undefined};
+ {error, What} -> {error, What};
+ What -> {error, What}
+ end.
+
+do_start_child_i(M, F, A) ->
+ case catch apply(M, F, A) of
+ {ok, Pid} when is_pid(Pid) ->
+ {ok, Pid};
+ {ok, Pid, Extra} when is_pid(Pid) ->
+ {ok, Pid, Extra};
+ ignore ->
+ {ok, undefined};
+ {error, Error} ->
+ {error, Error};
+ What ->
+ {error, What}
+ end.
+
+
+%%% ---------------------------------------------------
+%%%
+%%% Callback functions.
+%%%
+%%% ---------------------------------------------------
+handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) ->
+ #child{mfa = {M, F, A}} = hd(State#state.children),
+ Args = A ++ EArgs,
+ case do_start_child_i(M, F, Args) of
+ {ok, Pid} ->
+ NState = State#state{dynamics =
+ ?DICT:store(Pid, Args, State#state.dynamics)},
+ {reply, {ok, Pid}, NState};
+ {ok, Pid, Extra} ->
+ NState = State#state{dynamics =
+ ?DICT:store(Pid, Args, State#state.dynamics)},
+ {reply, {ok, Pid, Extra}, NState};
+ What ->
+ {reply, What, State}
+ end;
+
+%%% The requests terminate_child, delete_child and restart_child are
+%%% invalid for simple_one_for_one supervisors.
+handle_call({_Req, _Data}, _From, State) when ?is_simple(State) ->
+ {reply, {error, simple_one_for_one}, State};
+
+handle_call({start_child, ChildSpec}, _From, State) ->
+ case check_childspec(ChildSpec) of
+ {ok, Child} ->
+ {Resp, NState} = handle_start_child(Child, State),
+ {reply, Resp, NState};
+ What ->
+ {reply, {error, What}, State}
+ end;
+
+handle_call({restart_child, Name}, _From, State) ->
+ case get_child(Name, State) of
+ {value, Child} when Child#child.pid =:= undefined ->
+ case do_start_child(State#state.name, Child) of
+ {ok, Pid} ->
+ NState = replace_child(Child#child{pid = Pid}, State),
+ {reply, {ok, Pid}, NState};
+ {ok, Pid, Extra} ->
+ NState = replace_child(Child#child{pid = Pid}, State),
+ {reply, {ok, Pid, Extra}, NState};
+ Error ->
+ {reply, Error, State}
+ end;
+ {value, _} ->
+ {reply, {error, running}, State};
+ _ ->
+ {reply, {error, not_found}, State}
+ end;
+
+handle_call({delete_child, Name}, _From, State) ->
+ case get_child(Name, State) of
+ {value, Child} when Child#child.pid =:= undefined ->
+ NState = remove_child(Child, State),
+ {reply, ok, NState};
+ {value, _} ->
+ {reply, {error, running}, State};
+ _ ->
+ {reply, {error, not_found}, State}
+ end;
+
+handle_call({terminate_child, Name}, _From, State) ->
+ case get_child(Name, State) of
+ {value, Child} ->
+ NChild = do_terminate(Child, State#state.name),
+ {reply, ok, replace_child(NChild, State)};
+ _ ->
+ {reply, {error, not_found}, State}
+ end;
+
+handle_call(which_children, _From, State) when ?is_simple(State) ->
+ [#child{child_type = CT, modules = Mods}] = State#state.children,
+ Reply = lists:map(fun({Pid, _}) -> {undefined, Pid, CT, Mods} end,
+ ?DICT:to_list(State#state.dynamics)),
+ {reply, Reply, State};
+
+handle_call(which_children, _From, State) ->
+ Resp =
+ lists:map(fun(#child{pid = Pid, name = Name,
+ child_type = ChildType, modules = Mods}) ->
+ {Name, Pid, ChildType, Mods}
+ end,
+ State#state.children),
+ {reply, Resp, State}.
+
+
+%%% Hopefully cause a function-clause as there is no API function
+%%% that utilizes cast.
+handle_cast(null, State) ->
+ error_logger:error_msg("ERROR: Supervisor received cast-message 'null'~n",
+ []),
+
+ {noreply, State}.
+
+%%
+%% Take care of terminated children.
+%%
+handle_info({'EXIT', Pid, Reason}, State) ->
+ case restart_child(Pid, Reason, State) of
+ {ok, State1} ->
+ {noreply, State1};
+ {shutdown, State1} ->
+ {stop, shutdown, State1}
+ end;
+
+handle_info(Msg, State) ->
+ error_logger:error_msg("Supervisor received unexpected message: ~p~n",
+ [Msg]),
+ {noreply, State}.
+%%
+%% Terminate this server.
+%%
+terminate(_Reason, State) ->
+ terminate_children(State#state.children, State#state.name),
+ ok.
+
+%%
+%% Change code for the supervisor.
+%% Call the new call-back module and fetch the new start specification.
+%% Combine the new spec. with the old. If the new start spec. is
+%% not valid the code change will not succeed.
+%% Use the old Args as argument to Module:init/1.
+%% NOTE: This requires that the init function of the call-back module
+%% does not have any side effects.
+%%
+code_change(_, State, _) ->
+ case (State#state.module):init(State#state.args) of
+ {ok, {SupFlags, StartSpec}} ->
+ case catch check_flags(SupFlags) of
+ ok ->
+ {Strategy, MaxIntensity, Period} = SupFlags,
+ update_childspec(State#state{strategy = Strategy,
+ intensity = MaxIntensity,
+ period = Period},
+ StartSpec);
+ Error ->
+ {error, Error}
+ end;
+ ignore ->
+ {ok, State};
+ Error ->
+ Error
+ end.
+
+check_flags({Strategy, MaxIntensity, Period}) ->
+ validStrategy(Strategy),
+ validIntensity(MaxIntensity),
+ validPeriod(Period),
+ ok;
+check_flags(What) ->
+ {bad_flags, What}.
+
+update_childspec(State, StartSpec) when ?is_simple(State) ->
+ case check_startspec(StartSpec) of
+ {ok, [Child]} ->
+ {ok, State#state{children = [Child]}};
+ Error ->
+ {error, Error}
+ end;
+
+update_childspec(State, StartSpec) ->
+ case check_startspec(StartSpec) of
+ {ok, Children} ->
+ OldC = State#state.children, % In reverse start order !
+ NewC = update_childspec1(OldC, Children, []),
+ {ok, State#state{children = NewC}};
+ Error ->
+ {error, Error}
+ end.
+
+update_childspec1([Child|OldC], Children, KeepOld) ->
+ case update_chsp(Child, Children) of
+ {ok,NewChildren} ->
+ update_childspec1(OldC, NewChildren, KeepOld);
+ false ->
+ update_childspec1(OldC, Children, [Child|KeepOld])
+ end;
+update_childspec1([], Children, KeepOld) ->
+ % Return them in (keeped) reverse start order.
+ lists:reverse(Children ++ KeepOld).
+
+update_chsp(OldCh, Children) ->
+ case lists:map(fun(Ch) when OldCh#child.name =:= Ch#child.name ->
+ Ch#child{pid = OldCh#child.pid};
+ (Ch) ->
+ Ch
+ end,
+ Children) of
+ Children ->
+ false; % OldCh not found in new spec.
+ NewC ->
+ {ok, NewC}
+ end.
+
+%%% ---------------------------------------------------
+%%% Start a new child.
+%%% ---------------------------------------------------
+
+handle_start_child(Child, State) ->
+ case get_child(Child#child.name, State) of
+ false ->
+ case do_start_child(State#state.name, Child) of
+ {ok, Pid} ->
+ Children = State#state.children,
+ {{ok, Pid},
+ State#state{children =
+ [Child#child{pid = Pid}|Children]}};
+ {ok, Pid, Extra} ->
+ Children = State#state.children,
+ {{ok, Pid, Extra},
+ State#state{children =
+ [Child#child{pid = Pid}|Children]}};
+ {error, What} ->
+ {{error, {What, Child}}, State}
+ end;
+ {value, OldChild} when OldChild#child.pid =/= undefined ->
+ {{error, {already_started, OldChild#child.pid}}, State};
+ {value, _OldChild} ->
+ {{error, already_present}, State}
+ end.
+
+%%% ---------------------------------------------------
+%%% Restart. A process has terminated.
+%%% Returns: {ok, #state} | {shutdown, #state}
+%%% ---------------------------------------------------
+
+restart_child(Pid, Reason, State) when ?is_simple(State) ->
+ case ?DICT:find(Pid, State#state.dynamics) of
+ {ok, Args} ->
+ [Child] = State#state.children,
+ RestartType = Child#child.restart_type,
+ {M, F, _} = Child#child.mfa,
+ NChild = Child#child{pid = Pid, mfa = {M, F, Args}},
+ do_restart(RestartType, Reason, NChild, State);
+ error ->
+ {ok, State}
+ end;
+restart_child(Pid, Reason, State) ->
+ Children = State#state.children,
+ case lists:keysearch(Pid, #child.pid, Children) of
+ {value, Child} ->
+ RestartType = Child#child.restart_type,
+ do_restart(RestartType, Reason, Child, State);
+ _ ->
+ {ok, State}
+ end.
+
+do_restart(permanent, Reason, Child, State) ->
+ report_error(child_terminated, Reason, Child, State#state.name),
+ restart(Child, State);
+do_restart(_, normal, Child, State) ->
+ NState = state_del_child(Child, State),
+ {ok, NState};
+do_restart(_, shutdown, Child, State) ->
+ NState = state_del_child(Child, State),
+ {ok, NState};
+do_restart(transient, Reason, Child, State) ->
+ report_error(child_terminated, Reason, Child, State#state.name),
+ restart(Child, State);
+do_restart(temporary, Reason, Child, State) ->
+ report_error(child_terminated, Reason, Child, State#state.name),
+ NState = state_del_child(Child, State),
+ {ok, NState}.
+
+restart(Child, State) ->
+ case add_restart(State) of
+ {ok, NState} ->
+ restart(NState#state.strategy, Child, NState);
+ {terminate, NState} ->
+ report_error(shutdown, reached_max_restart_intensity,
+ Child, State#state.name),
+ {shutdown, remove_child(Child, NState)}
+ end.
+
+restart(simple_one_for_one, Child, State) ->
+ #child{mfa = {M, F, A}} = Child,
+ Dynamics = ?DICT:erase(Child#child.pid, State#state.dynamics),
+ case do_start_child_i(M, F, A) of
+ {ok, Pid} ->
+ NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)},
+ {ok, NState};
+ {ok, Pid, _Extra} ->
+ NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)},
+ {ok, NState};
+ {error, Error} ->
+ report_error(start_error, Error, Child, State#state.name),
+ restart(Child, State)
+ end;
+restart(one_for_one, Child, State) ->
+ case do_start_child(State#state.name, Child) of
+ {ok, Pid} ->
+ NState = replace_child(Child#child{pid = Pid}, State),
+ {ok, NState};
+ {ok, Pid, _Extra} ->
+ NState = replace_child(Child#child{pid = Pid}, State),
+ {ok, NState};
+ {error, Reason} ->
+ report_error(start_error, Reason, Child, State#state.name),
+ restart(Child, State)
+ end;
+restart(rest_for_one, Child, State) ->
+ {ChAfter, ChBefore} = split_child(Child#child.pid, State#state.children),
+ ChAfter2 = terminate_children(ChAfter, State#state.name),
+ case start_children(ChAfter2, State#state.name) of
+ {ok, ChAfter3} ->
+ {ok, State#state{children = ChAfter3 ++ ChBefore}};
+ {error, ChAfter3} ->
+ restart(Child, State#state{children = ChAfter3 ++ ChBefore})
+ end;
+restart(one_for_all, Child, State) ->
+ Children1 = del_child(Child#child.pid, State#state.children),
+ Children2 = terminate_children(Children1, State#state.name),
+ case start_children(Children2, State#state.name) of
+ {ok, NChs} ->
+ {ok, State#state{children = NChs}};
+ {error, NChs} ->
+ restart(Child, State#state{children = NChs})
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: terminate_children/2
+%% Args: Children = [#child] in termination order
+%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod}
+%% Returns: NChildren = [#child] in
+%% startup order (reversed termination order)
+%%-----------------------------------------------------------------
+terminate_children(Children, SupName) ->
+ terminate_children(Children, SupName, []).
+
+terminate_children([Child | Children], SupName, Res) ->
+ NChild = do_terminate(Child, SupName),
+ terminate_children(Children, SupName, [NChild | Res]);
+terminate_children([], _SupName, Res) ->
+ Res.
+
+do_terminate(Child, SupName) when Child#child.pid =/= undefined ->
+ case shutdown(Child#child.pid,
+ Child#child.shutdown) of
+ ok ->
+ Child#child{pid = undefined};
+ {error, OtherReason} ->
+ report_error(shutdown_error, OtherReason, Child, SupName),
+ Child#child{pid = undefined}
+ end;
+do_terminate(Child, _SupName) ->
+ Child.
+
+%%-----------------------------------------------------------------
+%% Shutdowns a child. We must check the EXIT value
+%% of the child, because it might have died with another reason than
+%% the wanted. In that case we want to report the error. We put a
+%% monitor on the child an check for the 'DOWN' message instead of
+%% checking for the 'EXIT' message, because if we check the 'EXIT'
+%% message a "naughty" child, who does unlink(Sup), could hang the
+%% supervisor.
+%% Returns: ok | {error, OtherReason} (this should be reported)
+%%-----------------------------------------------------------------
+shutdown(Pid, brutal_kill) ->
+
+ case monitor_child(Pid) of
+ ok ->
+ exit(Pid, kill),
+ receive
+ {'DOWN', _MRef, process, Pid, killed} ->
+ ok;
+ {'DOWN', _MRef, process, Pid, OtherReason} ->
+ {error, OtherReason}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end;
+
+shutdown(Pid, Time) ->
+
+ case monitor_child(Pid) of
+ ok ->
+ exit(Pid, shutdown), %% Try to shutdown gracefully
+ receive
+ {'DOWN', _MRef, process, Pid, shutdown} ->
+ ok;
+ {'DOWN', _MRef, process, Pid, OtherReason} ->
+ {error, OtherReason}
+ after Time ->
+ exit(Pid, kill), %% Force termination.
+ receive
+ {'DOWN', _MRef, process, Pid, OtherReason} ->
+ {error, OtherReason}
+ end
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%% Help function to shutdown/2 switches from link to monitor approach
+monitor_child(Pid) ->
+
+ %% Do the monitor operation first so that if the child dies
+ %% before the monitoring is done causing a 'DOWN'-message with
+ %% reason noproc, we will get the real reason in the 'EXIT'-message
+ %% unless a naughty child has already done unlink...
+ erlang:monitor(process, Pid),
+ unlink(Pid),
+
+ receive
+ %% If the child dies before the unlik we must empty
+ %% the mail-box of the 'EXIT'-message and the 'DOWN'-message.
+ {'EXIT', Pid, Reason} ->
+ receive
+ {'DOWN', _, process, Pid, _} ->
+ {error, Reason}
+ end
+ after 0 ->
+ %% If a naughty child did unlink and the child dies before
+ %% monitor the result will be that shutdown/2 receives a
+ %% 'DOWN'-message with reason noproc.
+ %% If the child should die after the unlink there
+ %% will be a 'DOWN'-message with a correct reason
+ %% that will be handled in shutdown/2.
+ ok
+ end.
+
+
+%%-----------------------------------------------------------------
+%% Child/State manipulating functions.
+%%-----------------------------------------------------------------
+state_del_child(#child{pid = Pid}, State) when ?is_simple(State) ->
+ NDynamics = ?DICT:erase(Pid, State#state.dynamics),
+ State#state{dynamics = NDynamics};
+state_del_child(Child, State) ->
+ NChildren = del_child(Child#child.name, State#state.children),
+ State#state{children = NChildren}.
+
+del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name ->
+ [Ch#child{pid = undefined} | Chs];
+del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid ->
+ [Ch#child{pid = undefined} | Chs];
+del_child(Name, [Ch|Chs]) ->
+ [Ch|del_child(Name, Chs)];
+del_child(_, []) ->
+ [].
+
+%% Chs = [S4, S3, Ch, S1, S0]
+%% Ret: {[S4, S3, Ch], [S1, S0]}
+split_child(Name, Chs) ->
+ split_child(Name, Chs, []).
+
+split_child(Name, [Ch|Chs], After) when Ch#child.name =:= Name ->
+ {lists:reverse([Ch#child{pid = undefined} | After]), Chs};
+split_child(Pid, [Ch|Chs], After) when Ch#child.pid =:= Pid ->
+ {lists:reverse([Ch#child{pid = undefined} | After]), Chs};
+split_child(Name, [Ch|Chs], After) ->
+ split_child(Name, Chs, [Ch | After]);
+split_child(_, [], After) ->
+ {lists:reverse(After), []}.
+
+get_child(Name, State) ->
+ lists:keysearch(Name, #child.name, State#state.children).
+replace_child(Child, State) ->
+ Chs = do_replace_child(Child, State#state.children),
+ State#state{children = Chs}.
+
+do_replace_child(Child, [Ch|Chs]) when Ch#child.name =:= Child#child.name ->
+ [Child | Chs];
+do_replace_child(Child, [Ch|Chs]) ->
+ [Ch|do_replace_child(Child, Chs)].
+
+remove_child(Child, State) ->
+ Chs = lists:keydelete(Child#child.name, #child.name, State#state.children),
+ State#state{children = Chs}.
+
+%%-----------------------------------------------------------------
+%% Func: init_state/4
+%% Args: SupName = {local, atom()} | {global, atom()} | self
+%% Type = {Strategy, MaxIntensity, Period}
+%% Strategy = one_for_one | one_for_all | simple_one_for_one |
+%% rest_for_one
+%% MaxIntensity = integer()
+%% Period = integer()
+%% Mod :== atom()
+%% Arsg :== term()
+%% Purpose: Check that Type is of correct type (!)
+%% Returns: {ok, #state} | Error
+%%-----------------------------------------------------------------
+init_state(SupName, Type, Mod, Args) ->
+ case catch init_state1(SupName, Type, Mod, Args) of
+ {ok, State} ->
+ {ok, State};
+ Error ->
+ Error
+ end.
+
+init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) ->
+ validStrategy(Strategy),
+ validIntensity(MaxIntensity),
+ validPeriod(Period),
+ {ok, #state{name = supname(SupName,Mod),
+ strategy = Strategy,
+ intensity = MaxIntensity,
+ period = Period,
+ module = Mod,
+ args = Args}};
+init_state1(_SupName, Type, _, _) ->
+ {invalid_type, Type}.
+
+validStrategy(simple_one_for_one) -> true;
+validStrategy(one_for_one) -> true;
+validStrategy(one_for_all) -> true;
+validStrategy(rest_for_one) -> true;
+validStrategy(What) -> throw({invalid_strategy, What}).
+
+validIntensity(Max) when is_integer(Max),
+ Max >= 0 -> true;
+validIntensity(What) -> throw({invalid_intensity, What}).
+
+validPeriod(Period) when is_integer(Period),
+ Period > 0 -> true;
+validPeriod(What) -> throw({invalid_period, What}).
+
+supname(self,Mod) -> {self(),Mod};
+supname(N,_) -> N.
+
+%%% ------------------------------------------------------
+%%% Check that the children start specification is valid.
+%%% Shall be a six (6) tuple
+%%% {Name, Func, RestartType, Shutdown, ChildType, Modules}
+%%% where Name is an atom
+%%% Func is {Mod, Fun, Args} == {atom, atom, list}
+%%% RestartType is permanent | temporary | transient
+%%% Shutdown = integer() | infinity | brutal_kill
+%%% ChildType = supervisor | worker
+%%% Modules = [atom()] | dynamic
+%%% Returns: {ok, [#child]} | Error
+%%% ------------------------------------------------------
+
+check_startspec(Children) -> check_startspec(Children, []).
+
+check_startspec([ChildSpec|T], Res) ->
+ case check_childspec(ChildSpec) of
+ {ok, Child} ->
+ case lists:keymember(Child#child.name, #child.name, Res) of
+ true -> {duplicate_child_name, Child#child.name};
+ false -> check_startspec(T, [Child | Res])
+ end;
+ Error -> Error
+ end;
+check_startspec([], Res) ->
+ {ok, lists:reverse(Res)}.
+
+check_childspec({Name, Func, RestartType, Shutdown, ChildType, Mods}) ->
+ catch check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods);
+check_childspec(X) -> {invalid_child_spec, X}.
+
+check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods) ->
+ validName(Name),
+ validFunc(Func),
+ validRestartType(RestartType),
+ validChildType(ChildType),
+ validShutdown(Shutdown, ChildType),
+ validMods(Mods),
+ {ok, #child{name = Name, mfa = Func, restart_type = RestartType,
+ shutdown = Shutdown, child_type = ChildType, modules = Mods}}.
+
+validChildType(supervisor) -> true;
+validChildType(worker) -> true;
+validChildType(What) -> throw({invalid_child_type, What}).
+
+validName(_Name) -> true.
+
+validFunc({M, F, A}) when is_atom(M),
+ is_atom(F),
+ is_list(A) -> true;
+validFunc(Func) -> throw({invalid_mfa, Func}).
+
+validRestartType(permanent) -> true;
+validRestartType(temporary) -> true;
+validRestartType(transient) -> true;
+validRestartType(RestartType) -> throw({invalid_restart_type, RestartType}).
+
+validShutdown(Shutdown, _)
+ when is_integer(Shutdown), Shutdown > 0 -> true;
+validShutdown(infinity, supervisor) -> true;
+validShutdown(brutal_kill, _) -> true;
+validShutdown(Shutdown, _) -> throw({invalid_shutdown, Shutdown}).
+
+validMods(dynamic) -> true;
+validMods(Mods) when is_list(Mods) ->
+ lists:foreach(fun(Mod) ->
+ if
+ is_atom(Mod) -> ok;
+ true -> throw({invalid_module, Mod})
+ end
+ end,
+ Mods);
+validMods(Mods) -> throw({invalid_modules, Mods}).
+
+%%% ------------------------------------------------------
+%%% Add a new restart and calculate if the max restart
+%%% intensity has been reached (in that case the supervisor
+%%% shall terminate).
+%%% All restarts accured inside the period amount of seconds
+%%% are kept in the #state.restarts list.
+%%% Returns: {ok, State'} | {terminate, State'}
+%%% ------------------------------------------------------
+
+add_restart(State) ->
+ I = State#state.intensity,
+ P = State#state.period,
+ R = State#state.restarts,
+ Now = erlang:now(),
+ R1 = add_restart([Now|R], Now, P),
+ State1 = State#state{restarts = R1},
+ case length(R1) of
+ CurI when CurI =< I ->
+ {ok, State1};
+ _ ->
+ {terminate, State1}
+ end.
+
+add_restart([R|Restarts], Now, Period) ->
+ case inPeriod(R, Now, Period) of
+ true ->
+ [R|add_restart(Restarts, Now, Period)];
+ _ ->
+ []
+ end;
+add_restart([], _, _) ->
+ [].
+
+inPeriod(Time, Now, Period) ->
+ case difference(Time, Now) of
+ T when T > Period ->
+ false;
+ _ ->
+ true
+ end.
+
+%%
+%% Time = {MegaSecs, Secs, MicroSecs} (NOTE: MicroSecs is ignored)
+%% Calculate the time elapsed in seconds between two timestamps.
+%% If MegaSecs is equal just subtract Secs.
+%% Else calculate the Mega difference and add the Secs difference,
+%% note that Secs difference can be negative, e.g.
+%% {827, 999999, 676} diff {828, 1, 653753} == > 2 secs.
+%%
+difference({TimeM, TimeS, _}, {CurM, CurS, _}) when CurM > TimeM ->
+ ((CurM - TimeM) * 1000000) + (CurS - TimeS);
+difference({_, TimeS, _}, {_, CurS, _}) ->
+ CurS - TimeS.
+
+%%% ------------------------------------------------------
+%%% Error and progress reporting.
+%%% ------------------------------------------------------
+
+report_error(Error, Reason, Child, SupName) ->
+ ErrorMsg = [{supervisor, SupName},
+ {errorContext, Error},
+ {reason, Reason},
+ {offender, extract_child(Child)}],
+ error_logger:error_report(supervisor_report, ErrorMsg).
+
+
+extract_child(Child) ->
+ [{pid, Child#child.pid},
+ {name, Child#child.name},
+ {mfa, Child#child.mfa},
+ {restart_type, Child#child.restart_type},
+ {shutdown, Child#child.shutdown},
+ {child_type, Child#child.child_type}].
+
+report_progress(Child, SupName) ->
+ Progress = [{supervisor, SupName},
+ {started, extract_child(Child)}],
+ error_logger:info_report(progress, Progress).
diff --git a/lib/stdlib/src/supervisor_bridge.erl b/lib/stdlib/src/supervisor_bridge.erl
new file mode 100644
index 0000000000..3d2bd2c9a5
--- /dev/null
+++ b/lib/stdlib/src/supervisor_bridge.erl
@@ -0,0 +1,116 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(supervisor_bridge).
+
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/2, start_link/3]).
+-export([behaviour_info/1]).
+%% Internal exports
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
+-export([code_change/3]).
+
+behaviour_info(callbacks) ->
+ [{init,1},{terminate,2}];
+behaviour_info(_Other) ->
+ undefined.
+
+%%%-----------------------------------------------------------------
+%%% This is a rewrite of supervisor_bridge from BS.3.
+%%%
+%%% This module is built to function as process code
+%%% for a process sitting inbetween a real supervisor
+%%% and a not start&recovery complient server/system
+%%% The process inbetween simulates start&recovery
+%%% behaviour of the server/system below.
+%%%
+%%% The supervisor_bridge behaviour must export the following
+%%% functions:
+%%% init(Args) -> {ok, Pid, State} | {error, Reason} | ignore
+%%% where Pid is the child process
+%%% terminate(Reason, State) -> ok
+%%%-----------------------------------------------------------------
+-record(state, {mod, pid, child_state, name}).
+
+start_link(Mod, StartArgs) ->
+ gen_server:start_link(supervisor_bridge, [Mod, StartArgs, self], []).
+
+start_link(Name, Mod, StartArgs) ->
+ gen_server:start_link(Name, supervisor_bridge, [Mod, StartArgs, Name], []).
+
+%%-----------------------------------------------------------------
+%% Callback functions from gen_server
+%%-----------------------------------------------------------------
+init([Mod, StartArgs, Name0]) ->
+ process_flag(trap_exit, true),
+ Name = supname(Name0, Mod),
+ case Mod:init(StartArgs) of
+ {ok, Pid, ChildState} when is_pid(Pid) ->
+ link(Pid),
+ report_progress(Pid, Mod, StartArgs, Name),
+ {ok, #state{mod = Mod, pid = Pid,
+ child_state = ChildState, name = Name}};
+ ignore ->
+ ignore;
+ {error, Reason} ->
+ {stop, Reason}
+ end.
+
+supname(self, Mod) -> {self(),Mod};
+supname(N, _) -> N.
+
+%% A supervisor *must* answer the supervisor:which_children call.
+handle_call(which_children, _From, State) ->
+ {reply, [], State};
+handle_call(_Req, _From, State) ->
+ {reply, {error, badcall}, State}.
+
+handle_cast(_, State) ->
+ {noreply, State}.
+
+handle_info({'EXIT', Pid, Reason}, State) when State#state.pid =:= Pid ->
+ report_error(child_terminated, Reason, State),
+ {stop, Reason, State#state{pid = undefined}};
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{pid = undefined}) ->
+ ok;
+terminate(Reason, State) ->
+ terminate_pid(Reason, State).
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% This function is supposed to terminate the 'real' server.
+terminate_pid(Reason, #state{mod = Mod, child_state = ChildState}) ->
+ Mod:terminate(Reason, ChildState).
+
+report_progress(Pid, Mod, StartArgs, SupName) ->
+ Progress = [{supervisor, SupName},
+ {started, [{pid, Pid}, {mfa, {Mod, init, [StartArgs]}}]}],
+ error_logger:info_report(progress, Progress).
+
+report_error(Error, Reason, #state{name = Name, pid = Pid, mod = Mod}) ->
+ ErrorMsg = [{supervisor, Name},
+ {errorContext, Error},
+ {reason, Reason},
+ {offender, [{pid, Pid}, {mod, Mod}]}],
+ error_logger:error_report(supervisor_report, ErrorMsg).
diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl
new file mode 100644
index 0000000000..e0f2dbcd3c
--- /dev/null
+++ b/lib/stdlib/src/sys.erl
@@ -0,0 +1,391 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(sys).
+
+%% External exports
+-export([suspend/1, suspend/2, resume/1, resume/2,
+ get_status/1, get_status/2,
+ change_code/4, change_code/5,
+ log/2, log/3, trace/2, trace/3, statistics/2, statistics/3,
+ log_to_file/2, log_to_file/3, no_debug/1, no_debug/2,
+ install/2, install/3, remove/2, remove/3]).
+-export([handle_system_msg/6, handle_system_msg/7, handle_debug/4,
+ print_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]).
+
+%%-----------------------------------------------------------------
+%% Types
+%%-----------------------------------------------------------------
+
+-type name() :: pid() | atom() | {'global', atom()}.
+-type system_event() :: {'in', _Msg} | {'in', _Msg, _From} | {'out', _Msg, _To}.
+
+%%-----------------------------------------------------------------
+%% System messages
+%%-----------------------------------------------------------------
+suspend(Name) -> send_system_msg(Name, suspend).
+suspend(Name, Timeout) -> send_system_msg(Name, suspend, Timeout).
+
+resume(Name) -> send_system_msg(Name, resume).
+resume(Name, Timeout) -> send_system_msg(Name, resume, Timeout).
+
+get_status(Name) -> send_system_msg(Name, get_status).
+get_status(Name, Timeout) -> send_system_msg(Name, get_status, Timeout).
+
+change_code(Name, Mod, Vsn, Extra) ->
+ send_system_msg(Name, {change_code, Mod, Vsn, Extra}).
+change_code(Name, Mod, Vsn, Extra, Timeout) ->
+ send_system_msg(Name, {change_code, Mod, Vsn, Extra}, Timeout).
+
+%%-----------------------------------------------------------------
+%% Debug commands
+%%-----------------------------------------------------------------
+
+-type log_flag() :: 'true' | {'true',pos_integer()} | 'false' | 'get' | 'print'.
+
+-spec log(name(), log_flag()) -> 'ok' | {'ok', [system_event()]}.
+log(Name, Flag) ->
+ send_system_msg(Name, {debug, {log, Flag}}).
+
+-spec log(name(), log_flag(), timeout()) -> 'ok' | {'ok', [system_event()]}.
+log(Name, Flag, Timeout) ->
+ send_system_msg(Name, {debug, {log, Flag}}, Timeout).
+
+-spec trace(name(), boolean()) -> 'ok'.
+trace(Name, Flag) ->
+ send_system_msg(Name, {debug, {trace, Flag}}).
+
+-spec trace(name(), boolean(), timeout()) -> 'ok'.
+trace(Name, Flag, Timeout) ->
+ send_system_msg(Name, {debug, {trace, Flag}}, Timeout).
+
+-type l2f_fname() :: string() | 'false'.
+
+-spec log_to_file(name(), l2f_fname()) -> 'ok' | {'error','open_file'}.
+log_to_file(Name, FileName) ->
+ send_system_msg(Name, {debug, {log_to_file, FileName}}).
+
+-spec log_to_file(name(), l2f_fname(), timeout()) -> 'ok' | {'error','open_file'}.
+log_to_file(Name, FileName, Timeout) ->
+ send_system_msg(Name, {debug, {log_to_file, FileName}}, Timeout).
+
+statistics(Name, Flag) ->
+ send_system_msg(Name, {debug, {statistics, Flag}}).
+statistics(Name, Flag, Timeout) ->
+ send_system_msg(Name, {debug, {statistics, Flag}}, Timeout).
+
+-spec no_debug(name()) -> 'ok'.
+no_debug(Name) -> send_system_msg(Name, {debug, no_debug}).
+
+-spec no_debug(name(), timeout()) -> 'ok'.
+no_debug(Name, Timeout) -> send_system_msg(Name, {debug, no_debug}, Timeout).
+
+install(Name, {Func, FuncState}) ->
+ send_system_msg(Name, {debug, {install, {Func, FuncState}}}).
+install(Name, {Func, FuncState}, Timeout) ->
+ send_system_msg(Name, {debug, {install, {Func, FuncState}}}, Timeout).
+
+remove(Name, Func) ->
+ send_system_msg(Name, {debug, {remove, Func}}).
+remove(Name, Func, Timeout) ->
+ send_system_msg(Name, {debug, {remove, Func}}, Timeout).
+
+%%-----------------------------------------------------------------
+%% All system messages sent are on the form {system, From, Msg}
+%% The receiving side should send Msg to handle_system_msg/5.
+%%-----------------------------------------------------------------
+send_system_msg(Name, Request) ->
+ case catch gen:call(Name, system, Request) of
+ {ok,Res} -> Res;
+ {'EXIT', Reason} -> exit({Reason, mfa(Name, Request)})
+ end.
+
+send_system_msg(Name, Request, Timeout) ->
+ case catch gen:call(Name, system, Request, Timeout) of
+ {ok,Res} -> Res;
+ {'EXIT', Reason} -> exit({Reason, mfa(Name, Request, Timeout)})
+ end.
+
+mfa(Name, {debug, {Func, Arg2}}) ->
+ {sys, Func, [Name, Arg2]};
+mfa(Name, {change_code, Mod, Vsn, Extra}) ->
+ {sys, change_code, [Name, Mod, Vsn, Extra]};
+mfa(Name, Atom) ->
+ {sys, Atom, [Name]}.
+mfa(Name, Req, Timeout) ->
+ {M, F, A} = mfa(Name, Req),
+ {M, F, A ++ [Timeout]}.
+
+%%-----------------------------------------------------------------
+%% Func: handle_system_msg/6
+%% Args: Msg ::= term()
+%% From ::= {pid(),Ref} but don't count on that
+%% Parent ::= pid()
+%% Module ::= atom()
+%% Debug ::= [debug_opts()]
+%% Misc ::= term()
+%% Purpose: Used by a process module that wishes to take care of
+%% system messages. The process receives a {system, From,
+%% Msg} message, and passes the Msg to this function.
+%% Returns: This function *never* returns! It calls the function
+%% Module:system_continue(Parent, NDebug, Misc)
+%% there the process continues the execution or
+%% Module:system_terminate(Raeson, Parent, Debug, Misc) if
+%% the process should terminate.
+%% The Module must export system_continue/3, system_terminate/4
+%% and format_status/2 for status information.
+%%-----------------------------------------------------------------
+handle_system_msg(Msg, From, Parent, Module, Debug, Misc) ->
+ handle_system_msg(running, Msg, From, Parent, Module, Debug, Misc, false).
+
+handle_system_msg(Msg, From, Parent, Mod, Debug, Misc, Hib) ->
+ handle_system_msg(running, Msg, From, Parent, Mod, Debug, Misc, Hib).
+
+handle_system_msg(SysState, Msg, From, Parent, Mod, Debug, Misc, Hib) ->
+ case do_cmd(SysState, Msg, Parent, Mod, Debug, Misc) of
+ {suspended, Reply, NDebug, NMisc} ->
+ gen:reply(From, Reply),
+ suspend_loop(suspended, Parent, Mod, NDebug, NMisc, Hib);
+ {running, Reply, NDebug, NMisc} ->
+ gen:reply(From, Reply),
+ Mod:system_continue(Parent, NDebug, NMisc)
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: handle_debug/4
+%% Args: Debug ::= [debug_opts()]
+%% Func ::= {M,F} | fun() arity 3
+%% State ::= term()
+%% Event ::= {in, Msg} | {in, Msg, From} | {out, Msg, To} | term()
+%% Purpose: Called by a process that wishes to debug an event.
+%% Func is a formatting function, called as Func(Device, Event).
+%% Returns: [debug_opts()]
+%%-----------------------------------------------------------------
+handle_debug([{trace, true} | T], FormFunc, State, Event) ->
+ print_event({Event, State, FormFunc}),
+ [{trace, true} | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{log, {N, LogData}} | T], FormFunc, State, Event) ->
+ NLogData = [{Event, State, FormFunc} | trim(N, LogData)],
+ [{log, {N, NLogData}} | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{log_to_file, Fd} | T], FormFunc, State, Event) ->
+ print_event(Fd, {Event, State, FormFunc}),
+ [{log_to_file, Fd} | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{statistics, StatData} | T], FormFunc, State, Event) ->
+ NStatData = stat(Event, StatData),
+ [{statistics, NStatData} | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{Func, FuncState} | T], FormFunc, State, Event) ->
+ case catch Func(FuncState, Event, State) of
+ done -> handle_debug(T, FormFunc, State, Event);
+ {'EXIT', _} -> handle_debug(T, FormFunc, State, Event);
+ NFuncState ->
+ [{Func, NFuncState} | handle_debug(T, FormFunc, State, Event)]
+ end;
+handle_debug([], _FormFunc, _State, _Event) ->
+ [].
+
+%%-----------------------------------------------------------------
+%% When a process is suspended, it can only respond to system
+%% messages.
+%%-----------------------------------------------------------------
+suspend_loop(SysState, Parent, Mod, Debug, Misc, Hib) ->
+ case Hib of
+ true ->
+ suspend_loop_hib(SysState, Parent, Mod, Debug, Misc, Hib);
+ _ ->
+ receive
+ {system, From, Msg} ->
+ handle_system_msg(SysState, Msg, From, Parent, Mod, Debug, Misc, Hib);
+ {'EXIT', Parent, Reason} ->
+ Mod:system_terminate(Reason, Parent, Debug, Misc)
+ end
+ end.
+
+suspend_loop_hib(SysState, Parent, Mod, Debug, Misc, Hib) ->
+ receive
+ {system, From, Msg} ->
+ handle_system_msg(SysState, Msg, From, Parent, Mod, Debug, Misc, Hib);
+ {'EXIT', Parent, Reason} ->
+ Mod:system_terminate(Reason, Parent, Debug, Misc)
+ after 0 -> % Not a system message, go back into hibernation
+ proc_lib:hibernate(?MODULE, suspend_loop_hib, [SysState, Parent, Mod,
+ Debug, Misc, Hib])
+ end.
+
+
+do_cmd(_, suspend, _Parent, _Mod, Debug, Misc) ->
+ {suspended, ok, Debug, Misc};
+do_cmd(_, resume, _Parent, _Mod, Debug, Misc) ->
+ {running, ok, Debug, Misc};
+do_cmd(SysState, get_status, Parent, Mod, Debug, Misc) ->
+ Res = get_status(SysState, Parent, Mod, Debug, Misc),
+ {SysState, Res, Debug, Misc};
+do_cmd(SysState, {debug, What}, _Parent, _Mod, Debug, Misc) ->
+ {Res, NDebug} = debug_cmd(What, Debug),
+ {SysState, Res, NDebug, Misc};
+do_cmd(suspended, {change_code, Module, Vsn, Extra}, _Parent,
+ Mod, Debug, Misc) ->
+ {Res, NMisc} = do_change_code(Mod, Module, Vsn, Extra, Misc),
+ {suspended, Res, Debug, NMisc};
+do_cmd(SysState, Other, _Parent, _Mod, Debug, Misc) ->
+ {SysState, {error, {unknown_system_msg, Other}}, Debug, Misc}.
+
+get_status(SysState, Parent, Mod, Debug, Misc) ->
+ {status, self(), {module, Mod},
+ [get(), SysState, Parent, Debug, Misc]}.
+
+%%-----------------------------------------------------------------
+%% These are the system debug commands.
+%% {trace, true|false} -> io:format
+%% {log, true|false|get|print} -> keeps the 10 last debug messages
+%% {log_to_file, FileName | false} -> io:format to file.
+%% {statistics, true|false|get} -> keeps track of messages in/out + reds.
+%%-----------------------------------------------------------------
+debug_cmd({trace, true}, Debug) ->
+ {ok, install_debug(trace, true, Debug)};
+debug_cmd({trace, false}, Debug) ->
+ {ok, remove_debug(trace, Debug)};
+debug_cmd({log, true}, Debug) ->
+ {_N, Logs} = get_debug(log, Debug, {0, []}),
+ {ok, install_debug(log, {10, trim(10, Logs)}, Debug)};
+debug_cmd({log, {true, N}}, Debug) when is_integer(N), N > 0 ->
+ {_N, Logs} = get_debug(log, Debug, {0, []}),
+ {ok, install_debug(log, {N, trim(N, Logs)}, Debug)};
+debug_cmd({log, false}, Debug) ->
+ {ok, remove_debug(log, Debug)};
+debug_cmd({log, print}, Debug) ->
+ print_log(Debug),
+ {ok, Debug};
+debug_cmd({log, get}, Debug) ->
+ {_N, Logs} = get_debug(log, Debug, {0, []}),
+ {{ok, lists:reverse(Logs)}, Debug};
+debug_cmd({log_to_file, false}, Debug) ->
+ NDebug = close_log_file(Debug),
+ {ok, NDebug};
+debug_cmd({log_to_file, FileName}, Debug) ->
+ NDebug = close_log_file(Debug),
+ case file:open(FileName, [write]) of
+ {ok, Fd} ->
+ {ok, install_debug(log_to_file, Fd, NDebug)};
+ _Error ->
+ {{error, open_file}, NDebug}
+ end;
+debug_cmd({statistics, true}, Debug) ->
+ {ok, install_debug(statistics, init_stat(), Debug)};
+debug_cmd({statistics, false}, Debug) ->
+ {ok, remove_debug(statistics, Debug)};
+debug_cmd({statistics, get}, Debug) ->
+ {{ok, get_stat(get_debug(statistics, Debug, []))}, Debug};
+debug_cmd(no_debug, Debug) ->
+ close_log_file(Debug),
+ {ok, []};
+debug_cmd({install, {Func, FuncState}}, Debug) ->
+ {ok, install_debug(Func, FuncState, Debug)};
+debug_cmd({remove, Func}, Debug) ->
+ {ok, remove_debug(Func, Debug)};
+debug_cmd(_Unknown, Debug) ->
+ {unknown_debug, Debug}.
+
+
+do_change_code(Mod, Module, Vsn, Extra, Misc) ->
+ case catch Mod:system_code_change(Misc, Module, Vsn, Extra) of
+ {ok, NMisc} -> {ok, NMisc};
+ Else -> {{error, Else}, Misc}
+ end.
+
+print_event(X) -> print_event(standard_io, X).
+
+print_event(Dev, {Event, State, FormFunc}) ->
+ FormFunc(Dev, Event, State).
+
+init_stat() -> {erlang:localtime(), process_info(self(), reductions), 0, 0}.
+get_stat({Time, {reductions, Reds}, In, Out}) ->
+ {reductions, Reds2} = process_info(self(), reductions),
+ [{start_time, Time}, {current_time, erlang:localtime()},
+ {reductions, Reds2 - Reds}, {messages_in, In}, {messages_out, Out}];
+get_stat(_) ->
+ no_statistics.
+
+stat({in, _Msg}, {Time, Reds, In, Out}) -> {Time, Reds, In+1, Out};
+stat({in, _Msg, _From}, {Time, Reds, In, Out}) -> {Time, Reds, In+1, Out};
+stat({out, _Msg, _To}, {Time, Reds, In, Out}) -> {Time, Reds, In, Out+1};
+stat(_, StatData) -> StatData.
+
+trim(N, LogData) ->
+ lists:sublist(LogData, 1, N-1).
+
+%%-----------------------------------------------------------------
+%% Debug structure manipulating functions
+%%-----------------------------------------------------------------
+install_debug(Item, Data, Debug) ->
+ case get_debug(Item, Debug, undefined) of
+ undefined -> [{Item, Data} | Debug];
+ _ -> Debug
+ end.
+remove_debug(Item, Debug) -> lists:keydelete(Item, 1, Debug).
+get_debug(Item, Debug, Default) ->
+ case lists:keysearch(Item, 1, Debug) of
+ {value, {Item, Data}} -> Data;
+ _ -> Default
+ end.
+
+print_log(Debug) ->
+ {_N, Logs} = get_debug(log, Debug, {0, []}),
+ lists:foreach(fun print_event/1,
+ lists:reverse(Logs)).
+
+close_log_file(Debug) ->
+ case get_debug(log_to_file, Debug, []) of
+ [] ->
+ Debug;
+ Fd ->
+ ok = file:close(Fd),
+ remove_debug(log_to_file, Debug)
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: debug_options/1
+%% Args: [trace|log|{log,N}|statistics|{log_to_file, FileName}|
+%% {install, {Func, FuncState}}]
+%% Purpose: Initiate a debug structure. Called by a process that
+%% wishes to initiate the debug structure without the
+%% system messages.
+%% Returns: [debug_opts()]
+%%-----------------------------------------------------------------
+debug_options(Options) ->
+ debug_options(Options, []).
+debug_options([trace | T], Debug) ->
+ debug_options(T, install_debug(trace, true, Debug));
+debug_options([log | T], Debug) ->
+ debug_options(T, install_debug(log, {10, []}, Debug));
+debug_options([{log, N} | T], Debug) when is_integer(N), N > 0 ->
+ debug_options(T, install_debug(log, {N, []}, Debug));
+debug_options([statistics | T], Debug) ->
+ debug_options(T, install_debug(statistics, init_stat(), Debug));
+debug_options([{log_to_file, FileName} | T], Debug) ->
+ case file:open(FileName, [write]) of
+ {ok, Fd} ->
+ debug_options(T, install_debug(log_to_file, Fd, Debug));
+ _Error ->
+ debug_options(T, Debug)
+ end;
+debug_options([{install, {Func, FuncState}} | T], Debug) ->
+ debug_options(T, install_debug(Func, FuncState, Debug));
+debug_options([_ | T], Debug) ->
+ debug_options(T, Debug);
+debug_options([], Debug) ->
+ Debug.
diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl
new file mode 100644
index 0000000000..36fdb48c75
--- /dev/null
+++ b/lib/stdlib/src/timer.erl
@@ -0,0 +1,364 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(timer).
+
+-export([apply_after/4,
+ send_after/3, send_after/2,
+ exit_after/3, exit_after/2, kill_after/2, kill_after/1,
+ apply_interval/4, send_interval/3, send_interval/2,
+ cancel/1, sleep/1, tc/3, now_diff/2,
+ seconds/1, minutes/1, hours/1, hms/3]).
+
+-export([start_link/0, start/0,
+ handle_call/3, handle_info/2,
+ init/1,
+ code_change/3, handle_cast/2, terminate/2]).
+
+%% internal exports for test purposes only
+-export([get_status/0]).
+
+%% Max
+-define(MAX_TIMEOUT, 16#0800000).
+-define(TIMER_TAB, timer_tab).
+-define(INTERVAL_TAB, timer_interval_tab).
+
+%%
+%% Time is in milliseconds.
+%%
+-opaque tref() :: any().
+-type time() :: non_neg_integer().
+-type timestamp() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
+
+%%
+%% Interface functions
+%%
+-spec apply_after(time(), atom(), atom(), [_]) -> {'ok', tref()} | {'error', _}.
+apply_after(Time, M, F, A) ->
+ req(apply_after, {Time, {M, F, A}}).
+
+-spec send_after(time(), pid() | atom(), term()) -> {'ok', tref()} | {'error', _}.
+send_after(Time, Pid, Message) ->
+ req(apply_after, {Time, {?MODULE, send, [Pid, Message]}}).
+
+-spec send_after(time(), _) -> {'ok', tref()} | {'error', _}.
+send_after(Time, Message) ->
+ send_after(Time, self(), Message).
+
+-spec exit_after(time(), pid() | atom(), _) -> {'ok', tref()} | {'error', _}.
+exit_after(Time, Pid, Reason) ->
+ req(apply_after, {Time, {erlang, exit, [Pid, Reason]}}).
+
+-spec exit_after(time(), term()) -> {'ok', tref()} | {'error', _}.
+exit_after(Time, Reason) ->
+ exit_after(Time, self(), Reason).
+
+-spec kill_after(time(), pid() | atom()) -> {'ok', tref()} | {'error', _}.
+kill_after(Time, Pid) ->
+ exit_after(Time, Pid, kill).
+
+-spec kill_after(time()) -> {'ok', tref()} | {'error', _}.
+kill_after(Time) ->
+ exit_after(Time, self(), kill).
+
+-spec apply_interval(time(), atom(), atom(), [_]) -> {'ok', tref()} | {'error', _}.
+apply_interval(Time, M, F, A) ->
+ req(apply_interval, {Time, self(), {M, F, A}}).
+
+-spec send_interval(time(), pid() | atom(), term()) -> {'ok', tref()} | {'error', _}.
+send_interval(Time, Pid, Message) ->
+ req(apply_interval, {Time, Pid, {?MODULE, send, [Pid, Message]}}).
+
+-spec send_interval(time(), term()) -> {'ok', tref()} | {'error', _}.
+send_interval(Time, Message) ->
+ send_interval(Time, self(), Message).
+
+-spec cancel(tref()) -> {'ok', 'cancel'} | {'error', _}.
+cancel(BRef) ->
+ req(cancel, BRef).
+
+-spec sleep(timeout()) -> 'ok'.
+sleep(T) ->
+ receive
+ after T -> ok
+ end.
+
+%%
+%% Measure the execution time (in microseconds) for an MFA.
+%%
+-spec tc(atom(), atom(), [_]) -> {time(), term()}.
+tc(M, F, A) ->
+ Before = erlang:now(),
+ Val = (catch apply(M, F, A)),
+ After = erlang:now(),
+ {now_diff(After, Before), Val}.
+
+%%
+%% Calculate the time difference (in microseconds) of two
+%% erlang:now() timestamps, T2-T1.
+%%
+-spec now_diff(timestamp(), timestamp()) -> integer().
+now_diff({A2, B2, C2}, {A1, B1, C1}) ->
+ ((A2-A1)*1000000 + B2-B1)*1000000 + C2-C1.
+
+%%
+%% Convert seconds, minutes etc. to milliseconds.
+%%
+-spec seconds(non_neg_integer()) -> non_neg_integer().
+seconds(Seconds) ->
+ 1000*Seconds.
+-spec minutes(non_neg_integer()) -> non_neg_integer().
+minutes(Minutes) ->
+ 1000*60*Minutes.
+-spec hours(non_neg_integer()) -> non_neg_integer().
+hours(Hours) ->
+ 1000*60*60*Hours.
+-spec hms(non_neg_integer(), non_neg_integer(), non_neg_integer()) -> non_neg_integer().
+hms(H, M, S) ->
+ hours(H) + minutes(M) + seconds(S).
+
+%%
+%% Start/init functions
+%%
+
+%% Start is only included because of backward compatibility!
+-spec start() -> 'ok'.
+start() ->
+ ensure_started().
+
+-spec start_link() -> {'ok', pid()} | {'error', _}.
+start_link() ->
+ gen_server:start_link({local, timer_server}, ?MODULE, [], []).
+
+-spec init([]) -> {'ok', [], 'infinity'}.
+init([]) ->
+ process_flag(trap_exit, true),
+ ?TIMER_TAB = ets:new(?TIMER_TAB, [named_table,ordered_set,protected]),
+ ?INTERVAL_TAB = ets:new(?INTERVAL_TAB, [named_table,protected]),
+ {ok, [], infinity}.
+
+ensure_started() ->
+ case whereis(timer_server) of
+ undefined ->
+ C = {timer_server, {?MODULE, start_link, []}, permanent, 1000,
+ worker, [?MODULE]},
+ supervisor:start_child(kernel_safe_sup, C), % kernel_safe_sup
+ ok;
+ _ -> ok
+ end.
+
+%% server calls
+
+req(Req, Arg) ->
+ SysTime = system_time(),
+ ensure_started(),
+ gen_server:call(timer_server, {Req, Arg, SysTime}, infinity).
+
+%%
+%% handle_call(Request, From, Timers) ->
+%% {reply, Response, Timers, Timeout}
+%%
+%% Time and Timeout is in milliseconds. Started is in microseconds.
+%%
+handle_call({apply_after, {Time, Op}, Started}, _From, _Ts)
+ when is_integer(Time), Time >= 0 ->
+ BRef = {Started + 1000*Time, make_ref()},
+ Timer = {BRef, timeout, Op},
+ ets:insert(?TIMER_TAB, Timer),
+ Timeout = timer_timeout(system_time()),
+ {reply, {ok, BRef}, [], Timeout};
+handle_call({apply_interval, {Time, To, MFA}, Started}, _From, _Ts)
+ when is_integer(Time), Time >= 0 ->
+ %% To must be a pid or a registered name
+ case get_pid(To) of
+ Pid when is_pid(Pid) ->
+ catch link(Pid),
+ SysTime = system_time(),
+ Ref = make_ref(),
+ BRef1 = {interval, Ref},
+ Interval = Time*1000,
+ BRef2 = {Started + Interval, Ref},
+ Timer = {BRef2, {repeat, Interval, Pid}, MFA},
+ ets:insert(?INTERVAL_TAB,{BRef1,BRef2,Pid}),
+ ets:insert(?TIMER_TAB, Timer),
+ Timeout = timer_timeout(SysTime),
+ {reply, {ok, BRef1}, [], Timeout};
+ _ ->
+ {reply, {error, badarg}, [], next_timeout()}
+ end;
+handle_call({cancel, BRef = {_Time, Ref}, _}, _From, Ts)
+ when is_reference(Ref) ->
+ delete_ref(BRef),
+ {reply, {ok, cancel}, Ts, next_timeout()};
+handle_call({cancel, _BRef, _}, _From, Ts) ->
+ {reply, {error, badarg}, Ts, next_timeout()};
+handle_call({apply_after, _, _}, _From, Ts) ->
+ {reply, {error, badarg}, Ts, next_timeout()};
+handle_call({apply_interval, _, _}, _From, Ts) ->
+ {reply, {error, badarg}, Ts, next_timeout()};
+handle_call(_Else, _From, Ts) -> % Catch anything else
+ {noreply, Ts, next_timeout()}.
+
+handle_info(timeout, Ts) -> % Handle timeouts
+ Timeout = timer_timeout(system_time()),
+ {noreply, Ts, Timeout};
+handle_info({'EXIT', Pid, _Reason}, Ts) -> % Oops, someone died
+ pid_delete(Pid),
+ {noreply, Ts, next_timeout()};
+handle_info(_OtherMsg, Ts) -> % Other Msg's
+ {noreply, Ts, next_timeout()}.
+
+handle_cast(_Req, Ts) -> % Not predicted but handled
+ {noreply, Ts, next_timeout()}.
+
+-spec terminate(_, _) -> 'ok'.
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ %% According to the man for gen server no timer can be set here.
+ {ok, State}.
+
+%%
+%% timer_timeout(Timers, SysTime)
+%%
+%% Apply and remove already timed-out timers. A timer is a tuple
+%% {Time, BRef, Op, MFA}, where Time is in microseconds.
+%% Returns {Timeout, Timers}, where Timeout is in milliseconds.
+%%
+timer_timeout(SysTime) ->
+ case ets:first(?TIMER_TAB) of
+ '$end_of_table' ->
+ infinity;
+ {Time, _Ref} when Time > SysTime ->
+ Timeout = (Time - SysTime) div 1000,
+ %% Returned timeout must fit in a small int
+ erlang:min(Timeout, ?MAX_TIMEOUT);
+ Key ->
+ case ets:lookup(?TIMER_TAB, Key) of
+ [{Key, timeout, MFA}] ->
+ ets:delete(?TIMER_TAB,Key),
+ do_apply(MFA),
+ timer_timeout(SysTime);
+ [{{Time, Ref}, Repeat = {repeat, Interv, To}, MFA}] ->
+ ets:delete(?TIMER_TAB,Key),
+ NewTime = Time + Interv,
+ %% Update the interval entry (last in table)
+ ets:insert(?INTERVAL_TAB,{{interval,Ref},{NewTime,Ref},To}),
+ do_apply(MFA),
+ ets:insert(?TIMER_TAB, {{NewTime, Ref}, Repeat, MFA}),
+ timer_timeout(SysTime)
+ end
+ end.
+
+%%
+%% delete_ref
+%%
+
+delete_ref(BRef = {interval, _}) ->
+ case ets:lookup(?INTERVAL_TAB, BRef) of
+ [{_, BRef2, _Pid}] ->
+ ets:delete(?INTERVAL_TAB, BRef),
+ ets:delete(?TIMER_TAB, BRef2);
+ _ -> % TimerReference does not exist, do nothing
+ ok
+ end;
+delete_ref(BRef) ->
+ ets:delete(?TIMER_TAB,BRef).
+
+%%
+%% pid_delete
+%%
+
+pid_delete(Pid) ->
+ IntervalTimerList =
+ ets:select(?INTERVAL_TAB,
+ [{{'_', '_','$1'},
+ [{'==','$1',Pid}],
+ ['$_']}]),
+ lists:foreach(fun({IntKey, TimerKey, _ }) ->
+ ets:delete(?INTERVAL_TAB,IntKey),
+ ets:delete(?TIMER_TAB,TimerKey)
+ end, IntervalTimerList).
+
+%% Calculate time to the next timeout. Returned timeout must fit in a
+%% small int.
+
+next_timeout() ->
+ case ets:first(?TIMER_TAB) of
+ '$end_of_table' ->
+ infinity;
+ {Time, _} ->
+ erlang:min(positive((Time - system_time()) div 1000), ?MAX_TIMEOUT)
+ end.
+
+%% Help functions
+do_apply({M,F,A}) ->
+ case {M, F, A} of
+ {?MODULE, send, A} ->
+ %% If send op. send directly, (faster than spawn)
+ catch send(A);
+ {erlang, exit, [Name, Reason]} ->
+ catch exit(get_pid(Name), Reason);
+ _ ->
+ %% else spawn process with the operation
+ catch spawn(M,F,A)
+ end.
+
+positive(X) ->
+ erlang:max(X, 0).
+
+
+%%
+%% system_time() -> time in microseconds
+%%
+system_time() ->
+ {M,S,U} = erlang:now(),
+ 1000000 * (M*1000000 + S) + U.
+
+
+send([Pid, Msg]) ->
+ Pid ! Msg.
+
+get_pid(Name) when is_pid(Name) ->
+ Name;
+get_pid(undefined) ->
+ undefined;
+get_pid(Name) when is_atom(Name) ->
+ get_pid(whereis(Name));
+get_pid(_) ->
+ undefined.
+
+%%
+%% get_status() ->
+%% {{TimerTabName,TotalNumTimers},{IntervalTabName,NumIntervalTimers}}
+%%
+%% This function is for test purposes only; it is used by the test suite.
+%% There is a small possibility that there is a mismatch of one entry
+%% between the 2 tables if this call is made when the timer server is
+%% in the middle of a transaction
+
+-spec get_status() ->
+ {{?TIMER_TAB,non_neg_integer()},{?INTERVAL_TAB,non_neg_integer()}}.
+
+get_status() ->
+ Info1 = ets:info(?TIMER_TAB),
+ {value,{size,TotalNumTimers}} = lists:keysearch(size, 1, Info1),
+ Info2 = ets:info(?INTERVAL_TAB),
+ {value,{size,NumIntervalTimers}} = lists:keysearch(size, 1, Info2),
+ {{?TIMER_TAB,TotalNumTimers},{?INTERVAL_TAB,NumIntervalTimers}}.
diff --git a/lib/stdlib/src/unicode.erl b/lib/stdlib/src/unicode.erl
new file mode 100644
index 0000000000..09b1deff9c
--- /dev/null
+++ b/lib/stdlib/src/unicode.erl
@@ -0,0 +1,677 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(unicode).
+
+%% Implemented in the emulator:
+%% characters_to_binary/2 (will trap to characters_to_binary_int/2
+%% if InEncoding is not {latin1 | unicode | utf8})
+%% characters_to_list/2 (will trap to characters_to_list_int/2 if
+%% InEncoding is not {latin1 | unicode | utf8})
+%%
+
+-export([characters_to_list/1, characters_to_list_int/2, characters_to_binary/1,characters_to_binary_int/2, characters_to_binary/3,bom_to_encoding/1, encoding_to_bom/1]).
+
+
+characters_to_list(ML) ->
+ unicode:characters_to_list(ML,unicode).
+
+characters_to_list_int(ML, Encoding) ->
+ try
+ do_characters_to_list(ML,Encoding)
+ catch
+ error:AnyError ->
+ TheError = case AnyError of
+ system_limit ->
+ system_limit;
+ _ ->
+ badarg
+ end,
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [ML,Encoding])),
+ erlang:raise(error,TheError,[{Mod,characters_to_list,L}|Rest])
+ end.
+
+% XXX: Optimize me!
+do_characters_to_list(ML, Encoding) ->
+ case unicode:characters_to_binary(ML,Encoding) of
+ Bin when is_binary(Bin) ->
+ unicode:characters_to_list(Bin,utf8);
+ {error,Encoded,Rest} ->
+ {error,unicode:characters_to_list(Encoded,utf8),Rest};
+ {incomplete, Encoded2, Rest2} ->
+ {incomplete,unicode:characters_to_list(Encoded2,utf8),Rest2}
+ end.
+
+
+characters_to_binary(ML) ->
+ try
+ unicode:characters_to_binary(ML,unicode)
+ catch
+ error:AnyError ->
+ TheError = case AnyError of
+ system_limit ->
+ system_limit;
+ _ ->
+ badarg
+ end,
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [ML])),
+ erlang:raise(error,TheError,[{Mod,characters_to_binary,L}|Rest])
+ end.
+
+
+characters_to_binary_int(ML,InEncoding) ->
+ try
+ characters_to_binary_int(ML,InEncoding,unicode)
+ catch
+ error:AnyError ->
+ TheError = case AnyError of
+ system_limit ->
+ system_limit;
+ _ ->
+ badarg
+ end,
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [ML,InEncoding])),
+ erlang:raise(error,TheError,[{Mod,characters_to_binary,L}|Rest])
+ end.
+
+characters_to_binary(ML, latin1, latin1) when is_binary(ML) ->
+ ML;
+characters_to_binary(ML, latin1, Uni) when is_binary(ML) and ((Uni =:= utf8) or (Uni =:= unicode)) ->
+ case unicode:bin_is_7bit(ML) of
+ true ->
+ ML;
+ false ->
+ try
+ characters_to_binary_int(ML,latin1,utf8)
+ catch
+ error:AnyError ->
+ TheError = case AnyError of
+ system_limit ->
+ system_limit;
+ _ ->
+ badarg
+ end,
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [ML,latin1,Uni])),
+ erlang:raise(error,TheError,
+ [{Mod,characters_to_binary,L}|Rest])
+ end
+ end;
+characters_to_binary(ML,Uni,latin1) when is_binary(ML) and ((Uni =:= utf8) or (Uni =:= unicode)) ->
+ case unicode:bin_is_7bit(ML) of
+ true ->
+ ML;
+ false ->
+ try
+ characters_to_binary_int(ML,utf8,latin1)
+ catch
+ error:AnyError ->
+ TheError = case AnyError of
+ system_limit ->
+ system_limit;
+ _ ->
+ badarg
+ end,
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [ML,Uni,latin1])),
+ erlang:raise(error,TheError,
+ [{Mod,characters_to_binary,L}|Rest])
+ end
+ end;
+
+characters_to_binary(ML, InEncoding, OutEncoding) ->
+ try
+ characters_to_binary_int(ML,InEncoding,OutEncoding)
+ catch
+ error:AnyError ->
+ TheError = case AnyError of
+ system_limit ->
+ system_limit;
+ _ ->
+ badarg
+ end,
+ {'EXIT',{new_stacktrace,[{Mod,_,L}|Rest]}} =
+ (catch erlang:error(new_stacktrace,
+ [ML,InEncoding,OutEncoding])),
+ erlang:raise(error,TheError,[{Mod,characters_to_binary,L}|Rest])
+ end.
+
+characters_to_binary_int(ML, InEncoding, OutEncoding) when
+ InEncoding =:= latin1, OutEncoding =:= unicode;
+ InEncoding =:= latin1, OutEncoding =:= utf8;
+ InEncoding =:= unicode, OutEncoding =:= unicode;
+ InEncoding =:= unicode, OutEncoding =:= utf8;
+ InEncoding =:= utf8, OutEncoding =:= unicode;
+ InEncoding =:= utf8, OutEncoding =:= utf8 ->
+ unicode:characters_to_binary(ML,InEncoding);
+
+characters_to_binary_int(ML, InEncoding, OutEncoding) ->
+ {InTrans,Limit} = case OutEncoding of
+ latin1 -> {i_trans_chk(InEncoding),255};
+ _ -> {i_trans(InEncoding),case InEncoding of latin1 -> 255; _ -> 16#10FFFF end}
+ end,
+ OutTrans = o_trans(OutEncoding),
+ Res =
+ ml_map(ML,
+ fun(Part,Accum) when is_binary(Part) ->
+ case InTrans(Part) of
+ List when is_list(List) ->
+ Tail = OutTrans(List),
+ <<Accum/binary, Tail/binary>>;
+ {error, Translated, Rest} ->
+ Tail = OutTrans(Translated),
+ {error, <<Accum/binary,Tail/binary>>, Rest};
+ {incomplete, Translated, Rest, Missing} ->
+ Tail = OutTrans(Translated),
+ {incomplete, <<Accum/binary,Tail/binary>>, Rest,
+ Missing}
+ end;
+ (Part, Accum) when is_integer(Part), Part =< Limit ->
+ case OutTrans([Part]) of
+ Binary when is_binary(Binary) ->
+ <<Accum/binary, Binary/binary>>;
+ {error, _, [Part]} ->
+ {error,Accum,[Part]}
+ end;
+ (Part, Accum) ->
+ {error, Accum, [Part]}
+ end,<<>>),
+ case Res of
+ {incomplete,A,B,_} ->
+ {incomplete,A,B};
+ _ ->
+ Res
+ end.
+
+bom_to_encoding(<<239,187,191,_/binary>>) ->
+ {utf8,3};
+bom_to_encoding(<<0,0,254,255,_/binary>>) ->
+ {{utf32,big},4};
+bom_to_encoding(<<255,254,0,0,_/binary>>) ->
+ {{utf32,little},4};
+bom_to_encoding(<<254,255,_/binary>>) ->
+ {{utf16,big},2};
+bom_to_encoding(<<255,254,_/binary>>) ->
+ {{utf16,little},2};
+bom_to_encoding(Bin) when is_binary(Bin) ->
+ {latin1,0}.
+
+encoding_to_bom(unicode) ->
+ <<239,187,191>>;
+encoding_to_bom(utf8) ->
+ <<239,187,191>>;
+encoding_to_bom(utf16) ->
+ <<254,255>>;
+encoding_to_bom({utf16,big}) ->
+ <<254,255>>;
+encoding_to_bom({utf16,little}) ->
+ <<255,254>>;
+encoding_to_bom(utf32) ->
+ <<0,0,254,255>>;
+encoding_to_bom({utf32,big}) ->
+ <<0,0,254,255>>;
+encoding_to_bom({utf32,little}) ->
+ <<255,254,0,0>>;
+encoding_to_bom(latin1) ->
+ <<>>.
+
+
+cbv(utf8,<<1:1,1:1,0:1,_:5>>) ->
+ 1;
+cbv(utf8,<<1:1,1:1,1:1,0:1,_:4,R/binary>>) ->
+ case R of
+ <<>> ->
+ 2;
+ <<1:1,0:1,_:6>> ->
+ 1;
+ _ ->
+ false
+ end;
+cbv(utf8,<<1:1,1:1,1:1,1:1,0:1,_:3,R/binary>>) ->
+ case R of
+ <<>> ->
+ 3;
+ <<1:1,0:1,_:6>> ->
+ 2;
+ <<1:1,0:1,_:6,1:1,0:1,_:6>> ->
+ 1;
+ _ ->
+ false
+ end;
+cbv(utf8,_) ->
+ false;
+
+cbv({utf16,big},<<A:8>>) when A =< 215; A >= 224 ->
+ 1;
+cbv({utf16,big},<<54:6,_:2>>) ->
+ 3;
+cbv({utf16,big},<<54:6,_:10>>) ->
+ 2;
+cbv({utf16,big},<<54:6,_:10,55:6,_:2>>) ->
+ 1;
+cbv({utf16,big},_) ->
+ false;
+cbv({utf16,little},<<_:8>>) ->
+ 1; % or 3, we'll see
+cbv({utf16,little},<<_:8,54:6,_:2>>) ->
+ 2;
+cbv({utf16,little},<<_:8,54:6,_:2,_:8>>) ->
+ 1;
+cbv({utf16,little},_) ->
+ false;
+
+
+cbv({utf32,big}, <<0:8>>) ->
+ 3;
+cbv({utf32,big}, <<0:8,X:8>>) when X =< 16 ->
+ 2;
+cbv({utf32,big}, <<0:8,X:8,Y:8>>)
+ when X =< 16, ((X > 0) or ((Y =< 215) or (Y >= 224))) ->
+ 1;
+cbv({utf32,big},_) ->
+ false;
+cbv({utf32,little},<<_:8>>) ->
+ 3;
+cbv({utf32,little},<<_:8,_:8>>) ->
+ 2;
+cbv({utf32,little},<<X:8,255:8,0:8>>) when X =:= 254; X =:= 255 ->
+ false;
+cbv({utf32,little},<<_:8,Y:8,X:8>>)
+ when X =< 16, ((X > 0) or ((Y =< 215) or (Y >= 224))) ->
+ 1;
+cbv({utf32,little},_) ->
+ false.
+
+
+ml_map([],_,{{Inc,X},Accum}) ->
+ {incomplete, Accum, Inc, X};
+ml_map([],_Fun,Accum) ->
+ Accum;
+ml_map([Part|_] = Whole,_,{{Incomplete, _}, Accum}) when is_integer(Part) ->
+ {error, Accum, [Incomplete | Whole]};
+ml_map([Part|T],Fun,Accum) when is_integer(Part) ->
+ case Fun(Part,Accum) of
+ Bin when is_binary(Bin) ->
+ case ml_map(T,Fun,Bin) of
+ Bin2 when is_binary(Bin2) ->
+ Bin2;
+ {error, Converted, Rest} ->
+ {error, Converted, Rest};
+ {incomplete, Converted, Rest,X} ->
+ {incomplete, Converted, Rest,X}
+ end;
+ % Can not be incomplete - it's an integer
+ {error, Converted, Rest} ->
+ {error, Converted, [Rest|T]}
+ end;
+ml_map([Part|T],Fun,{{Incomplete,Missing}, Accum}) when is_binary(Part) ->
+ % Ok, how much is needed to fill in the incomplete part?
+ case byte_size(Part) of
+ N when N >= Missing ->
+ <<FillIn:Missing/binary,Trailing/binary>> = Part,
+ NewPart = <<Incomplete/binary,FillIn/binary>>,
+ ml_map([NewPart,Trailing|T], Fun, Accum);
+ M ->
+ NewIncomplete = <<Incomplete/binary, Part/binary>>,
+ NewMissing = Missing - M,
+ ml_map(T,Fun,{{NewIncomplete, NewMissing}, Accum})
+ end;
+ml_map([Part|T],Fun,Accum) when is_binary(Part), byte_size(Part) > 8192 ->
+ <<Part1:8192/binary,Part2/binary>> = Part,
+ ml_map([Part1,Part2|T],Fun,Accum);
+ml_map([Part|T],Fun,Accum) when is_binary(Part) ->
+ case Fun(Part,Accum) of
+ Bin when is_binary(Bin) ->
+ ml_map(T,Fun,Bin);
+ {incomplete, Converted, Rest, Missing} ->
+ ml_map(T,Fun,{{Rest, Missing},Converted});
+ {error, Converted, Rest} ->
+ {error, Converted, [Rest|T]}
+ end;
+ml_map([List|T],Fun,Accum) when is_list(List) ->
+ case ml_map(List,Fun,Accum) of
+ Bin when is_binary(Bin) ->
+ ml_map(T,Fun,Bin);
+ {error, Converted,Rest} ->
+ {error, Converted, [Rest | T]};
+ {incomplete, Converted,Rest,N} ->
+ ml_map(T,Fun,{{Rest,N},Converted})
+ end;
+ml_map(Bin,Fun,{{Incomplete,Missing},Accum}) when is_binary(Bin) ->
+ case byte_size(Bin) of
+ N when N >= Missing ->
+ ml_map([Incomplete,Bin],Fun,Accum);
+ M ->
+ {incomplete, Accum, <<Incomplete/binary, Bin/binary>>, Missing - M}
+ end;
+ml_map(Part,Fun,Accum) when is_binary(Part), byte_size(Part) > 8192 ->
+ <<Part1:8192/binary,Part2/binary>> = Part,
+ ml_map([Part1,Part2],Fun,Accum);
+ml_map(Bin,Fun,Accum) when is_binary(Bin) ->
+ Fun(Bin,Accum).
+
+
+
+
+
+i_trans(latin1) ->
+ fun(Bin) -> binary_to_list(Bin) end;
+i_trans(unicode) ->
+ i_trans(utf8);
+i_trans(utf8) ->
+ fun do_i_utf8/1;
+i_trans(utf16) ->
+ fun do_i_utf16_big/1;
+i_trans({utf16,big}) ->
+ fun do_i_utf16_big/1;
+i_trans({utf16,little}) ->
+ fun do_i_utf16_little/1;
+i_trans(utf32) ->
+ fun do_i_utf32_big/1;
+i_trans({utf32,big}) ->
+ fun do_i_utf32_big/1;
+i_trans({utf32,little}) ->
+ fun do_i_utf32_little/1.
+
+i_trans_chk(latin1) ->
+ fun(Bin) -> binary_to_list(Bin) end;
+i_trans_chk(unicode) ->
+ i_trans_chk(utf8);
+i_trans_chk(utf8) ->
+ fun do_i_utf8_chk/1;
+i_trans_chk(utf16) ->
+ fun do_i_utf16_big_chk/1;
+i_trans_chk({utf16,big}) ->
+ fun do_i_utf16_big_chk/1;
+i_trans_chk({utf16,little}) ->
+ fun do_i_utf16_little_chk/1;
+i_trans_chk(utf32) ->
+ fun do_i_utf32_big_chk/1;
+i_trans_chk({utf32,big}) ->
+ fun do_i_utf32_big_chk/1;
+i_trans_chk({utf32,little}) ->
+ fun do_i_utf32_little_chk/1.
+
+o_trans(latin1) ->
+ fun(L) -> list_to_binary(L) end;
+o_trans(unicode) ->
+ o_trans(utf8);
+o_trans(utf8) ->
+ fun(L) ->
+ do_o_binary(fun(One) ->
+ <<One/utf8>>
+ end, L)
+ end;
+
+o_trans(utf16) ->
+ fun(L) ->
+ do_o_binary(fun(One) ->
+ <<One/utf16>>
+ end, L)
+ end;
+o_trans({utf16,big}) ->
+ o_trans(utf16);
+o_trans({utf16,little}) ->
+ fun(L) ->
+ do_o_binary(fun(One) ->
+ <<One/utf16-little>>
+ end, L)
+ end;
+o_trans(utf32) ->
+ fun(L) ->
+ do_o_binary(fun(One) ->
+ <<One/utf32>>
+ end, L)
+ end;
+o_trans({utf32,big}) ->
+ o_trans(utf32);
+o_trans({utf32,little}) ->
+ fun(L) ->
+ do_o_binary(fun(One) ->
+ <<One/utf32-little>>
+ end, L)
+ end.
+
+do_o_binary(F,L) ->
+ case do_o_binary2(F,L) of
+ {Tag,List,R} ->
+ {Tag,erlang:iolist_to_binary(List),R};
+ List ->
+ erlang:iolist_to_binary(List)
+ end.
+
+do_o_binary2(_F,[]) ->
+ <<>>;
+do_o_binary2(F,[H|T]) ->
+ case (catch F(H)) of
+ {'EXIT',_} ->
+ {error,<<>>,[H|T]};
+ Bin when is_binary(Bin) ->
+ case do_o_binary2(F,T) of
+ {error,Bin2,Rest} ->
+ {error,[Bin|Bin2],Rest};
+ Bin3 ->
+ [Bin|Bin3]
+ end
+ end.
+
+%% Specific functions only allowing codepoints in latin1 range
+
+do_i_utf8_chk(<<>>) ->
+ [];
+do_i_utf8_chk(<<U/utf8,R/binary>>) when U =< 255 ->
+ case do_i_utf8_chk(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf8_chk(<<_/utf8,_/binary>> = Bin) ->
+ {error, [], Bin};
+do_i_utf8_chk(Bin) when is_binary(Bin) ->
+ case cbv(utf8,Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin,N};
+ false ->
+ {error, [], Bin}
+ end.
+do_i_utf16_big_chk(<<>>) ->
+ [];
+do_i_utf16_big_chk(<<U/utf16,R/binary>>) when U =< 255 ->
+ case do_i_utf16_big_chk(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf16_big_chk(<<_/utf16,_/binary>> = Bin) ->
+ {error, [], Bin};
+do_i_utf16_big_chk(Bin) when is_binary(Bin) ->
+ case cbv({utf16,big},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
+do_i_utf16_little_chk(<<>>) ->
+ [];
+do_i_utf16_little_chk(<<U/utf16-little,R/binary>>) when U =< 255 ->
+ case do_i_utf16_little_chk(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf16_little_chk(<<_/utf16-little,_/binary>> = Bin) ->
+ {error, [], Bin};
+do_i_utf16_little_chk(Bin) when is_binary(Bin) ->
+ case cbv({utf16,little},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
+
+
+do_i_utf32_big_chk(<<>>) ->
+ [];
+do_i_utf32_big_chk(<<U/utf32,R/binary>>) when U =< 255 ->
+ case do_i_utf32_big_chk(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf32_big_chk(<<_/utf32,_/binary>> = Bin) ->
+ {error, [], Bin};
+do_i_utf32_big_chk(Bin) when is_binary(Bin) ->
+ case cbv({utf32,big},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
+do_i_utf32_little_chk(<<>>) ->
+ [];
+do_i_utf32_little_chk(<<U/utf32-little,R/binary>>) when U =< 255 ->
+ case do_i_utf32_little_chk(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf32_little_chk(<<_/utf32-little,_/binary>> = Bin) ->
+ {error, [], Bin};
+do_i_utf32_little_chk(Bin) when is_binary(Bin) ->
+ case cbv({utf32,little},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
+
+
+%% General versions
+
+do_i_utf8(<<>>) ->
+ [];
+do_i_utf8(<<U/utf8,R/binary>>) ->
+ case do_i_utf8(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf8(Bin) when is_binary(Bin) ->
+ case cbv(utf8,Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin,N};
+ false ->
+ {error, [], Bin}
+ end.
+
+do_i_utf16_big(<<>>) ->
+ [];
+do_i_utf16_big(<<U/utf16,R/binary>>) ->
+ case do_i_utf16_big(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf16_big(Bin) when is_binary(Bin) ->
+ case cbv({utf16,big},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
+do_i_utf16_little(<<>>) ->
+ [];
+do_i_utf16_little(<<U/utf16-little,R/binary>>) ->
+ case do_i_utf16_little(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf16_little(Bin) when is_binary(Bin) ->
+ case cbv({utf16,little},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
+
+
+do_i_utf32_big(<<>>) ->
+ [];
+do_i_utf32_big(<<U/utf32,R/binary>>) ->
+ case do_i_utf32_big(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf32_big(Bin) when is_binary(Bin) ->
+ case cbv({utf32,big},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
+do_i_utf32_little(<<>>) ->
+ [];
+do_i_utf32_little(<<U/utf32-little,R/binary>>) ->
+ case do_i_utf32_little(R) of
+ {error,Trans,Rest} ->
+ {error, [U|Trans], Rest};
+ {incomplete,Trans,Rest,N} ->
+ {incomplete, [U|Trans], Rest, N};
+ L when is_list(L) ->
+ [U|L]
+ end;
+do_i_utf32_little(Bin) when is_binary(Bin) ->
+ case cbv({utf32,little},Bin) of
+ N when is_integer(N) ->
+ {incomplete, [], Bin, N};
+ false ->
+ {error, [], Bin}
+ end.
diff --git a/lib/stdlib/src/win32reg.erl b/lib/stdlib/src/win32reg.erl
new file mode 100644
index 0000000000..ee0d17bc94
--- /dev/null
+++ b/lib/stdlib/src/win32reg.erl
@@ -0,0 +1,386 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(win32reg).
+
+-export([open/1, close/1,
+ current_key/1, change_key/2, change_key_create/2,
+ sub_keys/1, delete_key/1,
+ value/2, values/1, set_value/3, delete_value/2,
+ expand/1,
+ format_error/1]).
+
+%% Key handles (always open).
+-define(hkey_classes_root, 16#80000000).
+-define(hkey_current_user, 16#80000001).
+-define(hkey_local_machine, 16#80000002).
+-define(hkey_users, 16#80000003).
+-define(hkey_performance_data, 16#80000004).
+-define(hkey_current_config, 16#80000005).
+-define(hkey_dyn_data, 16#80000006).
+
+%% Driver commands.
+-define(cmd_get_current, 0).
+-define(cmd_open_key, 1).
+-define(cmd_create_key, 2).
+-define(cmd_get_all_subkeys, 3).
+-define(cmd_get_value, 4).
+-define(cmd_get_all_values, 5).
+-define(cmd_set_value, 6).
+-define(cmd_delete_key, 7).
+-define(cmd_delete_value, 8).
+
+%% Data types.
+-define(reg_sc, 1).
+-define(reg_expand_sc, 2).
+-define(reg_binary, 3).
+-define(reg_dword, 4).
+
+%% Basic types internal to this file.
+-type open_mode() :: 'read' | 'write'.
+-type reg_handle() :: {'win32reg',port()}.
+-type name() :: string() | 'default'.
+-type value() :: string() | integer() | binary().
+
+%%% Exported functions.
+
+-spec open([open_mode()]) -> {'ok', reg_handle()} | {'error', 'enotsup'}.
+
+open(Modes) ->
+ case os:type() of
+ {win32, _} ->
+ case open_mode(Modes, []) of
+ {error, Reason} ->
+ {error, Reason};
+ ModeStr ->
+ P = open_port({spawn, "registry__drv__ " ++ ModeStr}, []),
+ {ok, {win32reg, P}}
+ end;
+ _ ->
+ {error, enotsup}
+ end.
+
+-spec close(reg_handle()) -> 'ok'.
+
+close({win32reg, Reg}) when is_port(Reg) ->
+ unlink(Reg),
+ exit(Reg, die),
+ ok.
+
+-spec current_key(reg_handle()) -> {'ok', string()}.
+
+current_key({win32reg, Reg}) when is_port(Reg) ->
+ Cmd = [?cmd_get_current],
+ Reg ! {self(), {command, Cmd}},
+ {state, Hkey, Name} = get_result(Reg),
+ Root = hkey_to_string(Hkey),
+ {ok, case Name of
+ [] -> Root;
+ _ -> Root ++ [$\\|Name]
+ end}.
+
+-spec change_key(reg_handle(), string()) -> 'ok' | {'error', atom()}.
+
+change_key({win32reg, Reg}, Key) when is_port(Reg) ->
+ change_key(Reg, ?cmd_open_key, Key).
+
+-spec change_key_create(reg_handle(), string()) -> 'ok' | {'error', atom()}.
+
+change_key_create({win32reg, Reg}, Key) when is_port(Reg) ->
+ change_key(Reg, ?cmd_create_key, Key).
+
+change_key(Reg, Cmd, Key) ->
+ case parse_key(Key, Reg) of
+ {ok, Hkey, Path} ->
+ Reg ! {self(), {command, [Cmd, i32(Hkey), Path, 0]}},
+ get_result(Reg);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+-spec sub_keys(reg_handle()) -> {'ok', [string()]} | {'error', atom()}.
+
+sub_keys({win32reg, Reg}) when is_port(Reg) ->
+ Cmd = [?cmd_get_all_subkeys],
+ Reg ! {self(), {command, Cmd}},
+ collect_keys(Reg, []).
+
+-spec delete_key(reg_handle()) -> 'ok' | {'error', atom()}.
+
+delete_key({win32reg, Reg}) when is_port(Reg) ->
+ Cmd = [?cmd_delete_key],
+ Reg ! {self(), {command, Cmd}},
+ get_result(Reg).
+
+-spec set_value(reg_handle(), name(), value()) -> 'ok' | {'error', atom()}.
+
+set_value({win32reg, Reg}, Name0, Value) when is_port(Reg) ->
+ Name =
+ case Name0 of
+ default -> [];
+ _ -> Name0
+ end,
+ {Type, V} = term_to_value(Value),
+ Cmd = [?cmd_set_value, Type, Name, 0, V],
+ Reg ! {self(), {command, Cmd}},
+ get_result(Reg).
+
+-spec value(reg_handle(), name()) -> {'ok', value()} | {'error', atom()}.
+
+value({win32reg, Reg}, Name) when is_port(Reg) ->
+ Cmd = [?cmd_get_value, Name, 0],
+ Reg ! {self(), {command, Cmd}},
+ case get_result(Reg) of
+ {value, {Name, Value}} ->
+ {ok, Value};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+-spec values(reg_handle()) -> {'ok', [{name(), value()}]} | {'error', atom()}.
+
+values({win32reg, Reg}) when is_port(Reg) ->
+ Cmd = [?cmd_get_all_values],
+ Reg ! {self(), {command, Cmd}},
+ collect_values(Reg, []).
+
+-spec delete_value(reg_handle(), name()) -> 'ok' | {'error', atom()}.
+
+delete_value({win32reg, Reg}, Name0) when is_port(Reg) ->
+ Name =
+ case Name0 of
+ default -> [];
+ _ -> Name0
+ end,
+ Cmd = [?cmd_delete_value, Name, 0],
+ Reg ! {self(), {command, Cmd}},
+ get_result(Reg).
+
+-spec expand(string()) -> string().
+
+expand(Value) ->
+ expand(Value, [], []).
+
+expand([$%, $%|Rest], [], Result) ->
+ expand(Rest, [], [$%|Result]);
+expand([$%, C|Rest], [], Result) ->
+ expand(Rest, [C], Result);
+expand([C|Rest], [], Result) ->
+ expand(Rest, [], [C|Result]);
+expand([$%|Rest], Env0, Result) ->
+ Env = lists:reverse(Env0),
+ case os:getenv(Env) of
+ false ->
+ expand(Rest, [], Result);
+ Value ->
+ expand(Rest, [], lists:reverse(Value)++Result)
+ end;
+expand([C|Rest], Env, Result) ->
+ expand(Rest, [C|Env], Result);
+expand([], [], Result) ->
+ lists:reverse(Result).
+
+-spec format_error(atom()) -> string().
+
+format_error(ErrorId) ->
+ erl_posix_msg:message(ErrorId).
+
+%%% Implementation.
+
+-spec collect_values(port(), [{name(), value()}]) ->
+ {'ok', [{name(), value()}]} | {'error', atom()}.
+
+collect_values(P, Result) ->
+ case get_result(P) of
+ ok ->
+ {ok, lists:reverse(Result)};
+ {value, ValueData} ->
+ collect_values(P, [ValueData|Result]);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+-spec collect_keys(port(), string()) -> {'ok', [string()]} | {'error', atom()}.
+
+collect_keys(P, Result) ->
+ case get_result(P) of
+ ok ->
+ {ok, lists:reverse(Result)};
+ {key, KeyData} ->
+ collect_keys(P, [KeyData|Result]);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+get_result(P) ->
+ receive
+ {P, {data, Data}} ->
+ get_result1(Data)
+ end.
+
+get_result1([$e|Reason]) ->
+ {error, list_to_atom(Reason)};
+get_result1([$o]) ->
+ ok;
+get_result1([$k|Name]) ->
+ {key, Name};
+get_result1([$v|Rest0]) ->
+ {ok, Type, Rest1} = i32_on_head(Rest0),
+ {ok, Name0, Value} = get_cstring(Rest1),
+ Name =
+ case Name0 of
+ [] -> default;
+ _ -> Name0
+ end,
+ {value, {Name, encode_value(Type, Value)}};
+get_result1([$s|Rest0]) ->
+ {ok, Hkey, Name} = i32_on_head(Rest0),
+ {state, Hkey, Name}.
+
+encode_value(?reg_sc, Value) ->
+ Value;
+encode_value(?reg_expand_sc, Value) ->
+ Value;
+encode_value(?reg_dword, Value) ->
+ i32(Value);
+encode_value(_, Value) ->
+ list_to_binary(Value).
+
+term_to_value(Int) when is_integer(Int) ->
+ {i32(?reg_dword), i32(Int)};
+term_to_value(String) when is_list(String) ->
+ {i32(?reg_sc), [String, 0]};
+term_to_value(Bin) when is_binary(Bin) ->
+ {i32(?reg_binary), Bin};
+term_to_value(_) ->
+ exit(badarg).
+
+get_cstring(List) ->
+ get_cstring(List, []).
+
+get_cstring([0|Rest], Result) ->
+ {ok, lists:reverse(Result), Rest};
+get_cstring([C|Rest], Result) ->
+ get_cstring(Rest, [C|Result]);
+get_cstring([], Result) ->
+ {ok, lists:reverse(Result), []}.
+
+i32(Int) when is_integer(Int) ->
+ [(Int bsr 24) band 255,
+ (Int bsr 16) band 255,
+ (Int bsr 8) band 255,
+ Int band 255];
+i32([X1, X2, X3, X4]) ->
+ (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4.
+
+i32_on_head([X1, X2, X3, X4 | Rest]) ->
+ {ok, (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4, Rest}.
+
+parse_key([$\\|Rest], _) ->
+ parse_root(Rest, []);
+parse_key(Key, Reg) ->
+ parse_relative(Key, Reg).
+
+parse_relative(Path, Reg) ->
+ Cmd = [?cmd_get_current],
+ Reg ! {self(), {command, Cmd}},
+ {state, RootHandle, Name} = get_result(Reg),
+ Original = split_key(Name),
+ Relative = lists:reverse(split_key(Path)),
+ case parse_relative1(Relative, Original) of
+ NewPath ->
+ {ok, RootHandle, NewPath}
+ %% XXX Error handling.
+ end.
+
+parse_relative1([".."|T1], [_|T2]) ->
+ parse_relative1(T1, T2);
+parse_relative1([Comp|Rest], Result) ->
+ parse_relative1(Rest, [Comp|Result]);
+parse_relative1([], Result) ->
+ reverse_and_join(Result, []).
+
+reverse_and_join([X|Rest], []) ->
+ reverse_and_join(Rest, [X]);
+reverse_and_join([X|Rest], Result) ->
+ reverse_and_join(Rest, [X, "\\" | Result]);
+reverse_and_join([], Result) ->
+ Result.
+
+split_key(Key) ->
+ split_key(Key, [], []).
+
+split_key([$\\|Rest], Current, Result) ->
+ split_key(Rest, [], [lists:reverse(Current)|Result]);
+split_key([C|Rest], Current, Result) ->
+ split_key(Rest, [C|Current], Result);
+split_key([], [], Result) ->
+ Result;
+split_key([], Current, Result) ->
+ [lists:reverse(Current)|Result].
+
+parse_root([$\\|Rest], Result) ->
+ Root =
+ case lists:reverse(Result) of
+ [$h, $k, $e, $y, $_|Root0] ->
+ Root0;
+ Root0 ->
+ Root0
+ end,
+ case root_to_handle(list_to_atom(Root)) of
+ false ->
+ {error, enoent};
+ Handle ->
+ {ok, Handle, Rest}
+ end;
+parse_root([C|Rest], Result) ->
+ parse_root(Rest, [C|Result]);
+parse_root([], Result) ->
+ parse_root([$\\], Result).
+
+root_to_handle(classes_root) -> ?hkey_classes_root;
+root_to_handle(hkcr) -> ?hkey_classes_root;
+root_to_handle(current_user) -> ?hkey_current_user;
+root_to_handle(hkcu) -> ?hkey_current_user;
+root_to_handle(local_machine) -> ?hkey_local_machine;
+root_to_handle(hklm) -> ?hkey_local_machine;
+root_to_handle(users) -> ?hkey_users;
+root_to_handle(hku) -> ?hkey_users;
+root_to_handle(current_config) -> ?hkey_current_config;
+root_to_handle(hkcc) -> ?hkey_current_config;
+root_to_handle(dyn_data) -> ?hkey_dyn_data;
+root_to_handle(hkdd) -> ?hkey_dyn_data;
+root_to_handle(performance_data) -> ?hkey_performance_data;
+root_to_handle(_) -> false.
+
+hkey_to_string(?hkey_classes_root) -> "\\hkey_classes_root";
+hkey_to_string(?hkey_current_user) -> "\\hkey_current_user";
+hkey_to_string(?hkey_local_machine) -> "\\hkey_local_machine";
+hkey_to_string(?hkey_users) -> "\\hkey_users";
+hkey_to_string(?hkey_performance_data) -> "\\hkey_performance_data";
+hkey_to_string(?hkey_current_config) -> "\\hkey_current_config";
+hkey_to_string(?hkey_dyn_data) -> "\\hkey_dyn_data".
+
+open_mode([read|Rest], Result) ->
+ open_mode(Rest, [$r|Result]);
+open_mode([write|Rest], Result) ->
+ open_mode(Rest, [$w|Result]);
+open_mode([], Result) ->
+ Result;
+open_mode(_, _) ->
+ {error, einval}.
diff --git a/lib/stdlib/src/zip.erl b/lib/stdlib/src/zip.erl
new file mode 100644
index 0000000000..f44d97c227
--- /dev/null
+++ b/lib/stdlib/src/zip.erl
@@ -0,0 +1,1600 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(zip).
+
+%% Basic api
+-export([unzip/1, unzip/2, extract/1, extract/2,
+ zip/2, zip/3, create/2, create/3,
+ list_dir/1, list_dir/2, table/1, table/2,
+ t/1, tt/1]).
+
+%% unzipping peicemeal
+-export([openzip_open/1, openzip_open/2,
+ openzip_get/1, openzip_get/2,
+ openzip_t/1, openzip_tt/1,
+ openzip_list_dir/1, openzip_list_dir/2,
+ openzip_close/1]).
+%% openzip_add/2]).
+
+%% zip server
+-export([zip_open/1, zip_open/2,
+ zip_get/1, zip_get/2,
+ zip_t/1, zip_tt/1,
+ zip_list_dir/1, zip_list_dir/2,
+ zip_close/1]).
+
+%% just for debugging zip server, not documented, not tested, not to be used
+-export([zip_get_state/1]).
+
+%% includes
+-include("file.hrl"). % #file_info
+-include("zip.hrl"). % #zip_file, #zip_comment
+
+%% max bytes fed to zlib
+-define(WRITE_BLOCK_SIZE, 8*1024).
+
+%% for debugging, to turn off catch
+-define(CATCH, catch).
+
+%% option sets
+-record(unzip_opts, {
+ output, % output object (fun)
+ input, % input object (fun)
+ file_filter, % file filter (boolean fun)
+ open_opts, % options passed to file:open
+ feedback, % feeback (fun)
+ cwd % directory to relate paths to
+ }).
+
+-record(zip_opts, {
+ output, % output object (fun)
+ input, % input object (fun)
+ comment, % zip-file comment
+ open_opts, % options passed to file:open
+ feedback, % feeback (fun)
+ cwd, % directory to relate paths to
+ compress, % compress files with these suffixes
+ uncompress % uncompress files with these suffixes
+ }).
+
+-record(list_dir_opts, {
+ input, % input object (fun)
+ raw_iterator, % applied to each dir entry
+ open_opts % options passed to file:open
+ }).
+
+-record(openzip_opts, {
+ output, % output object (fun)
+ open_opts, % file:open options
+ cwd % directory to relate paths to
+ }).
+
+% openzip record, state for an open zip-file
+-record(openzip, {
+ zip_comment, % zip archive comment
+ files, % filenames, infos, comments and offsets
+ in, % archive handle
+ input, % archive io object (fun)
+ output, % output io object (fun)
+ zlib, % handle to open zlib
+ cwd % directory to relate paths to
+ }).
+
+% Things that I would like to add to the public record #zip_file,
+% but can't as it would make things fail at upgrade.
+% Instead we use {#zip_file,#zip_file_extra} internally.
+-record(zip_file_extra, {
+ crc32 % checksum
+ }).
+
+%% max bytes read from files and archives (and fed to zlib)
+-define(READ_BLOCK_SIZE, 16*1024).
+
+%% -record(primzip_file, {
+%% name,
+%% offset,
+%% chunk_size
+%% }).
+
+%% -record(primzip, {
+%% zlib, % handle to the zlib port from zlib:open
+%% input, % fun/2 for file/memory input
+%% in, % input (file handle or binary)
+%% files % [#primzip_file]
+%% }).
+
+%% ZIP-file format records and defines
+
+%% compression methods
+-define(STORED, 0).
+-define(UNCOMPRESSED, 0).
+-define(SHRUNK, 1).
+-define(REDUCED_1, 2).
+-define(REDUCED_2, 3).
+-define(REDUCED_3, 4).
+-define(REDUCED_4, 5).
+-define(IMPLODED, 6).
+-define(TOKENIZED, 7).
+-define(DEFLATED, 8).
+-define(DEFLATED_64, 9).
+-define(PKWARE_IMPLODED, 10).
+-define(PKWARE_RESERVED, 11).
+-define(BZIP2_COMPRESSED, 12).
+
+%% zip-file records
+-define(LOCAL_FILE_MAGIC,16#04034b50).
+-define(LOCAL_FILE_HEADER_SZ,(4+2+2+2+2+2+4+4+4+2+2)).
+-define(LOCAL_FILE_HEADER_CRC32_OFFSET, 4+2+2+2+2+2).
+-record(local_file_header, {version_needed,
+ gp_flag,
+ comp_method,
+ last_mod_time,
+ last_mod_date,
+ crc32,
+ comp_size,
+ uncomp_size,
+ file_name_length,
+ extra_field_length}).
+
+-define(CENTRAL_FILE_HEADER_SZ,(4+2+2+2+2+2+2+4+4+4+2+2+2+2+2+4+4)).
+
+-define(CENTRAL_DIR_MAGIC, 16#06054b50).
+-define(CENTRAL_DIR_SZ, (4+2+2+2+2+4+4+2)).
+-define(CENTRAL_DIR_DIGITAL_SIG_MAGIC, 16#05054b50).
+-define(CENTRAL_DIR_DIGITAL_SIG_SZ, (4+2)).
+
+-define(CENTRAL_FILE_MAGIC, 16#02014b50).
+
+-record(cd_file_header, {version_made_by,
+ version_needed,
+ gp_flag,
+ comp_method,
+ last_mod_time,
+ last_mod_date,
+ crc32,
+ comp_size,
+ uncomp_size,
+ file_name_length,
+ extra_field_length,
+ file_comment_length,
+ disk_num_start,
+ internal_attr,
+ external_attr,
+ local_header_offset}).
+
+%% Unix extra fields (not yet supported)
+-define(UNIX_EXTRA_FIELD_TAG, 16#000d).
+-record(unix_extra_field, {atime,
+ mtime,
+ uid,
+ gid}).
+
+%% extended timestamps (not yet supported)
+-define(EXTENDED_TIMESTAMP_TAG, 16#5455).
+%% -record(extended_timestamp, {mtime,
+%% atime,
+%% ctime}).
+
+-define(END_OF_CENTRAL_DIR_MAGIC, 16#06054b50).
+-define(END_OF_CENTRAL_DIR_SZ, (4+2+2+2+2+4+4+2)).
+
+-record(eocd, {disk_num,
+ start_disk_num,
+ entries_on_disk,
+ entries,
+ size,
+ offset,
+ zip_comment_length}).
+
+
+%% Open a zip archive with options
+%%
+
+openzip_open(F) ->
+ openzip_open(F, []).
+
+openzip_open(F, Options) ->
+ case ?CATCH do_openzip_open(F, Options) of
+ {ok, OpenZip} ->
+ {ok, OpenZip};
+ Error ->
+ {error, Error}
+ end.
+
+do_openzip_open(F, Options) ->
+ Opts = get_openzip_options(Options),
+ #openzip_opts{output = Output, open_opts = OpO, cwd = CWD} = Opts,
+ Input = get_zip_input(F),
+ In0 = Input({open, F, OpO -- [write]}, []),
+ {[#zip_comment{comment = C} | Files], In1} =
+ get_central_dir(In0, fun raw_file_info_etc/5, Input),
+ Z = zlib:open(),
+ {ok, #openzip{zip_comment = C,
+ files = Files,
+ in = In1,
+ input = Input,
+ output = Output,
+ zlib = Z,
+ cwd = CWD}}.
+
+%% retrieve all files from an open archive
+openzip_get(OpenZip) ->
+ case ?CATCH do_openzip_get(OpenZip) of
+ {ok, Result} -> {ok, Result};
+ Error -> {error, Error}
+ end.
+
+do_openzip_get(#openzip{files = Files, in = In0, input = Input,
+ output = Output, zlib = Z, cwd = CWD}) ->
+ ZipOpts = #unzip_opts{output = Output, input = Input,
+ file_filter = fun all/1, open_opts = [],
+ feedback = fun silent/1, cwd = CWD},
+ R = get_z_files(Files, Z, In0, ZipOpts, []),
+ {ok, R};
+do_openzip_get(_) ->
+ throw(einval).
+
+%% retrieve a file from an open archive
+openzip_get(FileName, OpenZip) ->
+ case ?CATCH do_openzip_get(FileName, OpenZip) of
+ {ok, Result} -> {ok, Result};
+ Error -> {error, Error}
+ end.
+
+do_openzip_get(F, #openzip{files = Files, in = In0, input = Input,
+ output = Output, zlib = Z, cwd = CWD}) ->
+ %%case lists:keysearch(F, #zip_file.name, Files) of
+ case file_name_search(F, Files) of
+ {#zip_file{offset = Offset},_}=ZFile ->
+ In1 = Input({seek, bof, Offset}, In0),
+ case get_z_file(In1, Z, Input, Output, [], fun silent/1, CWD, ZFile) of
+ {file, R, _In2} -> {ok, R};
+ _ -> throw(file_not_found)
+ end;
+ _ -> throw(file_not_found)
+ end;
+do_openzip_get(_, _) ->
+ throw(einval).
+
+file_name_search(Name,Files) ->
+ case lists:dropwhile(fun({ZipFile,_}) -> ZipFile#zip_file.name =/= Name end,
+ Files) of
+ [ZFile|_] -> ZFile;
+ [] -> false
+ end.
+
+%% %% add a file to an open archive
+%% openzip_add(File, OpenZip) ->
+%% case ?CATCH do_openzip_add(File, OpenZip) of
+%% {ok, Result} -> {ok, Result};
+%% Error -> {error, Error}
+%% end.
+
+%% do_openzip_add(File, #open_zip{files = Files, in = In0,
+%% opts = Opts} = OpenZip0) ->
+%% throw(nyi),
+%% Z = zlib:open(),
+%% R = get_z_files(Files, In0, Z, Opts, []),
+%% zlib:close(Z),
+%% {ok, R};
+%% do_openzip_add(_, _) ->
+%% throw(einval).
+
+%% get file list from open archive
+openzip_list_dir(#openzip{zip_comment = Comment,
+ files = Files}) ->
+ {ZipFiles,_Extras} = lists:unzip(Files),
+ {ok, [#zip_comment{comment = Comment} | ZipFiles]};
+openzip_list_dir(_) ->
+ {error, einval}.
+
+openzip_list_dir(#openzip{files = Files}, [names_only]) ->
+ {ZipFiles,_Extras} = lists:unzip(Files),
+ Names = [Name || {#zip_file{name=Name},_} <- ZipFiles],
+ {ok, Names};
+openzip_list_dir(_, _) ->
+ {error, einval}.
+
+%% close an open archive
+openzip_close(#openzip{in = In0, input = Input, zlib = Z}) ->
+ Input(close, In0),
+ zlib:close(Z);
+openzip_close(_) ->
+ {error, einval}.
+
+%% Extract from a zip archive with options
+%%
+%% Accepted options:
+%% verbose, cooked, file_list, keep_old_files, file_filter, memory
+
+unzip(F) -> unzip(F, []).
+
+unzip(F, Options) ->
+ case ?CATCH do_unzip(F, Options) of
+ {ok, R} -> {ok, R};
+ Error -> {error, Error}
+ end.
+
+do_unzip(F, Options) ->
+ Opts = get_unzip_options(F, Options),
+ #unzip_opts{input = Input, open_opts = OpO} = Opts,
+ In0 = Input({open, F, OpO -- [write]}, []),
+ RawIterator = fun raw_file_info_etc/5,
+ {Info, In1} = get_central_dir(In0, RawIterator, Input),
+ %% get rid of zip-comment
+ Z = zlib:open(),
+ Files = get_z_files(Info, Z, In1, Opts, []),
+ zlib:close(Z),
+ Input(close, In1),
+ {ok, Files}.
+
+%% Create zip archive name F from Files or binaries
+%%
+%% Accepted options:
+%% verbose, cooked, memory, comment
+
+zip(F, Files) -> zip(F, Files, []).
+
+zip(F, Files, Options) ->
+ case ?CATCH do_zip(F, Files, Options) of
+ {ok, R} -> {ok, R};
+ Error -> {error, Error}
+ end.
+
+do_zip(F, Files, Options) ->
+ Opts = get_zip_options(Files, Options),
+ #zip_opts{output = Output, open_opts = OpO} = Opts,
+ Out0 = Output({open, F, OpO}, []),
+ Z = zlib:open(),
+ {Out1, LHS, Pos} = put_z_files(Files, Z, Out0, 0, Opts, []),
+ zlib:close(Z),
+ Out2 = put_central_dir(LHS, Pos, Out1, Opts),
+ Out3 = Output({close, F}, Out2),
+ {ok, Out3}.
+
+%% List zip directory contents
+%%
+%% Accepted options:
+%% cooked, file_filter, file_output (latter 2 undocumented)
+
+list_dir(F) -> list_dir(F, []).
+
+list_dir(F, Options) ->
+ case ?CATCH do_list_dir(F, Options) of
+ {ok, R} -> {ok, R};
+ Error -> {error, Error}
+ end.
+
+do_list_dir(F, Options) ->
+ Opts = get_list_dir_options(F, Options),
+ #list_dir_opts{input = Input, open_opts = OpO,
+ raw_iterator = RawIterator} = Opts,
+ In0 = Input({open, F, OpO}, []),
+ {Info, In1} = get_central_dir(In0, RawIterator, Input),
+ Input(close, In1),
+ {ok, Info}.
+
+%% Print zip directory in short form
+
+t(F) when is_pid(F) -> zip_t(F);
+t(F) when is_record(F, openzip) -> openzip_t(F);
+t(F) -> t(F, fun raw_short_print_info_etc/5).
+
+t(F, RawPrint) ->
+ case ?CATCH do_t(F, RawPrint) of
+ ok -> ok;
+ Error -> {error, Error}
+ end.
+
+do_t(F, RawPrint) ->
+ Input = get_input(F),
+ OpO = [raw],
+ In0 = Input({open, F, OpO}, []),
+ {_Info, In1} = get_central_dir(In0, RawPrint, Input),
+ Input(close, In1),
+ ok.
+
+%% Print zip directory in long form (like ls -l)
+
+tt(F) when is_pid(F) -> zip_tt(F);
+tt(F) when is_record(F, openzip) -> openzip_tt(F);
+tt(F) -> t(F, fun raw_long_print_info_etc/5).
+
+
+%% option utils
+get_unzip_opt([], Opts) ->
+ Opts;
+get_unzip_opt([verbose | Rest], Opts) ->
+ get_unzip_opt(Rest, Opts#unzip_opts{feedback = fun verbose_unzip/1});
+get_unzip_opt([cooked | Rest], #unzip_opts{open_opts = OpO} = Opts) ->
+ get_unzip_opt(Rest, Opts#unzip_opts{open_opts = OpO -- [raw]});
+get_unzip_opt([memory | Rest], Opts) ->
+ get_unzip_opt(Rest, Opts#unzip_opts{output = fun binary_io/2});
+get_unzip_opt([{cwd, CWD} | Rest], Opts) ->
+ get_unzip_opt(Rest, Opts#unzip_opts{cwd = CWD});
+get_unzip_opt([{file_filter, F} | Rest], Opts) ->
+ Filter1 = fun({ZipFile,_Extra}) -> F(ZipFile) end,
+ Filter2 = fun_and_1(Filter1, Opts#unzip_opts.file_filter),
+ get_unzip_opt(Rest, Opts#unzip_opts{file_filter = Filter2});
+get_unzip_opt([{file_list, L} | Rest], Opts) ->
+ FileInList = fun(F) -> file_in_list(F, L) end,
+ Filter = fun_and_1(FileInList, Opts#unzip_opts.file_filter),
+ get_unzip_opt(Rest, Opts#unzip_opts{file_filter = Filter});
+get_unzip_opt([keep_old_files | Rest], Opts) ->
+ Keep = fun keep_old_file/1,
+ Filter = fun_and_1(Keep, Opts#unzip_opts.file_filter),
+ get_unzip_opt(Rest, Opts#unzip_opts{file_filter = Filter});
+get_unzip_opt([Unknown | _Rest], _Opts) ->
+ throw({bad_option, Unknown}).
+
+get_list_dir_opt([], Opts) ->
+ Opts;
+get_list_dir_opt([cooked | Rest], #list_dir_opts{open_opts = OpO} = Opts) ->
+ get_list_dir_opt(Rest, Opts#list_dir_opts{open_opts = OpO -- [raw]});
+get_list_dir_opt([names_only | Rest], Opts) ->
+ get_list_dir_opt(Rest, Opts#list_dir_opts{
+ raw_iterator = fun(A, B, C, D, E) -> raw_name_only(A, B, C, D, E) end});
+%% get_list_dir_opt([{file_output, F} | Rest], Opts) ->
+%% get_list_dir_opt(Rest, Opts#list_dir_opts{file_output = F});
+%% get_list_dir_opt([{file_filter, F} | Rest], Opts) ->
+%% get_list_dir_opt(Rest, Opts#list_dir_opts{file_filter = F});
+get_list_dir_opt([Unknown | _Rest], _Opts) ->
+ throw({bad_option, Unknown}).
+
+get_zip_opt([], Opts) ->
+ Opts;
+get_zip_opt([verbose | Rest], Opts) ->
+ get_zip_opt(Rest, Opts#zip_opts{feedback = fun verbose_zip/1});
+get_zip_opt([cooked | Rest], #zip_opts{open_opts = OpO} = Opts) ->
+ get_zip_opt(Rest, Opts#zip_opts{open_opts = OpO -- [raw]});
+get_zip_opt([memory | Rest], Opts) ->
+ get_zip_opt(Rest, Opts#zip_opts{output = fun binary_io/2});
+get_zip_opt([{cwd, CWD} | Rest], Opts) ->
+ get_zip_opt(Rest, Opts#zip_opts{cwd = CWD});
+get_zip_opt([{comment, C} | Rest], Opts) ->
+ get_zip_opt(Rest, Opts#zip_opts{comment = C});
+get_zip_opt([{compress, Which} = O| Rest], Opts) ->
+ Which2 =
+ case Which of
+ all ->
+ all;
+ Suffixes when is_list(Suffixes) ->
+ lists:usort(Suffixes);
+ {add, Suffixes} when is_list(Suffixes) ->
+ lists:usort(Opts#zip_opts.compress ++ Suffixes);
+ {del, Suffixes} when is_list(Suffixes) ->
+ lists:usort(Opts#zip_opts.compress -- Suffixes);
+ _ ->
+ throw({bad_option, O})
+ end,
+ get_zip_opt(Rest, Opts#zip_opts{compress = Which2});
+get_zip_opt([{uncompress, Which} = O| Rest], Opts) ->
+ Which2 =
+ case Which of
+ all ->
+ all;
+ Suffixes when is_list(Suffixes) ->
+ lists:usort(Suffixes);
+ {add, Suffixes} when is_list(Suffixes) ->
+ lists:usort(Opts#zip_opts.uncompress ++ Suffixes);
+ {del, Suffixes} when is_list(Suffixes) ->
+ lists:usort(Opts#zip_opts.uncompress -- Suffixes);
+ _ ->
+ throw({bad_option, O})
+ end,
+ get_zip_opt(Rest, Opts#zip_opts{uncompress = Which2});
+get_zip_opt([Unknown | _Rest], _Opts) ->
+ throw({bad_option, Unknown}).
+
+
+%% feedback funs
+silent(_) -> ok.
+
+verbose_unzip(FN) -> io:format("extracting: ~p\n", [FN]).
+
+verbose_zip(FN) -> io:format("adding: ~p\n", [FN]).
+
+%% file filter funs
+all(_) -> true.
+
+file_in_list({#zip_file{name = FileName},_}, List) ->
+ lists:member(FileName, List);
+file_in_list(_, _) ->
+ false.
+
+keep_old_file({#zip_file{name = FileName},_}) ->
+ not (filelib:is_file(FileName) orelse filelib:is_dir(FileName));
+keep_old_file(_) ->
+ false.
+
+%% fun combiner
+fun_and_1(Fun1, Fun2) ->
+ fun(A) -> Fun1(A) andalso Fun2(A) end.
+
+%% getting options
+get_zip_options(Files, Options) ->
+ Suffixes = [".Z", ".zip", ".zoo", ".arc", ".lzh", ".arj"],
+ Opts = #zip_opts{output = fun file_io/2,
+ input = get_zip_input({files, Files}),
+ open_opts = [raw, write],
+ comment = "",
+ feedback = fun silent/1,
+ cwd = "",
+ compress = all,
+ uncompress = Suffixes
+ },
+ get_zip_opt(Options, Opts).
+
+get_unzip_options(F, Options) ->
+ Opts = #unzip_opts{file_filter = fun all/1,
+ output = fun file_io/2,
+ input = get_input(F),
+ open_opts = [raw],
+ feedback = fun silent/1,
+ cwd = ""
+ },
+ get_unzip_opt(Options, Opts).
+
+get_openzip_options(Options) ->
+ Opts = #openzip_opts{open_opts = [raw, read],
+ output = fun file_io/2,
+ cwd = ""},
+ get_openzip_opt(Options, Opts).
+
+get_input(F) when is_binary(F) ->
+ fun binary_io/2;
+get_input(F) when is_list(F) ->
+ fun file_io/2.
+
+get_zip_input({F, B}) when is_binary(B), is_list(F) ->
+ fun binary_io/2;
+get_zip_input(F) when is_list(F) ->
+ fun file_io/2;
+get_zip_input({files, []}) ->
+ fun binary_io/2;
+get_zip_input({files, [File | _]}) ->
+ get_zip_input(File).
+
+get_list_dir_options(F, Options) ->
+ Opts = #list_dir_opts{raw_iterator = fun raw_file_info_public/5,
+ input = get_input(F),
+ open_opts = [raw]},
+ get_list_dir_opt(Options, Opts).
+
+%% aliases for erl_tar compatibility
+table(F) -> list_dir(F).
+table(F, O) -> list_dir(F, O).
+create(F, Fs) -> zip(F, Fs).
+create(F, Fs, O) -> zip(F, Fs, O).
+extract(F) -> unzip(F).
+extract(F, O) -> unzip(F, O).
+
+
+%% put the central directory, at the end of the zip archive
+put_central_dir(LHS, Pos, Out0,
+ #zip_opts{output = Output, comment = Comment}) ->
+ {Out1, Sz} = put_cd_files_loop(LHS, Output, Out0, 0),
+ put_eocd(length(LHS), Pos, Sz, Comment, Output, Out1).
+
+put_cd_files_loop([], _Output, Out, Sz) ->
+ {Out, Sz};
+put_cd_files_loop([{LH, Name, Pos} | LHRest], Output, Out0, Sz0) ->
+ CDFH = cd_file_header_from_lh_and_pos(LH, Pos),
+ BCDFH = cd_file_header_to_bin(CDFH),
+ B = [<<?CENTRAL_FILE_MAGIC:32/little>>, BCDFH, Name],
+ Out1 = Output({write, B}, Out0),
+ Sz1 = Sz0 + ?CENTRAL_FILE_HEADER_SZ +
+ LH#local_file_header.file_name_length,
+ put_cd_files_loop(LHRest, Output, Out1, Sz1).
+
+%% put end marker of central directory, the last record in the archive
+put_eocd(N, Pos, Sz, Comment, Output, Out0) ->
+ %% BComment = list_to_binary(Comment),
+ CommentSz = length(Comment), % size(BComment),
+ EOCD = #eocd{disk_num = 0,
+ start_disk_num = 0,
+ entries_on_disk = N,
+ entries = N,
+ size = Sz,
+ offset = Pos,
+ zip_comment_length = CommentSz},
+ BEOCD = eocd_to_bin(EOCD),
+ B = [<<?END_OF_CENTRAL_DIR_MAGIC:32/little>>, BEOCD, Comment], % BComment],
+ Output({write, B}, Out0).
+
+get_filename({Name, _}, Type) ->
+ get_filename(Name, Type);
+get_filename(Name, regular) ->
+ Name;
+get_filename(Name, directory) ->
+ %% Ensure trailing slash
+ case lists:reverse(Name) of
+ [$/ | _Rev] -> Name;
+ Rev -> lists:reverse([$/ | Rev])
+ end.
+
+add_cwd(_CWD, {_Name, _} = F) -> F;
+add_cwd("", F) -> F;
+add_cwd(CWD, F) -> filename:join(CWD, F).
+
+%% already compressed data should be stored as is in archive,
+%% a simple name-match is used to check for this
+%% files smaller than 10 bytes are also stored, not compressed
+get_comp_method(_, N, _, _) when is_integer(N), N < 10 ->
+ ?STORED;
+get_comp_method(_, _, _, directory) ->
+ ?STORED;
+get_comp_method(F, _, #zip_opts{compress = Compress, uncompress = Uncompress}, _) ->
+ Ext = filename:extension(F),
+ Test = fun(Which) -> (Which =:= all) orelse lists:member(Ext, Which) end,
+ case Test(Compress) andalso not Test(Uncompress) of
+ true -> ?DEFLATED;
+ false -> ?STORED
+ end.
+
+put_z_files([], _Z, Out, Pos, _Opts, Acc) ->
+ {Out, lists:reverse(Acc, []), Pos};
+put_z_files([F | Rest], Z, Out0, Pos0,
+ #zip_opts{input = Input, output = Output, open_opts = OpO,
+ feedback = FB, cwd = CWD} = Opts, Acc) ->
+ In0 = [],
+ F1 = add_cwd(CWD, F),
+ FileInfo = Input({file_info, F1}, In0),
+ Type = FileInfo#file_info.type,
+ UncompSize =
+ case Type of
+ regular -> FileInfo#file_info.size;
+ directory -> 0
+ end,
+ FileName = get_filename(F, Type),
+ CompMethod = get_comp_method(FileName, UncompSize, Opts, Type),
+ LH = local_file_header_from_info_method_name(FileInfo, UncompSize, CompMethod, FileName),
+ BLH = local_file_header_to_bin(LH),
+ B = [<<?LOCAL_FILE_MAGIC:32/little>>, BLH],
+ Out1 = Output({write, B}, Out0),
+ Out2 = Output({write, FileName}, Out1),
+ {Out3, CompSize, CRC} = put_z_file(CompMethod, UncompSize, Out2, F1,
+ 0, Input, Output, OpO, Z, Type),
+ FB(FileName),
+ Patch = <<CRC:32/little, CompSize:32/little>>,
+ Out4 = Output({pwrite, Pos0 + ?LOCAL_FILE_HEADER_CRC32_OFFSET, Patch}, Out3),
+ Out5 = Output({seek, eof, 0}, Out4),
+ Pos1 = Pos0 + ?LOCAL_FILE_HEADER_SZ + LH#local_file_header.file_name_length,
+ Pos2 = Pos1 + CompSize,
+ LH2 = LH#local_file_header{comp_size = CompSize, crc32 = CRC},
+ ThisAcc = [{LH2, FileName, Pos0}],
+ {Out6, SubAcc, Pos3} =
+ case Type of
+ regular ->
+ {Out5, ThisAcc, Pos2};
+ directory ->
+ Files = Input({list_dir, F1}, []),
+ RevFiles = reverse_join_files(F, Files, []),
+ put_z_files(RevFiles, Z, Out5, Pos2, Opts, ThisAcc)
+ end,
+ Acc2 = lists:reverse(SubAcc) ++ Acc,
+ put_z_files(Rest, Z, Out6, Pos3, Opts, Acc2).
+
+reverse_join_files(Dir, [File | Files], Acc) ->
+ reverse_join_files(Dir, Files, [filename:join([Dir, File]) | Acc]);
+reverse_join_files(_Dir, [], Acc) ->
+ Acc.
+
+%% flag for zlib
+-define(MAX_WBITS, 15).
+
+%% compress a file
+put_z_file(_Method, Sz, Out, _F, Pos, _Input, _Output, _OpO, _Z, directory) ->
+ {Out, Pos + Sz, 0};
+put_z_file(_Method, 0, Out, _F, Pos, _Input, _Output, _OpO, _Z, regular) ->
+ {Out, Pos, 0};
+put_z_file(?STORED, UncompSize, Out0, F, Pos0, Input, Output, OpO, Z, regular) ->
+ In0 = [],
+ In1 = Input({open, F, OpO -- [write]}, In0),
+ CRC0 = zlib:crc32(Z, <<>>),
+ {Data, In2} = Input({read, UncompSize}, In1),
+ Out1 = Output({write, Data}, Out0),
+ CRC = zlib:crc32(Z, CRC0, Data),
+ Input(close, In2),
+ {Out1, Pos0+erlang:iolist_size(Data), CRC};
+put_z_file(?DEFLATED, UncompSize, Out0, F, Pos0, Input, Output, OpO, Z, regular) ->
+ In0 = [],
+ In1 = Input({open, F, OpO -- [write]}, In0),
+ ok = zlib:deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default),
+ {Out1, Pos1} =
+ put_z_data_loop(UncompSize, In1, Out0, Pos0, Input, Output, Z),
+ CRC = zlib:crc32(Z),
+ ok = zlib:deflateEnd(Z),
+ Input(close, In1),
+ {Out1, Pos1, CRC}.
+
+%% zlib is finished with the last chunk compressed
+get_sync(N, N) -> finish;
+get_sync(_, _) -> full.
+
+%% compress data
+put_z_data_loop(0, _In, Out, Pos, _Input, _Output, _Z) ->
+ {Out, Pos};
+put_z_data_loop(UncompSize, In0, Out0, Pos0, Input, Output, Z) ->
+ N = erlang:min(?WRITE_BLOCK_SIZE, UncompSize),
+ case Input({read, N}, In0) of
+ {eof, _In1} ->
+ {Out0, Pos0};
+ {Uncompressed, In1} ->
+ Compressed = zlib:deflate(Z, Uncompressed, get_sync(N, UncompSize)),
+ Sz = erlang:iolist_size(Compressed),
+ Out1 = Output({write, Compressed}, Out0),
+ put_z_data_loop(UncompSize - N, In1, Out1, Pos0 + Sz,
+ Input, Output, Z)
+ end.
+
+%% raw iterators over central dir
+
+%% name only
+raw_name_only(CD, FileName, _FileComment, _BExtraField, Acc)
+ when is_record(CD, cd_file_header) ->
+ [FileName | Acc];
+raw_name_only(EOCD, _, _Comment, _, Acc) when is_record(EOCD, eocd) ->
+ Acc.
+
+%% for printing directory (t/1)
+raw_short_print_info_etc(CD, FileName, _FileComment, _BExtraField, Acc)
+ when is_record(CD, cd_file_header) ->
+ print_file_name(FileName),
+ Acc;
+raw_short_print_info_etc(EOCD, X, Comment, Y, Acc) when is_record(EOCD, eocd) ->
+ raw_long_print_info_etc(EOCD, X, Comment, Y, Acc).
+
+print_file_name(FileName) ->
+ io:format("~s\n", [FileName]).
+
+
+%% for printing directory (tt/1)
+raw_long_print_info_etc(#cd_file_header{comp_size = CompSize,
+ uncomp_size = UncompSize,
+ last_mod_date = LMDate,
+ last_mod_time = LMTime},
+ FileName, FileComment, _BExtraField, Acc) ->
+ MTime = dos_date_time_to_datetime(LMDate, LMTime),
+ print_header(CompSize, MTime, UncompSize, FileName, FileComment),
+ Acc;
+raw_long_print_info_etc(EOCD, _, Comment, _, Acc) when is_record(EOCD, eocd) ->
+ print_comment(Comment),
+ Acc.
+
+print_header(CompSize, MTime, UncompSize, FileName, FileComment) ->
+ io:format("~8w ~s ~8w ~2w% ~s ~s\n",
+ [CompSize, time_to_string(MTime), UncompSize,
+ get_percent(CompSize, UncompSize), FileName, FileComment]).
+
+print_comment("") ->
+ ok;
+print_comment(Comment) ->
+ io:format("Archive comment: ~s\n", [Comment]).
+
+get_percent(_, 0) -> 100;
+get_percent(CompSize, Size) -> round(CompSize * 100 / Size).
+
+%% time formatting ("borrowed" from erl_tar.erl)
+time_to_string({{Y, Mon, Day}, {H, Min, _}}) ->
+ io_lib:format("~s ~2w ~s:~s ~w",
+ [month(Mon), Day, two_d(H), two_d(Min), Y]).
+
+two_d(N) ->
+ tl(integer_to_list(N + 100)).
+
+month(1) -> "Jan";
+month(2) -> "Feb";
+month(3) -> "Mar";
+month(4) -> "Apr";
+month(5) -> "May";
+month(6) -> "Jun";
+month(7) -> "Jul";
+month(8) -> "Aug";
+month(9) -> "Sep";
+month(10) -> "Oct";
+month(11) -> "Nov";
+month(12) -> "Dec".
+
+%% zip header functions
+cd_file_header_from_lh_and_pos(LH, Pos) ->
+ #local_file_header{version_needed = VersionNeeded,
+ gp_flag = GPFlag,
+ comp_method = CompMethod,
+ last_mod_time = LastModTime,
+ last_mod_date = LastModDate,
+ crc32 = CRC32,
+ comp_size = CompSize,
+ uncomp_size = UncompSize,
+ file_name_length = FileNameLength,
+ extra_field_length = ExtraFieldLength} = LH,
+ #cd_file_header{version_made_by = 20,
+ version_needed = VersionNeeded,
+ gp_flag = GPFlag,
+ comp_method = CompMethod,
+ last_mod_time = LastModTime,
+ last_mod_date = LastModDate,
+ crc32 = CRC32,
+ comp_size = CompSize,
+ uncomp_size = UncompSize,
+ file_name_length = FileNameLength,
+ extra_field_length = ExtraFieldLength,
+ file_comment_length = 0, % FileCommentLength,
+ disk_num_start = 1, % DiskNumStart,
+ internal_attr = 0, % InternalAttr,
+ external_attr = 0, % ExternalAttr,
+ local_header_offset = Pos}.
+
+cd_file_header_to_bin(
+ #cd_file_header{version_made_by = VersionMadeBy,
+ version_needed = VersionNeeded,
+ gp_flag = GPFlag,
+ comp_method = CompMethod,
+ last_mod_time = LastModTime,
+ last_mod_date = LastModDate,
+ crc32 = CRC32,
+ comp_size = CompSize,
+ uncomp_size = UncompSize,
+ file_name_length = FileNameLength,
+ extra_field_length = ExtraFieldLength,
+ file_comment_length = FileCommentLength,
+ disk_num_start = DiskNumStart,
+ internal_attr = InternalAttr,
+ external_attr = ExternalAttr,
+ local_header_offset = LocalHeaderOffset}) ->
+ <<VersionMadeBy:16/little,
+ VersionNeeded:16/little,
+ GPFlag:16/little,
+ CompMethod:16/little,
+ LastModTime:16/little,
+ LastModDate:16/little,
+ CRC32:32/little,
+ CompSize:32/little,
+ UncompSize:32/little,
+ FileNameLength:16/little,
+ ExtraFieldLength:16/little,
+ FileCommentLength:16/little,
+ DiskNumStart:16/little,
+ InternalAttr:16/little,
+ ExternalAttr:32/little,
+ LocalHeaderOffset:32/little>>.
+
+local_file_header_to_bin(
+ #local_file_header{version_needed = VersionNeeded,
+ gp_flag = GPFlag,
+ comp_method = CompMethod,
+ last_mod_time = LastModTime,
+ last_mod_date = LastModDate,
+ crc32 = CRC32,
+ comp_size = CompSize,
+ uncomp_size = UncompSize,
+ file_name_length = FileNameLength,
+ extra_field_length = ExtraFieldLength}) ->
+ <<VersionNeeded:16/little,
+ GPFlag:16/little,
+ CompMethod:16/little,
+ LastModTime:16/little,
+ LastModDate:16/little,
+ CRC32:32/little,
+ CompSize:32/little,
+ UncompSize:32/little,
+ FileNameLength:16/little,
+ ExtraFieldLength:16/little>>.
+
+eocd_to_bin(#eocd{disk_num = DiskNum,
+ start_disk_num = StartDiskNum,
+ entries_on_disk = EntriesOnDisk,
+ entries = Entries,
+ size = Size,
+ offset = Offset,
+ zip_comment_length = ZipCommentLength}) ->
+ <<DiskNum:16/little,
+ StartDiskNum:16/little,
+ EntriesOnDisk:16/little,
+ Entries:16/little,
+ Size:32/little,
+ Offset:32/little,
+ ZipCommentLength:16/little>>.
+
+%% put together a local file header
+local_file_header_from_info_method_name(#file_info{mtime = MTime},
+ UncompSize,
+ CompMethod, Name) ->
+ {ModDate, ModTime} = dos_date_time_from_datetime(MTime),
+ #local_file_header{version_needed = 20,
+ gp_flag = 0,
+ comp_method = CompMethod,
+ last_mod_time = ModTime,
+ last_mod_date = ModDate,
+ crc32 = -1,
+ comp_size = -1,
+ uncomp_size = UncompSize,
+ file_name_length = length(Name),
+ extra_field_length = 0}.
+
+
+%% small, simple, stupid zip-archive server
+server_loop(OpenZip) ->
+ receive
+ {From, {open, Archive, Options}} ->
+ case openzip_open(Archive, Options) of
+ {ok, NewOpenZip} ->
+ From ! {self(), {ok, self()}},
+ server_loop(NewOpenZip);
+ Error ->
+ From ! {self(), Error}
+ end;
+ {From, close} ->
+ From ! {self(), openzip_close(OpenZip)};
+ {From, get} ->
+ From ! {self(), openzip_get(OpenZip)},
+ server_loop(OpenZip);
+ {From, {get, FileName}} ->
+ From ! {self(), openzip_get(FileName, OpenZip)},
+ server_loop(OpenZip);
+ {From, list_dir} ->
+ From ! {self(), openzip_list_dir(OpenZip)},
+ server_loop(OpenZip);
+ {From, {list_dir, Opts}} ->
+ From ! {self(), openzip_list_dir(OpenZip, Opts)},
+ server_loop(OpenZip);
+ {From, get_state} ->
+ From ! {self(), OpenZip},
+ server_loop(OpenZip);
+ _ ->
+ {error, bad_msg}
+ end.
+
+zip_open(Archive) -> zip_open(Archive, []).
+
+zip_open(Archive, Options) ->
+ Pid = spawn(fun() -> server_loop(not_open) end),
+ request(self(), Pid, {open, Archive, Options}).
+
+zip_get(Pid) when is_pid(Pid) ->
+ request(self(), Pid, get).
+
+zip_close(Pid) when is_pid(Pid) ->
+ request(self(), Pid, close).
+
+zip_get(FileName, Pid) when is_pid(Pid) ->
+ request(self(), Pid, {get, FileName}).
+
+zip_list_dir(Pid) when is_pid(Pid) ->
+ request(self(), Pid, list_dir).
+
+zip_list_dir(Pid, Opts) when is_pid(Pid) ->
+ request(self(), Pid, {list_dir, Opts}).
+
+zip_get_state(Pid) when is_pid(Pid) ->
+ request(self(), Pid, get_state).
+
+request(Self, Pid, Req) ->
+ Pid ! {Self, Req},
+ receive
+ {Pid, R} -> R
+ end.
+
+zip_t(Pid) when is_pid(Pid) ->
+ Openzip = request(self(), Pid, get_state),
+ openzip_t(Openzip).
+
+zip_tt(Pid) when is_pid(Pid) ->
+ Openzip = request(self(), Pid, get_state),
+ openzip_tt(Openzip).
+
+openzip_tt(#openzip{zip_comment = ZipComment, files = Files}) ->
+ print_comment(ZipComment),
+ lists_foreach(fun({#zip_file{comp_size = CompSize,
+ name = FileName,
+ comment = FileComment,
+ info = FI},_}) ->
+ #file_info{size = UncompSize, mtime = MTime} = FI,
+ print_header(CompSize, MTime, UncompSize,
+ FileName, FileComment)
+ end, Files),
+ ok.
+
+openzip_t(#openzip{zip_comment = ZipComment, files = Files}) ->
+ print_comment(ZipComment),
+ lists_foreach(fun({#zip_file{name = FileName},_}) ->
+ print_file_name(FileName)
+ end, Files),
+ ok.
+
+lists_foreach(_, []) ->
+ ok;
+lists_foreach(F, [Hd|Tl]) ->
+ F(Hd),
+ lists_foreach(F, Tl).
+
+%% option utils
+get_openzip_opt([], Opts) ->
+ Opts;
+get_openzip_opt([cooked | Rest], #openzip_opts{open_opts = OO} = Opts) ->
+ get_openzip_opt(Rest, Opts#openzip_opts{open_opts = OO -- [raw]});
+get_openzip_opt([memory | Rest], Opts) ->
+ get_openzip_opt(Rest, Opts#openzip_opts{output = fun binary_io/2});
+get_openzip_opt([{cwd, CWD} | Rest], Opts) ->
+ get_openzip_opt(Rest, Opts#openzip_opts{cwd = CWD});
+get_openzip_opt([Unknown | _Rest], _Opts) ->
+ throw({bad_option, Unknown}).
+
+%% get the central directory from the archive
+get_central_dir(In0, RawIterator, Input) ->
+ {B, In1} = get_end_of_central_dir(In0, ?END_OF_CENTRAL_DIR_SZ, Input),
+ {EOCD, BComment} = eocd_and_comment_from_bin(B),
+ In2 = Input({seek, bof, EOCD#eocd.offset}, In1),
+ N = EOCD#eocd.entries,
+ Acc0 = [],
+ Out0 = RawIterator(EOCD, "", binary_to_list(BComment), <<>>, Acc0),
+ get_cd_loop(N, In2, RawIterator, Input, Out0).
+
+get_cd_loop(0, In, _RawIterator, _Input, Acc) ->
+ {lists:reverse(Acc), In};
+get_cd_loop(N, In0, RawIterator, Input, Acc0) ->
+ {B, In1} = Input({read, ?CENTRAL_FILE_HEADER_SZ}, In0),
+ BCD = case B of
+ <<?CENTRAL_FILE_MAGIC:32/little, XBCD/binary>> -> XBCD;
+ _ -> throw(bad_central_directory)
+ end,
+ CD = cd_file_header_from_bin(BCD),
+ FileNameLen = CD#cd_file_header.file_name_length,
+ ExtraLen = CD#cd_file_header.extra_field_length,
+ CommentLen = CD#cd_file_header.file_comment_length,
+ ToRead = FileNameLen + ExtraLen + CommentLen,
+ {B2, In2} = Input({read, ToRead}, In1),
+ {FileName, Comment, BExtra} =
+ get_name_extra_comment(B2, FileNameLen, ExtraLen, CommentLen),
+ Acc1 = RawIterator(CD, FileName, Comment, BExtra, Acc0),
+ get_cd_loop(N-1, In2, RawIterator, Input, Acc1).
+
+get_name_extra_comment(B, FileNameLen, ExtraLen, CommentLen) ->
+ case B of
+ <<BFileName:FileNameLen/binary,
+ BExtra:ExtraLen/binary,
+ BComment:CommentLen/binary>> ->
+ {binary_to_list(BFileName), binary_to_list(BComment), BExtra};
+ _ ->
+ throw(bad_central_directory)
+ end.
+
+%% get end record, containing the offset to the central directory
+%% the end record is always at the end of the file BUT alas it is
+%% of variable size (yes that's dumb!)
+get_end_of_central_dir(_In, Sz, _Input) when Sz > 16#ffff ->
+ throw(bad_eocd);
+get_end_of_central_dir(In0, Sz, Input) ->
+ In1 = Input({seek, eof, -Sz}, In0),
+ {B, In2} = Input({read, Sz}, In1),
+ case find_eocd_header(B) of
+ none ->
+ get_end_of_central_dir(In2, Sz+Sz, Input);
+ Header ->
+ {Header, In2}
+ end.
+
+%% find the end record by matching for it
+find_eocd_header(<<?END_OF_CENTRAL_DIR_MAGIC:32/little, Rest/binary>>) ->
+ Rest;
+find_eocd_header(<<_:8, Rest/binary>>)
+ when byte_size(Rest) > ?END_OF_CENTRAL_DIR_SZ-4 ->
+ find_eocd_header(Rest);
+find_eocd_header(_) ->
+ none.
+
+%% from a central directory record, filter and accumulate what we need
+
+%% with zip_file_extra
+raw_file_info_etc(CD, FileName, FileComment, BExtraField, Acc)
+ when is_record(CD, cd_file_header) ->
+ #cd_file_header{comp_size = CompSize,
+ local_header_offset = Offset,
+ crc32 = CRC} = CD,
+ FileInfo = cd_file_header_to_file_info(FileName, CD, BExtraField),
+ [{#zip_file{name = FileName, info = FileInfo, comment = FileComment,
+ offset = Offset, comp_size = CompSize}, #zip_file_extra{crc32 = CRC}} | Acc];
+raw_file_info_etc(EOCD, _, Comment, _, Acc) when is_record(EOCD, eocd) ->
+ [#zip_comment{comment = Comment} | Acc].
+
+%% without zip_file_extra
+raw_file_info_public(CD, FileName, FileComment, BExtraField, Acc0) ->
+ [H1|T] = raw_file_info_etc(CD,FileName,FileComment,BExtraField,Acc0),
+ H2 = case H1 of
+ {ZF,Extra} when is_record(Extra,zip_file_extra) -> ZF;
+ Other -> Other
+ end,
+ [H2|T].
+
+
+%% make a file_info from a central directory header
+cd_file_header_to_file_info(FileName,
+ #cd_file_header{uncomp_size = UncompSize,
+ last_mod_time = ModTime,
+ last_mod_date = ModDate},
+ ExtraField) ->
+ T = dos_date_time_to_datetime(ModDate, ModTime),
+ Type =
+ case lists:last(FileName) of
+ $/ -> directory;
+ _ -> regular
+ end,
+ FI = #file_info{size = UncompSize,
+ type = Type,
+ access = read_write,
+ atime = T,
+ mtime = T,
+ ctime = T,
+ mode = 8#066,
+ links = 1,
+ major_device = 0,
+ minor_device = 0,
+ inode = 0,
+ uid = 0,
+ gid = 0},
+ add_extra_info(FI, ExtraField).
+
+%% add extra info to file (some day when we implement it)
+add_extra_info(FI, <<?EXTENDED_TIMESTAMP_TAG:16/little, _Rest/binary>>) ->
+ FI; % not yet supported, some other day...
+add_extra_info(FI, <<?UNIX_EXTRA_FIELD_TAG:16/little, Rest/binary>>) ->
+ _UnixExtra = unix_extra_field_and_var_from_bin(Rest),
+ FI; % not yet supported, and not widely used
+add_extra_info(FI, _) ->
+ FI.
+
+
+
+%% get all files using file list
+%% (the offset list is already filtered on which file to get... isn't it?)
+get_z_files([], _Z, _In, _Opts, Acc) ->
+ lists:reverse(Acc);
+get_z_files([#zip_comment{comment = _} | Rest], Z, In, Opts, Acc) ->
+ get_z_files(Rest, Z, In, Opts, Acc);
+get_z_files([{#zip_file{offset = Offset},_} = ZFile | Rest], Z, In0,
+ #unzip_opts{input = Input, output = Output, open_opts = OpO,
+ file_filter = Filter, feedback = FB,
+ cwd = CWD} = Opts, Acc0) ->
+ case Filter(ZFile) of
+ true ->
+ In1 = Input({seek, bof, Offset}, In0),
+ {In2, Acc1} =
+ case get_z_file(In1, Z, Input, Output, OpO, FB, CWD, ZFile) of
+ {file, GZD, Inx} -> {Inx, [GZD | Acc0]};
+ {dir, Inx} -> {Inx, Acc0}
+ end,
+ get_z_files(Rest, Z, In2, Opts, Acc1);
+ _ ->
+ get_z_files(Rest, Z, In0, Opts, Acc0)
+ end.
+
+%% get a file from the archive, reading chunks
+get_z_file(In0, Z, Input, Output, OpO, FB, CWD, {ZipFile,Extra}) ->
+ case Input({read, ?LOCAL_FILE_HEADER_SZ}, In0) of
+ {eof, In1} ->
+ {eof, In1};
+ %% Local File Header
+ {<<?LOCAL_FILE_MAGIC:32/little, B/binary>>, In1} ->
+ LH = local_file_header_from_bin(B),
+ #local_file_header{gp_flag = GPFlag,
+ comp_method = CompMethod,
+ file_name_length = FileNameLen,
+ extra_field_length = ExtraLen} = LH,
+
+ {CompSize,CRC32} = case GPFlag band 8 =:= 8 of
+ true -> {ZipFile#zip_file.comp_size,
+ Extra#zip_file_extra.crc32};
+ false -> {LH#local_file_header.comp_size,
+ LH#local_file_header.crc32}
+ end,
+ {BFileN, In3} = Input({read, FileNameLen + ExtraLen}, In1),
+ {FileName, _} = get_file_name_extra(FileNameLen, ExtraLen, BFileN),
+ FileName1 = add_cwd(CWD, FileName),
+ case lists:last(FileName) of
+ $/ ->
+ %% perhaps this should always be done?
+ Output({ensure_dir,FileName1},[]),
+ {dir, In3};
+ _ ->
+ %% FileInfo = local_file_header_to_file_info(LH)
+ %%{Out, In4, CRC, UncompSize} =
+ {Out, In4, CRC, _UncompSize} =
+ get_z_data(CompMethod, In3, FileName1,
+ CompSize, Input, Output, OpO, Z),
+ In5 = skip_z_data_descriptor(GPFlag, Input, In4),
+ %% TODO This should be fixed some day:
+ %% In5 = Input({set_file_info, FileName, FileInfo#file_info{size=UncompSize}}, In4),
+ FB(FileName),
+ CRC =:= CRC32 orelse throw({bad_crc, FileName}),
+ {file, Out, In5}
+ end;
+ _ ->
+ throw(bad_local_file_header)
+ end.
+
+
+get_file_name_extra(FileNameLen, ExtraLen, B) ->
+ case B of
+ <<BFileName:FileNameLen/binary, BExtra:ExtraLen/binary>> ->
+ {binary_to_list(BFileName), BExtra};
+ _ ->
+ throw(bad_file_header)
+ end.
+
+%% get compressed or stored data
+get_z_data(?DEFLATED, In0, FileName, CompSize, Input, Output, OpO, Z) ->
+ ok = zlib:inflateInit(Z, -?MAX_WBITS),
+ Out0 = Output({open, FileName, [write | OpO]}, []),
+ {In1, Out1, UncompSize} = get_z_data_loop(CompSize, 0, In0, Out0, Input, Output, Z),
+ CRC = zlib:crc32(Z),
+ ?CATCH zlib:inflateEnd(Z),
+ Out2 = Output({close, FileName}, Out1),
+ {Out2, In1, CRC, UncompSize};
+get_z_data(?STORED, In0, FileName, CompSize, Input, Output, OpO, Z) ->
+ Out0 = Output({open, FileName, [write | OpO]}, []),
+ CRC0 = zlib:crc32(Z, <<>>),
+ {In1, Out1, CRC} = copy_data_loop(CompSize, In0, Out0, Input, Output,
+ CRC0, Z),
+ Out2 = Output({close, FileName}, Out1),
+ {Out2, In1, CRC, CompSize};
+get_z_data(_, _, _, _, _, _, _, _) ->
+ throw(bad_file_header).
+
+copy_data_loop(0, In, Out, _Input, _Output, CRC, _Z) ->
+ {In, Out, CRC};
+copy_data_loop(CompSize, In0, Out0, Input, Output, CRC0, Z) ->
+ N = erlang:min(?READ_BLOCK_SIZE, CompSize),
+ case Input({read, N}, In0) of
+ {eof, In1} -> {Out0, In1};
+ {Uncompressed, In1} ->
+ CRC1 = zlib:crc32(Z, CRC0, Uncompressed),
+ Out1 = Output({write, Uncompressed}, Out0),
+ copy_data_loop(CompSize-N, In1, Out1, Input, Output, CRC1, Z)
+ end.
+
+get_z_data_loop(0, UncompSize, In, Out, _Input, _Output, _Z) ->
+ {In, Out, UncompSize};
+get_z_data_loop(CompSize, UncompSize, In0, Out0, Input, Output, Z) ->
+ N = erlang:min(?READ_BLOCK_SIZE, CompSize),
+ case Input({read, N}, In0) of
+ {eof, In1} ->
+ {Out0, In1};
+ {Compressed, In1} ->
+ Uncompressed = zlib:inflate(Z, Compressed),
+ Out1 = Output({write, Uncompressed}, Out0),
+ get_z_data_loop(CompSize-N, UncompSize + iolist_size(Uncompressed),
+ In1, Out1, Input, Output, Z)
+ end.
+
+
+%% skip data descriptor if any
+skip_z_data_descriptor(GPFlag, Input, In0) when GPFlag band 8 =:= 8 ->
+ Input({seek, cur, 12}, In0);
+skip_z_data_descriptor(_GPFlag, _Input, In0) ->
+ In0.
+
+%% convert between erlang datetime and the MSDOS date and time
+%% that's stored in the zip archive
+%% MSDOS Time MSDOS Date
+%% bit 0 - 4 5 - 10 11 - 15 16 - 20 21 - 24 25 - 31
+%% value second minute hour day (1 - 31) month (1 - 12) years from 1980
+dos_date_time_to_datetime(DosDate, DosTime) ->
+ <<Hour:5, Min:6, Sec:5>> = <<DosTime:16>>,
+ <<YearFrom1980:7, Month:4, Day:5>> = <<DosDate:16>>,
+ {{YearFrom1980+1980, Month, Day},
+ {Hour, Min, Sec}}.
+
+dos_date_time_from_datetime({{Year, Month, Day}, {Hour, Min, Sec}}) ->
+ YearFrom1980 = Year-1980,
+ <<DosTime:16>> = <<Hour:5, Min:6, Sec:5>>,
+ <<DosDate:16>> = <<YearFrom1980:7, Month:4, Day:5>>,
+ {DosDate, DosTime}.
+
+unix_extra_field_and_var_from_bin(<<TSize:16/little,
+ ATime:32/little,
+ MTime:32/little,
+ UID:16/little,
+ GID:16/little,
+ Var:TSize/binary>>) ->
+ {#unix_extra_field{atime = ATime,
+ mtime = MTime,
+ uid = UID,
+ gid = GID},
+ Var};
+unix_extra_field_and_var_from_bin(_) ->
+ throw(bad_unix_extra_field).
+
+
+%% A pwrite-like function for iolists (used by memory-option)
+
+split_iolist(B, Pos) when is_binary(B) ->
+ split_binary(B, Pos);
+split_iolist(L, Pos) when is_list(L) ->
+ splitter([], L, Pos).
+
+splitter(Left, Right, 0) ->
+ {Left, Right};
+splitter(Left, [A | Right], RelPos) when is_list(A) or is_binary(A) ->
+ Sz = erlang:iolist_size(A),
+ case Sz > RelPos of
+ true ->
+ {Leftx, Rightx} = split_iolist(A, RelPos),
+ {[Left | Leftx], [Rightx, Right]};
+ _ ->
+ splitter([Left | A], Right, RelPos - Sz)
+ end;
+splitter(Left, [A | Right], RelPos) when is_integer(A) ->
+ splitter([Left, A], Right, RelPos - 1);
+splitter(Left, Right, RelPos) when is_binary(Right) ->
+ splitter(Left, [Right], RelPos).
+
+skip_iolist(B, Pos) when is_binary(B) ->
+ case B of
+ <<_:Pos/binary, Bin/binary>> -> Bin;
+ _ -> <<>>
+ end;
+skip_iolist(L, Pos) when is_list(L) ->
+ skipper(L, Pos).
+
+skipper(Right, 0) ->
+ Right;
+skipper([A | Right], RelPos) when is_list(A) or is_binary(A) ->
+ Sz = erlang:iolist_size(A),
+ case Sz > RelPos of
+ true ->
+ Rightx = skip_iolist(A, RelPos),
+ [Rightx, Right];
+ _ ->
+ skip_iolist(Right, RelPos - Sz)
+ end;
+skipper([A | Right], RelPos) when is_integer(A) ->
+ skip_iolist(Right, RelPos - 1).
+
+pwrite_iolist(Iolist, Pos, Bin) ->
+ {Left, Right} = split_iolist(Iolist, Pos),
+ Sz = erlang:iolist_size(Bin),
+ R = skip_iolist(Right, Sz),
+ [Left, Bin | R].
+
+pwrite_binary(B, Pos, Bin) ->
+ erlang:iolist_to_binary(pwrite_iolist(B, Pos, Bin)).
+
+
+%% ZIP header manipulations
+eocd_and_comment_from_bin(<<DiskNum:16/little,
+ StartDiskNum:16/little,
+ EntriesOnDisk:16/little,
+ Entries:16/little,
+ Size:32/little,
+ Offset:32/little,
+ ZipCommentLength:16/little,
+ Comment:ZipCommentLength/binary>>) ->
+ {#eocd{disk_num = DiskNum,
+ start_disk_num = StartDiskNum,
+ entries_on_disk = EntriesOnDisk,
+ entries = Entries,
+ size = Size,
+ offset = Offset,
+ zip_comment_length = ZipCommentLength},
+ Comment};
+eocd_and_comment_from_bin(_) ->
+ throw(bad_eocd).
+
+cd_file_header_from_bin(<<VersionMadeBy:16/little,
+ VersionNeeded:16/little,
+ GPFlag:16/little,
+ CompMethod:16/little,
+ LastModTime:16/little,
+ LastModDate:16/little,
+ CRC32:32/little,
+ CompSize:32/little,
+ UncompSize:32/little,
+ FileNameLength:16/little,
+ ExtraFieldLength:16/little,
+ FileCommentLength:16/little,
+ DiskNumStart:16/little,
+ InternalAttr:16/little,
+ ExternalAttr:32/little,
+ LocalHeaderOffset:32/little>>) ->
+ #cd_file_header{version_made_by = VersionMadeBy,
+ version_needed = VersionNeeded,
+ gp_flag = GPFlag,
+ comp_method = CompMethod,
+ last_mod_time = LastModTime,
+ last_mod_date = LastModDate,
+ crc32 = CRC32,
+ comp_size = CompSize,
+ uncomp_size = UncompSize,
+ file_name_length = FileNameLength,
+ extra_field_length = ExtraFieldLength,
+ file_comment_length = FileCommentLength,
+ disk_num_start = DiskNumStart,
+ internal_attr = InternalAttr,
+ external_attr = ExternalAttr,
+ local_header_offset = LocalHeaderOffset};
+cd_file_header_from_bin(_) ->
+ throw(bad_cd_file_header).
+
+local_file_header_from_bin(<<VersionNeeded:16/little,
+ GPFlag:16/little,
+ CompMethod:16/little,
+ LastModTime:16/little,
+ LastModDate:16/little,
+ CRC32:32/little,
+ CompSize:32/little,
+ UncompSize:32/little,
+ FileNameLength:16/little,
+ ExtraFieldLength:16/little>>) ->
+ #local_file_header{version_needed = VersionNeeded,
+ gp_flag = GPFlag,
+ comp_method = CompMethod,
+ last_mod_time = LastModTime,
+ last_mod_date = LastModDate,
+ crc32 = CRC32,
+ comp_size = CompSize,
+ uncomp_size = UncompSize,
+ file_name_length = FileNameLength,
+ extra_field_length = ExtraFieldLength};
+local_file_header_from_bin(_) ->
+ throw(bad_local_file_header).
+
+%% make a file_info from a local directory header
+%% local_file_header_to_file_info(
+%% #local_file_header{last_mod_time = ModTime,
+%% last_mod_date = ModDate,
+%% uncomp_size = UncompSize}) ->
+%% T = dos_date_time_to_datetime(ModDate, ModTime),
+%% FI = #file_info{size = UncompSize,
+%% type = regular,
+%% access = read_write,
+%% atime = T,
+%% mtime = T,
+%% ctime = T,
+%% mode = 8#066,
+%% links = 1,
+%% major_device = 0,
+%% minor_device = 0,
+%% inode = 0,
+%% uid = 0,
+%% gid = 0},
+%% FI.
+
+%% io functions
+binary_io({file_info, {_Filename, _B, #file_info{} = FI}}, _A) ->
+ FI;
+binary_io({file_info, {_Filename, B}}, A) ->
+ binary_io({file_info, B}, A);
+binary_io({file_info, B}, _) ->
+ {Type, Size} =
+ if
+ is_binary(B) -> {regular, byte_size(B)};
+ B =:= directory -> {directory, 0}
+ end,
+ Now = calendar:local_time(),
+ #file_info{size = Size, type = Type,
+ access = read_write, atime = Now,
+ mtime = Now, ctime = Now, mode = 0,
+ links = 1, major_device = 0,
+ minor_device = 0, inode = 0,
+ uid = 0, gid = 0};
+binary_io({open, {_Filename, B, _FI}, _Opts}, _) ->
+ {0, B};
+binary_io({open, {_Filename, B}, _Opts}, _) ->
+ {0, B};
+binary_io({open, B, _Opts}, _) when is_binary(B) ->
+ {0, B};
+binary_io({open, Filename, _Opts}, _) when is_list(Filename) ->
+ {0, <<>>};
+binary_io({read, N}, {Pos, B}) when Pos >= byte_size(B) ->
+ {eof, {Pos+N, B}};
+binary_io({read, N}, {Pos, B}) when Pos + N > byte_size(B) ->
+ <<_:Pos/binary, Read/binary>> = B,
+ {Read, {byte_size(B), B}};
+binary_io({pread, Pos, N}, {OldPos, B}) ->
+ case B of
+ <<_:Pos/binary, Read:N/binary, _Rest/binary>> ->
+ {Read, {Pos+N, B}};
+ _ ->
+ {eof, {OldPos, B}}
+ end;
+binary_io({read, N}, {Pos, B}) ->
+ <<_:Pos/binary, Read:N/binary, _/binary>> = B,
+ {Read, {Pos+N, B}};
+binary_io({seek, bof, Pos}, {_OldPos, B}) ->
+ {Pos, B};
+binary_io({seek, cur, Pos}, {OldPos, B}) ->
+ {OldPos + Pos, B};
+binary_io({seek, eof, Pos}, {_OldPos, B}) ->
+ {byte_size(B) + Pos, B};
+binary_io({pwrite, Pos, Data}, {OldPos, B}) ->
+ {OldPos, pwrite_binary(B, Pos, Data)};
+binary_io({write, Data}, {Pos, B}) ->
+ {Pos + erlang:iolist_size(Data), pwrite_binary(B, Pos, Data)};
+binary_io(close, {_Pos, B}) ->
+ B;
+binary_io({close, FN}, {_Pos, B}) ->
+ {FN, B};
+binary_io({list_dir, _F}, _B) ->
+ [];
+binary_io({set_file_info, _F, _FI}, B) ->
+ B;
+binary_io({ensure_dir, _Dir}, B) ->
+ B.
+
+file_io({file_info, F}, _) ->
+ case file:read_file_info(F) of
+ {ok, Info} -> Info;
+ {error, E} -> throw(E)
+ end;
+file_io({open, FN, Opts}, _) ->
+ case lists:member(write, Opts) of
+ true -> ok = filelib:ensure_dir(FN);
+ _ -> ok
+ end,
+ case file:open(FN, Opts++[binary]) of
+ {ok, H} -> H;
+ {error, E} -> throw(E)
+ end;
+file_io({read, N}, H) ->
+ case file:read(H, N) of
+ {ok, B} -> {B, H};
+ eof -> {eof, H};
+ {error, E} -> throw(E)
+ end;
+file_io({pread, Pos, N}, H) ->
+ case file:pread(H, Pos, N) of
+ {ok, B} -> {B, H};
+ eof -> {eof, H};
+ {error, E} -> throw(E)
+ end;
+file_io({seek, S, Pos}, H) ->
+ case file:position(H, {S, Pos}) of
+ {ok, _NewPos} -> H;
+ {error, Error} -> throw(Error)
+ end;
+file_io({write, Data}, H) ->
+ case file:write(H, Data) of
+ ok -> H;
+ {error, Error} -> throw(Error)
+ end;
+file_io({pwrite, Pos, Data}, H) ->
+ case file:pwrite(H, Pos, Data) of
+ ok -> H;
+ {error, Error} -> throw(Error)
+ end;
+file_io({close, FN}, H) ->
+ case file:close(H) of
+ ok -> FN;
+ {error, Error} -> throw(Error)
+ end;
+file_io(close, H) ->
+ file_io({close, ok}, H);
+file_io({list_dir, F}, _H) ->
+ case file:list_dir(F) of
+ {ok, Files} -> Files;
+ {error, Error} -> throw(Error)
+ end;
+file_io({set_file_info, F, FI}, H) ->
+ case file:write_file_info(F, FI) of
+ ok -> H;
+ {error, Error} -> throw(Error)
+ end;
+file_io({ensure_dir, Dir}, H) ->
+ ok = filelib:ensure_dir(Dir),
+ H.
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
new file mode 100644
index 0000000000..7a87eef5f3
--- /dev/null
+++ b/lib/stdlib/test/Makefile
@@ -0,0 +1,134 @@
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES= \
+ array_SUITE \
+ base64_SUITE \
+ beam_lib_SUITE \
+ c_SUITE \
+ calendar_SUITE \
+ dets_SUITE \
+ dict_SUITE \
+ dict_test_lib \
+ digraph_SUITE \
+ digraph_utils_SUITE \
+ dummy1_h \
+ dummy_h \
+ epp_SUITE \
+ erl_eval_helper \
+ erl_eval_SUITE \
+ erl_expand_records_SUITE \
+ erl_internal_SUITE \
+ erl_lint_SUITE \
+ erl_pp_SUITE \
+ erl_scan_SUITE \
+ escript_SUITE \
+ ets_SUITE \
+ ets_tough_SUITE \
+ filelib_SUITE \
+ file_sorter_SUITE \
+ filename_SUITE \
+ fixtable_SUITE \
+ format_SUITE \
+ gen_event_SUITE \
+ gen_fsm_SUITE \
+ gen_server_SUITE \
+ id_transform_SUITE \
+ io_SUITE \
+ io_proto_SUITE \
+ lists_SUITE \
+ log_mf_h_SUITE \
+ ms_transform_SUITE \
+ proc_lib_SUITE \
+ qlc_SUITE \
+ queue_SUITE \
+ random_SUITE \
+ re_SUITE \
+ run_pcre_tests \
+ re_testoutput1_replacement_test \
+ re_testoutput1_split_test \
+ slave_SUITE \
+ sets_SUITE \
+ sets_test_lib \
+ sofs_SUITE \
+ stdlib_SUITE \
+ string_SUITE \
+ supervisor_1 \
+ naughty_child \
+ shell_SUITE \
+ supervisor_SUITE \
+ supervisor_bridge_SUITE \
+ sys_SUITE \
+ tar_SUITE \
+ timer_SUITE \
+ timer_simple_SUITE \
+ unicode_SUITE \
+ win32reg_SUITE \
+ y2k_SUITE \
+ select_SUITE \
+ zip_SUITE \
+ random_unicode_list \
+ random_iolist \
+ error_logger_forwarder
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+INSTALL_PROGS= $(TARGET_FILES)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/stdlib_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+ERL_MAKE_FLAGS +=
+ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include \
+ -I$(ERL_TOP)/lib/kernel/include
+
+EBIN = .
+
+EMAKEFILE=Emakefile
+COVERFILE=stdlib.cover
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+make_emakefile:
+ $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
+ >> $(EMAKEFILE)
+
+tests debug opt: make_emakefile
+ erl $(ERL_MAKE_FLAGS) -make
+
+clean:
+ rm -f $(EMAKEFILE)
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: make_emakefile
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) stdlib.spec stdlib.spec.vxworks $(EMAKEFILE) \
+ $(ERL_FILES) $(COVERFILE) $(RELSYSDIR)
+ chmod -f -R u+w $(RELSYSDIR)
+ @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
+
+release_docs_spec:
diff --git a/lib/stdlib/test/array_SUITE.erl b/lib/stdlib/test/array_SUITE.erl
new file mode 100644
index 0000000000..7cfdcf6dfd
--- /dev/null
+++ b/lib/stdlib/test/array_SUITE.erl
@@ -0,0 +1,816 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(array_SUITE).
+
+-include("test_server.hrl").
+
+%% Default timetrap timeout (set in init_per_testcase).
+%% This should be set relatively high (10-15 times the expected
+%% max testcasetime).
+-define(default_timeout, ?t:seconds(60)).
+
+%% Test server specific exports
+-export([all/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-export([
+ new_test/1,
+ fix_test/1,
+ relax_test/1,
+ resize_test/1,
+ set_get_test/1,
+ to_list_test/1,
+ sparse_to_list_test/1,
+ from_list_test/1,
+ to_orddict_test/1,
+ sparse_to_orddict_test/1,
+ from_orddict_test/1,
+ map_test/1,
+ sparse_map_test/1,
+ foldl_test/1,
+ sparse_foldl_test/1,
+ foldr_test/1,
+ sparse_foldr_test/1
+ ]).
+
+
+-export([t/0,t/1,extract_tests/0]).
+
+-import(array,
+ [new/0, new/1, new/2, is_array/1, set/3, get/2, %size/1,
+ sparse_size/1, default/1, reset/2, to_list/1, sparse_to_list/1,
+ from_list/1, from_list/2, to_orddict/1, sparse_to_orddict/1,
+ from_orddict/1, from_orddict/2, map/2, sparse_map/2, foldl/3,
+ foldr/3, sparse_foldl/3, sparse_foldr/3, fix/1, relax/1, is_fix/1,
+ resize/1, resize/2]).
+
+%%
+%% all/1
+%%
+all(doc) ->
+ [];
+all(suite) ->
+ [new_test,
+ fix_test,
+ relax_test,
+ resize_test,
+ set_get_test,
+ to_list_test,
+ sparse_to_list_test,
+ from_list_test,
+ to_orddict_test,
+ sparse_to_orddict_test,
+ from_orddict_test,
+ map_test,
+ sparse_map_test,
+ foldl_test,
+ sparse_foldl_test,
+ foldr_test,
+ sparse_foldr_test
+ ].
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+-define(LEAFSIZE,10).
+-define(NODESIZE,?LEAFSIZE).
+
+-record(array, {size, %% number of defined entries
+ max, %% maximum number of entries in current tree
+ default, %% the default value (usually 'undefined')
+ elements %% the tuple tree
+ }).
+
+-define(_assert(What),
+ begin ?line true = What end
+ ).
+-define(_assertNot(What),
+ begin ?line false = What end
+ ).
+
+-define(_assertMatch(Res,What),
+ begin
+ ?line case What of Res -> ok end
+ end
+ ).
+-define(_assertError(Reas,What),
+ begin ?line fun() ->
+ try What of
+ A_Success -> exit({test_error, A_Success})
+ catch error:Reas -> ok end
+ end()
+ end
+ ).
+
+-define(LET(Var,Expr, Test), begin ?line fun() -> Var = Expr, Test end() end).
+
+-define(_test(Expr), begin ?line Expr end).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Some helpers to be able to run the tests without testserver
+%%%%%%%%%%%%%%%%%%%%%%%%%
+t() -> t([all]).
+
+t(What) when not is_list(What) ->
+ t([What]);
+t(What) ->
+ lists:foreach(fun(T) ->
+ io:format("Test ~p ~n",[T]),
+ try
+ ?MODULE:T([])
+ catch _E:_R ->
+ Line = get(test_server_loc),
+ io:format("Failed ~p:~p ~p ~p~n ~p~n",
+ [T,Line,_E,_R, erlang:get_stacktrace()])
+ end
+ end, expand(What)).
+
+expand(All) ->
+ lists:reverse(expand(All,[])).
+expand([H|T], Acc) ->
+ case ?MODULE:H(suite) of
+ [] -> expand(T,[H|Acc]);
+ Cs ->
+ R = expand(Cs, Acc),
+ expand(T, R)
+ end;
+expand([], Acc) -> Acc.
+
+%%%%% extract tests
+
+extract_tests() ->
+ {ok, In} = file:open("../src/array.erl", [read]),
+ {ok, Out} = file:open("array_temp.erl", [write]),
+ try
+ Tests = extract_tests(In,Out,[]),
+ Call = fun(Test) ->
+ io:format(Out, "~s(doc) -> [];~n", [Test]),
+ io:format(Out, "~s(suite) -> [];~n", [Test]),
+ io:format(Out, "~s(Config) when is_list(Config) -> ~s_(), ok.~n",
+ [Test, Test])
+ end,
+ [Call(Test) || Test <- Tests],
+ io:format("Tests ~p~n", [Tests])
+ catch _:Err ->
+ io:format("Error: ~p ~p~n", [Err, erlang:get_stacktrace()])
+ end,
+ file:close(In),
+ file:close(Out).
+
+extract_tests(In,Out,Tests) ->
+ case io:get_line(In,"") of
+ eof -> lists:reverse(Tests);
+ "-ifdef(EUNIT)" ++ _ ->
+ Test = write_test(In,Out),
+ extract_tests(In,Out, [Test|Tests]);
+ _E ->
+ extract_tests(In,Out,Tests)
+ end.
+
+write_test(In,Out) ->
+ Line = io:get_line(In,""),
+ io:put_chars(Out, Line),
+ [$_|Test] = lists:dropwhile(fun($_) -> false;(_) -> true end,lists:reverse(Line)),
+ write_test_1(In,Out),
+ lists:reverse(Test).
+
+write_test_1(In,Out) ->
+ case io:get_line(In,"") of
+ "-endif" ++ _ ->
+ io:nl(Out),
+ ok;
+ Line ->
+ io:put_chars(Out, Line),
+ write_test_1(In,Out)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Actual tests
+
+new_test_() ->
+ N0 = ?LEAFSIZE,
+ N01 = N0+1,
+ N1 = ?NODESIZE*N0,
+ N11 = N1+1,
+ N2 = ?NODESIZE*N1,
+ [?_test(new()),
+
+ ?_test(new([])),
+ ?_test(new(10)),
+ ?_test(new({size,10})),
+ ?_test(new(fixed)),
+ ?_test(new({fixed,true})),
+ ?_test(new({fixed,false})),
+ ?_test(new({default,undefined})),
+ ?_test(new([{size,100},{fixed,false},{default,undefined}])),
+ ?_test(new([100,fixed,{default,0}])),
+
+ ?_assert(new() =:= new([])),
+ ?_assert(new() =:= new([{size,0},{default,undefined},{fixed,false}])),
+ ?_assert(new() =:= new(0, {fixed,false})),
+ ?_assert(new(fixed) =:= new(0)),
+ ?_assert(new(fixed) =:= new(0, [])),
+ ?_assert(new(10) =:= new([{size,0},{size,5},{size,10}])),
+ ?_assert(new(10) =:= new(0, {size,10})),
+ ?_assert(new(10, []) =:= new(10, [{default,undefined},{fixed,true}])),
+
+ ?_assertError(badarg, new(-1)),
+ ?_assertError(badarg, new(10.0)),
+ ?_assertError(badarg, new(undefined)),
+ ?_assertError(badarg, new([undefined])),
+ ?_assertError(badarg, new([{default,0} | fixed])),
+
+ ?_assertError(badarg, new(-1, [])),
+ ?_assertError(badarg, new(10.0, [])),
+ ?_assertError(badarg, new(undefined, [])),
+
+ ?_assertMatch(#array{size=0,max=N0,default=undefined,elements=N0},
+ new()),
+ ?_assertMatch(#array{size=0,max=0,default=undefined,elements=N0},
+ new(fixed)),
+ ?_assertMatch(#array{size=N0,max=N0,elements=N0},
+ new(N0, {fixed,false})),
+ ?_assertMatch(#array{size=N01,max=N1,elements=N1},
+ new(N01, {fixed,false})),
+ ?_assertMatch(#array{size=N1,max=N1,elements=N1},
+ new(N1, {fixed,false})),
+ ?_assertMatch(#array{size=N11,max=N2,elements=N2},
+ new(N11, {fixed,false})),
+ ?_assertMatch(#array{size=N2, max=N2, default=42,elements=N2},
+ new(N2, [{fixed,false},{default,42}])),
+
+ ?_assert(0 =:= array:size(new())),
+ ?_assert(17 =:= array:size(new(17))),
+ ?_assert(100 =:= array:size(array:set(99,0,new()))),
+ ?_assertError(badarg, array:size({bad_data,gives_error})),
+
+ ?_assert(undefined =:= default(new())),
+ ?_assert(4711 =:= default(new({default,4711}))),
+ ?_assert(0 =:= default(new(10, {default,0}))),
+ ?_assertError(badarg, default({bad_data,gives_error})),
+
+ ?_assert(is_array(new())),
+ ?_assert(false =:= is_array({foobar, 23, 23})),
+ ?_assert(false =:= is_array(#array{size=bad})),
+ ?_assert(false =:= is_array(#array{max=bad})),
+ ?_assert(is_array(new(10))),
+ ?_assert(is_array(new(10, {fixed,false})))
+ ].
+
+fix_test_() ->
+ [?_assert(is_array(fix(new()))),
+ ?_assert(fix(new()) =:= new(fixed)),
+
+ ?_assertNot(is_fix(new())),
+ ?_assertNot(is_fix(new([]))),
+ ?_assertNot(is_fix(new({fixed,false}))),
+ ?_assertNot(is_fix(new(10, {fixed,false}))),
+ ?_assert(is_fix(new({fixed,true}))),
+ ?_assert(is_fix(new(fixed))),
+ ?_assert(is_fix(new(10))),
+ ?_assert(is_fix(new(10, []))),
+ ?_assert(is_fix(new(10, {fixed,true}))),
+ ?_assert(is_fix(fix(new()))),
+ ?_assert(is_fix(fix(new({fixed,false})))),
+
+ ?_test(set(0, 17, new())),
+ ?_assertError(badarg, set(0, 17, new(fixed))),
+ ?_assertError(badarg, set(1, 42, fix(set(0, 17, new())))),
+
+ ?_test(set(9, 17, new(10))),
+ ?_assertError(badarg, set(10, 17, new(10))),
+ ?_assertError(badarg, set(10, 17, fix(new(10, {fixed,false}))))
+ ].
+
+relax_test_() ->
+ [?_assert(is_array(relax(new(fixed)))),
+ ?_assertNot(is_fix(relax(fix(new())))),
+ ?_assertNot(is_fix(relax(new(fixed)))),
+
+ ?_assert(new() =:= relax(new(fixed))),
+ ?_assert(new() =:= relax(new(0))),
+ ?_assert(new(17, {fixed,false}) =:= relax(new(17))),
+ ?_assert(new(100, {fixed,false})
+ =:= relax(fix(new(100, {fixed,false}))))
+ ].
+
+resize_test_() ->
+ [?_assert(resize(0, new()) =:= new()),
+ ?_assert(resize(99, new(99)) =:= new(99)),
+ ?_assert(resize(99, relax(new(99))) =:= relax(new(99))),
+ ?_assert(is_fix(resize(100, new(10)))),
+ ?_assertNot(is_fix(resize(100, relax(new(10))))),
+
+ ?_assert(array:size(resize(100, new())) =:= 100),
+ ?_assert(array:size(resize(0, new(100))) =:= 0),
+ ?_assert(array:size(resize(99, new(10))) =:= 99),
+ ?_assert(array:size(resize(99, new(1000))) =:= 99),
+
+ ?_assertError(badarg, set(99, 17, new(10))),
+ ?_test(set(99, 17, resize(100, new(10)))),
+ ?_assertError(badarg, set(100, 17, resize(100, new(10)))),
+
+ ?_assert(array:size(resize(new())) =:= 0),
+ ?_assert(array:size(resize(new(8))) =:= 0),
+ ?_assert(array:size(resize(array:set(7, 0, new()))) =:= 8),
+ ?_assert(array:size(resize(array:set(7, 0, new(10)))) =:= 8),
+ ?_assert(array:size(resize(array:set(99, 0, new(10,{fixed,false}))))
+ =:= 100),
+ ?_assert(array:size(resize(array:set(7, undefined, new()))) =:= 0),
+ ?_assert(array:size(resize(array:from_list([1,2,3,undefined])))
+ =:= 3),
+ ?_assert(array:size(
+ resize(array:from_orddict([{3,0},{17,0},{99,undefined}])))
+ =:= 18),
+ ?_assertError(badarg, resize(foo, bad_argument))
+ ].
+
+set_get_test_() ->
+ N0 = ?LEAFSIZE,
+ N1 = ?NODESIZE*N0,
+ [?_assert(array:get(0, new()) =:= undefined),
+ ?_assert(array:get(1, new()) =:= undefined),
+ ?_assert(array:get(99999, new()) =:= undefined),
+
+ ?_assert(array:get(0, new(1)) =:= undefined),
+ ?_assert(array:get(0, new(1,{default,0})) =:= 0),
+ ?_assert(array:get(9, new(10)) =:= undefined),
+
+ ?_assertError(badarg, array:get(0, new(fixed))),
+ ?_assertError(badarg, array:get(1, new(1))),
+ ?_assertError(badarg, array:get(-1, new(1))),
+ ?_assertError(badarg, array:get(10, new(10))),
+ ?_assertError(badarg, array:set(-1, foo, new(10))),
+ ?_assertError(badarg, array:set(10, foo, no_array)),
+
+ ?_assert(array:size(set(0, 17, new())) =:= 1),
+ ?_assert(array:size(set(N1-1, 17, new())) =:= N1),
+ ?_assert(array:size(set(0, 42, set(0, 17, new()))) =:= 1),
+ ?_assert(array:size(set(9, 42, set(0, 17, new()))) =:= 10),
+
+ ?_assert(array:get(0, set(0, 17, new())) =:= 17),
+ ?_assert(array:get(0, set(1, 17, new())) =:= undefined),
+ ?_assert(array:get(1, set(1, 17, new())) =:= 17),
+
+ ?_assert(array:get(0, fix(set(0, 17, new()))) =:= 17),
+ ?_assertError(badarg, array:get(1, fix(set(0, 17, new())))),
+
+ ?_assert(array:get(N1-2, set(N1-1, 17, new())) =:= undefined),
+ ?_assert(array:get(N1-1, set(N1-1, 17, new())) =:= 17),
+ ?_assertError(badarg, array:get(N1, fix(set(N1-1, 17, new())))),
+
+ ?_assert(array:get(0, set(0, 42, set(0, 17, new()))) =:= 42),
+
+ ?_assert(array:get(0, reset(0, new())) =:= undefined),
+ ?_assert(array:get(0, reset(0, set(0, 17, new()))) =:= undefined),
+ ?_assert(array:get(0, reset(0, new({default,42}))) =:= 42),
+ ?_assert(array:get(0, reset(0, set(0, 17, new({default,42}))))
+ =:= 42)
+ ].
+
+to_list_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= to_list(new())),
+ ?_assert([undefined] =:= to_list(new(1))),
+ ?_assert([undefined,undefined] =:= to_list(new(2))),
+ ?_assert(lists:duplicate(N0,0) =:= to_list(new(N0,{default,0}))),
+ ?_assert(lists:duplicate(N0+1,1) =:= to_list(new(N0+1,{default,1}))),
+ ?_assert(lists:duplicate(N0+2,2) =:= to_list(new(N0+2,{default,2}))),
+ ?_assert(lists:duplicate(666,6) =:= to_list(new(666,{default,6}))),
+ ?_assert([1,2,3] =:= to_list(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([3,2,1] =:= to_list(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([1|lists:duplicate(N0-2,0)++[1]] =:=
+ to_list(set(N0-1,1,set(0,1,new({default,0}))))),
+ ?_assert([1|lists:duplicate(N0-1,0)++[1]] =:=
+ to_list(set(N0,1,set(0,1,new({default,0}))))),
+ ?_assert([1|lists:duplicate(N0,0)++[1]] =:=
+ to_list(set(N0+1,1,set(0,1,new({default,0}))))),
+ ?_assert([1|lists:duplicate(N0*3,0)++[1]] =:=
+ to_list(set((N0*3)+1,1,set(0,1,new({default,0}))))),
+ ?_assertError(badarg, to_list(no_array))
+ ].
+
+sparse_to_list_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= sparse_to_list(new())),
+ ?_assert([] =:= sparse_to_list(new(1))),
+ ?_assert([] =:= sparse_to_list(new(1,{default,0}))),
+ ?_assert([] =:= sparse_to_list(new(2))),
+ ?_assert([] =:= sparse_to_list(new(2,{default,0}))),
+ ?_assert([] =:= sparse_to_list(new(N0,{default,0}))),
+ ?_assert([] =:= sparse_to_list(new(N0+1,{default,1}))),
+ ?_assert([] =:= sparse_to_list(new(N0+2,{default,2}))),
+ ?_assert([] =:= sparse_to_list(new(666,{default,6}))),
+ ?_assert([1,2,3] =:= sparse_to_list(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([3,2,1] =:= sparse_to_list(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([0,1] =:= sparse_to_list(set(N0-1,1,set(0,0,new())))),
+ ?_assert([0,1] =:= sparse_to_list(set(N0,1,set(0,0,new())))),
+ ?_assert([0,1] =:= sparse_to_list(set(N0+1,1,set(0,0,new())))),
+ ?_assert([0,1,2] =:= sparse_to_list(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
+ ?_assertError(badarg, sparse_to_list(no_array))
+ ].
+
+from_list_test_() ->
+ N0 = ?LEAFSIZE,
+ N1 = ?NODESIZE*N0,
+ N2 = ?NODESIZE*N1,
+ N3 = ?NODESIZE*N2,
+ N4 = ?NODESIZE*N3,
+ [?_assert(array:size(from_list([])) =:= 0),
+ ?_assert(array:is_fix(from_list([])) =:= false),
+ ?_assert(array:size(from_list([undefined])) =:= 1),
+ ?_assert(array:is_fix(from_list([undefined])) =:= false),
+ ?_assert(array:size(from_list(lists:seq(1,N1))) =:= N1),
+ ?_assert(to_list(from_list(lists:seq(1,N0))) =:= lists:seq(1,N0)),
+ ?_assert(to_list(from_list(lists:seq(1,N0+1))) =:= lists:seq(1,N0+1)),
+ ?_assert(to_list(from_list(lists:seq(1,N0+2))) =:= lists:seq(1,N0+2)),
+ ?_assert(to_list(from_list(lists:seq(1,N2))) =:= lists:seq(1,N2)),
+ ?_assert(to_list(from_list(lists:seq(1,N2+1))) =:= lists:seq(1,N2+1)),
+ ?_assert(to_list(from_list(lists:seq(0,N3))) =:= lists:seq(0,N3)),
+ ?_assert(to_list(from_list(lists:seq(0,N4))) =:= lists:seq(0,N4)),
+ ?_assertError(badarg, from_list([a,b,a,c|d])),
+ ?_assertError(badarg, from_list(no_array))
+ ].
+
+to_orddict_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= to_orddict(new())),
+ ?_assert([{0,undefined}] =:= to_orddict(new(1))),
+ ?_assert([{0,undefined},{1,undefined}] =:= to_orddict(new(2))),
+ ?_assert([{N,0}||N<-lists:seq(0,N0-1)]
+ =:= to_orddict(new(N0,{default,0}))),
+ ?_assert([{N,1}||N<-lists:seq(0,N0)]
+ =:= to_orddict(new(N0+1,{default,1}))),
+ ?_assert([{N,2}||N<-lists:seq(0,N0+1)]
+ =:= to_orddict(new(N0+2,{default,2}))),
+ ?_assert([{N,6}||N<-lists:seq(0,665)]
+ =:= to_orddict(new(666,{default,6}))),
+ ?_assert([{0,1},{1,2},{2,3}] =:=
+ to_orddict(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([{0,3},{1,2},{2,1}] =:=
+ to_orddict(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0-2)]++[{N0-1,1}]]
+ =:= to_orddict(set(N0-1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0-1)]++[{N0,1}]]
+ =:= to_orddict(set(N0,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0)]++[{N0+1,1}]]
+ =:= to_orddict(set(N0+1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,0} | [{N,undefined}||N<-lists:seq(1,N0*2)]] ++
+ [{N0*2+1,1} | [{N,undefined}||N<-lists:seq(N0*2+2,N0*10)]] ++
+ [{N0*10+1,2}] =:=
+ to_orddict(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
+ ?_assertError(badarg, to_orddict(no_array))
+ ].
+
+sparse_to_orddict_test_() ->
+ N0 = ?LEAFSIZE,
+ [?_assert([] =:= sparse_to_orddict(new())),
+ ?_assert([] =:= sparse_to_orddict(new(1))),
+ ?_assert([] =:= sparse_to_orddict(new(1,{default,0}))),
+ ?_assert([] =:= sparse_to_orddict(new(2))),
+ ?_assert([] =:= sparse_to_orddict(new(2,{default,0}))),
+ ?_assert([] =:= sparse_to_orddict(new(N0,{default,0}))),
+ ?_assert([] =:= sparse_to_orddict(new(N0+1,{default,1}))),
+ ?_assert([] =:= sparse_to_orddict(new(N0+2,{default,2}))),
+ ?_assert([] =:= sparse_to_orddict(new(666,{default,6}))),
+ ?_assert([{0,1},{1,2},{2,3}] =:=
+ sparse_to_orddict(set(2,3,set(1,2,set(0,1,new()))))),
+ ?_assert([{0,3},{1,2},{2,1}] =:=
+ sparse_to_orddict(set(0,3,set(1,2,set(2,1,new()))))),
+ ?_assert([{0,1},{N0-1,1}] =:=
+ sparse_to_orddict(set(N0-1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1},{N0,1}] =:=
+ sparse_to_orddict(set(N0,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,1},{N0+1,1}] =:=
+ sparse_to_orddict(set(N0+1,1,set(0,1,new({default,0}))))),
+ ?_assert([{0,0},{N0*2+1,1},{N0*10+1,2}] =:=
+ sparse_to_orddict(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
+ ?_assertError(badarg, sparse_to_orddict(no_array))
+ ].
+
+from_orddict_test_() ->
+ N0 = ?LEAFSIZE,
+ N1 = ?NODESIZE*N0,
+ N2 = ?NODESIZE*N1,
+ N3 = ?NODESIZE*N2,
+ N4 = ?NODESIZE*N3,
+ [?_assert(array:size(from_orddict([])) =:= 0),
+ ?_assert(array:is_fix(from_orddict([])) =:= false),
+ ?_assert(array:size(from_orddict([{0,undefined}])) =:= 1),
+ ?_assert(array:is_fix(from_orddict([{0,undefined}])) =:= false),
+ ?_assert(array:size(from_orddict([{N0-1,undefined}])) =:= N0),
+ ?_assert(array:size(from_orddict([{N,0}||N<-lists:seq(0,N1-1)]))
+ =:= N1),
+ ?_assertError({badarg,_}, from_orddict([foo])),
+ ?_assertError({badarg,_}, from_orddict([{200,foo},{1,bar}])),
+ ?_assertError({badarg,_}, from_orddict([{N,0}||N<-lists:seq(0,N0-1)] ++ not_a_list)),
+ ?_assertError(badarg, from_orddict(no_array)),
+
+
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N0-1)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N0)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N2-1)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N2)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N3-1)],
+ L =:= to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N4-1)],
+ L =:= to_orddict(from_orddict(L)))),
+
+ %% Hole in the begining
+ ?_assert(?LET(L, [{0,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N0,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N3,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N4,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N0-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N1-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N3-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{N4-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+
+ %% Hole in middle
+
+ ?_assert(?LET(L, [{0,0},{N0,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N3,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N4,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N0-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N1-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N3-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L)))),
+ ?_assert(?LET(L, [{0,0},{N4-1,0}],
+ L =:= sparse_to_orddict(from_orddict(L))))
+
+ ].
+
+map_test_() ->
+ N0 = ?LEAFSIZE,
+ Id = fun (_,X) -> X end,
+ Plus = fun(N) -> fun (_,X) -> X+N end end,
+ Default = fun(_K,undefined) -> no_value;
+ (K,V) -> K+V
+ end,
+ [?_assertError(badarg, map([], new())),
+ ?_assertError(badarg, map([], new(10))),
+ ?_assert(to_list(map(Id, new())) =:= []),
+ ?_assert(to_list(map(Id, new(1))) =:= [undefined]),
+ ?_assert(to_list(map(Id, new(5,{default,0}))) =:= [0,0,0,0,0]),
+ ?_assert(to_list(map(Id, from_list([1,2,3,4]))) =:= [1,2,3,4]),
+ ?_assert(to_list(map(Plus(1), from_list([0,1,2,3]))) =:= [1,2,3,4]),
+ ?_assert(to_list(map(Plus(-1), from_list(lists:seq(1,11))))
+ =:= lists:seq(0,10)),
+ ?_assert(to_list(map(Plus(11), from_list(lists:seq(0,99999))))
+ =:= lists:seq(11,100010)),
+ ?_assert([{0,0},{N0*2+1,N0*2+1+1},{N0*100+1,N0*100+1+2}] =:=
+ sparse_to_orddict((map(Default,
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))#array{default = no_value}))
+ ].
+
+sparse_map_test_() ->
+ N0 = ?LEAFSIZE,
+ Id = fun (_,X) -> X end,
+ Plus = fun(N) -> fun (_,X) -> X+N end end,
+ KeyPlus = fun (K,X) -> K+X end,
+ [?_assertError(badarg, sparse_map([], new())),
+ ?_assertError(badarg, sparse_map([], new(10))),
+ ?_assert(to_list(sparse_map(Id, new())) =:= []),
+ ?_assert(to_list(sparse_map(Id, new(1))) =:= [undefined]),
+ ?_assert(to_list(sparse_map(Id, new(5,{default,0}))) =:= [0,0,0,0,0]),
+ ?_assert(to_list(sparse_map(Id, from_list([1,2,3,4]))) =:= [1,2,3,4]),
+ ?_assert(to_list(sparse_map(Plus(1), from_list([0,1,2,3])))
+ =:= [1,2,3,4]),
+ ?_assert(to_list(sparse_map(Plus(-1), from_list(lists:seq(1,11))))
+ =:= lists:seq(0,10)),
+ ?_assert(to_list(sparse_map(Plus(11), from_list(lists:seq(0,99999))))
+ =:= lists:seq(11,100010)),
+ ?_assert(to_list(sparse_map(Plus(1), set(1,1,new({default,0}))))
+ =:= [0,2]),
+ ?_assert(to_list(sparse_map(Plus(1),
+ set(3,4,set(0,1,new({default,0})))))
+ =:= [2,0,0,5]),
+ ?_assert(to_list(sparse_map(Plus(1),
+ set(9,9,set(1,1,new({default,0})))))
+ =:= [0,2,0,0,0,0,0,0,0,10]),
+ ?_assert([{0,0},{N0*2+1,N0*2+1+1},{N0*100+1,N0*100+1+2}] =:=
+ sparse_to_orddict(sparse_map(KeyPlus,
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new()))))))
+
+ ].
+
+foldl_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ Reverse = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, foldl([], 0, new())),
+ ?_assertError(badarg, foldl([], 0, new(10))),
+ ?_assert(foldl(Count, 0, new()) =:= 0),
+ ?_assert(foldl(Count, 0, new(1)) =:= 1),
+ ?_assert(foldl(Count, 0, new(10)) =:= 10),
+ ?_assert(foldl(Count, 0, from_list([1,2,3,4])) =:= 4),
+ ?_assert(foldl(Count, 10, from_list([0,1,2,3,4,5,6,7,8,9])) =:= 20),
+ ?_assert(foldl(Count, 1000, from_list(lists:seq(0,999))) =:= 2000),
+ ?_assert(foldl(Sum, 0, from_list(lists:seq(0,10))) =:= 55),
+ ?_assert(foldl(Reverse, [], from_list(lists:seq(0,1000)))
+ =:= lists:reverse(lists:seq(0,1000))),
+ ?_assert({999,[N0*100+1+2,N0*2+1+1,0]} =:=
+ foldl(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+
+ ].
+
+sparse_foldl_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ Reverse = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, sparse_foldl([], 0, new())),
+ ?_assertError(badarg, sparse_foldl([], 0, new(10))),
+ ?_assert(sparse_foldl(Count, 0, new()) =:= 0),
+ ?_assert(sparse_foldl(Count, 0, new(1)) =:= 0),
+ ?_assert(sparse_foldl(Count, 0, new(10,{default,1})) =:= 0),
+ ?_assert(sparse_foldl(Count, 0, from_list([0,1,2,3,4],0)) =:= 4),
+ ?_assert(sparse_foldl(Count, 0, from_list([0,1,2,3,4,5,6,7,8,9,0],0))
+ =:= 9),
+ ?_assert(sparse_foldl(Count, 0, from_list(lists:seq(0,999),0))
+ =:= 999),
+ ?_assert(sparse_foldl(Sum, 0, from_list(lists:seq(0,10), 5)) =:= 50),
+ ?_assert(sparse_foldl(Reverse, [], from_list(lists:seq(0,1000), 0))
+ =:= lists:reverse(lists:seq(1,1000))),
+ ?_assert({0,[N0*100+1+2,N0*2+1+1,0]} =:=
+ sparse_foldl(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+ ].
+
+foldr_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ List = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, foldr([], 0, new())),
+ ?_assertError(badarg, foldr([], 0, new(10))),
+ ?_assert(foldr(Count, 0, new()) =:= 0),
+ ?_assert(foldr(Count, 0, new(1)) =:= 1),
+ ?_assert(foldr(Count, 0, new(10)) =:= 10),
+ ?_assert(foldr(Count, 0, from_list([1,2,3,4])) =:= 4),
+ ?_assert(foldr(Count, 10, from_list([0,1,2,3,4,5,6,7,8,9])) =:= 20),
+ ?_assert(foldr(Count, 1000, from_list(lists:seq(0,999))) =:= 2000),
+ ?_assert(foldr(Sum, 0, from_list(lists:seq(0,10))) =:= 55),
+ ?_assert(foldr(List, [], from_list(lists:seq(0,1000)))
+ =:= lists:seq(0,1000)),
+ ?_assert({999,[0,N0*2+1+1,N0*100+1+2]} =:=
+ foldr(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+
+ ].
+
+sparse_foldr_test_() ->
+ N0 = ?LEAFSIZE,
+ Count = fun (_,_,N) -> N+1 end,
+ Sum = fun (_,X,N) -> N+X end,
+ List = fun (_,X,L) -> [X|L] end,
+ Vals = fun(_K,undefined,{C,L}) -> {C+1,L};
+ (K,X,{C,L}) -> {C,[K+X|L]}
+ end,
+ [?_assertError(badarg, sparse_foldr([], 0, new())),
+ ?_assertError(badarg, sparse_foldr([], 0, new(10))),
+ ?_assert(sparse_foldr(Count, 0, new()) =:= 0),
+ ?_assert(sparse_foldr(Count, 0, new(1)) =:= 0),
+ ?_assert(sparse_foldr(Count, 0, new(10,{default,1})) =:= 0),
+ ?_assert(sparse_foldr(Count, 0, from_list([0,1,2,3,4],0)) =:= 4),
+ ?_assert(sparse_foldr(Count, 0, from_list([0,1,2,3,4,5,6,7,8,9,0],0))
+ =:= 9),
+ ?_assert(sparse_foldr(Count, 0, from_list(lists:seq(0,999),0))
+ =:= 999),
+ ?_assert(sparse_foldr(Sum, 0, from_list(lists:seq(0,10),5)) =:= 50),
+ ?_assert(sparse_foldr(List, [], from_list(lists:seq(0,1000),0))
+ =:= lists:seq(1,1000)),
+
+ ?_assert(sparse_size(new()) =:= 0),
+ ?_assert(sparse_size(new(8)) =:= 0),
+ ?_assert(sparse_size(array:set(7, 0, new())) =:= 8),
+ ?_assert(sparse_size(array:set(7, 0, new(10))) =:= 8),
+ ?_assert(sparse_size(array:set(99, 0, new(10,{fixed,false})))
+ =:= 100),
+ ?_assert(sparse_size(array:set(7, undefined, new())) =:= 0),
+ ?_assert(sparse_size(array:from_list([1,2,3,undefined])) =:= 3),
+ ?_assert(sparse_size(array:from_orddict([{3,0},{17,0},{99,undefined}]))
+ =:= 18),
+ ?_assert({0,[0,N0*2+1+1,N0*100+1+2]} =:=
+ sparse_foldr(Vals, {0,[]},
+ set(N0*100+1,2,
+ set(N0*2+1,1,
+ set(0,0,new())))))
+ ].
+
+new_test(doc) -> [];
+new_test(suite) -> [];
+new_test(Config) when is_list(Config) -> new_test_(), ok.
+fix_test(doc) -> [];
+fix_test(suite) -> [];
+fix_test(Config) when is_list(Config) -> fix_test_(), ok.
+relax_test(doc) -> [];
+relax_test(suite) -> [];
+relax_test(Config) when is_list(Config) -> relax_test_(), ok.
+resize_test(doc) -> [];
+resize_test(suite) -> [];
+resize_test(Config) when is_list(Config) -> resize_test_(), ok.
+set_get_test(doc) -> [];
+set_get_test(suite) -> [];
+set_get_test(Config) when is_list(Config) -> set_get_test_(), ok.
+to_list_test(doc) -> [];
+to_list_test(suite) -> [];
+to_list_test(Config) when is_list(Config) -> to_list_test_(), ok.
+sparse_to_list_test(doc) -> [];
+sparse_to_list_test(suite) -> [];
+sparse_to_list_test(Config) when is_list(Config) -> sparse_to_list_test_(), ok.
+from_list_test(doc) -> [];
+from_list_test(suite) -> [];
+from_list_test(Config) when is_list(Config) -> from_list_test_(), ok.
+to_orddict_test(doc) -> [];
+to_orddict_test(suite) -> [];
+to_orddict_test(Config) when is_list(Config) -> to_orddict_test_(), ok.
+sparse_to_orddict_test(doc) -> [];
+sparse_to_orddict_test(suite) -> [];
+sparse_to_orddict_test(Config) when is_list(Config) -> sparse_to_orddict_test_(), ok.
+from_orddict_test(doc) -> [];
+from_orddict_test(suite) -> [];
+from_orddict_test(Config) when is_list(Config) -> from_orddict_test_(), ok.
+map_test(doc) -> [];
+map_test(suite) -> [];
+map_test(Config) when is_list(Config) -> map_test_(), ok.
+sparse_map_test(doc) -> [];
+sparse_map_test(suite) -> [];
+sparse_map_test(Config) when is_list(Config) -> sparse_map_test_(), ok.
+foldl_test(doc) -> [];
+foldl_test(suite) -> [];
+foldl_test(Config) when is_list(Config) -> foldl_test_(), ok.
+sparse_foldl_test(doc) -> [];
+sparse_foldl_test(suite) -> [];
+sparse_foldl_test(Config) when is_list(Config) -> sparse_foldl_test_(), ok.
+foldr_test(doc) -> [];
+foldr_test(suite) -> [];
+foldr_test(Config) when is_list(Config) -> foldr_test_(), ok.
+sparse_foldr_test(doc) -> [];
+sparse_foldr_test(suite) -> [];
+sparse_foldr_test(Config) when is_list(Config) -> sparse_foldr_test_(), ok.
diff --git a/lib/stdlib/test/base64_SUITE.erl b/lib/stdlib/test/base64_SUITE.erl
new file mode 100644
index 0000000000..44742063b3
--- /dev/null
+++ b/lib/stdlib/test/base64_SUITE.erl
@@ -0,0 +1,257 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(base64_SUITE).
+-author('[email protected]').
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+%% Test server specific exports
+-export([all/1, init_per_testcase/2, end_per_testcase/2]).
+
+%% Test cases must be exported.
+-export([base64_encode/1, base64_decode/1, base64_otp_5635/1,
+ base64_otp_6279/1, big/1, illegal/1, mime_decode/1,
+ roundtrip/1]).
+
+init_per_testcase(_, Config) ->
+ Dog = test_server:timetrap(?t:minutes(2)),
+ NewConfig = lists:keydelete(watchdog, 1, Config),
+ [{watchdog, Dog} | NewConfig].
+
+end_per_testcase(_, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+all(doc) ->
+ ["Test library functions for base64 encode and decode "
+ "(taken from inets/test/http_format_SUITE)"];
+all(suite) ->
+ [base64_encode, base64_decode, base64_otp_5635,
+ base64_otp_6279, big, illegal, mime_decode,
+ roundtrip].
+
+
+%%-------------------------------------------------------------------------
+base64_encode(doc) ->
+ ["Test base64:encode/1."];
+base64_encode(suite) ->
+ [];
+base64_encode(Config) when is_list(Config) ->
+ %% Two pads
+ <<"QWxhZGRpbjpvcGVuIHNlc2FtZQ==">> =
+ base64:encode("Aladdin:open sesame"),
+ %% One pad
+ <<"SGVsbG8gV29ybGQ=">> = base64:encode(<<"Hello World">>),
+ %% No pad
+ "QWxhZGRpbjpvcGVuIHNlc2Ft" =
+ base64:encode_to_string("Aladdin:open sesam"),
+
+ "MDEyMzQ1Njc4OSFAIzBeJiooKTs6PD4sLiBbXXt9" =
+ base64:encode_to_string(<<"0123456789!@#0^&*();:<>,. []{}">>),
+ ok.
+%%-------------------------------------------------------------------------
+base64_decode(doc) ->
+ ["Test base64:decode/1."];
+base64_decode(suite) ->
+ [];
+base64_decode(Config) when is_list(Config) ->
+ %% Two pads
+ <<"Aladdin:open sesame">> =
+ base64:decode("QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
+ %% One pad
+ <<"Hello World">> = base64:decode(<<"SGVsbG8gV29ybGQ=">>),
+ %% No pad
+ <<"Aladdin:open sesam">> =
+ base64:decode("QWxhZGRpbjpvcGVuIHNlc2Ft"),
+
+ Alphabet = list_to_binary(lists:seq(0, 255)),
+ Alphabet = base64:decode(base64:encode(Alphabet)),
+
+ %% Encoded base 64 strings may be devided by non base 64 chars.
+ %% In this cases whitespaces.
+ "0123456789!@#0^&*();:<>,. []{}" =
+ base64:decode_to_string(
+ "MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9"),
+ "0123456789!@#0^&*();:<>,. []{}" =
+ base64:decode_to_string(
+ <<"MDEy MzQ1Njc4 \tOSFAIzBeJ \niooKTs6 PD4sLi \r\nBbXXt9">>),
+ ok.
+%%-------------------------------------------------------------------------
+base64_otp_5635(doc) ->
+ ["OTP-5635: Some data doesn't pass through base64:decode/1 "
+ "correctly"];
+base64_otp_5635(suite) ->
+ [];
+base64_otp_5635(Config) when is_list(Config) ->
+ <<"===">> = base64:decode(base64:encode("===")),
+ ok.
+%%-------------------------------------------------------------------------
+base64_otp_6279(doc) ->
+ ["OTP-6279: Guard needed so that function fails in a correct"
+ "way for faulty input i.e. function_clause"];
+base64_otp_6279(suite) ->
+ [];
+base64_otp_6279(Config) when is_list(Config) ->
+ {'EXIT',{function_clause, _}} = (catch base64:decode("dGVzda==a")),
+ ok.
+%%-------------------------------------------------------------------------
+big(doc) ->
+ ["Encode and decode big binaries."];
+big(suite) ->
+ [];
+big(Config) when is_list(Config) ->
+ Big = make_big_binary(300000),
+ B = base64:encode(Big),
+ true = is_binary(B),
+ 400000 = byte_size(B),
+ Big = base64:decode(B),
+ Big = base64:mime_decode(B),
+ ok.
+%%-------------------------------------------------------------------------
+illegal(doc) ->
+ ["Make sure illegal characters are rejected when decoding."];
+illegal(suite) ->
+ [];
+illegal(Config) when is_list(Config) ->
+ {'EXIT',{function_clause, _}} = (catch base64:decode("()")),
+ ok.
+%%-------------------------------------------------------------------------
+mime_decode(doc) ->
+ ["Test base64:mime_decode/1."];
+mime_decode(suite) ->
+ [];
+mime_decode(Config) when is_list(Config) ->
+ %% Two pads
+ <<"Aladdin:open sesame">> =
+ base64:mime_decode("QWxhZGRpbjpvc()GVuIHNlc2FtZQ=="),
+ %% One pad, followed by ignored text
+ <<"Hello World">> = base64:mime_decode(<<"SGVsb)(G8gV29ybGQ=apa">>),
+ %% No pad
+ "Aladdin:open sesam" =
+ base64:mime_decode_to_string("QWxhZGRpbjpvcG�\")(VuIHNlc2Ft"),
+
+ %% Encoded base 64 strings may be divided by non base 64 chars.
+ %% In this cases whitespaces.
+ "0123456789!@#0^&*();:<>,. []{}" =
+ base64:mime_decode_to_string(
+ <<"MDEy MzQ1Njc4 \tOSFAIzBeJ \nio)(oKTs6 PD4sLi \r\nBbXXt9">>),
+ ok.
+
+
+roundtrip(Config) when is_list(Config) ->
+ Sizes = lists:seq(1, 255) ++ lists:seq(2400-5, 2440),
+ roundtrip_1(Sizes, []).
+
+roundtrip_1([NextSize|Sizes], Current) ->
+ Len = length(Current),
+ io:format("~p", [Len]),
+ do_roundtrip(Current),
+ Next = random_byte_list(NextSize - Len, Current),
+ roundtrip_1(Sizes, Next);
+roundtrip_1([], Last) ->
+ io:format("~p", [length(Last)]),
+ do_roundtrip(Last).
+
+do_roundtrip(List) ->
+ Bin = list_to_binary(List),
+ Base64Bin = base64:encode(List),
+ Base64Bin = base64:encode(Bin),
+ Base64List = base64:encode_to_string(List),
+ Base64Bin = list_to_binary(Base64List),
+ Bin = base64:decode(Base64Bin),
+ List = base64:decode_to_string(Base64Bin),
+ Bin = base64:mime_decode(Base64Bin),
+ List = base64:mime_decode_to_string(Base64Bin),
+ append_roundtrip(8, Bin, List, Base64Bin),
+ prepend_roundtrip(8, Bin, List, Base64List),
+ interleaved_ws_roundtrip(Bin, List, Base64List).
+
+append_roundtrip(0, _, _, _) -> ok;
+append_roundtrip(N, Bin, List, Base64Bin0) ->
+ Base64Bin = <<Base64Bin0/binary,"\n">>,
+ Bin = base64:decode(Base64Bin),
+ List = base64:decode_to_string(Base64Bin),
+ Bin = base64:mime_decode(Base64Bin),
+ List = base64:mime_decode_to_string(Base64Bin),
+
+ Base64List = binary_to_list(Base64Bin),
+ Bin = base64:decode(Base64List),
+ List = base64:decode_to_string(Base64List),
+ Bin = base64:mime_decode(Base64List),
+ List = base64:mime_decode_to_string(Base64List),
+ append_roundtrip(N-1, Bin, List, Base64Bin).
+
+prepend_roundtrip(0, _, _, _) -> ok;
+prepend_roundtrip(N, Bin, List, Base64List0) ->
+ Base64List = [$\s|Base64List0],
+ Bin = base64:decode(Base64List),
+ List = base64:decode_to_string(Base64List),
+ Bin = base64:mime_decode(Base64List),
+ List = base64:mime_decode_to_string(Base64List),
+
+ Base64Bin = list_to_binary(Base64List),
+ Bin = base64:decode(Base64Bin),
+ List = base64:decode_to_string(Base64Bin),
+ Bin = base64:mime_decode(Base64Bin),
+ List = base64:mime_decode_to_string(Base64Bin),
+ prepend_roundtrip(N-1, Bin, List, Base64List).
+
+%% Do an exhaustive test of interleaving whitespace (for short strings).
+interleaved_ws_roundtrip(Bin, List, Base64List) when byte_size(Bin) =< 6 ->
+ interleaved_ws_roundtrip_1(lists:reverse(Base64List), [], Bin, List);
+interleaved_ws_roundtrip(_, _, _) -> ok.
+
+interleaved_ws_roundtrip_1([H|T], Tail, Bin, List) ->
+ interleaved_ws_roundtrip_1(T, [H|Tail], Bin, List),
+ interleaved_ws_roundtrip_1(T, [H,$\s|Tail], Bin, List),
+ interleaved_ws_roundtrip_1(T, [H,$\s,$\t|Tail], Bin, List),
+ interleaved_ws_roundtrip_1(T, [H,$\n,$\t|Tail], Bin, List);
+interleaved_ws_roundtrip_1([], Base64List, Bin, List) ->
+ Bin = base64:decode(Base64List),
+ List = base64:decode_to_string(Base64List),
+ Bin = base64:mime_decode(Base64List),
+ List = base64:mime_decode_to_string(Base64List),
+
+ Base64Bin = list_to_binary(Base64List),
+ Bin = base64:decode(Base64Bin),
+ List = base64:decode_to_string(Base64Bin),
+ Bin = base64:mime_decode(Base64Bin),
+ List = base64:mime_decode_to_string(Base64Bin),
+ ok.
+
+random_byte_list(0, Acc) ->
+ Acc;
+random_byte_list(N, Acc) ->
+ random_byte_list(N-1, [random:uniform(255)|Acc]).
+
+make_big_binary(N) ->
+ list_to_binary(mbb(N, [])).
+
+mbb(N, Acc) when N > 256 ->
+ B = list_to_binary(lists:seq(0, 255)),
+ mbb(N - 256, [B | Acc]);
+mbb(N, Acc) ->
+ B = list_to_binary(lists:seq(0, N-1)),
+ lists:reverse(Acc, B).
diff --git a/lib/stdlib/test/beam_lib_SUITE.erl b/lib/stdlib/test/beam_lib_SUITE.erl
new file mode 100644
index 0000000000..bc867a3770
--- /dev/null
+++ b/lib/stdlib/test/beam_lib_SUITE.erl
@@ -0,0 +1,761 @@
+%%
+%% %CopyrightBegin%
+%%
+%% %CopyrightEnd%
+%%
+-module(beam_lib_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), "./log_dir/").
+-define(t,test_server).
+-define(privdir, "beam_lib_SUITE_priv").
+-else.
+-include("test_server.hrl").
+-define(format(S, A), ok).
+-define(privdir, ?config(priv_dir, Conf)).
+-endif.
+
+-export([all/1, normal/1, error/1, cmp/1, cmp_literals/1, strip/1, otp_6711/1,
+ building/1, md5/1, encrypted_abstr/1, encrypted_abstr_file/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+all(suite) ->
+ [error, normal, cmp, cmp_literals, strip, otp_6711, building, md5,
+ encrypted_abstr, encrypted_abstr_file].
+
+init_per_testcase(_Case, Config) ->
+ Dog=?t:timetrap(?t:minutes(2)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+normal(suite) -> [];
+normal(doc) -> ["Read correct beam file"];
+normal(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line simple_file(Source),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ CompileFlags = [{outdir,PrivDir}, debug_info],
+ ?line {ok,_} = compile:file(Source, CompileFlags),
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line do_normal(BeamFile),
+ ?line do_normal(Binary),
+
+ ?line {ok,_} = compile:file(Source, [{outdir,PrivDir}, no_debug_info]),
+ ?line {ok, {simple, [{abstract_code, no_abstract_code}]}} =
+ beam_lib:chunks(BeamFile, [abstract_code]),
+
+ %% ?line {ok,_} = compile:file(Source, [compressed | CompileFlags]),
+ %% ?line do_normal(BeamFile),
+
+ ?line file:delete(BeamFile),
+ ?line file:delete(Source),
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ok.
+
+do_normal(BeamFile) ->
+ ?line Imports = {imports, [{erlang, get_module_info, 1},
+ {erlang, get_module_info, 2},
+ {lists, member, 2}]},
+ ?line Exports = {exports, [{module_info, 0}, {module_info, 1}, {t, 0}]},
+ ?line Local = {locals, [{t, 1}]},
+ ?line {ok, {simple, [Imports]}} = beam_lib:chunks(BeamFile, [imports]),
+ ?line {ok, {simple, [{"ImpT",_Bin}]}} =
+ beam_lib:chunks(BeamFile, ["ImpT"]),
+ ?line {ok, {simple, [Exports]}} = beam_lib:chunks(BeamFile, [exports]),
+ ?line {ok, {simple, [{attributes, [{vsn, [_]}]}]}} =
+ beam_lib:chunks(BeamFile, [attributes]),
+ ?line {ok, {simple, [{compile_info, _}=CompileInfo]}} =
+ beam_lib:chunks(BeamFile, [compile_info]),
+ ?line {ok, {simple, [Local]}} = beam_lib:chunks(BeamFile, [locals]),
+ ?line {ok, {simple, [{attributes, [{vsn, [_]}]}, CompileInfo,
+ Exports, Imports, Local]}} =
+ beam_lib:chunks(BeamFile, [attributes, compile_info, exports, imports, locals]),
+ ?line {ok, {simple, [{atoms, _Atoms}]}} =
+ beam_lib:chunks(BeamFile, [atoms]),
+ ?line {ok, {simple, [{labeled_exports, _LExports}]}} =
+ beam_lib:chunks(BeamFile, [labeled_exports]),
+ ?line {ok, {simple, [{labeled_locals, _LLocals}]}} =
+ beam_lib:chunks(BeamFile, [labeled_locals]),
+ ?line {ok, {simple, [_Vsn]}} = beam_lib:version(BeamFile),
+ ?line {ok, {simple, [{abstract_code, _}]}} =
+ beam_lib:chunks(BeamFile, [abstract_code]),
+
+ %% Test reading optional chunks.
+ All = ["Atom", "Code", "StrT", "ImpT", "ExpT", "FunT", "LitT"],
+ ?line {ok,{simple,Chunks}} = beam_lib:chunks(BeamFile, All, [allow_missing_chunks]),
+ ?line verify_simple(Chunks).
+
+verify_simple([{"Atom", AtomBin},
+ {"Code", CodeBin},
+ {"StrT", StrBin},
+ {"ImpT", ImpBin},
+ {"ExpT", ExpBin},
+ {"FunT", missing_chunk},
+ {"LitT", missing_chunk}])
+ when is_binary(AtomBin), is_binary(CodeBin), is_binary(StrBin),
+ is_binary(ImpBin), is_binary(ExpBin) ->
+ ok.
+
+error(suite) -> [];
+error(doc) -> ["Read invalid beam files"];
+error(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line WrongFile = Simple ++ "foo.beam",
+ ?line simple_file(Source),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+ ?line {ok,_} = compile:file(Source, [{outdir,PrivDir},debug_info]),
+ ?line ACopy = filename:join(PrivDir, "a_copy.beam"),
+ ?line copy_file(BeamFile, ACopy),
+
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line copy_file(ACopy, WrongFile),
+ ?line verify(file_error, beam_lib:info("./does_simply_not_exist")),
+
+ ?line do_error(BeamFile, ACopy),
+ ?line do_error(Binary, ACopy),
+
+ ?line copy_file(ACopy, BeamFile),
+ ?line verify(unknown_chunk, beam_lib:chunks(BeamFile, [not_a_chunk])),
+
+ ?line ok = file:write_file(BeamFile, <<>>),
+ ?line verify(not_a_beam_file, beam_lib:info(BeamFile)),
+ ?line verify(not_a_beam_file, beam_lib:info(<<>>)),
+ ?line ok = file:write_file(BeamFile, <<"short">>),
+ ?line verify(not_a_beam_file, beam_lib:info(BeamFile)),
+ ?line verify(not_a_beam_file, beam_lib:info(<<"short">>)),
+
+ ?line {Binary1, _} = split_binary(Binary, byte_size(Binary)-10),
+ ?line verify(chunk_too_big, beam_lib:chunks(Binary1, ["Abst"])),
+ ?line Chunks = chunk_info(Binary),
+ ?line {value, {_, AbstractStart, _}} = lists:keysearch("Abst", 1, Chunks),
+ ?line {Binary2, _} = split_binary(Binary, AbstractStart),
+ ?line verify(chunk_too_big, beam_lib:chunks(Binary2, ["Abst"])),
+ ?line {Binary3, _} = split_binary(Binary, AbstractStart-4),
+ ?line verify(invalid_beam_file, beam_lib:chunks(Binary3, ["Abst"])),
+
+ %% Instead of the 5:32 field below, there used to be control characters
+ %% (including zero bytes) directly in the string. Because inferior programs
+ %% such as sed and clearcasediff don't like zero bytes in text files,
+ %% we have eliminated them.
+ ?line ok = file:write_file(BeamFile, <<"FOR1",5:32,"BEAMfel">>),
+% ?line verify(invalid_beam_file, beam_lib:info(BeamFile)),
+% ?line verify(invalid_beam_file, beam_lib:info(<<"FOR1",5:32,"BEAMfel">>)),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ?line file:delete(Source),
+ ?line file:delete(WrongFile),
+ ?line file:delete(BeamFile),
+ ?line file:delete(ACopy),
+ ok.
+
+do_error(BeamFile, ACopy) ->
+ % evil tests
+ ?line Chunks = chunk_info(BeamFile),
+ ?line {value, {_, AtomStart, _}} = lists:keysearch("Atom", 1, Chunks),
+ ?line {value, {_, ImportStart, _}} = lists:keysearch("ImpT", 1, Chunks),
+ ?line {value, {_, AbstractStart, _}} = lists:keysearch("Abst", 1, Chunks),
+ ?line {value, {_, AttributesStart, _}} =
+ lists:keysearch("Attr", 1, Chunks),
+ ?line {value, {_, CompileInfoStart, _}} =
+ lists:keysearch("CInf", 1, Chunks),
+ ?line verify(missing_chunk, beam_lib:chunks(BeamFile, ["__"])),
+ ?line BF2 = set_byte(ACopy, BeamFile, ImportStart+4, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF2, [imports])),
+ ?line BF3 = set_byte(ACopy, BeamFile, AtomStart-6, 17),
+ ?line verify(missing_chunk, beam_lib:chunks(BF3, [imports])),
+ ?line BF4 = set_byte(ACopy, BeamFile, AbstractStart+10, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF4, [abstract_code])),
+ ?line BF5 = set_byte(ACopy, BeamFile, AttributesStart+10, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF5, [attributes])),
+
+ ?line BF6 = set_byte(ACopy, BeamFile, 1, 17),
+ ?line verify(not_a_beam_file, beam_lib:info(BF6)),
+ ?line BF7 = set_byte(ACopy, BeamFile, 9, 17),
+ ?line verify(not_a_beam_file, beam_lib:info(BF7)),
+
+ ?line BF8 = set_byte(ACopy, BeamFile, 13, 17),
+ ?line verify(missing_chunk, beam_lib:chunks(BF8, ["Atom"])),
+
+ ?line BF9 = set_byte(ACopy, BeamFile, CompileInfoStart+10, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF9, [compile_info])).
+
+
+cmp(suite) -> [];
+cmp(doc) -> ["Compare contents of BEAM files and directories"];
+cmp(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+
+ ?line Dir1 = filename:join(PrivDir, dir1),
+ ?line Dir2 = filename:join(PrivDir, dir2),
+
+ ok = file:make_dir(Dir1),
+ ok = file:make_dir(Dir2),
+
+ ?line {SourceD1, BeamFileD1} = make_beam(Dir1, simple, member),
+ ?line {Source2D1, BeamFile2D1} = make_beam(Dir1, simple2, concat),
+ ?line {SourceD2, BeamFileD2} = make_beam(Dir2, simple, concat),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% cmp
+ ?line ok = beam_lib:cmp(BeamFileD1, BeamFileD1),
+ ?line ver(modules_different, beam_lib:cmp(BeamFileD1, BeamFile2D1)),
+ ?line ver(chunks_different, beam_lib:cmp(BeamFileD1, BeamFileD2)),
+ ?line verify(file_error, beam_lib:cmp(foo, bar)),
+
+ ?line {ok, B1} = file:read_file(BeamFileD1),
+ ?line ok = beam_lib:cmp(B1, BeamFileD1),
+ ?line {ok, B2} = file:read_file(BeamFileD2),
+ ?line ver(chunks_different, beam_lib:cmp(B1, B2)),
+
+ %% cmp_dirs
+ ?line {[],[],[]} = beam_lib:cmp_dirs(Dir1, Dir1),
+ ?line true = {[BeamFile2D1], [], [{BeamFileD1,BeamFileD2}]} ==
+ beam_lib:cmp_dirs(Dir1, Dir2),
+ ?line true = {[], [BeamFile2D1], [{BeamFileD2,BeamFileD1}]} ==
+ beam_lib:cmp_dirs(Dir2, Dir1),
+ ?line ver(not_a_directory, beam_lib:cmp_dirs(foo, bar)),
+
+ %% diff_dirs
+ ?line ok = beam_lib:diff_dirs(Dir1, Dir1),
+ ?line ver(not_a_directory, beam_lib:diff_dirs(foo, bar)),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+ ?line delete_files([SourceD1, BeamFileD1, Source2D1,
+ BeamFile2D1, SourceD2, BeamFileD2]),
+
+ file:del_dir(Dir1),
+ file:del_dir(Dir2),
+ ok.
+
+cmp_literals(suite) -> [];
+cmp_literals(doc) -> ["Compare contents of BEAM files having literals"];
+cmp_literals(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+
+ ?line Dir1 = filename:join(PrivDir, dir1),
+ ?line Dir2 = filename:join(PrivDir, dir2),
+
+ ok = file:make_dir(Dir1),
+ ok = file:make_dir(Dir2),
+
+ ?line {SourceD1, BeamFileD1} = make_beam(Dir1, simple, constant),
+ ?line {SourceD2, BeamFileD2} = make_beam(Dir2, simple, constant2),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% cmp
+ ?line ok = beam_lib:cmp(BeamFileD1, BeamFileD1),
+ ?line ver(chunks_different, beam_lib:cmp(BeamFileD1, BeamFileD2)),
+
+ ?line {ok, B1} = file:read_file(BeamFileD1),
+ ?line ok = beam_lib:cmp(B1, BeamFileD1),
+ ?line {ok, B2} = file:read_file(BeamFileD2),
+ ?line ver(chunks_different, beam_lib:cmp(B1, B2)),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+
+ ?line delete_files([SourceD1, BeamFileD1, SourceD2, BeamFileD2]),
+
+ file:del_dir(Dir1),
+ file:del_dir(Dir2),
+ ok.
+
+strip(suite) -> [];
+strip(doc) -> ["Strip BEAM files"];
+strip(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line {SourceD1, BeamFileD1} = make_beam(PrivDir, simple, member),
+ ?line {Source2D1, BeamFile2D1} = make_beam(PrivDir, simple2, concat),
+ ?line {Source3D1, BeamFile3D1} = make_beam(PrivDir, make_fun, make_fun),
+ ?line {Source4D1, BeamFile4D1} = make_beam(PrivDir, constant, constant),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% strip binary
+ ?line verify(not_a_beam_file, beam_lib:strip(<<>>)),
+ ?line {ok, B1} = file:read_file(BeamFileD1),
+ ?line {ok, {simple, NB1}} = beam_lib:strip(B1),
+ ?line BId1 = chunk_ids(B1),
+ ?line NBId1 = chunk_ids(NB1),
+ ?line true = length(BId1) > length(NBId1),
+ ?line compare_chunks(B1, NB1, NBId1),
+
+ %% strip file
+ ?line verify(file_error, beam_lib:strip(foo)),
+ ?line {ok, {simple, _}} = beam_lib:strip(BeamFileD1),
+ ?line compare_chunks(NB1, BeamFileD1, NBId1),
+
+ %% strip_files
+ ?line {ok, B2} = file:read_file(BeamFile2D1),
+ ?line {ok, [{simple,_},{simple2,_}]} = beam_lib:strip_files([B1, B2]),
+ ?line {ok, [{simple,_},{simple2,_},{make_fun,_},{constant,_}]} =
+ beam_lib:strip_files([BeamFileD1, BeamFile2D1, BeamFile3D1, BeamFile4D1]),
+
+ %% check that each module can be loaded.
+ ?line {module, simple} = code:load_abs(filename:rootname(BeamFileD1)),
+ ?line {module, simple2} = code:load_abs(filename:rootname(BeamFile2D1)),
+ ?line {module, make_fun} = code:load_abs(filename:rootname(BeamFile3D1)),
+ ?line {module, constant} = code:load_abs(filename:rootname(BeamFile4D1)),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+
+ ?line delete_files([SourceD1, BeamFileD1,
+ Source2D1, BeamFile2D1,
+ Source3D1, BeamFile3D1,
+ Source4D1, BeamFile4D1]),
+ ok.
+
+
+otp_6711(Conf) when is_list(Conf) ->
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:info(3)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:chunks(a, b)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:chunks(a,b,c)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:all_chunks(3)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:cmp(3,4)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:strip(3)}),
+ ?line {'EXIT',{function_clause,_}} =
+ (catch {a, beam_lib:strip_files([3])}),
+
+ ?line PrivDir = ?privdir,
+ ?line Dir = filename:join(PrivDir, dir),
+ ?line Lib = filename:join(Dir, "lib"),
+ ?line App = filename:join(Lib, "app"),
+ ?line EBin = filename:join(App, "ebin"),
+
+ ok = file:make_dir(Dir),
+ ok = file:make_dir(Lib),
+ ok = file:make_dir(App),
+ ok = file:make_dir(EBin),
+
+ ?line {SourceD, BeamFileD} = make_beam(EBin, simple, member),
+
+ unwritable(BeamFileD),
+
+ %% There is no way that strip_release can fail with
+ %% function_clause or something like that...
+ ?line {error,_,{file_error,_,_}} = beam_lib:strip_release(Dir),
+
+ ?line delete_files([SourceD, BeamFileD]),
+ file:del_dir(EBin),
+ file:del_dir(App),
+ file:del_dir(Lib),
+ file:del_dir(Dir),
+ ok.
+
+-include_lib("kernel/include/file.hrl").
+
+unwritable(Fname) ->
+ {ok, Info} = file:read_file_info(Fname),
+ Mode = Info#file_info.mode - 8#00200,
+ file:write_file_info(Fname, Info#file_info{mode = Mode}).
+
+building(doc) -> "Testing building of BEAM files.";
+building(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+
+ ?line Dir1 = filename:join(PrivDir, b_dir1),
+ ?line Dir2 = filename:join(PrivDir, b_dir2),
+
+ ok = file:make_dir(Dir1),
+ ok = file:make_dir(Dir2),
+
+ ?line {SourceD1, BeamFileD1} = make_beam(Dir1, building, member),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% read all chunks
+ ?line ChunkIds = chunk_ids(BeamFileD1),
+ ?line {ok, _Mod, Chunks} = beam_lib:all_chunks(BeamFileD1),
+ ?line ChunkIds = lists:map(fun ({Id, Data}) when is_binary(Data) -> Id
+ end, Chunks),
+
+ %% write a new beam file, with reversed chunk order
+ ?line BeamFileD2 = filename:join(Dir2, "building.beam"),
+ ?line {ok,RevBeam} = beam_lib:build_module(lists:reverse(Chunks)),
+ ?line file:write_file(BeamFileD2, RevBeam),
+
+ %% compare files
+ ?line compare_chunks(BeamFileD1, BeamFileD2, ChunkIds),
+
+ %% test that we can retrieve a chunk before the atom table
+ %% (actually, try to retrieve all chunks)
+
+ ?line lists:foreach(fun(Id) ->
+ {ok, {building, [{Id, _Data}]}} =
+ beam_lib:chunks(BeamFileD1, [Id])
+ end, ChunkIds),
+ ?line lists:foreach(fun(Id) ->
+ {ok, {building, [{Id, _Data}]}} =
+ beam_lib:chunks(BeamFileD2, [Id])
+ end, ChunkIds),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+
+ ?line delete_files([SourceD1, BeamFileD1, BeamFileD2]),
+ file:del_dir(Dir1),
+ file:del_dir(Dir2),
+ ok.
+
+md5(suite) -> [];
+md5(doc) -> ["Compare beam_lib:md5/1 and code:module_md5/1."];
+md5(Conf) when is_list(Conf) ->
+ ?line Beams = collect_beams(),
+ io:format("Found ~w beam files", [length(Beams)]),
+ md5_1(Beams).
+
+md5_1([N|Ns]) ->
+ {ok,Beam0} = file:read_file(N),
+ Beam = maybe_uncompress(Beam0),
+ {ok,{Mod,MD5}} = beam_lib:md5(Beam),
+ {Mod,MD5} = {Mod,code:module_md5(Beam)},
+ md5_1(Ns);
+md5_1([]) -> ok.
+
+collect_beams() ->
+ SuperDir = filename:dirname(filename:dirname(code:which(?MODULE))),
+ TestDirs = filelib:wildcard(filename:join([SuperDir,"*_test"])),
+ AbsDirs = [filename:absname(X) || X <- code:get_path()],
+ collect_beams_1(AbsDirs ++ TestDirs).
+
+collect_beams_1([Dir|Dirs]) ->
+ filelib:wildcard(filename:join(Dir, "*.beam")) ++ collect_beams_1(Dirs);
+collect_beams_1([]) -> [].
+
+maybe_uncompress(<<"FOR1",_/binary>>=Beam) -> Beam;
+maybe_uncompress(Beam) -> zlib:gunzip(Beam).
+
+encrypted_abstr(suite) -> [];
+encrypted_abstr(doc) -> ["Test encrypted abstract format"];
+encrypted_abstr(Conf) when is_list(Conf) ->
+ run_if_crypto_works(fun() -> encrypted_abstr_1(Conf) end).
+
+encrypted_abstr_1(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line simple_file(Source),
+
+ %% Avoid getting an extra port when crypto starts erl_ddll.
+ ?line erl_ddll:start(),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ Key = "#a_crypto_key",
+ CompileFlags = [{outdir,PrivDir}, debug_info, {debug_info_key,Key}],
+ ?line {ok,_} = compile:file(Source, CompileFlags),
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line do_encrypted_abstr(BeamFile, Key),
+ ?line do_encrypted_abstr(Binary, Key),
+
+ ?line ok = crypto:stop(), %To get rid of extra ets tables.
+ ?line file:delete(BeamFile),
+ ?line file:delete(Source),
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ok.
+
+do_encrypted_abstr(Beam, Key) ->
+ ?line verify(key_missing_or_invalid, beam_lib:chunks(Beam, [abstract_code])),
+
+ %% The raw chunk "Abst" can still be read even without a key.
+ ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
+ ?line <<0:8,8:8,"des3_cbc",_/binary>> = Abst,
+
+ %% Try som invalid funs.
+ ?line bad_fun(badfun, fun() -> ok end),
+ ?line bad_fun(badfun, {a,b}),
+ ?line bad_fun(blurf),
+ ?line {function_clause,_} = bad_fun(fun(glurf) -> ok end),
+
+ %% Funs that return something strange.
+ ?line bad_fun(badfun, fun(init) -> {ok,fun() -> ok end} end),
+ ?line glurf = bad_fun(fun(init) -> {error,glurf} end),
+
+ %% Try clearing (non-existing fun).
+ ?line undefined = beam_lib:clear_crypto_key_fun(),
+
+ %% Install a fun which cannot retrieve a key.
+ ?line ok = beam_lib:crypto_key_fun(fun(init) -> ok end),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Install a fun which returns an incorrect key.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun("wrong key...")),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Installing a new key fun is not possible without clearing the old.
+ ?line verify(exists, beam_lib:crypto_key_fun(simple_crypto_fun(Key))),
+
+ %% Install the simplest possible working key fun.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun(Key)),
+ ?line verify_abstract(Beam),
+ ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
+
+ %% Installing a new key fun is not possible without clearing the old.
+ verify(exists, beam_lib:crypto_key_fun(ets_crypto_fun(Key))),
+
+ %% Install a key using an ets table.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line ok = beam_lib:crypto_key_fun(ets_crypto_fun(Key)),
+ ?line verify_abstract(Beam),
+ ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
+
+ ?line {ok,cleared} = beam_lib:clear_crypto_key_fun(),
+ ok.
+
+
+bad_fun(F) ->
+ {error,E} = beam_lib:crypto_key_fun(F),
+ E.
+
+bad_fun(S, F) ->
+ verify(S, beam_lib:crypto_key_fun(F)).
+
+
+verify_abstract(Beam) ->
+ {ok,{simple,[Chunk]}} = beam_lib:chunks(Beam, [abstract_code]),
+ {abstract_code,{raw_abstract_v1,_}} = Chunk.
+
+simple_crypto_fun(Key) ->
+ fun(init) -> ok;
+ ({debug_info, des3_cbc, simple, _}) -> Key
+ end.
+
+ets_crypto_fun(Key) ->
+ fun(init) ->
+ T = ets:new(beam_lib_SUITE_keys, [private, set]),
+ true = ets:insert(T, {key,Key}),
+ {ok,fun({debug_info, des3_cbc, simple, _}) ->
+ [{key,Val}] = ets:lookup(T, key),
+ Val;
+ (clear) ->
+ ets:delete(T),
+ cleared
+ end}
+ end.
+
+encrypted_abstr_file(suite) -> [];
+encrypted_abstr_file(doc) ->
+ ["Test encrypted abstract format with the key in .erlang.crypt"];
+encrypted_abstr_file(Conf) when is_list(Conf) ->
+ run_if_crypto_works(fun() -> encrypted_abstr_file_1(Conf) end).
+
+encrypted_abstr_file_1(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line simple_file(Source),
+
+ %% Avoid getting an extra port when crypto starts erl_ddll.
+ ?line erl_ddll:start(),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ Key = "Long And niCe 99Krypto Key",
+ CompileFlags = [{outdir,PrivDir}, debug_info, {debug_info_key,Key}],
+ ?line {ok,_} = compile:file(Source, CompileFlags),
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line {ok,OldCwd} = file:get_cwd(),
+ ?line ok = file:set_cwd(PrivDir),
+ ?line do_encrypted_abstr_file(BeamFile, Key),
+ ?line do_encrypted_abstr_file(Binary, Key),
+ ?line ok = file:set_cwd(OldCwd),
+
+ ?line ok = crypto:stop(), %To get rid of extra ets tables.
+ ?line file:delete(filename:join(PrivDir, ".erlang.crypt")),
+ ?line file:delete(BeamFile),
+ ?line file:delete(Source),
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ok.
+
+do_encrypted_abstr_file(Beam, Key) ->
+ %% No key.
+ ?line write_crypt_file(""),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% A wrong key.
+ ?line write_crypt_file(["[{debug_info,des3_cbc,simple,\"A Wrong Key\"}].\n"]),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Write correct key...
+ ?line write_crypt_file(["[{debug_info,des3_cbc,simple,\"",Key,"\"}].\n"]),
+
+ %% ... but the fun with the wrong key is still there.
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Clear the fun. Now it should work.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line verify_abstract(Beam),
+ ?line verify_abstract(Beam),
+ ?line ok = file:delete(".erlang.crypt"),
+ ?line verify_abstract(Beam),
+
+ %% Clear, otherwise the second pass will fail.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+ ok.
+
+write_crypt_file(Contents0) ->
+ Contents = list_to_binary([Contents0]),
+ io:format("~s\n", [binary_to_list(Contents)]),
+ ok = file:write_file(".erlang.crypt", Contents).
+
+compare_chunks(File1, File2, ChunkIds) ->
+ ?line {ok, {_, Chunks1}} = beam_lib:chunks(File1, ChunkIds),
+ ?line {ok, {_, Chunks2}} = beam_lib:chunks(File2, ChunkIds),
+ ?line true = Chunks1 == Chunks2.
+
+chunk_ids(File) ->
+ ?line lists:map(fun({Id,_Start,_Size}) -> Id end, chunk_info(File)).
+
+chunk_info(File) ->
+ ?line {value, {chunks, Chunks}} =
+ lists:keysearch(chunks, 1, beam_lib:info(File)),
+ Chunks.
+
+make_beam(Dir, Module, F) ->
+ ?line FileBase = filename:join(Dir, Module),
+ ?line Source = FileBase ++ ".erl",
+ ?line BeamFile = FileBase ++ ".beam",
+ ?line simple_file(Source, Module, F),
+ ?line {ok, _} = compile:file(Source, [{outdir,Dir}, debug_info, report]),
+ {Source, BeamFile}.
+
+set_byte(_Backup, Binary, Pos, Byte) when is_binary(Binary) ->
+ ?line <<B1:Pos/binary, _:1/binary, B2/binary>> = Binary,
+ NB = <<B1/binary, Byte:8, B2/binary>>,
+ NB;
+set_byte(Backup, File, Pos, Byte) ->
+ ?line copy_file(Backup, File),
+ ?line set_byte(File, Pos, Byte),
+ File.
+
+set_byte(File, Pos, Byte) ->
+ ?line {ok, Fd} = file:open(File, [read, write]),
+ ?line {ok, _} = file:position(Fd, Pos),
+ ?line ok = file:write(Fd, [Byte]),
+ ?line file:close(Fd).
+
+copy_file(Src, Dest) ->
+ % ?t:format("copying from ~p to ~p~n", [Src, Dest]),
+ ?line {ok, _} = file:copy(Src, Dest),
+ ?line ok = file:change_mode(Dest, 8#0666).
+
+delete_files(Files) ->
+ lists:foreach(fun(F) -> file:delete(F) end, Files).
+
+verify(S, {error, beam_lib, R}) ->
+ verify_error(S, R);
+verify(S, {error, R}) ->
+ verify_error(S, R).
+
+verify_error(S, R) ->
+ if
+ S =:= R -> ok;
+ true -> [S|_] = tuple_to_list(R)
+ end,
+
+ %% Most formatted messages begin with "./simple.beam:" or "<<...".
+ FM = string:str(lists:flatten(beam_lib:format_error(R)), "simpl") > 0,
+ BM = string:str(lists:flatten(beam_lib:format_error(R)), "<<") > 0,
+
+ %% Also make sure that formatted message is not just the term printed.
+ Handled = beam_lib:format_error(R) =/= io_lib:format("~p~n", [R]),
+ true = ((FM > 0) or (BM > 0)) and Handled.
+
+ver(S, {error, beam_lib, R}) ->
+ [S|_] = tuple_to_list(R),
+ case lists:flatten(beam_lib:format_error(R)) of
+ [${ | _] ->
+ test_server:fail({bad_format_error, R});
+ _ ->
+ ok
+ end.
+
+pps() ->
+ {erlang:ports()}.
+
+simple_file(File) ->
+ simple_file(File, simple).
+
+simple_file(File, Module) ->
+ simple_file(File, Module, member).
+
+simple_file(File, Module, make_fun) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/1]). "
+ "t(A) -> "
+ " fun(X) -> A+X end. "]),
+ ok = file:write_file(File, B);
+simple_file(File, Module, constant) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/1]). "
+ "t(A) -> "
+ " {a,b,[2,3],c,d}. "]),
+ ok = file:write_file(File, B);
+simple_file(File, Module, constant2) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/1]). "
+ "t(A) -> "
+ " {a,b,[2,3],x,y}. "]),
+ ok = file:write_file(File, B);
+simple_file(File, Module, F) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/0]). "
+ "t() -> "
+ " t([]). "
+ "t(L) -> "
+ " lists:",
+ atom_to_list(F), "(a, L). "]),
+ ok = file:write_file(File, B).
+
+run_if_crypto_works(Test) ->
+ try begin crypto:start(), crypto:info(), crypto:stop(), ok end of
+ ok ->
+ Test()
+ catch
+ error:_ ->
+ {skip,"The crypto application is missing or broken"}
+ end.
+
diff --git a/lib/stdlib/test/c_SUITE.erl b/lib/stdlib/test/c_SUITE.erl
new file mode 100644
index 0000000000..5608d73d19
--- /dev/null
+++ b/lib/stdlib/test/c_SUITE.erl
@@ -0,0 +1,116 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(c_SUITE).
+-export([all/1]).
+-export([c_1/1, c_2/1, c_3/1, c_4/1, memory/1]).
+
+-include("test_server.hrl").
+
+-import(c, [c/2]).
+
+all(doc) -> ["Test cases for the 'c' module."];
+all(suite) ->
+ [c_1, c_2, c_3, c_4, memory].
+
+%%% Write output to a directory other than current directory:
+
+c_1(doc) ->
+ ["Checks that c:c works also with option 'outdir' [ticket OTP-1209]."];
+c_1(suite) ->
+ [];
+c_1(Config) when list(Config) ->
+ ?line R = filename:join(?config(data_dir, Config), "m.erl"),
+ ?line W = ?config(priv_dir, Config),
+ ?line Result = c(R,[{outdir,W}]),
+ ?line {ok, m} = Result.
+
+c_2(doc) ->
+ ["Checks that c:c works also with option 'outdir' [ticket OTP-1209]."];
+c_2(suite) ->
+ [];
+c_2(Config) when list(Config) ->
+ ?line R = filename:join(?config(data_dir, Config), "m"),
+ ?line W = ?config(priv_dir, Config),
+ ?line Result = c(R,[{outdir,W}]),
+ ?line {ok, m} = Result.
+
+
+%%% Put results in current directory (or rather, change current dir
+%%% to the output dir):
+
+c_3(doc) ->
+ ["Checks that c:c works also with option 'outdir' (same as current"
+ "directory). [ticket OTP-1209]."];
+c_3(suite) ->
+ [];
+c_3(Config) when list(Config) ->
+ ?line R = filename:join(?config(data_dir, Config), "m.erl"),
+ ?line W = ?config(priv_dir, Config),
+ ?line file:set_cwd(W),
+ ?line Result = c(R,[{outdir,W}]),
+ ?line {ok, m} = Result.
+
+c_4(doc) ->
+ ["Checks that c:c works also with option 'outdir' (same as current"
+ "directory). [ticket OTP-1209]."];
+c_4(suite) ->
+ [];
+c_4(Config) when list(Config) ->
+ ?line R = filename:join(?config(data_dir, Config), "m"),
+ ?line W = ?config(priv_dir, Config),
+ ?line file:set_cwd(W),
+ ?line Result = c(R,[{outdir,W}]),
+ ?line {ok, m} = Result.
+
+memory(doc) ->
+ ["Checks that c:memory/[0,1] returns consistent results."];
+memory(suite) ->
+ [];
+memory(Config) when list(Config) ->
+ try
+ ?line ML = c:memory(),
+ ?line T = mget(total, ML),
+ ?line P = mget(processes, ML),
+ ?line S = mget(system, ML),
+ ?line A = mget(atom, ML),
+ ?line AU = mget(atom_used, ML),
+ ?line B = mget(binary, ML),
+ ?line C = mget(code, ML),
+ ?line E = mget(ets, ML),
+ ?line T = P + S,
+ ?line if S >= A + B + C + E -> ok end,
+ ?line if A >= AU -> ok end,
+ ?line ok
+ catch
+ error:notsup ->
+ ?line {skipped,
+ "erlang:memory/[0,1] and c:memory/[0,1] not supported"}
+ end.
+
+% Help function for c_SUITE:memory/1
+mget(K, L) ->
+ ?line {value,{K,V}} = lists:keysearch(K, 1, L),
+ ?line test_v(c:memory(K)), % Check that c:memory/1 also accept this
+ % argument and returns an integer (usally
+ % *not* the same as V).
+ ?line test_v(V).
+
+% Help function for c_SUITE:memory/1
+test_v(V) when integer(V) ->
+ ?line V.
diff --git a/lib/stdlib/test/c_SUITE_data/m.erl b/lib/stdlib/test/c_SUITE_data/m.erl
new file mode 100644
index 0000000000..59e0d80b83
--- /dev/null
+++ b/lib/stdlib/test/c_SUITE_data/m.erl
@@ -0,0 +1,25 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(m).
+-export([factorial/1]).
+
+factorial(0) ->
+ 1;
+factorial(N) ->
+ N * factorial(N-1).
diff --git a/lib/stdlib/test/calendar_SUITE.erl b/lib/stdlib/test/calendar_SUITE.erl
new file mode 100644
index 0000000000..ea81bb99a9
--- /dev/null
+++ b/lib/stdlib/test/calendar_SUITE.erl
@@ -0,0 +1,251 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(calendar_SUITE).
+
+-include("test_server.hrl").
+
+-export([all/1,
+ gregorian_days/1,
+ gregorian_seconds/1,
+ day_of_the_week/1,
+ day_of_the_week_calibrate/1,
+ leap_years/1,
+ last_day_of_the_month/1,
+ local_time_to_universal_time_dst/1]).
+
+-define(START_YEAR, 1947).
+-define(END_YEAR, 2012).
+
+all(suite) -> [gregorian_days,
+ gregorian_seconds,
+ day_of_the_week,
+ day_of_the_week_calibrate,
+ leap_years,
+ last_day_of_the_month,
+ local_time_to_universal_time_dst];
+
+all(doc) -> "This is the test suite for calendar.erl".
+
+gregorian_days(doc) ->
+ "Tests that date_to_gregorian_days and gregorian_days_to_date "
+ "are each others inverses from ?START_YEAR-01-01 up to ?END_YEAR-01-01. "
+ "At the same time valid_date is tested.";
+gregorian_days(suite) ->
+ [];
+gregorian_days(Config) when list(Config) ->
+ ?line Days = calendar:date_to_gregorian_days({?START_YEAR, 1, 1}),
+ ?line MaxDays = calendar:date_to_gregorian_days({?END_YEAR, 1, 1}),
+ ?line check_gregorian_days(Days, MaxDays).
+
+gregorian_seconds(doc) ->
+ "Tests that datetime_to_gregorian_seconds and "
+ "gregorian_seconds_to_date are each others inverses for a sampled "
+ "number of seconds from ?START_YEAR-01-01 up to ?END_YEAR-01-01: We check "
+ "every 2 days + 1 second.";
+gregorian_seconds(suite) ->
+ [];
+gregorian_seconds(Config) when list(Config) ->
+ ?line Secs = calendar:datetime_to_gregorian_seconds({{?START_YEAR, 1, 1},
+ {0, 0, 0}}),
+ ?line MaxSecs = calendar:datetime_to_gregorian_seconds({{?END_YEAR, 1, 1},
+ {0, 0, 0}}),
+ ?line check_gregorian_seconds(Secs, MaxSecs).
+
+day_of_the_week(doc) ->
+ "Tests that day_of_the_week reports correctly the day of the week from "
+ "year ?START_YEAR up to ?END_YEAR.";
+day_of_the_week(suite) ->
+ [];
+day_of_the_week(Config) when list(Config) ->
+ ?line Days = calendar:date_to_gregorian_days({?START_YEAR, 1, 1}),
+ ?line MaxDays = calendar:date_to_gregorian_days({?END_YEAR, 1, 1}),
+ ?line DayNumber = calendar:day_of_the_week({?START_YEAR, 1, 1}),
+ ?line check_day_of_the_week(Days, MaxDays, DayNumber).
+
+day_of_the_week_calibrate(doc) ->
+ "Tests that day_of_the_week for 1997-11-11 is Tuesday (2)";
+day_of_the_week_calibrate(suite) ->
+ [];
+day_of_the_week_calibrate(Config) when list(Config) ->
+ ?line 2 = calendar:day_of_the_week({1997, 11, 11}).
+
+leap_years(doc) ->
+ "Tests that is_leap_year reports correctly the leap years from "
+ "year ?START_YEAR up to ?END_YEAR.";
+leap_years(suite) ->
+ [];
+leap_years(Config) when list(Config) ->
+ ?line check_leap_years(?START_YEAR, ?END_YEAR).
+
+last_day_of_the_month(doc) ->
+ "Tests that last_day_of_the_month reports correctly from "
+ "year ?START_YEAR up to ?END_YEAR.";
+last_day_of_the_month(suite) ->
+ [];
+last_day_of_the_month(Config) when list(Config) ->
+ ?line check_last_day_of_the_month({?START_YEAR, 1}, {?END_YEAR, 1}).
+
+local_time_to_universal_time_dst(doc) ->
+ "Tests local_time_to_universal_time_dst for MET";
+local_time_to_universal_time_dst(suite) ->
+ [];
+local_time_to_universal_time_dst(Config) when list(Config) ->
+ case os:type() of
+ {unix,_} ->
+ case os:cmd("date '+%Z'") of
+ "SAST"++_ ->
+ {comment, "Spoky time zone with zero-set DST, skipped"};
+ _ ->
+ local_time_to_universal_time_dst_x(Config)
+ end;
+ _ ->
+ local_time_to_universal_time_dst_x(Config)
+ end.
+local_time_to_universal_time_dst_x(Config) when list(Config) ->
+ %% Assumes MET (UTC+1 / UTC+2(dst)
+ ?line LtW = {{2003,01,15},{14,00,00}}, % Winter
+ ?line UtW = {{2003,01,15},{13,00,00}}, %
+ ?line UtWd = {{2003,01,15},{12,00,00}}, % dst
+ ?line LtS = {{2003,07,15},{14,00,00}}, % Summer
+ ?line UtS = {{2003,07,15},{13,00,00}}, %
+ ?line UtSd = {{2003,07,15},{12,00,00}}, % dst
+ ?line LtWS = {{2003,03,30},{02,30,00}}, % Winter->Summer
+ ?line UtWS = {{2003,03,30},{01,30,00}}, %
+ ?line UtWSd = {{2003,03,30},{00,30,00}}, % dst
+ ?line LtSW = {{2003,10,26},{02,30,00}}, % Summer->Winter
+ ?line UtSW = {{2003,10,26},{01,30,00}}, %
+ ?line UtSWd = {{2003,10,26},{00,30,00}}, % dst
+ %%
+ ?line UtW = calendar:local_time_to_universal_time(LtW, false),
+ ?line UtWd = calendar:local_time_to_universal_time(LtW, true),
+ ?line UtW = calendar:local_time_to_universal_time(LtW, undefined),
+ %%
+ ?line UtS = calendar:local_time_to_universal_time(LtS, false),
+ ?line UtSd = calendar:local_time_to_universal_time(LtS, true),
+ ?line UtSd = calendar:local_time_to_universal_time(LtS, undefined),
+ %%
+ case calendar:local_time_to_universal_time(LtWS, false) of
+ UtWS ->
+ ?line UtWSd = calendar:local_time_to_universal_time(LtWS, true),
+ ?line [] = calendar:local_time_to_universal_time_dst(LtWS),
+ %%
+ ?line UtSW = calendar:local_time_to_universal_time(LtSW, false),
+ ?line UtSWd = calendar:local_time_to_universal_time(LtSW, true),
+ ?line [UtSWd, UtSW] = calendar:local_time_to_universal_time_dst(LtSW),
+ ok;
+ {{1969,12,31},{23,59,59}} ->
+ %% It seems that Apple has no intention of fixing this bug in
+ %% Mac OS 10.3.9, and we have no intention of implementing a
+ %% workaround.
+ {comment,"Bug in mktime() in this OS"}
+ end.
+
+
+%%
+%% LOCAL FUNCTIONS
+%%
+
+%% check_gregorian_days
+%%
+check_gregorian_days(Days, MaxDays) when Days < MaxDays ->
+ ?line Date = calendar:gregorian_days_to_date(Days),
+ ?line true = calendar:valid_date(Date),
+ ?line Days = calendar:date_to_gregorian_days(Date),
+ ?line check_gregorian_days(Days + 1, MaxDays);
+check_gregorian_days(_Days, _MaxDays) ->
+ ok.
+
+%% check_gregorian_seconds
+%%
+%% We increment with something prime (172801 = 2 days + 1 second).
+%%
+check_gregorian_seconds(Secs, MaxSecs) when Secs < MaxSecs ->
+ ?line DateTime = calendar:gregorian_seconds_to_datetime(Secs),
+ ?line Secs = calendar:datetime_to_gregorian_seconds(DateTime),
+ ?line check_gregorian_seconds(Secs + 172801, MaxSecs);
+check_gregorian_seconds(_Secs, _MaxSecs) ->
+ ok.
+
+
+%% check_day_of_the_week
+%%
+check_day_of_the_week(Days, MaxDays, DayNumber) when Days < MaxDays ->
+ ?line Date = calendar:gregorian_days_to_date(Days),
+ ?line DayNumber = calendar:day_of_the_week(Date),
+ ?line check_day_of_the_week(Days + 1, MaxDays,
+ ((DayNumber rem 7) + 1));
+check_day_of_the_week(_Days, _MaxDays, _DayNumber) ->
+ ok.
+
+%% check_leap_years
+%%
+%% SYr must be larger than 1800, and EYr must be less than ?END_YEAR.
+%%
+check_leap_years(SYr, EYr) when SYr < EYr ->
+ ?line Rem = SYr rem 4,
+ case Rem of
+ 0 ->
+ case SYr of
+ 1900 ->
+ ?line false = calendar:is_leap_year(SYr);
+ 2000 ->
+ ?line true = calendar:is_leap_year(SYr);
+ _ ->
+ ?line true = calendar:is_leap_year(SYr)
+ end;
+ _ ->
+ ?line false = calendar:is_leap_year(SYr)
+ end,
+ check_leap_years(SYr + 1, EYr);
+check_leap_years(_SYr, _EYr) ->
+ ok.
+
+check_last_day_of_the_month({SYr, SMon}, {EYr, EMon}) when SYr < EYr ->
+ ?line LastDay = calendar:last_day_of_the_month(SYr, SMon),
+ ?line LastDay = case SMon of
+ 1 -> 31;
+ 2 ->
+ case calendar:is_leap_year(SYr) of
+ true -> 29;
+ false -> 28
+ end;
+ 3 -> 31;
+ 4 -> 30;
+ 5 -> 31;
+ 6 -> 30;
+ 7 -> 31;
+ 8 -> 31;
+ 9 -> 30;
+ 10 -> 31;
+ 11 -> 30;
+ 12 -> 31
+ end,
+ ?line NYr = case SMon of
+ 12 -> SYr + 1;
+ _ -> SYr
+ end,
+ ?line check_last_day_of_the_month({NYr, (SMon rem 12) + 1},
+ {EYr, EMon});
+check_last_day_of_the_month(_, _) ->
+ ok.
+
+
+
+
+
diff --git a/lib/stdlib/test/dets_SUITE.erl b/lib/stdlib/test/dets_SUITE.erl
new file mode 100644
index 0000000000..760e610e00
--- /dev/null
+++ b/lib/stdlib/test/dets_SUITE.erl
@@ -0,0 +1,4136 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dets_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(t, test_server).
+-define(privdir(_), "./dets_SUITE_priv").
+-define(datadir(_), "./dets_SUITE_data").
+-else.
+-include("test_server.hrl").
+-define(format(S, A), ok).
+-define(privdir(Conf), ?config(priv_dir, Conf)).
+-define(datadir(Conf), ?config(data_dir, Conf)).
+-endif.
+
+-export([all/1, not_run/1, newly_started/1, basic_v8/1, basic_v9/1,
+ open_v8/1, open_v9/1, sets_v8/1, sets_v9/1, bags_v8/1,
+ bags_v9/1, duplicate_bags_v8/1, duplicate_bags_v9/1,
+ access_v8/1, access_v9/1, dirty_mark/1, dirty_mark2/1,
+ bag_next_v8/1, bag_next_v9/1, oldbugs_v8/1, oldbugs_v9/1,
+ unsafe_assumptions/1, truncated_segment_array_v8/1,
+ truncated_segment_array_v9/1, open_file_v8/1, open_file_v9/1,
+ init_table_v8/1, init_table_v9/1, repair_v8/1, repair_v9/1,
+ hash_v8b_v8c/1, phash/1, fold_v8/1, fold_v9/1, fixtable_v8/1,
+ fixtable_v9/1, match_v8/1, match_v9/1, select_v8/1,
+ select_v9/1, update_counter/1, badarg/1, cache_sets_v8/1,
+ cache_sets_v9/1, cache_bags_v8/1, cache_bags_v9/1,
+ cache_duplicate_bags_v8/1, cache_duplicate_bags_v9/1,
+ otp_4208/1, otp_4989/1, many_clients/1, otp_4906/1, otp_5402/1,
+ simultaneous_open/1, insert_new/1, repair_continuation/1,
+ otp_5487/1, otp_6206/1, otp_6359/1, otp_4738/1, otp_7146/1,
+ otp_8070/1]).
+
+-export([dets_dirty_loop/0]).
+
+-export([histogram/1, sum_histogram/1, ave_histogram/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+%% Internal export.
+-export([client/2]).
+
+-import(lists,
+ [append/1, delete/2, duplicate/2, filter/2, foreach/2, keysearch/3,
+ last/1, map/2, member/2, reverse/1, seq/2, sort/1, usort/1]).
+
+-include_lib("kernel/include/file.hrl").
+
+-define(DETS_SERVER, dets).
+
+%% HEADSZ taken from dets_v8.erl and dets_v9.erl.
+-define(HEADSZ_v8, 40).
+-define(HEADSZ_v9, (56+28*4+16)).
+-define(NO_KEYS_POS_v9, 36).
+-define(CLOSED_PROPERLY_POS, 8).
+
+-define(NOT_PROPERLY_CLOSED,0).
+-define(CLOSED_PROPERLY,1).
+
+init_per_testcase(_Case, Config) ->
+ Dog=?t:timetrap(?t:minutes(15)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog=?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ case os:type() of
+ vxworks ->
+ [not_run];
+ _ ->
+ {req,[stdlib],
+ [basic_v8, basic_v9, open_v8, open_v9, sets_v8, sets_v9,
+ bags_v8, bags_v9, duplicate_bags_v8, duplicate_bags_v9,
+ newly_started, open_file_v8, open_file_v9,
+ init_table_v8, init_table_v9, repair_v8, repair_v9,
+ access_v8, access_v9, oldbugs_v8, oldbugs_v9,
+ unsafe_assumptions, truncated_segment_array_v8,
+ truncated_segment_array_v9, dirty_mark, dirty_mark2,
+ bag_next_v8, bag_next_v9, hash_v8b_v8c, phash, fold_v8,
+ fold_v9, fixtable_v8, fixtable_v9, match_v8, match_v9,
+ select_v8, select_v9, update_counter, badarg,
+ cache_sets_v8, cache_sets_v9, cache_bags_v8,
+ cache_bags_v9, cache_duplicate_bags_v8,
+ cache_duplicate_bags_v9, otp_4208, otp_4989, many_clients,
+ otp_4906, otp_5402, simultaneous_open, insert_new,
+ repair_continuation, otp_5487, otp_6206, otp_6359, otp_4738,
+ otp_7146, otp_8070]}
+ end.
+
+not_run(suite) -> [];
+not_run(Conf) when is_list(Conf) ->
+ {comment, "Not runnable VxWorks/NFS"}.
+
+newly_started(doc) ->
+ ["OTP-3621"];
+newly_started(suite) ->
+ [];
+newly_started(Config) when is_list(Config) ->
+ ?line true = is_alive(),
+ ?line {ok, Node} = test_server:start_node(slave1, slave, []),
+ ?line [] = rpc:call(Node, dets, all, []),
+ ?line test_server:stop_node(Node),
+ ok.
+
+basic_v8(doc) ->
+ ["Basic test case."];
+basic_v8(suite) ->
+ [];
+basic_v8(Config) when is_list(Config) ->
+ basic(Config, 8).
+
+basic_v9(doc) ->
+ ["Basic test case."];
+basic_v9(suite) ->
+ [];
+basic_v9(Config) when is_list(Config) ->
+ basic(Config, 9).
+
+basic(Config, Version) ->
+ ?line Tab = dets_basic_test,
+ ?line FName = filename(Tab, Config),
+
+ P0 = pps(),
+ ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,Version}]),
+ ?line ok = dets:insert(Tab,{mazda,japan}),
+ ?line ok = dets:insert(Tab,{toyota,japan}),
+ ?line ok = dets:insert(Tab,{suzuki,japan}),
+ ?line ok = dets:insert(Tab,{honda,japan}),
+ ?line ok = dets:insert(Tab,{renault,france}),
+ ?line ok = dets:insert(Tab,{citroen,france}),
+ ?line ok = dets:insert(Tab,{opel,germany}),
+ ?line ok = dets:insert(Tab,{saab,sweden}),
+ ?line ok = dets:insert(Tab,{volvo,sweden}),
+ ?line [{opel,germany}] = dets:lookup(Tab,opel),
+ ?line Japs = dets:traverse(Tab, fun(Obj) ->
+ case Obj of
+ {_, japan} -> {continue, Obj};
+ _ -> continue
+ end
+ end),
+ ?line 4 = length(Japs),
+ ?line ok = dets:close(Tab),
+ ?line file:delete(FName),
+ ?line check_pps(P0),
+ ok.
+
+
+open_v8(doc) ->
+ [];
+open_v8(suite) ->
+ [];
+open_v8(Config) when is_list(Config) ->
+ open(Config, 8).
+
+open_v9(doc) ->
+ [];
+open_v9(suite) ->
+ [];
+open_v9(Config) when is_list(Config) ->
+ open(Config, 9).
+
+open(Config, Version) ->
+ %% Running this test twice means that the Dets server is restarted
+ %% twice. dets_sup specifies a maximum of 4 restarts in an hour.
+ %% If this becomes a problem, one should consider running this
+ %% test on a slave node.
+
+ ?line {Sets, Bags, Dups} = args(Config),
+
+ ?line All = Sets ++ Bags ++ Dups,
+ ?line delete_files(All),
+
+ ?line Data = make_data(1),
+
+ P0 = pps(),
+ ?line Tabs = open_files(1, All, Version),
+ ?line initialize(Tabs, Data),
+ ?line check(Tabs, Data),
+
+ ?line foreach(fun(Tab) -> ok = dets:close(Tab) end, Tabs),
+ %% Now reopen the files
+ ?format("Reopening closed files \n", []),
+ ?line Tabs = open_files(1, All, Version),
+ ?format("Checking contents of reopened files \n", []),
+ ?line check(Tabs, Data),
+ %% crash the dets server
+
+ ?format("Crashing dets server \n", []),
+ process_flag(trap_exit, true),
+ Procs = [whereis(?DETS_SERVER) | map(fun(Tab) -> dets:info(Tab, pid) end,
+ Tabs)],
+ foreach(fun(Pid) -> exit(Pid, kill) end, Procs),
+ timer:sleep(100),
+ c:flush(), %% flush all the EXIT sigs
+ timer:sleep(200),
+
+ %% Now reopen the files again
+ ?format("Reopening crashed files \n", []),
+ ?line open_files(1, All, Version),
+ ?format("Checking contents of repaired files \n", []),
+ ?line check(Tabs, Data),
+
+ ?line close_all(Tabs),
+
+ ?line delete_files(All),
+ P1 = pps(),
+ {Ports0, Procs0} = P0,
+ {Ports1, Procs1} = P1,
+ ?line true = Ports1 =:= Ports0,
+ %% The dets_server process has been restarted:
+ ?line [_] = Procs0 -- Procs1,
+ ?line [_] = Procs1 -- Procs0,
+ ok.
+
+check(Tabs, Data) ->
+ foreach(fun(Tab) ->
+ ?line Kp = dets:info(Tab, keypos),
+ ?format("checking ~p~n", [Tab]),
+ foreach(fun(Item) ->
+ case dets:lookup(Tab, k(Kp,Item)) of
+ [Item] -> ok;
+ _Other -> bad(Tab,Item)
+ end
+ end, Data)
+ end, Tabs),
+ ok.
+
+k(Kp, Obj) -> element(Kp, Obj).
+
+bad(_Tab, _Item) ->
+ ?format("Can't find item ~p in ~p ~n", [_Item, _Tab]),
+ exit(badtab).
+
+sets_v8(doc) ->
+ ["Performs traversal and match testing on set type dets tables."];
+sets_v8(suite) ->
+ [];
+sets_v8(Config) when is_list(Config) ->
+ sets(Config, 8).
+
+sets_v9(doc) ->
+ ["Performs traversal and match testing on set type dets tables."];
+sets_v9(suite) ->
+ [];
+sets_v9(Config) when is_list(Config) ->
+ sets(Config, 9).
+
+sets(Config, Version) ->
+ ?line {Sets, _, _} = args(Config),
+
+ ?line Data = make_data(1),
+ ?line delete_files(Sets),
+ P0 = pps(),
+ ?line Tabs = open_files(1, Sets, Version),
+ Bigger = [{17,q,w,w}, {48,q,w,w,w,w,w,w}], % 48 requires a bigger buddy
+ ?line initialize(Tabs, Data++Bigger++Data), % overwrite
+ ?line Len = length(Data),
+ ?line foreach(fun(Tab) -> trav_test(Data, Len, Tab) end, Tabs),
+ ?line size_test(Len, Tabs),
+ ?line no_keys_test(Tabs),
+ ?line foreach(fun(Tab) -> del_test(Tab) end, Tabs),
+ ?line initialize(Tabs, Data),
+ ?line foreach(fun(Tab) -> del_obj_test(Tab) end, Tabs),
+ ?line initialize(Tabs, Data),
+ ?line foreach(fun(Tab) ->
+ Len = dets:info(Tab, size) end,
+ Tabs),
+ ?line foreach(fun(Tab) -> match_test(Data, Tab) end, Tabs),
+ ?line foreach(fun(Tab) -> match_del_test(Tab) end, Tabs),
+
+ ?line close_all(Tabs),
+ ?line delete_files(Sets),
+ ?line check_pps(P0),
+ ok.
+
+bags_v8(doc) ->
+ ["Performs traversal and match testing on bag type dets tables."];
+bags_v8(suite) ->
+ [];
+bags_v8(Config) when is_list(Config) ->
+ bags(Config, 8).
+
+bags_v9(doc) ->
+ ["Performs traversal and match testing on bag type dets tables."];
+bags_v9(suite) ->
+ [];
+bags_v9(Config) when is_list(Config) ->
+ bags(Config, 9).
+
+bags(Config, Version) ->
+ {_, Bags, _} = args(Config),
+ ?line Data = make_data(1, bag), %% gives twice as many objects
+ ?line delete_files(Bags),
+ P0 = pps(),
+ ?line Tabs = open_files(1, Bags, Version),
+ ?line initialize(Tabs, Data++Data),
+ ?line Len = length(Data),
+ ?line foreach(fun(Tab) -> trav_test(Data, Len, Tab) end, Tabs),
+ ?line size_test(Len, Tabs),
+ ?line no_keys_test(Tabs),
+ ?line foreach(fun(Tab) -> del_test(Tab) end, Tabs),
+ ?line initialize(Tabs, Data),
+ ?line foreach(fun(Tab) -> del_obj_test(Tab) end, Tabs),
+ ?line initialize(Tabs, Data),
+ ?line foreach(fun(Tab) ->
+ Len = dets:info(Tab, size) end,
+ Tabs),
+ ?line foreach(fun(Tab) -> match_test(Data, Tab) end, Tabs),
+ ?line foreach(fun(Tab) -> match_del_test(Tab) end, Tabs),
+ ?line close_all(Tabs),
+ ?line delete_files(Bags),
+ ?line check_pps(P0),
+ ok.
+
+
+duplicate_bags_v8(doc) ->
+ ["Performs traversal and match testing on duplicate_bag type dets tables."];
+duplicate_bags_v8(suite) ->
+ [];
+duplicate_bags_v8(Config) when is_list(Config) ->
+ duplicate_bags(Config, 8).
+
+duplicate_bags_v9(doc) ->
+ ["Performs traversal and match testing on duplicate_bag type dets tables."];
+duplicate_bags_v9(suite) ->
+ [];
+duplicate_bags_v9(Config) when is_list(Config) ->
+ duplicate_bags(Config, 9).
+
+duplicate_bags(Config, Version) when is_list(Config) ->
+ {_, _, Dups} = args(Config),
+ ?line Data = make_data(1, duplicate_bag), %% gives twice as many objects
+ ?line delete_files(Dups),
+ P0 = pps(),
+ ?line Tabs = open_files(1, Dups, Version),
+ ?line initialize(Tabs, Data),
+ ?line Len = length(Data),
+ ?line foreach(fun(Tab) -> trav_test(Data, Len, Tab) end, Tabs),
+ ?line size_test(Len, Tabs),
+ ?line no_keys_test(Tabs),
+ ?line foreach(fun(Tab) -> del_test(Tab) end, Tabs),
+ ?line initialize(Tabs, Data),
+ ?line foreach(fun(Tab) -> del_obj_test(Tab) end, Tabs),
+ ?line initialize(Tabs, Data),
+ ?line foreach(fun(Tab) ->
+ Len = dets:info(Tab, size) end,
+ Tabs),
+ ?line foreach(fun(Tab) -> match_test(Data, Tab) end, Tabs),
+ ?line foreach(fun(Tab) -> match_del_test(Tab) end, Tabs),
+ ?line close_all(Tabs),
+ ?line delete_files(Dups),
+ ?line check_pps(P0),
+ ok.
+
+
+access_v8(doc) ->
+ [];
+access_v8(suite) ->
+ [];
+access_v8(Config) when is_list(Config) ->
+ access(Config, 8).
+
+access_v9(doc) ->
+ [];
+access_v9(suite) ->
+ [];
+access_v9(Config) when is_list(Config) ->
+ access(Config, 9).
+
+access(Config, Version) ->
+ Args_acc = [[{ram_file, true}, {access, read}],
+ [{access, read}]],
+ Args = [[{ram_file, true}],
+ []],
+
+ ?line {Args_acc_1, _, _} = zip_filename(Args_acc, [], [], Config),
+ ?line delete_files(Args_acc_1),
+ ?line {Args_1, _, _} = zip_filename(Args, [], [], Config),
+
+ P0 = pps(),
+ ?line {error, {file_error,_,enoent}} = dets:open_file('1', hd(Args_acc_1)),
+
+ ?line Tabs = open_files(1, Args_1, Version),
+ ?line close_all(Tabs),
+ ?line Tabs = open_files(1, Args_acc_1, Version),
+
+ ?line foreach(fun(Tab) ->
+ {error, {access_mode,_}} = dets:insert(Tab, {1,2}),
+ [] = dets:lookup(Tab, 11),
+ '$end_of_table' = dets:first(Tab),
+ {error, {access_mode,_}} = dets:delete(Tab, 22)
+ end, Tabs),
+ ?line close_all(Tabs),
+ ?line delete_files(Args_acc_1),
+ ?line check_pps(P0),
+ ok.
+
+
+dirty_mark(doc) ->
+ ["Test that the table is not marked dirty if not written"];
+dirty_mark(suite) ->
+ [];
+dirty_mark(Config) when is_list(Config) ->
+ ?line true = is_alive(),
+ ?line Tab = dets_dirty_mark_test,
+ ?line FName = filename(Tab, Config),
+ P0 = pps(),
+ ?line dets:open_file(Tab,[{file, FName}]),
+ ?line dets:insert(Tab,{mazda,japan}),
+ ?line dets:insert(Tab,{toyota,japan}),
+ ?line dets:insert(Tab,{suzuki,japan}),
+ ?line dets:insert(Tab,{honda,japan}),
+ ?line dets:insert(Tab,{renault,france}),
+ ?line dets:insert(Tab,{citroen,france}),
+ ?line dets:insert(Tab,{opel,germany}),
+ ?line dets:insert(Tab,{saab,sweden}),
+ ?line dets:insert(Tab,{volvo,sweden}),
+ ?line [{opel,germany}] = dets:lookup(Tab,opel),
+ ?line ok = dets:close(Tab),
+ ?line Call = fun(P,A) ->
+ P ! {self(), A},
+ receive
+ {P, Ans} ->
+ Ans
+ after 5000 ->
+ exit(other_process_dead)
+ end
+ end,
+ ?line {ok, Node} = test_server:start_node(dets_dirty_mark,
+ slave,
+ [{linked, false},
+ {args, "-pa " ++
+ filename:dirname
+ (code:which(?MODULE))}]),
+ ?line ok = ensure_node(20, Node),
+ %% io:format("~p~n",[rpc:call(Node, code, get_path, [])]),
+ %% io:format("~p~n",[rpc:call(Node, file, get_cwd, [])]),
+ %% io:format("~p~n",[Config]),
+ ?line Pid = rpc:call(Node,erlang, spawn,
+ [?MODULE, dets_dirty_loop, []]),
+ ?line {ok, Tab} = Call(Pid, [open, Tab, [{file, FName}]]),
+ ?line [{opel,germany}] = Call(Pid, [read,Tab,opel]),
+ ?line test_server:stop_node(Node),
+ ?line {ok, Tab} = dets:open_file(Tab,[{file, FName},
+ {repair,false}]),
+ ?line ok = dets:close(Tab),
+ ?line file:delete(FName),
+ ?line check_pps(P0),
+ ok.
+
+dirty_mark2(doc) ->
+ ["Test that the table is flushed when auto_save is in effect"];
+dirty_mark2(suite) ->
+ [];
+dirty_mark2(Config) when is_list(Config) ->
+ ?line true = is_alive(),
+ ?line Tab = dets_dirty_mark2_test,
+ ?line FName = filename(Tab, Config),
+ P0 = pps(),
+ ?line dets:open_file(Tab,[{file, FName}]),
+ ?line dets:insert(Tab,{toyota,japan}),
+ ?line dets:insert(Tab,{suzuki,japan}),
+ ?line dets:insert(Tab,{honda,japan}),
+ ?line dets:insert(Tab,{renault,france}),
+ ?line dets:insert(Tab,{citroen,france}),
+ ?line dets:insert(Tab,{opel,germany}),
+ ?line dets:insert(Tab,{saab,sweden}),
+ ?line dets:insert(Tab,{volvo,sweden}),
+ ?line [{opel,germany}] = dets:lookup(Tab,opel),
+ ?line ok = dets:close(Tab),
+ ?line Call = fun(P,A) ->
+ P ! {self(), A},
+ receive
+ {P, Ans} ->
+ Ans
+ after 5000 ->
+ exit(other_process_dead)
+ end
+ end,
+ ?line {ok, Node} = test_server:start_node(dets_dirty_mark2,
+ slave,
+ [{linked, false},
+ {args, "-pa " ++
+ filename:dirname
+ (code:which(?MODULE))}]),
+ ?line ok = ensure_node(20, Node),
+ ?line Pid = rpc:call(Node,erlang, spawn,
+ [?MODULE, dets_dirty_loop, []]),
+ ?line {ok, Tab} = Call(Pid, [open, Tab, [{file, FName},{auto_save,1000}]]),
+ ?line ok = Call(Pid, [write,Tab,{mazda,japan}]),
+ ?line timer:sleep(2100),
+ %% Read something, just to give auto save time to finish.
+ ?line [{opel,germany}] = Call(Pid, [read,Tab,opel]),
+ ?line test_server:stop_node(Node),
+ ?line {ok, Tab} = dets:open_file(Tab, [{file, FName}, {repair,false}]),
+ ?line ok = dets:close(Tab),
+ ?line file:delete(FName),
+ ?line check_pps(P0),
+ ok.
+
+dets_dirty_loop() ->
+ receive
+ {From, [open, Name, Args]} ->
+ Ret = dets:open_file(Name, Args),
+ From ! {self(), Ret},
+ dets_dirty_loop();
+ {From, [read, Name, Key]} ->
+ Ret = dets:lookup(Name, Key),
+ From ! {self(), Ret},
+ dets_dirty_loop();
+ {From, [write, Name, Value]} ->
+ Ret = dets:insert(Name, Value),
+ From ! {self(), Ret},
+ dets_dirty_loop()
+ end.
+
+
+bag_next_v8(suite) ->
+ [];
+bag_next_v8(doc) ->
+ ["Check that bags and next work as expected."];
+bag_next_v8(Config) when is_list(Config) ->
+ bag_next(Config, 8).
+
+bag_next_v9(suite) ->
+ [];
+bag_next_v9(doc) ->
+ ["Check that bags and next work as expected."];
+bag_next_v9(Config) when is_list(Config) ->
+ ?line Tab = dets_bag_next_test,
+ ?line FName = filename(Tab, Config),
+
+ %% first and next crash upon error
+ ?line dets:open_file(Tab,[{file, FName}, {type, bag},{version,9}]),
+ ?line ok = dets:insert(Tab, [{1,1},{2,2},{3,3},{4,4}]),
+ ?line FirstKey = dets:first(Tab),
+ ?line NextKey = dets:next(Tab, FirstKey),
+ ?line [FirstObj | _] = dets:lookup(Tab, FirstKey),
+ ?line [NextObj | _] = dets:lookup(Tab, NextKey),
+ ?line {ok, FirstPos} = dets:where(Tab, FirstObj),
+ ?line {ok, NextPos} = dets:where(Tab, NextObj),
+ crash(FName, NextPos+12),
+ ?line {'EXIT',BadObject1} = (catch dets:next(Tab, FirstKey)),
+ ?line bad_object(BadObject1, FName),
+ crash(FName, FirstPos+12),
+ ?line {'EXIT',BadObject2} = (catch dets:first(Tab)),
+ ?line bad_object(BadObject2, FName),
+ ?line dets:close(Tab),
+ ?line file:delete(FName),
+
+ bag_next(Config, 9).
+
+bag_next(Config, Version) ->
+ ?line Tab = dets_bag_next_test,
+ ?line FName = filename(Tab, Config),
+ P0 = pps(),
+ ?line dets:open_file(Tab,[{file, FName}, {type, bag},{version,Version}]),
+ ?line dets:insert(Tab,{698,hopp}),
+ ?line dets:insert(Tab,{186,hopp}),
+ ?line dets:insert(Tab,{hej,hopp}),
+ ?line dets:insert(Tab,{186,plopp}),
+ Loop = fun(N, Last, Self) ->
+ case N of
+ 0 ->
+ exit({unterminated_first_next_sequence, N, Last});
+ _ ->
+ case Last of
+ '$end_of_table' ->
+ ok;
+ _ ->
+ Self(N-1, dets:next(Tab,Last), Self)
+ end
+ end
+ end,
+ ?line ok = Loop(4,dets:first(Tab),Loop),
+ ?line dets:close(Tab),
+ ?line file:delete(FName),
+ ?line check_pps(P0),
+ ok.
+
+oldbugs_v8(doc) ->
+ [];
+oldbugs_v8(suite) ->
+ [];
+oldbugs_v8(Config) when is_list(Config) ->
+ oldbugs(Config, 8).
+
+oldbugs_v9(doc) ->
+ [];
+oldbugs_v9(suite) ->
+ [];
+oldbugs_v9(Config) when is_list(Config) ->
+ oldbugs(Config, 9).
+
+oldbugs(Config, Version) ->
+ FName = filename(dets_suite_oldbugs_test, Config),
+ P0 = pps(),
+ ?line {ok, ob} = dets:open_file(ob, [{version, Version},
+ {type, bag}, {file, FName}]),
+ ?line ok = dets:insert(ob, {1, 2}),
+ ?line ok = dets:insert(ob, {1,3}),
+ ?line ok = dets:insert(ob, {1, 2}),
+ ?line 2 = dets:info(ob, size), %% assertion
+ ?line ok = dets:close(ob),
+ ?line file:delete(FName),
+ ?line check_pps(P0),
+ ok.
+
+unsafe_assumptions(suite) -> [];
+unsafe_assumptions(doc) ->
+ "Tests that shrinking an object and then expanding it works.";
+unsafe_assumptions(Config) when is_list(Config) ->
+ FName = filename(dets_suite_unsafe_assumptions_test, Config),
+ ?line file:delete(FName),
+ P0 = pps(),
+ ?line {ok, a} = dets:open_file(a, [{version,8},{file, FName}]),
+ O0 = {2,false},
+ O1 = {1, false},
+ O2 = {1, true},
+ O3 = {1, duplicate(20,false)},
+ O4 = {1, duplicate(25,false)}, % same 2-log as O3
+ ?line ok = dets:insert(a, O1),
+ ?line ok = dets:insert(a, O0),
+ ?line true = [O1,O0] =:= sort(get_all_objects(a)),
+ ?line true = [O1,O0] =:= sort(get_all_objects_fast(a)),
+ ?line ok = dets:insert(a, O2),
+ ?line true = [O2,O0] =:= sort(get_all_objects(a)),
+ ?line true = [O2,O0] =:= sort(get_all_objects_fast(a)),
+ ?line ok = dets:insert(a, O3),
+ ?line true = [O3,O0] =:= sort(get_all_objects(a)),
+ ?line true = [O3,O0] =:= sort(get_all_objects_fast(a)),
+ ?line ok = dets:insert(a, O4),
+ ?line true = [O4,O0] =:= sort(get_all_objects(a)),
+ ?line true = [O4,O0] =:= sort(get_all_objects_fast(a)),
+ ?line ok = dets:close(a),
+ ?line file:delete(FName),
+ ?line check_pps(P0),
+ ok.
+
+truncated_segment_array_v8(suite) -> [];
+truncated_segment_array_v8(doc) ->
+ "Tests that a file where the segment array has been truncated "
+ "is possible to repair.";
+truncated_segment_array_v8(Config) when is_list(Config) ->
+ trunc_seg_array(Config, 8).
+
+truncated_segment_array_v9(suite) -> [];
+truncated_segment_array_v9(doc) ->
+ "Tests that a file where the segment array has been truncated "
+ "is possible to repair.";
+truncated_segment_array_v9(Config) when is_list(Config) ->
+ trunc_seg_array(Config, 9).
+
+trunc_seg_array(Config, V) ->
+ TabRef = dets_suite_truncated_segment_array_test,
+ Fname = filename(TabRef, Config),
+ %% Create file that needs to be repaired
+ ?line file:delete(Fname),
+ P0 = pps(),
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file, Fname},{version,V}]),
+ ?line ok = dets:close(TabRef),
+
+ %% Truncate the file
+ ?line HeadSize = headsz(V),
+ ?line truncate(Fname, HeadSize + 10),
+
+ %% Open the truncated file
+ ?line io:format("Expect repair:~n"),
+ ?line {ok, TabRef} = dets:open_file(TabRef,
+ [{file, Fname}, {repair, true}]),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+ ?line check_pps(P0),
+ ok.
+
+open_file_v8(doc) ->
+ ["open_file/1 test case."];
+open_file_v8(suite) ->
+ [];
+open_file_v8(Config) when is_list(Config) ->
+ open_1(Config, 8).
+
+open_file_v9(doc) ->
+ ["open_file/1 test case."];
+open_file_v9(suite) ->
+ [];
+open_file_v9(Config) when is_list(Config) ->
+ T = open_v9,
+ Fname = filename(T, Config),
+ ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9}]),
+ ?line 9 = dets:info(T, version),
+ ?line true = [self()] =:= dets:info(T, users),
+ ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9}]),
+ ?line {error,incompatible_arguments} =
+ dets:open_file(T, [{file,Fname},{version,8}]),
+ ?line true = [self(),self()] =:= dets:info(T, users),
+ ?line ok = dets:close(T),
+ ?line true = [self()] =:= dets:info(T, users),
+ ?line ok = dets:close(T),
+ ?line undefined = ets:info(T, users),
+ ?line file:delete(Fname),
+
+ open_1(Config, 9).
+
+open_1(Config, V) ->
+ TabRef = open_file_1_test,
+ Fname = filename(TabRef, Config),
+ ?line file:delete(Fname),
+
+ P0 = pps(),
+ ?line {error,{file_error,Fname,enoent}} = dets:open_file(Fname),
+
+ ?line ok = file:write_file(Fname, duplicate(100,65)),
+ ?line {error,{not_a_dets_file,Fname}} = dets:open_file(Fname),
+ ?line file:delete(Fname),
+
+ HeadSize = headsz(V),
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file, Fname},{version,V}]),
+ ?line ok = dets:close(TabRef),
+ ?line truncate(Fname, HeadSize + 10),
+ ?line true = dets:is_dets_file(Fname),
+ ?line io:format("Expect repair:~n"),
+ ?line {ok, Ref} = dets:open_file(Fname), % repairing
+ ?line ok = dets:close(Ref),
+ ?line file:delete(Fname),
+
+ %% truncated file header, invalid type
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line ok = ins(TabRef, 3000),
+ ?line ok = dets:close(TabRef),
+ TypePos = 12,
+ crash(Fname, TypePos),
+ ?line {error, {invalid_type_code,Fname}} = dets:open_file(Fname),
+ ?line truncate(Fname, HeadSize - 10),
+ ?line {error, {tooshort,Fname}} = dets:open_file(Fname),
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {error,{file_error,{foo,bar},_}} = dets:is_dets_file({foo,bar}),
+ ?line check_pps(P0),
+ ok.
+
+init_table_v8(doc) ->
+ ["initialize_table/2 and from_ets/2 test case."];
+init_table_v8(suite) ->
+ [];
+init_table_v8(Config) when is_list(Config) ->
+ init_table(Config, 8).
+
+init_table_v9(doc) ->
+ ["initialize_table/2 and from_ets/2 test case."];
+init_table_v9(suite) ->
+ [];
+init_table_v9(Config) when is_list(Config) ->
+ %% Objects are returned in "time order".
+ T = init_table_v9,
+ Fname = filename(T, Config),
+ ?line file:delete(Fname),
+ L = [{1,a},{2,b},{1,c},{2,c},{1,c},{2,a},{1,b}],
+ Input = init([L]),
+ ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9},
+ {type,duplicate_bag}]),
+ ?line ok = dets:init_table(T, Input),
+ ?line [{1,a},{1,c},{1,c},{1,b}] = dets:lookup(T, 1),
+ ?line [{2,b},{2,c},{2,a}] = dets:lookup(T, 2),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ init_table(Config, 9),
+ fast_init_table(Config).
+
+init_table(Config, V) ->
+ TabRef = init_table_test,
+ Fname = filename(TabRef, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ Args = [{file,Fname},{version,V},{auto_save,120000}],
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {'EXIT', _} =
+ (catch dets:init_table(TabRef, fun(foo) -> bar end)),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {'EXIT', _} = (catch dets:init_table(TabRef, fun() -> foo end)),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {'EXIT', {badarg, _}} = (catch dets:init_table(TabRef, nofun)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch dets:init_table(TabRef, fun(_X) -> end_of_input end,
+ [{foo,bar}])),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line away = (catch dets:init_table(TabRef, fun(_) -> throw(away) end)),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {error, {init_fun, fopp}} =
+ dets:init_table(TabRef, fun(read) -> fopp end),
+ dets:close(TabRef),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line dets:safe_fixtable(TabRef, true),
+ ?line {error, {fixed_table, TabRef}} = dets:init_table(TabRef, init([])),
+ ?line dets:safe_fixtable(TabRef, false),
+ ?line ET = ets:new(foo,[]),
+ ?line ok = dets:from_ets(TabRef, ET),
+ ?line [] = get_all_objects(TabRef),
+ ?line [] = get_all_objects_fast(TabRef),
+ ?line true = ets:insert(ET, {1,a}),
+ ?line true = ets:insert(ET, {2,b}),
+ ?line ok = dets:from_ets(TabRef, ET),
+ ?line [{1,a},{2,b}] = sort(get_all_objects(TabRef)),
+ ?line [{1,a},{2,b}] = sort(get_all_objects_fast(TabRef)),
+ ?line true = ets:delete(ET),
+ ?line 120000 = dets:info(TabRef, auto_save),
+ ?line ok = dets:close(TabRef),
+
+ ?line {ok, _} = dets:open_file(TabRef, [{access,read} | Args]),
+ ?line {error, {access_mode, Fname}} = dets:init_table(TabRef, init([])),
+ ?line ok = dets:close(TabRef),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {error, invalid_objects_list} =
+ (catch dets:init_table(TabRef, init([[{1,2},bad,{3,4}]]))),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ L1 = [[{1,a},{2,b}],[],[{3,c}],[{4,d}],[]],
+ bulk_init(L1, set, 4, Config, V),
+ L2 = [[{1,a},{2,b}],[],[{2,q},{3,c}],[{4,d}],[{4,e},{2,q}]],
+ bulk_init(L2, set, 4, Config, V),
+ bulk_init(L2, bag, 6, Config, V),
+ bulk_init(L2, duplicate_bag, 7, Config, V),
+ bulk_init(L1, set, 4, 512, Config, V),
+ bulk_init([], set, 0, 10000, Config, V),
+ file:delete(Fname),
+
+ %% Initiate a file that contains a lot of objects.
+ ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,10000} | Args]),
+ ?line ok = ins(TabRef, 6000),
+ Fun = init_fun(0, 10000),
+ ?line ok = dets:init_table(TabRef, Fun,{format,term}),
+ ?line All = sort(get_all_objects(TabRef)),
+ ?line FAll = get_all_objects_fast(TabRef),
+ ?line true = All =:= sort(FAll),
+ ?line true = length(All) =:= 10000,
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,4000} | Args]),
+ ?line ok = ins(TabRef, 6000),
+ ?line FileSize1 = dets:info(TabRef, file_size),
+ Fun2 = init_fun(0, 4000),
+ ?line ok = dets:init_table(TabRef, Fun2),
+ ?line FileSize2 = dets:info(TabRef, file_size),
+ ?line ok = dets:close(TabRef),
+ ?line true = FileSize1 > FileSize2,
+ ?line file:delete(Fname),
+
+ ?line check_pps(P0),
+ ok.
+
+bulk_init(Ls, Type, N, Config, V) ->
+ bulk_init(Ls, Type, N, 256, Config, V).
+
+bulk_init(Ls, Type, N, Est, Config, V) ->
+ T = init_table_test,
+ Fname = filename(T, Config),
+ ?line file:delete(Fname),
+ Input = init(Ls),
+ Args = [{ram_file,false}, {type,Type},{keypos,1},{file,Fname},
+ {estimated_no_objects, Est},{version,V}],
+ ?line {ok, T} = dets:open_file(T, Args),
+ ?line ok = dets:init_table(T, Input),
+ ?line All = sort(get_all_objects(T)),
+ ?line FAll = get_all_objects_fast(T),
+ ?line true = All =:= sort(FAll),
+ ?line true = length(All) =:= N,
+ ?line true = dets:info(T, size) =:= N,
+ ?line ok = dets:close(T),
+
+ ?line {ok, T} = dets:open_file(T, Args),
+ ?line All2 = sort(get_all_objects(T)),
+ ?line FAll2 = get_all_objects_fast(T),
+ ?line true = All =:= All2,
+ ?line true = All =:= sort(FAll2),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname).
+
+init(L) ->
+ fun(close) ->
+ ok;
+ (read) when [] =:= L ->
+ end_of_input;
+ (read) ->
+ [E | Es] = L,
+ {E, init(Es)}
+ end.
+
+init_fun(I, N) ->
+ fun(read) when I =:= N ->
+ end_of_input;
+ (read) ->
+ {NewN, Items} = items(I, N, 1000, []),
+ {Items, init_fun(NewN, N)};
+ (close) ->
+ ignored
+ end.
+
+fast_init_table(Config) ->
+ V = 9,
+ TabRef = init_table_test,
+ Fname = filename(TabRef, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ Args = [{file,Fname},{version,V},{auto_save,120000}],
+
+ Source = init_table_test_source,
+ SourceFname = filename(Source, Config),
+ ?line file:delete(SourceFname),
+ SourceArgs = [{file,SourceFname},{version,V},{auto_save,120000}],
+
+ ?line {ok, Source} = dets:open_file(Source, SourceArgs),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {'EXIT', _} =
+ (catch dets:init_table(TabRef, fun(foo) -> bar end, {format,bchunk})),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {'EXIT', _} = (catch dets:init_table(TabRef, fun() -> foo end,
+ {format,bchunk})),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {'EXIT', {badarg, _}} =
+ (catch dets:init_table(TabRef, nofun, {format,bchunk})),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line away = (catch dets:init_table(TabRef, fun(_) -> throw(away) end,
+ {format,bchunk})),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {error, {init_fun, fopp}} =
+ dets:init_table(TabRef, fun(read) -> fopp end, {format,bchunk}),
+ dets:close(TabRef),
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line dets:safe_fixtable(TabRef, true),
+ ?line {error, {fixed_table, TabRef}} =
+ dets:init_table(TabRef, init([]), {format,bchunk}),
+ ?line dets:safe_fixtable(TabRef, false),
+ ?line ok = dets:close(TabRef),
+
+ ?line {ok, _} = dets:open_file(TabRef, [{access,read} | Args]),
+ ?line {error, {access_mode, Fname}} =
+ dets:init_table(TabRef, init([]), {format,bchunk}),
+ ?line ok = dets:close(TabRef),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {error, {init_fun,{1,2}}} =
+ dets:init_table(TabRef, init([[{1,2},bad,{3,4}]]), {format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {error, {init_fun, end_of_input}} =
+ dets:init_table(TabRef, init([]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line {'EXIT', {badarg, _}} =
+ (catch dets:init_table(TabRef, init([]),{format,foppla})),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line ok = ins(TabRef, 100),
+
+ ?line [BParms | Objs] = collect_bchunk(TabRef, init_bchunk(TabRef)),
+ ?line Parms = binary_to_term(BParms),
+ ?line {error, {init_fun, <<"foobar">>}} =
+ dets:init_table(TabRef, init([[<<"foobar">>]]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line Parms1 = setelement(1, Parms, foobar),
+ BParms1 = term_to_binary(Parms1),
+ ?line {error, {init_fun, BParms1}} =
+ dets:init_table(TabRef, init([[BParms1 | Objs]]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ [{Sz1,No1} | NoColls17] = element(tuple_size(Parms), Parms),
+ Parms2 = setelement(tuple_size(Parms), Parms, [{Sz1,No1+1} | NoColls17]),
+ BParms2 = term_to_binary(Parms2),
+ ?line {error, invalid_objects_list} =
+ dets:init_table(TabRef, init([[BParms2 | Objs]]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line [{LSize1,Slot1,Obj1} | ObjsRest] = Objs,
+
+ ?line BadSize = byte_size(Obj1)-1,
+ ?line <<BadSizeObj:BadSize/binary,_:1/binary>> = Obj1,
+ ?line BadObjs = [{LSize1,Slot1,BadSizeObj} | ObjsRest],
+ ?line {error, invalid_objects_list} =
+ dets:init_table(TabRef, init([[BParms | BadObjs]]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line <<Size:32,BigObj0/binary>> = list_to_binary(lists:duplicate(16,Obj1)),
+ ?line BigObj = <<(Size*16):32,BigObj0/binary>>,
+ ?line BadColl = [BParms, {LSize1+4,Slot1,BigObj} | ObjsRest],
+ ?line {error, invalid_objects_list} =
+ dets:init_table(TabRef, init([BadColl]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ BadObj = <<"foobar">>,
+ ?line {error, invalid_objects_list} =
+ dets:init_table(TabRef, init([[BParms, BadObj]]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(TabRef, [{type,bag} | Args]),
+ ?line {error, {init_fun, _}} =
+ dets:init_table(TabRef, init([[BParms]]),{format,bchunk}),
+ ?line _ = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line ok = dets:close(Source),
+ ?line file:delete(SourceFname),
+
+ L1 = [{1,a},{2,b},{3,c},{4,d}],
+ fast_bulk_init(L1, set, 4, 4, Config, V),
+ L2 = [{1,a},{2,b},{2,q},{3,c},{4,d},{4,e},{2,q}],
+ fast_bulk_init(L2, set, 4, 4, Config, V),
+ fast_bulk_init(L2, bag, 6, 4, Config, V),
+ fast_bulk_init(L2, duplicate_bag, 7, 4, Config, V),
+ fast_bulk_init(L1, set, 4, 4, 512, Config, V),
+ fast_bulk_init([], set, 0, 0, 10000, Config, V),
+ file:delete(Fname),
+
+ %% Initiate a file that contains a lot of objects.
+ ?line {ok, _} = dets:open_file(Source, [{min_no_slots,10000} | SourceArgs]),
+ Fun1 = init_fun(0, 10000),
+ ?line ok = dets:init_table(Source, Fun1, {format,term}),
+
+ ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,10000} | Args]),
+ ?line ok = ins(TabRef, 6000),
+ Fun2 = init_bchunk(Source),
+ ?line true =
+ dets:is_compatible_bchunk_format(TabRef,
+ dets:info(Source, bchunk_format)),
+ ?line false = dets:is_compatible_bchunk_format(TabRef, <<"foobar">>),
+ ?line ok = dets:init_table(TabRef, Fun2, {format, bchunk}),
+ ?line ok = dets:close(Source),
+ ?line file:delete(SourceFname),
+ ?line All = sort(get_all_objects(TabRef)),
+ ?line FAll = get_all_objects_fast(TabRef),
+ ?line true = All =:= sort(FAll),
+ ?line true = length(All) =:= 10000,
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ %% Initiate inserts fewer objects than the table contains.
+ ?line {ok, _} = dets:open_file(Source, [{min_no_slots,1000} | SourceArgs]),
+ ?line ok = ins(Source, 4000),
+
+ ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,1000} | Args]),
+ ?line ok = ins(TabRef, 6000),
+ ?line FileSize1 = dets:info(TabRef, file_size),
+ Fun4 = init_bchunk(Source),
+ ?line ok = dets:init_table(TabRef, Fun4, {format, bchunk}),
+ ?line ok = dets:close(Source),
+ ?line file:delete(SourceFname),
+ ?line FileSize2 = dets:info(TabRef, file_size),
+ ?line All_2 = sort(get_all_objects(TabRef)),
+ ?line FAll_2 = get_all_objects_fast(TabRef),
+ ?line true = All_2 =:= sort(FAll_2),
+ ?line true = length(All_2) =:= 4000,
+ ?line ok = dets:close(TabRef),
+ ?line true = FileSize1 > FileSize2,
+
+ %% Bchunk and fixed table.
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line NoItems = dets:info(TabRef, no_objects),
+ ?line AllObjects1 = sort(get_all_objects_fast(TabRef)),
+ ?line dets:safe_fixtable(TabRef, true),
+ ?line true = dets:info(TabRef, fixed),
+ ?line Cont1 = init_bchunk(TabRef),
+ ?line NoDel =
+ dets:select_delete(TabRef, [{{'_',{item,'_','_'}},[],[true]}]),
+ ?line true = (NoDel > 0),
+ ?line AllObjects2 = sort(get_all_objects_fast(TabRef)),
+ ?line true = dets:info(TabRef, fixed),
+ ?line Cont2 = init_bchunk(TabRef),
+ ?line NoItems2 = dets:info(TabRef, no_objects),
+ ?line true = (NoItems =:= NoItems2 + NoDel),
+ ?line NoDel2 = dets:select_delete(TabRef, [{'_',[],[true]}]),
+ ?line true = (NoDel2 > 0),
+ ?line AllObjects3 = sort(get_all_objects_fast(TabRef)),
+ ?line NoItems3 = dets:info(TabRef, no_objects),
+ ?line true = (NoItems3 =:= 0),
+ ?line true = dets:info(TabRef, fixed),
+ ?line true = (NoItems2 =:= NoItems3 + NoDel2),
+ ?line Cont3 = init_bchunk(TabRef),
+
+ ?line BinColl1 = collect_bchunk(TabRef, Cont1),
+ ?line BinColl2 = collect_bchunk(TabRef, Cont2),
+ ?line BinColl3 = collect_bchunk(TabRef, Cont3),
+ ?line dets:safe_fixtable(TabRef, false),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ %% Now check that the above collected binaries are correct.
+ ?line {ok, _} = dets:open_file(TabRef, Args),
+ ?line ok = dets:init_table(TabRef, init([BinColl1]),{format,bchunk}),
+ ?line true = (AllObjects1 =:= sort(get_all_objects_fast(TabRef))),
+ ?line true = (length(AllObjects1) =:= dets:info(TabRef, no_objects)),
+ ?line ok = dets:init_table(TabRef, init([BinColl2]),{format,bchunk}),
+ ?line true = (AllObjects2 =:= sort(get_all_objects_fast(TabRef))),
+ ?line true = (length(AllObjects2) =:= dets:info(TabRef, no_objects)),
+ ?line ok = dets:init_table(TabRef, init([BinColl3]),{format,bchunk}),
+ ?line true = (AllObjects3 =:= sort(get_all_objects_fast(TabRef))),
+ ?line true = (length(AllObjects3) =:= dets:info(TabRef, no_objects)),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+ ?line check_pps(P0),
+ ok.
+
+fast_bulk_init(L, Type, N, NoKeys, Config, V) ->
+ fast_bulk_init(L, Type, N, NoKeys, 256, Config, V).
+
+fast_bulk_init(L, Type, N, NoKeys, Est, Config, V) ->
+ T = init_table_test,
+ Fname = filename(T, Config),
+ ?line file:delete(Fname),
+
+ Args0 = [{ram_file,false}, {type,Type},{keypos,1},
+ {estimated_no_objects, Est},{version,V}],
+ Args = [{file,Fname} | Args0],
+ S = init_table_test_source,
+ SFname = filename(S, Config),
+ ?line file:delete(SFname),
+ SArgs = [{file,SFname} | Args0],
+
+ ?line {ok, S} = dets:open_file(S, SArgs),
+ ?line ok = dets:insert(S, L),
+
+ Input = init_bchunk(S),
+ ?line {ok, T} = dets:open_file(T, Args),
+ ?line ok = dets:init_table(T, Input, [{format,bchunk}]),
+ ?line All = sort(get_all_objects(T)),
+ ?line FAll = get_all_objects_fast(T),
+ ?line true = All =:= sort(FAll),
+ ?line true = length(All) =:= N,
+ ?line true = dets:info(T, size) =:= N,
+ ?line true = dets:info(T, no_keys) =:= NoKeys,
+ ?line ok = dets:close(T),
+
+ ?line {ok, T} = dets:open_file(T, Args),
+ ?line All2 = sort(get_all_objects(T)),
+ ?line FAll2 = get_all_objects_fast(T),
+ ?line true = All =:= All2,
+ ?line true = All =:= sort(FAll2),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ ?line ok = dets:close(S),
+ ?line file:delete(SFname),
+ ok.
+
+init_bchunk(T) ->
+ Start = dets:bchunk(T, start),
+ init_bchunk(T, Start).
+
+init_bchunk(Tab, State) ->
+ fun(read) when State =:= '$end_of_table' ->
+ end_of_input;
+ (read) when element(1, State) =:= error ->
+ State;
+ (read) ->
+ {Cont, Objs} = State,
+ {Objs, init_bchunk(Tab, dets:bchunk(Tab, Cont))};
+ (close) ->
+ ok
+ end.
+
+collect_bchunk(Tab, Fun) ->
+ collect_bchunk(Tab, Fun, []).
+
+collect_bchunk(Tab, Fun, L) ->
+ case Fun(read) of
+ end_of_input ->
+ lists:append(lists:reverse(L));
+ {Objs, Fun2} when is_list(Objs) ->
+ collect_bchunk(Tab, Fun2, [Objs | L]);
+ Error ->
+ Error
+ end.
+
+items(I, N, C, L) when I =:= N; C =:= 0 ->
+ {I, L};
+items(I, N, C, L) ->
+ items(I+1, N, C-1, [{I, item(I)} | L]).
+
+repair_v8(doc) ->
+ ["open_file and repair."];
+repair_v8(suite) ->
+ [];
+repair_v8(Config) when is_list(Config) ->
+ repair(Config, 8).
+
+repair_v9(doc) ->
+ ["open_file and repair."];
+repair_v9(suite) ->
+ [];
+repair_v9(Config) when is_list(Config) ->
+ %% Convert from format 9 to format 8.
+ T = convert_98,
+ Fname = filename(T, Config),
+ ?line file:delete(Fname),
+ ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9},
+ {type,duplicate_bag}]),
+ ?line 9 = dets:info(T, version),
+ ?line true = is_binary(dets:info(T, bchunk_format)),
+ ?line ok = dets:insert(T, [{1,a},{2,b},{1,c},{2,c},{1,c},{2,a},{1,b}]),
+ ?line dets:close(T),
+ ?line {error, {version_mismatch, _}} =
+ dets:open_file(T, [{file,Fname},{version,8},{type,duplicate_bag}]),
+ ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,8},
+ {type,duplicate_bag},{repair,force}]),
+ ?line 8 = dets:info(T, version),
+ ?line true = undefined =:= dets:info(T, bchunk_format),
+ ?line [{1,a},{1,b},{1,c},{1,c}] = sort(dets:lookup(T, 1)),
+ ?line [{2,a},{2,b},{2,c}] = sort(dets:lookup(T, 2)),
+ ?line 7 = dets:info(T, no_objects),
+ ?line no_keys_test(T),
+ ?line _ = histogram(T, silent),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ %% The short lived format 9(a).
+ %% Not very throughly tested here.
+ A9 = a9,
+ ?line Version9aS = filename:join(?datadir(Config), "version_9a.dets"),
+ ?line Version9aT = filename('v9a.dets', Config),
+ ?line {ok, _} = file:copy(Version9aS, Version9aT),
+ ?line {ok, A9} = dets:open_file(A9, [{file,Version9aT}]),
+ ?line undefined = dets:info(A9, bchunk_format),
+ ?line [{1,a},{2,b},{3,c}] = sort(dets:match_object(A9, '_')),
+ ?line ok = dets:insert(A9, {4,d}),
+ ?line ok = dets:close(A9),
+ ?line {ok, A9} = dets:open_file(A9, [{file,Version9aT}]),
+ ?line {error, old_version} = dets:bchunk(A9, start),
+ ?line ok = dets:close(A9),
+ ?line io:format("Expect forced repair:~n"),
+ ?line {ok, A9} = dets:open_file(A9, [{file,Version9aT},{repair,force}]),
+ ?line {_, _} = dets:bchunk(A9, start),
+ ?line ok = dets:close(A9),
+ ?line file:delete(Version9aT),
+
+ repair(Config, 9).
+
+repair(Config, V) ->
+ TabRef = repair_test,
+ Fname = filename(TabRef, Config),
+ ?line file:delete(Fname),
+ HeadSize = headsz(V),
+
+ P0 = pps(),
+ ?line {'EXIT', {badarg, _}} =
+ (catch dets:open_file(TabRef, [{min_no_slots,1000},
+ {max_no_slots,500}])),
+ ?line {error,{file_error,hoppla,enoent}} = dets:file_info(hoppla),
+ ?line {error,{file_error,Fname,enoent}} =
+ dets:open_file(TabRef, [{file, Fname}, {access, read}]),
+
+ %% compacting, and some kind of test that free lists are saved OK on file
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line 0 = dets:info(TabRef, size),
+ ?line ok = ins(TabRef, 30000),
+ ?line ok = del(TabRef, 30000, 3),
+ ?line ok = dets:close(TabRef),
+ ?line {error, {access_mode,Fname}} =
+ dets:open_file(foo, [{file,Fname},{repair,force},{access,read}]),
+ ?line {ok, Ref3} = dets:open_file(Fname), % no repair!
+ ?line 20000 = dets:info(Ref3, size),
+ ?line 20000 = dets:foldl(fun(_, N) -> N+1 end, 0, Ref3),
+ ?line 20000 = count_objects_quite_fast(Ref3), % actually a test of match
+ ?line no_keys_test(Ref3),
+ ?line ok = dets:close(Ref3),
+ if
+ V =:= 8 ->
+ ?line {ok, TabRef} = dets:open_file(TabRef,
+ [{file, Fname},{version,V},{access,read}]),
+ ?line ok = dets:close(TabRef),
+ ?line io:format("Expect compacting repair:~n"),
+ ?line {ok, TabRef} = dets:open_file(TabRef,
+ [{file, Fname},{version,V}]),
+ ?line 20000 = dets:info(TabRef, size),
+ ?line _ = histogram(TabRef, silent),
+ ?line ok = dets:close(TabRef);
+ true ->
+ ok
+ end,
+ ?line {error,{keypos_mismatch,Fname}} =
+ dets:open_file(TabRef, [{file, Fname},{keypos,17}]),
+ ?line {error,{type_mismatch,Fname}} =
+ dets:open_file(TabRef, [{file, Fname},{type,duplicate_bag}]),
+
+ %% make one of the temporary files unwritable
+ TmpFile = if
+ V =:= 8 ->
+ Fname ++ ".TMP.10000";
+ true -> Fname ++ ".TMP.1"
+ end,
+ ?line file:delete(TmpFile),
+ ?line {ok, TmpFd} = file:open(TmpFile, [read,write]),
+ ?line ok = file:close(TmpFd),
+ ?line unwritable(TmpFile),
+ ?line {error,{file_error,TmpFile,eacces}} = dets:fsck(Fname, V),
+ ?line {ok, _} = dets:open_file(TabRef,
+ [{repair,false},{file, Fname},{version,V}]),
+ ?line 20000 = length(get_all_objects(TabRef)),
+ ?line _ = histogram(TabRef, silent),
+ ?line 20000 = length(get_all_objects_fast(TabRef)),
+ ?line ok = dets:close(TabRef),
+ ?line writable(TmpFile),
+ ?line file:delete(TmpFile),
+
+ ?line truncate(Fname, HeadSize + 10),
+ ?line {error,{not_closed, Fname}} =
+ dets:open_file(TabRef, [{file, Fname}, {access, read}]),
+ ?line {error,{not_closed, Fname}} =
+ dets:open_file(TabRef, [{file, Fname}, {access, read},
+ {repair,force}]),
+ ?line {error,{needs_repair, Fname}} =
+ dets:open_file(TabRef, [{file, Fname}, {repair, false}]),
+ ?line file:delete(Fname),
+
+ %% truncated file header
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line ok = ins(TabRef, 100),
+ ?line ok = dets:close(TabRef),
+ ?line truncate(Fname, HeadSize - 10),
+ %% a new file is created ('tooshort')
+ ?line {ok, TabRef} = dets:open_file(TabRef,
+ [{file,Fname},{version,V},
+ {min_no_slots,1000},
+ {max_no_slots,1000000}]),
+ case dets:info(TabRef, no_slots) of
+ undefined -> ok;
+ {Min1,Slot1,Max1} ->
+ ?line true = Min1 =< Slot1, true = Slot1 =< Max1,
+ ?line true = 1000 < Min1, true = 1000+256 > Min1,
+ ?line true = 1000000 < Max1, true = (1 bsl 20)+256 > Max1
+ end,
+ ?line 0 = dets:info(TabRef, size),
+ ?line no_keys_test(TabRef),
+ ?line _ = histogram(TabRef, silent),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ %% version bump (v8)
+ ?line Version7S = filename:join(?datadir(Config), "version_r2d.dets"),
+ ?line Version7T = filename('v2.dets', Config),
+ ?line {ok, _} = file:copy(Version7S, Version7T),
+ ?line {error,{version_bump, Version7T}} = dets:open_file(Version7T),
+ ?line {error,{version_bump, Version7T}} =
+ dets:open_file(Version7T, [{file,Version7T},{repair,false}]),
+ ?line {error,{version_bump, Version7T}} =
+ dets:open_file(Version7T, [{file, Version7T}, {access, read}]),
+ ?line io:format("Expect upgrade:~n"),
+ ?line {ok, _} = dets:open_file(Version7T,
+ [{file, Version7T},{version, V}]),
+ ?line [{1,a},{2,b}] = sort(get_all_objects(Version7T)),
+ ?line [{1,a},{2,b}] = sort(get_all_objects_fast(Version7T)),
+ Phash = if
+ V =:= 8 -> phash;
+ true -> phash2
+ end,
+ ?line Phash = dets:info(Version7T, hash),
+ ?line _ = histogram(Version7T, silent),
+ ?line ok = dets:close(Version7T),
+ ?line {ok, _} = dets:open_file(Version7T, [{file, Version7T}]),
+ ?line Phash = dets:info(Version7T, hash),
+ ?line ok = dets:close(Version7T),
+ ?line file:delete(Version7T),
+
+ %% converting free lists
+ ?line Version8aS = filename:join(?datadir(Config), "version_r3b02.dets"),
+ ?line Version8aT = filename('v3.dets', Config),
+ ?line {ok, _} = file:copy(Version8aS, Version8aT),
+ %% min_no_slots and max_no_slots are ignored - no repair is taking place
+ ?line {ok, _} = dets:open_file(version_8a,
+ [{file, Version8aT},{min_no_slots,1000},
+ {max_no_slots,100000}]),
+ ?line [{1,b},{2,a},{a,1},{b,2}] = sort(get_all_objects(version_8a)),
+ ?line [{1,b},{2,a},{a,1},{b,2}] = sort(get_all_objects_fast(version_8a)),
+ ?line ok = ins(version_8a, 1000),
+ ?line 1002 = dets:info(version_8a, size),
+ ?line no_keys_test(version_8a),
+ ?line All8a = sort(get_all_objects(version_8a)),
+ ?line 1002 = length(All8a),
+ ?line FAll8a = sort(get_all_objects_fast(version_8a)),
+ ?line true = sort(All8a) =:= sort(FAll8a),
+ ?line ok = del(version_8a, 300, 3),
+ ?line 902 = dets:info(version_8a, size),
+ ?line no_keys_test(version_8a),
+ ?line All8a2 = sort(get_all_objects(version_8a)),
+ ?line 902 = length(All8a2),
+ ?line FAll8a2 = sort(get_all_objects_fast(version_8a)),
+ ?line true = sort(All8a2) =:= sort(FAll8a2),
+ ?line _ = histogram(version_8a, silent),
+ ?line ok = dets:close(version_8a),
+ ?line file:delete(Version8aT),
+
+ %% will fail unless the slots are properly sorted when repairing (v8)
+ BArgs = [{file, Fname},{type,duplicate_bag},
+ {delayed_write,{3000,10000}},{version,V}],
+ ?line {ok, TabRef} = dets:open_file(TabRef, BArgs),
+ Seq = seq(1, 500),
+ Small = map(fun(X) -> {X,X} end, Seq),
+ Big = map(fun(X) -> erlang:make_tuple(20, X) end, Seq),
+ ?line ok = dets:insert(TabRef, Small),
+ ?line ok = dets:insert(TabRef, Big),
+ ?line ok = dets:insert(TabRef, Small),
+ ?line ok = dets:insert(TabRef, Big),
+ ?line All = sort(safe_get_all_objects(TabRef)),
+ ?line ok = dets:close(TabRef),
+ ?line io:format("Expect forced repair:~n"),
+ ?line {ok, _} =
+ dets:open_file(TabRef, [{repair,force},{min_no_slots,2000} | BArgs]),
+ if
+ V =:= 9 ->
+ ?line {MinNoSlots,_,MaxNoSlots} = dets:info(TabRef, no_slots),
+ ?line ok = dets:close(TabRef),
+ ?line io:format("Expect compaction:~n"),
+ ?line {ok, _} =
+ dets:open_file(TabRef, [{repair,force},
+ {min_no_slots,MinNoSlots},
+ {max_no_slots,MaxNoSlots} | BArgs]);
+ true ->
+ ok
+ end,
+ ?line All2 = get_all_objects(TabRef),
+ ?line true = All =:= sort(All2),
+ ?line FAll2 = get_all_objects_fast(TabRef),
+ ?line true = All =:= sort(FAll2),
+ ?line true = length(All) =:= dets:info(TabRef, size),
+ ?line no_keys_test(TabRef),
+ Fun = fun(X) -> 4 = length(dets:lookup(TabRef, X)) end,
+ ?line foreach(Fun, Seq),
+ ?line _ = histogram(TabRef, silent),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ %% object bigger than segments, the "hole" is taken care of
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file, Fname},{version,V}]),
+ Tuple = erlang:make_tuple(1000, foobar), % > 2 kB
+ ?line ok = dets:insert(TabRef, Tuple),
+ %% at least one full segment (objects smaller than 2 kB):
+ ?line ins(TabRef, 2000),
+ ?line ok = dets:close(TabRef),
+
+ if
+ V =:= 8 ->
+ %% first estimated number of objects is wrong, repair once more
+ ?line {ok, Fd} = file:open(Fname, read_write),
+ NoPos = HeadSize - 8, % no_objects
+ ?line file:pwrite(Fd, NoPos, <<0:32>>), % NoItems
+ ok = file:close(Fd),
+ ?line dets:fsck(Fname, V),
+ ?line {ok, _} =
+ dets:open_file(TabRef,
+ [{repair,false},{file, Fname},{version,V}]),
+ ?line 2001 = length(get_all_objects(TabRef)),
+ ?line _ = histogram(TabRef, silent),
+ ?line 2001 = length(get_all_objects_fast(TabRef)),
+ ?line ok = dets:close(TabRef);
+ true ->
+ ok
+ end,
+
+ ?line {ok, _} =
+ dets:open_file(TabRef,
+ [{repair,false},{file, Fname},{version,V}]),
+ ?line {ok, ObjPos} = dets:where(TabRef, {66,{item,number,66}}),
+ ?line ok = dets:close(TabRef),
+ %% Damaged object.
+ Pos = 12, % v9: compaction fails, proper repair follows
+ crash(Fname, ObjPos+Pos),
+ ?line io:format(
+ "Expect forced repair (possibly after attempted compaction):~n"),
+ ?line {ok, _} =
+ dets:open_file(TabRef, [{repair,force},{file, Fname},{version,V}]),
+ ?line true = dets:info(TabRef, size) < 2001,
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ %% The file is smaller than the padded object.
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line ok = dets:insert(TabRef, Tuple),
+ ?line ok = dets:close(TabRef),
+ ?line io:format("Expect forced repair or compaction:~n"),
+ ?line {ok, _} =
+ dets:open_file(TabRef, [{repair,force},{file, Fname},{version,V}]),
+ ?line true = 1 =:= dets:info(TabRef, size),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ %% Damaged free lists.
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line ok = ins(TabRef, 300),
+ ?line ok = dets:sync(TabRef),
+ ?line ok = del(TabRef, 300, 3),
+ %% FileSize is approximately where the free lists will be written.
+ ?line FileSize = dets:info(TabRef, memory),
+ ?line ok = dets:close(TabRef),
+ crash(Fname, FileSize+20),
+ ?line {error, {bad_freelists, Fname}} =
+ dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line file:delete(Fname),
+
+ %% File not closed, opening with read and read_write access tried.
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line ok = ins(TabRef, 300),
+ ?line ok = dets:close(TabRef),
+ ?line crash(Fname, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED),
+ ?line {error, {not_closed, Fname}} =
+ dets:open_file(foo, [{file,Fname},{version,V},{repair,force},
+ {access,read}]),
+ ?line {error, {not_closed, Fname}} =
+ dets:open_file(foo, [{file,Fname},{version,V},{repair,true},
+ {access,read}]),
+ ?line io:format("Expect repair:~n"),
+ ?line {ok, TabRef} =
+ dets:open_file(TabRef, [{file,Fname},{version,V},{repair,true},
+ {access,read_write}]),
+ ?line ok = dets:close(TabRef),
+ ?line crash(Fname, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED),
+ ?line io:format("Expect forced repair:~n"),
+ ?line {ok, TabRef} =
+ dets:open_file(TabRef, [{file,Fname},{version,V},{repair,force},
+ {access,read_write}]),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ %% The size of an object is huge.
+ ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]),
+ ?line ok = dets:insert(TabRef, [{1,2,3},{2,3,4}]),
+ ?line {ok, ObjPos2} = dets:where(TabRef, {1,2,3}),
+ ?line ok = dets:close(TabRef),
+ ObjPos3 = if
+ V =:= 8 -> ObjPos2 + 4;
+ V =:= 9 -> ObjPos2
+ end,
+ crash(Fname, ObjPos3, 255),
+ ?line io:format("Expect forced repair:~n"),
+ ?line {ok, TabRef} =
+ dets:open_file(TabRef, [{file,Fname},{version,V},{repair,force}]),
+ ?line ok = dets:close(TabRef),
+ ?line file:delete(Fname),
+
+ ?line check_pps(P0),
+ ok.
+
+hash_v8b_v8c(doc) ->
+ ["Test the use of different hashing algorithms in v8b and v8c of the "
+ "Dets file format."];
+hash_v8b_v8c(suite) ->
+ [];
+hash_v8b_v8c(Config) when is_list(Config) ->
+ ?line Source =
+ filename:join(?datadir(Config), "dets_test_v8b.dets"),
+ %% Little endian version of old file (there is an endianess bug in
+ %% the old hash). This is all about version 8 of the dets file format.
+
+ P0 = pps(),
+ ?line SourceLE =
+ filename:join(?datadir(Config),
+ "dets_test_v8b_little_endian.dets"),
+ ?line Target1 = filename('oldhash1.dets', Config),
+ ?line Target1LE = filename('oldhash1le.dets', Config),
+ ?line Target2 = filename('oldhash2.dets', Config),
+ ?line {ok, Bin} = file:read_file(Source),
+ ?line {ok, BinLE} = file:read_file(SourceLE),
+ ?line ok = file:write_file(Target1,Bin),
+ ?line ok = file:write_file(Target1LE,BinLE),
+ ?line ok = file:write_file(Target2,Bin),
+ ?line {ok, d1} = dets:open_file(d1,[{file,Target1}]),
+ ?line {ok, d1le} = dets:open_file(d1le,[{file,Target1LE}]),
+ ?line {ok, d2} = dets:open_file(d2,[{file,Target2},{repair,force},
+ {version,8}]),
+ ?line FF = fun(N,_F,_T) when N > 16#FFFFFFFFFFFFFFFF ->
+ ok;
+ (N,F,T) ->
+ V = integer_to_list(N),
+ case dets:lookup(T,N) of
+ [{N,V}] ->
+ F(N*2,F,T);
+ _Error ->
+ exit({failed,{lookup,T,N}})
+ end
+ end,
+ ?line Mess = case (catch FF(1,FF,d1)) of
+ {'EXIT', {failed, {lookup,_,_}}} ->
+ ?line ok = dets:close(d1),
+ ?line FF(1,FF,d1le),
+ ?line hash = dets:info(d1le,hash),
+ ?line dets:insert(d1le,{33333333333,hejsan}),
+ ?line [{33333333333,hejsan}] =
+ dets:lookup(d1le,33333333333),
+ ?line ok = dets:close(d1le),
+ ?line {ok, d1le} = dets:open_file(d1le,
+ [{file,Target1LE}]),
+ ?line [{33333333333,hejsan}] =
+ dets:lookup(d1le,33333333333),
+ ?line FF(1,FF,d1le),
+ ?line ok = dets:close(d1le),
+ "Seems to be a little endian machine";
+ {'EXIT', Fault} ->
+ exit(Fault);
+ _ ->
+ ?line ok = dets:close(d1le),
+ ?line hash = dets:info(d1,hash),
+ ?line dets:insert(d1,{33333333333,hejsan}),
+ ?line [{33333333333,hejsan}] =
+ dets:lookup(d1,33333333333),
+ ?line ok = dets:close(d1),
+ ?line {ok, d1} = dets:open_file(d1,[{file,Target1}]),
+ ?line [{33333333333,hejsan}] =
+ dets:lookup(d1,33333333333),
+ ?line FF(1,FF,d1),
+ ?line ok = dets:close(d1),
+ "Seems to be a big endian machine"
+ end,
+ ?line FF(1,FF,d2),
+ ?line phash = dets:info(d2,hash),
+ ?line ok = dets:close(d2),
+ ?line file:delete(Target1),
+ ?line file:delete(Target1LE),
+ ?line file:delete(Target2),
+ ?line check_pps(P0),
+ {comment, Mess}.
+
+phash(doc) ->
+ ["Test version 9(b) with erlang:phash/2 as hash function."];
+phash(suite) ->
+ [];
+phash(Config) when is_list(Config) ->
+ T = phash,
+ Phash_v9bS = filename:join(?datadir(Config), "version_9b_phash.dat"),
+ Fname = filename('v9b.dets', Config),
+ ?line {ok, _} = file:copy(Phash_v9bS, Fname),
+
+ %% Deleting all objects changes the hash function.
+ %% A feature... (it's for free)
+ ?line {ok, T} = dets:open_file(T, [{file, Fname}]),
+ ?line phash = dets:info(T, hash),
+ ?line dets:delete_all_objects(T),
+ ?line phash2 = dets:info(T, hash),
+ ?line [] = get_all_objects(T),
+ ?line [] = get_all_objects_fast(T),
+ ?line ok = dets:close(T),
+
+ %% The hash function is kept when compacting a table.
+ ?line {ok, _} = file:copy(Phash_v9bS, Fname),
+ ?line io:format("Expect compaction:~n"),
+ ?line {ok, T} = dets:open_file(T, [{file, Fname},{repair,force}]),
+ ?line phash = dets:info(T, hash),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e}] =
+ lists:sort(dets:lookup_keys(T, [1,2,3,4,5])),
+ ?line ok = dets:close(T),
+
+ %% The hash function is updated when repairing a table (no cost).
+ ?line {ok, _} = file:copy(Phash_v9bS, Fname),
+ crash(Fname, ?CLOSED_PROPERLY_POS+3, 0),
+ ?line io:format("Expect repair:~n"),
+ ?line {ok, T} = dets:open_file(T, [{file, Fname}]),
+ ?line phash2 = dets:info(T, hash),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e}] =
+ lists:sort(dets:lookup_keys(T, [1,2,3,4,5])),
+ ?line ok = dets:close(T),
+
+ %% One cannot use the bchunk format when copying between a phash
+ %% table and a phash2 table. (There is no test for the case an R9
+ %% (or later) node (using phash2) copies a table to an R8 node
+ %% (using phash).) See also the comment on HASH_PARMS in dets_v9.erl.
+ ?line {ok, _} = file:copy(Phash_v9bS, Fname),
+ ?line {ok, T} = dets:open_file(T, [{file, Fname}]),
+ ?line Type = dets:info(T, type),
+ ?line KeyPos = dets:info(T, keypos),
+ Input = init_bchunk(T),
+ T2 = phash_table,
+ Fname2 = filename(T2, Config),
+ Args = [{type,Type},{keypos,KeyPos},{version,9},{file,Fname2}],
+ ?line {ok, T2} = dets:open_file(T2, Args),
+ ?line {error, {init_fun, _}} =
+ dets:init_table(T2, Input, {format,bchunk}),
+ ?line _ = dets:close(T2),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname2),
+
+ ?line file:delete(Fname),
+ ok.
+
+fold_v8(doc) ->
+ ["foldl, foldr, to_ets"];
+fold_v8(suite) ->
+ [];
+fold_v8(Config) when is_list(Config) ->
+ fold(Config, 8).
+
+fold_v9(doc) ->
+ ["foldl, foldr, to_ets"];
+fold_v9(suite) ->
+ [];
+fold_v9(Config) when is_list(Config) ->
+ fold(Config, 9).
+
+fold(Config, Version) ->
+ T = test_table,
+ N = 100,
+ ?line Fname = filename(T, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ Args = [{version, Version}, {file,Fname}, {estimated_no_objects, N}],
+ ?line {ok, _} = dets:open_file(T, Args),
+
+ ?line ok = ins(T, N),
+
+ ?line Ets = ets:new(to_ets, [public]),
+ ?line dets:to_ets(T, Ets),
+ ?line true = N =:= ets:info(Ets, size),
+ ?line ets:delete(Ets),
+
+ ?line Ets2 = ets:new(to_ets, [private]),
+ ?line dets:to_ets(T, Ets2),
+ ?line true = N =:= ets:info(Ets2, size),
+ ?line ets:delete(Ets2),
+
+ ?line {'EXIT', {badarg, _}} = (catch dets:to_ets(T, not_an_ets_table)),
+
+ F0 = fun(X, A) -> [X | A] end,
+ ?line true = N =:= length(dets:foldl(F0, [], T)),
+ ?line true = N =:= length(dets:foldr(F0, [], T)),
+
+ F1 = fun(_X, _A) -> throw(away) end,
+ ?line away = (catch dets:foldl(F1, [], T)),
+ ?line away = (catch dets:foldr(F1, [], T)),
+
+ F2 = fun(X, A) -> X + A end,
+ ?line {'EXIT', _} = (catch dets:foldl(F2, [], T)),
+ ?line {'EXIT', _} = (catch dets:foldr(F2, [], T)),
+
+ F3 = fun(_X) -> throw(away) end,
+ ?line away = (catch dets:traverse(T, F3)),
+
+ F4 = fun(X) -> X + 17 end,
+ ?line {'EXIT', _} = (catch dets:traverse(T, F4)),
+
+ ?line F5 = fun(_X) -> done end,
+ ?line done = dets:traverse(T, F5),
+
+ ?line {ok, ObjPos} = dets:where(T, {66,{item,number,66}}),
+ ?line ok = dets:close(T),
+
+ %% Damaged object.
+ Pos = if
+ Version =:= 8 -> 12;
+ Version =:= 9 -> 8
+ end,
+ crash(Fname, ObjPos+Pos),
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line io:format("Expect corrupt table:~n"),
+ ?line BadObject1 = dets:foldl(F0, [], T),
+ ?line bad_object(BadObject1, Fname),
+ ?line BadObject2 = dets:close(T),
+ ?line bad_object(BadObject2, Fname),
+
+ ?line file:delete(Fname),
+ ?line check_pps(P0),
+ ok.
+
+fixtable_v8(doc) ->
+ ["Add objects to a fixed table."];
+fixtable_v8(suite) ->
+ [];
+fixtable_v8(Config) when is_list(Config) ->
+ fixtable(Config, 8).
+
+fixtable_v9(doc) ->
+ ["Add objects to a fixed table."];
+fixtable_v9(suite) ->
+ [];
+fixtable_v9(Config) when is_list(Config) ->
+ fixtable(Config, 9).
+
+fixtable(Config, Version) when is_list(Config) ->
+ T = fixtable,
+ ?line Fname = filename(fixtable, Config),
+ ?line file:delete(Fname),
+ Args = [{version,Version},{file,Fname}],
+ P0 = pps(),
+ ?line {ok, _} = dets:open_file(T, Args),
+
+ %% badarg
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:safe_fixtable(no_table,true)),
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[T,undefined]}|_]}} =
+ (catch dets:safe_fixtable(T,undefined)),
+
+ %% The table is not allowed to grow while the elements are inserted:
+
+ ?line ok = ins(T, 500),
+ ?line dets:safe_fixtable(T, false),
+ %% Now the table can grow. At the same time as elements are inserted,
+ %% the table tries to catch up with the previously inserted elements.
+ ?line ok = ins(T, 1000),
+ ?line 1000 = dets:info(T, size),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(T, [{type, duplicate_bag} | Args]),
+ %% In a fixed table, delete and re-insert an object.
+ ?line ok = dets:insert(T, {1, a, b}),
+ ?line dets:safe_fixtable(T, true),
+ ?line ok = dets:match_delete(T, {1, a, b}),
+ ?line ok = dets:insert(T, {1, a, b}),
+ ?line dets:safe_fixtable(T, false),
+ ?line 1 = length(dets:match_object(T, '_')),
+
+ ?line ok = dets:match_delete(T, '_'),
+ %% In a fixed table, delete and insert a smaller object.
+ ?line ok = dets:insert(T, {1, duplicate(100, e)}),
+ ?line dets:safe_fixtable(T, true),
+ ?line ok = dets:match_delete(T, {1, '_'}),
+ ?line ok = dets:insert(T, {1, a, b}),
+ ?line dets:safe_fixtable(T, false),
+ ?line 1 = length(dets:match_object(T, '_')),
+
+ ?line ok = dets:delete_all_objects(T),
+ %% Like the last one, but one extra object.
+ ?line ok = dets:insert(T, {1, duplicate(100, e)}),
+ ?line ok = dets:insert(T, {2, duplicate(100, e)}),
+ ?line dets:safe_fixtable(T, true),
+ ?line ok = dets:match_delete(T, {1, '_'}),
+ ?line ok = dets:insert(T, {1, a, b}),
+ ?line dets:safe_fixtable(T, false),
+ ?line 2 = length(dets:match_object(T, '_')),
+ ?line dets:safe_fixtable(T, true),
+ ?line ok = dets:delete_all_objects(T),
+ ?line true = dets:info(T, fixed),
+ ?line 0 = length(dets:match_object(T, '_')),
+
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+ ?line check_pps(P0),
+ ok.
+
+match_v8(doc) ->
+ ["Matching objects of a fixed table."];
+match_v8(suite) ->
+ [];
+match_v8(Config) when is_list(Config) ->
+ match(Config, 8).
+
+match_v9(doc) ->
+ ["Matching objects of a fixed table."];
+match_v9(suite) ->
+ [];
+match_v9(Config) when is_list(Config) ->
+ match(Config, 9).
+
+match(Config, Version) ->
+ T = match,
+ ?line Fname = filename(match, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ Args = [{version, Version}, {file,Fname}, {type, duplicate_bag},
+ {estimated_no_objects,550}],
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line ok = dets:insert(T, {1, a, b}),
+ ?line ok = dets:insert(T, {1, b, a}),
+ ?line ok = dets:insert(T, {2, a, b}),
+ ?line ok = dets:insert(T, {2, b, a}),
+
+ %% match, badarg
+ MSpec = [{'_',[],['$_']}],
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:match(no_table, '_')),
+ ?line {'EXIT', {badarg, [{dets,match,[T,'_',not_a_number]}|_]}} =
+ (catch dets:match(T, '_', not_a_number)),
+ ?line {EC1, _} = dets:select(T, MSpec, 1),
+ ?line {'EXIT', {badarg, [{dets,match,[EC1]}|_]}} =
+ (catch dets:match(EC1)),
+
+ %% match_object, badarg
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:match_object(no_table, '_')),
+ ?line {'EXIT', {badarg, [{dets,match_object,[T,'_',not_a_number]}|_]}} =
+ (catch dets:match_object(T, '_', not_a_number)),
+ ?line {EC2, _} = dets:select(T, MSpec, 1),
+ ?line {'EXIT', {badarg, [{dets,match_object,[EC2]}|_]}} =
+ (catch dets:match_object(EC2)),
+
+ dets:safe_fixtable(T, true),
+ ?line {[_, _], C1} = dets:match_object(T, '_', 2),
+ ?line {[_, _], C2} = dets:match_object(C1),
+ ?line '$end_of_table' = dets:match_object(C2),
+ ?line {[_, _], C3} = dets:match_object(T, {1, '_', '_'}, 100),
+ ?line '$end_of_table' = dets:match_object(C3),
+ ?line '$end_of_table' = dets:match_object(T, {'_'}, default),
+ ?line dets:safe_fixtable(T, false),
+
+ ?line dets:safe_fixtable(T, true),
+ ?line {[_, _], C30} = dets:match(T, '$1', 2),
+ ?line {[_, _], C31} = dets:match(C30),
+ ?line '$end_of_table' = dets:match(C31),
+ ?line {[_, _], C32} = dets:match(T, {1, '$1', '_'}, 100),
+ ?line '$end_of_table' = dets:match(C32),
+ ?line '$end_of_table' = dets:match(T, {'_'}, default),
+ ?line dets:safe_fixtable(T, false),
+ ?line [[1],[1],[2],[2]] = sort(dets:match(T, {'$1','_','_'})),
+
+ %% delete and insert while chunking
+ %% (this case almost worthless after changes in OTP-5232)
+ ?line ok = dets:match_delete(T, '_'),
+ L500 = seq(1, 500),
+ Fun = fun(X) -> ok = dets:insert(T, {X, a, b, c, d}) end,
+ ?line foreach(Fun, L500),
+ %% Select one object DI in L3 below to be deleted.
+ ?line {_, TmpCont} = dets:match_object(T, '_', 200),
+ ?line {_, TmpCont1} = dets:match_object(TmpCont),
+ ?line {TTL, _} = dets:match_object(TmpCont1),
+ ?line DI = if Version =:= 8 -> last(TTL); Version =:= 9 -> hd(TTL) end,
+ ?line dets:safe_fixtable(T, true),
+ ?line {L1, C20} = dets:match_object(T, '_', 200),
+ ?line true = 200 =< length(L1),
+ ?line ok = dets:match_delete(T, {'2','_','_'}), % no match
+ ?line ok = dets:match_delete(T, DI), % last object
+ Tiny = {1050},
+ ?line ok = dets:insert(T, Tiny),
+ ?line true = member(Tiny, dets:match_object(T, '_')),
+ ?line {_L2, C21} = dets:match_object(C20),
+ ?line {_L3, _C22} = dets:match_object(C21),
+ %% It used to be that Tiny was not visible here, but since the
+ %% scanning of files was changed to inspect the free lists every
+ %% now and then it may very well be visible here.
+ %% ?line false = member(Tiny, _L3),
+ %% DI used to visible here, but the above mentioned modification
+ %% has changed that; it may or may not be visible.
+ %% ?line true = member(DI, _L3),
+ ?line dets:safe_fixtable(T, false),
+ ?line true = dets:member(T, 1050),
+ ?line true = member(Tiny, dets:match_object(T, '_')),
+ ?line false = member(DI, dets:match_object(T, '_')),
+
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ N = 100,
+ ?line {ok, _} = dets:open_file(T, [{estimated_no_objects,N} | Args]),
+ ?line ok = ins(T, N),
+ Obj = {66,{item,number,66}},
+ Spec = {'_','_'},
+ ?line {ok, ObjPos} = dets:where(T, Obj),
+ ?line ok = dets:close(T),
+ %% Damaged object.
+ crash(Fname, ObjPos+12),
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line io:format("Expect corrupt table:~n"),
+ ?line case ins(T, N) of
+ ok ->
+ ?line bad_object(dets:sync(T), Fname);
+ Else ->
+ ?line bad_object(Else, Fname)
+ end,
+ ?line io:format("Expect corrupt table:~n"),
+ ?line bad_object(dets:match(T, Spec), Fname),
+ ?line io:format("Expect corrupt table:~n"),
+ ?line bad_object(dets:match_delete(T, Spec), Fname),
+ ?line bad_object(dets:close(T), Fname),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(T, [{estimated_no_objects,N} | Args]),
+ ?line ok = ins(T, N),
+ ?line {ok, ObjPos2} = dets:where(T, Obj),
+ ?line ok = dets:close(T),
+
+ %% Damaged size of object.
+ %% In v8, there is a next pointer before the size.
+ CrashPos = if Version =:= 8 -> 5; Version =:= 9 -> 1 end,
+ crash(Fname, ObjPos2+CrashPos),
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line io:format("Expect corrupt table:~n"),
+ ?line case ins(T, N) of
+ ok ->
+ ?line bad_object(dets:sync(T), Fname);
+ Else2 ->
+ ?line bad_object(Else2, Fname)
+ end,
+ %% Just echoes...
+ ?line bad_object(dets:match(T, Spec), Fname),
+ ?line bad_object(dets:match_delete(T, Spec), Fname),
+ ?line bad_object(dets:close(T), Fname),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(T, [{estimated_no_objects,N} | Args]),
+ ?line ok = ins(T, N),
+ ?line {ok, ObjPos3} = dets:where(T, Obj),
+ ?line ok = dets:close(T),
+
+ %% match_delete finds an error
+ CrashPos3 = if Version =:= 8 -> 12; Version =:= 9 -> 16 end,
+ crash(Fname, ObjPos3+CrashPos3),
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line bad_object(dets:match_delete(T, Spec), Fname),
+ ?line bad_object(dets:close(T), Fname),
+ ?line file:delete(Fname),
+
+ %% The key is not fixed, but not all objects with the key are removed.
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line ok = dets:insert(T, [{1,a},{1,b},{1,c},{1,a},{1,b},{1,c}]),
+ ?line 6 = dets:info(T, size),
+ ?line ok = dets:match_delete(T, {'_',a}),
+ ?line 4 = dets:info(T, size),
+ ?line [{1,b},{1,b},{1,c},{1,c}] =
+ sort(dets:match_object(T,{'_','_'})),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ ?line check_pps(P0),
+ ok.
+
+select_v8(doc) ->
+ ["Selecting objects of a fixed table."];
+select_v8(suite) ->
+ [];
+select_v8(Config) when is_list(Config) ->
+ select(Config, 8).
+
+select_v9(doc) ->
+ ["Selecting objects of a fixed table."];
+select_v9(suite) ->
+ [];
+select_v9(Config) when is_list(Config) ->
+ select(Config, 9).
+
+select(Config, Version) ->
+ T = select,
+ ?line Fname = filename(select, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ ?line Args = [{version,Version}, {file,Fname}, {type, duplicate_bag},
+ {estimated_no_objects,550}],
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line ok = dets:insert(T, {1, a, b}),
+ ?line ok = dets:insert(T, {1, b, a}),
+ ?line ok = dets:insert(T, {2, a, b}),
+ ?line ok = dets:insert(T, {2, b, a}),
+ ?line ok = dets:insert(T, {3, a, b}),
+ ?line ok = dets:insert(T, {3, b, a}),
+
+ %% badarg
+ MSpec = [{'_',[],['$_']}],
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:select(no_table, MSpec)),
+ ?line {'EXIT', {badarg, [{dets,select,[T,<<17>>]}|_]}} =
+ (catch dets:select(T, <<17>>)),
+ ?line {'EXIT', {badarg, [{dets,select,[T,[]]}|_]}} =
+ (catch dets:select(T, [])),
+ ?line {'EXIT', {badarg, [{dets,select,[T,MSpec,not_a_number]}|_]}} =
+ (catch dets:select(T, MSpec, not_a_number)),
+ ?line {EC, _} = dets:match(T, '_', 1),
+ ?line {'EXIT', {badarg, [{dets,select,[EC]}|_]}} =
+ (catch dets:select(EC)),
+
+ AllSpec = [{'_',[],['$_']}],
+
+ ?line dets:safe_fixtable(T, true),
+ ?line {[_, _], C1} = dets:select(T, AllSpec, 2),
+ ?line {[_, _], C2} = dets:select(C1),
+ ?line {[_, _], C2a} = dets:select(C2),
+ ?line '$end_of_table' = dets:select(C2a),
+ ?line {[_, _], C3} = dets:select(T, [{{1,'_','_'},[],['$_']}], 100),
+ ?line '$end_of_table' = dets:select(C3),
+ ?line '$end_of_table' = dets:select(T, [{{'_'},[],['$_']}], default),
+ ?line dets:safe_fixtable(T, false),
+ Sp1 = [{{1,'_','_'},[],['$_']},{{1,'_','_'},[],['$_']},
+ {{2,'_','_'},[],['$_']}],
+ ?line [_,_,_,_] = dets:select(T, Sp1),
+ Sp2 = [{{1,'_','_'},[],['$_']},{{1,'_','_'},[],['$_']},
+ {{'_','_','_'},[],['$_']}],
+ ?line [_,_,_,_,_,_] = dets:select(T, Sp2),
+
+ AllDeleteSpec = [{'_',[],[true]}],
+ %% delete and insert while chunking
+ %% (this case almost worthless after changes in OTP-5232)
+ ?line 6 = dets:select_delete(T, AllDeleteSpec),
+ L500 = seq(1, 500),
+ Fun = fun(X) -> ok = dets:insert(T, {X, a, b, c, d}) end,
+ ?line foreach(Fun, L500),
+ %% Select one object DI in L3 below to be deleted.
+ ?line {_, TmpCont} = dets:match_object(T, '_', 200),
+ ?line {_, TmpCont1} = dets:match_object(TmpCont),
+ ?line {TTL, _} = dets:match_object(TmpCont1),
+ ?line DI = if Version =:= 8 -> last(TTL); Version =:= 9 -> hd(TTL) end,
+ ?line dets:safe_fixtable(T, true),
+ ?line {L1, C20} = dets:select(T, AllSpec, 200),
+ ?line true = 200 =< length(L1),
+ ?line 0 = dets:select_delete(T, [{{2,'_','_'},[],[true]}]),
+ ?line 1 = dets:select_delete(T, [{DI,[],[true]}]), % last object
+ Tiny = {1050},
+ ?line ok = dets:insert(T, Tiny),
+ ?line true = member(Tiny, dets:select(T, AllSpec)),
+ ?line {_L2, C21} = dets:select(C20),
+ ?line {_L3, _C22} = dets:select(C21),
+ %% It used to be that Tiny was not visible here, but since the
+ %% scanning of files was changed to inspect the free lists every
+ %% now and then it may very well be visible here.
+ %% ?line false = member(Tiny, _L3),
+ %% DI used to visible here, but the above mentioned modification
+ %% has changed that; it may or may not be visible.
+ %% ?line true = member(DI, _L3),
+ ?line true = dets:member(T, 1050),
+ ?line true = member(Tiny, dets:select(T, AllSpec)),
+ ?line false = member(DI, dets:select(T, AllSpec)),
+ ?line dets:safe_fixtable(T, false),
+ ?line true = dets:member(T, 1050),
+ ?line true = member(Tiny, dets:select(T, AllSpec)),
+ ?line false = member(DI, dets:select(T, AllSpec)),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ %% The key is not fixed, but not all objects with the key are removed.
+ ?line {ok, _} = dets:open_file(T, Args),
+ ?line ok = dets:insert(T, [{1,a},{1,b},{1,c},{1,a},{1,b},{1,c}]),
+ ?line 6 = dets:info(T, size),
+ ?line 2 = dets:select_delete(T, [{{'_',a},[],[true]}]),
+ ?line 4 = dets:info(T, size),
+ ?line [{1,b},{1,b},{1,c},{1,c}] = sort(dets:select(T, AllSpec)),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ ?line check_pps(P0),
+ ok.
+
+update_counter(doc) ->
+ ["Test update_counter/1."];
+update_counter(suite) ->
+ [];
+update_counter(Config) when is_list(Config) ->
+ T = update_counter,
+ ?line Fname = filename(select, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ ?line {'EXIT', {badarg, [{dets,update_counter,[no_table,1,1]}|_]}} =
+ (catch dets:update_counter(no_table, 1, 1)),
+
+ Args = [{file,Fname},{keypos,2}],
+ ?line {ok, _} = dets:open_file(T, [{type,set} | Args]),
+ ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)),
+ ?line ok = dets:insert(T, {1,a}),
+ ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)),
+ ?line ok = dets:insert(T, {0,1}),
+ ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)),
+ ?line ok = dets:insert(T, {0,1,0}),
+ ?line 1 = dets:update_counter(T, 1, 1),
+ ?line 2 = dets:update_counter(T, 1, 1),
+ ?line 6 = dets:update_counter(T, 1, {3,4}),
+ ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, {0,3})),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+
+ ?line {ok, _} = dets:open_file(T, [{type,bag} | Args]),
+ ?line ok = dets:insert(T, {0,1,0}),
+ ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)),
+ ?line ok = dets:close(T),
+ ?line file:delete(Fname),
+ ?line check_pps(P0),
+
+ ok.
+
+badarg(doc) ->
+ ["Call some functions with bad arguments."];
+badarg(suite) ->
+ [];
+badarg(Config) when is_list(Config) ->
+ T = badarg,
+ ?line Fname = filename(select, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ Args = [{file,Fname},{keypos,3}],
+ ?line {ok, _} = dets:open_file(T, [{type,set} | Args]),
+ % ?line dets:verbose(),
+
+ %% badargs are tested in match, select and fixtable too.
+
+ %% open
+ ?line {'EXIT', {badarg, [{dets,open_file,[{a,tuple},[]]}|_]}} =
+ (catch dets:open_file({a,tuple},[])),
+ ?line {'EXIT', {badarg, [{dets,open_file,[{a,tuple}]}|_]}} =
+ (catch dets:open_file({a,tuple})),
+ ?line {'EXIT', {badarg, [{dets,open_file,[file,[foo]]}|_]}} =
+ (catch dets:open_file(file,[foo])),
+ ?line {'EXIT', {badarg,[{dets,open_file,[{hej,san},[{type,set}|3]]}|_]}} =
+ (catch dets:open_file({hej,san},[{type,set}|3])),
+
+ %% insert
+ ?line {'EXIT', {badarg, [{dets,insert,[no_table,{1,2}]}|_]}} =
+ (catch dets:insert(no_table, {1,2})),
+ ?line {'EXIT', {badarg, [{dets,insert,[no_table,[{1,2}]]}|_]}} =
+ (catch dets:insert(no_table, [{1,2}])),
+ ?line {'EXIT', {badarg, [{dets,insert,[T,{1,2}]}|_]}} =
+ (catch dets:insert(T, {1,2})),
+ ?line {'EXIT', {badarg, [{dets,insert,[T,[{1,2}]]}|_]}} =
+ (catch dets:insert(T, [{1,2}])),
+ ?line {'EXIT', {badarg, [{dets,insert,[T,[{1,2,3}|3]]}|_]}} =
+ (catch dets:insert(T, [{1,2,3} | 3])),
+
+ %% lookup{_keys}
+ ?line {'EXIT', {badarg, [{dets,lookup_keys,[badarg,[]]}|_]}} =
+ (catch dets:lookup_keys(T, [])),
+ ?line {'EXIT', {badarg, [{dets,lookup,[no_table,1]}|_]}} =
+ (catch dets:lookup(no_table, 1)),
+ ?line {'EXIT', {badarg, [{dets,lookup_keys,[T,[1|2]]}|_]}} =
+ (catch dets:lookup_keys(T, [1 | 2])),
+
+ %% member
+ ?line {'EXIT', {badarg, [{dets,member,[no_table,1]}|_]}} =
+ (catch dets:member(no_table, 1)),
+
+ %% sync
+ ?line {'EXIT', {badarg, [{dets,sync,[no_table]}|_]}} =
+ (catch dets:sync(no_table)),
+
+ %% delete{_keys}
+ ?line {'EXIT', {badarg, [{dets,delete,[no_table,1]}|_]}} =
+ (catch dets:delete(no_table, 1)),
+
+ %% delete_object
+ ?line {'EXIT', {badarg, [{dets,delete_object,[no_table,{1,2,3}]}|_]}} =
+ (catch dets:delete_object(no_table, {1,2,3})),
+ ?line {'EXIT', {badarg, [{dets,delete_object,[T,{1,2}]}|_]}} =
+ (catch dets:delete_object(T, {1,2})),
+ ?line {'EXIT', {badarg, [{dets,delete_object,[no_table,[{1,2,3}]]}|_]}} =
+ (catch dets:delete_object(no_table, [{1,2,3}])),
+ ?line {'EXIT', {badarg, [{dets,delete_object,[T,[{1,2}]]}|_]}} =
+ (catch dets:delete_object(T, [{1,2}])),
+ ?line {'EXIT', {badarg, [{dets,delete_object,[T,[{1,2,3}|3]]}|_]}} =
+ (catch dets:delete_object(T, [{1,2,3} | 3])),
+
+ %% first,next,slot
+ ?line {'EXIT', {badarg, [{dets,first,[no_table]}|_]}} =
+ (catch dets:first(no_table)),
+ ?line {'EXIT', {badarg, [{dets,next,[no_table,1]}|_]}} =
+ (catch dets:next(no_table, 1)),
+ ?line {'EXIT', {badarg, [{dets,slot,[no_table,0]}|_]}} =
+ (catch dets:slot(no_table, 0)),
+
+ %% info
+ ?line undefined = dets:info(no_table),
+ ?line undefined = dets:info(no_table, foo),
+ ?line undefined = dets:info(T, foo),
+
+ %% match_delete
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:match_delete(no_table, '_')),
+
+ %% delete_all_objects
+ ?line {'EXIT', {badarg, [{dets,delete_all_objects,[no_table]}|_]}} =
+ (catch dets:delete_all_objects(no_table)),
+
+ %% select_delete
+ MSpec = [{'_',[],['$_']}],
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:select_delete(no_table, MSpec)),
+ ?line {'EXIT', {badarg, [{dets,select_delete,[T, <<17>>]}|_]}} =
+ (catch dets:select_delete(T, <<17>>)),
+
+ %% traverse, fold
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:traverse(no_table, fun(_) -> continue end)),
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:foldl(fun(_, A) -> A end, [], no_table)),
+ ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} =
+ (catch dets:foldr(fun(_, A) -> A end, [], no_table)),
+
+ %% close
+ ?line ok = dets:close(T),
+ ?line {error, not_owner} = dets:close(T),
+ ?line {error, not_owner} = dets:close(T),
+
+ %% init_table
+ ?line {'EXIT', {badarg,[{dets,init_table,[no_table,_,[]]}|_]}} =
+ (catch dets:init_table(no_table, fun(X) -> X end)),
+ ?line {'EXIT', {badarg,[{dets,init_table,[no_table,_,[]]}|_]}} =
+ (catch dets:init_table(no_table, fun(X) -> X end, [])),
+
+ %% from_ets
+ Ets = ets:new(ets,[]),
+ ?line {'EXIT', {badarg,[{dets,from_ets,[no_table,_]}|_]}} =
+ (catch dets:from_ets(no_table, Ets)),
+ ets:delete(Ets),
+
+ ?line {ok, T} = dets:open_file(T, Args),
+ ?line {error,incompatible_arguments} =
+ dets:open_file(T, [{type,bag} | Args]),
+ ?line ok = dets:close(T),
+
+ file:delete(Fname),
+ ?line check_pps(P0),
+ ok.
+
+cache_sets_v8(doc) ->
+ ["Test the write cache for sets."];
+cache_sets_v8(suite) ->
+ [];
+cache_sets_v8(Config) when is_list(Config) ->
+ cache_sets(Config, 8).
+
+cache_sets_v9(doc) ->
+ ["Test the write cache for sets."];
+cache_sets_v9(suite) ->
+ [];
+cache_sets_v9(Config) when is_list(Config) ->
+ cache_sets(Config, 9).
+
+cache_sets(Config, Version) ->
+ Small = 2,
+ cache_sets(Config, {0,0}, false, Small, Version),
+ cache_sets(Config, {0,0}, true, Small, Version),
+ cache_sets(Config, {5000,5000}, false, Small, Version),
+ cache_sets(Config, {5000,5000}, true, Small, Version),
+ %% Objects of size greater than 2 kB.
+ Big = 1200,
+ cache_sets(Config, {0,0}, false, Big, Version),
+ cache_sets(Config, {0,0}, true, Big, Version),
+ cache_sets(Config, {5000,5000}, false, Big, Version),
+ cache_sets(Config, {5000,5000}, true, Big, Version),
+ ok.
+
+cache_sets(Config, DelayedWrite, Extra, Sz, Version) ->
+ %% Extra = bool(). Insert tuples until the tested key is not alone.
+ %% Sz = integer(). Size of the inserted tuples.
+
+ T = cache,
+ ?line Fname = filename(cache, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ ?line {ok, _} =
+ dets:open_file(T,[{version, Version}, {file,Fname}, {type,set},
+ {delayed_write, DelayedWrite}]),
+
+ Dups = 1,
+ {Key, OtherKeys} =
+ if
+ Extra ->
+ %% Insert enough to get three keys in some slot.
+ ?line dets:safe_fixtable(T, true),
+ insert_objs(T, 1, Sz, Dups);
+ true ->
+ {1,[]}
+ end,
+ Tuple = erlang:make_tuple(Sz, Key),
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:sync(T),
+
+ %% The values of keys in the same slot as Key are checked.
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+
+ ?line ok = dets:insert(T, Tuple),
+ ?line [Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:insert(T, [Tuple,Tuple]),
+ %% If no delay, the cache gets filled immediately, and written.
+ ?line [Tuple] = dets:lookup_keys(T, [Key,a,b,c,d,e,f]),
+ ?line true = dets:member(T, Key),
+
+ %% If delay, this happens without file access.
+ ?line ok = dets:delete(T,Key),
+ ?line ok = dets:insert(T,Tuple),
+ ?line ok = dets:insert(T,Tuple),
+ ?line [Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:sync(T),
+ ?line [Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+
+ %% Key's objects are is on file only,
+ %% key 'toto' in the cache (if there is one).
+ ?line ok = dets:delete(T,toto),
+ ?line ok = dets:insert(T,[{toto,b},{toto,b}]),
+ ?line true = sort([Tuple,{toto,b}]) =:=
+ sort(dets:lookup_keys(T, [Key,toto])),
+ ?line true = dets:member(T, toto),
+
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:sync(T),
+ ?line false = dets:member(T, Key),
+ ?line Size = dets:info(T, size),
+
+ %% No object with the key on the file.
+ %% Delete, add one object.
+ Size1 = Size + 2,
+ del_and_ins(key, T, Size1, Tuple, Key, 1),
+ del_and_ins(object, T, Size1, Tuple, Key, 1),
+ del_and_ins(both, T, Size1, Tuple, Key, 1),
+
+ %% One object with the key on the file.
+ %% Delete it, add one object.
+ Size2 = Size + 2,
+ del_and_ins(key, T, Size2, Tuple, Key, 1),
+ del_and_ins(object, T, Size2, Tuple, Key, 1),
+ del_and_ins(both, T, Size2, Tuple, Key, 1),
+
+ %% Overwrite an old objekt with exactly the same size.
+ Element = case element(2, Tuple) of
+ 255 -> 254;
+ E -> E + 1
+ end,
+ Tuple2 = setelement(2, Tuple, Element),
+ ?line ok = dets:sync(T),
+ ?line ok = dets:insert(T, Tuple2),
+ ?line [Tuple2] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:sync(T),
+ ?line [Tuple2] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+
+ ?line ok = dets:insert(T, {3,a}),
+ ?line ok = dets:insert(T, {3,b}),
+ ?line ok = dets:delete_object(T, {3,c}),
+ ?line ok = dets:delete_object(T, {3,d}),
+ ?line [{3,b}] = dets:lookup(T, 3),
+
+ ?line ok = dets:delete(T, 3),
+ ?line ok = dets:delete_object(T, {3,c}),
+ ?line ok = dets:delete_object(T, {3,d}),
+ ?line [] = dets:lookup(T, 3),
+
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+ if
+ Extra ->
+ %% Let the table grow a while, if it needs to.
+ ?line All1 = get_all_objects(T),
+ ?line dets:safe_fixtable(T, false),
+ ?line timer:sleep(1000),
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+ ?line dets:safe_fixtable(T, true),
+ ?line All2 = get_all_objects(T),
+ ?line FAll2 = get_all_objects_fast(T),
+ ?line true = sort(All2) =:= sort(FAll2),
+ case symdiff(All1, All2) of
+ {[],[]} -> ok;
+ {X,Y} ->
+ NoBad = length(X) + length(Y),
+ test_server:fail({sets,DelayedWrite,Extra,Sz,NoBad})
+ end;
+ true ->
+ ok
+ end,
+ ?line ok = dets:close(T),
+
+ file:delete(Fname),
+ ?line check_pps(P0),
+ ok.
+
+cache_bags_v8(doc) ->
+ ["Test the write cache for bags."];
+cache_bags_v8(suite) ->
+ [];
+cache_bags_v8(Config) when is_list(Config) ->
+ cache_bags(Config, 8).
+
+cache_bags_v9(doc) ->
+ ["Test the write cache for bags."];
+cache_bags_v9(suite) ->
+ [];
+cache_bags_v9(Config) when is_list(Config) ->
+ cache_bags(Config, 9).
+
+cache_bags(Config, Version) ->
+ Small = 2,
+ cache_bags(Config, {0,0}, false, Small, Version),
+ cache_bags(Config, {0,0}, true, Small, Version),
+ cache_bags(Config, {5000,5000}, false, Small, Version),
+ cache_bags(Config, {5000,5000}, true, Small, Version),
+ %% Objects of size greater than 2 kB.
+ Big = 1200,
+ cache_bags(Config, {0,0}, false, Big, Version),
+ cache_bags(Config, {0,0}, true, Big, Version),
+ cache_bags(Config, {5000,5000}, false, Big, Version),
+ cache_bags(Config, {5000,5000}, true, Big, Version),
+ ok.
+
+cache_bags(Config, DelayedWrite, Extra, Sz, Version) ->
+ %% Extra = bool(). Insert tuples until the tested key is not alone.
+ %% Sz = integer(). Size of the inserted tuples.
+
+ T = cache,
+ ?line Fname = filename(cache, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ ?line {ok, _} =
+ dets:open_file(T,[{version, Version}, {file,Fname}, {type,bag},
+ {delayed_write, DelayedWrite}]),
+
+ Dups = 1,
+ {Key, OtherKeys} =
+ if
+ Extra ->
+ %% Insert enough to get three keys in some slot.
+ ?line dets:safe_fixtable(T, true),
+ insert_objs(T, 1, Sz, Dups);
+ true ->
+ {1,[]}
+ end,
+ Tuple = erlang:make_tuple(Sz, Key),
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:sync(T),
+
+ %% The values of keys in the same slot as Key are checked.
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+
+ ?line ok = dets:insert(T, Tuple),
+ ?line [Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:insert(T, [Tuple,Tuple]),
+ %% If no delay, the cache gets filled immediately, and written.
+ ?line [Tuple] = dets:lookup_keys(T, [Key,a,b,c,d,e,f]),
+ ?line true = dets:member(T, Key),
+
+ %% If delay, this happens without file access.
+ %% (This is no longer true; cache lookup has been simplified.)
+ ?line ok = dets:delete(T,Key),
+ ?line ok = dets:insert(T,Tuple),
+ ?line ok = dets:insert(T,Tuple),
+ ?line [Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:sync(T),
+ ?line [Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+
+ %% Key's objects are is on file only,
+ %% key toto in the cache (if there is one).
+ ?line ok = dets:delete(T,toto),
+ ?line false = dets:member(T, toto),
+ ?line ok = dets:insert(T,[{toto,b},{toto,b}]),
+ ?line true = sort([Tuple,{toto,b}]) =:=
+ sort(dets:lookup_keys(T, [Key,toto])),
+ ?line true = dets:member(T, toto),
+
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:sync(T),
+ ?line Size = dets:info(T, size),
+
+ %% No object with the key on the file.
+ %% Delete, add one object.
+ Size1 = Size + 2,
+ del_and_ins(key, T, Size1, Tuple, Key, 1),
+ del_and_ins(object, T, Size1, Tuple, Key, 1),
+ del_and_ins(both, T, Size1, Tuple, Key, 1),
+
+ %% One object with the key on the file.
+ %% Delete it, add one object.
+ Size2 = Size + 2,
+ del_and_ins(key, T, Size2, Tuple, Key, 1),
+ del_and_ins(object, T, Size2, Tuple, Key, 1),
+ del_and_ins(both, T, Size2, Tuple, Key, 1),
+
+ %% Overwrite an objekt on file with the same object.
+ ?line ok = dets:insert(T, Tuple),
+ ?line ok = dets:sync(T),
+ ?line [Tuple2] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:insert(T, Tuple),
+ ?line ok = dets:sync(T),
+ ?line [Tuple2] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+
+ %% A mix of insert and delete.
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:sync(T),
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:insert(T, {Key,foo}),
+ ?line ok = dets:insert(T, {Key,bar}),
+ ?line [{Key,bar},{Key,foo}] = sort(dets:lookup(T, Key)),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:delete_object(T, {Key,foo}),
+ ?line ok = dets:insert(T, {Key,kar}),
+ ?line [{Key,bar},{Key,kar}] = sort(dets:lookup(T, Key)),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:insert(T, [{Key,kar},{Key,kar}]),
+ ?line [{Key,bar},{Key,kar}] = sort(dets:lookup(T, Key)),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:delete_object(T, {Key,bar}),
+ ?line ok = dets:delete_object(T, {Key,kar}),
+ ?line [] = dets:lookup(T, Key),
+ ?line false = dets:member(T, Key),
+ ?line ok = dets:sync(T),
+ ?line [] = dets:lookup(T, Key),
+ ?line false = dets:member(T, Key),
+
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+ if
+ Extra ->
+ %% Let the table grow for a while, if it needs to.
+ ?line All1 = get_all_objects(T),
+ ?line dets:safe_fixtable(T, false),
+ ?line timer:sleep(1200),
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+ ?line dets:safe_fixtable(T, true),
+ ?line All2 = get_all_objects(T),
+ ?line FAll2 = get_all_objects_fast(T),
+ ?line true = sort(All2) =:= sort(FAll2),
+ case symdiff(All1, All2) of
+ {[],[]} -> ok;
+ {X,Y} ->
+ NoBad = length(X) + length(Y),
+ test_server:fail({bags,DelayedWrite,Extra,Sz,NoBad})
+ end;
+ true ->
+ ok
+ end,
+ ?line ok = dets:close(T),
+ file:delete(Fname),
+
+ %% Second object of a key added and looked up simultaneously.
+ R1 = {index_test,1,2,3,4},
+ R2 = {index_test,2,2,13,14},
+ R3 = {index_test,1,12,13,14},
+ ?line {ok, _} = dets:open_file(T,[{version,Version},{type,bag},
+ {keypos,2},{file,Fname}]),
+ ?line ok = dets:insert(T,R1),
+ ?line ok = dets:sync(T),
+ ?line ok = dets:insert(T,R2),
+ ?line ok = dets:sync(T),
+ ?line ok = dets:insert(T,R3),
+ ?line [R1,R3] = sort(dets:lookup(T,1)),
+ ?line true = dets:member(T, 1),
+ ?line [R1,R3] = sort(dets:lookup(T,1)),
+ ?line true = dets:member(T, 1),
+ ?line ok = dets:close(T),
+ file:delete(Fname),
+
+ ?line check_pps(P0),
+ ok.
+
+cache_duplicate_bags_v8(doc) ->
+ ["Test the write cache for duplicate bags."];
+cache_duplicate_bags_v8(suite) ->
+ [];
+cache_duplicate_bags_v8(Config) when is_list(Config) ->
+ cache_duplicate_bags(Config, 8).
+
+cache_duplicate_bags_v9(doc) ->
+ ["Test the write cache for duplicate bags."];
+cache_duplicate_bags_v9(suite) ->
+ [];
+cache_duplicate_bags_v9(Config) when is_list(Config) ->
+ cache_duplicate_bags(Config, 9).
+
+cache_duplicate_bags(Config, Version) ->
+ Small = 2,
+ cache_dup_bags(Config, {0,0}, false, Small, Version),
+ cache_dup_bags(Config, {0,0}, true, Small, Version),
+ cache_dup_bags(Config, {5000,5000}, false, Small, Version),
+ cache_dup_bags(Config, {5000,5000}, true, Small, Version),
+ %% Objects of size greater than 2 kB.
+ Big = 1200,
+ cache_dup_bags(Config, {0,0}, false, Big, Version),
+ cache_dup_bags(Config, {0,0}, true, Big, Version),
+ cache_dup_bags(Config, {5000,5000}, false, Big, Version),
+ cache_dup_bags(Config, {5000,5000}, true, Big, Version).
+
+cache_dup_bags(Config, DelayedWrite, Extra, Sz, Version) ->
+ %% Extra = bool(). Insert tuples until the tested key is not alone.
+ %% Sz = integer(). Size of the inserted tuples.
+
+ T = cache,
+ ?line Fname = filename(cache, Config),
+ ?line file:delete(Fname),
+ P0 = pps(),
+
+ ?line {ok, _} =
+ dets:open_file(T,[{version, Version}, {file,Fname},
+ {type,duplicate_bag},
+ {delayed_write, DelayedWrite}]),
+
+ Dups = 2,
+ {Key, OtherKeys} =
+ if
+ Extra ->
+ %% Insert enough to get three keys in some slot.
+ ?line dets:safe_fixtable(T, true),
+ insert_objs(T, 1, Sz, Dups);
+ true ->
+ {1,[]}
+ end,
+ Tuple = erlang:make_tuple(Sz, Key),
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:sync(T),
+ ?line false = dets:member(T, Key),
+
+ %% The values of keys in the same slot as Key are checked.
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+
+ ?line ok = dets:insert(T, Tuple),
+ ?line [Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:insert(T, [Tuple,Tuple]),
+ %% If no delay, the cache gets filled immediately, and written.
+ ?line [Tuple,Tuple,Tuple] = dets:lookup_keys(T, [Key,a,b,c,d,e,f]),
+ ?line true = dets:member(T, Key),
+
+ %% If delay, this happens without file access.
+ %% (This is no longer true; cache lookup has been simplified.)
+ ?line ok = dets:delete(T,Key),
+ ?line ok = dets:insert(T,Tuple),
+ ?line ok = dets:insert(T,Tuple),
+ ?line [Tuple,Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+ ?line ok = dets:sync(T),
+ ?line [Tuple,Tuple] = dets:lookup(T, Key),
+ ?line true = dets:member(T, Key),
+
+ %% One object in the cache, one on the file.
+ ?line ok = dets:delete(T,Key),
+ ?line ok = dets:insert(T,Tuple),
+ ?line ok = dets:sync(T),
+ ?line ok = dets:insert(T,Tuple),
+ ?line true = dets:member(T, Key), % should not read the file, but it does..
+
+ %% Key's objects are is on file only,
+ %% key toto in the cache (if there is one).
+ ?line ok = dets:delete(T,toto),
+ ?line ok = dets:insert(T,[{toto,b},{toto,b}]),
+ ?line true = sort([Tuple,Tuple,{toto,b},{toto,b}]) =:=
+ sort(dets:lookup_keys(T, [Key,toto])),
+ ?line true = dets:member(T, toto),
+ ?line Size = dets:info(T, size),
+
+ %% Two objects with the same key on the file.
+ %% Delete them, add two objects.
+ del_and_ins(key, T, Size, Tuple, Key, 2),
+
+ del_and_ins(object, T, Size, Tuple, Key, 2),
+ del_and_ins(both, T, Size, Tuple, Key, 2),
+
+ %% Two objects with the same key on the file.
+ %% Delete them, add three objects.
+ del_and_ins(key, T, Size, Tuple, Key, 3),
+ del_and_ins(object, T, Size, Tuple, Key, 3),
+ del_and_ins(both, T, Size, Tuple, Key, 3),
+
+ %% Two objects with the same key on the file.
+ %% Delete them, add one object.
+ del_and_ins(key, T, Size, Tuple, Key, 1),
+ del_and_ins(object, T, Size, Tuple, Key, 1),
+ del_and_ins(both, T, Size, Tuple, Key, 1),
+
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+ if
+ Extra ->
+ %% Let the table grow for a while, if it needs to.
+ ?line All1 = get_all_objects(T),
+ ?line dets:safe_fixtable(T, false),
+ ?line timer:sleep(1200),
+ ?line OtherValues = sort(lookup_keys(T, OtherKeys)),
+ ?line dets:safe_fixtable(T, true),
+ ?line All2 = get_all_objects(T),
+ ?line FAll2 = get_all_objects_fast(T),
+ ?line true = sort(All2) =:= sort(FAll2),
+ case symdiff(All1, All2) of
+ {[],[]} -> ok;
+ {X,Y} ->
+ NoBad = length(X) + length(Y),
+ test_server:fail({dup_bags,DelayedWrite,Extra,Sz,NoBad})
+ end;
+ true ->
+ ok
+ end,
+ ?line ok = dets:close(T),
+
+ file:delete(Fname),
+ ?line check_pps(P0),
+ ok.
+
+lookup_keys(_T, []) ->
+ [];
+lookup_keys(T, Keys) ->
+ dets:lookup_keys(T, Keys).
+
+del_and_ins(W, T, Size, Obj, Key, N) ->
+ case W of
+ object ->
+ ?line ok = dets:delete_object(T, Obj);
+ key ->
+
+ ?line ok = dets:delete(T, Key);
+ both ->
+ ?line ok = dets:delete(T, Key),
+ ?line ok = dets:delete_object(T, Obj)
+ end,
+ Objs = duplicate(N, Obj),
+ ?line [] = dets:lookup(T, Key),
+ ?line ok = dets:insert(T, Objs),
+ ?line Objs = dets:lookup_keys(T, [snurrespratt,Key]),
+ ?line true = Size + length(Objs)-2 =:= dets:info(T, size),
+ ?line Objs = dets:lookup(T, Key).
+
+
+insert_objs(T, N, Sz, Dups) ->
+ Seq = seq(N,N+255),
+ L0 = map(fun(I) -> erlang:make_tuple(Sz, I) end, Seq),
+ L = append(duplicate(Dups, L0)),
+ ?line ok = dets:insert(T, L),
+ ?line case search_slot(T, 0) of
+ false ->
+ insert_objs(T, N+256, Sz, Dups);
+ Keys ->
+ Keys
+ end.
+
+search_slot(T, I) ->
+ ?line case dets:slot(T, I) of
+ '$end_of_table' ->
+ false;
+ Objs ->
+ case usort(map(fun(X) -> element(1, X) end, Objs)) of
+ [_, Key, _ | _] = Keys0 ->
+ Keys = delete(Key, Keys0),
+ {Key, Keys};
+ _ ->
+ search_slot(T, I+1)
+ end
+ end.
+
+symdiff(L1, L2) ->
+ {X, _, Y} =
+ sofs:symmetric_partition(sofs:set(L1), sofs:set(L2)),
+ {sofs:to_external(X), sofs:to_external(Y)}.
+
+otp_4208(doc) ->
+ ["Read only table and traversal caused crash."];
+otp_4208(suite) ->
+ [];
+otp_4208(Config) when is_list(Config) ->
+ Tab = otp_4208,
+ ?line FName = filename(Tab, Config),
+ Expected = sort([{3,ghi,12},{1,abc,10},{4,jkl,13},{2,def,11}]),
+
+ file:delete(FName),
+ ?line {ok, Tab} = dets:open_file(Tab, [{file,FName}]),
+ ?line ok = dets:insert(Tab, [{1,abc,10},{2,def,11},{3,ghi,12},{4,jkl,13}]),
+ ?line Expected = sort(dets:traverse(Tab, fun(X) -> {continue, X} end)),
+ ?line ok = dets:close(Tab),
+
+ ?line {ok, Tab} = dets:open_file(Tab, [{access, read},{file,FName}]),
+ ?line Expected = sort(dets:traverse(Tab, fun(X) -> {continue, X} end)),
+ ?line ok = dets:close(Tab),
+ file:delete(FName),
+
+ ok.
+
+otp_4989(doc) ->
+ ["Read only table and growth."];
+otp_4989(suite) ->
+ [];
+otp_4989(Config) when is_list(Config) ->
+ Tab = otp_4989,
+ ?line FName = filename(Tab, Config),
+
+ %% Do exactly as in the error report.
+ ?line _Ets = ets:new(Tab, [named_table]),
+ ets_init(Tab, 100000),
+ ?line {ok, Tab} =
+ dets:open_file(Tab, [{access, read_write}, {file,FName}, {keypos,2}]),
+ ?line ok = dets:from_ets(Tab, Tab),
+ ?line ok = dets:close(Tab),
+ %% Restore.
+ ?line {ok, Tab} =
+ dets:open_file(Tab, [{access, read}, {keypos, 2}, {file, FName}]),
+ ?line true = ets:delete_all_objects(Tab),
+ ?line true = ets:from_dets(Tab, Tab),
+ ?line ok = dets:close(Tab),
+ ets:delete(Tab),
+ file:delete(FName),
+ ok.
+
+ets_init(_Tab, 0) ->
+ ok;
+ets_init(Tab, N) ->
+ ets:insert(Tab, {N,N}),
+ ets_init(Tab, N - 1).
+
+many_clients(doc) ->
+ ["Several clients accessing a table simultaneously."];
+many_clients(suite) ->
+ [];
+many_clients(Config) when is_list(Config) ->
+ Tab = many_clients,
+ ?line FName = filename(Tab, Config),
+
+ Server = self(),
+
+ ?line file:delete(FName),
+ P0 = pps(),
+ ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,9}]),
+ ?line [P1,P2,P3,P4] = new_clients(4, Tab),
+
+ %% dets:init_table/2 is used for making sure that all processes
+ %% start sending requests before the Dets process begins to handle
+ %% them; the requests arrive "in parallel".
+
+ %% Four processes accessing the same table at almost the same time.
+
+ %% One key is read, updated, and read again.
+ Seq1 = [{P1,[{lookup,1,[{1,a}]}]}, {P2,[{insert,{1,b}}]},
+ {P3,[{lookup,1,[{1,b}]}]}, {P4,[{lookup,1,[{1,b}]}]}],
+ ?line atomic_requests(Server, Tab, [[{1,a}]], Seq1),
+ ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]),
+
+ %% Different keys read by different processes
+ Seq2 = [{P1,[{member,1,true}]}, {P2,[{lookup,2,[{2,b}]}]},
+ {P3,[{lookup,1,[{1,a}]}]}, {P4,[{lookup,3,[{3,c}]}]}],
+ ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq2),
+ ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]),
+
+ %% Reading deleted key.
+ Seq3 = [{P1,[{delete_key,2}]}, {P2,[{lookup,1,[{1,a}]}]},
+ {P3,[{lookup,1,[{1,a}]}]}, {P4,[{member,2,false}]}],
+ ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq3),
+ ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]),
+
+ %% Inserting objects.
+ Seq4 = [{P1,[{insert,[{1,a},{2,b}]}]}, {P2,[{insert,[{2,c},{3,a}]}]},
+ {P3,[{insert,[{3,b},{4,d}]}]},
+ {P4,[{lookup_keys,[1,2,3,4],[{1,a},{2,c},{3,b},{4,d}]}]}],
+ ?line atomic_requests(Server, Tab, [], Seq4),
+ ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]),
+
+ %% Deleting objects.
+ Seq5 = [{P1,[{delete_object,{1,a}}]}, {P2,[{delete_object,{1,a}}]},
+ {P3,[{delete_object,{3,c}}]},
+ {P4,[{lookup_keys,[1,2,3,4],[{2,b}]}]}],
+ ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq5),
+ ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]),
+
+ %% Some request not streamed.
+ Seq6 = [{P1,[{lookup,1,[{1,a}]}]}, {P2,[{info,size,3}]},
+ {P3,[{lookup,1,[{1,a}]}]}, {P4,[{info,size,3}]}],
+ ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq6),
+ ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]),
+
+ %% Some request not streamed.
+ Seq7 = [{P1,[{insert,[{3,a}]}]}, {P2,[{insert,[{3,b}]}]},
+ {P3,[{delete_object,{3,c}}]},
+ {P4,[{lookup,3,[{3,b}]}]}],
+ ?line atomic_requests(Server, Tab, [[{3,c}]], Seq7),
+ ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]),
+
+ ?line put_requests(Server, [{P1,stop},{P2,stop},{P3,stop},{P4,stop}]),
+ ?line ok = dets:close(Tab),
+ ?line file:delete(FName),
+
+ %% Check that errors are handled correctly by the streaming operators.
+ ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,9}]),
+ ?line ok = ins(Tab, 100),
+ Obj = {66,{item,number,66}},
+ ?line {ok, ObjPos} = dets:where(Tab, Obj),
+ ?line ok = dets:close(Tab),
+ %% Damaged object.
+ crash(FName, ObjPos+12),
+ ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,9}]),
+ ?line BadObject1 = dets:lookup_keys(Tab, [65,66,67,68,69]),
+ ?line bad_object(BadObject1, FName),
+ ?line _Error = dets:close(Tab),
+ ?line file:delete(FName),
+
+ ?line check_pps(P0),
+
+ ok.
+
+%% Tab is initiated with the objects in Objs. Objs = [[object()]].
+atomic_requests(Server, Tab, Objs, Req) ->
+ ok = dets:init_table(Tab, atomic_requests(Server, Objs, Req)).
+
+atomic_requests(Server, L, Req) ->
+ fun(close) ->
+ ok;
+ (read) when [] =:= L ->
+ put_requests(Server, Req),
+ end_of_input;
+ (read) ->
+ [E | Es] = L,
+ {E, atomic_requests(Server, Es, Req)}
+ end.
+
+put_requests(Server, L) ->
+ lists:foreach(fun({Pid,R}) -> Pid ! {Server,R}, timer:sleep(1) end, L).
+
+get_replies(L) ->
+ lists:all(fun({Pid,Reply}) -> Reply =:= get_reply(Pid) end, L).
+
+get_reply(Pid) ->
+ ?line receive {Pid, Reply} -> Reply end.
+
+new_clients(0, _Tab) ->
+ [];
+new_clients(N, Tab) ->
+ [new_client(Tab) | new_clients(N-1, Tab)].
+
+new_client(Tab) ->
+ spawn(?MODULE, client, [self(), Tab]).
+
+client(S, Tab) ->
+ receive
+ {S, stop} ->
+ exit(normal);
+ {S, ToDo} ->
+ ?line Reply = eval(ToDo, Tab),
+ case Reply of
+ {error, _} -> io:format("~p: ~p~n", [self(), Reply]);
+ _ -> ok
+ end,
+ S ! {self(), Reply}
+ end,
+ client(S, Tab).
+
+eval([], _Tab) ->
+ ok;
+eval([sync | L], Tab) ->
+ ?line case dets:sync(Tab) of
+ ok -> eval(L, Tab);
+ Error -> {error, {sync,Error}}
+ end;
+eval([{insert,Stuff} | L], Tab) ->
+ ?line case dets:insert(Tab, Stuff) of
+ ok -> eval(L, Tab);
+ Error -> {error, {insert,Stuff,Error}}
+ end;
+eval([{lookup,Key,Expected} | L], Tab) ->
+ ?line case dets:lookup(Tab, Key) of
+ Expected -> eval(L, Tab);
+ Else -> {error, {lookup,Key,Expected,Else}}
+ end;
+eval([{lookup_keys,Keys,Expected} | L], Tab) ->
+ %% Time order is destroyed...
+ ?line case dets:lookup_keys(Tab, Keys) of
+ R when is_list(R) ->
+ case lists:sort(Expected) =:= lists:sort(R) of
+ true -> eval(L, Tab);
+ false -> {error, {lookup_keys,Keys,Expected,R}}
+ end;
+ Else -> {error, {lookup_keys,Keys,Expected,Else}}
+ end;
+eval([{member,Key,Expected} | L], Tab) ->
+ ?line case dets:member(Tab, Key) of
+ Expected -> eval(L, Tab);
+ Else -> {error, {member,Key,Expected,Else}}
+ end;
+eval([{delete_key,Key} | L], Tab) ->
+ ?line case dets:delete(Tab, Key) of
+ ok -> eval(L, Tab);
+ Else -> {error, {delete_key,Key,Else}}
+ end;
+eval([{delete_object,Object} | L], Tab) ->
+ ?line case dets:delete_object(Tab, Object) of
+ ok -> eval(L, Tab);
+ Else -> {error, {delete_object,Object,Else}}
+ end;
+eval([{info,Tag,Expected} | L], Tab) ->
+ ?line case dets:info(Tab, Tag) of
+ Expected -> eval(L, Tab);
+ Else -> {error, {info,Tag,Else,Expected}}
+ end;
+eval(Else, _Tab) ->
+ {error, {bad_request,Else}}.
+
+otp_4906(doc) ->
+ ["More than 128k keys caused crash."];
+otp_4906(suite) ->
+ [];
+otp_4906(Config) when is_list(Config) ->
+ N = 256*512 + 400,
+ Tab = otp_4906,
+ ?line FName = filename(Tab, Config),
+
+ file:delete(FName),
+ ?line {ok, Tab} = dets:open_file(Tab, [{file, FName}]),
+ ?line ok = ins_small(Tab, 0, N),
+ ?line ok = dets:close(Tab),
+ ?line {ok, Tab} = dets:open_file(Tab, [{file, FName}]),
+ ?line ok = read_4906(Tab, N-1),
+ ?line ok = dets:close(Tab),
+ file:delete(FName),
+
+ %% If the (only) process fixing a table updates the table, the
+ %% process will no longer be punished with a 1 ms delay (hm, the
+ %% server is delayed, it should be the client...). In this example
+ %% the writing process *is* delayed.
+ ?line {ok,Tab} = dets:open_file(Tab, [{file,FName}]),
+ Parent = self(),
+ FixPid = spawn_link(fun() ->
+ dets:safe_fixtable(Tab, true),
+ receive {Parent, stop} -> ok end
+ end),
+ ?line ok = ins_small(Tab, 0, 1000),
+ FixPid ! {Parent, stop},
+ timer:sleep(1),
+ ?line ok = dets:close(Tab),
+ file:delete(FName),
+ ok.
+
+read_4906(_T, N) when N < 0 ->
+ ok;
+read_4906(T, N) ->
+ ?line [_] = dets:lookup(T, N),
+ read_4906(T, N-1).
+
+ins_small(_T, I, N) when I =:= N ->
+ ok;
+ins_small(T, I, N) ->
+ ?line ok = dets:insert(T, {I}),
+ ins_small(T, I+1, N).
+
+otp_5402(doc) ->
+ ["Unwritable ramfile caused krasch."];
+otp_5402(suite) ->
+ [];
+otp_5402(Config) when is_list(Config) ->
+ Tab = otp_5402,
+ ?line File = filename:join([cannot, write, this, file]),
+
+ %% close
+ ?line{ok, T} = dets:open_file(Tab, [{ram_file,true},
+ {file, File}]),
+ ?line ok = dets:insert(T, {1,a}),
+ ?line {error,{file_error,_,_}} = dets:close(T),
+
+ %% sync
+ ?line {ok, T} = dets:open_file(Tab, [{ram_file,true},
+ {file, File}]),
+ ?line ok = dets:insert(T, {1,a}),
+ ?line {error,{file_error,_,_}} = dets:sync(T),
+ ?line {error,{file_error,_,_}} = dets:close(T),
+
+ %% auto_save
+ ?line {ok, T} = dets:open_file(Tab, [{ram_file,true},
+ {auto_save, 2000},
+ {file, File}]),
+ ?line ok = dets:insert(T, {1,a}),
+ ?line timer:sleep(5000),
+ ?line {error,{file_error,_,_}} = dets:close(T),
+ ok.
+
+simultaneous_open(doc) ->
+ ["Several clients open and close tables simultaneously."];
+simultaneous_open(suite) ->
+ [];
+simultaneous_open(Config) ->
+ Tab = sim_open,
+ File = filename(Tab, Config),
+
+ ?line ok = monit(Tab, File),
+ ?line ok = kill_while_repairing(Tab, File),
+ ?line ok = kill_while_init(Tab, File),
+ ?line ok = open_ro(Tab, File),
+ ?line ok = open_w(Tab, File, 0, Config),
+ ?line ok = open_w(Tab, File, 100, Config),
+ ok.
+
+%% One process logs and another process closes the log. Before
+%% monitors were used, this would make the client never return.
+monit(Tab, File) ->
+ file:delete(File),
+ {ok, Tab} = dets:open_file(Tab, [{file,File}]),
+ F1 = fun() -> dets:close(Tab) end,
+ F2 = fun() -> {'EXIT', {badarg, _}} = do_log(Tab) end,
+ spawn(F2),
+ timer:sleep(100),
+ spawn(F1),
+ dets:close(Tab),
+ file:delete(File),
+ ok.
+
+do_log(Tab) ->
+ case catch dets:insert(Tab, {hej,san,sa}) of
+ ok -> do_log(Tab);
+ Else -> Else
+ end.
+
+%% Kill the Dets process while repair is in progress.
+kill_while_repairing(Tab, File) ->
+ ?line create_opened_log(File),
+ Delay = 1000,
+ dets:start(),
+ Parent = self(),
+ Ps = processes(),
+ F = fun() ->
+ R = (catch dets:open_file(Tab, [{file,File}])),
+ timer:sleep(Delay),
+ Parent ! {self(), R}
+ end,
+ ?line P1 = spawn(F), % will repair
+ timer:sleep(100),
+ ?line P2 = spawn(F), % pending...
+ ?line P3 = spawn(F), % pending...
+ ?line DetsPid = find_dets_pid([P1, P2, P3 | Ps]),
+ exit(DetsPid, kill),
+
+ ?line receive {P1,R1} -> {'EXIT', {dets_process_died, _}} = R1 end,
+ ?line receive {P2,R2} -> {ok, _} = R2 end,
+ ?line receive {P3,R3} -> {ok, _} = R3 end,
+
+ timer:sleep(200),
+ case dets:info(Tab) of
+ undefined ->
+ ok;
+ _Info ->
+ timer:sleep(5000),
+ ?line undefined = dets:info(Tab)
+ end,
+
+ file:delete(File),
+ ok.
+
+find_dets_pid(P0) ->
+ case lists:sort(processes() -- P0) of
+ [P, _] -> P;
+ _ -> timer:sleep(100), find_dets_pid(P0)
+ end.
+
+%% Kill the Dets process when there are users and an on-going
+%% initiailization.
+kill_while_init(Tab, File) ->
+ file:delete(File),
+ Parent = self(),
+ F = fun() ->
+ R = dets:open_file(Tab, [{file,File}]),
+ Parent ! {self(), R},
+ receive {Parent, die} -> ok end,
+ {error, not_owner} = dets:close(Tab)
+ end,
+ ?line P1 = spawn(F),
+ ?line P2 = spawn(F),
+ ?line P3 = spawn(F),
+ IF = fun() ->
+ R = dets:open_file(Tab, [{file,File}]),
+ Parent ! {self(), R},
+ Fun = fun(_) -> timer:sleep(100000) end,
+ {'EXIT', {badarg, _}} = (catch dets:init_table(Tab, Fun)),
+ receive {Parent, die} -> ok end
+ end,
+ ?line P4 = spawn(IF),
+ ?line receive {P1,R1} -> {ok, _} = R1 end,
+ ?line receive {P2,R2} -> {ok, _} = R2 end,
+ ?line receive {P3,R3} -> {ok, _} = R3 end,
+ ?line receive {P4,R4} -> {ok, _} = R4 end,
+ ?line [DetsPid] =
+ lists:filter(fun(P) -> dets:pid2name(P) =/= undefined end,
+ erlang:processes()),
+ exit(DetsPid, kill),
+
+ timer:sleep(1000),
+ ?line undefined = dets:info(Tab),
+ ?line P1 ! {Parent, die},
+ ?line P2 ! {Parent, die},
+ ?line P3 ! {Parent, die},
+ ?line P4 ! {Parent, die},
+
+ file:delete(File),
+ timer:sleep(100),
+ ok.
+
+open_ro(Tab, File) ->
+ ?line create_opened_log(File),
+ Delay = 1000,
+ Parent = self(),
+ F = fun() ->
+ R = dets:open_file(Tab, [{file,File},{access,read}]),
+ timer:sleep(Delay),
+ Parent ! {self(), R}
+ end,
+ ?line P1 = spawn(F),
+ ?line P2 = spawn(F),
+ ?line P3 = spawn(F),
+
+ ?line receive {P1,R1} -> {error,{not_closed,_}} = R1 end,
+ ?line receive {P2,R2} -> {error,{not_closed,_}} = R2 end,
+ ?line receive {P3,R3} -> {error,{not_closed,_}} = R3 end,
+ ok.
+
+open_w(Tab, File, Delay, Config) ->
+ ?line create_opened_log(File),
+ Parent = self(),
+ F = fun() ->
+ R = dets:open_file(Tab, [{file,File}]),
+ timer:sleep(Delay),
+ Parent ! {self(), R}
+ end,
+ ?line Pid1 = spawn(F),
+ ?line Pid2 = spawn(F),
+ ?line Pid3 = spawn(F),
+ ?line undefined = dets:info(Tab), % is repairing now
+ ?line 0 = qlen(),
+
+ Tab2 = t2,
+ File2 = filename(Tab2, Config),
+ ?line file:delete(File2),
+ ?line {ok,Tab2} = dets:open_file(Tab2, [{file,File2}]),
+ ?line ok = dets:close(Tab2),
+ ?line file:delete(File2),
+ ?line 0 = qlen(), % still repairing
+
+ ?line receive {Pid1,R1} -> {ok, Tab} = R1 end,
+ ?line receive {Pid2,R2} -> {ok, Tab} = R2 end,
+ ?line receive {Pid3,R3} -> {ok, Tab} = R3 end,
+ timer:sleep(200),
+ case dets:info(Tab) of
+ undefined ->
+ ok;
+ _Info ->
+ timer:sleep(5000),
+ ?line undefined = dets:info(Tab)
+ end,
+
+ file:delete(File),
+ ok.
+
+qlen() ->
+ {_, {_, N}} = lists:keysearch(message_queue_len, 1, process_info(self())),
+ N.
+
+create_opened_log(File) ->
+ Tab = t,
+ file:delete(File),
+ ?line {ok, Tab} = dets:open_file(Tab, [{file,File}]),
+ ?line ok = ins(Tab, 60000),
+ ?line ok = dets:close(Tab),
+ ?line crash(File, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED),
+ ok.
+
+insert_new(doc) ->
+ ["OTP-5075. insert_new/2"];
+insert_new(suite) ->
+ [];
+insert_new(Config) ->
+ Tab = insert_new,
+ File = filename(Tab, Config),
+ file:delete(File),
+ ?line {ok, T} = dets:open_file(Tab, [{file,File}]),
+ ?line {'EXIT', {badarg, _}} = (catch dets:insert_new(Tab, 14)),
+ ?line {'EXIT', {badarg, _}} = (catch dets:insert_new(Tab, {})),
+ ?line true = dets:insert_new(Tab, {1,a}),
+ ?line false = dets:insert_new(Tab, {1,a}),
+ ?line true = dets:insert_new(Tab, [{2,b}, {3,c}]),
+ ?line false = dets:insert_new(Tab, [{2,b}, {3,c}]),
+ ?line false = dets:insert_new(Tab, [{1,a}, {4,d}]),
+ ?line ok = dets:close(Tab),
+
+ file:delete(File),
+ ?line {ok, T} = dets:open_file(Tab, [{file,File},{type,bag}]),
+ ?line true = dets:insert_new(Tab, {1,a}),
+ ?line false = dets:insert_new(Tab, {1,b}),
+ ?line true = dets:insert_new(Tab, [{2,b}, {3,c}]),
+ ?line false = dets:insert_new(Tab, [{2,a}, {3,d}]),
+ ?line false = dets:insert_new(Tab, [{1,a}, {4,d}]),
+ ?line ok = dets:close(Tab),
+
+
+ file:delete(File),
+ ok.
+
+repair_continuation(doc) ->
+ ["OTP-5126. repair_continuation/2"];
+repair_continuation(suite) ->
+ [];
+repair_continuation(Config) ->
+ Tab = repair_continuation_table,
+ ?line Fname = filename(repair_cont, Config),
+ ?line file:delete(Fname),
+ ?line {ok, _} = dets:open_file(Tab, [{file,Fname}]),
+ ?line ok = dets:insert(Tab, [{1,a},{2,b},{3,c}]),
+
+ ?line MS = [{'_',[],[true]}],
+
+ ?line {[true], C1} = dets:select(Tab, MS, 1),
+ ?line C2 = binary_to_term(term_to_binary(C1)),
+ ?line {'EXIT', {badarg, _}} = (catch dets:select(C2)),
+ ?line C3 = dets:repair_continuation(C2, MS),
+ ?line {[true], C4} = dets:select(C3),
+ ?line C5 = dets:repair_continuation(C4, MS),
+ ?line {[true], _} = dets:select(C5),
+ ?line {'EXIT', {badarg, _}} = (catch dets:repair_continuation(Tab, bu)),
+
+ ?line ok = dets:close(Tab),
+ ?line file:delete(Fname),
+ ok.
+
+otp_5487(doc) ->
+ ["OTP-5487. Growth of read-only table (again)."];
+otp_5487(suite) ->
+ [];
+otp_5487(Config) ->
+ otp_5487(Config, 9),
+ otp_5487(Config, 8),
+ ok.
+
+otp_5487(Config, Version) ->
+ Tab = otp_5487,
+ ?line Fname = filename(otp_5487, Config),
+ ?line file:delete(Fname),
+ ?line Ets = ets:new(otp_5487, [public, set]),
+ ?line lists:foreach(fun(I) -> ets:insert(Ets, {I,I+1}) end,
+ lists:seq(0,1000)),
+ ?line {ok, _} = dets:open_file(Tab, [{file,Fname},{version,Version}]),
+ ?line ok = dets:from_ets(Tab, Ets),
+ ?line ok = dets:sync(Tab),
+ ?line ok = dets:close(Tab),
+ ?line {ok, _} = dets:open_file(Tab, [{file,Fname},{access,read}]),
+ ?line [{1,2}] = dets:lookup(Tab, 1),
+ ?line ok = dets:close(Tab),
+ ?line ets:delete(Ets),
+ ?line file:delete(Fname).
+
+otp_6206(doc) ->
+ ["OTP-6206. Badly formed free lists."];
+otp_6206(suite) ->
+ [];
+otp_6206(Config) ->
+ Tab = otp_6206,
+ File = filename(Tab, Config),
+
+ file:delete(File),
+ Options = [{file,File}],
+ ?line {ok, Tab} = dets:open_file(Tab, Options),
+ NObjs = 13006,
+ ?line ok = ins(Tab, NObjs),
+ ?line ok = del(Tab, NObjs, 2),
+ ?line ok = dets:close(Tab),
+
+ %% Used to return {badmatch,{error,{bad_freelists,File}}.
+ ?line {ok, Tab} = dets:open_file(Tab, [{repair,false}|Options]),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+ ok.
+
+otp_6359(doc) ->
+ ["OTP-6359. select and match never return the empty list."];
+otp_6359(suite) ->
+ [];
+otp_6359(Config) ->
+ Tab = otp_6359,
+ File = filename(Tab, Config),
+
+ file:delete(File),
+ ?line {ok, _} = dets:open_file(Tab, [{file, File}]),
+ %% Used to return {[], Cont}:
+ ?line '$end_of_table' = dets:match(Tab, '_', 100),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+ ok.
+
+otp_4738(doc) ->
+ ["OTP-4738. ==/2 and =:=/2."];
+otp_4738(suite) ->
+ [];
+otp_4738(Config) ->
+ %% Version 8 has not been corrected.
+ %% (The constant -12857447 is for version 9 only.)
+ otp_4738_set(9, Config),
+ otp_4738_bag(9, Config),
+ otp_4738_dupbag(9, Config),
+ ok.
+
+otp_4738_dupbag(Version, Config) ->
+ Tab = otp_4738,
+ File = filename(Tab, Config),
+ file:delete(File),
+ I = -12857447,
+ F = float(I),
+ One = 1,
+ FOne = float(One),
+ Args = [{file,File},{type,duplicate_bag},{version,Version}],
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]),
+ ?line ok = dets:sync(Tab),
+ ?line [{F,One},{F,FOne}] = dets:lookup(Tab, F),
+ ?line [{I,One},{I,FOne}] = dets:lookup(Tab, I),
+ ?line ok = dets:insert(Tab, [{F,One},{F,FOne}]),
+ ?line [{I,One},{I,FOne},{F,One},{F,FOne},{F,One},{F,FOne}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:insert(Tab, [{F,FOne},{F,One}]),
+ ?line [{I,One},{I,FOne},{F,One},{F,FOne},{F,One},
+ {F,FOne},{F,FOne},{F,One}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:delete_object(Tab, {I,FOne}),
+ ?line [{I,One},{F,One},{F,FOne},{F,One},{F,FOne},{F,FOne},{F,One}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:insert(Tab, {I,FOne}),
+ ?line [{I,One},{I,FOne},{F,One},{F,FOne},{F,One},
+ {F,FOne},{F,FOne},{F,One}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:delete_object(Tab, {F,FOne}),
+ ?line [{I,One},{I,FOne},{F,One},{F,One},{F,One}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:delete(Tab, F),
+ ?line [{I,One},{I,FOne}] = dets:match_object(Tab, '_'),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+
+ Zero = 0,
+ FZero = float(Zero),
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]),
+ ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]),
+ ?line ok = dets:insert(Tab, [{I,Zero},{F,Zero},{I,FZero},{I,FZero}]),
+ ?line Objs0 = dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:close(Tab),
+ crash(File, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED),
+ io:format("Expect repair:~n"),
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ?line Objs1 = dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:close(Tab),
+ ?line Objs1 = Objs0,
+ file:delete(File),
+ ok.
+
+otp_4738_bag(Version, Config) ->
+ Tab = otp_4738,
+ File = filename(Tab, Config),
+ file:delete(File),
+ I = -12857447,
+ F = float(I),
+ One = 1,
+ FOne = float(One),
+ Args = [{file,File},{type,bag},{version,Version}],
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]),
+ ?line ok = dets:sync(Tab),
+ ?line [{F,One},{F,FOne}] = dets:lookup(Tab, F),
+ ?line [{I,One},{I,FOne}] = dets:lookup(Tab, I),
+ ?line ok = dets:insert(Tab, [{F,One},{F,FOne}]),
+ ?line [{I,One},{I,FOne},{F,One},{F,FOne}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:insert(Tab, [{F,FOne},{F,One}]),
+ ?line [{I,One},{I,FOne},{F,FOne},{F,One}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:delete_object(Tab, {I,FOne}),
+ ?line [{I,One},{F,FOne},{F,One}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:insert(Tab, {I,FOne}),
+ ?line [{I,One},{I,FOne},{F,FOne},{F,One}] =
+ dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:delete(Tab, F),
+ ?line [{I,One},{I,FOne}] = dets:match_object(Tab, '_'),
+ ?line ok = dets:close(Tab),
+ file:delete(File).
+
+otp_4738_set(Version, Config) ->
+ Tab = otp_4738,
+ File = filename(Tab, Config),
+ file:delete(File),
+ Args = [{file,File},{type,set},{version,Version}],
+
+ %% I and F share the same slot.
+ I = -12857447,
+ F = float(I),
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ?line ok = dets:insert(Tab, [{I},{F}]),
+ ?line ok = dets:sync(Tab),
+ ?line [{F}] = dets:lookup(Tab, F),
+ ?line [{I}] = dets:lookup(Tab, I),
+ ?line ok = dets:insert(Tab, [{F}]),
+ ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ?line ok = dets:insert(Tab, [{I}]),
+ ?line ok = dets:sync(Tab),
+ ?line [] = dets:lookup(Tab, F),
+ ?line [{I}] = dets:lookup(Tab, I),
+ ?line ok = dets:insert(Tab, [{F}]),
+ ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ok = dets:insert(Tab, [{I},{F}]),
+ %% {insert, ...} in the cache, try lookup:
+ ?line [{F}] = dets:lookup(Tab, F),
+ ?line [{I}] = dets:lookup(Tab, I),
+ %% Both were found, but that cannot be verified.
+ ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ ?line ok = dets:insert(Tab, [{I}]),
+ ?line ok = dets:sync(Tab),
+ ?line ok = dets:insert(Tab, [{F}]),
+ %% {insert, ...} in the cache, try lookup:
+ ?line [{F}] = dets:lookup(Tab, F),
+ ?line [{I}] = dets:lookup(Tab, I),
+ ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+
+ ?line {ok, Tab} = dets:open_file(Tab, Args),
+ %% Both operations in the cache:
+ ?line ok = dets:insert(Tab, [{I}]),
+ ?line ok = dets:insert(Tab, [{F}]),
+ ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+ ok.
+
+otp_7146(doc) ->
+ ["OTP-7146. Bugfix: missing test when re-hashing."];
+otp_7146(suite) ->
+ [];
+otp_7146(Config) ->
+ Tab = otp_7146,
+ File = filename(Tab, Config),
+ file:delete(File),
+
+ Max = 2048,
+ ?line {ok, Tab} = dets:open_file(Tab, [{max_no_slots,Max}, {file,File}]),
+ write_dets(Tab, Max),
+ ?line ok = dets:close(Tab),
+
+ file:delete(File),
+ ok.
+
+write_dets(Tab, Max) ->
+ write_dets(Tab, 0, Max).
+
+write_dets(_Tab, N, Max) when N > Max ->
+ ok;
+write_dets(Tab, N, Max) ->
+ ok = dets:insert(Tab,{ N, {entry,N}}),
+ write_dets(Tab, N+1, Max).
+
+otp_8070(doc) ->
+ ["OTP-8070. Duplicated objects with insert_new() and duplicate_bag."];
+otp_8070(suite) ->
+ [];
+otp_8070(Config) when is_list(Config) ->
+ Tab = otp_8070,
+ File = filename(Tab, Config),
+ file:delete(File),
+ ?line {ok, _} = dets:open_file(Tab, [{file,File},{type, duplicate_bag}]),
+ ?line ok = dets:insert(Tab, [{3,0}]),
+ ?line false = dets:insert_new(Tab, [{3,1},{3,1}]),
+ ?line [{3,0}] = dets:lookup(Tab, 3),
+ ?line ok = dets:close(Tab),
+ file:delete(File),
+ ok.
+
+%%
+%% Parts common to several test cases
+%%
+
+crash(File, Where) ->
+ crash(File, Where, 10).
+
+crash(File, Where, What) when is_integer(What) ->
+ ?line {ok, Fd} = file:open(File, read_write),
+ ?line file:position(Fd, Where),
+ ?line ok = file:write(Fd, [What]),
+ ?line ok = file:close(Fd).
+
+args(Config) ->
+ {Sets, Bags, Dups} =
+ {[
+ [],
+ [{type, set}, {estimated_no_objects, 300},
+ {ram_file, true}],
+ [{type, set}, {estimated_no_objects, 300}],
+ [{type, set}, {estimated_no_objects, 300}],
+ [{auto_save,20}, {type, set},
+ {estimated_no_objects, 300}]
+ ],
+
+ [
+ [{type, bag}, {estimated_no_objects, 300}, {ram_file, true}],
+ [{type, bag}],
+ [{type, bag}, {estimated_no_objects, 300}],
+ [{type, bag}, {estimated_no_objects, 300}],
+ [{type, bag},
+ {auto_save,20}, {estimated_no_objects, 300}],
+ [{type, bag}, {estimated_no_objects, 300}, {ram_file, true}]
+ ],
+
+ [
+ [{type, duplicate_bag}, {estimated_no_objects, 300},
+ {ram_file, true}],
+ [{type, duplicate_bag}],
+ [{type, duplicate_bag}, {estimated_no_objects, 300}],
+ [{type, duplicate_bag}, {estimated_no_objects, 300}],
+ [{type, duplicate_bag},
+ {auto_save,20}, {estimated_no_objects, 300}],
+ [{type, duplicate_bag}, {estimated_no_objects, 300},
+ {ram_file, true}]
+ ]
+ },
+ zip_filename(Sets, Bags, Dups, Config).
+
+zip_filename(S, B, D, Conf) ->
+ zip_filename(S, B, D, [], [], [], 1, Conf).
+
+zip_filename([H|T], B, D, S1, B1, D1, I, Conf) ->
+ zip_filename(T, B, D, [[{file, new_filename(I, Conf)} | H] | S1],
+ B1, D1, I+1, Conf);
+zip_filename([], [H|B], D, S1, B1, D1, I, Conf) ->
+ zip_filename([], B, D, S1, [[{file, new_filename(I, Conf)} | H] | B1],
+ D1, I+1, Conf);
+zip_filename([], [], [H|T], S1, B1, D1, I, Conf) ->
+ zip_filename([], [], T, S1, B1, [[{file, new_filename(I, Conf)} | H] | D1],
+ I+1, Conf);
+zip_filename([], [], [], S1, B1, D1, _, _Conf) ->
+ {reverse(S1), reverse(B1), reverse(D1)}.
+
+del_test(Tab) ->
+ ?format("Deltest on ~p~n", [Tab]),
+ ?line Objs = safe_get_all_objects(Tab),
+ ?line Keys = map(fun(X) -> element(1, X) end, Objs),
+ ?line foreach(fun(Key) -> dets:delete(Tab, Key) end, Keys),
+ ?line 0 = length(get_all_objects(Tab)),
+ ?line [] = get_all_objects_fast(Tab),
+ ?line 0 = dets:info(Tab, size).
+
+del_obj_test(Tab) ->
+ ?format("Delobjtest on ~p~n", [Tab]),
+ ?line Objs = safe_get_all_objects(Tab),
+ ?line LL = length(Objs),
+ ?line LL = dets:info(Tab, size),
+ ?line foreach(fun(Obj) -> dets:delete_object(Tab, Obj) end, Objs),
+ ?line 0 = length(get_all_objects(Tab)),
+ ?line [] = get_all_objects_fast(Tab),
+ ?line 0 = dets:info(Tab, size).
+
+match_del_test(Tab) ->
+ ?line ?format("Match delete test on ~p~n", [Tab]),
+ ?line ok = dets:match_delete(Tab, {'_','_','_'}),
+ ?line Sz = dets:info(Tab, size),
+ ?line true = Sz =:= length(dets:match_object(Tab, '_')),
+ ?line ok = dets:match_delete(Tab, '_'),
+ ?line 0 = dets:info(Tab, size),
+ ?line 0 = length(get_all_objects(Tab)),
+ ?line [] = get_all_objects_fast(Tab).
+
+trav_test(_Data, Len, Tab) ->
+ ?format("Travtest on ~p~n", [Tab]),
+ ?line _X0 = dets:traverse(Tab, fun(_X) -> continue end),
+ ?line XX = dets:traverse(Tab, fun(X) -> {continue, X} end),
+ ?line case Len =:= length(XX) of
+ false -> ?format("DIFF ~p~n", [XX -- _Data]);
+ true -> ok
+ end,
+ ?line 1 = length(dets:traverse(Tab, fun(X) -> {done, X} end)).
+
+match_test(Data, Tab) ->
+ ?line ?format("Match test on ~p~n", [Tab]),
+ ?line Data1 = sort(filter(fun(X) when tuple_size(X) =:= 3 -> true;
+ (_X) -> false
+ end, Data)),
+ ?line Data1 = sort(dets:match_object(Tab, {'$1', '$2', '$3'})),
+
+ ?line Len = length(Data),
+ ?line Len = length(dets:match(Tab, '_')),
+ ?line Len2 = length(Data1),
+ ?line Len2 = length(dets:match(Tab, {'$1', '_', '_'})),
+
+ ?line Data3 =
+ filter(fun(X) ->
+ K = element(1, X),
+ if
+ tuple_size(X) =:= 3, tuple_size(K) =:= 2 -> true;
+ true -> false
+ end
+ end, Data),
+ ?line Len3 = length(Data3),
+ ?line Len3 = length(dets:match(Tab, {{'$1', '$2'}, '_', '_'})),
+ ?line Len3 = length(dets:match_object(Tab, {{'$1', '$2'}, '_', '_'})),
+
+ ?line R = make_ref(),
+ ?line dets:insert(Tab, {{R, R}, 33 ,44}),
+ ?line 1 = length(dets:match(Tab, {{R, R}, '_', '_'})),
+ ?line 1 = length(dets:match_object(Tab, {{R, R}, '_', '_'})).
+
+%%
+%% Utilities
+%%
+
+headsz(8) ->
+ ?HEADSZ_v8;
+headsz(_) ->
+ ?HEADSZ_v9.
+
+unwritable(Fname) ->
+ ?line {ok, Info} = file:read_file_info(Fname),
+ Mode = Info#file_info.mode - 8#00200,
+ ?line file:write_file_info(Fname, Info#file_info{mode = Mode}).
+
+writable(Fname) ->
+ ?line {ok, Info} = file:read_file_info(Fname),
+ Mode = Info#file_info.mode bor 8#00200,
+ ?line file:write_file_info(Fname, Info#file_info{mode = Mode}).
+
+truncate(File, Where) ->
+ ?line {ok, Fd} = file:open(File, read_write),
+ ?line file:position(Fd, Where),
+ ?line ok = file:truncate(Fd),
+ ?line ok = file:close(Fd).
+
+new_filename(Name, _Config) when is_integer(Name) ->
+ filename:join(?privdir(_Config),
+ integer_to_list(Name) ++ ".DETS").
+
+filename(Name, Config) when is_atom(Name) ->
+ filename(atom_to_list(Name), Config);
+filename(Name, _Config) ->
+ filename:join(?privdir(_Config), Name).
+
+open_files(_Name, [], _Version) ->
+ [];
+open_files(Name0, [Args | Tail], Version) ->
+ ?format("init ~p~n", [Args]),
+ ?line Name = list_to_atom(integer_to_list(Name0)),
+ ?line {ok, Name} = dets:open_file(Name, [{version,Version} | Args]),
+ [Name | open_files(Name0+1, Tail, Version)].
+
+close_all(Tabs) -> foreach(fun(Tab) -> ok = dets:close(Tab) end, Tabs).
+
+delete_files(Args) ->
+ Fun = fun(F) ->
+ {value, {file, File}} = keysearch(file, 1, F),
+ file:delete(File),
+ File
+ end,
+ map(Fun, Args).
+
+%% Initialize all tables
+initialize(Tabs, Data) ->
+ ?line foreach(fun(Tab) ->
+ Fun = fun(Obj) -> ok = dets:insert(Tab, Obj) end,
+ foreach(Fun, Data),
+ dets:sync(Tab)
+ end, Tabs).
+
+%% need more than 512 objects to really trig overflow
+make_data(Kp) ->
+ make_data(Kp, set).
+
+make_data(Kp, Type) ->
+ dup(Type, make_data(Kp, Type, 520)).
+
+dup(duplicate_bag, [H1, H2 |T]) ->
+ [H1,H2, H1, H2 | dup(duplicate_bag, T)];
+dup(_, Other) ->
+ Other.
+
+make_data(_Kp, Type, 0) ->
+ odd_keys(Type);
+make_data(1, set, I) ->
+ [{I, q,w} | make_data(1, set, I-1)];
+make_data(2, set, I) ->
+ [{hh, I, q,w} | make_data(2, set, I-1)];
+make_data(1, bag, I) ->
+ [{I, q,w} , {I, hah, 77} | make_data(1, bag, I-1)];
+make_data(2, bag, I) ->
+ [{hh, I, q,w} , {hh, I, lalal, 900} | make_data(2, bag, I-1)];
+make_data(1, duplicate_bag, I) ->
+ [{I, q,w} , {I, hah, 77} | make_data(1, duplicate_bag, I-1)];
+make_data(2, duplicate_bag, I) ->
+ [{hh, I, q,w} , {hh, I, lalal, 900} | make_data(2, duplicate_bag, I-1)].
+
+odd_keys(_) ->
+ [{foo, 1 ,2},
+ {{foo, foo}, 2,3},
+ {"kakaka", {{{}}}, jj},
+ {{"kallll", "kkk", []}, 66.7777},
+ {make_ref(), 99, 66},
+ {{1},2,3,4,5,6,7,duplicate(50, 8)},
+ {self(), 7,8,88},
+ {[self()], 8, 11}].
+
+
+ins(_T, 0) ->
+ ok;
+ins(T, N) ->
+ case dets:insert(T, {N, item(N)}) of
+ ok -> ins(T, N-1);
+ Error -> Error
+ end.
+
+item(N) when N rem 2 =:= 0 ->
+ {item, number, N};
+item(N) ->
+ {item, number, N, a, much, bigger, one, i, think}.
+
+del(_T, N, _I) when N =< 0 ->
+ ok;
+del(T, N, I) ->
+ ok = dets:delete(T, N),
+ del(T, N-I, I).
+
+ensure_node(0, _Node) ->
+ could_not_start_node;
+ensure_node(N, Node) ->
+ case net_adm:ping(Node) of
+ pang ->
+ receive after 1000 ->
+ ok
+ end,
+ ensure_node(N-1,Node);
+ pong ->
+ ok
+ end.
+
+size_test(Len, Tabs) ->
+ ?line foreach(fun(Tab) ->
+ Len = dets:info(Tab, size)
+ end, Tabs).
+
+no_keys_test([T | Ts]) ->
+ no_keys_test(T),
+ no_keys_test(Ts);
+no_keys_test([]) ->
+ ok;
+no_keys_test(T) ->
+ case dets:info(T, version) of
+ 8 ->
+ ok;
+ 9 ->
+ Kp = dets:info(T, keypos),
+ ?line All = dets:match_object(T, '_'),
+ ?line L = lists:map(fun(X) -> element(Kp, X) end, All),
+ ?line NoKeys = length(lists:usort(L)),
+ ?line case {dets:info(T, no_keys), NoKeys} of
+ {N, N} ->
+ ok;
+ {N1, N2} ->
+ exit({no_keys_test, N1, N2})
+ end
+ end.
+
+safe_get_all_objects(Tab) ->
+ dets:safe_fixtable(Tab, true),
+ Objects = get_all_objects(Tab),
+ dets:safe_fixtable(Tab, false),
+ Objects.
+
+%% Caution: unless the table has been fixed, strange results can be returned.
+get_all_objects(Tab) -> get_all_objects(dets:first(Tab), Tab, []).
+
+%% Assuming no key matches {error, Reason}...
+get_all_objects('$end_of_table', _Tab, L) -> L;
+get_all_objects({error, Reason}, _Tab, _L) ->
+ exit({get_all_objects, get(line), {error, Reason}});
+get_all_objects(Key, Tab, L) ->
+ Objs = dets:lookup(Tab, Key),
+ ?line get_all_objects(dets:next(Tab, Key), Tab, Objs ++ L).
+
+count_objects_quite_fast(Tab) ->
+ ?line R1 = dets:match_object(Tab, '_', 1),
+ count_objs_1(R1, 0).
+
+count_objs_1('$end_of_table', N) ->
+ N;
+count_objs_1({Ts,C}, N) when is_list(Ts) ->
+ count_objs_1(dets:match_object(C), length(Ts) + N).
+
+get_all_objects_fast(Tab) ->
+ dets:match_object(Tab, '_').
+
+%% Relevant for version 8.
+histogram(Tab) ->
+ OnePercent = case dets:info(Tab, no_slots) of
+ undefined -> undefined;
+ {_, NoSlots, _} -> NoSlots/100
+ end,
+ histogram(Tab, OnePercent).
+
+histogram(Tab, OnePercent) ->
+ ?line E = ets:new(histo, []),
+ ?line dets:safe_fixtable(Tab, true),
+ ?line Hist = histo(Tab, E, 0, OnePercent, OnePercent),
+ ?line dets:safe_fixtable(Tab, false),
+ ?line case Hist of
+ ok ->
+ ?line H = ets:tab2list(E),
+ ?line true = ets:delete(E),
+ sort(H);
+ Error ->
+ ets:delete(E),
+ Error
+ end.
+
+histo(T, E, I, One, Count) when is_number(Count), I > Count ->
+ io:format("."),
+ histo(T, E, I, One, Count+One);
+histo(T, E, I, One, Count) ->
+ ?line case dets:slot(T, I) of
+ '$end_of_table' when is_number(Count) ->
+ io:format("~n"),
+ ok;
+ '$end_of_table' ->
+ ok;
+ Objs when is_list(Objs) ->
+ L = length(Objs),
+ case catch ets:update_counter(E, L, 1) of
+ {'EXIT', _} ->
+ ets:insert(E, {L, 1});
+ _ ->
+ ok
+ end,
+ histo(T, E, I+1, One, Count);
+ Error ->
+ Error
+ end.
+
+sum_histogram(H) ->
+ sum_histogram(H, 0).
+
+sum_histogram([{S,N1} | H], N) ->
+ sum_histogram(H, N + S*N1);
+sum_histogram([], N) ->
+ N.
+
+ave_histogram(H) ->
+ ave_histogram(H, 0)/sum_histogram(H).
+
+ave_histogram([{S,N1} | H], N) ->
+ ave_histogram(H, N + S*S*N1);
+ave_histogram([], N) ->
+ N.
+
+bad_object({error,{bad_object,FileName}}, FileName) ->
+ ok; % Version 8, no debug.
+bad_object({error,{{bad_object,_,_},FileName}}, FileName) ->
+ ok; % Version 8, debug...
+bad_object({error,{{bad_object,_}, FileName}}, FileName) ->
+ ok; % No debug.
+bad_object({error,{{{bad_object,_,_},_,_,_}, FileName}}, FileName) ->
+ ok. % Debug.
+
+check_pps(P0) ->
+ case pps() of
+ P0 ->
+ ok;
+ _ ->
+ %% On some (rare) occasions the dets process is still
+ %% running although the call to close() has returned, as
+ %% it seems...
+ timer:sleep(500),
+ case pps() of
+ P0 ->
+ ok;
+ P1 ->
+ io:format("failure, got ~p~n, expected ~p\n", [P1, P0]),
+ {Ports0,Procs0} = P0,
+ {Ports1,Procs1} = P1,
+ show("Old ports", Ports0 -- Ports1),
+ show("New ports", Ports1 -- Ports0),
+ show("Old procs", Procs0 -- Procs1),
+ show("New procs", Procs1 -- Procs0),
+ ?t:fail()
+ end
+ end.
+
+show(_S, []) ->
+ ok;
+show(S, L) ->
+ io:format("~s: ~p~n", [S, L]).
+
+pps() ->
+ dets:start(),
+ {port_list(), process_list()}.
+
+port_list() ->
+ [{P,safe_second_element(erlang:port_info(P, name))} ||
+ P <- erlang:ports()].
+
+process_list() ->
+ [{P,process_info(P, registered_name),
+ safe_second_element(process_info(P, initial_call))} ||
+ P <- processes()].
+
+safe_second_element({_,Info}) -> Info;
+safe_second_element(Other) -> Other.
diff --git a/lib/stdlib/test/dets_SUITE_data/dets_test_v8b.dets b/lib/stdlib/test/dets_SUITE_data/dets_test_v8b.dets
new file mode 100644
index 0000000000..d0aa20fe06
--- /dev/null
+++ b/lib/stdlib/test/dets_SUITE_data/dets_test_v8b.dets
Binary files differ
diff --git a/lib/stdlib/test/dets_SUITE_data/dets_test_v8b_little_endian.dets b/lib/stdlib/test/dets_SUITE_data/dets_test_v8b_little_endian.dets
new file mode 100644
index 0000000000..bf490afa1a
--- /dev/null
+++ b/lib/stdlib/test/dets_SUITE_data/dets_test_v8b_little_endian.dets
Binary files differ
diff --git a/lib/stdlib/test/dets_SUITE_data/version_9a.dets b/lib/stdlib/test/dets_SUITE_data/version_9a.dets
new file mode 100644
index 0000000000..6023d79c94
--- /dev/null
+++ b/lib/stdlib/test/dets_SUITE_data/version_9a.dets
Binary files differ
diff --git a/lib/stdlib/test/dets_SUITE_data/version_9b_phash.dat b/lib/stdlib/test/dets_SUITE_data/version_9b_phash.dat
new file mode 100644
index 0000000000..ebf254010f
--- /dev/null
+++ b/lib/stdlib/test/dets_SUITE_data/version_9b_phash.dat
Binary files differ
diff --git a/lib/stdlib/test/dets_SUITE_data/version_r2d.dets b/lib/stdlib/test/dets_SUITE_data/version_r2d.dets
new file mode 100644
index 0000000000..327072f99e
--- /dev/null
+++ b/lib/stdlib/test/dets_SUITE_data/version_r2d.dets
Binary files differ
diff --git a/lib/stdlib/test/dets_SUITE_data/version_r3b02.dets b/lib/stdlib/test/dets_SUITE_data/version_r3b02.dets
new file mode 100644
index 0000000000..058cd15b31
--- /dev/null
+++ b/lib/stdlib/test/dets_SUITE_data/version_r3b02.dets
Binary files differ
diff --git a/lib/stdlib/test/dict_SUITE.erl b/lib/stdlib/test/dict_SUITE.erl
new file mode 100644
index 0000000000..6a90870bda
--- /dev/null
+++ b/lib/stdlib/test/dict_SUITE.erl
@@ -0,0 +1,133 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% This module tests the ordsets, sets, and gb_sets modules.
+%%
+
+-module(dict_SUITE).
+
+-export([all/1,init_per_testcase/2,fin_per_testcase/2,
+ create/1,store/1]).
+
+-include("test_server.hrl").
+
+-import(lists, [foldl/3,reverse/1]).
+
+all(suite) ->
+ [create,store].
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?t:minutes(5)),
+ [{watchdog,Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+create(Config) when is_list(Config) ->
+ test_all(fun create_1/1).
+
+create_1(M) ->
+ ?line D0 = M:empty(),
+ ?line [] = M:to_list(D0),
+ ?line 0 = M:size(D0),
+ D0.
+
+store(Config) when is_list(Config) ->
+ test_all([{0,132},{253,258},{510,514}], fun store_1/2).
+
+store_1(List, M) ->
+ ?line D0 = M:from_list(List),
+
+ %% Make sure that we get the same result by inserting
+ %% elements one at the time.
+ ?line D1 = foldl(fun({K,V}, Dict) -> M:enter(K, V, Dict) end,
+ M:empty(), List),
+ ?line true = M:equal(D0, D1),
+ D0.
+
+%%%
+%%% Helper functions.
+%%%
+
+dict_mods() ->
+ Orddict = dict_test_lib:new(orddict, fun(X, Y) -> X == Y end),
+ Dict = dict_test_lib:new(dict, fun(X, Y) ->
+ lists:sort(dict:to_list(X)) ==
+ lists:sort(dict:to_list(Y)) end),
+ Gb = dict_test_lib:new(gb_trees, fun(X, Y) ->
+ gb_trees:to_list(X) ==
+ gb_trees:to_list(Y) end),
+ [Orddict,Dict,Gb].
+
+test_all(Tester) ->
+ ?line Pids = [spawn_tester(M, Tester) || M <- dict_mods()],
+ collect_all(Pids, []).
+
+spawn_tester(M, Tester) ->
+ Parent = self(),
+ spawn_link(fun() ->
+ random:seed(1, 2, 42),
+ S = Tester(M),
+ Res = {M:size(S),lists:sort(M:to_list(S))},
+ Parent ! {result,self(),Res}
+ end).
+
+collect_all([Pid|Pids], Acc) ->
+ receive
+ {result,Pid,Result} ->
+ collect_all(Pids, [Result|Acc])
+ end;
+collect_all([], Acc) ->
+ all_same(Acc).
+
+test_all(ListTemplate, Tester) ->
+ List = random_list(ListTemplate),
+ test_all(fun(M) -> Tester(List, M) end).
+
+all_same([H|T]) ->
+ all_same_1(T, H).
+
+all_same_1([H|T], H) ->
+ all_same_1(T, H);
+all_same_1([], _) -> ok.
+
+rnd_list(Sz) ->
+ rnd_list_1(Sz, []).
+
+random_list([{Low,High}|T]) ->
+ random_list(lists:seq(Low, High)++T);
+random_list([Sz|T]) when is_integer(Sz) ->
+ rnd_list(Sz)++random_list(T);
+random_list([]) -> [].
+
+rnd_list_1(0, Acc) ->
+ Acc;
+rnd_list_1(N, Acc) ->
+ Key = atomic_rnd_term(),
+ Value = random:uniform(100),
+ rnd_list_1(N-1, [{Key,Value}|Acc]).
+
+atomic_rnd_term() ->
+ case random:uniform(3) of
+ 1 -> list_to_atom(integer_to_list($\s+random:uniform(94))++"rnd");
+ 2 -> random:uniform();
+ 3 -> random:uniform(50)-37
+ end.
diff --git a/lib/stdlib/test/dict_test_lib.erl b/lib/stdlib/test/dict_test_lib.erl
new file mode 100644
index 0000000000..fd15baa5ff
--- /dev/null
+++ b/lib/stdlib/test/dict_test_lib.erl
@@ -0,0 +1,83 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(dict_test_lib, [Mod,Equal]).
+
+-export([module/0,equal/2,empty/0,size/1,to_list/1,from_list/1,
+ enter/3,delete/2,lookup/2]).
+
+module() ->
+ Mod.
+
+equal(X, Y) ->
+ Equal(X, Y).
+
+empty() ->
+ case erlang:function_exported(Mod, new, 0) of
+ false -> Mod:empty();
+ true -> Mod:new()
+ end.
+
+size(S) ->
+ Mod:size(S).
+
+to_list(S) ->
+ Mod:to_list(S).
+
+from_list(S) ->
+ case erlang:function_exported(Mod, from_orddict, 1) of
+ false ->
+ Mod:from_list(S);
+ true ->
+ %% The gb_trees module has no from_list/1 function.
+ %%
+ %% The keys in S are not unique. To make sure
+ %% that we pick the same key/value pairs as
+ %% dict/orddict, first convert the list to an orddict.
+ Orddict = orddict:from_list(S),
+ Mod:from_orddict(Orddict)
+ end.
+
+%% Store new value into dictionary or update previous value in dictionary.
+enter(Key, Val, Dict) ->
+ case erlang:function_exported(Mod, store, 3) of
+ false ->
+ Mod:enter(Key, Val, Dict);
+ true ->
+ Mod:store(Key, Val, Dict)
+ end.
+
+%% Delete an EXISTING key.
+delete(Key, Dict) ->
+ case erlang:function_exported(Mod, delete, 2) of
+ true -> Mod:delete(Key, Dict);
+ false -> Mod:erase(Key, Dict)
+ end.
+
+%% -> none | {value,Value}
+lookup(Key, Dict) ->
+ case erlang:function_exported(Mod, lookup, 2) of
+ false ->
+ case Mod:find(Key, Dict) of
+ error -> none;
+ {ok,Value} -> {value,Value}
+ end;
+ true ->
+ Mod:lookup(Key, Dict)
+ end.
diff --git a/lib/stdlib/test/digraph_SUITE.erl b/lib/stdlib/test/digraph_SUITE.erl
new file mode 100644
index 0000000000..6ef5b1ddef
--- /dev/null
+++ b/lib/stdlib/test/digraph_SUITE.erl
@@ -0,0 +1,520 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(digraph_SUITE).
+
+%-define(STANDALONE,1).
+
+-ifdef(STANDALONE).
+-define(line, put(line, ?LINE), ).
+-else.
+-include("test_server.hrl").
+-endif.
+
+-export([all/1]).
+
+-export([opts/1, degree/1, path/1, cycle/1, misc/1, vertices/1,
+ edges/1, data/1, tickets/1, otp_3522/1, otp_3630/1, otp_8066/1]).
+
+-export([spawn_graph/2]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(suite) -> {req, [stdlib], [opts, degree, path, cycle, misc, tickets]}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+opts(doc) -> [];
+opts(suite) -> [];
+opts(Config) when is_list(Config) ->
+ %% OTP-5985: the 'public' option has been removed
+ ?line {'EXIT',{badarg,_}} = (catch digraph:new([public])),
+ ?line {P2,G2} = spawn_graph([private]),
+ ?line {'EXIT',{badarg,_}} = (catch digraph:add_vertex(G2, x)),
+ ?line kill_graph(P2),
+ ?line {P3,G3} = spawn_graph([protected]),
+ ?line {'EXIT',{badarg,_}} = (catch digraph:add_vertex(G3, x)),
+ ?line kill_graph(P3),
+ ?line Template = [{v1,[v2]}, {v2,[v3]}, {v3,[v4]}, {v4,[]}],
+ ?line G4 = build_graph([], Template),
+ ?line e = digraph:add_edge(G4, e, v4, v1, []),
+ ?line digraph:delete(G4),
+ ?line G5 = build_graph([cyclic], Template),
+ ?line e = digraph:add_edge(G5, e, v4, v1, []),
+ ?line digraph:delete(G5),
+ ?line G6 = build_graph([acyclic], Template),
+ ?line acyclic = info(G6, cyclicity),
+ ?line {error, {bad_edge,_}} = digraph:add_edge(G6, v4, v1),
+ ?line digraph:delete(G6),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+degree(doc) -> [];
+degree(suite) -> [];
+degree(Config) when is_list(Config) ->
+ ?line G = build_graph([], [{x1,[]}, {x2,[x1]}, {x3,[x1,x2]},
+ {x4,[x1,x2,x3]}, {x5,[x1,x2,x3,x4]}]),
+ %% out degree
+ ?line 0 = digraph:out_degree(G, x1),
+ ?line 1 = digraph:out_degree(G, x2),
+ ?line 2 = digraph:out_degree(G, x3),
+ ?line 3 = digraph:out_degree(G, x4),
+ ?line 4 = digraph:out_degree(G, x5),
+ %% out neighbours
+ ?line [] = check(digraph:out_neighbours(G, x1), []),
+ ?line [] = check(digraph:out_neighbours(G, x2), [x1]),
+ ?line [] = check(digraph:out_neighbours(G, x3), [x1,x2]),
+ ?line [] = check(digraph:out_neighbours(G, x4), [x1,x2,x3]),
+ ?line [] = check(digraph:out_neighbours(G, x5), [x1,x2,x3,x4]),
+
+ %% in degree
+ ?line 4 = digraph:in_degree(G, x1),
+ ?line 3 = digraph:in_degree(G, x2),
+ ?line 2 = digraph:in_degree(G, x3),
+ ?line 1 = digraph:in_degree(G, x4),
+ ?line 0 = digraph:in_degree(G, x5),
+ %% in neighbours
+ ?line [] = check(digraph:in_neighbours(G, x1), [x2,x3,x4,x5]),
+ ?line [] = check(digraph:in_neighbours(G, x2), [x3,x4,x5]),
+ ?line [] = check(digraph:in_neighbours(G, x3), [x4,x5]),
+ ?line [] = check(digraph:in_neighbours(G, x4), [x5]),
+ ?line [] = check(digraph:in_neighbours(G, x5), []),
+ digraph:delete(G),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+path(doc) -> [];
+path(suite) -> [];
+path(Config) when is_list(Config) ->
+ ?line G = build_graph([], [{x1,[x2,x3]}, {x2,[x4]}, {x3,[x4]},
+ {x4,[x5,x6]}, {x5,[x7]}, {x6,[x7]}]),
+ ?line Vi = case digraph:get_path(G, x1, x7) of
+ [x1,x2,x4,x5,x7] -> digraph:del_vertex(G, x5), x6;
+ [x1,x2,x4,x6,x7] -> digraph:del_vertex(G, x6), x5;
+ [x1,x3,x4,x5,x7] -> digraph:del_vertex(G, x5), x6;
+ [x1,x3,x4,x6,x7] -> digraph:del_vertex(G, x6), x5
+ end,
+ ?line Vj = case digraph:get_path(G, x1, x7) of
+ [x1,x2,x4,Vi,x7] -> digraph:del_vertex(G,x2), x3;
+ [x1,x3,x4,Vi,x7] -> digraph:del_vertex(G,x3), x2
+ end,
+ ?line [x1,Vj,x4,Vi,x7] = digraph:get_path(G, x1, x7),
+ ?line digraph:del_vertex(G, Vj),
+ ?line false = digraph:get_path(G, x1, x7),
+ ?line [] = check(digraph:vertices(G), [x1,x4,Vi,x7]),
+ digraph:delete(G),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+cycle(doc) -> [];
+cycle(suite) -> [];
+cycle(Config) when is_list(Config) ->
+ ?line G = build_graph([], [{x1,[x2,x3]}, {x2,[x4]}, {x3,[x4]},
+ {x4,[x5,x6]}, {x5,[x7]}, {x6,[x7,x8]},
+ {x8,[x3,x8]}]),
+ ?line false = digraph:get_cycle(G, x1),
+ ?line false = digraph:get_cycle(G, x2),
+ ?line false = digraph:get_cycle(G, x5),
+ ?line false = digraph:get_cycle(G, x7),
+ ?line [x3,x4,x6,x8,x3] = digraph:get_cycle(G, x3),
+ ?line [x4,x6,x8,x3,x4] = digraph:get_cycle(G, x4),
+ ?line [x6,x8,x3,x4,x6] = digraph:get_cycle(G, x6),
+ ?line [x8,x3,x4,x6,x8] = digraph:get_cycle(G, x8),
+ ?line digraph:del_vertex(G, x4),
+ ?line [x8] = digraph:get_cycle(G, x8),
+ digraph:delete(G),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+misc(suite) -> [vertices, edges, data].
+
+vertices(doc) -> [];
+vertices(suite) -> [];
+vertices(Config) when is_list(Config) ->
+ ?line G = build_graph([], [{x,[]}, {y,[]}]),
+ ?line [] = check(digraph:vertices(G), [x,y]),
+ ?line digraph:del_vertices(G, [x,y]),
+ ?line [] = digraph:vertices(G),
+ ?line digraph:delete(G),
+ ok.
+
+edges(doc) -> [];
+edges(suite) -> [];
+edges(Config) when is_list(Config) ->
+ ?line G = build_graph([], [{x, [{exy,y},{exx,x}]},
+ {y, [{eyx,x}]}
+ ]),
+ ?line [] = check(digraph:edges(G), [exy, eyx, exx]),
+ ?line [] = check(digraph:out_edges(G, x), [exy,exx]),
+ ?line [] = check(digraph:in_edges(G, x), [eyx,exx]),
+ ?line [] = check(digraph:out_edges(G, y), [eyx]),
+ ?line [] = check(digraph:in_edges(G, y), [exy]),
+ ?line true = digraph:del_edges(G, [exy, eyx, does_not_exist]),
+ ?line [exx] = digraph:edges(G),
+ ?line [] = check(digraph:out_edges(G, x), [exx]),
+ ?line [] = check(digraph:in_edges(G, x), [exx]),
+ ?line [] = check(digraph:out_edges(G, y), []),
+ ?line [] = check(digraph:in_edges(G, y), []),
+ ?line digraph:del_vertices(G, [x,y]),
+ ?line [] = digraph:edges(G),
+ ?line [] = digraph:vertices(G),
+ ?line digraph:delete(G),
+ ok.
+
+data(doc) -> [];
+data(suite) -> [];
+data(Config) when is_list(Config) ->
+ ?line G = build_graph([], [{x, [{exy, y}]}, {y, []}]),
+
+ ?line {x,[]} = digraph:vertex(G, x),
+ ?line {y,[]} = digraph:vertex(G, y),
+ ?line {exy,x,y,[]} = digraph:edge(G, exy),
+
+ ?line digraph:add_edge(G, exy, x, y, {data,x,y}),
+ ?line E = digraph:add_edge(G, x, y, {data,y,x}),
+ ?line digraph:add_vertex(G, x, {any}),
+ ?line digraph:add_vertex(G, y, '_'),
+
+ ?line {x,{any}} = digraph:vertex(G, x),
+ ?line {y,'_'} = digraph:vertex(G, y),
+ ?line {exy,x,y,{data,x,y}} = digraph:edge(G, exy),
+ ?line {E,x,y,{data,y,x}} = digraph:edge(G, E),
+ ?line true = digraph:del_edge(G, E),
+ ?line false = digraph:edge(G, E),
+ ?line true = sane(G),
+ ?line digraph:delete(G),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+tickets(suite) -> [otp_3522, otp_3630, otp_8066].
+
+otp_3522(doc) -> [];
+otp_3522(suite) -> [];
+otp_3522(Config) when is_list(Config) ->
+ ?line G1 = build_graph([acyclic], [{x, []}]),
+ ?line {error, {bad_edge,_}} = digraph:add_edge(G1, x, x),
+ ?line true = digraph:delete(G1),
+
+ ?line G = digraph:new(),
+ ?line 0 = digraph:no_vertices(G),
+ ?line 0 = digraph:no_edges(G),
+ ?line V1 = digraph:add_vertex(G),
+ ?line '$vid' = digraph:add_vertex(G, '$vid'),
+ ?line V2 = digraph:add_vertex(G),
+ ?line '$eid' = digraph:add_edge(G, '$eid', V1, V2, []),
+ ?line E = digraph:add_edge(G, V1, V2),
+ ?line 3 = digraph:no_vertices(G),
+ ?line 2 = digraph:no_edges(G),
+ ?line cyclic = info(G, cyclicity),
+ ?line protected = info(G, protection),
+
+ ?line [] = check(digraph:in_edges(G, V2), ['$eid', E]),
+ ?line [] = check(digraph:out_edges(G, V1), ['$eid', E]),
+ ?line [] = check(digraph:vertices(G), [V1,V2,'$vid']),
+ ?line [] = check(digraph:edges(G), [E, '$eid']),
+ ?line true = sane(G),
+ ?line true = digraph:delete(G),
+ ok.
+
+otp_3630(doc) -> [];
+otp_3630(suite) -> [];
+otp_3630(Config) when is_list(Config) ->
+ ?line G = build_graph([], [{x, [{exy,y},{exx,x}]},
+ {y, [{eyy,y},{eyx,x}]}
+ ]),
+ ?line [x,y] = digraph:get_path(G, x, y),
+ ?line [y,x] = digraph:get_path(G, y, x),
+
+ ?line [x,x] = digraph:get_short_path(G, x, x),
+ ?line [y,y] = digraph:get_short_path(G, y, y),
+ ?line true = digraph:delete(G),
+
+ ?line G1 = build_graph([], [{1, [{12,2},{13,3},{11,1}]},
+ {2, [{23,3}]},
+ {3, [{34,4},{35,5}]},
+ {4, [{45,5}]},
+ {5, [{56,6},{57,7}]},
+ {6, [{67,7}]},
+ {7, [{71,1}]}
+ ]),
+
+ ?line [1,3,5,7] = digraph:get_short_path(G1, 1, 7),
+ ?line [3,5,7,1,3] = digraph:get_short_cycle(G1, 3),
+ ?line [1,1] = digraph:get_short_cycle(G1, 1),
+ ?line true = digraph:delete(G1),
+
+ F = 0.0, I = round(F),
+ ?line G2 = digraph:new([acyclic]),
+ ?line digraph:add_vertex(G2, F),
+ ?line digraph:add_vertex(G2, I),
+ ?line E = digraph:add_edge(G2, F, I),
+ ?line true = not is_tuple(E),
+ ?line true = sane(G2),
+ ?line true = digraph:delete(G2),
+
+ ok.
+
+otp_8066(doc) -> [];
+otp_8066(suite) -> [];
+otp_8066(Config) when is_list(Config) ->
+ fun() ->
+ D = digraph:new(),
+ V1 = digraph:add_vertex(D),
+ V2 = digraph:add_vertex(D),
+ _ = digraph:add_edge(D, V1, V2),
+ ?line [V1, V2] = digraph:get_path(D, V1, V2),
+ ?line true = sane(D),
+ ?line true = digraph:del_path(D, V1, V2),
+ ?line true = sane(D),
+ ?line false = digraph:get_path(D, V1, V2),
+ ?line true = digraph:del_path(D, V1, V2),
+ ?line true = digraph:delete(D)
+ end(),
+
+ fun() ->
+ D = digraph:new(),
+ V1 = digraph:add_vertex(D),
+ V2 = digraph:add_vertex(D),
+ _ = digraph:add_edge(D, V1, V2),
+ _ = digraph:add_edge(D, V1, V2),
+ _ = digraph:add_edge(D, V1, V1),
+ _ = digraph:add_edge(D, V2, V2),
+ ?line [V1, V2] = digraph:get_path(D, V1, V2),
+ ?line true = sane(D),
+ ?line true = digraph:del_path(D, V1, V2),
+ ?line false = digraph:get_short_path(D, V2, V1),
+
+ ?line true = sane(D),
+ ?line false = digraph:get_path(D, V1, V2),
+ ?line true = digraph:del_path(D, V1, V2),
+ ?line true = digraph:delete(D)
+ end(),
+
+ fun() ->
+ G = digraph:new(),
+ W1 = digraph:add_vertex(G),
+ W2 = digraph:add_vertex(G),
+ W3 = digraph:add_vertex(G),
+ W4 = digraph:add_vertex(G),
+ _ = digraph:add_edge(G,['$e'|0], W1, W2, {}),
+ ?line {error,{bad_vertex, bv}} =
+ digraph:add_edge(G, edge, bv, W1, {}),
+ ?line {error,{bad_vertex, bv}} =
+ digraph:add_edge(G, edge, W1, bv, {}),
+ ?line false = digraph:get_short_cycle(G, W1),
+ ?line {error, {bad_edge,_}} =
+ digraph:add_edge(G,['$e'|0], W3, W4, {}),
+ ?line true = sane(G),
+ ?line true = digraph:delete(G)
+ end(),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sane(G) ->
+ sane1(G),
+ erase(sane) =:= undefined.
+
+sane1(G) ->
+ %% etab: {E, V1, V2, Label}
+ %% ntab: {{out,V},E} eller {{in,V},E}
+ %% vtab: {V,Label}
+
+ Es = digraph:edges(G),
+ Vs = digraph:vertices(G),
+ VEs = lists:flatmap(fun(V) -> digraph:edges(G, V) end, Vs),
+ case lists:sort(Es++Es) =:= lists:sort(VEs) of
+ true -> ok;
+ false ->
+ io:format("Bad edges~n", []), put(sane, no)
+ end,
+
+ lists:foreach(
+ fun(E) ->
+ Edge = {E, V1, V2, _L} = digraph:edge(G, E),
+ case {digraph:vertex(G, V1), digraph:vertex(G, V2)} of
+ {{V1, _}, {V2, _}} -> ok;
+ _ -> io:format("Missing vertex ~p~n", [Edge]), put(sane, no)
+ end,
+ In = digraph:in_edges(G, V2),
+ case lists:member(E, In) of
+ true -> ok;
+ false ->
+ io:format("Missing in-neighbour ~p~n", [Edge]),
+ put(sane, no)
+ end,
+ Out = digraph:out_edges(G, V1),
+ case lists:member(E, Out) of
+ true -> ok;
+ false ->
+ io:format("Missing out-neighbour ~p~n", [Edge]),
+ put(sane, no)
+ end
+ end, Es),
+
+ lists:foreach(
+ fun(V) ->
+ InEs = digraph:in_edges(G, V),
+ %% Nu har man *alla* inkanter f�r V
+ lists:foreach(
+ fun(E) ->
+ case digraph:edge(G, E) of
+ {E, _, V, _} -> ok;
+ _ ->
+ io:format("Bad in-edge ~p: ~p~n", [V, E]),
+ put(sane, no)
+ end
+ end, InEs),
+ OutEs = digraph:out_edges(G, V),
+ lists:foreach(
+ fun(E) ->
+ case digraph:edge(G, E) of
+ {E, V, _, _} -> ok;
+ _ ->
+ io:format("Bad out-edge ~p: ~p~n", [V, E]),
+ put(sane, no)
+ end
+ end, OutEs)
+ end, Vs),
+
+ InEs = lists:flatmap(fun(V) -> digraph:in_edges(G, V) end, Vs),
+ OutEs = lists:flatmap(fun(V) -> digraph:out_edges(G, V) end, Vs),
+ lists:foreach(
+ fun(E) ->
+ case digraph:edge(G, E) of
+ {E, _, _, _} -> ok;
+ _ ->
+ io:format("Unknown edge (neighbour) ~p~n", [E]),
+ put(sane, no)
+ end
+ end, InEs++OutEs),
+
+ N_in = length(InEs),
+ N_out = length(OutEs),
+ N_edges = digraph:no_edges(G),
+ if
+ N_in =/= N_out ->
+ io:format("Number of in- and out-edges differs~n", []),
+ put(sane, no);
+ N_in+N_out =/= N_edges+N_edges ->
+ io:format("Invalid number of edges (~p+~p =/= 2*~p)~n",
+ [N_in, N_out, N_edges]),
+ put(sane, no);
+ true -> ok
+ end,
+ Edges = [digraph:edge(G, E) || E <- Es],
+ EVs = lists:usort([V || {_, V, _, _} <- Edges] ++
+ [V || {_, _, V, _} <- Edges]),
+ lists:foreach(
+ fun(V) ->
+ case digraph:vertex(G, V) of
+ {_, _} -> ok;
+ false ->
+ io:format("Unknown vertex in edge: ~p~n", [V]),
+ put(sane, no)
+ end
+ end, EVs),
+
+ %% sink_vertices and source_vertices were introduced in 2001. They
+ %% are not documented.
+
+ %% sink: a vertex with no outgoing edges
+ SinkVs = [V || V <- Vs, digraph:out_edges(G, V) =:= [] ],
+ case lists:sort(SinkVs) =:= lists:sort(digraph:sink_vertices(G)) of
+ true -> ok;
+ false ->
+ io:format("Bad sinks~n"), put(sane, no)
+ end,
+ %% sink: a vertex with no incoming edges
+ SourceVs = [V || V <- Vs, digraph:in_edges(G, V) =:= [] ],
+ case lists:sort(SourceVs) =:= lists:sort(digraph:source_vertices(G)) of
+ true -> ok;
+ false ->
+ io:format("Bad sources~n"), put(sane, no)
+ end,
+
+ true.
+
+build_graph(Opts, Gs) ->
+ G = digraph:new(Opts),
+ build_g(G, Gs).
+
+build_g(G, [{V,Ns} | Gs]) ->
+ digraph:add_vertex(G, V),
+ build_ns(G, V, Ns),
+ build_g(G, Gs);
+build_g(G, []) ->
+ true = sane(G),
+ G.
+
+build_ns(G, V, [{E,W} | Ns]) ->
+ digraph:add_vertex(G, W),
+ digraph:add_edge(G, E, V, W, []),
+ build_ns(G, V, Ns);
+build_ns(G, V, [W | Ns]) ->
+ digraph:add_vertex(G, W),
+ digraph:add_edge(G, V, W),
+ build_ns(G, V, Ns);
+build_ns(_G, _V, []) ->
+ true.
+
+%% Spawn a process that create a graph return {Pid, Graph}
+
+spawn_graph(Opts) ->
+ Pid = spawn(?MODULE, spawn_graph, [self(),Opts]),
+ receive
+ {Pid, G} -> {Pid,G}
+ end.
+
+%% Create a graph and wait for die message
+spawn_graph(Starter, Opts) ->
+ G = digraph:new(Opts),
+ Starter ! {self(), G},
+ receive
+ die -> true
+ end.
+
+info(G, What) ->
+ case lists:keysearch(What, 1, digraph:info(G)) of
+ {value, {What, Value}} -> Value;
+ false -> []
+ end.
+
+%% Kill process created by spawn_graph
+kill_graph(Pid) ->
+ Pid ! die.
+
+check(R0, E0) ->
+ R = lists:sort(R0),
+ E = lists:sort(E0),
+ case R of
+ E ->
+ [];
+ _ ->
+ (R -- E) ++ (E -- R)
+ end.
diff --git a/lib/stdlib/test/digraph_utils_SUITE.erl b/lib/stdlib/test/digraph_utils_SUITE.erl
new file mode 100644
index 0000000000..d6d477b388
--- /dev/null
+++ b/lib/stdlib/test/digraph_utils_SUITE.erl
@@ -0,0 +1,316 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(digraph_utils_SUITE).
+
+%-define(debug, true).
+-ifdef(debug).
+-define(line, put(line, ?LINE), ).
+-else.
+-include("test_server.hrl").
+-endif.
+
+-export([all/1]).
+
+-export([simple/1, loop/1, isolated/1, topsort/1, subgraph/1,
+ condensation/1, tree/1]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(suite) -> {req, [stdlib], [simple, loop, isolated, topsort,
+ subgraph, condensation, tree]}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+simple(doc) -> [];
+simple(suite) -> [];
+simple(Config) when is_list(Config) ->
+ ?line G = digraph:new(),
+ ?line add_vertices(G, [a]),
+ ?line add_edges(G, [{b,c},{b,d},{e,f},{f,g},{g,e},{h,h},{i,i},{i,j}]),
+ ?line 10 = length(digraph_utils:postorder(G)),
+ ?line 10 = length(digraph_utils:preorder(G)),
+ ?line ok = evall(digraph_utils:components(G),
+ [[a],[b,c,d],[e,f,g],[h],[i,j]]),
+ ?line ok = evall(digraph_utils:strong_components(G),
+ [[a],[b],[c],[d],[e,f,g],[h],[i],[j]]),
+ ?line ok = evall(digraph_utils:cyclic_strong_components(G),
+ [[e,f,g],[h],[i]]),
+ ?line true = path(G, e, e),
+ ?line false = path(G, e, j),
+ ?line false = path(G, a, a),
+ ?line false = digraph_utils:topsort(G),
+ ?line false = digraph_utils:is_acyclic(G),
+ ?line ok = eval(digraph_utils:loop_vertices(G), [h,i]),
+ ?line ok = eval(digraph_utils:reaching([e], G), [e,f,g]),
+ ?line ok = eval(digraph_utils:reaching_neighbours([e], G), [e,f,g]),
+ ?line ok = eval(digraph_utils:reachable([e], G), [e,f,g]),
+ ?line ok = eval(digraph_utils:reachable_neighbours([e], G), [e,f,g]),
+ ?line ok = eval(digraph_utils:reaching([b], G), [b]),
+ ?line ok = eval(digraph_utils:reaching_neighbours([b], G), []),
+ ?line ok = eval(digraph_utils:reachable([b], G), [b,c,d]),
+ ?line ok = eval(digraph_utils:reachable_neighbours([b], G), [c,d]),
+ ?line ok = eval(digraph_utils:reaching([h], G), [h]),
+ ?line ok = eval(digraph_utils:reaching_neighbours([h], G), [h]),
+ ?line ok = eval(digraph_utils:reachable([h], G), [h]),
+ ?line ok = eval(digraph_utils:reachable_neighbours([h], G), [h]),
+ ?line ok = eval(digraph_utils:reachable([e,f], G), [e,f,g]),
+ ?line ok = eval(digraph_utils:reachable_neighbours([e,f], G), [e,f,g]),
+ ?line ok = eval(digraph_utils:reachable([h,h,h], G), [h]),
+ ?line true = digraph:delete(G),
+ ok.
+
+loop(doc) -> [];
+loop(suite) -> [];
+loop(Config) when is_list(Config) ->
+ ?line G = digraph:new(),
+ ?line add_vertices(G, [a,b]),
+ ?line add_edges(G, [{a,a},{b,b}]),
+ ?line ok = evall(digraph_utils:components(G), [[a],[b]]),
+ ?line ok = evall(digraph_utils:strong_components(G), [[a],[b]]),
+ ?line ok = evall(digraph_utils:cyclic_strong_components(G), [[a],[b]]),
+ ?line [_,_] = digraph_utils:topsort(G),
+ ?line false = digraph_utils:is_acyclic(G),
+ ?line ok = eval(digraph_utils:loop_vertices(G), [a,b]),
+ ?line [_,_] = digraph_utils:preorder(G),
+ ?line [_,_] = digraph_utils:postorder(G),
+ ?line ok = eval(digraph_utils:reaching([b], G), [b]),
+ ?line ok = eval(digraph_utils:reaching_neighbours([b], G), [b]),
+ ?line ok = eval(digraph_utils:reachable([b], G), [b]),
+ ?line ok = eval(digraph_utils:reachable_neighbours([b], G), [b]),
+ ?line true = path(G, a, a),
+ ?line true = digraph:delete(G),
+ ok.
+
+isolated(doc) -> [];
+isolated(suite) -> [];
+isolated(Config) when is_list(Config) ->
+ ?line G = digraph:new(),
+ ?line add_vertices(G, [a,b]),
+ ?line ok = evall(digraph_utils:components(G), [[a],[b]]),
+ ?line ok = evall(digraph_utils:strong_components(G), [[a],[b]]),
+ ?line ok = evall(digraph_utils:cyclic_strong_components(G), []),
+ ?line [_,_] = digraph_utils:topsort(G),
+ ?line true = digraph_utils:is_acyclic(G),
+ ?line ok = eval(digraph_utils:loop_vertices(G), []),
+ ?line [_,_] = digraph_utils:preorder(G),
+ ?line [_,_] = digraph_utils:postorder(G),
+ ?line ok = eval(digraph_utils:reaching([b], G), [b]),
+ ?line ok = eval(digraph_utils:reaching_neighbours([b], G), []),
+ ?line ok = eval(digraph_utils:reachable([b], G), [b]),
+ ?line ok = eval(digraph_utils:reachable_neighbours([b], G), []),
+ ?line false = path(G, a, a),
+ ?line true = digraph:delete(G),
+ ok.
+
+topsort(doc) -> [];
+topsort(suite) -> [];
+topsort(Config) when is_list(Config) ->
+ ?line G = digraph:new(),
+ ?line add_edges(G, [{a,b},{b,c},{c,d},{d,e},{e,f}]),
+ ?line ok = eval(digraph_utils:topsort(G), [a,b,c,d,e,f]),
+ ?line true = digraph:delete(G),
+ ok.
+
+subgraph(doc) -> [];
+subgraph(suite) -> [];
+subgraph(Config) when is_list(Config) ->
+ ?line G = digraph:new([acyclic]),
+ ?line add_edges(G, [{b,c},{b,d},{e,f},{f,fg,fgl,g},{f,fg2,fgl2,g},{g,e},
+ {h,h},{i,i},{i,j}]),
+ ?line add_vertices(G, [{b,bl},{f,fl}]),
+ ?line SG = digraph_utils:subgraph(G, [u1,b,c,u2,f,g,i,u3]),
+ ?line [b,c,f,g,i] = lists:sort(digraph:vertices(SG)),
+ ?line {b,bl} = digraph:vertex(SG, b),
+ ?line {c,[]} = digraph:vertex(SG, c),
+ ?line {fg,f,g,fgl} = digraph:edge(SG, fg),
+ ?line {fg2,f,g,fgl2} = digraph:edge(SG, fg2),
+ ?line {_, {_, acyclic}} = lists:keysearch(cyclicity, 1, digraph:info(SG)),
+ ?line true = digraph:delete(SG),
+
+ ?line SG1 = digraph_utils:subgraph(G, [f, g, h],
+ [{type, []}, {keep_labels, false}]),
+ ?line [f,g,h] = lists:sort(digraph:vertices(SG1)),
+ ?line {f,[]} = digraph:vertex(SG1, f),
+ ?line {fg,f,g,[]} = digraph:edge(SG1, fg),
+ ?line {_, {_, cyclic}} = lists:keysearch(cyclicity, 1, digraph:info(SG1)),
+ ?line true = digraph:delete(SG1),
+
+ ?line SG2 = digraph_utils:subgraph(G, [f, g, h],
+ [{type, [acyclic]},
+ {keep_labels, true}]),
+ ?line [f,g,h] = lists:sort(digraph:vertices(SG2)),
+ ?line {f,fl} = digraph:vertex(SG2, f),
+ ?line {fg,f,g,fgl} = digraph:edge(SG2, fg),
+ ?line {_, {_, acyclic}} = lists:keysearch(cyclicity, 1, digraph:info(SG2)),
+ ?line true = digraph:delete(SG2),
+
+ ?line {'EXIT',{badarg,_}} =
+ (catch digraph_utils:subgraph(G, [f], [{invalid, opt}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch digraph_utils:subgraph(G, [f], [{keep_labels, not_Bool}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch digraph_utils:subgraph(G, [f], [{type, not_type}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch digraph_utils:subgraph(G, [f], [{type, [not_type]}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch digraph_utils:subgraph(G, [f], not_a_list)),
+
+ ?line true = digraph:delete(G),
+
+ ok.
+
+condensation(doc) -> [];
+condensation(suite) -> [];
+condensation(Config) when is_list(Config) ->
+ ?line G = digraph:new([]),
+ ?line add_edges(G, [{b,c},{b,d},{e,f},{f,fg,fgl,g},{f,fg2,fgl2,g},{g,e},
+ {h,h},{j,i},{i,j}]),
+ ?line add_vertices(G, [q]),
+ ?line CG = digraph_utils:condensation(G),
+ ?line Vs = sort_2(digraph:vertices(CG)),
+ ?line [[b],[c],[d],[e,f,g],[h],[i,j],[q]] = Vs,
+ ?line Fun = fun(E) ->
+ {_E, V1, V2, _L} = digraph:edge(CG, E),
+ {lists:sort(V1), lists:sort(V2)}
+ end,
+ ?line Es = lists:map(Fun, digraph:edges(CG)),
+ ?line [{[b],[c]},{[b],[d]},{[e,f,g],[e,f,g]},{[h],[h]},{[i,j],[i,j]}] =
+ lists:sort(Es),
+ ?line true = digraph:delete(CG),
+ ?line true = digraph:delete(G),
+ ok.
+
+tree(doc) -> ["OTP-7081"];
+tree(suite) -> [];
+tree(Config) when is_list(Config) ->
+ ?line false = is_tree([], []),
+ ?line true = is_tree([a], []),
+ ?line false = is_tree([a,b], []),
+ ?line true = is_tree([{a,b}]),
+ ?line false = is_tree([{a,b},{b,a}]),
+ ?line true = is_tree([{a,b},{a,c},{b,d},{b,e}]),
+ ?line false = is_tree([{a,b},{a,c},{b,d},{b,e}, {d,e}]),
+ ?line false = is_tree([{a,b},{a,c},{b,d},{b,e}, {b,e}]),
+ ?line true = is_tree([{a,c},{c,b}]),
+ ?line true = is_tree([{b,a},{c,a}]),
+ %% Parallel edges. Acyclic and with one componets
+ %% (according to the digraph module).
+ ?line false = is_tree([{a,b},{a,b}]),
+
+ ?line no = arborescence_root([], []),
+ ?line {yes, a} = arborescence_root([a], []),
+ ?line no = arborescence_root([a,b], []),
+ ?line {yes, a} = arborescence_root([{a,b}]),
+ ?line no = arborescence_root([{a,b},{b,a}]),
+ ?line {yes, a} = arborescence_root([{a,b},{a,c},{b,d},{b,e}]),
+ ?line no = arborescence_root([{a,b},{a,c},{b,d},{b,e}, {d,e}]),
+ ?line no = arborescence_root([{a,b},{a,c},{b,d},{b,e}, {b,e}]),
+ ?line {yes, a} = arborescence_root([{a,c},{c,b}]),
+ ?line no = arborescence_root([{b,a},{c,a}]),
+
+ ?line false = is_arborescence([], []),
+ ?line true = is_arborescence([a], []),
+ ?line false = is_arborescence([a,b], []),
+ ?line true = is_arborescence([{a,b}]),
+ ?line false = is_arborescence([{a,b},{b,a}]),
+ ?line true = is_arborescence([{a,b},{a,c},{b,d},{b,e}]),
+ ?line false = is_arborescence([{a,b},{a,c},{b,d},{b,e}, {d,e}]),
+ ?line false = is_arborescence([{a,b},{a,c},{b,d},{b,e}, {b,e}]),
+ ?line true = is_arborescence([{a,c},{c,b}]),
+ ?line false = is_arborescence([{b,a},{c,a}]),
+
+ %% Parallel edges.
+ ?line false = is_arborescence([{a,b},{a,b}]),
+
+ ok.
+
+is_tree(Es) ->
+ is_tree([], Es).
+
+is_tree(Vs, Es) ->
+ gu(Vs, Es, is_tree).
+
+is_arborescence(Es) ->
+ is_arborescence([], Es).
+
+is_arborescence(Vs, Es) ->
+ gu(Vs, Es, is_arborescence).
+
+arborescence_root(Es) ->
+ arborescence_root([], Es).
+
+arborescence_root(Vs, Es) ->
+ gu(Vs, Es, arborescence_root).
+
+gu(Vs, Es, F) ->
+ G = digraph:new(),
+ add_vertices(G, Vs),
+ add_edges(G, Es),
+ Reply = digraph_utils:F(G),
+ true = digraph:delete(G),
+ Reply.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sort_2(L) ->
+ lists:sort(lists:map(fun(V) -> lists:sort(V) end, L)).
+
+path(G, V1, V2) ->
+ digraph:get_path(G, V1, V2) /= false.
+
+add_vertices(G, Vs) ->
+ lists:foreach(fun({V, Label}) -> digraph:add_vertex(G, V, Label);
+ (V) -> digraph:add_vertex(G, V)
+ end, Vs).
+
+add_edges(G, L) ->
+ Fun = fun({From, To}) ->
+ digraph:add_vertex(G, From),
+ digraph:add_vertex(G, To),
+ digraph:add_edge(G, From, To);
+ ({From, Edge, Label, To}) ->
+ digraph:add_vertex(G, From),
+ digraph:add_vertex(G, To),
+ digraph:add_edge(G, Edge, From, To, Label)
+ end,
+ lists:foreach(Fun, L).
+
+eval(L, E) ->
+ Expected = lists:sort(E),
+ Got = lists:sort(L),
+ if
+ Expected == Got ->
+ ok;
+ true ->
+ not_ok
+ end.
+
+evall(L, E) ->
+ F = fun(L1) -> lists:sort(L1) end,
+ Fun = fun(LL) -> F(lists:map(F, LL)) end,
+
+ Expected = Fun(E),
+ Got = Fun(L),
+ if
+ Expected == Got ->
+ ok;
+ true ->
+ not_ok
+ end.
diff --git a/lib/stdlib/test/dummy1_h.erl b/lib/stdlib/test/dummy1_h.erl
new file mode 100644
index 0000000000..4377d774a3
--- /dev/null
+++ b/lib/stdlib/test/dummy1_h.erl
@@ -0,0 +1,70 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dummy1_h).
+
+%% Test event handler for gen_event_SUITE.erl
+
+-export([init/1, handle_event/2, handle_call/2, handle_info/2,
+ terminate/2]).
+
+init(make_error) ->
+ {error, my_error};
+init({_, error}) -> % swap from non-existing handler.
+ non_existing;
+init({swap, {ok, OldState}}) ->
+ {ok, OldState};
+init([Parent]) ->
+ {ok, Parent}. %% We will send special responses for every handled event.
+
+handle_event(delete_event, _Parent) ->
+ remove_handler;
+handle_event(do_crash, _State) ->
+ erlang:error({badmatch,4});
+%Inverse of dummy_h
+handle_event(hibernate, Parent) ->
+ {ok,Parent};
+handle_event(wakeup, Parent) ->
+ {ok,Parent,hibernate};
+handle_event(Event, Parent) ->
+ Parent ! {dummy1_h, Event},
+ {ok, Parent}.
+
+handle_call(delete_call, _State) ->
+ {remove_handler, ok};
+handle_call(_Query, State) ->
+ {ok, ok, State}.
+
+handle_info(delete_info, _Parent) ->
+ remove_handler;
+handle_info(do_crash, _State) ->
+ erlang:error({badmatch,4});
+handle_info(gnurf, Parent) ->
+ {ok, Parent, hibernate};
+handle_info(Info, Parent) ->
+ Parent ! {dummy1_h, Info},
+ {ok, Parent}.
+
+terminate(return_hej, _State) ->
+ return_hej;
+terminate(remove_handler, Parent) ->
+ Parent ! {dummy1_h, removed};
+terminate(_Reason, _State) ->
+ ok.
+
+
diff --git a/lib/stdlib/test/dummy_h.erl b/lib/stdlib/test/dummy_h.erl
new file mode 100644
index 0000000000..01eb790a75
--- /dev/null
+++ b/lib/stdlib/test/dummy_h.erl
@@ -0,0 +1,88 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dummy_h).
+
+%% Test event handler for gen_event_SUITE.erl
+
+-export([init/1, handle_event/2, handle_call/2, handle_info/2,
+ terminate/2]).
+
+init(make_error) ->
+ {error, my_error};
+init([Parent]) ->
+ {ok, Parent}; %% We will send special responses for every handled event.
+init([Parent,hibernate]) ->
+ {ok, Parent, hibernate}. %% We will send special responses for every handled event.
+
+handle_event({swap_event,Mod,Args}, State) ->
+ {swap_handler, swap, State, Mod, Args};
+handle_event(error_event, _State) ->
+ {return, faulty};
+handle_event(do_crash, _State) ->
+ erlang:error({badmatch,4});
+handle_event(hibernate, _State) ->
+ {ok,[],hibernate};
+handle_event(wakeup, _State) ->
+ {ok,[]};
+handle_event(Event, Parent) ->
+ Parent ! {dummy_h, Event},
+ {ok, Parent}.
+
+handle_call(hejsan, State) ->
+ {ok, {ok, hejhopp}, State};
+handle_call({swap_call,Mod,Args}, State) ->
+ {swap_handler, {ok, swapped}, swap, State, Mod, Args};
+handle_call(error_call, _State) ->
+ {return, faulty};
+handle_call(exit_call, _State) ->
+ erlang:error({badmatch,4});
+handle_call(hibernate, _State) ->
+ {ok,true,[],hibernate};
+handle_call(hibernate_later, _State) ->
+ timer:send_after(1000,sleep),
+ {ok,later,[]};
+handle_call(_Query, State) ->
+ {ok, ok, State}.
+
+handle_info({swap_info,Mod,Args}, State) ->
+ {swap_handler, swap, State, Mod, Args};
+handle_info(error_info, _State) ->
+ {return, faulty};
+handle_info(do_crash, _State) ->
+ erlang:error({badmatch,4});
+handle_info(sleep, _State) ->
+ {ok, [], hibernate};
+handle_info(wake, _State) ->
+ {ok, []};
+handle_info(gnurf, _State) ->
+ {ok, []};
+handle_info(Info, Parent) ->
+ Parent ! {dummy_h, Info},
+ {ok, Parent}.
+
+terminate(return_hej, _State) ->
+ return_hej;
+terminate(swap, State) ->
+ {ok, State};
+terminate({error, {return, faulty}}, Parent) ->
+ Parent ! {dummy_h, returned_error};
+terminate(_Reason, _State) ->
+ ok.
+
+
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
new file mode 100644
index 0000000000..67e20fd2e1
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -0,0 +1,1148 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(epp_SUITE).
+-export([all/1]).
+
+-export([rec_1/1, predef_mac/1,
+ upcase_mac/1, upcase_mac_1/1, upcase_mac_2/1,
+ variable/1, variable_1/1, otp_4870/1, otp_4871/1, otp_5362/1,
+ pmod/1, not_circular/1, skip_header/1, otp_6277/1, otp_7702/1,
+ otp_8130/1]).
+
+-export([epp_parse_erl_form/2]).
+
+%%
+%% Define to run outside of test server
+%%
+%-define(STANDALONE,1).
+
+-ifdef(STANDALONE).
+-compile(export_all).
+-define(line, put(line, ?LINE), ).
+-define(config(A,B),config(A,B)).
+%% -define(t, test_server).
+-define(t, io).
+config(priv_dir, _) ->
+ filename:absname("./epp_SUITE_priv");
+config(data_dir, _) ->
+ filename:absname("./epp_SUITE_data").
+-else.
+-include("test_server.hrl").
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+fin_per_testcase(_, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+-endif.
+
+all(doc) ->
+ ["Test cases for epp."];
+all(suite) ->
+ [rec_1, upcase_mac, predef_mac, variable, otp_4870, otp_4871, otp_5362,
+ pmod, not_circular, skip_header, otp_6277, otp_7702, otp_8130].
+
+rec_1(doc) ->
+ ["Recursive macros hang or crash epp (OTP-1398)."];
+rec_1(suite) ->
+ [];
+rec_1(Config) when is_list(Config) ->
+ ?line File = filename:join(?config(data_dir, Config), "mac.erl"),
+ ?line {ok, List} = epp_parse_file(File, [], []),
+ %% we should encounter errors
+ ?line {value, _} = lists:keysearch(error, 1, List),
+ ?line check_errors(List),
+ ok.
+
+%%% Here is a little reimplementation of epp:parse_file, which times out
+%%% after 4 seconds if the epp server doesn't respond. If we use the
+%%% regular epp:parse_file, the test case will time out, and then epp
+%%% server will go on growing until we dump core.
+epp_parse_file(File, Inc, Predef) ->
+ {ok, Epp} = epp:open(File, Inc, Predef),
+ List = collect_epp_forms(Epp),
+ epp:close(Epp),
+ {ok, List}.
+
+collect_epp_forms(Epp) ->
+ Result = epp_parse_erl_form(Epp),
+ case Result of
+ {error, _Error} ->
+ [Result | collect_epp_forms(Epp)];
+ {ok, Form} ->
+ [Form | collect_epp_forms(Epp)];
+ {eof, _} ->
+ [Result]
+ end.
+
+epp_parse_erl_form(Epp) ->
+ P = spawn(?MODULE, epp_parse_erl_form, [Epp, self()]),
+ receive
+ {P, Result} ->
+ Result
+ after 4000 ->
+ exit(Epp, kill),
+ exit(P, kill),
+ timeout
+ end.
+
+epp_parse_erl_form(Epp, Parent) ->
+ Parent ! {self(), epp:parse_erl_form(Epp)}.
+
+check_errors([]) ->
+ ok;
+check_errors([{error, Info} | Rest]) ->
+ ?line {Line, Mod, Desc} = Info,
+ ?line case Line of
+ I when is_integer(I) -> ok;
+ {L,C} when is_integer(L), is_integer(C), C >= 1 -> ok
+ end,
+ ?line Str = lists:flatten(Mod:format_error(Desc)),
+ ?line [Str] = io_lib:format("~s", [Str]),
+ check_errors(Rest);
+check_errors([_ | Rest]) ->
+ check_errors(Rest).
+
+upcase_mac(doc) ->
+ ["Check that uppercase macro names are implicitly quoted (OTP-2608)"];
+upcase_mac(suite) ->
+ [upcase_mac_1, upcase_mac_2].
+
+upcase_mac_1(doc) ->
+ [];
+upcase_mac_1(suite) ->
+ [];
+upcase_mac_1(Config) when is_list(Config) ->
+ ?line File = filename:join(?config(data_dir, Config), "mac2.erl"),
+ ?line {ok, List} = epp:parse_file(File, [], []),
+ ?line [_, {attribute, _, plupp, Tuple} | _] = List,
+ ?line Tuple = {1, 1, 3, 3},
+ ok.
+
+upcase_mac_2(doc) ->
+ [];
+upcase_mac_2(suite) ->
+ [];
+upcase_mac_2(Config) when is_list(Config) ->
+ ?line File = filename:join(?config(data_dir, Config), "mac2.erl"),
+ ?line {ok, List} = epp:parse_file(File, [], [{p, 5}, {'P', 6}]),
+ ?line [_, {attribute, _, plupp, Tuple} | _] = List,
+ ?line Tuple = {5, 5, 6, 6},
+ ok.
+
+predef_mac(doc) ->
+ [];
+predef_mac(suite) ->
+ [];
+predef_mac(Config) when is_list(Config) ->
+ ?line File = filename:join(?config(data_dir, Config), "mac3.erl"),
+ ?line {ok, List} = epp:parse_file(File, [], []),
+ ?line [_,
+ {attribute, LineCol1, l, Line1},
+ {attribute, _, f, File},
+ {attribute, _, machine1, _},
+ {attribute, _, module, mac3},
+ {attribute, _, m, mac3},
+ {attribute, _, ms, "mac3"},
+ {attribute, _, machine2, _}
+ | _] = List,
+ ?line case LineCol1 of
+ Line1 -> ok;
+ {Line1,_} -> ok
+ end,
+ ok.
+
+variable(doc) ->
+ ["Check variable as first file component of the include directives."];
+variable(suite) ->
+ [variable_1].
+
+variable_1(doc) ->
+ [];
+variable_1(suite) ->
+ [];
+variable_1(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line File = filename:join(DataDir, "variable_1.erl"),
+ ?line true = os:putenv("VAR", DataDir),
+ %% variable_1.erl includes variable_1_include.hrl and
+ %% variable_1_include_dir.hrl.
+ ?line {ok, List} = epp:parse_file(File, [], []),
+ ?line {value, {attribute,_,a,{value1,value2}}} =
+ lists:keysearch(a,3,List),
+ ok.
+
+otp_4870(doc) ->
+ ["undef without module declaration"];
+otp_4870(suite) ->
+ [];
+otp_4870(Config) when is_list(Config) ->
+ Ts = [{otp_4870,
+ <<"-undef(foo).
+ ">>,
+ []}],
+ ?line [] = check(Config, Ts),
+ ok.
+
+otp_4871(doc) ->
+ ["crashing erl_scan"];
+otp_4871(suite) ->
+ [];
+otp_4871(Config) when is_list(Config) ->
+ ?line Dir = ?config(priv_dir, Config),
+ ?line File = filename:join(Dir, "otp_4871.erl"),
+ ?line ok = file:write_file(File, "-module(otp_4871)."),
+ %% Testing crash in erl_scan. Unfortunately there currently is
+ %% no known way to crash erl_scan so it is emulated by killing the
+ %% file io server. This assumes lots of things about how
+ %% the processes are started and how monitors are set up,
+ %% so there are some sanity checks before killing.
+ ?line {ok,Epp} = epp:open(File, []),
+ timer:sleep(1),
+ ?line {current_function,{epp,_,_}} = process_info(Epp, current_function),
+ ?line {monitored_by,[Io]} = process_info(Epp, monitored_by),
+ ?line {current_function,{file_io_server,_,_}} =
+ process_info(Io, current_function),
+ ?line exit(Io, emulate_crash),
+ timer:sleep(1),
+ ?line {error,{_Line,epp,cannot_parse}} = otp_4871_parse_file(Epp),
+ ?line epp:close(Epp),
+ ok.
+
+otp_4871_parse_file(Epp) ->
+ case epp:parse_erl_form(Epp) of
+ {ok,_} -> otp_4871_parse_file(Epp);
+ Other -> Other
+ end.
+
+otp_5362(doc) ->
+ ["OTP-5362. The -file attribute is recognized."];
+otp_5362(suite) ->
+ [];
+otp_5362(Config) when is_list(Config) ->
+ Dir = ?config(priv_dir, Config),
+
+ Copts = [return, strong_validation,{i,Dir}],
+
+ File_Incl = filename:join(Dir, "incl_5362.erl"),
+ File_Incl2 = filename:join(Dir, "incl2_5362.erl"),
+ File_Incl3 = filename:join(Dir, "incl3_5362.erl"),
+ Incl = <<"-module(incl_5362).
+
+ -include(\"incl2_5362.erl\").
+
+ -include_lib(\"incl3_5362.erl\").
+
+ hi(There) -> % line 7
+ a.
+ ">>,
+ Incl2 = <<"-file(\"some.file\", 100).
+
+ foo(Bar) -> % line 102
+ foo.
+ ">>,
+ Incl3 = <<"glurk(Foo) -> % line 1
+ bar.
+ ">>,
+ ?line ok = file:write_file(File_Incl, Incl),
+ ?line ok = file:write_file(File_Incl2, Incl2),
+ ?line ok = file:write_file(File_Incl3, Incl3),
+
+ ?line {ok, incl_5362, InclWarnings} = compile:file(File_Incl, Copts),
+ ?line true = message_compare(
+ [{File_Incl3,[{{1,1},erl_lint,{unused_function,{glurk,1}}},
+ {{1,7},erl_lint,{unused_var,'Foo'}}]},
+ {File_Incl,[{{7,15},erl_lint,{unused_function,{hi,1}}},
+ {{7,18},erl_lint,{unused_var,'There'}}]},
+ {"some.file",[{{102,16},erl_lint,{unused_function,{foo,1}}},
+ {{102,20},erl_lint,{unused_var,'Bar'}}]}],
+ lists:usort(InclWarnings)),
+
+ file:delete(File_Incl),
+ file:delete(File_Incl2),
+ file:delete(File_Incl3),
+
+ %% A -file attribute referring back to the including file.
+ File_Back = filename:join(Dir, "back_5362.erl"),
+ File_Back_hrl = filename:join(Dir, "back_5362.hrl"),
+ Back = <<"-module(back_5362).
+
+ -compile(export_all).
+
+ -file(?FILE, 1).
+ -include(\"back_5362.hrl\").
+
+ foo(V) -> % line 4
+ bar.
+ ">>,
+ Back_hrl = [<<"
+ -file(\"">>,File_Back,<<"\", 2).
+ ">>],
+
+ ?line ok = file:write_file(File_Back, Back),
+ ?line ok = file:write_file(File_Back_hrl, list_to_binary(Back_hrl)),
+
+ ?line {ok, back_5362, BackWarnings} = compile:file(File_Back, Copts),
+ ?line true = message_compare(
+ [{File_Back,[{{4,19},erl_lint,{unused_var,'V'}}]}],
+ BackWarnings),
+ file:delete(File_Back),
+ file:delete(File_Back_hrl),
+
+ %% Set filename but keep line.
+ File_Change = filename:join(Dir, "change_5362.erl"),
+ Change = [<<"-module(change_5362).
+
+ -file(?FILE, 100).
+
+ -compile(export_all).
+
+ -file(\"other.file\", ?LINE). % like an included file...
+ foo(A) -> % line 105
+ bar.
+
+ -file(\"">>,File_Change,<<"\", 1000).
+
+ bar(B) -> % line 1002
+ foo.
+ ">>],
+
+ ?line ok = file:write_file(File_Change, list_to_binary(Change)),
+
+ ?line {ok, change_5362, ChangeWarnings} =
+ compile:file(File_Change, Copts),
+ ?line true = message_compare(
+ [{File_Change,[{{1002,21},erl_lint,{unused_var,'B'}}]},
+ {"other.file",[{{105,21},erl_lint,{unused_var,'A'}}]}],
+ lists:usort(ChangeWarnings)),
+
+ file:delete(File_Change),
+
+ %% -file attribute ending with a blank (not a newline).
+ File_Blank = filename:join(Dir, "blank_5362.erl"),
+
+ Blank = <<"-module(blank_5362).
+
+ -compile(export_all).
+
+ -
+ file(?FILE, 18). q(Q) -> foo. % line 18
+
+ a(A) -> % line 20
+ 1.
+
+ -file(?FILE, 42).
+
+ b(B) -> % line 44
+ 2.
+
+ -file(?FILE, ?LINE). c(C) -> % line 47
+ 3.
+ ">>,
+ ?line ok = file:write_file(File_Blank, Blank),
+ ?line {ok, blank_5362, BlankWarnings} = compile:file(File_Blank, Copts),
+ ?line true = message_compare(
+ [{File_Blank,[{{18,3},erl_lint,{unused_var,'Q'}},
+ {{20,18},erl_lint,{unused_var,'A'}},
+ {{44,18},erl_lint,{unused_var,'B'}},
+ {{47,3},erl_lint,{unused_var,'C'}}]}],
+ lists:usort(BlankWarnings)),
+ file:delete(File_Blank),
+
+ %% __FILE__ is set by inclusion and by -file attribute
+ FILE_incl = filename:join(Dir, "file_5362.erl"),
+ FILE_incl1 = filename:join(Dir, "file_incl_5362.erl"),
+ FILE = <<"-module(file_5362).
+
+ -export([ff/0, ii/0]).
+
+ -include(\"file_incl_5362.erl\").
+
+ -file(\"other_file\", 100).
+
+ ff() ->
+ ?FILE.">>,
+ FILE1 = <<"ii() -> ?FILE.
+ ">>,
+ FILE_Mod = file_5362,
+ ?line ok = file:write_file(FILE_incl, FILE),
+ ?line ok = file:write_file(FILE_incl1, FILE1),
+ FILE_Copts = [return, {i,Dir},{outdir,Dir}],
+ ?line {ok, file_5362, []} = compile:file(FILE_incl, FILE_Copts),
+ AbsFile = filename:rootname(FILE_incl, ".erl"),
+ ?line {module, FILE_Mod} = code:load_abs(AbsFile, FILE_Mod),
+ ?line II = FILE_Mod:ii(),
+ ?line "file_incl_5362.erl" = filename:basename(II),
+ ?line FF = FILE_Mod:ff(),
+ ?line "other_file" = filename:basename(FF),
+ code:purge(file_5362),
+
+ file:delete(FILE_incl),
+ file:delete(FILE_incl1),
+
+ ok.
+
+pmod(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line Pmod = filename:join(DataDir, "pmod.erl"),
+ ?line case epp:parse_file([Pmod], [], []) of
+ {ok,Forms} ->
+ %% ?line io:format("~p\n", [Forms]),
+ ?line [] = [F || {error,_}=F <- Forms],
+ ok
+ end,
+ ok.
+
+not_circular(Config) when is_list(Config) ->
+ %% Used to generate a compilation error, wrongly saying that it
+ %% was a circular definition.
+
+ Ts = [{circular_1,
+ <<"-define(S(S), ??S).\n"
+ "t() -> \"string\" = ?S(string), ok.\n">>,
+ ok}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+skip_header(doc) ->
+ ["Skip some bytes in the beginning of the file."];
+skip_header(suite) ->
+ [];
+skip_header(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line File = filename:join([PrivDir, "epp_test_skip_header.erl"]),
+ ?line ok = file:write_file(File,
+ <<"some bytes
+ in the beginning of the file
+ that should be skipped
+ -module(epp_test_skip_header).
+ -export([main/1]).
+
+ main(_) -> ?MODULE.
+
+ ">>),
+ ?line {ok, Fd} = file:open(File, [read]),
+ ?line io:get_line(Fd, ''),
+ ?line io:get_line(Fd, ''),
+ ?line io:get_line(Fd, ''),
+ ?line {ok, Epp} = epp:open(list_to_atom(File), Fd, 4, [], []),
+
+ ?line Forms = epp:parse_file(Epp),
+ ?line [] = [Reason || {error, Reason} <- Forms],
+ ?line ok = epp:close(Epp),
+ ?line ok = file:close(Fd),
+
+ ok.
+
+otp_6277(doc) ->
+ ["?MODULE before module declaration."];
+otp_6277(suite) ->
+ [];
+otp_6277(Config) when is_list(Config) ->
+ Ts = [{otp_6277,
+ <<"-undef(ASSERT).
+ -define(ASSERT, ?MODULE).
+
+ ?ASSERT().">>,
+ [{error,{{4,16},epp,{undefined,'MODULE'}}}]}],
+ ?line [] = check(Config, Ts),
+ ok.
+
+otp_7702(doc) ->
+ ["OTP-7702. Wrong line number in stringifying macro expansion."];
+otp_7702(suite) ->
+ [];
+otp_7702(Config) when is_list(Config) ->
+ Dir = ?config(priv_dir, Config),
+ File = filename:join(Dir, "file_7702.erl"),
+ Contents = <<"-module(file_7702).
+
+ -export([t/0]).
+
+ -define(RECEIVE(Msg,Body),
+ receive
+ Msg -> Body;
+ M ->
+ exit({unexpected_message,M,on_line,?LINE,was_expecting,??Msg})
+ after 10000 ->
+ exit({timeout,on_line,?LINE,was_expecting,??Msg})
+ end).
+ t() ->
+ ?RECEIVE(foo, bar).">>,
+ ?line ok = file:write_file(File, Contents),
+ ?line {ok, file_7702, []} =
+ compile:file(File, [debug_info,return,{outdir,Dir}]),
+
+ BeamFile = filename:join(Dir, "file_7702.beam"),
+ {ok, AC} = beam_lib:chunks(BeamFile, [abstract_code]),
+
+ {file_7702,[{abstract_code,{_,Forms}}]} = AC,
+ Fun = fun(Attrs) ->
+ {line, L} = erl_parse:get_attribute(Attrs, line),
+ L
+ end,
+ Forms2 = [erl_lint:modify_line(Form, Fun) || Form <- Forms],
+ ?line
+ [{attribute,1,file,_},
+ _,
+ _,
+ {function,_,t,0,
+ [{clause,_,[],[],
+ [{'receive',14,
+ [_,
+ {clause,14,
+ [{var,14,'M'}],
+ [],
+ [{_,_,_,
+ [{tuple,14,
+ [{atom,14,unexpected_message},
+ {var,14,'M'},
+ {atom,14,on_line},
+ {integer,14,14},
+ {atom,14,was_expecting},
+ {string,14,"foo"}]}]}]}],
+ {integer,14,10000},
+ [{call,14,
+ {atom,14,exit},
+ [{tuple,14,
+ [{atom,14,timeout},
+ {atom,14,on_line},
+ {integer,14,14},
+ {atom,14,was_expecting},
+ {string,14,"foo"}]}]}]}]}]},
+ {eof,14}] = Forms2,
+
+ file:delete(File),
+ file:delete(BeamFile),
+
+ ok.
+
+otp_8130(doc) ->
+ ["OTP-8130. Misc tests."];
+otp_8130(suite) ->
+ [];
+otp_8130(Config) when is_list(Config) ->
+ true = os:putenv("epp_inc1", "stdlib"),
+ Ts = [{otp_8130_1,
+ %% The scanner handles UNICODE in a special way. Hopefully
+ %% temporarily.
+ <<"-define(M(A), ??A). "
+ "t() -> "
+ " \"{ 34 , [ $1 , 2730 ] , \\\"34\\\" , X . a , 2730 }\" = "
+ " ?M({34,\"1\\x{aaa}\",\"34\",X.a,$\\x{aaa}}), ok. ">>,
+ ok},
+
+ {otp_8130_2,
+ <<"-define(M(A), ??B). "
+ "t() -> B = 18, 18 = ?M(34), ok. ">>,
+ ok},
+
+ {otp_8130_2a,
+ <<"-define(m(A), ??B). "
+ "t() -> B = 18, 18 = ?m(34), ok. ">>,
+ ok},
+
+ {otp_8130_3,
+ <<"-define(M1(A, B), {A,B}).\n"
+ "t0() -> 1.\n"
+ "t() ->\n"
+ " {2,7} =\n"
+ " ?M1(begin 1 = fun() -> 1 end(),\n" % Bug -R13B01
+ " 2 end,\n"
+ " 7),\n"
+ " {2,7} =\n"
+ " ?M1(begin 1 = fun t0/0(),\n"
+ " 2 end,\n"
+ " 7),\n"
+ " {2,7} =\n"
+ " ?M1(begin 2 = byte_size(<<\"34\">>),\n"
+ " 2 end,\n"
+ " 7),\n"
+ " R2 = math:sqrt(2.0),\n"
+ " {2,7} =\n"
+ " ?M1(begin yes = if R2 > 1 -> yes end,\n"
+ " 2 end,\n"
+ " 7),\n"
+ " {2,7} =\n"
+ " ?M1(begin yes = case R2 > 1 of true -> yes end,\n"
+ " 2 end,\n"
+ " 7),\n"
+ " {2,7} =\n"
+ " ?M1(begin yes = receive 1 -> 2 after 0 -> yes end,\n"
+ " 2 end,\n"
+ " 7),\n"
+ " {2,7} =\n"
+ " ?M1(begin yes = try 1 of 1 -> yes after foo end,\n"
+ " 2 end,\n"
+ " 7),\n"
+ "ok.\n">>,
+ ok},
+
+ {otp_8130_4,
+ <<"-define(M3(), A).\n"
+ "t() -> A = 1, ?M3(), ok.\n">>,
+ ok},
+
+ {otp_8130_5,
+ <<"-include_lib(\"$epp_inc1/include/qlc.hrl\").\n"
+ "t() -> [1] = qlc:e(qlc:q([X || X <- [1]])), ok.\n">>,
+ ok},
+
+ {otp_8130_6,
+ <<"-include_lib(\"kernel/include/file.hrl\").\n"
+ "t() -> 14 = (#file_info{size = 14})#file_info.size, ok.\n">>,
+ ok},
+
+ {otp_8130_7,
+ <<"-record(b, {b}).\n"
+ "-define(A, {{a,#b.b.\n"
+ "t() -> {{a,2}} = ?A}}, ok.">>,
+ ok},
+
+ {otp_8130_8,
+ <<"\n-define(A(B), B).\n"
+ "-undef(A).\n"
+ "-define(A, ok).\n"
+ "t() -> ?A.\n">>,
+ ok},
+ {otp_8130_9,
+ <<"-define(a, 1).\n"
+ "-define(b, {?a,?a}).\n"
+ "t() -> ?b.\n">>,
+ {1,1}}
+
+ ],
+ ?line [] = run(Config, Ts),
+
+ Cs = [{otp_8130_c1,
+ <<"-define(M1(A), if\n"
+ "A =:= 1 -> B;\n"
+ "true -> 2\n"
+ "end).\n"
+ "t() -> {?M1(1), ?M1(2)}. \n">>,
+ {errors,[{{5,13},erl_lint,{unbound_var,'B'}},
+ {{5,21},erl_lint,{unbound_var,'B'}}],
+ []}},
+
+ {otp_8130_c2,
+ <<"-define(M(A), A).\n"
+ "t() -> ?M(1\n">>,
+ {errors,[{{2,9},epp,{arg_error,'M'}}],[]}},
+
+ {otp_8130_c3,
+ <<"-define(M(A), A).\n"
+ "t() -> ?M.\n">>,
+ {errors,[{{2,9},epp,{mismatch,'M'}}],[]}},
+
+ {otp_8130_c4,
+ <<"-define(M(A), A).\n"
+ "t() -> ?M(1, 2).\n">>,
+ {errors,[{{2,9},epp,{mismatch,'M'}}],[]}},
+
+ {otp_8130_c5,
+ <<"-define(M(A), A).\n"
+ "t() -> ?M().\n">>,
+ {errors,[{{2,9},epp,{mismatch,'M'}}],[]}},
+
+ {otp_8130_c6,
+ <<"-define(M3(), A).\n"
+ "t() -> A = 1, ?3.14159}.\n">>,
+ {errors,[{{2,16},epp,{call,"?3.14159"}}],[]}},
+
+ {otp_8130_c7,
+ <<"\nt() -> ?A.\n">>,
+ {errors,[{{2,9},epp,{undefined,'A'}}],[]}},
+
+ {otp_8130_c8,
+ <<"\n-include_lib(\"$apa/foo.hrl\").\n">>,
+ {errors,[{{2,2},epp,{include,lib,"$apa/foo.hrl"}}],[]}},
+
+
+ {otp_8130_c9,
+ <<"-define(S, ?S).\n"
+ "t() -> ?S.\n">>,
+ {errors,[{{2,9},epp,{circular,'S'}}],[]}},
+
+ {otp_8130_c10,
+ <<"\n-file.">>,
+ {errors,[{{2,2},epp,{bad,file}}],[]}},
+
+ {otp_8130_c11,
+ <<"\n-include_lib 92.">>,
+ {errors,[{{2,2},epp,{bad,include_lib}}],[]}},
+
+ {otp_8130_c12,
+ <<"\n-include_lib(\"kernel/include/fopp.hrl\").\n">>,
+ {errors,[{{2,2},epp,{include,lib,"kernel/include/fopp.hrl"}}],[]}},
+
+ {otp_8130_c13,
+ <<"\n-include(foo).\n">>,
+ {errors,[{{2,2},epp,{bad,include}}],[]}},
+
+ {otp_8130_c14,
+ <<"\n-undef({foo}).\n">>,
+ {errors,[{{2,2},epp,{bad,undef}}],[]}},
+
+ {otp_8130_c15,
+ <<"\n-define(a, 1).\n"
+ "-define(a, 1).\n">>,
+ {errors,[{{3,9},epp,{redefine,a}}],[]}},
+
+ {otp_8130_c16,
+ <<"\n-define(A, 1).\n"
+ "-define(A, 1).\n">>,
+ {errors,[{{3,9},epp,{redefine,'A'}}],[]}},
+
+ {otp_8130_c17,
+ <<"\n-define(A(B), B).\n"
+ "-define(A, 1).\n">>,
+ {errors,[{{3,9},epp,{redefine,'A'}}],[]}},
+
+ {otp_8130_c18,
+ <<"\n-define(A, 1).\n"
+ "-define(A(B), B).\n">>,
+ {errors,[{{3,9},epp,{redefine,'A'}}],[]}},
+
+ {otp_8130_c19,
+ <<"\n-define(a(B), B).\n"
+ "-define(a, 1).\n">>,
+ {errors,[{{3,9},epp,{redefine,a}}],[]}},
+
+ {otp_8130_c20,
+ <<"\n-define(a, 1).\n"
+ "-define(a(B), B).\n">>,
+ {errors,[{{3,9},epp,{redefine,a}}],[]}},
+
+ {otp_8130_c21,
+ <<"\n-define(A(B, B), B).\n">>,
+ {errors,[{{2,2},epp,{bad,define}}],[]}},
+
+ {otp_8130_c22,
+ <<"\n-define(a(B, B), B).\n">>,
+ {errors,[{{2,2},epp,{bad,define}}],[]}},
+
+ {otp_8130_c23,
+ <<"\n-file(?b, 3).\n">>,
+ {errors,[{{2,8},epp,{undefined,b}}],[]}},
+
+ {otp_8130_c24,
+ <<"\n-include(\"no such file.erl\").\n">>,
+ {errors,[{{2,2},epp,{include,file,"no such file.erl"}}],[]}}
+
+ ],
+ ?line [] = compile(Config, Cs),
+
+ Cks = [{otp_check_1,
+ <<"\n-include_lib(\"epp_test.erl\").\n">>,
+ [{error,{{2,2},epp,{depth,"include_lib"}}}]},
+
+ {otp_check_2,
+ <<"\n-include(\"epp_test.erl\").\n">>,
+ [{error,{{2,2},epp,{depth,"include"}}}]}
+ ],
+ ?line [] = check(Config, Cks),
+
+ ?line Dir = ?config(priv_dir, Config),
+ ?line File = filename:join(Dir, "otp_8130.erl"),
+ ?line ok = file:write_file(File,
+ "-module(otp_8130).\n"
+ "-define(a, 3.14).\n"
+ "t() -> ?a.\n"),
+ ?line {ok,Epp} = epp:open(File, []),
+ ?line ['BASE_MODULE','BASE_MODULE_STRING','BEAM','FILE','LINE',
+ 'MACHINE','MODULE','MODULE_STRING'] = macs(Epp),
+ ?line {ok,[{'-',_},{atom,_,file}|_]} = epp:scan_erl_form(Epp),
+ ?line {ok,[{'-',_},{atom,_,module}|_]} = epp:scan_erl_form(Epp),
+ ?line {ok,[{atom,_,t}|_]} = epp:scan_erl_form(Epp),
+ ?line {eof,_} = epp:scan_erl_form(Epp),
+ ?line ['BASE_MODULE','BASE_MODULE_STRING','BEAM','FILE','LINE',
+ 'MACHINE','MODULE','MODULE_STRING',a] = macs(Epp),
+ ?line epp:close(Epp),
+
+ %% escript
+ ModuleStr = "any_name",
+ Module = list_to_atom(ModuleStr),
+ fun() ->
+ PreDefMacros = [{'MODULE', Module, redefine},
+ {'MODULE_STRING', ModuleStr, redefine},
+ a, {b,2}],
+ ?line {ok,Epp2} = epp:open(File, [], PreDefMacros),
+ ?line [{atom,_,true}] = macro(Epp2, a),
+ ?line [{integer,_,2}] = macro(Epp2, b),
+ ?line false = macro(Epp2, c),
+ ?line epp:close(Epp2)
+ end(),
+ fun() ->
+ PreDefMacros = [{a,b,c}],
+ ?line {error,{bad,{a,b,c}}} = epp:open(File, [], PreDefMacros)
+ end(),
+ fun() ->
+ PreDefMacros = [a, {a,1}],
+ ?line {error,{redefine,a}} = epp:open(File, [], PreDefMacros)
+ end(),
+ fun() ->
+ PreDefMacros = [{a,1},a],
+ ?line {error,{redefine,a}} = epp:open(File, [], PreDefMacros)
+ end(),
+
+ ?line {error,enoent} = epp:open("no such file", []),
+ ?line {error,enoent} = epp:parse_file("no such file", [], []),
+
+ _ = ifdef(Config),
+
+ ok.
+
+macs(Epp) ->
+ Macros = epp:macro_defs(Epp), % not documented
+ lists:sort([MName || {{atom,MName},_} <- Macros]).
+
+macro(Epp, N) ->
+ case lists:keyfind({atom,N}, 1, epp:macro_defs(Epp)) of
+ false -> false;
+ {{atom,N},{_,V}} -> V
+ end.
+
+ifdef(Config) ->
+ Cs = [{ifdef_c1,
+ <<"-ifdef(a).\n"
+ "a bug.\n"
+ "-else.\n"
+ "-ifdef(A).\n"
+ "a bug.\n"
+ "-endif.\n"
+ "-else.\n"
+ "t() -> ok.\n"
+ "-endif.">>,
+ {errors,[{{7,2},epp,{illegal,"repeated",'else'}}],[]}},
+
+ {ifdef_c2,
+ <<"-define(a, true).\n"
+ "-ifdef(a).\n"
+ "a bug.\n"
+ "-endif.">>,
+ {errors,[{{3,3},erl_parse,["syntax error before: ","bug"]}],[]}},
+
+ {ifdef_c3,
+ <<"-define(a, true).\n"
+ "-ifdef(a).\n"
+ "-endif">>,
+
+ {errors,[{{3,2},epp,{bad,endif}},
+ {{3,7},epp,{illegal,"unterminated",ifdef}}],
+ []}},
+
+ {ifdef_c4,
+ <<"\n-ifdef a.\n"
+ "-endif.\n">>,
+ {errors,[{{2,2},epp,{bad,ifdef}}],[]}},
+
+ {ifdef_c5,
+ <<"-ifdef(a).\n"
+ "-else.\n"
+ "-endif.\n"
+ "-endif.\n">>,
+ {errors,[{{4,2},epp,{illegal,"unbalanced",endif}}],[]}},
+
+ {ifdef_c6,
+ <<"-ifdef(a).\n"
+ "-else.\n"
+ "-endif.\n"
+ "-else.\n">>,
+ {errors,[{{4,2},epp,{illegal,"unbalanced",'else'}}],[]}},
+
+ {ifdef_c7,
+ <<"-ifndef(a).\n"
+ "-else\n"
+ "foo bar\n"
+ "-else.\n"
+ "t() -> a.\n"
+ "-endif.\n">>,
+ {errors,[{{2,2},epp,{bad,else}}],[]}},
+
+ {ifdef_c8,
+ <<"-ifdef(a).\n"
+ "-foo bar.">>,
+ {errors,[{{2,10},epp,{illegal,"unterminated",ifdef}}],[]}},
+
+ {ifdef_c9,
+ <<"-ifdef(a).\n"
+ "3.3e12000.\n"
+ "-endif.\n">>,
+ []},
+
+ {ifdef_c10,
+ <<"\nt() -> 3.3e12000.\n">>,
+ {errors,[{{2,8},erl_scan,{illegal,float}},
+ {{2,17},erl_parse,["syntax error before: ","'.'"]}], % ...
+ []}},
+
+ {ifndef_c1,
+ <<"-ifndef(a).\n"
+ "-ifndef(A).\n"
+ "t() -> ok.\n"
+ "-endif.\n"
+ "-else.\n"
+ "a bug.\n"
+ "-else.\n"
+ "a bug.\n"
+ "-endif.">>,
+ {errors,[{{7,2},epp,{illegal,"repeated",'else'}}],[]}},
+
+ {ifndef_c3,
+ <<"-ifndef(a).\n"
+ "-endif">>,
+
+ {errors,[{{2,2},epp,{bad,endif}},
+ {{2,7},epp,{illegal,"unterminated",ifndef}}],
+ []}},
+
+ {ifndef_c4,
+ <<"\n-ifndef a.\n"
+ "-endif.\n">>,
+ {errors,[{{2,2},epp,{bad,ifndef}}],[]}},
+
+ {define_c5,
+ <<"-\ndefine a.\n">>,
+ {errors,[{{2,1},epp,{bad,define}}],[]}},
+
+ {define_c6,
+ <<"\n-if.\n"
+ "-endif.\n">>,
+ {errors,[{{2,2},epp,{'NYI','if'}}],[]}},
+
+ {define_c7,
+ <<"-ifndef(a).\n"
+ "-elif.\n"
+ "-endif.\n">>,
+ {errors,[{{2,2},epp,{'NYI',elif}}],[]}},
+
+ {define_c7,
+ <<"-ifndef(a).\n"
+ "-if.\n"
+ "-elif.\n"
+ "-endif.\n"
+ "-endif.\n"
+ "t() -> a.\n">>,
+ {errors,[{{2,2},epp,{'NYI','if'}}],[]}}
+ ],
+ ?line [] = compile(Config, Cs),
+
+ Ts = [{ifdef_1,
+ <<"-ifdef(a).\n"
+ "a bug.\n"
+ "-else.\n"
+ "-ifdef(A).\n"
+ "a bug.\n"
+ "-endif.\n"
+ "t() -> ok.\n"
+ "-endif.">>,
+ ok},
+
+ {ifdef_2,
+ <<"-define(a, true).\n"
+ "-ifdef(a).\n"
+ "-define(A, true).\n"
+ "-ifdef(A).\n"
+ "t() -> ok.\n"
+ "-else.\n"
+ "a bug.\n"
+ "-endif.\n"
+ "-else.\n"
+ "a bug.\n"
+ "-endif.">>,
+ ok},
+
+ {ifdef_3,
+ <<"\n-define(a, true).\n"
+ "-ifndef(a).\n"
+ "a bug.\n"
+ "-else.\n"
+ "-define(A, true).\n"
+ "-ifndef(A).\n"
+ "a bug.\n"
+ "-else.\n"
+ "t() -> ok.\n"
+ "-endif.\n"
+ "-endif.">>,
+ ok},
+
+ {ifdef_4,
+ <<"-ifdef(a).\n"
+ "a bug.\n"
+ "-ifdef(a).\n"
+ "a bug.\n"
+ "-else.\n"
+ "-endif.\n"
+ "-ifdef(A).\n"
+ "a bug.\n"
+ "-endif.\n"
+ "-else.\n"
+ "t() -> ok.\n"
+ "-endif.">>,
+ ok},
+
+ {ifdef_5,
+ <<"-ifdef(a).\n"
+ "-ifndef(A).\n"
+ "a bug.\n"
+ "-else.\n"
+ "-endif.\n"
+ "a bug.\n"
+ "-else.\n"
+ "t() -> ok.\n"
+ "-endif.">>,
+ ok},
+
+ {ifdef_6,
+ <<"-ifdef(a).\n"
+ "-if(A).\n"
+ "a bug.\n"
+ "-else.\n"
+ "-endif.\n"
+ "a bug.\n"
+ "-else.\n"
+ "t() -> ok.\n"
+ "-endif.">>,
+ ok}
+
+ ],
+ ?line [] = run(Config, Ts).
+
+check(Config, Tests) ->
+ eval_tests(Config, fun check_test/2, Tests).
+
+compile(Config, Tests) ->
+ eval_tests(Config, fun compile_test/2, Tests).
+
+run(Config, Tests) ->
+ eval_tests(Config, fun run_test/2, Tests).
+
+eval_tests(Config, Fun, Tests) ->
+ F = fun({N,P,E}, BadL) ->
+ %% io:format("Testing ~p~n", [P]),
+ Return = Fun(Config, P),
+ case message_compare(E, Return) of
+ true ->
+ BadL;
+ false ->
+ ?t:format("~nTest ~p failed. Expected~n ~p~n"
+ "but got~n ~p~n", [N, E, Return]),
+ fail()
+ end
+ end,
+ lists:foldl(F, [], Tests).
+
+
+check_test(Config, Test) ->
+ Filename = 'epp_test.erl',
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line File = filename:join(PrivDir, Filename),
+ ?line ok = file:write_file(File, Test),
+ ?line case epp:parse_file(File, [PrivDir], []) of
+ {ok,Forms} ->
+ [E || E={error,_} <- Forms];
+ {error,Error} ->
+ Error
+ end.
+
+compile_test(Config, Test0) ->
+ Test = [<<"-module(epp_test). -compile(export_all). ">>, Test0],
+ Filename = 'epp_test.erl',
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line File = filename:join(PrivDir, Filename),
+ ?line ok = file:write_file(File, Test),
+ Opts = [export_all,return,nowarn_unused_record,{outdir,PrivDir}],
+ case compile_file(File, Opts) of
+ {ok, Ws} -> warnings(File, Ws);
+ Else -> Else
+ end.
+
+warnings(File, Ws) ->
+ case lists:append([W || {F, W} <- Ws, F =:= File]) of
+ [] -> [];
+ L -> {warnings, L}
+ end.
+
+compile_file(File, Opts) ->
+ case compile:file(File, Opts) of
+ {ok, _M, Ws} -> {ok, Ws};
+ {error, FEs, []} -> {errors, errs(FEs, File), []};
+ {error, FEs, [{File,Ws}]} -> {error, errs(FEs, File), Ws}
+ end.
+
+errs([{File,Es}|L], File) ->
+ Es ++ errs(L, File);
+errs([_|L], File) ->
+ errs(L, File);
+errs([], _File) ->
+ [].
+
+run_test(Config, Test0) ->
+ Test = [<<"-module(epp_test). -compile(export_all). ">>, Test0],
+ Filename = "epp_test.erl",
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line File = filename:join(PrivDir, Filename),
+ ?line ok = file:write_file(File, Test),
+ Opts = [return, {i,PrivDir},{outdir,PrivDir}],
+ ?line {ok, epp_test, []} = compile:file(File, Opts),
+ AbsFile = filename:rootname(File, ".erl"),
+ ?line {module, epp_test} = code:load_abs(AbsFile, epp_test),
+ ?line Reply = epp_test:t(),
+ code:purge(epp_test),
+ Reply.
+
+fail() ->
+ io:format("failed~n"),
+ test_server:fail().
+
+message_compare(T, T) ->
+ true;
+message_compare(T1, T2) ->
+ ln(T1) =:= T2.
+
+%% Replaces locations like {Line,Column} with Line.
+ln({warnings,L}) ->
+ {warnings,ln0(L)};
+ln({errors,EL,WL}) ->
+ {errors,ln0(EL),ln0(WL)};
+ln(L) ->
+ ln0(L).
+
+ln0(L) ->
+ lists:keysort(1, ln1(L)).
+
+ln1([]) ->
+ [];
+ln1([{File,Ms}|MsL]) when is_list(File) ->
+ [{File,ln0(Ms)}|ln1(MsL)];
+ln1([M|Ms]) ->
+ [ln2(M)|ln1(Ms)].
+
+ln2({{L,_C},Mod,Mess}) ->
+ {L,Mod,Mess};
+ln2({error,M}) ->
+ {error,ln2(M)};
+ln2(M) ->
+ M.
diff --git a/lib/stdlib/test/epp_SUITE_data/mac.erl b/lib/stdlib/test/epp_SUITE_data/mac.erl
new file mode 100644
index 0000000000..e3329d76f9
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE_data/mac.erl
@@ -0,0 +1,46 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(mac).
+
+-compile(export_all).
+
+-define(A, ?A + ?A).
+
+-define(a, ?a + ?a).
+-define(b, x + ?c(2)).
+-define(c(Y), x + Y).
+-define(d(X), X X).
+
+bar() ->
+ 1 ?d(?d(?d(?d(?d(?d(?d(+1))))))).
+
+foo1() ->
+ ?a.
+
+foo2() ->
+ ?b.
+
+foo3() ->
+ ?A.
+
+-define( this, ?that).
+-define( that, ?this).
+
+talkAbout()->
+ ?this==?that.
diff --git a/lib/stdlib/test/epp_SUITE_data/mac2.erl b/lib/stdlib/test/epp_SUITE_data/mac2.erl
new file mode 100644
index 0000000000..0547cdb8b3
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE_data/mac2.erl
@@ -0,0 +1,38 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-ifndef(p).
+-define(p, 1).
+-endif.
+
+-ifndef('p').
+-define('p', 2).
+-endif.
+
+-ifndef(P).
+-define(P, 3).
+-endif.
+
+-ifndef('P').
+-define('P', 4).
+-endif.
+
+-plupp({?p,
+ ?'p',
+ ?P,
+ ?'P'}).
diff --git a/lib/stdlib/test/epp_SUITE_data/mac3.erl b/lib/stdlib/test/epp_SUITE_data/mac3.erl
new file mode 100644
index 0000000000..e5fb964a91
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE_data/mac3.erl
@@ -0,0 +1,36 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-l(?LINE).
+
+-f(?FILE).
+
+-machine1(?MACHINE).
+
+-module(mac3).
+
+-m(?MODULE).
+-ms(?MODULE_STRING).
+
+-ifdef(JAM).
+-machine2(jam).
+-endif.
+
+-ifdef(BEAM).
+-machine2(beam).
+-endif.
diff --git a/lib/stdlib/test/epp_SUITE_data/pmod.erl b/lib/stdlib/test/epp_SUITE_data/pmod.erl
new file mode 100644
index 0000000000..a4d4843a69
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE_data/pmod.erl
@@ -0,0 +1,25 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(pmod, [Props]).
+
+-export([update/1]).
+
+update(X) ->
+ ?MODULE:new(X).
+
diff --git a/lib/stdlib/test/epp_SUITE_data/variable_1.erl b/lib/stdlib/test/epp_SUITE_data/variable_1.erl
new file mode 100644
index 0000000000..b178f519a9
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE_data/variable_1.erl
@@ -0,0 +1,24 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(variable_1).
+
+-include("$VAR/variable_1_include.hrl").
+-include_lib("$VAR/variable_1_include_dir.hrl").
+
+-a({?variable_1_var1, ?variable_1_var2}).
diff --git a/lib/stdlib/test/epp_SUITE_data/variable_1_include.hrl b/lib/stdlib/test/epp_SUITE_data/variable_1_include.hrl
new file mode 100644
index 0000000000..039d72aa49
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE_data/variable_1_include.hrl
@@ -0,0 +1,19 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-define(variable_1_var1, value1).
diff --git a/lib/stdlib/test/epp_SUITE_data/variable_1_include_dir.hrl b/lib/stdlib/test/epp_SUITE_data/variable_1_include_dir.hrl
new file mode 100644
index 0000000000..727ccd421b
--- /dev/null
+++ b/lib/stdlib/test/epp_SUITE_data/variable_1_include_dir.hrl
@@ -0,0 +1,19 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-define(variable_1_var2, value2).
diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl
new file mode 100644
index 0000000000..c60a558fa1
--- /dev/null
+++ b/lib/stdlib/test/erl_eval_SUITE.erl
@@ -0,0 +1,1399 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(erl_eval_SUITE).
+-export([all/1]).
+
+-export([guard_1/1, guard_2/1,
+ match_pattern/1,
+ match_bin/1,
+ string_plusplus/1,
+ pattern_expr/1,
+ guard_3/1, guard_4/1,
+ lc/1,
+ simple_cases/1,
+ unary_plus/1,
+ apply_atom/1,
+ otp_5269/1,
+ otp_6539/1,
+ otp_6543/1,
+ otp_6787/1,
+ otp_6977/1,
+ otp_7550/1,
+ otp_8133/1,
+ funs/1,
+ try_catch/1,
+ eval_expr_5/1]).
+
+%%
+%% Define to run outside of test server
+%%
+%%-define(STANDALONE,1).
+
+-import(lists,[concat/1, sort/1]).
+
+-export([count_down/2, count_down_fun/0, do_apply/2,
+ local_func/3, local_func_value/2]).
+
+-ifdef(STANDALONE).
+-define(config(A,B),config(A,B)).
+-export([config/2]).
+-define(line, noop, ).
+config(priv_dir,_) ->
+ ".".
+-else.
+-include("test_server.hrl").
+-export([init_per_testcase/2, fin_per_testcase/2]).
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+-endif.
+
+all(doc) ->
+ ["Test cases for the 'erl_eval' module."];
+all(suite) ->
+ [guard_1, guard_2, match_pattern, string_plusplus, pattern_expr,
+ match_bin, guard_3, guard_4,
+ lc, simple_cases, unary_plus, apply_atom, otp_5269, otp_6539, otp_6543,
+ otp_6787, otp_6977, otp_7550, otp_8133, funs, try_catch, eval_expr_5].
+
+guard_1(doc) ->
+ ["(OTP-2405)"];
+guard_1(suite) ->
+ [];
+guard_1(Config) when is_list(Config) ->
+ ?line {ok,Tokens ,_} =
+ erl_scan:string("if a+4 == 4 -> yes; true -> no end. "),
+ ?line {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ ?line no = guard_1_compiled(),
+ ?line {value, no, []} = erl_eval:expr(Expr, []),
+ ok.
+
+guard_1_compiled() ->
+ if a+4 == 4 -> yes; true -> no end.
+
+guard_2(doc) ->
+ ["Similar to guard_1, but type-correct"];
+guard_2(suite) ->
+ [];
+guard_2(Config) when is_list(Config) ->
+ ?line {ok,Tokens ,_} =
+ erl_scan:string("if 6+4 == 4 -> yes; true -> no end. "),
+ ?line {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ ?line no = guard_2_compiled(),
+ ?line {value, no, []} = erl_eval:expr(Expr, []),
+ ok.
+
+guard_2_compiled() ->
+ if 6+4 == 4 -> yes; true -> no end.
+
+string_plusplus(doc) ->
+ ["OTP-3069: syntactic sugar string ++ ..."];
+string_plusplus(suite) ->
+ [];
+string_plusplus(Config) when is_list(Config) ->
+ ?line check(fun() -> case "abc" of "ab" ++ L -> L end end,
+ "case \"abc\" of \"ab\" ++ L -> L end. ",
+ "c"),
+ ?line check(fun() -> case "abcde" of "ab" ++ "cd" ++ L -> L end end,
+ "case \"abcde\" of \"ab\" ++ \"cd\" ++ L -> L end. ",
+ "e"),
+ ?line check(fun() -> case "abc" of [97, 98] ++ L -> L end end,
+ "case \"abc\" of [97, 98] ++ L -> L end. ",
+ "c"),
+ ok.
+
+match_pattern(doc) ->
+ ["OTP-2983: match operator in pattern"];
+match_pattern(suite) ->
+ [];
+match_pattern(Config) when is_list(Config) ->
+ ?line check(fun() -> case {a, b} of {a, _X}=Y -> {x,Y} end end,
+ "case {a, b} of {a, X}=Y -> {x,Y} end. ",
+ {x, {a, b}}),
+ ?line check(fun() -> case {a, b} of Y={a, _X} -> {x,Y} end end,
+ "case {a, b} of Y={a, X} -> {x,Y} end. ",
+ {x, {a, b}}),
+ ?line check(fun() -> case {a, b} of Y={a, _X}=Z -> {Z,Y} end end,
+ "case {a, b} of Y={a, X}=Z -> {Z,Y} end. ",
+ {{a, b}, {a, b}}),
+ ?line check(fun() -> A = 4, B = 28, <<13:(A+(X=B))>>, X end,
+ "begin A = 4, B = 28, <<13:(A+(X=B))>>, X end.",
+ 28),
+ ok.
+
+match_bin(doc) ->
+ ["binary match problems"];
+match_bin(suite) ->
+ [];
+match_bin(Config) when is_list(Config) ->
+ ?line check(fun() -> <<"abc">> = <<"abc">> end,
+ "<<\"abc\">> = <<\"abc\">>. ",
+ <<"abc">>),
+ ?line check(fun() ->
+ <<Size,B:Size/binary,Rest/binary>> = <<2,"AB","CD">>,
+ {Size,B,Rest}
+ end,
+ "begin <<Size,B:Size/binary,Rest/binary>> = <<2,\"AB\",\"CD\">>, "
+ "{Size,B,Rest} end. ",
+ {2,<<"AB">>,<<"CD">>}),
+ ok.
+
+pattern_expr(doc) ->
+ ["OTP-3144: compile-time expressions in pattern"];
+pattern_expr(suite) ->
+ [];
+pattern_expr(Config) when is_list(Config) ->
+ ?line check(fun() -> case 4 of 2+2 -> ok end end,
+ "case 4 of 2+2 -> ok end. ",
+ ok),
+ ?line check(fun() -> case 2 of +2 -> ok end end,
+ "case 2 of +2 -> ok end. ",
+ ok),
+ ok.
+
+guard_3(doc) ->
+ ["OTP-4518."];
+guard_3(suite) ->
+ [];
+guard_3(Config) when is_list(Config) ->
+ ?line check(fun() -> if false -> false; true -> true end end,
+ "if false -> false; true -> true end.",
+ true),
+ ?line check(fun() -> if <<"hej">> == <<"hopp">> -> true;
+ true -> false end end,
+ "begin if <<\"hej\">> == <<\"hopp\">> -> true;
+ true -> false end end.",
+ false),
+ ?line check(fun() -> if <<"hej">> == <<"hej">> -> true;
+ true -> false end end,
+ "begin if <<\"hej\">> == <<\"hej\">> -> true;
+ true -> false end end.",
+ true),
+ ok.
+
+guard_4(doc) ->
+ ["OTP-4885."];
+guard_4(suite) ->
+ [];
+guard_4(Config) when is_list(Config) ->
+ ?line check(fun() -> if {erlang,'+'}(3,a) -> true ; true -> false end end,
+ "if {erlang,'+'}(3,a) -> true ; true -> false end.",
+ false),
+ ?line check(fun() -> if {erlang,is_integer}(3) -> true ; true -> false end
+ end,
+ "if {erlang,is_integer}(3) -> true ; true -> false end.",
+ true),
+ ?line check(fun() -> [X || X <- [1,2,3], erlang:is_integer(X)] end,
+ "[X || X <- [1,2,3], erlang:is_integer(X)].",
+ [1,2,3]),
+ ?line check(fun() -> if is_atom(is_integer(a)) -> true ; true -> false end
+ end,
+ "if is_atom(is_integer(a)) -> true ; true -> false end.",
+ true),
+ ?line check(fun() -> if {erlang,is_atom}({erlang,is_integer}(a)) -> true;
+ true -> false end end,
+ "if {erlang,is_atom}({erlang,is_integer}(a)) -> true; "
+ "true -> false end.",
+ true),
+ ?line check(fun() -> if is_atom(3+a) -> true ; true -> false end end,
+ "if is_atom(3+a) -> true ; true -> false end.",
+ false),
+ ?line check(fun() -> if erlang:is_atom(3+a) -> true ; true -> false end
+ end,
+ "if erlang:is_atom(3+a) -> true ; true -> false end.",
+ false),
+ ok.
+
+
+lc(doc) ->
+ ["OTP-4518."];
+lc(suite) ->
+ [];
+lc(Config) when is_list(Config) ->
+ ?line check(fun() -> X = 32, [X || X <- [1,2,3]] end,
+ "begin X = 32, [X || X <- [1,2,3]] end.",
+ [1,2,3]),
+ ?line check(fun() -> X = 32,
+ [X || <<X:X>> <- [<<1:32>>,<<2:32>>,<<3:8>>]] end,
+ %% "binsize variable" ^
+ "begin X = 32,
+ [X || <<X:X>> <- [<<1:32>>,<<2:32>>,<<3:8>>]] end.",
+ [1,2]),
+ ?line check(fun() -> Y = 13,[X || {X,Y} <- [{1,2}]] end,
+ "begin Y = 13,[X || {X,Y} <- [{1,2}]] end.",
+ [1]),
+ ?line error_check("begin [A || X <- [{1,2}], 1 == A] end.",
+ {unbound_var,'A'}),
+ ?line error_check("begin X = 32,
+ [{Y,W} || X <- [1,2,32,Y=4], Z <- [1,2,W=3]] end.",
+ {unbound_var,'Y'}),
+ ?line error_check("begin X = 32,<<A:B>> = <<100:X>> end.",
+ {unbound_var,'B'}),
+ ?line check(fun() -> [X || X <- [1,2,3,4], not (X < 2)] end,
+ "begin [X || X <- [1,2,3,4], not (X < 2)] end.",
+ [2,3,4]),
+ ?line check(fun() -> [X || X <- [true,false], X] end,
+ "[X || X <- [true,false], X].", [true]),
+ ok.
+
+simple_cases(doc) ->
+ ["Simple cases, just to cover some code."];
+simple_cases(suite) ->
+ [];
+simple_cases(Config) when is_list(Config) ->
+ ?line check(fun() -> A = $C end, "A = $C.", $C),
+ %% ?line check(fun() -> A = 3.14 end, "A = 3.14.", 3.14),
+ ?line check(fun() -> self() ! a, A = receive a -> true end end,
+ "begin self() ! a, A = receive a -> true end end.",
+ true),
+ ?line check(fun() -> c:flush(), self() ! a, self() ! b, self() ! c,
+ receive b -> b end,
+ {messages, [a,c]} =
+ erlang:process_info(self(), messages),
+ c:flush() end,
+ "begin c:flush(), self() ! a, self() ! b, self() ! c,"
+ "receive b -> b end,"
+ "{messages, [a,c]} ="
+ " erlang:process_info(self(), messages), c:flush() end.",
+ ok),
+ ?line check(fun() -> self() ! a, A = receive a -> true
+ after 0 -> false end end,
+ "begin self() ! a, A = receive a -> true"
+ " after 0 -> false end end.",
+ true),
+ ?line check(fun() -> c:flush(), self() ! a, self() ! b, self() ! c,
+ receive b -> b after 0 -> true end,
+ {messages, [a,c]} =
+ erlang:process_info(self(), messages),
+ c:flush() end,
+ "begin c:flush(), self() ! a, self() ! b, self() ! c,"
+ "receive b -> b after 0 -> true end,"
+ "{messages, [a,c]} ="
+ " erlang:process_info(self(), messages), c:flush() end.",
+ ok),
+ ?line check(fun() -> receive _ -> true after 10 -> false end end,
+ "receive _ -> true after 10 -> false end.",
+ false),
+ ?line check(fun() -> F = fun(A) -> A end, true = 3 == F(3) end,
+ "begin F = fun(A) -> A end, true = 3 == F(3) end.",
+ true),
+ ?line check(fun() -> F = fun(A) -> A end, true = 3 == apply(F, [3]) end,
+ "begin F = fun(A) -> A end, true = 3 == apply(F,[3]) end.",
+ true),
+ ?line check(fun() -> catch throw(a) end, "catch throw(a).", a),
+ ?line check(fun() -> catch a end, "catch a.", a),
+ ?line check(fun() -> 4 == 3 end, "4 == 3.", false),
+ ?line check(fun() -> not true end, "not true.", false),
+ ?line check(fun() -> -3 end, "-3.", -3),
+
+ ?line error_check("3.0 = 4.0.", {badmatch,4.0}),
+ ?line check(fun() -> <<(3.0+2.0):32/float>> = <<5.0:32/float>> end,
+ "<<(3.0+2.0):32/float>> = <<5.0:32/float>>.",
+ <<5.0:32/float>>),
+
+ ?line check(fun() -> false andalso kludd end, "false andalso kludd.",
+ false),
+ ?line check(fun() -> true andalso true end, "true andalso true.",
+ true),
+ ?line check(fun() -> true andalso false end, "true andalso false.",
+ false),
+ ?line check(fun() -> true andalso kludd end, "true andalso kludd.",
+ kludd),
+ ?line error_check("kladd andalso kludd.", {badarg,kladd}),
+
+ ?line check(fun() -> if false andalso kludd -> a; true -> b end end,
+ "if false andalso kludd -> a; true -> b end.",
+ b),
+ ?line check(fun() -> if true andalso true -> a; true -> b end end,
+ "if true andalso true -> a; true -> b end.",
+ a),
+ ?line check(fun() -> if true andalso false -> a; true -> b end end,
+ "if true andalso false -> a; true -> b end.",
+ b),
+
+ ?line check(fun() -> true orelse kludd end,
+ "true orelse kludd.", true),
+ ?line check(fun() -> false orelse false end,
+ "false orelse false.", false),
+ ?line check(fun() -> false orelse true end,
+ "false orelse true.", true),
+ ?line check(fun() -> false orelse kludd end,
+ "false orelse kludd.", kludd),
+ ?line error_check("kladd orelse kludd.", {badarg,kladd}),
+ ?line error_check("[X || X <- [1,2,3], begin 1 end].",{bad_filter,1}),
+ ?line error_check("[X || X <- a].",{bad_generator,a}),
+
+ ?line check(fun() -> if true orelse kludd -> a; true -> b end end,
+ "if true orelse kludd -> a; true -> b end.", a),
+ ?line check(fun() -> if false orelse false -> a; true -> b end end,
+ "if false orelse false -> a; true -> b end.", b),
+ ?line check(fun() -> if false orelse true -> a; true -> b end end,
+ "if false orelse true -> a; true -> b end.", a),
+
+ ?line check(fun() -> [X || X <- [1,2,3], X+2] end,
+ "[X || X <- [1,2,3], X+2].", []),
+
+ ?line check(fun() -> [X || X <- [1,2,3], [X] == [X || X <- [2]]] end,
+ "[X || X <- [1,2,3], [X] == [X || X <- [2]]].",
+ [2]),
+ ?line check(fun() -> F = fun(1) -> ett; (2) -> zwei end,
+ ett = F(1), zwei = F(2) end,
+ "begin F = fun(1) -> ett; (2) -> zwei end,
+ ett = F(1), zwei = F(2) end.",
+ zwei),
+ ?line check(fun() -> F = fun(X) when X == 1 -> ett;
+ (X) when X == 2 -> zwei end,
+ ett = F(1), zwei = F(2) end,
+ "begin F = fun(X) when X == 1 -> ett;
+ (X) when X == 2 -> zwei end,
+ ett = F(1), zwei = F(2) end.",
+ zwei),
+ ?line error_check("begin F = fun(1) -> ett end, zwei = F(2) end.",
+ function_clause),
+ ?line check(fun() -> if length([1]) == 1 -> yes;
+ true -> no end end,
+ "if length([1]) == 1 -> yes;
+ true -> no end.",
+ yes),
+ ?line check(fun() -> if is_integer(3) -> true; true -> false end end,
+ "if is_integer(3) -> true; true -> false end.", true),
+ ?line check(fun() -> if integer(3) -> true; true -> false end end,
+ "if integer(3) -> true; true -> false end.", true),
+ ?line check(fun() -> if is_float(3) -> true; true -> false end end,
+ "if is_float(3) -> true; true -> false end.", false),
+ ?line check(fun() -> if float(3) -> true; true -> false end end,
+ "if float(3) -> true; true -> false end.", false),
+ ?line check(fun() -> if is_number(3) -> true; true -> false end end,
+ "if is_number(3) -> true; true -> false end.", true),
+ ?line check(fun() -> if number(3) -> true; true -> false end end,
+ "if number(3) -> true; true -> false end.", true),
+ ?line check(fun() -> if is_atom(a) -> true; true -> false end end,
+ "if is_atom(a) -> true; true -> false end.", true),
+ ?line check(fun() -> if atom(a) -> true; true -> false end end,
+ "if atom(a) -> true; true -> false end.", true),
+ ?line check(fun() -> if is_list([]) -> true; true -> false end end,
+ "if is_list([]) -> true; true -> false end.", true),
+ ?line check(fun() -> if list([]) -> true; true -> false end end,
+ "if list([]) -> true; true -> false end.", true),
+ ?line check(fun() -> if is_tuple({}) -> true; true -> false end end,
+ "if is_tuple({}) -> true; true -> false end.", true),
+ ?line check(fun() -> if tuple({}) -> true; true -> false end end,
+ "if tuple({}) -> true; true -> false end.", true),
+ ?line check(fun() -> if is_pid(self()) -> true; true -> false end end,
+ "if is_pid(self()) -> true; true -> false end.", true),
+ ?line check(fun() -> if pid(self()) -> true; true -> false end end,
+ "if pid(self()) -> true; true -> false end.", true),
+ ?line check(fun() -> R = make_ref(), if is_reference(R) -> true;
+ true -> false end end,
+ "begin R = make_ref(), if is_reference(R) -> true;"
+ "true -> false end end.", true),
+ ?line check(fun() -> R = make_ref(), if reference(R) -> true;
+ true -> false end end,
+ "begin R = make_ref(), if reference(R) -> true;"
+ "true -> false end end.", true),
+ ?line check(fun() -> if is_port(a) -> true; true -> false end end,
+ "if is_port(a) -> true; true -> false end.", false),
+ ?line check(fun() -> if port(a) -> true; true -> false end end,
+ "if port(a) -> true; true -> false end.", false),
+ ?line check(fun() -> if is_function(a) -> true; true -> false end end,
+ "if is_function(a) -> true; true -> false end.", false),
+ ?line check(fun() -> if function(a) -> true; true -> false end end,
+ "if function(a) -> true; true -> false end.", false),
+ ?line check(fun() -> if is_binary(<<>>) -> true; true -> false end end,
+ "if is_binary(<<>>) -> true; true -> false end.", true),
+ ?line check(fun() -> if binary(<<>>) -> true; true -> false end end,
+ "if binary(<<>>) -> true; true -> false end.", true),
+ ?line check(fun() -> if is_integer(a) == true -> yes;
+ true -> no end end,
+ "if is_integer(a) == true -> yes;
+ true -> no end.",
+ no),
+ ?line check(fun() -> if [] -> true; true -> false end end,
+ "if [] -> true; true -> false end.", false),
+ ?line error_check("if lists:member(1,[1]) -> true; true -> false end.",
+ illegal_guard_expr),
+ ?line error_check("if false -> true end.", if_clause),
+ ?line check(fun() -> if a+b -> true; true -> false end end,
+ "if a + b -> true; true -> false end.", false),
+ ?line check(fun() -> if + b -> true; true -> false end end,
+ "if + b -> true; true -> false end.", false),
+ ?line error_check("case foo of bar -> true end.", {case_clause,foo}),
+ ?line error_check("case 4 of 2+a -> true; _ -> false end.",
+ illegal_pattern),
+ ?line error_check("case 4 of +a -> true; _ -> false end.",
+ illegal_pattern),
+ ?line check(fun() -> case a of
+ X when X == b -> one;
+ X when X == a -> two
+ end end,
+ "begin case a of
+ X when X == b -> one;
+ X when X == a -> two
+ end end.", two),
+ ?line error_check("3 = 4.", {badmatch,4}),
+ ?line error_check("a = 3.", {badmatch,3}),
+ %% ?line error_check("3.1 = 2.7.",{badmatch,2.7}),
+ ?line error_check("$c = 4.", {badmatch,4}),
+ ?line check(fun() -> $c = $c end, "$c = $c.", $c),
+ ?line check(fun() -> _ = bar end, "_ = bar.", bar),
+ ?line check(fun() -> A = 14, A = 14 end,
+ "begin A = 14, A = 14 end.", 14),
+ ?line error_check("begin A = 14, A = 16 end.", {badmatch,16}),
+ ?line error_check("\"hej\" = \"san\".", {badmatch,"san"}),
+ ?line check(fun() -> "hej" = "hej" end,
+ "\"hej\" = \"hej\".", "hej"),
+ ?line error_check("[] = [a].", {badmatch,[a]}),
+ ?line check(fun() -> [] = [] end, "[] = [].", []),
+ ?line error_check("[a] = [].", {badmatch,[]}),
+ ?line error_check("{a,b} = 34.", {badmatch,34}),
+ ?line check(fun() -> <<X:7>> = <<8:7>>, X end,
+ "begin <<X:7>> = <<8:7>>, X end.", 8),
+ ?line error_check("<<34:32>> = \"hej\".", {badmatch,"hej"}),
+ ?line check(fun() -> trunc((1 * 3 div 3 + 4 - 3) / 1) rem 2 end,
+ "begin trunc((1 * 3 div 3 + 4 - 3) / 1) rem 2 end.", 0),
+ ?line check(fun() -> (2#101 band 2#10101) bor (2#110 bxor 2#010) end,
+ "(2#101 band 2#10101) bor (2#110 bxor 2#010).", 5),
+ ?line check(fun() -> (2#1 bsl 4) + (2#10000 bsr 3) end,
+ "(2#1 bsl 4) + (2#10000 bsr 3).", 18),
+ ?line check(fun() -> ((1<3) and ((1 =:= 2) or (1 =/= 2))) xor (1=<2) end,
+ "((1<3) and ((1 =:= 2) or (1 =/= 2))) xor (1=<2).", false),
+ ?line check(fun() -> (a /= b) or (2 > 4) or (3 >= 3) end,
+ "(a /= b) or (2 > 4) or (3 >= 3).", true),
+ ?line check(fun() -> "hej" ++ "san" =/= "hejsan" -- "san" end,
+ "\"hej\" ++ \"san\" =/= \"hejsan\" -- \"san\".", true),
+ ?line check(fun() -> (bnot 1) < -0 end, "(bnot (+1)) < -0.", true),
+ ok.
+
+unary_plus(doc) ->
+ ["OTP-4929. Unary plus rejects non-numbers."];
+unary_plus(suite) ->
+ [];
+unary_plus(Config) when is_list(Config) ->
+ ?line check(fun() -> F = fun(X) -> + X end,
+ true = -1 == F(-1) end,
+ "begin F = fun(X) -> + X end,"
+ " true = -1 == F(-1) end.", true, ['F'], none, none),
+ ?line error_check("+a.", badarith),
+ ok.
+
+apply_atom(doc) ->
+ ["OTP-5064. Can no longer apply atoms."];
+apply_atom(suite) ->
+ [];
+apply_atom(Config) when is_list(Config) ->
+ ?line error_check("[X || X <- [[1],[2]],
+ begin L = length, L(X) =:= 1 end].",
+ {badfun,length}),
+ ok.
+
+otp_5269(doc) ->
+ ["OTP-5269. Bugs in the bit syntax."];
+otp_5269(suite) ->
+ [];
+otp_5269(Config) when is_list(Config) ->
+ ?line check(fun() -> L = 8,
+ F = fun(<<A:L,B:A>>) -> B end,
+ F(<<16:8, 7:16>>)
+ end,
+ "begin
+ L = 8, F = fun(<<A:L,B:A>>) -> B end, F(<<16:8, 7:16>>)
+ end.",
+ 7),
+ ?line check(fun() -> L = 8,
+ F = fun(<<L:L,B:L>>) -> B end,
+ F(<<16:8, 7:16>>)
+ end,
+ "begin
+ L = 8, F = fun(<<L:L,B:L>>) -> B end, F(<<16:8, 7:16>>)
+ end.",
+ 7),
+ ?line check(fun() -> L = 8, <<A:L,B:A>> = <<16:8, 7:16>>, B end,
+ "begin L = 8, <<A:L,B:A>> = <<16:8, 7:16>>, B end.",
+ 7),
+ ?line error_check("begin L = 8, <<L:L,B:L>> = <<16:8, 7:16>> end.",
+ {badmatch,<<16:8,7:16>>}),
+
+ ?line error_check("begin <<L:16,L:L>> = <<16:16,8:16>>, L end.",
+ {badmatch, <<16:16,8:16>>}),
+ ?line check(fun() -> U = 8, (fun(<<U:U>>) -> U end)(<<32:8>>) end,
+ "begin U = 8, (fun(<<U:U>>) -> U end)(<<32:8>>) end.",
+ 32),
+ ?line check(fun() -> U = 8, [U || <<U:U>> <- [<<32:8>>]] end,
+ "begin U = 8, [U || <<U:U>> <- [<<32:8>>]] end.",
+ [32]),
+ ?line error_check("(fun({3,<<A:32,A:32>>}) -> a end)
+ ({3,<<17:32,19:32>>}).",
+ function_clause),
+ ?line check(fun() -> [X || <<A:8,
+ B:A>> <- [<<16:8,19:16>>],
+ <<X:8>> <- [<<B:8>>]] end,
+ "[X || <<A:8,
+ B:A>> <- [<<16:8,19:16>>],
+ <<X:8>> <- [<<B:8>>]].",
+ [19]),
+ ok.
+
+otp_6539(doc) ->
+ ["OTP-6539. try/catch bugs."];
+otp_6539(suite) ->
+ [];
+otp_6539(Config) when is_list(Config) ->
+ ?line check(fun() ->
+ F = fun(A,B) ->
+ try A+B
+ catch _:_ -> dontthinkso
+ end
+ end,
+ lists:zipwith(F, [1,2], [2,3])
+ end,
+ "begin
+ F = fun(A,B) ->
+ try A+B
+ catch _:_ -> dontthinkso
+ end
+ end,
+ lists:zipwith(F, [1,2], [2,3])
+ end.",
+ [3, 5]),
+ ok.
+
+otp_6543(doc) ->
+ ["OTP-6543. bitlevel binaries."];
+otp_6543(suite) ->
+ [];
+otp_6543(Config) when is_list(Config) ->
+ ?line check(fun() ->
+ << <<X>> || <<X>> <- [1,2,3] >>
+ end,
+ "<< <<X>> || <<X>> <- [1,2,3] >>.",
+ <<>>),
+ ?line check(fun() ->
+ << <<X>> || X <- [1,2,3] >>
+ end,
+ "<< <<X>> || X <- [1,2,3] >>.",
+ <<1,2,3>>),
+ ?line check(fun() ->
+ << <<X:8>> || <<X:2>> <= <<"hej">> >>
+ end,
+ "<< <<X:8>> || <<X:2>> <= <<\"hej\">> >>.",
+ <<1,2,2,0,1,2,1,1,1,2,2,2>>),
+ ?line check(fun() ->
+ << <<X:8>> ||
+ <<65,X:4>> <= <<65,7:4,65,3:4,66,8:4>> >>
+ end,
+ "<< <<X:8>> ||
+ <<65,X:4>> <= <<65,7:4,65,3:4,66,8:4>> >>.",
+ <<7,3>>),
+ ?line check(fun() -> <<34:18/big>> end,
+ "<<34:18/big>>.",
+ <<0,8,2:2>>),
+ ?line check(fun() -> <<34:18/big-unit:2>> end,
+ "<<34:18/big-unit:2>>.",
+ <<0,0,0,2,2:4>>),
+ ?line check(fun() -> <<34:18/little>> end,
+ "<<34:18/little>>.",
+ <<34,0,0:2>>),
+ ?line case eval_string("<<34:18/native>>.") of
+ <<0,8,2:2>> -> ok;
+ <<34,0,0:2>> -> ok
+ end,
+ ?line check(fun() -> <<34:18/big-signed>> end,
+ "<<34:18/big-signed>>.",
+ <<0,8,2:2>>),
+ ?line check(fun() -> <<34:18/little-signed>> end,
+ "<<34:18/little-signed>>.",
+ <<34,0,0:2>>),
+ ?line case eval_string("<<34:18/native-signed>>.") of
+ <<0,8,2:2>> -> ok;
+ <<34,0,0:2>> -> ok
+ end,
+ ?line check(fun() -> <<34:18/big-unsigned>> end,
+ "<<34:18/big-unsigned>>.",
+ <<0,8,2:2>>),
+ ?line check(fun() -> <<34:18/little-unsigned>> end,
+ "<<34:18/little-unsigned>>.",
+ <<34,0,0:2>>),
+ ?line case eval_string("<<34:18/native-unsigned>>.") of
+ <<0,8,2:2>> -> ok;
+ <<34,0,0:2>> -> ok
+ end,
+ ?line check(fun() -> <<3.14:32/float-big>> end,
+ "<<3.14:32/float-big>>.",
+ <<64,72,245,195>>),
+ ?line check(fun() -> <<3.14:32/float-little>> end,
+ "<<3.14:32/float-little>>.",
+ <<195,245,72,64>>),
+ ?line case eval_string("<<3.14:32/float-native>>.") of
+ <<64,72,245,195>> -> ok;
+ <<195,245,72,64>> -> ok
+ end,
+ ?line error_check("<<(<<17,3:2>>)/binary>>.", badarg),
+ ?line check(fun() -> <<(<<17,3:2>>)/bitstring>> end,
+ "<<(<<17,3:2>>)/bitstring>>.",
+ <<17,3:2>>),
+ ?line check(fun() -> <<(<<17,3:2>>):10/bitstring>> end,
+ "<<(<<17,3:2>>):10/bitstring>>.",
+ <<17,3:2>>),
+ ?line check(fun() -> <<<<344:17>>/binary-unit:17>> end,
+ "<<<<344:17>>/binary-unit:17>>.",
+ <<344:17>>),
+
+ ?line check(fun() -> <<X:18/big>> = <<34:18/big>>, X end,
+ "begin <<X:18/big>> = <<34:18/big>>, X end.",
+ 34),
+ ?line check(fun() -> <<X:18/big-unit:2>> = <<34:18/big-unit:2>>, X end,
+ "begin <<X:18/big-unit:2>> = <<34:18/big-unit:2>>, X end.",
+ 34),
+ ?line check(fun() -> <<X:18/little>> = <<34:18/little>>, X end,
+ "begin <<X:18/little>> = <<34:18/little>>, X end.",
+ 34),
+ ?line check(fun() -> <<X:18/native>> = <<34:18/native>>, X end,
+ "begin <<X:18/native>> = <<34:18/native>>, X end.",
+ 34),
+ ?line check(fun() -> <<X:18/big-signed>> = <<34:18/big-signed>>, X end,
+ "begin <<X:18/big-signed>> = <<34:18/big-signed>>, X end.",
+ 34),
+ ?line check(fun() -> <<X:18/little-signed>> = <<34:18/little-signed>>,
+ X end,
+ "begin <<X:18/little-signed>> = <<34:18/little-signed>>,
+ X end.",
+ 34),
+ ?line check(fun() -> <<X:18/native-signed>> = <<34:18/native-signed>>,
+ X end,
+ "begin <<X:18/native-signed>> = <<34:18/native-signed>>,
+ X end.",
+ 34),
+ ?line check(fun() -> <<X:18/big-unsigned>> = <<34:18/big-unsigned>>,
+ X end,
+ "begin <<X:18/big-unsigned>> = <<34:18/big-unsigned>>,
+ X end.",
+ 34),
+ ?line check(fun() ->
+ <<X:18/little-unsigned>> = <<34:18/little-unsigned>>,
+ X end,
+ "begin <<X:18/little-unsigned>> = <<34:18/little-unsigned>>,
+ X end.",
+ 34),
+ ?line check(fun() ->
+ <<X:18/native-unsigned>> = <<34:18/native-unsigned>>,
+ X end,
+ "begin <<X:18/native-unsigned>> = <<34:18/native-unsigned>>,
+ X end.",
+ 34),
+ ?line check(fun() -> <<X:32/float-big>> = <<2.0:32/float-big>>, X end,
+ "begin <<X:32/float-big>> = <<2.0:32/float-big>>,
+ X end.",
+ 2.0),
+ ?line check(fun() -> <<X:32/float-little>> = <<2.0:32/float-little>>,
+ X end,
+ "begin <<X:32/float-little>> = <<2.0:32/float-little>>,
+ X end.",
+ 2.0),
+ ?line check(fun() -> <<X:32/float-native>> = <<2.0:32/float-native>>,
+ X end,
+ "begin <<X:32/float-native>> = <<2.0:32/float-native>>,
+ X end.",
+ 2.0),
+
+ ?line check(
+ fun() ->
+ [X || <<"hej",X:8>> <= <<"hej",8,"san",9,"hej",17,"hej">>]
+ end,
+ "[X || <<\"hej\",X:8>> <=
+ <<\"hej\",8,\"san\",9,\"hej\",17,\"hej\">>].",
+ [8,17]),
+ ?line check(
+ fun() ->
+ L = 8, << <<B:32>> || <<L:L,B:L>> <= <<16:8, 7:16>> >>
+ end,
+ "begin L = 8, << <<B:32>> || <<L:L,B:L>> <= <<16:8, 7:16>> >>
+ end.",
+ <<0,0,0,7>>),
+ %% Test the Value part of a binary segment.
+ %% "Old" bugs have been fixed (partial_eval is called on Value).
+ ?line check(fun() -> [ 3 || <<17/float>> <= <<17.0/float>>] end,
+ "[ 3 || <<17/float>> <= <<17.0/float>>].",
+ [3]),
+ ?line check(fun() -> [ 3 || <<17/float>> <- [<<17.0/float>>]] end,
+ "[ 3 || <<17/float>> <- [<<17.0/float>>]].",
+ [3]),
+ ?line check(fun() -> [ X || <<17/float,X:3>> <= <<17.0/float,2:3>>] end,
+ "[ X || <<17/float,X:3>> <= <<17.0/float,2:3>>].",
+ [2]),
+ ?line check(fun() ->
+ [ foo || <<(1 bsl 1023)/float>> <= <<(1 bsl 1023)/float>>]
+ end,
+ "[ foo || <<(1 bsl 1023)/float>> <= <<(1 bsl 1023)/float>>].",
+ [foo]),
+ ?line check(fun() ->
+ [ foo || <<(1 bsl 1023)/float>> <- [<<(1 bsl 1023)/float>>]]
+ end,
+ "[ foo || <<(1 bsl 1023)/float>> <- [<<(1 bsl 1023)/float>>]].",
+ [foo]),
+ ?line error_check("[ foo || <<(1 bsl 1024)/float>> <-
+ [<<(1 bsl 1024)/float>>]].",
+ badarg),
+ ?line check(fun() ->
+ [ foo || <<(1 bsl 1024)/float>> <- [<<(1 bsl 1023)/float>>]]
+ end,
+ "[ foo || <<(1 bsl 1024)/float>> <-
+ [<<(1 bsl 1023)/float>>]].",
+ []),
+ ?line check(fun() ->
+ [ foo || <<(1 bsl 1024)/float>> <= <<(1 bsl 1023)/float>>]
+ end,
+ "[ foo || <<(1 bsl 1024)/float>> <=
+ <<(1 bsl 1023)/float>>].",
+ []),
+ ?line check(fun() ->
+ L = 8,
+ [{L,B} || <<L:L,B:L/float>> <= <<32:8,7:32/float>>]
+ end,
+ "begin L = 8,
+ [{L,B} || <<L:L,B:L/float>> <= <<32:8,7:32/float>>]
+ end.",
+ [{32,7.0}]),
+ ?line check(fun() ->
+ L = 8,
+ [{L,B} || <<L:L,B:L/float>> <- [<<32:8,7:32/float>>]]
+ end,
+ "begin L = 8,
+ [{L,B} || <<L:L,B:L/float>> <- [<<32:8,7:32/float>>]]
+ end.",
+ [{32,7.0}]),
+ ?line check(fun() ->
+ [foo || <<"s">> <= <<"st">>]
+ end,
+ "[foo || <<\"s\">> <= <<\"st\">>].",
+ [foo]),
+ ?line check(fun() -> <<_:32>> = <<17:32>> end,
+ "<<_:32>> = <<17:32>>.",
+ <<17:32>>),
+ ?line check(fun() -> [foo || <<_:32>> <= <<17:32,20:32>>] end,
+ "[foo || <<_:32>> <= <<17:32,20:32>>].",
+ [foo,foo]),
+
+ ?line check(fun() -> << <<X:32>> || X <- [1,2,3], X > 1 >> end,
+ "<< <<X:32>> || X <- [1,2,3], X > 1 >>.",
+ <<0,0,0,2,0,0,0,3>>),
+ ?line error_check("[X || <<X>> <= [a,b]].",{bad_generator,[a,b]}),
+ ok.
+
+otp_6787(doc) ->
+ ["OTP-6787. bitlevel binaries."];
+otp_6787(suite) ->
+ [];
+otp_6787(Config) when is_list(Config) ->
+ ?line check(
+ fun() -> <<16:(1024*1024)>> = <<16:(1024*1024)>> end,
+ "<<16:(1024*1024)>> = <<16:(1024*1024)>>.",
+ <<16:1048576>>),
+ ok.
+
+otp_6977(doc) ->
+ ["OTP-6977. ++ bug."];
+otp_6977(suite) ->
+ [];
+otp_6977(Config) when is_list(Config) ->
+ ?line check(
+ fun() -> (fun([$X] ++ _) -> ok end)("X") end,
+ "(fun([$X] ++ _) -> ok end)(\"X\").",
+ ok),
+ ok.
+
+otp_7550(doc) ->
+ ["OTP-7550. Support for UTF-8, UTF-16, UTF-32."];
+otp_7550(Config) when is_list(Config) ->
+
+ %% UTF-8.
+ ?line check(
+ fun() -> <<65>> = <<65/utf8>> end,
+ "<<65>> = <<65/utf8>>.",
+ <<65>>),
+ ?line check(
+ fun() -> <<350/utf8>> = <<197,158>> end,
+ "<<350/utf8>> = <<197,158>>.",
+ <<197,158>>),
+ ?line check(
+ fun() -> <<$b,$j,$\303,$\266,$r,$n>> = <<"bj\366rn"/utf8>> end,
+ "<<$b,$j,$\303,$\266,$r,$n>> = <<\"bj\366rn\"/utf8>>.",
+ <<$b,$j,$\303,$\266,$r,$n>>),
+
+ %% UTF-16.
+ ?line check(
+ fun() -> <<0,65>> = <<65/utf16>> end,
+ "<<0,65>> = <<65/utf16>>.",
+ <<0,65>>),
+ ?line check(
+ fun() -> <<16#D8,16#08,16#DF,16#45>> = <<16#12345/utf16>> end,
+ "<<16#D8,16#08,16#DF,16#45>> = <<16#12345/utf16>>.",
+ <<16#D8,16#08,16#DF,16#45>>),
+ ?line check(
+ fun() -> <<16#08,16#D8,16#45,16#DF>> = <<16#12345/little-utf16>> end,
+ "<<16#08,16#D8,16#45,16#DF>> = <<16#12345/little-utf16>>.",
+ <<16#08,16#D8,16#45,16#DF>>),
+
+ ?line check(
+ fun() -> <<350/utf16>> = <<1,94>> end,
+ "<<350/utf16>> = <<1,94>>.",
+ <<1,94>>),
+ ?line check(
+ fun() -> <<350/little-utf16>> = <<94,1>> end,
+ "<<350/little-utf16>> = <<94,1>>.",
+ <<94,1>>),
+ ?line check(
+ fun() -> <<16#12345/utf16>> = <<16#D8,16#08,16#DF,16#45>> end,
+ "<<16#12345/utf16>> = <<16#D8,16#08,16#DF,16#45>>.",
+ <<16#D8,16#08,16#DF,16#45>>),
+ ?line check(
+ fun() -> <<16#12345/little-utf16>> = <<16#08,16#D8,16#45,16#DF>> end,
+ "<<16#12345/little-utf16>> = <<16#08,16#D8,16#45,16#DF>>.",
+ <<16#08,16#D8,16#45,16#DF>>),
+
+ %% UTF-32.
+ ?line check(
+ fun() -> <<16#12345/utf32>> = <<16#0,16#01,16#23,16#45>> end,
+ "<<16#12345/utf32>> = <<16#0,16#01,16#23,16#45>>.",
+ <<16#0,16#01,16#23,16#45>>),
+ ?line check(
+ fun() -> <<16#0,16#01,16#23,16#45>> = <<16#12345/utf32>> end,
+ "<<16#0,16#01,16#23,16#45>> = <<16#12345/utf32>>.",
+ <<16#0,16#01,16#23,16#45>>),
+ ?line check(
+ fun() -> <<16#12345/little-utf32>> = <<16#45,16#23,16#01,16#00>> end,
+ "<<16#12345/little-utf32>> = <<16#45,16#23,16#01,16#00>>.",
+ <<16#45,16#23,16#01,16#00>>),
+ ?line check(
+ fun() -> <<16#12345/little-utf32>> end,
+ "<<16#12345/little-utf32>>.",
+ <<16#45,16#23,16#01,16#00>>),
+
+ %% Mixed.
+ ?line check(
+ fun() -> <<16#41,16#12345/utf32,16#0391:16,16#2E:8>> end,
+ "<<16#41,16#12345/utf32,16#0391:16,16#2E:8>>.",
+ <<16#41,16#00,16#01,16#23,16#45,16#03,16#91,16#2E>>),
+ ok.
+
+
+otp_8133(doc) ->
+ ["OTP-8133. Bit comprehension bug."];
+otp_8133(suite) ->
+ [];
+otp_8133(Config) when is_list(Config) ->
+ ?line check(
+ fun() ->
+ E = fun(N) ->
+ if
+ is_integer(N) -> <<N/integer>>;
+ true -> throw(foo)
+ end
+ end,
+ try << << (E(V))/binary >> || V <- [1,2,3,a] >>
+ catch foo -> ok
+ end
+ end,
+ "begin
+ E = fun(N) ->
+ if is_integer(N) -> <<N/integer>>;
+ true -> throw(foo)
+ end
+ end,
+ try << << (E(V))/binary >> || V <- [1,2,3,a] >>
+ catch foo -> ok
+ end
+ end.",
+ ok),
+ ?line check(
+ fun() ->
+ E = fun(N) ->
+ if
+ is_integer(N) -> <<N/integer>>;
+ true -> erlang:error(foo)
+ end
+ end,
+ try << << (E(V))/binary >> || V <- [1,2,3,a] >>
+ catch error:foo -> ok
+ end
+ end,
+ "begin
+ E = fun(N) ->
+ if is_integer(N) -> <<N/integer>>;
+ true -> erlang:error(foo)
+ end
+ end,
+ try << << (E(V))/binary >> || V <- [1,2,3,a] >>
+ catch error:foo -> ok
+ end
+ end.",
+ ok),
+ ok.
+
+funs(doc) ->
+ ["Simple cases, just to cover some code."];
+funs(suite) ->
+ [];
+funs(Config) when is_list(Config) ->
+ do_funs(none, none),
+ do_funs(lfh(), none),
+ do_funs(lfh(), efh()),
+
+ ?line error_check("nix:foo().", {access_not_allowed,nix}, lfh(), efh()),
+ ?line error_check("bar().", undef, none, none),
+
+ ?line check(fun() -> F1 = fun(F,N) -> ?MODULE:count_down(F, N) end,
+ F1(F1, 1000) end,
+ "begin F1 = fun(F,N) -> count_down(F, N) end,"
+ "F1(F1,1000) end.",
+ 0, ['F1'], lfh(), none),
+
+ ?line check(fun() -> F1 = fun(F,N) -> ?MODULE:count_down(F, N) end,
+ F1(F1, 1000) end,
+ "begin F1 = fun(F,N) -> count_down(F, N) end,"
+ "F1(F1,1000) end.",
+ 0, ['F1'], lfh_value(), none),
+
+ ?line check(fun() -> F1 = fun(F,N) -> ?MODULE:count_down(F, N) end,
+ F1(F1, 1000) end,
+ "begin F1 = fun(F,N) -> count_down(F, N) end,"
+ "F1(F1,1000) end.",
+ 0, ['F1'], lfh_value_extra(), none),
+
+ ?line check(fun() -> F1 = fun(F,N) -> ?MODULE:count_down(F, N) end,
+ F1(F1, 1000) end,
+ "begin F1 = fun(F,N) -> count_down(F, N) end,"
+ "F1(F1,1000) end.",
+ 0, ['F1'], {?MODULE,local_func_value}, none),
+ %% This is not documented, and only for backward compatibility (good!).
+ B0 = erl_eval:new_bindings(),
+ ?line check(fun() -> is_function(?MODULE:count_down_fun()) end,
+ "begin is_function(count_down_fun()) end.",
+ true, [], {?MODULE,local_func,[B0]},none),
+
+ EF = fun({timer,sleep}, As) when length(As) == 1 -> exit({got_it,sleep});
+ ({M,F}, As) -> apply(M, F, As)
+ end,
+ EFH = {value, EF},
+ ?line error_check("apply(timer, sleep, [1]).", got_it, none, EFH),
+ ?line error_check("begin F = fun(T) -> timer:sleep(T) end,F(1) end.",
+ got_it, none, EFH),
+ ?line error_check("fun c/1.", undef),
+ ?line error_check("fun a:b/0().", undef),
+
+ MaxArgs = 20,
+ ?line [true] =
+ lists:usort([run_many_args(SAs) || SAs <- many_args(MaxArgs)]),
+ ?line {'EXIT',{{argument_limit,_},_}} =
+ (catch run_many_args(many_args1(MaxArgs+1))),
+ ok.
+
+run_many_args({S, As}) ->
+ apply(eval_string(S), As) =:= As.
+
+many_args(N) ->
+ [many_args1(I) || I <- lists:seq(1, N)].
+
+many_args1(N) ->
+ F = fun(L, P) ->
+ tl(lists:flatten([","++P++integer_to_list(E) || E <- L]))
+ end,
+ L = lists:seq(1, N),
+ T = F(L, "V"),
+ S = lists:flatten(io_lib:format("fun(~s) -> [~s] end.", [T, T])),
+ {S, L}.
+
+do_funs(LFH, EFH) ->
+ %% LFH is not really used by these examples...
+
+ %% These tests do not prove that tail recursive functions really
+ %% work (that the process does not grow); one should also run them
+ %% manually with 1000 replaced by 1000000.
+
+ M = atom_to_list(?MODULE),
+ ?line check(fun() -> F1 = fun(F,N) -> ?MODULE:count_down(F, N) end,
+ F1(F1, 1000) end,
+ concat(["begin F1 = fun(F,N) -> ", M,
+ ":count_down(F, N) end, F1(F1,1000) end."]),
+ 0, ['F1'], LFH, EFH),
+ ?line check(fun() -> F1 = fun(F,N) -> apply(?MODULE,count_down,[F,N])
+ end, F1(F1, 1000) end,
+ concat(["begin F1 = fun(F,N) -> apply(", M,
+ ",count_down,[F, N]) end, F1(F1,1000) end."]),
+ 0, ['F1'], LFH, EFH),
+ ?line check(fun() -> F1 = fun(F,N) -> {?MODULE,count_down}(F,N)
+ end, F1(F1, 1000) end,
+ concat(["begin F1 = fun(F,N) -> {", M,
+ ",count_down}(F, N) end, F1(F1,1000) end."]),
+ 0, ['F1'], LFH, EFH),
+ ?line check(fun() -> F = fun(F,N) when N > 0 -> apply(F,[F,N-1]);
+ (_F,0) -> ok end,
+ F(F, 1000)
+ end,
+ "begin F = fun(F,N) when N > 0 -> apply(F,[F,N-1]);"
+ "(_F,0) -> ok end,"
+ "F(F, 1000) end.",
+ ok, ['F'], LFH, EFH),
+ ?line check(fun() -> F = fun(F,N) when N > 0 ->
+ apply(erlang,apply,[F,[F,N-1]]);
+ (_F,0) -> ok end,
+ F(F, 1000)
+ end,
+ "begin F = fun(F,N) when N > 0 ->"
+ "apply(erlang,apply,[F,[F,N-1]]);"
+ "(_F,0) -> ok end,"
+ "F(F, 1000) end.",
+ ok, ['F'], LFH, EFH),
+ ?line check(fun() -> F = count_down_fun(),
+ SF = fun(SF, F1, N) -> F(SF, F1, N) end,
+ SF(SF, F, 1000) end,
+ concat(["begin F = ", M, ":count_down_fun(),"
+ "SF = fun(SF, F1, N) -> F(SF, F1, N) end,"
+ "SF(SF, F, 1000) end."]),
+ ok, ['F','SF'], LFH, EFH),
+
+
+ ?line check(fun() -> F = fun(X) -> A = 1+X, {X,A} end,
+ true = {2,3} == F(2) end,
+ "begin F = fun(X) -> A = 1+X, {X,A} end,
+ true = {2,3} == F(2) end.", true, ['F'], LFH, EFH),
+ ?line check(fun() -> F = fun(X) -> {erlang,'+'}(X,2) end,
+ true = 3 == F(1) end,
+ "begin F = fun(X) -> {erlang,'+'}(X,2) end,"
+ " true = 3 == F(1) end.", true, ['F'],
+ LFH, EFH),
+ ?line check(fun() -> F = fun(X) -> byte_size(X) end,
+ ?MODULE:do_apply(F,<<"hej">>) end,
+ concat(["begin F = fun(X) -> size(X) end,",
+ M,":do_apply(F,<<\"hej\">>) end."]),
+ 3, ['F'], LFH, EFH),
+
+ ?line check(fun() -> F1 = fun(X, Z) -> {X,Z} end,
+ Z = 5,
+ F2 = fun(X, Y) -> F1(Z,{X,Y}) end,
+ F3 = fun(X, Y) -> {a,F1(Z,{X,Y})} end,
+ {5,{x,y}} = F2(x,y),
+ {a,{5,{y,x}}} = F3(y,x),
+ {5,{5,y}} = F2(Z,y),
+ true = {5,{x,5}} == F2(x,Z) end,
+ "begin F1 = fun(X, Z) -> {X,Z} end,
+ Z = 5,
+ F2 = fun(X, Y) -> F1(Z,{X,Y}) end,
+ F3 = fun(X, Y) -> {a,F1(Z,{X,Y})} end,
+ {5,{x,y}} = F2(x,y),
+ {a,{5,{y,x}}} = F3(y,x),
+ {5,{5,y}} = F2(Z,y),
+ true = {5,{x,5}} == F2(x,Z) end.",
+ true, ['F1','Z','F2','F3'], LFH, EFH),
+ ?line check(fun() -> F = fun(X) -> byte_size(X) end,
+ F2 = fun(Y) -> F(Y) end,
+ ?MODULE:do_apply(F2,<<"hej">>) end,
+ concat(["begin F = fun(X) -> size(X) end,",
+ "F2 = fun(Y) -> F(Y) end,",
+ M,":do_apply(F2,<<\"hej\">>) end."]),
+ 3, ['F','F2'], LFH, EFH),
+ ?line check(fun() -> Z = 5, F = fun(X) -> {Z,X} end,
+ F2 = fun(Z) -> F(Z) end, F2(3) end,
+ "begin Z = 5, F = fun(X) -> {Z,X} end,
+ F2 = fun(Z) -> F(Z) end, F2(3) end.",
+ {5,3},['F','F2','Z'], LFH, EFH),
+ ?line check(fun() -> F = fun(Z) -> Z end,
+ F2 = fun(X) -> F(X), Z = {X,X}, Z end,
+ {1,1} = F2(1), Z = 7, Z end,
+ "begin F = fun(Z) -> Z end,
+ F2 = fun(X) -> F(X), Z = {X,X}, Z end,
+ {1,1} = F2(1), Z = 7, Z end.", 7, ['F','F2','Z'],
+ LFH, EFH),
+ ?line check(fun() -> F = fun(F, N) -> [?MODULE:count_down(F,N) || X <-[1]]
+ end, F(F,2) end,
+ concat(["begin F = fun(F, N) -> [", M,
+ ":count_down(F,N) || X <-[1]] end, F(F,2) end."]),
+ [[[0]]], ['F'], LFH, EFH),
+
+ %% Tests for a bug found by the Dialyzer - used to crash.
+ ?line check(fun() -> Pmod = erl_eval_helper:new(42), Pmod:add(5) end,
+ "begin Pmod = erl_eval_helper:new(42), Pmod:add(5) end.",
+ 47,
+ ['Pmod'], LFH, EFH),
+ ?line check(fun() -> Pmod = erl_eval_helper:new(42), B = Pmod:add(7), B end,
+ "begin Pmod = erl_eval_helper:new(42), B = Pmod:add(7), B end.",
+ 49,
+ ['B','Pmod'], LFH, EFH),
+
+ ok.
+
+count_down(F, N) when N > 0 ->
+ F(F, N-1);
+count_down(_F, N) ->
+ N.
+
+count_down_fun() ->
+ fun(SF,F,N) when N > 0 -> SF(SF,F,N-1);
+ (_SF,_F,_N) -> ok
+ end.
+
+do_apply(F, V) ->
+ F(V).
+
+lfh() ->
+ {eval, fun(F, As, Bs) -> local_func(F, As, Bs) end}.
+
+local_func(F, As0, Bs0) when is_atom(F) ->
+ {As,Bs} = erl_eval:expr_list(As0, Bs0, {eval,lfh()}),
+ case erlang:function_exported(?MODULE, F, length(As)) of
+ true ->
+ {value,apply(?MODULE, F, As),Bs};
+ false ->
+ {value,apply(shell_default, F, As),Bs}
+ end.
+
+lfh_value_extra() ->
+ %% Not documented.
+ {value, fun(F, As) -> local_func_value(F, As) end, []}.
+
+lfh_value() ->
+ {value, fun(F, As) -> local_func_value(F, As) end}.
+
+local_func_value(F, As) when is_atom(F) ->
+ case erlang:function_exported(?MODULE, F, length(As)) of
+ true ->
+ apply(?MODULE, F, As);
+ false ->
+ apply(shell_default, F, As)
+ end.
+
+efh() ->
+ {value, fun(F, As) -> external_func(F, As) end}.
+
+external_func({M,_}, _As) when M == nix ->
+ exit({{access_not_allowed,M},[mfa]});
+external_func(F, As) when is_function(F) ->
+ apply(F, As);
+external_func({M,F}, As) ->
+ apply(M, F, As).
+
+
+
+try_catch(doc) ->
+ ["Test try-of-catch-after-end statement"];
+try_catch(suite) ->
+ [];
+try_catch(Config) when is_list(Config) ->
+ %% Match in of with catch
+ ?line check(fun() -> try 1 of 1 -> 2 catch _:_ -> 3 end end,
+ "try 1 of 1 -> 2 catch _:_ -> 3 end.", 2),
+ ?line check(fun() -> try 1 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end end,
+ "try 1 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end.", 2),
+ ?line check(fun() -> try 3 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end end,
+ "try 3 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end.", 4),
+ %% Just after
+ ?line check(fun () -> X = try 1 after put(try_catch, 2) end,
+ {X,get(try_catch)} end,
+ "begin X = try 1 after put(try_catch, 2) end, "
+ "{X,get(try_catch)} end.", {1,2}),
+ %% Match in of with after
+ ?line check(fun() -> X = try 1 of 1 -> 2 after put(try_catch, 3) end,
+ {X,get(try_catch)} end,
+ "begin X = try 1 of 1 -> 2 after put(try_catch, 3) end, "
+ "{X,get(try_catch)} end.", {2,3}),
+ ?line check(fun() -> X = try 1 of 1 -> 2; 3 -> 4
+ after put(try_catch, 5) end,
+ {X,get(try_catch)} end,
+ "begin X = try 1 of 1 -> 2; 3 -> 4 "
+ " after put(try_catch, 5) end, "
+ " {X,get(try_catch)} end.", {2,5}),
+ ?line check(fun() -> X = try 3 of 1 -> 2; 3 -> 4
+ after put(try_catch, 5) end,
+ {X,get(try_catch)} end,
+ "begin X = try 3 of 1 -> 2; 3 -> 4 "
+ " after put(try_catch, 5) end, "
+ " {X,get(try_catch)} end.", {4,5}),
+ %% Nomatch in of
+ ?line error_check("try 1 of 2 -> 3 catch _:_ -> 4 end.",
+ {try_clause,1}),
+ %% Nomatch in of with after
+ ?line check(fun () -> {'EXIT',{{try_clause,1},_}} =
+ begin catch try 1 of 2 -> 3
+ after put(try_catch, 4) end end,
+ get(try_catch) end,
+ "begin {'EXIT',{{try_clause,1},_}} = "
+ " begin catch try 1 of 2 -> 3 "
+ " after put(try_catch, 4) end end, "
+ " get(try_catch) end. ", 4),
+ %% Exception in try
+ ?line check(fun () -> try 1=2 catch error:{badmatch,2} -> 3 end end,
+ "try 1=2 catch error:{badmatch,2} -> 3 end.", 3),
+ ?line check(fun () -> try 1=2 of 3 -> 4
+ catch error:{badmatch,2} -> 5 end end,
+ "try 1=2 of 3 -> 4 "
+ "catch error:{badmatch,2} -> 5 end.", 5),
+ %% Exception in try with after
+ ?line check(fun () -> X = try 1=2
+ catch error:{badmatch,2} -> 3
+ after put(try_catch, 4) end,
+ {X,get(try_catch)} end,
+ "begin X = try 1=2 "
+ " catch error:{badmatch,2} -> 3 "
+ " after put(try_catch, 4) end, "
+ " {X,get(try_catch)} end. ", {3,4}),
+ ?line check(fun () -> X = try 1=2 of 3 -> 4
+ catch error:{badmatch,2} -> 5
+ after put(try_catch, 6) end,
+ {X,get(try_catch)} end,
+ "begin X = try 1=2 of 3 -> 4"
+ " catch error:{badmatch,2} -> 5 "
+ " after put(try_catch, 6) end, "
+ " {X,get(try_catch)} end. ", {5,6}),
+ %% Uncaught exception
+ ?line error_check("try 1=2 catch error:undefined -> 3 end. ",
+ {badmatch,2}),
+ ?line error_check("try 1=2 of 3 -> 4 catch error:undefined -> 5 end. ",
+ {badmatch,2}),
+ %% Uncaught exception with after
+ ?line check(fun () -> {'EXIT',{{badmatch,2},_}} =
+ begin catch try 1=2
+ after put(try_catch, 3) end end,
+ get(try_catch) end,
+ "begin {'EXIT',{{badmatch,2},_}} = "
+ " begin catch try 1=2 "
+ " after put(try_catch, 3) end end, "
+ " get(try_catch) end. ", 3),
+ ?line check(fun () -> {'EXIT',{{badmatch,2},_}} =
+ begin catch try 1=2 of 3 -> 4
+ after put(try_catch, 5) end end,
+ get(try_catch) end,
+ "begin {'EXIT',{{badmatch,2},_}} = "
+ " begin catch try 1=2 of 3 -> 4"
+ " after put(try_catch, 5) end end, "
+ " get(try_catch) end. ", 5),
+ ?line check(fun () -> {'EXIT',{{badmatch,2},_}} =
+ begin catch try 1=2 catch error:undefined -> 3
+ after put(try_catch, 4) end end,
+ get(try_catch) end,
+ "begin {'EXIT',{{badmatch,2},_}} = "
+ " begin catch try 1=2 catch error:undefined -> 3 "
+ " after put(try_catch, 4) end end, "
+ " get(try_catch) end. ", 4),
+ ?line check(fun () -> {'EXIT',{{badmatch,2},_}} =
+ begin catch try 1=2 of 3 -> 4
+ catch error:undefined -> 5
+ after put(try_catch, 6) end end,
+ get(try_catch) end,
+ "begin {'EXIT',{{badmatch,2},_}} = "
+ " begin catch try 1=2 of 3 -> 4 "
+ " catch error:undefined -> 5 "
+ " after put(try_catch, 6) end end, "
+ " get(try_catch) end. ", 6),
+ ok.
+
+
+eval_expr_5(doc) ->
+ ["(OTP-7933)"];
+eval_expr_5(suite) ->
+ [];
+eval_expr_5(Config) when is_list(Config) ->
+ ?line {ok,Tokens ,_} =
+ erl_scan:string("if a+4 == 4 -> yes; true -> no end. "),
+ ?line {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ ?line {value, no, []} = erl_eval:expr(Expr, [], none, none, none),
+ ?line no = erl_eval:expr(Expr, [], none, none, value),
+ try
+ erl_eval:expr(Expr, [], none, none, 4711),
+ ?line function_clause = should_never_reach_here
+ catch
+ error:function_clause ->
+ ok
+ end.
+
+%% Check the string in different contexts: as is; in fun; from compiled code.
+check(F, String, Result) ->
+ check1(F, String, Result),
+ FunString = concat(["fun() -> ", no_final_dot(String), " end(). "]),
+ check1(F, FunString, Result),
+ CompileString = concat(["hd(lists:map(fun(_) -> ", no_final_dot(String),
+ " end, [foo])). "]),
+ check1(F, CompileString, Result).
+
+check1(F, String, Result) ->
+ Result = F(),
+ case catch parse_and_run(String) of
+ {value, Result, _} ->
+ ok;
+ Other ->
+ test_server:fail({eval, Other, Result})
+ end.
+
+check(F, String, Result, BoundVars, LFH, EFH) ->
+ Result = F(),
+ case catch parse_and_run(String, LFH, EFH) of
+ {value, Result, Bs} ->
+ %% We just assume that Bs is an orddict...
+ Keys = orddict:fetch_keys(Bs),
+ case sort(BoundVars) == Keys of
+ true ->
+ ok;
+ false ->
+ test_server:fail({check, BoundVars, Keys})
+ end,
+ ok;
+ Other ->
+ test_server:fail({check, Other, Result})
+ end.
+
+error_check(String, Result) ->
+ case catch parse_and_run(String) of
+ {'EXIT', {Result,_}} ->
+ ok;
+ Other ->
+ test_server:fail({eval, Other, Result})
+ end.
+
+error_check(String, Result, LFH, EFH) ->
+ case catch parse_and_run(String, LFH, EFH) of
+ {'EXIT', {Result,_}} ->
+ ok;
+ Other ->
+ test_server:fail({eval, Other, Result})
+ end.
+
+eval_string(String) ->
+ {value, Result, _} = parse_and_run(String),
+ Result.
+
+parse_and_run(String) ->
+ {ok,Tokens,_} = erl_scan:string(String),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ erl_eval:expr(Expr, []).
+
+parse_and_run(String, LFH, EFH) ->
+ {ok,Tokens,_} = erl_scan:string(String),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ erl_eval:expr(Expr, [], LFH, EFH).
+
+no_final_dot(S) ->
+ case lists:reverse(S) of
+ " ." ++ R -> lists:reverse(R);
+ "." ++ R -> lists:reverse(R);
+ _ -> S
+ end.
diff --git a/lib/stdlib/test/erl_eval_helper.erl b/lib/stdlib/test/erl_eval_helper.erl
new file mode 100644
index 0000000000..7fdbabcb17
--- /dev/null
+++ b/lib/stdlib/test/erl_eval_helper.erl
@@ -0,0 +1,28 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(erl_eval_helper, [Base]).
+
+-export([add/1]).
+
+add(Arg) ->
+ Base+Arg.
+
+
+
diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl
new file mode 100644
index 0000000000..1d621c65df
--- /dev/null
+++ b/lib/stdlib/test/erl_expand_records_SUITE.erl
@@ -0,0 +1,823 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(erl_expand_records_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(privdir, "erl_expand_records_SUITE_priv").
+-define(t, test_server).
+-else.
+-include("test_server.hrl").
+-define(privdir, ?config(priv_dir, Config)).
+-endif.
+
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export([abstract_module/1, attributes/1, expr/1, guard/1,
+ init/1, pattern/1, strict/1, update/1,
+ tickets/1, otp_5915/1, otp_7931/1, otp_5990/1,
+ otp_7078/1, otp_7101/1]).
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog = ?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [abstract_module, attributes, expr, guard, init, pattern,
+ strict, update, tickets].
+
+abstract_module(doc) ->
+ "Compile an abstract module.";
+abstract_module(suite) -> [];
+abstract_module(Config) when is_list(Config) ->
+ %% erl_expand_records does not handle abstract modules. But anyway...
+ File = filename("param.erl", Config),
+ Beam = filename("param.beam", Config),
+ Test = <<"-module(param, [A, B]).
+
+ -export([args/1]).
+
+ args(C) ->
+ X = local(C),
+ Z = new(A, B),
+ {X, Z}.
+
+ local(C) ->
+ module_info(C).
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, param} = compile:file(File, [{outdir,?privdir}]),
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+attributes(doc) ->
+ "Import module and functions.";
+attributes(suite) -> [];
+attributes(Config) when is_list(Config) ->
+ Ts = [
+ <<"-import(erl_expand_records_SUITE).
+ -import(lists, [append/2, reverse/1]).
+
+ -record(r, {a,b}).
+
+ t() ->
+ [2,1] = reverse(append([1],[2])),
+ 3 = length([1,2,3]),
+ 3 = record_info(size, r),
+ [a, b] = record_info(fields, r),
+ [] = erl_expand_records_SUITE:attributes(suite),
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+expr(doc) ->
+ "Some expressions.";
+expr(suite) -> [];
+expr(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {a,b,c}).
+
+ t() ->
+ [1,2] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ R#r.a < 3],
+ [1,2] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin R#r.a < 3 end],
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin is_record(R, r) end],
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin erlang:is_record(R, r) end],
+ ok.
+ ">>,
+ <<"
+ -record(r, {a,b,c}).
+
+ f(X) -> X.
+
+ t() ->
+ A = {$c, 1, 3.14, a, \"hi\", [], [a,b]},
+ R = #r{a = element(6, A), b = #r.b},
+ 3 = R#r.b,
+ <<1:8>> = <<(begin erlang:element(2, A) end):8>>,
+ self() ! {a, message, []},
+ One = 1 = fun f/1(1),
+ 2 = fun(X) -> X end(One + One),
+ 3 = fun exprec_test:f/1(3),
+ 4 = {exprec_test,f}(4),
+ 5 = ''.f(5),
+ L = receive
+ {a,message,L0} ->
+ L0
+ end,
+ case catch a.b.c:foo(bar) of
+ {'EXIT', _} -> ok
+ end,
+ _ = receive %Suppress warning.
+ noop ->
+ 1/(length(L) - 0)
+ after 0 ->
+ ok
+ end,
+ if
+ R#r.c =:= undefined ->
+ ok;
+ true ->
+ not_ok
+ end.
+ ">>
+ ],
+
+ %% The code above should run equally well with and without
+ %% strict record tests.
+ ?line run(Config, Ts, [no_strict_record_tests]),
+ ?line run(Config, Ts, [strict_record_tests]),
+
+ ok.
+
+guard(doc) ->
+ "is_record in guards.";
+guard(suite) -> [];
+guard(Config) when is_list(Config) ->
+ File = filename("guard.erl", Config),
+ Beam = filename("guard.beam", Config),
+ Test = <<"-module(guard, [A, B]).
+
+ -export([t/1]).
+
+ -record(r, {a,b}).
+
+ t(_) when is_record(3, r) ->
+ 1;
+ t(_) when is_record(a, r) ->
+ 2;
+ t(_) when is_record(3.14, r) ->
+ 3;
+ t(_) when is_record([], r) ->
+ 4;
+ t(_) when is_record([a], r) ->
+ 5;
+ t(_) when is_record($a, r) ->
+ 6;
+ t(_) when is_record(\"foo\", r) ->
+ 7;
+ t(_) when is_record(#r.a, r) ->
+ 8;
+ t(_) when is_record(<<\"foo\">>, r) -> % line 23
+ 9;
+ t(_) when is_record(1 + 2, r) ->
+ 10;
+ t(_) when is_record(+ 3, r) ->
+ 11;
+ t(_) ->
+ 12.
+ ">>,
+
+ ?line ok = file:write_file(File, Test),
+ ?line {ok, guard, Ws} = compile:file(File, [return,{outdir,?privdir}]),
+ ?line Warnings = [L || {_File,WL} <- Ws, {L,_M,nomatch_guard} <- WL],
+ ?line [7,9,11,13,15,17,19,21,23,25,27] = Warnings,
+
+ ?line ok = file:delete(File),
+ ?line ok = file:delete(Beam),
+ ok.
+
+init(doc) ->
+ "Wildcard initialisation.";
+init(suite) -> [];
+init(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {a,b,c,d = foo}).
+
+ t() ->
+ R = #r{_ = init, b = b},
+ #r{c = init, b = b, a = init} = R,
+ case R of
+ #r{b = b, _ = init} -> ok;
+ _ -> not_ok
+ end.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+pattern(doc) ->
+ "Some patterns.";
+pattern(suite) -> [];
+pattern(Config) when is_list(Config) ->
+ Ts = [
+ <<"-import(erl_expand_records_SUITE).
+ -import(lists, [append/2, reverse/1]).
+
+ -record(r, {a,b}).
+
+ t() ->
+ 1 = t(#r{}),
+ 2 = t($a),
+ 3 = t(1000),
+ 4 = t({1000}),
+ 5 = t(3),
+ 6 = t(-3.14),
+ 7 = t({4.0}),
+ 8 = t(3.14),
+ 9 = t(\"str\"),
+ 10 = t([]),
+ 11 = t([a|b]),
+ 12 = t(\"string\"),
+ 13 = t({[]}),
+ 14 = t({a,b}),
+ 15 = t({{}}),
+ 16 = t({tuple,tupel}),
+ 17 = t(4),
+ 18 = t(10),
+ 19 = t({a}),
+ 20 = t(<<100:8,220:8>>),
+ 21 = t(#r{a = #r{}}),
+ 22 = t(2),
+ 23 = t(#r{a = #r{}, b = b}),
+ 24 = t(a.b.c),
+ ok.
+
+ t(a.b.c) ->
+ 24;
+ t($a) ->
+ 2;
+ t(3) ->
+ 5;
+ t(3.14) ->
+ 8;
+ t(\"str\") ->
+ 9;
+ t([]) ->
+ 10;
+ t([a|b]) ->
+ 11;
+ t(L) when is_list(L) ->
+ 12;
+ t({L}) when list(L) ->
+ 13;
+ t({a,b}) ->
+ 14;
+ t({T}) when is_tuple(T) ->
+ 15;
+ t(+ 4) ->
+ 17;
+ t(3+7) ->
+ 18;
+ t(<<A:8, (100+120):8>>) when A =:= 100 ->
+ 20;
+ t(#r{a = #r{}, b = undefined}) ->
+ 21;
+ t(#r.a) ->
+ 22;
+ t(A) when is_record(A, r), record(element(2, A), r) ->
+ 23;
+ t(A) when is_record(A, r) ->
+ 1;
+ t(I) when is_integer(I) ->
+ 3;
+ t({I}) when integer(I) ->
+ 4;
+ t({F}) when float(F) ->
+ 7;
+ t({A} = B) when A < B ->
+ 19;
+ t(F) when is_float(F) ->
+ 6;
+ t(T) when tuple(T) ->
+ 16.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+strict(doc) ->
+ "";
+strict(suite) -> [];
+strict(Config) when is_list(Config) ->
+ Ts1 = [
+ <<"-record(r1, {a,b}).
+ -record(r2, {a,b}).
+
+ t() ->
+ A = #r1{a = 1, b = 2},
+ ok = try
+ {1, 2} = {A#r2.a, A#r2.b},
+ not_ok
+ catch error:{badrecord,r2} -> ok
+ end,
+ try
+ case foo of
+ _ when A#r2.a =:= 1 -> not_ok
+ end
+ catch error:_ -> ok
+ end.
+ ">>
+ ],
+ ?line run(Config, Ts1, [strict_record_tests]),
+
+ Ts2 = [
+ <<"-record(r1, {a,b}).
+ -record(r2, {a,b}).
+
+ t() ->
+ A = #r1{a = 1, b = 2},
+ {1, 2} = {A#r2.a, A#r2.b},
+ case foo of
+ _ when A#r2.a =:= 1 -> ok
+ end.
+ ">>
+ ],
+ ?line run(Config, Ts2, [no_strict_record_tests]),
+ ok.
+
+update(doc) ->
+ "Record updates.";
+update(suite) -> [];
+update(Config) when is_list(Config) ->
+ Ts = [
+ <<"-record(r, {a,b,c,d,e,f}).
+
+ t() ->
+ R0 = #r{},
+ R1 = R0#r{a = #r.a, e = {x,y}},
+ 2 = R1#r.a,
+ R2 = R1#r{},
+ true = R1 =:= R2,
+ R3 = R2#r{c = fun(X) -> X end,
+ d = <<\"foo\">>,
+ e = [x,y,z],
+ f = {R0,R1}},
+ R4 = R3#r{a = R3#r{b = #r{}}},
+ true = erlang:is_record((R4#r.a)#r.b, r),
+ #r{a = R0, b = 3, c = 3.14, d = [], e = [[]], f = [{}]} =
+ R4#r{a = R0, b = 3, c = 3.14, d = [], e = [[]], f = [{}]},
+ ok.
+
+ %% Just playing around a bit...
+ t1() ->
+ ((#r{a = (#r{b = #r{}})#r{a = #r{}}})#r{b = #r{}})#r{c = #r{}}.
+
+ t2() ->
+ R0 = #r{},
+ #r{_ = R0#r{a = ok}}.
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+tickets(suite) ->
+ [otp_5915, otp_7931, otp_5990, otp_7078, otp_7101].
+
+otp_5915(doc) ->
+ "Strict record tests in guards.";
+otp_5915(suite) -> [];
+otp_5915(Config) when is_list(Config) ->
+ %% These tests are also run by the compiler's record_SUITE.
+ Ts = [
+ <<"-record(r, {a = 4,b}).
+ -record(r1, {a,b}).
+ -record(r2, {a = #r1{},b,c=length([1,2,3])}).
+ -record(r3, {a = fun(_) -> #r1{} end(1), b}).
+
+ t() ->
+ foo = fun(A) when A#r1.a > A#r1.b -> foo end(#r1{b = 2}),
+ 0 = fun(A) when A#r2.a -> 0 end(#r2{a = true}),
+ 1 = fun(A) when (#r1{a = A})#r1.a > 2 -> 1 end(3),
+ 2 = fun(N) when ((#r2{a = #r{a = 4}, b = length([a,b,c])})#r2.a)#r.a > N ->
+ 2 end(2),
+ 3 = fun(A) when (A#r2.a)#r1.a =:= 3 -> 3 end(#r2{a = #r1{a = 3}}),
+ ok = fun() ->
+ F = fun(A) when record(A#r.a, r1) -> 4;
+ (A) when record(A#r1.a, r1) -> 5
+ end,
+ 5 = F(#r1{a = #r1{}}),
+ 4 = F(#r{a = #r1{}}),
+ ok
+ end(),
+ 3 = fun(A) when record(A#r1.a, r),
+ (A#r1.a)#r.a > 3 -> 3
+ end(#r1{a = #r{a = 4}}),
+ 7 = fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}),
+ [#r1{a = 2,b = 1}] =
+ fun() ->
+ [A || A <- [#r1{a = 1, b = 3},
+ #r2{a = 2,b = 1},
+ #r1{a = 2, b = 1}],
+ A#r1.a >
+ A#r1.b]
+ end(),
+ {[_],b} =
+ fun(L) ->
+ %% A is checked only once:
+ R1 = [{A,B} || A <- L, A#r1.a, B <- L, A#r1.b],
+ A = #r2{a = true},
+ %% A is checked again:
+ B = if A#r1.a -> a; true -> b end,
+ {R1,B}
+ end([#r1{a = true, b = true}]),
+
+ p = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ o = fun(A) when (A#r1.a =:= 2) orelse (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ 3 = fun(A) when A#r1.a > 3,
+ record(A, r1) -> 3
+ end(#r1{a = 5}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r2.a =:= 1) orelse (A#r2.a) -> 2;
+ (A) when (A#r1.a =:= 1) orelse (A#r1.a) -> 1;
+ (A) when (A#r2.a =:= 2) andalso (A#r2.b) -> 3
+ end,
+ 1 = F(#r1{a = 1}),
+ 2 = F(#r2{a = true}),
+ 3 = F(#r2{a = 2, b = true}),
+ ok
+ end(),
+
+ b = fun(A) when false or not (A#r.a =:= 1) -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+ b = fun(A) when not (A#r.a =:= 1) or false -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+
+ ok = fun() ->
+ F = fun(A) when not (A#r.a =:= 1) -> yes;
+ (_) -> no
+ end,
+ no = F(#r1{a = 2}),
+ yes = F(#r{a = 2}),
+ no = F(#r{a = 1}),
+ ok
+ end(),
+
+ a = fun(A) when record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 ->a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when erlang:is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+
+ nop = fun(A) when (is_record(A, r1) and (A#r1.a > 3)) or (A#r2.a < 1) ->
+ japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+ nop = fun(A) when (A#r1.a > 3) or (A#r2.a < 1) -> japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end,
+ p = F(#r2{a = 1}),
+ p = F(#r1{a = 2}),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(A) when fail, A#r1.a; A#r1.a -> ab;
+ (_) -> bu
+ end,
+ ab = F(#r1{a = true}),
+ bu = F(#r2{a = true}),
+ ok
+ end(),
+
+ both = fun(A) when A#r.a, A#r.b -> both
+ end(#r{a = true, b = true}),
+
+ ok = fun() ->
+ F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
+ or (B#r2.b) or (A#r1.b) -> true;
+ (_, _) -> false
+ end,
+ true = F(#r1{a = false, b = false}, #r2{a = false, b = true}),
+ false = F(#r1{a = true, b = true}, #r1{a = false, b = true}),
+ ok
+ end(),
+
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+otp_7931(doc) ->
+ "Test optimization of record accesses and is_record/3 tests in guards";
+otp_7931(suite) -> [];
+otp_7931(Config) when is_list(Config) ->
+ Ts = [
+ <<"-record(r, {a = 4,b}).
+ -record(r1, {a,b}).
+ -record(r2, {a = #r1{},b,c=length([1,2,3])}).
+ -record(r3, {a = fun(_) -> #r1{} end(1), b}).
+
+ t() ->
+ ok = fun() ->
+ F = fun(F, [H,H|T]) when is_record(H, r) ->
+ [H|F(F, T)];
+ (F, [H|T]) when is_record(H, r) ->
+ [H|F(F, T)];
+ (_, []) -> []
+ end,
+ [#r{a=4,b=7},#r{a=1,b=42}] =
+ F(F, [#r{a=4,b=7},#r{a=4,b=7},#r{a=1,b=42}]),
+ {'EXIT',_} = (catch F(F, [#r1{}])),
+ ok
+ end(),
+
+ true = fun() ->
+ R = #r{},
+ if is_record(R, r) -> true; true -> false end
+ end(),
+
+ ok = fun() ->
+ F = fun(true, B) when B#r1.a -> ok;
+ (false, _) -> error
+ end,
+ ok = F(true, #r1{a=true}),
+ error = F(false, anything_goes),
+ {'EXIT',_} = (catch F(true, #r1{})),
+ {'EXIT',_} = (catch F(true, #r{})),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun([{a,R}=T]) when R#r.a =:= 42 ->
+ {ok,tuple_size(T)};
+ ([{a,R}=T]) when R#r1.a =:= 7 ->
+ {ok,tuple_size(T)};
+ (_) -> error
+ end,
+ {ok,2} = F([{a,#r{a=42}}]),
+ {ok,2} = F([{a,#r1{a=7}}]),
+ error = F([{a,#r1{}}]),
+ error = F({a,b,c}),
+ error = F([]),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(X, Y, Z) when is_record(X, r1) andalso
+ (is_record(Y, r2) orelse
+ is_record(Z, r3)) -> true;
+ (_, _, _) -> false
+ end,
+ true = F(#r1{}, #r2{}, #r3{}),
+ true = F(#r1{}, #r2{}, blurf),
+ true = F(#r1{}, blurf, #r3{}),
+ false = F(#r1{}, blurf, blurf),
+ false = F(blurf, #r2{}, #r3{}),
+ false = F(blurf, #r2{}, blurf),
+ false = F(blurf, blurf, #r3{}),
+ false = F(blurf, blurf, blurf),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(R=#r{a=42}) when R#r.b =:= 7 ->
+ {ok,R};
+ (_) -> error
+ end,
+ {ok,#r{a=42,b=7}} = F(#r{a=42,b=7}),
+ error = F(#r{}),
+ error = F([a,b,c]),
+ ok
+ end(),
+
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+otp_5990(doc) ->
+ "OTP-5990. {erlang,is_record}.";
+otp_5990(suite) -> [];
+otp_5990(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {a,b,c}).
+
+ t() ->
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin {erlang,is_record}(R, r) end],
+ [1,2,3] = [R#r.a || R <- [#r{a = 1}, #r{a = 2}, #r{a = 3}],
+ begin {erlang,is_record}(R, r) end],
+ ok.
+ ">>,
+
+ <<"
+ -record('OrdSet', {orddata = {},
+ ordtype = {}}).
+
+ to_sets(S) when tuple(S#'OrdSet'.ordtype) ->
+ ok.
+
+ lc(S) ->
+ [X || X <- [S], tuple(X#'OrdSet'.ordtype)].
+
+ t() ->
+ S = #'OrdSet'{},
+ ok = to_sets(S),
+ [S] = lc(S),
+ ok.
+ ">>
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+
+otp_7078(doc) ->
+ "OTP-7078. Record update: missing test.";
+otp_7078(suite) -> [];
+otp_7078(Config) when is_list(Config) ->
+ Ts = [
+ <<"
+ -record(r, {f}).
+ -record(r2, {}).
+
+ t() ->
+ {'EXIT',_} = (catch (#r2{})#r{}),
+ {'EXIT',_} = (catch (#r2{})#r{f = 2}),
+ ok.
+ ">>,
+
+ <<"
+ -record(r, {f}).
+
+ maker(F) ->
+ put(a, get(a)+1),
+ #r{f = F}.
+
+ t() ->
+ put(a, 0),
+ (maker(2))#r{},
+ 1 = get(a),
+ ok.
+ ">>
+
+ ],
+ ?line run(Config, Ts, [strict_record_tests]),
+ ok.
+
+-record(otp_7101, {a,b,c=[],d=[],e=[]}).
+
+otp_7101(doc) ->
+ "OTP-7101. Record update: more than one call to setelement/3.";
+otp_7101(suite) -> [];
+otp_7101(Config) when is_list(Config) ->
+ Rec = #otp_7101{},
+
+ %% Spawn a tracer process to count the number of setelement/3 calls.
+ %% The tracer will forward all trace messages to us.
+ Self = self(),
+ Tracer = spawn_link(fun() -> otp_7101_tracer(Self, 0) end),
+ ?line 1 = erlang:trace_pattern({erlang,setelement,3}, true),
+ ?line erlang:trace(self(), true, [{tracer,Tracer},call]),
+
+ %% Update the record.
+ ?line #otp_7101{a=2,b=1,c=[],d=[],e=[]} = otp_7101_update1(Rec),
+ ?line #otp_7101{a=1,b=2,c=[],d=[],e=[]} = otp_7101_update2(Rec),
+ ?line #otp_7101{a=2,b=1,c=[],d=[],e=[]} = otp_7101_update3(Rec),
+ ?line #otp_7101{a=1,b=2,c=[],d=[],e=[]} = otp_7101_update4(Rec),
+
+ %% Verify that setelement/3 was called the same number of times as
+ %% the number of record updates.
+ ?line Ref = erlang:trace_delivered(Self),
+ receive
+ {trace_delivered, Self, Ref} ->
+ Tracer ! done
+ end,
+ ?line 1 = erlang:trace_pattern({erlang,setelement,3}, false),
+ receive
+ 4 ->
+ ok;
+ Other ->
+ ?line ?t:fail({unexpected,Other})
+ end.
+
+otp_7101_tracer(Parent, N) ->
+ receive
+ {trace,Parent,call,{erlang,setelement,[_,_,_]}} ->
+ otp_7101_tracer(Parent, N+1);
+ done ->
+ Parent ! N
+ end.
+
+otp_7101_update1(R) ->
+ R#otp_7101{b=1,
+ a=2}.
+
+otp_7101_update2(R) ->
+ R#otp_7101{a=1,
+ b=2}.
+
+otp_7101_update3(R) ->
+ R#otp_7101{b=1,a=2}.
+
+otp_7101_update4(R) ->
+ R#otp_7101{a=1,b=2}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+run(Config, Tests) ->
+ run(Config, Tests, []).
+
+run(Config, Tests, Opts) ->
+ F = fun(P) ->
+ {SourceFile, Mod} = compile_file_mod(Config),
+ _ = compile_file(Config, P, Opts),
+ AbsFile = filename:rootname(SourceFile, ".erl"),
+ code:purge(Mod),
+ code:load_abs(AbsFile, Mod),
+%io:format("run~n"),
+ case catch Mod:t() of
+ {'EXIT', _Reason} = Error ->
+ ?t:format("failed, got ~p~n", [Error]),
+ fail();
+ ok ->
+ ok
+ end
+ end,
+ lists:foreach(F, Tests).
+
+%% Compiles a test module and returns the list of errors and warnings.
+
+compile_file(Config, Test0, Opts0) ->
+ {File, _Mod} = compile_file_mod(Config),
+ Filename = 'exprec_test.erl',
+ Test = list_to_binary(["-module(exprec_test). "
+ "-compile(export_all). ",
+ Test0]),
+ File = filename(Filename, Config),
+ Opts = [export_all,return,{outdir,?privdir}|Opts0],
+ ok = file:write_file(File, Test),
+ {ok, _M, Ws} = compile:file(File, Opts),
+ warnings(File, Ws).
+
+compile_file_mod(Config) ->
+ {filename('exprec_test.erl', Config), exprec_test}.
+
+filename(Name, Config) when is_atom(Name) ->
+ filename(atom_to_list(Name), Config);
+filename(Name, Config) ->
+ filename:join(?privdir, Name).
+
+warnings(File, Ws) ->
+ case lists:append([W || {F, W} <- Ws, F =:= File]) of
+ [] -> [];
+ L -> {warnings, L}
+ end.
+
+fail() ->
+ io:format("failed~n"),
+ ?t:fail().
diff --git a/lib/stdlib/test/erl_internal_SUITE.erl b/lib/stdlib/test/erl_internal_SUITE.erl
new file mode 100644
index 0000000000..8f675c94ec
--- /dev/null
+++ b/lib/stdlib/test/erl_internal_SUITE.erl
@@ -0,0 +1,69 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_internal_SUITE).
+
+-export([all/1]).
+
+-export([behav/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-include("test_server.hrl").
+
+all(suite) -> [behav].
+
+-define(default_timeout, ?t:minutes(2)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+behav(suite) -> [];
+behav(doc) ->
+ ["Check that the behaviour callbacks are correctly defined"];
+behav(_) ->
+ ?line check_behav_list([{start,2}, {stop,1}],
+ application:behaviour_info(callbacks)),
+ ?line check_behav_list([{init,1}, {handle_call,3}, {handle_cast,2},
+ {handle_info,2}, {terminate,2}, {code_change,3}],
+ gen_server:behaviour_info(callbacks)),
+ ?line check_behav_list([{init,1}, {handle_event,3}, {handle_sync_event,4},
+ {handle_info,3}, {terminate,3}, {code_change,4}],
+ gen_fsm:behaviour_info(callbacks)),
+ ?line check_behav_list([{init,1}, {handle_event,2}, {handle_call,2},
+ {handle_info,2}, {terminate,2}, {code_change,3}],
+ gen_event:behaviour_info(callbacks)),
+ ?line check_behav_list( [{init,1}, {terminate,2}],
+ supervisor_bridge:behaviour_info(callbacks)),
+ ?line check_behav_list([{init,1}],
+ supervisor:behaviour_info(callbacks)),
+ ok.
+
+check_behav_list([], []) -> ok;
+check_behav_list([L | L1], L2) ->
+ ?line true = lists:member(L, L2),
+ ?line L3 = lists:delete(L, L2),
+ check_behav_list(L1, L3).
+
+
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
new file mode 100644
index 0000000000..bfbd7b3dc1
--- /dev/null
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -0,0 +1,2783 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_lint_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(datadir, "erl_lint_SUITE_data").
+-define(privdir, "erl_lint_SUITE_priv").
+-define(t, test_server).
+-else.
+-include("test_server.hrl").
+-define(datadir, ?config(data_dir, Conf)).
+-define(privdir, ?config(priv_dir, Conf)).
+-endif.
+
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export([unused_vars_warn/1,
+ unused_vars_warn_basic/1,
+ unused_vars_warn_lc/1,
+ unused_vars_warn_rec/1,
+ unused_vars_warn_fun/1,
+ unused_vars_OTP_4858/1,
+ export_vars_warn/1,
+ shadow_vars/1,
+ unused_import/1,
+ unused_function/1,
+ unsafe_vars/1,unsafe_vars2/1,
+ unsafe_vars_try/1,
+ guard/1, otp_4886/1, otp_4988/1, otp_5091/1, otp_5276/1, otp_5338/1,
+ otp_5362/1, otp_5371/1, otp_7227/1, otp_5494/1, otp_5644/1, otp_5878/1,
+ otp_5917/1, otp_6585/1, otp_6885/1, export_all/1,
+ bif_clash/1,
+ behaviour_basic/1, behaviour_multiple/1,
+ otp_7550/1,
+ otp_8051/1,
+ format_warn/1,
+ on_load/1, on_load_successful/1, on_load_failing/1
+ ]).
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog = ?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [unused_vars_warn, export_vars_warn,
+ shadow_vars, unused_import, unused_function,
+ unsafe_vars, unsafe_vars2, unsafe_vars_try,
+ guard, otp_4886, otp_4988, otp_5091, otp_5276, otp_5338,
+ otp_5362, otp_5371, otp_7227, otp_5494, otp_5644, otp_5878, otp_5917, otp_6585,
+ otp_6885, export_all, bif_clash,
+ behaviour_basic, behaviour_multiple, otp_7550, otp_8051, format_warn,
+ on_load].
+
+unused_vars_warn(suite) ->
+ [unused_vars_warn_basic, unused_vars_warn_lc, unused_vars_warn_rec,
+ unused_vars_warn_fun, unused_vars_OTP_4858].
+
+unused_vars_warn_basic(doc) ->
+ "Warnings for unused variables in some simple cases.";
+unused_vars_warn_basic(suite) -> [];
+unused_vars_warn_basic(Config) when is_list(Config) ->
+ Ts = [{basic1,
+ <<"f(F) -> % F unused.
+ ok.
+
+ f(F, F) ->
+ ok.
+
+ g(_X) ->
+ y.
+
+ h(P) ->
+ P.
+
+ x(N) ->
+ case a:b() of
+ [N|Y] -> % Y unused.
+ ok
+ end.
+
+ y(N, L) ->
+ lists:map(fun(T) -> T*N end, L).
+
+ z(N, L) -> % N unused
+ lists:map(fun(N, T) -> T*N end, L). % N shadowed.
+
+
+ c(A) ->
+ case A of
+ 1 -> B = []; % B unused.
+ 2 -> B = []; % B unused.
+ 3 -> B = f, B
+ end.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{1,erl_lint,{unused_var,'F'}},
+ {15,erl_lint,{unused_var,'Y'}},
+ {22,erl_lint,{unused_var,'N'}},
+ {23,erl_lint,{shadowed_var,'N','fun'}},
+ {28,erl_lint,{unused_var,'B'}},
+ {29,erl_lint,{unused_var,'B'}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+unused_vars_warn_lc(doc) ->
+ "Warnings for unused variables in list comprehensions.";
+unused_vars_warn_lc(suite) -> [];
+unused_vars_warn_lc(Config) when is_list(Config) ->
+ Ts = [{lc1,
+ <<"bin([X]) ->
+ [A || <<A:X>> <- []]; % X used, not shadowed.
+ bin({X}) ->
+ [X || <<X:X>> <- []]; % X used, and shadowed.
+ bin({X,Y,Z}) ->
+ [{A,B} || <<A:X>> <- Z, <<B:Y>> <- Z];
+ bin([X,Y,Z]) -> % Y unused.
+ [C || <<V:X>> <- Z, <<B:V>> <- Z, <<C:B>> <- Z].
+ ">>,
+ [warn_unused_vars],
+ {warnings, [{4,erl_lint,{shadowed_var,'X',generate}},
+ {7,erl_lint,{unused_var,'Y'}}]}},
+
+ {lc2,
+ <<"bin([X]) ->
+ [A || <<A:X>> <- []]; % X used, not shadowed.
+ bin({X}) ->
+ [X || <<X:X>> <- []]; % X used, and shadowed.
+ bin({X,Y,Z}) ->
+ [{A,B} || <<A:X>> <- Z, <<B:Y>> <- Z];
+ bin([X,Y,Z]) -> % Y unused.
+ [C || <<V:X>> <- Z, <<B:V>> <- Z, <<C:B>> <- Z].
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{4,erl_lint,{shadowed_var,'X',generate}},
+ {7,erl_lint,{unused_var,'Y'}}]}},
+
+ {lc3,
+ <<"a([A]) ->
+ B = foo,
+ [{C,B} || {C,_} <- A];
+ a({A}) ->
+ B = foo,
+ [C || {C,_} <- [B,A]];
+ a({A,A}) ->
+ B = foo,
+ [C || {C,_} <- B, B < A].
+ ">>,
+ [warn_unused_vars],
+ []},
+
+ {lc4,
+ <<"b(A) ->
+ B = foo, % B unused.
+ [C || {C,_} <- A].
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{2,erl_lint,{unused_var,'B'}}]}},
+
+ {lc5,
+ <<"c(A) ->
+ B = foo,
+ [C || {C,_} <- A],
+ B.
+ ">>,
+ [warn_unused_vars],
+ []},
+
+ {lc6,
+ <<"d(A) ->
+ B = foo,
+ [{A,B} || {Id,_} <- A]. % Id unused.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{3,erl_lint,{unused_var,'Id'}}]}},
+
+ {lc7,
+ <<"e(A) ->
+ B = foo, % B unused.
+ [B || B <- A]. % B shadowed.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{2,erl_lint,{unused_var,'B'}},
+ {3,erl_lint,{shadowed_var,'B',generate}}]}},
+
+ {lc8,
+ <<"f(A) ->
+ B = foo,
+ [B || B <- A], % B shadowed.
+ B.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{3,erl_lint,{shadowed_var,'B',generate}}]}},
+
+ {lc9,
+ <<"g(A) ->
+ B = foo, % B unused.
+ [A || B <- A]. % B shadowed, B unused.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{2,erl_lint,{unused_var,'B'}},
+ {3,erl_lint,{unused_var,'B'}},
+ {3,erl_lint,{shadowed_var,'B',generate}}]}},
+
+ {lc10,
+ <<"h(A) ->
+ B = foo,
+ [A || B <- A], % B shadowed, B unused.
+ B.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{3,erl_lint,{unused_var,'B'}},
+ {3,erl_lint,{shadowed_var,'B',generate}}]}},
+
+ {lc11,
+ <<"i(X) ->
+ [Z || Z <- X, % Z unused.
+ Z = X <- [foo]]. % X and Z shadowed. X unused!
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{2,erl_lint,{unused_var,'Z'}},
+ {3,erl_lint,{unused_var,'X'}},
+ {3,erl_lint,{shadowed_var,'X',generate}},
+ {3,erl_lint,{shadowed_var,'Z',generate}}]}},
+
+ {lc12,
+ <<"j({X}) ->
+ [Z || Z <- X, % Z unused.
+ Z <- X = [[1,2,3]], % Z shadowed. Z unused.
+ Z <- X, % Z shadowed. Z unused.
+ Z <- X]; % Z shadowed.
+ j(X) ->
+ [foo || X <- X, % X shadowed.
+ X <- % X shadowed. X unused.
+ X =
+ Y = [[1,2,3]], % Y unused.
+ X <- [], % X shadowed.
+ X <- X]. % X shadowed. X unused.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{2,erl_lint,{unused_var,'Z'}},
+ {3,erl_lint,{unused_var,'Z'}},
+ {3,erl_lint,{shadowed_var,'Z',generate}},
+ {4,erl_lint,{unused_var,'Z'}},
+ {4,erl_lint,{shadowed_var,'Z',generate}},
+ {5,erl_lint,{shadowed_var,'Z',generate}},
+ {7,erl_lint,{shadowed_var,'X',generate}},
+ {8,erl_lint,{unused_var,'X'}},
+ {8,erl_lint,{shadowed_var,'X',generate}},
+ {10,erl_lint,{unused_var,'Y'}},
+ {11,erl_lint,{shadowed_var,'X',generate}},
+ {12,erl_lint,{unused_var,'X'}},
+ {12,erl_lint,{shadowed_var,'X',generate}}]}},
+
+ {lc13,
+ <<"k(X) ->
+ [Z || Z <- Y = X]; % Y unused.
+ k(X) ->
+ [Z || Z <- X = Y = X]; % Y unused!
+ k(X) ->
+ [Z || Z <- begin X = Y = X, Y end];
+ k(X) ->
+ [{Y,W} || W <- Y = X]; % Y unbound
+ k(X) ->
+ [Z || Z <- (Y = X), % Y unused.
+ Y > X]; % Y unbound.
+ k(X) ->
+ [Y || Y = X > 3, Z = X]; % Z unused.
+ k(X) ->
+ [Z || Y = X > 3, Z = X]. % Y unused.
+ ">>,
+ [warn_unused_vars],
+ {error,[{8,erl_lint,{unbound_var,'Y'}},
+ {11,erl_lint,{unbound_var,'Y'}}],
+ [{2,erl_lint,{unused_var,'Y'}},
+ {4,erl_lint,{unused_var,'Y'}},
+ {8,erl_lint,{unused_var,'Y'}},
+ {10,erl_lint,{unused_var,'Y'}},
+ {13,erl_lint,{unused_var,'Z'}},
+ {15,erl_lint,{unused_var,'Y'}}]}},
+
+ {lc14,
+ <<"lc2() ->
+ Z = [[1],[2],[3]],
+ [X || Z <- Z, % Z shadowed.
+ X <- Z].
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{3,erl_lint,{shadowed_var,'Z',generate}}]}},
+
+ {lc15,
+ <<"lc3() ->
+ Z = [1,2,3],
+ [X || X <- Z,
+ Z <- Z]. % Z shadowed. Z unused.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{4,erl_lint,{unused_var,'Z'}},
+ {4,erl_lint,{shadowed_var,'Z',generate}}]}},
+
+ {lc16,
+ <<"bin(Z) ->
+ case bar of
+ true ->
+ U = 2;
+ false ->
+ true
+ end,
+ case bar of
+ true ->
+ X = 2;
+ false ->
+ X = 3
+ end,
+ case foo of
+ true ->
+ Y = 3; % Y unused.
+ false ->
+ 4
+ end,
+ case foo of
+ 1 ->
+ U; % U unsafe.
+ 2 ->
+ [Z || <<U:X>> <- Z]; % (X exported.) U unused.
+ 3 ->
+ [Z || <<U:X>> <- Z], % (X exported.) U unused.
+ U % U unsafe.
+ end.
+ ">>,
+ [warn_unused_vars],
+ {error,[{22,erl_lint,{unsafe_var,'U',{'case',2}}},
+ {27,erl_lint,{unsafe_var,'U',{'case',2}}}],
+ [{16,erl_lint,{unused_var,'Y'}},
+ % {24,erl_lint,{exported_var,'X',{'case',8}}},
+ {24,erl_lint,{unused_var,'U'}},
+ % {26,erl_lint,{exported_var,'X',{'case',8}}},
+ {26,erl_lint,{unused_var,'U'}}]}},
+
+ {lc17,
+ <<"bin(Z) ->
+ %% This used to pass erl_lint...
+ case bar of
+ true ->
+ U = 2;
+ false ->
+ true
+ end,
+ case bar of
+ true ->
+ X = 2;
+ false ->
+ X = 3
+ end,
+ case foo of
+ true ->
+ Y = 3; % Y unused.
+ false ->
+ 4
+ end,
+ [Z || <<U:X>> <- Z], % (X exported.) U unused.
+ U. % U unsafe.
+ ">>,
+ [warn_unused_vars],
+ {error,[{22,erl_lint,{unsafe_var,'U',{'case',3}}}],
+ [{17,erl_lint,{unused_var,'Y'}},
+ % {21,erl_lint,{exported_var,'X',{'case',9}}},
+ {21,erl_lint,{unused_var,'U'}}]}},
+
+ {lc18,
+ <<"bin(Z) ->
+ case bar of
+ true ->
+ U = 2;
+ false ->
+ true
+ end,
+ case bar of
+ true ->
+ X = 2;
+ false ->
+ X = 3
+ end,
+ case foo of
+ true ->
+ Y = 3;
+ false ->
+ 4
+ end,
+ [B || <<U: % U unused
+ U>> <- X, <<B:Y>> <- Z]. % U unsafe. Y unsafe.
+ % U shadowed. (X exported.)
+ ">>,
+ [warn_unused_vars],
+ {error,[{21,erl_lint,{unsafe_var,'U',{'case',2}}},
+ {21,erl_lint,{unsafe_var,'Y',{'case',14}}}],
+ [{20,erl_lint,{unused_var,'U'}}
+ % ,{21,erl_lint,{exported_var,'X',{'case',8}}}
+ % ,{21,erl_lint,{shadowed_var,'U',generate}}
+ ]}},
+
+ {lc19,
+ <<"p({B,C}) ->
+ <<A:B,A:C>> = <<17:32>>;
+ p(B) ->
+ <<A:B>> = <<17:32>>. % A unused.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{4,erl_lint,{unused_var,'A'}}]}},
+
+ {lc20,
+ <<"c({I1,I2}) ->
+ if
+ <<I1:I2>> == <<>> ->
+ foo
+ end;
+ c([C1,C2]) -> % C1 unused.
+ case foo of
+ <<C2:C2,
+ C3:C2>> -> % C3 unused.
+ bar
+ end.
+
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{6,erl_lint,{unused_var,'C1'}},
+ {7,sys_core_fold,no_clause_match},
+ {9,erl_lint,{unused_var,'C3'}}]}},
+
+ {lc21,
+ <<"t() ->
+ S = 8,
+ case <<3:8>> of
+ <<X:S>> ->
+ X;
+ <<S:X>> -> % X unbound
+ foo
+ end;
+ t() ->
+ S = 8,
+ case <<3:8>> of
+ <<S:S>> ->
+ S;
+ <<Q:32>> -> % Q unused.
+ foo
+ end.
+ ">>,
+ [warn_unused_vars],
+ {error,[{6,erl_lint,{unbound_var,'X'}}],
+ [{14,erl_lint,{unused_var,'Q'}}]}}
+
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+
+unused_vars_warn_rec(doc) ->
+ "Warnings for unused variables in records.";
+unused_vars_warn_rec(suite) -> [];
+unused_vars_warn_rec(Config) when is_list(Config) ->
+ Ts = [{rec1, % An example provided by Bjorn.
+ <<"-record(edge,
+ {ltpr,
+ ltsu,
+ rtpr,
+ rtsu
+ }).
+
+ f1(#edge{ltpr = A, ltsu = A}) ->
+ true;
+ f1({Q, Q}) ->
+ true.
+
+ f2(Edge, Etab) ->
+ case gb_trees:lookup(Edge, Etab) of
+ {value,#edge{ltpr=Same,ltsu=Same}} -> ok;
+ {value,_} -> error
+ end.
+
+ bar(Edge, Etab) ->
+ case gb_trees:lookup(Edge, Etab) of
+ {Same,Same} -> ok;
+ {value,#edge{ltpr=Same}} -> ok; % Same unused.
+ {value,_} -> error
+ end.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{22,erl_lint,{unused_var,'Same'}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+unused_vars_warn_fun(doc) ->
+ "Warnings for unused variables in records.";
+unused_vars_warn_fun(suite) -> [];
+unused_vars_warn_fun(Config) when is_list(Config) ->
+ Ts = [{fun1,
+ <<"a({A,B}) -> % A unused.
+ fun(A) -> B end; % A shadowed. A unused.
+ a([A,B]) ->
+ fun(<<A:B>>, % A shadowed. A unused.
+ <<Q:A>>) -> foo % Q unused.
+ end;
+ a({A,B,C,D,E}) ->
+ fun(E) when C == <<A:A>>, <<17:B>> == D -> % E shadowed. E unused.
+ foo
+ end,
+ E;
+ a([A,B,C,D,E]) -> % E unused.
+ fun() ->
+ (C == <<A:A>>) and (<<17:B>> == D)
+ end.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{1,erl_lint,{unused_var,'A'}},
+ {2,erl_lint,{unused_var,'A'}},
+ {2,erl_lint,{shadowed_var,'A','fun'}},
+ {4,erl_lint,{unused_var,'A'}},
+ {4,erl_lint,{shadowed_var,'A','fun'}},
+ {5,erl_lint,{unused_var,'Q'}},
+ {8,erl_lint,{unused_var,'E'}},
+ {8,erl_lint,{shadowed_var,'E','fun'}},
+ {8,sys_core_fold,useless_building},
+ {12,erl_lint,{unused_var,'E'}}]}},
+
+ {fun2,
+ <<"u() ->
+ case foo of
+ true ->
+ U = 2;
+ false ->
+ true
+ end,
+ fun(U) -> foo end, % U unused.
+ U; % U unsafe.
+ u() ->
+ case foo of
+ true ->
+ U = 2;
+ false ->
+ U = 3
+ end,
+ fun(U) -> foo end, % U shadowed. U unused.
+ U;
+ u() ->
+ case foo of
+ true ->
+ U = 2; % U unused.
+ false ->
+ U = 3 % U unused.
+ end,
+ fun(U) -> foo end. % U shadowed. U unused.
+ ">>,
+ [warn_unused_vars],
+ {error,[{9,erl_lint,{unsafe_var,'U',{'case',2}}}],
+ [{8,erl_lint,{unused_var,'U'}},
+ {17,erl_lint,{unused_var,'U'}},
+ {17,erl_lint,{shadowed_var,'U','fun'}},
+ {22,erl_lint,{unused_var,'U'}},
+ {24,erl_lint,{unused_var,'U'}},
+ {26,erl_lint,{unused_var,'U'}},
+ {26,erl_lint,{shadowed_var,'U','fun'}}]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+unused_vars_OTP_4858(doc) ->
+ "Bit syntax, binsize variable used in the same matching.";
+unused_vars_OTP_4858(suite) -> [];
+unused_vars_OTP_4858(Config) when is_list(Config) ->
+ Ts = [{otp_4858,
+ <<"objs(<<Size:4/unit:8, B:Size/binary>>) ->
+ B.
+
+ fel(<<Size:4/unit:8, B:BadSize/binary>>) -> % BadSize unbound.
+ BadSize. % B, Size unused.
+
+ r9c_highlight() -> % B, Rest unused.
+ <<Size, B:Size/binary,Rest/binary>> = <<2,\"AB\",3,\"CDE\">>.
+ ">>,
+ [warn_unused_vars],
+ {error,[{4,erl_lint,{unbound_var,'BadSize'}}],
+ [{4,erl_lint,{unused_var,'B'}},
+ {4,erl_lint,{unused_var,'Size'}},
+ {8,erl_lint,{unused_var,'B'}},
+ {8,erl_lint,{unused_var,'Rest'}}]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+export_vars_warn(doc) ->
+ "Warnings for exported variables";
+export_vars_warn(suite) -> [];
+export_vars_warn(Config) when is_list(Config) ->
+ Ts = [{exp1,
+ <<"u() ->
+ case foo of
+ 1 ->
+ A = 1,
+ B = 2,
+ W = 3, % W unused.
+ Z = 3; % Z unused.
+ 2 ->
+ B = 2,
+ Z = 4 % Z unused.
+ end,
+ case bar of
+ true ->
+ A = 17, % A unsafe.
+ X = 3, % X unused.
+ U = 2,
+ U;
+ false ->
+ B = 19, % B exported.
+ U = 3; % U unused.
+ foo ->
+ X = 3,
+ X;
+ bar ->
+ X = 9, % X unused.
+ U = 14 % U unused.
+ end.
+ ">>,
+ [warn_unused_vars],
+ {error,[{14,erl_lint,{unsafe_var,'A',{'case',2}}}],
+ [{6,erl_lint,{unused_var,'W'}},
+ {7,erl_lint,{unused_var,'Z'}},
+ {10,erl_lint,{unused_var,'Z'}},
+ {15,erl_lint,{unused_var,'X'}},
+ {19,erl_lint,{exported_var,'B',{'case',2}}},
+ {20,erl_lint,{unused_var,'U'}},
+ {25,erl_lint,{unused_var,'X'}},
+ {26,erl_lint,{unused_var,'U'}}]}},
+
+ {exp2,
+ <<"bin(A) ->
+ receive
+ M ->
+ X = M,
+ Y = M,
+ Z = M
+ end,
+ [B || <<B:X>> <- A], % X exported.
+ Y = B, % Y exported. B unbound.
+ [B || B <- Z]. % Z exported. B shadowed.
+ ">>,
+ [warn_export_vars],
+ {error,[{9,erl_lint,{unbound_var,'B'}}],
+ [{8,erl_lint,{exported_var,'X',{'receive',2}}},
+ {9,erl_lint,{exported_var,'Y',{'receive',2}}},
+ {10,erl_lint,{exported_var,'Z',{'receive',2}}},
+ {10,erl_lint,{shadowed_var,'B',generate}}]}},
+
+ {exp3,
+ <<"bin(A) ->
+ receive
+ M ->
+ X = M,
+ Y = M,
+ Z = M
+ end,
+ [B || <<B:X>> <- A], % (X exported.)
+ Y = B, % Y exported. B unbound.
+ [B || B <- Z]. % (Z exported.) B shadowed.
+ ">>,
+ [],
+ {error,[{9,erl_lint,{unbound_var,'B'}}],
+ [{9,erl_lint,{exported_var,'Y',{'receive',2}}},
+ {10,erl_lint,{shadowed_var,'B',generate}}]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+shadow_vars(doc) ->
+ "Shadowed variables are tested in other places, but here we test "
+ "that the warning can be turned off.";
+shadow_vars(suite) -> [];
+shadow_vars(Config) when is_list(Config) ->
+ Ts = [{shadow1,
+ <<"bin(A) ->
+ receive
+ M ->
+ X = M,
+ Y = M,
+ Z = M
+ end,
+ [B || <<B:X>> <- A],
+ Y = B,
+ [B || B <- Z]. % B shadowed.
+ ">>,
+ [nowarn_shadow_vars],
+ {error,[{9,erl_lint,{unbound_var,'B'}}],
+ [{9,erl_lint,{exported_var,'Y',{'receive',2}}}]}}],
+
+ ?line [] = run(Config, Ts),
+ ok.
+
+unused_import(doc) ->
+ "Test that the 'warn_unused_import' option works.";
+unused_import(suite) -> [];
+unused_import(Config) when is_list(Config) ->
+ Ts = [{imp1,
+ <<"-import(lists, [map/2,foldl/3]).
+ t(L) ->
+ map(fun(X) -> 2*X end, L).
+ ">>,
+ [warn_unused_import],
+ {warnings,[{1,erl_lint,{unused_import,{{foldl,3},lists}}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+unused_function(doc) ->
+ "Test warnings for unused functions.";
+unused_function(suite) -> [];
+unused_function(Config) when is_list(Config) ->
+ Ts = [{func1,
+ <<"-export([t/1]).
+ t(L) ->
+ lists:map(fun(X) -> 2*X end, L).
+
+ fact(N) ->
+ fact_1(N, 1).
+
+ fact_1(1, P) -> P;
+ fact_1(N, P) -> fact_1(N-1, P*N).
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ {warnings,[{5,erl_lint,{unused_function,{fact,1}}},
+ {8,erl_lint,{unused_function,{fact_1,2}}}]}},
+
+ %% Turn off warnings for unused functions.
+ {func2,
+ <<"-export([t/1]).
+ t(L) ->
+ lists:map(fun(X) -> 2*X end, L).
+
+ b(X) ->
+ 32*X.
+ ">>,
+ {[nowarn_unused_function]}, %Tuple indicates no 'export_all'.
+ []},
+
+ %% Turn off warnings for unused functions using a -compile() directive.
+ {func3,
+ <<"-export([t/1]).
+ -compile(nowarn_unused_function).
+
+ t(L) ->
+ lists:map(fun(X) -> 2*X end, L).
+
+ b(X) ->
+ 32*X.
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ []}],
+
+ ?line [] = run(Config, Ts),
+ ok.
+
+unsafe_vars(doc) ->
+ "OTP-4671. Errors for unsafe variables";
+unsafe_vars(suite) -> [];
+unsafe_vars(Config) when is_list(Config) ->
+ Ts = [{unsafe1,
+ <<"t() ->
+ (X = true) orelse (Y = false),
+ Y.
+ ">>,
+ [warn_unused_vars],
+ {error,[{3,erl_lint,{unsafe_var,'Y',{'orelse',2}}}],
+ [{2,erl_lint,{unused_var,'X'}}]}},
+ {unsafe2,
+ <<"t2() ->
+ (X = true) orelse (Y = false),
+ X.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{2,erl_lint,{unused_var,'Y'}}]}},
+ {unsafe3,
+ <<"t3() ->
+ (X = true) andalso (Y = false),
+ Y.
+ ">>,
+ [warn_unused_vars],
+ {error,[{3,erl_lint,{unsafe_var,'Y',{'andalso',2}}}],
+ [{2,erl_lint,{unused_var,'X'}}]}},
+ {unsafe4,
+ <<"t4() ->
+ (X = true) andalso (true = X),
+ X.
+ ">>,
+ [warn_unused_vars],
+ []},
+ {unsafe5,
+ <<"t5() ->
+ Y = 3,
+ (X = true) andalso (X = true),
+ {X,Y}.
+ ">>,
+ [warn_unused_vars],
+ []},
+ {unsafe6,
+ <<"t6() ->
+ X = true,
+ (X = true) andalso (true = X),
+ X.
+ ">>,
+ [warn_unused_vars],
+ []},
+ {unsafe7,
+ <<"t7() ->
+ (if true -> X = 3; false -> true end)
+ andalso (X > 2),
+ X.
+ ">>,
+ [warn_unused_vars],
+ {errors,[{3,erl_lint,{unsafe_var,'X',{'if',2}}},
+ {4,erl_lint,{unsafe_var,'X',{'if',2}}}],
+ []}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+unsafe_vars2(doc) ->
+ "OTP-4831, seq8202. No warn_unused_vars and unsafe variables";
+unsafe_vars2(suite) -> [];
+unsafe_vars2(Config) when is_list(Config) ->
+ Ts = [{unsafe2_1,
+ <<"foo(State) ->
+ case State of
+ true ->
+ if
+ false -> ok;
+ true -> State1=State
+ end
+ end,
+ State1. % unsafe
+ ">>,
+ [warn_unused_vars],
+ {errors,[{9,erl_lint,{unsafe_var,'State1',{'if',4}}}],[]}},
+ {unsafe2_2,
+ <<"foo(State) ->
+ case State of
+ true ->
+ if
+ false -> ok;
+ true -> State1=State
+ end
+ end,
+ State1. % unsafe
+ ">>,
+ [],
+ {errors,[{9,erl_lint,{unsafe_var,'State1',{'if',4}}}],[]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+unsafe_vars_try(doc) ->
+ "Errors for unsafe variables in try/catch constructs.";
+unsafe_vars_try(suite) -> [];
+unsafe_vars_try(Config) when is_list(Config) ->
+ Ts = [{unsafe_try1,
+ <<"foo2() ->
+ try self()
+ catch
+ Class:Data -> Result={Class,Data}
+ end,
+ Result.
+ foo3a() ->
+ try self() of
+ R -> R
+ catch
+ Class:Data -> Result={Class,Data}
+ end,
+ Result.
+ foo3b() ->
+ try self() of
+ Result -> ok
+ catch
+ Class:Data -> {Class,Data}
+ end,
+ Result.
+ ">>,
+ [],
+ {errors,[{6,erl_lint,{unsafe_var,'Result',{'try',2}}},
+ {13,erl_lint,{unsafe_var,'Result',{'try',8}}},
+ {20,erl_lint,{unsafe_var,'Result',{'try',15}}}],
+ []}},
+ {unsafe_try2,
+ <<"foo1a() ->
+ Try =
+ try self()
+ catch
+ Class:Data -> Rc={Class,Data}
+ after
+ Ra=ok
+ end,
+ {Try,Rc,Ra}.
+ foo1b() ->
+ Try =
+ try self() of
+ R -> R
+ catch
+ Class:Data -> Rc={Class,Data}
+ after
+ Ra=R
+ end,
+ {Try,Rc,Ra}.
+ foo2() ->
+ Try =
+ try self() of
+ R -> Ro=R
+ catch
+ Class:Data -> {Class,Data}
+ after
+ Ra=R
+ end,
+ {Try,Ro,Ra}.
+ foo3() ->
+ Try =
+ try self() of
+ R -> Ro=R
+ catch
+ Class:Data -> Rc={Class,Data}
+ after
+ Ra=R
+ end,
+ {Try,R,Ro,Rc,Ra}.
+ ">>,
+ [],
+ {errors,[{9,erl_lint,{unsafe_var,'Ra',{'try',3}}},
+ {9,erl_lint,{unsafe_var,'Rc',{'try',3}}},
+ {17,erl_lint,{unsafe_var,'R',{'try',12}}},
+ {19,erl_lint,{unsafe_var,'Ra',{'try',12}}},
+ {19,erl_lint,{unsafe_var,'Rc',{'try',12}}},
+ {27,erl_lint,{unsafe_var,'R',{'try',22}}},
+ {29,erl_lint,{unsafe_var,'Ra',{'try',22}}},
+ {29,erl_lint,{unsafe_var,'Ro',{'try',22}}},
+ {37,erl_lint,{unsafe_var,'R',{'try',32}}},
+ {39,erl_lint,{unsafe_var,'R',{'try',32}}},
+ {39,erl_lint,{unsafe_var,'Ra',{'try',32}}},
+ {39,erl_lint,{unsafe_var,'Rc',{'try',32}}},
+ {39,erl_lint,{unsafe_var,'Ro',{'try',32}}}],
+ []}},
+ {unsafe_try3,
+ <<"foo1(X) ->
+ Try =
+ try R=self()
+ catch
+ Class:Data -> Rc={X,R,Class,Data}
+ end,
+ {X,Try,Rc}.
+ foo2(X) ->
+ Try =
+ try R=self() of
+ RR -> Ro={X,R,RR}
+ catch
+ Class:Data -> {X,R,RR,Ro,Class,Data}
+ end,
+ {X,Try,R,RR,Ro,Class,Data}.
+ foo3(X) ->
+ Try =
+ try R=self() of
+ RR -> {X,R,RR}
+ catch
+ Class:Data -> {X,R,RR,Class,Data}
+ after
+ Ra={X,R,RR,Class,Data}
+ end,
+ {X,Try,R,RR,Ra,Class,Data}.
+ ">>,
+ [],
+ {errors,[{5,erl_lint,{unsafe_var,'R',{'try',3}}},
+ {7,erl_lint,{unsafe_var,'Rc',{'try',3}}},
+ {11,erl_lint,{unsafe_var,'R',{'try',10}}},
+ {13,erl_lint,{unbound_var,'RR'}},
+ {13,erl_lint,{unbound_var,'Ro'}},
+ {13,erl_lint,{unsafe_var,'R',{'try',10}}},
+ {15,erl_lint,{unsafe_var,'Class',{'try',10}}},
+ {15,erl_lint,{unsafe_var,'Data',{'try',10}}},
+ {15,erl_lint,{unsafe_var,'R',{'try',10}}},
+ {15,erl_lint,{unsafe_var,'RR',{'try',10}}},
+ {15,erl_lint,{unsafe_var,'Ro',{'try',10}}},
+ {19,erl_lint,{unsafe_var,'R',{'try',18}}},
+ {21,erl_lint,{unbound_var,'RR'}},
+ {21,erl_lint,{unsafe_var,'R',{'try',18}}},
+ {23,erl_lint,{unsafe_var,'Class',{'try',18}}},
+ {23,erl_lint,{unsafe_var,'Data',{'try',18}}},
+ {23,erl_lint,{unsafe_var,'R',{'try',18}}},
+ {23,erl_lint,{unsafe_var,'RR',{'try',18}}},
+ {25,erl_lint,{unsafe_var,'Class',{'try',18}}},
+ {25,erl_lint,{unsafe_var,'Data',{'try',18}}},
+ {25,erl_lint,{unsafe_var,'R',{'try',18}}},
+ {25,erl_lint,{unsafe_var,'RR',{'try',18}}},
+ {25,erl_lint,{unsafe_var,'Ra',{'try',18}}}],
+ []}},
+ {unsafe_try4,
+ <<"foo1(X) ->
+ Try =
+ try R=self() of
+ RR -> Ro={X,R,RR}
+ catch
+ Class:Data -> Rc={X,R,RR,Ro,Class,Data}
+ after
+ Ra={X,R,RR,Ro,Rc,Class,Data}
+ end,
+ {X,Try,R,RR,Ro,Rc,Ra,Class,Data}.
+ ">>,
+ [],
+ {errors,[{4,erl_lint,{unsafe_var,'R',{'try',3}}},
+ {6,erl_lint,{unbound_var,'RR'}},
+ {6,erl_lint,{unbound_var,'Ro'}},
+ {6,erl_lint,{unsafe_var,'R',{'try',3}}},
+ {8,erl_lint,{unsafe_var,'Class',{'try',3}}},
+ {8,erl_lint,{unsafe_var,'Data',{'try',3}}},
+ {8,erl_lint,{unsafe_var,'R',{'try',3}}},
+ {8,erl_lint,{unsafe_var,'RR',{'try',3}}},
+ {8,erl_lint,{unsafe_var,'Rc',{'try',3}}},
+ {8,erl_lint,{unsafe_var,'Ro',{'try',3}}},
+ {10,erl_lint,{unsafe_var,'Class',{'try',3}}},
+ {10,erl_lint,{unsafe_var,'Data',{'try',3}}},
+ {10,erl_lint,{unsafe_var,'R',{'try',3}}},
+ {10,erl_lint,{unsafe_var,'RR',{'try',3}}},
+ {10,erl_lint,{unsafe_var,'Ra',{'try',3}}},
+ {10,erl_lint,{unsafe_var,'Rc',{'try',3}}},
+ {10,erl_lint,{unsafe_var,'Ro',{'try',3}}}],
+ []}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+guard(doc) ->
+ "OTP-4670. Guards, is_record in particular.";
+guard(suite) -> [];
+guard(Config) when is_list(Config) ->
+ %% Well, these could be plain code...
+ Ts = [{guard1,
+ <<"-record(apa, {}).
+ t(A) when atom(A) ->
+ atom;
+ t(A) when binary(A) ->
+ binary;
+ t(A) when constant(A) ->
+ constant;
+ t(A) when float(A) ->
+ float;
+ t(A) when function(A) ->
+ function;
+ t(A) when integer(A) ->
+ integer;
+ t(A) when is_atom(A) ->
+ is_atom;
+ t(A) when is_binary(A) ->
+ is_binary;
+ t(A) when is_constant(A) ->
+ is_constant;
+ t(A) when is_float(A) ->
+ is_float;
+ t(A) when is_function(A) ->
+ is_function;
+ t(A) when is_integer(A) ->
+ is_integer;
+ t(A) when is_list(A) ->
+ is_list;
+ t(A) when is_number(A) ->
+ is_number;
+ t(A) when is_pid(A) ->
+ is_pid;
+ t(A) when is_port(A) ->
+ is_port;
+ t(A) when is_record(A, apa) ->
+ is_record;
+ t(A) when is_record(A, apa, 1) ->
+ is_record;
+ t(A) when is_reference(A) ->
+ is_reference;
+ t(A) when is_tuple(A) ->
+ is_tuple;
+ t(A) when list(A) ->
+ list;
+ t(A) when number(A) ->
+ number;
+ t(A) when pid(A) ->
+ pid;
+ t(A) when port(A) ->
+ port;
+ t(A) when record(A, apa) ->
+ record;
+ t(A) when reference(A) ->
+ reference;
+ t(A) when tuple(A) ->
+ tuple.
+ ">>,
+ [nowarn_obsolete_guard],
+ {error,
+ [{6,erl_lint,illegal_guard_expr},{18,erl_lint,illegal_guard_expr}],
+ [{18,erl_lint,{removed,{erlang,is_constant,1},
+ "Removed in R13B"}}]}},
+ {guard2,
+ <<"-record(apa,{}).
+ t1(A) when atom(A), atom(A) ->
+ atom;
+ t1(A) when binary(A), binary(A) ->
+ binary;
+ t1(A) when constant(A), constant(A) ->
+ constant;
+ t1(A) when float(A), float(A) ->
+ float;
+ t1(A) when function(A), function(A) ->
+ function;
+ t1(A) when integer(A), integer(A) ->
+ integer;
+ t1(A) when is_atom(A), is_atom(A) ->
+ is_atom;
+ t1(A) when is_binary(A), is_binary(A) ->
+ is_binary;
+ t1(A) when is_constant(A), is_constant(A) ->
+ is_constant;
+ t1(A) when is_float(A), is_float(A) ->
+ is_float;
+ t1(A) when is_function(A), is_function(A) ->
+ is_function;
+ t1(A) when is_integer(A), is_integer(A) ->
+ is_integer;
+ t1(A) when is_list(A), is_list(A) ->
+ is_list;
+ t1(A) when is_number(A), is_number(A) ->
+ is_number;
+ t1(A) when is_pid(A), is_pid(A) ->
+ is_pid;
+ t1(A) when is_port(A), is_port(A) ->
+ is_port;
+ t1(A) when is_record(A, apa), is_record(A, apa) ->
+ is_record;
+ t1(A) when is_record(A, apa, 1), is_record(A, apa, 1) ->
+ is_record;
+ t1(A) when is_reference(A), is_reference(A) ->
+ is_reference;
+ t1(A) when is_tuple(A), is_tuple(A) ->
+ is_tuple;
+ t1(A) when list(A), list(A) ->
+ list;
+ t1(A) when number(A), number(A) ->
+ number;
+ t1(A) when pid(A), pid(A) ->
+ pid;
+ t1(A) when port(A), port(A) ->
+ port;
+ t1(A) when record(A, apa), record(A, apa) ->
+ record;
+ t1(A) when reference(A), reference(A) ->
+ reference;
+ t1(A) when tuple(A), tuple(A) ->
+ tuple.
+ ">>,
+ [nowarn_obsolete_guard],
+ {error,[{6,erl_lint,illegal_guard_expr},
+ {6,erl_lint,illegal_guard_expr},
+ {18,erl_lint,illegal_guard_expr},
+ {18,erl_lint,illegal_guard_expr}],
+ [{18,erl_lint,{removed,{erlang,is_constant,1},
+ "Removed in R13B"}},
+ {18,erl_lint,{removed,{erlang,is_constant,1},
+ "Removed in R13B"}}]}},
+ {guard3,
+ <<"-record(apa,{}).
+ t2(A) when atom(A); atom(A) ->
+ atom;
+ t2(A) when binary(A); binary(A) ->
+ binary;
+ t2(A) when float(A); float(A) ->
+ float;
+ t2(A) when function(A); function(A) ->
+ function;
+ t2(A) when integer(A); integer(A) ->
+ integer;
+ t2(A) when is_atom(A); is_atom(A) ->
+ is_atom;
+ t2(A) when is_binary(A); is_binary(A) ->
+ is_binary;
+ t2(A) when is_float(A); is_float(A) ->
+ is_float;
+ t2(A) when is_function(A); is_function(A) ->
+ is_function;
+ t2(A) when is_integer(A); is_integer(A) ->
+ is_integer;
+ t2(A) when is_list(A); is_list(A) ->
+ is_list;
+ t2(A) when is_number(A); is_number(A) ->
+ is_number;
+ t2(A) when is_pid(A); is_pid(A) ->
+ is_pid;
+ t2(A) when is_port(A); is_port(A) ->
+ is_port;
+ t2(A) when is_record(A, apa); is_record(A, apa) ->
+ is_record;
+ t2(A) when is_record(A, gurka, 1); is_record(A, gurka, 1) ->
+ is_record;
+ t2(A) when is_reference(A); is_reference(A) ->
+ is_reference;
+ t2(A) when is_tuple(A); is_tuple(A) ->
+ is_tuple;
+ t2(A) when list(A); list(A) ->
+ list;
+ t2(A) when number(A); number(A) ->
+ number;
+ t2(A) when pid(A); pid(A) ->
+ pid;
+ t2(A) when port(A); port(A) ->
+ port;
+ t2(A) when record(A, apa); record(A, apa) ->
+ record;
+ t2(A) when reference(A); reference(A) ->
+ reference;
+ t2(A) when tuple(A); tuple(A) ->
+ tuple.
+ ">>,
+ [nowarn_obsolete_guard],
+ []},
+ {guard4,
+ <<"-record(apa, {}).
+ t3(A) when float(A) or float(A) -> % coercing... (badarg)
+ float;
+ t3(A) when is_atom(A) or is_atom(A) ->
+ is_atom;
+ t3(A) when is_binary(A) or is_binary(A) ->
+ is_binary;
+ t3(A) when is_float(A) or is_float(A) ->
+ is_float;
+ t3(A) when is_function(A) or is_function(A) ->
+ is_function;
+ t3(A) when is_integer(A) or is_integer(A) ->
+ is_integer;
+ t3(A) when is_list(A) or is_list(A) ->
+ is_list;
+ t3(A) when is_number(A) or is_number(A) ->
+ is_number;
+ t3(A) when is_pid(A) or is_pid(A) ->
+ is_pid;
+ t3(A) when is_port(A) or is_port(A) ->
+ is_port;
+ t3(A) when is_record(A, apa) or is_record(A, apa) ->
+ is_record;
+ t3(A) when is_record(A, apa, 1) or is_record(A, apa, 1) ->
+ is_record;
+ t3(A) when is_reference(A) or is_reference(A) ->
+ is_reference;
+ t3(A) when is_tuple(A) or is_tuple(A) ->
+ is_tuple.
+ ">>,
+ [nowarn_obsolete_guard],
+ []}],
+ ?line [] = run(Config, Ts),
+ Ts1 = [{guard5,
+ <<"-record(apa, {}).
+ t3(A) when record(A, {apa}) ->
+ foo;
+ t3(A) when is_record(A, {apa}) ->
+ foo;
+ t3(A) when erlang:is_record(A, {apa}) ->
+ foo;
+ t3(A) when {erlang,is_record}(A, {apa}) ->
+ foo;
+ t3(A) when is_record(A, {apa}, 1) ->
+ foo;
+ t3(A) when erlang:is_record(A, {apa}, 1) ->
+ foo;
+ t3(A) when {erlang,is_record}(A, {apa}, 1) ->
+ foo;
+ t3(A) when is_record(A, apa, []) ->
+ foo;
+ t3(A) when erlang:is_record(A, apa, []) ->
+ foo;
+ t3(A) when {erlang,is_record}(A, apa, []) ->
+ foo;
+ t3(A) when record(A, apa) ->
+ foo;
+ t3(A) when is_record(A, apa) ->
+ foo;
+ t3(A) when erlang:is_record(A, apa) ->
+ foo;
+ t3(A) when {erlang,is_record}(A, apa) ->
+ foo.
+ ">>,
+ [warn_unused_vars, nowarn_obsolete_guard],
+ {errors,[{2,erl_lint,illegal_guard_expr},
+ {4,erl_lint,illegal_guard_expr},
+ {6,erl_lint,illegal_guard_expr},
+ {8,erl_lint,illegal_guard_expr},
+ {10,erl_lint,illegal_guard_expr},
+ {12,erl_lint,illegal_guard_expr},
+ {14,erl_lint,illegal_guard_expr},
+ {16,erl_lint,illegal_guard_expr},
+ {18,erl_lint,illegal_guard_expr},
+ {20,erl_lint,illegal_guard_expr}],
+ []}},
+ {guard6,
+ <<"-record(apa,{a=a,b=foo:bar()}).
+ apa() ->
+ [X || X <- [], #apa{a = a} == {r,X,foo}];
+ apa() ->
+ [X || X <- [], #apa{b = b} == {r,X,foo}];
+ apa() ->
+ [X || X <- [], #ful{a = a} == {r,X,foo}].
+ ">>,
+ [],
+ {errors,[{7,erl_lint,{undefined_record,ful}}],
+ []}},
+ {guard7,
+ <<"-record(apa,{}).
+ t() ->
+ [X || X <- [1,#apa{},3], (3+is_record(X, apa)) or
+ (is_record(X, apa)*2)].
+ ">>,
+ [],
+ []}],
+ ?line [] = run(Config, Ts1),
+ ok.
+
+otp_4886(doc) ->
+ "OTP-4886. Calling is_record with given record name.";
+otp_4886(suite) -> [];
+otp_4886(Config) when is_list(Config) ->
+ Ts = [{otp_4886,
+ <<"t() ->
+ X = {foo},
+ is_record(X, foo),
+ erlang:is_record(X, foo),
+ {erlang,is_record}(X, foo),
+ %% Note: is_record/3 does not verify that the record is defined,
+ %% so the following lines should give no errors.
+ is_record(X, foo, 1),
+ erlang:is_record(X, foo, 1),
+ {erlang,is_record}(X, foo, 1).
+ ">>,
+ [],
+ {errors,[{3,erl_lint,{undefined_record,foo}},
+ {4,erl_lint,{undefined_record,foo}},
+ {5,erl_lint,{undefined_record,foo}}],
+ []}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_4988(doc) ->
+ "OTP-4988. Error when in-lining non-existent functions.";
+otp_4988(suite) -> [];
+otp_4988(Config) when is_list(Config) ->
+ Ts = [{otp_4988,
+ <<"-compile({inline, [{f,3},{f,4},{f,2},{f,a},{1,foo}]}).
+ -compile({inline, {g,1}}).
+ -compile({inline, {g,12}}).
+ -compile(inline).
+ -compile({inline_size,100}).
+
+ f(A, B) ->
+ {g(A), B}.
+
+ g(A) ->
+ {A}.
+ ">>,
+ [],
+ {errors,[{1,erl_lint,{bad_inline,{1,foo}}},
+ {1,erl_lint,{bad_inline,{f,3}}},
+ {1,erl_lint,{bad_inline,{f,4}}},
+ {1,erl_lint,{bad_inline,{f,a}}},
+ {3,erl_lint,{bad_inline,{g,12}}}],
+ []}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5091(doc) ->
+ "OTP-5091. Patterns and the bit syntax: invalid warnings.";
+otp_5091(suite) -> [];
+otp_5091(Config) when is_list(Config) ->
+ Ts = [{otp_5091_1,
+ <<"t() ->
+ [{Type, Value} || <<Type:16, _Len:16,
+ Value:_Len/binary>> <- []].
+ ">>,
+ [],
+ []},
+ {otp_5091_2,
+ <<"t() ->
+ %% This one has always been handled OK:
+ <<Type:16, _Len:16,
+ Value:_Len/binary>> = <<18:16, 9:16, \"123456789\">>,
+ {Type, Value}.
+ ">>,
+ [],
+ []},
+ {otp_5091_3,
+ <<"t() ->
+ fun(<<Type:16, _Len:16, Value:_Len/binary>>) ->
+ {Type, Value}
+ end.
+ ">>,
+ [],
+ []},
+ {otp_5091_4,
+ <<"t() ->
+ L = 8,
+ F = fun(<<A:L,B:A>>) -> B end,
+ F(<<16:8, 7:16>>).
+ ">>,
+ [],
+ []},
+ {otp_5091_5,
+ <<"t() ->
+ L = 8,
+ F = fun(<<L: % L shadowed.
+ L,
+ B:
+ L>>) -> B end,
+ F(<<16:8, 7:16>>).
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{shadowed_var,'L','fun'}}]}},
+ {otp_5091_6,
+ <<"t(A) ->
+ (fun(<<L:16,M:L,N:M>>) -> ok end)(A).
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{unused_var,'N'}}]}},
+ {otp_5091_7,
+ <<"t() ->
+ U = 8,
+ (fun(<<U: % U shadowed.
+ U>>) -> U end)(<<32:8>>).
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{shadowed_var,'U','fun'}}]}},
+ {otp_5091_8,
+ <<"t() ->
+ [X || <<A:8,
+ B:A>> <- [],
+ <<X:8>> <- [B]].
+ ">>,
+ [],
+ []},
+ {otp_5091_9,
+ <<"t() ->
+ L = 8,
+ F = fun(<<L: % Shadow.
+ L,
+ L:
+ L,
+ L:
+ L
+ >>) ->
+ L
+ end,
+ F(<<16:8, 8:16, 32:8>>).
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{shadowed_var,'L','fun'}}]}},
+ {otp_5091_10,
+ <<"t() ->
+ L = 8, <<A:L,B:A>> = <<16:8, 7:16>>, B.
+ ">>,
+ [],
+ []},
+ {otp_5091_11,
+ <<"t() ->
+ fun(<<L:16,L:L,L:L>>) -> ok end.
+ ">>,
+ [],
+ []},
+ {otp_5091_12,
+ <<"t([A,B]) ->
+ fun(<<A:B>>, % A shadowed and unused
+ <<Q:A>>) -> foo % Q unused. 'outer' A is used.
+ end.
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{unused_var,'A'}},
+ {2,erl_lint,{shadowed_var,'A','fun'}},
+ {3,erl_lint,{unused_var,'Q'}}]}},
+ {otp_5091_13,
+ <<"t([A,B]) -> % A unused, B unused
+ fun({A,B}, % A shadowed, B unused, B shadowed
+ {Q,A}) -> foo % Q unused. 'inner' A is used
+ end.
+ ">>,
+ [],
+ {warnings,[{1,erl_lint,{unused_var,'A'}},
+ {1,erl_lint,{unused_var,'B'}},
+ {2,erl_lint,{unused_var,'B'}},
+ {2,erl_lint,{shadowed_var,'A','fun'}},
+ {2,erl_lint,{shadowed_var,'B','fun'}},
+ {3,erl_lint,{unused_var,'Q'}}]}},
+ {otp_5091_14,
+ <<"t() ->
+ A = 4,
+ fun(<<A: % shadowed, unused
+ A>>) -> 2 end.
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{unused_var,'A'}},
+ {3,erl_lint,{shadowed_var,'A','fun'}}]}},
+ {otp_5091_15,
+ <<"t() ->
+ A = 4, % unused
+ fun(<<A:8, % shadowed
+ 16:A>>) -> 2 end.
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{unused_var,'A'}},
+ {3,erl_lint,{shadowed_var,'A','fun'}}]}},
+ {otp_5091_16,
+ <<"t() ->
+ A = 4,
+ fun(<<8:A, %
+ A:8>>) -> 7 end. % shadowed, unused
+ ">>,
+ [],
+ {warnings,[{4,erl_lint,{unused_var,'A'}},
+ {4,erl_lint,{shadowed_var,'A','fun'}}]}},
+ {otp_5091_17,
+ <<"t() ->
+ L = 16,
+ fun(<<L: % shadow
+ L>>, % 'outer' L
+ <<L: % shadow and match
+ L>>) -> % 'outer' L
+ a
+ end.
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{shadowed_var,'L','fun'}}]}},
+ {otp_5091_18,
+ <<"t() ->
+ L = 4, % L unused
+ fun({L, % L shadowed
+ L},
+ {L,
+ L}) ->
+ a
+ end.
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{unused_var,'L'}},
+ {3,erl_lint,{shadowed_var,'L','fun'}}]}},
+ {otp_5091_19,
+ <<"t() ->
+ L = 4,
+ [L || <<L: % shadowed
+ L,
+ L:
+ L>> <- []].
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{shadowed_var,'L',generate}}]}},
+ {otp_5091_20,
+ <<"t() ->
+ L = 4, % L unused.
+ [1 || L <- []]. % L unused, L shadowed.
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{unused_var,'L'}},
+ {3,erl_lint,{unused_var,'L'}},
+ {3,erl_lint,{shadowed_var,'L',generate}}]}},
+ {otp_5091_21,
+ <<"t() ->
+ L = 4,
+ [1 || L <- [L]]. % L shadowed. L unused.
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{unused_var,'L'}},
+ {3,erl_lint,{shadowed_var,'L',generate}}]}},
+ {otp_5091_22,
+ <<"t() ->
+ L = 4, % unused
+ fun(L) -> L end. % shadowed
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{unused_var,'L'}},
+ {3,erl_lint,{shadowed_var,'L','fun'}}]}},
+ {otp_5091_23,
+ <<"t([A,A]) -> a.">>, [], []},
+ {otp_5091_24,
+ <<"t({A,A}) -> a.">>, [], []},
+ {otp_5091_25,
+ <<"-record(r, {f1,f2}).
+ t(#r{f1 = A, f2 = A}) -> a.">>, [], []}],
+
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5276(doc) ->
+ "OTP-5276. Check the 'deprecated' attributed.";
+otp_5276(suite) -> [];
+otp_5276(Config) when is_list(Config) ->
+ Ts = [{otp_5276_1,
+ <<"-deprecated([{frutt,0,next_version}]).
+ -deprecated([{does_not_exist,1}]).
+ -deprecated('foo bar').
+ -deprecated(module).
+ -deprecated([{f,'_'}]).
+ -deprecated([{t,0}]).
+ -deprecated([{t,'_',eventually}]).
+ -deprecated([{'_','_',never}]).
+ -deprecated([{{badly,formed},1}]).
+ -deprecated([{'_','_',next_major_release}]).
+ -export([t/0]).
+ frutt() -> ok.
+ t() -> ok.
+ ">>,
+ {[]},
+ {error,[{1,erl_lint,{bad_deprecated,{frutt,0}}},
+ {2,erl_lint,{bad_deprecated,{does_not_exist,1}}},
+ {3,erl_lint,{invalid_deprecated,'foo bar'}},
+ {5,erl_lint,{bad_deprecated,{f,'_'}}},
+ {8,erl_lint,{invalid_deprecated,{'_','_',never}}},
+ {9,erl_lint,{invalid_deprecated,{{badly,formed},1}}}],
+ [{12,erl_lint,{unused_function,{frutt,0}}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5917(doc) ->
+ "OTP-5917. Check the 'deprecated' attributed.";
+otp_5917(suite) -> [];
+otp_5917(Config) when is_list(Config) ->
+ Ts = [{otp_5917_1,
+ <<"-compile(export_all).
+
+ -deprecated({t,0}).
+
+ t() ->
+ foo.
+ ">>,
+ {[]},
+ []}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_6585(doc) ->
+ "OTP-6585. Check the deprecated guards list/1, pid/1, ....";
+otp_6585(suite) -> [];
+otp_6585(Config) when is_list(Config) ->
+ Ts = [{otp_6585_1,
+ <<"-compile(export_all).
+
+ -record(r, {}).
+
+ f(A) when list(A) -> list;
+ f(R) when record(R, r) -> rec;
+ f(P) when pid(P) -> pid.
+
+ t() ->
+ f([]).
+ ">>,
+ [warn_obsolete_guard],
+ {warnings,[{5,erl_lint,{obsolete_guard,{list,1}}},
+ {6,erl_lint,{obsolete_guard,{record,2}}},
+ {7,erl_lint,{obsolete_guard,{pid,1}}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5338(doc) ->
+ "OTP-5338. Bad warning in record initialization.";
+otp_5338(suite) -> [];
+otp_5338(Config) when is_list(Config) ->
+ %% OTP-5878: variables like X are no longer allowed in initialisations
+ Ts = [{otp_5338,
+ <<"-record(c, {a = <<X:7/binary-unit:8>>}).
+ t() ->
+ X = <<\"hejsans\">>,
+ #c{}.
+ ">>,
+ [],
+ {error,[{1,erl_lint,{unbound_var,'X'}}],
+ [{3,erl_lint,{unused_var,'X'}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5362(doc) ->
+ "OTP-5362. deprecated_function, "
+ "{nowarn_unused_funtion,FAs}, 'better' line numbers.";
+otp_5362(suite) -> [];
+otp_5362(Config) when is_list(Config) ->
+ Ts = [{otp_5362_1,
+ <<"-include_lib(\"stdlib/include/qlc.hrl\").
+
+ -file(?FILE, 1000).
+
+ t() ->
+ qlc:q([X || X <- [],
+ begin A = 3, true end]).
+ ">>,
+ {[warn_unused_vars]},
+ {warnings,[{1002,erl_lint,{unused_function,{t,0}}},
+ {1004,erl_lint,{unused_var,'A'}}]}},
+
+ {otp_5362_2,
+ <<"-export([inline/0]).
+
+ -import(lists.foo, [a/1,b/1]). % b/1 is not used
+
+ -compile([{inline,{inl,7}}]). % undefined
+ -compile([{inline,[{inl,17}]}]). % undefined
+ -compile([{inline,{inl,1}}]). % OK
+
+ foop() -> % unused function
+ a([]), % used import, OK
+ fipp(). % undefined
+
+ inline() ->
+ inl(foo).
+
+ inl(_) ->
+ true.
+
+ not_used() -> % unused function
+ true.
+
+ -compile({nowarn_unused_function,[{and_not_used,2}]}). % unknown
+ and_not_used(_) -> % unused function
+ foo.
+
+ -compile({nowarn_unused_function,{unused_function,2}}).
+ unused_function(_, _) ->
+ ok.
+ ">>,
+ {[warn_unused_vars, warn_unused_import]},
+ {error,[{5,erl_lint,{bad_inline,{inl,7}}},
+ {6,erl_lint,{bad_inline,{inl,17}}},
+ {11,erl_lint,{undefined_function,{fipp,0}}},
+ {22,erl_lint,{bad_nowarn_unused_function,{and_not_used,2}}}],
+ [{3,erl_lint,{unused_import,{{b,1},'lists.foo'}}},
+ {9,erl_lint,{unused_function,{foop,0}}},
+ {19,erl_lint,{unused_function,{not_used,0}}},
+ {23,erl_lint,{unused_function,{and_not_used,1}}}]}},
+
+ {otp_5362_3,
+ <<"-record(a, {x,
+ x}).
+ -record(a, {x,
+ X}). % erl_parse
+ -record(a, [x,
+ x]). % erl_parse
+ -record(ok, {a,b}).
+
+ -record(r, {a = #ok{},
+ b = (#ok{})#ok.a}).
+
+ t() ->
+ {#a{},
+ #nix{},
+ #ok{nix = []},
+ #ok{Var = 4},
+ #r{}
+ }.
+ ">>,
+ {[nowarn_unused_function]},
+ {errors2, [{4,erl_parse,"bad record field"},
+ {5,erl_parse,"bad record declaration"}],
+ [{2,erl_lint,{redefine_field,a,x}},
+ {14,erl_lint,{undefined_record,nix}},
+ {15,erl_lint,{undefined_field,ok,nix}},
+ {16,erl_lint,{field_name_is_variable,ok,'Var'}}]}},
+
+ {otp_5362_4,
+ <<"-compile(nowarn_deprecated_function).
+ -compile(nowarn_bif_clash).
+ spawn(A) ->
+ erlang:hash(A, 3000),
+ spawn(A).
+ ">>,
+ {[nowarn_unused_function,
+ warn_deprecated_function,
+ warn_bif_clash]},
+ {error,
+ [{5,erl_lint,{call_to_redefined_bif,{spawn,1}}}],
+ [{3,erl_lint,{redefine_bif,{spawn,1}}},
+ {4,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2},
+ "in a future release"}}]}},
+
+ {otp_5362_5,
+ <<"-compile(nowarn_deprecated_function).
+ -compile(nowarn_bif_clash).
+ spawn(A) ->
+ erlang:hash(A, 3000),
+ spawn(A).
+ ">>,
+ {[nowarn_unused_function]},
+ {warnings,
+ [{3,erl_lint,{redefine_bif,{spawn,1}}}]}},
+
+ %% The special nowarn_X are not affected by general warn_X.
+ {otp_5362_6,
+ <<"-compile({nowarn_deprecated_function,{erlang,hash,2}}).
+ -compile({nowarn_bif_clash,{spawn,1}}).
+ spawn(A) ->
+ erlang:hash(A, 3000),
+ spawn(A).
+ ">>,
+ {[nowarn_unused_function,
+ warn_deprecated_function,
+ warn_bif_clash]},
+ {warnings,
+ [{3,erl_lint,{redefine_bif,{spawn,1}}}]}},
+
+ {otp_5362_7,
+ <<"-export([spawn/1]).
+ -compile({nowarn_deprecated_function,{erlang,hash,2}}).
+ -compile({nowarn_bif_clash,{spawn,1}}).
+ -compile({nowarn_bif_clash,{spawn,2}}). % bad
+ -compile([{nowarn_deprecated_function,
+ [{erlang,hash,-1},{3,hash,-1}]}, % 2 bad
+ {nowarn_deprecated_function, {{a,b,c},hash,-1}}]). % bad
+ spawn(A) ->
+ erlang:hash(A, 3000),
+ spawn(A).
+ ">>,
+ {[nowarn_unused_function]},
+ {error,[{4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}],
+ [{5,erl_lint,{bad_nowarn_deprecated_function,{3,hash,-1}}},
+ {5,erl_lint,{bad_nowarn_deprecated_function,{erlang,hash,-1}}},
+ {5,erl_lint,{bad_nowarn_deprecated_function,{{a,b,c},hash,-1}}}]}
+ },
+
+ {otp_5362_8,
+ <<"-export([spawn/1]).
+ -compile(warn_deprecated_function).
+ -compile(warn_bif_clash).
+ spawn(A) ->
+ erlang:hash(A, 3000),
+ spawn(A).
+ ">>,
+ {[nowarn_unused_function,
+ {nowarn_bif_clash,{spawn,1}}]}, % has no effect
+ {warnings,
+ [{5,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2},
+ "in a future release"}}]}},
+
+ {otp_5362_9,
+ <<"-include_lib(\"stdlib/include/qlc.hrl\").
+ -record(a, {x = qlc:q([{X,Y} || {X} <- [],{Y} <- [],X =:= Y])}).
+ -export([t/0]).
+ t() -> #a{}.
+ ">>,
+ {[]},
+ []}
+
+ ],
+
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5371(doc) ->
+ "OTP-5371. Aliases for bit syntax expressions are no longer allowed.";
+otp_5371(suite) -> [];
+otp_5371(Config) when is_list(Config) ->
+ Ts = [{otp_5371_1,
+ <<"t(<<A:8>> = <<B:8>>) ->
+ {A,B}.
+ ">>,
+ [],
+ {errors,[{1,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_5371_2,
+ <<"x([<<A:8>>] = [<<B:8>>]) ->
+ {A,B}.
+ y({a,<<A:8>>} = {b,<<B:8>>}) ->
+ {A,B}.
+ ">>,
+ [],
+ {errors,[{1,erl_lint,illegal_bin_pattern},
+ {3,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_5371_3,
+ <<"-record(foo, {a,b,c}).
+ -record(bar, {x,y,z}).
+ -record(buzz, {x,y}).
+ a(#foo{a = <<X:8>>} = #bar{x = <<Y:8>>}) ->
+ {X,Y}.
+ b(#foo{b = <<X:8>>} = #foo{b = <<Y:4,Z:4>>}) ->
+ {X,Y,Z}.
+ c(#foo{a = <<X:8>>} = #buzz{x = <<Y:8>>}) ->
+ {X,Y}.
+ d(#foo{a=x,b = <<X:8>>} = #buzz{y = <<Y:8>>}) ->
+ {X,Y}.
+ e(#foo{a=x,b = <<X:8>>} = #buzz{x=glurf,y = <<Y:8>>}) ->
+ {X,Y}.
+ ">>,
+ [],
+ {errors,[{4,erl_lint,illegal_bin_pattern},
+ {6,erl_lint,illegal_bin_pattern},
+ {8,erl_lint,illegal_bin_pattern},
+ {10,erl_lint,illegal_bin_pattern},
+ {12,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_5371_4,
+ <<"-record(foo, {a,b,c}).
+ -record(bar, {x,y,z}).
+ -record(buzz, {x,y}).
+ a(#foo{a = <<X:8>>,b=x} = #foo{b = <<Y:8>>}) ->
+ {X,Y}.
+ b(#foo{a = <<X:8>>} = #bar{y = <<Y:4,Z:4>>}) ->
+ {X,Y,Z}.
+ c(#foo{a = <<X:8>>} = #buzz{y = <<Y:8>>}) ->
+ {X,Y}.
+ ">>,
+ [],
+ {warnings,[{4,v3_core,nomatch},
+ {6,v3_core,nomatch},
+ {8,v3_core,nomatch}]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_7227(doc) -> "OTP_7227. Some aliases for bit syntax expressions were still allowed.";
+otp_7227(Config) when is_list(Config) ->
+ Ts = [{otp_7227_1,
+ <<"t([<<A:8>> = {C,D} = <<B:8>>]) ->
+ {A,B,C,D}.
+ ">>,
+ [],
+ {errors,[{1,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_7227_2,
+ <<"t([(<<A:8>> = {C,D}) = <<B:8>>]) ->
+ {A,B,C,D}.
+ ">>,
+ [],
+ {errors,[{1,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_7227_3,
+ <<"t([(<<A:8>> = {C,D}) = (<<B:8>> = <<C:8>>)]) ->
+ {A,B,C,D}.
+ ">>,
+ [],
+ {errors,[{1,erl_lint,illegal_bin_pattern},
+ {1,erl_lint,illegal_bin_pattern},
+ {1,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_7227_4,
+ <<"t(Val) ->
+ <<A:8>> = <<B:8>> = Val,
+ {A,B}.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_7227_5,
+ <<"t(Val) ->
+ <<A:8>> = X = <<B:8>> = Val,
+ {A,B,X}.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_7227_6,
+ <<"t(X, Y) ->
+ <<A:8>> = <<X:4,Y:4>>,
+ A.
+ ">>,
+ [],
+ []},
+ {otp_7227_7,
+ <<"t(Val) ->
+ (<<A:8>> = X) = (<<B:8>> = <<A:4,B:4>>) = Val,
+ {A,B,X}.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,illegal_bin_pattern},
+ {2,erl_lint,illegal_bin_pattern},
+ {2,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_7227_8,
+ <<"t(Val) ->
+ (<<A:8>> = X) = (Y = <<B:8>>) = Val,
+ {A,B,X,Y}.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,illegal_bin_pattern}],[]}},
+ {otp_7227_9,
+ <<"t(Val) ->
+ (Z = <<A:8>> = X) = (Y = <<B:8>> = W) = Val,
+ {A,B,X,Y,Z,W}.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,illegal_bin_pattern}],[]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5494(doc) ->
+ "OTP-5494. Warnings for functions exported more than once.";
+otp_5494(suite) -> [];
+otp_5494(Config) when is_list(Config) ->
+ Ts = [{otp_5494_1,
+ <<"-export([t/0]).
+ -export([t/0]).
+ t() -> a.
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{duplicated_export,{t,0}}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5644(doc) ->
+ "OTP-5644. M:F/A in record initialization.";
+otp_5644(suite) -> [];
+otp_5644(Config) when is_list(Config) ->
+ %% This test is a no-op. Although {function,mfa,i,1} was
+ %% transformed into {function,Line,i,1} by copy_expr, the module
+ %% was never checked (Line is the line number).
+ %% (OTP-5878: somewhat modified.)
+ Ts = [{otp_5644,
+ <<"-record(c, {a = fun ?MODULE:i/1(17)}).
+ t() ->
+ #c{}.
+
+ i(X) ->
+ X.
+ ">>,
+ [],
+ []}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_5878(doc) ->
+ "OTP-5878. Record declaration: forward references, introduced variables.";
+otp_5878(suite) -> [];
+otp_5878(Config) when is_list(Config) ->
+ Ts = [{otp_5878_10,
+ <<"-record(rec1, {a = #rec2{}}).
+ -record(rec2, {a = #rec1{}}).
+ t() ->#rec1{}.
+ ">>,
+ [warn_unused_record],
+ {error,[{1,erl_lint,{undefined_record,rec2}}],
+ [{2,erl_lint,{unused_record,rec2}}]}},
+
+ {otp_5878_20,
+ <<"-record(r1, {a = begin A = 4, {A,B} end}). % B unbound
+ -record(r2, {e = begin A = 3, #r1{} end}).
+ t() -> #r2{}.
+ ">>,
+ [warn_unused_record],
+ {error,[{1,erl_lint,{unbound_var,'B'}},
+ {1,erl_lint,{variable_in_record_def,'A'}},
+ {2,erl_lint,{variable_in_record_def,'A'}}],
+ [{1,erl_lint,{unused_record,r1}}]}},
+
+ {otp_5878_30,
+ <<"-record(r1, {t = case foo of _ -> 3 end}).
+ -record(r2, {a = case foo of A -> A; _ -> 3 end}).
+ -record(r3, {a = case foo of A -> A end}).
+ t() -> {#r1{},#r2{},#r3{}}.
+ ">>,
+ [warn_unused_record],
+ {errors,[{2,erl_lint,{variable_in_record_def,'A'}},
+ {3,erl_lint,{variable_in_record_def,'A'}}],
+ []}},
+
+ {otp_5878_40,
+ <<"-record(r1, {foo = A}). % A unbound
+ -record(r2, {a = fun(X) -> X end(3)}).
+ -record(r3, {a = [X || X <- [1,2,3]]}).
+ t() -> {#r1{},#r2{},#r3{}}.
+ ">>,
+ [warn_unused_record],
+ {errors,[{1,erl_lint,{unbound_var,'A'}}],[]}},
+
+ {otp_5878_50,
+ <<"-record(r1, {a = {A, % A unbound
+ A}}). % A unbound
+ -record(r2, {a = begin case foo of
+ A -> A
+ end,
+ A
+ end}).
+ -record(r3, {a = fun(X) ->
+ case foo of
+ A -> A
+ end
+ end
+ }).
+ -record(r4, {a = case foo of
+ foo ->
+ case foo of
+ A -> A
+ end;
+ _ ->
+ bar
+ end}).
+ t() -> {#r1{},#r2{},#r3{},#r4{}}.
+ ">>,
+ [warn_unused_record],
+ {error,[{1,erl_lint,{unbound_var,'A'}},
+ {2,erl_lint,{unbound_var,'A'}},
+ {4,erl_lint,{variable_in_record_def,'A'}},
+ {17,erl_lint,{variable_in_record_def,'A'}}],
+ [{8,erl_lint,{unused_var,'X'}}]}},
+
+ {otp_5878_60,
+ <<"-record(r1, {a = fun(NotShadowing) -> NotShadowing end}).
+ t() ->
+ NotShadowing = 17,
+ {#r1{}, NotShadowing}.
+ ">>,
+ [warn_unused_record],
+ []},
+
+ {otp_5878_70,
+ <<"-record(r1, {a = fun(<<X:8>>) -> X end,
+ b = case <<17:8>> of
+ <<_:Y>> -> Y;
+ <<Y:8>> ->
+ Y
+ end}).
+ t() -> #r1{}.
+ ">>,
+ [warn_unused_record],
+ {errors,[{3,erl_lint,{unbound_var,'Y'}},
+ {4,erl_lint,{variable_in_record_def,'Y'}}],
+ []}},
+
+ {otp_5878_80,
+ <<"-record(r, {a = [X || {A,Y} <- [{1,2},V={3,4}],
+ begin Z = [1,2,3], true end,
+ X <- Z ++ [A,Y]]}).
+ t() ->#r{}.
+ ">>,
+ [warn_unused_record],
+ {warnings,[{1,erl_lint,{unused_var,'V'}}]}},
+
+ {otp_5878_90,
+ <<"-record(r, {a = foo()}). % unused
+
+ t() -> ok.
+ ">>,
+ [warn_unused_record],
+ {error,[{1,erl_lint,{undefined_function,{foo,0}}}],
+ [{1,erl_lint,{unused_record,r}}]}}
+
+ ],
+ ?line [] = run(Config, Ts),
+
+ Abstr = <<"-module(lint_test, [A, B]).
+
+ -export([args/1]).
+
+ -record(r, {a = A, b = THIS}). % A and THIS are unbound
+
+ %% param:args(compile,param:new(1,2)).
+
+ args(C) ->
+ X = local(C),
+ Z = new(A, B),
+ F = fun(THIS) -> {x, A} end, % THIS unused and shadowed
+ {X, Z, THIS, F, #r{}}.
+
+ local(C) ->
+ module_info(C).
+ ">>,
+ ?line {error,[{5,erl_lint,{unbound_var,'A'}},
+ {5,erl_lint,{unbound_var,'THIS'}}],
+ [{12,erl_lint,{unused_var,'THIS'}},
+ {12,erl_lint,{shadowed_var,'THIS','fun'}}]}
+ = run_test2(Config, Abstr, [warn_unused_record]),
+
+ QLC1 = <<"-module(lint_test).
+ -include_lib(\"stdlib/include/qlc.hrl\").
+ -export([t/0]).
+ -record(r1, {a = qlc:e(qlc:q([X || X <- [1,2,3]]))}).
+ -record(r2, {a = qlc:q([X || X <- [1,2,3]])}).
+ -record(r3, {a = qlc:q([X || {A,Y} <- [{1,2},V={3,4}],
+ begin Z = [1,2,3], true end,
+ X <- Z ++ [A,Y]])}).
+ t() -> {#r1{},#r2{},#r3{}}.
+ ">>,
+ ?line {error,[{8,qlc,{used_generator_variable,'A'}},
+ {8,qlc,{used_generator_variable,'Y'}},
+ {8,qlc,{used_generator_variable,'Z'}}],
+ [{6,erl_lint,{unused_var,'V'}}]} =
+ run_test2(Config, QLC1, [warn_unused_record]),
+
+ Ill1 = <<"-module(lint_test).
+ -export([t/0]).
+ -record(r, {a = true}).
+ -record(r1, {a,b}).
+ -record(r2, {a = #r1{a = true}}).
+ -record(r3, {a = A}). % A is unbound
+ -record(r4, {a = dict:new()}).
+
+ t() ->
+ case x() of
+ _ when (#r{})#r.a ->
+ a;
+ _ when (#r4{})#r.a -> % illegal
+ b;
+ _ when (#r3{q = 5})#r.a -> % no warning for unbound A
+ q;
+ _ when (#r{q = 5})#r.a ->
+ a;
+ _ when (((#r{a = #r2{}})#r.a)#r2.a)#r1.a ->
+ b;
+ _ when #r{a = dict:new()} -> % illegal
+ c;
+ _ when l() > 3 -> % illegal, does not use l/0...
+ d;
+ _ ->
+ w
+ end.
+
+ l() ->
+ foo.
+
+ x() ->
+ bar.
+ ">>,
+
+ ?line {errors,[{6,erl_lint,{unbound_var,'A'}},
+ {13,erl_lint,illegal_guard_expr},
+ {15,erl_lint,{undefined_field,r3,q}},
+ {17,erl_lint,{undefined_field,r,q}},
+ {21,erl_lint,illegal_guard_expr},
+ {23,erl_lint,illegal_guard_expr}],
+ []} =
+ run_test2(Config, Ill1, [warn_unused_record]),
+
+ Ill2 = <<"-module(lint_test).
+ -export([t/0]).
+ t() ->
+ case x() of
+ _ when l()
+ or
+ l() ->
+ foo
+ end.
+ ">>,
+ ?line {errors,[{4,erl_lint,{undefined_function,{x,0}}},
+ {5,erl_lint,illegal_guard_expr},
+ {7,erl_lint,illegal_guard_expr}],
+ []} =
+ run_test2(Config, Ill2, [warn_unused_record]),
+
+ Ill3 = <<"t() -> ok.">>,
+ ?line {errors,[{1,erl_lint,undefined_module}],[]} =
+ run_test2(Config, Ill3, [warn_unused_record]),
+
+ Usage1 = <<"-module(lint_test).
+ -export([t/0]).
+ -record(u1, {a}).
+ -record(u2, {a = #u1{}}).
+ -record(u3, {a}). % unused
+ -record(u4, {a = #u3{}}). % unused
+
+ t() ->
+ {#u2{}}.
+ ">>,
+ ?line {warnings,[{5,erl_lint,{unused_record,u3}},
+ {6,erl_lint,{unused_record,u4}}]} =
+ run_test2(Config, Usage1, [warn_unused_record]),
+
+ Usage2 = <<"-module(lint_test).
+ -export([t/0]).
+ -record(u1, {a}).
+ -record(u2, {a = #u1{}}).
+ -file(\"some_file.hrl\", 1).
+ -record(u3, {a}). % unused, but on other file
+ -record(u4, {a = #u3{}}). % -\"-
+
+ t() ->
+ {#u2{}}.
+ ">>,
+ ?line [] = run_test2(Config, Usage2, [warn_unused_record]),
+
+ %% This a completely different story...
+ %% The linter checks if qlc.hrl hasn't been included
+ QLC2 = <<"-module(lint_test).
+ -import(qlc, [q/2]).
+ -export([t/0]).
+
+ t() ->
+ H1 = qlc:q([X || X <- [1,2]]),
+ H2 = qlc:q([X || X <- [1,2]], []),
+ H3 = q([X || X <- [1,2]], []),
+ {H1,H2,H3}.
+ ">>,
+ ?line {warnings,[{6,erl_lint,{missing_qlc_hrl,1}},
+ {7,erl_lint,{missing_qlc_hrl,2}},
+ {8,erl_lint,{missing_qlc_hrl,2}}]} =
+ run_test2(Config, QLC2, [warn_unused_record]),
+
+ %% Records that are used by types are not unused.
+ %% (Thanks to Fredrik Thulin and Kostis Sagonas.)
+ UsedByType = <<"-module(t).
+ -export([foo/1]).
+ -record(sipurl, {host :: string()}).
+ -record(keylist, {list = [] :: [_]}).
+ -type sip_headers() :: #keylist{}.
+ -record(request, {uri :: #sipurl{}, header :: sip_headers()}).
+
+ foo(#request{}) -> ok.
+ ">>,
+ ?line [] = run_test2(Config, UsedByType, [warn_unused_record]),
+
+ ok.
+
+otp_6885(doc) ->
+ "OTP-6885. Binary fields in bit syntax matching is now only allowed at the end.";
+otp_6885(suite) -> [];
+otp_6885(Config) when is_list(Config) ->
+ Ts = <<"-module(otp_6885).
+ -export([t/1]).
+ t(<<_/binary,I>>) -> I;
+ t(<<X/binary,I:X>>) -> I;
+ t(<<B/binary,T/binary>>) -> {B,T}.
+
+ build(A, B) ->
+ <<A/binary,B/binary>>.
+
+ foo(<<\"abc\"/binary>>) ->
+ ok;
+ foo(<<\"abc\":13/integer>>) ->
+ ok;
+ foo(<<\"abc\"/float>>) ->
+ ok;
+ foo(<<\"abc\":19>>) ->
+ ok;
+ foo(<<\"abc\"/utf8>>) ->
+ ok;
+ foo(<<\"abc\"/utf16>>) ->
+ ok;
+ foo(<<\"abc\"/utf32>>) ->
+ ok.
+
+ ">>,
+ ?line {errors,[{3,erl_lint,unsized_binary_not_at_end},
+ {4,erl_lint,unsized_binary_not_at_end},
+ {5,erl_lint,unsized_binary_not_at_end},
+ {10,erl_lint,typed_literal_string},
+ {12,erl_lint,typed_literal_string},
+ {14,erl_lint,typed_literal_string},
+ {16,erl_lint,typed_literal_string}],
+ []} = run_test2(Config, Ts, []),
+ ok.
+
+export_all(doc) ->
+ "OTP-7392. Warning for export_all.";
+export_all(Config) when is_list(Config) ->
+ Ts = <<"-module(export_all_module).
+ -compile([export_all]).
+
+ id(I) -> I.
+ ">>,
+ ?line [] = run_test2(Config, Ts, []),
+ ?line {warnings,[{2,erl_lint,export_all}]} =
+ run_test2(Config, Ts, [warn_export_all]),
+ ok.
+
+bif_clash(doc) ->
+ "Test warnings for functions that clash with BIFs.";
+bif_clash(suite) -> [];
+bif_clash(Config) when is_list(Config) ->
+ Ts = [{clash1,
+ <<"t(X) ->
+ size(X).
+
+ %% No warning for the following calls, since they
+ %% are unambigous.
+ b(X) ->
+ erlang:size(X).
+
+ c(X) ->
+ ?MODULE:size(X).
+
+ size({N,_}) ->
+ N.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,{call_to_redefined_bif,{size,1}}}],[]}},
+
+ %% Verify that (some) warnings can be turned off.
+ {clash2,
+ <<"-export([t/1,size/1]).
+ t(X) ->
+ size(X).
+
+ size({N,_}) ->
+ N.
+
+ %% My own abs/1 function works on lists too.
+ %% Unfortunately, it is not exported, so there will
+ %% be a warning that can't be turned off.
+ abs([H|T]) when $a =< H, H =< $z -> [H-($a-$A)|abs(T)];
+ abs([H|T]) -> [H|abs(T)];
+ abs([]) -> [];
+ abs(X) -> erlang:abs(X).
+ ">>,
+ {[nowarn_bif_clash]},
+ {warnings,[{11,erl_lint,{redefine_bif,{abs,1}}},
+ {11,erl_lint,{unused_function,{abs,1}}}]}}],
+
+ ?line [] = run(Config, Ts),
+ ok.
+
+behaviour_basic(doc) ->
+ "Basic tests with one behaviour.";
+behaviour_basic(suite) -> [];
+behaviour_basic(Config) when is_list(Config) ->
+ Ts = [{behaviour1,
+ <<"-behaviour(application).
+ ">>,
+ [],
+ {warnings,[{1,erl_lint,{undefined_behaviour_func,{start,2},application}},
+ {1,erl_lint,{undefined_behaviour_func,{stop,1},application}}]}},
+
+ {behaviour2,
+ <<"-behaviour(application).
+ -export([stop/1]).
+ stop(_) -> ok.
+ ">>,
+ [],
+ {warnings,[{1,erl_lint,{undefined_behaviour_func,{start,2},application}}]}},
+
+ {behaviour3,
+ <<"-behavior(application). %% Test American spelling.
+ -export([start/2,stop/1]).
+ start(_, _) -> ok.
+ stop(_) -> ok.
+ ">>,
+ [],
+ []}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+behaviour_multiple(doc) ->
+ "Basic tests with multiple behaviours.";
+behaviour_multiple(suite) -> [];
+behaviour_multiple(Config) when is_list(Config) ->
+ Ts = [{behaviour1,
+ <<"-behaviour(application).
+ -behaviour(supervisor).
+ ">>,
+ [],
+ {warnings,[{1,erl_lint,{undefined_behaviour_func,{start,2},application}},
+ {1,erl_lint,{undefined_behaviour_func,{stop,1},application}},
+ {2,erl_lint,{undefined_behaviour_func,{init,1},supervisor}}]}},
+
+ {behaviour2,
+ <<"-behaviour(application).
+ -behaviour(supervisor).
+ -export([start/2,stop/1,init/1]).
+ start(_, _) -> ok.
+ stop(_) -> ok.
+ init(_) -> ok.
+ ">>,
+ [],
+ []},
+
+ {american_behavior2,
+ <<"-behavior(application).
+ -behavior(supervisor).
+ -export([start/2,stop/1,init/1]).
+ start(_, _) -> ok.
+ stop(_) -> ok.
+ init(_) -> ok.
+ ">>,
+ [],
+ []},
+
+ {behaviour3,
+ <<"-behaviour(gen_server).
+ -behaviour(supervisor).
+ -export([handle_call/3,handle_cast/2,handle_info/2]).
+ handle_call(_, _, _) -> ok.
+ handle_cast(_, _) -> ok.
+ handle_info(_, _) -> ok.
+ ">>,
+ [],
+ {warnings,[{1,erl_lint,
+ {undefined_behaviour_func,{code_change,3},gen_server}},
+ {1,erl_lint,{undefined_behaviour_func,{init,1},gen_server}},
+ {1,erl_lint,{undefined_behaviour_func,{terminate,2},gen_server}},
+ {2,erl_lint,{undefined_behaviour_func,{init,1},supervisor}},
+ {2,
+ erl_lint,
+ {conflicting_behaviours,{init,1},supervisor,1,gen_server}}]}},
+ {american_behavior3,
+ <<"-behavior(gen_server).
+ -behavior(supervisor).
+ -export([handle_call/3,handle_cast/2,handle_info/2]).
+ handle_call(_, _, _) -> ok.
+ handle_cast(_, _) -> ok.
+ handle_info(_, _) -> ok.
+ ">>,
+ [],
+ {warnings,[{1,erl_lint,
+ {undefined_behaviour_func,{code_change,3},gen_server}},
+ {1,erl_lint,{undefined_behaviour_func,{init,1},gen_server}},
+ {1,erl_lint,{undefined_behaviour_func,{terminate,2},gen_server}},
+ {2,erl_lint,{undefined_behaviour_func,{init,1},supervisor}},
+ {2,
+ erl_lint,
+ {conflicting_behaviours,{init,1},supervisor,1,gen_server}}]}},
+
+ {behaviour4,
+ <<"-behaviour(gen_server).
+ -behaviour(gen_fsm).
+ -behaviour(supervisor).
+ -export([init/1,handle_call/3,handle_cast/2,
+ handle_info/2,handle_info/3,
+ handle_event/3,handle_sync_event/4,
+ code_change/3,code_change/4,
+ terminate/2,terminate/3,terminate/4]).
+ init(_) -> ok.
+ handle_call(_, _, _) -> ok.
+ handle_event(_, _, _) -> ok.
+ handle_sync_event(_, _, _, _) -> ok.
+ handle_cast(_, _) -> ok.
+ handle_info(_, _) -> ok.
+ handle_info(_, _, _) -> ok.
+ code_change(_, _, _) -> ok.
+ code_change(_, _, _, _) -> ok.
+ terminate(_, _) -> ok.
+ terminate(_, _, _) -> ok.
+ terminate(_, _, _, _) -> ok.
+ ">>,
+ [],
+ {warnings,[{2,
+ erl_lint,
+ {conflicting_behaviours,{init,1},gen_fsm,1,gen_server}},
+ {3,
+ erl_lint,
+ {conflicting_behaviours,{init,1},supervisor,1,gen_server}}]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+otp_7550(doc) ->
+ "Test that the new utf8/utf16/utf32 types do not allow size or unit specifiers.";
+otp_7550(Config) when is_list(Config) ->
+ Ts = [{otp_7550,
+ <<"f8(A) ->
+ <<A:8/utf8>>.
+ g8(A) ->
+ <<A:8/utf8-unit:1>>.
+ h8(A) ->
+ <<A/utf8-unit:1>>.
+
+ f16(A) ->
+ <<A:8/utf16>>.
+ g16(A) ->
+ <<A:8/utf16-unit:1>>.
+ h16(A) ->
+ <<A/utf16-unit:1>>.
+
+ f32(A) ->
+ <<A:8/utf32>>.
+ g32(A) ->
+ <<A:8/utf32-unit:1>>.
+ h32(A) ->
+ <<A/utf32-unit:1>>.
+ ">>,
+ [],
+ {errors,[{2,erl_lint,utf_bittype_size_or_unit},
+ {4,erl_lint,utf_bittype_size_or_unit},
+ {6,erl_lint,utf_bittype_size_or_unit},
+ {9,erl_lint,utf_bittype_size_or_unit},
+ {11,erl_lint,utf_bittype_size_or_unit},
+ {13,erl_lint,utf_bittype_size_or_unit},
+ {16,erl_lint,utf_bittype_size_or_unit},
+ {18,erl_lint,utf_bittype_size_or_unit},
+ {20,erl_lint,utf_bittype_size_or_unit}
+ ],
+ []}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+
+otp_8051(doc) ->
+ "Bugfix: -opaque with invalid type.";
+otp_8051(Config) when is_list(Config) ->
+ Ts = [{otp_8051,
+ <<"-opaque foo() :: bar().
+ ">>,
+ [],
+ {error,[{1,erl_lint,{type_ref,{bar,0}}}],
+ [{1,erl_lint,{unused_type,{foo,0}}}]}}],
+ ?line [] = run(Config, Ts),
+ ok.
+
+format_warn(doc) ->
+ "Check that format warnings are generated.";
+format_warn(suite) -> [];
+format_warn(Config) when is_list(Config) ->
+ L1 = 14,
+ L2 = 4,
+ format_level(1, L1, Config),
+ format_level(2, L1+L2, Config),
+ format_level(3, L1+L2, Config), %there is no level 3
+ ok.
+
+format_level(Level, Count, Config) ->
+ ?line W = get_compilation_warnings(Config, "format",
+ [{warn_format, Level}]),
+ %% Pick out the 'format' warnings.
+ ?line FW = lists:filter(fun({_Line, erl_lint, {format_error, _}}) -> true;
+ (_) -> false
+ end,
+ W),
+ ?line case length(FW) of
+ Count ->
+ ok;
+ Other ->
+ ?t:format("Expected ~w warning(s); got ~w", [Count,Other]),
+ fail()
+ end,
+ ok.
+
+%% Test the -on_load(Name/0) directive.
+
+on_load(suite) ->
+ [on_load_successful, on_load_failing].
+
+on_load_successful(Config) when is_list(Config) ->
+ Ts = [{on_load_1,
+ %% Exported on_load function.
+ <<"-export([do_on_load/0]).
+ -on_load(do_on_load/0).
+ do_on_load() -> ok.
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ []},
+
+ {on_load_2,
+ %% Local on_load function.
+ <<"-on_load(do_on_load/0).
+ do_on_load() -> ok.
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ []},
+
+ {on_load_3,
+ %% Local on_load function, calling other local functions.
+ <<"-on_load(do_on_load/0).
+ do_on_load() -> foo().
+ foo() -> bar(5) + 42.
+ bar(N) -> 2*N.
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ []}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+on_load_failing(Config) when is_list(Config) ->
+ Ts = [{on_load_1,
+ %% Badly formed.
+ <<"-on_load(atom).
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ {errors,
+ [{1,erl_lint,{bad_on_load,atom}}],[]}},
+
+ {on_load_2,
+ %% Badly formed.
+ <<"-on_load({42,0}).
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ {errors,
+ [{1,erl_lint,{bad_on_load,{42,0}}}],[]}},
+
+ {on_load_3,
+ %% Multiple on_load attributes.
+ <<"-on_load(foo/0).
+ -on_load(bar/0).
+ foo() -> ok.
+ bar() -> ok.
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ {errors,
+ [{2,erl_lint,multiple_on_loads}],[]}},
+
+ {on_load_4,
+ %% Wrong arity.
+ <<"-on_load(foo/1).
+ foo(_) -> ok.
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ {errors,
+ [{1,erl_lint,{bad_on_load_arity,{foo,1}}}],[]}},
+
+ {on_load_5,
+ %% Non-existing function.
+ <<"-on_load(non_existing/0).
+ ">>,
+ {[]}, %Tuple indicates no 'export_all'.
+ {errors,
+ [{1,erl_lint,{undefined_on_load,{non_existing,0}}}],[]}}
+ ],
+ ?line [] = run(Config, Ts),
+ ok.
+
+run(Config, Tests) ->
+ F = fun({N,P,Ws,E}, BadL) ->
+ case catch run_test(Config, P, Ws) of
+ E ->
+ BadL;
+ Bad ->
+ ?t:format("~nTest ~p failed. Expected~n ~p~n"
+ "but got~n ~p~n", [N, E, Bad]),
+ fail()
+ end
+ end,
+ lists:foldl(F, [], Tests).
+
+%% Compiles a test file and returns the list of warnings.
+
+get_compilation_warnings(Conf, Filename, Warnings) ->
+ ?line DataDir = ?datadir,
+ ?line File = filename:join(DataDir, Filename),
+ {ok,Bin} = file:read_file(File++".erl"),
+ FileS = binary_to_list(Bin),
+ {match,[{Start,Length}|_]} = re:run(FileS, "-module.*\\n"),
+ Test = lists:nthtail(Start+Length, FileS),
+ {warnings, Ws} = run_test(Conf, Test, Warnings),
+ Ws.
+
+%% Compiles a test module and returns the list of errors and warnings.
+
+run_test(Conf, Test0, Warnings0) ->
+ Test = list_to_binary(["-module(lint_test). ", Test0]),
+ run_test2(Conf, Test, Warnings0).
+
+run_test2(Conf, Test, Warnings0) ->
+ Filename = 'lint_test.erl',
+ DataDir = ?privdir,
+ File = filename:join(DataDir, Filename),
+ Opts = case Warnings0 of
+ {Warnings} -> %Hairy trick to not add export_all.
+ [return|Warnings];
+ Warnings ->
+ [export_all,return|Warnings]
+ end,
+ ok = file:write_file(File, Test),
+
+ %% We will use the 'binary' option so that the compiler will not
+ %% compare the module name to the output file name. Also, there
+ %% is no reason to produce an output file since we are only
+ %% interested in the errors and warnings.
+
+ %% Print warnings, call erl_lint:format_error/1.
+ compile:file(File, [binary,report|Opts]),
+
+ case compile:file(File, [binary|Opts]) of
+ {ok, _M, Code, Ws} when is_binary(Code) -> warnings(File, Ws);
+ {error, [{File,Es}], []} -> {errors, Es, []};
+ {error, [{File,Es}], [{File,Ws}]} -> {error, Es, Ws};
+ {error, [{File,Es1},{File,Es2}], []} -> {errors2, Es1, Es2}
+ end.
+
+warnings(File, Ws) ->
+ case lists:append([W || {F, W} <- Ws, F =:= File]) of
+ [] -> [];
+ L -> {warnings, L}
+ end.
+
+fail() ->
+ io:format("failed~n"),
+ ?t:fail().
diff --git a/lib/stdlib/test/erl_lint_SUITE_data/format.erl b/lib/stdlib/test/erl_lint_SUITE_data/format.erl
new file mode 100644
index 0000000000..20c4c5b57f
--- /dev/null
+++ b/lib/stdlib/test/erl_lint_SUITE_data/format.erl
@@ -0,0 +1,55 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(format).
+
+-export([f/1]).
+
+%%% There will be warnings at level 2 and 3.
+
+f(F) ->
+ io:format("~", F), %2
+ io:format("~", [F]), %1
+ io:format(a, b), %1
+ io:format(a, "abc"), %1
+ io:format(a, [a | "abc"]), %2
+ io:format(4,5,6,7), %1
+
+ io:format("la cucaracha~n"),
+ io:format(""),
+ io:format("~p ~p~n", [F]), %1
+ io:format("~p~n", [F]),
+ io:format("~m"), %1
+ io:format(F, "~p", []), %1
+ io:format("~x~n", [F]), %1
+ io:format("~p~n", F), %2
+ io:format(F, [3]), %2
+
+ io:format("~p", a), %1
+ io:format("~p~", [F]), %1
+ io:format("~p ~p", [F, 4 | 7]), %1
+ io:format("~14p", [F]),
+ io:format("~*p", [a, F]), %no type checking
+ io:format("~*p", [14, F]),
+
+ io:fwrite("~p", []), %1
+ io_lib:format("~p", []), %1
+ foo:format("~p", []),
+ io:format(), %1
+
+ ok.
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
new file mode 100644
index 0000000000..0a119d1e38
--- /dev/null
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -0,0 +1,1073 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------
+%%% Purpose:Test Suite for the 'erl_pp' module.
+%%%-----------------------------------------------------------------
+-module(erl_pp_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(datadir, "erl_pp_SUITE_data").
+-define(privdir, "erl_pp_SUITE_priv").
+-define(t, test_server).
+-else.
+-include("test_server.hrl").
+-define(datadir, ?config(data_dir, Config)).
+-define(privdir, ?config(priv_dir, Config)).
+-endif.
+
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export([expr/1, func/1, call/1, recs/1, try_catch/1, if_then/1,
+ receive_after/1, bits/1, head_tail/1, package/1,
+ cond1/1, block/1, case1/1, ops/1, messages/1,
+ old_mnemosyne_syntax/1,
+ attributes/1, import_export/1, misc_attrs/1,
+ hook/1,
+ neg_indent/1,
+ tickets/1,
+ otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1]).
+
+%% Internal export.
+-export([ehook/6]).
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(2)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog = ?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [expr, attributes, hook, neg_indent, tickets].
+
+expr(suite) ->
+ [func, call, recs, try_catch, if_then, receive_after, bits, head_tail,
+ package, cond1, block, case1, ops, messages, old_mnemosyne_syntax].
+
+func(suite) ->
+ [];
+func(Config) when is_list(Config) ->
+ Ts = [{func_1,
+ <<"-record(r1, {a,b}).
+ -record(r3, {a = fun(_) -> #r1{} end(1), b}).
+
+ t() ->
+ fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}).
+ ">>},
+ {func_2,
+ <<"-record(r1, {a,b}).
+ -record(r3, {a = fun(_) -> #r1{} end(1), b}).
+
+ t() ->
+ fsdfsdfjsdfjkljf:sdlfjdsfjlf(
+ fun(sdfsd) -> {sdkjsdf,sdfjsdkljfsdl,sdfkjdklf} end).
+ ">>},
+ {func_3,
+ <<"t() -> fun t/0.">>},
+ {func_4,
+ %% Has already been expanded away in sys_pre_expand.
+ <<"t() -> fun modul:foo/3.">>},
+ {func_5, % 'when' is moved down one line
+ <<"tkjlksjflksdjflsdjlk()
+ when kljlsajflksjdfklsjdlkjfklsdklfjsdlf <
+ kljasjfdsjflsdjfklsdjfklsdjfklsd ->
+ foo.">>},
+ {func_6,
+ <<"t() ->
+ (fun() ->
+ true
+ end)().">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+call(suite) ->
+ [];
+call(Config) when is_list(Config) ->
+ Ts = [{call_1,
+ <<"t() ->
+ fookjljsflj:barlkjlkjsdfj(kjdslfjsdl,hej,san,sa,
+ foo,sdfds,sdfsdf,sdfsd,sdfdsf,sdfdsf,sfdsf,
+ sfds,sdfsdf,sfds).
+ ">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+recs(suite) ->
+ [];
+recs(Config) when is_list(Config) ->
+ %% Evolved while testing strict record tests in guards...
+ Ts = [{recs_1,
+ <<"-compile(strict_record_tests).
+ -record(r, {a = 4,b}).
+ -record(r1, {a,b}).
+ -record(r2, {a = #r1{},b,c=length([1,2,3])}).
+ -record(r3, {a = fun(_) -> #r1{} end(1), b}).
+
+ t() ->
+ foo = fun(A) when A#r1.a > A#r1.b -> foo end(#r1{b = 2}),
+ 0 = fun(A) when A#r2.a -> 0 end(#r2{a = true}),
+ 1 = fun(A) when (#r1{a = A})#r1.a > 2 -> 1 end(3),
+ 2 = fun(N) when ((#r2{a = #r{a = 4}, b = length([a,b,c])})#r2.a)#r.a > N ->
+ 2 end(2),
+ 3 = fun(A) when (A#r2.a)#r1.a =:= 3 -> 3 end(#r2{a = #r1{a = 3}}),
+ ok = fun() ->
+ F = fun(A) when record(A#r.a, r1) -> 4;
+ (A) when record(A#r1.a, r1) -> 5
+ end,
+ 5 = F(#r1{a = #r1{}}),
+ 4 = F(#r{a = #r1{}}),
+ ok
+ end(),
+ 3 = fun(A) when record(A#r1.a, r),
+ (A#r1.a)#r.a > 3 -> 3
+ end(#r1{a = #r{a = 4}}),
+ 7 = fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}),
+ [#r1{a = 2,b = 1}] =
+ fun() ->
+ [A || A <- [#r1{a = 1, b = 3},
+ #r2{a = 2,b = 1},
+ #r1{a = 2, b = 1}],
+ A#r1.a >
+ A#r1.b]
+ end(),
+ {[_],b} =
+ fun(L) ->
+ %% A is checked only once:
+ R1 = [{A,B} || A <- L, A#r1.a, B <- L, A#r1.b],
+ A = #r2{a = true},
+ %% A is checked again:
+ B = if A#r1.a -> a; true -> b end,
+ {R1,B}
+ end([#r1{a = true, b = true}]),
+
+ p = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ o = fun(A) when (A#r1.a =:= 2) orelse (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ %% The test done twice (an effect of doing the test as soon as possible).
+ 3 = fun(A) when A#r1.a > 3,
+ record(A, r1) -> 3
+ end(#r1{a = 5}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r2.a =:= 1) orelse (A#r2.a) -> 2;
+ (A) when (A#r1.a =:= 1) orelse (A#r1.a) -> 1;
+ (A) when (A#r2.a =:= 2) andalso (A#r2.b) -> 3
+ end,
+ 1 = F(#r1{a = 1}),
+ 2 = F(#r2{a = true}),
+ 3 = F(#r2{a = 2, b = true}),
+ ok
+ end(),
+
+ b = fun(A) when false or not (A#r.a =:= 1) -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+ b = fun(A) when not (A#r.a =:= 1) or false -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+
+ ok = fun() ->
+ F = fun(A) when not (A#r.a =:= 1) -> yes;
+ (_) -> no
+ end,
+ no = F(#r1{a = 2}),
+ yes = F(#r{a = 2}),
+ no = F(#r{a = 1}),
+ ok
+ end(),
+
+ %% No extra check added:
+ a = fun(A) when record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 ->a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when erlang:is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+
+ nop = fun(A) when (is_record(A, r1) and (A#r1.a > 3)) or (A#r2.a < 1) ->
+ japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+ nop = fun(A) when (A#r1.a > 3) or (A#r2.a < 1) -> japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end,
+ p = F(#r2{a = 1}),
+ p = F(#r1{a = 2}),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(A) when fail, A#r1.a; A#r1.a -> ab;
+ (_) -> bu
+ end,
+ ab = F(#r1{a = true}),
+ bu = F(#r2{a = true}),
+ ok
+ end(),
+
+ both = fun(A) when A#r.a, A#r.b -> both
+ end(#r{a = true, b = true}),
+
+ ok = fun() ->
+ F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
+ or (B#r2.b) or (A#r1.b) ->
+ true;
+ (_, _) -> false
+ end,
+ true = F(#r1{a = false, b = false},
+ #r2{a = false, b = true}),
+ false = F(#r1{a = true, b = true},
+ #r1{a = false, b = true}),
+ ok
+ end(),
+
+ ok.
+ ">>},
+ {recs_2,
+ <<"-record(r1, {a, b = foo:bar(kljlfjsdlf, kjlksdjf)}).
+ -record(r2, {c = #r1{}, d = #r1{a = bar:foo(kljklsjdf)}}).
+
+ t() ->
+ R = #r2{},
+ R#r2{c = R, d = #r1{}}.">>}
+ ],
+ ?line compile(Config, Ts),
+
+ ?line ok = pp_expr(<<"case #r{a={1,2},b=#r{}} of
+ X=Y=#r{a=foo,b=bar} ->
+ {(foooo:baaaar(X))#r{a = rep},Y,#r.b}
+ end">>),
+ ?line ok = pp_expr(<<"R#r{a = {kljasdklf,sdkfjsdl,sdafjkllsdf,sdfkjsd,
+ sdafjsd,sdf,sdafsd,sdfdsf,sdfdsf,dsfds}}">>),
+ ok.
+
+try_catch(suite) ->
+ [];
+try_catch(Config) when is_list(Config) ->
+ Ts = [{try_1, % copied from erl_eval_SUITE
+ <<"t() -> try 1 of 1 -> 2 catch _:_ -> 3 end.">>},
+ {try_2,
+ <<"t() -> try 1 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end.">>},
+ {try_3,
+ <<"t() -> try 3 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end.">>},
+ {try_4,
+ <<"t() -> try 1 after put(try_catch, 2) end.">>},
+ {try_5,
+ <<"t() -> try 1 of 1 -> 2; 3 -> 4
+ after put(try_catch, 5) end.">>},
+ {try_6,
+ <<"t() -> try 1=2 catch throw:{badmatch,2} -> 3 end.">>},
+ {try_7,
+ <<"t() -> try 1=2 of 3 -> 4
+ catch error:{badmatch,2} -> 5 end.">>},
+ {try_8,
+ <<"t() -> try 1=2
+ catch error:{badmatch,2} -> 3
+ after put(try_catch, 4) end.">>},
+ {try_9,
+ <<"t() -> try 1=2
+ catch error:{badmatch,2} -> 3
+ after put(try_catch, 4) end.">>},
+ {try_10,
+ <<"t() -> try a,b,c
+ catch exit:_ -> d;
+ throw:_ -> t;
+ error:{foo,bar} -> foo,
+ bar
+ end.">>},
+
+ {catch_1,
+ <<"t() -> catch foo.">>},
+ {catch_2,
+ <<"t() -> case catch foo of bar -> foo end.">>},
+ {catch_3,
+ <<"t() -> catch begin begin foo, bar, foo:bar(kljsldkfjdls,kljsdl),
+ (catch bar:foo(foo)) end end.">>}
+ ],
+ ?line compile(Config, Ts),
+ ?line ok = pp_expr(<<"try
+ erl_internal:bif(M,F,length(Args))
+ of
+ true ->
+ call(N,Args,Prec,Hook);
+ false ->
+ call(Name,Args,Prec,Hook)
+ after foo end">>),
+ ok.
+
+if_then(suite) ->
+ [];
+if_then(Config) when is_list(Config) ->
+ Ts = [{if_1,
+ <<"t() -> if 1 > 2 -> 1; true -> b end.">>},
+ {if_2,
+ <<"t() -> if true -> true end.">>},
+ {if_3,
+ <<"t() -> if 1 == 2 -> a; 1 > 2 -> b; 1 < 2 -> c end.">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+receive_after(suite) ->
+ [];
+receive_after(Config) when is_list(Config) ->
+ Ts = [{rec_1,
+ <<"t() -> receive foo -> bar; bar -> foo end.">>},
+ {rec_2,
+ <<"t() -> receive foo -> bar after foo:bar() -> 0 end.">>},
+ {rec_3,
+ <<"t() -> receive after 1 -> ok end.">>},
+ {rec_4,
+ <<"t() -> receive {X,Y} -> {a,X,Y} end.">>},
+ {rec_5,
+ <<"t() -> receive
+ {X,Y} ->
+ {X,Y};
+ Z ->
+ Z
+ after
+ foo:bar() ->
+ {3,4}
+ end.">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+bits(suite) ->
+ [];
+bits(Config) when is_list(Config) ->
+ Ts = [{bit_1, % copied from shell_SUITE
+ <<"t() -> <<(<<\"abc\">>):3/binary>>.">>},
+ {bit_2,
+ <<"t() -> <<(<<\"abc\">>)/binary>>.">>},
+ {bit_3,
+ <<"t() -> <<3.14:64/float>>.">>},
+ {bit_4,
+ <<"t() -> <<-20/signed>> = <<-20>>.">>},
+ {bit_5,
+ <<"t() -> <<-300:16/signed>> = <<-300:16>>.">>},
+ {bit_6,
+ <<"t() -> <<-(1 bsl 29):32/signed>> = <<-(1 bsl 29):32>>.">>},
+ {bit_7,
+ <<"t() -> <<A:4,B:4,C:4,D:4,E:4,F:4>> = <<\"hej\">>.">>},
+ {bit_8,
+ <<"t() -> <<>>.">>},
+ {bit_9,
+ <<"">>}
+ ],
+ ?line compile(Config, Ts),
+ ?line ok = pp_expr(<<"<<(list_to_binary([1,2]))/binary>>">>),
+ ?line ok = pp_expr(
+ <<"<<(list_to_binary([1,2])):all/binary-unit:8-unsigned-big>>">>),
+ ?line ok = pp_expr(<<"<<<<\"hej\">>/binary>>">>),
+ ?line ok = pp_expr(<<"<<(foo:bar())/binary>>">>),
+ ?line ok = pp_expr(<<"<<(a)/binary>>">>),
+ ?line ok = pp_expr(<<"<<a/binary>>">>),
+ ?line ok = pp_expr(<<"<<{a,b}/binary>>">>),
+ ?line ok = pp_expr(<<"<<{foo:bar(),b}/binary>>">>),
+ ?line ok = pp_expr(<<"<<(foo:bar()):(foo:bar())/binary>>">>),
+ ?line ok = pp_expr(<<"<<(foo.bar)/binary>>">>),
+ ?line ok = pp_expr(<<"<<(foo.bar):all/binary>>">>),
+ ?line ok = pp_expr(<<"<<(foo.bar):(foo.bar)/binary>>">>),
+ ok.
+
+head_tail(suite) ->
+ [];
+head_tail(Config) when is_list(Config) ->
+ Ts = [{list_1,
+ <<"t() -> [a | b].">>},
+ {list_2,
+ <<"t() -> [a,b,$\n].">>},
+ {list_3,
+ <<"t() -> [].">>},
+ {list_4,
+ <<"t() -> [a].">>},
+ {list_5,
+ <<"t() ->
+ [foo:bar(lkjljlskdfj, klsdajflds, sdafkljsdlfkjdas, kjlsdadjl),
+ bar:foo(kljlkjsdf, lkjsdlfj, [kljsfj, sdfdsfsad])].">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+package(suite) ->
+ [];
+package(Config) when is_list(Config) ->
+ Ts = [{package_1,
+ <<"t() -> a.b:foo().">>},
+ {package_2,
+ <<"t() -> .lists:sort([]).">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+cond1(suite) ->
+ [];
+cond1(Config) when is_list(Config) ->
+ C = {'cond',1,[{clause,2,[],[[{tuple,2,[{atom,2,foo},{atom,2,bar}]}]],
+ [{cons,3,{atom,3,a},{cons,3,{atom,3,b},{nil,3}}}]},
+ {clause,4,[],[[{atom,4,true}]],
+ [{tuple,5,[{atom,5,x},{atom,5,y}]}]}]},
+ ?line CChars = lists:flatten(erl_pp:expr(C)),
+% ?line "cond {foo,bar} -> [a,b]; true -> {x,y} end" = CChars,
+ ?line "cond\n"
+ " {foo,bar} ->\n"
+ " [a,b];\n"
+ " true ->\n"
+ " {x,y}\n"
+ "end" = CChars,
+% ?line ok = pp_expr(<<"cond
+% {foo,bar} ->
+% [a,b];
+% true ->
+% {x,y}
+% end">>),
+ ok.
+
+block(suite) ->
+ [];
+block(Config) when is_list(Config) ->
+ Ts = [{block_1,
+ <<"t() -> begin a,{c,d} end.">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+case1(suite) ->
+ [];
+case1(Config) when is_list(Config) ->
+ Ts = [{case_1,
+ <<"t() -> case {foo,bar} of
+ {A,B} when true ->
+ [A,B];
+ _ ->
+ foo
+ end.">>}
+ ],
+ ?line compile(Config, Ts),
+ ?line ok = pp_expr(<<"case
+ erl_internal:bif(M,F,length(Args))
+ of
+ true ->
+ call(N,Args,Prec,Hook);
+ false ->
+ call(Name,Args,Prec,Hook)
+ end">>),
+ ok.
+
+ops(suite) ->
+ [];
+ops(Config) when is_list(Config) ->
+ Ts = [{ops_1,
+ <<"t() -> {a,b} + (3 - 2) + 4.">>},
+ {ops_2,
+ <<"t() -> a - (3 + 4).">>},
+ {ops_3,
+ <<"t() -> - (- (- (- (- 3)))).">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+messages(suite) ->
+ [];
+messages(Config) when is_list(Config) ->
+ ?line true = "{error,{some,\"error\"}}\n" =:=
+ lists:flatten(erl_pp:form({error,{some,"error"}})),
+ ?line true = "{warning,{some,\"warning\"}}\n" =:=
+ lists:flatten(erl_pp:form({warning,{some,"warning"}})),
+ ?line true = "\n" =:= lists:flatten(erl_pp:form({eof,0})),
+ ok.
+
+old_mnemosyne_syntax(suite) ->
+ [];
+old_mnemosyne_syntax(Config) when is_list(Config) ->
+ %% Since we have kept the 'query' syntax and ':-' token,
+ %% better test that we can pretty print it.
+ Q = {'query',6,
+ {lc,6,
+ {var,6,'X'},
+ [{generate,6,
+ {var,6,'X'},
+ {call,6,{atom,6,table},[{atom,6,tab}]}},
+ {match,7,
+ {record_field,7,{var,7,'X'},{atom,7,foo}},
+ {atom,7,bar}}]}},
+ ?line "query\n"
+ " [ \n" % extra space...
+ " X ||\n"
+ " X <- table(tab),\n"
+ " X.foo = bar\n"
+ " ]\n"
+ "end" =
+ lists:flatten(erl_pp:expr(Q)),
+
+ R = {rule,12,sales,2,
+ [{clause,12,
+ [{var,12,'E'},{atom,12,employee}],
+ [],
+ [{generate,13,
+ {var,13,'E'},
+ {call,13,{atom,13,table},[{atom,13,employee}]}},
+ {match,14,
+ {record_field,14,{var,14,'E'},{atom,14,salary}},
+ {atom,14,sales}}]}]},
+ ?line "sales(E, employee) :-\n"
+ " E <- table(employee),\n"
+ " E.salary = sales.\n" =
+ lists:flatten(erl_pp:form(R)),
+ ok.
+
+
+attributes(suite) ->
+ [misc_attrs, import_export].
+
+import_export(suite) ->
+ [];
+import_export(Config) when is_list(Config) ->
+ Ts = [{import_1,
+ <<"-import(lists, [max/1, reverse/1]).
+ -import(sofs, []).
+ t(L) ->
+ max(reverse(L)).">>},
+ {export_1,
+ <<"-export([t/0]).
+ -export([]).
+ t() -> [].">>},
+ {qlc_1,
+ <<"-include_lib(\"stdlib/include/qlc.hrl\").
+ t() -> qlc:q([X || X <- []]).">>}
+ ],
+ ?line compile(Config, Ts),
+ ok.
+
+misc_attrs(suite) ->
+ [];
+misc_attrs(Config) when is_list(Config) ->
+ ?line ok = pp_forms(<<"-module(m). ">>),
+ ?line ok = pp_forms(<<"-module(m.p, [A,B]). ">>),
+ ?line ok = pp_forms(<<"-module(m, [Aafjlksfjdlsjflsdfjlsdjflkdsfjlk,"
+ "Blsjfdlslfjsdf]). ">>),
+ ?line ok = pp_forms(<<"-export([]). ">>),
+ ?line ok = pp_forms(<<"-export([foo/2, bar/0]). ">>),
+ ?line ok = pp_forms(<<"-export([bar/0]). ">>),
+ ?line ok = pp_forms(<<"-import(.lists). ">>),
+ ?line ok = pp_forms(<<"-import(lists, []). ">>),
+ ?line ok = pp_forms(<<"-import(lists, [map/2]). ">>),
+ ?line ok = pp_forms(<<"-import(lists, [map/2, foreach/2]). ">>),
+ ?line ok = pp_forms(<<"-'wild '({attr2,3}). ">>),
+ ?line ok = pp_forms(<<"-record(a, {b,c}). ">>),
+ ?line ok = pp_forms(<<"-record(' a ', {}). ">>),
+ ?line ok = pp_forms(<<"-record(' a ', {foo = foo:bar()}). ">>),
+
+ ok.
+
+hook(suite) ->
+ [];
+hook(Config) when is_list(Config) ->
+ Lc = parse_expr(binary_to_list(<<"[X || X <- [1,2,3]].">>)),
+ H = fun hook/4,
+ Expr = {call,0,{atom,0,fff},[{foo,Lc},{foo,Lc},{foo,Lc}]},
+ EChars = lists:flatten(erl_pp:expr(Expr, 0, H)),
+ Call = {call,0,{atom,0,foo},[Lc]},
+ Expr2 = {call,0,{atom,0,fff},[Call,Call,Call]},
+ EChars2 = erl_pp:exprs([Expr2]),
+ ?line true = EChars =:= lists:flatten(EChars2),
+
+ EsChars = erl_pp:exprs([Expr], H),
+ ?line true = EChars =:= lists:flatten(EsChars),
+
+ F = {function,1,ffff,0,[{clause,1,[],[],[Expr]}]},
+ FuncChars = lists:flatten(erl_pp:function(F, H)),
+ F2 = {function,1,ffff,0,[{clause,1,[],[],[Expr2]}]},
+ FuncChars2 = erl_pp:function(F2),
+ ?line true = FuncChars =:= lists:flatten(FuncChars2),
+ FFormChars = erl_pp:form(F, H),
+ ?line true = FuncChars =:= lists:flatten(FFormChars),
+
+ A = {attribute,1,record,{r,[{record_field,1,{atom,1,a},Expr}]}},
+ AChars = lists:flatten(erl_pp:attribute(A, H)),
+ A2 = {attribute,1,record,{r,[{record_field,1,{atom,1,a},Expr2}]}},
+ AChars2 = erl_pp:attribute(A2),
+ ?line true = AChars =:= lists:flatten(AChars2),
+ AFormChars = erl_pp:form(A, H),
+ ?line true = AChars =:= lists:flatten(AFormChars),
+
+ R = {rule,0,sales,0,
+ [{clause,0,[{var,0,'E'},{atom,0,employee}],[],
+ [{generate,2,{var,2,'E'},
+ {call,2,{atom,2,table},[{atom,2,employee}]}},
+ {match,3,
+ {record_field,3,{var,3,'E'},{atom,3,salary}},
+ {foo,Expr}}]}]},
+ RChars = lists:flatten(erl_pp:rule(R, H)),
+ R2 = {rule,0,sales,0,
+ [{clause,0,[{var,0,'E'},{atom,0,employee}],[],
+ [{generate,2,{var,2,'E'},
+ {call,2,{atom,2,table},[{atom,2,employee}]}},
+ {match,3,
+ {record_field,3,{var,3,'E'},{atom,3,salary}},
+ {call,0,{atom,0,foo},[Expr2]}}]}]},
+ RChars2 = erl_pp:rule(R2),
+ ?line true = RChars =:= lists:flatten(RChars2),
+ ARChars = erl_pp:form(R, H),
+ ?line true = RChars =:= lists:flatten(ARChars),
+
+ ?line "INVALID-FORM:{foo,bar}:" = lists:flatten(erl_pp:expr({foo,bar})),
+
+ %% A list (as before R6), not a list of lists.
+ G = [{op,1,'>',{atom,1,a},{foo,{atom,1,b}}}], % not a proper guard
+ GChars = lists:flatten(erl_pp:guard(G, H)),
+ G2 = [{op,1,'>',{atom,1,a},
+ {call,0,{atom,0,foo},[{atom,1,b}]}}], % not a proper guard
+ GChars2 = erl_pp:guard(G2),
+ ?line true = GChars =:= lists:flatten(GChars2),
+
+ EH = {?MODULE, ehook, [foo,bar]},
+ XEChars = erl_pp:expr(Expr, -1, EH),
+ ?line true = remove_indentation(EChars) =:= lists:flatten(XEChars),
+ XEChars2 = erl_pp:expr(Expr, EH),
+ ?line true = EChars =:= lists:flatten(XEChars2),
+
+ %% Note: no leading spaces before "begin".
+ Block = {block,0,[{match,0,{var,0,'A'},{integer,0,3}},
+ {atom,0,true}]},
+ ?line "begin\n A =" ++ _ =
+ lists:flatten(erl_pp:expr(Block, 17, none)),
+
+ %% Special...
+ ?line true =
+ "{some,value}" =:= lists:flatten(erl_pp:expr({value,0,{some,value}})),
+
+ %% Silly...
+ ?line true =
+ "if true -> 0 end" =:=
+ flat_expr({'if',0,[{clause,0,[],[],[{atom,0,0}]}]}),
+
+ %% More compatibility: before R6
+ OldIf = {'if',0,[{clause,0,[],[{atom,0,true}],[{atom,0,b}]}]},
+ NewIf = {'if',0,[{clause,0,[],[[{atom,0,true}]],[{atom,0,b}]}]},
+ OldIfChars = lists:flatten(erl_pp:expr(OldIf)),
+ NewIfChars = lists:flatten(erl_pp:expr(NewIf)),
+ ?line true = OldIfChars =:= NewIfChars,
+
+ ok.
+
+remove_indentation(S) ->
+ %% T is for the very special leaf(" ") used for lc and bc.
+ T = re:replace(S, " \n *", "", [{return,list},global]),
+ re:replace(T, "\n *", " ", [{return,list},global]).
+
+ehook(HE, I, P, H, foo, bar) ->
+ hook(HE, I, P, H).
+
+hook({foo,E}, I, P, H) ->
+ erl_pp:expr({call,0,{atom,0,foo},[E]}, I, P, H).
+
+neg_indent(suite) ->
+ [];
+neg_indent(Config) when is_list(Config) ->
+ ?line ok = pp_expr(<<"begin a end">>),
+ ?line ok = pp_expr(<<"begin a,b end">>),
+ ?line ok = pp_expr(<<"try a,b,c
+ catch exit:_ -> d;
+ throw:_ -> t;
+ error:{foo,bar} -> foo,
+ bar
+ end">>),
+ ?line ok = pp_expr(
+ <<"fun() ->
+ F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
+ or (B#r2.b) or (A#r1.b) ->
+ true;
+ (_, _) -> false
+ end,
+ true = F(#r1{a = false, b = false},
+ #r2{a = false, b = true}),
+ false = F(#r1{a = true, b = true},
+ #r1{a = false, b = true}),
+ ok
+ end()">>),
+
+ ?line ok = pp_expr(<<"[X || X <- a, true]">>),
+ ?line ok = pp_expr(<<"{[a,b,c],[d,e|f]}">>),
+ ?line ok = pp_expr(<<"f(a,b,c)">>),
+ ?line ok = pp_expr(<<"fun() when a,b;c,d -> a end">>),
+ ?line ok = pp_expr(<<"<<34:32,17:32>>">>),
+ ?line ok = pp_expr(<<"if a,b,c -> d; e,f,g -> h,i end">>),
+ ?line ok = pp_expr(<<"if a -> d; c -> d end">>),
+ ?line ok = pp_expr(<<"receive after 1 -> 2 end">>),
+ ?line ok = pp_expr(<<"begin a,b,c end">>),
+
+ ?line "\"\"" = flat_expr({string,0,""}),
+ ?line ok = pp_expr(<<"\"abc\"">>),
+ ?line ok = pp_expr(<<"\"abc\n\n\n\n\nkjsd\n\n\n\n\nkljsddf\n\n\n\n\n"
+ "klafd\n\n\n\n\nkljsdf\n\n\n\n\nsdf\n\n\n\n\n\"">>),
+ ?line ok = pp_expr(<<"fkjlskljklkkljlkjlkjkljlkjsljklf"
+ "lsdjlfdsjlfjsdlfjdslfjdlsjfsdjfklsdkfjsdf("
+ "\"abc\n\n\n\n\nkjsd\n\n\n\n\nkljsddf\n\n\n\n\n"
+ "kljsafd\n\n\n\n\nkljsdf\n\n\n\n\nkjsdf"
+ "\n\n\n\n\n\")">>),
+
+ %% fun-info is skipped when everything is to fit on one single line
+ Fun1 = {'fun',1,{function,t,0},{0,45353021,'-t/0-fun-0-'}},
+ ?line "fun t/0" = flat_expr(Fun1),
+ Fun2 = {'fun',2,{clauses,[{clause,2,[],[],[{atom,3,true}]}]},
+ {0,108059557,'-t/0-fun-0-'}},
+ ?line "fun() -> true end" = flat_expr(Fun2),
+
+ ok.
+
+tickets(suite) ->
+ [otp_6321, otp_6911, otp_6914, otp_8150, otp_8238].
+
+otp_6321(doc) ->
+ "OTP_6321. Bug fix of exprs().";
+otp_6321(suite) -> [];
+otp_6321(Config) when is_list(Config) ->
+ Str = "S = hopp, {hej, S}. ",
+ {done, {ok, Tokens, _EndLine}, ""} = erl_scan:tokens("", Str, _L=1),
+ {ok, Exprs} = erl_parse:parse_exprs(Tokens),
+ "S = hopp, {hej,S}" = lists:flatten(erl_pp:exprs(Exprs)),
+ ok.
+
+otp_6911(doc) ->
+ "OTP_6911. More newlines.";
+otp_6911(suite) -> [];
+otp_6911(Config) when is_list(Config) ->
+ F = {function,5,thomas,1,
+ [{clause,5,
+ [{var,5,'X'}],
+ [],
+ [{'case',6,
+ {var,6,'X'},
+ [{clause,7,[{atom,7,true}],[],[{integer,7,12}]},
+ {clause,8,[{atom,8,false}],[],[{integer,8,14}]}]}]}]},
+ ?line Chars = lists:flatten(erl_pp:form(F)),
+ ?line "thomas(X) ->\n"
+ " case X of\n"
+ " true ->\n"
+ " 12;\n"
+ " false ->\n"
+ " 14\n"
+ " end.\n" = Chars,
+ ?line ok = pp_expr(<<"case X of true -> 12; false -> 14 end">>),
+ ?line ok = pp_expr(<<"receive after 1 -> ok end">>),
+ ok.
+
+otp_6914(doc) ->
+ "OTP_6914. Binary comprehensions.";
+otp_6914(suite) -> [];
+otp_6914(Config) when is_list(Config) ->
+ ?line ok = pp_expr(<<"<< <<B:1>> || B <- [0,1,1] >>">>),
+ ?line ok = pp_expr(<<"[ B || <<B:1>> <= <<\"hi\">>]">>),
+ ?line ok = pp_expr(<<"<< <<1:1>> || true >>">>),
+ ok.
+
+otp_8150(doc) ->
+ "OTP_8150. Types.";
+otp_8150(suite) -> [];
+otp_8150(Config) when is_list(Config) ->
+ ?line _ = [{N,ok} = {N,pp_forms(B)} ||
+ {N,B} <- type_examples()
+ ],
+ ok.
+
+otp_8238(doc) ->
+ "OTP_8238. Bugfix 'E'.";
+otp_8238(suite) -> [];
+otp_8238(Config) when is_list(Config) ->
+ Ex = [<<"-record(rec1, {}).\n"
+ "-record(rec2, {a, b}).\n"
+ "-record(rec3, {f123, g, h}).\n">>,
+ <<"-type line() :: integer().\n">>,
+ <<"-type info_line() :: integer() | term().\n">>,
+ <<"-type column() :: pos_integer().\n">>,
+ [["\n", B] || {_,B} <- type_examples()],
+ <<"t1(T) ->\n"
+ " foo:bar(#rec1{}, #rec2{}),\n"
+ " T.\n"
+ "t2() ->\n"
+ " #r{}.\n">>
+ ],
+ ?line compile(Config, [{otp_8238,iolist_to_binary(Ex)}]),
+ ok.
+
+type_examples() ->
+ [{ex1,<<"-type ann() :: Var :: integer(). ">>},
+ {ex2,<<"-type ann2() :: Var :: "
+ "'return' | 'return_white_spaces' | 'return_comments'"
+ " | 'text' | ann(). ">>},
+ {ex3,<<"-type paren() :: (ann2()). ">>},
+ {ex4,<<"-type t1() :: atom(). ">>},
+ {ex5,<<"-type t2() :: [t1()]. ">>},
+ {ex6,<<"-type t3(Atom) :: integer(Atom). ">>},
+ {ex7,<<"-type t4() :: t3(foobar). ">>},
+ {ex8,<<"-type t5() :: {t1(), t3(foo)}. ">>},
+ {ex9,<<"-type t6() :: 1 | 2 | 3 | 'foo' | 'bar'. ">>},
+ {ex10,<<"-type t7() :: []. ">>},
+ {ex11,<<"-type t71() :: [_]. ">>},
+ {ex12,<<"-type t8() :: {any(),none(),pid(),port(),"
+ "reference(),float()}. ">>},
+ {ex13,<<"-type t9() :: [1|2|3|foo|bar] | "
+ "list(a | b | c) | t71(). ">>},
+ {ex14,<<"-type t10() :: {1|2|3|foo|t9()} | {}. ">>},
+ {ex15,<<"-type t11() :: 1..2. ">>},
+ {ex16,<<"-type t13() :: maybe_improper_list(integer(), t11()). ">>},
+ {ex17,<<"-type t14() :: [erl_scan:foo() | "
+ "erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. ">>},
+ {ex18,<<"-type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>,"
+ "<<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>|"
+ "<<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>|"
+ "<<_:34>>|<<_:34>>|<<_:34>>]. ">>},
+ {ex19,<<"-type t16() :: fun(). ">>},
+ {ex20,<<"-type t17() :: fun((...) -> paren()). ">>},
+ {ex21,<<"-type t18() :: fun(() -> t17() | t16()). ">>},
+ {ex22,<<"-type t19() :: fun((t18()) -> t16()) |"
+ "fun((nonempty_maybe_improper_list('integer', any())|"
+ "1|2|3|a|b|<<_:3,_:_*14>>|integer()) ->"
+ "nonempty_maybe_improper_list('integer', any())|"
+ "1|2|3|a|b|<<_:3,_:_*14>>|integer()). ">>},
+ {ex23,<<"-type t20() :: [t19(), ...]. ">>},
+ {ex24,<<"-type t21() :: tuple(). ">>},
+ {ex25,<<"-type t21(A) :: A. ">>},
+ {ex26,<<"-type t22() :: t21(integer()). ">>},
+ {ex27,<<"-type t23() :: #rec1{}. ">>},
+ {ex28,<<"-type t24() :: #rec2{a :: t23(), b :: [atom()]}. ">>},
+ {ex29,<<"-type t25() :: #rec3{f123 :: [t24() | "
+ "1|2|3|4|a|b|c|d| "
+ "nonempty_maybe_improper_list(integer, any())]}. ">>},
+ {ex30,<<"-type t99() ::"
+ "{t2(),t4(),t5(),t6(),t7(),t8(),t10(),t14(),"
+ "t15(),t20(),t21(), t22(),t25()}. ">>},
+ {ex31,<<"-spec t1(FooBar :: t99()) -> t99();"
+ "(t2()) -> t2();"
+ "(t4()) -> t4() when is_subtype(t4(), t24);"
+ "(t23()) -> t23() when is_subtype(t23(), atom()),"
+ " is_subtype(t23(), t14());"
+ "(t24()) -> t24() when is_subtype(t24(), atom()),"
+ " is_subtype(t24(), t14()),"
+ " is_subtype(t24(), t4()).">>},
+ {ex32,<<"-spec mod:t2() -> any(). ">>},
+ {ex33,<<"-opaque attributes_data() :: "
+ "[{'column', column()} | {'line', info_line()} |"
+ " {'text', string()}] | {line(),column()}. ">>},
+ {ex34,<<"-record(r,{"
+ "f1 :: attributes_data(),"
+ "f222 = foo:bar(34, #rec3{}, 234234234423, "
+ " aassdsfsdfsdf, 2234242323) :: "
+ " [t24() | 1|2|3|4|a|b|c|d| "
+ " nonempty_maybe_improper_list(integer, any())],"
+ "f333 :: [t24() | 1|2|3|4|a|b|c|d| "
+ " nonempty_maybe_improper_list(integer, any())],"
+ "f3 = x:y(),"
+ "f4 = x:z() :: t99(),"
+ "f17 :: 'undefined',"
+ "f18 :: 1 | 2 | 'undefined',"
+ "f19 = 3 :: integer()|undefined,"
+ "f5 = 3 :: undefined|integer()}). ">>}].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+compile(Config, Tests) ->
+ F = fun({N,P}, BadL) ->
+ case catch compile_file(Config, P) of
+ ok ->
+ case pp_forms(P) of
+ ok ->
+ BadL;
+ not_ok ->
+ ?t:format("~nTest ~p failed.~n", [N]),
+ fail()
+ end;
+ Bad ->
+ ?t:format("~nTest ~p failed. got~n ~p~n",
+ [N, Bad]),
+ fail()
+ end
+ end,
+ lists:foldl(F, [], Tests).
+
+compile_file(Config, Test0) ->
+ case compile_file(Config, Test0, ['E']) of
+ {ok, RootFile} ->
+ File = RootFile ++ ".E",
+ {ok, Bin0} = file:read_file(File),
+ Bin = strip_module_info(Bin0),
+ %% A very simple check: just try to compile the output.
+ case compile_file(Config, Bin, []) of
+ {ok, RootFile2} ->
+ File2 = RootFile2 ++ ".E",
+ {ok, Bin1} = file:read_file(File2),
+ case Bin0 =:= Bin1 of
+ true ->
+ test_max_line(binary_to_list(Bin));
+ false ->
+ {error, file_contents_modified, {Bin0, Bin1}}
+ end;
+ Error ->
+ {error, could_not_compile_E_file, Error}
+ end;
+ Error ->
+ Error
+ end.
+
+compile_file(Config, Test0, Opts0) ->
+ FileName = filename('erl_pp_test.erl', Config),
+ Test = list_to_binary(["-module(erl_pp_test). "
+ "-compile(export_all). ",
+ Test0]),
+ Opts = [export_all,return,nowarn_unused_record,{outdir,?privdir} | Opts0],
+ ok = file:write_file(FileName, Test),
+ case compile:file(FileName, Opts) of
+ {ok, _M, _Ws} ->
+ {ok, filename:rootname(FileName)};
+ Error -> Error
+ end.
+
+strip_module_info(Bin) ->
+ {match, [{Start,_Len}|_]} = re:run(Bin, "module_info"),
+ <<R:Start/binary,_/binary>> = Bin,
+ R.
+
+flat_expr(Expr) ->
+ lists:flatten(erl_pp:expr(Expr, -1, none)).
+
+pp_forms(Bin) ->
+ pp_forms(Bin, none).
+
+pp_forms(Bin, Hook) ->
+ PP1 = (catch parse_and_pp_forms(binary_to_list(Bin), Hook)),
+ PP2 = (catch parse_and_pp_forms(PP1, Hook)),
+ case PP1 =:= PP2 of % same line numbers
+ true ->
+ test_max_line(PP1);
+ false ->
+ not_ok
+ end.
+
+parse_and_pp_forms(String, Hook) ->
+ lists:append(lists:map(fun(AF) -> erl_pp:form(AF, Hook)
+ end, parse_forms(String))).
+
+parse_forms(Chars) ->
+ String = lists:flatten(Chars),
+ parse_forms2(String, [], 1, []).
+
+parse_forms2([], _Cont, _Line, Forms) ->
+ lists:reverse(Forms);
+parse_forms2(String, Cont0, Line, Forms) ->
+ case erl_scan:tokens(Cont0, String, Line) of
+ {done, {ok, Tokens, EndLine}, Chars} ->
+ {ok, Form} = erl_parse:parse_form(Tokens),
+ parse_forms2(Chars, [], EndLine, [Form | Forms]);
+ {more, Cont} when element(3, Cont) =:= [] ->
+ %% extra spaces after forms...
+ parse_forms2([], Cont, Line, Forms);
+ {more, Cont} ->
+ %% final dot needs a space...
+ parse_forms2(" ", Cont, Line, Forms)
+ end.
+
+pp_expr(Bin) ->
+ pp_expr(Bin, none).
+
+%% Final dot is added.
+pp_expr(Bin, Hook) ->
+ PP1 = (catch parse_and_pp_expr(binary_to_list(Bin), 0, Hook)),
+ PPneg = (catch parse_and_pp_expr(binary_to_list(Bin), -1, Hook)),
+ PP2 = (catch parse_and_pp_expr(PPneg, 0, Hook)),
+ if
+ PP1 =:= PP2 -> % same line numbers
+ case
+ (test_max_line(PP1) =:= ok) and (test_new_line(PPneg) =:= ok)
+ of
+ true ->
+ ok;
+ false ->
+ not_ok
+ end;
+ true ->
+ not_ok
+ end.
+
+parse_and_pp_expr(String, Indent, Hook) ->
+ StringDot = lists:flatten(String) ++ ".",
+ erl_pp:expr(parse_expr(StringDot), Indent, Hook).
+
+parse_expr(Chars) ->
+ {ok, Tokens, _} = erl_scan:string(Chars),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ Expr.
+
+test_new_line(String) ->
+ case string:chr(String, $\n) of
+ 0 -> ok;
+ _ -> not_ok
+ end.
+
+test_max_line(String) ->
+ case max_line(String) of
+ ML when ML > 100 ->
+ {error, max_line_too_big, {ML,String}};
+ _ML ->
+ ok
+ end.
+
+max_line(String) ->
+ lists:max([0 | [length(Sub) ||
+ Sub <- string:tokens(String, "\n"),
+ string:substr(Sub, 1, 5) =/= "-file"]]).
+
+filename(Name, Config) when is_atom(Name) ->
+ filename(atom_to_list(Name), Config);
+filename(Name, Config) ->
+ filename:join(?privdir, Name).
+
+fail() ->
+ io:format("failed~n"),
+ ?t:fail().
diff --git a/lib/stdlib/test/erl_scan_SUITE.erl b/lib/stdlib/test/erl_scan_SUITE.erl
new file mode 100644
index 0000000000..32a06d15c7
--- /dev/null
+++ b/lib/stdlib/test/erl_scan_SUITE.erl
@@ -0,0 +1,1214 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(erl_scan_SUITE).
+-export([all/1]).
+
+-export([error/1, error_1/1, error_2/1, iso88591/1, otp_7810/1]).
+
+-import(lists, [nth/2,flatten/1]).
+-import(io_lib, [print/1]).
+
+%%
+%% Define to run outside of test server
+%%
+%-define(STANDALONE,1).
+
+-ifdef(STANDALONE).
+-compile(export_all).
+-define(line, put(line, ?LINE), ).
+-define(config(A,B),config(A,B)).
+-define(t, test_server).
+%% config(priv_dir, _) ->
+%% ".";
+%% config(data_dir, _) ->
+%% ".".
+-else.
+-include("test_server.hrl").
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+init_per_testcase(_Case, Config) when is_list(Config) ->
+ ?line Dog=test_server:timetrap(test_server:seconds(1200)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+-endif.
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+all(doc) ->
+ ["Test cases for the 'erl_scan' module."];
+all(suite) ->
+ [error,iso88591,otp_7810].
+
+error(doc) ->
+ ["Error cases"];
+error(suite) ->
+ [error_1, error_2].
+
+error_1(doc) ->
+ ["(OTP-2347)"];
+error_1(suite) ->
+ [];
+error_1(Config) when is_list(Config) ->
+ ?line {error, _, _} = erl_scan:string("'a"),
+ ok.
+
+error_2(doc) ->
+ ["Checks that format_error works on the error cases."];
+error_2(suite) ->
+ [];
+error_2(Config) when is_list(Config) ->
+ ?line lists:foreach(fun check/1, error_cases()),
+ ok.
+
+error_cases() ->
+ ["'a",
+ "\"a",
+ "'\\",
+ "\"\\",
+ "$",
+ "$\\",
+ "2.3e",
+ "2.3e-",
+ "91#9"
+].
+
+assert_type(N, integer) when is_integer(N) ->
+ ok;
+assert_type(N, atom) when is_atom(N) ->
+ ok.
+
+check(String) ->
+ Error = erl_scan:string(String),
+ check_error(Error, erl_scan).
+
+%%% (This should be useful for all format_error functions.)
+check_error({error, Info, EndLine}, Module0) ->
+ ?line {ErrorLine, Module, Desc} = Info,
+ ?line true = (Module == Module0),
+ ?line assert_type(EndLine, integer),
+ ?line assert_type(ErrorLine, integer),
+ ?line true = (ErrorLine =< EndLine),
+ ?line String = lists:flatten(Module0:format_error(Desc)),
+ ?line true = io_lib:printable_list(String).
+
+iso88591(doc) -> ["Tests the support for ISO-8859-1 i.e Latin-1"];
+iso88591(suite) -> [];
+iso88591(Config) when is_list(Config) ->
+ ?line ok =
+ case catch begin
+ %% Some atom and variable names
+ V1s = [$�,$�,$�,$�],
+ V2s = [$N,$�,$r],
+ A1s = [$h,$�,$r],
+ A2s = [$�,$r,$e],
+ %% Test parsing atom and variable characters.
+ {ok,Ts1,_} = erl_scan:string(V1s ++ " " ++ V2s ++
+ "\327" ++
+ A1s ++ " " ++ A2s),
+ V1s = atom_to_list(element(3, nth(1, Ts1))),
+ V2s = atom_to_list(element(3, nth(2, Ts1))),
+ A1s = atom_to_list(element(3, nth(4, Ts1))),
+ A2s = atom_to_list(element(3, nth(5, Ts1))),
+ %% Test printing atoms
+ A1s = flatten(print(element(3, nth(4, Ts1)))),
+ A2s = flatten(print(element(3, nth(5, Ts1)))),
+ %% Test parsing and printing strings.
+ S1 = V1s ++ "\327" ++ A1s ++ "\250" ++ A2s,
+ S1s = "\"" ++ S1 ++ "\"",
+ {ok,Ts2,_} = erl_scan:string(S1s),
+ S1 = element(3, nth(1, Ts2)),
+ S1s = flatten(print(element(3, nth(1, Ts2)))),
+ ok %It all worked
+ end of
+ {'EXIT',R} -> %Something went wrong!
+ {error,R};
+ ok -> ok %Aok
+ end.
+
+otp_7810(doc) ->
+ ["OTP-7810. White spaces, comments, and more.."];
+otp_7810(suite) ->
+ [];
+otp_7810(Config) when is_list(Config) ->
+ ?line ok = reserved_words(),
+ ?line ok = atoms(),
+ ?line ok = punctuations(),
+ ?line ok = comments(),
+ ?line ok = errors(),
+ ?line ok = integers(),
+ ?line ok = base_integers(),
+ ?line ok = floats(),
+ ?line ok = dots(),
+ ?line ok = chars(),
+ ?line ok = variables(),
+ ?line ok = eof(),
+ ?line ok = illegal(),
+ ?line ok = crashes(),
+
+ ?line ok = options(),
+ ?line ok = token_info(),
+ ?line ok = column_errors(),
+ ?line ok = white_spaces(),
+
+ ?line ok = unicode(),
+
+ ?line ok = more_chars(),
+ ?line ok = more_options(),
+ ?line ok = attributes_info(),
+ ?line ok = set_attribute(),
+
+ ok.
+
+reserved_words() ->
+ L = ['after', 'begin', 'case', 'try', 'cond', 'catch',
+ 'andalso', 'orelse', 'end', 'fun', 'if', 'let', 'of',
+ 'query', 'receive', 'when', 'bnot', 'not', 'div',
+ 'rem', 'band', 'and', 'bor', 'bxor', 'bsl', 'bsr',
+ 'or', 'xor', 'spec'] , % 'spec' shouldn't be there...
+ [begin
+ ?line {RW, true} = {RW, erl_scan:reserved_word(RW)},
+ S = atom_to_list(RW),
+ Ts = [{RW,1}],
+ ?line test_string(S, Ts)
+ end || RW <- L],
+ ok.
+
+
+atoms() ->
+ ?line test_string("a
+ b", [{atom,1,a},{atom,2,b}]),
+ ?line test_string("'a b'", [{atom,1,'a b'}]),
+ ?line test_string("a", [{atom,1,a}]),
+ ?line test_string("a@2", [{atom,1,a@2}]),
+ ?line test_string([39,65,200,39], [{atom,1,'A�'}]),
+ ?line test_string("�rlig �sten", [{atom,1,�rlig},{atom,1,�sten}]),
+ ?line {ok,[{atom,_,'$a'}],{1,6}} =
+ erl_scan:string("'$\\a'", {1,1}),
+ ?line test("'$\\a'"),
+ ok.
+
+punctuations() ->
+ L = ["<<", "<-", "<=", "<", ">>", ">=", ">", "->", "--",
+ "-", "++", "+", "=:=", "=/=", "=<", "==", "=", "/=",
+ "/", "||", "|", ":-", "::", ":"],
+ %% One token at a time:
+ [begin
+ W = list_to_atom(S),
+ Ts = [{W,1}],
+ ?line test_string(S, Ts)
+ end || S <- L],
+ Three = ["/=:=", "<=:=", "==:=", ">=:="], % three tokens...
+ No = Three ++ L,
+ SL0 = [{S1++S2,{-length(S1),S1,S2}} ||
+ S1 <- L,
+ S2 <- L,
+ not lists:member(S1++S2, No)],
+ SL = family_list(SL0),
+ %% Two tokens. When there are several answers, the one with
+ %% the longest first token is chosen:
+ %% [the special case "=<<" is among the tested ones]
+ [begin
+ W1 = list_to_atom(S1),
+ W2 = list_to_atom(S2),
+ Ts = [{W1,1},{W2,1}],
+ ?line test_string(S, Ts)
+ end || {S,[{_,S1,S2}|_]} <- SL],
+
+ PTs1 = [{'!',1},{'(',1},{')',1},{',',1},{';',1},{'=',1},{'[',1},
+ {']',1},{'{',1},{'|',1},{'}',1}],
+ ?line test_string("!(),;=[]{|}", PTs1),
+
+ PTs2 = [{'#',1},{'&',1},{'*',1},{'+',1},{'/',1},
+ {':',1},{'<',1},{'>',1},{'?',1},{'@',1},
+ {'\\',1},{'^',1},{'`',1},{'~',1}],
+ ?line test_string("#&*+/:<>?@\\^`~", PTs2),
+
+ ok.
+
+comments() ->
+ ?line test("a %%\n b"),
+ ?line {ok,[],1} = erl_scan:string("%"),
+ ?line test("a %%\n b"),
+ ?line {ok,[{atom,_,a},{atom,_,b}],{2,3}} =
+ erl_scan:string("a %%\n b",{1,1}),
+ ?line {ok,[{atom,_,a},{comment,_,"%%"},{atom,_,b}],{2,3}} =
+ erl_scan:string("a %%\n b",{1,1}, [return_comments]),
+ ?line {ok,[{atom,_,a},
+ {white_space,_," "},
+ {white_space,_,"\n "},
+ {atom,_,b}],
+ {2,3}} =
+ erl_scan:string("a %%\n b",{1,1},[return_white_spaces]),
+ ?line {ok,[{atom,_,a},
+ {white_space,_," "},
+ {comment,_,"%%"},
+ {white_space,_,"\n "},
+ {atom,_,b}],
+ {2,3}} = erl_scan:string("a %%\n b",{1,1},[return]),
+ ok.
+
+errors() ->
+ ?line {error,{1,erl_scan,{string,$',"qa"}},1} = erl_scan:string("'qa"), %'
+ ?line {error,{1,erl_scan,{string,$","str"}},1} = %"
+ erl_scan:string("\"str"), %"
+ ?line {error,{1,erl_scan,char},1} = erl_scan:string("$"),
+ ?line test_string([34,65,200,34], [{string,1,"A�"}]),
+ ?line test_string("\\", [{'\\',1}]),
+ ?line {'EXIT',_} =
+ (catch {foo, erl_scan:string('$\\a', {1,1})}), % type error
+ ?line {'EXIT',_} =
+ (catch {foo, erl_scan:tokens([], '$\\a', {1,1})}), % type error
+
+ ?line "{a,tuple}" = erl_scan:format_error({a,tuple}),
+ ok.
+
+integers() ->
+ [begin
+ I = list_to_integer(S),
+ Ts = [{integer,1,I}],
+ ?line test_string(S, Ts)
+ end || S <- [[N] || N <- lists:seq($0, $9)] ++ ["2323","000"] ],
+ ok.
+
+base_integers() ->
+ [begin
+ B = list_to_integer(BS),
+ I = erlang:list_to_integer(S, B),
+ Ts = [{integer,1,I}],
+ ?line test_string(BS++"#"++S, Ts)
+ end || {BS,S} <- [{"2","11"}, {"5","23234"}, {"12","05a"},
+ {"16","abcdef"}, {"16","ABCDEF"}] ],
+
+ ?line {error,{1,erl_scan,{base,1}},1} = erl_scan:string("1#000"),
+
+ ?line test_string("12#bc", [{integer,1,11},{atom,1,c}]),
+
+ [begin
+ Str = BS ++ "#" ++ S,
+ ?line {error,{1,erl_scan,{illegal,integer}},1} =
+ erl_scan:string(Str)
+ end || {BS,S} <- [{"3","3"},{"15","f"}, {"12","c"}] ],
+
+ ?line {ok,[{integer,1,239},{'@',1}],1} = erl_scan:string("16#ef@"),
+ ?line {ok,[{integer,1,14},{atom,1,g@}],1} = erl_scan:string("16#eg@"),
+
+ ok.
+
+floats() ->
+ [begin
+ F = list_to_float(FS),
+ Ts = [{float,1,F}],
+ ?line test_string(FS, Ts)
+ end || FS <- ["1.0","001.17","3.31200","1.0e0","1.0E17",
+ "34.21E-18", "17.0E+14"]],
+ ?line test_string("1.e2", [{integer,1,1},{'.',1},{atom,1,e2}]),
+
+ ?line {error,{1,erl_scan,{illegal,float}},1} =
+ erl_scan:string("1.0e400"),
+ [begin
+ ?line {error,{1,erl_scan,{illegal,float}},1} = erl_scan:string(S)
+ end || S <- ["1.14Ea"]],
+
+ ok.
+
+dots() ->
+ Dot = [{".", {ok,[{dot,1}],1}},
+ {". ", {ok,[{dot,1}],1}},
+ {".\n", {ok,[{dot,1}],2}},
+ {".%", {ok,[{dot,1}],1}},
+ {".\210",{ok,[{dot,1}],1}},
+ {".% �h",{ok,[{dot,1}],1}},
+ {".%\n", {ok,[{dot,1}],2}},
+ {".$", {error,{1,erl_scan,char},1}},
+ {".$\\", {error,{1,erl_scan,char},1}},
+ {".a", {ok,[{'.',1},{atom,1,a}],1}}
+ ],
+ ?line [R = erl_scan:string(S) || {S, R} <- Dot],
+
+ ?line {ok,[{dot,_}=T1],{1,2}} = erl_scan:string(".", {1,1}, text),
+ ?line [{column,1},{length,1},{line,1},{text,"."}] =
+ erl_scan:token_info(T1, [column, length, line, text]),
+ ?line {ok,[{dot,_}=T2],{1,3}} = erl_scan:string(".%", {1,1}, text),
+ ?line [{column,1},{length,1},{line,1},{text,"."}] =
+ erl_scan:token_info(T2, [column, length, line, text]),
+ ?line {ok,[{dot,_}=T3],{1,6}} =
+ erl_scan:string(".% �h", {1,1}, text),
+ ?line [{column,1},{length,1},{line,1},{text,"."}] =
+ erl_scan:token_info(T3, [column, length, line, text]),
+ ?line {error,{{1,2},erl_scan,char},{1,3}} =
+ erl_scan:string(".$", {1,1}),
+ ?line {error,{{1,2},erl_scan,char},{1,4}} =
+ erl_scan:string(".$\\", {1,1}),
+
+ ?line test(". "),
+ ?line test(". "),
+ ?line test(".\n"),
+ ?line test(".\n\n"),
+ ?line test(".\n\r"),
+ ?line test(".\n\n\n"),
+ ?line test(".\210"),
+ ?line test(".%\n"),
+ ?line test(".a"),
+
+ ?line test("%. \n. "),
+ ?line {more,C} = erl_scan:tokens([], "%. ",{1,1}, return),
+ ?line {done,{ok,[{comment,_,"%. "},
+ {white_space,_,"\n"},
+ {dot,_}],
+ {2,3}}, ""} =
+ erl_scan:tokens(C, "\n. ", {1,1}, return), % any loc, any options
+
+ ?line [test_string(S, R) ||
+ {S, R} <- [{".$\n", [{'.',1},{char,1,$\n}]},
+ {"$\\\n", [{char,1,$\n}]},
+ {"'\\\n'", [{atom,1,'\n'}]},
+ {"$\n", [{char,1,$\n}]}] ],
+ ok.
+
+chars() ->
+ [begin
+ L = lists:flatten(io_lib:format("$\\~.8b", [C])),
+ Ts = [{char,1,C}],
+ ?line test_string(L, Ts)
+ end || C <- lists:seq(0, 255)],
+
+ %% Leading zeroes...
+ [begin
+ L = lists:flatten(io_lib:format("$\\~3.8.0b", [C])),
+ Ts = [{char,1,C}],
+ ?line test_string(L, Ts)
+ end || C <- lists:seq(0, 255)],
+
+ %% $\^\n now increments the line...
+ [begin
+ L = "$\\^" ++ [C],
+ Ts = [{char,1,C band 2#11111}],
+ ?line test_string(L, Ts)
+ end || C <- lists:seq(0, 255)],
+
+ [begin
+ L = "$\\" ++ [C],
+ Ts = [{char,1,V}],
+ ?line test_string(L, Ts)
+ end || {C,V} <- [{$n,$\n}, {$r,$\r}, {$t,$\t}, {$v,$\v},
+ {$b,$\b}, {$f,$\f}, {$e,$\e}, {$s,$\s},
+ {$d,$\d}]],
+
+ EC = [$\n,$\r,$\t,$\v,$\b,$\f,$\e,$\s,$\d],
+ Ds = lists:seq($0, $9),
+ X = [$^,$n,$r,$t,$v,$b,$f,$e,$s,$d],
+ New = [${,$x],
+ No = EC ++ Ds ++ X ++ New,
+ [begin
+ L = "$\\" ++ [C],
+ Ts = [{char,1,C}],
+ ?line test_string(L, Ts)
+ end || C <- lists:seq(0, 255) -- No],
+
+ [begin
+ L = "'$\\" ++ [C] ++ "'",
+ Ts = [{atom,1,list_to_atom("$"++[C])}],
+ ?line test_string(L, Ts)
+ end || C <- lists:seq(0, 255) -- No],
+
+ ?line test_string("\"\\013a\\\n\"", [{string,1,"\va\n"}]),
+
+ ?line test_string("'\n'", [{atom,1,'\n'}]),
+ ?line test_string("\"\n\a\"", [{string,1,"\na"}]),
+
+ %% No escape
+ [begin
+ L = "$" ++ [C],
+ Ts = [{char,1,C}],
+ ?line test_string(L, Ts)
+ end || C <- lists:seq(0, 255) -- (No ++ [$\\])],
+ ?line test_string("$\n", [{char,1,$\n}]),
+
+ ?line {error,{{1,1},erl_scan,char},{1,4}} =
+ erl_scan:string("$\\^",{1,1}),
+ ?line test_string("$\\\n", [{char,1,$\n}]),
+ %% Robert's scanner returns line 1:
+ ?line test_string("$\\\n", [{char,1,$\n}]),
+ ?line test_string("$\n\n", [{char,1,$\n}]),
+ ?line test("$\n\n"),
+ ok.
+
+
+variables() ->
+ ?line test_string(" \237_Aou�eiy��", [{var,1,'_Aou�eiy��'}]),
+ ?line test_string("A_b_c@", [{var,1,'A_b_c@'}]),
+ ?line test_string("V@2", [{var,1,'V@2'}]),
+ ?line test_string("ABD�", [{var,1,'ABD�'}]),
+ ?line test_string("�rlig �sten", [{var,1,'�rlig'},{var,1,'�sten'}]),
+ ok.
+
+eof() ->
+ ?line {done,{eof,1},eof} = erl_scan:tokens([], eof, 1),
+ {more, C1} = erl_scan:tokens([]," \n", 1),
+ ?line {done,{eof,2},eof} = erl_scan:tokens(C1, eof, 1),
+ {more, C2} = erl_scan:tokens([], "abra", 1),
+ %% An error before R13A.
+ %% ?line {done,Err={error,{1,erl_scan,scan},1},eof} =
+ ?line {done,{ok,[{atom,1,abra}],1},eof} =
+ erl_scan:tokens(C2, eof, 1),
+
+ %% With column.
+ ?line {more, C3} = erl_scan:tokens([]," \n",{1,1}),
+ ?line {done,{eof,{2,1}},eof} = erl_scan:tokens(C3, eof, 1),
+ {more, C4} = erl_scan:tokens([], "abra", {1,1}),
+ %% An error before R13A.
+ %% ?line {done,{error,{{1,1},erl_scan,scan},{1,5}},eof} =
+ ?line {done,{ok,[{atom,_,abra}],{1,5}},eof} =
+ erl_scan:tokens(C4, eof, 1),
+
+ %% Robert's scanner returns "" as LeftoverChars;
+ %% the R12B scanner returns eof as LeftoverChars: (eof is correct)
+ ?line {more, C5} = erl_scan:tokens([], "a", 1),
+ %% An error before R13A.
+ %% ?line {done,{error,{1,erl_scan,scan},1},eof} =
+ ?line {done,{ok,[{atom,1,a}],1},eof} =
+ erl_scan:tokens(C5,eof,1),
+
+ %% A dot followed by eof is special:
+ ?line {more, C} = erl_scan:tokens([], "a.", 1),
+ ?line {done,{ok,[{atom,1,a},{dot,1}],1},eof} = erl_scan:tokens(C,eof,1),
+ ?line {ok,[{atom,1,foo},{dot,1}],1} = erl_scan:string("foo."),
+
+ ok.
+
+illegal() ->
+ Atom = lists:duplicate(1000, $a),
+ ?line {error,{1,erl_scan,{illegal,atom}},1} = erl_scan:string(Atom),
+ ?line {done,{error,{1,erl_scan,{illegal,atom}},1},". "} =
+ erl_scan:tokens([], Atom++". ", 1),
+ QAtom = "'" ++ Atom ++ "'",
+ ?line {error,{1,erl_scan,{illegal,atom}},1} = erl_scan:string(QAtom),
+ ?line {done,{error,{1,erl_scan,{illegal,atom}},1},". "} =
+ erl_scan:tokens([], QAtom++". ", 1),
+ Var = lists:duplicate(1000, $A),
+ ?line {error,{1,erl_scan,{illegal,var}},1} = erl_scan:string(Var),
+ ?line {done,{error,{1,erl_scan,{illegal,var}},1},". "} =
+ erl_scan:tokens([], Var++". ", 1),
+ Float = "1" ++ lists:duplicate(400, $0) ++ ".0",
+ ?line {error,{1,erl_scan,{illegal,float}},1} = erl_scan:string(Float),
+ ?line {done,{error,{1,erl_scan,{illegal,float}},1},". "} =
+ erl_scan:tokens([], Float++". ", 1),
+ String = "\"43\\x{aaaaaa}34\"",
+ ?line {error,{1,erl_scan,{illegal,character}},1} = erl_scan:string(String),
+ ?line {done,{error,{1,erl_scan,{illegal,character}},1},"34\". "} =
+ %% Would be nice if `34\"' were skipped...
+ %% Maybe, but then the LeftOverChars would not be the characters
+ %% immediately following the end location of the error.
+ erl_scan:tokens([], String++". ", 1),
+
+ ?line {error,{{1,1},erl_scan,{illegal,atom}},{1,1001}} =
+ erl_scan:string(Atom, {1,1}),
+ ?line {done,{error,{{1,5},erl_scan,{illegal,atom}},{1,1005}},". "} =
+ erl_scan:tokens([], "foo "++Atom++". ", {1,1}),
+ ?line {error,{{1,1},erl_scan,{illegal,atom}},{1,1003}} =
+ erl_scan:string(QAtom, {1,1}),
+ ?line {done,{error,{{1,5},erl_scan,{illegal,atom}},{1,1007}},". "} =
+ erl_scan:tokens([], "foo "++QAtom++". ", {1,1}),
+ ?line {error,{{1,1},erl_scan,{illegal,var}},{1,1001}} =
+ erl_scan:string(Var, {1,1}),
+ ?line {done,{error,{{1,5},erl_scan,{illegal,var}},{1,1005}},". "} =
+ erl_scan:tokens([], "foo "++Var++". ", {1,1}),
+ ?line {error,{{1,1},erl_scan,{illegal,float}},{1,404}} =
+ erl_scan:string(Float, {1,1}),
+ ?line {done,{error,{{1,5},erl_scan,{illegal,float}},{1,408}},". "} =
+ erl_scan:tokens([], "foo "++Float++". ", {1,1}),
+ ?line {error,{{1,4},erl_scan,{illegal,character}},{1,14}} =
+ erl_scan:string(String, {1,1}),
+ ?line {done,{error,{{1,4},erl_scan,{illegal,character}},{1,14}},"34\". "} =
+ erl_scan:tokens([], String++". ", {1,1}),
+ ok.
+
+crashes() ->
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string([-1])}), % type error
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("$"++[-1])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("$\\"++[-1])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("$\\^"++[-1])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string([$",-1,$"],{1,1})}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("\"\\v"++[-1,$"])}), %$"
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string([$",-1,$"])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("% foo"++[-1])}),
+ ?line {'EXIT',_} =
+ (catch {foo, erl_scan:string("% foo"++[-1],{1,1})}),
+
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string([a])}), % type error
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("$"++[a])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("$\\"++[a])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("$\\^"++[a])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string([$",a,$"],{1,1})}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("\"\\v"++[a,$"])}), %$"
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string([$",a,$"])}),
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string("% foo"++[a])}),
+ ?line {'EXIT',_} =
+ (catch {foo, erl_scan:string("% foo"++[a],{1,1})}),
+
+ ?line {'EXIT',_} = (catch {foo, erl_scan:string([3.0])}), % type error
+
+ ok.
+
+options() ->
+ %% line and column are not options, but tested here
+ ?line {ok,[{atom,1,foo},{white_space,1," "},{comment,1,"% bar"}], 1} =
+ erl_scan:string("foo % bar", 1, return),
+ ?line {ok,[{atom,1,foo},{white_space,1," "}],1} =
+ erl_scan:string("foo % bar", 1, return_white_spaces),
+ ?line {ok,[{atom,1,foo},{comment,1,"% bar"}],1} =
+ erl_scan:string("foo % bar", 1, return_comments),
+ ?line {ok,[{atom,17,foo}],17} =
+ erl_scan:string("foo % bar", 17),
+ ?line {'EXIT',{function_clause,_}} =
+ (catch {foo,
+ erl_scan:string("foo % bar", {a,1}, [])}), % type error
+ ?line {ok,[{atom,_,foo}],{17,18}} =
+ erl_scan:string("foo % bar", {17,9}, []),
+ ?line {'EXIT',{function_clause,_}} =
+ (catch {foo,
+ erl_scan:string("foo % bar", {1,0}, [])}), % type error
+ ?line {ok,[{foo,1}],1} =
+ erl_scan:string("foo % bar",1, [{reserved_word_fun,
+ fun(W) -> W =:= foo end}]),
+ ?line {'EXIT',{badarg,_}} =
+ (catch {foo,
+ erl_scan:string("foo % bar",1, % type error
+ [{reserved_word_fun,
+ fun(W,_) -> W =:= foo end}])}),
+ ok.
+
+more_options() ->
+ ?line {ok,[{atom,A1,foo}],{19,20}} =
+ erl_scan:string("foo", {19,17},[]),
+ ?line [{column,17},{line,19}] = erl_scan:attributes_info(A1),
+ ?line {done,{ok,[{atom,A2,foo},{dot,_}],{19,22}},[]} =
+ erl_scan:tokens([], "foo. ", {19,17}, [bad_opt]), % type error
+ ?line [{column,17},{line,19}] = erl_scan:attributes_info(A2),
+ ?line {ok,[{atom,A3,foo}],{19,20}} =
+ erl_scan:string("foo", {19,17},[text]),
+ ?line [{column,17},{length,3},{line,19},{text,"foo"}] =
+ erl_scan:attributes_info(A3),
+
+ ?line {ok,[{atom,A4,foo}],1} = erl_scan:string("foo", 1, [text]),
+ ?line [{length,3},{line,1},{text,"foo"}] = erl_scan:attributes_info(A4),
+
+ ok.
+
+token_info() ->
+ ?line {ok,[T1],_} = erl_scan:string("foo", {1,18}, [text]),
+ {'EXIT',{badarg,_}} =
+ (catch {foo, erl_scan:token_info(T1, foo)}), % type error
+ ?line {line,1} = erl_scan:token_info(T1, line),
+ ?line {column,18} = erl_scan:token_info(T1, column),
+ ?line {length,3} = erl_scan:token_info(T1, length),
+ ?line {text,"foo"} = erl_scan:token_info(T1, text),
+ ?line [{category,atom},{column,18},{length,3},{line,1},
+ {symbol,foo},{text,"foo"}] =
+ erl_scan:token_info(T1),
+ ?line [{length,3},{column,18}] =
+ erl_scan:token_info(T1, [length, column]),
+ ?line [{location,{1,18}}] =
+ erl_scan:token_info(T1, [location]),
+ ?line {category,atom} = erl_scan:token_info(T1, category),
+ ?line [{symbol,foo}] = erl_scan:token_info(T1, [symbol]),
+
+ ?line {ok,[T2],_} = erl_scan:string("foo", 1, []),
+ ?line {line,1} = erl_scan:token_info(T2, line),
+ ?line undefined = erl_scan:token_info(T2, column),
+ ?line undefined = erl_scan:token_info(T2, length),
+ ?line undefined = erl_scan:token_info(T2, text),
+ ?line {location,1} = erl_scan:token_info(T2, location),
+ ?line [{category,atom},{line,1},{symbol,foo}] = erl_scan:token_info(T2),
+ ?line [{line,1}] = erl_scan:token_info(T2, [length, line]),
+
+ ?line {ok,[T3],_} = erl_scan:string("=", 1, []),
+ ?line [{line,1}] = erl_scan:token_info(T3, [column, line]),
+ ?line {category,'='} = erl_scan:token_info(T3, category),
+ ?line [{symbol,'='}] = erl_scan:token_info(T3, [symbol]),
+ ok.
+
+attributes_info() ->
+ ?line {'EXIT',_} =
+ (catch {foo,erl_scan:attributes_info(foo)}), % type error
+ ?line [{line,18}] = erl_scan:attributes_info(18),
+ ?line {location,19} = erl_scan:attributes_info(19, location),
+ ?line {ok,[{atom,A0,foo}],_} = erl_scan:string("foo", 19, [text]),
+ ?line {location,19} = erl_scan:attributes_info(A0, location),
+
+ ?line {ok,[{atom,A3,foo}],_} = erl_scan:string("foo", {1,3}, [text]),
+ ?line {line,1} = erl_scan:attributes_info(A3, line),
+ ?line {column,3} = erl_scan:attributes_info(A3, column),
+ ?line {location,{1,3}} = erl_scan:attributes_info(A3, location),
+ ?line {text,"foo"} = erl_scan:attributes_info(A3, text),
+
+ ?line {ok,[{atom,A4,foo}],_} = erl_scan:string("foo", 2, [text]),
+ ?line {line,2} = erl_scan:attributes_info(A4, line),
+ ?line undefined = erl_scan:attributes_info(A4, column),
+ ?line {location,2} = erl_scan:attributes_info(A4, location),
+ ?line {text,"foo"} = erl_scan:attributes_info(A4, text),
+
+ ?line {ok,[{atom,A5,foo}],_} = erl_scan:string("foo", {1,3}, []),
+ ?line {line,1} = erl_scan:attributes_info(A5, line),
+ ?line {column,3} = erl_scan:attributes_info(A5, column),
+ ?line {location,{1,3}} = erl_scan:attributes_info(A5, location),
+ ?line undefined = erl_scan:attributes_info(A5, text),
+
+ ?line undefined = erl_scan:attributes_info([], line), % type error
+
+ ok.
+
+set_attribute() ->
+ F = fun(Line) -> -Line end,
+ ?line -2 = erl_scan:set_attribute(line, 2, F),
+ ?line {ok,[{atom,A1,foo}],_} = erl_scan:string("foo", {9,17}),
+ ?line A2 = erl_scan:set_attribute(line, A1, F),
+ ?line {line,-9} = erl_scan:attributes_info(A2, line),
+ ?line {location,{-9,17}} = erl_scan:attributes_info(A2, location),
+ ?line [{line,-9},{column,17}] =
+ erl_scan:attributes_info(A2, [line,column,text]),
+
+ F2 = fun(Line) -> {17,Line} end,
+ ?line Attr1 = erl_scan:set_attribute(line, 2, F2),
+ ?line {line,{17,2}} = erl_scan:attributes_info(Attr1, line),
+ ?line undefined = erl_scan:attributes_info(Attr1, column),
+ ?line {location,{17,2}} = % a bit mixed up
+ erl_scan:attributes_info(Attr1, location),
+
+ ?line A3 = erl_scan:set_attribute(line, A1, F2),
+ ?line {line,{17,9}} = erl_scan:attributes_info(A3, line),
+ ?line {location,{{17,9},17}} = erl_scan:attributes_info(A3, location),
+ ?line [{line,{17,9}},{column,17}] =
+ erl_scan:attributes_info(A3, [line,column,text]),
+
+ ?line {ok,[{atom,A4,foo}],_} = erl_scan:string("foo", {9,17}, [text]),
+ ?line A5 = erl_scan:set_attribute(line, A4, F),
+ ?line {line,-9} = erl_scan:attributes_info(A5, line),
+ ?line {location,{-9,17}} = erl_scan:attributes_info(A5, location),
+ ?line [{line,-9},{column,17},{text,"foo"}] =
+ erl_scan:attributes_info(A5, [line,column,text]),
+
+ ?line {ok,[{atom,A6,foo}],_} = erl_scan:string("foo", 11, [text]),
+ ?line A7 = erl_scan:set_attribute(line, A6, F2),
+ ?line {line,{17,11}} = erl_scan:attributes_info(A7, line),
+ ?line {location,{17,11}} = % mixed up
+ erl_scan:attributes_info(A7, location),
+ ?line [{line,{17,11}},{text,"foo"}] =
+ erl_scan:attributes_info(A7, [line,column,text]),
+
+ ?line {'EXIT',_} =
+ (catch {foo, erl_scan:set_attribute(line, [], F2)}), % type error
+ ?line {'EXIT',{badarg,_}} =
+ (catch {foo, erl_scan:set_attribute(column, [], F2)}), % type error
+ ok.
+
+column_errors() ->
+ ?line {error,{{1,1},erl_scan,{string,$',""}},{1,3}} = % $'
+ erl_scan:string("'\\",{1,1}),
+ ?line {error,{{1,1},erl_scan,{string,$",""}},{1,3}} = % $"
+ erl_scan:string("\"\\",{1,1}),
+
+ ?line {error,{{1,1},erl_scan,{string,$',""}},{1,2}} = % $'
+ erl_scan:string("'",{1,1}),
+ ?line {error,{{1,1},erl_scan,{string,$",""}},{1,2}} = % $"
+ erl_scan:string("\"",{1,1}),
+
+ ?line {error,{{1,1},erl_scan,char},{1,2}} =
+ erl_scan:string("$",{1,1}),
+
+ ?line {error,{{1,2},erl_scan,{string,$',"1234567890123456"}},{1,20}} = %'
+ erl_scan:string(" '12345678901234567", {1,1}),
+ ?line {error,{{1,2},erl_scan,{string,$',"123456789012345 "}}, {1,20}} = %'
+ erl_scan:string(" '123456789012345\\s", {1,1}),
+ ?line {error,{{1,2},erl_scan,{string,$","1234567890123456"}},{1,20}} = %"
+ erl_scan:string(" \"12345678901234567", {1,1}),
+ ?line {error,{{1,2},erl_scan,{string,$","123456789012345 "}}, {1,20}} = %"
+ erl_scan:string(" \"123456789012345\\s", {1,1}),
+ ?line {error,{{1,2},erl_scan,{string,$',"1234567890123456"}},{2,1}} = %'
+ erl_scan:string(" '12345678901234567\n", {1,1}),
+ ok.
+
+white_spaces() ->
+ ?line {ok,[{white_space,_,"\r"},
+ {white_space,_," "},
+ {atom,_,a},
+ {white_space,_,"\n"}],
+ _} = erl_scan:string("\r a\n", {1,1}, return),
+ ?line test("\r a\n"),
+ L = "{\"a\nb\", \"a\\nb\",\nabc\r,def}.\n\n",
+ ?line {ok,[{'{',_},
+ {string,_,"a\nb"},
+ {',',_},
+ {white_space,_," "},
+ {string,_,"a\nb"},
+ {',',_},
+ {white_space,_,"\n"},
+ {atom,_,abc},
+ {white_space,_,"\r"},
+ {',',_},
+ {atom,_,def},
+ {'}',_},
+ {dot,_},
+ {white_space,_,"\n"}],
+ _} = erl_scan:string(L, {1,1}, return),
+ ?line test(L),
+ ?line test("\"\n\"\n"),
+ ?line test("\n\r\n"),
+ ?line test("\n\r"),
+ ?line test("\r\n"),
+ ?line test("\n\f"),
+ ?line [test(lists:duplicate(N, $\t)) || N <- lists:seq(1, 20)],
+ ?line [test([$\n|lists:duplicate(N, $\t)]) || N <- lists:seq(1, 20)],
+ ?line [test(lists:duplicate(N, $\s)) || N <- lists:seq(1, 20)],
+ ?line [test([$\n|lists:duplicate(N, $\s)]) || N <- lists:seq(1, 20)],
+ ?line test("\v\f\n\v "),
+ ?line test("\n\e\n\b\f\n\da\n"),
+ ok.
+
+unicode() ->
+ ?line {ok,[{char,1,83},{integer,1,45}],1} =
+ erl_scan:string("$\\12345"), % not unicode
+
+ ?line {error,{1,erl_scan,{illegal,character}},1} =
+ erl_scan:string([1089]),
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,2}} =
+ erl_scan:string([1089], {1,1}),
+ ?line {error,{1,erl_scan,{illegal,character}},1} =
+ %% ?line {error,{1,erl_scan,{illegal,atom}},1} =
+ erl_scan:string("'a"++[1089]++"b'"),
+ ?line {error,{{1,3},erl_scan,{illegal,character}},{1,4}} =
+ erl_scan:string("'a"++[1089]++"b'", {1,1}),
+ ?line test("\"a"++[1089]++"b\""),
+ ?line {ok,[{char,1,1}],1} = erl_scan:string([$$,$\\,$^,1089]),
+
+ ?line {error,{1,erl_scan,Error},1} = erl_scan:string("\"qa\x{aaa}"),
+ ?line "unterminated string starting with \"qa\\x{AAA}\"" =
+ erl_scan:format_error(Error),
+ ?line {error,{{1,1},erl_scan,_},{1,11}} =
+ erl_scan:string("\"qa\\x{aaa}",{1,1}),
+ ?line {error,{{1,4},erl_scan,{illegal,character}},{1,11}} =
+ erl_scan:string("'qa\\x{aaa}'",{1,1}),
+
+ Tags = [category, column, length, line, symbol, text],
+
+ %% Workaround. No character codes greater than 255! To be changed.
+ %% Note: don't remove these tests, just modify them!
+
+ ?line {ok,[{integer,1,1089}],1} = erl_scan:string([$$,1089]),
+ ?line {ok,[{integer,1,1089}],1} = erl_scan:string([$$,$\\,1089]),
+
+ Qs = "$\\x{aaa}",
+ ?line {ok,[{integer,1,16#aaa}],1} = erl_scan:string(Qs),
+ ?line {ok,[Q2],{1,9}} = erl_scan:string("$\\x{aaa}", {1,1}, text),
+ ?line [{category,integer},{column,1},{length,8},
+ {line,1},{symbol,16#aaa},{text,Qs}] =
+ erl_scan:token_info(Q2),
+
+ U1 = "\"\\x{aaa}\"",
+ ?line {ok,[T1,T2,T3],{1,10}} = erl_scan:string(U1, {1,1}, text),
+ ?line [{category,'['},{column,1},{length,1},{line,1},
+ {symbol,'['},{text,"\""}] = erl_scan:token_info(T1, Tags),
+ ?line [{category,integer},{column,2},{length,7},
+ {line,1},{symbol,16#aaa},{text,"\\x{aaa}"}] =
+ erl_scan:token_info(T2, Tags),
+ ?line [{category,']'},{column,9},{length,1},{line,1},
+ {symbol,']'},{text,"\""}] = erl_scan:token_info(T3, Tags),
+ ?line {ok,[{'[',1},{integer,1,16#aaa},{']',1}],1} =
+ erl_scan:string(U1, 1),
+
+ U2 = "\"\\x41\\x{fff}\\x42\"",
+ ?line {ok,[{'[',1},{char,1,16#41},{',',1},{integer,1,16#fff},
+ {',',1},{char,1,16#42},{']',1}],1} = erl_scan:string(U2, 1),
+
+ U3 = "\"a\n\\x{fff}\n\"",
+ ?line {ok,[{'[',1},{char,1,$a},{',',1},{char,1,$\n},
+ {',',2},{integer,2,16#fff},{',',2},{char,2,$\n},
+ {']',3}],3} =
+ erl_scan:string(U3, 1),
+
+ U4 = "\"\\^\n\\x{aaa}\\^\n\"",
+ ?line {ok,[{'[',1},{char,1,$\n},{',',2},{integer,2,16#aaa},
+ {',',2},{char,2,$\n},{']',3}],3} = erl_scan:string(U4, 1),
+
+ %% Keep these tests:
+ ?line test(Qs),
+ ?line test(U1),
+ ?line test(U2),
+ ?line test(U3),
+ ?line test(U4),
+
+ Str1 = "\"ab" ++ [1089] ++ "cd\"",
+ ?line {ok,[{'[',1},{char,1,$a},{',',1},{char,1,$b},{',',1},
+ {integer,1,1089},{',',1},{char,1,$c},{',',1},
+ {char,1,$d},{']',1}],1} = erl_scan:string(Str1),
+ ?line {ok,[{'[',_},{char,_,$a},{',',_},{char,_,$b},{',',_},
+ {integer,_,1089},{',',_},{char,_,$c},{',',_},
+ {char,_,$d},{']',_}],{1,8}} = erl_scan:string(Str1, {1,1}),
+ ?line test(Str1),
+ Comment = "%% "++[1089],
+ ?line {ok,[{comment,1,[$%,$%,$\s,1089]}],1} =
+ erl_scan:string(Comment, 1, return),
+ ?line {ok,[{comment,_,[$%,$%,$\s,1089]}],{1,5}} =
+ erl_scan:string(Comment, {1,1}, return),
+ ok.
+
+more_chars() ->
+ %% Due to unicode, the syntax has been incompatibly augmented:
+ %% $\x{...}, $\xHH
+
+ %% All kinds of tests...
+ ?line {ok,[{char,_,123}],{1,4}} =
+ erl_scan:string("$\\{",{1,1}),
+ ?line {more, C1} = erl_scan:tokens([], "$\\{", {1,1}),
+ ?line {done,{ok,[{char,_,123}],{1,4}},eof} =
+ erl_scan:tokens(C1, eof, 1),
+ ?line {ok,[{char,1,123},{atom,1,a},{'}',1}],1} =
+ erl_scan:string("$\\{a}"),
+
+ ?line {error,{{1,1},erl_scan,char},{1,4}} =
+ erl_scan:string("$\\x", {1,1}),
+ ?line {error,{{1,1},erl_scan,char},{1,5}} =
+ erl_scan:string("$\\x{",{1,1}),
+ ?line {more, C3} = erl_scan:tokens([], "$\\x", {1,1}),
+ ?line {done,{error,{{1,1},erl_scan,char},{1,4}},eof} =
+ erl_scan:tokens(C3, eof, 1),
+ ?line {error,{{1,1},erl_scan,char},{1,5}} =
+ erl_scan:string("$\\x{",{1,1}),
+ ?line {more, C2} = erl_scan:tokens([], "$\\x{", {1,1}),
+ ?line {done,{error,{{1,1},erl_scan,char},{1,5}},eof} =
+ erl_scan:tokens(C2, eof, 1),
+ ?line {error,{1,erl_scan,{illegal,character}},1} =
+ erl_scan:string("$\\x{g}"),
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,5}} =
+ erl_scan:string("$\\x{g}", {1,1}),
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,6}} =
+ erl_scan:string("$\\x{}",{1,1}),
+
+ ?line test("\"\\{0}\""),
+ ?line test("\"\\x{0}\""),
+ ?line test("\'\\{0}\'"),
+ ?line test("\'\\x{0}\'"),
+
+ ?line {error,{{2,3},erl_scan,{illegal,character}},{2,6}} =
+ erl_scan:string("\"ab \n $\\x{g}\"",{1,1}),
+ ?line {error,{{2,3},erl_scan,{illegal,character}},{2,6}} =
+ erl_scan:string("\'ab \n $\\x{g}\'",{1,1}),
+
+ ?line test("$\\{34}"),
+ ?line test("$\\x{34}"),
+ ?line test("$\\{377}"),
+ ?line test("$\\x{FF}"),
+ ?line test("$\\{400}"),
+ ?line test("$\\x{100}"),
+ ?line test("$\\x{10FFFF}"),
+ ?line test("$\\x{10ffff}"),
+ ?line test("\"$\n \\{1}\""),
+ ?line {error,{1,erl_scan,{illegal,character}},1} =
+ erl_scan:string("$\\x{110000}"),
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,12}} =
+ erl_scan:string("$\\x{110000}", {1,1}),
+
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,4}} =
+ erl_scan:string("$\\xfg", {1,1}),
+
+ ?line test("$\\xffg"),
+
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,4}} =
+ erl_scan:string("$\\xg", {1,1}),
+ ok.
+
+test_string(String, Expected) ->
+ {ok, Expected, _End} = erl_scan:string(String),
+ test(String).
+
+%% test_string(String, Expected, StartLocation, Options) ->
+%% {ok, Expected, _End} = erl_scan:string(String, StartLocation, Options),
+%% test(String).
+
+%% There are no checks of the tags...
+test(String) ->
+ %% io:format("Testing `~ts'~n", [String]),
+ [{Tokens, End},
+ {Wtokens, Wend},
+ {Ctokens, Cend},
+ {CWtokens, CWend},
+ {CWtokens2, _}] =
+ [scan_string_with_column(String, X) ||
+ X <- [[],
+ [return_white_spaces],
+ [return_comments],
+ [return],
+ [return]]], % for white space compaction test
+
+ {end1,End,Wend} = {end1,Wend,End},
+ {end2,Wend,Cend} = {end2,Cend,Wend},
+ {end3,Cend,CWend} = {end3,CWend,Cend},
+
+ %% Test that the tokens that are common to two token lists are identical.
+ {none,Tokens} = {none, filter_tokens(CWtokens, [white_space,comment])},
+ {comments,Ctokens} =
+ {comments,filter_tokens(CWtokens, [white_space])},
+ {white_spaces,Wtokens} =
+ {white_spaces,filter_tokens(CWtokens, [comment])},
+
+ %% Use token attributes to extract parts from the original string,
+ %% and check that the parts are identical to the token strings.
+ {Line,Column} = test_decorated_tokens(String, CWtokens),
+ {deco,{Line,Column},End} = {deco,End,{Line,Column}},
+
+ %% Almost the same again: concat texts to get the original:
+ Text = get_text(CWtokens),
+ {text,Text,String} = {text,String,Text},
+
+ %% Test that white spaces occupy less heap than the worst case.
+ ok = test_white_space_compaction(CWtokens, CWtokens2),
+
+ %% Test that white newlines are always first in text:
+ WhiteTokens = select_tokens(CWtokens, [white_space]),
+ ok = newlines_first(WhiteTokens),
+
+ %% Line attribute only:
+ [Simple,Wsimple,Csimple,WCsimple] = Simples =
+ [element(2, erl_scan:string(String, 1, Opts)) ||
+ Opts <- [[],
+ [return_white_spaces],
+ [return_comments],
+ [return]]],
+ {consistent,true} = {consistent,consistent_attributes(Simples)},
+ {simple_wc,WCsimple} = {simple_wc,simplify(CWtokens)},
+ {simple,Simple} = {simple,filter_tokens(WCsimple, [white_space,comment])},
+ {simple_c,Csimple} = {simple_c,filter_tokens(WCsimple, [white_space])},
+ {simple_w,Wsimple} = {simple_w,filter_tokens(WCsimple, [comment])},
+
+ %% Line attribute only, with text:
+ [SimpleTxt,WsimpleTxt,CsimpleTxt,WCsimpleTxt] = SimplesTxt =
+ [element(2, erl_scan:string(String, 1, [text|Opts])) ||
+ Opts <- [[],
+ [return_white_spaces],
+ [return_comments],
+ [return]]],
+ TextTxt = get_text(WCsimpleTxt),
+ {text_txt,TextTxt,String} = {text_txt,String,TextTxt},
+ {consistent_txt,true} =
+ {consistent_txt,consistent_attributes(SimplesTxt)},
+ {simple_txt,SimpleTxt} =
+ {simple_txt,filter_tokens(WCsimpleTxt, [white_space,comment])},
+ {simple_c_txt,CsimpleTxt} =
+ {simple_c_txt,filter_tokens(WCsimpleTxt, [white_space])},
+ {simple_w_txt,WsimpleTxt} =
+ {simple_w_txt,filter_tokens(WCsimpleTxt, [comment])},
+
+ ok.
+
+test_white_space_compaction(Tokens, Tokens2) when Tokens =:= Tokens2 ->
+ [WS, WS2] = [select_tokens(Ts, [white_space]) || Ts <- [Tokens, Tokens2]],
+ test_wsc(WS, WS2).
+
+test_wsc([], []) ->
+ ok;
+test_wsc([Token|Tokens], [Token2|Tokens2]) ->
+ [Text, Text2] = [Text ||
+ {text, Text} <-
+ [erl_scan:token_info(T, text) ||
+ T <- [Token, Token2]]],
+ Sz = erts_debug:size(Text),
+ Sz2 = erts_debug:size({Text, Text2}),
+ IsCompacted = Sz2 < 2*Sz+erts_debug:size({a,a}),
+ ToBeCompacted = is_compacted(Text),
+ if
+ IsCompacted =:= ToBeCompacted ->
+ test_wsc(Tokens, Tokens2);
+ true ->
+ {compaction_error, Token}
+ end.
+
+is_compacted("\r") ->
+ true;
+is_compacted("\n\r") ->
+ true;
+is_compacted("\n\f") ->
+ true;
+is_compacted([$\n|String]) ->
+ all_spaces(String)
+ orelse
+ all_tabs(String);
+is_compacted(String) ->
+ all_spaces(String)
+ orelse
+ all_tabs(String).
+
+all_spaces(L) ->
+ all_same(L, $\s).
+
+all_tabs(L) ->
+ all_same(L, $\t).
+
+all_same(L, Char) ->
+ lists:all(fun(C) -> C =:= Char end, L).
+
+newlines_first([]) ->
+ ok;
+newlines_first([Token|Tokens]) ->
+ {text,Text} = erl_scan:token_info(Token, text),
+ Nnls = length([C || C <- Text, C =:= $\n]),
+ OK = case Text of
+ [$\n|_] ->
+ Nnls =:= 1;
+ _ ->
+ Nnls =:= 0
+ end,
+ if
+ OK -> newlines_first(Tokens);
+ true -> OK
+ end.
+
+filter_tokens(Tokens, Tags) ->
+ lists:filter(fun(T) -> not lists:member(element(1, T), Tags) end, Tokens).
+
+select_tokens(Tokens, Tags) ->
+ lists:filter(fun(T) -> lists:member(element(1, T), Tags) end, Tokens).
+
+simplify([Token|Tokens]) ->
+ {line,Line} = erl_scan:token_info(Token, line),
+ [setelement(2, Token, Line) | simplify(Tokens)];
+simplify([]) ->
+ [].
+
+get_text(Tokens) ->
+ lists:flatten(
+ [T ||
+ Token <- Tokens,
+ ({text,T} = erl_scan:token_info(Token, text)) =/= []]).
+
+test_decorated_tokens(String, Tokens) ->
+ ToksAttrs = token_attrs(Tokens),
+ test_strings(ToksAttrs, String, 1, 1).
+
+token_attrs(Tokens) ->
+ [{L,C,Len,T} ||
+ Token <- Tokens,
+ ([{line,L},{column,C},{length,Len},{text,T}] =
+ erl_scan:token_info(Token, [line,column,length,text])) =/= []].
+
+test_strings([], _S, Line, Column) ->
+ {Line,Column};
+test_strings([{L,C,Len,T}=Attr|Attrs], String0, Line0, Column0) ->
+ {String1, Column1} = skip_newlines(String0, L, Line0, Column0),
+ String = skip_chars(String1, C-Column1),
+ {Str,Rest} = lists:split(Len, String),
+ if
+ Str =:= T ->
+ {Line,Column} = string_newlines(T, L, C),
+ test_strings(Attrs, Rest, Line, Column);
+ true ->
+ {token_error, Attr, Str}
+ end.
+
+skip_newlines(String, Line, Line, Column) ->
+ {String, Column};
+skip_newlines([$\n|String], L, Line, _Column) ->
+ skip_newlines(String, L, Line+1, 1);
+skip_newlines([_|String], L, Line, Column) ->
+ skip_newlines(String, L, Line, Column+1).
+
+skip_chars(String, 0) ->
+ String;
+skip_chars([_|String], N) ->
+ skip_chars(String, N-1).
+
+string_newlines([$\n|String], Line, _Column) ->
+ string_newlines(String, Line+1, 1);
+string_newlines([], Line, Column) ->
+ {Line, Column};
+string_newlines([_|String], Line, Column) ->
+ string_newlines(String, Line, Column+1).
+
+scan_string_with_column(String, Options0) ->
+ Options = [text | Options0],
+ StartLoc = {1, 1},
+ {ok, Ts1, End1} = erl_scan:string(String, StartLoc, Options),
+ TString = String ++ ". ",
+ {ok,Ts2,End2} = scan_tokens(TString, Options, [], StartLoc),
+ {ok, Ts3, End3} =
+ scan_tokens_1({more, []}, TString, Options, [], StartLoc),
+ {end_2,End2,End3} = {end_2,End3,End2},
+ {EndLine1,EndColumn1} = End1,
+ End2 = {EndLine1,EndColumn1+2},
+ {ts_1,Ts2,Ts3} = {ts_1,Ts3,Ts2},
+ Ts2 = Ts1 ++ [lists:last(Ts2)],
+
+ %% Attributes are keylists, but have no text.
+ {ok, Ts7, End7} = erl_scan:string(String, {1,1}, Options),
+ {ok, Ts8, End8} = scan_tokens(TString, Options, [], {1,1}),
+ {end1, End1} = {end1, End7},
+ {end2, End2} = {end2, End8},
+ Ts8 = Ts7 ++ [lists:last(Ts8)],
+ {cons,true} = {cons,consistent_attributes([Ts1,Ts2,Ts3,Ts7,Ts8])},
+
+ {Ts1, End1}.
+
+scan_tokens(String, Options, Rs, Location) ->
+ case erl_scan:tokens([], String, Location, Options) of
+ {done, {ok,Ts,End}, ""} ->
+ {ok, lists:append(lists:reverse([Ts|Rs])), End};
+ {done, {ok,Ts,End}, Rest} ->
+ scan_tokens(Rest, Options, [Ts|Rs], End)
+ end.
+
+scan_tokens_1({done, {ok,Ts,End}, ""}, "", _Options, Rs, _Location) ->
+ {ok,lists:append(lists:reverse([Ts|Rs])),End};
+scan_tokens_1({done, {ok,Ts,End}, Rest}, Cs, Options, Rs, _Location) ->
+ scan_tokens_1({more,[]}, Rest++Cs, Options, [Ts|Rs], End);
+scan_tokens_1({more, Cont}, [C | Cs], Options, Rs, Loc) ->
+ R = erl_scan:tokens(Cont, [C], Loc, Options),
+ scan_tokens_1(R, Cs, Options, Rs, Loc).
+
+consistent_attributes([]) ->
+ true;
+consistent_attributes([Ts | TsL]) ->
+ L = [T || T <- Ts, is_integer(element(2, T))],
+ case L of
+ [] ->
+ TagsL = [[Tag || {Tag,_} <-
+ erl_scan:attributes_info(element(2, T))] ||
+ T <- Ts],
+ case lists:usort(TagsL) of
+ [_] ->
+ consistent_attributes(TsL);
+ [] when Ts =:= [] ->
+ consistent_attributes(TsL);
+ _ ->
+ Ts
+ end;
+ Ts ->
+ consistent_attributes(TsL);
+ _ ->
+ Ts
+ end.
+
+family_list(L) ->
+ sofs:to_external(family(L)).
+
+family(L) ->
+ sofs:relation_to_family(sofs:relation(L)).
diff --git a/lib/stdlib/test/error_logger_forwarder.erl b/lib/stdlib/test/error_logger_forwarder.erl
new file mode 100644
index 0000000000..7d99d07860
--- /dev/null
+++ b/lib/stdlib/test/error_logger_forwarder.erl
@@ -0,0 +1,48 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(error_logger_forwarder).
+
+%% API.
+-export([register/0]).
+
+%% Internal export for error_logger.
+-export([init/1,
+ handle_event/2, handle_call/2, handle_info/2,
+ terminate/2]).
+
+%% Any crash report messages generated will be forwarded
+%% to the current process (the one doing the call to register/0).
+%%
+register() ->
+ error_logger:add_report_handler(?MODULE, self()).
+
+init(Tester) ->
+ {ok,Tester}.
+
+handle_event(Event, Tester) ->
+ Tester ! Event,
+ {ok,Tester}.
+
+handle_info(_, State) ->
+ {ok,State}.
+
+handle_call(_Query, State) -> {ok,{error,bad_query},State}.
+
+terminate(_Reason, State) ->
+ State.
diff --git a/lib/stdlib/test/escript_SUITE.erl b/lib/stdlib/test/escript_SUITE.erl
new file mode 100644
index 0000000000..70aae54d62
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE.erl
@@ -0,0 +1,540 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(escript_SUITE).
+-export([
+ all/1,
+ init_per_testcase/2,
+ fin_per_testcase/2,
+ basic/1,
+ errors/1,
+ strange_name/1,
+ emulator_flags/1,
+ module_script/1,
+ beam_script/1,
+ archive_script/1,
+ epp/1
+ ]).
+
+-include("test_server.hrl").
+
+all(suite) ->
+ [
+ basic,
+ errors,
+ strange_name,
+ emulator_flags,
+ module_script,
+ beam_script,
+ archive_script,
+ epp
+ ].
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?t:minutes(1)),
+ [{watchdog,Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+basic(Config) when is_list(Config) ->
+ Data = ?config(data_dir, Config),
+ Dir = filename:absname(Data), %Get rid of trailing slash.
+ ?line run(Dir, "factorial 5",
+ <<"factorial 5 = 120\nExitCode:0">>),
+ ?line run(Dir, "factorial_compile 10",
+ <<"factorial 10 = 3628800\nExitCode:0">>),
+ ?line run(Dir, "factorial_compile_main 7",
+ <<"factorial 7 = 5040\nExitCode:0">>),
+ ?line run(Dir, "factorial_warning 20",
+ [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\n"
+ "factorial 20 = 2432902008176640000\nExitCode:0">>]),
+ ?line run(Dir, "-s", "factorial_warning",
+ [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]),
+ ?line run(Dir, "-s -i", "factorial_warning",
+ [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]),
+ ?line run(Dir, "-c -s", "factorial_warning",
+ [data_dir,<<"factorial_warning:12: Warning: function bar/0 is unused\nExitCode:0">>]),
+ ?line run(Dir, "filesize "++filename:join(?config(data_dir, Config),"filesize"),
+ [data_dir,<<"filesize:11: Warning: function id/1 is unused\n324\nExitCode:0">>]),
+ ?line run(Dir, "test_script_name",
+ [data_dir,<<"test_script_name\nExitCode:0">>]),
+ ?line run(Dir, "tail_rec 1000",
+ [<<"ok\nExitCode:0">>]),
+
+ %% We expect the trap_exit flag for the process to be false,
+ %% since that is the default state for newly spawned processes.
+ ?line run(Dir, "trap_exit",
+ <<"false\nExitCode:0">>),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+errors(Config) when is_list(Config) ->
+ Data = ?config(data_dir, Config),
+ Dir = filename:absname(Data), %Get rid of trailing slash.
+ ?line run(Dir, "compile_error",
+ [data_dir,<<"compile_error:5: syntax error before: '*'\n">>,
+ data_dir,<<"compile_error:8: syntax error before: blarf\n">>,
+ <<"escript: There were compilation errors.\nExitCode:127">>]),
+ ?line run(Dir, "lint_error",
+ [data_dir,<<"lint_error:6: function main/1 already defined\n">>,
+ data_dir,"lint_error:8: variable 'ExitCode' is unbound\n",
+ <<"escript: There were compilation errors.\nExitCode:127">>]),
+ ?line run(Dir, "-s", "lint_error",
+ [data_dir,<<"lint_error:6: function main/1 already defined\n">>,
+ data_dir,"lint_error:8: variable 'ExitCode' is unbound\n",
+ <<"escript: There were compilation errors.\nExitCode:127">>]),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+strange_name(Config) when is_list(Config) ->
+ Data = ?config(data_dir, Config),
+ Dir = filename:absname(Data), %Get rid of trailing slash.
+ ?line run(Dir, "strange.name -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "ExitCode:0">>]),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+emulator_flags(Config) when is_list(Config) ->
+ Data = ?config(data_dir, Config),
+ Dir = filename:absname(Data), %Get rid of trailing slash.
+ ?line run(Dir, "emulator_flags -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[{nostick,[]}]\n"
+ "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Pick the source code from the emulator_flags script
+%% Generate a new escript with a module header
+
+module_script(Config) when is_list(Config) ->
+ %% Read orig file
+ Data = ?config(data_dir, Config),
+ OrigFile = filename:join([Data,"emulator_flags"]),
+ {ok, OrigBin} = file:read_file(OrigFile),
+ ?line [Shebang, Mode, Flags | Source] = string:tokens(binary_to_list(OrigBin), "\n"),
+ ?line {ok, OrigFI} = file:read_file_info(OrigFile),
+
+ %% Write source file
+ Priv = ?config(priv_dir, Config),
+ Dir = filename:absname(Priv), % Get rid of trailing slash.
+ Base = "module_script",
+ ErlFile = filename:join([Priv, Base ++ ".erl"]),
+ ErlCode = ["-module(", Base, ").\n",
+ "-export([main/1]).\n\n",
+ string:join(Source, "\n"),
+ "\n"],
+ ?line ok = file:write_file(ErlFile, ErlCode),
+
+ %%%%%%%
+ %% Create and run scripts without emulator flags
+
+ %% With shebang
+ NoArgsBase = Base ++ "_no_args_with_shebang",
+ NoArgsFile = filename:join([Priv, NoArgsBase]),
+ ?line ok = file:write_file(NoArgsFile,
+ [Shebang, "\n",
+ ErlCode]),
+ ?line ok = file:write_file_info(NoArgsFile, OrigFI),
+
+ ?line run(Dir, NoArgsBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ ?line run(Dir, "", NoArgsBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ %% Without shebang
+ NoArgsBase2 = Base ++ "_no_args_without_shebang",
+ NoArgsFile2 = filename:join([Priv, NoArgsBase2]),
+ ?line ok = file:write_file(NoArgsFile2,
+ ["Something else than shebang!!!", "\n",
+ ErlCode]),
+ ?line ok = file:write_file_info(NoArgsFile2, OrigFI),
+
+ ?line run(Dir, "", NoArgsBase2 ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ %% Plain module without header
+ NoArgsBase3 = Base ++ "_no_args_without_header",
+ NoArgsFile3 = filename:join([Priv, NoArgsBase3]),
+ ?line ok = file:write_file(NoArgsFile3, [ErlCode]),
+ ?line ok = file:write_file_info(NoArgsFile3, OrigFI),
+
+ ?line run(Dir, "", NoArgsBase3 ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ %%%%%%%
+ %% Create and run scripts with emulator flags
+
+ %% With shebang
+ ArgsBase = Base ++ "_args_with_shebang",
+ ArgsFile = filename:join([Priv, ArgsBase]),
+ ?line ok = file:write_file(ArgsFile,
+ [Shebang, "\n",
+ Mode, "\n",
+ Flags, "\n",
+ ErlCode]),
+ ?line ok = file:write_file_info(ArgsFile, OrigFI),
+
+ ?line run(Dir, ArgsBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[{nostick,[]}]\n"
+ "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Pick the source code from the emulator_flags script and compile it.
+%% Generate a new escript containing the beam code and the escript header
+beam_script(Config) when is_list(Config) ->
+ %% Read orig file
+ Data = ?config(data_dir, Config),
+ OrigFile = filename:join([Data,"emulator_flags"]),
+ {ok, OrigBin} = file:read_file(OrigFile),
+ ?line [Shebang, Mode, Flags | Source] = string:tokens(binary_to_list(OrigBin), "\n"),
+ ?line {ok, OrigFI} = file:read_file_info(OrigFile),
+
+ %% Write source file
+ Priv = ?config(priv_dir, Config),
+ Dir = filename:absname(Priv), % Get rid of trailing slash.
+ Base = "beam_script",
+ ErlFile = filename:join([Priv, Base ++ ".erl"]),
+ ?line ok = file:write_file(ErlFile,
+ ["-module(", Base, ").\n",
+ "-export([main/1]).\n\n",
+ string:join(Source, "\n"),
+ "\n"]),
+
+ %% Compile the code
+ ?line {ok, _Mod, BeamCode} = compile:file(ErlFile, [binary]),
+
+ %%%%%%%
+ %% Create and run scripts without emulator flags
+
+ %% With shebang
+ NoArgsBase = Base ++ "_no_args_with_shebang",
+ NoArgsFile = filename:join([Priv, NoArgsBase]),
+ ?line ok = file:write_file(NoArgsFile,
+ [Shebang, "\n",
+ BeamCode]),
+ ?line ok = file:write_file_info(NoArgsFile, OrigFI),
+
+ ?line run(Dir, NoArgsBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ ?line run(Dir, "", NoArgsBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ %% Without shebang
+ NoArgsBase2 = Base ++ "_no_args_without_shebang",
+ NoArgsFile2 = filename:join([Priv, NoArgsBase2]),
+ ?line ok = file:write_file(NoArgsFile2,
+ ["Something else than shebang!!!", "\n",
+ BeamCode]),
+ ?line ok = file:write_file_info(NoArgsFile2, OrigFI),
+
+ ?line run(Dir, "", NoArgsBase2 ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ %% Plain beam file without header
+ NoArgsBase3 = Base ++ "_no_args_without_header",
+ NoArgsFile3 = filename:join([Priv, NoArgsBase3]),
+ ?line ok = file:write_file(NoArgsFile3, [BeamCode]),
+ ?line ok = file:write_file_info(NoArgsFile3, OrigFI),
+
+ ?line run(Dir, "", NoArgsBase3 ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[]\n"
+ "mnesia:[]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+
+ %%%%%%%
+ %% Create and run scripts with emulator flags
+
+ %% With shebang
+ ArgsBase = Base ++ "_args",
+ ArgsFile = filename:join([Priv, ArgsBase]),
+ ?line ok = file:write_file(ArgsFile,
+ [Shebang, "\n",
+ Mode, "\n",
+ Flags, "\n",
+ BeamCode]),
+ ?line ok = file:write_file_info(ArgsFile, OrigFI),
+
+ ?line run(Dir, ArgsBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "nostick:[{nostick,[]}]\n"
+ "mnesia:[{mnesia,[\"dir\",\"a/directory\"]},{mnesia,[\"debug\",\"verbose\"]}]\n"
+ "ERL_FLAGS=false\n"
+ "unknown:[]\n"
+ "ExitCode:0">>]),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create an archive file containing two entire applications plus two
+%% alternate main modules. Generate a new escript containing the archive
+%% (with .app and .beam files and ) and the escript header.
+
+archive_script(Config) when is_list(Config) ->
+ %% Copy the orig files to priv_dir
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Archive = filename:join([PrivDir, "archive_script.zip"]),
+ ?line {ok, _} = zip:create(Archive, ["archive_script"],
+ [{compress, []}, {cwd, DataDir}]),
+ ?line {ok, _} = zip:extract(Archive, [{cwd, PrivDir}]),
+ TopDir = filename:join([PrivDir, "archive_script"]),
+
+ %% Compile the code
+ ?line ok = compile_app(TopDir, "archive_script_dict"),
+ ?line ok = compile_app(TopDir, "archive_script_dummy"),
+ ?line {ok, MainFiles} = file:list_dir(TopDir),
+ ?line ok = compile_files(MainFiles, TopDir, TopDir),
+
+ %% Create the archive
+ {ok, TopFiles} = file:list_dir(TopDir),
+ ?line {ok, {_, ArchiveBin}} = zip:create(Archive, TopFiles,
+ [memory, {compress, []}, {cwd, TopDir}]),
+
+ %% Read the source script
+ OrigFile = filename:join([DataDir, "emulator_flags"]),
+ {ok, OrigBin} = file:read_file(OrigFile),
+ ?line [Shebang, Mode, _Flags | _Source] =
+ string:tokens(binary_to_list(OrigBin), "\n"),
+ Flags = "%%! -archive_script_dict foo bar"
+ " -archive_script_dict foo"
+ " -archive_script_dummy bar",
+ ?line {ok, OrigFI} = file:read_file_info(OrigFile),
+
+ %%%%%%%
+ %% Create and run scripts without emulator flags
+ MainBase = "archive_script_main",
+ MainScript = filename:join([PrivDir, MainBase]),
+
+ %% With shebang
+ ?line ok = file:write_file(MainScript,
+ [Shebang, "\n",
+ Flags, "\n",
+ ArchiveBin]),
+ ?line ok = file:write_file_info(MainScript, OrigFI),
+
+ ?line run(PrivDir, MainBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n"
+ "dummy:[{archive_script_dummy,[\"bar\"]}]\n"
+ "priv:{ok,<<\"Some private data...\\n\">>}\n"
+ "ExitCode:0">>]),
+
+ ?line run(PrivDir, "", MainBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n"
+ "dummy:[{archive_script_dummy,[\"bar\"]}]\n"
+ "priv:{ok,<<\"Some private data...\\n\">>}\n"
+ "ExitCode:0">>]),
+
+ ?line ok = file:rename(MainScript, MainScript ++ "_with_shebang"),
+
+ %% Without shebang (no flags)
+ ?line ok = file:write_file(MainScript,
+ ["Something else than shebang!!!", "\n",
+ ArchiveBin]),
+ ?line ok = file:write_file_info(MainScript, OrigFI),
+
+ ?line run(PrivDir, "", MainBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "dict:[]\n"
+ "dummy:[]\n"
+ "priv:{ok,<<\"Some private data...\\n\">>}\n"
+ "ExitCode:0">>]),
+ ?line ok = file:rename(MainScript, MainScript ++ "_without_shebang"),
+
+ %% Plain archive without header (no flags)
+
+ ?line ok = file:write_file(MainScript, [ArchiveBin]),
+ ?line ok = file:write_file_info(MainScript, OrigFI),
+
+ ?line run(PrivDir, "", MainBase ++ " -arg1 arg2 arg3",
+ [<<"main:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "dict:[]\n"
+ "dummy:[]\n"
+ "priv:{ok,<<\"Some private data...\\n\">>}\n"
+ "ExitCode:0">>]),
+ ?line ok = file:rename(MainScript, MainScript ++ "_without_header"),
+
+ %%%%%%%
+ %% Create and run scripts with emulator flags
+ AltBase = "archive_script_alternate_main",
+ AltScript = filename:join([PrivDir, AltBase]),
+ ?line ok = file:write_file(AltScript,
+ [Shebang, "\n",
+ Mode, "\n",
+ Flags, " -escript main archive_script_main2\n",
+ ArchiveBin]),
+ ?line ok = file:write_file_info(AltScript, OrigFI),
+
+ ?line run(PrivDir, AltBase ++ " -arg1 arg2 arg3",
+ [<<"main2:[\"-arg1\",\"arg2\",\"arg3\"]\n"
+ "dict:[{archive_script_dict,[\"foo\",\"bar\"]},{archive_script_dict,[\"foo\"]}]\n"
+ "dummy:[{archive_script_dummy,[\"bar\"]}]\n"
+ "priv:{ok,<<\"Some private data...\\n\">>}\n"
+ "ExitCode:0">>]),
+
+ ok.
+
+compile_app(TopDir, AppName) ->
+ AppDir = filename:join([TopDir, AppName]),
+ SrcDir = filename:join([AppDir, "src"]),
+ OutDir = filename:join([AppDir, "ebin"]),
+ ?line {ok, Files} = file:list_dir(SrcDir),
+ compile_files(Files, SrcDir, OutDir).
+
+compile_files([File | Files], SrcDir, OutDir) ->
+ case filename:extension(File) of
+ ".erl" ->
+ AbsFile = filename:join([SrcDir, File]),
+ case compile:file(AbsFile, [{outdir, OutDir}]) of
+ {ok, _Mod} ->
+ compile_files(Files, SrcDir, OutDir);
+ Error ->
+ {compilation_error, AbsFile, OutDir, Error}
+ end;
+ _ ->
+ compile_files(Files, SrcDir, OutDir)
+ end;
+compile_files([], _, _) ->
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+epp(Config) when is_list(Config) ->
+ Data = ?config(data_dir, Config),
+ Dir = filename:absname(Data), %Get rid of trailing slash.
+ ?line run(Dir, "factorial_epp 5",
+ <<"factorial 5 = 120\nExitCode:0">>),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+run(Dir, Cmd0, Expected0) ->
+ Expected = iolist_to_binary(expected_output(Expected0, Dir)),
+ Cmd = case os:type() of
+ {win32,_} -> "escript " ++ filename:nativename(Dir) ++ "\\" ++ Cmd0;
+ _ -> Cmd0
+ end,
+ do_run(Dir, Cmd, Expected).
+
+run(Dir, Opts, Cmd0, Expected) ->
+ Cmd = case os:type() of
+ {win32,_} -> "escript " ++ Opts ++ " " ++ filename:nativename(Dir) ++ "\\" ++ Cmd0;
+ _ -> "escript " ++ Opts ++ " " ++ Dir ++ "/" ++ Cmd0
+ end,
+ do_run(Dir, Cmd, Expected).
+
+do_run(Dir, Cmd, Expected0) ->
+ io:format("Run: ~p\n", [Cmd]),
+ Expected = iolist_to_binary(expected_output(Expected0, Dir)),
+
+ Env = [{"PATH",Dir++":"++os:getenv("PATH")}],
+ Port = open_port({spawn,Cmd}, [exit_status,eof,in,{env,Env}]),
+ Res = get_data(Port, []),
+ receive
+ {Port,{exit_status,ExitCode}} ->
+ case iolist_to_binary([Res,"ExitCode:"++integer_to_list(ExitCode)]) of
+ Expected ->
+ ok;
+ Actual ->
+ io:format("Expected: ~p\n", [Expected]),
+ io:format("Actual: ~p\n", [Actual]),
+ ?t:fail()
+ end
+ end.
+
+get_data(Port, SoFar) ->
+ receive
+ {Port,{data,Bytes}} ->
+ get_data(Port, [SoFar|Bytes]);
+ {Port,eof} ->
+ erlang:port_close(Port),
+ SoFar
+ end.
+
+expected_output([data_dir|T], Data) ->
+ Slash = case os:type() of
+ {win32,_} -> "\\";
+ _ -> "/"
+ end,
+ [filename:nativename(Data)++Slash|expected_output(T, Data)];
+expected_output([H|T], Data) ->
+ [H|expected_output(T, Data)];
+expected_output([], _) ->
+ [];
+expected_output(Bin, _) when is_binary(Bin) ->
+ Bin.
+
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/ebin/archive_script_dict.app b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/ebin/archive_script_dict.app
new file mode 100644
index 0000000000..d703977a1d
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/ebin/archive_script_dict.app
@@ -0,0 +1,12 @@
+{application, archive_script_dict,
+ [{description, "archive_script_dict"},
+ {vsn, "0.1"},
+ {modules, [
+ archive_script_dict,
+ archive_script_dict_sup
+ ]},
+ {registered, [
+ archive_script_dict_sup
+ ]},
+ {applications, [kernel, stdlib]},
+ {mod, {archive_script_dict_app, [[]]}}]}.
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/priv/archive_script_dict.txt b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/priv/archive_script_dict.txt
new file mode 100644
index 0000000000..8fa2c8c064
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/priv/archive_script_dict.txt
@@ -0,0 +1 @@
+Some private data...
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict.erl
new file mode 100644
index 0000000000..a614817b04
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict.erl
@@ -0,0 +1,125 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_dict).
+-behaviour(sys).
+
+%% Public
+-export([new/1, store/3, erase/2, find/2, foldl/3, erase/1]).
+
+%% Internal
+-export([init/3, loop/3]).
+
+%% supervisor callback
+-export([start_link/2]).
+
+%% sys callback functions
+-export([
+ system_continue/3,
+ system_terminate/4,
+ system_code_change/4
+ ]).
+
+-define(SUPERVISOR, archive_script_dict_sup).
+
+start_link(Name, Debug) ->
+ proc_lib:start_link(?MODULE, init, [self(), Name, Debug], infinity, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Client
+
+new(Name) ->
+ supervisor:start_child(?SUPERVISOR, [Name]).
+
+store(Pid, Key, Val) ->
+ call(Pid, {store, Key, Val}).
+
+erase(Pid, Key) ->
+ call(Pid, {erase, Key}).
+
+find(Pid, Key) ->
+ call(Pid, {find, Key}).
+
+foldl(Pid, Fun, Acc) ->
+ call(Pid, {foldl, Fun, Acc}).
+
+erase(Pid) ->
+ call(Pid, stop).
+
+call(Name, Msg) when is_atom(Name) ->
+ call(whereis(Name), Msg);
+call(Pid, Msg) when is_pid(Pid) ->
+ Ref = erlang:monitor(process, Pid),
+ Pid ! {self(), Ref, Msg},
+ receive
+ {Ref, Reply} ->
+ erlang:demonitor(Ref, [flush]),
+ Reply;
+ {'DOWN', Ref, _, _, Reason} ->
+ {error, Reason}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Server
+
+init(Parent, Name, Debug) ->
+ register(Name, self()),
+ Dict = dict:new(),
+ proc_lib:init_ack(Parent, {ok, self()}),
+ loop(Dict, Parent, Debug).
+
+loop(Dict, Parent, Debug) ->
+ receive
+ {system, From, Msg} ->
+ sys:handle_system_msg(Msg, From, Parent, ?MODULE, Debug, Dict);
+ {ReplyTo, Ref, {store, Key, Val}} ->
+ Dict2 = dict:store(Key, Val, Dict),
+ ReplyTo ! {Ref, ok},
+ ?MODULE:loop(Dict2, Parent, Debug);
+ {ReplyTo, Ref, {erase, Key}} ->
+ Dict2 = dict:erase(Key, Dict),
+ ReplyTo ! {Ref, ok},
+ ?MODULE:loop(Dict2, Parent, Debug);
+ {ReplyTo, Ref, {find, Key}} ->
+ Res = dict:find(Key, Dict),
+ ReplyTo ! {Ref, Res},
+ ?MODULE:loop(Dict, Parent, Debug);
+ {ReplyTo, Ref, {foldl, Fun, Acc}} ->
+ Acc2 = dict:foldl(Fun, Acc, Dict),
+ ReplyTo ! {Ref, {ok, Acc2}},
+ ?MODULE:loop(Dict, Parent, Debug);
+ {ReplyTo, Ref, stop} ->
+ ReplyTo ! {Ref, ok},
+ exit(normal);
+ Msg ->
+ error_logger:format("~p got unexpected message: ~p\n",
+ [self(), Msg]),
+ ?MODULE:loop(Dict, Parent, Debug)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% sys callbacks
+
+system_continue(Parent, Debug, Dict) ->
+ ?MODULE:loop(Dict, Parent, Debug).
+
+system_terminate(Reason, _Parent, _Debug, _Dict) ->
+ exit(Reason).
+
+system_code_change(Dict,_Module,_OldVsn,_Extra) ->
+ {ok, Dict}.
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_app.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_app.erl
new file mode 100644
index 0000000000..09b22ea532
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_app.erl
@@ -0,0 +1,29 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_dict_app).
+-behaviour(application).
+
+%% Public
+-export([start/2, stop/1]).
+
+start(_Type, Args) ->
+ archive_script_dict_sup:start_link(Args).
+
+stop(_State) ->
+ ok.
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_sup.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_sup.erl
new file mode 100644
index 0000000000..9a6c088552
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dict/src/archive_script_dict_sup.erl
@@ -0,0 +1,39 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_dict_sup).
+-behaviour(supervisor).
+
+%% Public
+-export([start_link/1]).
+
+%% Internal
+-export([init/1, start_simple_child/2]).
+
+-define(CHILD_MOD, archive_script_dict).
+
+start_link(Debug) ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [Debug]).
+
+init([Debug]) ->
+ Flags = {simple_one_for_one, 0, 3600},
+ MFA = {?MODULE, start_simple_child, [Debug]},
+ {ok, {Flags, [{?MODULE, MFA, transient, timer:seconds(3), worker, [?CHILD_MOD]}]}}.
+
+start_simple_child(Debug, Name) ->
+ ?CHILD_MOD:start_link(Name, Debug).
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/ebin/archive_script_dummy.app b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/ebin/archive_script_dummy.app
new file mode 100644
index 0000000000..bbb071c19b
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/ebin/archive_script_dummy.app
@@ -0,0 +1,10 @@
+{application, archive_script_dummy,
+ [{description, "archive_script_dummy"},
+ {vsn, "0.1"},
+ {modules, [
+ archive_script_main,
+ archive_script_main2
+ ]},
+ {registered, []},
+ {applications, [kernel, stdlib, archive_script_dict]},
+ {mod, {archive_script_dummy_app, [[]]}}]}.
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy.erl
new file mode 100644
index 0000000000..7c19ebf82f
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy.erl
@@ -0,0 +1,29 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_dummy).
+-behaviour(application).
+
+%% Public
+-export([start/2, stop/1]).
+
+start(_Type, Args) ->
+ archive_script_main_sup:start_link(Args).
+
+stop(_State) ->
+ ok.
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_app.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_app.erl
new file mode 100644
index 0000000000..c0910d379e
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_app.erl
@@ -0,0 +1,29 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_dummy_app).
+-behaviour(application).
+
+%% Public
+-export([start/2, stop/1]).
+
+start(_Type, Args) ->
+ archive_script_dummy_sup:start_link(Args).
+
+stop(_State) ->
+ ok.
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_sup.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_sup.erl
new file mode 100644
index 0000000000..8dff5c9335
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_dummy/src/archive_script_dummy_sup.erl
@@ -0,0 +1,33 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_dummy_sup).
+-behaviour(supervisor).
+
+%% Public
+-export([start_link/1]).
+
+%% Internal
+-export([init/1]).
+
+start_link(Debug) ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [Debug]).
+
+init([Debug]) ->
+ Flags = {one_for_one, 0, 3600},
+ {ok, {Flags, []}}.
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main.erl
new file mode 100644
index 0000000000..d257744cd7
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main.erl
@@ -0,0 +1,61 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_main).
+-behaviour(escript).
+
+-export([main/1]).
+
+-define(DUMMY, archive_script_dummy).
+-define(DICT, archive_script_dict).
+
+main(MainArgs) ->
+ %% Some printouts
+ io:format("main:~p\n",[MainArgs]),
+ ErlArgs = init:get_arguments(),
+ io:format("dict:~p\n",[[E || E <- ErlArgs, element(1, E) =:= ?DICT]]),
+ io:format("dummy:~p\n",[[E || E <- ErlArgs, element(1, E) =:= ?DUMMY]]),
+
+ %% Start the applications
+ {error, {not_started, ?DICT}} = application:start(?DUMMY),
+ ok = application:start(?DICT),
+ ok = application:start(?DUMMY),
+
+ %% Access dict priv dir
+ PrivDir = code:priv_dir(?DICT),
+ PrivFile = filename:join([PrivDir, "archive_script_dict.txt"]),
+ case erl_prim_loader:get_file(PrivFile) of
+ {ok, Bin, _FullPath} ->
+ io:format("priv:~p\n", [{ok, Bin}]);
+ error ->
+ io:format("priv:~p\n", [{error, PrivFile}])
+ end,
+
+ %% Use the dict app
+ Tab = archive_script_main_tab,
+ Key = foo,
+ Val = bar,
+ {ok, _Pid} = ?DICT:new(Tab),
+ error = ?DICT:find(Tab, Key),
+ ok = ?DICT:store(Tab, Key, Val),
+ {ok, Val} = ?DICT:find(Tab, Key),
+ ok = ?DICT:erase(Tab, Key),
+ error = ?DICT:find(Tab, Key),
+ ok = ?DICT:erase(Tab),
+ ok.
+
diff --git a/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main2.erl b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main2.erl
new file mode 100644
index 0000000000..de56579998
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/archive_script/archive_script_main2.erl
@@ -0,0 +1,60 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(archive_script_main2).
+-behaviour(escript).
+
+-export([main/1]).
+
+-define(DUMMY, archive_script_dummy).
+-define(DICT, archive_script_dict).
+
+main(MainArgs) ->
+ %% Some printouts
+ io:format("main2:~p\n",[MainArgs]),
+ ErlArgs = init:get_arguments(),
+ io:format("dict:~p\n",[[E || E <- ErlArgs, element(1, E) =:= ?DICT]]),
+ io:format("dummy:~p\n",[[E || E <- ErlArgs, element(1, E) =:= ?DUMMY]]),
+
+ %% Start the applications
+ {error, {not_started, ?DICT}} = application:start(archive_script_dummy),
+ ok = application:start(?DICT),
+ ok = application:start(?DUMMY),
+
+ %% Access dict priv dir
+ PrivDir = code:priv_dir(?DICT),
+ PrivFile = filename:join([PrivDir, "archive_script_dict.txt"]),
+ case erl_prim_loader:get_file(PrivFile) of
+ {ok, Bin, _FullPath} ->
+ io:format("priv:~p\n", [{ok, Bin}]);
+ error ->
+ io:format("priv:~p\n", [{error, PrivFile}])
+ end,
+
+ %% Use the dict app
+ Tab = archive_script_main_tab,
+ Key = foo,
+ Val = bar,
+ {ok, _Pid} = ?DICT:new(Tab),
+ error = ?DICT:find(Tab, Key),
+ ok = ?DICT:store(Tab, Key, Val),
+ {ok, Val} = ?DICT:find(Tab, Key),
+ ok = ?DICT:erase(Tab, Key),
+ error = ?DICT:find(Tab, Key),
+ ok = ?DICT:erase(Tab),
+ ok.
diff --git a/lib/stdlib/test/escript_SUITE_data/compile_error b/lib/stdlib/test/escript_SUITE_data/compile_error
new file mode 100755
index 0000000000..ae644a9af7
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/compile_error
@@ -0,0 +1,12 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+foo() ->
+ gurka ** nisse.
+
+
+blurf() blarf().
+
+
+
+
diff --git a/lib/stdlib/test/escript_SUITE_data/emulator_flags b/lib/stdlib/test/escript_SUITE_data/emulator_flags
new file mode 100755
index 0000000000..9e16818da5
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/emulator_flags
@@ -0,0 +1,11 @@
+#! /usr/bin/env escript
+%% -*- erlang -*-
+%%! -nostick -mnesia dir a/directory -mnesia debug verbose
+
+main(MainArgs) ->
+ io:format("main:~p\n",[MainArgs]),
+ ErlArgs = init:get_arguments(),
+ io:format("nostick:~p\n",[[E || E <- ErlArgs, element(1, E) =:= nostick]]),
+ io:format("mnesia:~p\n", [[E || E <- ErlArgs, element(1, E) =:= mnesia]]),
+ io:format("ERL_FLAGS=~p\n", [os:getenv("ERL_FLAGS")]),
+ io:format("unknown:~p\n",[[E || E <- ErlArgs, element(1, E) =:= unknown]]).
diff --git a/lib/stdlib/test/escript_SUITE_data/factorial b/lib/stdlib/test/escript_SUITE_data/factorial
new file mode 100755
index 0000000000..200e320e9a
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/factorial
@@ -0,0 +1,11 @@
+#!/usr/bin/env escript
+
+main([In]) ->
+ X = list_to_integer(In),
+ N = fac(X),
+ io:format("factorial ~w = ~w~n",[X, N]).
+
+fac(0) -> 1;
+fac(N) ->
+ N * fac(N-1).
+
diff --git a/lib/stdlib/test/escript_SUITE_data/factorial_compile b/lib/stdlib/test/escript_SUITE_data/factorial_compile
new file mode 100755
index 0000000000..c822808b90
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/factorial_compile
@@ -0,0 +1,12 @@
+#!/usr/bin/env escript
+-mode(compile).
+
+main([In]) ->
+ X = list_to_integer(In),
+ N = fac(X),
+ io:format("factorial ~w = ~w~n",[X, N]).
+
+fac(0) -> 1;
+fac(N) ->
+ N * fac(N-1).
+
diff --git a/lib/stdlib/test/escript_SUITE_data/factorial_compile_main b/lib/stdlib/test/escript_SUITE_data/factorial_compile_main
new file mode 100755
index 0000000000..a59bb105dc
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/factorial_compile_main
@@ -0,0 +1,13 @@
+#!/usr/bin/env escript
+-mode(compile).
+-export([main/1]).
+
+main([In]) ->
+ X = list_to_integer(In),
+ N = fac(X),
+ io:format("factorial ~w = ~w~n",[X, N]).
+
+fac(0) -> 1;
+fac(N) ->
+ N * fac(N-1).
+
diff --git a/lib/stdlib/test/escript_SUITE_data/factorial_epp b/lib/stdlib/test/escript_SUITE_data/factorial_epp
new file mode 100755
index 0000000000..dbdf974985
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/factorial_epp
@@ -0,0 +1,17 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+-module(factorial).
+-export([main/1]).
+
+-define(PREFIX, ?MODULE_STRING).
+
+main([In]) ->
+ X = list_to_integer(In),
+ N = fac(X),
+ io:format("~s ~w = ~w~n",[?PREFIX, X, N]).
+
+fac(0) -> 1;
+fac(N) ->
+ N * fac(N-1).
+
diff --git a/lib/stdlib/test/escript_SUITE_data/factorial_warning b/lib/stdlib/test/escript_SUITE_data/factorial_warning
new file mode 100755
index 0000000000..ef214e096a
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/factorial_warning
@@ -0,0 +1,13 @@
+#!/usr/bin/env escript
+
+main([In]) ->
+ X = list_to_integer(In),
+ N = fac(X),
+ io:format("factorial ~w = ~w~n",[X, N]).
+
+fac(0) -> 1;
+fac(N) ->
+ N * fac(N-1).
+
+bar() ->
+ ok.
diff --git a/lib/stdlib/test/escript_SUITE_data/filesize b/lib/stdlib/test/escript_SUITE_data/filesize
new file mode 100755
index 0000000000..fd211c55cd
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/filesize
@@ -0,0 +1,11 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+-include_lib("kernel/include/file.hrl").
+
+main([Filename]) ->
+ {ok,#file_info{size=Size}} = file:read_file_info(Filename),
+ io:format("~p\n", [Size]).
+
+%% Deliberate warning follows so that we can check that line numbers
+%% correct after an -include_lib directive.
+id(I) -> I.
diff --git a/lib/stdlib/test/escript_SUITE_data/lint_error b/lib/stdlib/test/escript_SUITE_data/lint_error
new file mode 100755
index 0000000000..7be6e59b1d
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/lint_error
@@ -0,0 +1,14 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+main([]) ->
+ ok.
+main(Args) ->
+ io:format("~p\n", [length(Args)]),
+ halt(ExitCode).
+
+
+
+
+
+
diff --git a/lib/stdlib/test/escript_SUITE_data/strange.name b/lib/stdlib/test/escript_SUITE_data/strange.name
new file mode 100755
index 0000000000..19ad8aa40a
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/strange.name
@@ -0,0 +1,7 @@
+#! /usr/bin/env escript
+%% -*- erlang -*-
+
+-mode(compile).
+
+main(MainArgs) ->
+ io:format("main:~p\n",[MainArgs]).
diff --git a/lib/stdlib/test/escript_SUITE_data/tail_rec b/lib/stdlib/test/escript_SUITE_data/tail_rec
new file mode 100755
index 0000000000..2ef64e1239
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/tail_rec
@@ -0,0 +1,25 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+-mode(interpret).
+
+tail_rec(PrevSize, N) ->
+ {_, Size} = process_info(self(), stack_size),
+ if
+ N =< 0 ->
+ ok;
+ PrevSize =:= undefined ->
+ tail_rec(Size, N - 1);
+ PrevSize =:= Size ->
+ tail_rec(Size, N - 1);
+ true ->
+ io:format("Not tail recursive (~p): Stack size ~p should be ~p\n",
+ [N, Size, PrevSize]),
+ tail_rec(Size, N - 1)
+ end.
+
+main([Repetitions]) ->
+ tail_rec(undefined, list_to_integer(Repetitions)),
+ io:format("ok\n", []);
+main(_) ->
+ io:format("Usage: ~s Repetitions\n", [escript:script_name()]),
+ init:stop(1).
diff --git a/lib/stdlib/test/escript_SUITE_data/test_script_name b/lib/stdlib/test/escript_SUITE_data/test_script_name
new file mode 100755
index 0000000000..c4a3d93646
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/test_script_name
@@ -0,0 +1,5 @@
+#!/usr/bin/env escript
+
+main(_) ->
+ io:format("~s\n", [escript:script_name()]).
+
diff --git a/lib/stdlib/test/escript_SUITE_data/trap_exit b/lib/stdlib/test/escript_SUITE_data/trap_exit
new file mode 100755
index 0000000000..81fcfa9d12
--- /dev/null
+++ b/lib/stdlib/test/escript_SUITE_data/trap_exit
@@ -0,0 +1,6 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+main(_) ->
+ {trap_exit,Bool} = process_info(self(), trap_exit),
+ io:format("~p\n", [Bool]).
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
new file mode 100644
index 0000000000..6016bc9bdc
--- /dev/null
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -0,0 +1,5355 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ets_SUITE).
+
+-export([all/1]).
+-export([new/1,default/1,setbag/1,badnew/1,verybadnew/1,named/1,keypos2/1,
+ privacy/1,privacy_owner/2]).
+-export([insert/1,empty/1,badinsert/1]).
+-export([lookup/1,time_lookup/1,badlookup/1,lookup_order/1]).
+-export([delete/1,delete_elem/1,delete_tab/1,delete_large_tab/1,
+ delete_large_named_table/1,
+ evil_delete/1,baddelete/1,match_delete/1,table_leak/1]).
+-export([match_delete3/1]).
+-export([firstnext/1,firstnext_concurrent/1]).
+-export([slot/1]).
+-export([match/1, match1/1, match2/1, match_object/1, match_object2/1]).
+-export([misc/1, dups/1, misc1/1, safe_fixtable/1, info/1, tab2list/1]).
+-export([files/1, tab2file/1, tab2file2/1, tab2file3/1, tabfile_ext1/1,
+ tabfile_ext2/1, tabfile_ext3/1, tabfile_ext4/1]).
+-export([heavy/1, heavy_lookup/1, heavy_lookup_element/1]).
+-export([lookup_element/1, lookup_element_mult/1]).
+-export([fold/1]).
+-export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]).
+-export([t_delete_object/1, t_init_table/1, t_whitebox/1,
+ t_delete_all_objects/1, t_insert_list/1, t_test_ms/1,
+ t_select_delete/1,t_ets_dets/1]).
+
+-export([do_lookup/2, do_lookup_element/3]).
+
+-export([ordered/1, ordered_match/1, interface_equality/1,
+ fixtable_next/1, fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1,
+ update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]).
+-export([member/1]).
+-export([memory/1]).
+-export([select_fail/1]).
+-export([t_insert_new/1]).
+-export([t_repair_continuation/1]).
+-export([t_match_spec_run/1]).
+-export([t_bucket_disappears/1]).
+-export([otp_5340/1]).
+-export([otp_6338/1]).
+-export([otp_6842_select_1000/1]).
+-export([otp_7665/1]).
+-export([meta_wb/1]).
+-export([grow_shrink/1, grow_pseudo_deleted/1, shrink_pseudo_deleted/1]).
+-export([meta_smp/1,
+ meta_lookup_unnamed_read/1, meta_lookup_unnamed_write/1,
+ meta_lookup_named_read/1, meta_lookup_named_write/1,
+ meta_newdel_unnamed/1, meta_newdel_named/1]).
+-export([smp_insert/1, smp_fixed_delete/1, smp_unfix_fix/1, smp_select_delete/1, otp_8166/1]).
+-export([exit_large_table_owner/1,
+ exit_many_large_table_owner/1,
+ exit_many_tables_owner/1,
+ exit_many_many_tables_owner/1]).
+-export([write_concurrency/1, heir/1, give_away/1, setopts/1]).
+-export([bad_table/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2, end_per_suite/1]).
+%% Convenience for manual testing
+-export([random_test/0]).
+
+% internal exports
+-export([dont_make_worse_sub/0, make_better_sub1/0, make_better_sub2/0]).
+-export([t_repair_continuation_do/1, default_do/1, t_bucket_disappears_do/1,
+ select_fail_do/1, whitebox_1/1, whitebox_2/1, t_delete_all_objects_do/1,
+ t_delete_object_do/1, t_init_table_do/1, t_insert_list_do/1,
+ update_element_opts/1, update_element_opts/4, update_element/4, update_element_do/4,
+ update_element_neg/1, update_element_neg_do/1, update_counter_do/1, update_counter_neg/1,
+ evil_update_counter_do/1, fixtable_next_do/1, heir_do/1, give_away_do/1, setopts_do/1,
+ rename_do/1, rename_unnamed_do/1, interface_equality_do/1, ordered_match_do/1,
+ ordered_do/1, privacy_do/1, empty_do/1, badinsert_do/1, time_lookup_do/1,
+ lookup_order_do/1, lookup_element_mult_do/1, delete_tab_do/1, delete_elem_do/1,
+ match_delete_do/1, match_delete3_do/1, firstnext_do/1,
+ slot_do/1, match1_do/1, match2_do/1, match_object_do/1, match_object2_do/1,
+ misc1_do/1, safe_fixtable_do/1, info_do/1, dups_do/1, heavy_lookup_do/1,
+ heavy_lookup_element_do/1, member_do/1, otp_5340_do/1, otp_7665_do/1, meta_wb_do/1
+ ]).
+
+-include("test_server.hrl").
+
+init_per_testcase(Case, Config) ->
+ Seed = {S1,S2,S3} = random:seed0(), %now(),
+ random:seed(S1,S2,S3),
+ io:format("*** SEED: ~p ***\n", [Seed]),
+ start_spawn_logger(),
+ wait_for_test_procs(), %% Ensure previous case cleaned up
+ Dog=test_server:timetrap(test_server:minutes(20)),
+ [{watchdog, Dog}, {test_case, Case} | Config].
+
+fin_per_testcase(_Func, Config) ->
+ Dog=?config(watchdog, Config),
+ wait_for_test_procs(true),
+ test_server:timetrap_cancel(Dog).
+
+
+end_per_suite(_Config) ->
+ stop_spawn_logger(),
+ catch erts_debug:set_internal_state(available_internal_state, false).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(suite) ->
+ [
+ new,insert,lookup,delete,firstnext,firstnext_concurrent,slot,match,
+ t_match_spec_run,
+ lookup_element, misc,files, heavy,
+ ordered, ordered_match, interface_equality,
+ fixtable_next, fixtable_insert, rename, rename_unnamed, evil_rename,
+ update_element, update_counter, evil_update_counter, partly_bound,
+ match_heavy, fold, member,
+ t_delete_object, t_init_table, t_whitebox,
+ t_delete_all_objects, t_insert_list, t_test_ms,
+ t_select_delete, t_ets_dets, memory,
+ t_bucket_disappears,
+ select_fail,t_insert_new, t_repair_continuation, otp_5340, otp_6338,
+ otp_6842_select_1000, otp_7665,
+ meta_wb,
+ grow_shrink, grow_pseudo_deleted, shrink_pseudo_deleted,
+ meta_smp,
+ smp_insert, smp_fixed_delete, smp_unfix_fix, smp_select_delete, otp_8166,
+ exit_large_table_owner,
+ exit_many_large_table_owner,
+ exit_many_tables_owner,
+ exit_many_many_tables_owner,
+ write_concurrency, heir, give_away, setopts,
+ bad_table
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+t_bucket_disappears(suite) ->
+ [];
+t_bucket_disappears(doc) ->
+ ["Test that a disappearing bucket during select of a non-fixed table works."];
+t_bucket_disappears(Config) when is_list(Config) ->
+ repeat_for_opts(t_bucket_disappears_do).
+
+t_bucket_disappears_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line ets:new(abcd, [named_table, public, {keypos, 2} | Opts]),
+ ?line ets:insert(abcd, {abcd,1,2}),
+ ?line ets:insert(abcd, {abcd,2,2}),
+ ?line ets:insert(abcd, {abcd,3,2}),
+ ?line {_, Cont} = ets:select(abcd, [{{'_', '$1', '_'},
+ [{'<', '$1', {const, 10}}],
+ ['$1']}], 1),
+ ?line ets:delete(abcd, 2),
+ ?line ets:select(Cont),
+ ?line true = ets:delete(abcd),
+ ?line verify_etsmem(EtsMem).
+
+
+t_match_spec_run(suite) ->
+ [];
+t_match_spec_run(doc) ->
+ ["Check ets:match_spec_run/2."];
+t_match_spec_run(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line [2,3] = ets:match_spec_run([{1},{2},{3}],
+ ets:match_spec_compile(
+ [{{'$1'},[{'>','$1',1}],['$1']}])),
+ ?line Huge = [{X} || X <- lists:seq(1,2500)],
+ ?line L = lists:seq(2476,2500),
+ ?line L = ets:match_spec_run(Huge,
+ ets:match_spec_compile(
+ [{{'$1'},[{'>','$1',2475}],['$1']}])),
+ ?line L2 = [{X*16#FFFFFFF} || X <- L],
+ ?line L2 = ets:match_spec_run(Huge,
+ ets:match_spec_compile(
+ [{{'$1'},
+ [{'>','$1',2475}],
+ [{{{'*','$1',16#FFFFFFF}}}]}])),
+ ?line [500,1000,1500,2000,2500] =
+ ets:match_spec_run(Huge,
+ ets:match_spec_compile(
+ [{{'$1'},
+ [{'=:=',{'rem','$1',500},0}],
+ ['$1']}])),
+ ?line verify_etsmem(EtsMem).
+
+
+
+t_repair_continuation(suite) ->
+ [];
+t_repair_continuation(doc) ->
+ ["Check ets:repair_continuation/2."];
+t_repair_continuation(Config) when is_list(Config) ->
+ repeat_for_opts(t_repair_continuation_do).
+
+
+t_repair_continuation_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line MS = [{'_',[],[true]}],
+ ?line MS2 = [{{{'$1','_'},'_'},[],['$1']}],
+ (fun() ->
+ ?line T = ets:new(x,[ordered_set|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS,5),
+ ?line C2 = erlang:setelement(5,C,<<>>),
+ ?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
+ ?line C3 = ets:repair_continuation(C2,MS),
+ ?line {[true,true,true,true,true],_} = ets:select(C3),
+ ?line {[true,true,true,true,true],_} = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+ (fun() ->
+ ?line T = ets:new(x,[ordered_set|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS,1001),
+ ?line C = '$end_of_table',
+ ?line C3 = ets:repair_continuation(C,MS),
+ ?line '$end_of_table' = ets:select(C3),
+ ?line '$end_of_table' = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+
+ (fun() ->
+ ?line T = ets:new(x,[ordered_set|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) ->
+ ets:insert(T,{integer_to_list(N),N}),
+ F(N-1,F)
+ end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS,5),
+ ?line C2 = erlang:setelement(5,C,<<>>),
+ ?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
+ ?line C3 = ets:repair_continuation(C2,MS),
+ ?line {[true,true,true,true,true],_} = ets:select(C3),
+ ?line {[true,true,true,true,true],_} = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+ (fun() ->
+ ?line T = ets:new(x,[ordered_set|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) ->
+ ets:insert(T,{{integer_to_list(N),N},N}),
+ F(N-1,F)
+ end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS2,5),
+ ?line C2 = erlang:setelement(5,C,<<>>),
+ ?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
+ ?line C3 = ets:repair_continuation(C2,MS2),
+ ?line {[_,_,_,_,_],_} = ets:select(C3),
+ ?line {[_,_,_,_,_],_} = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+
+ (fun() ->
+ ?line T = ets:new(x,[set|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) ->
+ ets:insert(T,{N,N}),
+ F(N-1,F)
+ end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS,5),
+ ?line C2 = erlang:setelement(4,C,<<>>),
+ ?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
+ ?line C3 = ets:repair_continuation(C2,MS),
+ ?line {[true,true,true,true,true],_} = ets:select(C3),
+ ?line {[true,true,true,true,true],_} = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+ (fun() ->
+ ?line T = ets:new(x,[set|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) ->
+ ets:insert(T,{integer_to_list(N),N}),
+ F(N-1,F)
+ end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS,5),
+ ?line C2 = erlang:setelement(4,C,<<>>),
+ ?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
+ ?line C3 = ets:repair_continuation(C2,MS),
+ ?line {[true,true,true,true,true],_} = ets:select(C3),
+ ?line {[true,true,true,true,true],_} = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+ (fun() ->
+ ?line T = ets:new(x,[bag|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) ->
+ ets:insert(T,{integer_to_list(N),N}),
+ F(N-1,F)
+ end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS,5),
+ ?line C2 = erlang:setelement(4,C,<<>>),
+ ?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
+ ?line C3 = ets:repair_continuation(C2,MS),
+ ?line {[true,true,true,true,true],_} = ets:select(C3),
+ ?line {[true,true,true,true,true],_} = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+ (fun() ->
+ ?line T = ets:new(x,[duplicate_bag|Opts]),
+ ?line F = fun(0,_)->ok;(N,F) ->
+ ets:insert(T,{integer_to_list(N),N}),
+ F(N-1,F)
+ end,
+ ?line F(1000,F),
+ ?line {_,C} = ets:select(T,MS,5),
+ ?line C2 = erlang:setelement(4,C,<<>>),
+ ?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
+ ?line C3 = ets:repair_continuation(C2,MS),
+ ?line {[true,true,true,true,true],_} = ets:select(C3),
+ ?line {[true,true,true,true,true],_} = ets:select(C),
+ ?line true = ets:delete(T)
+ end)(),
+ ?line false = ets:is_compiled_ms(<<>>),
+ ?line true = ets:is_compiled_ms(ets:match_spec_compile(MS)),
+ ?line verify_etsmem(EtsMem).
+
+new(suite) -> [default,setbag,badnew,verybadnew,named,keypos2,privacy].
+
+default(doc) ->
+ ["Test case to check that a new ets table is defined as a `set' and "
+ "`protected'"];
+default(suite) -> [];
+default(Config) when is_list(Config) ->
+ %% Default should be set,protected
+ repeat_for_opts(default_do).
+
+default_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Def = ets:new(def,Opts),
+ ?line set = ets:info(Def,type),
+ ?line protected = ets:info(Def,protection),
+ ?line ets:delete(Def),
+ ?line verify_etsmem(EtsMem).
+
+select_fail(doc) ->
+ ["Test that select fails even if nothing can match"];
+select_fail(suite) ->
+ [];
+select_fail(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(select_fail_do, [all_types,write_concurrency]),
+ ?line verify_etsmem(EtsMem).
+
+select_fail_do(Opts) ->
+ ?line T = ets:new(x,Opts),
+ ?line ets:insert(T,{a,a}),
+ ?line case (catch
+ ets:select(T,[{{a,'_'},[],[{snuffla}]}])) of
+ {'EXIT',{badarg,_}} ->
+ ok;
+ Else0 ->
+ exit({type,ets:info(T,type),
+ expected,'EXIT',got,Else0})
+ end,
+ ?line case (catch
+ ets:select(T,[{{b,'_'},[],[{snuffla}]}])) of
+ {'EXIT',{badarg,_}} ->
+ ok;
+ Else1 ->
+ exit({type,ets:info(T,type),
+ expected,'EXIT',got,Else1})
+ end,
+ ?line ets:delete(T).
+
+
+-define(S(T),ets:info(T,memory)).
+-define(TAB_STRUCT_SZ, erts_debug:get_internal_state('DbTable_words')).
+-define(NORMAL_TAB_STRUCT_SZ, 26). %% SunOS5.8, 32-bit, non smp, private heap
+%%
+%% The hardcoded expected memory sizes (in words) are the ones we expect on:
+%% SunOS5.8, 32-bit, non smp, private heap
+%%
+memory(doc) ->
+ ["Whitebox test of ets:info(X,memory)"];
+memory(suite) ->
+ [];
+memory(Config) when is_list(Config) ->
+ ?line erts_debug:set_internal_state(available_internal_state, true),
+ ?line ok = chk_normal_tab_struct_size(),
+ ?line L = [T1,T2,T3,T4] = fill_sets_int(1000),
+ ?line XRes1 = adjust_xmem(L, {16862,16072,16072,16078}),
+ ?line Res1 = {?S(T1),?S(T2),?S(T3),?S(T4)},
+ ?line lists:foreach(fun(T) ->
+ Before = ets:info(T,size),
+ Key = 2, %894, %%ets:first(T),
+ Objs = ets:lookup(T,Key),
+ ?line ets:delete(T,Key),
+ io:format("deleted key ~p from ~p changed size ~p to ~p: ~p\n",
+ [Key, ets:info(T,type), Before, ets:info(T,size), Objs])
+ end,
+ L),
+ ?line XRes2 = adjust_xmem(L, {16849,16060,16048,16054}),
+ ?line Res2 = {?S(T1),?S(T2),?S(T3),?S(T4)},
+ ?line lists:foreach(fun(T) ->
+ Before = ets:info(T,size),
+ Key = 4, %802, %ets:first(T),
+ Objs = ets:lookup(T,Key),
+ ?line ets:match_delete(T,{Key,'_'}),
+ io:format("match_deleted key ~p from ~p changed size ~p to ~p: ~p\n",
+ [Key, ets:info(T,type), Before, ets:info(T,size), Objs])
+ end,
+ L),
+ ?line XRes3 = adjust_xmem(L, {16836,16048,16024,16030}),
+ ?line Res3 = {?S(T1),?S(T2),?S(T3),?S(T4)},
+ ?line lists:foreach(fun(T) ->
+ ?line ets:delete_all_objects(T)
+ end,
+ L),
+ ?line XRes4 = adjust_xmem(L, {76,286,286,286}),
+ ?line Res4 = {?S(T1),?S(T2),?S(T3),?S(T4)},
+ lists:foreach(fun(T) ->
+ ?line ets:delete(T)
+ end,
+ L),
+ ?line L2 = [T11,T12,T13,T14] = fill_sets_int(1000),
+ ?line lists:foreach(fun(T) ->
+ ?line ets:select_delete(T,[{'_',[],[true]}])
+ end,
+ L2),
+ ?line XRes5 = adjust_xmem(L2, {76,286,286,286}),
+ ?line Res5 = {?S(T11),?S(T12),?S(T13),?S(T14)},
+ ?line ?t:format("XRes1 = ~p~n"
+ " Res1 = ~p~n~n"
+ "XRes2 = ~p~n"
+ " Res2 = ~p~n~n"
+ "XRes3 = ~p~n"
+ " Res3 = ~p~n~n"
+ "XRes4 = ~p~n"
+ " Res4 = ~p~n~n"
+ "XRes5 = ~p~n"
+ " Res5 = ~p~n~n",
+ [XRes1, Res1,
+ XRes2, Res2,
+ XRes3, Res3,
+ XRes4, Res4,
+ XRes5, Res5]),
+ ?line XRes1 = Res1,
+ ?line XRes2 = Res2,
+ ?line XRes3 = Res3,
+ ?line XRes4 = Res4,
+ ?line XRes5 = Res5,
+ ?line catch erts_debug:set_internal_state(available_internal_state, false),
+ ?line ok.
+
+chk_normal_tab_struct_size() ->
+ ?line System = {os:type(),
+ os:version(),
+ erlang:system_info(wordsize),
+ erlang:system_info(smp_support),
+ erlang:system_info(heap_type)},
+ ?line ?t:format("System = ~p~n", [System]),
+ ?line ?t:format("?NORMAL_TAB_STRUCT_SZ=~p~n", [?NORMAL_TAB_STRUCT_SZ]),
+ ?line ?t:format("?TAB_STRUCT_SZ=~p~n", [?TAB_STRUCT_SZ]),
+ ?line case System of
+ {{unix, sunos}, {5, 8, 0}, 4, false, private} ->
+ ?line ?NORMAL_TAB_STRUCT_SZ = ?TAB_STRUCT_SZ,
+ ?line ok;
+ _ ->
+ ?line ok
+ end.
+
+adjust_xmem([T1,T2,T3,T4], {A0,B0,C0,D0} = Mem0) ->
+ %% Adjust for 64-bit, smp, and os:
+ %% Table struct size may differ.
+ Mem1 = case ?TAB_STRUCT_SZ of
+ ?NORMAL_TAB_STRUCT_SZ ->
+ Mem0;
+ TabStructSz ->
+ TabDiff = TabStructSz - ?NORMAL_TAB_STRUCT_SZ,
+ {A0+TabDiff, B0+TabDiff, C0+TabDiff, D0+TabDiff}
+ end,
+ %% Adjust for hybrid and shared heaps:
+ %% Each record is one word smaller.
+ Mem2 = case erlang:system_info(heap_type) of
+ private ->
+ Mem1;
+ _ ->
+ {A1,B1,C1,D1} = Mem1,
+ {A1-ets:info(T1, size),B1-ets:info(T2, size),
+ C1-ets:info(T3, size),D1-ets:info(T4, size)}
+ end,
+ %%{Mem2,{ets:info(T1,stats),ets:info(T2,stats),ets:info(T3,stats),ets:info(T4,stats)}}.
+ Mem2.
+
+t_whitebox(doc) ->
+ ["Diverse whitebox testes"];
+t_whitebox(suite) ->
+ [];
+t_whitebox(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(whitebox_1),
+ repeat_for_opts(whitebox_1),
+ repeat_for_opts(whitebox_1),
+ repeat_for_opts(whitebox_2),
+ repeat_for_opts(whitebox_2),
+ repeat_for_opts(whitebox_2),
+ ?line verify_etsmem(EtsMem).
+
+whitebox_1(Opts) ->
+ ?line T=ets:new(x,[bag | Opts]),
+ ?line ets:insert(T,[{du,glade},{ta,en}]),
+ ?line ets:insert(T,[{hej,hopp2},{du,glade2},{ta,en2}]),
+ ?line {_,C}=ets:match(T,{ta,'$1'},1),
+ ?line ets:select(C),
+ ?line ets:match(C),
+ ?line ets:delete(T),
+ ok.
+
+whitebox_2(Opts) ->
+ ?line T=ets:new(x,[ordered_set, {keypos,2} | Opts]),
+ ?line T2=ets:new(x,[set, {keypos,2}| Opts]),
+ ?line 0 = ets:select_delete(T,[{{hej},[],[true]}]),
+ ?line 0 = ets:select_delete(T,[{{hej,hopp},[],[true]}]),
+ ?line 0 = ets:select_delete(T2,[{{hej},[],[true]}]),
+ ?line 0 = ets:select_delete(T2,[{{hej,hopp},[],[true]}]),
+ ?line ets:delete(T),
+ ?line ets:delete(T2),
+ ok.
+
+
+t_ets_dets(doc) ->
+ ["Test ets:to/from_dets"];
+t_ets_dets(suite) ->
+ [];
+t_ets_dets(Config) when is_list(Config) ->
+ repeat_for_opts(fun(Opts) -> t_ets_dets(Config,Opts) end).
+
+t_ets_dets(Config, Opts) ->
+ ?line Fname = gen_dets_filename(Config,1),
+ ?line (catch file:delete(Fname)),
+ ?line {ok,DTab} = dets:open_file(testdets_1,
+ [{file, Fname}]),
+ ?line ETab = ets:new(x,Opts),
+ ?line filltabint(ETab,3000),
+ ?line DTab = ets:to_dets(ETab,DTab),
+ ?line ets:delete_all_objects(ETab),
+ ?line 0 = ets:info(ETab,size),
+ ?line true = ets:from_dets(ETab,DTab),
+ ?line 3000 = ets:info(ETab,size),
+ ?line ets:delete(ETab),
+ ?line {'EXIT',{badarg,[{ets,to_dets,[ETab,DTab]}|_]}} =
+ (catch ets:to_dets(ETab,DTab)),
+ ?line {'EXIT',{badarg,[{ets,from_dets,[ETab,DTab]}|_]}} =
+ (catch ets:from_dets(ETab,DTab)),
+ ?line ETab2 = ets:new(x,Opts),
+ ?line filltabint(ETab2,3000),
+ ?line dets:close(DTab),
+ ?line {'EXIT',{badarg,[{ets,to_dets,[ETab2,DTab]}|_]}} =
+ (catch ets:to_dets(ETab2,DTab)),
+ ?line {'EXIT',{badarg,[{ets,from_dets,[ETab2,DTab]}|_]}} =
+ (catch ets:from_dets(ETab2,DTab)),
+ ?line ets:delete(ETab2),
+ ?line (catch file:delete(Fname)),
+ ok.
+
+t_delete_all_objects(doc) ->
+ ["Test ets:delete_all_objects/1"];
+t_delete_all_objects(suite) ->
+ [];
+t_delete_all_objects(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(t_delete_all_objects_do),
+ ?line verify_etsmem(EtsMem).
+
+t_delete_all_objects_do(Opts) ->
+ ?line T=ets:new(x,Opts),
+ ?line filltabint(T,4000),
+ ?line O=ets:first(T),
+ ?line ets:next(T,O),
+ ?line ets:safe_fixtable(T,true),
+ ?line true = ets:delete_all_objects(T),
+ ?line '$end_of_table' = ets:next(T,O),
+ ?line 0 = ets:info(T,size),
+ ?line 4000 = ets:info(T,kept_objects),
+ ?line ets:safe_fixtable(T,false),
+ ?line 0 = ets:info(T,size),
+ ?line 0 = ets:info(T,kept_objects),
+ ?line filltabint(T,4000),
+ ?line 4000 = ets:info(T,size),
+ ?line true = ets:delete_all_objects(T),
+ ?line 0 = ets:info(T,size),
+ ?line ets:delete(T).
+
+
+t_delete_object(doc) ->
+ ["Test ets:delete_object/2"];
+t_delete_object(suite) ->
+ [];
+t_delete_object(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(t_delete_object_do),
+ ?line verify_etsmem(EtsMem).
+
+t_delete_object_do(Opts) ->
+ ?line T = ets:new(x,Opts),
+ ?line filltabint(T,4000),
+ ?line del_one_by_one_set(T,1,4001),
+ ?line filltabint(T,4000),
+ ?line del_one_by_one_set(T,4000,0),
+ ?line filltabint(T,4000),
+ ?line First = ets:first(T),
+ ?line Next = ets:next(T,First),
+ ?line ets:safe_fixtable(T,true),
+ ?line ets:delete_object(T,{First, integer_to_list(First)}),
+ ?line Next = ets:next(T,First),
+ ?line 3999 = ets:info(T,size),
+ ?line 1 = ets:info(T,kept_objects),
+ ?line ets:safe_fixtable(T,false),
+ ?line 3999 = ets:info(T,size),
+ ?line 0 = ets:info(T,kept_objects),
+ ?line ets:delete(T),
+ ?line T1 = ets:new(x,[ordered_set | Opts]),
+ ?line filltabint(T1,4000),
+ ?line del_one_by_one_set(T1,1,4001),
+ ?line filltabint(T1,4000),
+ ?line del_one_by_one_set(T1,4000,0),
+ ?line ets:delete(T1),
+ ?line T2 = ets:new(x,[bag | Opts]),
+ ?line filltabint2(T2,4000),
+ ?line del_one_by_one_bag(T2,1,4001),
+ ?line filltabint2(T2,4000),
+ ?line del_one_by_one_bag(T2,4000,0),
+ ?line ets:delete(T2),
+ ?line T3 = ets:new(x,[duplicate_bag | Opts]),
+ ?line filltabint3(T3,4000),
+ ?line del_one_by_one_dbag_1(T3,1,4001),
+ ?line filltabint3(T3,4000),
+ ?line del_one_by_one_dbag_1(T3,4000,0),
+ ?line filltabint(T3,4000),
+ ?line filltabint3(T3,4000),
+ ?line del_one_by_one_dbag_2(T3,1,4001),
+ ?line filltabint(T3,4000),
+ ?line filltabint3(T3,4000),
+ ?line del_one_by_one_dbag_2(T3,4000,0),
+
+ ?line filltabint2(T3,4000),
+ ?line filltabint(T3,4000),
+ ?line del_one_by_one_dbag_3(T3,4000,0),
+ ?line ets:delete(T3),
+ ok.
+
+make_init_fun(N) when N > 4000->
+ fun(read) ->
+ end_of_input;
+ (close) ->
+ exit(close_not_expected)
+ end;
+make_init_fun(N) ->
+ fun(read) ->
+ case N rem 2 of
+ 0 ->
+ {[{N, integer_to_list(N)}, {N, integer_to_list(N)}],
+ make_init_fun(N + 1)};
+ 1 ->
+ {[], make_init_fun(N + 1)}
+ end;
+ (close) ->
+ exit(close_not_expected)
+ end.
+
+t_init_table(doc) ->
+ ["Test ets:init_table/2"];
+t_init_table(suite) ->
+ [];
+t_init_table(Config) when is_list(Config)->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(t_init_table_do),
+ ?line verify_etsmem(EtsMem).
+
+t_init_table_do(Opts) ->
+ ?line T = ets:new(x,[duplicate_bag | Opts]),
+ ?line filltabint(T,4000),
+ ?line ets:init_table(T, make_init_fun(1)),
+ ?line del_one_by_one_dbag_1(T,4000,0),
+ ?line ets:delete(T),
+ ok.
+
+do_fill_dbag_using_lists(T,0) ->
+ T;
+do_fill_dbag_using_lists(T,N) ->
+ ets:insert(T,[{N,integer_to_list(N)},
+ {N + N rem 2,integer_to_list(N + N rem 2)}]),
+ do_fill_dbag_using_lists(T,N - 1).
+
+
+t_insert_new(doc) ->
+ ["Test the insert_new function"];
+t_insert_new(suite) ->
+ [];
+t_insert_new(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = fill_sets_int(1000) ++ fill_sets_int(1000,[{write_concurrency,true}]),
+ lists:foreach(fun(Tab) ->
+ ?line false = ets:insert_new(Tab,{2,"2"}),
+ ?line true = ets:insert_new(Tab,{2002,"2002"}),
+ ?line false = ets:insert_new(Tab,{2002,"2002"}),
+ ?line true = ets:insert(Tab,{2002,"2002"}),
+ ?line false = ets:insert_new(Tab,[{2002,"2002"}]),
+ ?line false = ets:insert_new(Tab,[{2002,"2002"},
+ {2003,"2003"}]),
+ ?line false = ets:insert_new(Tab,[{2001,"2001"},
+ {2002,"2002"},
+ {2003,"2003"}]),
+ ?line false = ets:insert_new(Tab,[{2001,"2001"},
+ {2002,"2002"}]),
+ ?line true = ets:insert_new(Tab,[{2001,"2001"},
+ {2003,"2003"}]),
+ ?line false = ets:insert_new(Tab,{2001,"2001"}),
+ ?line false = ets:insert_new(Tab,{2002,"2002"}),
+ ?line false = ets:insert_new(Tab,{2003,"2003"}),
+ ?line true = ets:insert_new(Tab,{2004,"2004"}),
+ ?line true = ets:insert_new(Tab,{2000,"2000"}),
+ ?line true = ets:insert_new(Tab,[{2005,"2005"},
+ {2006,"2006"},
+ {2007,"2007"}]),
+ ?line Num =
+ case ets:info(Tab,type) of
+ bag ->
+ ?line true =
+ ets:insert(Tab,{2004,"2004-2"}),
+ ?line false =
+ ets:insert_new(Tab,{2004,"2004-3"}),
+ 1009;
+ duplicate_bag ->
+ ?line true =
+ ets:insert(Tab,{2004,"2004"}),
+ ?line false =
+ ets:insert_new(Tab,{2004,"2004"}),
+ 1010;
+ _ ->
+ 1008
+ end,
+ ?line Num = ets:info(Tab,size),
+ ?line List = ets:tab2list(Tab),
+ ?line ets:delete_all_objects(Tab),
+ ?line true = ets:insert_new(Tab,List),
+ ?line false = ets:insert_new(Tab,List),
+ ?line ets:delete(Tab)
+ end,
+ L),
+ ?line verify_etsmem(EtsMem).
+
+t_insert_list(doc) ->
+ ["Test ets:insert/2 with list of objects."];
+t_insert_list(suite) ->
+ [];
+t_insert_list(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(t_insert_list_do),
+ ?line verify_etsmem(EtsMem).
+
+t_insert_list_do(Opts) ->
+ ?line T = ets:new(x,[duplicate_bag | Opts]),
+ ?line do_fill_dbag_using_lists(T,4000),
+ ?line del_one_by_one_dbag_2(T,4000,0),
+ ?line ets:delete(T).
+
+
+t_test_ms(doc) ->
+ ["Test interface of ets:test_ms/2"];
+t_test_ms(suite) ->
+ [];
+t_test_ms(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line {ok,[a,b]} = ets:test_ms({a,b},
+ [{{'$1','$2'},[{'<','$1','$2'}],['$$']}]),
+ ?line {ok,false} = ets:test_ms({a,b},
+ [{{'$1','$2'},[{'>','$1','$2'}],['$$']}]),
+ ?line {error,[{error,String}]} = ets:test_ms({a,b},
+ [{{'$1','$2'},
+ [{'flurp','$1','$2'}],
+ ['$$']}]),
+ ?line true = (if is_list(String) -> true; true -> false end),
+ ?line verify_etsmem(EtsMem).
+
+t_select_delete(doc) ->
+ ["Test the ets:select_delete/2 and ets:select_count/2 BIF's"];
+t_select_delete(suite) ->
+ [];
+t_select_delete(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Tables = fill_sets_int(10000) ++ fill_sets_int(10000,[{write_concurrency,true}]),
+ lists:foreach
+ (fun(Table) ->
+ ?line 4000 = ets:select_count(Table,[{{'$1', '_'},
+ [{'>',
+ {'rem',
+ '$1', 5},
+ 2}],
+ [true]}]),
+ ?line 4000 = ets:select_delete(Table,[{{'$1', '_'},
+ [{'>',
+ {'rem',
+ '$1', 5},
+ 2}],
+ [true]}]),
+ ?line check(Table,
+ fun({N,_}) when (N rem 5) =< 2 ->
+ true;
+ (_) ->
+ false
+ end,
+ 6000)
+
+ end,
+ Tables),
+ lists:foreach
+ (fun(Table) ->
+ ?line ets:select_delete(Table,[{'_',[],[true]}]),
+ ?line xfilltabint(Table,4000),
+ ?line successive_delete(Table,1,4001,bound),
+ ?line 0 = ets:info(Table,size),
+ ?line xfilltabint(Table,4000),
+ ?line successive_delete(Table,4000,0, bound),
+ ?line 0 = ets:info(Table,size),
+ ?line xfilltabint(Table,4000),
+ ?line successive_delete(Table,1,4001,unbound),
+ ?line 0 = ets:info(Table,size),
+ ?line xfilltabint(Table,4000),
+ ?line successive_delete(Table,4000,0, unbound),
+ ?line 0 = ets:info(Table,size)
+
+ end,
+ Tables),
+ lists:foreach
+ (fun(Table) ->
+ F = case ets:info(Table,type) of
+ X when X == bag; X == duplicate_bag ->
+ 2;
+ _ ->
+ 1
+ end,
+ ?line xfilltabstr(Table, 4000),
+ ?line 1000 = ets:select_count(Table,
+ [{{[$3 | '$1'], '_'},
+ [{'==',
+ {'length', '$1'},
+ 3}],[true]}]) div F,
+ ?line 1000 = ets:select_delete(Table,
+ [{{[$3 | '$1'], '_'},
+ [{'==',
+ {'length', '$1'},
+ 3}],[true]}]) div F,
+ ?line check(Table, fun({[3,_,_,_],_}) -> false;
+ (_) -> true
+ end, 3000*F),
+ ?line 8 = ets:select_count(Table,
+ [{{"7",'_'},[],[false]},
+ {{['_'], '_'},
+ [],[true]}]) div F,
+ ?line 8 = ets:select_delete(Table,
+ [{{"7",'_'},[],[false]},
+ {{['_'], '_'},
+ [],[true]}]) div F,
+ ?line check(Table, fun({"7",_}) -> true;
+ ({[_],_}) -> false;
+ (_) -> true
+ end, 2992*F),
+ ?line xfilltabstr(Table, 4000),
+ %% This happens to be interesting for other select types too
+ ?line 200 = length(ets:select(Table,
+ [{{[$3,'_','_'],'_'},
+ [],[true]},
+ {{[$1,'_','_'],'_'},
+ [],[true]}])) div F,
+ ?line 200 = ets:select_count(Table,
+ [{{[$3,'_','_'],'_'},
+ [],[true]},
+ {{[$1,'_','_'],'_'},
+ [],[true]}]) div F,
+ ?line 200 = length(element(1,ets:select(Table,
+ [{{[$3,'_','_'],'_'},
+ [],[true]},
+ {{[$1,'_','_'],'_'},
+ [],[true]}],
+ 1000))) div F,
+ ?line 200 = length(
+ ets:select_reverse(Table,
+ [{{[$3,'_','_'],'_'},
+ [],[true]},
+ {{[$1,'_','_'],'_'},
+ [],[true]}])) div F,
+ ?line 200 = length(
+ element(1,
+ ets:select_reverse
+ (Table,
+ [{{[$3,'_','_'],'_'},
+ [],[true]},
+ {{[$1,'_','_'],'_'},
+ [],[true]}],
+ 1000))) div F,
+ ?line 200 = ets:select_delete(Table,
+ [{{[$3,'_','_'],'_'},
+ [],[true]},
+ {{[$1,'_','_'],'_'},
+ [],[true]}]) div F,
+ ?line 0 = ets:select_count(Table,
+ [{{[$3,'_','_'],'_'},
+ [],[true]},
+ {{[$1,'_','_'],'_'},
+ [],[true]}]) div F,
+ ?line check(Table, fun({[$3,_,_],_}) -> false;
+ ({[$1,_,_],_}) -> false;
+ (_) -> true
+ end, 3800*F)
+ end,
+ Tables),
+ lists:foreach(fun(Tab) -> ets:delete(Tab) end,Tables),
+ ?line verify_etsmem(EtsMem).
+
+partly_bound(doc) ->
+ ["Test that partly bound keys gives faster matches"];
+partly_bound(suite) ->
+ [];
+partly_bound(Config) when is_list(Config) ->
+ case os:type() of
+ {win32,_} ->
+ {skip,"Inaccurate measurements on Windows"};
+ _ ->
+ ?line EtsMem = etsmem(),
+ ?line dont_make_worse(),
+ ?line make_better(),
+ ?line verify_etsmem(EtsMem)
+ end.
+
+dont_make_worse() ->
+ seventyfive_percent_success({?MODULE,dont_make_worse_sub,[]},0,0,10).
+
+dont_make_worse_sub() ->
+ ?line T = build_table([a,b],[a,b],15000),
+ ?line T1 = time_match_object(T,{'_',a,a,1500}, [{{a,a,1500},a,a,1500}]),
+ ?line T2 = time_match_object(T,{{a,a,'_'},a,a,1500},
+ [{{a,a,1500},a,a,1500}]),
+ ?line ets:delete(T),
+ ?line true = (T1 > T2),
+ ok.
+
+make_better() ->
+ fifty_percent_success({?MODULE,make_better_sub2,[]},0,0,10),
+ fifty_percent_success({?MODULE,make_better_sub1,[]},0,0,10).
+make_better_sub1() ->
+ ?line T = build_table2([a,b],[a,b],15000),
+ ?line T1 = time_match_object(T,{'_',1500,a,a}, [{{1500,a,a},1500,a,a}]),
+ ?line T2 = time_match_object(T,{{1500,a,'_'},1500,a,a},
+ [{{1500,a,a},1500,a,a}]),
+ ?line ets:delete(T),
+ ?line io:format("~p>~p~n",[(T1 / 100),T2]),
+ ?line true = ((T1 / 100) > T2), % More marginal than needed.
+ ok.
+
+make_better_sub2() ->
+ ?line T = build_table2([a,b],[a,b],15000),
+ ?line T1 = time_match(T,{'$1',1500,a,a}),
+ ?line T2 = time_match(T,{{1500,a,'$1'},1500,a,a}),
+ ?line ets:delete(T),
+ ?line io:format("~p>~p~n",[(T1 / 100),T2]),
+ ?line true = ((T1 / 100) > T2), % More marginal than needed.
+ ok.
+
+
+match_heavy(doc) ->
+ ["Heavy random matching, comparing set with ordered_set."];
+match_heavy(suite) ->
+ [];
+match_heavy(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ DataDir = ?config(data_dir, Config),
+ %% Easier to have in process dictionary when manually
+ %% running the test function.
+ put(where_to_read,DataDir),
+ put(where_to_write,PrivDir),
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ NewDog=test_server:timetrap(test_server:seconds(1000)),
+ NewConfig = [{watchdog, NewDog} | lists:keydelete(watchdog,1,Config)],
+ random_test(),
+ drop_match(),
+ NewConfig.
+
+%%% Extra safety for the very low probability that this is not
+%%% caught by the random test (Statistically impossible???)
+drop_match() ->
+ ?line EtsMem = etsmem(),
+ ?line T = build_table([a,b],[a],1500),
+ ?line [{{a,a,1},a,a,1},{{b,a,1},b,a,1}] =
+ ets:match_object(T, {'_','_','_',1}),
+ ?line true = ets:delete(T),
+ ?line verify_etsmem(EtsMem).
+
+
+
+ets_match(Tab,Expr) ->
+ case random:uniform(2) of
+ 1 ->
+ ets:match(Tab,Expr);
+ _ ->
+ match_chunked(Tab,Expr)
+ end.
+
+match_chunked(Tab,Expr) ->
+ match_chunked_collect(ets:match(Tab,Expr,
+ random:uniform(1999) + 1)).
+match_chunked_collect('$end_of_table') ->
+ [];
+match_chunked_collect({Results, Continuation}) ->
+ Results ++ match_chunked_collect(ets:match(Continuation)).
+
+ets_match_object(Tab,Expr) ->
+ case random:uniform(2) of
+ 1 ->
+ ets:match_object(Tab,Expr);
+ _ ->
+ match_object_chunked(Tab,Expr)
+ end.
+
+match_object_chunked(Tab,Expr) ->
+ match_object_chunked_collect(ets:match_object(Tab,Expr,
+ random:uniform(1999) + 1)).
+match_object_chunked_collect('$end_of_table') ->
+ [];
+match_object_chunked_collect({Results, Continuation}) ->
+ Results ++ match_object_chunked_collect(ets:match_object(Continuation)).
+
+
+
+random_test() ->
+ ?line ReadDir = get(where_to_read),
+ ?line WriteDir = get(where_to_write),
+ ?line (catch file:make_dir(WriteDir)),
+ ?line Seed = case file:consult(filename:join([ReadDir,
+ "preset_random_seed.txt"])) of
+ {ok,[X]} ->
+ X;
+ _ ->
+ {A,B,C} = erlang:now(),
+ random:seed(A,B,C),
+ get(random_seed)
+ end,
+ put(random_seed,Seed),
+ ?line {ok, F} = file:open(filename:join([WriteDir,
+ "last_random_seed.txt"]),
+ [write]),
+ io:format(F,"~p. ~n",[Seed]),
+ file:close(F),
+ io:format("Random seed ~p written to ~s, copy to ~s to rerun with "
+ "same seed.",[Seed,
+ filename:join([WriteDir, "last_random_seed.txt"]),
+ filename:join([ReadDir,
+ "preset_random_seed.txt"])]),
+ do_random_test().
+
+do_random_test() ->
+ ?line EtsMem = etsmem(),
+ ?line OrdSet = ets:new(xxx,[ordered_set]),
+ ?line Set = ets:new(xxx,[]),
+ ?line do_n_times(fun() ->
+ ?line Key = create_random_string(25),
+ ?line Value = create_random_tuple(25),
+ ?line ets:insert(OrdSet,{Key,Value}),
+ ?line ets:insert(Set,{Key,Value})
+ end, 5000),
+ ?line io:format("~nData inserted~n"),
+ ?line do_n_times(fun() ->
+ ?line I = random:uniform(25),
+ ?line Key = create_random_string(I) ++ '_',
+ ?line L1 = ets_match_object(OrdSet,{Key,'_'}),
+ ?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p~n",
+ [L1,L2]),
+ ?line exit({not_eq, L1, L2});
+ true ->
+ ok
+ end
+ end,
+ 2000),
+ ?line io:format("~nData matched~n"),
+ ?line ets:match_delete(OrdSet,'_'),
+ ?line ets:match_delete(Set,'_'),
+ ?line do_n_times(fun() ->
+ ?line Value = create_random_string(25),
+ ?line Key = create_random_tuple(25),
+ ?line ets:insert(OrdSet,{Key,Value}),
+ ?line ets:insert(Set,{Key,Value})
+ end, 2000),
+ ?line io:format("~nData inserted~n"),
+ (fun() ->
+ ?line Key = list_to_tuple(lists:duplicate(25,'_')),
+ ?line L1 = ets_match_object(OrdSet,{Key,'_'}),
+ ?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
+ ?line 2000 = length(L1),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p~n",
+ [L1,L2]),
+ ?line exit({not_eq, L1, L2});
+ true ->
+ ok
+ end
+ end)(),
+ (fun() ->
+ ?line Key = {'$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'},
+ ?line L1 = ets_match_object(OrdSet,{Key,'_'}),
+ ?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
+ ?line 2000 = length(L1),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p~n",
+ [L1,L2]),
+ ?line exit({not_eq, L1, L2});
+ true ->
+ ok
+ end
+ end)(),
+ (fun() ->
+ ?line Key = {'$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'},
+ ?line L1 = ets_match(OrdSet,{Key,'_'}),
+ ?line L2 = lists:sort(ets_match(Set,{Key,'_'})),
+ ?line 2000 = length(L1),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p~n",
+ [L1,L2]),
+ ?line exit({not_eq, L1, L2});
+ true ->
+ ok
+ end
+ end)(),
+ ?line ets:match_delete(OrdSet,'_'),
+ ?line ets:match_delete(Set,'_'),
+ ?line do_n_times(fun() ->
+ ?line Value = create_random_string(25),
+ ?line Key = create_random_tuple(25),
+ ?line ets:insert(OrdSet,{Key,Value}),
+ ?line ets:insert(Set,{Key,Value})
+ end, 2000),
+ ?line io:format("~nData inserted~n"),
+ do_n_times(fun() ->
+ ?line Key = create_partly_bound_tuple(25),
+ ?line L1 = ets_match_object(OrdSet,{Key,'_'}),
+ ?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p~n",
+ [L1,L2]),
+ ?line exit({not_eq, L1, L2});
+ true ->
+ ok
+ end
+ end,
+ 2000),
+ ?line do_n_times(fun() ->
+ ?line Key = create_partly_bound_tuple2(25),
+ ?line L1 = ets_match_object(OrdSet,{Key,'_'}),
+ ?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p~n",
+ [L1,L2]),
+ ?line exit({not_eq, L1, L2});
+ true ->
+ ok
+ end
+ end,
+ 2000),
+ do_n_times(fun() ->
+ ?line Key = create_partly_bound_tuple2(25),
+ ?line L1 = ets_match(OrdSet,{Key,'_'}),
+ ?line L2 = lists:sort(ets_match(Set,{Key,'_'})),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p~n",
+ [L1,L2]),
+ ?line exit({not_eq, L1, L2});
+ true ->
+ ok
+ end
+ end,
+ 2000),
+ io:format("~nData matched~n"),
+ ets:match_delete(OrdSet,'_'),
+ ets:match_delete(Set,'_'),
+ do_n_times(fun() ->
+ do_n_times(fun() ->
+ ?line Value =
+ create_random_string(25),
+ ?line Key = create_random_tuple(25),
+ ?line ets:insert(OrdSet,{Key,Value}),
+ ?line ets:insert(Set,{Key,Value})
+ end, 500),
+ io:format("~nData inserted~n"),
+ do_n_times(fun() ->
+ ?line Key =
+ create_partly_bound_tuple(25),
+ ets:match_delete(OrdSet,{Key,'_'}),
+ ets:match_delete(Set,{Key,'_'}),
+ L1 = ets:info(OrdSet,size),
+ L2 = ets:info(Set,size),
+ [] = ets_match_object(OrdSet,
+ {Key,'_'}),
+ case L1 == L2 of
+ false ->
+ io:format("~p != ~p "
+ "(deleted ~p)~n",
+ [L1,L2,Key]),
+ exit({not_eq, L1, L2,
+ {deleted,Key}});
+ true ->
+ ok
+ end
+ end,
+ 50),
+ io:format("~nData deleted~n")
+ end,
+ 10),
+ ets:delete(OrdSet),
+ ets:delete(Set),
+ ?line verify_etsmem(EtsMem).
+
+update_element(doc) ->
+ ["test various variants of update_element"];
+update_element(suite) ->
+ [];
+update_element(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(update_element_opts),
+ ?line verify_etsmem(EtsMem).
+
+update_element_opts(Opts) ->
+ TupleCases = [{{key,val}, 1 ,2},
+ {{val,key}, 2, 1},
+ {{key,val}, 1 ,[2]},
+ {{key,val,val}, 1, [2,3]},
+ {{val,key,val,val}, 2, [3,4,1]},
+ {{val,val,key,val}, 3, [1,4,1,2]}, % update pos1 twice
+ {{val,val,val,key}, 4, [2,1,2,3]}],% update pos2 twice
+
+ lists:foreach(fun({Tuple,KeyPos,UpdPos}) -> update_element_opts(Tuple,KeyPos,UpdPos,Opts) end,
+ TupleCases),
+
+ update_element_neg(Opts).
+
+
+
+update_element_opts(Tuple,KeyPos,UpdPos,Opts) ->
+ Set = ets:new(set,[{keypos,KeyPos} | Opts]),
+ OrdSet = ets:new(ordered_set,[ordered_set,{keypos,KeyPos} | Opts]),
+ update_element(Set,Tuple,KeyPos,UpdPos),
+ update_element(OrdSet,Tuple,KeyPos,UpdPos),
+ true = ets:delete(Set),
+ true = ets:delete(OrdSet),
+ ok.
+
+update_element(T,Tuple,KeyPos,UpdPos) ->
+ KeyList = [Key || Key <- lists:seq(1,100)],
+ lists:foreach(fun(Key) ->
+ TupleWithKey = setelement(KeyPos,Tuple,Key),
+ update_element_do(T,TupleWithKey,Key,UpdPos)
+ end,
+ KeyList).
+
+update_element_do(Tab,Tuple,Key,UpdPos) ->
+
+ % Strategy: Step around in Values array and call ets:update_element for the values.
+ % Take Length number of steps of size 1, then of size 2, ..., Length-1.
+ % This will try all combinations of {fromValue,toValue}
+ %
+ % IMPORTANT: size(Values) must be a prime number for this to work!!!
+ Big32 = 16#12345678,
+ Big64 = 16#123456789abcdef0,
+ Values = { 623, -27, 0, Big32, -Big32, Big64, -Big64, Big32*Big32,
+ -Big32*Big32, Big32*Big64, -Big32*Big64, Big64*Big64, -Big64*Big64,
+ "A", "Sverker", [], {12,-132}, {},
+ <<45,232,0,12,133>>, <<234,12,23>>, list_to_binary(lists:seq(1,100)),
+ (fun(X) -> X*Big32 end),
+ make_ref(), make_ref(), self(), ok, update_element, 28, 29 },
+ Length = size(Values),
+
+ PosValArgF = fun(ToIx, ResList, [Pos | PosTail], Rand, MeF) ->
+ NextIx = (ToIx+Rand) rem Length,
+ MeF(NextIx, [{Pos,element(ToIx+1,Values)} | ResList], PosTail, Rand, MeF);
+
+ (_ToIx, ResList, [], _Rand, _MeF) ->
+ ResList;
+
+ (ToIx, [], Pos, _Rand, _MeF) ->
+ {Pos, element(ToIx+1,Values)} % single {pos,value} arg
+ end,
+
+ NewTupleF = fun({Pos,Val}, Tpl, _MeF) ->
+ setelement(Pos, Tpl, Val);
+ ([{Pos,Val} | Tail], Tpl, MeF) ->
+ MeF(Tail,setelement(Pos, Tpl, Val),MeF);
+ ([], Tpl, _MeF) ->
+ Tpl
+ end,
+
+ UpdateF = fun(ToIx,Rand) ->
+ PosValArg = PosValArgF(ToIx,[],UpdPos,Rand,PosValArgF),
+ %%io:format("update_element(~p)~n",[PosValArg]),
+ ArgHash = erlang:phash2({Tab,Key,PosValArg}),
+ ?line true = ets:update_element(Tab, Key, PosValArg),
+ ?line ArgHash = erlang:phash2({Tab,Key,PosValArg}),
+ NewTuple = NewTupleF(PosValArg,Tuple,NewTupleF),
+ ?line [NewTuple] = ets:lookup(Tab,Key)
+ end,
+
+ LoopF = fun(_FromIx, Incr, _Times, Checksum, _MeF) when Incr >= Length ->
+ Checksum; % done
+
+ (FromIx, Incr, 0, Checksum, MeF) ->
+ MeF(FromIx, Incr+1, Length, Checksum, MeF);
+
+ (FromIx, Incr, Times, Checksum, MeF) ->
+ ToIx = (FromIx + Incr) rem Length,
+ UpdateF(ToIx,Checksum),
+ if
+ Incr =:= 0 -> UpdateF(ToIx,Checksum); % extra update to same value
+ true -> true
+ end,
+ MeF(ToIx, Incr, Times-1, Checksum+ToIx+1, MeF)
+ end,
+
+ FirstTuple = Tuple,
+ ?line true = ets:insert(Tab,FirstTuple),
+ ?line [FirstTuple] = ets:lookup(Tab,Key),
+
+ Checksum = LoopF(0, 1, Length, 0, LoopF),
+ ?line Checksum = (Length-1)*Length*(Length+1) div 2, % if Length is a prime
+ ok.
+
+update_element_neg(Opts) ->
+ Set = ets:new(set,Opts),
+ OrdSet = ets:new(ordered_set,[ordered_set | Opts]),
+ update_element_neg_do(Set),
+ update_element_neg_do(OrdSet),
+ ets:delete(Set),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_element(Set,key,{2,1})),
+ ets:delete(OrdSet),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_element(OrdSet,key,{2,1})),
+
+ ?line Bag = ets:new(bag,[bag | Opts]),
+ ?line DBag = ets:new(duplicate_bag,[duplicate_bag | Opts]),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_element(Bag,key,{2,1})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_element(DBag,key,{2,1})),
+ true = ets:delete(Bag),
+ true = ets:delete(DBag),
+ ok.
+
+
+update_element_neg_do(T) ->
+ Object = {key, 0, "Hej"},
+ ?line true = ets:insert(T,Object),
+
+ UpdateF = fun(Arg3) ->
+ ArgHash = erlang:phash2({T,key,Arg3}),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_element(T,key,Arg3)),
+ ?line ArgHash = erlang:phash2({T,key,Arg3}),
+ ?line [Object] = ets:lookup(T,key)
+ end,
+
+ %% List of invalid {Pos,Value} tuples
+ InvList = [false, {2}, {2,1,false}, {false,1}, {0,1}, {1,1}, {-1,1}, {4,1}],
+
+ lists:foreach(UpdateF, InvList),
+ lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl]) end, InvList),
+ lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{2,1}]) end, InvList),
+ lists:foreach(fun(InvTpl) -> UpdateF([{2,1},{3,"Hello"},InvTpl]) end, InvList),
+ lists:foreach(fun(InvTpl) -> UpdateF([{3,"Hello"},{2,1},InvTpl]) end, InvList),
+ lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl,{3,"Hello"}]) end, InvList),
+ lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{3,"Hello"},{2,1}]) end, InvList),
+ UpdateF([{2,1} | {3,1}]),
+ lists:foreach(fun(InvTpl) -> UpdateF([{2,1} | InvTpl]) end, InvList),
+
+ ?line true = ets:update_element(T,key,[]),
+ ?line false = ets:update_element(T,false,[]),
+ ?line false = ets:update_element(T,false,{2,1}),
+ ?line ets:delete(T,key),
+ ?line false = ets:update_element(T,key,{2,1}),
+ ok.
+
+
+update_counter(doc) ->
+ ["test various variants of update_counter"];
+update_counter(suite) ->
+ [];
+update_counter(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(update_counter_do),
+ ?line verify_etsmem(EtsMem).
+
+update_counter_do(Opts) ->
+ Set = ets:new(set,Opts),
+ OrdSet = ets:new(ordered_set,[ordered_set | Opts]),
+ update_counter_for(Set),
+ update_counter_for(OrdSet),
+ ets:delete(Set),
+ ets:delete(OrdSet),
+ update_counter_neg(Opts).
+
+update_counter_for(T) ->
+ ?line ets:insert(T,{a,1,1}),
+ ?line 101 = ets:update_counter(T,a,100),
+ ?line [{a,101,1}] = ets:lookup(T,a),
+ ?line 101 = ets:update_counter(T,a,{3,100}),
+ ?line [{a,101,101}] = ets:lookup(T,a),
+
+
+ LooperF = fun(Obj, 0, _, _) ->
+ Obj;
+
+ (Obj, Times, Arg3, Myself) ->
+ ?line {NewObj, Ret} = uc_mimic(Obj,Arg3),
+ ArgHash = erlang:phash2({T,a,Arg3}),
+ ?line Ret = ets:update_counter(T,a,Arg3),
+ ?line ArgHash = erlang:phash2({T,a,Arg3}),
+ %%io:format("NewObj=~p~n ",[NewObj]),
+ ?line [NewObj] = ets:lookup(T,a),
+ Myself(NewObj,Times-1,Arg3,Myself)
+ end,
+
+ LoopF = fun(Obj, Times, Arg3) ->
+ %%io:format("Loop start:\nObj = ~p\nArg3=~p\n",[Obj,Arg3]),
+ LooperF(Obj,Times,Arg3,LooperF)
+ end,
+
+ SmallMax32 = (1 bsl 27) - 1,
+ SmallMax64 = (1 bsl (27+32)) - 1,
+ Big1Max32 = (1 bsl 32) - 1,
+ Big1Max64 = (1 bsl 64) - 1,
+
+ Steps = 100,
+ Obj0 = {a,0,0,0,0},
+ ?line ets:insert(T,Obj0),
+ ?line Obj1 = LoopF(Obj0, Steps, {2,(SmallMax32 div Steps)*2}),
+ ?line Obj2 = LoopF(Obj1, Steps, {3,(SmallMax64 div Steps)*2}),
+ ?line Obj3 = LoopF(Obj2, Steps, {4,(Big1Max32 div Steps)*2}),
+ ?line Obj4 = LoopF(Obj3, Steps, {5,(Big1Max64 div Steps)*2}),
+
+ ?line Obj5 = LoopF(Obj4, Steps, {2,-(SmallMax32 div Steps)*4}),
+ ?line Obj6 = LoopF(Obj5, Steps, {3,-(SmallMax64 div Steps)*4}),
+ ?line Obj7 = LoopF(Obj6, Steps, {4,-(Big1Max32 div Steps)*4}),
+ ?line Obj8 = LoopF(Obj7, Steps, {5,-(Big1Max64 div Steps)*4}),
+
+ ?line Obj9 = LoopF(Obj8, Steps, {2,(SmallMax32 div Steps)*2}),
+ ?line ObjA = LoopF(Obj9, Steps, {3,(SmallMax64 div Steps)*2}),
+ ?line ObjB = LoopF(ObjA, Steps, {4,(Big1Max32 div Steps)*2}),
+ ?line Obj0 = LoopF(ObjB, Steps, {5,(Big1Max64 div Steps)*2}),
+
+ %% back at zero, same trip again with lists
+
+ ?line Obj4 = LoopF(Obj0,Steps,[{2, (SmallMax32 div Steps)*2},
+ {3, (SmallMax64 div Steps)*2},
+ {4, (Big1Max32 div Steps)*2},
+ {5, (Big1Max64 div Steps)*2}]),
+
+ ?line Obj8 = LoopF(Obj4,Steps,[{4, -(Big1Max32 div Steps)*4},
+ {2, -(SmallMax32 div Steps)*4},
+ {5, -(Big1Max64 div Steps)*4},
+ {3, -(SmallMax64 div Steps)*4}]),
+
+ ?line Obj0 = LoopF(Obj8,Steps,[{5, (Big1Max64 div Steps)*2},
+ {2, (SmallMax32 div Steps)*2},
+ {4, (Big1Max32 div Steps)*2},
+ {3, (SmallMax64 div Steps)*2}]),
+
+ %% make them shift size at the same time
+ ?line ObjC = LoopF(Obj0,Steps,[{5, (Big1Max64 div Steps)*2},
+ {3, (Big1Max64 div Steps)*2 + 1},
+ {2, -(Big1Max64 div Steps)*2},
+ {4, -(Big1Max64 div Steps)*2 + 1}]),
+
+ %% update twice in same list
+ ?line ObjD = LoopF(ObjC,Steps,[{5, -(Big1Max64 div Steps) + 1},
+ {3, -(Big1Max64 div Steps)*2 - 1},
+ {5, -(Big1Max64 div Steps) - 1},
+ {4, (Big1Max64 div Steps)*2 - 1}]),
+
+ ?line Obj0 = LoopF(ObjD,Steps,[{2, (Big1Max64 div Steps) - 1},
+ {4, Big1Max64*2},
+ {2, (Big1Max64 div Steps) + 1},
+ {4, -Big1Max64*2}]),
+
+ %% warping with list
+ ?line ObjE = LoopF(Obj0,1000,
+ [{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2},
+ {5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2},
+ {4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2},
+ {2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]),
+
+ %% warping without list
+ ?line ObjF = LoopF(ObjE,1000,{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2}),
+ ?line ObjG = LoopF(ObjF,1000,{5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2}),
+ ?line ObjH = LoopF(ObjG,1000,{4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2}),
+ ?line ObjI = LoopF(ObjH,1000,{2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}),
+
+ %% mixing it up
+ ?line LoopF(ObjI,1000,
+ [{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2},
+ {5,-SmallMax64*4 div 3},
+ {3,-SmallMax32*4 div 11},
+ {5,0},
+ {4,1},
+ {5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2},
+ {2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]),
+ ok.
+
+%% uc_mimic works kind of like the real ets:update_counter
+%% Obj = Tuple in ets
+%% Pits = {Pos,Incr} | {Pos,Incr,Thres,Warp}
+%% Returns {Updated tuple in ets, Return value from update_counter}
+uc_mimic(Obj, Pits) when is_tuple(Pits) ->
+ ?line Pos = element(1,Pits),
+ ?line NewObj = setelement(Pos, Obj, uc_adder(element(Pos,Obj),Pits)),
+ ?line {NewObj, element(Pos,NewObj)};
+
+uc_mimic(Obj, PitsList) when is_list(PitsList) ->
+ ?line {NewObj,ValList} = uc_mimic(Obj,PitsList,[]),
+ ?line {NewObj,lists:reverse(ValList)}.
+
+uc_mimic(Obj, [], Acc) ->
+ ?line {Obj,Acc};
+uc_mimic(Obj, [Pits|Tail], Acc) ->
+ ?line {NewObj,NewVal} = uc_mimic(Obj,Pits),
+ ?line uc_mimic(NewObj,Tail,[NewVal|Acc]).
+
+uc_adder(Init, {_Pos, Add}) ->
+ Init + Add;
+uc_adder(Init, {_Pos, Add, Thres, Warp}) ->
+ case Init + Add of
+ X when X > Thres, Add > 0 ->
+ Warp;
+ Y when Y < Thres, Add < 0 ->
+ Warp;
+ Z ->
+ Z
+ end.
+
+update_counter_neg(Opts) ->
+ Set = ets:new(set,Opts),
+ OrdSet = ets:new(ordered_set,[ordered_set | Opts]),
+ update_counter_neg_for(Set),
+ update_counter_neg_for(OrdSet),
+ ets:delete(Set),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_counter(Set,key,1)),
+ ets:delete(OrdSet),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_counter(OrdSet,key,1)),
+
+ ?line Bag = ets:new(bag,[bag | Opts]),
+ ?line DBag = ets:new(duplicate_bag,[duplicate_bag | Opts]),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_counter(Bag,key,1)),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_counter(DBag,key,1)),
+ true = ets:delete(Bag),
+ true = ets:delete(DBag),
+ ok.
+
+update_counter_neg_for(T) ->
+ Object = {key,0,false,1},
+ ?line true = ets:insert(T,Object),
+
+ UpdateF = fun(Arg3) ->
+ ArgHash = erlang:phash2({T,key,Arg3}),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,Arg3)),
+ ?line ArgHash = erlang:phash2({T,key,Arg3}),
+ ?line [Object] = ets:lookup(T,key)
+ end,
+
+ %% List of invalid arg3-tuples
+ InvList = [false, {2}, {2,false}, {false,1},
+ {0,1}, {-1,1}, % BUG < R12B-2
+ {1,1}, {3,1}, {5,1}, {2,1,100}, {2,1,100,0,false}, {2,1,false,0}, {2,1,0,false}],
+
+ lists:foreach(UpdateF, InvList),
+ lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv]) end, InvList),
+ lists:foreach(fun(Inv) -> UpdateF([Inv,{2,1}]) end, InvList),
+ lists:foreach(fun(Inv) -> UpdateF([{2,1},{4,-100},Inv]) end, InvList),
+ lists:foreach(fun(Inv) -> UpdateF([{4,100,50,0},{2,1},Inv]) end, InvList),
+ lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv,{4,100,50,0}]) end, InvList),
+ lists:foreach(fun(Inv) -> UpdateF([Inv,{4,100,50,0},{2,1}]) end, InvList),
+ UpdateF([{2,1} | {4,1}]),
+ lists:foreach(fun(Inv) -> UpdateF([{2,1} | Inv]) end, InvList),
+
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_counter(T,false,1)),
+ ?line ets:delete(T,key),
+ ?line {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,1)),
+ ok.
+
+
+evil_update_counter(Config) when is_list(Config) ->
+ %% The code server uses ets table. Pre-load modules that might not be
+ %% already loaded.
+ gb_sets:module_info(),
+ math:module_info(),
+ ordsets:module_info(),
+ random:module_info(),
+
+ repeat_for_opts(evil_update_counter_do).
+
+evil_update_counter_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line process_flag(trap_exit, true),
+ ?line Pids = [spawn_link(fun() -> evil_counter(I,Opts) end) || I <- lists:seq(1, 40)],
+ ?line wait_for_all(gb_sets:from_list(Pids)),
+ ?line verify_etsmem(EtsMem),
+ ok.
+
+wait_for_all(Pids0) ->
+ case gb_sets:is_empty(Pids0) of
+ true ->
+ ok;
+ false ->
+ receive
+ {'EXIT',Pid,normal} ->
+ ?line Pids = gb_sets:delete(Pid, Pids0),
+ wait_for_all(Pids);
+ Other ->
+ io:format("unexpected: ~p\n", [Other]),
+ ?line ?t:fail()
+ end
+ end.
+
+evil_counter(I,Opts) ->
+ T = ets:new(a, Opts),
+ Start0 = case I rem 3 of
+ 0 -> 16#12345678;
+ 1 -> 16#12345678FFFFFFFF;
+ 2 -> 16#7777777777FFFFFFFF863648726743
+ end,
+ Start = Start0 + random:uniform(100000),
+ ets:insert(T, {dracula,Start}),
+ Iter = 90000,
+ End = Start + Iter,
+ End = evil_counter_1(Iter, T),
+ ets:delete(T).
+
+evil_counter_1(0, T) ->
+ [{dracula,Count}] = ets:lookup(T, dracula),
+ Count;
+evil_counter_1(Iter, T) ->
+ ets:update_counter(T, dracula, 1),
+ evil_counter_1(Iter-1, T).
+
+fixtable_next(doc) ->
+ ["Check that a first-next sequence always works on a fixed table"];
+fixtable_next(suite) ->
+ [];
+fixtable_next(Config) when is_list(Config) ->
+ repeat_for_opts(fixtable_next_do, [write_concurrency,all_types]).
+
+fixtable_next_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line do_fixtable_next(ets:new(set,[public | Opts])),
+ ?line verify_etsmem(EtsMem).
+
+do_fixtable_next(Tab) ->
+ ?line F = fun(X,T,FF) -> case X of
+ 0 -> true;
+ _ ->
+ ets:insert(T, {X,
+ integer_to_list(X),
+ X rem 10}),
+ FF(X-1,T,FF)
+ end
+ end,
+ ?line F(100,Tab,F),
+ ?line ets:safe_fixtable(Tab,true),
+ ?line First = ets:first(Tab),
+ ?line ets:delete(Tab, First),
+ ?line ets:next(Tab, First),
+ ?line ets:match_delete(Tab,{'_','_','_'}),
+ ?line '$end_of_table' = ets:next(Tab, First),
+ ?line true = ets:info(Tab, fixed),
+ ?line ets:safe_fixtable(Tab, false),
+ ?line false = ets:info(Tab, fixed),
+ ?line ets:delete(Tab).
+
+fixtable_insert(doc) ->
+ ["Check inserts of deleted keys in fixed bags"];
+fixtable_insert(suite) ->
+ [];
+fixtable_insert(Config) when is_list(Config) ->
+ Combos = [[Type,{write_concurrency,WC}] || Type<- [bag,duplicate_bag],
+ WC <- [false,true]],
+ lists:foreach(fun(Opts) -> fixtable_insert_do(Opts) end,
+ Combos),
+ ok.
+
+fixtable_insert_do(Opts) ->
+ io:format("Opts = ~p\n",[Opts]),
+ Ets = make_table(ets, Opts, [{a,1}, {a,2}, {b,1}, {b,2}]),
+ ets:safe_fixtable(Ets,true),
+ ets:match_delete(Ets,{b,1}),
+ First = ets:first(Ets),
+ ?line Next = case First of
+ a -> b;
+ b -> a
+ end,
+ ?line Next = ets:next(Ets,First),
+ ets:delete(Ets,Next),
+ ?line '$end_of_table' = ets:next(Ets,First),
+ ets:insert(Ets, {Next,1}),
+ ?line false = ets:insert_new(Ets, {Next,1}),
+ ?line Next = ets:next(Ets,First),
+ ?line '$end_of_table' = ets:next(Ets,Next),
+ ets:delete(Ets,Next),
+ '$end_of_table' = ets:next(Ets,First),
+ ets:insert(Ets, {Next,2}),
+ ?line false = ets:insert_new(Ets, {Next,1}),
+ Next = ets:next(Ets,First),
+ '$end_of_table' = ets:next(Ets,Next),
+ ets:delete(Ets,First),
+ ?line Next = ets:first(Ets),
+ ?line '$end_of_table' = ets:next(Ets,Next),
+ ets:delete(Ets,Next),
+ ?line '$end_of_table' = ets:next(Ets,First),
+ ?line true = ets:insert_new(Ets,{Next,1}),
+ ?line false = ets:insert_new(Ets,{Next,2}),
+ ?line Next = ets:next(Ets,First),
+ ets:delete_object(Ets,{Next,1}),
+ ?line '$end_of_table' = ets:next(Ets,First),
+ ?line true = ets:insert_new(Ets,{Next,2}),
+ ?line false = ets:insert_new(Ets,{Next,1}),
+ ?line Next = ets:next(Ets,First),
+ ets:delete(Ets,First),
+ ets:safe_fixtable(Ets,false),
+ {'EXIT',{badarg,_}} = (catch ets:next(Ets,First)),
+ ok.
+
+write_concurrency(doc) -> ["The 'write_concurrency' option"];
+write_concurrency(suite) -> [];
+write_concurrency(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ Yes1 = ets:new(foo,[public,{write_concurrency,true}]),
+ Yes2 = ets:new(foo,[protected,{write_concurrency,true}]),
+ No1 = ets:new(foo,[private,{write_concurrency,true}]),
+
+ Yes3 = ets:new(foo,[bag,public,{write_concurrency,true}]),
+ Yes4 = ets:new(foo,[bag,protected,{write_concurrency,true}]),
+ No2 = ets:new(foo,[bag,private,{write_concurrency,true}]),
+
+ Yes5 = ets:new(foo,[duplicate_bag,public,{write_concurrency,true}]),
+ Yes6 = ets:new(foo,[duplicate_bag,protected,{write_concurrency,true}]),
+ No3 = ets:new(foo,[duplicate_bag,private,{write_concurrency,true}]),
+
+ No4 = ets:new(foo,[ordered_set,public,{write_concurrency,true}]),
+ No5 = ets:new(foo,[ordered_set,protected,{write_concurrency,true}]),
+ No6 = ets:new(foo,[ordered_set,private,{write_concurrency,true}]),
+
+ No7 = ets:new(foo,[public,{write_concurrency,false}]),
+ No8 = ets:new(foo,[protected,{write_concurrency,false}]),
+
+ ?line YesMem = ets:info(Yes1,memory),
+ ?line NoHashMem = ets:info(No1,memory),
+ ?line NoTreeMem = ets:info(No4,memory),
+ io:format("YesMem=~p NoHashMem=~p NoTreeMem=~p\n",[YesMem,NoHashMem,NoTreeMem]),
+
+ ?line YesMem = ets:info(Yes2,memory),
+ ?line YesMem = ets:info(Yes3,memory),
+ ?line YesMem = ets:info(Yes4,memory),
+ ?line YesMem = ets:info(Yes5,memory),
+ ?line YesMem = ets:info(Yes6,memory),
+ ?line NoHashMem = ets:info(No2,memory),
+ ?line NoHashMem = ets:info(No3,memory),
+ ?line NoTreeMem = ets:info(No5,memory),
+ ?line NoTreeMem = ets:info(No6,memory),
+ ?line NoHashMem = ets:info(No7,memory),
+ ?line NoHashMem = ets:info(No8,memory),
+
+ case erlang:system_info(smp_support) of
+ true ->
+ ?line true = YesMem > NoHashMem,
+ ?line true = YesMem > NoTreeMem;
+ false ->
+ ?line true = YesMem =:= NoHashMem
+ end,
+
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,{write_concurrency,foo}])),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,{write_concurrency}])),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,{write_concurrency,true,foo}])),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,write_concurrency])),
+
+ lists:foreach(fun(T) -> ets:delete(T) end,
+ [Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,
+ No1,No2,No3,No4,No5,No6,No7,No8]),
+ ?line verify_etsmem(EtsMem),
+ ok.
+
+
+heir(doc) -> ["The 'heir' option"];
+heir(suite) -> [];
+heir(Config) when is_list(Config) ->
+ repeat_for_opts(heir_do).
+
+heir_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ Master = self(),
+
+ %% Different types of heir data and link/monitor relations
+ TestFun = fun(Arg) -> {EtsMem,Arg} end,
+ Combos = [{Data,Mode} || Data<-[foo_data, <<"binary">>,
+ lists:seq(1,10), {17,TestFun,self()},
+ "The busy heir"],
+ Mode<-[none,link,monitor]],
+ ?line lists:foreach(fun({Data,Mode})-> heir_1(Data,Mode,Opts) end,
+ Combos),
+
+ %% No heir
+ {Founder1,MrefF1} = spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end),
+ Founder1 ! {go, none},
+ ?line {"No heir",Founder1} = receive_any(),
+ ?line {'DOWN', MrefF1, process, Founder1, normal} = receive_any(),
+ ?line undefined = ets:info(foo),
+
+ %% An already dead heir
+ {Heir2,MrefH2} = spawn_monitor(fun()->die end),
+ ?line {'DOWN', MrefH2, process, Heir2, normal} = receive_any(),
+ {Founder2,MrefF2} = spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end),
+ Founder2 ! {go, Heir2},
+ ?line {"No heir",Founder2} = receive_any(),
+ ?line {'DOWN', MrefF2, process, Founder2, normal} = receive_any(),
+ ?line undefined = ets:info(foo),
+
+ %% When heir dies before founder
+ {Founder3,MrefF3} = spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end),
+ {Heir3,MrefH3} = spawn_monitor(fun()->heir_heir(Founder3)end),
+ Founder3 ! {go, Heir3},
+ ?line {'DOWN', MrefH3, process, Heir3, normal} = receive_any(),
+ Founder3 ! die_please,
+ ?line {'DOWN', MrefF3, process, Founder3, normal} = receive_any(),
+ ?line undefined = ets:info(foo),
+
+ %% When heir dies and pid reused before founder dies
+ erts_debug:set_internal_state(available_internal_state,true),
+ NextPidIx = erts_debug:get_internal_state(next_pid),
+ {Founder4,MrefF4} = spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end),
+ {Heir4,MrefH4} = spawn_monitor(fun()->heir_heir(Founder4)end),
+ Founder4 ! {go, Heir4},
+ ?line {'DOWN', MrefH4, process, Heir4, normal} = receive_any(),
+ erts_debug:set_internal_state(next_pid, NextPidIx),
+ erts_debug:set_internal_state(available_internal_state,false),
+ {Heir4,MrefH4_B} = spawn_monitor_with_pid(Heir4,
+ fun()-> ?line die_please = receive_any() end),
+ Founder4 ! die_please,
+ ?line {'DOWN', MrefF4, process, Founder4, normal} = receive_any(),
+ Heir4 ! die_please,
+ ?line {'DOWN', MrefH4_B, process, Heir4, normal} = receive_any(),
+ ?line undefined = ets:info(foo),
+
+ ?line verify_etsmem(EtsMem).
+
+heir_founder(Master, HeirData, Opts) ->
+ ?line {go,Heir} = receive_any(),
+ HeirTpl = case Heir of
+ none -> {heir,none};
+ _ -> {heir, Heir, HeirData}
+ end,
+ ?line T = ets:new(foo,[named_table, private, HeirTpl | Opts]),
+ ?line true = ets:insert(T,{key,1}),
+ ?line [{key,1}] = ets:lookup(T,key),
+ Self = self(),
+ ?line Self = ets:info(T,owner),
+ ?line case ets:info(T,heir) of
+ none ->
+ ?line true = (Heir =:= none) orelse (not is_process_alive(Heir)),
+ Master ! {"No heir",self()};
+
+ Heir ->
+ ?line true = is_process_alive(Heir),
+ Heir ! {table,T,HeirData},
+ die_please = receive_any()
+ end.
+
+
+heir_heir(Founder) ->
+ heir_heir(Founder, none).
+heir_heir(Founder, Mode) ->
+ ?line {table,T,HeirData} = receive_any(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
+ ?line case HeirData of
+ "The dying heir" -> exit(normal);
+ _ -> ok
+ end,
+
+ ?line Mref = case Mode of
+ link -> process_flag(trap_exit, true),
+ link(Founder);
+ monitor -> erlang:monitor(process,Founder);
+ none -> ok
+ end,
+ ?line Founder ! die_please,
+ ?line Msg = case HeirData of
+ "The busy heir" -> receive_any_spinning();
+ _ -> receive_any()
+ end,
+ ?line {'ETS-TRANSFER', T, Founder, HeirData} = Msg,
+ ?line foo = T,
+ ?line Self = self(),
+ ?line Self = ets:info(T,owner),
+ ?line Self = ets:info(T,heir),
+ ?line [{key,1}] = ets:lookup(T,key),
+ ?line true = ets:insert(T,{key,2}),
+ ?line [{key,2}] = ets:lookup(T,key),
+ ?line case Mode of % Verify that EXIT or DOWN comes after ETS-TRANSFER
+ link ->
+ {'EXIT',Founder,normal} = receive_any(),
+ process_flag(trap_exit, false);
+ monitor ->
+ {'DOWN', Mref, process, Founder, normal} = receive_any();
+ none -> ok
+ end.
+
+
+heir_1(HeirData,Mode,Opts) ->
+ io:format("test with heir_data = ~p\n", [HeirData]),
+ Master = self(),
+ ?line Founder = spawn_link(fun() -> heir_founder(Master,HeirData,Opts) end),
+ io:format("founder spawned = ~p\n", [Founder]),
+ ?line {Heir,Mref} = spawn_monitor(fun() -> heir_heir(Founder,Mode) end),
+ io:format("heir spawned = ~p\n", [{Heir,Mref}]),
+ ?line Founder ! {go, Heir},
+ ?line {'DOWN', Mref, process, Heir, normal} = receive_any().
+
+give_away(doc) -> ["ets:give_way/3"];
+give_away(suite) -> [];
+give_away(Config) when is_list(Config) ->
+ repeat_for_opts(give_away_do).
+
+give_away_do(Opts) ->
+ ?line T = ets:new(foo,[named_table, private | Opts]),
+ ?line true = ets:insert(T,{key,1}),
+ ?line [{key,1}] = ets:lookup(T,key),
+ Parent = self(),
+
+ %% Give and then give back
+ ?line {Receiver,Mref} = spawn_monitor(fun()-> give_away_receiver(T,Parent) end),
+ ?line give_me = receive_any(),
+ ?line true = ets:give_away(T,Receiver,here_you_are),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
+ ?line Receiver ! give_back,
+ ?line {'ETS-TRANSFER',T,Receiver,"Tillbakakaka"} = receive_any(),
+ ?line [{key,2}] = ets:lookup(T,key),
+ ?line {'DOWN', Mref, process, Receiver, normal} = receive_any(),
+
+ %% Give and then let receiver keep it
+ ?line true = ets:insert(T,{key,1}),
+ ?line {Receiver3,Mref3} = spawn_monitor(fun()-> give_away_receiver(T,Parent) end),
+ ?line give_me = receive_any(),
+ ?line true = ets:give_away(T,Receiver3,here_you_are),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
+ ?line Receiver3 ! die_please,
+ ?line {'DOWN', Mref3, process, Receiver3, normal} = receive_any(),
+ ?line undefined = ets:info(T),
+
+ %% Give and then kill receiver to get back
+ ?line T2 = ets:new(foo,[private | Opts]),
+ ?line true = ets:insert(T2,{key,1}),
+ ?line ets:setopts(T2,{heir,self(),"Som en gummiboll..."}),
+ ?line {Receiver2,Mref2} = spawn_monitor(fun()-> give_away_receiver(T2,Parent) end),
+ ?line give_me = receive_any(),
+ ?line true = ets:give_away(T2,Receiver2,here_you_are),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(T2,key)),
+ ?line Receiver2 ! die_please,
+ ?line {'ETS-TRANSFER',T2,Receiver2,"Som en gummiboll..."} = receive_any(),
+ ?line [{key,2}] = ets:lookup(T2,key),
+ ?line {'DOWN', Mref2, process, Receiver2, normal} = receive_any(),
+
+ %% Some negative testing
+ ?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,Receiver,"To a dead one")),
+ ?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,self(),"To myself")),
+ ?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,"not a pid","To wrong type")),
+
+ ?line true = ets:delete(T2),
+ ?line {ReceiverNeg,MrefNeg} = spawn_monitor(fun()-> give_away_receiver(T2,Parent) end),
+ ?line give_me = receive_any(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,ReceiverNeg,"A deleted table")),
+
+ ?line T3 = ets:new(foo,[public | Opts]),
+ spawn_link(fun()-> {'EXIT',{badarg,_}} = (catch ets:give_away(T3,ReceiverNeg,"From non owner")),
+ Parent ! done
+ end),
+ ?line done = receive_any(),
+ ?line ReceiverNeg ! no_soup_for_you,
+ ?line {'DOWN', MrefNeg, process, ReceiverNeg, normal} = receive_any(),
+ ok.
+
+give_away_receiver(T, Giver) ->
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
+ ?line Giver ! give_me,
+ ?line case receive_any() of
+ {'ETS-TRANSFER',T,Giver,here_you_are} ->
+ ?line [{key,1}] = ets:lookup(T,key),
+ ?line true = ets:insert(T,{key,2}),
+ ?line case receive_any() of
+ give_back ->
+ ?line true = ets:give_away(T,Giver,"Tillbakakaka"),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key));
+ die_please ->
+ ok
+ end;
+ no_soup_for_you ->
+ ok
+ end.
+
+
+setopts(doc) -> ["ets:setopts/2"];
+setopts(suite) -> [];
+setopts(Config) when is_list(Config) ->
+ repeat_for_opts(setopts_do,[write_concurrency,all_types]).
+
+setopts_do(Opts) ->
+ Self = self(),
+ ?line T = ets:new(foo,[named_table, private | Opts]),
+ ?line none = ets:info(T,heir),
+ Heir = spawn_link(fun()->heir_heir(Self) end),
+ ?line ets:setopts(T,{heir,Heir,"Data"}),
+ ?line Heir = ets:info(T,heir),
+ ?line ets:setopts(T,{heir,self(),"Data"}),
+ ?line Self = ets:info(T,heir),
+ ?line ets:setopts(T,[{heir,Heir,"Data"}]),
+ ?line Heir = ets:info(T,heir),
+ ?line ets:setopts(T,[{heir,none}]),
+ ?line none = ets:info(T,heir),
+
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,[{heir,self(),"Data"},false])),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,self()})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,heir)),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false,"Data"})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{false,self(),"Data"})),
+
+ ?line ets:setopts(T,{protection,protected}),
+ ?line ets:setopts(T,{protection,public}),
+ ?line ets:setopts(T,{protection,private}),
+ ?line ets:setopts(T,[{protection,protected}]),
+ ?line ets:setopts(T,[{protection,public}]),
+ ?line ets:setopts(T,[{protection,private}]),
+
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,false})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,private,false})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,protection)),
+ ?line ets:delete(T),
+ ok.
+
+bad_table(doc) -> ["All kinds of operations with bad table argument"];
+bad_table(suite) -> [];
+bad_table(Config) when is_list(Config) ->
+
+ %% Open and close disk_log to stabilize etsmem.
+ Name = make_ref(),
+ ?line File = filename:join([?config(priv_dir, Config),"bad_table.dummy"]),
+ ?line {ok, Name} = disk_log:open([{name, Name}, {file, File}]),
+ ?line disk_log:close(Name),
+ file:delete(File),
+
+ ?line EtsMem = etsmem(),
+
+ repeat_for_opts(fun(Opts) -> bad_table_do(Opts,File) end,
+ [write_concurrency, all_types]),
+ ?line verify_etsmem(EtsMem),
+ ok.
+
+bad_table_do(Opts, DummyFile) ->
+ Parent = self(),
+ {Pid,Mref} = spawn_opt(fun()-> ets:new(priv,[private,named_table | Opts]),
+ Priv = ets:new(priv,[private | Opts]),
+ ets:new(prot,[protected,named_table | Opts]),
+ Prot = ets:new(prot,[protected | Opts]),
+ Parent ! {self(),Priv,Prot},
+ die_please = receive_any()
+ end,
+ [link, monitor]),
+ {Pid,Priv,Prot} = receive_any(),
+ MatchSpec = {{key,'_'}, [], ['$$']},
+ Fun = fun(X,_) -> X end,
+ OpList = [{delete,[key],update},
+ {delete_all_objects,[],update},
+ {delete_object,[{key,data}],update},
+ {first,[],read},
+ {foldl,[Fun, 0], read, tabarg_last},
+ {foldr,[Fun, 0], read, tabarg_last},
+ %%{from_dets,[DetsTab], update},
+ {give_away,[Pid, data], update},
+ %%{info, [], read},
+ %%{info, [safe_fixed], read},
+ %%{init_table,[Name, InitFun],update},
+ {insert, [{key,data}], update},
+ {insert_new, [{key,data}], update},
+ {insert_new, [[{key,data},{other,data}]], update},
+ {last, [], read},
+ {lookup, [key], read},
+ {lookup_element, [key, 2], read},
+ {match, [{}], read},
+ {match, [{},17], read},
+ {match_delete, [{}], update},
+ {match_object, [{}], read},
+ {match_object, [{},17], read},
+ {member,[key], read},
+ {next, [key], read},
+ {prev, [key], read},
+ {rename, [new_name], update},
+ {safe_fixtable, [true], read},
+ {select,[MatchSpec], read},
+ {select,[MatchSpec,17], read},
+ {select_count,[MatchSpec], read},
+ {select_delete,[MatchSpec], update},
+ {setopts, [{heir,none}], update},
+ {slot, [0], read},
+ {tab2file, [DummyFile], read, {return,{error,badtab}}},
+ {tab2file, [DummyFile,[]], read, {return,{error,badtab}}},
+ {tab2list, [], read},
+ %%{table,[], read},
+ %%{to_dets, [DetsTab], read},
+ {update_counter,[key,1], update},
+ {update_element,[key,{2,new_data}], update}
+ ],
+ Info = {Opts, Priv, Prot},
+ lists:foreach(fun(Op) -> bad_table_op(Info, Op) end,
+ OpList),
+ Pid ! die_please,
+ {'DOWN', Mref, process, Pid, normal} = receive_any(),
+ ok.
+
+bad_table_op({Opts,Priv,Prot}, Op) ->
+ %%io:format("Doing Op=~p on ~p's\n",[Op,Type]),
+ T1 = ets:new(noname,Opts),
+ bad_table_call(noname,Op),
+ ets:delete(T1),
+ bad_table_call(T1,Op),
+ T2 = ets:new(named,[named_table | Opts]),
+ ets:delete(T2),
+ bad_table_call(named,Op),
+ bad_table_call(T2,Op),
+ bad_table_call(priv,Op),
+ bad_table_call(Priv,Op),
+ case element(3,Op) of
+ update ->
+ bad_table_call(prot,Op),
+ bad_table_call(Prot,Op);
+ read -> ok
+ end.
+
+bad_table_call(T,{F,Args,_}) ->
+ ?line {'EXIT',{badarg,_}} = (catch apply(ets, F, [T|Args]));
+bad_table_call(T,{F,Args,_,tabarg_last}) ->
+ ?line {'EXIT',{badarg,_}} = (catch apply(ets, F, Args++[T]));
+bad_table_call(T,{F,Args,_,{return,Return}}) ->
+ try
+ ?line Return = apply(ets, F, [T|Args])
+ catch
+ error:badarg -> ok
+ end.
+
+
+rename(doc) ->
+ ["Check rename of ets tables"];
+rename(suite) ->
+ [];
+rename(Config) when is_list(Config) ->
+ repeat_for_opts(rename_do, [write_concurrency, all_types]).
+
+rename_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ets:new(foobazz,[named_table, public | Opts]),
+ ets:insert(foobazz,{foo,bazz}),
+ ungermanbazz = ets:rename(foobazz,ungermanbazz),
+ {'EXIT',{badarg, _}} = (catch ets:lookup(foobazz,foo)),
+ [{foo,bazz}] = ets:lookup(ungermanbazz,foo),
+ {'EXIT',{badarg,_}} = (catch ets:rename(ungermanbazz,"no atom")),
+ ets:delete(ungermanbazz),
+ ?line verify_etsmem(EtsMem).
+
+rename_unnamed(doc) ->
+ ["Check rename of unnamed ets table"];
+rename_unnamed(suite) ->
+ [];
+rename_unnamed(Config) when is_list(Config) ->
+ repeat_for_opts(rename_unnamed_do,[write_concurrency,all_types]).
+
+rename_unnamed_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(bonkz,[public | Opts]),
+ ?line {'EXIT',{badarg, _}} = (catch ets:insert(bonkz,{foo,bazz})),
+ ?line bonkz = ets:info(Tab, name),
+ ?line Tab = ets:rename(Tab, tjabonkz),
+ ?line {'EXIT',{badarg, _}} = (catch ets:insert(tjabonkz,{foo,bazz})),
+ ?line tjabonkz = ets:info(Tab, name),
+ ?line ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+evil_rename(doc) ->
+ "Rename a table with many fixations, and at the same time delete it.";
+evil_rename(Config) when is_list(Config) ->
+ ?line evil_rename_1(old_hash, new_hash, [public,named_table]),
+ ?line EtsMem = etsmem(),
+ ?line evil_rename_1(old_tree, new_tree, [public,ordered_set,named_table]),
+ ?line verify_etsmem(EtsMem).
+
+evil_rename_1(Old, New, Flags) ->
+ ?line process_flag(trap_exit, true),
+ ?line Old = ets:new(Old, Flags),
+ ?line Fixer = fun() -> ets:safe_fixtable(Old, true) end,
+ ?line crazy_fixtable(15000, Fixer),
+ ?line erlang:yield(),
+ ?line New = ets:rename(Old, New),
+ ?line erlang:yield(),
+ ets:delete(New),
+ ok.
+
+crazy_fixtable(N, Fixer) ->
+ Dracula = ets:new(count_dracula, [public]),
+ ets:insert(Dracula, {count,0}),
+ SpawnFun = fun() ->
+ Fixer(),
+ case ets:update_counter(Dracula, count, 1) rem 15 of
+ 0 -> evil_creater_destroyer();
+ _ -> erlang:hibernate(erlang, error, [dont_wake_me])
+ end
+ end,
+ crazy_fixtable_1(N, SpawnFun),
+ crazy_fixtable_wait(N, Dracula),
+ Dracula.
+
+crazy_fixtable_wait(N, Dracula) ->
+ case ets:lookup(Dracula, count) of
+ [{count,N}] ->
+ ets:delete(Dracula);
+ Other ->
+ io:format("~p\n", [Other]),
+ receive after 10 -> ok end,
+ crazy_fixtable_wait(N, Dracula)
+ end.
+
+crazy_fixtable_1(0, _) ->
+ ok;
+crazy_fixtable_1(N, Fun) ->
+ spawn_link(Fun),
+ crazy_fixtable_1(N-1, Fun).
+
+evil_creater_destroyer() ->
+ T1 = evil_create_fixed_tab(),
+ ets:delete(T1).
+
+evil_create_fixed_tab() ->
+ T = ets:new(arne, [public]),
+ ets:safe_fixtable(T, true),
+ T.
+
+interface_equality(doc) ->
+ ["Tests that the return values and errors are equal for set's and"
+ " ordered_set's where applicable"];
+interface_equality(suite) ->
+ [];
+interface_equality(Config) when is_list(Config) ->
+ repeat_for_opts(interface_equality_do).
+
+interface_equality_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Set = ets:new(set,[set | Opts]),
+ ?line OrderedSet = ets:new(ordered_set,[ordered_set | Opts]),
+ ?line F = fun(X,T,FF) -> case X of
+ 0 -> true;
+ _ ->
+ ets:insert(T, {X,
+ integer_to_list(X),
+ X rem 10}),
+ FF(X-1,T,FF)
+ end
+ end,
+ ?line F(100,Set,F),
+ ?line F(100,OrderedSet,F),
+ ?line equal_results(ets, insert, Set, OrderedSet, [{a,"a"}]),
+ ?line equal_results(ets, insert, Set, OrderedSet, [{1,1,"1"}]),
+ ?line equal_results(ets, lookup, Set, OrderedSet, [10]),
+ ?line equal_results(ets, lookup, Set, OrderedSet, [1000]),
+ ?line equal_results(ets, delete, Set, OrderedSet, [10]),
+ ?line equal_results(ets, delete, Set, OrderedSet, [nott]),
+ ?line equal_results(ets, lookup, Set, OrderedSet, [1000]),
+ ?line equal_results(ets, insert, Set, OrderedSet, [10]),
+ ?line equal_results(ets, next, Set, OrderedSet, ['$end_of_table']),
+ ?line equal_results(ets, prev, Set, OrderedSet, ['$end_of_table']),
+ ?line equal_results(ets, match, Set, OrderedSet, [{'_','_','_'}]),
+ ?line equal_results(ets, match, Set, OrderedSet, [{'_','_','_','_'}]),
+ ?line equal_results(ets, match, Set, OrderedSet, [{$3,$2,2}]),
+ ?line equal_results(ets, match, Set, OrderedSet, ['_']),
+ ?line equal_results(ets, match, Set, OrderedSet, ['$1']),
+ ?line equal_results(ets, match, Set, OrderedSet, [{'_','$50',3}]),
+ ?line equal_results(ets, match, Set, OrderedSet, [['_','$50',3]]),
+ ?line equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]),
+ ?line equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]),
+ ?line equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]),
+ ?line equal_results(ets, match_object, Set, OrderedSet, [{'_','_',5}]),
+ ?line equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]),
+ ?line equal_results(ets, match_object, Set, OrderedSet, ['_']),
+ ?line equal_results(ets, match_object, Set, OrderedSet, ['$5011']),
+ ?line equal_results(ets, match_delete, Set, OrderedSet, ['$20']),
+ ?line equal_results(ets, lookup_element, Set, OrderedSet, [13,2]),
+ ?line equal_results(ets, lookup_element, Set, OrderedSet, [13,4]),
+ ?line equal_results(ets, lookup_element, Set, OrderedSet, [14,2]),
+ ?line equal_results(ets, delete, Set, OrderedSet, []),
+ ?line verify_etsmem(EtsMem).
+
+equal_results(M, F, FirstArg1, FirstArg2 ,ACommon) ->
+ Res = maybe_sort((catch apply(M,F, [FirstArg1 | ACommon]))),
+ Res = maybe_sort((catch apply(M,F,[FirstArg2 | ACommon]))).
+
+maybe_sort(L) when is_list(L) ->
+ lists:sort(L);
+%maybe_sort({'EXIT',{Reason, [{Module, Function, _}|_]}}) ->
+% {'EXIT',{Reason, [{Module, Function, '_'}]}};
+maybe_sort({'EXIT',{Reason, List}}) when is_list(List) ->
+ {'EXIT',{Reason, lists:map(fun({Module, Function, _}) ->
+ {Module, Function, '_'}
+ end,
+ List)}};
+maybe_sort(Any) ->
+ Any.
+
+ordered_match(doc) ->
+ ["Test match, match_object and match_delete in ordered set's"];
+ordered_match(suite) ->
+ [];
+ordered_match(Config) when is_list(Config)->
+ repeat_for_opts(ordered_match_do).
+
+ordered_match_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line F = fun(X,T,FF) -> case X of
+ 0 -> true;
+ _ ->
+ ets:insert(T, {X,
+ integer_to_list(X),
+ X rem 10,
+ X rem 100,
+ X rem 1000}),
+ FF(X-1,T,FF)
+ end
+ end,
+ ?line T1 = ets:new(xxx,[ordered_set| Opts]),
+ ?line F(3000,T1,F),
+ ?line [[3,3],[3,3],[3,3]] = ets:match(T1, {'_','_','$1','$2',3}),
+ ?line F2 = fun(X,Rem,Res,FF) -> case X of
+ 0 -> [];
+ _ ->
+ case X rem Rem of
+ Res ->
+ FF(X-1,Rem,Res,FF) ++
+ [{X,
+ integer_to_list(X),
+ X rem 10,
+ X rem 100,
+ X rem 1000}];
+ _ ->
+ FF(X-1,Rem,Res,FF)
+ end
+ end
+ end,
+ ?line OL1 = F2(3000,100,2,F2),
+ ?line OL1 = ets:match_object(T1, {'_','_','_',2,'_'}),
+ ?line true = ets:match_delete(T1,{'_','_','_',2,'_'}),
+ ?line [] = ets:match_object(T1, {'_','_','_',2,'_'}),
+ ?line OL2 = F2(3000,100,3,F2),
+ ?line OL2 = ets:match_object(T1, {'_','_','_',3,'_'}),
+ ?line ets:delete(T1),
+ ?line verify_etsmem(EtsMem).
+
+
+ordered(doc) ->
+ ["Test basic functionality in ordered_set's."];
+ordered(suite) ->
+ [];
+ordered(Config) when is_list(Config) ->
+ repeat_for_opts(ordered_do).
+
+ordered_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line T = ets:new(oset, [ordered_set | Opts]),
+ ?line InsList = [
+ 25,26,27,28,
+ 5,6,7,8,
+ 21,22,23,24,
+ 9,10,11,12,
+ 1,2,3,4,
+ 17,18,19,20,
+ 13,14,15,16
+ ],
+ ?line lists:foreach(fun(X) ->
+ ets:insert(T,{X,integer_to_list(X)})
+ end,
+ InsList),
+ ?line IL2 = lists:map(fun(X) -> {X,integer_to_list(X)} end, InsList),
+ ?line L1 = pick_all_forward(T),
+ ?line L2 = pick_all_backwards(T),
+ ?line S1 = lists:sort(IL2),
+ ?line S2 = lists:reverse(lists:sort(IL2)),
+ ?line S1 = L1,
+ ?line S2 = L2,
+ ?line [{1,"1"}] = ets:slot(T,0),
+ ?line [{28,"28"}] = ets:slot(T,27),
+ ?line 27 = ets:prev(T,28),
+ ?line [{7,"7"}] = ets:slot(T,6),
+ ?line '$end_of_table' = ets:next(T,28),
+ ?line [{12,"12"}] = ets:slot(T,11),
+ ?line '$end_of_table' = ets:slot(T,28),
+ ?line [{1,"1"}] = ets:slot(T,0),
+ ?line 28 = ets:prev(T,29),
+ ?line 1 = ets:next(T,0),
+ ?line pick_all_forward(T),
+ ?line [{7,"7"}] = ets:slot(T,6),
+ ?line L2 = pick_all_backwards(T),
+ ?line [{7,"7"}] = ets:slot(T,6),
+ ?line ets:delete(T),
+ ?line verify_etsmem(EtsMem).
+
+pick_all(_T,'$end_of_table',_How) ->
+ [];
+pick_all(T,Last,How) ->
+ ?line This = case How of
+ next ->
+ ?line ets:next(T,Last);
+ prev ->
+ ?line ets:prev(T,Last)
+ end,
+ ?line [LastObj] = ets:lookup(T,Last),
+ ?line [LastObj | pick_all(T,This,How)].
+
+pick_all_forward(T) ->
+ ?line pick_all(T,ets:first(T),next).
+pick_all_backwards(T) ->
+ ?line pick_all(T,ets:last(T),prev).
+
+
+
+setbag(doc) -> ["Small test case for both set and bag type ets tables."];
+setbag(suite) -> [];
+setbag(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Set = ets:new(set,[set]),
+ ?line Bag = ets:new(bag,[bag]),
+ ?line Key = {foo,bar},
+
+ %% insert some value
+ ?line ets:insert(Set,{Key,val1}),
+ ?line ets:insert(Bag,{Key,val1}),
+
+ %% insert new value for same key again
+ ?line ets:insert(Set,{Key,val2}),
+ ?line ets:insert(Bag,{Key,val2}),
+
+ %% check
+ ?line [{Key,val2}] = ets:lookup(Set,Key),
+ ?line [{Key,val1},{Key,val2}] = ets:lookup(Bag,Key),
+
+ true = ets:delete(Set),
+ true = ets:delete(Bag),
+ ?line verify_etsmem(EtsMem).
+
+badnew(doc) ->
+ ["Test case to check proper return values for illegal ets:new() calls."];
+badnew(suite) -> [];
+badnew(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(12,[])),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new({a,b},[])),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(name,[foo])),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(name,{bag})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(name,bag)),
+ ?line verify_etsmem(EtsMem).
+
+verybadnew(doc) ->
+ ["Test case to check that a not well formed list does not crash the "
+ "emulator. OTP-2314 "];
+verybadnew(suite) -> [];
+verybadnew(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:new(verybad,[set|protected])),
+ ?line verify_etsmem(EtsMem).
+
+named(doc) -> ["Small check to see if named tables work."];
+named(suite) -> [];
+named(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(foo,
+ [named_table],
+ [{key,val}]),
+ ?line [{key,val}] = ets:lookup(foo,key),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+keypos2(doc) -> ["Test case to check if specified keypos works."];
+keypos2(suite) -> [];
+keypos2(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(foo,
+ [set,{keypos,2}],
+ [{val,key}, {val2,key}]),
+ ?line [{val2,key}] = ets:lookup(Tab,key),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+privacy(doc) ->
+ ["Privacy check. Check that a named(public/private/protected) table "
+ "cannot be read by",
+ "the wrong process(es)."];
+privacy(suite) -> [];
+privacy(Config) when is_list(Config) ->
+ repeat_for_opts(privacy_do).
+
+privacy_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line process_flag(trap_exit,true),
+ ?line Owner = my_spawn_link(?MODULE,privacy_owner,[self(),Opts]),
+ receive
+ {'EXIT',Owner,Reason} ->
+ ?line exit({privacy_test,Reason});
+ ok ->
+ ok
+ end,
+
+ privacy_check(pub,prot,priv),
+
+ Owner ! {shift,1,{pub,prot,priv}},
+ receive {Pub1,Prot1,Priv1} -> ok end,
+ privacy_check(Pub1,Prot1,Priv1),
+
+ Owner ! {shift,2,{Pub1,Prot1,Priv1}},
+ receive {Pub2,Prot2,Priv2} -> ok end,
+ privacy_check(Pub2,Prot2,Priv2),
+
+ Owner ! {shift,0,{Pub2,Prot2,Priv2}},
+ receive {Pub2,Prot2,Priv2} -> ok end,
+ privacy_check(Pub2,Prot2,Priv2),
+
+ Owner ! die,
+ receive {'EXIT',Owner,_} -> ok end,
+ ?line verify_etsmem(EtsMem).
+
+privacy_check(Pub,Prot,Priv) ->
+ %% check read rights
+ ?line [] = ets:lookup(Pub, foo),
+ ?line [] = ets:lookup(Prot,foo),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(Priv,foo)),
+
+ %% check write rights
+ ?line true = ets:insert(Pub, {1,foo}),
+ ?line {'EXIT',{badarg,_}} = (catch ets:insert(Prot,{2,foo})),
+ ?line {'EXIT',{badarg,_}} = (catch ets:insert(Priv,{3,foo})),
+
+ %% check that it really wasn't written, either
+ ?line [] = ets:lookup(Prot,foo).
+
+privacy_owner(Boss, Opts) ->
+ ets:new(pub, [public,named_table | Opts]),
+ ets:new(prot,[protected,named_table | Opts]),
+ ets:new(priv,[private,named_table | Opts]),
+ Boss ! ok,
+ privacy_owner_loop(Boss).
+
+privacy_owner_loop(Boss) ->
+ receive
+ {shift,N,Pub_Prot_Priv} ->
+ {Pub,Prot,Priv} = rotate_tuple(Pub_Prot_Priv, N),
+
+ ets:setopts(Pub,{protection,public}),
+ ets:setopts(Prot,{protection,protected}),
+ ets:setopts(Priv,{protection,private}),
+ Boss ! {Pub,Prot,Priv},
+ privacy_owner_loop(Boss);
+
+ die -> ok
+ end.
+
+rotate_tuple(Tuple, 0) ->
+ Tuple;
+rotate_tuple(Tuple, N) ->
+ [H|T] = tuple_to_list(Tuple),
+ rotate_tuple(list_to_tuple(T ++ [H]), N-1).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+insert(doc) -> ["Test proper and improper inserts into a table."];
+insert(suite) -> [empty,badinsert].
+
+empty(doc) ->
+ ["Check lookup in an empty table and lookup of a non-existing key"];
+empty(suite) -> [];
+empty(Config) when is_list(Config) ->
+ repeat_for_opts(empty_do).
+
+empty_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line [] = ets:lookup(Tab,key),
+ ?line true = ets:insert(Tab,{key2,val}),
+ ?line [] = ets:lookup(Tab,key),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+badinsert(doc) ->
+ ["Check proper return values for illegal insert operations."];
+badinsert(suite) -> [];
+badinsert(Config) when is_list(Config) ->
+ repeat_for_opts(badinsert_do).
+
+badinsert_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:insert(foo,{key,val})),
+
+ ?line Tab = ets:new(foo,Opts),
+ ?line {'EXIT',{badarg,_}} = (catch ets:insert(Tab,{})),
+
+ ?line Tab3 = ets:new(foo,[{keypos,3}| Opts]),
+ ?line {'EXIT',{badarg,_}} = (catch ets:insert(Tab3,{a,b})),
+
+ ?line {'EXIT',{badarg,_}} = (catch ets:insert(Tab,[key,val2])),
+ ?line true = ets:delete(Tab),
+ ?line true = ets:delete(Tab3),
+ ?line verify_etsmem(EtsMem).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+lookup(doc) -> ["Some tests for lookups (timing, bad lookups, etc.)."];
+lookup(suite) -> [time_lookup,badlookup,lookup_order].
+
+time_lookup(doc) -> ["Lookup timing."];
+time_lookup(suite) -> [];
+time_lookup(Config) when is_list(Config) ->
+ %% just for timing, really
+ ?line EtsMem = etsmem(),
+ Values = repeat_for_opts(time_lookup_do),
+ ?line verify_etsmem(EtsMem),
+ ?line {comment,lists:flatten(io_lib:format(
+ "~p ets lookups/s",[Values]))}.
+
+time_lookup_do(Opts) ->
+ ?line Tab = ets:new(foo,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line ets:insert(Tab,{{a,key},foo}),
+ ?line {Time,_} = ?t:timecall(test_server,do_times,
+ [10000,ets,lookup,[Tab,{a,key}]]),
+ ?line true = ets:delete(Tab),
+ round(10000 / Time). % lookups/s
+
+badlookup(doc) ->
+ ["Check proper return values from bad lookups in existing/non existing "
+ " ets tables"];
+badlookup(suite) -> [];
+badlookup(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(foo,key)),
+ ?line Tab = ets:new(foo,[]),
+ ?line ets:delete(Tab),
+ ?line {'EXIT',{badarg,_}} = (catch ets:lookup(Tab,key)),
+ ?line verify_etsmem(EtsMem).
+
+lookup_order(doc) -> ["Test that lookup returns objects in order of insertion for bag and dbag."];
+lookup_order(suite) -> [];
+lookup_order(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(lookup_order_do, [write_concurrency,[bag,duplicate_bag]]),
+ ?line verify_etsmem(EtsMem),
+ ok.
+
+lookup_order_do(Opts) ->
+ lookup_order_2(Opts, false),
+ lookup_order_2(Opts, true).
+
+lookup_order_2(Opts, Fixed) ->
+ io:format("Opts=~p Fixed=~p\n",[Opts,Fixed]),
+
+ A = 1, B = 2, C = 3,
+ ABC = [A,B,C],
+ Pair = [{A,B},{B,A},{A,C},{C,A},{B,C},{C,B}],
+ Combos = [{D1,D2,D3} || D1<-ABC, D2<-Pair, D3<-Pair],
+ lists:foreach(fun({D1,{D2a,D2b},{D3a,D3b}}) ->
+ T = ets:new(foo,Opts),
+ case Fixed of
+ true -> ets:safe_fixtable(T,true);
+ false -> ok
+ end,
+ S10 = {T,[],key},
+ S20 = check_insert(S10,A),
+ S30 = check_insert(S20,B),
+ S40 = check_insert(S30,C),
+ S50 = check_delete(S40,D1),
+ S55 = check_insert(S50,D1),
+ S60 = check_insert(S55,D1),
+ S70 = check_delete(S60,D2a),
+ S80 = check_delete(S70,D2b),
+ S90 = check_insert(S80,D2a),
+ SA0 = check_delete(S90,D3a),
+ SB0 = check_delete(SA0,D3b),
+ check_insert_new(SB0,D3b),
+
+ true = ets:delete(T)
+ end,
+ Combos).
+
+
+check_insert({T,List0,Key},Val) ->
+ %%io:format("insert ~p into ~p\n",[Val,List0]),
+ ets:insert(T,{Key,Val}),
+ List1 = case (ets:info(T,type) =:= bag andalso
+ lists:member({Key,Val},List0)) of
+ true -> List0;
+ false -> [{Key,Val} | List0]
+ end,
+ check_check({T,List1,Key}).
+
+check_insert_new({T,List0,Key},Val) ->
+ %%io:format("insert_new ~p into ~p\n",[Val,List0]),
+ Ret = ets:insert_new(T,{Key,Val}),
+ ?line Ret = (List0 =:= []),
+ List1 = case Ret of
+ true -> [{Key,Val}];
+ false -> List0
+ end,
+ check_check({T,List1,Key}).
+
+
+check_delete({T,List0,Key},Val) ->
+ %%io:format("delete ~p from ~p\n",[Val,List0]),
+ ets:delete_object(T,{Key,Val}),
+ List1 = lists:filter(fun(Obj) -> Obj =/= {Key,Val} end,
+ List0),
+ check_check({T,List1,Key}).
+
+check_check(S={T,List,Key}) ->
+ case lists:reverse(ets:lookup(T,Key)) of
+ List -> ok;
+ ETS -> io:format("check failed:\nETS: ~p\nCHK: ~p\n", [ETS,List]),
+ ?t:fail("Invalid return value from ets:lookup")
+ end,
+ ?line Items = ets:info(T,size),
+ ?line Items = length(List),
+ S.
+
+
+
+fill_tab(Tab,Val) ->
+ ?line ets:insert(Tab,{key,Val}),
+ ?line ets:insert(Tab,{{a,144},Val}),
+ ?line ets:insert(Tab,{{a,key2},Val}),
+ ?line ets:insert(Tab,{14,Val}),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+lookup_element(doc) -> ["Some tests for lookup_element."];
+lookup_element(suite) -> [lookup_element_mult].
+
+lookup_element_mult(doc) -> ["Multiple return elements (OTP-2386)"];
+lookup_element_mult(suite) -> [];
+lookup_element_mult(Config) when is_list(Config) ->
+ repeat_for_opts(lookup_element_mult_do).
+
+lookup_element_mult_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line T = ets:new(service, [bag, {keypos, 2} | Opts]),
+ ?line D = lists:reverse(lem_data()),
+ ?line lists:foreach(fun(X) -> ets:insert(T, X) end, D),
+ ?line ok = lem_crash_3(T),
+ ?line true = ets:delete(T),
+ ?line verify_etsmem(EtsMem).
+
+lem_data() ->
+ [
+ {service,'eddie2@boromir',{150,236,14,103},httpd88,self()},
+ {service,'eddie2@boromir',{150,236,14,103},httpd80,self()},
+ {service,'eddie3@boromir',{150,236,14,107},httpd88,self()},
+ {service,'eddie3@boromir',{150,236,14,107},httpd80,self()},
+ {service,'eddie4@boromir',{150,236,14,108},httpd88,self()}
+ ].
+
+lem_crash(T) ->
+ L = ets:lookup_element(T, 'eddie2@boromir', 3),
+ {erlang:phash(L, 256), L}.
+
+lem_crash_3(T) ->
+ lem_crash(T),
+ io:format("Survived once~n"),
+ lem_crash(T),
+ io:format("Survived twice~n"),
+ lem_crash(T),
+ io:format("Survived all!~n"),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+delete(doc) ->
+ ["Check delete functionality (proper/improper deletes)"];
+delete(suite) ->
+ [delete_elem,delete_tab,delete_large_tab,delete_large_named_table,evil_delete,
+ table_leak,baddelete,match_delete,match_delete3].
+
+delete_elem(doc) ->
+ ["Check delete of an element inserted in a `filled' table."];
+delete_elem(suite) -> [];
+delete_elem(Config) when is_list(Config) ->
+ repeat_for_opts(delete_elem_do, [write_concurrency, all_types]).
+
+delete_elem_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line ets:insert(Tab,{{b,key},foo}),
+ ?line ets:insert(Tab,{{c,key},foo}),
+ ?line true = ets:delete(Tab,{b,key}),
+ ?line [] = ets:lookup(Tab,{b,key}),
+ ?line [{{c,key},foo}] = ets:lookup(Tab,{c,key}),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+delete_tab(doc) ->
+ ["Check that ets:delete() works and releases the name of the deleted "
+ "table."];
+delete_tab(suite) -> [];
+delete_tab(Config) when is_list(Config) ->
+ repeat_for_opts(delete_tab_do,[write_concurrency,all_types]).
+
+delete_tab_do(Opts) ->
+ Name = foo,
+ ?line EtsMem = etsmem(),
+ ?line Name = ets:new(Name, [named_table | Opts]),
+ ?line true = ets:delete(foo),
+ %% The name should be available again.
+ ?line Name = ets:new(Name, [named_table | Opts]),
+ ?line true = ets:delete(Name),
+ ?line verify_etsmem(EtsMem).
+
+delete_large_tab(doc) ->
+ "Check that ets:delete/1 works and that other processes can run.";
+delete_large_tab(Config) when is_list(Config) ->
+ ?line Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)],
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(fun(Opts) -> delete_large_tab_do(Opts,Data) end),
+ ?line verify_etsmem(EtsMem).
+
+delete_large_tab_do(Opts,Data) ->
+ ?line delete_large_tab_1(foo_hash, Opts, Data, false),
+ ?line delete_large_tab_1(foo_tree, [ordered_set | Opts], Data, false),
+ ?line delete_large_tab_1(foo_hash, Opts, Data, true).
+
+
+delete_large_tab_1(Name, Flags, Data, Fix) ->
+ ?line Tab = ets:new(Name, Flags),
+ ?line ets:insert(Tab, Data),
+
+ case Fix of
+ false -> ok;
+ true ->
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data)
+ end,
+
+ {priority, Prio} = process_info(self(), priority),
+ ?line Deleter = self(),
+ ?line [SchedTracer]
+ = start_loopers(1,
+ Prio,
+ fun (SC) ->
+ receive
+ {trace, Deleter, out, _} ->
+ undefined = ets:info(Tab),
+ SC+1;
+ {trace,
+ Deleter,
+ register,
+ delete_large_tab_done_marker}->
+ Deleter ! {schedule_count, SC},
+ exit(normal);
+ _ ->
+ SC
+ end
+ end,
+ 0),
+ ?line Loopers = start_loopers(erlang:system_info(schedulers),
+ Prio,
+ fun (_) -> erlang:yield() end,
+ ok),
+ ?line erlang:yield(),
+ ?line 1 = erlang:trace(self(),true,[running,procs,{tracer,SchedTracer}]),
+ ?line true = ets:delete(Tab),
+ %% The register stuff is just a trace marker
+ ?line true = register(delete_large_tab_done_marker, self()),
+ ?line true = unregister(delete_large_tab_done_marker),
+ ?line undefined = ets:info(Tab),
+ ?line ok = stop_loopers(Loopers),
+ ?line receive
+ {schedule_count, N} ->
+ ?line io:format("~s: context switches: ~p", [Name,N]),
+ if
+ N >= 5 -> ?line ok;
+ true -> ?line ?t:fail()
+ end
+ end.
+
+delete_large_named_table(doc) ->
+ "Delete a large name table and try to create a new table with the same name in another process.";
+delete_large_named_table(Config) when is_list(Config) ->
+ ?line Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)],
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(fun(Opts) -> delete_large_named_table_do(Opts,Data) end),
+ ?line verify_etsmem(EtsMem),
+ ok.
+
+delete_large_named_table_do(Opts,Data) ->
+ ?line delete_large_named_table_1(foo_hash, [named_table | Opts], Data, false),
+ ?line delete_large_named_table_1(foo_tree, [ordered_set,named_table | Opts], Data, false),
+ ?line delete_large_named_table_1(foo_hash, [named_table | Opts], Data, true).
+
+delete_large_named_table_1(Name, Flags, Data, Fix) ->
+ ?line Tab = ets:new(Name, Flags),
+ ?line ets:insert(Tab, Data),
+
+ case Fix of
+ false -> ok;
+ true ->
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data)
+ end,
+ Parent = self(),
+ Pid = spawn_link(fun() ->
+ receive
+ {trace,Parent,call,_} ->
+ ets:new(Name, [named_table])
+ end
+ end),
+ ?line erlang:trace(self(), true, [call,{tracer,Pid}]),
+ ?line erlang:trace_pattern({ets,delete,1}, true, [global]),
+ ?line erlang:yield(), true = ets:delete(Tab),
+ ?line erlang:trace_pattern({ets,delete,1}, false, [global]),
+ ok.
+
+evil_delete(doc) ->
+ "Delete a large table, and kill the process during the delete.";
+evil_delete(Config) when is_list(Config) ->
+ ?line Data = [{I,I*I} || I <- lists:seq(1, 100000)],
+ repeat_for_opts(fun(Opts) -> evil_delete_do(Opts,Data) end).
+
+evil_delete_do(Opts,Data) ->
+ ?line EtsMem = etsmem(),
+ ?line evil_delete_owner(foo_hash, Opts, Data, false),
+ ?line verify_etsmem(EtsMem),
+ ?line evil_delete_owner(foo_hash, Opts, Data, true),
+ ?line verify_etsmem(EtsMem),
+ ?line evil_delete_owner(foo_tree, [ordered_set | Opts], Data, false),
+ ?line verify_etsmem(EtsMem),
+ ?line TabA = evil_delete_not_owner(foo_hash, Opts, Data, false),
+ ?line verify_etsmem(EtsMem),
+ ?line TabB = evil_delete_not_owner(foo_hash, Opts, Data, true),
+ ?line verify_etsmem(EtsMem),
+ ?line TabC = evil_delete_not_owner(foo_tree, [ordered_set | Opts], Data, false),
+ ?line verify_etsmem(EtsMem),
+ ?line lists:foreach(fun(T) -> undefined = ets:info(T) end,
+ [TabA,TabB,TabC]).
+
+evil_delete_not_owner(Name, Flags, Data, Fix) ->
+ io:format("Not owner: ~p, fix = ~p", [Name,Fix]),
+ ?line Tab = ets:new(Name, [public|Flags]),
+ ?line ets:insert(Tab, Data),
+ case Fix of
+ false -> ok;
+ true ->
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data)
+ end,
+ ?line Pid = my_spawn(fun() ->
+ P = my_spawn_link(
+ fun() ->
+ receive kill -> ok end,
+ erlang:yield(),
+ exit(kill_linked_processes_now)
+ end),
+ erlang:yield(),
+ P ! kill,
+ true = ets:delete(Tab)
+ end),
+ ?line Ref = erlang:monitor(process, Pid),
+ ?line receive {'DOWN',Ref,_,_,_} -> ok end,
+ Tab.
+
+evil_delete_owner(Name, Flags, Data, Fix) ->
+ ?line Fun = fun() ->
+ ?line Tab = ets:new(Name, [public|Flags]),
+ ?line ets:insert(Tab, Data),
+ case Fix of
+ false -> ok;
+ true ->
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line lists:foreach(fun({K,_}) ->
+ ets:delete(Tab, K)
+ end, Data)
+ end,
+ erlang:yield(),
+ my_spawn_link(fun() ->
+ erlang:yield(),
+ exit(kill_linked_processes_now)
+ end),
+ true = ets:delete(Tab)
+ end,
+ ?line Pid = my_spawn(Fun),
+ ?line Ref = erlang:monitor(process, Pid),
+ ?line receive {'DOWN',Ref,_,_,_} -> ok end.
+
+
+exit_large_table_owner(doc) ->
+ [];
+exit_large_table_owner(suite) ->
+ [];
+exit_large_table_owner(Config) when is_list(Config) ->
+ ?line Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)],
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(fun(Opts) -> exit_large_table_owner_do(Opts,Data,Config) end),
+ ?line verify_etsmem(EtsMem).
+
+exit_large_table_owner_do(Opts,Data,Config) ->
+ ?line verify_rescheduling_exit(Config, Data, [named_table | Opts], true, 1, 1),
+ ?line verify_rescheduling_exit(Config, Data, Opts, false, 1, 1).
+
+exit_many_large_table_owner(doc) -> [];
+exit_many_large_table_owner(suite) -> [];
+exit_many_large_table_owner(Config) when is_list(Config) ->
+ ?line Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)],
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(fun(Opts) -> exit_many_large_table_owner_do(Opts,Data,Config) end),
+ ?line verify_etsmem(EtsMem).
+
+exit_many_large_table_owner_do(Opts,Data,Config) ->
+ ?line verify_rescheduling_exit(Config, Data, Opts, true, 1, 4),
+ ?line verify_rescheduling_exit(Config, Data, [named_table | Opts], false, 1, 4).
+
+exit_many_tables_owner(doc) -> [];
+exit_many_tables_owner(suite) -> [];
+exit_many_tables_owner(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line verify_rescheduling_exit(Config, [], [named_table], false, 1000, 1),
+ ?line verify_rescheduling_exit(Config, [], [named_table,{write_concurrency,true}], false, 1000, 1),
+ ?line verify_etsmem(EtsMem).
+
+exit_many_many_tables_owner(doc) -> [];
+exit_many_many_tables_owner(suite) -> [];
+exit_many_many_tables_owner(Config) when is_list(Config) ->
+ ?line Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 50)],
+ repeat_for_opts(fun(Opts) -> exit_many_many_tables_owner_do(Opts,Data,Config) end).
+
+exit_many_many_tables_owner_do(Opts,Data,Config) ->
+ ?line verify_rescheduling_exit(Config, Data, [named_table | Opts], true, 200, 5),
+ ?line verify_rescheduling_exit(Config, Data, Opts, false, 200, 5),
+ ?line wait_for_test_procs(),
+ ?line EtsMem = etsmem(),
+ ?line verify_rescheduling_exit(Config, Data, Opts, true, 200, 5),
+ ?line verify_rescheduling_exit(Config, Data, [named_table | Opts], false, 200, 5),
+ ?line verify_etsmem(EtsMem).
+
+
+count_exit_sched(TP) ->
+ receive
+ {trace, TP, in_exiting, 0} ->
+ count_exit_sched_out(TP, 1);
+ {trace, TP, out_exiting, 0} ->
+ count_exit_sched_in(TP, 1);
+ {trace, TP, out_exited, 0} ->
+ 0
+ end.
+
+count_exit_sched_in(TP, N) ->
+ receive
+ {trace, TP, in_exiting, 0} ->
+ count_exit_sched_out(TP, N);
+ {trace, TP, _, _} = Msg ->
+ exit({unexpected_trace_msg, Msg})
+ end.
+
+count_exit_sched_out(TP, N) ->
+ receive
+ {trace, TP, out_exiting, 0} ->
+ count_exit_sched_in(TP, N+1);
+ {trace, TP, out_exited, 0} ->
+ N;
+ {trace, TP, _, _} = Msg ->
+ exit({unexpected_trace_msg, Msg})
+ end.
+
+vre_fix_tables(Tab) ->
+ Parent = self(),
+ Go = make_ref(),
+ my_spawn_link(fun () ->
+ true = ets:safe_fixtable(Tab, true),
+ Parent ! Go,
+ receive infinity -> ok end
+ end),
+ receive Go -> ok end,
+ ok.
+
+verify_rescheduling_exit(Config, Data, Flags, Fix, NOTabs, NOProcs) ->
+ ?line NoFix = 5,
+ ?line TestCase = atom_to_list(?config(test_case, Config)),
+ ?line Parent = self(),
+ ?line KillMe = make_ref(),
+ ?line PFun =
+ fun () ->
+ repeat(
+ fun () ->
+ {A, B, C} = now(),
+ ?line Name = list_to_atom(
+ TestCase
+ ++ "-" ++ integer_to_list(A)
+ ++ "-" ++ integer_to_list(B)
+ ++ "-" ++ integer_to_list(C)),
+ Tab = ets:new(Name, Flags),
+ ets:insert(Tab, Data),
+ case Fix of
+ false -> ok;
+ true ->
+ lists:foreach(fun (_) ->
+ vre_fix_tables(Tab)
+ end,
+ lists:seq(1,NoFix)),
+ lists:foreach(fun({K,_}) ->
+ ets:delete(Tab, K)
+ end,
+ Data)
+ end
+ end,
+ NOTabs),
+ Parent ! {KillMe, self()},
+ receive after infinity -> ok end
+ end,
+ ?line TPs = lists:map(fun (_) ->
+ ?line TP = my_spawn_link(PFun),
+ ?line 1 = erlang:trace(TP, true, [exiting]),
+ TP
+ end,
+ lists:seq(1, NOProcs)),
+ ?line lists:foreach(fun (TP) ->
+ receive {KillMe, TP} -> ok end
+ end,
+ TPs),
+ ?line LPs = start_loopers(erlang:system_info(schedulers),
+ normal,
+ fun (_) ->
+ erlang:yield()
+ end,
+ ok),
+ ?line lists:foreach(fun (TP) ->
+ ?line unlink(TP),
+ ?line exit(TP, bang)
+ end,
+ TPs),
+ ?line lists:foreach(fun (TP) ->
+ ?line XScheds = count_exit_sched(TP),
+ ?line ?t:format("~p XScheds=~p~n",
+ [TP, XScheds]),
+ ?line true = XScheds >= 5
+ end,
+ TPs),
+ ?line stop_loopers(LPs),
+ ?line ok.
+
+
+
+table_leak(doc) ->
+ "Make sure that slots for ets tables are cleared properly.";
+table_leak(Config) when is_list(Config) ->
+ repeat_for_opts(fun(Opts) -> table_leak_1(Opts,20000) end).
+
+table_leak_1(_,0) -> ok;
+table_leak_1(Opts,N) ->
+ ?line T = ets:new(fooflarf, Opts),
+ ?line true = ets:delete(T),
+ table_leak_1(Opts,N-1).
+
+baddelete(doc) ->
+ ["Check proper return values for illegal delete operations."];
+baddelete(suite) -> [];
+baddelete(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:delete(foo)),
+ ?line Tab = ets:new(foo,[]),
+ ?line true = ets:delete(Tab),
+ ?line {'EXIT',{badarg,_}} = (catch ets:delete(Tab)),
+ ?line verify_etsmem(EtsMem).
+
+match_delete(doc) ->
+ ["Check that match_delete works. Also tests tab2list function."];
+match_delete(suite) -> [];
+match_delete(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(match_delete_do,[write_concurrency,all_types]),
+ ?line verify_etsmem(EtsMem).
+
+match_delete_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(kad,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line ets:insert(Tab,{{c,key},bar}),
+ ?line _ = ets:match_delete(Tab,{'_',foo}),
+ ?line [{{c,key},bar}] = ets:tab2list(Tab),
+ ?line _ = ets:match_delete(Tab,'_'),
+ ?line [] = ets:tab2list(Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match_delete3(doc) ->
+ ["OTP-3005: check match_delete with constant argument."];
+match_delete3(suite) -> [];
+match_delete3(Config) when is_list(Config) ->
+ repeat_for_opts(match_delete3_do).
+
+match_delete3_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ T = make_table(test,
+ [duplicate_bag | Opts],
+ [{aa,17},
+ {cA,1000},
+ {cA,17},
+ {cA,1000},
+ {aa,17}]),
+ %% 'aa' and 'cA' have the same hash value in the current
+ %% implementation. This causes the aa's to precede the cA's, to make
+ %% the test more interesting.
+ [{cA,1000},{cA,1000}] = ets:match_object(T, {'_', 1000}),
+ ets:match_delete(T, {cA,1000}),
+ [] = ets:match_object(T, {'_', 1000}),
+ ets:delete(T),
+ ?line verify_etsmem(EtsMem).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+firstnext(doc) -> ["Tests ets:first/1 & ets:next/2."];
+firstnext(suite) -> [];
+firstnext(Config) when is_list(Config) ->
+ repeat_for_opts(firstnext_do).
+
+firstnext_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line [] = firstnext_collect(Tab,ets:first(Tab),[]),
+ ?line fill_tab(Tab,foo),
+ ?line Len = length(ets:tab2list(Tab)),
+ ?line Len = length(firstnext_collect(Tab,ets:first(Tab),[])),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+firstnext_collect(_Tab,'$end_of_table',List) ->
+ ?line List;
+firstnext_collect(Tab,Key,List) ->
+ ?line firstnext_collect(Tab,ets:next(Tab,Key),[Key|List]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+firstnext_concurrent(doc) -> "Tests ets:first/1 & ets:next/2.";
+firstnext_concurrent(Config) when is_list(Config) ->
+ register(master, self()),
+ ets_init(?MODULE, 20),
+ [dynamic_go() || _ <- lists:seq(1, 2)],
+ receive
+ after 5000 -> ok
+ end.
+
+ets_init(Tab, N) ->
+ ets:new(Tab, [named_table,public,ordered_set]),
+ cycle(Tab, lists:seq(1,N+1)).
+
+cycle(_Tab, [H|T]) when H > length(T)-> ok;
+cycle(Tab, L) ->
+ ets:insert(Tab,list_to_tuple(L)),
+ cycle(Tab, tl(L)++[hd(L)]).
+
+dynamic_go() -> spawn_link(fun dynamic_init/0).
+
+dynamic_init() -> [dyn_lookup(?MODULE) || _ <- lists:seq(1, 10)].
+
+dyn_lookup(T) -> dyn_lookup(T, ets:first(T)).
+
+dyn_lookup(_T, '$end_of_table') -> [];
+dyn_lookup(T, K) ->
+ NextKey=ets:next(T,K),
+ case ets:next(T,K) of
+ NextKey ->
+ dyn_lookup(T, NextKey);
+ NK ->
+ io:fwrite("hmmm... ~p =/= ~p~n", [NextKey,NK]),
+ exit(failed)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+slot(suite) -> [];
+slot(Config) when is_list(Config) ->
+ repeat_for_opts(slot_do).
+
+slot_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line Elts = ets:info(Tab,size),
+ ?line Elts = slot_loop(Tab,0,0),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+slot_loop(Tab,SlotNo,EltsSoFar) ->
+ ?line case ets:slot(Tab,SlotNo) of
+ '$end_of_table' ->
+ ?line {'EXIT',{badarg,_}} =
+ (catch ets:slot(Tab,SlotNo+1)),
+ ?line EltsSoFar;
+ Elts ->
+ ?line slot_loop(Tab,SlotNo+1,EltsSoFar+length(Elts))
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+match(suite) -> [match1, match2, match_object, match_object2].
+
+match1(suite) -> [];
+match1(Config) when is_list(Config) ->
+ repeat_for_opts(match1_do).
+
+match1_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line [] = ets:match(Tab,{}),
+ ?line ets:insert(Tab,{{one,4},4}),
+ ?line ets:insert(Tab,{{one,5},5}),
+ ?line ets:insert(Tab,{{two,4},4}),
+ ?line ets:insert(Tab,{{two,5},6}),
+ ?line case ets:match(Tab,{{one,'_'},'$0'}) of
+ [[4],[5]] -> ok;
+ [[5],[4]] -> ok
+ end,
+ ?line case ets:match(Tab,{{two,'$1'},'$0'}) of
+ [[4,4],[6,5]] -> ok;
+ [[6,5],[4,4]] -> ok
+ end,
+ ?line case ets:match(Tab,{{two,'$9'},'$4'}) of
+ [[4,4],[6,5]] -> ok;
+ [[6,5],[4,4]] -> ok
+ end,
+ ?line case ets:match(Tab,{{two,'$9'},'$22'}) of
+ [[4,4],[5,6]] -> ok;
+ [[5,6],[4,4]] -> ok
+ end,
+ ?line [[4]] = ets:match(Tab,{{two,'$0'},'$0'}),
+ ?line Len = length(ets:match(Tab,'$0')),
+ ?line Len = length(ets:match(Tab,'_')),
+ ?line if Len > 4 -> ok end,
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match2(doc) -> ["Tests match with specified keypos bag table."];
+match2(suite) -> [];
+match2(Config) when is_list(Config) ->
+ repeat_for_opts(match2_do).
+
+match2_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(foobar,
+ [bag, named_table, {keypos, 2} | Opts],
+ [{value1, key1},
+ {value2_1, key2},
+ {value2_2, key2},
+ {value3_1, key3},
+ {value3_2, key3},
+ {value2_1, key2_wannabe}]),
+ ?line case length(ets:match(Tab, '$1')) of
+ 6 -> ok;
+ _ -> ?t:fail("Length of matched list is wrong.")
+ end,
+ ?line [[value3_1],[value3_2]] = ets:match(Tab, {'$1', key3}),
+ ?line [[key1]] = ets:match(Tab, {value1, '$1'}),
+ ?line [[key2_wannabe],[key2]] = ets:match(Tab, {value2_1, '$2'}),
+ ?line [] = ets:match(Tab,{'$1',nosuchkey}),
+ ?line [] = ets:match(Tab,{'$1',kgY2}), % same hash as key2
+ ?line [] = ets:match(Tab,{nosuchvalue,'$1'}),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match_object(doc) -> ["Some ets:match_object test."];
+match_object(suite) -> [];
+match_object(Config) when is_list(Config) ->
+ repeat_for_opts(match_object_do).
+
+match_object_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foobar, Opts),
+ ?line fill_tab(Tab, foo),
+ ?line ets:insert(Tab, {{one, 4}, 4}),
+ ?line ets:insert(Tab,{{one,5},5}),
+ ?line ets:insert(Tab,{{two,4},4}),
+ ?line ets:insert(Tab,{{two,5},6}),
+ ?line case ets:match_object(Tab, {{one, '_'}, '$0'}) of
+ [{{one,5},5},{{one,4},4}] -> ok;
+ [{{one,4},4},{{one,5},5}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ ?line case ets:match_object(Tab, {{two, '$1'}, '$0'}) of
+ [{{two,5},6},{{two,4},4}] -> ok;
+ [{{two,4},4},{{two,5},6}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ ?line case ets:match_object(Tab, {{two, '$9'}, '$4'}) of
+ [{{two,5},6},{{two,4},4}] -> ok;
+ [{{two,4},4},{{two,5},6}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ ?line case ets:match_object(Tab, {{two, '$9'}, '$22'}) of
+ [{{two,5},6},{{two,4},4}] -> ok;
+ [{{two,4},4},{{two,5},6}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ % Check that unsucessful match returns an empty list.
+ ?line [] = ets:match_object(Tab, {{three,'$0'}, '$92'}),
+ % Check that '$0' equals '_'.
+ Len = length(ets:match_object(Tab, '$0')),
+ Len = length(ets:match_object(Tab, '_')),
+ ?line if Len > 4 -> ok end,
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match_object2(suite) -> [];
+match_object2(doc) -> ["Tests that db_match_object does not generate "
+ "a `badarg' when resuming a search with no "
+ "previous matches."];
+match_object2(Config) when is_list(Config) ->
+ repeat_for_opts(match_object2_do).
+
+match_object2_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo, [bag, {keypos, 2} | Opts]),
+ ?line fill_tab2(Tab, 0, 13005), % match_db_object does 1000
+ % elements per pass, might
+ % change in the future.
+ ?line case catch ets:match_object(Tab, {hej, '$1'}) of
+ {'EXIT', _} ->
+ ets:delete(Tab),
+ ?t:fail("match_object EXIT:ed");
+ [] ->
+ io:format("Nothing matched.");
+ List ->
+ io:format("Matched:~p~n",[List])
+ end,
+ ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+misc(suite) -> [misc1, safe_fixtable, info, dups, tab2list].
+
+tab2list(doc) -> ["Tests tab2list (OTP-3319)"];
+tab2list(suite) -> [];
+tab2list(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(foo,
+ [ordered_set],
+ [{a,b}, {c,b}, {b,b}, {a,c}]),
+ ?line [{a,c},{b,b},{c,b}] = ets:tab2list(Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+misc1(doc) -> ["Simple general small test. ",
+ "If this fails, ets is in really bad shape."];
+misc1(suite) -> [];
+misc1(Config) when is_list(Config) ->
+ repeat_for_opts(misc1_do).
+
+misc1_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line true = lists:member(Tab,ets:all()),
+ ?line ets:delete(Tab),
+ ?line false = lists:member(Tab,ets:all()),
+ ?line case catch ets:delete(Tab) of
+ {'EXIT',_Reason} ->
+ ?line verify_etsmem(EtsMem);
+ true ->
+ ?t:fail("Delete of nonexisting table returned `true'.")
+ end,
+ ok.
+
+safe_fixtable(doc) -> ["Check the safe_fixtable function."];
+safe_fixtable(suite) -> [];
+safe_fixtable(Config) when is_list(Config) ->
+ repeat_for_opts(safe_fixtable_do).
+
+safe_fixtable_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo, Opts),
+ ?line fill_tab(Tab, foobar),
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line receive after 1 -> ok end,
+ ?line true = ets:safe_fixtable(Tab, false),
+ ?line false = ets:info(Tab,safe_fixed),
+ ?line true = ets:safe_fixtable(Tab, true),
+ Self = self(),
+ ?line {{_,_,_},[{Self,1}]} = ets:info(Tab,safe_fixed),
+ %% Test that an unjustified 'unfix' is a no-op.
+ {Pid,MRef} = spawn_monitor(fun() -> true = ets:safe_fixtable(Tab,false) end),
+ {'DOWN', MRef, process, Pid, normal} = receive M -> M end,
+ ?line true = ets:info(Tab,fixed),
+ ?line {{_,_,_},[{Self,1}]} = ets:info(Tab,safe_fixed),
+ %% badarg's
+ ?line {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)),
+ ?line true = ets:info(Tab,fixed),
+ ?line true = ets:safe_fixtable(Tab, false),
+ ?line false = ets:info(Tab,fixed),
+ ?line {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)),
+ ?line false = ets:info(Tab,fixed),
+ ?line ets:delete(Tab),
+ ?line case catch ets:safe_fixtable(Tab, true) of
+ {'EXIT', _Reason} ->
+ ?line verify_etsmem(EtsMem);
+ _ ->
+ ?t:fail("Fixtable on nonexisting table returned `true'")
+ end,
+ ok.
+
+info(doc) -> ["Tests ets:info result for required tuples."];
+info(suite) -> [];
+info(Config) when is_list(Config) ->
+ repeat_for_opts(info_do).
+
+info_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line MeMyselfI=self(),
+ ?line ThisNode=node(),
+ ?line Tab = ets:new(foobar, [{keypos, 2} | Opts]),
+
+ %% Note: ets:info/1 used to return a tuple, but from R11B onwards it
+ %% returns a list.
+ ?line Res = ets:info(Tab),
+ ?line {value, {memory, _Mem}} = lists:keysearch(memory, 1, Res),
+ ?line {value, {owner, MeMyselfI}} = lists:keysearch(owner, 1, Res),
+ ?line {value, {name, foobar}} = lists:keysearch(name, 1, Res),
+ ?line {value, {size, 0}} = lists:keysearch(size, 1, Res),
+ ?line {value, {node, ThisNode}} = lists:keysearch(node, 1, Res),
+ ?line {value, {named_table, false}} = lists:keysearch(named_table, 1, Res),
+ ?line {value, {type, set}} = lists:keysearch(type, 1, Res),
+ ?line {value, {keypos, 2}} = lists:keysearch(keypos, 1, Res),
+ ?line {value, {protection, protected}} =
+ lists:keysearch(protection, 1, Res),
+ ?line true = ets:delete(Tab),
+ ?line undefined = ets:info(non_existing_table_xxyy),
+ ?line undefined = ets:info(non_existing_table_xxyy,type),
+ ?line undefined = ets:info(non_existing_table_xxyy,node),
+ ?line undefined = ets:info(non_existing_table_xxyy,named_table),
+ ?line undefined = ets:info(non_existing_table_xxyy,safe_fixed),
+ ?line verify_etsmem(EtsMem).
+
+dups(doc) -> ["Test various duplicate_bags stuff"];
+dups(suite) -> [];
+dups(Config) when is_list(Config) ->
+ repeat_for_opts(dups_do).
+
+dups_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line T = make_table(funky,
+ [duplicate_bag | Opts],
+ [{1, 2}, {1, 2}]),
+ ?line 2 = length(ets:tab2list(T)),
+ ?line ets:delete(T, 1),
+ ?line [] = ets:lookup(T, 1),
+
+ ?line ets:insert(T, {1, 2, 2}),
+ ?line ets:insert(T, {1, 2, 4}),
+ ?line ets:insert(T, {1, 2, 2}),
+ ?line ets:insert(T, {1, 2, 2}),
+ ?line ets:insert(T, {1, 2, 4}),
+
+ ?line 5 = length(ets:tab2list(T)),
+
+ ?line 5 = length(ets:match(T, {'$1', 2, '$2'})),
+ ?line 3 = length(ets:match(T, {'_', '$1', '$1'})),
+ ?line ets:match_delete(T, {'_', '$1', '$1'}),
+ ?line 0 = length(ets:match(T, {'_', '$1', '$1'})),
+ ?line ets:delete(T),
+ ?line verify_etsmem(EtsMem).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+files(suite) -> [tab2file, tab2file2, tab2file3, tabfile_ext1, tabfile_ext2,
+ tabfile_ext3, tabfile_ext4].
+
+tab2file(doc) -> ["Check the ets:tab2file function on an empty "
+ "ets table."];
+tab2file(suite) -> [];
+tab2file(Config) when is_list(Config) ->
+ %% Write an empty ets table to a file, read back and check properties.
+ ?line Tab = ets:new(ets_SUITE_foo_tab, [named_table, set, private,
+ {keypos, 2}]),
+ ?line FName = filename:join([?config(priv_dir, Config),"tab2file_case"]),
+ ?line ok = ets:tab2file(Tab, FName),
+ ?line true = ets:delete(Tab),
+ %
+ ?line EtsMem = etsmem(),
+ ?line {ok, Tab2} = ets:file2tab(FName),
+ ?line private = ets:info(Tab2, protection),
+ ?line true = ets:info(Tab2, named_table),
+ ?line 2 = ets:info(Tab2, keypos),
+ ?line set = ets:info(Tab2, type),
+ ?line true = ets:delete(Tab2),
+ ?line verify_etsmem(EtsMem).
+
+tab2file2(doc) -> ["Check the ets:tab2file function on a ",
+ "filled set type ets table."];
+tab2file2(suite) -> [];
+tab2file2(Config) when is_list(Config) ->
+ %% Try the same on a filled set table.
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(ets_SUITE_foo_tab, [named_table, set, private,
+ {keypos, 2}]),
+ ?line FName = filename:join([?config(priv_dir, Config),"tab2file2_case"]),
+ ?line ok = fill_tab2(Tab, 0, 10000), % Fill up the table (grucho mucho!)
+ ?line Len = length(ets:tab2list(Tab)),
+ ?line ok = ets:tab2file(Tab, FName),
+ ?line true = ets:delete(Tab),
+ %
+ ?line {ok, Tab2} = ets:file2tab(FName),
+ ?line private = ets:info(Tab2, protection),
+ ?line true = ets:info(Tab2, named_table),
+ ?line 2 = ets:info(Tab2, keypos),
+ ?line set = ets:info(Tab2, type),
+ ?line Len = length(ets:tab2list(Tab2)),
+ ?line true = ets:delete(Tab2),
+ ?line verify_etsmem(EtsMem).
+
+tab2file3(doc) -> ["Check the ets:tab2file function on a ",
+ "filled bag type ets table."];
+tab2file3(suite) -> [];
+tab2file3(Config) when is_list(Config) ->
+ %% Try the same on a filled bag table.
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(ets_SUITE_foo_tab, [named_table, bag, private,
+ {keypos, 2}]),
+ ?line FName = filename:join([?config(priv_dir, Config),"tab2file3_case"]),
+ ?line ok = fill_tab2(Tab, 0, 10000), % Fill up the table (grucho mucho!)
+ ?line Len = length(ets:tab2list(Tab)),
+ ?line Mem = ets:info(Tab, memory),
+ ?line ok = ets:tab2file(Tab, FName),
+ ?line true = ets:delete(Tab),
+
+ ?line {ok, Tab2} = ets:file2tab(FName),
+ ?line private = ets:info(Tab2, protection),
+ ?line true = ets:info(Tab2, named_table),
+ ?line 2 = ets:info(Tab2, keypos),
+ ?line bag = ets:info(Tab2, type),
+ ?line Len = length(ets:tab2list(Tab2)),
+ ?line Mem = ets:info(Tab2, memory),
+ ?line true = ets:delete(Tab2),
+ ?line verify_etsmem(EtsMem).
+
+-define(test_list, [8,5,4,1,58,125,255, 250, 245, 240, 235,
+ 230, Num rem 255, 255, 125, 130, 135, 140, 145,
+ 150, 134, 12, 54, Val rem 255, 12, 3, 6, 9, 126]).
+-define(big_test_list, [Num rem 256|lists:seq(1, 66)]).
+-define(test_integer, 2846287468+Num).
+-define(test_float, 187263.18236-Val).
+-define(test_atom, some_crazy_atom).
+-define(test_tuple, {just, 'Some', 'Tuple', 1, [list, item], Val+Num}).
+
+%% Insert different datatypes into a ets table.
+fill_tab2(_Tab, _Val, 0) ->
+ ok;
+fill_tab2(Tab, Val, Num) ->
+ ?line Item =
+ case Num rem 10 of
+ 0 -> "String";
+ 1 -> ?line ?test_atom;
+ 2 -> ?line ?test_tuple;
+ 3 -> ?line ?test_integer;
+ 4 -> ?line ?test_float;
+ 5 -> ?line list_to_binary(?test_list); %Heap binary
+ 6 -> ?line list_to_binary(?big_test_list); %Refc binary
+ 7 -> ?line make_sub_binary(?test_list, Num); %Sub binary
+ 8 -> ?line ?test_list;
+ 9 -> ?line fun(X) -> {Tab,Val,X*Num} end
+ end,
+ ?line true=ets:insert(Tab, {Item, Val}),
+ ?line fill_tab2(Tab, Val+1, Num-1),
+ ok.
+
+tabfile_ext1(suite) ->
+ [];
+tabfile_ext1(doc) ->
+ ["Tests verification of tables with object count extended_info"];
+tabfile_ext1(Config) when is_list(Config) ->
+ repeat_for_opts(fun(Opts) -> tabfile_ext1_do(Opts, Config) end).
+
+tabfile_ext1_do(Opts,Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"nisse.dat"]),
+ ?line FName2 = filename:join([?config(priv_dir, Config),"countflip.dat"]),
+ L = lists:seq(1,10),
+ T = ets:new(x,Opts),
+ Name = make_ref(),
+ [ets:insert(T,{X,integer_to_list(X)}) || X <- L],
+ ok = ets:tab2file(T,FName,[{extended_info,[object_count]}]),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(
+ element(2,ets:file2tab(FName,[{verify,true}])))),
+ {ok,Name} = disk_log:open([{name,Name},{file,FName}]),
+ {_,[H0|T0]} = disk_log:chunk(Name,start),
+ disk_log:close(Name),
+ LH0=tuple_to_list(H0),
+ {value,{size,N}}=lists:keysearch(size,1,LH0),
+ NewLH0 = lists:keyreplace(size,1,LH0,{size,N-1}),
+ NewH0 = list_to_tuple(NewLH0),
+ NewT0=lists:keydelete(8,1,T0),
+ file:delete(FName2),
+ disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
+ disk_log:log_terms(Name,[NewH0|NewT0]),
+ disk_log:close(Name),
+ 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
+ {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
+ {ok, _} = ets:tabfile_info(FName2),
+ {ok, _} = ets:tabfile_info(FName),
+ file:delete(FName),
+ file:delete(FName2),
+ ok.
+
+tabfile_ext2(suite) ->
+ [];
+tabfile_ext2(doc) ->
+ ["Tests verification of tables with md5sum extended_info"];
+tabfile_ext2(Config) when is_list(Config) ->
+ repeat_for_opts(fun(Opts) -> tabfile_ext2_do(Opts,Config) end).
+
+tabfile_ext2_do(Opts,Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"olle.dat"]),
+ ?line FName2 = filename:join([?config(priv_dir, Config),"bitflip.dat"]),
+ L = lists:seq(1,10),
+ T = ets:new(x,Opts),
+ Name = make_ref(),
+ [ets:insert(T,{X,integer_to_list(X)}) || X <- L],
+ ok = ets:tab2file(T,FName,[{extended_info,[md5sum]}]),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(
+ element(2,ets:file2tab(FName,[{verify,true}])))),
+ {ok, Name} = disk_log:open([{name,Name},{file,FName}]),
+ {_,[H1|T1]} = disk_log:chunk(Name,start),
+ disk_log:close(Name),
+ NewT1=lists:keyreplace(8,1,T1,{8,"9"}),
+ file:delete(FName2),
+ disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
+ disk_log:log_terms(Name,[H1|NewT1]),
+ disk_log:close(Name),
+ {value,{8,"9"}} = lists:keysearch(8,1,
+ ets:tab2list(
+ element(2,ets:file2tab(FName2)))),
+ {error,checksum_error} = ets:file2tab(FName2,[{verify,true}]),
+ {value,{extended_info,[md5sum]}} =
+ lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName2))),
+ {value,{extended_info,[md5sum]}} =
+ lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName))),
+ file:delete(FName),
+ file:delete(FName2),
+ ok.
+
+tabfile_ext3(suite) ->
+ [];
+tabfile_ext3(doc) ->
+ ["Tests verification of (named) tables without extended info"];
+tabfile_ext3(Config) when is_list(Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"namn.dat"]),
+ ?line FName2 = filename:join([?config(priv_dir, Config),"ncountflip.dat"]),
+ L = lists:seq(1,10),
+ Name = make_ref(),
+ ?MODULE = ets:new(?MODULE,[named_table]),
+ [ets:insert(?MODULE,{X,integer_to_list(X)}) || X <- L],
+ ets:tab2file(?MODULE,FName),
+ {error,cannot_create_table} = ets:file2tab(FName),
+ true = ets:delete(?MODULE),
+ {ok,?MODULE} = ets:file2tab(FName),
+ true = ets:delete(?MODULE),
+ disk_log:open([{name,Name},{file,FName}]),
+ {_,[H2|T2]} = disk_log:chunk(Name,start),
+ disk_log:close(Name),
+ NewT2=lists:keydelete(8,1,T2),
+ file:delete(FName2),
+ disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
+ disk_log:log_terms(Name,[H2|NewT2]),
+ disk_log:close(Name),
+ 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
+ true = ets:delete(?MODULE),
+ {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
+ {'EXIT',_} = (catch ets:delete(?MODULE)),
+ {ok,_} = ets:tabfile_info(FName2),
+ {ok,_} = ets:tabfile_info(FName),
+ file:delete(FName),
+ file:delete(FName2),
+ ok.
+
+tabfile_ext4(suite) ->
+ [];
+tabfile_ext4(doc) ->
+ ["Tests verification of large table with md5 sum"];
+tabfile_ext4(Config) when is_list(Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"bauta.dat"]),
+ LL = lists:seq(1,10000),
+ TL = ets:new(x,[]),
+ Name2 = make_ref(),
+ [ets:insert(TL,{X,integer_to_list(X)}) || X <- LL],
+ ok = ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
+ {ok, Name2} = disk_log:open([{name, Name2}, {file, FName},
+ {mode, read_only}]),
+ {C,[_|_]} = disk_log:chunk(Name2,start),
+ {_,[_|_]} = disk_log:chunk(Name2,C),
+ disk_log:close(Name2),
+ true = lists:sort(ets:tab2list(TL)) =:=
+ lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
+ Res = [
+ begin
+ {ok,FD} = file:open(FName,[binary,read,write]),
+ {ok, Bin} = file:pread(FD,0,1000),
+ <<B1:N/binary,Ch:8,B2/binary>> = Bin,
+ Ch2 = (Ch + 1) rem 255,
+ Bin2 = <<B1/binary,Ch2:8,B2/binary>>,
+ ok = file:pwrite(FD,0,Bin2),
+ ok = file:close(FD),
+ X = case ets:file2tab(FName) of
+ {ok,TL2} ->
+ true = lists:sort(ets:tab2list(TL)) =/=
+ lists:sort(ets:tab2list(TL2));
+ _ ->
+ totally_broken
+ end,
+ {error,Y} = ets:file2tab(FName,[{verify,true}]),
+ ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
+ {X,Y}
+ end || N <- lists:seq(400,500) ],
+ io:format("~p~n",[Res]),
+ file:delete(FName),
+ ok.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+make_sub_binary(List, Num) when is_list(List) ->
+ N = Num rem 23,
+ Bin = list_to_binary([lists:seq(0, N)|List]),
+ {_,B} = split_binary(Bin, N+1),
+ B.
+
+heavy(suite) -> [heavy_lookup, heavy_lookup_element].
+
+%% Lookup stuff like crazy...
+heavy_lookup(doc) -> ["Performs multiple lookups for every key ",
+ "in a large table."];
+heavy_lookup(suite) -> [];
+heavy_lookup(Config) when is_list(Config) ->
+ repeat_for_opts(heavy_lookup_do).
+
+heavy_lookup_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foobar_table, [set, protected, {keypos, 2} | Opts]),
+ ?line ok = fill_tab2(Tab, 0, 7000),
+ ?line ?t:do_times(50, ?MODULE, do_lookup, [Tab, 6999]),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+do_lookup(_Tab, 0) -> ok;
+do_lookup(Tab, N) ->
+ case ets:lookup(Tab, N) of
+ [] -> ?t:format("Set #~p was reported as empty. Not valid.",
+ [N]),
+ exit('Invalid lookup');
+ _ -> do_lookup(Tab, N-1)
+ end.
+
+heavy_lookup_element(doc) -> ["Performs multiple lookups for ",
+ "every element in a large table."];
+heavy_lookup_element(suite) -> [];
+heavy_lookup_element(Config) when is_list(Config) ->
+ repeat_for_opts(heavy_lookup_element_do).
+
+heavy_lookup_element_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foobar_table, [set, protected, {keypos, 2} | Opts]),
+ ?line ok = fill_tab2(Tab, 0, 7000),
+ case os:type() of
+ vxworks ->
+ ?line ?t:do_times(5, ?MODULE, do_lookup_element,
+ [Tab, 6999, 1]);
+ % lookup ALL elements 5 times.
+ _ ->
+ ?line ?t:do_times(50, ?MODULE, do_lookup_element,
+ [Tab, 6999, 1])
+ % lookup ALL elements 50 times.
+ end,
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+do_lookup_element(_Tab, 0, _) -> ok;
+do_lookup_element(Tab, N, M) ->
+ ?line case catch ets:lookup_element(Tab, N, M) of
+ {'EXIT', {badarg, _}} ->
+ case M of
+ 1 -> ?t:fail("Set #~p reported as empty. Not valid.",
+ [N]),
+ exit('Invalid lookup_element');
+ _ -> ?line do_lookup_element(Tab, N-1, 1)
+ end;
+ _ -> ?line do_lookup_element(Tab, N, M+1)
+ end.
+
+
+fold(suite) -> [foldl_ordered, foldr_ordered,
+ foldl, foldr,
+ fold_empty].
+
+fold_empty(doc) ->
+ [];
+fold_empty(suite) -> [];
+fold_empty(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(a, [], []),
+ ?line [] = ets:foldl(fun(_X) -> exit(hej) end, [], Tab),
+ ?line [] = ets:foldr(fun(_X) -> exit(hej) end, [], Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldl(doc) ->
+ [];
+foldl(suite) -> [];
+foldl(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [bag], L),
+ ?line LS = lists:sort(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldr(doc) ->
+ [];
+foldr(suite) -> [];
+foldr(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [bag], L),
+ ?line LS = lists:sort(ets:foldr(fun(E,A) -> [E|A] end, [], Tab)),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldl_ordered(doc) ->
+ [];
+foldl_ordered(suite) -> [];
+foldl_ordered(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [ordered_set], L),
+ ?line LS = lists:reverse(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldr_ordered(doc) ->
+ [];
+foldr_ordered(suite) -> [];
+foldr_ordered(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [ordered_set], L),
+ ?line LS = ets:foldr(fun(E,A) -> [E|A] end, [], Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+member(suite) ->
+ [];
+member(doc) ->
+ ["Tests ets:member BIF"];
+member(Config) when is_list(Config) ->
+ repeat_for_opts(member_do, [write_concurrency, all_types]).
+
+member_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line T = ets:new(xxx, Opts),
+ ?line false = ets:member(T,hej),
+ ?line E = fun(0,_F)->ok;
+ (N,F) ->
+ ?line ets:insert(T,{N,N rem 10}),
+ F(N-1,F)
+ end,
+ ?line E(10000,E),
+ ?line false = ets:member(T,hej),
+ ?line true = ets:member(T,1),
+ ?line false = ets:member(T,20000),
+ ?line ets:delete(T,5),
+ ?line false = ets:member(T,5),
+ ?line ets:safe_fixtable(T,true),
+ ?line ets:delete(T,6),
+ ?line false = ets:member(T,6),
+ ?line ets:safe_fixtable(T,false),
+ ?line false = ets:member(T,6),
+ ?line ets:delete(T),
+ ?line {'EXIT',{badarg,_}} = (catch ets:member(finnsinte, 23)),
+ ?line {'EXIT',{badarg,_}} = (catch ets:member(T, 23)),
+ ?line verify_etsmem(EtsMem).
+
+
+build_table(L1,L2,Num) ->
+ T = ets:new(xxx, [ordered_set]
+ ),
+ lists:foreach(
+ fun(X1) ->
+ lists:foreach(
+ fun(X2) ->
+ F = fun(FF,N) ->
+ ets:insert(T,{{X1,X2,N},
+ X1, X2, N}),
+ case N of
+ 0 ->
+ ok;
+ _ ->
+ FF(FF,N-1)
+ end
+ end,
+ F(F,Num)
+ end, L2)
+ end, L1),
+ T.
+
+build_table2(L1,L2,Num) ->
+ T = ets:new(xxx, [ordered_set]
+ ),
+ lists:foreach(
+ fun(X1) ->
+ lists:foreach(
+ fun(X2) ->
+ F = fun(FF,N) ->
+ ets:insert(T,{{N,X1,X2},
+ N, X1, X2}),
+ case N of
+ 0 ->
+ ok;
+ _ ->
+ FF(FF,N-1)
+ end
+ end,
+ F(F,Num)
+ end, L2)
+ end, L1),
+ T.
+
+time_match_object(Tab,Match, Res) ->
+ T1 = erlang:now(),
+ Res = ets:match_object(Tab,Match),
+ T2 = erlang:now(),
+ nowdiff(T1,T2).
+
+time_match(Tab,Match) ->
+ T1 = erlang:now(),
+ ets:match(Tab,Match),
+ T2 = erlang:now(),
+ nowdiff(T1,T2).
+
+seventyfive_percent_success(_,S,Fa,0) ->
+ true = (S > ((S + Fa) * 0.75));
+
+seventyfive_percent_success({M,F,A},S,Fa,N) ->
+ case (catch apply(M,F,A)) of
+ {'EXIT', _} ->
+ seventyfive_percent_success({M,F,A},S,Fa+1,N-1);
+ _ ->
+ seventyfive_percent_success({M,F,A},S+1,Fa,N-1)
+ end.
+
+fifty_percent_success(_,S,Fa,0) ->
+ true = (S > ((S + Fa) * 0.5));
+
+fifty_percent_success({M,F,A},S,Fa,N) ->
+ case (catch apply(M,F,A)) of
+ {'EXIT', _} ->
+ fifty_percent_success({M,F,A},S,Fa+1,N-1);
+ _ ->
+ fifty_percent_success({M,F,A},S+1,Fa,N-1)
+ end.
+
+
+nowtonumber({Mega, Secs, Milli}) ->
+ Milli + Secs * 1000000 + Mega * 1000000000000.
+nowdiff(T1,T2) ->
+ nowtonumber(T2) - nowtonumber(T1).
+
+create_random_string(0) ->
+ [];
+
+create_random_string(OfLength) ->
+ C = case random:uniform(2) of
+ 1 ->
+ (random:uniform($Z - $A + 1) - 1) + $A;
+ _ ->
+ (random:uniform($z - $a + 1) - 1) + $a
+ end,
+ [C | create_random_string(OfLength - 1)].
+
+
+create_random_tuple(OfLength) ->
+ list_to_tuple(lists:map(fun(X) ->
+ list_to_atom([X])
+ end,create_random_string(OfLength))).
+
+create_partly_bound_tuple(OfLength) ->
+ case random:uniform(2) of
+ 1 ->
+ create_partly_bound_tuple1(OfLength);
+ _ ->
+ create_partly_bound_tuple3(OfLength)
+ end.
+
+create_partly_bound_tuple1(OfLength) ->
+ T0 = create_random_tuple(OfLength),
+ I = random:uniform(OfLength),
+ setelement(I,T0,'$1').
+
+
+set_n_random_elements(T0,0,_,_) ->
+ T0;
+set_n_random_elements(T0,N,OfLength,GenFun) ->
+ I = random:uniform(OfLength),
+ What = GenFun(I),
+ case element(I,T0) of
+ What ->
+ set_n_random_elements(T0,N,OfLength,GenFun);
+ _Else ->
+ set_n_random_elements(setelement(I,T0,What),
+ N-1,OfLength,GenFun)
+ end.
+
+make_dollar_atom(I) ->
+ list_to_atom([$$] ++ integer_to_list(I)).
+create_partly_bound_tuple2(OfLength) ->
+ T0 = create_random_tuple(OfLength),
+ I = random:uniform(OfLength - 1),
+ set_n_random_elements(T0,I,OfLength,fun make_dollar_atom/1).
+
+create_partly_bound_tuple3(OfLength) ->
+ T0 = create_random_tuple(OfLength),
+ I = random:uniform(OfLength - 1),
+ set_n_random_elements(T0,I,OfLength,fun(_) -> '_' end).
+
+do_n_times(_,0) ->
+ ok;
+do_n_times(Fun,N) ->
+ Fun(),
+ case N rem 1000 of
+ 0 ->
+ io:format(".");
+ _ ->
+ ok
+ end,
+ do_n_times(Fun,N-1).
+
+make_table(Name, Options, Elements) ->
+ T = ets:new(Name, Options),
+ lists:foreach(fun(E) -> ets:insert(T, E) end, Elements),
+ T.
+filltabint(Tab,0) ->
+ Tab;
+filltabint(Tab,N) ->
+ ets:insert(Tab,{N,integer_to_list(N)}),
+ filltabint(Tab,N-1).
+filltabint2(Tab,0) ->
+ Tab;
+filltabint2(Tab,N) ->
+ ets:insert(Tab,{N + N rem 2,integer_to_list(N)}),
+ filltabint2(Tab,N-1).
+filltabint3(Tab,0) ->
+ Tab;
+filltabint3(Tab,N) ->
+ ets:insert(Tab,{N + N rem 2,integer_to_list(N + N rem 2)}),
+ filltabint3(Tab,N-1).
+xfilltabint(Tab,N) ->
+ case ets:info(Tab,type) of
+ bag ->
+ filltabint2(Tab,N);
+ duplicate_bag ->
+ ets:select_delete(Tab,[{'_',[],[true]}]),
+ filltabint3(Tab,N);
+ _ ->
+ filltabint(Tab,N)
+ end.
+
+
+filltabstr(Tab,N) ->
+ filltabstr(Tab,0,N).
+filltabstr(Tab,N,N) ->
+ Tab;
+filltabstr(Tab,Floor,N) when N > Floor ->
+ ets:insert(Tab,{integer_to_list(N),N}),
+ filltabstr(Tab,Floor,N-1).
+
+filltabstr2(Tab,0) ->
+ Tab;
+filltabstr2(Tab,N) ->
+ ets:insert(Tab,{integer_to_list(N),N}),
+ ets:insert(Tab,{integer_to_list(N),N+1}),
+ filltabstr2(Tab,N-1).
+filltabstr3(Tab,0) ->
+ Tab;
+filltabstr3(Tab,N) ->
+ ets:insert(Tab,{integer_to_list(N),N}),
+ ets:insert(Tab,{integer_to_list(N),N}),
+ filltabstr3(Tab,N-1).
+xfilltabstr(Tab,N) ->
+ case ets:info(Tab,type) of
+ bag ->
+ filltabstr2(Tab,N);
+ duplicate_bag ->
+ ets:select_delete(Tab,[{'_',[],[true]}]),
+ filltabstr3(Tab,N);
+ _ ->
+ filltabstr(Tab,N)
+ end.
+
+fill_sets_int(N) ->
+ fill_sets_int(N,[]).
+fill_sets_int(N,Opts) ->
+ Tab1 = ets:new(xxx, [ordered_set|Opts]),
+ filltabint(Tab1,N),
+ Tab2 = ets:new(xxx, [set|Opts]),
+ filltabint(Tab2,N),
+ Tab3 = ets:new(xxx, [bag|Opts]),
+ filltabint2(Tab3,N),
+ Tab4 = ets:new(xxx, [duplicate_bag|Opts]),
+ filltabint3(Tab4,N),
+ [Tab1,Tab2,Tab3,Tab4].
+
+check_fun(_Tab,_Fun,'$end_of_table') ->
+ ok;
+check_fun(Tab,Fun,Item) ->
+ lists:foreach(fun(Obj) ->
+ true = Fun(Obj)
+ end,
+ ets:lookup(Tab,Item)),
+ check_fun(Tab,Fun,ets:next(Tab,Item)).
+
+check(Tab,Fun,N) ->
+ N = ets:info(Tab, size),
+ check_fun(Tab,Fun,ets:first(Tab)).
+
+
+
+del_one_by_one_set(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_set(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From, integer_to_list(From)}),
+ N = (ets:info(T,size) + 1),
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_set(T,Next,To).
+
+del_one_by_one_bag(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_bag(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From + From rem 2, integer_to_list(From)}),
+ N = (ets:info(T,size) + 1),
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_bag(T,Next,To).
+
+
+del_one_by_one_dbag_1(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_dbag_1(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From, integer_to_list(From)}),
+ case From rem 2 of
+ 0 ->
+ N = (ets:info(T,size) + 2);
+ 1 ->
+ N = ets:info(T,size)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_dbag_1(T,Next,To).
+
+del_one_by_one_dbag_2(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_dbag_2(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From, integer_to_list(From)}),
+ case From rem 2 of
+ 0 ->
+ N = (ets:info(T,size) + 3);
+ 1 ->
+ N = (ets:info(T,size) + 1)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_dbag_2(T,Next,To).
+
+del_one_by_one_dbag_3(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_dbag_3(T,From,To) ->
+ N = ets:info(T,size),
+ Obj = {From + From rem 2, integer_to_list(From)},
+ ets:delete_object(T,Obj),
+ case From rem 2 of
+ 0 ->
+ N = (ets:info(T,size) + 2);
+ 1 ->
+ N = (ets:info(T,size) + 1),
+ Obj2 = {From, integer_to_list(From)},
+ ets:delete_object(T,Obj2),
+ N = (ets:info(T,size) + 2)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_dbag_3(T,Next,To).
+
+
+successive_delete(Table,From,To,Type) ->
+ successive_delete(Table,From,To,Type,ets:info(Table,type)).
+
+successive_delete(_Table,N,N,_,_) ->
+ ok;
+successive_delete(Table,From,To,Type,TType) ->
+ MS = case Type of
+ bound ->
+ [{{From,'_'},[],[true]}];
+ unbound ->
+ [{{'$1','_'},[],[{'==', '$1', From}]}]
+ end,
+ case TType of
+ X when X == bag; X == duplicate_bag ->
+ %erlang:display(From),
+ case From rem 2 of
+ 0 ->
+ 2 = ets:select_delete(Table,MS);
+ _ ->
+ 0 = ets:select_delete(Table,MS)
+ end;
+ _ ->
+ 1 = ets:select_delete(Table,MS)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ successive_delete(Table, Next, To, Type,TType).
+
+gen_dets_filename(Config,N) ->
+ filename:join(?config(priv_dir,Config),
+ "testdets_" ++ integer_to_list(N) ++ ".dets").
+
+otp_6842_select_1000(Config) when is_list(Config) ->
+ ?line Tab = ets:new(xxx,[ordered_set]),
+ ?line [ets:insert(Tab,{X,X}) || X <- lists:seq(1,10000)],
+ ?line AllTrue = lists:duplicate(10,true),
+ ?line AllTrue =
+ [ length(
+ element(1,
+ ets:select(Tab,[{'_',[],['$_']}],X*1000))) =:=
+ X*1000 || X <- lists:seq(1,10) ],
+ ?line Sequences = [[1000,1000,1000,1000,1000,1000,1000,1000,1000,1000],
+ [2000,2000,2000,2000,2000],
+ [3000,3000,3000,1000],
+ [4000,4000,2000],
+ [5000,5000],
+ [6000,4000],
+ [7000,3000],
+ [8000,2000],
+ [9000,1000],
+ [10000]],
+ ?line AllTrue = [ check_seq(Tab, ets:select(Tab,[{'_',[],['$_']}],hd(L)),L) ||
+ L <- Sequences ],
+ ?line ets:delete(Tab),
+ ok.
+
+check_seq(_,'$end_of_table',[]) ->
+ true;
+check_seq(Tab,{L,C},[H|T]) when length(L) =:= H ->
+ check_seq(Tab, ets:select(C),T);
+check_seq(A,B,C) ->
+ erlang:display({A,B,C}),
+ false.
+
+otp_6338(Config) when is_list(Config) ->
+ L = binary_to_term(<<131,108,0,0,0,2,104,2,108,0,0,0,2,103,100,0,19,112,112,98,49,95,98,115,49,50,64,98,108,97,100,101,95,48,95,53,0,0,33,50,0,0,0,4,1,98,0,0,23,226,106,100,0,4,101,120,105,116,104,2,108,0,0,0,2,104,2,100,0,3,115,98,109,100,0,19,112,112,98,50,95,98,115,49,50,64,98,108,97,100,101,95,48,95,56,98,0,0,18,231,106,100,0,4,114,101,99,118,106>>),
+ T = ets:new(xxx,[ordered_set]),
+ lists:foreach(fun(X) -> ets:insert(T,X) end,L),
+ [[4839,recv]] = ets:match(T,{[{sbm,ppb2_bs12@blade_0_8},'$1'],'$2'}),
+ ets:delete(T).
+
+%% Elements could come in the wrong order in a bag if a rehash occurred.
+otp_5340(Config) when is_list(Config) ->
+ repeat_for_opts(otp_5340_do).
+
+otp_5340_do(Opts) ->
+ N = 3000,
+ T = ets:new(otp_5340, [bag,public | Opts]),
+ Ids = [1,2,3,4,5],
+ [w(T, N, Id) || Id <- Ids],
+ verify(T, Ids),
+ ets:delete(T).
+
+w(_,0, _) -> ok;
+w(T,N, Id) ->
+ ets:insert(T, {N, Id}),
+ w(T,N-1,Id).
+
+verify(T, Ids) ->
+ List = my_tab_to_list(T),
+ Errors = lists:filter(fun(Bucket) ->
+ verify2(Bucket, Ids)
+ end, List),
+ case Errors of
+ [] ->
+ ok;
+ _ ->
+ io:format("Failed:\n~p\n", [Errors]),
+ ?t:fail()
+ end.
+
+verify2([{_N,Id}|RL], [Id|R]) ->
+ verify2(RL,R);
+verify2([],[]) -> false;
+verify2(_Err, _) ->
+ true.
+
+otp_7665(doc) -> ["delete_object followed by delete on fixed bag failed to delete objects."];
+otp_7665(suite) -> [];
+otp_7665(Config) when is_list(Config) ->
+ repeat_for_opts(otp_7665_do).
+
+otp_7665_do(Opts) ->
+ Tab = ets:new(otp_7665,[bag | Opts]),
+ Min = 0,
+ Max = 10,
+ lists:foreach(fun(N)-> otp_7665_act(Tab,Min,Max,N) end,
+ lists:seq(Min,Max)),
+ ?line true = ets:delete(Tab).
+
+otp_7665_act(Tab,Min,Max,DelNr) ->
+ List1 = [{key,N} || N <- lists:seq(Min,Max)],
+ ?line true = ets:insert(Tab, List1),
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line true = ets:delete_object(Tab, {key,DelNr}),
+ List2 = lists:delete({key,DelNr}, List1),
+
+ %% Now verify that we find all remaining objects
+ ?line List2 = ets:lookup(Tab,key),
+ ?line EList2 = lists:map(fun({key,N})-> N end,
+ List2),
+ ?line EList2 = ets:lookup_element(Tab,key,2),
+ ?line true = ets:delete(Tab, key),
+ ?line [] = ets:lookup(Tab, key),
+ ?line true = ets:safe_fixtable(Tab, false),
+ ok.
+
+%% Whitebox testing of meta name table hashing.
+meta_wb(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line erts_debug:set_internal_state(available_internal_state, true),
+ try
+ repeat_for_opts(meta_wb_do)
+ after
+ erts_debug:set_internal_state(available_internal_state, false)
+ end,
+ ?line verify_etsmem(EtsMem).
+
+
+meta_wb_do(Opts) ->
+ %% Do random new/delete/rename of colliding named tables
+ Names = [pioneer | colliding_names(pioneer)],
+ Len = length(Names),
+ OpFuns = {fun meta_wb_new/4, fun meta_wb_delete/4, fun meta_wb_rename/4},
+
+ io:format("Colliding names = ~p\n",[Names]),
+ F = fun(0,_,_) -> ok;
+ (N,Tabs,Me) -> Name1 = lists:nth(random:uniform(Len),Names),
+ Name2 = lists:nth(random:uniform(Len),Names),
+ Op = element(random:uniform(3),OpFuns),
+ NTabs = Op(Name1, Name2, Tabs, Opts),
+ Me(N-1,NTabs,Me)
+ end,
+ F(Len*100, [], F),
+
+ % cleanup
+ lists:foreach(fun(Name)->catch ets:delete(Name) end,
+ Names).
+
+meta_wb_new(Name, _, Tabs, Opts) ->
+ case (catch ets:new(Name,[named_table|Opts])) of
+ Name ->
+ ?line false = lists:member(Name, Tabs),
+ [Name | Tabs];
+ {'EXIT',{badarg,_}} ->
+ ?line true = lists:member(Name, Tabs),
+ Tabs
+ end.
+meta_wb_delete(Name, _, Tabs, _) ->
+ case (catch ets:delete(Name)) of
+ true ->
+ ?line true = lists:member(Name, Tabs),
+ lists:delete(Name, Tabs);
+ {'EXIT',{badarg,_}} ->
+ ?line false = lists:member(Name, Tabs),
+ Tabs
+ end.
+meta_wb_rename(Old, New, Tabs, _) ->
+ case (catch ets:rename(Old,New)) of
+ New ->
+ ?line true = lists:member(Old, Tabs)
+ andalso not lists:member(New, Tabs),
+ [New | lists:delete(Old, Tabs)];
+ {'EXIT',{badarg,_}} ->
+ ?line true = not lists:member(Old, Tabs)
+ orelse lists:member(New,Tabs),
+ Tabs
+ end.
+
+
+colliding_names(Name) ->
+ erts_debug:set_internal_state(colliding_names, {Name,5}).
+
+
+%% OTP_6913: Grow and shrink.
+
+grow_shrink(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ grow_shrink_0(lists:seq(3071, 5000), EtsMem).
+
+grow_shrink_0([N|Ns], EtsMem) ->
+ ?line grow_shrink_1(N, [set]),
+ ?line grow_shrink_1(N, [ordered_set]),
+ ?line verify_etsmem(EtsMem),
+ grow_shrink_0(Ns, EtsMem);
+grow_shrink_0([], _) -> ok.
+
+grow_shrink_1(N, Flags) ->
+ ?line T = ets:new(a, Flags),
+ ?line grow_shrink_2(N, N, T),
+ ?line ets:delete(T).
+
+grow_shrink_2(0, Orig, T) ->
+ List = [{I,a} || I <- lists:seq(1, Orig)],
+ List = lists:sort(ets:tab2list(T)),
+ grow_shrink_3(Orig, T);
+grow_shrink_2(N, Orig, T) ->
+ true = ets:insert(T, {N,a}),
+ grow_shrink_2(N-1, Orig, T).
+
+grow_shrink_3(0, T) ->
+ [] = ets:tab2list(T);
+grow_shrink_3(N, T) ->
+ true = ets:delete(T, N),
+ grow_shrink_3(N-1, T).
+
+grow_pseudo_deleted(doc) -> ["Grow a table that still contains pseudo-deleted objects"];
+grow_pseudo_deleted(suite) -> [];
+grow_pseudo_deleted(Config) when is_list(Config) ->
+ only_if_smp(fun() -> grow_pseudo_deleted_do() end).
+
+grow_pseudo_deleted_do() ->
+ lists:foreach(fun(Type) -> grow_pseudo_deleted_do(Type) end,
+ [set,bag,duplicate_bag]).
+
+grow_pseudo_deleted_do(Type) ->
+ process_flag(scheduler,1),
+ Self = self(),
+ ?line T = ets:new(kalle,[Type,public,{write_concurrency,true}]),
+ Mod = 7, Mult = 10000,
+ filltabint(T,Mod*Mult),
+ ?line true = ets:safe_fixtable(T,true),
+ ?line Mult = ets:select_delete(T,
+ [{{'$1', '_'},
+ [{'=:=', {'rem', '$1', Mod}, 0}],
+ [true]}]),
+ Left = Mult*(Mod-1),
+ ?line Left = ets:info(T,size),
+ ?line Mult = ets:info(T,kept_objects),
+ filltabstr(T,Mult),
+ spawn_opt(fun()-> ?line true = ets:info(T,fixed),
+ Self ! start,
+ io:format("Starting to filltabstr... ~p\n",[now()]),
+ filltabstr(T,Mult,Mult+10000),
+ io:format("Done with filltabstr. ~p\n",[now()]),
+ Self ! done
+ end, [link, {scheduler,2}]),
+ ?line start = receive_any(),
+ io:format("Unfixing table...~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line true = ets:safe_fixtable(T,false),
+ io:format("Unfix table done. ~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ ?line done = receive_any(),
+ %%verify_table_load(T), % may fail if concurrency is poor (genny)
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+shrink_pseudo_deleted(doc) -> ["Shrink a table that still contains pseudo-deleted objects"];
+shrink_pseudo_deleted(suite) -> [];
+shrink_pseudo_deleted(Config) when is_list(Config) ->
+ only_if_smp(fun()->shrink_pseudo_deleted_do() end).
+
+shrink_pseudo_deleted_do() ->
+ lists:foreach(fun(Type) -> shrink_pseudo_deleted_do(Type) end,
+ [set,bag,duplicate_bag]).
+
+shrink_pseudo_deleted_do(Type) ->
+ process_flag(scheduler,1),
+ Self = self(),
+ ?line T = ets:new(kalle,[Type,public,{write_concurrency,true}]),
+ Half = 10000,
+ filltabint(T,Half*2),
+ ?line true = ets:safe_fixtable(T,true),
+ ?line Half = ets:select_delete(T,
+ [{{'$1', '_'},
+ [{'>', '$1', Half}],
+ [true]}]),
+ ?line Half = ets:info(T,size),
+ ?line Half = ets:info(T,kept_objects),
+ spawn_opt(fun()-> ?line true = ets:info(T,fixed),
+ Self ! start,
+ io:format("Starting to delete... ~p\n",[now()]),
+ del_one_by_one_set(T,1,Half+1),
+ io:format("Done with delete. ~p\n",[now()]),
+ Self ! done
+ end, [link, {scheduler,2}]),
+ ?line start = receive_any(),
+ io:format("Unfixing table...~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line true = ets:safe_fixtable(T,false),
+ io:format("Unfix table done. ~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ ?line done = receive_any(),
+ %%verify_table_load(T), % may fail if concurrency is poor (genny)
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+
+meta_smp(suite) ->
+ [meta_lookup_unnamed_read,
+ meta_lookup_unnamed_write,
+ meta_lookup_named_read,
+ meta_lookup_named_write,
+ meta_newdel_unnamed,
+ meta_newdel_named].
+
+meta_lookup_unnamed_read(suite) -> [];
+meta_lookup_unnamed_read(Config) when is_list(Config) ->
+ InitF = fun(_) -> Tab = ets:new(unnamed,[]),
+ true = ets:insert(Tab,{key,data}),
+ Tab
+ end,
+ ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key),
+ Tab
+ end,
+ FiniF = fun(Tab) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_lookup_unnamed_write(suite) -> [];
+meta_lookup_unnamed_write(Config) when is_list(Config) ->
+ InitF = fun(_) -> Tab = ets:new(unnamed,[]),
+ {Tab,0}
+ end,
+ ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}),
+ {Tab,N+1}
+ end,
+ FiniF = fun({Tab,_}) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_lookup_named_read(suite) -> [];
+meta_lookup_named_read(Config) when is_list(Config) ->
+ InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)),
+ Tab = ets:new(Name,[named_table]),
+ true = ets:insert(Tab,{key,data}),
+ Tab
+ end,
+ ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key),
+ Tab
+ end,
+ FiniF = fun(Tab) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_lookup_named_write(suite) -> [];
+meta_lookup_named_write(Config) when is_list(Config) ->
+ InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)),
+ Tab = ets:new(Name,[named_table]),
+ {Tab,0}
+ end,
+ ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}),
+ {Tab,N+1}
+ end,
+ FiniF = fun({Tab,_}) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_newdel_unnamed(suite) -> [];
+meta_newdel_unnamed(Config) when is_list(Config) ->
+ InitF = fun(_) -> ok end,
+ ExecF = fun(_) -> Tab = ets:new(unnamed,[]),
+ true = ets:delete(Tab)
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_newdel_named(suite) -> [];
+meta_newdel_named(Config) when is_list(Config) ->
+ InitF = fun([ProcN|_]) -> list_to_atom(integer_to_list(ProcN))
+ end,
+ ExecF = fun(Name) -> Name = ets:new(Name,[named_table]),
+ true = ets:delete(Name),
+ Name
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+smp_insert(doc) -> ["Concurrent insert's on same table"];
+smp_insert(suite) -> [];
+smp_insert(Config) when is_list(Config) ->
+ ets:new(smp_insert,[named_table,public,{write_concurrency,true}]),
+ InitF = fun(_) -> ok end,
+ ExecF = fun(_) -> true = ets:insert(smp_insert,{random:uniform(10000)})
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers(InitF,ExecF,FiniF,100000),
+ verify_table_load(smp_insert),
+ ets:delete(smp_insert).
+
+smp_fixed_delete(doc) -> ["Concurrent delete's on same fixated table"];
+smp_fixed_delete(suite) -> [];
+smp_fixed_delete(Config) when is_list(Config) ->
+ only_if_smp(fun()->smp_fixed_delete_do() end).
+
+smp_fixed_delete_do() ->
+ T = ets:new(foo,[public,{write_concurrency,true}]),
+ %%Mem = ets:info(T,memory),
+ NumOfObjs = 100000,
+ filltabint(T,NumOfObjs),
+ ets:safe_fixtable(T,true),
+ Buckets = num_of_buckets(T),
+ InitF = fun([ProcN,NumOfProcs|_]) -> {ProcN,NumOfProcs} end,
+ ExecF = fun({Key,_}) when Key > NumOfObjs ->
+ [end_of_work];
+ ({Key,Increment}) ->
+ true = ets:delete(T,Key),
+ {Key+Increment,Increment}
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers_do(InitF,ExecF,FiniF,NumOfObjs),
+ ?line 0 = ets:info(T,size),
+ ?line true = ets:info(T,fixed),
+ ?line Buckets = num_of_buckets(T),
+ ?line NumOfObjs = ets:info(T,kept_objects),
+ ets:safe_fixtable(T,false),
+ %% Will fail as unfix does not shrink the table:
+ %%?line Mem = ets:info(T,memory),
+ %%verify_table_load(T),
+ ets:delete(T).
+
+num_of_buckets(T) ->
+ ?line element(1,ets:info(T,stats)).
+
+smp_unfix_fix(doc) -> ["Fixate hash table while other process is busy doing unfix"];
+smp_unfix_fix(suite) -> [];
+smp_unfix_fix(Config) when is_list(Config) ->
+ only_if_smp(fun()-> smp_unfix_fix_do() end).
+
+smp_unfix_fix_do() ->
+ process_flag(scheduler,1),
+ Parent = self(),
+ T = ets:new(foo,[public,{write_concurrency,true}]),
+ %%Mem = ets:info(T,memory),
+ NumOfObjs = 100000,
+ Deleted = 50000,
+ filltabint(T,NumOfObjs),
+ ets:safe_fixtable(T,true),
+ Buckets = num_of_buckets(T),
+ ?line Deleted = ets:select_delete(T,[{{'$1', '_'},
+ [{'=<','$1', Deleted}],
+ [true]}]),
+ ?line Buckets = num_of_buckets(T),
+ Left = NumOfObjs - Deleted,
+ ?line Left = ets:info(T,size),
+ ?line true = ets:info(T,fixed),
+ ?line Deleted = ets:info(T,kept_objects),
+
+ {Child, Mref} =
+ spawn_opt(fun()-> ?line true = ets:info(T,fixed),
+ Parent ! start,
+ io:format("Child waiting for table to be unfixed... now=~p mem=~p\n",
+ [now(),ets:info(T,memory)]),
+ repeat_while(fun()-> ets:info(T,fixed) end),
+ io:format("Table unfixed. Child Fixating! now=~p mem=~p\n",
+ [now(),ets:info(T,memory)]),
+ ?line true = ets:safe_fixtable(T,true),
+ repeat_while(fun(Key) when Key =< NumOfObjs ->
+ ets:delete(T,Key), {true,Key+1};
+ (Key) -> {false,Key}
+ end,
+ Deleted),
+ ?line 0 = ets:info(T,size),
+ ?line true = ets:info(T,kept_objects) >= Left,
+ ?line done = receive_any()
+ end,
+ [link, monitor, {scheduler,2}]),
+
+ ?line start = receive_any(),
+ ?line true = ets:info(T,fixed),
+ io:format("Parent starting to unfix... ~p\n",[now()]),
+ ets:safe_fixtable(T,false),
+ io:format("Parent done with unfix. ~p\n",[now()]),
+ Child ! done,
+ {'DOWN', Mref, process, Child, normal} = receive_any(),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ %%verify_table_load(T),
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+otp_8166(doc) -> ["Unsafe unfix was done by trapping select/match"];
+otp_8166(suite) -> [];
+otp_8166(Config) when is_list(Config) ->
+ only_if_smp(3, fun()-> otp_8166_do(false),
+ otp_8166_do(true)
+ end).
+
+otp_8166_do(WC) ->
+ %% Bug scenario: One process segv while reading the table because another
+ %% process is doing unfix without write-lock at the end of a trapping match_object.
+ process_flag(scheduler,1),
+ T = ets:new(foo,[public, {write_concurrency,WC}]),
+ NumOfObjs = 3000, %% Need more than 1000 live objects for match_object to trap one time
+ Deleted = NumOfObjs div 2,
+ filltabint(T,NumOfObjs),
+ {ReaderPid, ReaderMref} =
+ spawn_opt(fun()-> otp_8166_reader(T,NumOfObjs) end,
+ [link, monitor, {scheduler,2}]),
+ {ZombieCrPid, ZombieCrMref} =
+ spawn_opt(fun()-> otp_8166_zombie_creator(T,Deleted) end,
+ [link, monitor, {scheduler,3}]),
+
+ repeat(fun() -> ZombieCrPid ! {loop, self()},
+ zombies_created = receive_any(),
+ otp_8166_trapper(T, 10, ZombieCrPid)
+ end,
+ 100),
+
+ ReaderPid ! quit,
+ {'DOWN', ReaderMref, process, ReaderPid, normal} = receive_any(),
+ ZombieCrPid ! quit,
+ {'DOWN', ZombieCrMref, process, ZombieCrPid, normal} = receive_any(),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ %%verify_table_load(T),
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+%% Keep reading the table
+otp_8166_reader(T, NumOfObjs) ->
+ repeat_while(fun(0) ->
+ receive quit -> {false,done}
+ after 0 -> {true,NumOfObjs}
+ end;
+ (Key) ->
+ ets:lookup(T,Key),
+ {true, Key-1}
+ end,
+ NumOfObjs).
+
+%% Do a match_object that will trap and thereby fixate and then unfixate the table
+otp_8166_trapper(T, Try, ZombieCrPid) ->
+ [] = ets:match_object(T,{'_',"Pink Unicorn"}),
+ case {ets:info(T,fixed),Try} of
+ {true,1} ->
+ io:format("failed to provoke unsafe unfix, give up...\n",[]),
+ ZombieCrPid ! unfix;
+ {true,_} ->
+ io:format("trapper too fast, trying again...\n",[]),
+ otp_8166_trapper(T, Try-1, ZombieCrPid);
+ {false,_} -> done
+ end.
+
+
+%% Fixate table and create some pseudo-deleted objects (zombies)
+%% Then wait for trapper to fixate before unfixing, as we want the trappers'
+%% unfix to be the one that purges the zombies.
+otp_8166_zombie_creator(T,Deleted) ->
+ case receive_any() of
+ quit -> done;
+
+ {loop,Pid} ->
+ filltabint(T,Deleted),
+ ets:safe_fixtable(T,true),
+ ?line Deleted = ets:select_delete(T,[{{'$1', '_'},
+ [{'=<','$1', Deleted}],
+ [true]}]),
+ Pid ! zombies_created,
+ repeat_while(fun() -> case ets:info(T,safe_fixed) of
+ {_,[_P1,_P2]} ->
+ false;
+ _ ->
+ receive unfix -> false
+ after 0 -> true
+ end
+ end
+ end),
+ ets:safe_fixtable(T,false),
+ otp_8166_zombie_creator(T,Deleted);
+
+ unfix ->
+ io:format("ignore unfix in outer loop?\n",[]),
+ otp_8166_zombie_creator(T,Deleted)
+ end.
+
+
+
+
+verify_table_load(T) ->
+ ?line Stats = ets:info(T,stats),
+ ?line {Buckets,AvgLen,StdDev,ExpSD,_MinLen,_MaxLen} = Stats,
+ ?line ok = if
+ AvgLen > 7 ->
+ io:format("Table overloaded: Stats=~p\n~p\n",
+ [Stats, ets:info(T)]),
+ false;
+
+ Buckets>256, AvgLen < 6 ->
+ io:format("Table underloaded: Stats=~p\n~p\n",
+ [Stats, ets:info(T)]),
+ false;
+
+ StdDev > ExpSD*2 ->
+ io:format("Too large standard deviation (poor hashing?),"
+ " stats=~p\n~p\n",[Stats, ets:info(T)]),
+ false;
+
+ true ->
+ io:format("Stats = ~p\n",[Stats]),
+ ok
+ end.
+
+
+
+
+
+smp_select_delete(suite) -> [];
+smp_select_delete(doc) ->
+ ["Run concurrent select_delete (and inserts) on same table."];
+smp_select_delete(Config) when is_list(Config) ->
+ T = ets:new(smp_select_delete,[named_table,public,{write_concurrency,true}]),
+ Mod = 17,
+ Zeros = erlang:make_tuple(Mod,0),
+ InitF = fun(_) -> Zeros end,
+ ExecF = fun(Diffs0) ->
+ case random:uniform(20) of
+ 1 ->
+ Mod = 17,
+ Eq = random:uniform(Mod) - 1,
+ Deleted = ets:select_delete(T,
+ [{{'_', '$1'},
+ [{'=:=', {'rem', '$1', Mod}, Eq}],
+ [true]}]),
+ Diffs1 = setelement(Eq+1, Diffs0,
+ element(Eq+1,Diffs0) - Deleted),
+ Diffs1;
+ _ ->
+ Key = random:uniform(10000),
+ Eq = Key rem Mod,
+ ?line case ets:insert_new(T,{Key,Key}) of
+ true ->
+ Diffs1 = setelement(Eq+1, Diffs0,
+ element(Eq+1,Diffs0)+1),
+ Diffs1;
+ false -> Diffs0
+ end
+ end
+ end,
+ FiniF = fun(Result) -> Result end,
+ Results = run_workers_do(InitF,ExecF,FiniF,20000),
+ ?line TotCnts = lists:foldl(fun(Diffs, Sum) -> add_lists(Sum,tuple_to_list(Diffs)) end,
+ lists:duplicate(Mod, 0), Results),
+ io:format("TotCnts = ~p\n",[TotCnts]),
+ ?line LeftInTab = lists:foldl(fun(N,Sum) -> Sum+N end,
+ 0, TotCnts),
+ io:format("LeftInTab = ~p\n",[LeftInTab]),
+ ?line LeftInTab = ets:info(T,size),
+ lists:foldl(fun(Cnt,Eq) ->
+ WasCnt = ets:select_count(T,
+ [{{'_', '$1'},
+ [{'=:=', {'rem', '$1', Mod}, Eq}],
+ [true]}]),
+ io:format("~p: ~p =?= ~p\n",[Eq,Cnt,WasCnt]),
+ ?line Cnt = WasCnt,
+ Eq+1
+ end,
+ 0, TotCnts),
+ verify_table_load(T),
+ ?line LeftInTab = ets:select_delete(T, [{{'$1','$1'}, [], [true]}]),
+ ?line 0 = ets:info(T,size),
+ ?line false = ets:info(T,fixed),
+ ets:delete(T).
+
+add_lists(L1,L2) ->
+ add_lists(L1,L2,[]).
+add_lists([],[],Acc) ->
+ lists:reverse(Acc);
+add_lists([E1|T1], [E2|T2], Acc) ->
+ add_lists(T1, T2, [E1+E2 | Acc]).
+
+run_workers(InitF,ExecF,FiniF,Laps) ->
+ case erlang:system_info(smp_support) of
+ true ->
+ run_workers_do(InitF,ExecF,FiniF,Laps);
+ false ->
+ {skipped,"No smp support"}
+ end.
+
+run_workers_do(InitF,ExecF,FiniF,Laps) ->
+ NumOfProcs = erlang:system_info(schedulers),
+ io:format("smp starting ~p workers\n",[NumOfProcs]),
+ Seeds = [{ProcN,random:uniform(9999)} || ProcN <- lists:seq(1,NumOfProcs)],
+ Parent = self(),
+ Pids = [spawn_link(fun()-> worker(Seed,InitF,ExecF,FiniF,Laps,Parent,NumOfProcs) end)
+ || Seed <- Seeds],
+ wait_pids(Pids).
+
+worker({ProcN,Seed}, InitF, ExecF, FiniF, Laps, Parent, NumOfProcs) ->
+ io:format("smp worker ~p, seed=~p~n",[self(),Seed]),
+ random:seed(Seed,Seed,Seed),
+ State1 = InitF([ProcN, NumOfProcs]),
+ State2 = worker_loop(Laps, ExecF, State1),
+ Result = FiniF(State2),
+ io:format("worker ~p done\n",[self()]),
+ Parent ! {self(), Result}.
+
+worker_loop(0, _, State) ->
+ State;
+worker_loop(_, _, [end_of_work|State]) ->
+ State;
+worker_loop(N, ExecF, State) ->
+ worker_loop(N-1,ExecF,ExecF(State)).
+
+wait_pids(Pids) ->
+ wait_pids(Pids,[]).
+wait_pids([],Acc) ->
+ Acc;
+wait_pids(Pids, Acc) ->
+ receive
+ {Pid,Result} ->
+ ?line true = lists:member(Pid,Pids),
+ Others = lists:delete(Pid,Pids),
+ io:format("wait_pid got ~p from ~p, still waiting for ~p\n",[Result,Pid,Others]),
+ wait_pids(Others,[Result | Acc])
+ end.
+
+
+
+
+my_tab_to_list(Ts) ->
+ Key = ets:first(Ts),
+ my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)]).
+
+my_tab_to_list(_Ts,'$end_of_table', Acc) -> lists:reverse(Acc);
+my_tab_to_list(Ts,Key, Acc) ->
+ my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)| Acc]).
+
+etsmem() ->
+ {try erlang:memory(ets) catch error:notsup -> notsup end,
+ case erlang:system_info({allocator,ets_alloc}) of
+ false -> undefined;
+ MemInfo ->
+ MSBCS = lists:foldl(
+ fun ({instance, _, L}, Acc) ->
+ {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L),
+ {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L),
+ [MBCS,SBCS | Acc]
+ end,
+ [],
+ MemInfo),
+ lists:foldl(
+ fun(L, {Bl0,BlSz0}) ->
+ {value,{_,Bl,_,_}} = lists:keysearch(blocks, 1, L),
+ {value,{_,BlSz,_,_}} = lists:keysearch(blocks_size, 1, L),
+ {Bl0+Bl,BlSz0+BlSz}
+ end, {0,0}, MSBCS)
+ end}.
+
+verify_etsmem(MemInfo) ->
+ wait_for_test_procs(),
+ case etsmem() of
+ MemInfo ->
+ io:format("Ets mem info: ~p", [MemInfo]),
+ case MemInfo of
+ {ErlMem,EtsAlloc} when ErlMem == notsup; EtsAlloc == undefined ->
+ %% Use 'erl +Mea max' to do more complete memory leak testing.
+ {comment,"Incomplete or no mem leak testing"};
+ _ ->
+ ok
+ end;
+ Other ->
+ io:format("Expected: ~p", [MemInfo]),
+ io:format("Actual: ~p", [Other]),
+ ?t:fail()
+ end.
+
+start_loopers(N, Prio, Fun, State) ->
+ lists:map(fun (_) ->
+ my_spawn_opt(fun () -> looper(Fun, State) end,
+ [{priority, Prio}, link])
+ end,
+ lists:seq(1, N)).
+
+stop_loopers(Loopers) ->
+ lists:foreach(fun (P) ->
+ unlink(P),
+ exit(P, bang)
+ end,
+ Loopers),
+ ok.
+
+looper(Fun, State) ->
+ looper(Fun, Fun(State)).
+
+spawn_logger(Procs) ->
+ receive
+ {new_test_proc, Proc} ->
+ spawn_logger([Proc|Procs]);
+ {sync_test_procs, Kill, From} ->
+ lists:foreach(fun (Proc) when From == Proc ->
+ ok;
+ (Proc) ->
+ Mon = erlang:monitor(process, Proc),
+ receive
+ {'DOWN', Mon, _, _, _} ->
+ ok
+ after 0 ->
+ case Kill of
+ true -> exit(Proc, kill);
+ _ -> ok
+ end,
+ receive
+ {'DOWN', Mon, _, _, _} ->
+ ok
+ end
+ end
+ end, Procs),
+ From ! test_procs_synced,
+ spawn_logger([From])
+ end.
+
+start_spawn_logger() ->
+ case whereis(ets_test_spawn_logger) of
+ Pid when is_pid(Pid) -> true;
+ _ -> register(ets_test_spawn_logger,
+ spawn_opt(fun () -> spawn_logger([]) end,
+ [{priority, max}]))
+ end.
+
+%% restart_spawn_logger() ->
+%% stop_spawn_logger(),
+%% start_spawn_logger().
+
+stop_spawn_logger() ->
+ Mon = erlang:monitor(process, ets_test_spawn_logger),
+ (catch exit(whereis(ets_test_spawn_logger), kill)),
+ receive {'DOWN', Mon, _, _, _} -> ok end,
+ ok.
+
+wait_for_test_procs() ->
+ wait_for_test_procs(false).
+
+wait_for_test_procs(Kill) ->
+ ets_test_spawn_logger ! {sync_test_procs, Kill, self()},
+ receive test_procs_synced -> ok end.
+
+log_test_proc(Proc) ->
+ ets_test_spawn_logger ! {new_test_proc, Proc},
+ Proc.
+
+my_spawn(Fun) -> log_test_proc(spawn(Fun)).
+%%my_spawn(M,F,A) -> log_test_proc(spawn(M,F,A)).
+%%my_spawn(N,M,F,A) -> log_test_proc(spawn(N,M,F,A)).
+
+my_spawn_link(Fun) -> log_test_proc(spawn_link(Fun)).
+my_spawn_link(M,F,A) -> log_test_proc(spawn_link(M,F,A)).
+%%my_spawn_link(N,M,F,A) -> log_test_proc(spawn_link(N,M,F,A)).
+
+my_spawn_opt(Fun,Opts) -> log_test_proc(spawn_opt(Fun,Opts)).
+%%my_spawn_opt(M,F,A,Opts) -> log_test_proc(spawn_opt(M,F,A,Opts)).
+%%my_spawn_opt(N,M,F,A,Opts) -> log_test_proc(spawn_opt(N,M,F,A,Opts)).
+
+repeat(_Fun, 0) ->
+ ok;
+repeat(Fun, N) ->
+ Fun(),
+ repeat(Fun, N-1).
+
+repeat_while(Fun) ->
+ case Fun() of
+ true -> repeat_while(Fun);
+ false -> false
+ end.
+
+repeat_while(Fun, Arg0) ->
+ case Fun(Arg0) of
+ {true,Arg1} -> repeat_while(Fun,Arg1);
+ {false,Ret} -> Ret
+ end.
+
+receive_any() ->
+ receive M ->
+ io:format("Process ~p got msg ~p\n", [self(),M]),
+ M
+ end.
+
+receive_any_spinning() ->
+ receive_any_spinning(1000000).
+receive_any_spinning(Loops) ->
+ receive_any_spinning(Loops,Loops,1).
+receive_any_spinning(Loops,0,Tries) ->
+ receive M ->
+ io:format("Spinning process ~p got msg ~p after ~p tries\n", [self(),M,Tries]),
+ M
+ after 0 ->
+ receive_any_spinning(Loops, Loops, Tries+1)
+ end;
+receive_any_spinning(Loops, N, Tries) when N>0 ->
+ receive_any_spinning(Loops, N-1, Tries).
+
+
+
+spawn_monitor_with_pid(Pid, Fun) when is_pid(Pid) ->
+ spawn_monitor_with_pid(Pid, Fun, 1, 10).
+
+spawn_monitor_with_pid(Pid, Fun, N, M) when N > M*10 ->
+ spawn_monitor_with_pid(Pid, Fun, N, M*10);
+spawn_monitor_with_pid(Pid, Fun, N, M) ->
+ ?line false = is_process_alive(Pid),
+ case spawn(fun()-> case self() of
+ Pid -> Fun();
+ _ -> die
+ end
+ end) of
+ Pid ->
+ {Pid, erlang:monitor(process, Pid)};
+ Other ->
+ case N rem M of
+ 0 -> io:format("Failed ~p times to get pid ~p (current = ~p)\n",[N,Pid,Other]);
+ _ -> ok
+ end,
+ spawn_monitor_with_pid(Pid,Fun,N+1,M)
+ end.
+
+
+only_if_smp(Func) ->
+ only_if_smp(2, Func).
+only_if_smp(Schedulers, Func) ->
+ case {erlang:system_info(smp_support),
+ erlang:system_info(schedulers_online)} of
+ {false,_} -> {skip,"No smp support"};
+ {true,N} when N < Schedulers -> {skip,"Too few schedulers online"};
+ {true,_} -> Func()
+ end.
+
+
+%% Repeat test function with different combination of table options
+%%
+repeat_for_opts(F) ->
+ repeat_for_opts(F, [write_concurrency]).
+
+repeat_for_opts(F, OptGenList) when is_atom(F) ->
+ repeat_for_opts(fun(Opts) -> ?MODULE:F(Opts) end, OptGenList);
+repeat_for_opts(F, OptGenList) ->
+ repeat_for_opts(F, OptGenList, []).
+
+repeat_for_opts(F, [], Acc) ->
+ lists:map(fun(Opts) ->
+ io:format("Calling with options ~p\n",[Opts]),
+ F(Opts)
+ end, Acc);
+repeat_for_opts(F, [OptList | Tail], []) when is_list(OptList) ->
+ repeat_for_opts(F, Tail, [[Opt] || Opt <- OptList]);
+repeat_for_opts(F, [OptList | Tail], AccList) when is_list(OptList) ->
+ repeat_for_opts(F, Tail, [[Opt|Acc] || Opt <- OptList, Acc <- AccList]);
+repeat_for_opts(F, [Atom | Tail], AccList) when is_atom(Atom) ->
+ repeat_for_opts(F, [repeat_for_opts_atom2list(Atom) | Tail ], AccList).
+
+repeat_for_opts_atom2list(all_types) -> [set,ordered_set,bag,duplicate_bag];
+repeat_for_opts_atom2list(write_concurrency) -> [{write_concurrency,false},{write_concurrency,true}].
+
+
diff --git a/lib/stdlib/test/ets_SUITE_data/dummy.txt b/lib/stdlib/test/ets_SUITE_data/dummy.txt
new file mode 100644
index 0000000000..2cdad292f7
--- /dev/null
+++ b/lib/stdlib/test/ets_SUITE_data/dummy.txt
@@ -0,0 +1 @@
+Dummy
diff --git a/lib/stdlib/test/ets_tough_SUITE.erl b/lib/stdlib/test/ets_tough_SUITE.erl
new file mode 100644
index 0000000000..e3d44d00b9
--- /dev/null
+++ b/lib/stdlib/test/ets_tough_SUITE.erl
@@ -0,0 +1,1093 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ets_tough_SUITE).
+-export([all/1,ex1/1]).
+-export([init/1,terminate/2,handle_call/3,handle_info/2]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+-compile([export_all]).
+-include("test_server.hrl").
+
+all(suite) -> [ex1].
+
+
+-define(DEBUG(X),debug_disabled).
+%%-define(DEBUG(X),X).
+-define(GLOBAL_PARAMS,ets_tough_SUITE_global_params).
+
+init_per_testcase(_Func, Config) ->
+ Dog=test_server:timetrap(test_server:seconds(300)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Func, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ets:delete(?GLOBAL_PARAMS).
+
+
+ex1(Config) when list(Config) ->
+ ?line ets:new(?GLOBAL_PARAMS,[named_table,public]),
+ ?line ets:insert(?GLOBAL_PARAMS,{a,set}),
+ ?line ets:insert(?GLOBAL_PARAMS,{b,set}),
+ ?line ex1_sub(Config),
+ ?line ets:insert(?GLOBAL_PARAMS,{a,ordered_set}),
+ ?line ets:insert(?GLOBAL_PARAMS,{b,set}),
+ ?line ex1_sub(Config),
+ ?line ets:insert(?GLOBAL_PARAMS,{a,ordered_set}),
+ ?line ets:insert(?GLOBAL_PARAMS,{b,ordered_set}),
+ ?line ex1_sub(Config).
+
+
+
+
+ex1_sub(Config) ->
+ {A,B} = prep(Config),
+ N =
+ case ?config(ets_tough_SUITE_iters,Config) of
+ undefined ->
+ 5000;
+ Other ->
+ Other
+ end,
+ {NewA,NewB} = run({A,B},N),
+ _Gurkor = lists:keysearch(gurka,1,ets:all()),
+ (catch stop(NewA)),
+ (catch stop(NewB)),
+ ok.
+
+prep(Config) ->
+ random:seed(),
+ put(dump_ticket,none),
+ DumpDir = filename:join(?config(priv_dir,Config), "ets_tough"),
+ file:make_dir(DumpDir),
+ put(dump_dir,DumpDir),
+ process_flag(trap_exit,true),
+ {ok, A} = start(a),
+ {ok, B} = start(b),
+ {A,B}.
+
+run({A,B},N) ->
+ run(A,B,0,N).
+
+run(A,B,N,N) ->
+ {A,B};
+run(A,B,N,M) ->
+ eat_msgs(),
+ Op = random_operation(),
+ ?DEBUG(io:format("~w: ",[N])),
+ case catch operate(Op,A,B) of
+ {'EXIT',Reason} ->
+ io:format("\nFAILURE on ~w: ~w, reason: ~w\n",[N,Op,Reason]),
+ exit(failed);
+ {new_a,NewA} ->
+ run(NewA,B,N+1,M);
+ _ ->
+ run(A,B,N+1,M)
+ end.
+
+eat_msgs() ->
+ receive
+ _Anything ->
+ eat_msgs()
+ after 0 ->
+ ok
+ end.
+
+operate(get,A,B) ->
+ case random_key() of
+ 1 ->
+ Class = random_class(),
+ AnsA = lists:sort(dget_class(A,Class,all)),
+ AnsB = lists:sort(dget_class(B,Class,all)),
+ ?DEBUG(io:format("get_class ~w (~w)\n",[Class,AnsA])),
+ AnsA = AnsB;
+ _Other ->
+ Class = random_class(),
+ Key = random_key(),
+ AnsA = dget(A,Class,Key),
+ AnsB = dget(B,Class,Key),
+ ?DEBUG(io:format("get ~w,~w (~w)\n",[Class,Key,AnsA])),
+ AnsA = AnsB
+ end;
+
+operate(put,A,B) ->
+ Class = random_class(),
+ Key = random_key(),
+ Value = random_value(),
+ AnsA = dput(A,Class,Key,Value),
+ AnsB = dput(B,Class,Key,Value),
+ ?DEBUG(io:format("put ~w,~w=~w (~w)\n",[Class,Key,Value,AnsA])),
+ AnsA = AnsB;
+
+operate(erase,A,B) ->
+ case random_key() of
+ 1 ->
+ Class = random_class(),
+ AnsA = derase_class(A,Class),
+ AnsB = derase_class(B,Class),
+ ?DEBUG(io:format("erase_class ~w\n",[Class])),
+ AnsA = AnsB;
+ _Other ->
+ Class = random_class(),
+ Key = random_key(),
+ AnsA = derase(A,Class,Key),
+ AnsB = derase(B,Class,Key),
+ ?DEBUG(io:format("erase ~w,~w (~w)\n",[Class,Key,AnsA])),
+ AnsA = AnsB
+ end;
+
+operate(dirty_get,A,_B) ->
+ Class = random_class(),
+ Key = random_key(),
+ %% only try dirty get on the b-side (which is never dumping)
+ AnsA = dget(A,Class,Key),
+ AnsB = dirty_dget(b,Class,Key),
+ ?DEBUG(io:format("dirty_get ~w,~w (~w)\n",[Class,Key,AnsA])),
+ AnsA = AnsB;
+
+operate(dump,A,_B) ->
+ case get(dump_ticket) of
+ {dump_more,Ticket} ->
+ Units = random_key(),
+ NewTicket = ddump_next(A,Units,Ticket),
+ put(dump_ticket,NewTicket),
+ _Result = case NewTicket of
+ done -> done;
+ _ -> dump_more
+ end,
+ ?DEBUG(io:format("dump ~w (~w)\n",[Units,_Result]));
+ _ ->
+ DumpDir = get(dump_dir),
+ case random_key() of
+ 1 ->
+ ?DEBUG(io:format("start_dump\n",[])),
+ NewTicket = ddump_first(A,DumpDir),
+ put(dump_ticket,NewTicket);
+ 2 ->
+ ?DEBUG(io:format("dump_and_restore\n",[])),
+ {dump_more,NewTicket} = ddump_first(A,DumpDir),
+ done = ddump_next(A,1000000,NewTicket),
+ stop(A),
+ {ok, NewA} = start(a,DumpDir),
+ {new_a,NewA};
+ _ ->
+ ?DEBUG(io:format("idle\n",[])),
+ ok
+ end
+ end.
+
+random_operation() ->
+ Ops = {get,put,erase,dirty_get,dump},
+ random_element(Ops).
+
+random_class() ->
+ Classes = {foo,bar,tomat,gurka},
+ random_element(Classes).
+
+random_key() ->
+ random:uniform(8).
+
+random_value() ->
+ case random:uniform(5) of
+ 1 -> ok;
+ 2 -> {data,random_key()};
+ 3 -> {foo,bar,random_class()};
+ 4 -> random:uniform(1000);
+ 5 -> {recursive,random_value()}
+ end.
+
+random_element(T) ->
+ I = random:uniform(tuple_size(T)),
+ element(I,T).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+show_table(N) ->
+ FileName = ["etsdump.",integer_to_list(N)],
+ case file:open(FileName,read) of
+ {ok,Fd} ->
+ show_entries(Fd);
+ _ ->
+ error
+ end.
+
+show_entries(Fd) ->
+ case phys_read_len(Fd) of
+ {ok,Len} ->
+ case phys_read_entry(Fd,Len) of
+ {ok,ok} ->
+ ok;
+ {ok,{Key,Val}} ->
+ io:format("~w\n",[{Key,Val}]),
+ show_entries(Fd);
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% DEFINITIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(NAMED_TABLES,true).
+-define(DB_NAME_KEY, {'$db_name'}).
+-define(LIST_OF_CLASSES_KEY,{'$list_of_classes'}).
+-define(DUMPING_FLAG_KEY,{'$dumping_flag'}).
+-define(DUMP_DIRECTORY_KEY,{'$dump_directory'}).
+-define(ERASE_MARK(Key),{{{'$erased'},Key}}).
+-define(ets_new,ets:new).
+-define(ets_lookup,ets:lookup).
+-define(ets_insert,ets:insert). % erlang:db_put
+-define(ets_delete,ets:delete). % erlang:db_erase
+-define(ets_first,ets:first). % erlang:db_first
+-define(ets_next,ets:next). % erlang:db_next_key
+-define(ets_info,ets:info). % erlang:db_info
+
+%%% INTERFACE FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% start(DbName) -> Pid | {error,Reason}
+%%%
+%%% Starts the ets table database with name DbName
+
+start(DbName) ->
+ case gen_server:start_link(ets_tough_SUITE,{DbName,no_dump_dir},[]) of
+ {ok,Pid} when pid(Pid) ->
+ {ok, Pid};
+ Other ->
+ Other
+ end.
+
+%%% start(DbName,DumpDir) -> Pid | {error,Reason}
+%%%
+%%% Starts the ets table database with name DbName, and reads a dump
+%%% from DumpDir when it starts.
+
+start(DbName,DumpDir) ->
+ case gen_server:start_link(ets_tough_SUITE,
+ {DbName,{dump_dir,DumpDir}},[]) of
+ {ok,Pid} when pid(Pid) ->
+ {ok, Pid};
+ Other ->
+ Other
+ end.
+
+%%% stop(ServerPid) -> {'EXIT',shutdown}
+%%%
+%%% Shuts down the ets table database
+
+stop(ServerPid) ->
+ gen_server:call(ServerPid,stop).
+
+%%% dget(ServerPid,Class,Key) -> {value,Value} | undefined
+%%%
+%%% Returns a value identified by Class,Key from the database, or
+%%% 'undefined' if there is no such value.
+
+dget(ServerPid,Class,Key) ->
+ gen_server:call(ServerPid,{handle_lookup,Class,Key}).
+
+%%% dirty_dget(DbName,Class,Key) -> {value,Value} | undefined
+%%%
+%%% This is looks up the value directly in the ets table
+%%% to avoid message passing. Several databases may be started,
+%%% so the admin table must be registered.
+
+dirty_dget(DbName,Class,Key) ->
+ Admin = admin_table_name(DbName),
+ case catch(?ets_lookup(Admin,Class)) of
+ [{_Class,[Tab|_Tabs]}] ->
+ case ?ets_lookup(Tab,Key) of
+ [{_Key,Value}] ->
+ {value,Value};
+ _ ->
+ undefined
+ end;
+ _ ->
+ undefined
+ end.
+
+%%% dput(ServerPid,Class,Key,Value) -> undefined | {value,OldValue}
+%%%
+%%% Inserts the given Value to be identified by Class,Key. Any prevoius
+%%% value is returned, or otherwise 'undefined'.
+
+dput(ServerPid,Class,Key,Value) ->
+ gen_server:call(ServerPid,{handle_insert,Class,Key,Value}).
+
+%%% derase(ServerPid,Class,Key) -> undefined | {value,OldValue}
+%%%
+%%% Erases any value identified by Class,Key
+
+derase(ServerPid,Class,Key) ->
+ gen_server:call(ServerPid,{handle_delete,Class,Key}).
+
+%%% dget_class(ServerPid,Class,Condition) -> KeyList
+%%%
+%%% Returns a list of keys where the instance match Condition.
+%%% Condition = 'all' returns all keys in the class.
+%%% The condition is supplied as Condition = {Mod, Fun, ExtraArgs},
+%%% where the instance will be prepended to ExtraArgs before each
+%%% call is made.
+
+dget_class(ServerPid,Class,Condition) ->
+ gen_server:call(ServerPid,
+ {handle_get_class,Class,Condition},infinity).
+
+%%% derase_class(ServerPid,Class) -> ok
+%%%
+%%% Erases a whole class, identified by Class
+
+derase_class(ServerPid,Class) ->
+ gen_server:call(ServerPid,{handle_delete_class,Class}, infinity).
+
+%%% dmodify(ServerPid,Application) -> ok
+%%%
+%%% Applies a function on every instance in the database.
+%%% The user provided function must always return one of the
+%%% terms {ok,NewItem}, true, or false.
+%%% Aug 96, this is only used to reset all timestamp values
+%%% in the database.
+%%% The function is supplied as Application = {Mod, Fun, ExtraArgs},
+%%% where the instance will be prepended to ExtraArgs before each
+%%% call is made.
+
+dmodify(ServerPid,Application) ->
+ gen_server:call(ServerPid,{handle_dmodify,Application}, infinity).
+
+%%% ddump_first(ServerPid,DumpDir) -> {dump_more,Ticket} | already_dumping
+%%%
+%%% Starts dumping the database. This call redirects all database updates
+%%% to temporary tables, so that exactly the same database image will be
+%%% written to disk as is in memory when this function is called.
+%%% The returned Ticket is to be used with ddump_next/2
+
+ddump_first(ServerPid,DumpDir) ->
+ gen_server:call(ServerPid,{handle_dump_first,DumpDir}, infinity).
+
+%%% ddump_next(ServerPid,Count,Ticket) -> {dump_more,Ticket} | done
+%%%
+%%% Dumps the database. This function performs Count units of dump work.
+%%% Higher value of Count makes the entire dump operation more efficient,
+%%% but blocks the database for longer periods of time.
+%%% If there is still more work to be done, a new Ticket is returned,
+%%% or 'done' otherwise.
+
+ddump_next(ServerPid,Count,Ticket) ->
+ gen_server:call(ServerPid,{handle_dump_next,Ticket,Count},150000).
+
+%%% PRIVATE HANDLER FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% Admin
+%%% -----
+%%%
+%%% The database has a main administrative table Admin. It always contains
+%%% these four items:
+%%%
+%%% {{'$db_name'},Name}
+%%% {{'$list_of_classes'},ListOfClasses}
+%%% {{'$dumping_flag'},BoolDumping}
+%%% {{'$dump_directory'},Dir}
+%%%
+%%% The ListOfClasses is simply a list of all Classes that has ever been
+%%% inserted in the database. It's used to know which tables to dump.
+%%% The dump flag is 'true' while dump is in progress, to make it
+%%% impossible to start a new dump before an old dump is completed.
+%%%
+%%% For each class there is an entry of the form
+%%%
+%%% {Class,ListOfTables}
+%%%
+%%% Where the ListOfTables is the list of class tables (see below)
+%%%
+%%% Class Tables
+%%% ------------
+%%%
+%%% The class tables are common ets tables that have the actual user
+%%% data stored in them.
+%%%
+%%% Normally there is only one class table, Mtab (main table).
+%%% When dumping is initiated, each class is syncronously given a
+%%% temporary table, Ttab, where all updates are stored. Reads are
+%%% directed to the Ttab first, and only if not found there, Mtab is
+%%% consulted.
+%%%
+%%% Writes always go to the first table in the table sequence. This
+%%% ensures that the dump algorithm can enumerate the entries in the
+%%% other tables, without risk of being disrupted.
+%%%
+%%% When the dumping to disk is completed, it's time to write back
+%%% whatever updates that came into the Ttab to Mtab. To do this, a
+%%% third table is needed, Utab, to handle all updates while Ttab is
+%%% being copied to Mtab. When all of Ttab is copied, Ttab is thrown
+%%% away, and the whole process is repeated with Utab as Ttab until
+%%% eventually nobody wrote to Utab while Ttab was copied (clean run).
+%%%
+%%% There is no _guarantee_ that this will ever happen, but unless there
+%%% is a constant (and quite high frequency) stream of updates to a
+%%% particular class, this should work.
+%%%
+%%% (It is possible to make this failsafe, by copying the elements in
+%%% Mtab to Ttab. This is probably a lot more expensive, though)
+%%%
+%%% Erasure during dump
+%%% -------------------
+%%%
+%%% Erasing need special attention when a single class has several
+%%% tables. It really boils down to a number of cases:
+%%%
+%%% - element does not exist in Ttab.
+%%% A special erase record is written, {{{'$erased'},Key}} which
+%%% is hopefully different from all other keys used by the user.
+%%% - element exists in Ttab
+%%% The element is deleted, and erase record is written
+%%% - element does not exist in Ttab, but there is an erase record
+%%% fine, do nothing
+%%% - element exist in Ttab, and there is an erase record
+%%% This happens when a record is deleted from Ttab, then written
+%%% back again. Erase records are not looked for when inserting
+%%% new data (and that's not necessary)
+%%%
+%%% Then when Ttab should be copied to Mtab:
+%%%
+%%% - found an element
+%%% Usual case, just copy
+%%% - found erase record
+%%% Check if there is an element with the same key as the erase
+%%% record. If so it has been written later than the erasure, so
+%%% the erasure is obsolete. Otherwise erase the record from Mtab.
+%%%
+%%% Delete Class
+%%% ------------
+%%%
+%%% A slight problem is deleting an entire class while dumping is in
+%%% progress. For consitency, all user visible traces of the class must
+%%% be deleted, while dumping must not be affected. On top of that, the
+%%% deleted class may well be recreated while dumping is still going on,
+%%% and entries added.
+%%%
+%%% This is solved by having the dump algorithm keep track of the table
+%%% identifiers of the tables to dump, rather than asking the admin table
+%%% (since the class might be deleted there). The dump algorithm will
+%%% itself take care of deleting the tables used in the dumping, while the
+%%% normal database interface deletes the "first table", the table that is
+%%% currently accepting all write operations.
+
+
+init({DbName,DumpDir}) ->
+ case DumpDir of
+ no_dump_dir ->
+ Admin = make_admin_table(DbName),
+ ?ets_insert(Admin,{?LIST_OF_CLASSES_KEY,[]}),
+ init2(DbName,Admin);
+ {dump_dir,Dir} ->
+ case load_dump(DbName,Dir) of
+ {ok,Admin} ->
+ ?ets_insert(Admin,{?DUMP_DIRECTORY_KEY,Dir}),
+ init2(DbName,Admin);
+ _ ->
+ cant_load_dump
+ end
+ end.
+
+init2(DbName,Admin) ->
+ ?ets_insert(Admin,{?DUMPING_FLAG_KEY,false}),
+ ?ets_insert(Admin,{?DB_NAME_KEY,DbName}),
+ {ok, Admin}.
+
+terminate(_Reason,_Admin) ->
+ ok.
+
+handle_call({handle_lookup,Class,Key},_From,Admin) ->
+ %% Lookup tables to search in
+ Reply =
+ case ?ets_lookup(Admin,Class) of
+ [] ->
+ undefined; %% no such class => no such record
+ [{_,TabList}] ->
+ {_,Ans} = table_lookup(TabList, Key),
+ Ans
+ end,
+ {reply,Reply,Admin};
+
+handle_call({handle_insert,Class,Key,Value},_From,Admin) ->
+ %% Lookup in which table to write
+ Reply =
+ case ?ets_lookup(Admin,Class) of
+ [] ->
+ %% undefined class, let's create it
+ Mtab = make_db_table(db_name(Admin),Class),
+ ?ets_insert(Admin,{Class,[Mtab]}),
+ [{_,Classes}] = ?ets_lookup(Admin,?LIST_OF_CLASSES_KEY),
+ ?ets_insert(Admin,{?LIST_OF_CLASSES_KEY,[Class|Classes]}),
+ ?ets_insert(Mtab, {Key, Value}),
+ undefined;
+ [{_,[Tab|Tabs]}] ->
+ {_,Old} = table_lookup([Tab|Tabs], Key),
+ ?ets_insert(Tab, {Key, Value}),
+ Old
+ end,
+ {reply,Reply,Admin};
+
+handle_call({handle_delete,Class,Key},_From,Admin) ->
+ %% Lookup in which table to write
+ Reply =
+ case ?ets_lookup(Admin, Class) of
+ [] ->
+ undefined; %% no such class, but delete is happy anyway
+ [{_,[Tab]}] ->
+ %% When there is only one table, simply deleting is enough
+ {_,Old} = table_lookup(Tab,Key),
+ ?ets_delete(Tab,Key),
+ Old;
+ [{_,[Tab|Tabs]}] ->
+ %% When there are more tables, we have to write a delete
+ %% record into the first one, so that nobody goes looking
+ %% for this entry in some other table
+ {_,Old} = table_lookup([Tab|Tabs],Key),
+ ?ets_insert(Tab, {?ERASE_MARK(Key), erased}),
+ ?ets_delete(Tab,Key),
+ Old
+ end,
+ {reply,Reply,Admin};
+
+handle_call({handle_get_class,Class,Cond},_From,Admin) ->
+ Reply =
+ case ?ets_lookup(Admin,Class) of % Lookup tables to search in
+ [] ->
+ []; % no such class
+ [{_,TabList}] ->
+ table_lookup_batch(TabList, Class, Cond) % get class data
+ end,
+ {reply,Reply,Admin};
+
+handle_call({handle_delete_class,Class},_From,Admin) ->
+ Reply =
+ case ?ets_lookup(Admin, Class) of
+ [] ->
+ ok; % no such class, but delete_class is happy anyway
+ [{_,[Tab|_Tabs]}] ->
+ %% Always delete the first table (the one we're writing into)
+ %% In case we're dumping, the rest of the tables will be
+ %% taken care of by the dump algorithm.
+ ?ets_delete(Tab),
+ [{_, Classes}] = ?ets_lookup(Admin, ?LIST_OF_CLASSES_KEY),
+ NewClasses = lists:delete(Class, Classes),
+ ?ets_insert(Admin, {?LIST_OF_CLASSES_KEY, NewClasses}),
+ ?ets_delete(Admin, Class),
+ ok
+ end,
+ {reply,Reply,Admin};
+
+handle_call({handle_dmodify,Application},_From,Admin) ->
+ [{_, Classes}] = ?ets_lookup(Admin, ?LIST_OF_CLASSES_KEY),
+ modify(Application, Classes, Admin),
+ {reply,ok,Admin};
+
+handle_call({handle_dump_first,DumpDir},_From,Admin) ->
+ case ?ets_lookup(Admin,?DUMPING_FLAG_KEY) of
+ [{_,true}] ->
+ {reply,already_dumping,Admin};
+ _ ->
+ phys_remove_ok(DumpDir),
+ [{_,Classes}] = ?ets_lookup(Admin,?LIST_OF_CLASSES_KEY),
+ Tables = dump_prepare_classes(Classes,Admin),
+ ?ets_insert(Admin,{?DUMPING_FLAG_KEY,true}),
+ %% this is the new dir for dumping:
+ ?ets_insert(Admin,{?DUMP_DIRECTORY_KEY,DumpDir}),
+ handle_dump_next({[{admin,Classes}|Tables]},0,Admin)
+ end;
+
+%% All done, good work!
+handle_call({handle_dump_next,Ticket,Count},_From,Admin) ->
+ handle_dump_next(Ticket,Count,Admin);
+
+handle_call(stop,_From,Admin) ->
+ ?ets_delete(Admin), % Make sure table is gone before reply is sent.
+ {stop, normal, ok, []}.
+
+handle_info({'EXIT',_Pid,_Reason},Admin) ->
+ {stop,normal,Admin}.
+
+handle_delete(Class, Key, Admin) ->
+ handle_call({handle_delete,Class,Key},from,Admin).
+
+handle_insert(Class, Key, Value, Admin) ->
+ handle_call({handle_insert,Class,Key,Value},from,Admin).
+
+handle_lookup(Class, Key, Admin) ->
+ handle_call({handle_lookup,Class,Key},from,Admin).
+
+
+handle_dump_next({[]},_Count,Admin) ->
+ [{_Key,DumpDir}] = ?ets_lookup(Admin,?DUMP_DIRECTORY_KEY),
+ phys_ok_dump(DumpDir),
+ ?ets_insert(Admin,{?DUMPING_FLAG_KEY,false}),
+ {reply,done,Admin};
+
+%% No more operations, return to user asking for more
+handle_dump_next(Ticket,0,Admin) ->
+ {reply,{dump_more,Ticket},Admin};
+
+%% Dump the admin table. Costs one dump-work unit.
+handle_dump_next({[{admin,Classes}|Tables]},Count,Admin) ->
+ [{_Key,DumpDir}] = ?ets_lookup(Admin,?DUMP_DIRECTORY_KEY),
+ DumpData = phys_init_dump(admin,DumpDir,0),
+ phys_dump({?LIST_OF_CLASSES_KEY,Classes},DumpData),
+ phys_finish_dump(DumpData),
+ handle_dump_next({Tables},Count-1,Admin);
+
+%% Pick out a class and start dumping it
+handle_dump_next({[{Class,Mtab}|Tables]},Count,Admin) ->
+ ?DEBUG(io:format("DUMP CLASS ~w\n",[Class])),
+ [{_Key,DumpDir}] = ?ets_lookup(Admin,?DUMP_DIRECTORY_KEY),
+ DumpData = phys_init_dump(Class,DumpDir,length(Tables)+1),
+ First = ?ets_first(Mtab),
+ handle_dump_next({Class,Tables,Mtab,First,DumpData},Count,Admin);
+
+%% All keys in this class have been written to disk, now we have to
+%% copy all items from temporary Ttab to main Mtab
+handle_dump_next({Class,Tables,Stab,'$end_of_table',DumpData},Count,Admin) ->
+ phys_finish_dump(DumpData),
+ ?DEBUG(io:format("Cleaning up temporary table in ~p\n",[Class])),
+ case ?ets_lookup(Admin,Class) of
+ [{Key,[Utab,Mtab]}] ->
+ Ttab = make_db_table(db_name(Admin),Class),
+ ?ets_insert(Admin,{Key,[Ttab,Utab,Mtab]}),
+ First = ?ets_first(Utab),
+ handle_dump_next({3,Class,Tables,Utab,First,Mtab},Count,Admin);
+ _Other ->
+ %% Class deleted (and maybe recreated) while dumping, no need to
+ %% bring this one up to date. Just discard late additions.
+ ?ets_delete(Stab),
+ handle_dump_next({Tables},Count,Admin)
+ end;
+
+%% Dumping one key to disk. Costs one dump-work unit.
+handle_dump_next({Class,Tables,Tab,Key,DumpData},Count,Admin) ->
+ [KeyVal] = ?ets_lookup(Tab,Key),
+ phys_dump(KeyVal,DumpData),
+ NextKey = ?ets_next(Tab,Key),
+ handle_dump_next({Class,Tables,Tab,NextKey,DumpData},Count-1,Admin);
+
+%% Done copying elements from Ttab to Mtab
+%% check if Utab is empty and go on with next class, or
+%% make Utab the current Ttab, and run again
+%% ... will this ever end? ;-)
+handle_dump_next({3,Class,Tables,Stab,'$end_of_table',Dtab},Count,Admin) ->
+ case ?ets_lookup(Admin,Class) of
+ [{Key,[Ttab,Utab,Mtab]}] ->
+ case ?ets_info(Ttab,size) of
+ 0 ->
+ ?ets_insert(Admin,{Key,[Mtab]}),
+ ?ets_delete(Ttab),
+ ?ets_delete(Utab),
+ handle_dump_next({Tables},Count,Admin);
+ _Work ->
+ ?DEBUG(io:format("Switching direction in ~p\n",[Class])),
+ %% Which is faster, deleting all the entries
+ %% in a table, or deleting it and create a new?
+ ?ets_delete(Utab),
+ Ntab = make_db_table(db_name(Admin),Class),
+ ?ets_insert(Admin,{Key,[Ntab,Ttab,Mtab]}),
+ First = ?ets_first(Ttab),
+ handle_dump_next({3,Class,Tables,Ttab,First,Mtab},
+ Count,Admin)
+ end;
+ _Other ->
+ %% Class deleted (and maybe recreated) while dumping, no need to
+ %% bring this one up to date. Just discard late additions.
+ ?ets_delete(Stab),
+ ?ets_delete(Dtab),
+ handle_dump_next({Tables},Count,Admin)
+ end;
+
+%% Copy one key from Ttab to Mtab
+%% costs one dump-work unit
+handle_dump_next({3,Class,Tables,Stab,Key,Dtab},Count,Admin) ->
+ copy_dump_entry(Stab,Key,Dtab),
+ NextKey = ?ets_next(Stab,Key),
+ handle_dump_next({3,Class,Tables,Stab,NextKey,Dtab},Count-1,Admin).
+
+%%% INTERNAL HELPER FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% admin_table_name(DbName) -> Name
+%%%
+%%% Returns the name of the admin table of the table DbName
+
+admin_table_name(DbName) ->
+ list_to_atom(lists:append(atom_to_list(DbName),"#admin")).
+
+%%% make_admin_table(DbName) -> EtsAdminTable
+%%%
+%%% Creates and registers an ETS Admin table
+
+make_admin_table(DbName) ->
+ ?ets_new(admin_table_name(DbName),[named_table,protected,db_type(DbName)]).
+
+%%% make_db_table(DbName,Name) -> EtsTable
+%%%
+%%% Creates an ETS database table
+
+make_db_table(DbName, Name) ->
+ ?ets_new(Name,[protected,db_type(DbName)]).
+
+db_name(Admin) ->
+ ets:lookup_element(Admin,?DB_NAME_KEY,2).
+
+db_type(DbName) ->
+ case ets:lookup(?GLOBAL_PARAMS, DbName) of
+ [] ->
+ set;
+ [{DbName,X}] ->
+ X
+ end.
+
+%%% table_lookup(Table,Key) ->
+%%% table_lookup(TableList,Key) ->
+%%% {def,{value,Value}} | {undef,undefined} | (erased,undefined}
+%%%
+%%% Looks up key in the table and returns it value, or undefined
+%%% if there is no such key.
+%%% If a list of tables is given, they are searched one after another
+%%% for a matching key, until one is found. The search is discontinued
+%%% if a record telling that the key was deleted is found.
+
+table_lookup([], _Key) ->
+ {undef,undefined};
+table_lookup([Table|Tables], Key) ->
+ case table_lookup(Table,Key) of
+ {_,undefined} ->
+ case ?ets_lookup(Table,?ERASE_MARK(Key)) of
+ [] ->
+ table_lookup(Tables,Key);
+ _Definition ->
+ %% The element has been deleted, don't look further!
+ %% Pretend we never saw anything..
+ {erased,undefined}
+ end;
+ Answer ->
+ Answer
+ end;
+table_lookup(Table, Key) ->
+ case ?ets_lookup(Table,Key) of
+ [] ->
+ {undef,undefined};
+ [{_Key,Value}] ->
+ {def,{value,Value}}
+ end.
+
+%%% table_lookup_batch(Tables, Class, Cond) -> KeyList
+%%%
+%%% Extract the keys from a table or a table group.
+%%% If a condition is supplied, it is on the form {Mod, Fun, ExtraArgs}
+%%% and returns {true,Key} or false when called using
+%%% apply(Mod, Fun, [Instance|ExtraArgs]).
+%%% Instance is, for historic reasons, {{Class, Key}, Value} when the function
+%%% is called. Cond = 'all' can be used to get all keys from a class.
+
+table_lookup_batch([],_Class,_Cond) ->
+ [];
+table_lookup_batch([Table|Tables],Class,Cond) ->
+ table_lookup_batch([],Tables,Table,ets:first(Table),Class,Cond,[]).
+
+table_lookup_batch(_Passed,[],_,'$end_of_table',_Class,_Cond,Ack) ->
+ Ack;
+table_lookup_batch(Passed,[NewTable|Tables],Table,'$end_of_table',
+ Class,Cond,Ack) ->
+ table_lookup_batch(lists:append(Passed,[Table]),Tables,
+ NewTable,ets:first(NewTable),Class,Cond,Ack);
+table_lookup_batch(Passed,Tables,Table,?ERASE_MARK(Key),Class,Cond,Ack) ->
+ table_lookup_batch(Passed,Tables,Table,?ets_next(Table,?ERASE_MARK(Key)),
+ Class,Cond,Ack);
+
+table_lookup_batch(Passed,Tables,Table,Key,Class,Cond,Ack) ->
+ NewAck =
+ case table_lookup(Passed,Key) of
+ {undef,undefined} ->
+ [{_Key,Value}] = ?ets_lookup(Table,Key),
+ case Cond of % are there any conditions?
+ all ->
+ [Key|Ack];
+ {M, F, A} ->
+ %% apply the condition test.
+ %% Applications need keys to consist of
+ %% {class, primkey}, so we make it that way
+ case apply(M, F, [{{Class, Key}, Value}|A]) of
+ {true, Key} -> [Key|Ack];
+ false -> Ack
+ end
+ end;
+ _Other ->
+ %% Already processed (or erased) key
+ %% {def,{value,Value}} ->
+ %% {erased,undefined} ->
+ Ack
+ end,
+ table_lookup_batch(Passed,Tables,Table,?ets_next(Table,Key),
+ Class,Cond,NewAck).
+
+%%% modify(Application, ClassList, Admin) -> ok.
+%%%
+%%% This function modifies each element of the classes
+
+modify(_Application, [], _Admin) ->
+ ok;
+modify(Application, [Class|Classes], Admin) ->
+ ?DEBUG(io:format("modifying class ~p\n", [Class])),
+ [{_,Tables}] = ?ets_lookup(Admin, Class),
+ modify_class(Application, Class, table_lookup_batch(Tables, Class, all),
+ Admin),
+ modify(Application, Classes, Admin).
+
+modify_class(_Application, _Class, [], _Admin) ->
+ ok;
+modify_class({Mod, Fun, ExtraArgs}, Class, [Key|Keys], Admin) ->
+ {ok, {{value, Value}, _Admin}} = handle_lookup(Class, Key, Admin),
+ %% The applications think that a key consists of {class, primkey},
+ %% so let them.
+ case apply(Mod,Fun,[{{Class, Key}, Value}|ExtraArgs]) of
+ {ok,{{NewClass, NewKey}, NewValue}} -> % The item is modified.
+ %% remove old instance, insert new
+ %% JALI could be optimized (we don't care about previous values),
+ %% but ets_delete/insert is *not* enough
+ handle_delete(Class, Key, Admin),
+ handle_insert(NewClass, NewKey, NewValue, Admin);
+ true -> % The item should be left as it is.
+ ok;
+ false -> % The item should be removed!
+ %% JALI could be optimized (we don't care about previous values),
+ %% but ets_delete is *not* enough
+ handle_delete(Class, Key, Admin)
+ end,
+ modify_class({Mod, Fun, ExtraArgs}, Class, Keys, Admin).
+
+%%% dump_prepare_classes(Classes,Admin) -> ok
+%%%
+%%% Create a Ttab for each class, and insert
+%%% the new table order in Admin
+
+dump_prepare_classes(Classes,Admin) ->
+ ?DEBUG(io:format("DUMP CLASSES ~w\n",[Classes])),
+ dump_prepare_classes(Classes,Admin,[]).
+
+dump_prepare_classes([],_Admin,Ack) ->
+ Ack;
+dump_prepare_classes([Class|Classes],Admin,Ack) ->
+ [{_Class,[Mtab]}] = ?ets_lookup(Admin,Class),
+ %% Only one table => we can prepare for dumping
+ %% In case there are several tables defined, dumping is
+ %% already (still) in progress for this class (database inconsistent)
+ Ttab = make_db_table(db_name(Admin),Class),
+ ?ets_insert(Admin,{Class,[Ttab,Mtab]}),
+ dump_prepare_classes(Classes,Admin,lists:append(Ack,[{Class,Mtab}])).
+
+%%% copy_dump_entry(SourceTable,Key,DestinationTable) -> NobodyCares
+%%%
+%%% Copies Key from SourceTable to DestinationTable.
+%%% If Key is an erase record, then the corresponding entry is deleted
+%%% from DestinationTable, if it should be (see Erasure during dump, above)
+
+copy_dump_entry(Stab,Key,Dtab) ->
+ ?DEBUG(io:format("Copying key ~p\n",[Key])),
+ case ?ets_lookup(Stab,Key) of
+ [{?ERASE_MARK(RealKey),_}] ->
+ %% Only erase if the entry RealKey hasn't been written again
+ case ?ets_lookup(Stab,RealKey) of
+ [] ->
+ %% No, it hasn't: we should delete
+ ?DEBUG(io:format("Erasing: ~p\n",[RealKey])),
+ ?ets_delete(Dtab,RealKey);
+ _Definition ->
+ %% It has, don't erase. In this case the new value
+ %% has already or will soon be written to Dtab
+ ok
+ end;
+ [KeyVal] ->
+ ?DEBUG(io:format("Forwarding: ~p\n",[KeyVal])),
+ ?ets_insert(Dtab,KeyVal)
+ end.
+
+%%% DUMP LOADING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+load_dump(DbName,DumpDir) ->
+ case phys_load_dump_ok(DumpDir) of
+ ok ->
+ Admin = make_admin_table(DbName),
+ ?ets_insert(Admin,{?DB_NAME_KEY,DbName}),
+ case phys_load_table(DumpDir,0,Admin) of
+ ok ->
+ load_dump2(DumpDir,Admin);
+ Other ->
+ load_dump_failed(Admin,[]),
+ {error,{load_dump1,Other}}
+ end;
+ Other ->
+ {error,{load_dump2,Other}}
+ end.
+
+load_dump2(DumpDir,Admin) ->
+ case ?ets_lookup(Admin,?LIST_OF_CLASSES_KEY) of
+ [{_Key,Classes}] ->
+ case load_dump_tables(DumpDir,Admin,Classes) of
+ ok ->
+ {ok, Admin};
+ Other ->
+ io:format("Dumping failed: ~p\n",[Other]),
+ load_dump_failed(Admin,Classes)
+ end;
+ Other ->
+ io:format("Dumping failed2: ~p\n",[Other]),
+ load_dump_failed(Admin,[])
+ end.
+
+load_dump_failed(Admin,[]) ->
+ ?ets_delete(Admin),
+ {error,load_dump_failed};
+load_dump_failed(Admin,[Class|Classes]) ->
+ case ?ets_lookup(Admin,Class) of
+ [{_Key,[Tab]}] ->
+ ?ets_delete(Tab);
+ _ ->
+ ok
+ end,
+ load_dump_failed(Admin,Classes).
+
+load_dump_tables(_DumpDir,_Admin,[]) ->
+ ok;
+load_dump_tables(DumpDir,Admin,[Class|Classes]) ->
+ Mtab = make_db_table(db_name(Admin),Class),
+ ?ets_insert(Admin,{Class,[Mtab]}),
+ Num = length(Classes)+1,
+ case phys_load_table(DumpDir,Num,Mtab) of
+ ok ->
+ load_dump_tables(DumpDir,Admin,Classes);
+ Other ->
+ {error,{load_dump_failed,Other}}
+ end.
+
+%%% FILE ACCESS LAYER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% phys_init_dump(Class,DumpDir) -> DumpData
+
+phys_init_dump(Class,DumpDir,Num) ->
+ ?DEBUG(io:format("Opened ~p for writing\n",[Class])),
+ FileName = [DumpDir,"/etsdump.",integer_to_list(Num)],
+ {tag1,{ok,Fd}} = {tag1,file:open(FileName,write)},
+ {Class,Fd}.
+
+%%% phys_finish_dump(DumpData) -> NobodyCares
+
+phys_finish_dump({_Class,Fd}) ->
+ ?DEBUG(io:format("Closed ~p\n",[_Class])),
+ phys_dump_term(Fd,ok),
+ file:close(Fd), % JALI: OTP P1D returns true instead of ok, so no check
+ ok.
+
+%%% phys_dump(KeyVal,DumpData) -> NobodyCares
+
+phys_dump({Key,Val},{_Class,Fd}) ->
+ ?DEBUG(io:format("To disk (~p.dump): {~p,~p}\n",[_Class,Key,Val])),
+ phys_dump_term(Fd,{Key,Val}),
+ ok.
+
+phys_dump_term(Fd,Term) ->
+ Bin = binary_to_list(term_to_binary(Term)),
+ {tag2,ok} = {tag2,io:put_chars(Fd,encode32(length(Bin)))},
+ {tag3,ok} = {tag3,io:put_chars(Fd,Bin)}.
+
+%%% phys_ok_dump(DumpDir) -> NobodyCares
+
+phys_ok_dump(DumpDir) ->
+ ?DEBUG(io:format("Ok:ing dump dir ~s\n",[DumpDir])),
+ FileName = [DumpDir,"/ok"],
+ {tag4,{ok,Fd}} = {tag4,file:open(FileName,write)},
+ {tag5,ok} = {tag5,io:format(Fd,"ok.\n",[])},
+ file:close(Fd), % JALI: OTP P1D returns true instead of ok, so no check
+ ok.
+
+phys_remove_ok(DumpDir) ->
+ ?DEBUG(io:format("Removing any Ok in dump dir ~s\n",[DumpDir])),
+ FileName = [DumpDir,"/ok"],
+ %% don't care if delete returns ok, file probably doesn't exist
+ file:delete(FileName),
+ ok.
+
+phys_load_dump_ok(DumpDir) ->
+ FileName = [DumpDir,"/ok"],
+ case file:consult(FileName) of
+ {ok,[ok]} ->
+ ok;
+ Other ->
+ {error,{consult_error,Other}}
+ end.
+
+phys_load_table(DumpDir,N,Tab) ->
+ ?DEBUG(io:format("LOAD TABLE ~w\n",[N])),
+ FileName = [DumpDir,"/etsdump.",integer_to_list(N)],
+ case file:open(FileName,read) of
+ {ok,Fd} ->
+ phys_load_entries(Fd,Tab);
+ Other ->
+ {error,{open_error,Other}}
+ end.
+
+phys_load_entries(Fd,Tab) ->
+ case phys_read_len(Fd) of
+ {ok,Len} ->
+ case phys_read_entry(Fd,Len) of
+ {ok,ok} ->
+ ok;
+ {ok,{Key,Val}} ->
+ ?ets_insert(Tab,{Key,Val}),
+ phys_load_entries(Fd,Tab);
+ Other ->
+ {error,{read_len,Other}}
+ end;
+ Other ->
+ {error,{read_len2,Other}}
+ end.
+
+phys_read_len(Fd) ->
+ case io:get_chars(Fd,'',4) of
+ [A,B,C,D] ->
+ {ok,decode32(A,B,C,D)};
+ Other ->
+ {error,{decode,Other}}
+ end.
+
+phys_read_entry(Fd,Len) ->
+ case io:get_chars(Fd,'',Len) of
+ L when list(L), length(L) == Len ->
+ {ok,binary_to_term(list_to_binary(L))};
+ Other ->
+ {error,{read_term,Other}}
+ end.
+
+encode32(N) ->
+ [(N bsr 24) band 255,
+ (N bsr 16) band 255,
+ (N bsr 8) band 255,
+ N band 255].
+
+decode32(A,B,C,D) ->
+ (A bsl 24) bor (B bsl 16) bor (C bsl 8) bor D.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/stdlib/test/file_sorter_SUITE.erl b/lib/stdlib/test/file_sorter_SUITE.erl
new file mode 100644
index 0000000000..c00ed91fe7
--- /dev/null
+++ b/lib/stdlib/test/file_sorter_SUITE.erl
@@ -0,0 +1,1345 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(file_sorter_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(t,test_server).
+-define(privdir(_), "./file_sorter_SUITE_priv").
+-else.
+-include("test_server.hrl").
+-define(format(S, A), ok).
+-define(privdir(Conf), ?config(priv_dir, Conf)).
+-endif.
+
+-export([all/1, basic/1, badarg/1,
+ term_sort/1, term_keysort/1,
+ binary_term_sort/1, binary_term_keysort/1,
+ binary_sort/1,
+ term_merge/1, term_keymerge/1,
+ binary_term_merge/1, binary_term_keymerge/1,
+ binary_merge/1,
+ term_check/1, term_keycheck/1,
+ binary_term_check/1, binary_term_keycheck/1,
+ binary_check/1,
+ inout/1, misc/1, many/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+init_per_testcase(_Case, Config) ->
+ Dog=?t:timetrap(?t:minutes(2)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ {req,[stdlib,kernel],
+ [basic, badarg,
+ term_sort, term_keysort,
+ binary_term_sort, binary_term_keysort,
+ binary_sort,
+ term_merge, term_keymerge,
+ binary_term_merge, binary_term_keymerge,
+ binary_merge,
+ term_check, binary_term_keycheck,
+ binary_term_check, binary_term_keycheck,
+ binary_check,
+ inout, misc, many]}.
+
+basic(doc) ->
+ ["Basic test case."];
+basic(suite) ->
+ [];
+basic(Config) when is_list(Config) ->
+ Fmt = binary,
+ Arg = {format,Fmt},
+ Foo = outfile(foo, Config),
+ P0 = pps(),
+
+ ?line F1s = [F1] = to_files([[]], Fmt, Config),
+ ?line ok = file_sorter:sort(F1),
+ ?line [] = from_files(F1, Fmt),
+ ?line ok = file_sorter:keysort(17, F1),
+ ?line [] = from_files(F1, Fmt),
+ ?line ok = file_sorter:merge(F1s, Foo),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:keymerge(17, F1s, Foo),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files([Foo | F1s]),
+
+ ?line [F2] = to_files([[foo,bar]], Fmt, Config),
+ ?line ok = file_sorter:sort([F2], F2, Arg),
+ ?line [bar,foo] = from_files(F2, Fmt),
+ ?line delete_files(F2),
+
+ ?line Fs1 = to_files([[foo],[bar]], Fmt, Config),
+ ?line ok = file_sorter:sort(Fs1, Foo, Arg),
+ ?line [bar,foo] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:merge(Fs1, Foo, Arg),
+ ?line [bar,foo] = from_files(Foo, Fmt),
+ ?line delete_files([Foo | Fs1]),
+
+ ?line Fmt2 = binary_term,
+ ?line Arg2 = {format, Fmt2},
+ ?line [F3] = to_files([[{foo,1},{bar,2}]], Fmt2, Config),
+ ?line ok = file_sorter:keysort([2], [F3], F3, Arg2),
+ ?line [{foo,1},{bar,2}] = from_files(F3, Fmt2),
+ ?line delete_files(F3),
+
+ ?line Fs2 = to_files([[{foo,1}],[{bar,2}]], Fmt2, Config),
+ ?line ok = file_sorter:keysort(1, Fs2, Foo, Arg2),
+ ?line [{bar,2},{foo,1}] = from_files(Foo, Fmt2),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:keymerge(1, Fs2, Foo, Arg2),
+ ?line [{bar,2},{foo,1}] = from_files(Foo, Fmt2),
+ ?line delete_files([Foo | Fs2]),
+
+ ?line true = P0 =:= pps(),
+
+ ok.
+
+badarg(doc) ->
+ ["Call functions with bad arguments."];
+badarg(suite) ->
+ [];
+badarg(Config) when is_list(Config) ->
+ PrivDir = ?privdir(Config),
+ BadFile = filename:join(PrivDir, "not_a_file"),
+ ABadFile = filename:absname(BadFile),
+ ?line file:delete(BadFile),
+ ?line {error,{file_error,ABadFile,enoent}} =
+ file_sorter:sort(BadFile),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:sort({flipp})),
+ ?line {error,{file_error,ABadFile,enoent}} =
+ file_sorter:keysort(1, BadFile),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:keysort(1, {flipp})),
+
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:merge([{flipp}],foo)),
+ ?line {error,{file_error,ABadFile,enoent}} =
+ file_sorter:keymerge(1,[BadFile],foo),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:keymerge(1,[{flipp}],foo)),
+ ?line {'EXIT', {{badarg, _}, _}} =
+ (catch file_sorter:merge(fun(X) -> X end, foo)),
+ ?line {'EXIT', {{badarg, _}, _}} =
+ (catch file_sorter:keymerge(1, fun(X) -> X end, foo)),
+
+ ?line {error,{file_error,ABadFile,enoent}} =
+ file_sorter:check(BadFile),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:check({flipp})),
+ ?line {error,{file_error,ABadFile,enoent}} =
+ file_sorter:keycheck(1, BadFile),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:keycheck(1, {flipp})),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:check([{flipp}],foo)),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:keycheck(1,[{flipp}],foo)),
+ ?line {'EXIT', {{badarg, _}, _}} =
+ (catch file_sorter:check(fun(X) -> X end, foo)),
+ ?line {'EXIT', {{badarg, _}, _}} =
+ (catch file_sorter:keycheck(1, fun(X) -> X end, foo)),
+
+ ?line Fs1 = to_files([[1,2,3]], binary_term, Config),
+ ?line {'EXIT', {{badarg, flipp}, _}} =
+ (catch file_sorter:check(Fs1 ++ flipp, [])),
+ [F1] = Fs1,
+ ?line {error,{file_error,_,_}} =
+ file_sorter:sort(Fs1, foo, [{tmpdir,F1},{size,0}]),
+ ?line delete_files(Fs1),
+ ?line Fs2 = to_files([[1,2,3]], binary_term, Config),
+ {error,{file_error,_,enoent}} =
+ file_sorter:sort(Fs2, foo, [{tmpdir,filename:absname(BadFile)},
+ {size,0}]),
+ ?line delete_files(Fs2),
+
+ ?line {'EXIT', {{badarg, bad}, _}} =
+ (catch file_sorter:check([], [{format,term} | bad])),
+ ?line {'EXIT', {{badarg, [{flipp}]}, _}} =
+ (catch file_sorter:check([{flipp}])),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch file_sorter:keycheck(1, {flipp})),
+ ?line {'EXIT', {{badarg, [{flipp}]}, _}} =
+ (catch file_sorter:keycheck(2, [{flipp}])),
+ ?line {error,{file_error,_,eisdir}} = file_sorter:keycheck(1, []),
+ ?line {'EXIT', {{badarg, kp}, _}} = (catch file_sorter:keycheck(kp, [])),
+ ?line {'EXIT', {{badarg, kp}, _}} =
+ (catch file_sorter:keycheck([1, kp], [])),
+ ?line {'EXIT', {{badarg, kp}, _}} =
+ (catch file_sorter:keycheck([1 | kp], [])),
+ ?line {'EXIT', {{badarg, []}, _}} = (catch file_sorter:keycheck([], [])),
+ ?line {'EXIT', {{badarg, {format, foo}}, _}} =
+ (catch file_sorter:check([], {format,foo})),
+ ?line {'EXIT', {{badarg, not_an_option}, _}} =
+ (catch file_sorter:keycheck(7, [], [not_an_option])),
+ ?line {'EXIT', {{badarg, format}, _}} =
+ (catch file_sorter:keycheck(1, [], [{format, binary}])),
+ ?line {'EXIT', {{badarg, order}, _}} =
+ (catch file_sorter:keycheck(1, [], [{order, fun compare/2}])),
+
+ ?line do_badarg(fun(I, O) -> file_sorter:sort(I, O) end,
+ fun(Kp, I, O) -> file_sorter:keysort(Kp, I, O) end,
+ BadFile),
+ ?line do_badarg_opt(fun(I, O, X) -> file_sorter:sort(I, O, X) end,
+ fun(Kp, I, O, X) -> file_sorter:keysort(Kp, I, O, X)
+ end),
+ ?line do_badarg(fun(I, O) -> file_sorter:merge(I, O) end,
+ fun(Kp, I, O) -> file_sorter:keymerge(Kp, I, O) end,
+ BadFile),
+ ?line do_badarg_opt(fun(I, O, X) -> file_sorter:merge(I, O, X) end,
+ fun(Kp, I, O, X) -> file_sorter:keymerge(Kp, I, O, X)
+ end).
+
+do_badarg(F, KF, BadFile) ->
+ [Char | _] = BadFile,
+ AFlipp = filename:absname(flipp),
+ ?line {error,{file_error,AFlipp,enoent}} = F([flipp | flopp], foo),
+ ?line {'EXIT', {{badarg, {foo,bar}}, _}} = (catch F([], {foo,bar})),
+ ?line {'EXIT', {{badarg, Char}, _}} = (catch F(BadFile, [])),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} = (catch F({flipp}, [])),
+
+ ?line {'EXIT', {{badarg, Char}, _}} = (catch KF(1, BadFile, [])),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} = (catch KF(1, {flipp}, [])),
+ ?line {error,{file_error,AFlipp,enoent}} =
+ KF(2, [flipp | flopp], foo),
+ ?line {'EXIT', {{badarg, {foo,bar}}, _}} = (catch KF(1, [], {foo,bar})),
+ ?line {'EXIT', {{badarg, kp}, _}} = (catch KF(kp, [], foo)),
+ ?line {'EXIT', {{badarg, kp}, _}} = (catch KF([1, kp], [], foo)),
+ ?line {'EXIT', {{badarg, kp}, _}} = (catch KF([1 | kp], [], foo)),
+ ?line {'EXIT', {{badarg, []}, _}} = (catch KF([], [], foo)),
+ ok.
+
+do_badarg_opt(F, KF) ->
+ AFlipp = filename:absname(flipp),
+ ?line {error,{file_error,AFlipp,enoent}} =
+ F([flipp | flopp], foo, []),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} = (catch F([{flipp}], foo, [])),
+ ?line {'EXIT', {{badarg, {out,put}}, _}} = (catch F([], {out,put}, [])),
+ ?line {'EXIT', {{badarg, not_an_option}, _}} =
+ (catch F([], foo, [not_an_option])),
+ ?line {'EXIT', {{badarg, {format, foo}}, _}} =
+ (catch F([], foo, {format,foo})),
+ ?line {'EXIT', {{badarg, {size,foo}}, _}} = (catch F([], foo, {size,foo})),
+
+ ?line {'EXIT', {{badarg, {size, -1}}, _}} = (catch F([], foo, {size,-1})),
+ ?line {'EXIT', {{badarg, {no_files, foo}}, _}} =
+ (catch F([], foo, {no_files,foo})),
+ ?line {'EXIT', {{badarg, {no_files, 1}}, _}} =
+ (catch F([], foo, {no_files,1})),
+ ?line {'EXIT', {{badarg, 1}, _}} = (catch F([], foo, {tmpdir,1})),
+ ?line {'EXIT', {{badarg, {order,1}}, _}} = (catch F([], foo, {order,1})),
+ ?line {'EXIT', {{badarg, {compressed, flopp}}, _}} =
+ (catch F([], foo, {compressed,flopp})),
+ ?line {'EXIT', {{badarg, {unique,flopp}}, _}} =
+ (catch F([], foo, {unique,flopp})),
+ ?line {'EXIT', {{badarg, {header,foo}}, _}} =
+ (catch F([], foo, {header,foo})),
+ ?line {'EXIT', {{badarg, {header, 0}}, _}} =
+ (catch F([], foo, {header,0})),
+ ?line {'EXIT', {{badarg, {header, 1 bsl 35}}, _}} =
+ (catch F([], foo, {header,1 bsl 35})),
+ ?line {'EXIT', {{badarg, header}, _}} =
+ (catch F([], foo, [{header,1},{format,term}])),
+
+ ?line {'EXIT', {{badarg, not_an_option}, _}} =
+ (catch KF(7, [], foo, [not_an_option])),
+ ?line {'EXIT', {{badarg,format}, _}} =
+ (catch KF(1, [], foo, [{format, binary}])),
+ ?line {'EXIT', {{badarg, order}, _}} =
+ (catch KF(1, [], foo, [{order, fun compare/2}])),
+ ?line {'EXIT', {{badarg, {flipp}}, _}} =
+ (catch KF(2, [{flipp}], foo,[])),
+ ?line {error,{file_error,AFlipp,enoent}} =
+ KF(2, [flipp | flopp], foo,[]),
+ ?line {'EXIT', {{badarg, {out, put}}, _}} =
+ (catch KF(1, [], {out,put}, [])),
+ ?line {'EXIT', {{badarg, kp}, _}} = (catch KF(kp, [], foo, [])),
+ ?line {'EXIT', {{badarg, kp}, _}} = (catch KF([1, kp], [], foo, [])),
+ ?line {'EXIT', {{badarg, kp}, _}} = (catch KF([1 | kp], [], foo, [])),
+ ok.
+
+term_sort(doc) ->
+ ["Sort terms on files."];
+term_sort(suite) ->
+ [];
+term_sort(Config) when is_list(Config) ->
+ ?line sort(term, [{compressed,false}], Config),
+ ?line sort(term, [{order, fun compare/2}], Config),
+ ?line sort(term, [{order, ascending}, {compressed,true}], Config),
+ ?line sort(term, [{order, descending}], Config),
+ ok.
+
+term_keysort(doc) ->
+ ["Keysort terms on files."];
+term_keysort(suite) ->
+ [];
+term_keysort(Config) when is_list(Config) ->
+ ?line keysort(term, [{tmpdir, ""}], Config),
+ ?line keysort(term, [{order,descending}], Config),
+ ok.
+
+binary_term_sort(doc) ->
+ ["Sort binary terms on files."];
+binary_term_sort(suite) ->
+ [];
+binary_term_sort(Config) when is_list(Config) ->
+ PrivDir = ?privdir(Config),
+ ?line sort({2, binary_term}, [], Config),
+ ?line sort(binary_term, [{tmpdir, list_to_atom(PrivDir)}], Config),
+ ?line sort(binary_term, [{tmpdir,PrivDir}], Config),
+ ?line sort({3,binary_term}, [{order, fun compare/2}], Config),
+ ?line sort(binary_term, [{order, fun compare/2}], Config),
+ ?line sort(binary_term, [{order,descending}], Config),
+ ok.
+
+binary_term_keysort(doc) ->
+ ["Keysort binary terms on files."];
+binary_term_keysort(suite) ->
+ [];
+binary_term_keysort(Config) when is_list(Config) ->
+ ?line keysort({3, binary_term}, [], Config),
+ ?line keysort(binary_term, [], Config),
+ ?line keysort(binary_term, [{order,descending}], Config),
+ ok.
+
+binary_sort(doc) ->
+ ["Sort binaries on files."];
+binary_sort(suite) ->
+ [];
+binary_sort(Config) when is_list(Config) ->
+ PrivDir = ?privdir(Config),
+ ?line sort({2, binary}, [], Config),
+ ?line sort(binary, [{tmpdir, list_to_atom(PrivDir)}], Config),
+ ?line sort(binary, [{tmpdir,PrivDir}], Config),
+ ?line sort({3,binary}, [{order, fun compare/2}], Config),
+ ?line sort(binary, [{order, fun compare/2}], Config),
+ ?line sort(binary, [{order,descending}], Config),
+ ok.
+
+term_merge(doc) ->
+ ["Merge terms on files."];
+term_merge(suite) ->
+ [];
+term_merge(Config) when is_list(Config) ->
+ ?line merge(term, [{order, fun compare/2}], Config),
+ ?line merge(term, [{order, ascending}, {compressed,true}], Config),
+ ?line merge(term, [{order, descending}, {compressed,false}], Config),
+ ok.
+
+term_keymerge(doc) ->
+ ["Keymerge terms on files."];
+term_keymerge(suite) ->
+ [];
+term_keymerge(Config) when is_list(Config) ->
+ ?line keymerge(term, [], Config),
+ ?line keymerge(term, [{order, descending}], Config),
+ ?line funmerge(term, [], Config),
+ ok.
+
+binary_term_merge(doc) ->
+ ["Merge binary terms on files."];
+binary_term_merge(suite) ->
+ [];
+binary_term_merge(Config) when is_list(Config) ->
+ ?line merge(binary_term, [], Config),
+ ?line merge({7, binary_term}, [], Config),
+ ?line merge({3, binary_term}, [{order, fun compare/2}], Config),
+ ok.
+
+binary_term_keymerge(doc) ->
+ ["Keymerge binary terms on files."];
+binary_term_keymerge(suite) ->
+ [];
+binary_term_keymerge(Config) when is_list(Config) ->
+ ?line keymerge({3, binary_term}, [], Config),
+ ?line keymerge(binary_term, [], Config),
+ ?line funmerge({3, binary_term}, [], Config),
+ ?line funmerge(binary_term, [], Config),
+ ok.
+
+binary_merge(doc) ->
+ ["Merge binaries on files."];
+binary_merge(suite) ->
+ [];
+binary_merge(Config) when is_list(Config) ->
+ ?line merge(binary, [], Config),
+ ?line merge({7, binary}, [], Config),
+ ?line merge({3, binary}, [{order, fun compare/2}], Config),
+ ok.
+
+term_check(doc) ->
+ ["Check terms on files."];
+term_check(suite) ->
+ [];
+term_check(Config) when is_list(Config) ->
+ ?line check(term, Config),
+ ok.
+
+binary_term_check(doc) ->
+ ["Check binary terms on files."];
+binary_term_check(suite) ->
+ [];
+binary_term_check(Config) when is_list(Config) ->
+ ?line check(binary_term, Config),
+ ok.
+
+term_keycheck(doc) ->
+ ["Keycheck terms on files."];
+term_keycheck(suite) ->
+ [];
+term_keycheck(Config) when is_list(Config) ->
+ ?line keycheck(term, Config),
+ ok.
+
+binary_term_keycheck(doc) ->
+ ["Keycheck binary terms on files."];
+binary_term_keycheck(suite) ->
+ [];
+binary_term_keycheck(Config) when is_list(Config) ->
+ ?line keycheck(binary_term, Config),
+ ok.
+
+binary_check(doc) ->
+ ["Check binary terms on files."];
+binary_check(suite) ->
+ [];
+binary_check(Config) when is_list(Config) ->
+ ?line check(binary, Config),
+ ok.
+
+inout(doc) ->
+ ["Funs as input or output."];
+inout(suite) ->
+ [];
+inout(Config) when is_list(Config) ->
+ BTF = {format, binary_term},
+ Foo = outfile(foo, Config),
+
+ %% Input is fun.
+ End = fun(read) -> end_of_input end,
+
+ IF1 = fun(read) -> {[1,7,5], End} end,
+ ?line ok = file_sorter:sort(IF1, Foo, [{format, term}]),
+ %% 'close' is called, but the return value is caught and ignored.
+ IF2 = fun(read) -> {[1,2,3], fun(close) -> throw(ignored) end} end,
+ ?line {error, bad_object} = file_sorter:sort(IF2, Foo, BTF),
+
+ IF3 = fun(no_match) -> foo end,
+ ?line {'EXIT', {function_clause, _}} =
+ (catch file_sorter:sort(IF3, Foo)),
+ IF4 = fun(read) -> throw(my_message) end,
+ ?line my_message = (catch file_sorter:sort(IF4, Foo)),
+ IF5 = fun(read) -> {error, my_error} end,
+ ?line {error, my_error} = file_sorter:sort(IF5, Foo),
+
+ %% Output is fun.
+ ?line {error, bad_object} =
+ file_sorter:sort(IF2, fun(close) -> ignored end, BTF),
+ Args = [{format, term}],
+ ?line {error, bad_object} =
+ file_sorter:keysort(1, IF2, fun(close) -> ignored end, Args),
+ OF1 = fun(close) -> fine; (L) when is_list(L) -> fun(close) -> nice end end,
+ ?line nice = file_sorter:sort(IF1, OF1, Args),
+ OF2 = fun(_) -> my_return end,
+ ?line my_return = file_sorter:sort(IF1, OF2, Args),
+ OF3 = fun(_) -> throw(my_message) end,
+ ?line my_message = (catch file_sorter:sort(IF1, OF3, Args)),
+ OF4 = fun(no_match) -> foo end,
+ ?line {'EXIT', {function_clause, _}} =
+ (catch file_sorter:sort(IF1, OF4, Args)),
+
+ ?line P0 = pps(),
+ ?line Fs1 = to_files([[3,1,2,5,4], [8,3,10]], term, Config),
+ ?line error = file_sorter:sort(Fs1, fun(_) -> error end, Args),
+ ?line delete_files(Fs1),
+
+ ?line true = P0 =:= pps(),
+
+ %% Passing a value from the input functions to the output functions.
+ IFV1 = fun(read) -> {end_of_input, 17} end,
+ OFV1 = fun({value, Value}) -> ofv(Value, []) end,
+ ?line {17, []} = file_sorter:sort(IFV1, OFV1, Args),
+
+ %% Output is not a fun. The value returned by input funs is ignored.
+ %% OTP-5009.
+ ?line ok = file_sorter:sort(IFV1, Foo, [{format,term}]),
+ ?line [] = from_files(Foo, term),
+ ?line delete_files(Foo),
+
+ ok.
+
+ofv(Value, A) ->
+ fun(close) ->
+ {Value, lists:append(lists:reverse(A))};
+ (L) when is_list(L) ->
+ ofv(Value, [L | A])
+ end.
+
+many(doc) ->
+ ["Many temporary files."];
+many(suite) ->
+ [];
+many(Config) when is_list(Config) ->
+ Foo = outfile(foo, Config),
+ PrivDir = ?privdir(Config),
+ P0 = pps(),
+
+ Args = [{format, term}],
+ L1 = lists:map(fun(I) -> {one, two, three, I} end, lists:seq(1,1000)),
+ L2 = lists:map(fun(I) -> {four, five, six, I} end, lists:seq(1,1000)),
+ ?line Fs2 = to_files([L1, L2], term, Config),
+ ?line ok = file_sorter:sort(Fs2, Foo, [{size,1000} | Args]),
+ ?line R = lists:sort(L1++L2),
+ ?line R = from_files(Foo, term),
+ ?line 2000 = length(R),
+ ?line ok = file_sorter:sort(Fs2, Foo, [{no_files,4},{size,1000} | Args]),
+ ?line R = from_files(Foo, term),
+ ?line ok =
+ file_sorter:sort(Fs2, Foo,
+ [{no_files,4},{size,1000},{order,descending} | Args]),
+ ?line true = lists:reverse(R) =:= from_files(Foo, term),
+ ?line ok =
+ file_sorter:sort(Fs2, Foo,
+ [{no_files,4},{size,1000},
+ {order,fun compare/2} | Args]),
+ ?line R = from_files(Foo, term),
+ ?line ok = file_sorter:keysort(4, Fs2, Foo,
+ [{no_files,4},{size,1000} | Args]),
+ ?line RK = lists:keysort(4, L1++L2),
+ ?line RK = from_files(Foo, term),
+ ?line delete_files(Foo),
+ ?line ok =
+ file_sorter:keysort(4, Fs2, Foo,
+ [{no_files,4},{size,1000},{order,descending} | Args]),
+ ?line true = lists:reverse(RK) =:= from_files(Foo, term),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:keysort(4, Fs2, Foo,
+ [{size,500},{order,descending} | Args]),
+ ?line true = lists:reverse(RK) =:= from_files(Foo, term),
+ ?line delete_files(Foo),
+ ?line error = file_sorter:sort(Fs2, fun(_) -> error end,
+ [{tmpdir, PrivDir}, {no_files,3},
+ {size,10000} | Args]),
+
+ TmpDir = filename:join(PrivDir, "tmpdir"),
+ file:del_dir(TmpDir),
+ ?line ok = file:make_dir(TmpDir),
+ ?line case os:type() of
+ {unix, _} ->
+ ?line ok = file:change_mode(TmpDir, 8#0000),
+ ?line {error, {file_error, _,_}} =
+ file_sorter:sort(Fs2, fun(_M) -> foo end,
+ [{no_files,3},{size,10000},
+ {tmpdir,TmpDir} | Args]);
+ _ ->
+ true
+ end,
+ ?line ok = file:del_dir(TmpDir),
+ delete_files(Fs2),
+ ?line true = P0 =:= pps(),
+ ok.
+
+misc(doc) ->
+ ["Some other tests."];
+misc(suite) ->
+ [];
+misc(Config) when is_list(Config) ->
+ BTF = {format, binary_term},
+ Foo = outfile(foo, Config),
+ FFoo = filename:absname(Foo),
+ P0 = pps(),
+
+ ?line [File] = Fs1 = to_files([[1,3,2]], term, Config),
+ ?line ok = file:write_file(Foo,<<>>),
+ ?line case os:type() of
+ {unix, _} ->
+ ok = file:change_mode(Foo, 8#0000),
+ {error,{file_error,FFoo,eacces}} =
+ file_sorter:sort(Fs1, Foo, {format,term});
+ _ ->
+ true
+ end,
+ ?line file:delete(Foo),
+ ?line NoBytes = 16, % RAM memory will never get this big, or?
+ ?line ALot = (1 bsl (NoBytes*8)) - 1,
+ ?line ok = file:write_file(File, <<ALot:NoBytes/unit:8,"foobar">>),
+ FFile = filename:absname(File),
+ ?line {error, {bad_object,FFile}} =
+ file_sorter:sort(Fs1, Foo, [BTF, {header, 20}]),
+ ?line ok = file:write_file(File, <<30:32,"foobar">>),
+ ?line {error, {premature_eof, FFile}} = file_sorter:sort(Fs1, Foo, BTF),
+ ?line ok = file:write_file(File, <<6:32,"foobar">>),
+ ?line {error, {bad_object,FFile}} = file_sorter:sort(Fs1, Foo, BTF),
+ ?line case os:type() of
+ {unix, _} ->
+ ok = file:change_mode(File, 8#0000),
+ {error, {file_error,FFile,eacces}} =
+ file_sorter:sort(Fs1, Foo),
+ {error, {file_error,FFile,eacces}} =
+ file_sorter:sort(Fs1, Foo, {format, binary_term});
+ _ ->
+ true
+ end,
+ ?line delete_files(Fs1),
+ ?line true = P0 =:= pps(),
+
+ %% bigger than chunksize
+ ?line E1 = <<32000:32, 10:256000>>,
+ ?line E2 = <<32000:32, 5:256000>>,
+ ?line E3 = <<32000:32, 8:256000>>,
+ ?line ok = file:write_file(Foo, [E1, E2, E3]),
+ ?line ok = file_sorter:sort([Foo], Foo, [{format,binary},{size,10000}]),
+ ?line ok = file_sorter:sort([Foo], Foo, [{format,fun(X) -> X end},
+ {size,10000}]),
+ ?line Es = list_to_binary([E2,E3,E1]),
+ ?line {ok, Es} = file:read_file(Foo),
+ ?line delete_files(Foo),
+ ?line true = P0 =:= pps(),
+
+ %% keysort more than one element
+ L = [{c,1,a},{c,2,b},{c,3,c},{b,1,c},{b,2,b},{b,3,a},{a,1,a},{a,2,b},
+ {a,3,c}],
+ ?line Fs2 = to_files([L], binary_term, Config),
+ ?line ok = file_sorter:keysort([2,3], Fs2, Foo, {format, binary_term}),
+ ?line KS2_1 = from_files(Foo, binary_term),
+ ?line KS2_2 = lists:keysort(2,lists:keysort(3, L)),
+ ?line KS2_1 = KS2_2,
+ ?line ok = file_sorter:keysort([2,3], Fs2, Foo,
+ [{format, binary_term},{size,5}]),
+ ?line KS2_3 = from_files(Foo, binary_term),
+ ?line KS2_3 = KS2_2,
+ ?line ok = file_sorter:keysort([2,3,1], Fs2, Foo, {format, binary_term}),
+ ?line KS3_1 = from_files(Foo, binary_term),
+ ?line KS3_2 = lists:keysort(2, lists:keysort(3,lists:keysort(1, L))),
+ ?line KS3_1 = KS3_2,
+ ?line ok = file_sorter:keysort([2,3,1], Fs2, Foo,
+ [{format, binary_term},{size,5}]),
+ ?line KS3_3 = from_files(Foo, binary_term),
+ ?line KS3_3 = KS3_2,
+ ?line delete_files([Foo | Fs2]),
+ ?line true = P0 =:= pps(),
+
+ %% bigger than chunksize
+ %% Assumes that CHUNKSIZE = 16384. Illustrates that the Last argument
+ %% of merge_files/5 is necessary.
+ ?line EP1 = erlang:make_tuple(2728,foo),
+ ?line EP2 = lists:duplicate(2729,qqq),
+ ?line LL = [EP1, EP2, EP1, EP2, EP1, EP2],
+ ?line Fs3 = to_files([LL], binary, Config),
+ ?line ok = file_sorter:sort(Fs3, Foo, [{format,binary}, {unique,true}]),
+ ?line [EP1,EP2] = from_files(Foo, binary),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:sort(Fs3, Foo,
+ [{format,binary_term}, {unique,true},
+ {size,30000}]),
+ ?line [EP1,EP2] = from_files(Foo, binary_term),
+ ?line delete_files([Foo | Fs3]),
+
+ ?line true = P0 =:= pps(),
+
+ ?line BE1 = <<20000:32, 17:160000>>,
+ ?line BE2 = <<20000:32, 1717:160000>>,
+ ?line ok = file:write_file(Foo, [BE1,BE2,BE1,BE2]),
+ ?line ok = file_sorter:sort([Foo], Foo, [{format,binary},
+ {size,10000},
+ {unique,true}]),
+ ?line BEs = list_to_binary([BE1, BE2]),
+ ?line {ok, BEs} = file:read_file(Foo),
+ ?line delete_files(Foo),
+ ?line true = P0 =:= pps(),
+
+ ?line Fs4 = to_files([[7,4,1]], binary_term, Config),
+ ?line {error, {bad_term, _}} = file_sorter:sort(Fs4, Foo, {format, term}),
+ ?line delete_files([Foo | Fs4]),
+ ?line true = P0 =:= pps(),
+
+ ok.
+
+%%%
+%%% Utilities.
+%%%
+
+sort(Fmt, XArgs, Config) ->
+ Args = make_args(Fmt, [{size,5} | XArgs]),
+ TmpArgs = [{tmpdir,?privdir(Config)} | Args],
+ Foo = outfile(foo, Config),
+
+ %% Input is a fun. Output is a fun.
+ ?line [] = file_sorter:sort(input([], 2, Fmt), output([], Fmt), Args),
+ ?line L1 = [3,1,2,5,4],
+ ?line S1 = file_sorter:sort(input(L1, 2, Fmt), output([], Fmt), TmpArgs),
+ ?line S1 = rev(lists:sort(L1), TmpArgs),
+
+ %% Input is a file. Output is a fun.
+ ?line [] = file_sorter:sort([], output([], Fmt), Args),
+ ?line L2 = [3,1,2,5,4],
+ ?line Fs1 = to_files([L2], Fmt, Config),
+ ?line S2 = file_sorter:sort(Fs1, output([], Fmt), TmpArgs),
+ ?line S2 = rev(lists:sort(L2), TmpArgs),
+ ?line delete_files(Fs1),
+
+ %% Input is a file. Output is a file
+ ?line ok = file_sorter:sort([], Foo, Args),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:sort([], Foo, [{unique,true} | Args]),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line L3 = [3,1,2,5,4,6],
+ ?line Fs2 = to_files([L3], Fmt, Config),
+ ?line ok = file_sorter:sort(Fs2, Foo, Args),
+ ?line true = rev(lists:sort(L3), Args) =:= from_files(Foo, Fmt),
+ ?line delete_files([Foo | Fs2]),
+ ?line L4 = [1,3,4,1,2,5,4,5,6],
+ ?line Fs3 = to_files([L4], Fmt, Config),
+ ?line ok = file_sorter:sort(Fs3, Foo, Args++[{unique,true},
+ {size,100000}]),
+ ?line true = rev(lists:usort(L4), Args) =:= from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:sort(Fs3, Foo, Args++[{unique,true}]),
+ ?line true = rev(lists:usort(L4), Args) =:= from_files(Foo, Fmt),
+ ?line delete_files([Foo | Fs3]),
+
+ %% Input is a fun. Output is a file.
+ ?line ok = file_sorter:sort(input([], 2, Fmt), Foo, Args),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line L5 = [3,1,2,5,4,7],
+ ?line ok = file_sorter:sort(input(L5, 2, Fmt), Foo, Args),
+ ?line true = rev(lists:sort(L5), Args) =:= from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+
+ %% Removing duplicate keys.
+ KFun = key_compare(2),
+ L6 = [{5,e},{2,b},{3,c},{1,a},{4,d}] ++ [{2,c},{1,b},{4,a}],
+ KUArgs = lists:keydelete(order, 1, Args) ++
+ [{unique, true}, {order, KFun},{size,100000}],
+ ?line ok = file_sorter:sort(input(L6, 2, Fmt), Foo, KUArgs),
+ ?line true = rev(lists:ukeysort(2, L6), KUArgs) =:= from_files(Foo, Fmt),
+ KArgs = lists:keydelete(unique, 1, KUArgs),
+ ?line ok = file_sorter:sort(input(L6, 2, Fmt), Foo, KArgs),
+ ?line true = rev(lists:keysort(2, L6), KArgs) =:= from_files(Foo, Fmt),
+
+ %% Removing duplicate keys. Again.
+ KUArgs2 = lists:keydelete(order, 1, Args) ++
+ [{unique, true}, {order, KFun},{size,5}],
+ ?line ok = file_sorter:sort(input(L6, 2, Fmt), Foo, KUArgs2),
+ ?line true = rev(lists:ukeysort(2, L6), KUArgs2) =:= from_files(Foo, Fmt),
+ KArgs2 = lists:keydelete(unique, 1, KUArgs2),
+ ?line ok = file_sorter:sort(input(L6, 2, Fmt), Foo, KArgs2),
+ ?line true = rev(lists:keysort(2, L6), KArgs2) =:= from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+
+ ok.
+
+keysort(Fmt, XArgs, Config) ->
+ Args = make_args(Fmt, [{size,50}, {no_files, 2} | XArgs]),
+ TmpArgs = Args ++ [{tmpdir,?privdir(Config)}],
+ Foo = outfile(foo, Config),
+
+ %% Input is files. Output is a file.
+ ?line ok = file_sorter:keysort(2, [], Foo, Args),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:keysort(2, [], Foo, [{unique,true} | Args]),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line L0 = [{a,2},{a,1},{a,2},{a,2},{a,1},{a,2},{a,2},{a,3}],
+ ?line Fs0 = to_files([L0], Fmt, Config),
+ ?line S = rev(lists:ukeysort(1, L0), Args),
+ ?line ok =
+ file_sorter:keysort(1, Fs0, Foo, Args ++ [{unique,true},
+ {size,100000}]),
+ ?line S = from_files(Foo, Fmt),
+ ?line ok =
+ file_sorter:keysort(1, Fs0, Foo, Args ++ [{unique,true},
+ {size,5}]),
+ ?line S = from_files(Foo, Fmt),
+ ?line ok = file_sorter:keysort(1, Fs0, Foo, Args ++ [{unique,true}]),
+ ?line S = from_files(Foo, Fmt),
+ ?line delete_files([Foo | Fs0]),
+ ?line L11 = [{a,1,x4},{b,2,x4},{c,3,x4}],
+ ?line L21 = [{a,1,x3},{b,2,x3},{c,3,x3}],
+ ?line L31 = [{a,1,x2},{b,2,x2},{c,3,x2}],
+ ?line L41 = [{a,1,x1},{b,2,x1},{c,3,x1}],
+ ?line All = [L11, L21, L31, L41],
+ ?line AllFlat = lists:append(All),
+ ?line Sorted = rev(lists:keysort(2, AllFlat), Args),
+ ?line Fs1 = to_files(All, Fmt, Config),
+ ?line ok = file_sorter:keysort(2, Fs1, Foo, Args),
+ ?line Sorted = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+
+ %% Input is files. Output is a fun.
+ ?line [] = file_sorter:keysort(2, [], output([], Fmt), Args),
+ ?line KS1 = file_sorter:keysort(2, Fs1, output([], Fmt), TmpArgs),
+ ?line Sorted = KS1,
+ ?line delete_files(Fs1),
+
+ %% Input is a fun. Output is a file.
+ ?line ok = file_sorter:keysort(2, input([], 2, Fmt), Foo, Args),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:keysort(2, input(AllFlat, 4, Fmt), Foo, Args),
+ ?line Sorted = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+
+ %% Input is a fun. Output is a fun.
+ ?line [] = file_sorter:keysort(2, input([], 2, Fmt), output([], Fmt),Args),
+ ?line KS2 =
+ file_sorter:keysort(2, input(AllFlat, 4, Fmt), output([], Fmt),
+ TmpArgs),
+ ?line Sorted = KS2,
+ ok.
+
+merge(Fmt, XArgs, Config) ->
+ Args = make_args(Fmt, [{size,5} | XArgs]),
+ Foo = outfile(foo, Config),
+
+ %% Input is a file. Output is a fun.
+ ?line [] = file_sorter:merge([], output([], Fmt), Args),
+ ?line L2 = [[1,3,5],[2,4,5]],
+ ?line Fs1 = to_files(L2, Fmt, Config),
+ ?line S2 = file_sorter:sort(Fs1, output([], Fmt), Args),
+ ?line S2 = rev(lists:sort(lists:append(L2)), Args),
+ ?line delete_files(Fs1),
+
+ %% Input is a file. Output is a file
+ ?line ok = file_sorter:merge([], Foo, Args),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:merge([], Foo, [{unique,true} | Args]),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line L31 = [1,2,3],
+ ?line L32 = [2,3,4],
+ ?line L33 = [4,5,6],
+ ?line L3r = [L31, L32, L33],
+ ?line L3 = [rev(L31,Args), rev(L32,Args), rev(L33,Args)],
+ ?line Fs2 = to_files(L3, Fmt, Config),
+ ?line ok = file_sorter:merge(Fs2, Foo, Args),
+ ?line true = rev(lists:merge(L3r), Args) =:= from_files(Foo, Fmt),
+ ?line ok = file_sorter:merge(Fs2, Foo, Args++[{unique,true},
+ {size,100000}]),
+ ?line true = rev(lists:umerge(L3r), Args) =:= from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:merge(Fs2, Foo, Args++[{unique,true}]),
+ ?line true = rev(lists:umerge(L3r), Args) =:= from_files(Foo, Fmt),
+ ?line delete_files([Foo | Fs2]),
+
+ ok.
+
+keymerge(Fmt, XArgs, Config) ->
+ Args = make_args(Fmt, [{size,50}, {no_files, 2} | XArgs]),
+ Foo = outfile(foo, Config),
+
+ %% Input is files. Output is a file.
+ ?line ok = file_sorter:keymerge(2, [], Foo, Args),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:keymerge(2, [], Foo, [{unique,true} | Args]),
+ ?line [] = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+ ?line L0 = [rev([{a,1},{a,2}], Args), rev([{a,2},{a,1},{a,3}], Args)],
+ ?line Fs0 = to_files(L0, Fmt, Config),
+ ?line delete_files(Foo),
+ ?line ok = file_sorter:keymerge(1, Fs0, Foo, Args ++ [{unique,false}]),
+ ?line S2 = rev([{a,1},{a,2},{a,2},{a,1},{a,3}], Args),
+ ?line S2 = from_files(Foo, Fmt),
+ ?line delete_files([Foo | Fs0]),
+ ?line L11 = [{a,1,x4},{b,2,x4},{c,3,x4}],
+ ?line L21 = [{a,1,x3},{b,2,x3},{c,3,x3}],
+ ?line L31 = [{a,1,x2},{b,2,x2},{c,3,x2}],
+ ?line L41 = [{a,1,x1},{b,2,x1},{c,3,x1}],
+ ?line All =
+ [rev(L11, Args), rev(L21, Args), rev(L31, Args), rev(L41, Args)],
+ ?line Merged1 = lists:keymerge(2, L11, L21),
+ ?line Merged2 = lists:keymerge(2, L31, L41),
+ ?line Merged = rev(lists:keymerge(2, Merged1, Merged2), Args),
+ ?line Fs1 = to_files(All, Fmt, Config),
+ ?line ok = file_sorter:keymerge(2, Fs1, Foo, Args),
+ ?line Merged = from_files(Foo, Fmt),
+
+ fun() ->
+ UArgs = [{unique,true} | Args],
+ ?line UMerged1 = lists:ukeymerge(2, L11, L21),
+ ?line UMerged2 = lists:ukeymerge(2, L31, L41),
+ ?line UMerged = rev(lists:ukeymerge(2, UMerged1, UMerged2), Args),
+ ?line ok = file_sorter:keymerge(2, Fs1, Foo, UArgs),
+ ?line UMerged = from_files(Foo, Fmt),
+ UArgs2 = make_args(Fmt, [{unique,true}, {size,50} | XArgs]),
+ ?line ok = file_sorter:keymerge(2, Fs1, Foo, UArgs2),
+ ?line UMerged = from_files(Foo, Fmt),
+ ?line List = rev([{a,1,x4},{b,2,x4},{c,3,x4}], Args),
+ ?line FsL = to_files([List], Fmt, Config),
+ ?line ok = file_sorter:keymerge(2, FsL, Foo, UArgs),
+ ?line List = from_files(Foo, Fmt),
+ ?line List1 = [{a,1,x4},{b,2,x4},{c,3,x4}],
+ ?line List2 = [{a,3,x4},{b,4,x4},{c,5,x4}],
+ ?line FsLL = to_files([rev(List1, Args), rev(List2, Args)], Fmt, Config),
+ ?line ok = file_sorter:keymerge(2, FsLL, Foo, UArgs),
+ ?line List1_2 = rev(lists:ukeymerge(2, List1, List2), Args),
+ ?line List1_2 = from_files(Foo, Fmt),
+ ?line delete_files(Foo)
+ end(),
+
+ %% Input is files. Output is a fun.
+ ?line Fs3 = to_files(All, Fmt, Config),
+ ?line [] = file_sorter:keysort(2, [], output([], Fmt), Args),
+ ?line KS1 = file_sorter:keymerge(2, Fs3, output([], Fmt), Args),
+ ?line Merged = KS1,
+ ?line delete_files([Foo | Fs3]),
+
+ ?line L2 = [[{a,1}],[{a,2}],[{a,3}],[{a,4}],[{a,5}],[{a,6}],[{a,7}]],
+ ?line Fs2 = to_files(L2, Fmt, Config),
+ ?line M = file_sorter:keymerge(1, Fs2, output([], Fmt), Args),
+ ?line M = rev(lists:append(L2), Args),
+ ?line delete_files(Fs2),
+
+ ?line LL1 = [{d,4},{e,5},{f,6}],
+ ?line LL2 = [{a,1},{b,2},{c,3}],
+ ?line LL3 = [{j,10},{k,11},{l,12}],
+ ?line LL4 = [{g,7},{h,8},{i,9}],
+ ?line LL5 = [{p,16},{q,17},{r,18}],
+ ?line LL6 = [{m,13},{n,14},{o,15}],
+ ?line LLAll = [rev(LL1, Args),rev(LL2, Args),rev(LL3, Args),
+ rev(LL4, Args),rev(LL5, Args),rev(LL6, Args)],
+ ?line FsLL6 = to_files(LLAll, Fmt, Config),
+ ?line LL = rev(lists:sort(lists:append(LLAll)), Args),
+ ?line ok = file_sorter:keymerge(1, FsLL6, Foo, Args),
+ ?line LL = from_files(Foo, Fmt),
+ ?line ok = file_sorter:keymerge(1, FsLL6, Foo, [{unique,true} | Args]),
+ ?line LL = from_files(Foo, Fmt),
+ ?line delete_files([Foo | FsLL6]),
+
+ ok.
+
+funmerge(Fmt, XArgs, Config) ->
+ KComp = key_compare(2),
+ Args = make_args(Fmt, [{order,KComp},{size,5}, {no_files, 5} | XArgs]),
+ UArgs = [{unique,true} | Args],
+ Foo = outfile(foo, Config),
+
+ ?line EFs = to_files([[]], Fmt, Config),
+ ?line ok = file_sorter:merge(EFs, Foo, UArgs),
+ ?line [] = from_files(Foo, Fmt),
+ delete_files([Foo | EFs]),
+
+ ?line L11 = [{a,1,x4},{b,2,x4},{c,3,x4}],
+ ?line L21 = [{a,1,x3},{b,2,x3},{c,3,x3}],
+ ?line L31 = [{a,1,x2},{b,2,x2},{c,3,x2}],
+ ?line L41 = [{a,1,x1},{b,2,x1},{c,3,x1}],
+ ?line CAll = [L11, L21, L31, L41],
+ ?line CMerged1 = lists:merge(KComp, L11, L21),
+ ?line CMerged2 = lists:merge(KComp, L31, L41),
+ ?line CMerged = lists:merge(KComp, CMerged1, CMerged2),
+ ?line CFs1 = to_files(CAll, Fmt, Config),
+ ?line ok = file_sorter:merge(CFs1, Foo, Args),
+ ?line CMerged = from_files(Foo, Fmt),
+
+ Args4 = make_args(Fmt, [{size,50} | XArgs]),
+ ?line ok = file_sorter:merge(CFs1, Foo, [{order,KComp} | Args4]),
+ ?line CMerged = from_files(Foo, Fmt),
+
+ ?line UMerged1 = lists:umerge(KComp, L11, L21),
+ ?line UMerged2 = lists:umerge(KComp, L31, L41),
+ ?line UMerged = lists:umerge(KComp, UMerged1, UMerged2),
+ ?line ok = file_sorter:merge(CFs1, Foo, [{order,KComp} | UArgs]),
+ ?line UMerged = from_files(Foo, Fmt),
+ UArgs2 =
+ lists:keydelete(order, 1,
+ make_args(Fmt, [{unique,true}, {size,50} | XArgs])),
+ ?line ok = file_sorter:merge(CFs1, Foo, [{order,KComp} | UArgs2]),
+ ?line UMerged = from_files(Foo, Fmt),
+ ?line delete_files(Foo),
+
+ ?line List1 = [{a,1,x4},{b,2,x4},{c,3,x4}],
+ ?line List2 = [{a,3,x4},{b,4,x4},{c,5,x4}],
+ ?line List3 = [{a,5,x4},{b,6,x4},{c,7,x4}],
+ ?line FsLL = to_files([List1, List2, List3], Fmt, Config),
+ ?line ok = file_sorter:merge(FsLL, Foo, Args),
+ ?line List1_2 = lists:merge(KComp,lists:merge(KComp,List1,List2),List3),
+ ?line List1_2 = from_files(Foo, Fmt),
+ ?line ok = file_sorter:merge(FsLL, Foo, [{order,KComp} | UArgs]),
+ ?line UList1_2 =
+ lists:umerge(KComp,lists:umerge(KComp, List1, List2),List3),
+ ?line UList1_2 = from_files(Foo, Fmt),
+ ?line delete_files([Foo | CFs1]),
+
+ fun() ->
+ ?line LL1 = [{d,4},{e,5},{f,6}],
+ ?line LL2 = [{a,1},{b,2},{c,3}],
+ ?line LL3 = [{j,10},{k,11},{l,12}],
+ ?line LL4 = [{g,7},{h,8},{i,9}],
+ ?line LL5 = [{p,16},{q,17},{r,18}],
+ ?line LL6 = [{m,13},{n,14},{o,15}],
+ ?line LLAll = [LL1,LL2,LL3,LL4,LL5,LL6],
+ ?line FsLL6 = to_files(LLAll, Fmt, Config),
+ ?line LL = lists:sort(lists:append(LLAll)),
+ ?line ok = file_sorter:merge(FsLL6, Foo, Args),
+ ?line LL = from_files(Foo, Fmt),
+ ?line ok = file_sorter:merge(FsLL6, Foo, UArgs),
+ ?line LL = from_files(Foo, Fmt),
+ ?line delete_files([Foo | FsLL6])
+ end(),
+
+ fun() ->
+ ?line RLL1 = [{b,2},{h,8},{n,14}],
+ ?line RLL2 = [{a,1},{g,7},{m,13}],
+ ?line RLL3 = [{d,4},{j,10},{p,16}],
+ ?line RLL4 = [{c,3},{i,9},{o,15}],
+ ?line RLL5 = [{f,6},{l,12},{r,18}],
+ ?line RLL6 = [{e,5},{k,11},{q,17}],
+ ?line RLLAll = [RLL1,RLL2,RLL3,RLL4,RLL5,RLL6],
+ ?line RFsLL6 = to_files(RLLAll, Fmt, Config),
+ ?line RLL = lists:sort(lists:append(RLLAll)),
+ ?line ok = file_sorter:merge(RFsLL6, Foo, Args),
+ ?line RLL = from_files(Foo, Fmt),
+ ?line ok = file_sorter:merge(RFsLL6, Foo, UArgs),
+ ?line RLL = from_files(Foo, Fmt),
+ ?line delete_files([Foo | RFsLL6])
+ end(),
+
+ ok.
+
+check(Fmt, Config) ->
+ Args0 = make_args(Fmt, [{size,5}]),
+ Args = Args0 ++ [{tmpdir,?privdir(Config)}],
+
+ Fun = fun compare/2,
+
+ L1 = [3,1,2,5,4],
+ [F1_0] = Fs1 = to_files([L1], Fmt, Config),
+ F1 = filename:absname(F1_0),
+ ?line {ok, [{F1,2,1}]} = file_sorter:check(Fs1, Args),
+ ?line {ok, [{F1,2,1}]} = file_sorter:check(Fs1, [{order,Fun} | Args]),
+ ?line {ok, [{F1,2,1}]} = file_sorter:check(Fs1, [{unique,true} | Args]),
+ ?line {ok, [{F1,2,1}]} =
+ file_sorter:check(Fs1, [{order,Fun},{unique,true} | Args]),
+ ?line {ok, [{F1,3,2}]} =
+ file_sorter:check(Fs1, [{order,descending} | Args]),
+ ?line {ok, [{F1,3,2}]} =
+ file_sorter:check(Fs1, [{unique,true},{order,descending} | Args]),
+ ?line delete_files(Fs1),
+
+ L2 = [[1,2,2,3,3,4,5,5],[5,5,4,3,3,2,2,1]],
+ [F2_0,F3_0] = Fs2 = to_files(L2, Fmt, Config),
+ F2 = filename:absname(F2_0),
+ F3 = filename:absname(F3_0),
+ ?line {ok, [{F3,3,4}]} = file_sorter:check(Fs2, Args),
+ ?line {ok, [{F3,3,4}]} = file_sorter:check(Fs2, [{order,Fun} | Args]),
+ ?line {ok, [{F2,3,2},{F3,2,5}]} =
+ file_sorter:check(Fs2, [{unique, true} | Args]),
+ ?line {ok, [{F2,3,2},{F3,2,5}]} =
+ file_sorter:check(Fs2, [{order,Fun},{unique, true} | Args]),
+ ?line {ok, [{F2,2,2}]} =
+ file_sorter:check(Fs2, [{order,descending} | Args]),
+ ?line {ok, [{F2,2,2},{F3,2,5}]} =
+ file_sorter:check(Fs2, [{unique,true},{order,descending} | Args]),
+ ?line delete_files(Fs2),
+
+ L3 = [1,2,3,4],
+ ?line Fs3 = to_files([L3], Fmt, Config),
+ ?line {ok, []} = file_sorter:check(Fs3, [{unique,true} | Args]),
+ ?line {ok, []} =
+ file_sorter:check(Fs3, [{unique,true},{order,Fun} | Args]),
+ ?line delete_files(Fs3),
+
+ %% big objects
+ ?line T1 = erlang:make_tuple(10000,foo),
+ ?line T2 = erlang:make_tuple(10000,bar),
+ ?line L4 = [T1,T2],
+ ?line [FF_0] = Fs4 = to_files([L4], Fmt, Config),
+ FF = filename:absname(FF_0),
+ ?line {ok, [{FF,2,T2}]} = file_sorter:check(Fs4, [{unique,true} | Args]),
+ ?line delete_files(Fs4),
+
+ CFun = key_compare(2),
+ L10 = [[{1,a},{2,b},T10_1={1,b},{3,c}], [{1,b},T10_2={2,a}]],
+ [F10_0,F11_0] = Fs10 = to_files(L10, Fmt, Config),
+ F10_1 = filename:absname(F10_0),
+ F11_1 = filename:absname(F11_0),
+ ?line {ok, [{F10_1,3,T10_1},{F11_1,2,T10_2}]} =
+ file_sorter:check(Fs10, [{unique,true},{order,CFun} | Args]),
+ ?line delete_files(Fs10),
+
+ ok.
+
+keycheck(Fmt, Config) ->
+ Args0 = make_args(Fmt, [{size,5}]),
+ Args = Args0 ++ [{tmpdir,?privdir(Config)}],
+
+ ?line L1 = [[{a,1},{b,2}], [{c,2},{b,1},{a,3}]],
+ ?line [F1_0,F2_0] = Fs1 = to_files(L1, Fmt, Config),
+ F1 = filename:absname(F1_0),
+ F2 = filename:absname(F2_0),
+ ?line {ok, [{F2,2,{b,1}}]} = file_sorter:keycheck(1, Fs1, Args),
+ ?line {ok, [{F2,2,{b,1}}]} =
+ file_sorter:keycheck(1, Fs1, [{unique,true} | Args]),
+ ?line {ok, [{F1,2,{b,2}}]} =
+ file_sorter:keycheck(1, Fs1, [{order,descending},{unique,true} | Args]),
+ ?line delete_files(Fs1),
+
+ L2 = [[{a,1},{a,2},{a,2},{b,2}], [{c,2},{b,1},{b,2},{b,2},{a,3}]],
+ ?line [F3_0,F4_0] = Fs2 = to_files(L2, Fmt, Config),
+ F3 = filename:absname(F3_0),
+ F4 = filename:absname(F4_0),
+ ?line {ok, [{F4,2,{b,1}}]} = file_sorter:keycheck(1, Fs2, Args),
+ ?line {ok, [{F3,2,{a,2}},{F4,2,{b,1}}]} =
+ file_sorter:keycheck(1, Fs2, [{unique,true} | Args]),
+ ?line {ok, [{F3,4,{b,2}}]} =
+ file_sorter:keycheck(1, Fs2, [{order,descending} | Args]),
+ ?line {ok, [{F3,2,{a,2}},{F4,3,{b,2}}]} =
+ file_sorter:keycheck(1, Fs2,
+ [{order,descending},{unique,true} | Args]),
+ ?line delete_files(Fs2),
+
+ ok.
+
+rev(L, Args) ->
+ case lists:member({order, descending}, Args) of
+ true ->
+ lists:reverse(L);
+ false ->
+ L
+ end.
+
+make_args({HL, Fmt}, Args) ->
+ make_args(Fmt, [{header, HL} | Args]);
+make_args(Fmt, Args) ->
+ [{format, Fmt} | Args].
+
+compare(X, Y) ->
+ X =< Y.
+
+key_compare(I) ->
+ fun(X, Y) ->
+ element(I, bin_to_term(X)) =< element(I, bin_to_term(Y))
+ end.
+
+bin_to_term(B) when is_binary(B) -> binary_to_term(B);
+bin_to_term(T) -> T.
+
+-define(CHUNKSIZE, 8096).
+
+pps() ->
+ erlang:ports().
+
+input(L, N, term) ->
+ input(L, N);
+input(L, N, {_HL, Format}) when Format =:= binary_term; Format =:= binary ->
+ binput(L, N);
+input(L, N, Format) when Format =:= binary_term; Format =:= binary ->
+ binput(L, N).
+
+binput(L, N) ->
+ Bs = lists:map(fun(T) -> term_to_binary(T) end, L),
+ input(Bs, N).
+
+input(L, N) ->
+ fun(close) ->
+ ok;
+ (read) ->
+ case L of
+ [] -> end_of_input;
+ _ ->
+ R = lists:sublist(L, N),
+ NL = lists:nthtail(length(R), L),
+ {R, input(NL, N)}
+ end
+ end.
+
+output(L, term) ->
+ output(L);
+output(L, {_HL, Format}) when Format =:= binary_term; Format =:= binary ->
+ boutput(L);
+output(L, Format) when Format =:= binary_term; Format =:= binary ->
+ boutput(L).
+
+output(A) ->
+ fun(close) ->
+ lists:append(lists:reverse(A));
+ (L) when is_list(L) ->
+ output([L | A])
+ end.
+
+boutput(A) ->
+ fun(close) ->
+ Bs = lists:append(lists:reverse(A)),
+ lists:map(fun(B) -> binary_to_term(B) end, Bs);
+ (L) when is_list(L) ->
+ boutput([L | A])
+ end.
+
+outfile(Name, Config) ->
+ list_to_atom(filename:join(?privdir(Config), Name)).
+
+%% [[term()]] -> [filename()]
+to_files(Lists, term, Config) ->
+ terms_to_files(Lists, Config);
+to_files(Lists, Format, Config) when Format =:= binary_term;
+ Format =:= binary ->
+ bins_to_files(Lists, 4, Config);
+to_files(Lists, {HL, Format}, Config) when Format =:= binary_term;
+ Format =:= binary ->
+ bins_to_files(Lists, HL, Config).
+
+%% [[term()]] -> [filename()]
+terms_to_files(Lists, Config) ->
+ PrivDir = ?privdir(Config),
+ terms_to_files(Lists, PrivDir, 1).
+
+terms_to_files([L | Ls], PrivDir, N) ->
+ F = lists:concat([?MODULE, '_', N]),
+ File = filename:join(PrivDir, F),
+ {ok, Fd} = file:open(File, [write]),
+ write_terms(Fd, L),
+ file:close(Fd),
+ [list_to_atom(File) | terms_to_files(Ls, PrivDir, N+1)];
+terms_to_files([], _PrivDir, _N) ->
+ [].
+
+write_terms(Fd, [T | Ts]) ->
+ io:format(Fd, "~p.~n", [T]),
+ write_terms(Fd, Ts);
+write_terms(_Fd, []) ->
+ ok.
+
+%% [[term()]] -> [filename()]
+bins_to_files(Lists, HL, Config) ->
+ PrivDir = ?privdir(Config),
+ bins_to_files(Lists, PrivDir, 1, HL).
+
+bins_to_files([L | Fs], PrivDir, N, HL) ->
+ F = lists:concat([?MODULE, '_', N]),
+ File = filename:join(PrivDir, F),
+ {ok, Fd} = file:open(File, [raw,binary,write]),
+ write_bins(Fd, L, HL),
+ file:close(Fd),
+ [list_to_atom(File) | bins_to_files(Fs, PrivDir, N+1, HL)];
+bins_to_files([], _PrivDir, _N, _HL) ->
+ [].
+
+write_bins(Fd, [T | Ts], HL) ->
+ B = term_to_binary(T),
+ Sz = byte_size(B),
+ ok = file:write(Fd, [<<Sz:HL/unit:8>>, B]),
+ write_bins(Fd, Ts, HL);
+write_bins(_Fd, [], _HL) ->
+ ok.
+
+%% [filename()] -> [[term()]] or filename() -> [term()]
+from_files(Files, term) ->
+ terms_from_files(Files);
+from_files(Files, Format) when Format =:= binary_term; Format =:= binary ->
+ bins_from_files(Files, 4);
+from_files(Files, {HL, Format}) when Format =:= binary_term;
+ Format =:= binary ->
+ bins_from_files(Files, HL).
+
+%% [filename()] -> [[term()]] or filename() -> [term()]
+terms_from_files(File) when is_atom(File) ->
+ [Terms] = terms_from_files([File]),
+ Terms;
+terms_from_files(Files) ->
+ lists:map(fun(F) -> terms_from_file(F) end, Files).
+
+terms_from_file(File) ->
+ {ok, Fd} = file:open(File, [read,compressed]),
+ terms_from_file(Fd, []).
+
+terms_from_file(Fd, L) ->
+ case io:read(Fd, '') of
+ {ok, Term} ->
+ terms_from_file(Fd, [Term | L]);
+ eof ->
+ file:close(Fd),
+ lists:reverse(L)
+ end.
+
+%% [filename()] -> [[term()]]
+bins_from_files(File, HL) when is_atom(File) ->
+ [Bins] = bins_from_files([File], HL),
+ Bins;
+bins_from_files(Files, HL) ->
+ lists:map(fun(F) -> collect(F, HL) end, Files).
+
+delete_files(File) when is_atom(File) ->
+ file:delete(File);
+delete_files(Files) ->
+ lists:foreach(fun(F) -> file:delete(F) end, Files).
+
+%%%
+%%% Collects binaries converted to terms in a list. Not very efficient.
+%%%
+collect(F, HL) ->
+ {ok, Fd} = file:open(F, [read, binary, raw, compressed]),
+ R = (catch c(Fd, <<>>, 0, ?CHUNKSIZE, HL, [])),
+ file:close(Fd),
+ R.
+
+c(Fd, Bin0, Size0, NoBytes, HL, L) ->
+ case file:read(Fd, NoBytes) of
+ {ok, Bin} ->
+ Size = Size0 + byte_size(Bin),
+ NBin = list_to_binary([Bin0, Bin]),
+ c1(Fd, NBin, Size, HL, L);
+ eof when Size0 =:= 0 ->
+ lists:reverse(L);
+ eof ->
+ test_server:fail({error, premature_eof});
+ Error ->
+ test_server:fail(Error)
+ end.
+
+c1(Fd, B, BinSize, HL, L) ->
+ case B of
+ <<Size:HL/unit:8, Bin/binary>> ->
+ if
+ Size > BinSize - HL, Size > ?CHUNKSIZE ->
+ c(Fd, B, BinSize, Size + HL, HL, L);
+ Size > BinSize - HL ->
+ c(Fd, B, BinSize, ?CHUNKSIZE, HL, L);
+ true ->
+ <<BinTerm:Size/binary, R/binary>> = Bin,
+ E = case catch binary_to_term(BinTerm) of
+ {'EXIT', _} ->
+ test_server:fail({error, bad_object});
+ Term ->
+ Term
+ end,
+ NBinSize = BinSize - HL - Size,
+ c1(Fd, R, NBinSize, HL, [E | L])
+ end;
+ _ ->
+ c(Fd, B, BinSize, ?CHUNKSIZE, HL, L)
+ end.
diff --git a/lib/stdlib/test/filelib_SUITE.erl b/lib/stdlib/test/filelib_SUITE.erl
new file mode 100644
index 0000000000..c9c6054f7b
--- /dev/null
+++ b/lib/stdlib/test/filelib_SUITE.erl
@@ -0,0 +1,241 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(filelib_SUITE).
+
+-export([all/1,init_per_testcase/2,fin_per_testcase/2,
+ wildcard_one/1,wildcard_two/1,wildcard_errors/1,
+ fold_files/1,otp_5960/1]).
+
+-import(lists, [foreach/2]).
+
+-include("test_server.hrl").
+-include_lib("kernel/include/file.hrl").
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?t:minutes(5)),
+ [{watchdog,Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [wildcard_one,wildcard_two,wildcard_errors,fold_files,otp_5960].
+
+wildcard_one(Config) when is_list(Config) ->
+ ?line {ok,OldCwd} = file:get_cwd(),
+ ?line Dir = filename:join(?config(priv_dir, Config), "wildcard_one"),
+ ?line ok = file:make_dir(Dir),
+ ?line file:set_cwd(Dir),
+ ?line do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc) end),
+ ?line file:set_cwd(OldCwd),
+ ?line ok = file:del_dir(Dir),
+ ok.
+
+wildcard_two(Config) when is_list(Config) ->
+ ?line Dir = filename:join(?config(priv_dir, Config), "wildcard_two"),
+ ?line ok = file:make_dir(Dir),
+ ?line do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, Dir) end),
+ ?line do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, Dir++"/") end),
+ case os:type() of
+ {win32,_} ->
+ ok;
+ _ ->
+ ?line do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, "//"++Dir) end)
+ end,
+ ?line ok = file:del_dir(Dir),
+ ok.
+
+wildcard_errors(Config) when is_list(Config) ->
+ ?line wcc("{", missing_delimiter),
+ ?line wcc("{a", missing_delimiter),
+ ?line wcc("{a,", missing_delimiter),
+ ?line wcc("{a,b", missing_delimiter),
+ ok.
+
+wcc(Wc, Error) ->
+ {'EXIT',{{badpattern,Error},
+ [{filelib,compile_wildcard,1}|_]}} = (catch filelib:compile_wildcard(Wc)),
+ {'EXIT',{{badpattern,Error},
+ [{filelib,wildcard,1}|_]}} = (catch filelib:wildcard(Wc)),
+ {'EXIT',{{badpattern,Error},
+ [{filelib,wildcard,2}|_]}} = (catch filelib:wildcard(Wc, ".")).
+
+do_wildcard_1(Dir, Wcf0) ->
+ do_wildcard_2(Dir, Wcf0),
+ Wcf = fun(Wc0) ->
+ Wc = filename:join(Dir, Wc0),
+ L = Wcf0(Wc),
+ [subtract_dir(N, Dir) || N <- L]
+ end,
+ do_wildcard_2(Dir, Wcf).
+
+subtract_dir([C|Cs], [C|Dir]) -> subtract_dir(Cs, Dir);
+subtract_dir("/"++Cs, []) -> Cs.
+
+do_wildcard_2(Dir, Wcf) ->
+ %% Basic wildcards.
+ All = ["abc","abcdef","glurf"],
+ ?line Files = mkfiles(lists:reverse(All), Dir),
+ ?line All = Wcf("*"),
+ ?line ["abc","abcdef"] = Wcf("a*"),
+ ?line ["abc","abcdef"] = Wcf("abc*"),
+ ?line ["abcdef"] = Wcf("abc???"),
+ ?line ["abcdef"] = Wcf("abcd*"),
+ ?line ["abcdef"] = Wcf("*def"),
+ ?line ["abcdef","glurf"] = Wcf("{*def,gl*}"),
+ ?line ["abc","abcdef"] = Wcf("a*{def,}"),
+ ?line ["abc","abcdef"] = Wcf("a*{,def}"),
+
+ %% Negative tests.
+ ?line [] = Wcf("b*"),
+ ?line [] = Wcf("bufflig"),
+
+ ?line del(Files),
+ do_wildcard_3(Dir, Wcf).
+
+do_wildcard_3(Dir, Wcf) ->
+ %% Some character sets.
+ All = ["a01","a02","a03","b00","c02","d19"],
+ ?line Files = mkfiles(lists:reverse(All), Dir),
+ ?line All = Wcf("[a-z]*"),
+ ?line All = Wcf("[a-d]*"),
+ ?line All = Wcf("[adbc]*"),
+ ?line All = Wcf("?[0-9][0-9]"),
+ ?line All = Wcf("?[0-1][0-39]"),
+ ?line All = Wcf("[abcdefgh][10][01239]"),
+ ?line ["a01","a02","a03","b00","c02"] = Wcf("[a-z]0[0-3]"),
+ ?line [] = Wcf("?[a-z][0-39]"),
+ ?line del(Files),
+ do_wildcard_4(Dir, Wcf).
+
+do_wildcard_4(Dir, Wcf) ->
+ %% More character sets: tricky characters.
+ All = ["a-","aA","aB","aC","a[","a]"],
+ ?line Files = mkfiles(lists:reverse(All), Dir),
+ ?line All = Wcf("a[][A-C-]"),
+ ?line del(Files),
+ do_wildcard_5(Dir, Wcf).
+
+do_wildcard_5(Dir, Wcf) ->
+ Dirs = ["xa","blurf","yyy"],
+ ?line foreach(fun(D) -> ok = file:make_dir(filename:join(Dir, D)) end, Dirs),
+ All = ["blurf/nisse","xa/arne","xa/kalle","yyy/arne"],
+ ?line Files = mkfiles(lists:reverse(All), Dir),
+
+ %% Test.
+ ?line All = Wcf("*/*"),
+ ?line ["blurf/nisse","xa/arne","xa/kalle"] = Wcf("{blurf,xa}/*"),
+ ?line ["xa/arne","yyy/arne"] = Wcf("*/arne"),
+ ?line ["blurf/nisse"] = Wcf("*/nisse"),
+ ?line [] = Wcf("mountain/*"),
+ ?line [] = Wcf("xa/gurka"),
+
+ %% Cleanup
+ ?line del(Files),
+ ?line foreach(fun(D) -> ok = file:del_dir(filename:join(Dir, D)) end, Dirs).
+
+
+
+fold_files(Config) when is_list(Config) ->
+ ?line Dir = filename:join(?config(priv_dir, Config), "fold_files"),
+ ?line ok = file:make_dir(Dir),
+ ?line Dirs = [filename:join(Dir, D) || D <- ["blurf","blurf/blarf"]],
+ ?line foreach(fun(D) -> ok = file:make_dir(D) end, Dirs),
+ All = ["fb.txt","ko.txt",
+ "blurf/nisse.text","blurf/blarf/aaa.txt","blurf/blarf/urfa.txt"],
+ ?line Files = mkfiles(lists:reverse(All), Dir),
+
+ %% Test.
+ ?line Files0 = filelib:fold_files(Dir, "^", false,
+ fun(H, T) -> [H|T] end, []),
+ ?line same_lists(["fb.txt","ko.txt"], Files0, Dir),
+
+ ?line Files1 = filelib:fold_files(Dir, "^", true,
+ fun(H, T) -> [H|T] end, []),
+ ?line same_lists(All, Files1, Dir),
+
+ ?line Files2 = filelib:fold_files(Dir, "[.]text$", true,
+ fun(H, T) -> [H|T] end, []),
+ ?line same_lists(["blurf/nisse.text"], Files2, Dir),
+
+
+ ?line Files3 = filelib:fold_files(Dir, "^..[.]", true,
+ fun(H, T) -> [H|T] end, []),
+ ?line same_lists(["fb.txt","ko.txt"], Files3, Dir),
+
+ ?line Files4 = filelib:fold_files(Dir, "^ko[.]txt$", true,
+ fun(H, T) -> [H|T] end, []),
+ ?line same_lists(["ko.txt"], Files4, Dir),
+ ?line Files4 = filelib:fold_files(Dir, "^ko[.]txt$", false,
+ fun(H, T) -> [H|T] end, []),
+
+ ?line [] = filelib:fold_files(Dir, "^$", true,
+ fun(H, T) -> [H|T] end, []),
+
+ %% Cleanup
+ ?line del(Files),
+ ?line foreach(fun(D) -> ok = file:del_dir(D) end, lists:reverse(Dirs)),
+ ?line ok = file:del_dir(Dir).
+
+same_lists(Expected0, Actual0, BaseDir) ->
+ Expected = [filename:absname(N, BaseDir) || N <- lists:sort(Expected0)],
+ Actual = lists:sort(Actual0),
+ Expected = Actual.
+
+mkfiles([H|T], Dir) ->
+ Name = filename:join(Dir, H),
+ Garbage = [31+random:uniform(95) || _ <- lists:seq(1, random:uniform(1024))],
+ file:write_file(Name, Garbage),
+ [Name|mkfiles(T, Dir)];
+mkfiles([], _) -> [].
+
+del([H|T]) ->
+ ok = file:delete(H),
+ del(T);
+del([]) -> ok.
+
+otp_5960(suite) ->
+ [];
+otp_5960(doc) ->
+ ["Test that filelib:ensure_dir/1 returns ok or {error,Reason}"];
+otp_5960(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Dir = filename:join(PrivDir, otp_5960_dir),
+ ?line Name1 = filename:join(Dir, name1),
+ ?line Name2 = filename:join(Dir, name2),
+ ?line ok = filelib:ensure_dir(Name1), % parent is created
+ ?line ok = filelib:ensure_dir(Name2), % parent already exists
+ ?line Name3 = filename:join(Name1, name3),
+ ?line {ok, FileInfo} = file:read_file_info(Dir),
+ case os:type() of
+ {win32,_} ->
+ %% Not possibly to write protect directories on Windows
+ %% (at least not using file:write_file_info/2).
+ ok;
+ _ ->
+ ?line Mode = FileInfo#file_info.mode,
+ ?line NoWriteMode = Mode - 8#00200 - 8#00020 - 8#00002,
+ ?line ok = file:write_file_info(Dir, #file_info{mode=NoWriteMode}),
+ ?line {error, _} = filelib:ensure_dir(Name3),
+ ?line ok = file:write_file_info(Dir, #file_info{mode=Mode}),
+ ok
+ end.
diff --git a/lib/stdlib/test/filename_SUITE.erl b/lib/stdlib/test/filename_SUITE.erl
new file mode 100644
index 0000000000..ab6521f37b
--- /dev/null
+++ b/lib/stdlib/test/filename_SUITE.erl
@@ -0,0 +1,459 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(filename_SUITE).
+-export([all/1]).
+-export([absname/1, absname_2/1,
+ basename_1/1, basename_2/1,
+ dirname/1, extension/1, join/1, t_nativename/1]).
+-export([pathtype/1,rootname/1,split/1,find_src/1]).
+-include("test_server.hrl").
+
+all(suite) ->
+ [absname, absname_2, basename_1, basename_2, dirname,
+ extension,
+ join, pathtype, rootname, split, t_nativename, find_src].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+absname(Config) when is_list(Config) ->
+ case os:type() of
+ {win32, _} ->
+ ?line [Drive|_] = ?config(priv_dir, Config),
+ ?line Temp = filename:join([Drive|":/"], "temp"),
+ ?line case file:make_dir(Temp) of
+ ok -> ok;
+ {error,eexist} -> ok
+ end,
+ ?line {ok,Cwd} = file:get_cwd(),
+ ?line ok = file:set_cwd(Temp),
+ ?line [Drive|":/temp/foo"] = filename:absname(foo),
+ ?line [Drive|":/temp/foo"] = filename:absname("foo"),
+ ?line [Drive|":/temp/../ebin"] = filename:absname("../ebin"),
+ ?line [Drive|":/erlang"] = filename:absname("/erlang"),
+ ?line [Drive|":/erlang/src"] = filename:absname("/erlang/src"),
+ ?line [Drive|":/erlang/src"] = filename:absname("\\erlang\\src"),
+ ?line [Drive|":/temp/erlang"] = filename:absname([Drive|":erlang"]),
+ ?line [Drive|":/temp/erlang/src"] =
+ filename:absname([Drive|":erlang/src"]),
+ ?line [Drive|":/temp/erlang/src"] =
+ filename:absname([Drive|":erlang\\src\\"]),
+ ?line "a:/erlang" = filename:absname("a:erlang"),
+
+ ?line file:set_cwd([Drive|":/"]),
+ ?line [Drive|":/foo"] = filename:absname(foo),
+ ?line [Drive|":/foo"] = filename:absname("foo"),
+ ?line [Drive|":/../ebin"] = filename:absname("../ebin"),
+ ?line [Drive|":/erlang"] = filename:absname("/erlang"),
+ ?line [Drive|":/erlang/src"] = filename:absname("/erlang/src"),
+ ?line [Drive|":/erlang/src"] = filename:absname(["/erlang",'/src']),
+ ?line [Drive|":/erlang/src"] = filename:absname("\\erlang\\\\src"),
+ ?line [Drive|":/erlang"] = filename:absname([Drive|":erlang"]),
+ ?line [Drive|":/erlang/src"] = filename:absname([Drive|":erlang/src"]),
+ ?line "a:/erlang" = filename:absname("a:erlang"),
+
+ ?line file:set_cwd(Cwd),
+ ok;
+ {unix, _} ->
+ ?line ok = file:set_cwd("/usr"),
+ ?line "/usr/foo" = filename:absname(foo),
+ ?line "/usr/foo" = filename:absname("foo"),
+ ?line "/usr/../ebin" = filename:absname("../ebin"),
+
+ ?line file:set_cwd("/"),
+ ?line "/foo" = filename:absname(foo),
+ ?line "/foo" = filename:absname("foo"),
+ ?line "/../ebin" = filename:absname("../ebin"),
+ ?line "/erlang" = filename:absname("/erlang"),
+ ?line "/erlang/src" = filename:absname("/erlang/src"),
+ ?line "/erlang/src" = filename:absname(["/erl",'ang/s',"rc"]),
+ ?line "/erlang/src" = filename:absname(["/erl",'a','ng',"/",'s',"rc"]),
+ ?line "/erlang/src" = filename:absname("/erlang///src"),
+ ?line "/file_sorter.erl" = filename:absname([file_sorter|'.erl']),
+ ok;
+ vxworks ->
+ Test_dir = ?config(priv_dir, Config),
+ Test1 = Test_dir ++ "/foo",
+ Test2 = Test_dir ++ "/ebin",
+ ?line ok = file:set_cwd(Test_dir),
+ ?line Test1 = filename:absname(foo),
+ ?line Test1= filename:absname("foo"),
+ ?line Test2 = filename:absname("foo/../ebin"),
+ ?line "/erlang" = filename:absname("/erlang"),
+ ?line "/erlang/src" = filename:absname("/erlang/src"),
+ ?line "/erlang/src" = filename:absname(["/erlan",'g/s',"rc"]),
+ ?line "/erlang/src" = filename:absname("/erlang///src"),
+ ok
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+absname_2(Config) when is_list(Config) ->
+ case os:type() of
+ {win32, _} ->
+ ?line [Drive|_] = ?config(priv_dir, Config),
+ ?line [Drive|":/temp/foo"] = filename:absname(foo, [Drive|":/temp"]),
+ ?line [Drive|":/temp/foo"] = filename:absname("foo", [Drive|":/temp"]),
+ ?line [Drive|":/temp/../ebin"] = filename:absname("../ebin",
+ [Drive|":/temp"]),
+ ?line [Drive|":/erlang"] = filename:absname("/erlang", [Drive|":/temp"]),
+ ?line [Drive|":/erlang/src"] = filename:absname("/erlang/src",
+ [Drive|":/temp"]),
+ ?line [Drive|":/erlang/src"] = filename:absname("\\erlang\\src",
+ [Drive|":/temp"]),
+ ?line [Drive|":/temp/erlang"] = filename:absname([Drive|":erlang"],
+ [Drive|":/temp"]),
+ ?line [Drive|":/temp/erlang/src"] = filename:absname([Drive|":erlang/src"],
+ [Drive|":/temp"]),
+ ?line [Drive|":/temp/erlang/src"] =
+ filename:absname([Drive|":erlang\\src\\"], [Drive|":/temp"]),
+ ?line "a:/erlang" = filename:absname("a:erlang", [Drive|":/temp"]),
+
+ ?line file:set_cwd([Drive|":/"]),
+ ?line [Drive|":/foo"] = filename:absname(foo, [Drive|":/"]),
+ ?line [Drive|":/foo"] = filename:absname("foo", [Drive|":/"]),
+ ?line [Drive|":/../ebin"] = filename:absname("../ebin", [Drive|":/"]),
+ ?line [Drive|":/erlang"] = filename:absname("/erlang", [Drive|":/"]),
+ ?line [Drive|":/erlang/src"] = filename:absname("/erlang/src",
+ [Drive|":/"]),
+ ?line [Drive|":/erlang/src"] = filename:absname("\\erlang\\\\src",
+ [Drive|":/"]),
+ ?line [Drive|":/erlang"] = filename:absname([Drive|":erlang"],
+ [Drive|":/"]),
+ ?line [Drive|":/erlang/src"] = filename:absname([Drive|":erlang/src"],
+ [Drive|":/"]),
+ ?line "a:/erlang" = filename:absname("a:erlang", [Drive|":/"]),
+
+ ok;
+ {unix, _} ->
+ ?line "/usr/foo" = filename:absname(foo, "/usr"),
+ ?line "/usr/foo" = filename:absname("foo", "/usr"),
+ ?line "/usr/../ebin" = filename:absname("../ebin", "/usr"),
+
+ ?line "/foo" = filename:absname(foo, "/"),
+ ?line "/foo" = filename:absname("foo", "/"),
+ ?line "/../ebin" = filename:absname("../ebin", "/"),
+ ?line "/erlang" = filename:absname("/erlang", "/"),
+ ?line "/erlang/src" = filename:absname("/erlang/src", "/"),
+ ?line "/erlang/src" = filename:absname("/erlang///src", "/"),
+ ok;
+ vxworks ->
+ ?line "/usr/foo" = filename:absname(foo, "/usr"),
+ ?line "/usr/foo" = filename:absname("foo", "/usr"),
+ ?line "/usr/ebin" = filename:absname("../ebin", "/usr"),
+ ?line "/usr/ebin" = filename:absname("../ebin", "/usr/src"),
+ ?line "/erlang" = filename:absname("/erlang", "/usr"),
+ ?line "/erlang/src" = filename:absname("/erlang/src", "/usr"),
+ ?line "/erlang/src" = filename:absname("/erlang///src", "/usr"),
+ ok
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+basename_1(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(10)),
+ ?line "." = filename:basename("."),
+ ?line "foo" = filename:basename("foo"),
+ ?line "foo" = filename:basename("/usr/foo"),
+ ?line "foo.erl" = filename:basename("A:usr/foo.erl"),
+ ?line "foo" = filename:basename('/usr/foo'),
+ ?line "foo" = filename:basename(["/usr","/","f","o","o"]),
+ ?line "foo" = filename:basename(["/usr/",foo]),
+ ?line "foo" = filename:basename(["/usr/f",oo]),
+ ?line "foo" = filename:basename(["usr/", "foo"]),
+ ?line "foo" = filename:basename(["usr/"|foo]),
+ ?line "foo" = filename:basename(["usr/foo/"]),
+ ?line case os:type() of
+ {win32, _} ->
+ ?line "foo" = filename:basename(["usr\\foo\\"]),
+ ?line "foo" = filename:basename("A:\\usr\\foo"),
+ ?line "foo" = filename:basename("A:foo");
+ {unix, _} ->
+ ?line "strange\\but\\true" =
+ filename:basename("strange\\but\\true");
+ vxworks ->
+ ?line "foo" = filename:basename(["usr\\foo\\"]),
+ ?line "foo" = filename:basename("elrond:usr\\foo\\"),
+ ?line "foo" = filename:basename("disk:/foo")
+ end,
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+basename_2(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(10)),
+ ?line "." = filename:basename(".", ".erl"),
+ ?line "foo" = filename:basename("foo.erl", ".erl"),
+ ?line "foo" = filename:basename('foo.erl', ".erl"),
+ ?line "foo" = filename:basename("foo.erl", '.erl'),
+ ?line "foo" = filename:basename(["/usr","/","f","oo"], ".erl"),
+ ?line "foo.erl" = filename:basename("/usr/foo.erl", ".hrl"),
+ ?line "foo.erl" = filename:basename("/usr.hrl/foo.erl", ".hrl"),
+ ?line "foo" = filename:basename("/usr.hrl/foo", ".hrl"),
+ ?line "foo" = filename:basename("usr/foo/", ".erl"),
+ ?line "foo.erl" = filename:basename("usr/foo.erl/", ".erl"),
+ ?line "foo.erl" = filename:basename("usr/foo.erl/", '.erl'),
+ ?line "foo" = filename:basename(["/usr",'/','f','oo'], ".erl"),
+ ?line "foo.erl" = filename:basename(["usr/foo.e",'rl/'], ".erl"),
+ ?line case os:type() of
+ {win32, _} ->
+ ?line "foo" = filename:basename("A:foo", ".erl"),
+ ?line "foo.erl" = filename:basename("a:\\usr\\foo.erl",
+ ".hrl"),
+ ?line "foo.erl" = filename:basename("c:\\usr.hrl\\foo.erl",
+ ".hrl"),
+ ?line "foo" = filename:basename("A:\\usr\\foo", ".hrl");
+ {unix, _} ->
+ ?line "strange\\but\\true" =
+ filename:basename("strange\\but\\true.erl", ".erl"),
+ ?line "strange\\but\\true" =
+ filename:basename("strange\\but\\true", ".erl");
+ vxworks ->
+ ?line "foo" = filename:basename("net:foo", ".erl"),
+ ?line "foo.erl" = filename:basename("net:\\usr\\foo.erl",
+ ".hrl"),
+ ?line "foo.erl" =
+ filename:basename("/disk0:\\usr.hrl\\foo.erl",
+ ".hrl"),
+ ?line "foo" = filename:basename("/home\\usr\\foo", ".hrl")
+ end,
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dirname(Config) when is_list(Config) ->
+ case os:type() of
+ {win32,_} ->
+ ?line "A:/usr" = filename:dirname("A:/usr/foo.erl"),
+ ?line "A:usr" = filename:dirname("A:usr/foo.erl"),
+ ?line "/usr" = filename:dirname("\\usr\\foo.erl"),
+ ?line "/" = filename:dirname("\\usr"),
+ ?line "A:" = filename:dirname("A:");
+ vxworks ->
+ ?line "net:/usr" = filename:dirname("net:/usr/foo.erl"),
+ ?line "/disk0:/usr" = filename:dirname("/disk0:/usr/foo.erl"),
+ ?line "/usr" = filename:dirname("\\usr\\foo.erl"),
+ ?line "/usr" = filename:dirname("\\usr"),
+ ?line "net:" = filename:dirname("net:");
+ _ -> true
+ end,
+ ?line "usr" = filename:dirname("usr///foo.erl"),
+ ?line "." = filename:dirname("foo.erl"),
+ ?line "." = filename:dirname("."),
+ ?line "usr" = filename:dirname('usr/foo.erl'),
+ ?line "usr" = filename:dirname(['usr','/foo.erl']),
+ ?line "usr" = filename:dirname(['us','r/foo.erl']),
+ ?line "usr" = filename:dirname(['usr/','/foo.erl']),
+ ?line "usr" = filename:dirname(['usr/','foo.erl']),
+ ?line "usr" = filename:dirname(['usr/'|'foo.erl']),
+ ?line "usr" = filename:dirname(['usr/f','oo.erl']),
+ case os:type() of
+ vxworks ->
+ ?line "/" = filename:dirname("/"),
+ ?line "/usr" = filename:dirname("/usr");
+ _ ->
+ ?line "/" = filename:dirname("/"),
+ ?line "/" = filename:dirname("/usr")
+ end,
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+extension(Config) when is_list(Config) ->
+ ?line ".erl" = filename:extension("A:/usr/foo.erl"),
+ ?line ".erl" = filename:extension("A:/usr/foo.nisse.erl"),
+ ?line ".erl" = filename:extension(["A:/usr/", 'foo.ni', "sse.erl"]),
+ ?line ".erl" = filename:extension(["A:/usr/", 'foo.ni', "sse.e", 'rl']),
+ ?line ".erl" = filename:extension(["A:/usr/", 'foo.ni', "sse.e"|'rl']),
+ ?line ".erl" = filename:extension("A:/usr.bar/foo.nisse.erl"),
+ ?line "" = filename:extension("A:/usr.bar/foo"),
+ ?line "" = filename:extension("A:/usr/foo"),
+ ?line case os:type() of
+ {win32, _} ->
+ ?line "" = filename:extension("A:\\usr\\foo"),
+ ?line ".erl" =
+ filename:extension("A:/usr.bar/foo.nisse.erl"),
+ ?line "" = filename:extension("A:/usr.bar/foo"),
+ ok;
+ vxworks ->
+ ?line "" = filename:extension("/disk0:\\usr\\foo"),
+ ?line ".erl" =
+ filename:extension("net:/usr.bar/foo.nisse.erl"),
+ ?line "" = filename:extension("net:/usr.bar/foo"),
+ ok;
+ _ -> ok
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+join(Config) when is_list(Config) ->
+ ?line "/" = filename:join(["/"]),
+ ?line "/" = filename:join(["//"]),
+ ?line "usr/foo.erl" = filename:join("usr","foo.erl"),
+ ?line "/src/foo.erl" = filename:join(usr, "/src/foo.erl"),
+ ?line "/src/foo.erl" = filename:join(["/src/",'foo.erl']),
+ ?line "/src/foo.erl" = filename:join(usr, ["/sr", 'c/foo.erl']),
+ ?line "/src/foo.erl" = filename:join("usr", "/src/foo.erl"),
+
+ %% Make sure that redundant slashes work too.
+ ?line "a/b/c/d/e/f/g" = filename:join(["a//b/c/////d//e/f/g"]),
+ ?line "a/b/c/d/e/f/g" = filename:join(["a//b/c/", "d//e/f/g"]),
+ ?line "a/b/c/d/e/f/g" = filename:join(["a//b/c", "d//e/f/g"]),
+ ?line "/d/e/f/g" = filename:join(["a//b/c", "/d//e/f/g"]),
+ ?line "/d/e/f/g" = filename:join(["a//b/c", "//d//e/f/g"]),
+
+ ?line "foo/bar" = filename:join([$f,$o,$o,$/,[]], "bar"),
+
+ ?line case os:type() of
+ {win32, _} ->
+ ?line "d:/" = filename:join(["D:/"]),
+ ?line "d:/" = filename:join(["D:\\"]),
+ ?line "d:/abc" = filename:join(["D:/", "abc"]),
+ ?line "d:abc" = filename:join(["D:", "abc"]),
+ ?line "a/b/c/d/e/f/g" =
+ filename:join(["a//b\\c//\\/\\d/\\e/f\\g"]),
+ ?line "a:usr/foo.erl" =
+ filename:join(["A:","usr","foo.erl"]),
+ ?line "/usr/foo.erl" =
+ filename:join(["A:","/usr","foo.erl"]),
+ ?line "c:usr" = filename:join("A:","C:usr"),
+ ?line "a:usr" = filename:join("A:","usr"),
+ ?line "c:/usr" = filename:join("A:", "C:/usr"),
+ ?line "c:/usr/foo.erl" =
+ filename:join(["A:","C:/usr","foo.erl"]),
+ ?line "c:usr/foo.erl" =
+ filename:join(["A:","C:usr","foo.erl"]),
+ ?line "d:/foo" = filename:join([$D, $:, $/, []], "foo"),
+ ok;
+ vxworks ->
+ ?line "Net:" = filename:join(["Net:/"]),
+ ?line "net:" = filename:join(["net:\\"]),
+ ?line "net:/abc" = filename:join(["net:/", "abc"]),
+ ?line "net:/abc" = filename:join(["net:", "abc"]),
+ ?line "a/b/c/d/e/f/g" =
+ filename:join(["a//b\\c//\\/\\d/\\e/f\\g"]),
+ ?line "net:/usr/foo.erl" =
+ filename:join(["net:","usr","foo.erl"]),
+ ?line "/usr/foo.erl" =
+ filename:join(["net:","/usr","foo.erl"]),
+ ?line "/target:usr" = filename:join("net:","/target:usr"),
+ ?line "kernel:/usr" = filename:join("net:", "kernel:/usr"),
+ ?line "foo:/usr/foo.erl" =
+ filename:join(["A:","foo:/usr","foo.erl"]),
+ ?line "/disk0:usr/foo.erl" =
+ filename:join(["kalle:","/disk0:usr","foo.erl"]),
+ ?line "D:/foo" = filename:join([$D, $:, $/, []], "foo"),
+ ok;
+ {unix, _} ->
+ ok
+ end.
+
+pathtype(Config) when is_list(Config) ->
+ ?line relative = filename:pathtype(".."),
+ ?line relative = filename:pathtype("foo"),
+ ?line relative = filename:pathtype("foo/bar"),
+ ?line relative = filename:pathtype('foo/bar'),
+ ?line relative = filename:pathtype(['f','oo',"/bar"]),
+ case os:type() of
+ {win32, _} ->
+ ?line volumerelative = filename:pathtype("/usr/local/bin"),
+ ?line volumerelative = filename:pathtype("A:usr/local/bin"),
+ ok;
+ {unix, _} ->
+ ?line absolute = filename:pathtype("/"),
+ ?line absolute = filename:pathtype("/usr/local/bin"),
+ ok;
+ vxworks ->
+ ?line absolute = filename:pathtype("/usr/local/bin"),
+ ?line absolute = filename:pathtype("net:usr/local/bin"),
+ ok
+ end.
+
+rootname(Config) when is_list(Config) ->
+ ?line "/jam.src/kalle" = filename:rootname("/jam.src/kalle"),
+ ?line "/jam.src/foo" = filename:rootname("/jam.src/foo.erl"),
+ ?line "/jam.src/foo" = filename:rootname(["/ja",'m.sr',"c/foo.erl"]),
+ ?line "/jam.src/foo" = filename:rootname("/jam.src/foo.erl", ".erl"),
+ ?line "/jam.src/foo.jam" = filename:rootname("/jam.src/foo.jam", ".erl"),
+ ?line "/jam.src/foo.jam" = filename:rootname(["/jam.sr",'c/foo.j',"am"],".erl"),
+ ?line "/jam.src/foo.jam" = filename:rootname(["/jam.sr",'c/foo.j'|am],".erl"),
+ ok.
+
+split(Config) when is_list(Config) ->
+ case os:type() of
+ vxworks ->
+ ?line ["/usr","local","bin"] = filename:split("/usr/local/bin");
+ _ ->
+ ?line ["/","usr","local","bin"] = filename:split("/usr/local/bin")
+ end,
+ ?line ["foo","bar"]= filename:split("foo/bar"),
+ ?line ["foo", "bar", "hello"]= filename:split("foo////bar//hello"),
+ ?line ["foo", "bar", "hello"]= filename:split(["foo//",'//bar//h',"ello"]),
+ ?line ["foo", "bar", "hello"]= filename:split(["foo//",'//bar//h'|ello]),
+ case os:type() of
+ {win32,_} ->
+ ?line ["a:/","msdev","include"] =
+ filename:split("a:/msdev/include"),
+ ?line ["a:/","msdev","include"] =
+ filename:split("A:/msdev/include"),
+ ?line ["msdev","include"] =
+ filename:split("msdev\\include"),
+ ?line ["a:/","msdev","include"] =
+ filename:split("a:\\msdev\\include"),
+ ?line ["a:","msdev","include"] =
+ filename:split("a:msdev\\include"),
+ ok;
+ vxworks ->
+ ?line ["net:","msdev","include"] =
+ filename:split("net:/msdev/include"),
+ ?line ["Target:","msdev","include"] =
+ filename:split("Target:/msdev/include"),
+ ?line ["msdev","include"] =
+ filename:split("msdev\\include"),
+ ?line ["/disk0:","msdev","include"] =
+ filename:split("/disk0:\\msdev\\include"),
+ ?line ["a:","msdev","include"] =
+ filename:split("a:msdev\\include"),
+ ok;
+ _ ->
+ ok
+ end.
+
+t_nativename(Config) when is_list(Config) ->
+ ?line "abcedf" = filename:nativename(abcedf),
+ ?line "abcedf" = filename:nativename(["abc", "edf"]),
+ ?line "abcgluff" = filename:nativename(["abc", gluff]),
+ case os:type() of
+ {win32, _} ->
+ ?line "a:\\temp\\arne.exe" =
+ filename:nativename("A:/temp//arne.exe/");
+ _ ->
+ ?line "/usr/tmp/arne" =
+ filename:nativename("/usr/tmp//arne/")
+ end.
+
+find_src(Config) when is_list(Config) ->
+ ?line {Source,_} = filename:find_src(file),
+ ?line ["file"|_] = lists:reverse(filename:split(Source)),
+ ?line {_,_} = filename:find_src(init, [{".","."}, {"ebin","src"}]),
+
+ %% Try to find the source for a preloaded module.
+ ?line {error,{preloaded,init}} = filename:find_src(init),
+ ok.
diff --git a/lib/stdlib/test/fixtable_SUITE.erl b/lib/stdlib/test/fixtable_SUITE.erl
new file mode 100644
index 0000000000..9f21308ad4
--- /dev/null
+++ b/lib/stdlib/test/fixtable_SUITE.erl
@@ -0,0 +1,414 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------------
+%%% Purpose : Tests the safe_fixtable functions in both ets and dets.
+%%%----------------------------------------------------------------------
+
+-module(fixtable_SUITE).
+-export([all/1]).
+%%% Test cases
+-export([multiple_fixes/1, multiple_processes/1,
+ other_process_deletes/1, owner_dies/1,
+ other_process_closes/1,insert_same_key/1]).
+-export([fixbag/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+%%% Internal exports
+-export([command_loop/0,start_commander/0]).
+
+all(suite) -> {req, [stdlib],
+ [multiple_fixes, multiple_processes,
+ other_process_deletes, owner_dies,
+ other_process_closes,insert_same_key,fixbag]}.
+
+-include("test_server.hrl").
+
+%%% I wrote this thinking I would use more than one temporary at a time, but
+%%% I wasn't... Well, maybe in the future...
+-define(DETS_TEMPORARIES, [tmp1]).
+-define(ETS_TEMPORARIES, [gurksmetsmedaljong]).
+-define(DETS_TMP1,hd(?DETS_TEMPORARIES)).
+-define(ETS_TMP1,hd(?ETS_TEMPORARIES)).
+
+-define(HELPER_NODE, (atom_to_list(?MODULE) ++ "_helper1")).
+
+init_per_testcase(_Func, Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ file:make_dir(PrivDir),
+ Dog=test_server:timetrap(test_server:seconds(60)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Func, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ lists:foreach(fun(X) ->
+ (catch dets:close(X)),
+ (catch file:delete(dets_filename(X,Config)))
+ end,
+ ?DETS_TEMPORARIES),
+ lists:foreach(fun(X) ->
+ (catch ets:delete(X))
+ end,
+ ?ETS_TEMPORARIES).
+
+
+-ifdef(DEBUG).
+-define(LOG(X), show(X,?LINE)).
+
+show(Term, Line) ->
+ io:format("~p: ~p~n", [Line,Term]),
+ Term.
+-else.
+-define(LOG(X),X).
+-endif.
+
+
+fixbag(doc) ->
+ ["Check for bug OTP-5087, safe_fixtable for bags could give "
+ "incorrect lookups"];
+fixbag(suite) ->
+ [];
+fixbag(Config) when list(Config) ->
+ ?line T = ets:new(x,[bag]),
+ ?line ets:insert(T,{a,1}),
+ ?line ets:insert(T,{a,2}),
+ ?line ets:safe_fixtable(T,true),
+ ?line ets:match_delete(T,{a,2}),
+ ?line ets:insert(T,{a,3}),
+ ?line Res = ets:lookup(T,a),
+ ?line ets:safe_fixtable(T,false),
+ ?line Res = ets:lookup(T,a),
+ ok.
+
+
+
+insert_same_key(doc) ->
+ ["Check correct behaviour if a key is deleted and reinserted during fixation."];
+insert_same_key(suite) ->
+ [];
+insert_same_key(Config) when list(Config) ->
+ ?line {ok,Dets1} = dets:open_file(?DETS_TMP1,
+ [{file, dets_filename(?DETS_TMP1,Config)}]),
+ ?line Ets1 = ets:new(ets,[]),
+ ?line insert_same_key(Dets1,dets,Config),
+ ?line insert_same_key(Ets1,ets,Config),
+ ?line ets:insert(Ets1,{1,2}),
+ ?line 1 = ets:info(Ets1,size),
+ ?line dets:insert(Dets1,{1,2}),
+ ?line 1 = dets:info(Dets1,size),
+ ?line dets:close(Dets1),
+ ?line (catch file:delete(dets_filename(Dets1,Config))),
+ ?line ets:delete(Ets1),
+ ?line {ok,Dets2} = dets:open_file(?DETS_TMP1,
+ [{type,bag},{file, dets_filename(?DETS_TMP1,Config)}]),
+ ?line Ets2 = ets:new(ets,[bag]),
+ ?line insert_same_key(Dets2,dets,Config),
+ ?line insert_same_key(Ets2,ets,Config),
+ ?line ets:insert(Ets2,{1,2}),
+ ?line 2 = ets:info(Ets2,size),
+ ?line ets:insert(Ets2,{1,2}),
+ ?line 2 = ets:info(Ets2,size),
+ ?line dets:insert(Dets2,{1,2}),
+ ?line 2 = dets:info(Dets2,size),
+ ?line dets:insert(Dets2,{1,2}),
+ ?line 2 = dets:info(Dets2,size),
+ ?line dets:close(Dets2),
+ ?line (catch file:delete(dets_filename(Dets2,Config))),
+ ?line ets:delete(Ets2),
+ ?line {ok,Dets3} = dets:open_file(?DETS_TMP1,
+ [{type,duplicate_bag},
+ {file, dets_filename(?DETS_TMP1,Config)}]),
+ ?line Ets3 = ets:new(ets,[duplicate_bag]),
+ ?line insert_same_key(Dets3,dets,Config),
+ ?line insert_same_key(Ets3,ets,Config),
+ ?line ets:insert(Ets3,{1,2}),
+ ?line 2 = ets:info(Ets3,size),
+ ?line ets:insert(Ets3,{1,2}),
+ ?line 3 = ets:info(Ets3,size),
+ ?line dets:insert(Dets3,{1,2}),
+ ?line 2 = dets:info(Dets3,size),
+ ?line dets:insert(Dets3,{1,2}),
+ ?line 3 = dets:info(Dets3,size),
+ ?line dets:close(Dets3),
+ ?line (catch file:delete(dets_filename(Dets3,Config))),
+ ?line ets:delete(Ets3),
+ ok.
+
+insert_same_key(Tab,Mod,_Config) ->
+ ?line Mod:insert(Tab,{1,1}),
+ ?line Mod:insert(Tab,{1,2}),
+ ?line Mod:insert(Tab,{2,2}),
+ ?line Mod:insert(Tab,{2,2}),
+ ?line Mod:safe_fixtable(Tab,true),
+ ?line Mod:delete(Tab,1),
+ ?line Mod:insert(Tab,{1,1}),
+ ?line Expect = case Mod:info(Tab,type) of
+ bag ->
+ Mod:insert(Tab,{1,2}),
+ 2;
+ _ ->
+ 1
+ end,
+ ?line Mod:delete(Tab,2),
+ ?line Mod:safe_fixtable(Tab,false),
+ ?line case Mod:info(Tab,size) of
+ Expect ->
+ ok;
+ _ ->
+ exit({size_field_wrong,{Mod,Mod:info(Tab)}})
+ end.
+
+
+
+
+owner_dies(doc) ->
+ ["Check correct behaviour if the table owner dies."];
+owner_dies(suite) ->
+ [];
+owner_dies(Config) when list(Config) ->
+ ?line P1 = start_commander(),
+ ?line Ets1 = command(P1,{ets,new,[ets,[]]}),
+ ?line command(P1,{ets,safe_fixtable,[Ets1,true]}),
+ ?line {_,[{P1,1}]} = ets:info(Ets1, safe_fixed),
+ ?line stop_commander(P1),
+ ?line undefined = ets:info(Ets1, safe_fixed),
+ ?line P2 = start_commander(),
+ ?line Ets2 = command(P2,{ets,new,[ets,[public]]}),
+ ?line command(P2,{ets,safe_fixtable,[Ets2,true]}),
+ ?line ets:safe_fixtable(Ets2,true),
+ ?line true = ets:info(Ets2, fixed),
+ ?line {_,[{_,1},{_,1}]} = ets:info(Ets2, safe_fixed),
+ ?line stop_commander(P2),
+ ?line undefined = ets:info(Ets2, safe_fixed),
+ ?line undefined = ets:info(Ets2, fixed),
+ ?line P3 = start_commander(),
+ ?line {ok,Dets} = ?LOG(command(P3, {dets, open_file,
+ [?DETS_TMP1,
+ [{file,
+ dets_filename(?DETS_TMP1,
+ Config)}]]})),
+ ?line command(P3, {dets, safe_fixtable, [Dets, true]}),
+ ?line {_,[{P3,1}]} = dets:info(Dets, safe_fixed),
+ ?line true = dets:info(Dets, fixed),
+ ?line stop_commander(P3),
+ ?line undefined = dets:info(Dets, safe_fixed),
+ ?line undefined = dets:info(Dets, fixed),
+ ?line P4 = start_commander(),
+ ?line {ok,Dets} = command(P4, {dets, open_file,
+ [?DETS_TMP1,
+ [{file, dets_filename(?DETS_TMP1,Config)}]]}),
+ ?line {ok,Dets} = dets:open_file(?DETS_TMP1,
+ [{file, dets_filename(?DETS_TMP1,Config)}]),
+ ?line false = dets:info(Dets, safe_fixed),
+ ?line command(P4, {dets, safe_fixtable, [Dets, true]}),
+ ?line dets:safe_fixtable(Dets, true),
+ ?line {_,[{_,1},{_,1}]} = dets:info(Dets, safe_fixed),
+ ?line dets:safe_fixtable(Dets, true),
+ ?line stop_commander(P4),
+ ?line S = self(),
+ ?line {_,[{S,2}]} = dets:info(Dets, safe_fixed),
+ ?line true = dets:info(Dets, fixed),
+ ?line dets:close(Dets),
+ ?line undefined = dets:info(Dets, fixed),
+ ?line undefined = dets:info(Dets, safe_fixed),
+ ok.
+
+
+other_process_closes(doc) ->
+ ["When another process closes an dets table, different "
+ "things should happen depending on if it has opened it before."];
+
+other_process_closes(suite) ->
+ [];
+
+other_process_closes(Config) when list(Config) ->
+ ?line {ok,Dets} = dets:open_file(?DETS_TMP1,
+ [{file, dets_filename(tmp1,Config)}]),
+ ?line P2 = start_commander(),
+ ?line dets:safe_fixtable(Dets,true),
+ ?line S = self(),
+ ?line {_,[{S,1}]} = dets:info(Dets, safe_fixed),
+ ?line command(P2,{dets, safe_fixtable, [Dets, true]}),
+ ?line {_,[_,_]} = dets:info(Dets, safe_fixed),
+ ?line {error, not_owner} = command(P2,{dets, close, [Dets]}),
+ ?line {_,[_,_]} = dets:info(Dets, safe_fixed),
+ ?line command(P2,{dets, open_file,[?DETS_TMP1,
+ [{file,
+ dets_filename(?DETS_TMP1, Config)}]]}),
+ ?line {_,[_,_]} = dets:info(Dets, safe_fixed),
+ ?line command(P2,{dets, close, [Dets]}),
+ ?line stop_commander(P2),
+ ?line {_,[{S,1}]} = dets:info(Dets, safe_fixed),
+ ?line true = dets:info(Dets,fixed),
+ ?line dets:close(Dets),
+ ?line undefined = dets:info(Dets,fixed),
+ ?line undefined = dets:info(Dets, safe_fixed),
+ ok.
+
+other_process_deletes(doc) ->
+ ["Check that fixtable structures are cleaned up if another process "
+ "deletes an ets table"];
+other_process_deletes(suite) ->
+ [];
+other_process_deletes(Config) when list(Config) ->
+ ?line Ets = ets:new(ets,[public]),
+ ?line P = start_commander(),
+ ?line ets:safe_fixtable(Ets,true),
+ ?line ets:safe_fixtable(Ets,true),
+ ?line true = ets:info(Ets, fixed),
+ ?line {_,_} = ets:info(Ets, safe_fixed),
+ ?line command(P,{ets,delete,[Ets]}),
+ ?line stop_commander(P),
+ ?line undefined = ets:info(Ets, fixed),
+ ?line undefined = ets:info(Ets, safe_fixed),
+ ok.
+
+multiple_fixes(doc) ->
+ ["Check that multiple safe_fixtable keeps the reference counter."];
+multiple_fixes(suite) ->
+ [];
+multiple_fixes(Config) when list(Config) ->
+ ?line {ok,Dets} = dets:open_file(?DETS_TMP1,
+ [{file, dets_filename(?DETS_TMP1,Config)}]),
+ ?line Ets = ets:new(ets,[]),
+ ?line multiple_fixes(Dets,dets),
+ ?line multiple_fixes(Ets,ets),
+ ?line dets:close(Dets),
+ ok.
+
+multiple_fixes(Tab, Mod) ->
+ ?line false = Mod:info(Tab,fixed),
+ ?line false = Mod:info(Tab, safe_fixed),
+ ?line Mod:safe_fixtable(Tab, true),
+ ?line true = Mod:info(Tab,fixed),
+ ?line S = self(),
+ ?line {_,[{S,1}]} = Mod:info(Tab, safe_fixed),
+ ?line Mod:safe_fixtable(Tab, true),
+ ?line Mod:safe_fixtable(Tab, true),
+ ?line {_,[{S,3}]} = Mod:info(Tab, safe_fixed),
+ ?line true = Mod:info(Tab,fixed),
+ ?line Mod:safe_fixtable(Tab, false),
+ ?line {_,[{S,2}]} = Mod:info(Tab, safe_fixed),
+ ?line true = Mod:info(Tab,fixed),
+ ?line Mod:safe_fixtable(Tab, false),
+ ?line {_,[{S,1}]} = Mod:info(Tab, safe_fixed),
+ ?line true = Mod:info(Tab,fixed),
+ ?line Mod:safe_fixtable(Tab, false),
+ ?line false = Mod:info(Tab, safe_fixed),
+ ?line false = Mod:info(Tab,fixed).
+
+multiple_processes(doc) ->
+ ["Check that multiple safe_fixtable across processes are reference "
+ "counted OK"];
+multiple_processes(suite) ->
+ [];
+multiple_processes(Config) when list(Config) ->
+ ?line {ok,Dets} = dets:open_file(?DETS_TMP1,[{file,
+ dets_filename(?DETS_TMP1,
+ Config)}]),
+ ?line Ets = ets:new(ets,[public]),
+ ?line multiple_processes(Dets,dets),
+ ?line multiple_processes(Ets,ets),
+ ok.
+
+multiple_processes(Tab, Mod) ->
+ ?line io:format("Mod = ~p\n", [Mod]),
+ ?line P1 = start_commander(),
+ ?line P2 = start_commander(),
+ ?line false = Mod:info(Tab,fixed),
+ ?line false = Mod:info(Tab, safe_fixed),
+ ?line command(P1, {Mod, safe_fixtable, [Tab,true]}),
+ ?line true = Mod:info(Tab,fixed),
+ ?line {_,[{P1,1}]} = Mod:info(Tab, safe_fixed),
+ ?line command(P2, {Mod, safe_fixtable, [Tab,true]}),
+ ?line true = Mod:info(Tab,fixed),
+ ?line {_,L} = Mod:info(Tab,safe_fixed),
+ ?line true = (lists:sort(L) == lists:sort([{P1,1},{P2,1}])),
+ ?line command(P2, {Mod, safe_fixtable, [Tab,true]}),
+ ?line {_,L2} = Mod:info(Tab,safe_fixed),
+ ?line true = (lists:sort(L2) == lists:sort([{P1,1},{P2,2}])),
+ ?line command(P2, {Mod, safe_fixtable, [Tab,false]}),
+ ?line true = Mod:info(Tab,fixed),
+ ?line {_,L3} = Mod:info(Tab,safe_fixed),
+ ?line true = (lists:sort(L3) == lists:sort([{P1,1},{P2,1}])),
+ ?line command(P2, {Mod, safe_fixtable, [Tab,false]}),
+ ?line true = Mod:info(Tab,fixed),
+ ?line {_,[{P1,1}]} = Mod:info(Tab, safe_fixed),
+ ?line stop_commander(P1),
+ ?line receive after 1000 -> ok end,
+ ?line false = Mod:info(Tab,fixed),
+ ?line false = Mod:info(Tab, safe_fixed),
+ ?line command(P2, {Mod, safe_fixtable, [Tab,true]}),
+ ?line true = Mod:info(Tab,fixed),
+ ?line {_,[{P2,1}]} = Mod:info(Tab, safe_fixed),
+ case Mod of
+ dets ->
+ ?line dets:close(Tab);
+ ets ->
+ ?line ets:delete(Tab)
+ end,
+ ?line stop_commander(P2),
+ ?line receive after 1000 -> ok end,
+ ?line undefined = Mod:info(Tab, safe_fixed),
+ ok.
+
+
+
+%%% Helpers
+dets_filename(Base, Config) when atom(Base) ->
+ dets_filename(atom_to_list(Base) ++ ".dat", Config);
+dets_filename(Basename, Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ filename:join(PrivDir, Basename).
+
+command_loop() ->
+ receive
+ {From, command, {M,F,A}} ->
+ Res = (catch apply(M, F, A)),
+ From ! {self(), Res},
+ command_loop();
+ die ->
+ ok
+ end.
+
+start_commander() ->
+ spawn(?MODULE, command_loop, []).
+
+stop_commander(Pid) ->
+ process_flag(trap_exit, true),
+ link(Pid),
+ Pid ! die,
+ receive
+ {'EXIT',Pid,_} ->
+ timer:sleep(1), % let other processes handle the signal as well
+ true
+ after 5000 ->
+ exit(stop_timeout)
+ end.
+
+command(Pid,MFA) ->
+ Pid ! {self(), command, MFA},
+ receive
+ {Pid, Res} ->
+ Res
+ after 20000 ->
+ exit(command_timeout)
+ end.
+
+
+
diff --git a/lib/stdlib/test/format_SUITE.erl b/lib/stdlib/test/format_SUITE.erl
new file mode 100644
index 0000000000..2c415894f4
--- /dev/null
+++ b/lib/stdlib/test/format_SUITE.erl
@@ -0,0 +1,51 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(format_SUITE).
+-export([all/1]).
+
+-export([hang_1/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-include("test_server.hrl").
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(doc) ->
+ ["Test cases for io:format/[2,3]."];
+all(suite) ->
+ [hang_1].
+
+hang_1(doc) ->
+ ["Bad args can hang (OTP-2400)"];
+hang_1(suite) ->
+ [];
+hang_1(Config) when list(Config) ->
+ ?line _ = (catch io:format(a, "", [])),
+ ?line _ = (catch io:format({}, "", [])),
+ ok.
diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl
new file mode 100644
index 0000000000..dc5ddebf53
--- /dev/null
+++ b/lib/stdlib/test/gen_event_SUITE.erl
@@ -0,0 +1,846 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen_event_SUITE).
+
+-include("test_server.hrl").
+
+-export([all/1]).
+-export([start/1, test_all/1, add_handler/1, add_sup_handler/1,
+ delete_handler/1, swap_handler/1, swap_sup_handler/1,
+ notify/1, sync_notify/1, call/1, info/1, hibernate/1]).
+
+all(suite) -> {req, [stdlib], [start, test_all, hibernate]}.
+
+%% --------------------------------------
+%% Start an event manager.
+%% --------------------------------------
+
+start(doc) -> [];
+start(suite) -> [];
+start(Config) when list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, Pid0} = gen_event:start(), %anonymous
+ ?line [] = gen_event:which_handlers(Pid0),
+ ?line ok = gen_event:stop(Pid0),
+
+ ?line {ok, Pid1} = gen_event:start_link(), %anonymous
+ ?line [] = gen_event:which_handlers(Pid1),
+ ?line ok = gen_event:stop(Pid1),
+
+ ?line {ok, Pid2} = gen_event:start({local, my_dummy_name}),
+ ?line [] = gen_event:which_handlers(my_dummy_name),
+ ?line [] = gen_event:which_handlers(Pid2),
+ ?line ok = gen_event:stop(my_dummy_name),
+
+ ?line {ok, Pid3} = gen_event:start_link({local, my_dummy_name}),
+ ?line [] = gen_event:which_handlers(my_dummy_name),
+ ?line [] = gen_event:which_handlers(Pid3),
+ ?line ok = gen_event:stop(my_dummy_name),
+
+ ?line {ok, Pid4} = gen_event:start_link({global, my_dummy_name}),
+ ?line [] = gen_event:which_handlers({global, my_dummy_name}),
+ ?line [] = gen_event:which_handlers(Pid4),
+ ?line ok = gen_event:stop({global, my_dummy_name}),
+
+ ?line {ok, _} = gen_event:start_link({local, my_dummy_name}),
+ ?line {error, {already_started, _}} =
+ gen_event:start_link({local, my_dummy_name}),
+ ?line {error, {already_started, _}} =
+ gen_event:start({local, my_dummy_name}),
+ ?line ok = gen_event:stop(my_dummy_name),
+
+ ?line {ok, Pid5} = gen_event:start_link({global, my_dummy_name}),
+ ?line {error, {already_started, _}} =
+ gen_event:start_link({global, my_dummy_name}),
+ ?line {error, {already_started, _}} =
+ gen_event:start({global, my_dummy_name}),
+
+ exit(Pid5, shutdown),
+ receive
+ {'EXIT', Pid5, shutdown} -> ok
+ after 10000 ->
+ ?t:fail(exit_gen_event)
+ end,
+
+ ?t:messages_get(),
+ process_flag(trap_exit, OldFl),
+ ok.
+
+
+hibernate(suite) -> [];
+hibernate(Config) when is_list(Config) ->
+ ?line {ok,Pid} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line true = gen_event:call(my_dummy_handler, dummy_h, hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line Pid ! wake,
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line later = gen_event:call(my_dummy_handler, dummy_h, hibernate_later),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line receive after 2000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line Pid ! wake,
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line gen_event:notify(my_dummy_handler,hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line gen_event:notify(my_dummy_handler,wakeup),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line gen_event:notify(my_dummy_handler,hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line gen_event:sync_notify(my_dummy_handler,wakeup),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line ok = gen_event:sync_notify(my_dummy_handler,hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line Pid ! wake,
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy1_h, [self()]),
+ ?line [_,_] = gen_event:which_handlers(my_dummy_handler),
+ ?line gen_event:notify(my_dummy_handler,hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line gen_event:notify(my_dummy_handler,wakeup),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line Pid ! wake,
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line Pid ! gnurf,
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line Pid ! sleep,
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line Pid ! wake,
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid,current_function)),
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ?line {ok,Pid2} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self(),hibernate]),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid2,current_function),
+ ?line sys:suspend(my_dummy_handler),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid2,current_function),
+ ?line sys:resume(my_dummy_handler),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid2,current_function),
+ ?line Pid2 ! wake,
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/=
+ erlang:process_info(Pid2,current_function)),
+
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+
+ ok.
+
+
+test_all(suite) -> [add_handler, add_sup_handler, delete_handler,
+ swap_handler, swap_sup_handler, notify,
+ sync_notify, call, info].
+
+add_handler(doc) -> [];
+add_handler(suite) -> [];
+add_handler(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line {error, my_error} =
+ gen_event:add_handler(my_dummy_handler, dummy_h, make_error),
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line {error, my_error} =
+ gen_event:add_handler(my_dummy_handler, {dummy_h, self()}, make_error),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,self()},
+ [self()]),
+ Self = self(),
+ ?line [{dummy_h, Self}, dummy_h] =
+ gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
+
+add_sup_handler(doc) -> [];
+add_sup_handler(suite) -> [];
+add_sup_handler(Config) when list(Config) ->
+ ?line {ok,Pid} = gen_event:start({local, my_dummy_handler}),
+ ?line {error, my_error} =
+ gen_event:add_sup_handler(my_dummy_handler, dummy_h, make_error),
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line exit(Pid, sup_died),
+ ?t:sleep(1000),
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line {error, my_error} =
+ gen_event:add_handler(my_dummy_handler, {dummy_h, self()}, make_error),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, {dummy_h,self()},
+ [self()]),
+ Self = self(),
+ ?line [{dummy_h, Self}, dummy_h] =
+ gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:stop(my_dummy_handler),
+
+ ?line receive
+ {gen_event_EXIT, dummy_h, shutdown} ->
+ ok
+ after 1000 ->
+ ?t:fail({no,{gen_event_EXIT, dummy_h, shutdown}})
+ end,
+
+ ?line receive
+ {gen_event_EXIT, {dummy_h,Self}, shutdown} ->
+ ok
+ after 1000 ->
+ ?t:fail({no,{gen_event_EXIT, {dummy_h,Self},
+ shutdown}})
+ end,
+ ok.
+
+delete_handler(doc) -> [];
+delete_handler(suite) -> [];
+delete_handler(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line {error, module_not_found} =
+ gen_event:delete_handler(my_dummy_handler, duuuuuuuuumy, []),
+ ?line return_hej =
+ gen_event:delete_handler(my_dummy_handler, dummy_h, return_hej),
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line ok =
+ gen_event:delete_handler(my_dummy_handler, dummy_h, []),
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,1}, [self()]),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,2}, [self()]),
+ ?line {error, module_not_found} =
+ gen_event:delete_handler(my_dummy_handler, {duuuuuuuuumy,1}, []),
+ ?line return_hej =
+ gen_event:delete_handler(my_dummy_handler, {dummy_h,1}, return_hej),
+ ?line return_hej =
+ gen_event:delete_handler(my_dummy_handler, {dummy_h,2}, return_hej),
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,2}, [self()]),
+ ?line ok =
+ gen_event:delete_handler(my_dummy_handler, {dummy_h,2}, []),
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
+
+swap_handler(doc) -> [];
+swap_handler(suite) -> [];
+swap_handler(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line {error, non_existing} =
+ gen_event:swap_handler(my_dummy_handler, {faulty_h, swap},
+ {dummy1_h, []}),
+ ?line ok =
+ gen_event:swap_handler(my_dummy_handler, {dummy_h, swap},
+ {dummy1_h, swap}),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:delete_handler(my_dummy_handler, dummy1_h, []),
+
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,3}, [self()]),
+ ?line {error, non_existing} =
+ gen_event:swap_handler(my_dummy_handler, {faulty_h, swap},
+ {dummy1_h, []}),
+ ?line ok =
+ gen_event:swap_handler(my_dummy_handler, {{dummy_h,3}, swap},
+ {{dummy1_h,4}, swap}),
+ ?line [{dummy1_h,4}] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:delete_handler(my_dummy_handler, {dummy1_h,4}, []),
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
+
+swap_sup_handler(doc) -> [];
+swap_sup_handler(suite) -> [];
+swap_sup_handler(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line {error, non_existing} =
+ gen_event:swap_handler(my_dummy_handler, {faulty_h, swap},
+ {dummy1_h, []}),
+ ?line ok =
+ gen_event:swap_handler(my_dummy_handler, {dummy_h, swap},
+ {dummy1_h, swap}),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:delete_handler(my_dummy_handler, dummy1_h, []),
+ ?line receive
+ {gen_event_EXIT, dummy1_h, normal} ->
+ ok
+ after 1000 ->
+ ?t:fail({no,{gen_event_EXIT, dummy1_h, normal}})
+ end,
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, {dummy_h,3},
+ [self()]),
+ ?line {error, non_existing} =
+ gen_event:swap_sup_handler(my_dummy_handler, {faulty_h, swap},
+ {dummy1_h, []}),
+ ?line ok =
+ gen_event:swap_sup_handler(my_dummy_handler, {{dummy_h,3}, swap},
+ {{dummy1_h,4}, swap}),
+ ?line [{dummy1_h,4}] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:delete_handler(my_dummy_handler, {dummy1_h,4}, []),
+ ?line receive
+ {gen_event_EXIT, {dummy1_h,4}, normal} ->
+ ok
+ after 1000 ->
+ ?t:fail({no,{gen_event_EXIT, {dummy1_h,4}, normal}})
+ end,
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
+
+notify(doc) -> [];
+notify(suite) -> [];
+notify(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ Event = {event, self()},
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:notify(my_dummy_handler, {swap_event,dummy1_h,swap}),
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy1_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:notify(my_dummy_handler, delete_event),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line ok = gen_event:notify(my_dummy_handler, error_event),
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Handler with id, {Mod,Id}
+
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,5}, [self()]),
+ ?line [{dummy_h,5}] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:notify(my_dummy_handler,
+ {swap_event, {dummy1_h, 9}, swap}),
+ ?t:sleep(1000),
+ ?line [{dummy1_h,9}] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy1_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:notify(my_dummy_handler, delete_event),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,a}, [self()]),
+
+ ?line ok = gen_event:notify(my_dummy_handler, error_event),
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Supervised handler.
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy_h, Event} ->
+ ok
+ end,
+
+ ?line ok = gen_event:notify(my_dummy_handler, do_crash),
+ ?line receive
+ {gen_event_EXIT, dummy_h, {'EXIT',_}} ->
+ ok
+ end,
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line ok = gen_event:notify(my_dummy_handler, {swap_event,dummy1_h,swap}),
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:notify(my_dummy_handler, do_crash),
+ ?line receive
+ {gen_event_EXIT, dummy1_h, {'EXIT',_}} ->
+ ok
+ end,
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line ok = gen_event:notify(my_dummy_handler, {swap_event,dummy1_h,swap}),
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:notify(my_dummy_handler, delete_event),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+
+ ?line receive
+ {gen_event_EXIT, dummy1_h, normal} ->
+ ok
+ end,
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
+
+sync_notify(doc) -> [];
+sync_notify(suite) -> [];
+sync_notify(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ Event = {event, self()},
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:sync_notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:sync_notify(my_dummy_handler,
+ {swap_event, dummy1_h, swap}),
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:sync_notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy1_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:sync_notify(my_dummy_handler, delete_event),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line ok = gen_event:sync_notify(my_dummy_handler, error_event),
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Handler with id, {Mod,Id}
+
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,5}, [self()]),
+ ?line [{dummy_h,5}] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:sync_notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:sync_notify(my_dummy_handler,
+ {swap_event, {dummy1_h, 9}, swap}),
+ ?t:sleep(1000),
+ ?line [{dummy1_h,9}] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:sync_notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy1_h, Event} ->
+ ok
+ end,
+ ?line ok = gen_event:sync_notify(my_dummy_handler, delete_event),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,a}, [self()]),
+
+ ?line ok = gen_event:sync_notify(my_dummy_handler, error_event),
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Supervised handler.
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:sync_notify(my_dummy_handler, Event),
+ ?line receive
+ {dummy_h, Event} ->
+ ok
+ end,
+
+ ?line ok = gen_event:sync_notify(my_dummy_handler, do_crash),
+ ?line receive
+ {gen_event_EXIT, dummy_h, {'EXIT',_}} ->
+ ok
+ end,
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line ok = gen_event:sync_notify(my_dummy_handler,
+ {swap_event,dummy1_h,swap}),
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:sync_notify(my_dummy_handler, do_crash),
+ ?line receive
+ {gen_event_EXIT, dummy1_h, {'EXIT',_}} ->
+ ok
+ end,
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line ok = gen_event:sync_notify(my_dummy_handler,
+ {swap_event,dummy1_h,swap}),
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:sync_notify(my_dummy_handler, delete_event),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+
+ ?line receive
+ {gen_event_EXIT, dummy1_h, normal} ->
+ ok
+ end,
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
+
+call(doc) -> [];
+call(suite) -> [];
+call(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h, 1}, [self()]),
+ ?line [{dummy_h, 1}, dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line {'EXIT',_} = (catch gen_event:call(non_exist, dummy_h, hejsan)),
+ ?line {error, bad_module} =
+ gen_event:call(my_dummy_handler, bad_h, hejsan),
+ ?line {ok, hejhopp} = gen_event:call(my_dummy_handler, dummy_h, hejsan),
+ ?line {ok, hejhopp} = gen_event:call(my_dummy_handler, {dummy_h, 1},
+ hejsan),
+ ?line {ok, hejhopp} = gen_event:call(my_dummy_handler, dummy_h, hejsan,
+ 10000),
+ ?line {'EXIT', {timeout, _}} =
+ (catch gen_event:call(my_dummy_handler, dummy_h, hejsan, 0)),
+ flush(),
+ ?line ok = gen_event:delete_handler(my_dummy_handler, {dummy_h, 1}, []),
+ ?line {ok, swapped} = gen_event:call(my_dummy_handler, dummy_h,
+ {swap_call,dummy1_h,swap}),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line {error, bad_module} =
+ gen_event:call(my_dummy_handler, dummy_h, hejsan),
+ ?line ok = gen_event:call(my_dummy_handler, dummy1_h, delete_call),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line {error, {return, faulty}} =
+ gen_event:call(my_dummy_handler, dummy_h, error_call),
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line {error, {'EXIT', _}} =
+ gen_event:call(my_dummy_handler, dummy_h, exit_call),
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Handler with id, {Mod,Id}
+
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,1}, [self()]),
+ ?line [{dummy_h,1}] = gen_event:which_handlers(my_dummy_handler),
+ ?line {error, bad_module} =
+ gen_event:call(my_dummy_handler, bad_h, hejsan),
+ ?line {ok, hejhopp} = gen_event:call(my_dummy_handler, {dummy_h,1},
+ hejsan),
+ ?line {ok, swapped} = gen_event:call(my_dummy_handler, {dummy_h,1},
+ {swap_call,{dummy1_h,2},swap}),
+ ?line [{dummy1_h,2}] = gen_event:which_handlers(my_dummy_handler),
+ ?line {error, bad_module} =
+ gen_event:call(my_dummy_handler, dummy_h, hejsan),
+ ?line ok = gen_event:call(my_dummy_handler, {dummy1_h,2}, delete_call),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,3}, [self()]),
+
+ ?line {error, {return, faulty}} =
+ gen_event:call(my_dummy_handler, {dummy_h,3}, error_call),
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,4}, [self()]),
+
+ ?line {error, {'EXIT', _}} =
+ gen_event:call(my_dummy_handler, {dummy_h,4}, exit_call),
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Supervised handler.
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line {error, bad_module} =
+ gen_event:call(my_dummy_handler, bad_h, hejsan),
+ ?line {ok, hejhopp} = gen_event:call(my_dummy_handler, dummy_h, hejsan),
+ ?line {ok, swapped} = gen_event:call(my_dummy_handler, dummy_h,
+ {swap_call,dummy1_h,swap}),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line {error, bad_module} =
+ gen_event:call(my_dummy_handler, dummy_h, hejsan),
+ ?line ok = gen_event:call(my_dummy_handler, dummy1_h, delete_call),
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+
+ ?line receive
+ {gen_event_EXIT, dummy1_h, normal} ->
+ ok
+ end,
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line {error, {return, faulty}} =
+ gen_event:call(my_dummy_handler, dummy_h, error_call),
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+
+ ?line receive
+ {gen_event_EXIT, dummy_h, {return,faulty}} ->
+ ok
+ after 1000 ->
+ ?t:fail({no, {gen_event_EXIT, dummy_h, {return,faulty}}})
+ end,
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line {error, {'EXIT', _}} =
+ gen_event:call(my_dummy_handler, dummy_h, exit_call),
+
+ ?line receive
+ {gen_event_EXIT, dummy_h, {'EXIT',_}} ->
+ ok
+ after 1000 ->
+ ?t:fail({no, {gen_event_EXIT, dummy_h, {'EXIT','_'}}})
+ end,
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
+
+flush() ->
+ receive _ -> flush() after 0 -> ok end.
+
+info(doc) -> [];
+info(suite) -> [];
+info(Config) when list(Config) ->
+ ?line {ok,_} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+ Info = {info, self()},
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line my_dummy_handler ! Info,
+ ?line receive
+ {dummy_h, Info} ->
+ ok
+ end,
+ ?line my_dummy_handler ! {swap_info,dummy1_h,swap},
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line my_dummy_handler ! Info,
+ ?line receive
+ {dummy1_h, Info} ->
+ ok
+ end,
+ ?line my_dummy_handler ! delete_info,
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line my_dummy_handler ! error_info,
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Handler with id, {Mod,Id}
+
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,1}, [self()]),
+ ?line [{dummy_h,1}] = gen_event:which_handlers(my_dummy_handler),
+ ?line my_dummy_handler ! Info,
+ ?line receive
+ {dummy_h, Info} ->
+ ok
+ end,
+ ?line my_dummy_handler ! {swap_info,{dummy1_h,2},swap},
+ ?t:sleep(1000),
+ ?line [{dummy1_h,2}] = gen_event:which_handlers(my_dummy_handler),
+ ?line my_dummy_handler ! Info,
+ ?line receive
+ {dummy1_h, Info} ->
+ ok
+ end,
+ ?line my_dummy_handler ! delete_info,
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+ ?line ok = gen_event:add_handler(my_dummy_handler, {dummy_h,3}, [self()]),
+
+ ?line my_dummy_handler ! error_info,
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ %% Supervised handler
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line [dummy_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line my_dummy_handler ! Info,
+ ?line receive
+ {dummy_h, Info} ->
+ ok
+ end,
+ ?line my_dummy_handler ! {swap_info,dummy1_h,swap},
+ ?t:sleep(1000),
+ ?line [dummy1_h] = gen_event:which_handlers(my_dummy_handler),
+ ?line my_dummy_handler ! Info,
+ ?line receive
+ {dummy1_h, Info} ->
+ ok
+ end,
+ ?line my_dummy_handler ! delete_info,
+ ?line receive
+ {dummy1_h, removed} ->
+ ok
+ end,
+
+ ?line receive
+ {gen_event_EXIT, dummy1_h, normal} ->
+ ok
+ after 1000 ->
+ ?t:fail({no, {gen_event_EXIT, dummy1_h, normal}})
+ end,
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+
+ ?line my_dummy_handler ! error_info,
+ ?line receive
+ {dummy_h, returned_error} ->
+ ok
+ end,
+
+ ?line receive
+ {gen_event_EXIT, dummy_h, {return,faulty}} ->
+ ok
+ after 1000 ->
+ ?t:fail({no, {gen_event_EXIT, dummy_h, {return,faulty}}})
+ end,
+
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy_h, [self()]),
+ ?line my_dummy_handler ! do_crash,
+
+ ?line receive
+ {gen_event_EXIT, dummy_h, {'EXIT',_}} ->
+ ok
+ after 1000 ->
+ ?t:fail({no, {gen_event_EXIT, dummy_h, {'EXIT','_'}}})
+ end,
+
+ ?line [] = gen_event:which_handlers(my_dummy_handler),
+
+ ?line ok = gen_event:stop(my_dummy_handler),
+ ok.
diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl
new file mode 100644
index 0000000000..62f8b2f9dd
--- /dev/null
+++ b/lib/stdlib/test/gen_fsm_SUITE.erl
@@ -0,0 +1,838 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen_fsm_SUITE).
+
+-include("test_server.hrl").
+
+%% Test cases
+-export([all/1]).
+
+-export([start/1, start1/1, start2/1, start3/1, start4/1 , start5/1, start6/1,
+ start7/1, start8/1, start9/1, start10/1, start11/1]).
+
+-export([abnormal/1, abnormal1/1, abnormal2/1]).
+
+-export([shutdown/1]).
+
+-export([sys/1, sys1/1]).
+
+-export([hibernate/1,hiber_idle/3,hiber_wakeup/3,hiber_idle/2,hiber_wakeup/2]).
+
+-export([enter_loop/1]).
+
+%% Exports for apply
+-export([do_msg/1, do_sync_msg/1]).
+-export([enter_loop/2]).
+
+% The gen_fsm behaviour
+-export([init/1, handle_event/3, handle_sync_event/4, terminate/3,
+ handle_info/3]).
+-export([idle/2, idle/3,
+ timeout/2,
+ wfor_conf/2, wfor_conf/3,
+ connected/2, connected/3]).
+-export([state0/3]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+all(suite) ->
+ [start, abnormal, shutdown, sys, hibernate, enter_loop].
+
+
+
+start(suite) -> [start1, start2, start3, start4, start5, start6, start7,
+ start8, start9, start10, start11].
+
+%% anonymous
+start1(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, Pid0} = gen_fsm:start_link(gen_fsm_SUITE, [], []),
+ ?line ok = do_func_test(Pid0),
+ ?line ok = do_sync_func_test(Pid0),
+ stop_it(Pid0),
+%% ?line stopped = gen_fsm:sync_send_all_state_event(Pid0, stop),
+%% ?line {'EXIT', {timeout,_}} =
+%% (catch gen_fsm:sync_send_event(Pid0, hej)),
+
+ ?line test_server:messages_get(),
+ %%process_flag(trap_exit, OldFl),
+ ok.
+
+%% anonymous w. shutdown
+start2(Config) when is_list(Config) ->
+ %% Dont link when shutdown
+ ?line {ok, Pid0} = gen_fsm:start(gen_fsm_SUITE, [], []),
+ ?line ok = do_func_test(Pid0),
+ ?line ok = do_sync_func_test(Pid0),
+ ?line shutdown_stopped =
+ gen_fsm:sync_send_all_state_event(Pid0, stop_shutdown),
+ ?line {'EXIT', {noproc,_}} =
+ (catch gen_fsm:sync_send_event(Pid0, hej)),
+
+ ?line test_server:messages_get(),
+ ok.
+
+%% anonymous with timeout
+start3(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, Pid0} = gen_fsm:start(gen_fsm_SUITE, [], [{timeout,5}]),
+ ?line ok = do_func_test(Pid0),
+ ?line ok = do_sync_func_test(Pid0),
+ ?line stop_it(Pid0),
+
+ ?line {error, timeout} = gen_fsm:start(gen_fsm_SUITE, sleep,
+ [{timeout,5}]),
+
+ test_server:messages_get(),
+ %%process_flag(trap_exit, OldFl),
+ ok.
+
+%% anonymous with ignore
+start4(suite) -> [];
+start4(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ ?line ignore = gen_fsm:start(gen_fsm_SUITE, ignore, []),
+
+ test_server:messages_get(),
+ process_flag(trap_exit, OldFl),
+ ok.
+
+%% anonymous with stop
+start5(suite) -> [];
+start5(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ ?line {error, stopped} = gen_fsm:start(gen_fsm_SUITE, stop, []),
+
+ test_server:messages_get(),
+ process_flag(trap_exit, OldFl),
+ ok.
+
+%% anonymous linked
+start6(Config) when is_list(Config) ->
+ ?line {ok, Pid} = gen_fsm:start_link(gen_fsm_SUITE, [], []),
+ ?line ok = do_func_test(Pid),
+ ?line ok = do_sync_func_test(Pid),
+ ?line stop_it(Pid),
+
+ test_server:messages_get(),
+
+ ok.
+
+%% global register linked
+start7(Config) when is_list(Config) ->
+ ?line {ok, Pid} =
+ gen_fsm:start_link({global, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line {error, {already_started, Pid}} =
+ gen_fsm:start_link({global, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line {error, {already_started, Pid}} =
+ gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
+
+ ?line ok = do_func_test(Pid),
+ ?line ok = do_sync_func_test(Pid),
+ ?line ok = do_func_test({global, my_fsm}),
+ ?line ok = do_sync_func_test({global, my_fsm}),
+ ?line stop_it({global, my_fsm}),
+
+ test_server:messages_get(),
+ ok.
+
+
+%% local register
+start8(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, Pid} =
+ gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line {error, {already_started, Pid}} =
+ gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),
+
+ ?line ok = do_func_test(Pid),
+ ?line ok = do_sync_func_test(Pid),
+ ?line ok = do_func_test(my_fsm),
+ ?line ok = do_sync_func_test(my_fsm),
+ ?line stop_it(Pid),
+
+ test_server:messages_get(),
+ %%process_flag(trap_exit, OldFl),
+ ok.
+
+%% local register linked
+start9(Config) when is_list(Config) ->
+ %%OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, Pid} =
+ gen_fsm:start_link({local, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line {error, {already_started, Pid}} =
+ gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),
+
+ ?line ok = do_func_test(Pid),
+ ?line ok = do_sync_func_test(Pid),
+ ?line ok = do_func_test(my_fsm),
+ ?line ok = do_sync_func_test(my_fsm),
+ ?line stop_it(Pid),
+
+ test_server:messages_get(),
+ %%process_flag(trap_exit, OldFl),
+ ok.
+
+%% global register
+start10(Config) when is_list(Config) ->
+ ?line {ok, Pid} =
+ gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line {error, {already_started, Pid}} =
+ gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line {error, {already_started, Pid}} =
+ gen_fsm:start_link({global, my_fsm}, gen_fsm_SUITE, [], []),
+
+ ?line ok = do_func_test(Pid),
+ ?line ok = do_sync_func_test(Pid),
+ ?line ok = do_func_test({global, my_fsm}),
+ ?line ok = do_sync_func_test({global, my_fsm}),
+ ?line stop_it({global, my_fsm}),
+
+ test_server:messages_get(),
+ ok.
+
+
+%% Stop registered processes
+start11(Config) when is_list(Config) ->
+ ?line {ok, Pid} =
+ gen_fsm:start_link({local, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line stop_it(Pid),
+
+ ?line {ok, _Pid1} =
+ gen_fsm:start_link({local, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line stop_it(my_fsm),
+
+ ?line {ok, Pid2} =
+ gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
+ ?line stop_it(Pid2),
+ receive after 1 -> true end,
+ ?line Result =
+ gen_fsm:start({global, my_fsm}, gen_fsm_SUITE, [], []),
+ io:format("Result = ~p~n",[Result]),
+ ?line {ok, _Pid3} = Result,
+ ?line stop_it({global, my_fsm}),
+
+ test_server:messages_get(),
+ ok.
+
+abnormal(suite) -> [abnormal1, abnormal2].
+
+%% Check that time outs in calls work
+abnormal1(suite) -> [];
+abnormal1(Config) when is_list(Config) ->
+ ?line {ok, _Pid} =
+ gen_fsm:start({local, my_fsm}, gen_fsm_SUITE, [], []),
+
+ %% timeout call.
+ case os:type() of
+ vxworks ->
+ %% timeout call for VxWorks must be in 16ms increments.
+ ?line delayed = gen_fsm:sync_send_event(my_fsm, {delayed_answer,1}, 17),
+ ?line {'EXIT',{timeout,_}} =
+ (catch gen_fsm:sync_send_event(my_fsm, {delayed_answer,17}, 1));
+ _ ->
+ ?line delayed = gen_fsm:sync_send_event(my_fsm, {delayed_answer,1}, 100),
+ ?line {'EXIT',{timeout,_}} =
+ (catch gen_fsm:sync_send_event(my_fsm, {delayed_answer,10}, 1))
+ end,
+ test_server:messages_get(),
+ ok.
+
+%% Check that bad return values makes the fsm crash. Note that we must
+%% trap exit since we must link to get the real bad_return_ error
+abnormal2(suite) -> [];
+abnormal2(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+ ?line {ok, Pid} =
+ gen_fsm:start_link(gen_fsm_SUITE, [], []),
+
+ %% bad return value in the gen_fsm loop
+ ?line {'EXIT',{{bad_return_value, badreturn},_}} =
+ (catch gen_fsm:sync_send_event(Pid, badreturn)),
+
+ test_server:messages_get(),
+ process_flag(trap_exit, OldFl),
+ ok.
+
+shutdown(Config) when is_list(Config) ->
+ ?line error_logger_forwarder:register(),
+
+ process_flag(trap_exit, true),
+
+ ?line {ok,Pid0} = gen_fsm:start_link(gen_fsm_SUITE, [], []),
+ ?line ok = do_func_test(Pid0),
+ ?line ok = do_sync_func_test(Pid0),
+ ?line {shutdown,reason} =
+ gen_fsm:sync_send_all_state_event(Pid0, stop_shutdown_reason),
+ receive {'EXIT',Pid0,{shutdown,reason}} -> ok end,
+ process_flag(trap_exit, false),
+
+ ?line {'EXIT', {noproc,_}} =
+ (catch gen_fsm:sync_send_event(Pid0, hej)),
+
+ receive
+ Any ->
+ ?line io:format("Unexpected: ~p", [Any]),
+ ?line ?t:fail()
+ after 500 ->
+ ok
+ end,
+
+ ok.
+
+
+sys(suite) -> [sys1].
+
+sys1(Config) when is_list(Config) ->
+ ?line {ok, Pid} =
+ gen_fsm:start(gen_fsm_SUITE, [], []),
+ ?line {status, Pid, {module,gen_fsm}, _} = sys:get_status(Pid),
+ ?line sys:suspend(Pid),
+ ?line {'EXIT', {timeout,_}} =
+ (catch gen_fsm:sync_send_event(Pid, hej)),
+ ?line sys:resume(Pid),
+ ?line stop_it(Pid).
+
+
+%% Hibernation
+hibernate(suite) -> [];
+hibernate(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, Pid0} = gen_fsm:start_link(?MODULE, hiber_now, []),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid0,current_function),
+ ?line stop_it(Pid0),
+ test_server:messages_get(),
+
+
+ ?line {ok, Pid} = gen_fsm:start_link(?MODULE, hiber, []),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line hibernating = gen_fsm:sync_send_event(Pid,hibernate_sync),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line good_morning = gen_fsm:sync_send_event(Pid,wakeup_sync),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line hibernating = gen_fsm:sync_send_event(Pid,hibernate_sync),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line five_more = gen_fsm:sync_send_event(Pid,snooze_sync),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line good_morning = gen_fsm:sync_send_event(Pid,wakeup_sync),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line ok = gen_fsm:send_event(Pid,hibernate_async),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line ok = gen_fsm:send_event(Pid,wakeup_async),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line ok = gen_fsm:send_event(Pid,hibernate_async),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line ok = gen_fsm:send_event(Pid,snooze_async),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line ok = gen_fsm:send_event(Pid,wakeup_async),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line Pid ! hibernate_later,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line receive after 2000 -> ok end,
+ ?line ({current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function)),
+ ?line 'alive!' = gen_fsm:sync_send_event(Pid,'alive?'),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line Pid ! hibernate_now,
+ ?line receive after 1000 -> ok end,
+ ?line ({current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function)),
+ ?line 'alive!' = gen_fsm:sync_send_event(Pid,'alive?'),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+
+
+ ?line hibernating = gen_fsm:sync_send_all_state_event(Pid,hibernate_sync),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line good_morning = gen_fsm:sync_send_all_state_event(Pid,wakeup_sync),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line hibernating = gen_fsm:sync_send_all_state_event(Pid,hibernate_sync),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line five_more = gen_fsm:sync_send_all_state_event(Pid,snooze_sync),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line good_morning = gen_fsm:sync_send_all_state_event(Pid,wakeup_sync),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line ok = gen_fsm:send_all_state_event(Pid,hibernate_async),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line ok = gen_fsm:send_all_state_event(Pid,wakeup_async),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line ok = gen_fsm:send_all_state_event(Pid,hibernate_async),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line ok = gen_fsm:send_all_state_event(Pid,snooze_async),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line ok = gen_fsm:send_all_state_event(Pid,wakeup_async),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+
+ ?line hibernating = gen_fsm:sync_send_all_state_event(Pid,hibernate_sync),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line sys:suspend(Pid),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line sys:resume(Pid),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} =
+ erlang:process_info(Pid,current_function),
+ ?line good_morning = gen_fsm:sync_send_all_state_event(Pid,wakeup_sync),
+ ?line receive after 1000 -> ok end,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line stop_it(Pid),
+ test_server:messages_get(),
+ process_flag(trap_exit, OldFl),
+ ok.
+
+
+
+%%sys1(suite) -> [];
+%%sys1(_) ->
+
+enter_loop(suite) ->
+ [];
+enter_loop(doc) ->
+ ["Test gen_fsm:enter_loop/4,5,6"];
+enter_loop(Config) when is_list(Config) ->
+ OldFlag = process_flag(trap_exit, true),
+
+ %% Locally registered process + {local, Name}
+ ?line {ok, Pid1a} =
+ proc_lib:start_link(?MODULE, enter_loop, [local, local]),
+ ?line yes = gen_fsm:sync_send_event(Pid1a, 'alive?'),
+ ?line stopped = gen_fsm:sync_send_event(Pid1a, stop),
+ receive
+ {'EXIT', Pid1a, normal} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(gen_fsm_did_not_die)
+ end,
+
+ %% Unregistered process + {local, Name}
+ ?line {ok, Pid1b} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon, local]),
+ receive
+ {'EXIT', Pid1b, process_not_registered} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(gen_fsm_did_not_die)
+ end,
+
+ %% Globally registered process + {global, Name}
+ ?line {ok, Pid2a} =
+ proc_lib:start_link(?MODULE, enter_loop, [global, global]),
+ ?line yes = gen_fsm:sync_send_event(Pid2a, 'alive?'),
+ ?line stopped = gen_fsm:sync_send_event(Pid2a, stop),
+ receive
+ {'EXIT', Pid2a, normal} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(gen_fsm_did_not_die)
+ end,
+
+ %% Unregistered process + {global, Name}
+ ?line {ok, Pid2b} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon, global]),
+ receive
+ {'EXIT', Pid2b, process_not_registered_globally} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(gen_fsm_did_not_die)
+ end,
+
+ %% Unregistered process + no name
+ ?line {ok, Pid3} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon, anon]),
+ ?line yes = gen_fsm:sync_send_event(Pid3, 'alive?'),
+ ?line stopped = gen_fsm:sync_send_event(Pid3, stop),
+ receive
+ {'EXIT', Pid3, normal} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(gen_fsm_did_not_die)
+ end,
+
+ %% Process not started using proc_lib
+ ?line Pid4 =
+ spawn_link(gen_fsm, enter_loop, [?MODULE, [], state0, []]),
+ receive
+ {'EXIT', Pid4, process_was_not_started_by_proc_lib} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(gen_fsm_did_not_die)
+ end,
+
+ %% Make sure I am the parent, ie that ordering a shutdown will
+ %% result in the process terminating with Reason==shutdown
+ ?line {ok, Pid5} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon, anon]),
+ ?line yes = gen_fsm:sync_send_event(Pid5, 'alive?'),
+ ?line exit(Pid5, shutdown),
+ receive
+ {'EXIT', Pid5, shutdown} ->
+ ok
+ after 5000 ->
+ ?line test_server:fail(gen_fsm_did_not_die)
+ end,
+
+ %% Make sure gen_fsm:enter_loop does not accept {local,Name}
+ %% when it's another process than the calling one which is
+ %% registered under that name
+ register(armitage, self()),
+ ?line {ok, Pid6a} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon, local]),
+ receive
+ {'EXIT', Pid6a, process_not_registered} ->
+ ok
+ after 1000 ->
+ ?line test_server:fail(gen_fsm_started)
+ end,
+ unregister(armitage),
+
+ %% Make sure gen_fsm:enter_loop does not accept {global,Name}
+ %% when it's another process than the calling one which is
+ %% registered under that name
+ global:register_name(armitage, self()),
+ ?line {ok, Pid6b} =
+ proc_lib:start_link(?MODULE, enter_loop, [anon, global]),
+ receive
+ {'EXIT', Pid6b, process_not_registered_globally} ->
+ ok
+ after 1000 ->
+ ?line test_server:fail(gen_server_started)
+ end,
+ global:unregister_name(armitage),
+
+ process_flag(trap_exit, OldFlag),
+ ok.
+
+enter_loop(Reg1, Reg2) ->
+ process_flag(trap_exit, true),
+ case Reg1 of
+ local -> register(armitage, self());
+ global -> global:register_name(armitage, self());
+ anon -> ignore
+ end,
+ proc_lib:init_ack({ok, self()}),
+ case Reg2 of
+ local ->
+ gen_fsm:enter_loop(?MODULE, [], state0, [], {local,armitage});
+ global ->
+ gen_fsm:enter_loop(?MODULE, [], state0, [], {global,armitage});
+ anon ->
+ gen_fsm:enter_loop(?MODULE, [], state0, [])
+ end.
+
+%%
+%% Functionality check
+%%
+
+wfor(Msg) ->
+ receive
+ Msg -> ok
+ after 5000 ->
+ throw(timeout)
+ end.
+
+
+stop_it(FSM) ->
+ ?line stopped = gen_fsm:sync_send_all_state_event(FSM, stop),
+ ?line {'EXIT',_} = (catch gen_fsm:sync_send_event(FSM, hej)),
+ ok.
+
+
+
+do_func_test(FSM) ->
+ ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
+ wfor(yes),
+ ok = do_connect(FSM),
+ ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
+ wfor(yes),
+ test_server:do_times(3, ?MODULE, do_msg, [FSM]),
+ ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
+ wfor(yes),
+ ok = do_disconnect(FSM),
+ ok = gen_fsm:send_all_state_event(FSM, {'alive?', self()}),
+ wfor(yes),
+ ok.
+
+
+do_connect(FSM) ->
+ check_state(FSM, idle),
+ gen_fsm:send_event(FSM, {connect, self()}),
+ wfor(accept),
+ check_state(FSM, wfor_conf),
+ gen_fsm:send_event(FSM, confirmation),
+ check_state(FSM, connected),
+ ok.
+
+do_msg(FSM) ->
+ check_state(FSM, connected),
+ R = make_ref(),
+ ok = gen_fsm:send_event(FSM, {msg, R, self(), hej_pa_dig_quasimodo}),
+ wfor({ak, R}).
+
+
+do_disconnect(FSM) ->
+ ok = gen_fsm:send_event(FSM, disconnect),
+ check_state(FSM, idle).
+
+check_state(FSM, State) ->
+ case gen_fsm:sync_send_all_state_event(FSM, {get, self()}) of
+ {state, State, _} -> ok
+ end.
+
+do_sync_func_test(FSM) ->
+ yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
+ ok = do_sync_connect(FSM),
+ yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
+ test_server:do_times(3, ?MODULE, do_sync_msg, [FSM]),
+ yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
+ ok = do_sync_disconnect(FSM),
+ yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
+ check_state(FSM, idle),
+ ok = gen_fsm:sync_send_event(FSM, {timeout,200}),
+ yes = gen_fsm:sync_send_all_state_event(FSM, 'alive?'),
+ check_state(FSM, idle),
+ ok.
+
+
+do_sync_connect(FSM) ->
+ check_state(FSM, idle),
+ accept = gen_fsm:sync_send_event(FSM, {connect, self()}),
+ check_state(FSM, wfor_conf),
+ yes = gen_fsm:sync_send_event(FSM, confirmation),
+ check_state(FSM, connected),
+ ok.
+
+do_sync_msg(FSM) ->
+ check_state(FSM, connected),
+ R = make_ref(),
+ Res = gen_fsm:sync_send_event(FSM, {msg, R, self(), hej_pa_dig_quasimodo}),
+ if Res == {ak, R} ->
+ ok
+ end.
+
+do_sync_disconnect(FSM) ->
+ yes = gen_fsm:sync_send_event(FSM, disconnect),
+ check_state(FSM, idle).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% The Finite State Machine
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init(ignore) ->
+ ignore;
+init(stop) ->
+ {stop, stopped};
+init(stop_shutdown) ->
+ {stop, shutdown};
+init(sleep) ->
+ test_server:sleep(1000),
+ {ok, idle, data};
+init({timeout, T}) ->
+ {ok, idle, state, T};
+init(hiber) ->
+ {ok, hiber_idle, []};
+init(hiber_now) ->
+ {ok, hiber_idle, [], hibernate};
+init(_) ->
+ {ok, idle, state_data}.
+
+
+terminate({From, stopped}, State, _Data) ->
+ From ! {self(), {stopped, State}},
+ ok;
+terminate(_Reason, _State, _Data) ->
+ ok.
+
+
+idle({connect, Pid}, Data) ->
+ Pid ! accept,
+ {next_state, wfor_conf, Data};
+idle(badreturn, _Data) ->
+ badreturn;
+idle(_, Data) ->
+ {next_state, idle, Data}.
+
+idle({connect, _Pid}, _From, Data) ->
+ {reply, accept, wfor_conf, Data};
+idle({delayed_answer, T}, _From, Data) ->
+ test_server:sleep(T),
+ {reply, delayed, idle, Data};
+idle(badreturn, _From, _Data) ->
+ badreturn;
+idle({timeout,Time}, From, _Data) ->
+ gen_fsm:send_event_after(Time, {timeout,Time}),
+ {next_state, timeout, From};
+idle(_, _From, Data) ->
+ {reply, 'eh?', idle, Data}.
+
+timeout({timeout,Time}, From) ->
+ Ref = gen_fsm:start_timer(Time, {timeout,Time}),
+ {next_state, timeout, {From,Ref}};
+timeout({timeout,Ref,{timeout,Time}}, {From,Ref}) ->
+ Ref2 = gen_fsm:start_timer(Time, ok),
+ Cref = gen_fsm:start_timer(Time, cancel),
+ Time4 = Time*4,
+ receive after Time4 -> ok end,
+ gen_fsm:cancel_timer(Cref),
+ {next_state, timeout, {From,Ref2}};
+timeout({timeout,Ref2,ok},{From,Ref2}) ->
+ gen_fsm:reply(From, ok),
+ {next_state, idle, state}.
+
+wfor_conf(confirmation, Data) ->
+ {next_state, connected, Data};
+wfor_conf(_, Data) ->
+ {next_state, idle, Data}.
+
+wfor_conf(confirmation, _From, Data) ->
+ {reply, yes, connected, Data};
+wfor_conf(_, _From, Data) ->
+ {reply, 'eh?', idle, Data}.
+
+connected({msg, Ref, From, _Msg}, Data) ->
+ From ! {ak, Ref},
+ {next_state, connected, Data};
+connected(disconnect, Data) ->
+ {next_state, idle, Data};
+connected(_, Data) ->
+ {next_state, connected, Data}.
+
+connected({msg, Ref, _From, _Msg}, _, Data) ->
+ {reply, {ak, Ref}, connected, Data};
+connected(disconnect, _From, Data) ->
+ {reply, yes, idle, Data};
+connected(_, _, Data) ->
+ {reply, 'eh?', connected, Data}.
+
+state0('alive?', _From, Data) ->
+ {reply, yes, state0, Data};
+state0(stop, _From, Data) ->
+ {stop, normal, stopped, Data}.
+
+hiber_idle('alive?', _From, Data) ->
+ {reply, 'alive!', hiber_idle, Data};
+hiber_idle(hibernate_sync, _From, Data) ->
+ {reply, hibernating, hiber_wakeup, Data,hibernate}.
+hiber_idle(timeout, hibernate_me) -> % Arrive here from
+ % handle_info(hibernate_later,...)
+ {next_state, hiber_idle, [], hibernate};
+hiber_idle(hibernate_async, Data) ->
+ {next_state,hiber_wakeup, Data, hibernate}.
+
+hiber_wakeup(wakeup_sync,_From,Data) ->
+ {reply,good_morning,hiber_idle,Data};
+hiber_wakeup(snooze_sync,_From,Data) ->
+ {reply,five_more,hiber_wakeup,Data,hibernate}.
+hiber_wakeup(wakeup_async,Data) ->
+ {next_state,hiber_idle,Data};
+hiber_wakeup(snooze_async,Data) ->
+ {next_state,hiber_wakeup,Data,hibernate}.
+
+
+handle_info(hibernate_now, _SName, _State) -> % Arrive here from by direct ! from testcase
+ {next_state, hiber_idle, [], hibernate};
+handle_info(hibernate_later, _SName, _State) ->
+ {next_state, hiber_idle, hibernate_me, 1000};
+
+handle_info(Info, _State, Data) ->
+ {stop, {unexpected,Info}, Data}.
+
+handle_event(hibernate_async, hiber_idle, Data) ->
+ {next_state,hiber_wakeup, Data, hibernate};
+handle_event(wakeup_async,hiber_wakeup,Data) ->
+ {next_state,hiber_idle,Data};
+handle_event(snooze_async,hiber_wakeup,Data) ->
+ {next_state,hiber_wakeup,Data,hibernate};
+handle_event({get, Pid}, State, Data) ->
+ Pid ! {state, State, Data},
+ {next_state, State, Data};
+handle_event(stop, _State, Data) ->
+ {stop, normal, Data};
+handle_event(stop_shutdown, _State, Data) ->
+ {stop, shutdown, Data};
+handle_event(stop_shutdown_reason, _State, Data) ->
+ {stop, shutdown, Data};
+handle_event({'alive?', Pid}, State, Data) ->
+ Pid ! yes,
+ {next_state, State, Data}.
+
+handle_sync_event(hibernate_sync, _From, hiber_idle, Data) ->
+ {reply, hibernating, hiber_wakeup, Data, hibernate};
+handle_sync_event(wakeup_sync,_From,hiber_wakeup, Data) ->
+ {reply,good_morning,hiber_idle,Data};
+handle_sync_event(snooze_sync,_From,hiber_wakeup,Data) ->
+ {reply,five_more,hiber_wakeup,Data,hibernate};
+handle_sync_event('alive?', _From, State, Data) ->
+ {reply, yes, State, Data};
+handle_sync_event(stop, _From, _State, Data) ->
+ {stop, normal, stopped, Data};
+handle_sync_event(stop_shutdown, _From, _State, Data) ->
+ {stop, shutdown, shutdown_stopped, Data};
+handle_sync_event(stop_shutdown_reason, _From, _State, Data) ->
+ {stop, {shutdown,reason}, {shutdown,reason}, Data};
+handle_sync_event({get, _Pid}, _From, State, Data) ->
+ {reply, {state, State, Data}, State, Data}.
diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl
new file mode 100644
index 0000000000..86a5a65ba3
--- /dev/null
+++ b/lib/stdlib/test/gen_server_SUITE.erl
@@ -0,0 +1,1049 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(gen_server_SUITE).
+
+-include("test_server.hrl").
+-include_lib("kernel/include/inet.hrl").
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-export([all/1]).
+-export([start/1, crash/1, call/1, cast/1, cast_fast/1,
+ info/1, abcast/1, multicall/1, multicall_down/1,
+ call_remote1/1, call_remote2/1, call_remote3/1,
+ call_remote_n1/1, call_remote_n2/1, call_remote_n3/1, spec_init/1,
+ spec_init_local_registered_parent/1,
+ spec_init_global_registered_parent/1,
+ otp_5854/1, hibernate/1, otp_7669/1
+ ]).
+
+% spawn export
+-export([spec_init_local/2, spec_init_global/2,
+ spec_init_default_timeout/2, spec_init_anonymous/1,
+ spec_init_anonymous_default_timeout/1,
+ spec_init_not_proc_lib/1, cast_fast_messup/0]).
+
+
+% The gen_server behaviour
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2]).
+
+all(suite) ->
+ [start, crash, call, cast, cast_fast, info,
+ abcast, multicall, multicall_down, call_remote1,
+ call_remote2, call_remote3, call_remote_n1,
+ call_remote_n2, call_remote_n3, spec_init,
+ spec_init_local_registered_parent,
+ spec_init_global_registered_parent,
+ otp_5854,hibernate,otp_7669].
+
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+
+%% --------------------------------------
+%% Start and stop a gen_server.
+%% --------------------------------------
+
+start(suite) -> [];
+start(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ %% anonymous
+ ?line {ok, Pid0} = gen_server:start(gen_server_SUITE, [], []),
+ ?line ok = gen_server:call(Pid0, started_p),
+ ?line ok = gen_server:call(Pid0, stop),
+ ?line busy_wait_for_process(Pid0,600),
+ ?line {'EXIT', {noproc,_}} = (catch gen_server:call(Pid0, started_p, 1)),
+
+ %% anonymous with timeout
+ ?line {ok, Pid00} = gen_server:start(gen_server_SUITE, [],
+ [{timeout,1000}]),
+ ?line ok = gen_server:call(Pid00, started_p),
+ ?line ok = gen_server:call(Pid00, stop),
+ ?line {error, timeout} = gen_server:start(gen_server_SUITE, sleep,
+ [{timeout,100}]),
+
+ %% anonymous with ignore
+ ?line ignore = gen_server:start(gen_server_SUITE, ignore, []),
+
+ %% anonymous with stop
+ ?line {error, stopped} = gen_server:start(gen_server_SUITE, stop, []),
+
+ %% anonymous linked
+ ?line {ok, Pid1} =
+ gen_server:start_link(gen_server_SUITE, [], []),
+ ?line ok = gen_server:call(Pid1, started_p),
+ ?line ok = gen_server:call(Pid1, stop),
+ ?line receive
+ {'EXIT', Pid1, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(not_stopped)
+ end,
+
+ %% local register
+ ?line {ok, Pid2} =
+ gen_server:start({local, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call(my_test_name, started_p),
+ ?line {error, {already_started, Pid2}} =
+ gen_server:start({local, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call(my_test_name, stop),
+
+ ?line busy_wait_for_process(Pid2,600),
+
+ ?line {'EXIT', {noproc,_}} = (catch gen_server:call(Pid2, started_p, 10)),
+
+ %% local register linked
+ ?line {ok, Pid3} =
+ gen_server:start_link({local, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call(my_test_name, started_p),
+ ?line {error, {already_started, Pid3}} =
+ gen_server:start({local, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call(my_test_name, stop),
+ ?line receive
+ {'EXIT', Pid3, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(not_stopped)
+ end,
+
+ %% global register
+ ?line {ok, Pid4} =
+ gen_server:start({global, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call({global, my_test_name}, started_p),
+ ?line {error, {already_started, Pid4}} =
+ gen_server:start({global, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call({global, my_test_name}, stop),
+ test_server:sleep(1),
+ ?line {'EXIT', {noproc,_}} = (catch gen_server:call(Pid4, started_p, 10)),
+
+ %% global register linked
+ ?line {ok, Pid5} =
+ gen_server:start_link({global, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call({global, my_test_name}, started_p),
+ ?line {error, {already_started, Pid5}} =
+ gen_server:start({global, my_test_name},
+ gen_server_SUITE, [], []),
+ ?line ok = gen_server:call({global, my_test_name}, stop),
+ ?line receive
+ {'EXIT', Pid5, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(not_stopped)
+ end,
+
+ test_server:messages_get(),
+
+ %% Must wait for all error messages before going to next test.
+ %% (otherwise it interferes too much with real time characteristics).
+ case os:type() of
+ vxworks ->
+ receive after 5000 -> ok end;
+ _ ->
+ ok
+ end,
+ process_flag(trap_exit, OldFl),
+ ok.
+
+crash(Config) when is_list(Config) ->
+ ?line error_logger_forwarder:register(),
+
+ process_flag(trap_exit, true),
+
+ %% This crash should not generate a crash report.
+ ?line {ok,Pid0} = gen_server:start_link(?MODULE, [], []),
+ ?line {'EXIT',{{shutdown,reason},_}} =
+ (catch gen_server:call(Pid0, shutdown_reason)),
+ receive {'EXIT',Pid0,{shutdown,reason}} -> ok end,
+
+ %% This crash should not generate a crash report.
+ ?line {ok,Pid1} = gen_server:start_link(?MODULE, {state,state1}, []),
+ ?line {'EXIT',{{shutdown,stop_reason},_}} =
+ (catch gen_server:call(Pid1, stop_shutdown_reason)),
+ receive {'EXIT',Pid1,{shutdown,stop_reason}} -> ok end,
+
+ %% This crash should not generate a crash report.
+ ?line {ok,Pid2} = gen_server:start_link(?MODULE, [], []),
+ ?line {'EXIT',{shutdown,_}} =
+ (catch gen_server:call(Pid2, exit_shutdown)),
+ receive {'EXIT',Pid2,shutdown} -> ok end,
+
+ %% This crash should not generate a crash report.
+ ?line {ok,Pid3} = gen_server:start_link(?MODULE, {state,state3}, []),
+ ?line {'EXIT',{shutdown,_}} =
+ (catch gen_server:call(Pid3, stop_shutdown)),
+ receive {'EXIT',Pid3,shutdown} -> ok end,
+
+ process_flag(trap_exit, false),
+
+ %% This crash should generate a crash report and a report
+ %% from gen_server.
+ ?line {ok,Pid4} = gen_server:start(?MODULE, {state,state4}, []),
+ ?line {'EXIT',{crashed,_}} = (catch gen_server:call(Pid4, crash)),
+ receive
+ {error,_GroupLeader4,{Pid4,
+ "** Generic server"++_,
+ [Pid4,crash,state4,crashed]}} ->
+ ok;
+ Other4a ->
+ ?line io:format("Unexpected: ~p", [Other4a]),
+ ?line ?t:fail()
+ end,
+ receive
+ {error_report,_,{Pid4,crash_report,[List4|_]}} ->
+ {exit,crashed,_} = proplists:get_value(error_info, List4),
+ Pid4 = proplists:get_value(pid, List4);
+ Other4 ->
+ ?line io:format("Unexpected: ~p", [Other4]),
+ ?line ?t:fail()
+ end,
+
+ receive
+ Any ->
+ ?line io:format("Unexpected: ~p", [Any]),
+ ?line ?t:fail()
+ after 500 ->
+ ok
+ end,
+
+ ok.
+
+%% --------------------------------------
+%% Test gen_server:call and handle_call.
+%% Test all different return values from
+%% handle_call.
+%% --------------------------------------
+
+call(suite) -> [];
+call(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, _Pid} =
+ gen_server:start_link({local, my_test_name},
+ gen_server_SUITE, [], []),
+
+ ?line ok = gen_server:call(my_test_name, started_p),
+ ?line delayed = gen_server:call(my_test_name, {delayed_answer,1}),
+
+ %% two requests within a specified time.
+ ?line ok = gen_server:call(my_test_name, {call_within, 1000}),
+ test_server:sleep(500),
+ ?line ok = gen_server:call(my_test_name, next_call),
+ ?line ok = gen_server:call(my_test_name, {call_within, 1000}),
+ test_server:sleep(1500),
+ ?line false = gen_server:call(my_test_name, next_call),
+
+ %% timeout call.
+ ?line delayed = gen_server:call(my_test_name, {delayed_answer,1}, 30),
+ ?line {'EXIT',{timeout,_}} =
+ (catch gen_server:call(my_test_name, {delayed_answer,30}, 1)),
+
+ %% bad return value in the gen_server loop from handle_call.
+ ?line {'EXIT',{{bad_return_value, badreturn},_}} =
+ (catch gen_server:call(my_test_name, badreturn)),
+
+ process_flag(trap_exit, OldFl),
+ ok.
+
+%% --------------------------------------
+%% Test call to nonexisting processes on remote nodes
+%% --------------------------------------
+
+start_node(Name) ->
+ ?line Pa = filename:dirname(code:which(?MODULE)),
+ ?line N = test_server:start_node(Name, slave, [{args, " -pa " ++ Pa}]),
+ %% After starting a slave, it takes a little while until global knows
+ %% about it, even if nodes() includes it, so we make sure that global
+ %% knows about it before registering something on all nodes.
+ global:sync(),
+ N.
+
+call_remote1(suite) -> [];
+call_remote1(Config) when is_list(Config) ->
+ ?line N = hubba,
+ ?line {ok, Node} = start_node(N),
+ ?line {ok, Pid} = rpc:call(Node, gen_server, start,
+ [{global, N}, ?MODULE, [], []]),
+ ?line ok = (catch gen_server:call({global, N}, started_p, infinity)),
+ ?line exit(Pid, boom),
+ ?line {'EXIT', {Reason, _}} = (catch gen_server:call({global, N},
+ started_p, infinity)),
+ ?line true = (Reason == noproc) orelse (Reason == boom),
+ ok.
+
+call_remote2(suite) -> [];
+call_remote2(Config) when is_list(Config) ->
+ ?line N = hubba,
+ ?line {ok, Node} = start_node(N),
+
+ ?line {ok, Pid} = rpc:call(Node, gen_server, start,
+ [{global, N}, ?MODULE, [], []]),
+ ?line ok = (catch gen_server:call(Pid, started_p, infinity)),
+ ?line exit(Pid, boom),
+ ?line {'EXIT', {Reason, _}} = (catch gen_server:call(Pid,
+ started_p, infinity)),
+ ?line true = (Reason == noproc) orelse (Reason == boom),
+ ok.
+
+call_remote3(suite) -> [];
+call_remote3(Config) when is_list(Config) ->
+ ?line N = hubba,
+ ?line {ok, Node} = start_node(N),
+
+ ?line {ok, Pid} = rpc:call(Node, gen_server, start,
+ [{local, piller}, ?MODULE, [], []]),
+ ?line ok = (catch gen_server:call({piller, Node}, started_p, infinity)),
+ ?line exit(Pid, boom),
+ ?line {'EXIT', {Reason, _}} = (catch gen_server:call({piller, Node},
+ started_p, infinity)),
+ ?line true = (Reason == noproc) orelse (Reason == boom),
+ ok.
+
+%% --------------------------------------
+%% Test call to nonexisting node
+%% --------------------------------------
+
+call_remote_n1(suite) -> [];
+call_remote_n1(Config) when is_list(Config) ->
+ ?line N = hubba,
+ ?line {ok, Node} = start_node(N),
+ ?line {ok, _Pid} = rpc:call(Node, gen_server, start,
+ [{global, N}, ?MODULE, [], []]),
+ ?line _ = test_server:stop_node(Node),
+ ?line {'EXIT', {noproc, _}} =
+ (catch gen_server:call({global, N}, started_p, infinity)),
+
+ ok.
+
+call_remote_n2(suite) -> [];
+call_remote_n2(Config) when is_list(Config) ->
+ ?line N = hubba,
+ ?line {ok, Node} = start_node(N),
+
+ ?line {ok, Pid} = rpc:call(Node, gen_server, start,
+ [{global, N}, ?MODULE, [], []]),
+ ?line _ = test_server:stop_node(Node),
+ ?line {'EXIT', {{nodedown, Node}, _}} = (catch gen_server:call(Pid,
+ started_p, infinity)),
+
+ ok.
+
+call_remote_n3(suite) -> [];
+call_remote_n3(Config) when is_list(Config) ->
+ ?line N = hubba,
+ ?line {ok, Node} = start_node(N),
+
+ ?line {ok, _Pid} = rpc:call(Node, gen_server, start,
+ [{local, piller}, ?MODULE, [], []]),
+ ?line _ = test_server:stop_node(Node),
+ ?line {'EXIT', {{nodedown, Node}, _}} = (catch gen_server:call({piller, Node},
+ started_p, infinity)),
+
+ ok.
+
+%% --------------------------------------
+%% Test gen_server:cast and handle_cast.
+%% Test all different return values from
+%% handle_cast.
+%% --------------------------------------
+
+cast(suite) -> [];
+cast(Config) when is_list(Config) ->
+ ?line {ok, Pid} =
+ gen_server:start({local, my_test_name},
+ gen_server_SUITE, [], []),
+
+ ?line ok = gen_server:call(my_test_name, started_p),
+
+ ?line ok = gen_server:cast(my_test_name, {self(),handle_cast}),
+ ?line receive
+ {Pid, handled_cast} ->
+ ok
+ after 1000 ->
+ test_server:fail(handle_cast)
+ end,
+
+ ?line ok = gen_server:cast(my_test_name, {self(),delayed_cast,1}),
+ ?line receive
+ {Pid, delayed} ->
+ ok
+ after 1000 ->
+ test_server:fail(delayed_cast)
+ end,
+
+ ?line ok = gen_server:cast(my_test_name, {self(),stop}),
+ ?line receive
+ {Pid, stopped} ->
+ ok
+ after 1000 ->
+ test_server:fail(stop)
+ end,
+ ok.
+
+cast_fast(suite) -> [];
+cast_fast(doc) -> ["Test that cast really return immediately"];
+cast_fast(Config) when is_list(Config) ->
+ ?line {ok,Node} = start_node(hubba),
+ ?line {_,"@"++Host} = lists:splitwith(fun ($@) -> false; (_) -> true end,
+ atom_to_list(Node)),
+ ?line FalseNode = list_to_atom("hopp@"++Host),
+ ?line true = rpc:cast(Node, ?MODULE, cast_fast_messup, []),
+% ?line io:format("Nodes ~p~n", [rpc:call(N, ?MODULE, cast_fast_messup, [])]),
+ ?line test_server:sleep(1000),
+ ?line [Node] = nodes(),
+ ?line {Time,ok} = test_server:timecall(gen_server, cast,
+ [{hopp,FalseNode},hopp]),
+ ?line true = test_server:stop_node(Node),
+ ?line if Time > 1.0 -> % Default listen timeout is about 7.0 s
+ test_server:fail(hanging_cast);
+ true ->
+ ok
+ end.
+
+cast_fast_messup() ->
+ %% Register a false node: hopp@hostname
+ unregister(erl_epmd),
+ erl_epmd:start_link(),
+ {ok,S} = gen_tcp:listen(0, []),
+ {ok,P} = inet:port(S),
+ {ok,_Creation} = erl_epmd:register_node(hopp, P),
+ receive after infinity -> ok end.
+
+%% --------------------------------------
+%% Test handle_info.
+%% --------------------------------------
+
+info(suite) -> [];
+info(Config) when is_list(Config) ->
+ ?line {ok, Pid} =
+ gen_server:start({local, my_test_name},
+ gen_server_SUITE, [], []),
+
+ ?line ok = gen_server:call(my_test_name, started_p),
+
+ ?line Pid ! {self(),handle_info},
+ ?line receive
+ {Pid, handled_info} ->
+ ok
+ after 1000 ->
+ test_server:fail(handle_info)
+ end,
+
+ ?line Pid ! {self(),delayed_info,1},
+ ?line receive
+ {Pid, delayed_info} ->
+ ok
+ after 1000 ->
+ test_server:fail(delayed_info)
+ end,
+
+ ?line Pid ! {self(),stop},
+ ?line receive
+ {Pid, stopped_info} ->
+ ok
+ after 1000 ->
+ test_server:fail(stop_info)
+ end,
+ ok.
+
+hibernate(suite) -> [];
+hibernate(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+ ?line {ok, Pid0} =
+ gen_server:start_link({local, my_test_name_hibernate0},
+ gen_server_SUITE, hibernate, []),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid0,current_function),
+ ?line ok = gen_server:call(my_test_name_hibernate0, stop),
+ receive
+ {'EXIT', Pid0, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line {ok, Pid} =
+ gen_server:start_link({local, my_test_name_hibernate},
+ gen_server_SUITE, [], []),
+
+ ?line ok = gen_server:call(my_test_name_hibernate, started_p),
+ ?line true = gen_server:call(my_test_name_hibernate, hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line Parent = self(),
+ Fun = fun() ->
+ receive
+ go ->
+ ok
+ end,
+ receive
+ after 1000 ->
+ ok
+ end,
+ X = erlang:process_info(Pid,current_function),
+ Pid ! continue,
+ Parent ! {result,X}
+ end,
+ ?line Pid2 = spawn_link(Fun),
+ ?line true = gen_server:call(my_test_name_hibernate, {hibernate_noreply,Pid2}),
+
+ ?line gen_server:cast(my_test_name_hibernate, hibernate_later),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line receive after 2000 -> ok end,
+ ?line ({current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function)),
+ ?line ok = gen_server:call(my_test_name_hibernate, started_p),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line gen_server:cast(my_test_name_hibernate, hibernate_now),
+ ?line receive after 1000 -> ok end,
+ ?line ({current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function)),
+ ?line ok = gen_server:call(my_test_name_hibernate, started_p),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line Pid ! hibernate_later,
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line receive after 2000 -> ok end,
+ ?line ({current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function)),
+ ?line ok = gen_server:call(my_test_name_hibernate, started_p),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line Pid ! hibernate_now,
+ ?line receive after 1000 -> ok end,
+ ?line ({current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function)),
+ ?line ok = gen_server:call(my_test_name_hibernate, started_p),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+ ?line receive
+ {result,R} ->
+ ?line {current_function,{erlang,hibernate,3}} = R
+ end,
+ ?line true = gen_server:call(my_test_name_hibernate, hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line sys:suspend(my_test_name_hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line sys:resume(my_test_name_hibernate),
+ ?line receive after 1000 -> ok end,
+ ?line {current_function,{erlang,hibernate,3}} = erlang:process_info(Pid,current_function),
+ ?line ok = gen_server:call(my_test_name_hibernate, started_p),
+ ?line true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)),
+
+ ?line ok = gen_server:call(my_test_name_hibernate, stop),
+ receive
+ {'EXIT', Pid, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+ process_flag(trap_exit, OldFl),
+ ok.
+
+%% --------------------------------------
+%% Test gen_server:abcast and handle_cast.
+%% Test all different return values from
+%% handle_cast.
+%% --------------------------------------
+
+abcast(suite) -> [];
+abcast(Config) when is_list(Config) ->
+ ?line {ok, Pid} =
+ gen_server:start({local, my_test_name},
+ gen_server_SUITE, [], []),
+
+ ?line ok = gen_server:call(my_test_name, started_p),
+
+ ?line abcast = gen_server:abcast(my_test_name, {self(),handle_cast}),
+ ?line receive
+ {Pid, handled_cast} ->
+ ok
+ after 1000 ->
+ test_server:fail(abcast)
+ end,
+
+ ?line abcast = gen_server:abcast([node()], my_test_name,
+ {self(),delayed_cast,1}),
+ ?line receive
+ {Pid, delayed} ->
+ ok
+ after 1000 ->
+ test_server:fail(delayed_abcast)
+ end,
+
+ ?line abcast = gen_server:abcast(my_test_name, {self(),stop}),
+ ?line receive
+ {Pid, stopped} ->
+ ok
+ after 1000 ->
+ test_server:fail(abcast_stop)
+ end,
+ ok.
+
+%% --------------------------------------
+%% Test gen_server:multicall and handle_call.
+%% Test all different return values from
+%% handle_call.
+%% --------------------------------------
+
+multicall(suite) -> [];
+multicall(Config) when is_list(Config) ->
+ OldFl = process_flag(trap_exit, true),
+
+ ?line {ok, Pid} =
+ gen_server:start_link({local, my_test_name},
+ gen_server_SUITE, [], []),
+
+ ?line ok = gen_server:call(my_test_name, started_p),
+ Nodes = nodes(),
+ Node = node(),
+ ?line {[{Node,delayed}],Nodes} =
+ gen_server:multi_call(my_test_name, {delayed_answer,1}),
+
+ %% two requests within a specified time.
+ ?line {[{Node,ok}],[]} =
+ gen_server:multi_call([Node], my_test_name, {call_within, 1000}),
+ test_server:sleep(500),
+ ?line {[{Node,ok}],[]} =
+ gen_server:multi_call([Node], my_test_name, next_call),
+ ?line {[{Node,ok}],[]} =
+ gen_server:multi_call([Node], my_test_name, {call_within, 1000}),
+ test_server:sleep(1500),
+ ?line {[{Node,false}],[]} =
+ gen_server:multi_call([Node],my_test_name, next_call),
+
+ %% Stop the server.
+ ?line {[{Node,ok}],[]} =
+ gen_server:multi_call([Node],my_test_name, stop),
+ receive
+ {'EXIT', Pid, stopped} -> ok
+ after 1000 ->
+ test_server:fail(multicall_stop)
+ end,
+
+ process_flag(trap_exit, OldFl),
+
+ ok.
+
+%% OTP-3587
+multicall_down(suite) -> [];
+multicall_down(Config) when is_list(Config) ->
+ %% We need a named host which is inaccessible.
+ ?line Name = node@test01,
+
+ %% We use 'global' as a gen_server to call.
+ ?line {Good, Bad} = gen_server:multi_call([Name, node()],
+ global_name_server,
+ {whereis, gurkburk},
+ 3000),
+ io:format("good = ~p, bad = ~p~n", [Good, Bad]),
+ ?line [Name] = Bad,
+ ok.
+
+busy_wait_for_process(Pid,N) ->
+ case erlang:is_process_alive(Pid) of
+ true ->
+ receive
+ after 100 ->
+ ok
+ end,
+ busy_wait_for_process(Pid,N-1);
+ _ ->
+ ok
+ end.
+%%--------------------------------------------------------------
+spec_init(doc) ->
+ ["Test gen_server:enter_loop/[3,4,5]. Used when you want to write "
+ "your own special init-phase."];
+spec_init(suite) ->
+ [];
+spec_init(Config) when is_list(Config) ->
+
+ OldFlag = process_flag(trap_exit, true),
+
+ ?line {ok, Pid0} = start_link(spec_init_local, [{ok, my_server}, []]),
+ ?line ok = gen_server:call(Pid0, started_p),
+ ?line ok = gen_server:call(Pid0, stop),
+ receive
+ {'EXIT', Pid0, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line {ok, Pid01} = start_link(spec_init_local, [{not_ok, my_server}, []]),
+ receive
+ {'EXIT', Pid01, process_not_registered} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line {ok, Pid1} = start_link(spec_init_global, [{ok, my_server}, []]),
+ ?line ok = gen_server:call(Pid1, started_p),
+ ?line ok = gen_server:call(Pid1, stop),
+ receive
+ {'EXIT', Pid1, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line {ok, Pid11} =
+ start_link(spec_init_global, [{not_ok, my_server}, []]),
+
+ receive
+ {'EXIT', Pid11, process_not_registered_globally} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line {ok, Pid2} = start_link(spec_init_anonymous, [[]]),
+ ?line ok = gen_server:call(Pid2, started_p),
+ ?line ok = gen_server:call(Pid2, stop),
+ receive
+ {'EXIT', Pid2, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line {ok, Pid3} = start_link(spec_init_anonymous_default_timeout, [[]]),
+ ?line ok = gen_server:call(Pid3, started_p),
+ ?line ok = gen_server:call(Pid3, stop),
+ receive
+ {'EXIT', Pid3, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line {ok, Pid4} =
+ start_link(spec_init_default_timeout, [{ok, my_server}, []]),
+ ?line ok = gen_server:call(Pid4, started_p),
+ ?line ok = gen_server:call(Pid4, stop),
+ receive
+ {'EXIT', Pid4, stopped} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+
+ ?line Pid5 =
+ erlang:spawn_link(?MODULE, spec_init_not_proc_lib, [[]]),
+ receive
+ {'EXIT', Pid5, process_was_not_started_by_proc_lib} ->
+ ok
+ after 5000 ->
+ test_server:fail(gen_server_did_not_die)
+ end,
+ process_flag(trap_exit, OldFlag),
+ ok.
+
+%%--------------------------------------------------------------
+spec_init_local_registered_parent(doc) ->
+ ["Test that terminate is run when the parent is a locally registered "
+ "process OTP-4820"];
+spec_init_local_registered_parent(suite) -> [];
+spec_init_local_registered_parent(Config) when is_list(Config) ->
+
+ register(foobar, self()),
+ process_flag(trap_exit, true),
+
+ ?line {ok, Pid} = start_link(spec_init_local, [{ok, my_server}, []]),
+
+ ?line ok = gen_server:cast(my_server, {self(),stop}),
+ ?line receive
+ {Pid, stopped} ->
+ ok
+ after 1000 ->
+ test_server:fail(stop)
+ end,
+ unregister(foobar),
+ ok.
+%%--------------------------------------------------------------
+spec_init_global_registered_parent(doc) ->
+ ["Test that terminate is run when the parent is a global registered "
+ "process OTP-4820"];
+spec_init_global_registered_parent(suite) -> [];
+spec_init_global_registered_parent(Config) when is_list(Config) ->
+
+ global:register_name(foobar, self()),
+ process_flag(trap_exit, true),
+
+ ?line {ok, Pid} = start_link(spec_init_global, [{ok, my_server}, []]),
+
+ ?line ok = gen_server:call(Pid, started_p),
+ ?line ok = gen_server:cast(Pid, {self(),stop}),
+
+ ?line receive
+ {Pid, stopped} ->
+ ok
+ after 1000 ->
+ test_server:fail(stop)
+ end,
+ global:unregister_name(foobar),
+ ok.
+%%--------------------------------------------------------------
+otp_5854(suite) ->
+ [];
+otp_5854(doc) ->
+ ["Test check for registered name in enter_loop/3,4,5"];
+otp_5854(Config) when is_list(Config) ->
+ OldFlag = process_flag(trap_exit, true),
+
+ %% Make sure gen_server:enter_loop does not accept {local,Name}
+ %% when it's another process than the calling one which is
+ %% registered under that name
+ register(armitage, self()),
+ ?line {ok, Pid1} =
+ start_link(spec_init_local, [{not_ok, armitage}, []]),
+ receive
+ {'EXIT', Pid1, process_not_registered} ->
+ ok
+ after 1000 ->
+ ?line test_server:fail(gen_server_started)
+ end,
+ unregister(armitage),
+
+ %% Make sure gen_server:enter_loop does not accept {global,Name}
+ %% when it's another process than the calling one which is
+ %% registered under that name
+ global:register_name(armitage, self()),
+ ?line {ok, Pid2} =
+ start_link(spec_init_global, [{not_ok, armitage}, []]),
+ receive
+ {'EXIT', Pid2, process_not_registered_globally} ->
+ ok
+ after 1000 ->
+ ?line test_server:fail(gen_server_started)
+ end,
+ global:unregister_name(armitage),
+
+ process_flag(trap_exit, OldFlag),
+ ok.
+
+%% If initialization fails (with ignore or {stop,Reason}),
+%% make sure that the process is not registered when gen_sever:start()
+%% returns.
+
+otp_7669(Config) when is_list(Config) ->
+ ?line ?t:do_times(100, fun do_otp_7669_local_ignore/0),
+ ?line ?t:do_times(100, fun do_otp_7669_global_ignore/0),
+ ?line ?t:do_times(10, fun do_otp_7669_stop/0),
+ ok.
+
+do_otp_7669_local_ignore() ->
+ %% The name should never be registered after the return
+ %% from gen_server:start/3.
+ ?line ignore = gen_server:start({local,?MODULE}, ?MODULE, ignore, []),
+ ?line undefined = whereis(?MODULE),
+ ?line ignore = gen_server:start({local,?MODULE}, ?MODULE, ignore, []),
+ ?line undefined = whereis(?MODULE),
+ ?line ignore = gen_server:start_link({local,?MODULE}, ?MODULE, ignore, []),
+ ?line undefined = whereis(?MODULE).
+
+do_otp_7669_global_ignore() ->
+ ?line ignore = gen_server:start({global,?MODULE}, ?MODULE, ignore, []),
+ ?line undefined = global:whereis_name(?MODULE),
+ ?line ignore = gen_server:start_link({global,?MODULE}, ?MODULE, ignore, []),
+ ?line undefined = global:whereis_name(?MODULE).
+
+do_otp_7669_stop() ->
+ %% The name should never be registered after the return
+ %% from gen_server:start/3.
+ ?line {error,stopped} = gen_server:start({local,?MODULE},
+ ?MODULE, stop, []),
+ ?line undefined = whereis(?MODULE),
+
+ ?line {error,stopped} = gen_server:start({global,?MODULE},
+ ?MODULE, stop, []),
+ ?line undefined = global:whereis_name(?MODULE).
+
+%%--------------------------------------------------------------
+%% Help functions to spec_init_*
+start_link(Init, Options) ->
+ proc_lib:start_link(?MODULE, Init, Options).
+
+spec_init_local({ok, Name}, Options) ->
+ process_flag(trap_exit, true),
+ register(Name, self()),
+ proc_lib:init_ack({ok, self()}),
+ %% Supervised init can occur here ...
+ gen_server:enter_loop(?MODULE, Options, {}, {local, Name}, infinity);
+
+spec_init_local({not_ok, Name}, Options) ->
+ process_flag(trap_exit, true),
+ proc_lib:init_ack({ok, self()}),
+ %% Supervised init can occur here ...
+ gen_server:enter_loop(?MODULE, Options, {}, {local, Name}, infinity).
+
+spec_init_global({ok, Name}, Options) ->
+ process_flag(trap_exit, true),
+ global:register_name(Name, self()),
+ proc_lib:init_ack({ok, self()}),
+ %% Supervised init can occur here ...
+ gen_server:enter_loop(?MODULE, Options, {}, {global, Name}, infinity);
+
+spec_init_global({not_ok, Name}, Options) ->
+ process_flag(trap_exit, true),
+ proc_lib:init_ack({ok, self()}),
+ %% Supervised init can occur here ...
+ gen_server:enter_loop(?MODULE, Options, {}, {global, Name}, infinity).
+
+spec_init_default_timeout({ok, Name}, Options) ->
+ process_flag(trap_exit, true),
+ register(Name, self()),
+ proc_lib:init_ack({ok, self()}),
+ %% Supervised init can occur here ...
+ gen_server:enter_loop(?MODULE, Options, {}, {local, Name}).
+
+spec_init_anonymous(Options) ->
+ process_flag(trap_exit, true),
+ proc_lib:init_ack({ok, self()}),
+ %% Supervised init can occur here ...
+ gen_server:enter_loop(?MODULE, Options, {}, infinity).
+
+spec_init_anonymous_default_timeout(Options) ->
+ process_flag(trap_exit, true),
+ proc_lib:init_ack({ok, self()}),
+ %% Supervised init can occur here ...
+ gen_server:enter_loop(?MODULE, Options, {}).
+
+spec_init_not_proc_lib(Options) ->
+ gen_server:enter_loop(?MODULE, Options, {}, infinity).
+
+%%% --------------------------------------------------------
+%%% Here is the tested gen_server behaviour.
+%%% --------------------------------------------------------
+
+init([]) ->
+ {ok, []};
+init(ignore) ->
+ ignore;
+init(stop) ->
+ {stop, stopped};
+init(hibernate) ->
+ {ok,[],hibernate};
+init(sleep) ->
+ test_server:sleep(1000),
+ {ok, []};
+init({state,State}) ->
+ {ok, State}.
+
+handle_call(started_p, _From, State) ->
+ io:format("FROZ"),
+ {reply,ok,State};
+handle_call({delayed_answer, T}, From, _State) ->
+ {noreply,{reply_to,From},T};
+handle_call({call_within, T}, _From, _) ->
+ {reply,ok,call_within,T};
+handle_call(next_call, _From, call_within) ->
+ {reply,ok,[]};
+handle_call(next_call, _From, State) ->
+ {reply,false,State};
+handle_call(badreturn, _From, _State) ->
+ badreturn;
+handle_call(hibernate, _From, _State) ->
+ {reply,true,[],hibernate};
+handle_call({hibernate_noreply,Pid}, From, _State) ->
+ Pid ! go,
+ {noreply,From,hibernate};
+handle_call(stop, _From, State) ->
+ {stop,stopped,ok,State};
+handle_call(crash, _From, _State) ->
+ exit(crashed);
+handle_call(exit_shutdown, _From, _State) ->
+ exit(shutdown);
+handle_call(stop_shutdown, _From, State) ->
+ {stop,shutdown,State};
+handle_call(shutdown_reason, _From, _State) ->
+ exit({shutdown,reason});
+handle_call(stop_shutdown_reason, _From, State) ->
+ {stop,{shutdown,stop_reason},State}.
+
+handle_cast({From,handle_cast}, State) ->
+ From ! {self(), handled_cast},
+ {noreply, State};
+handle_cast({From,delayed_cast,T}, _State) ->
+ {noreply, {delayed_cast,From}, T};
+handle_cast(hibernate_now, _State) ->
+ {noreply, [], hibernate};
+handle_cast(hibernate_later, _State) ->
+ timer:send_after(1000,self(),hibernate_now),
+ {noreply, []};
+handle_cast({From, stop}, State) ->
+ io:format("BAZ"),
+ {stop, {From,stopped}, State}.
+
+handle_info(timeout, {reply_to, From}) ->
+ gen_server:reply(From, delayed),
+ {noreply, []};
+handle_info(timeout, hibernate_me) -> % Arrive here from
+ % handle_info(hibernate_later,...)
+ {noreply, [], hibernate};
+handle_info(hibernate_now, _State) -> % Arrive here from
+ % handle_cast({_,hibernate_later},...)
+ % and by direct ! from testcase
+ {noreply, [], hibernate};
+handle_info(hibernate_later, _State) ->
+ {noreply, hibernate_me, 1000};
+handle_info(timeout, call_within) ->
+ {noreply, []};
+handle_info(timeout, {delayed_cast, From}) ->
+ From ! {self(), delayed},
+ {noreply, []};
+handle_info(timeout, {delayed_info, From}) ->
+ From ! {self(), delayed_info},
+ {noreply, []};
+handle_info({From, handle_info}, _State) ->
+ From ! {self(), handled_info},
+ {noreply, []};
+handle_info({From, delayed_info, T}, _State) ->
+ {noreply, {delayed_info, From}, T};
+handle_info(continue, From) ->
+ gen_server:reply(From,true),
+ {noreply, []};
+handle_info({From, stop}, State) ->
+ {stop, {From,stopped_info}, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate({From, stopped}, _State) ->
+ io:format("FOOBAR"),
+ From ! {self(), stopped},
+ ok;
+terminate({From, stopped_info}, _State) ->
+ From ! {self(), stopped_info},
+ ok;
+terminate(_Reason, _State) ->
+ ok.
+
+
diff --git a/lib/stdlib/test/id_transform_SUITE.erl b/lib/stdlib/test/id_transform_SUITE.erl
new file mode 100644
index 0000000000..95ee509833
--- /dev/null
+++ b/lib/stdlib/test/id_transform_SUITE.erl
@@ -0,0 +1,398 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(id_transform_SUITE).
+-author('[email protected]').
+
+-include_lib("kernel/include/file.hrl").
+
+-export([all/1,
+ id_transform/1]).
+
+-export([check/2,check2/1,g/0,f/1,t/1,t1/1,t2/1,t3/1,t4/1,
+ t5/1,t6/1,apa/1,new_fun/0]).
+
+% Serves as test...
+-hej(hopp).
+-include("test_server.hrl").
+
+all(suite) -> [id_transform].
+
+id_transform(doc) -> "Test erl_id_trans.";
+id_transform(Config) when is_list(Config) ->
+ ?line File=filename:join([code:lib_dir(stdlib),"examples",
+ "erl_id_trans.erl"]),
+ ?line {ok,erl_id_trans,Bin}=compile:file(File,[binary]),
+ ?line {module,erl_id_trans}=code:load_binary(erl_id_trans,File,Bin),
+ ?line case test_server:purify_is_running() of
+ false ->
+ Dog = ?t:timetrap(?t:hours(1)),
+ ?line Res = run_in_test_suite(),
+ ?t:timetrap_cancel(Dog),
+ Res;
+ true ->
+ {skip,"Purify (too slow)"}
+ end.
+
+run_in_test_suite() ->
+ LibDir = code:lib_dir(),
+ SuperDir = filename:dirname(filename:dirname(code:which(?MODULE))),
+ TestDirs = filelib:wildcard(filename:join([SuperDir,"*_test"])),
+ {All,Res} = case LibDir of
+ "/clearcase/otp/erts/lib" ->
+ %% Only test_suites 'cause clearcase is too slow...
+ {false,run_list(TestDirs)};
+ _ ->
+ {true,run_codepath_and(TestDirs)}
+ end,
+ Comment0 = case All of
+ true -> [];
+ false -> "Only testsuite directories traversed"
+ end,
+ case Res of
+ {error,Reason} when Comment0 =/= [] ->
+ {failed,Comment0++"; "++Reason};
+ {error,Reason} ->
+ {failed,Reason};
+ ok ->
+ {comment,Comment0}
+ end.
+
+run_codepath_and(DirList) ->
+ AbsDirs = [filename:absname(X) || X <- code:get_path()],
+ run_list(ordsets:from_list([X || X <- AbsDirs] ++ DirList)).
+
+run_list(PathL) ->
+ io:format("Where to search for beam files:\n~p\n", [PathL]),
+ io:format("Searching for beam files ...~n",[]),
+ Beams = collect_beams(PathL, []),
+ io:format("~p beam files\n", [length(Beams)]),
+ io:format("Applying erl_id_trans to found beam files...~n",[]),
+ Res = [do_trans(X) || X <- Beams],
+ io:format("...done~n",[]),
+ Successes = [X || {ok,X} <- Res],
+ SevereFailures = [{F,E} || {failed,{F,{transform,E}}} <- Res],
+ BeamLib = [{F,E} || {failed,{F,{beam_lib,E}}} <- Res],
+ io:format("~p files processed", [length(Res)]),
+ io:format("~p files successfully transformed", [length(Successes)]),
+ case length(SevereFailures) of
+ 0 -> ok;
+ SevLen ->
+ io:format("\n~p severe failures:\n~p",
+ [SevLen,SevereFailures])
+ end,
+ case BeamLib of
+ [] -> ok;
+ _ -> io:format("\nbeam_lib failures:\n~p", [BeamLib])
+ end,
+ case length(SevereFailures) of
+ 0 -> ok;
+ Len -> {error,integer_to_list(Len)++" failures"}
+ end.
+
+
+collect_beams([P0|Ps], Acc) ->
+ Wc = filename:join(filename:absname(P0), "*.beam"),
+ collect_beams(Ps, filelib:wildcard(Wc)++Acc);
+collect_beams([], Acc) -> Acc.
+
+do_trans(Beam) ->
+ case beam_lib:chunks(Beam, [abstract_code]) of
+ {ok,{_Mod,[{abstract_code,no_abstract_code}]}} ->
+ {failed,{Beam,{beam_lib,no_debug_info}}};
+ {ok,{_Mod,[{abstract_code,{raw_abstract_v1,Abst}}]}} ->
+ do_trans_1(Beam, Abst);
+ {ok,{_Mod,[{abstract_code,{Tag,_}}]}} ->
+ {failed,{Beam,{beam_lib,{wrong_type_of_debug_info,Tag}}}};
+ {ok,{_Mod,[{abstract_code,_}]}} ->
+ {failed,{Beam,{beam_lib,unknown_type_of_debug_info}}};
+ {error,beam_lib,{missing_chunk,_,_}} ->
+ {failed,{Beam,{beam_lib,no_debug_info}}};
+ Error ->
+ {failed,{Beam,{beam_lib,Error}}}
+ end.
+
+do_trans_1(File, Tree0) ->
+ case catch erl_id_trans:parse_transform(Tree0, []) of
+ Tree0 when is_list(Tree0) ->
+ {ok,File};
+ Tree when is_list(Tree) ->
+ {failed,{File,{transform,output_not_same_as_input}}};
+ {'EXIT', Reason} ->
+ {failed,{File,{transform,{crash,Reason}}}};
+ Else ->
+ {failed,{File,{transform,{unknown,Else}}}}
+ end.
+
+% From here on there's only fake code to serve as test cases
+% for the id_transform.
+% They need to be exported.
+
+check(X,_Y) when X ->
+ true;
+check(A,_) when atom(A) ->
+ atom;
+check(A,_) when erlang:is_list(A) ->
+ list;
+check(A,B) when erlang:'+'(A,B) ->
+ atom;
+check(_,_) ->
+ false.
+
+check2(A) ->
+ case A of
+ "hej" ++ "hopp" ->
+ a;
+ [$l,$e,$k] ++ "hopp" ->
+ a;
+ [1] ++ [2] ->
+ b
+ end.
+
+-record(x,{x,y}).
+-record(y,{x=1,y=0}).
+
+g() ->
+ #y.y.
+
+f(#y.y) ->
+ vansinne;
+
+f(X) when X =:= #y.y ->
+ {#y.y,_Y} = {X,hej};
+f(#x{_='_'}) ->
+ hopp;
+f(#x{x=true,y=true}) ->
+ babba;
+f(A) when A == #x{x=true,y=true} ->
+ true;
+f(A) when A#x.x == 4 ->
+ #x{x = 1, _ = 2};
+f(X) ->
+ if X#x.y ->
+ ok;
+ element(3,X) ->
+ banan;
+ true ->
+ nok
+ end.
+
+% Stolen from erl_lint_SUITE.erl
+-record(apa, {}).
+
+t(A) when atom(A) ->
+ atom;
+t(A) when binary(A) ->
+ binary;
+t(A) when float(A) ->
+ float;
+t(A) when function(A) ->
+ function;
+t(A) when integer(A) ->
+ integer;
+t(A) when is_atom(A) ->
+ is_atom;
+t(A) when is_binary(A) ->
+ is_binary;
+t(A) when is_float(A) ->
+ is_float;
+t(A) when is_function(A) ->
+ is_function;
+t(A) when is_integer(A) ->
+ is_integer;
+t(A) when is_list(A) ->
+ is_list;
+t(A) when is_number(A) ->
+ is_number;
+t(A) when is_pid(A) ->
+ is_pid;
+t(A) when is_port(A) ->
+ is_port;
+t(A) when is_record(A, apa) ->
+ is_record;
+t(A) when is_reference(A) ->
+ is_reference;
+t(A) when is_tuple(A) ->
+ is_tuple;
+t(A) when list(A) ->
+ list;
+t(A) when number(A) ->
+ number;
+t(A) when pid(A) ->
+ pid;
+t(A) when port(A) ->
+ port;
+t(A) when record(A, apa) ->
+ record;
+t(A) when reference(A) ->
+ reference;
+t(A) when tuple(A) ->
+ tuple.
+
+t1(A) when atom(A), atom(A) ->
+ atom;
+t1(A) when binary(A), binary(A) ->
+ binary;
+t1(A) when float(A), float(A) ->
+ float;
+t1(A) when function(A), function(A) ->
+ function;
+t1(A) when integer(A), integer(A) ->
+ integer;
+t1(A) when is_atom(A), is_atom(A) ->
+ is_atom;
+t1(A) when is_binary(A), is_binary(A) ->
+ is_binary;
+t1(A) when is_float(A), is_float(A) ->
+ is_float;
+t1(A) when is_function(A), is_function(A) ->
+ is_function;
+t1(A) when is_integer(A), is_integer(A) ->
+ is_integer;
+t1(A) when is_list(A), is_list(A) ->
+ is_list;
+t1(A) when is_number(A), is_number(A) ->
+ is_number;
+t1(A) when is_pid(A), is_pid(A) ->
+ is_pid;
+t1(A) when is_port(A), is_port(A) ->
+ is_port;
+t1(A) when is_record(A, apa), is_record(A, apa) ->
+ is_record;
+t1(A) when is_reference(A), is_reference(A) ->
+ is_reference;
+t1(A) when is_tuple(A), is_tuple(A) ->
+ is_tuple;
+t1(A) when list(A), list(A) ->
+ list;
+t1(A) when number(A), number(A) ->
+ number;
+t1(A) when pid(A), pid(A) ->
+ pid;
+t1(A) when port(A), port(A) ->
+ port;
+t1(A) when record(A, apa), record(A, apa) ->
+ record;
+t1(A) when reference(A), reference(A) ->
+ reference;
+t1(A) when tuple(A), tuple(A) ->
+ tuple.
+
+t2(A) when atom(A); atom(A) ->
+ atom;
+t2(A) when binary(A); binary(A) ->
+ binary;
+t2(A) when float(A); float(A) ->
+ float;
+t2(A) when function(A); function(A) ->
+ function;
+t2(A) when integer(A); integer(A) ->
+ integer;
+t2(A) when is_atom(A); is_atom(A) ->
+ is_atom;
+t2(A) when is_binary(A); is_binary(A) ->
+ is_binary;
+t2(A) when is_float(A); is_float(A) ->
+ is_float;
+t2(A) when is_function(A); is_function(A) ->
+ is_function;
+t2(A) when is_integer(A); is_integer(A) ->
+ is_integer;
+t2(A) when is_list(A); is_list(A) ->
+ is_list;
+t2(A) when is_number(A); is_number(A) ->
+ is_number;
+t2(A) when is_pid(A); is_pid(A) ->
+ is_pid;
+t2(A) when is_port(A); is_port(A) ->
+ is_port;
+t2(A) when is_record(A, apa); is_record(A, apa) ->
+ is_record;
+t2(A) when is_reference(A); is_reference(A) ->
+ is_reference;
+t2(A) when is_tuple(A); is_tuple(A) ->
+ is_tuple;
+t2(A) when list(A); list(A) ->
+ list;
+t2(A) when number(A); number(A) ->
+ number;
+t2(A) when pid(A); pid(A) ->
+ pid;
+t2(A) when port(A); port(A) ->
+ port;
+t2(A) when record(A, apa); record(A, apa) ->
+ record;
+t2(A) when reference(A); reference(A) ->
+ reference;
+t2(A) when tuple(A); tuple(A) ->
+ tuple.
+
+t3(A) when is_atom(A) or is_atom(A) ->
+ is_atom;
+t3(A) when is_binary(A) or is_binary(A) ->
+ is_binary;
+t3(A) when is_float(A) or is_float(A) ->
+ is_float;
+t3(A) when is_function(A) or is_function(A) ->
+ is_function;
+t3(A) when is_integer(A) or is_integer(A) ->
+ is_integer;
+t3(A) when is_list(A) or is_list(A) ->
+ is_list;
+t3(A) when is_number(A) or is_number(A) ->
+ is_number;
+t3(A) when is_pid(A) or is_pid(A) ->
+ is_pid;
+t3(A) when is_port(A) or is_port(A) ->
+ is_port;
+t3(A) when is_record(A, apa) or is_record(A, apa) ->
+ is_record;
+t3(A) when is_reference(A) or is_reference(A) ->
+ is_reference;
+t3(A) when is_tuple(A) or is_tuple(A) ->
+ is_tuple;
+t3(A) when record(A, apa) ->
+ foo;
+t3(A) when {erlang,is_record}(A, apa) ->
+ foo;
+t3(A) when erlang:is_record(A, apa) ->
+ foo;
+t3(A) when is_record(A, apa) ->
+ foo;
+t3(A) when record({apa}, apa) ->
+ {A,foo}.
+
+t4(_) when {erlang,is_record}({apa}, apa) ->
+ foo.
+
+t5(A) when erlang:is_record({apa}, apa) ->
+ {A,foo}.
+
+t6(A) when is_record({apa}, apa) ->
+ {A,foo}.
+
+-record(apa2,{a=a,b=foo:bar()}).
+apa(1) ->
+ [X || X <- [], #apa2{a = a} == {r,X,foo}];
+apa(2) ->
+ [X || X <- [], #apa2{b = b} == {r,X,foo}];
+apa(3) ->
+ [X || X <- [], 3 == X#apa2.a].
+
+new_fun() ->
+ lists:map(fun erlang:abs/1, [-1,3,4]).
diff --git a/lib/stdlib/test/id_transform_SUITE_data/External.hrl b/lib/stdlib/test/id_transform_SUITE_data/External.hrl
new file mode 100644
index 0000000000..c0cd9e68ac
--- /dev/null
+++ b/lib/stdlib/test/id_transform_SUITE_data/External.hrl
@@ -0,0 +1,41 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Generated by the Erlang ASN.1 compiler version:0.8.1
+%% Purpose: Erlang record definitions for each named and unnamed
+%% SEQUENCE and SET in module External
+
+-record('XSeq1',{bool1, int1, seq1}).
+-record('XSeqIn',{boolIn, intIn}).
+-record('XSet1',{bool1, int1, set1}).
+-record('XSetIn',{boolIn, intIn}).
+-record('XSetExt1',{asn1_EXT}).
+-record('XSetExt2',{bool, int, asn1_EXT}).
+-record('XSetExt3',{asn1_EXT, bool, int}).
+-record('XSetExt4',{bool, asn1_EXT, int}).
+-record('XSeqExt1',{asn1_EXT}).
+-record('XSeqExt2',{bool, int, asn1_EXT}).
+-record('XSeqExt3',{asn1_EXT, bool, int}).
+-record('XSeqExt4',{bool, asn1_EXT, int}).
+-record('XSetNT',{os, bool}).
+-record('XSetImp',{os, bool}).
+-record('XSetExp',{os, bool}).
+-record('XSeqNT',{os, bool}).
+-record('XSeqImp',{os, bool}).
+-record('XSeqExp',{os, bool}).
+
diff --git a/lib/stdlib/test/id_transform_SUITE_data/m.hrl b/lib/stdlib/test/id_transform_SUITE_data/m.hrl
new file mode 100644
index 0000000000..ed485e24be
--- /dev/null
+++ b/lib/stdlib/test/id_transform_SUITE_data/m.hrl
@@ -0,0 +1,30 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+-ifndef(M_HRL).
+-define(M_HRL, true).
+
+
+-record(m_NotAnInteger, {'OE_ID'="IDL:m/NotAnInteger:1.0"}).
+
+
+-endif.
+
+
diff --git a/lib/stdlib/test/id_transform_SUITE_data/m_i.hrl b/lib/stdlib/test/id_transform_SUITE_data/m_i.hrl
new file mode 100644
index 0000000000..53a279999e
--- /dev/null
+++ b/lib/stdlib/test/id_transform_SUITE_data/m_i.hrl
@@ -0,0 +1,29 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+-ifndef(M_I_HRL).
+-define(M_I_HRL, true).
+
+
+
+
+-endif.
+
+
diff --git a/lib/stdlib/test/id_transform_SUITE_data/oe_ex.hrl b/lib/stdlib/test/id_transform_SUITE_data/oe_ex.hrl
new file mode 100644
index 0000000000..72a12908df
--- /dev/null
+++ b/lib/stdlib/test/id_transform_SUITE_data/oe_ex.hrl
@@ -0,0 +1,29 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+-ifndef(OE_EX_HRL).
+-define(OE_EX_HRL, true).
+
+
+
+
+-endif.
+
+
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
new file mode 100644
index 0000000000..73efeb004a
--- /dev/null
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -0,0 +1,1900 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(io_SUITE).
+
+-export([all/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-export([error_1/1, float_g/1, otp_5403/1, otp_5813/1, otp_6230/1,
+ otp_6282/1, otp_6354/1, otp_6495/1, otp_6517/1, otp_6502/1,
+ manpage/1, otp_6708/1, otp_7084/1, otp_7421/1,
+ io_lib_collect_line_3_wb/1, cr_whitespace_in_string/1,
+ io_fread_newlines/1]).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(t, test_server).
+-define(privdir(_), "./io_SUITE_priv").
+-else.
+-include("test_server.hrl").
+-define(format(S, A), ok).
+-define(privdir(Conf), ?config(priv_dir, Conf)).
+-endif.
+
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+fin_per_testcase(_Case, _Config) ->
+ Dog = ?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(doc) ->
+ ["Test cases for io."];
+all(suite) ->
+ [error_1,float_g,otp_5403,otp_5813,otp_6230,otp_6282,otp_6354,otp_6495,
+ otp_6517,otp_6502,manpage,otp_6708,otp_7084,otp_7421,
+ io_lib_collect_line_3_wb,cr_whitespace_in_string,io_fread_newlines].
+
+error_1(doc) ->
+ ["Error cases for output"];
+error_1(suite) ->
+ [];
+error_1(Config) when is_list(Config) ->
+ %% We don't do erroneous output on stdout - the test server
+ %% seems to catch that somehow.
+ ?line PrivDir = ?privdir(Config),
+ ?line File = filename:join(PrivDir, "slask"),
+ ?line {ok, F1} = file:open(File, [write]),
+ ?line {'EXIT', _} = (catch io:format(muttru, "hej", [])),
+ ?line {'EXIT', _} = (catch io:format(F1, pelle, "hej")),
+ ?line {'EXIT', _} = (catch io:format(F1, 1, "hej")),
+ ?line {'EXIT', _} = (catch io:format(F1, "~p~", [kaka])),
+ ?line {'EXIT', _} = (catch io:format(F1, "~m~n", [kaka])),
+
+ %% This causes the file process to die, and it is linked to us,
+ %% so we can't catch the error this easily.
+% ?line {'EXIT', _} = (catch io:put_chars(F1, 666)),
+
+ ?line file:close(F1),
+ ?line {'EXIT', _} = (catch io:format(F1, "~p", ["hej"])),
+ ok.
+
+float_g(Config) when is_list(Config) ->
+ ?line ["5.00000e-2",
+ "0.500000",
+ "5.00000",
+ "50.0000",
+ "500.000",
+ "5000.00",
+ "5.00000e+4",
+ "5.00000e+5"] = float_g_1("~g", 5.0, -2, 5),
+
+ ?line ["-5.0000e-2",
+ "-0.50000",
+ "-5.0000",
+ "-50.000",
+ "-500.00",
+ "-5000.0",
+ "-5.0000e+4",
+ "-5.0000e+5"] = float_g_1("~.5g", -5.0, -2, 5),
+
+ ?line ["5.000e-2",
+ "0.5000",
+ "5.000",
+ "50.00",
+ "500.0",
+ "5.000e+3",
+ "5.000e+4",
+ "5.000e+5"] = float_g_1("~.4g", 5.0, -2, 5),
+
+ ?line ["-5.00e-2",
+ "-0.500",
+ "-5.00",
+ "-50.0",
+ "-5.00e+2",
+ "-5.00e+3",
+ "-5.00e+4",
+ "-5.00e+5"] = float_g_1("~.3g", -5.0, -2, 5),
+
+ ?line ["5.0e-2",
+ "0.50",
+ "5.0",
+ "5.0e+1",
+ "5.0e+2",
+ "5.0e+3",
+ "5.0e+4",
+ "5.0e+5"] = float_g_1("~.2g", 5.0, -2, 5),
+
+ ?line
+ case catch fmt("~.1g", [0.5]) of
+ "0.5" ->
+ ?line
+ ["5.0e-2",
+ "0.5",
+ "5.0e+0",
+ "5.0e+1",
+ "5.0e+2",
+ "5.0e+3",
+ "5.0e+4",
+ "5.0e+5"] = float_g_1("~.1g", 5.0, -2, 5);
+ {'EXIT',_} -> ok
+ end,
+
+ ?line ["4.99999e-2",
+ "0.499999",
+ "4.99999",
+ "49.9999",
+ "499.999",
+ "4999.99",
+ "4.99999e+4",
+ "4.99999e+5"] = float_g_1("~g", 4.9999949999, -2, 5),
+
+ ?line ["-5.00000e-2",
+ "-0.500000",
+ "-5.00000",
+ "-50.0000",
+ "-500.000",
+ "-5000.00",
+ "-5.00000e+4",
+ "-5.00000e+5"] = float_g_1("~g", -4.9999950001, -2, 5),
+ ok.
+
+float_g_1(Fmt, V, Min, Max) ->
+ [fmt(Fmt, [V*math:pow(10, E)]) || E <- lists:seq(Min, Max)].
+
+otp_5403(doc) ->
+ ["OTP-5403. ~s formats I/O lists and a single binary."];
+otp_5403(suite) ->
+ [];
+otp_5403(Config) when is_list(Config) ->
+ ?line "atom" = fmt("~s", [atom]),
+ ?line "binary" = fmt("~s", [<<"binary">>]),
+ ?line "atail" = fmt("~s", [["a" | <<"tail">>]]),
+ ?line "deepcharlist" = fmt("~s", [["deep",["char",["list"]]]]),
+ ?line "somebinaries" = fmt("~s", [[<<"some">>,[<<"binaries">>]]]),
+ ok.
+
+otp_5813(doc) ->
+ ["OTP-5813. read/3 is new."];
+otp_5813(suite) ->
+ [];
+otp_5813(Config) when is_list(Config) ->
+ ?line PrivDir = ?privdir(Config),
+ ?line File = filename:join(PrivDir, "test"),
+
+ ?line ok = file:write_file(File, <<"a. ">>),
+ ?line {ok, Fd} = file:open(File, [read]),
+ ?line {ok, a, 1} = io:read(Fd, '', 1),
+ ?line {eof,1} = io:read(Fd, '', 1),
+ ok = file:close(Fd),
+
+ ?line ok = file:write_file(File, <<"[}.">>),
+ ?line {ok, Fd2} = file:open(File, [read]),
+ ?line {error,{1,_,_},1} = io:read(Fd2, '', 1),
+ ?line ok = file:close(Fd),
+
+ file:delete(File),
+ ok.
+
+otp_6230(doc) ->
+ ["OTP-6230. ~p and ~P with (huge) binaries."];
+otp_6230(suite) ->
+ [];
+otp_6230(Config) when is_list(Config) ->
+ %% The problem is actually huge binaries, but the small tests here
+ %% just run through most of the modified code.
+ ?line "<<>>" = fmt("~P", [<<"">>,-1]),
+ ?line "<<\"hej\">>" = fmt("~P", [<<"hej">>,-1]),
+ ?line "{hej,...}" = fmt("~P", [{hej,<<"hej">>},2]),
+ ?line "{hej,<<...>>}" = fmt("~P", [{hej,<<"hej">>},3]),
+ ?line "{hej,<<\"hejs\"...>>}" = fmt("~P", [{hej,<<"hejsan">>},4]),
+ ?line "{hej,<<\"hej\">>}" = fmt("~P", [{hej,<<"hej">>},6]),
+ ?line "<<...>>" = fmt("~P", [<<"hej">>,1]),
+ ?line "<<\"hejs\"...>>" = fmt("~P", [<<"hejsan">>,2]),
+ ?line "<<\"hej\">>" = fmt("~P", [<<"hej">>,4]),
+ ?line "{hej,<<127,...>>}" =
+ fmt("~P", [{hej,<<127:8,<<"hej">>/binary>>},4]),
+ ?line "{hej,<<127,104,101,...>>}" =
+ fmt("~P", [{hej,<<127:8,<<"hej">>/binary>>},6]),
+
+ B = list_to_binary(lists:duplicate(30000, $a)),
+ ?line "<<\"aaaa"++_ = fmt("~P", [B, 20000]),
+ ok.
+
+otp_6282(doc) ->
+ ["OTP-6282. ~p truncates strings (like binaries) depending on depth."];
+otp_6282(suite) ->
+ [];
+otp_6282(Config) when is_list(Config) ->
+ ?line "[]" = p("", 1, 20, 1),
+ ?line "[]" = p("", 1, 20, -1),
+ ?line "[...]" = p("a", 1, 20, 1),
+ ?line "\"a\"" = p("a", 1, 20, 2),
+ ?line "\"aa\"" = p("aa", 1, 20, 2),
+ ?line "\"aaa\"" = p("aaa", 1, 20, 2),
+ ?line "\"aaaa\"" = p("aaaa", 1, 20, 2),
+ % ?line "\"aaaa\"..." = p("aaaaaa", 1, 20, 2),
+ ?line "\"a\"" = p("a", 1, 20, -1),
+ % ?line "\"aa\"..." = p([$a,$a,1000], 1, 20, 2),
+ % ?line "\"aa\"..." = p([$a,$a,1000], 1, 20, 3),
+ ?line "[97,97,1000]" = p([$a,$a,1000], 1, 20, 4),
+ S1 = lists:duplicate(200,$a),
+ ?line "[...]" = p(S1, 1, 20, 1),
+ % ?line "\"aaaaaaaaaaaaaaaa\"\n \"aaaaaaaaaaaaaaaa\"\n \"aaaa\"..." =
+ % ?line "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"..." =
+ % p(S1, 1, 20, 10),
+ ?line true = "\"" ++ S1 ++ "\"" =:= p(S1, 1, 205, -1),
+ ?line "[97,97,1000|...]" = p([$a,$a,1000,1000], 1, 20, 4),
+
+ ?line "[[]]" = p([""], 1, 20, 2),
+ ?line "[[]]" = p([""], 1, 20, -1),
+ ?line "[[...]]" = p(["a"], 1, 20, 2),
+ ?line "[\"a\"]" = p(["a"], 1, 20, 3),
+ ?line "[\"aa\"]" = p(["aa"], 1, 20, 3),
+ ?line "[\"aaa\"]" = p(["aaa"], 1, 20, 3),
+ ?line "[\"a\"]" = p(["a"], 1, 20, -1),
+ % ?line "[\"aa\"...]" = p([[$a,$a,1000]], 1, 20, 3),
+ % ?line "[\"aa\"...]" = p([[$a,$a,1000]], 1, 20, 4),
+ ?line "[[97,97,1000]]" = p([[$a,$a,1000]], 1, 20, 5),
+ ?line "[[...]]" = p([S1], 1, 20, 2),
+ % ?line "[\"aaaaaaaaaaaaaa\"\n \"aaaaaaaaaaaaaa\"\n \"aaaaaaaa\"...]" =
+ % ?line "[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"...]" =
+ % p([S1], 1, 20, 11),
+ ?line true = "[\"" ++ S1 ++ "\"]" =:= p([S1], 1, 210, -1),
+ ?line "[[97,97,1000|...]]" = p([[$a,$a,1000,1000]], 1, 20, 5),
+
+ % ?line "[\"aaaa\"...]" = p(["aaaaa"], 1, 10, 3),
+ ?line "[\"aaaaa\"]" = p(["aaaaa"], 1, 10, 6),
+
+ ok.
+
+otp_6354(doc) ->
+ ["OTP-6354. io_lib_pretty rewritten."];
+otp_6354(suite) ->
+ [];
+otp_6354(Config) when is_list(Config) ->
+ %% A few tuples:
+ ?line "{}" = p({}, 1, 20, -1),
+ ?line "..." = p({}, 1, 20, 0),
+ ?line "{}" = p({}, 1, 20, 1),
+ ?line "{}" = p({}, 1, 20, 2),
+ ?line "{a}" = p({a}, 1, 20, -1),
+ ?line "..." = p({a}, 1, 20, 0),
+ ?line "{...}" = p({a}, 1, 20, 1),
+ ?line "{a}" = p({a}, 1, 20, 2),
+ ?line "{a,b}" = p({a,b}, 1, 20, -1),
+ ?line "..." = p({a,b}, 1, 20, 0),
+ ?line "{...}" = p({a,b}, 1, 20, 1),
+ ?line "{a,...}" = p({a,b}, 1, 20, 2),
+ ?line "{a,b}" = p({a,b}, 1, 20, 3),
+ ?line "{}" = p({}, 1, 1, -1),
+ ?line "..." = p({}, 1, 1, 0),
+ ?line "{}" = p({}, 1, 1, 1),
+ ?line "{}" = p({}, 1, 1, 2),
+ ?line "{a}" = p({a}, 1, 1, -1),
+ ?line "..." = p({a}, 1, 1, 0),
+ ?line "{...}" = p({a}, 1, 1, 1),
+ ?line "{a}" = p({a}, 1, 1, 2),
+ ?line "{a,\n b}" = p({a,b}, 1, 1, -1),
+ ?line "{1,\n b}" = p({1,b}, 1, 1, -1),
+ ?line "..." = p({a,b}, 1, 1, 0),
+ ?line "{...}" = p({a,b}, 1, 1, 1),
+ ?line "{a,...}" = p({a,b}, 1, 1, 2),
+ ?line "{a,\n b}" = p({a,b}, 1, 1, 3),
+ ?line "{{}}" = p({{}}, 1, 1, 2),
+ ?line "{[]}" = p({[]}, 1, 1, 2),
+ ?line bt(<<"{1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]}">>,
+ p({1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]}, -1)),
+ ?line bt(<<"{abcd,ddddd,\n ddddd}">>,
+ p({abcd,ddddd,ddddd}, 1,16, -1)),
+ ?line bt(<<"{1,2,a,b,\n {sfdsf,sdfdsfs},\n [sfsdf,sdfsdf]}">>,
+ p({1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]}, 1, 35, 100)),
+ % With other terms than atomic ones on the same line:
+% ?line bt(<<"{1,2,a,b,{sfdsf,sdfdsfs},\n [sfsdf,sdfsdf]}">>,
+% p({1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]}, 1, 35, 100)),
+ % With line breaks:
+% ?line bt(<<"{1,\n"
+% " 2,\n"
+% " a,\n"
+% " b,\n"
+% " {sfdsf,sdfdsfs},\n"
+% " [sfsdf,sdfsdf]}">>,
+% p({1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]}, 1, 35, 100)),
+ ?line "{1,{1,{2,3}}}" = p({1,{1,{2,3}}}, 1, 80, 100),
+
+ ?line bt(<<"{wwwww,{wwwww,{wwwww,{wwwww,{wwwww,lkjsldfj,klsdjfjklds,\n"
+ " sdkfjdsl,sdakfjdsklj,sdkljfsdj}}}}}">>,
+ p({wwwww,{wwwww,{wwwww,{wwwww,{wwwww,lkjsldfj,klsdjfjklds,
+ sdkfjdsl,sdakfjdsklj,sdkljfsdj}}}}}, -1)),
+
+ % With no restriction on number of characters per line:
+% ?line bt(<<"{wwwww,{wwwww,{wwwww,{wwwww,{wwwww,lkjsldfj,klsdjfjklds,"
+% "sdkfjdsl,sdakfjdsklj,\n"
+% " sdkljfsdj}}}}}">>,
+% p({wwwww,{wwwww,{wwwww,{wwwww,{wwwww,lkjsldfj,klsdjfjklds,
+% sdkfjdsl,sdakfjdsklj,sdkljfsdj}}}}}, -1)),
+
+ % With line breaks:
+% ?line bt(<<"{wwwww,{wwwww,{wwwww,{wwwww,{wwwww,lkjsldfj,\n"
+% " klsdjfjklds,\n"
+% " sdkfjdsl,\n"
+% " sdakfjdsklj,\n"
+% " sdkljfsdj}}}}}">>,
+% p({wwwww,{wwwww,{wwwww,{wwwww,{wwwww,lkjsldfj,klsdjfjklds,
+% sdkfjdsl,sdakfjdsklj,sdkljfsdj}}}}}, -1)),
+ ?line bt(<<"{wwwww,\n"
+ " {wwwww,\n"
+ " {wwwww,\n"
+ " {wwwww,\n"
+ " {wwwww,\n"
+ " {lkjsldfj,\n"
+ " {klsdjfjklds,\n"
+ " {klajsljls,\n"
+ " #aaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa{}}}}}}}}}">>,
+ p({wwwww,{wwwww,{wwwww,{wwwww,{wwwww,{lkjsldfj,
+ {klsdjfjklds,{klajsljls,
+ {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}}}}}}}}},
+ -1)),
+ ?line "{{...},...}" = p({{a,b},{a,b,c},{d,e,f}},1,8,2),
+ %% Closing brackets and parentheses count:
+ ?line "{{a,b,c},\n {{1,2,\n 3}}}" = p({{a,b,c},{{1,2,3}}},1,11,-1),
+ % With line breaks:
+% ?line "{{a,b,c},\n {{1,\n 2,\n 3}}}" = p({{a,b,c},{{1,2,3}}},1,11,-1),
+ ?line "{{a,b,c},\n [1,2,\n 3]}" = p({{a,b,c},[1,2,3]},1,10,-1),
+ % With line breaks:
+% ?line "{{a,b,c},\n [1,\n 2,\n 3]}" = p({{a,b,c},[1,2,3]},1,10,-1),
+ ?line "[{{a,b,c},\n {1,2,\n 3}}]" = p([{{a,b,c},{1,2,3}}],1,12,-1),
+ % With line breaks:
+% ?line "[{{a,b,c},\n {1,\n 2,\n 3}}]" = p([{{a,b,c},{1,2,3}}],1,12,-1),
+
+ %% A few lists:
+ ?line "[]" = p([], 1, 20, -1),
+ ?line "..." = p([], 1, 20, 0),
+ ?line "[]" = p([], 1, 20, 1),
+ ?line "[]" = p([], 1, 20, 2),
+ ?line "[a]" = p([a], 1, 20, -1),
+ ?line "..." = p([a], 1, 20, 0),
+ ?line "[...]" = p([a], 1, 20, 1),
+ ?line "[a]" = p([a], 1, 20, 2),
+ ?line "[a,b]" = p([a,b], 1, 20, -1),
+ ?line "..." = p([a,b], 1, 20, 0),
+ ?line "[...]" = p([a,b], 1, 20, 1),
+ ?line "[a|...]" = p([a,b], 1, 20, 2),
+ ?line "[a,b]" = p([a,b], 1, 20, 3),
+ ?line "[a|b]" = p([a|b], 1, 20, -1),
+ ?line "..." = p([a|b], 1, 20, 0),
+ ?line "[...]" = p([a|b], 1, 20, 1),
+ ?line "[a|...]" = p([a|b], 1, 20, 2),
+ ?line "[a|b]" = p([a|b], 1, 20, 3),
+ ?line "[]" = p([], 1, 1, -1),
+ ?line "..." = p([], 1, 1, 0),
+ ?line "[]" = p([], 1, 1, 1),
+ ?line "[]" = p([], 1, 1, 2),
+ ?line "[a]" = p([a], 1, 1, -1),
+ ?line "..." = p([a], 1, 1, 0),
+ ?line "[...]" = p([a], 1, 1, 1),
+ ?line "[a]" = p([a], 1, 1, 2),
+ ?line "[a,\n b]" = p([a,b], 1, 1, -1),
+ ?line "..." = p([a,b], 1, 1, 0),
+ ?line "[...]" = p([a,b], 1, 1, 1),
+ ?line "[a|...]" = p([a,b], 1, 1, 2),
+ ?line "[a,\n b]" = p([a,b], 1, 1, 3),
+ ?line "[a|\n b]" = p([a|b], 1, 1, -1),
+ ?line "..." = p([a|b], 1, 1, 0),
+ ?line "[...]" = p([a|b], 1, 1, 1),
+ ?line "[a|...]" = p([a|b], 1, 1, 2),
+ ?line "[a|\n b]" = p([a|b], 1, 1, 3),
+ ?line "[{}]" = p([{}], 1, 1, 2),
+ ?line "[[]]" = p([[]], 1, 1, 2),
+ ?line bt(<<"[1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]]">>,
+ p([1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]], -1)),
+ ?line bt(<<"[1,2,a,b,\n {sfdsf,sdfdsfs},\n [sfsdf,sdfsdf]]">>,
+ p([1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]], 1, 35, 100)),
+ % With other terms than atomic ones on the same line:
+% ?line bt(<<"[1,2,a,b,{sfdsf,sdfdsfs},\n [sfsdf,sdfsdf]]">>,
+% p([1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]], 1, 35, 100)),
+ % With line breaks:
+% ?line bt(<<"[1,\n"
+% " 2,\n"
+% " a,\n"
+% " b,\n"
+% " {sfdsf,sdfdsfs},\n"
+% " [sfsdf,sdfsdf]]">>,
+% p([1,2,a,b,{sfdsf,sdfdsfs},[sfsdf,sdfsdf]], 1, 35, 100)),
+ %% Element #8 is not printable:
+ ?line "[49," ++ _ = p("1234567"++[3,4,5,6,7], 1, 100, 9),
+ % ?line "\"1234567\"..." = p("1234567"++[3,4,5,6,7], 1, 100, 8),
+
+ %% A few records:
+ %% -record(a, {}).
+ %% -record(a, {}).
+ ?line "..." = p({a}, 0),
+ ?line "{...}" = p({a}, 1),
+ ?line "#a{}" = p({a}, 2),
+ ?line "#a{}" = p({a}, -1),
+ %% -record(b, {f}).
+ ?line "{...}" = p({b}, 1),
+ ?line "..." = p({b,c}, 0),
+ ?line "{...}" = p({b,c}, 1),
+ ?line "#b{...}" = p({b,c}, 2),
+ ?line "#b{f = c}" = p({b,c}, 3),
+ ?line "#b{f = c}" = p({b,c}, -1),
+ ?line "..." = p({b,{c,d}}, 0),
+ ?line "{...}" = p({b,{c,d}}, 1),
+ ?line "#b{...}" = p({b,{c,d}}, 2),
+ ?line "#b{f = {...}}" = p({b,{c,d}}, 3),
+ ?line "#b{f = {c,...}}" = p({b,{c,d}}, 4),
+ ?line "#b{f = {c,d}}" = p({b,{c,d}}, 5),
+ ?line "#b{f = {...}}" = p({b,{b,c}}, 3),
+ ?line "#b{f = #b{...}}" = p({b,{b,c}}, 4),
+ ?line "#b{f = #b{f = c}}" = p({b,{b,c}}, 5),
+ %% -record(c, {f1, f2}).
+ ?line "#c{f1 = d,f2 = e}" = p({c,d,e}, -1),
+ ?line "..." = p({c,d,e}, 0),
+ ?line "{...}" = p({c,d,e}, 1),
+ ?line "#c{...}" = p({c,d,e}, 2),
+ ?line "#c{f1 = d,...}" = p({c,d,e}, 3),
+ ?line "#c{f1 = d,f2 = e}" = p({c,d,e}, 4),
+ %% -record(d, {a..., b..., c.., d...}).
+ ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,\n"
+ " cccccccccccccccccccc = 3,dddddddddddddddddddd = 4,\n"
+ " eeeeeeeeeeeeeeeeeeee = 5}">>,
+ p({d,1,2,3,4,5}, -1)),
+ % With no restriction on number of characters per line:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,"
+% "cccccccccccccccccccc = 3,\n dddddddddddddddddddd = 4,"
+% "eeeeeeeeeeeeeeeeeeee = 5}">>,
+% p({d,1,2,3,4,5}, -1)),
+ % With line breaks:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+% " bbbbbbbbbbbbbbbbbbbb = 2,\n"
+% " cccccccccccccccccccc = 3,\n"
+% " dddddddddddddddddddd = 4,\n"
+% " eeeeeeeeeeeeeeeeeeee = 5}">>,
+% p({d,1,2,3,4,5}, -1)),
+ ?line "..." = p({d,1,2,3,4,5}, 0),
+ ?line "{...}" = p({d,1,2,3,4,5}, 1),
+ ?line "#d{...}" = p({d,1,2,3,4,5}, 2),
+ ?line "#d{aaaaaaaaaaaaaaaaaaaa = 1,...}" = p({d,1,2,3,4,5}, 3),
+ ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,...}">>,
+ p({d,1,2,3,4,5}, 4)),
+ ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,\n"
+ " cccccccccccccccccccc = 3,...}">>,
+ p({d,1,2,3,4,5}, 5)), % longer than 80 characters...
+ % With no restriction on number of characters per line:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,"
+% "cccccccccccccccccccc = 3,...}">>,
+% p({d,1,2,3,4,5}, 5)), % longer than 80 characters...
+ % With line breaks:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+% " bbbbbbbbbbbbbbbbbbbb = 2,\n"
+% " cccccccccccccccccccc = 3,...}">>,
+% p({d,1,2,3,4,5}, 5)),
+ ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,\n"
+ " cccccccccccccccccccc = 3,dddddddddddddddddddd = 4,...}">>,
+ p({d,1,2,3,4,5}, 6)),
+ % With no restriction on number of characters per line:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,"
+% "cccccccccccccccccccc = 3,\n dddddddddddddddddddd = 4,...}">>,
+% p({d,1,2,3,4,5}, 6)),
+ % With line breaks:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+% " bbbbbbbbbbbbbbbbbbbb = 2,\n"
+% " cccccccccccccccccccc = 3,\n"
+% " dddddddddddddddddddd = 4,...}">>,
+% p({d,1,2,3,4,5}, 6)),
+ ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,\n"
+ " cccccccccccccccccccc = 3,dddddddddddddddddddd = 4,\n"
+ " eeeeeeeeeeeeeeeeeeee = 5}">>,
+ p({d,1,2,3,4,5}, 7)),
+ % With no restriction on number of characters per line:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,bbbbbbbbbbbbbbbbbbbb = 2,"
+% "cccccccccccccccccccc = 3,\n dddddddddddddddddddd = 4,"
+% "eeeeeeeeeeeeeeeeeeee = 5}">>,
+% p({d,1,2,3,4,5}, 7)),
+ % With line breaks:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+% " bbbbbbbbbbbbbbbbbbbb = 2,\n"
+% " cccccccccccccccccccc = 3,\n"
+% " dddddddddddddddddddd = 4,\n"
+% " eeeeeeeeeeeeeeeeeeee = 5}">>,
+% p({d,1,2,3,4,5}, 7)),
+ ?line bt(<<"#rrrrr{\n"
+ " f1 = 1,\n"
+ " f2 = #rrrrr{f1 = a,f2 = b,f3 = c},\n"
+ " f3 = \n"
+ " #rrrrr{\n"
+ " f1 = h,f2 = i,\n"
+ " f3 = \n"
+ " #rrrrr{\n"
+ " f1 = aa,\n"
+ " f2 = \n"
+ " #rrrrr{\n"
+ " f1 = #rrrrr{f1 = a,f2 = b,f3 = c},\n"
+ " f2 = 2,f3 = 3},\n"
+ " f3 = bb}}}">>,
+ p({rrrrr,1,{rrrrr,a,b,c},{rrrrr,h,i,
+ {rrrrr,aa,{rrrrr,{rrrrr,a,b,c},
+ 2,3},bb}}},
+ -1)),
+ % With other terms than atomic ones on the same line:
+% ?line bt(<<"#rrrrr{\n"
+% " f1 = 1,f2 = #rrrrr{f1 = a,f2 = b,f3 = c},\n"
+% " f3 = \n"
+% " #rrrrr{\n"
+% " f1 = h,f2 = i,\n"
+% " f3 = \n"
+% " #rrrrr{\n"
+% " f1 = aa,\n"
+% " f2 = \n"
+% " #rrrrr{\n"
+% " f1 = #rrrrr{f1 = a,f2 = b,"
+% "f3 = c},f2 = 2,f3 = 3},\n"
+% " f3 = bb}}}">>,
+% p({rrrrr,1,{rrrrr,a,b,c},{rrrrr,h,i,
+% {rrrrr,aa,{rrrrr,{rrrrr,a,b,c},
+% 2,3},bb}}},
+% -1)),
+ % With line breaks:
+% ?line bt(<<"#rrrrr{\n"
+% " f1 = 1,\n"
+% " f2 = #rrrrr{f1 = a,f2 = b,f3 = c},\n"
+% " f3 = \n"
+% " #rrrrr{\n"
+% " f1 = h,\n"
+% " f2 = i,\n"
+% " f3 = \n"
+% " #rrrrr{\n"
+% " f1 = aa,\n"
+% " f2 = \n"
+% " #rrrrr{\n"
+% " f1 = #rrrrr{f1 = a,f2 = b,"
+% "f3 = c},\n"
+% " f2 = 2,\n"
+% " f3 = 3},\n"
+% " f3 = bb}}}">>,
+% p({rrrrr,1,{rrrrr,a,b,c},{rrrrr,h,i,
+% {rrrrr,aa,{rrrrr,{rrrrr,a,b,c},
+% 2,3},bb}}},
+% -1)),
+ ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+ " bbbbbbbbbbbbbbbbbbbb = \n"
+ " #d{aaaaaaaaaaaaaaaaaaaa = a,bbbbbbbbbbbbbbbbbbbb = b,\n"
+ " cccccccccccccccccccc = c,dddddddddddddddddddd = d,\n"
+ " eeeeeeeeeeeeeeeeeeee = e},\n"
+ " cccccccccccccccccccc = 3,\n"
+ " dddddddddddddddddddd = \n"
+ " #d{aaaaaaaaaaaaaaaaaaaa = h,bbbbbbbbbbbbbbbbbbbb = i,\n"
+ " cccccccccccccccccccc = \n"
+ " #d{aaaaaaaaaaaaaaaaaaaa = aa,"
+ "bbbbbbbbbbbbbbbbbbbb = bb,\n"
+ " cccccccccccccccccccc = \n"
+ " #d{aaaaaaaaaaaaaaaaaaaa = 1,"
+ "bbbbbbbbbbbbbbbbbbbb = 2,\n"
+ " cccccccccccccccccccc = 3,"
+ "dddddddddddddddddddd = 4,\n"
+ " eeeeeeeeeeeeeeeeeeee = 5},\n"
+ " dddddddddddddddddddd = dd,"
+ "eeeeeeeeeeeeeeeeeeee = ee},\n"
+ " dddddddddddddddddddd = k,"
+ "eeeeeeeeeeeeeeeeeeee = l},\n"
+ " eeeeeeeeeeeeeeeeeeee = 5}">>,
+ p({d,1,{d,a,b,c,d,e},3,{d,h,i,{d,aa,bb,{d,1,2,3,4,5},dd,ee},
+ k,l},5}, -1)),
+ % With line breaks:
+% ?line bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+% " bbbbbbbbbbbbbbbbbbbb = \n"
+% " #d{aaaaaaaaaaaaaaaaaaaa = a,\n"
+% " bbbbbbbbbbbbbbbbbbbb = b,\n"
+% " cccccccccccccccccccc = c,\n"
+% " dddddddddddddddddddd = d,\n"
+% " eeeeeeeeeeeeeeeeeeee = e},\n"
+% " cccccccccccccccccccc = 3,\n"
+% " dddddddddddddddddddd = \n"
+% " #d{aaaaaaaaaaaaaaaaaaaa = h,\n"
+% " bbbbbbbbbbbbbbbbbbbb = i,\n"
+% " cccccccccccccccccccc = \n"
+% " #d{aaaaaaaaaaaaaaaaaaaa = aa,\n"
+% " bbbbbbbbbbbbbbbbbbbb = bb,\n"
+% " cccccccccccccccccccc = \n"
+% " #d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+% " bbbbbbbbbbbbbbbbbbbb = 2,\n"
+% " cccccccccccccccccccc = 3,\n"
+% " dddddddddddddddddddd = 4,\n"
+% " eeeeeeeeeeeeeeeeeeee = 5},\n"
+% " dddddddddddddddddddd = dd,\n"
+% " eeeeeeeeeeeeeeeeeeee = ee},\n"
+% " dddddddddddddddddddd = k,\n"
+% " eeeeeeeeeeeeeeeeeeee = l},\n"
+% " eeeeeeeeeeeeeeeeeeee = 5}">>,
+% p({d,1,{d,a,b,c,d,e},3,{d,h,i,{d,aa,bb,{d,1,2,3,4,5},dd,ee},
+% k,l},5}, -1)),
+
+ A = aaaaaaaaaaaaa,
+ %% Print the record with dots at the end of the line (Ll = 80).
+ ?line "{aaaaaaa" ++ _ =
+ p({A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,
+ {A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,
+ {A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,
+ {A,{A,{ggg,{hhh,{ii,{jj,{kk,{ll,{mm,{nn,{oo,{d,1,2,3,4,5}
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+ }}}}}}}}}}}}}}}}, 146),
+ ?line "{aaaaaaa" ++ _ =
+ p({A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,
+ {A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,
+ {A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{A,
+ {A,{A,{A,{A,{A,{ggg,{hhh,{ii,{jj,{kk,{ll,{mm,{nn,{oo,{a}
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+ }}}}}}}}}}}}}}}}}}}, 152),
+
+ ?line bt(<<"{aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {g,{h,{i,{j,{k,{l,{m,{n,{o,#"
+ "d{...}}}}}}}}}}}}}}}}">>,
+ p({A,{A,{A,{A,{A,{A,
+ {g,{h,{i,{j,{k,{l,{m,{n,{o,{d,1,2,3,4,5}}}}}}}}}}}}}}}}, 32)),
+ ?line bt(<<"{a,#b{f = {c,{d,{e,{f,...}}}}}}">>,
+ p({a,{b,{c,{d,{e,{f,g}}}}}}, 12)),
+ ?line bt(<<"{aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,#c{f1 = ddd,"
+ "f2 = eee}}}}}}}}}}">>,
+ p({A,{A,{A,{A,{A,{A,{A,{A,{A,{c,ddd,eee}}}}}}}}}}, 100)),
+ ?line bt(<<"{aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,{aaaaaaaaaaaaa,{aaaaaaaaaaaaa,...}}}}">>,
+ p({A,{A,{A,{A,{b}}}}}, 8)),
+ % With no restriction on number of characters per line:
+% ?line bt(<<"{aaaaaaaaaaaaa,{aaaaaaaaaaaaa,{aaaaaaaaaaaaa,"
+% "{aaaaaaaaaaaaa,...}}}}">>,
+% p({A,{A,{A,{A,{b}}}}}, 8)),
+ ?line bt(<<"{aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,{aaaaaaaaaaaaa,{aaaaaaaaaaaaa,...}}}}}">>,
+ p({A,{A,{A,{A,{A,{b}}}}}}, 10)),
+ % With no restriction on number of characters per line:
+% ?line bt(<<"{aaaaaaaaaaaaa,\n"
+% " {aaaaaaaaaaaaa,{aaaaaaaaaaaaa,{aaaaaaaaaaaaa,"
+% "{aaaaaaaaaaaaa,...}}}}}">>,
+% p({A,{A,{A,{A,{A,{b}}}}}}, 10)),
+ ?line bt(<<"{aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,"
+ "{aaaaaaaaaaaaa,#a{}}}}}}}}}}}">>,
+ p({A,{A,{A,{A,{A,{A,{A,{A,{A,{A,{a}}}}}}}}}}}, 23)),
+ ?line bt(<<"{aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n",
+ " #rrrrr{\n"
+ " f1 = kljlkjlksfdgkljlsdkjf,"
+ "f2 = kljkljsdaflkjlkjsdf,...}}}}">>,
+ p({A,{A,{A,{rrrrr, kljlkjlksfdgkljlsdkjf,
+ kljkljsdaflkjlkjsdf,
+ asdfkldsjfklkljsdklfds}}}}, 10)),
+ % With no restriction on number of characters per line:
+% ?line bt(<<"{aaaaaaaaaaaaa,\n"
+% " {aaaaaaaaaaaaa,\n"
+% " {aaaaaaaaaaaaa,\n",
+% " #rrrrr{f1 = kljlkjlksfdgkljlsdkjf,f2 = "
+% "kljkljsdaflkjlkjsdf,...}}}}">>,
+% p({A,{A,{A,{rrrrr, kljlkjlksfdgkljlsdkjf,
+% kljkljsdaflkjlkjsdf,
+% asdfkldsjfklkljsdklfds}}}}, 10)),
+ ?line bt(<<"{aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {aaaaaaaaaaaaa,\n"
+ " {g,{h,{i,{j,{k,{l,{m,{n,"
+ "{o,#a{}}}}}}}}}}}}}}}}}">>,
+ p({A,{A,{A,{A,{A,{A,{A,
+ {g,{h,{i,{j,{k,{l,{m,{n,{o,{a}}}}}}}}}}}}}}}}}, 100)),
+ ?line bt(<<"#c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = \n"
+ " #c{\n"
+ " f1 = #c{f1 = #c{f1 = #c{f1 = a,"
+ "f2 = b},f2 = b},f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b},\n"
+ " f2 = b}">>,
+ p({c,{c,{c,{c,{c,{c,{c,{c,{c,{c,{c,{c,a,b},b},b},b},b},b},
+ b},b},b},b},b},b}, -1)),
+ ?line bt(<<"#rrrrr{\n"
+ " f1 = \n"
+ " #rrrrr{\n"
+ " f1 = \n"
+ " #rrrrr{\n"
+ " f1 = \n"
+ " #rrrrr{\n"
+ " f1 = \n"
+ " {rrrrr,{rrrrr,a,#rrrrr{f1 = {rrrrr,1,2},f2 = a,"
+ "f3 = b}},b},\n"
+ " f2 = {rrrrr,c,d},\n"
+ " f3 = {rrrrr,1,2}},\n"
+ " f2 = 1,f3 = 2},\n"
+ " f2 = 3,f3 = 4},\n"
+ " f2 = 5,f3 = 6}">>,
+ p({rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,a,{rrrrr,
+ {rrrrr,1,2},a,b}},b},{rrrrr,c,d},{rrrrr,1,2}},
+ 1,2},3,4},5,6}, -1)),
+ % With other terms than atomic ones on the same line:
+% ?line bt(<<"#rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = \n"
+% " {rrrrr,{rrrrr,a,#rrrrr{f1 = {rrrrr,1,2},f2 = a,"
+% "f3 = b}},b},\n"
+% " f2 = {rrrrr,c,d},f3 = {rrrrr,1,2}},\n"
+% " f2 = 1,f3 = 2},\n"
+% " f2 = 3,f3 = 4},\n"
+% " f2 = 5,f3 = 6}">>,
+% p({rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,a,{rrrrr,
+% {rrrrr,1,2},a,b}},b},{rrrrr,c,d},{rrrrr,1,2}},
+% 1,2},3,4},5,6}, -1)),
+ % With no restriction on number of characters per line:
+% ?line bt(<<"#rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = {rrrrr,{rrrrr,a,#rrrrr{f1 = {rrrrr,1,2},f2 = a,"
+% "f3 = b}},b},\n"
+% " f2 = {rrrrr,c,d},f3 = {rrrrr,1,2}},\n"
+% " f2 = 1,f3 = 2},\n"
+% " f2 = 3,f3 = 4},\n"
+% " f2 = 5,f3 = 6}">>,
+% p({rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,a,{rrrrr,
+% {rrrrr,1,2},a,b}},b},{rrrrr,c,d},{rrrrr,1,2}},
+% 1,2},3,4},5,6}, -1)),
+ % With line breaks:
+% ?line bt(<<"#rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = \n"
+% " #rrrrr{\n"
+% " f1 = {rrrrr,{rrrrr,a,#rrrrr{f1 = {rrrrr,1,2},f2 = a,"
+% "f3 = b}},b},\n"
+% " f2 = {rrrrr,c,d},\n"
+% " f3 = {rrrrr,1,2}},\n"
+% " f2 = 1,\n"
+% " f3 = 2},\n"
+% " f2 = 3,\n"
+% " f3 = 4},\n"
+% " f2 = 5,\n"
+% " f3 = 6}">>,
+% p({rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,{rrrrr,a,{rrrrr,
+% {rrrrr,1,2},a,b}},b},{rrrrr,c,d},{rrrrr,1,2}},
+% 1,2},3,4},5,6}, -1)),
+ ?line "{aaa,\n {aaa," ++ _ =
+ p({aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,
+ {aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,
+ {aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,
+ {aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,{aaa,
+ {aaa,a}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}},
+ 1, 80, -1),
+
+ %% A few other cases...
+ ?line "{a,#Fun<" ++ _ = lists:flatten(io_lib_pretty:print({a,fun fmt/2})),
+ ?line "#Fun<" ++ _ = io_lib_pretty:print(fun() -> foo end),
+ % ?line "[<<\"foobar\">>|<<\"barf\"...>>]" =
+ % p([<<"foobar">>|<<"barfoo">>], 1, 30, 4),
+ %% No support for negative columns any more:
+ ?line "[a,\n [b,\n c,\n d,\n [e,\n f]],\n c]" =
+ p([a,[b,c,d,[e,f]],c], -1, 2, 10),
+ ?line "[a,\n [b,\n c,\n d,\n [e,\n f]],\n c]" =
+ p([a,[b,c,d,[e,f]],c], 0, 2, 10),
+ %% 20 bytes are tried first, then the rest. Try 21 bytes:
+ L = lists:duplicate(20, $a),
+ % ?line bt(<<"<<\"aaaaaa\"\n \"aaaaaa\"\n \"aaaaaa\"\n \"aaa\">>">>,
+ ?line bt(<<"<<\"aaaaaaaaaaaaaaaaaaaaa\">>">>,
+ p(list_to_binary([$a | L]), 1, 10, -1)),
+ ?line "<<97," ++ _ = p(list_to_binary(L ++ [3]), 1, 10, -1),
+ % ?line "<<\"aaaa\"...>>" = p(list_to_binary(L ++ [3]), 1, 10, 2),
+ % ?line "<<\"aaaaaa\"\n \"aa\"...>>" =
+ % ?line "<<\"aaaaaaaa\"...>>" =
+ % p(list_to_binary(L ++ [3]), 1, 10, 3),
+ % ?line "<<\"aaaaaa\"\n \"aaaaaa\"\n \"aaaaaa\"\n \"aa\"...>>" =
+ % ?line "<<\"aaaaaaaaaaaaaaaaaaaa\"...>>" =
+ % p(list_to_binary(L ++ [3]), 1, 10, 21),
+ ?line "<<97," ++ _ = p(list_to_binary(L ++ [3]), 1, 10, 22),
+
+ ?line "\"\\b\\t\\n\\v\\f\\r\\e\250\"" =
+ p([8,9,10,11,12,13,27,168], 1, 40, -1),
+ % ?line "\"\\b\\t\\n\"\n \"\\v\\f\\r\"\n \"\\e\250\"" =
+ ?line "\"\\b\\t\\n\\v\\f\\r\\e�\"" =
+ p([8,9,10,11,12,13,27,168], 1, 10, -1),
+ ?line "\"\\b\\t\\n\\v\\f\\r\\e\250\"" =
+ p([8,9,10,11,12,13,27,168], 1, 40, 100),
+ % ?line "\"\\e\\t\\nab\"\n \"cd\"" =
+ ?line "\"\\e\\t\\nabcd\"" =
+ p("\e\t\nabcd", 1, 12, -1),
+
+ %% DEL (127) is special...
+ ?line "[127]" = p("\d", 1, 10, -1),
+ ?line "[127]" = p([127], 1, 10, 100),
+
+ ?line "<<\"\\b\\t\\n\\v\\f\\r\\e\250\">>" =
+ p(<<8,9,10,11,12,13,27,168>>, 1, 40, -1),
+ ?line "<<\"\\b\\t\\n\\v\\f\\r\\e\250\">>" =
+ p(<<8,9,10,11,12,13,27,168>>, 1, 10, -1),
+ ?line "<<127>>" = p(<<127>>, 1, 10, 100),
+
+ %% "Partial" string binaries:
+ ?line "<<\"he\"...>>" = p(list_to_binary("he"++[3]), 1, 80, 2),
+ ?line "<<\"he\"...>>" = p(list_to_binary("he"++[3]), 1, 80, 3),
+ ?line "<<104,101,3>>" = p(list_to_binary("he"++[3]), 1, 80, 4),
+ ?line "<<...>>" = p(list_to_binary([3] ++ "he"), 1, 80, 1),
+ ?line "<<3,...>>" = p(list_to_binary([3] ++ "he"), 1, 80, 2),
+ ?line "<<3,104,...>>" = p(list_to_binary([3] ++ "he"), 1, 80, 3),
+
+ ?line "<<\"12345678901234567890\"...>>" =
+ p(list_to_binary("12345678901234567890"++[3]), 1, 80, 8),
+ ?line "<<\"12345678901234567890\"...>>" =
+ p(list_to_binary("12345678901234567890"++[3]), 1, 80, 21),
+ ?line "<<49," ++ _ =
+ p(list_to_binary("12345678901234567890"++[3]), 1, 80, 22),
+
+ ?line "{sdfsdfj,\n 23" ++ _ =
+ p({sdfsdfj,23423423342.23432423}, 1, 17, -1),
+
+ ?line bt(<<"kljkljlksdjjlf kljalkjlsdajafasjdfj [kjljklasdf,kjlljsfd,sdfsdkjfsd,kjjsdf,jl,
+ lkjjlajsfd|jsdf]">>,
+ fmt("~w ~w ~p",
+ [kljkljlksdjjlf,
+ kljalkjlsdajafasjdfj,
+ [kjljklasdf,kjlljsfd,sdfsdkjfsd,kjjsdf,jl,lkjjlajsfd |
+ jsdf]])),
+
+ %% Binaries are split as well:
+ ?line bt(<<"<<80,100,0,55,55,55,55,55,55,55,55,55,\n "
+ "55,55,55,55,55,55,55,...>>">>,
+ p(<<80,100,0,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,
+ 55,55,55,55,55,55,55,55,55,55,55,55>>,1,40,20)),
+ ?line bt(<<"<<80,100,0,55,55,55,55,55,55,55,55,55,\n "
+ "55,55,55,55,55,55,55,55,55,55,55,55,\n 55,55,55,55,55,55>>">>,
+ p(<<80,100,0,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,
+ 55,55,55,55,55,55,55,55,55,55,55,55>>,1,40,-1)),
+ ?line "<<0,0,0,\n ...>>" = p(<<0,0,0,0,0>>, 1, 10, 4),
+
+ %% ~W now uses ",..." when printing tuples
+ ?line "[a,b|...]" = fmt("~W", [[a,b,c,d,e], 3]),
+ ?line "{a,b,...}" = fmt("~W", [{a,b,c,d,e}, 3]),
+ ok.
+
+otp_6495(doc) ->
+ ["OTP-6495. io_lib_pretty bugfix."];
+otp_6495(suite) ->
+ [];
+otp_6495(Config) when is_list(Config) ->
+ ?line bt(<<"[120,120,120,120,120,120,120,120,120,120,120,120,120,120,"
+ "120,120,120,120,120]<<1>>">>,
+ fmt("~w~p", ["xxxxxxxxxxxxxxxxxxx", <<1>>])),
+ ok.
+
+otp_6517(doc) ->
+ ["OTP-6517. The Format argument of fwrite can be a binary."];
+otp_6517(suite) ->
+ [];
+otp_6517(Config) when is_list(Config) ->
+ ?line "string" = fmt(<<"~s">>, [<<"string">>]),
+ ok.
+
+otp_6502(doc) ->
+ ["OTP-6502. Bits."];
+otp_6502(suite) ->
+ [];
+otp_6502(Config) when is_list(Config) ->
+ ?line bt(<<
+ "[0,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]"
+ "<<0,0,8,\n"
+ " "
+ " 1:1>>">>,
+ fmt("~w~p", [lists:seq(0, 25), <<17:25>>])),
+ ok.
+
+otp_7421(doc) ->
+ ["OTP-7421. Soft limit of 60 chars removed when pretty printing."];
+otp_7421(suite) ->
+ [];
+otp_7421(Config) when is_list(Config) ->
+ bt(<<"{aa,bb,\n"
+ " c,dd,\n"
+ " eee,\n"
+ " fff}">>,
+ rp({aa,bb,c,dd,eee,fff}, 1, 80, -1, 5, none)),
+ bt(<<"{aa,bb,\n"
+ " c,\n"
+ " dd,\n"
+ " eee,\n"
+ " fff}">>,
+ rp({aa,bb,c,dd,eee,fff}, 1, 80, -1, 4, none)),
+ ok.
+
+bt(Bin, R) ->
+ R = binary_to_list(Bin).
+
+p(Term, D) ->
+ rp(Term, 1, 80, D).
+
+p(Term, Col, Ll, D) ->
+ rp(Term, Col, Ll, D, no_fun).
+
+rp(Term, Col, Ll, D) ->
+ rp(Term, Col, Ll, D, fun rfd/2).
+
+-define(MAXCS, 60).
+
+rp(Term, Col, Ll, D, RF) ->
+ rp(Term, Col, Ll, D, ?MAXCS, RF).
+
+rp(Term, Col, Ll, D, M, RF) ->
+ %% io:format("~n~n*** Col = ~p Ll = ~p D = ~p~n~p~n-->~n",
+ %% [Col, Ll, D, Term]),
+ R = io_lib_pretty:print(Term, Col, Ll, D, M, RF),
+ %% io:format("~s~n<--~n", [R]),
+ lists:flatten(io_lib:format("~s", [R])).
+
+fmt(Fmt, Args) ->
+ lists:flatten(io_lib:format(Fmt, Args)).
+
+rfd(a, 0) ->
+ [];
+rfd(b, 1) ->
+ [f];
+rfd(c, 2) ->
+ [f1, f2];
+rfd(e, 3) ->
+ [f, g, h];
+rfd(d, 5) ->
+ [aaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbb,
+ cccccccccccccccccccc, dddddddddddddddddddd,
+ eeeeeeeeeeeeeeeeeeee];
+rfd(rrrrr, 3) ->
+ [f1, f2, f3];
+rfd(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 0) ->
+ [];
+rfd(_, _) ->
+ no.
+
+manpage(doc) ->
+ ["The examples in io(3) and io_lib(3)."];
+manpage(suite) ->
+ [];
+manpage(Config) when is_list(Config) ->
+ %% The examples that write or print only, not the ones that read...
+
+ ?line bt(<<"Hello world!\n">>,
+ fmt("Hello world!~n", [])),
+ ?line bt(<<"| aaaaa|bbbbb |ccccc|\n">>, % bugfix
+ fmt("|~10.5c|~-10.5c|~5c|~n", [$a, $b, $c])),
+ ?line bt(<<"|**********|\n">>,
+ fmt("|~10w|~n", [{hey, hey, hey}])),
+ ?line bt(<<"|{hey,hey,h|\n">>,
+ fmt("|~10s|~n", [io_lib:write({hey, hey, hey})])),
+
+ T = [{attributes,[[{id,age,1.50000},{mode,explicit},
+ {typename,"INTEGER"}], [{id,cho},{mode,explicit},{typename,'Cho'}]]},
+ {typename,'Person'},{tag,{'PRIVATE',3}},{mode,implicit}],
+ ?line bt(<<"[{attributes,[[{id,age,1.5},{mode,explicit},{typename,"
+ "[73,78,84,69,71,69,82]}],[{id,cho},{mode,explicit},"
+ "{typename,'Cho'}]]},{typename,'Person'},{tag,{'PRIVATE',3}},"
+ "{mode,implicit}]\n">>,
+ fmt("~w~n", [T])),
+ ?line bt(<<"[{attributes,[[{id,age,1.5},\n"
+ " {mode,explicit},\n"
+ " {typename,\"INTEGER\"}],\n"
+ " [{id,cho},{mode,explicit},{typename,'Cho'}]]},\n"
+ " {typename,'Person'},\n"
+ " {tag,{'PRIVATE',3}},\n"
+ " {mode,implicit}]\n">>,
+ fmt("~62p~n", [T])),
+ ?line bt(<<"Here T = [{attributes,[[{id,age,1.5},\n"
+ " {mode,explicit},\n"
+ " {typename,\"INTEGER\"}],\n"
+ " [{id,cho},\n"
+ " {mode,explicit},\n"
+ " {typename,'Cho'}]]},\n"
+ " {typename,'Person'},\n"
+ " {tag,{'PRIVATE',3}},\n"
+ " {mode,implicit}]\n">>,
+ fmt("Here T = ~62p~n", [T])),
+ ?line bt(<<"[{attributes,[[{id,age,1.5},{mode,explicit},"
+ "{typename,...}],[{id,cho},{mode,...},{...}]]},"
+ "{typename,'Person'},{tag,{'PRIVATE',3}},{mode,implicit}]\n">>,
+ fmt("~W~n", [T,9])),
+ ?line bt(<<"[{attributes,[[{id,age,1.5},{mode,explicit},{typename,...}],"
+ "\n "
+ "[{id,cho},{mode,...},{...}]]},\n {typename,'Person'},\n "
+ "{tag,{'PRIVATE',3}},\n {mode,implicit}]\n">>,
+ fmt("~62P~n", [T,9])),
+
+ ?line "1F\n" = fmt("~.16B~n", [31]),
+ ?line "-10011\n" = fmt("~.2B~n", [-19]),
+ ?line "5Z\n" = fmt("~.36B~n", [5*36+35]),
+ ?line "10#31\n" = fmt("~X~n", [31,"10#"]),
+ ?line "-0x1F\n" = fmt("~.16X~n", [-31,"0x"]),
+ ?line "10#31\n" = fmt("~.10#~n", [31]),
+ ?line "-16#1F\n" = fmt("~.16#~n", [-31]),
+ ?line "abc def 'abc def' {foo,1} A \n" =
+ fmt("~s ~w ~i ~w ~c ~n",
+ ['abc def', 'abc def', {foo, 1},{foo, 1}, 65]),
+ % fmt("~s", [65]),
+
+ %% io_lib(3)
+ ?line bt(<<"{1,[2],[3],[...],...}">>,
+ lists:flatten(io_lib:write({1,[2],[3],[4,5],6,7,8,9}, 5))),
+ ok.
+
+otp_6708(doc) ->
+ ["OTP-6708. Fewer newlines when pretty-printing."];
+otp_6708(suite) ->
+ [];
+otp_6708(Config) when is_list(Config) ->
+ ?line bt(<<"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,\n"
+ " 23,24,25,26,27,28,29|...]">>,
+ p(lists:seq(1,1000), 30)),
+ ?line bt(<<"{lkjasklfjsdak,mlkasjdflksj,klasdjfklasd,jklasdfjkl,\n"
+ " jklsdjfklsd,masdfjkkl}">>,
+ p({lkjasklfjsdak,mlkasjdflksj,klasdjfklasd,jklasdfjkl,
+ jklsdjfklsd, masdfjkkl}, -1)),
+ ?line bt(<<"#b{f = {lkjljalksdf,jklaskfjd,kljasdlf,kljasdf,kljsdlkf,\n"
+ " kjdd}}">>,
+ p({b, {lkjljalksdf,jklaskfjd,kljasdlf,kljasdf,kljsdlkf,kjdd}},
+ -1)),
+ ?line bt(<<"#b{f = {lkjljalksdf,jklaskfjd,kljasdlf,kljasdf,kljsdlkf,\n"
+ " kdd}}">>,
+ p({b, {lkjljalksdf,jklaskfjd,kljasdlf,kljasdf,kljsdlkf,kdd}},
+ -1)),
+ ?line bt(<<"#e{f = undefined,g = undefined,\n"
+ " h = #e{f = 11,g = 22,h = 333}}">>,
+ p({e,undefined,undefined,{e,11,22,333}}, -1)),
+ ?line bt(<<"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21|\n"
+ " apa11]">>,
+ p(lists:seq(1,21) ++ apa11, -1)),
+ ?line bt(<<"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,\n"
+ " 23,\n"
+ " {{abadalkjlasdjflksdajfksdklfsdjlkfdlskjflsdj"
+ "flsdjfldsdsdddd}}]">>,
+ p(lists:seq(1,23) ++
+ [{{abadalkjlasdjflksdajfksdklfsdjlkfdlskjflsdjflsdjfldsdsdddd}}],
+ -1)),
+ ?line bt(<<"{lkjasdf,\n"
+ " {kjkjsd,\n"
+ " {kjsd,\n"
+ " {kljsdf,\n"
+ " {kjlsd,{dkjsdf,{kjlds,{kljsd,{kljs,"
+ "{kljlkjsd}}}}}}}}}}">>,
+ p({lkjasdf,{kjkjsd,{kjsd,
+ {kljsdf,
+ {kjlsd,
+ {dkjsdf,{kjlds,
+ {kljsd,{kljs,{kljlkjsd}}}}}}}}}},
+ -1)),
+ ?line bt(<<"{lkjasdf,\n"
+ " {kjkjsd,\n"
+ " {kjsd,{kljsdf,{kjlsd,{dkjsdf,{kjlds,"
+ "{kljsd,{kljs}}}}}}}}}">>,
+ p({lkjasdf,{kjkjsd,{kjsd,
+ {kljsdf,{kjlsd,{dkjsdf,
+ {kjlds,{kljsd,{kljs}}}}}}}}},
+ -1)),
+ ?line bt(<<"<<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,\n"
+ " 22,23>>">>,
+ p(list_to_binary(lists:seq(1,23)), -1)),
+ ?line bt(<<"<<100,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,\n"
+ " 27>>">>,
+ p(list_to_binary([100|lists:seq(10,27)]), -1)),
+ ?line bt(<<"<<100,101,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,\n"
+ " 26>>">>,
+ p(list_to_binary([100,101|lists:seq(10,26)]), -1)),
+ ?line bt(<<"{{<<100,101,102,10,11,12,13,14,15,16,17,18,19,20,21,22,\n"
+ " 23>>}}">>,
+ p({{list_to_binary([100,101,102|lists:seq(10,23)])}}, -1)),
+ ?line bt(<<"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22|\n"
+ " ap]">>,
+ p(lists:seq(1,22) ++ ap, -1)),
+ ?line bt(<<"[1,2,3,4,5,6,7,8,9,10,{},[],\n <<>>,11,12,13,14,15]">>,
+ p(lists:seq(1,10) ++ [{},[],<<>>] ++ lists:seq(11,15),1,30,-1)),
+ ?line bt(<<"[ddd,ddd,\n"
+ " {1},\n"
+ " [1,2],\n"
+ " ddd,kdfd,\n"
+ " [[1,2],a,b,c],\n"
+ " <<\"foo\">>,<<\"bar\">>,1,\n"
+ " {2}]">>,
+ p([ddd,ddd,{1},[1,2],ddd,kdfd,[[1,2],a,b,c],<<"foo">>,<<"bar">>,
+ 1,{2}],1,50,-1)),
+
+ ?line bt(<<"{dskljsadfkjsdlkjflksdjflksdjfklsdjklfjsdklfjlsdjfkl,jksd,\n"
+ " "
+ "lkjsdf,kljsdf,kljsf,kljsdf,kljsdf,jkldf,jklsdf,kljsdf,\n"
+ " "
+ "kljsdf,jklsdf,lkjfd,lkjsdf,kljsdf,kljsdf,lkjsdf,kljsdf,\n"
+ " "
+ "lkjsdfsd,kljsdf,kjsfj}">>,
+ p({dskljsadfkjsdlkjflksdjflksdjfklsdjklfjsdklfjlsdjfkl,jksd,
+ lkjsdf,kljsdf,kljsf,kljsdf,kljsdf,jkldf,jklsdf,kljsdf,
+ kljsdf,jklsdf,lkjfd,lkjsdf,kljsdf,kljsdf,lkjsdf,kljsdf,
+ lkjsdfsd,kljsdf,kjsfj}, 1, 110, -1)),
+ ?line bt(<<"{dskljsadfkjsdlkjflksdjflksdjfklsdjklfjsdklfjlsdjfkl,"
+ "#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
+ " "
+ "bbbbbbbbbbbbbbbbbbbb = 2,cccccccccccccccccccc = 3,\n"
+ " "
+ "dddddddddddddddddddd = 4,eeeeeeeeeeeeeeeeeeee = 5}}">>,
+ rp({dskljsadfkjsdlkjflksdjflksdjfklsdjklfjsdklfjlsdjfkl,
+ {d,1,2,3,4,5}},1,200,-1)),
+ ok.
+
+-define(ONE(N), ((1 bsl N) - 1)).
+-define(ALL_ONES, ((1 bsl 52) - 1)).
+
+otp_7084(doc) ->
+ ["OTP-7084. Printing floating point numbers nicely."];
+otp_7084(suite) ->
+ [];
+otp_7084(Config) when is_list(Config) ->
+ OldDog=?config(watchdog, Config),
+ test_server:timetrap_cancel(OldDog),
+ Timeout = 180,
+ ?line Dog = test_server:timetrap({seconds,Timeout}),
+ L = [{g_warm_up, fun g_warm_up/0},
+ {g_big_pos_float, fun g_big_pos_float/0},
+ {g_small_neg_float, fun g_small_neg_float/0},
+ {g_close_to_zero, fun g_close_to_zero/0},
+ {g_denormalized, fun g_denormalized/0},
+ {g_normalized, fun g_normalized/0},
+ {g_choice, fun g_choice/0},
+ {g_misc, fun g_misc/0}],
+ F = fun({M,T}) -> io:format("~p~n", [M]), T() end,
+ R = try
+ lists:foreach(fun(T) -> F(T) end, L),
+ ok
+ catch throw:Reason ->
+ Reason
+ end,
+ ?line test_server:timetrap_cancel(Dog),
+ R.
+
+g_warm_up() ->
+ g_t(0.5),
+ g_t(-0.5),
+ g_t((1 bsl 55) * 0.5),
+ g_t(-(1 bsl 55) * 0.5),
+ g_t(1.6799127650033296e+308),
+ g_t(pack(1, 0, 2#1010101010001100101000010111100101000000101100110001)),
+ g_t(pack(1, 0, 2#0001010000001001110100000101010101001110010001010110)),
+ g_t(234324324.23432432432432),
+ ok.
+
+g_big_pos_float() ->
+ %% The greatest positive float:
+ ft({{0,2046,?ONE(52)}, 100, 0}),
+ ok.
+
+g_small_neg_float() ->
+ %% The least negative float:
+ ft({{1,2046,?ONE(52)}, 0, 100}),
+ ok.
+
+g_close_to_zero() ->
+ %% A few denormalized floats close to zero:
+ ft({{0,0,0}, 100, 100}),
+ g_t(pack(1, 0, 0)), % -0.0
+ ok.
+
+g_denormalized() ->
+ %% Denormalized floats (mantissa carry):
+% D = 5,
+ %% Faster:
+ D = 1,
+ [ft({{S,0,?ONE(N)},D,D}) || S <- [0,1], N <- lists:seq(0, 52)],
+ ok.
+
+g_normalized() ->
+ %% Normalized floats (exponent carry):
+% D = 5,
+ %% Faster:
+ D = 1,
+ [ft({{S,E,?ONE(52)},D,D}) || S <- [0,1], E <- lists:seq(0, 2045)],
+ ok.
+
+g_choice() ->
+ %% Exponent should be used when and only when the string is shorter.
+ %% (g_misc/0 checks this too, and probably more throughly).
+ L = [0.0003, 3.0e-5, 3.3e-5, 3.3e-4,
+ 314.0, 314.1, 310.0, 3.1e6, -100.0,
+ 3.34e4, 3.0e3, 3.34333e9, 3.3433323e10, 33433323700.0,
+ 0.00197963, 1.97963e-4],
+ lists:foreach(fun(V) -> g_t(V) end, L),
+ ok.
+
+g_misc() ->
+ L_0_308 = lists:seq(0, 308),
+ L_0_307 = lists:seq(0, 307),
+% L_1_9 = lists:seq(1, 9),
+% L_0_9 = lists:seq(0, 9),
+ %% Faster:
+ L_1_9 = [1,5,9],
+ L_0_9 = [0,1,5,9],
+
+ %% 1.0,10.0, ... 2.0,20.0, ... 9.0,90.0, ... -1,-10, ... -2.0,-20.0...
+ [g_t(S*T*pow10(N)) || S <- [1.0, -1.0], T <- L_1_9, N <- L_0_307],
+
+ %% 1.0,1.0/10,1.0/100,... 2.0,2.0/10,2.0/100, ... 9.0,9.0/10,9.0/100,
+ %% -1.0,-1.0/10,... -9.0,-9.0/10...
+ [g_t(S*T/pow10(N)) || S <- [1.0, -1.0], T <- L_1_9, N <- L_0_308],
+
+ %% 0.0,1.0,2.0,...,9.0, 0.0,10.0,20.0,...,90.0,...
+ %% 0.0,-1.0,-2.0,...,-9.0, 0.0,-10.0,-20.0,...,-90.0,...
+ [g_t(S*list_to_float([D+$0]++lists:duplicate(N, $0)++".0")) ||
+ S <- [1.0,-1.0], N <- lists:seq(0, 300), D <- L_0_9],
+
+ %% 0.0,0.1,0.2,...0,9, 0.0,0.01,0.02,...,0.09,
+ %% 0.0,-0.1,-0.2,...-0,9, 0.0,-0.01,-0.02,...,-0.09,
+ [g_t(S*list_to_float("0."++lists:duplicate(N, $0)++[D+$0])) ||
+ S <- [1.0,-1.0], N <- lists:seq(0, 300), D <- L_0_9],
+ ok.
+
+ft({{S,E,M}, L, G}) ->
+ ft({pack(S, E, M), L, G});
+ft({V, Less, Greater}) when is_float(V) ->
+ _ = g_t(V),
+ ft(V, fun inc/1, Greater),
+ ft(V, fun dec/1, Less).
+
+ft(V0, F, I) when I > 0, is_float(V0) ->
+ V = F(V0),
+ _ = g_t(V),
+ ft(V, F, I - 1);
+ft(V, _F, 0) when is_float(V) ->
+ ok.
+
+g_t(V) when is_float(V) ->
+ %% io:format("Testing ~.17g~n", [V]),
+ Io = io_lib:format("~p", [V]),
+ Sv = binary_to_list(iolist_to_binary(Io)),
+ ok = g_t(V, Sv),
+ Sv.
+
+%% -> ok | THROW
+
+%% Checks that Sv is the shortest, correctly rounded string that
+%% converts to V when read back with list_to_float/1.
+%% Note: in a few cases the least significant digit has been
+%% incremented by one, namely when the correctly rounded string
+%% converts to another floating point number.
+g_t(0.0, "0.0") ->
+ ok;
+g_t(V, Sv) ->
+ try
+ g_t_1(V, Sv)
+ catch throw:Reason ->
+ throw({Reason, V, Sv})
+ end.
+
+g_t_1(V, Sv) ->
+ %% Check that the least significant digit is correct.
+ %% If Sv is "3.14" then Sv- is "3.13" and Sv+ is "3.15".
+ %% Check that |V - Sv| =< (V - Sv-) and
+ %% that |V - Sv| =< (Sv+ - V)
+ Times = least_significant_digit(Sv),
+ case Times of
+ 0 -> throw(least_significant_digit_is_zero);
+ _ -> ok
+ end,
+ S = if V < 0 -> -1; true -> 1 end,
+ SvMinus = incr_lsd(Sv, -S),
+ SvPlus = incr_lsd(Sv, S),
+ Svr = s2r(Sv),
+ Svminusr = s2r(SvMinus),
+ Svplusr = s2r(SvPlus),
+ Vr = f2r(V),
+
+ Abs_Sv_Vr = rat_abs(rat_minus(Svr, Vr)),
+ Svminus_Vr = rat_minus(Vr, Svminusr),
+ Svplus_Vr = rat_minus(Svplusr, Vr),
+ %% The are 45 (negative) floats where SvMinus (SvPlus) is closer
+ %% to V than Sv, but such that when reading SvMinus (SvPlus) wrong
+ %% float would be returned.
+ case rat_lte(Abs_Sv_Vr, Svminus_Vr) of
+ true ->
+ ok;
+ false ->
+ case list_to_float(SvMinus) of
+ V -> throw(vsminus_too_close_to_v);
+ _Vminus -> ok
+ end
+ end,
+ case rat_lte(Abs_Sv_Vr, Svplus_Vr) of
+ true ->
+ ok;
+ false ->
+ case list_to_float(SvPlus) of
+ V -> throw(vsplus_too_close_to_v);
+ _Vplus -> ok
+ end
+ end,
+
+ %% Check that Sv is closer to V than to V- and V+.
+ %% Check that |V - Sv| =< (V - V-) and
+ %% that |V - Sv| =< (V+ - V)
+ %% (An alternative is V- + V =< 2*Sv =< V + V+.)
+ case inc(V) of
+ inf ->
+ ok;
+ Vplus ->
+ Vplusr = f2r(Vplus),
+ V_Vplusr = rat_minus(Vplusr, Vr),
+ case rat_lte(Abs_Sv_Vr, V_Vplusr) of
+ true -> ok;
+ false -> throw(vplus_too_close_to_sv)
+ end
+ end,
+ case dec(V) of
+ '-inf' ->
+ ok;
+ Vminus ->
+ Vminusr = f2r(Vminus),
+ V_Vminusr = rat_minus(Vr, Vminusr),
+ case rat_lte(Abs_Sv_Vr, V_Vminusr) of
+ true -> ok;
+ false -> throw(vminus_too_close_to_sv)
+ end
+ end,
+
+ %% Check that no prefix of Sv yields V.
+ %% If Sv is "3.14" then Svlow is "3.1" and Svhigh is "3.2".
+ %%
+ %% This is just one way of getting Svlow and Svhigh:
+ if
+ V < 0 ->
+ SvHigh = step_lsd(Sv, -Times),
+ SvLow = step_lsd(Sv, 10 - Times);
+ true ->
+ SvHigh = step_lsd(Sv, 10 - Times),
+ SvLow = step_lsd(Sv, -Times)
+ end,
+
+ case catch list_to_float(SvLow) of
+ V -> throw(low_is_v);
+ _ -> ok
+ end,
+
+ case catch list_to_float(SvHigh) of
+ V -> throw(high_is_v);
+ _ -> ok
+ end,
+
+ %% Check that Sv has enough digits.
+ case list_to_float(Sv) of
+ V -> ok;
+ _ -> throw(wrong_float) % cannot happen
+ end,
+
+ g_choice(Sv),
+
+ ok.
+
+%%% In "123450000.0", '5' is the lsd;
+%%% in "1234.0000", (the last) '0' is the lsd;
+%%% in "1234.0", '4' is the lsd (the Erlang syntax requires the final zero).
+
+%% Trailing zeroes are not significant ("3.0", "5.0e-324", "232000.0").
+least_significant_digit("-"++Ds) ->
+ least_significant_digit(Ds);
+least_significant_digit("+"++Ds) ->
+ least_significant_digit(Ds);
+least_significant_digit(Ds) ->
+ [MS|_E] = string:tokens(Ds, "eE"),
+ lsd0(lists:reverse(MS))-$0.
+
+lsd0("0."++Ds) ->
+ lsd1(Ds);
+lsd0([D | _Ds]) ->
+ D.
+
+lsd1("0"++Ds) ->
+ lsd1(Ds);
+lsd1([D | _Ds]) ->
+ D.
+
+step_lsd(Ds, 0) ->
+ Ds;
+step_lsd(Ds, N) when N > 0 ->
+ NDs = incr_lsd(Ds, 1),
+ step_lsd(NDs, N - 1);
+step_lsd(Ds, N) when N < 0 ->
+ NDs = incr_lsd(Ds, -1),
+ step_lsd(NDs, N + 1).
+
+%% Assumes Ds represents some other number than zero.
+%% Increments or decrements the least significant digit.
+incr_lsd("-"++Ds, I) ->
+ "-"++incr_lsd(Ds, I);
+incr_lsd(Ds, I) when I =:= 1; I =:= -1 ->
+ [MS|E] = string:tokens(Ds, "eE"),
+ X = ["e" || true <- [E =/= []]],
+ lists:flatten([incr_lsd0(lists:reverse(MS), I, []), X, E]).
+
+incr_lsd0("0."++Ds, C, L) ->
+ incr_lsd1(Ds, C, [$., $0 | L]);
+incr_lsd0(Ds, C, L) ->
+ incr_lsd2(Ds, C, L).
+
+incr_lsd1("0"++Ds, C, L) ->
+ incr_lsd1(Ds, C, [$0 | L]);
+incr_lsd1(Ds, C, L) ->
+ incr_lsd2(Ds, C, L).
+
+incr_lsd2([], C, L) ->
+ [C + $0 | L];
+incr_lsd2("."++Ds, C, L) ->
+ incr_lsd2(Ds, C, [$. | L]);
+incr_lsd2("9"++Ds, 1=C, L) ->
+ incr_lsd2(Ds, C, [$0 | L]);
+incr_lsd2("0"++Ds, -1=C, L) ->
+ incr_lsd2(Ds, C, [$9 | L]);
+incr_lsd2([D | Ds], C, L) ->
+ lists:reverse(Ds, [D + C | L]).
+
+s2r(S) when is_list(S) ->
+ case string:tokens(S, "eE") of
+ [MS] ->
+ s10(MS);
+ [MS, ES] ->
+ Mr = s10(MS),
+ E = list_to_integer(ES),
+ if
+ E < 0 ->
+ rat_multiply(Mr, {1,pow10(-E)});
+ true ->
+ rat_multiply(Mr, {pow10(E), 1})
+ end
+ end.
+
+s10("-"++S) ->
+ rat_multiply({-1,1},s10(S));
+s10(S) ->
+ [AS, BS] = string:tokens(S, "."),
+ Sc = length(BS),
+ A = list_to_integer(AS),
+ B = list_to_integer(BS),
+ F = pow10(Sc),
+ rat_multiply({1,1}, {A*F + B, F}).
+
+pow10(X) ->
+ int_pow(10, X).
+
+int_pow(X, 0) when is_integer(X) ->
+ 1;
+int_pow(X, N) when is_integer(X), is_integer(N), N > 0 ->
+ int_pow(X, N, 1).
+
+int_pow(X, N, R) when N < 2 ->
+ R * X;
+int_pow(X, N, R) ->
+ int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end).
+
+dec(F) when is_float(F) ->
+ <<S:1, BE:11, M:52>> = <<F:64/float>>,
+ dec({S,BE,M});
+dec({1,2046,?ALL_ONES}) ->
+ '-inf';
+dec({S,BE,M}) when 0 =< S, S =< 1,
+ 0 =< BE, BE =< 2046,
+ 0 =< M, M =< ?ALL_ONES ->
+ {S1,BE1,M1} = dec1(S, BE, M),
+ <<F:64/float>> = <<S:1, BE:11, M:52>>,
+ <<F1:64/float>> = <<S1:1, BE1:11, M1:52>>,
+ true = F1 < F,
+ F1.
+
+
+dec1(0, 0, 0) ->
+ dec1(1, 0, 0);
+dec1(0, BE, 0) ->
+ {0,BE-1,?ALL_ONES};
+dec1(0, BE, M) ->
+ {0,BE,M-1};
+dec1(1, BE, ?ALL_ONES) ->
+ {1,BE+1,0};
+dec1(1, BE, M) ->
+ {1,BE,M+1}.
+
+inc(F) when is_float(F) ->
+ <<S:1, BE:11, M:52>> = <<F:64/float>>,
+ inc({S,BE,M});
+inc({0,2046,?ALL_ONES}) ->
+ inf;
+inc({S,BE,M}) when 0 =< S, S =< 1,
+ 0 =< BE, BE =< 2046,
+ 0 =< M, M =< ?ALL_ONES ->
+ {S1,BE1,M1} = inc1(S, BE, M),
+ <<F:64/float>> = <<S:1, BE:11, M:52>>,
+ <<F1:64/float>> = <<S1:1, BE1:11, M1:52>>,
+ true = F1 > F,
+ F1.
+
+inc1(0, BE, ?ALL_ONES) ->
+ {0,BE+1,0};
+inc1(0, BE, M) ->
+ {0,BE,M+1};
+inc1(1, 0, 0) ->
+ inc1(0, 0, 0);
+inc1(1, BE, 0) ->
+ {1,BE-1,?ALL_ONES};
+inc1(1, BE, M) ->
+ {1,BE,M-1}.
+
+f2r(F) when is_float(F) ->
+ <<S:1, BE:11, M:52>> = <<F:64/float>>,
+ f2r({S,BE,M});
+f2r({S,BE,M}) when 0 =< S, S =< 1,
+ 0 =< BE, BE =< 2046,
+ 0 =< M, M =< ?ALL_ONES ->
+ Vr = {T,N} = f2r1(S, BE, M),
+ <<F:64/float>> = <<S:1, BE:11, M:52>>,
+ case catch T/N of
+ {'EXIT', _} -> ok;
+ TN -> true = F =:= TN
+ end,
+ Vr.
+
+f2r1(S, 0, M) ->
+ rat_multiply({sign(S),1}, {M, 1 bsl 1074});
+f2r1(S, BE, M) when BE - 1075 >= 0 ->
+ rat_multiply({sign(S),1}, {((1 bsl 52)+M) * (1 bsl (BE-1075)),1});
+f2r1(S, BE, M) ->
+ rat_multiply({sign(S),1}, {(1 bsl 52)+M, 1 bsl (1075-BE)}).
+
+sign(0) ->
+ 1;
+sign(1) ->
+ -1.
+
+%%% Rational numbers (very scetchy).
+
+rat_abs({A,B}) when A < 0 ->
+ {-A,B};
+rat_abs({A,B}) ->
+ {A,B}.
+
+-ifdef(not_used).
+rat_equal(R1, R2) ->
+ R1 =:= R2.
+
+rat_negate({A,B}) ->
+ {-A,B}.
+
+rat_divide({A,B},{C,D}) ->
+ rat_multiply({A,B},{D,C}).
+-endif.
+
+rat_lte({A,B}, {C,D}) when B =/= 0, D =/= 0 ->
+ A*D =< C*B.
+
+rat_minus({A,B}, {C,D}) ->
+ rat_plus({A,B}, {-C,D}).
+
+rat_plus({A,B}, {C,D}) when B =/= 0, D =/= 0 ->
+ rat_normalize({A*D+B*C, B*D}).
+
+rat_multiply({A,B}, {C,D}) when B =/= 0, D =/= 0 ->
+ rat_normalize({A * C, B * D}).
+
+rat_normalize({T,N}) when N =/= 0 ->
+ G = gcd(T, N),
+ T2 = T div G,
+ N2 = N div G,
+ if
+ T2 < 0 ->
+ if
+ N2 < 0 -> {-T2,-N2};
+ true -> {T2,N2}
+ end;
+ true ->
+ if
+ N2 < 0 -> {-T2,-N2};
+ true -> {T2,N2}
+ end
+ end.
+
+gcd(A, 0) -> A;
+gcd(A, B) -> gcd(B, A rem B).
+
+%%% End of rational numbers.
+
+%% Check that there is an exponent if and only if characters are
+%% saved. Note: this assumes floating point numbers "Erlang style"
+%% (with a single zero before and after the dot, and no extra leading
+%% zero in the exponent).
+g_choice(S) when is_list(S) ->
+ [MS | ES0] = string:tokens(S, "eE"),
+ [IS, FS] = string:tokens(MS, "."),
+ Il = length(IS),
+ Fl = length(FS),
+ Pre = z(MS),
+ Post = z(lists:reverse(MS)),
+ ES = lists:append(ES0),
+ El = length(ES),
+ I = list_to_integer(IS),
+ if
+ El =/= 0, ((I > 9) or (I < -9)) ->
+ throw(too_many_digits_before_the_dot);
+ El =/= 0, I =:= 0 ->
+ throw(zero_before_the_dot);
+ Pre =:= 0, Post > 0, El =:= 0 -> % DDDD0000.0
+ Saving = if
+ I < 0, Il =:= Post + 2 ->
+ Post;
+ I > 0, Il =:= Post + 1 ->
+ Post;
+ I =/= 0, true ->
+ Post + 1
+ end,
+ Cost = 1 + length(integer_to_list(Il - 1)),
+ if
+ Cost < Saving ->
+ throw(with_exponent_is_shorter);
+ true ->
+ ok
+ end;
+ Pre > 0, Post =:= 0, El =:= 0 -> % 0.000DDDD
+ Saving = if
+ Fl =:= Pre + 1 ->
+ Pre;
+ true ->
+ Pre + 1
+ end,
+ Cost = 2 + length(integer_to_list(Pre + 1)),
+ if
+ Cost < Saving ->
+ throw(with_exponent_is_shorter);
+ true ->
+ ok
+ end;
+ Pre =:= 0, Post =:= 0, El > 0 -> % D.DDDeDD
+ E = list_to_integer(ES),
+ if
+ E >= 0 ->
+ Cost = E - (Fl - 1);
+ E < 0 ->
+ Cost = -E
+ end,
+ Saving = length(ES) + 1,
+ if
+ Cost =:= Saving ->
+ throw(draw_but_choose_form_without_exponent);
+ Cost < Saving ->
+ throw(without_exponent_is_shorter);
+ true ->
+ ok
+ end;
+ Pre =:= 0, Post =:= 0, El =:= 0 -> % DDD.DDD
+ ok;
+ true ->
+ throw(badly_formed_floating_point_string)
+ end.
+
+z("0."++Ds) ->
+ length(lists:takewhile(fun(D) -> D =:= $0 end, Ds));
+z(_Ds) ->
+ 0.
+
+pack(Sign, Exp, Frac) ->
+ <<Float:64/float>> = <<Sign:1, Exp:11, Frac:52>>,
+ Float.
+
+%% Whitebox test of io_lib:collect_line/3.
+io_lib_collect_line_3_wb(Config) when is_list(Config) ->
+ ?line do_collect_line(binary, "\n"),
+ ?line do_collect_line(binary, "\r\n"),
+ ?line do_collect_line(list, "\n"),
+ ?line do_collect_line(list, "\r\n"),
+ ok.
+
+do_collect_line(Mode, Eol) ->
+ First = "abcde",
+ FirstNL = First++"\n",
+ Second = "unterminated line",
+ Data0 = First ++ Eol ++ Second,
+ {Data1,Result0} = do_collect_line_combine(Mode, Data0, FirstNL, Second),
+ do_collect_line_1(Mode, Data1, Result0, []),
+
+ {Data,Result} = do_collect_line_combine(Mode, "unterm", "unterm", eof),
+ do_collect_line_1(Mode, Data, Result, []).
+
+do_collect_line_combine(binary, Data0, FirstNL, eof) ->
+ {list_to_binary(Data0),
+ {stop,list_to_binary(FirstNL),eof}};
+do_collect_line_combine(binary, Data0, FirstNL, Second) ->
+ {list_to_binary(Data0),
+ {stop,list_to_binary(FirstNL),list_to_binary(Second)}};
+do_collect_line_combine(list, Data0, FirstNL, Second) ->
+ {Data0,{stop,FirstNL,Second}}.
+
+do_collect_line_1(Mode, [H|T], Result, Acc0) ->
+ Acc = [H|Acc0],
+ Result = do_collect_line_2(lists:reverse(Acc), T),
+ do_collect_line_1(Mode, T, Result, Acc);
+do_collect_line_1(Mode, <<H,T/binary>>, Result, Acc0) ->
+ Acc = [H|Acc0],
+ Result = do_collect_line_2(list_to_binary(lists:reverse(Acc)), T),
+ do_collect_line_1(Mode, T, Result, Acc);
+do_collect_line_1(_Mode, [], _Result, _Acc) ->
+ ok;
+do_collect_line_1(_Mode, <<>>, _Result, _Acc) ->
+ ok.
+
+do_collect_line_2(Part1, Part2) ->
+ Dummy = make_ref(),
+ do_collect_line_3(start, [Part1,Part2,eof], Dummy).
+
+do_collect_line_3(State0, [H|T], Dummy) ->
+ case io_lib:collect_line(State0, H, Dummy) of
+ {stop,Line,Rest} ->
+ {stop,Line,do_collect_line_adjust_rest(Rest, T)};
+ State ->
+ do_collect_line_3(State, T, Dummy)
+ end.
+
+do_collect_line_adjust_rest(eof, []) -> eof;
+do_collect_line_adjust_rest(eof, <<>>) -> eof;
+do_collect_line_adjust_rest(Rest, [eof]) -> Rest;
+do_collect_line_adjust_rest(Rest, [Bin|T]) when is_binary(Bin) ->
+ do_collect_line_adjust_rest(<<Rest/binary,Bin/binary>>, T);
+do_collect_line_adjust_rest(Rest, [List|T]) when is_list(List) ->
+ do_collect_line_adjust_rest(Rest++List, T).
+
+
+
+cr_whitespace_in_string(Config) when is_list(Config) ->
+ ?line {ok,["abc"],[]} = io_lib:fread("~s", "\rabc").
+
+
+
+io_fread_newlines(Config) when is_list(Config) ->
+ ?line PrivDir = ?privdir(Config),
+ ?line Fname = filename:join(PrivDir, "io_fread_newlines.txt"),
+ ?line F0 = [[0,1,2,3,4,5,6,7,8,9]],
+ ?line F1 = [[0,1,2,3,4,5,6,7,8],[9]],
+ ?line F2 = [[0,1,2,3,4,5,6,7],[8,9]],
+ ?line F3 = [[0,1,2,3,4,5,6],[7,8,9]],
+ ?line F4 = [[0,1,2,3,4,5],[6,7,8,9]],
+ ?line F5 = [[0,1,2,3,4],[5,6,7,8,9]],
+ ?line F6 = [[0,1,2,3],[4,5,6,7],[8,9]],
+ ?line F7 = [[0,1,2],[3,4,5],[6,7,8],[9]],
+ ?line F8 = [[0,1],[2,3],[4,5],[6,7],[8,9]],
+ ?line F9 = [[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]],
+ ?line Newlines = ["\n", "\r\n", "\r"],
+ try
+ ?line io_fread_newlines_1([F0,F1,F2,F3,F4,F5,F6,F7,F8,F9],
+ Fname, Newlines)
+ after
+ file:delete(Fname)
+ end.
+
+io_fread_newlines_1(Fs, Fname, [Newline|Newlines]) ->
+ ?line ok = io_fread_newlines_2(Fs, Fname, Newline),
+ ?line io_fread_newlines_1(Fs, Fname, Newlines);
+io_fread_newlines_1(_, _, []) -> ok.
+
+io_fread_newlines_2([F|Fs], Fname, Newline) ->
+ ?line N1 = write_newlines_file(Fname, F, Newline),
+ ?line {F2,N2} = read_newlines_file(Fname),
+ ?line io:format("~w ~p ~w~n~n", [N1,F,N2]),
+ ?line F2 = lists:flatten(F),
+ %% Intermediate newlines are not counted
+ ?line N2 = N1 - (length(F) - 1)*length(Newline),
+ ?line io_fread_newlines_2(Fs, Fname, Newline);
+io_fread_newlines_2([], _, _) -> ok.
+
+
+write_newlines_file(Fname, F, Newline) ->
+ Bytes = list_to_binary(digit_lines(F, Newline)),
+ io:format("Data: ~w~n~w~n", [Newline,Bytes]),
+ ok = file:write_file(Fname, Bytes),
+ size(Bytes).
+
+digit_lines([L], _) ->
+ digit_line(L);
+digit_lines([L|Ls], Newline) ->
+ [digit_line(L),Newline|digit_lines(Ls, Newline)].
+
+digit_line([D]) ->
+ integer_to_list(D);
+digit_line([D|Ds]) ->
+ [integer_to_list(D), " ", digit_line(Ds)].
+
+read_newlines_file(Fname) ->
+ {ok,Fd} = file:open(Fname, [read]),
+ try {L, N0} = R0 = read_newlines(Fd, [], 0),
+ case io:fread(Fd, "", "~*s~l") of
+ eof -> R0;
+ {ok,[N]} -> {L,N0+N}
+ end
+ after
+ file:close(Fd)
+ end.
+
+
+read_newlines(Fd, Acc, N0) ->
+ case io:fread(Fd, "", "~d~l") of
+ {ok,[D,N]} ->
+ read_newlines(Fd, [D|Acc], N0+N);
+ eof ->
+ {lists:reverse(Acc),N0}
+ end.
diff --git a/lib/stdlib/test/io_proto_SUITE.erl b/lib/stdlib/test/io_proto_SUITE.erl
new file mode 100644
index 0000000000..46407193d7
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE.erl
@@ -0,0 +1,1824 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(io_proto_SUITE).
+
+-export([all/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-export([setopts_getopts/1,unicode_options/1,unicode_options_gen/1, binary_options/1, bc_with_r12/1,
+ bc_with_r12_gl/1, read_modes_gl/1,bc_with_r12_ogl/1, read_modes_ogl/1, broken_unicode/1,eof_on_pipe/1]).
+
+
+-export([io_server_proxy/1,start_io_server_proxy/0, proxy_getall/1, proxy_setnext/2, proxy_quit/1]).
+%% For spawn
+-export([toerl_server/3,hold_the_line/3,answering_machine1/3,
+ answering_machine2/3]).
+
+%-define(without_test_server, true).
+
+-ifdef(without_test_server).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(t, test_server).
+-define(privdir(_), "./io_SUITE_priv").
+-else.
+-include("test_server.hrl").
+-define(privdir(Conf), ?config(priv_dir, Conf)).
+-endif.
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(dbg(Data),io:format(standard_error, "DBG: ~p\r\n",[Data])).
+-else.
+-define(format(S, A), ok).
+-define(dbg(Data),noop).
+-endif.
+
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(20)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ Term = case os:getenv("TERM") of
+ List when is_list(List) ->
+ List;
+ _ ->
+ "dumb"
+ end,
+ os:putenv("TERM","vt100"),
+ [{watchdog, Dog}, {term, Term} | Config].
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ Term = ?config(term,Config),
+ os:putenv("TERM",Term),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(doc) ->
+ ["Test cases for the io_protocol."];
+all(suite) ->
+ [setopts_getopts, unicode_options, unicode_options_gen, binary_options, bc_with_r12,
+ bc_with_r12_gl,bc_with_r12_ogl, read_modes_gl, read_modes_ogl,
+ broken_unicode,eof_on_pipe].
+
+
+-record(state, {
+ q = [],
+ nxt = eof,
+ mode = list
+ }).
+
+setopts_getopts(suite) ->
+ [];
+setopts_getopts(doc) ->
+ ["Check io:setopts and io:getopts functions"];
+setopts_getopts(Config) when is_list(Config) ->
+ ?line FileName = filename:join([?config(priv_dir,Config),
+ "io_proto_SUITE_setopts_getopts.dat"]),
+ ?line {ok,WFile} = file:open(FileName,[write]),
+ ?line Server = start_io_server_proxy(),
+ ?line [{binary, false}] = io:getopts(Server),
+ ?line [getopts] = proxy_getall(Server),
+ ?line [{binary,false},{encoding,latin1}] = lists:sort(io:getopts(WFile)),
+ ?line proxy_setnext(Server,"Hej"),
+ ?line "Hej" = io:get_line(Server,''),
+ ?line proxy_setnext(Server,"Hej"++[532]),
+ ?line [$H,$e,$j,532] = io:get_line(Server,''),
+ ?line ok = io:setopts(Server,[{binary,true}]),
+ ?line proxy_setnext(Server,"Hej"),
+ ?line <<"Hej">> = io:get_line(Server,''),
+ ?line proxy_setnext(Server,"Hej"++[532]),
+ ?line <<72,101,106,200,148>> = io:get_line(Server,''),
+ ?line [$H,$e,$j,532] = lists:flatten(io_lib:format("~ts",[<<72,101,106,200,148>>])),
+ ?line file:write(WFile,<<"HejA">>),
+ ?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,unicode)),
+ ?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf16,big})),
+ ?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf16,little})),
+ ?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf32,big})),
+ ?line file:write(WFile,unicode:characters_to_binary("Hej"++[532],unicode,{utf32,little})),
+ ?line file:close(WFile),
+ ?line {ok,RFile} = file:open(FileName,[read]),
+ ?line [{binary,false},{encoding,latin1}] = lists:sort(io:getopts(RFile)),
+ ?line [$H,$e,$j,$A] = io:get_chars(RFile,'',4),
+ ?line io:setopts(RFile,[{encoding,unicode}]),
+ ?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
+ ?line [{binary,false},{encoding,unicode}] = lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf16,big}}]),
+ ?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
+ ?line [{binary,false},{encoding,{utf16,big}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf16,little}}]),
+ ?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
+ ?line [{binary,false},{encoding,{utf16,little}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf32,big}}]),
+ ?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
+ ?line [{binary,false},{encoding,{utf32,big}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf32,little}}]),
+ ?line [$H,$e,$j,532] = io:get_chars(RFile,'',4),
+ ?line [{binary,false},{encoding,{utf32,little}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line eof = io:get_line(RFile,''),
+ ?line file:position(RFile,0),
+ ?line io:setopts(RFile,[{binary,true},{encoding,latin1}]),
+ ?line <<$H,$e,$j,$A>> = io:get_chars(RFile,'',4),
+ ?line [{binary,true},{encoding,latin1}] = lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,unicode}]),
+ ?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
+ ?line [{binary,true},{encoding,unicode}] = lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf16,big}}]),
+ ?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
+ ?line [{binary,true},{encoding,{utf16,big}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf16,little}}]),
+ ?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
+ ?line [{binary,true},{encoding,{utf16,little}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf32,big}}]),
+ ?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
+ ?line [{binary,true},{encoding,{utf32,big}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line io:setopts(RFile,[{encoding,{utf32,little}}]),
+ ?line <<$H,$e,$j,532/utf8>> = io:get_chars(RFile,'',4),
+ ?line [{binary,true},{encoding,{utf32,little}}] =
+ lists:sort(io:getopts(RFile)),
+ ?line eof = io:get_line(RFile,''),
+ ?line file:close(RFile),
+ %% So, lets test another node with new interactive shell
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline, "2"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {getline, "{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline, "\"hej\\n\""},
+ {putline, "io:setopts([{binary,true}])."},
+ {getline, "ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline, "<<\"hej\\n\">>"}
+ ],[]),
+ %% And one with oldshell
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline_re, ".*2."},
+ {getline, "2"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {getline_re, ".*{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline_re, ".*\"hej\\\\n\""},
+ {putline, "io:setopts([{binary,true}])."},
+ {getline_re, ".*ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline_re, ".*<<\"hej\\\\n\">>"}
+ ],[],[],"-oldshell"),
+ ok.
+
+unicode_options(suite) ->
+ [];
+unicode_options(doc) ->
+ ["Tests various unicode options"];
+unicode_options(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir,Config),
+ PrivDir = ?config(priv_dir,Config),
+ %% A string in both russian and greek characters, which is present
+ %% in all the internal test files (but in different formats of course)...
+ TestData = [1090,1093,1077,32,1073,1080,1075,32,
+ 1088,1077,1076,32,1092,1086,1100,32,1093,
+ 1072,1089,32,1089,1086,1100,32,932,951,949,
+ 32,946,953,947,32,961,949,948,32,
+ 963,959,967,32,945,961,949,32,966,959,967,949,963],
+ %% Testdata from Chinese open source customer, that triggered OTP-7974
+ TestData2 = [46,46,46,12411,12370,12411,12370,44,12411,12370,12411,12370,44,
+ 12411,12370,12411,12370,44,12411,12370,12411,12370,44,12411,12370,
+ 12411,12370,44,44,44,12411,12370,12411,12370,44,44,12411,12370,12411,
+ 12370,44,12411,12370,12411,12370,44,12411,12370,12411,12370,44,12411,
+ 12370,12411,12370,44,12411,12370,12411,12370,44,44,44,10],
+
+ %% The external test files are generated with a BOM writing
+ %% text editor. A shorter line is written (with two characters
+ %% larger than 127).
+ ExternalTestData = [197,116,101,114,101,114,246,118,114,97],
+ InternalBomFiles = ["testdata_utf8_bom.dat",
+ "testdata_utf16_big_bom.dat",
+ "testdata_utf16_little_bom.dat",
+ "testdata_utf32_big_bom.dat",
+ "testdata_utf32_little_bom.dat"],
+ AllNoBom = [{utf8,"testdata_utf8.dat"},
+ {utf16,"testdata_utf16_big.dat"},
+ {{utf16,big},"testdata_utf16_big.dat"},
+ {{utf16,little},"testdata_utf16_little.dat"},
+ {utf32,"testdata_utf32_big.dat"},
+ {{utf32,big},"testdata_utf32_big.dat"},
+ {{utf32,little},"testdata_utf32_little.dat"}],
+ ExternalBomFiles = ["external_utf8_bom.dat",
+ "external_utf16_little_bom.dat",
+ "external_utf16_big_bom.dat"],
+ ReadBomFile = fun(File,Dir) ->
+ %io:format(standard_error,"~s\r\n",[filename:join([Dir,File])]),
+ {ok,F} = file:open(filename:join([Dir,File]),
+ [read,binary]),
+ {ok,Bin} = file:read(F,4),
+ {Type,Bytes} = unicode:bom_to_encoding(Bin),
+ %io:format(standard_error,"~p\r\n",[{Type,Bytes}]),
+
+ file:position(F,Bytes),
+ io:setopts(F,[{encoding,Type}]),
+ R = unicode:characters_to_list(
+ io:get_chars(F,'',length(TestData)),unicode),
+ file:close(F),
+ R
+ end,
+ ReadBomlessFile = fun({Type,File},DataLen,Dir) ->
+ {ok,F} = file:open(filename:join([Dir,File]),
+ [read,binary,
+ {encoding,Type}]),
+ R = unicode:characters_to_list(
+ io:get_chars(F,'',DataLen),unicode),
+ file:close(F),
+ R
+ end,
+ ReadBomlessFileList = fun({Type,File},DataLen,Dir) ->
+ {ok,F} = file:open(filename:join([Dir,File]),
+ [read,
+ {encoding,Type}]),
+ R = io:get_chars(F,'',DataLen),
+ file:close(F),
+ R
+ end,
+ ReadBomlessFileListLine = fun({Type,File},Dir) ->
+ {ok,F} = file:open(filename:join([Dir,File]),
+ [read,
+ {encoding,Type}]),
+ R = io:get_line(F,''),
+ file:close(F),
+ R
+ end,
+ ?line [TestData = ReadBomFile(F,DataDir) || F <- InternalBomFiles ],
+ ?line [ExternalTestData = ReadBomFile(F,DataDir) || F <- ExternalBomFiles ],
+ ?line [TestData = ReadBomlessFile(F,length(TestData),DataDir) || F <- AllNoBom ],
+ ?line [TestData = ReadBomlessFileList(F,length(TestData),DataDir) || F <- AllNoBom ],
+ ?line [TestData = ReadBomlessFileListLine(F,DataDir) || F <- AllNoBom ],
+
+ BomDir = filename:join([PrivDir,"BOMDATA"]),
+ BomlessDir = filename:join([PrivDir,"BOMLESSDATA"]),
+ file:make_dir(BomDir),
+ file:make_dir(BomlessDir),
+
+ WriteBomFile = fun({Enc,File},Dir) ->
+ {ok,F} = file:open(filename:join([Dir,File]),
+ [write,binary]),
+ file:write(F,unicode:encoding_to_bom(Enc)),
+ io:setopts(F,[{encoding,Enc}]),
+ io:put_chars(F,TestData),
+ file:close(F),
+ ok
+ end,
+ ?line [ ok = WriteBomFile(F,BomDir) || F <- AllNoBom ],
+ ?line [TestData = ReadBomFile(F,BomDir) || {_,F} <- AllNoBom ],
+ WriteBomlessFile = fun({Enc,File},TData,Dir) ->
+ {ok,F} = file:open(
+ filename:join([Dir,File]),
+ [write,binary,{encoding,Enc}]),
+ io:put_chars(F,TData),
+ file:close(F),
+ ok
+ end,
+ ?line [ ok = WriteBomlessFile(F,TestData,BomlessDir) || F <- AllNoBom ],
+ ?line [TestData = ReadBomlessFile(F,length(TestData),BomlessDir) || F <- AllNoBom ],
+ ?line [TestData = ReadBomlessFileList(F,length(TestData),BomlessDir) || F <- AllNoBom ],
+ ?line [TestData = ReadBomlessFileListLine(F,BomlessDir) || F <- AllNoBom ],
+
+ CannotReadFile = fun({Enc,File},Dir) ->
+ %io:format(standard_error,"~s\r\n",[filename:join([Dir,File])]),
+ {ok,F} = file:open(
+ filename:join([Dir,File]),
+ [read,binary,{encoding,Enc}]),
+ Enc2 = case Enc of
+ utf8 ->
+ unicode;
+ Tpl when is_tuple(Tpl) ->
+ Tpl;
+ Atom when is_atom(Atom) ->
+ {Atom, big}
+ end,
+ {error, {no_translation,Enc2,latin1}} =
+ file:read(F,10),
+ {error,terminated} = io:get_chars(F,'',10),
+ ok
+ end,
+ ?line [ ok = CannotReadFile(F,DataDir) || F <- AllNoBom ],
+ ?line [ ok = CannotReadFile(F,BomlessDir) || F <- AllNoBom ],
+ ?line [ ok = CannotReadFile(F,BomDir) || F <- AllNoBom ],
+
+ ?line [ ok = WriteBomlessFile(F,TestData2,BomlessDir) || F <- AllNoBom ],
+ ?line [TestData2 = ReadBomlessFile(F,length(TestData2),BomlessDir) || F <- AllNoBom ],
+ ?line [TestData2 = ReadBomlessFileList(F,length(TestData2),BomlessDir) || F <- AllNoBom ],
+ ?line [TestData2 = ReadBomlessFileListLine(F,BomlessDir) || F <- AllNoBom ],
+
+
+ FailDir = filename:join([PrivDir,"FAIL"]),
+ file:make_dir(FailDir),
+
+ CannotWriteFile = fun({_Enc,File},Dir) ->
+ {ok,F} = file:open(
+ filename:join([Dir,File]),
+ [write,binary]),
+ ?line {'EXIT', {no_translation,_}} =
+ (catch io:put_chars(F,TestData)),
+ ?line {'EXIT', {terminated,_}} = (catch io:put_chars(F,TestData)),
+ ok
+ end,
+ ?line [ ok = CannotWriteFile(F,FailDir) || F <- AllNoBom ],
+
+ %% OK, time for the group_leaders...
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline, "2"},
+ {putline, "lists:keyfind(encoding,1,io:getopts())."},
+ {getline, "{encoding,latin1}"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {getline, "\\x{400}"},
+ {putline, "io:setopts([unicode])."},
+ {getline, "ok"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {getline,
+ binary_to_list(unicode:characters_to_binary(
+ [1024],unicode,utf8))}
+ ],[],"LC_CTYPE=\"ISO-8859-1\"; export LC_CTYPE; "),
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline_re, ".*2."},
+ {getline, "2"},
+ {putline, "lists:keyfind(encoding,1,io:getopts())."},
+ {getline_re, ".*{encoding,latin1}"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {getline_re, ".*\\\\x{400\\}"},
+ {putline, "io:setopts([{encoding,unicode}])."},
+ {getline_re, ".*ok"},
+ {putline, "io:format(\"~ts~n\",[[1024]])."},
+ {getline_re,
+ ".*"++binary_to_list(unicode:characters_to_binary(
+ [1024],unicode,utf8))}
+ ],[],"LC_CTYPE=\"ISO-8859-1\"; export LC_CTYPE; ",
+ " -oldshell "),
+
+ ok.
+
+unicode_options_gen(suite) ->
+ [];
+unicode_options_gen(doc) ->
+ ["Tests various unicode options on random generated files"];
+unicode_options_gen(Config) when is_list(Config) ->
+ ?line random:seed(1240,900586,553728),
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line AllModes = [utf8,utf16,{utf16,big},{utf16,little},utf32,{utf32,big},{utf32,little}],
+ ?line FSize = 17*1024,
+ ?line NumItersRead = 2,
+ ?line NumItersWrite = 2,
+ ?line Dir = filename:join([PrivDir,"GENDATA1"]),
+ ?line file:make_dir(Dir),
+
+ %dbg:tracer(process,{fun(A,_) -> erlang:display(A) end,true}),
+ %dbg:tpl(file_io_server,x),
+ %dbg:ctpl(file_io_server,cafu),
+ %dbg:tp(unicode,x),
+
+ DoOneFile1 = fun(Encoding,N,M) ->
+ ?dbg({Encoding,M,N}),
+ io:format("Read test: Encoding ~p, Chunk size ~p, Iteration ~p~n",[Encoding,M,N]),
+ io:format(standard_error,"Read test: Encoding ~p, Chunk size ~p, Iteration ~p\r\n",[Encoding,M,N]),
+ ?line Fname = filename:join([Dir,"genfile_"++enc2str(Encoding)++"_"++integer_to_list(N)]),
+ ?dbg(?LINE),
+ ?line Ulist = random_unicode(FSize),
+ ?dbg(?LINE),
+ ?line my_write_file(Fname,Ulist,Encoding),
+ ?dbg(?LINE),
+ ?line {ok,F1} = file:open(Fname,[read,{encoding,Encoding}]),
+
+ ?dbg(?LINE),
+ ?line Res1 = read_whole_file(fun(FD) -> io:get_line(FD,'') end,F1),
+ ?dbg(?LINE),
+ ?line Ulist = unicode:characters_to_list(Res1,unicode),
+ ?dbg(?LINE),
+ ?line file:close(F1),
+ ?line {ok,F2} = file:open(Fname, [read,binary,{encoding,Encoding}]),
+ ?line Res2 = read_whole_file(fun(FD) -> io:get_chars(FD,'',M) end,F2),
+ ?line Ulist = unicode:characters_to_list(Res2,unicode),
+ ?dbg(?LINE),
+ ?line file:close(F2),
+ ?line {ok,F3} = file:open(Fname, [read,binary,{encoding,Encoding}]),
+ ?dbg(?LINE),
+%% case {Encoding,M,N} of
+%% {{utf16,little},10,2} ->
+%% dbg:p(F3,call);
+%% _ ->
+%% ok
+%% end,
+
+ ?line Res3 = read_whole_file(fun(FD) -> case io:fread(FD,'',"~ts") of {ok,D} -> D; O -> O end end, F3),
+ ?dbg(?LINE),
+ ?line Ulist2 = [ X || X <- Ulist,
+ X =/= $\n, X =/= $ ],
+ ?dbg(?LINE),
+ ?line Ulist2 = unicode:characters_to_list(Res3,unicode),
+ ?dbg(?LINE),
+ ?line file:close(F3),
+ ?line {ok,F4} = file:open(Fname, [read,{encoding,Encoding}]),
+ ?line Res4 = read_whole_file(fun(FD) -> case io:fread(FD,'',"~tc") of {ok,D} -> D; O -> O end end,F4),
+ ?line Ulist3 = [ X || X <- Ulist,
+ X =/= $\n ],
+ ?line Ulist3 = unicode:characters_to_list(Res4,unicode),
+ ?dbg(?LINE),
+ ?line file:close(F4),
+ ?line file:delete(Fname)
+ end,
+
+ [ [ [ DoOneFile1(E,N,M) || E <- AllModes ] || M <- [10,1000,128,1024,8192,8193] ] || N <- lists:seq(1,NumItersRead)],
+ DoOneFile2 = fun(Encoding,N,M) ->
+ ?dbg({Encoding,M,N}),
+ io:format("Write test: Encoding ~p, Chunk size ~p, Iteration ~p~n",[Encoding,M,N]),
+ io:format(standard_error,"Write test: Encoding ~p, Chunk size ~p, Iteration ~p\r\n",[Encoding,M,N]),
+ ?line Fname = filename:join([Dir,"genfile_"++enc2str(Encoding)++"_"++integer_to_list(N)]),
+ ?dbg(?LINE),
+ ?line Ulist = random_unicode(FSize),
+ ?dbg(?LINE),
+ ?line {ok,F1} = file:open(Fname,[write,{encoding,Encoding}]),
+ ?line io:put_chars(F1,Ulist),
+ ?line file:close(F1),
+ ?line Ulist = my_read_file(Fname,Encoding),
+ ?line file:delete(Fname),
+ ?line {ok,F2} = file:open(Fname,[write,binary,{encoding,Encoding}]),
+ ?line io:put_chars(F2,Ulist),
+ ?line file:close(F2),
+ ?line Ulist = my_read_file(Fname,Encoding),
+ ?line file:delete(Fname),
+ ?line {ok,F3} = file:open(Fname,[write,{encoding,Encoding}]),
+ ?line LL = string:tokens(Ulist,"\n"),
+ ?line Ulist2 = lists:flatten(LL),
+ ?line [ io:format(F3,"~ts",[L]) || L <- LL ],
+ ?line file:close(F3),
+ ?line Ulist2 = my_read_file(Fname,Encoding),
+ ?line file:delete(Fname),
+ ?line {ok,F4} = file:open(Fname,[write,{encoding,Encoding}]),
+ ?line [ io:format(F4,"~tc",[C]) || C <- Ulist ],
+ ?line file:close(F4),
+ ?line Ulist = my_read_file(Fname,Encoding),
+ ?line file:delete(Fname),
+ ?line {ok,F5} = file:open(Fname,[write,{encoding,Encoding}]),
+ ?line io:put_chars(F5,unicode:characters_to_binary(Ulist)),
+ ?line file:close(F5),
+ ?line Ulist = my_read_file(Fname,Encoding),
+ ?line file:delete(Fname),
+ ok
+ end,
+ [ [ [ DoOneFile2(E,N,M) || E <- AllModes ] || M <- [10,1000,128,1024,8192,8193] ] || N <- lists:seq(1,NumItersWrite)],
+ ok.
+
+
+
+
+read_whole_file(Fun,F) ->
+ case Fun(F) of
+ eof ->
+ [];
+ {error,Error} ->
+ ?dbg(Error),
+ receive after 10000 -> ok end,
+ exit(Error);
+ Other ->
+ %?dbg(Other),
+ [Other | read_whole_file(Fun,F)]
+ end.
+
+
+enc2str(Atom) when is_atom(Atom) ->
+ atom_to_list(Atom);
+enc2str({A1,A2}) when is_atom(A1), is_atom(A2) ->
+ atom_to_list(A1)++"_"++atom_to_list(A2).
+
+
+
+
+my_write_file(Filename,UniList,Encoding) ->
+ Bin = unicode:characters_to_binary(UniList,utf8,Encoding),
+ file:write_file(Filename,Bin).
+
+my_read_file(Filename,Encoding) ->
+ {ok,Bin} = file:read_file(Filename),
+ unicode:characters_to_list(Bin,Encoding).
+
+random_unicode(0) ->
+ [];
+random_unicode(N) ->
+ % Favour large unicode and make linebreaks
+ X = case random:uniform(20) of
+ A when A =< 1 -> $\n;
+ A0 when A0 =< 3 -> random:uniform(16#10FFFF);
+ A1 when A1 =< 6 -> random:uniform(16#10FFFF - 16#7F) + 16#7F;
+ A2 when A2 =< 12 -> random:uniform(16#10FFFF - 16#7FF) + 16#7FF;
+ _ -> random:uniform(16#10FFFF - 16#FFFF) + 16#FFFF
+ end,
+ case X of
+ Inv1 when Inv1 >= 16#D800, Inv1 =< 16#DFFF;
+ Inv1 =:= 16#FFFE;
+ Inv1 =:= 16#FFFF ->
+ random_unicode(N);
+ _ ->
+ [X | random_unicode(N-1)]
+ end.
+
+
+binary_options(suite) ->
+ [];
+binary_options(doc) ->
+ ["Tests variants with binary option"];
+binary_options(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir,Config),
+ PrivDir = ?config(priv_dir,Config),
+ TestData = unicode:characters_to_binary(
+ [1090,1093,1077,32,1073,1080,1075,32,
+ 1088,1077,1076,32,1092,1086,1100,32,1093,
+ 1072,1089,32,1089,1086,1100,32,932,951,949,
+ 32,946,953,947,32,961,949,948,32,
+ 963,959,967,32,945,961,949,32,966,959,967,949,963]),
+ <<First10:10/binary,Second10:10/binary,_/binary>> = TestData,
+ First10List = binary_to_list(First10),
+ Second10List = binary_to_list(Second10),
+ TestFile = filename:join([DataDir, "testdata_utf8.dat"]),
+ ?line {ok, F} = file:open(TestFile,[read]),
+ ?line {ok, First10List} = file:read(F,10),
+ ?line io:setopts(F,[binary]),
+ ?line {ok, Second10} = file:read(F,10),
+ ?line file:close(F),
+ ?line {ok, F2} = file:open(TestFile,[read,binary]),
+ ?line {ok, First10} = file:read(F2,10),
+ ?line io:setopts(F2,[list]),
+ ?line {ok, Second10List} = file:read(F2,10),
+ ?line file:position(F2,0),
+ %dbg:tracer(),dbg:p(F2,call),dbg:tpl(file_io_server,x),
+ ?line First10List = io:get_chars(F2,'',10),
+ ?line io:setopts(F2,[binary]),
+ ?line Second10 = unicode:characters_to_binary(io:get_chars(F2,'',10),unicode,latin1),
+ ?line file:close(F2),
+ ?line LineBreakFileName = filename:join([PrivDir, "testdata.dat"]),
+ ?line LineBreakTestData = <<TestData/binary,$\n>>,
+ ?line LineBreakTestDataList = binary_to_list(LineBreakTestData),
+ ?line file:write_file(LineBreakFileName,[LineBreakTestData,LineBreakTestData,LineBreakTestData,TestData]),
+ ?line {ok, F3} = file:open(LineBreakFileName,[read]),
+ ?line LineBreakTestDataList = io:get_line(F3,''),
+ ?line io:setopts(F3,[binary]),
+ ?line LineBreakTestData = unicode:characters_to_binary(io:get_line(F3,''),unicode,latin1),
+ ?line io:setopts(F3,[list]),
+ ?line LineBreakTestDataList = io:get_line(F3,''),
+ ?line io:setopts(F3,[binary]),
+ %ok = io:format(standard_error,"TestData = ~w~n",[TestData]),
+ ?line TestData = unicode:characters_to_binary(io:get_line(F3,''),unicode,latin1),
+ ?line eof = io:get_line(F3,''),
+ ?line file:close(F3),
+ %% OK, time for the group_leaders...
+ %% io:format(standard_error,"Hmmm:~w~n",["<<\""++binary_to_list(<<"\345\344\366"/utf8>>)++"\\n\">>"]),
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline, "2"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {getline, "{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline, "\"hej\\n\""},
+ {putline, "io:setopts([{binary,true},unicode])."},
+ {getline, "ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline, "<<\"hej\\n\">>"},
+ {putline, "io:get_line('')."},
+ {putline, binary_to_list(<<"\345\344\366"/utf8>>)},
+ {getline, "<<\""++binary_to_list(unicode:characters_to_binary(<<"\345\344\366"/utf8>>,latin1,utf8))++"\\n\">>"}
+ ],[]),
+ %% And one with oldshell
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline_re, ".*2."},
+ {getline, "2"},
+ {putline, "lists:keyfind(binary,1,io:getopts())."},
+ {getline_re, ".*{binary,false}"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline_re, ".*\"hej\\\\n\""},
+ {putline, "io:setopts([{binary,true},unicode])."},
+ {getline_re, ".*ok"},
+ {putline, "io:get_line('')."},
+ {putline, "hej"},
+ {getline_re, ".*<<\"hej\\\\n\">>"},
+ {putline, "io:get_line('')."},
+ {putline, binary_to_list(<<"\345\344\366"/utf8>>)},
+ {getline_re, ".*<<\""++binary_to_list(unicode:characters_to_binary(<<"\345\344\366"/utf8>>,latin1,utf8))++"\\\\n\">>"}
+ ],[],[],"-oldshell"),
+ ok.
+
+bc_with_r12(suite) ->
+ [];
+bc_with_r12(doc) ->
+ ["Test io protocol compatibility with R12 nodes"];
+bc_with_r12(Config) when is_list(Config) ->
+ case ?t:is_release_available("r12b") of
+ true -> bc_with_r12_1(Config);
+ false -> {skip,"No R12B found"}
+ end.
+
+bc_with_r12_1(Config) ->
+ PA = filename:dirname(code:which(?MODULE)),
+ Name1 = io_proto_r12_1,
+ ?line N1 = list_to_atom(atom_to_list(Name1) ++ "@" ++ hostname()),
+ ?line ?t:start_node(Name1, peer, [{args, "-pz "++PA},{erl,[{release,"r12b"}]}]),
+ DataDir = ?config(data_dir,Config),
+ %PrivDir = ?config(priv_dir,Config),
+ FileName1 = filename:join([DataDir,"testdata_latin1.dat"]),
+ TestDataLine1 = [229,228,246],
+ TestDataLine2 = [197,196,214],
+ ?line SPid1 = rpc:call(N1,erlang,spawn,[?MODULE,hold_the_line,[self(),FileName1,[read]]]),
+ ?line {ok,F1} = receive
+ {SPid1,Res1} ->
+ Res1
+ after 5000 ->
+ exit(timeout)
+ end,
+ ?line TestDataLine1 = chomp(io:get_line(F1,'')),
+ ?line SPid1 ! die,
+ receive after 1000 -> ok end,
+ ?line SPid2 = rpc:call(N1,erlang,spawn,[?MODULE,hold_the_line,[self(),FileName1,[read,binary]]]),
+ ?line {ok,F2} = receive
+ {SPid2,Res2} ->
+ Res2
+ after 5000 ->
+ exit(timeout)
+ end,
+ TestDataLine1BinUtf = unicode:characters_to_binary(TestDataLine1),
+ TestDataLine1BinLatin = list_to_binary(TestDataLine1),
+ TestDataLine2BinUtf = unicode:characters_to_binary(TestDataLine2),
+ TestDataLine2BinLatin = list_to_binary(TestDataLine2),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(F2,'')),
+ ?line TestDataLine2BinUtf = chomp(io:get_line(F2,'')),
+ %io:format(standard_error,"Exec:~s\r\n",[rpc:call(N1,os,find_executable,["erl"])]),
+ %io:format(standard_error,"Io:~s\r\n",[rpc:call(N1,code,which,[io])]),
+ %io:format(standard_error,"File_io_server:~s\r\n",[rpc:call(N1,code,which,[file_io_server])]),
+ ?line file:position(F2,0),
+ ?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[F2,''])),
+ ?line TestDataLine2BinUtf = chomp(io:get_line(F2,'')),
+ ?line file:position(F2,0),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(F2,'')),
+ ?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F2,''])),
+ ?line eof = chomp(rpc:call(N1,io,get_line,[F2,''])),
+ ?line file:position(F2,0),
+ ?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[F2,'',3]),
+ io:get_chars(F2,'',1),
+ ?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F2,''])),
+ ?line file:position(F2,0),
+ ?line {ok,[TestDataLine1]} = io:fread(F2,'',"~s"),
+ ?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F2,'',"~s"]),
+
+ ?line DataLen1 = length(TestDataLine1),
+ ?line DataLen2 = length(TestDataLine2),
+
+ ?line file:position(F2,0),
+ ?line {ok,TestDataLine1BinLatin} = file:read(F2,DataLen1),
+ ?line {ok,_} = file:read(F2,1),
+ ?line {ok,TestDataLine2BinLatin} = rpc:call(N1,file,read,[F2,DataLen2]),
+ ?line {ok,_} = file:read(F2,1),
+ ?line eof = rpc:call(N1,file,read,[F2,1]),
+ %% As r12 has a bug when setting options with setopts, we need
+ %% to reopen the file...
+ ?line SPid2 ! die,
+ receive after 1000 -> ok end,
+ ?line SPid3 = rpc:call(N1,erlang,spawn,[?MODULE,hold_the_line,[self(),FileName1,[read]]]),
+ ?line {ok,F3} = receive
+ {SPid3,Res3} ->
+ Res3
+ after 5000 ->
+ exit(timeout)
+ end,
+
+ ?line file:position(F3,0),
+ ?line {ok,[TestDataLine1]} = io:fread(F3,'',"~s"),
+ ?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F3,'',"~s"]),
+
+
+ ?line file:position(F3,0),
+ ?line {ok,TestDataLine1} = file:read(F3,DataLen1),
+ ?line {ok,_} = file:read(F3,1),
+ ?line {ok,TestDataLine2} = rpc:call(N1,file,read,[F3,DataLen2]),
+ ?line {ok,_} = file:read(F3,1),
+ ?line eof = rpc:call(N1,file,read,[F3,1]),
+
+
+ %% So, lets do it all again, but the other way around
+ {ok,F4} = file:open(FileName1,[read]),
+ ?line TestDataLine1 = chomp(io:get_line(F4,'')),
+ ?line file:position(F4,0),
+ ?line io:setopts(F4,[binary]),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(F4,'')),
+ ?line TestDataLine2BinUtf = chomp(io:get_line(F4,'')),
+ ?line file:position(F4,0),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(F4,'')),
+ ?line TestDataLine2BinUtf = chomp(io:get_line(F4,'')),
+ ?line file:position(F4,0),
+ %dbg:tracer(),dbg:p(F4,[call,m]),dbg:tpl(file_io_server,x),dbg:tpl(io_lib,x),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(F4,'')),
+ ?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F4,''])),
+ ?line file:position(F4,0),
+ ?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[F4,''])),
+ ?line TestDataLine2BinUtf = chomp(io:get_line(F4,'')),
+ ?line eof = chomp(rpc:call(N1,io,get_line,[F4,''])),
+ ?line file:position(F4,0),
+ ?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[F4,'',3]),
+ io:get_chars(F4,'',1),
+ ?line TestDataLine2BinLatin = chomp(rpc:call(N1,io,get_line,[F4,''])),
+ ?line file:position(F4,0),
+ ?line {ok,[TestDataLine1]} = io:fread(F4,'',"~s"),
+ ?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F4,'',"~s"]),
+ ?line file:position(F4,0),
+ ?line {ok,TestDataLine1BinLatin} = file:read(F4,DataLen1),
+ ?line {ok,_} = file:read(F4,1),
+ ?line {ok,TestDataLine2BinLatin} = rpc:call(N1,file,read,[F4,DataLen2]),
+ ?line {ok,_} = file:read(F4,1),
+ ?line eof = rpc:call(N1,file,read,[F4,1]),
+ ?line io:setopts(F4,[list]),
+
+ ?line file:position(F4,0),
+ ?line {ok,[TestDataLine1]} = io:fread(F4,'',"~s"),
+ ?line {ok,[TestDataLine2]} = rpc:call(N1,io,fread,[F4,'',"~s"]),
+
+
+ ?line file:position(F4,0),
+ ?line {ok,TestDataLine1} = file:read(F4,DataLen1),
+ ?line {ok,_} = file:read(F4,1),
+ ?line {ok,TestDataLine2} = rpc:call(N1,file,read,[F4,DataLen2]),
+ ?line {ok,_} = file:read(F4,1),
+ ?line eof = rpc:call(N1,file,read,[F4,1]),
+
+ file:close(F4),
+ ?t:stop_node(N1),
+ ok.
+
+hold_the_line(Parent,Filename,Options) ->
+ Parent ! {self(), file:open(Filename,Options)},
+ receive
+ die ->
+ ok
+ end.
+
+
+bc_with_r12_gl(suite) ->
+ [];
+bc_with_r12_gl(doc) ->
+ ["Test io protocol compatibility with R12 nodes (terminals)"];
+bc_with_r12_gl(Config) when is_list(Config) ->
+ case ?t:is_release_available("r12b") of
+ true ->
+ case get_progs() of
+ {error,Reason} ->
+ {skip, Reason};
+ _ ->
+ bc_with_r12_gl_1(Config,answering_machine1)
+ end;
+ false ->
+ {skip,"No R12B found"}
+ end.
+
+bc_with_r12_ogl(suite) ->
+ [];
+bc_with_r12_ogl(doc) ->
+ ["Test io protocol compatibility with R12 nodes (oldshell)"];
+bc_with_r12_ogl(Config) when is_list(Config) ->
+ case ?t:is_release_available("r12b") of
+ true ->
+ case get_progs() of
+ {error,Reason} ->
+ {skip, Reason};
+ _ ->
+ bc_with_r12_gl_1(Config,answering_machine2)
+ end;
+ false ->
+ {skip,"No R12B found"}
+ end.
+
+bc_with_r12_gl_1(_Config,Machine) ->
+ PA = filename:dirname(code:which(?MODULE)),
+ Name1 = io_proto_r12_gl_1,
+ ?line N1 = list_to_atom(atom_to_list(Name1) ++ "@" ++ hostname()),
+ ?line ?t:start_node(Name1, peer, [{args, "-pz "++PA},{erl,[{release,"r12b"}]}]),
+ TestDataLine1 = [229,228,246],
+ TestDataLine1BinUtf = unicode:characters_to_binary(TestDataLine1),
+ TestDataLine1BinLatin = list_to_binary(TestDataLine1),
+
+ N2List = create_nodename(),
+ MyNodeList = atom_to_list(node()),
+ register(io_proto_suite,self()),
+ AM1 = spawn(?MODULE,Machine,
+ [MyNodeList, "io_proto_suite", N2List]),
+
+ ?line GL = receive X when is_pid(X) -> X end,
+ %% get_line
+ ?line "Hej\n" = rpc:call(N1,io,get_line,[GL,"Prompt\n"]),
+ ?line io:setopts(GL,[binary]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line <<"Hej\n">> = rpc:call(N1,io,get_line,[GL,"Prompt\n"]),
+ ?line io:setopts(GL,[{encoding,latin1}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[GL,"Prompt\n"])),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
+ ?line io:setopts(GL,[{encoding,unicode}]),
+
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = chomp(rpc:call(N1,io,get_line,[GL,"Prompt\n"])),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
+ ?line io:setopts(GL,[list]),
+ ?line io:format(GL,"Okej~n",[]),
+
+ %%get_chars
+ ?line "Hej" = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
+ ?line io:setopts(GL,[binary]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line <<"Hej">> = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
+ ?line io:setopts(GL,[{encoding,latin1}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
+ ?line io:setopts(GL,[{encoding,unicode}]),
+
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = rpc:call(N1,io,get_chars,[GL,"Prompt\n",3]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
+ ?line io:setopts(GL,[list]),
+ ?line io:format(GL,"Okej~n",[]),
+ %%fread
+ ?line {ok,["Hej"]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
+ ?line io:setopts(GL,[binary]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,["Hej"]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
+ ?line io:setopts(GL,[{encoding,latin1}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:setopts(GL,[{encoding,unicode}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = rpc:call(N1,io,fread,[GL,"Prompt\n","~s"]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:setopts(GL,[list]),
+ ?line io:format(GL,"Okej~n",[]),
+
+
+ ?line receive
+ {AM1,done} ->
+ ok
+ after 5000 ->
+ exit(timeout)
+ end,
+ ?t:stop_node(N1),
+ ok.
+
+
+answering_machine1(OthNode,OthReg,Me) ->
+ TestDataLine1 = [229,228,246],
+ TestDataUtf = binary_to_list(unicode:characters_to_binary(TestDataLine1)),
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline, "2"},
+ {putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
+ {getline, "<"},
+ % get_line
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ % get_chars
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ % fread
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"}
+
+ ],Me,"LC_CTYPE=\"ISO-8859-1\"; export LC_CTYPE; "),
+ O = list_to_atom(OthReg),
+ O ! {self(),done},
+ ok.
+
+answering_machine2(OthNode,OthReg,Me) ->
+ TestDataLine1 = [229,228,246],
+ TestDataUtf = binary_to_list(unicode:characters_to_binary(TestDataLine1)),
+ ?line rtnode([{putline,""},
+ {putline, "2."},
+ {getline, "2"},
+ {putline, "{"++OthReg++","++OthNode++"} ! group_leader()."},
+ {getline_re, ".*<[0-9].*"},
+ % get_line
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ % get_chars
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ % fread
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, "Hej"},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataLine1},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"},
+ {getline_re, ".*Prompt"},
+ {putline, TestDataUtf},
+ {getline_re, ".*Okej"}
+
+ ],Me,"LC_CTYPE=\"ISO-8859-1\"; export LC_CTYPE; "," -oldshell "),
+ O = list_to_atom(OthReg),
+ O ! {self(),done},
+ ok.
+
+
+read_modes_ogl(suite) ->
+ [];
+read_modes_ogl(doc) ->
+ ["Test various modes when reading from the group leade from another machine"];
+read_modes_ogl(Config) when is_list(Config) ->
+ case get_progs() of
+ {error,Reason} ->
+ {skipped,Reason};
+ _ ->
+ read_modes_gl_1(Config,answering_machine2)
+ end.
+
+read_modes_gl(suite) ->
+ [];
+read_modes_gl(doc) ->
+ ["Test various modes when reading from the group leade from another machine"];
+read_modes_gl(Config) when is_list(Config) ->
+ case get_progs() of
+ {error,Reason} ->
+ {skipped,Reason};
+ _ ->
+ read_modes_gl_1(Config,answering_machine1)
+ end.
+
+read_modes_gl_1(_Config,Machine) ->
+ TestDataLine1 = [229,228,246],
+ TestDataLine1BinUtf = unicode:characters_to_binary(TestDataLine1),
+ TestDataLine1BinLatin = list_to_binary(TestDataLine1),
+
+ N2List = create_nodename(),
+ MyNodeList = atom_to_list(node()),
+ register(io_proto_suite,self()),
+ AM1 = spawn(?MODULE,Machine,
+ [MyNodeList, "io_proto_suite", N2List]),
+
+ ?line GL = receive X when is_pid(X) -> X end,
+ %% get_line
+ ?line "Hej\n" = io:get_line(GL,"Prompt\n"),
+ ?line io:setopts(GL,[binary]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line <<"Hej\n">> = io:get_line(GL,"Prompt\n"),
+ ?line io:setopts(GL,[{encoding,latin1}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = chomp(io:request(GL,{get_line,latin1,"Prompt\n"})),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
+ ?line io:setopts(GL,[{encoding,unicode}]),
+
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = chomp(io:request(GL,{get_line,latin1,"Prompt\n"})),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = chomp(io:get_line(GL,"Prompt\n")),
+ ?line io:setopts(GL,[list]),
+ ?line io:format(GL,"Okej~n",[]),
+
+ %%get_chars
+ ?line "Hej" = io:get_chars(GL,"Prompt\n",3),
+ ?line io:setopts(GL,[binary]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line <<"Hej">> = io:get_chars(GL,"Prompt\n",3),
+ ?line io:setopts(GL,[{encoding,latin1}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = io:request(GL,{get_chars,latin1,"Prompt\n",3}),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
+ ?line io:setopts(GL,[{encoding,unicode}]),
+
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinLatin = io:request(GL,{get_chars,latin1,"Prompt\n",3}),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line TestDataLine1BinUtf = io:get_chars(GL,"Prompt\n",3),
+ ?line io:setopts(GL,[list]),
+ ?line io:format(GL,"Okej~n",[]),
+ %%fread
+ ?line {ok,["Hej"]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:setopts(GL,[binary]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,["Hej"]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:setopts(GL,[{encoding,latin1}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:setopts(GL,[{encoding,unicode}]),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:format(GL,"Okej~n",[]),
+ ?line {ok,[TestDataLine1]} = io:fread(GL,"Prompt\n","~s"),
+ ?line io:setopts(GL,[list]),
+ ?line io:format(GL,"Okej~n",[]),
+
+
+ ?line receive
+ {AM1,done} ->
+ ok
+ after 5000 ->
+ exit(timeout)
+ end,
+ ok.
+
+
+broken_unicode(suite) ->
+ [];
+broken_unicode(doc) ->
+ ["Test behaviour when reading broken Unicode files"];
+broken_unicode(Config) when is_list(Config) ->
+ Dir = ?config(priv_dir,Config),
+ Latin1Name = filename:join([Dir,"latin1_data_file.dat"]),
+ Utf8Name = filename:join([Dir,"utf8_data_file.dat"]),
+ Latin1Data = iolist_to_binary(lists:duplicate(10,lists:seq(0,255)++[255,255,255])),
+ Utf8Data = unicode:characters_to_binary(
+ lists:duplicate(10,lists:seq(0,255))),
+ file:write_file(Latin1Name,Latin1Data),
+ file:write_file(Utf8Name,Utf8Data),
+ ?line [ latin1 = heuristic_encoding_file2(Latin1Name,N,utf8) || N <- lists:seq(1,100)++[1024,2048,10000]],
+ ?line [ utf8 = heuristic_encoding_file2(Utf8Name,N,utf8) || N <- lists:seq(1,100)++[1024,2048,10000]],
+ ?line [ latin1 = heuristic_encoding_file2(Latin1Name,N,utf16) || N <- lists:seq(1,100)++[1024,2048,10000]],
+ ?line [ latin1 = heuristic_encoding_file2(Latin1Name,N,utf32) || N <- lists:seq(1,100)++[1024,2048,10000]],
+ ok.
+
+
+%%
+%% From the cookbook, more or less
+heuristic_encoding_file2(FileName,Chunk,Enc) ->
+ {ok,F} = file:open(FileName,[read,binary,{encoding,Enc}]),
+ loop_through_file2(F,io:get_chars(F,'',Chunk),Chunk,Enc).
+
+loop_through_file2(_,eof,_,Enc) ->
+ Enc;
+loop_through_file2(_,{error,_Err},_,_) ->
+ latin1;
+loop_through_file2(F,Bin,Chunk,Enc) when is_binary(Bin) ->
+ loop_through_file2(F,io:get_chars(F,'',Chunk),Chunk,Enc).
+
+
+
+eof_on_pipe(suite) ->
+ [];
+eof_on_pipe(doc) ->
+ ["tests eof before newline on stdin when erlang is in pipe"];
+eof_on_pipe(Config) when is_list(Config) ->
+ case {get_progs(),os:type()} of
+ {{error,Reason},_} ->
+ {skipped,Reason};
+ {{_,_,Erl},{unix,linux}} ->
+ %% Not even Linux is reliable - echo can be both styles
+ try
+ EchoLine = case os:cmd("echo -ne \"test\\ntest\"") of
+ "test\ntest" ->
+ "echo -ne \"a\\nbu\" | ";
+ _ ->
+ case os:cmd("echo \"test\\ntest\\c\"") of
+ "test\ntest" ->
+ "echo \"a\\nbu\\c\" | ";
+ _ ->
+ throw(skip)
+ end
+ end,
+ CommandLine1 = EchoLine ++
+ Erl++" -noshell -eval "
+ "'io:format(\"~p\",[io:get_line(\"\")]),"
+ "io:format(\"~p\",[io:get_line(\"\")]),"
+ "io:format(\"~p\",[io:get_line(\"\")]).' -run init stop",
+ case os:cmd(CommandLine1) of
+ "\"a\\n\"\"bu\"eof" ->
+ ok;
+ Other1 ->
+ exit({unexpected1,Other1})
+ end,
+ CommandLine2 = EchoLine ++
+ Erl++" -noshell -eval "
+ "'io:setopts([binary]),io:format(\"~p\",[io:get_line(\"\")]),"
+ "io:format(\"~p\",[io:get_line(\"\")]),"
+ "io:format(\"~p\",[io:get_line(\"\")]).' -run init stop",
+ case os:cmd(CommandLine2) of
+ "<<\"a\\n\">><<\"bu\">>eof" ->
+ ok;
+ Other2 ->
+ exit({unexpected2,Other2})
+ end
+ catch
+ throw:skip ->
+ {skipped,"unsupported echo program"}
+ end;
+ {_,_} ->
+ {skipped,"Only on linux"}
+ end.
+
+
+%%
+%% Tool for running interactive shell (stolen from the kernel
+%% test suite interactive_shell_SUITE)
+%%
+-undef(line).
+-define(line,).
+rtnode(C,N) ->
+ rtnode(C,N,[]).
+rtnode(Commands,Nodename,ErlPrefix) ->
+ rtnode(Commands,Nodename,ErlPrefix,[]).
+rtnode(Commands,Nodename,ErlPrefix,Extra) ->
+ ?line case get_progs() of
+ {error,_Reason} ->
+ ?line {skip,"No runerl present"};
+ {RunErl,ToErl,Erl} ->
+ ?line case create_tempdir() of
+ {error, Reason2} ->
+ ?line {skip, Reason2};
+ Tempdir ->
+ ?line SPid =
+ start_runerl_node(RunErl,ErlPrefix++Erl,
+ Tempdir,Nodename, Extra),
+ ?line CPid = start_toerl_server(ToErl,Tempdir),
+ ?line erase(getline_skipped),
+ ?line Res =
+ (catch get_and_put(CPid, Commands,1)),
+ ?line case stop_runerl_node(CPid) of
+ {error,_} ->
+ ?line CPid2 =
+ start_toerl_server
+ (ToErl,Tempdir),
+ ?line erase(getline_skipped),
+ ?line ok = get_and_put
+ (CPid2,
+ [{putline,[7]},
+ {sleep,
+ timeout(short)},
+ {putline,""},
+ {getline," -->"},
+ {putline,"s"},
+ {putline,"c"},
+ {putline,""}],1),
+ ?line stop_runerl_node(CPid2);
+ _ ->
+ ?line ok
+ end,
+ ?line wait_for_runerl_server(SPid),
+ ?line ok = rm_rf(Tempdir),
+ ?line ok = Res
+ end
+ end.
+
+timeout(long) ->
+ 2 * timeout(normal);
+timeout(short) ->
+ timeout(normal) div 10;
+timeout(normal) ->
+ 10000 * test_server:timetrap_scale_factor().
+
+
+%% start_noshell_node(Name) ->
+%% PADir = filename:dirname(code:which(?MODULE)),
+%% {ok, Node} = test_server:start_node(Name,slave,[{args," -noshell -pa "++
+%% PADir++" "}]),
+%% Node.
+%% stop_noshell_node(Node) ->
+%% test_server:stop_node(Node).
+
+
+rm_rf(Dir) ->
+ try
+ {ok,List} = file:list_dir(Dir),
+ Files = [filename:join([Dir,X]) || X <- List],
+ [case file:list_dir(Y) of
+ {error, enotdir} ->
+ ok = file:delete(Y);
+ _ ->
+ ok = rm_rf(Y)
+ end || Y <- Files],
+ ok = file:del_dir(Dir),
+ ok
+ catch
+ _:Exception -> {error, {Exception,Dir}}
+ end.
+
+
+get_and_put(_CPid,[],_) ->
+ ok;
+get_and_put(CPid, [{sleep, X}|T],N) ->
+ ?dbg({sleep, X}),
+ receive
+ after X ->
+ get_and_put(CPid,T,N+1)
+ end;
+get_and_put(CPid, [{getline, Match}|T],N) ->
+ ?dbg({getline, Match}),
+ CPid ! {self(), {get_line, timeout(normal)}},
+ receive
+ {get_line, timeout} ->
+ error_logger:error_msg("~p: getline timeout waiting for \"~s\" "
+ "(command number ~p, skipped: ~p)~n",
+ [?MODULE, Match,N,get(getline_skipped)]),
+ {error, timeout};
+ {get_line, Data} ->
+ ?dbg({data,Data}),
+ case lists:prefix(Match, Data) of
+ true ->
+ erase(getline_skipped),
+ get_and_put(CPid, T,N+1);
+ false ->
+ case get(getline_skipped) of
+ undefined ->
+ put(getline_skipped,[Data]);
+ List ->
+ put(getline_skipped,List ++ [Data])
+ end,
+ get_and_put(CPid, [{getline, Match}|T],N)
+ end
+ end;
+get_and_put(CPid, [{getline_re, Match}|T],N) ->
+ ?dbg({getline_re, Match}),
+ CPid ! {self(), {get_line, timeout(normal)}},
+ receive
+ {get_line, timeout} ->
+ error_logger:error_msg("~p: getline_re timeout waiting for \"~s\" "
+ "(command number ~p, skipped: ~p)~n",
+ [?MODULE, Match,N,get(getline_skipped)]),
+ {error, timeout};
+ {get_line, Data} ->
+ ?dbg({data,Data}),
+ case re:run(Data, Match,[{capture,none}]) of
+ match ->
+ erase(getline_skipped),
+ get_and_put(CPid, T,N+1);
+ _ ->
+ case get(getline_skipped) of
+ undefined ->
+ put(getline_skipped,[Data]);
+ List ->
+ put(getline_skipped,List ++ [Data])
+ end,
+ get_and_put(CPid, [{getline_re, Match}|T],N)
+ end
+ end;
+
+get_and_put(CPid, [{putline_raw, Line}|T],N) ->
+ ?dbg({putline_raw, Line}),
+ CPid ! {self(), {send_line, Line}},
+ Timeout = timeout(normal),
+ receive
+ {send_line, ok} ->
+ get_and_put(CPid, T,N+1)
+ after Timeout ->
+ error_logger:error_msg("~p: putline_raw timeout (~p) sending "
+ "\"~s\" (command number ~p)~n",
+ [?MODULE, Timeout, Line, N]),
+ {error, timeout}
+ end;
+
+get_and_put(CPid, [{putline, Line}|T],N) ->
+ ?dbg({putline, Line}),
+ CPid ! {self(), {send_line, Line}},
+ Timeout = timeout(normal),
+ receive
+ {send_line, ok} ->
+ get_and_put(CPid, [{getline, []}|T],N)
+ after Timeout ->
+ error_logger:error_msg("~p: putline timeout (~p) sending "
+ "\"~s\" (command number ~p)~n[~p]~n",
+ [?MODULE, Timeout, Line, N,get()]),
+ {error, timeout}
+ end.
+
+wait_for_runerl_server(SPid) ->
+ Ref = erlang:monitor(process, SPid),
+ Timeout = timeout(long),
+ receive
+ {'DOWN', Ref, process, SPid, _} ->
+ ok
+ after Timeout ->
+ {error, timeout}
+ end.
+
+
+
+stop_runerl_node(CPid) ->
+ Ref = erlang:monitor(process, CPid),
+ CPid ! {self(), kill_emulator},
+ Timeout = timeout(long),
+ receive
+ {'DOWN', Ref, process, CPid, noproc} ->
+ ok;
+ {'DOWN', Ref, process, CPid, normal} ->
+ ok;
+ {'DOWN', Ref, process, CPid, {error, Reason}} ->
+ {error, Reason}
+ after Timeout ->
+ {error, timeout}
+ end.
+
+get_progs() ->
+ case os:type() of
+ {unix,freebsd} ->
+ {error,"cant use run_erl on freebsd"};
+ {unix,openbsd} ->
+ {error,"cant use run_erl on openbsd"};
+ {unix,_} ->
+ case os:find_executable("run_erl") of
+ RE when is_list(RE) ->
+ case os:find_executable("to_erl") of
+ TE when is_list(TE) ->
+ case os:find_executable("erl") of
+ E when is_list(E) ->
+ {RE,TE,E};
+ _ ->
+ {error, "Could not find erl command"}
+ end;
+ _ ->
+ {error, "Could not find to_erl command"}
+ end;
+ _ ->
+ {error, "Could not find run_erl command"}
+ end;
+ _ ->
+ {error, "Not a unix OS"}
+ end.
+
+create_tempdir() ->
+ create_tempdir(filename:join(["/tmp","rtnode"++os:getpid()]),$A).
+
+create_tempdir(Dir,X) when X > $Z, X < $a ->
+ create_tempdir(Dir,$a);
+create_tempdir(Dir,X) when X > $z ->
+ Estr = lists:flatten(
+ io_lib:format("Unable to create ~s, reason eexist",
+ [Dir++[$z]])),
+ {error, Estr};
+create_tempdir(Dir0, Ch) ->
+ % Expect fairly standard unix.
+ Dir = Dir0++[Ch],
+ case file:make_dir(Dir) of
+ {error, eexist} ->
+ create_tempdir(Dir0, Ch+1);
+ {error, Reason} ->
+ Estr = lists:flatten(
+ io_lib:format("Unable to create ~s, reason ~p",
+ [Dir,Reason])),
+ {error,Estr};
+ ok ->
+ Dir
+ end.
+
+create_nodename() ->
+ create_nodename($A).
+
+create_nodename(X) when X > $Z, X < $a ->
+ create_nodename($a);
+create_nodename(X) when X > $z ->
+ {error,out_of_nodenames};
+create_nodename(X) ->
+ NN = "rtnode"++os:getpid()++[X],
+ case file:read_file_info(filename:join(["/tmp",NN])) of
+ {error,enoent} ->
+ Host = lists:nth(2,string:tokens(atom_to_list(node()),"@")),
+ NN++"@"++Host;
+ _ ->
+ create_nodename(X+1)
+ end.
+
+
+start_runerl_node(RunErl,Erl,Tempdir,Nodename,Extra) ->
+ XArg = case Nodename of
+ [] ->
+ [];
+ _ ->
+ " -sname "++(if is_atom(Nodename) -> atom_to_list(Nodename);
+ true -> Nodename
+ end)++
+ " -setcookie "++atom_to_list(erlang:get_cookie())
+ end,
+ XXArg = case Extra of
+ [] ->
+ [];
+ _ ->
+ " "++Extra
+ end,
+ spawn(fun() ->
+ os:cmd(RunErl++" "++Tempdir++"/ "++Tempdir++" \""++
+ Erl++XArg++XXArg++"\"")
+ end).
+
+start_toerl_server(ToErl,Tempdir) ->
+ Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir]),
+ receive
+ {Pid,started} ->
+ Pid;
+ {Pid,error,Reason} ->
+ {error,Reason}
+ end.
+
+try_to_erl(_Command, 0) ->
+ {error, cannot_to_erl};
+try_to_erl(Command, N) ->
+ ?dbg({?LINE,N}),
+ Port = open_port({spawn, Command},[eof,{line,1000}]),
+ Timeout = timeout(normal) div 2,
+ receive
+ {Port, eof} ->
+ receive after Timeout ->
+ ok
+ end,
+ try_to_erl(Command, N-1)
+ after Timeout ->
+ ?dbg(Port),
+ Port
+ end.
+
+toerl_server(Parent,ToErl,Tempdir) ->
+ Port = try_to_erl(ToErl++" "++Tempdir++"/ 2>/dev/null",8),
+ case Port of
+ P when is_port(P) ->
+ Parent ! {self(),started};
+ {error,Other} ->
+ Parent ! {self(),error,Other},
+ exit(Other)
+ end,
+ case toerl_loop(Port,[]) of
+ normal ->
+ ok;
+ {error, Reason} ->
+ error_logger:error_msg("toerl_server exit with reason ~p~n",
+ [Reason]),
+ exit(Reason)
+ end.
+
+toerl_loop(Port,Acc) ->
+ ?dbg({toerl_loop, Port, Acc}),
+ receive
+ {Port,{data,{Tag0,Data}}} when is_port(Port) ->
+ ?dbg({?LINE,Port,{data,{Tag0,Data}}}),
+ case Acc of
+ [{noeol,Data0}|T0] ->
+ toerl_loop(Port,[{Tag0, Data0++Data}|T0]);
+ _ ->
+ toerl_loop(Port,[{Tag0,Data}|Acc])
+ end;
+ {Pid,{get_line,Timeout}} ->
+ case Acc of
+ [] ->
+ case get_data_within(Port,Timeout,[]) of
+ timeout ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,[]);
+ {noeol,Data1} ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,[{noeol,Data1}]);
+ {eol,Data2} ->
+ Pid ! {get_line, Data2},
+ toerl_loop(Port,[])
+ end;
+ [{noeol,Data3}] ->
+ case get_data_within(Port,Timeout,Data3) of
+ timeout ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,Acc);
+ {noeol,Data4} ->
+ Pid ! {get_line, timeout},
+ toerl_loop(Port,[{noeol,Data4}]);
+ {eol,Data5} ->
+ Pid ! {get_line, Data5},
+ toerl_loop(Port,[])
+ end;
+ List ->
+ {NewAcc,[{eol,Data6}]} = lists:split(length(List)-1,List),
+ Pid ! {get_line,Data6},
+ toerl_loop(Port,NewAcc)
+ end;
+ {Pid, {send_line, Data7}} ->
+ Port ! {self(),{command, Data7++"\n"}},
+ Pid ! {send_line, ok},
+ toerl_loop(Port,Acc);
+ {_Pid, kill_emulator} ->
+ Port ! {self(),{command, "init:stop().\n"}},
+ Timeout1 = timeout(long),
+ receive
+ {Port,eof} ->
+ normal
+ after Timeout1 ->
+ {error, kill_timeout}
+ end;
+ {Port, eof} ->
+ {error, unexpected_eof};
+ Other ->
+ {error, {unexpected, Other}}
+ end.
+
+millistamp() ->
+ {Mega, Secs, Micros} = erlang:now(),
+ (Micros div 1000) + Secs * 1000 + Mega * 1000000000.
+
+get_data_within(Port, X, Acc) when X =< 0 ->
+ ?dbg({get_data_within, X, Acc, ?LINE}),
+ receive
+ {Port,{data,{Tag0,Data}}} ->
+ ?dbg({?LINE,Port,{data,{Tag0,Data}}}),
+ {Tag0, Acc++Data}
+ after 0 ->
+ case Acc of
+ [] ->
+ timeout;
+ Noeol ->
+ {noeol,Noeol}
+ end
+ end;
+
+
+get_data_within(Port, Timeout, Acc) ->
+ ?dbg({get_data_within, Timeout, Acc, ?LINE}),
+ T1 = millistamp(),
+ receive
+ {Port,{data,{noeol,Data}}} ->
+ ?dbg({?LINE,Port,{data,{noeol,Data}}}),
+ Elapsed = millistamp() - T1 + 1,
+ get_data_within(Port, Timeout - Elapsed, Acc ++ Data);
+ {Port,{data,{eol,Data1}}} ->
+ ?dbg({?LINE,Port,{data,{eol,Data1}}}),
+ {eol, Acc ++ Data1}
+ after Timeout ->
+ timeout
+ end.
+
+%%
+%% Test I/O-server
+%%
+
+start_io_server_proxy() ->
+ spawn_link(?MODULE,io_server_proxy,[#state{}]).
+
+proxy_getall(Pid) ->
+ req(Pid,{self(),getall}).
+proxy_setnext(Pid,Data) when is_list(Data) ->
+ req(Pid,{self(),next,Data}).
+proxy_quit(Pid) ->
+ req(Pid,{self(),quit}).
+
+req(Pid,Mess) ->
+ Pid ! Mess,
+ receive
+ {Pid, Answer} ->
+ Answer
+ after 5000 ->
+ exit(timeout)
+ end.
+
+io_server_proxy(State) ->
+ receive
+ {io_request, From, ReplyAs, Request} ->
+ case request(Request,State) of
+ {Tag, Reply, NewState} when Tag =:= ok; Tag =:= error ->
+ reply(From, ReplyAs, Reply),
+ io_server_proxy(NewState);
+ {stop, Reply, _NewState} ->
+ reply(From, ReplyAs, Reply),
+ exit(Reply)
+ end;
+ %% Private message
+ {From, next, Data} ->
+ From ! {self(), ok},
+ io_server_proxy(State#state{nxt = Data});
+ {From, getall} ->
+ From ! {self(), lists:reverse(State#state.q)},
+ io_server_proxy(State#state{q=[]});
+ {From, quit} ->
+ From ! {self(), lists:reverse(State#state.q)},
+ ok;
+ _Unknown ->
+ io_server_proxy(State)
+ end.
+
+reply(From, ReplyAs, Reply) ->
+ From ! {io_reply, ReplyAs, Reply}.
+
+request({put_chars, Encoding, Chars}, State) ->
+ {ok, ok, State#state{q=[{put_chars, Encoding, Chars} | State#state.q ]}};
+request({put_chars, Encoding, Module, Function, Args}, State) ->
+ {ok, ok, State#state{q=[{put_chars, Encoding, Module, Function, Args} |
+ State#state.q ]}};
+request({put_chars,Chars}, State) ->
+ {ok, ok, State#state{q=[{put_chars, Chars} | State#state.q ]}};
+request({put_chars,M,F,As}, State) ->
+ {ok, ok, State#state{q=[{put_chars, M,F,As} | State#state.q ]}};
+request({get_until, Encoding, Prompt, M, F, As}, State) ->
+ {ok, convert(State#state.nxt, Encoding, State#state.mode), State#state{nxt = eof, q = [{get_until, Encoding, Prompt, M, F, As} | State#state.q]}};
+request({get_chars, Encoding, Prompt, N}, State) ->
+ {ok, convert(State#state.nxt, Encoding, State#state.mode), State#state{nxt = eof,
+ q = [{get_chars, Encoding, Prompt, N} |
+ State#state.q]}};
+request({get_line, Encoding, Prompt}, State) ->
+ {ok, convert(State#state.nxt, Encoding, State#state.mode),
+ State#state{nxt = eof,
+ q = [{get_line, Encoding, Prompt} |
+ State#state.q]}};
+request({get_until, Prompt, M, F, As}, State) ->
+ {ok, convert(State#state.nxt, latin1, State#state.mode),
+ State#state{nxt = eof,
+ q = [{get_until, Prompt, M, F, As} | State#state.q]}};
+request({get_chars, Prompt, N}, State) ->
+ {ok, convert(State#state.nxt, latin1, State#state.mode),
+ State#state{nxt = eof,
+ q = [{get_chars, Prompt, N} |
+ State#state.q]}};
+request({get_line, Prompt}, State) ->
+ {ok, convert(State#state.nxt, latin1, State#state.mode),
+ State#state{nxt = eof,
+ q = [{get_line, Prompt} |
+ State#state.q]}};
+request({get_geomentry,_}, State) ->
+ {error, {error,enotsup}, State};
+request({setopts, Opts}, State) when Opts =:= [{binary, false}]; Opts =:= [list] ->
+ {ok, ok, State#state{q=[{setopts, Opts} | State#state.q ], mode = list}};
+request({setopts, Opts}, State) when Opts =:= [{binary, true}]; Opts =:= [binary] ->
+ {ok, ok, State#state{q=[{setopts, Opts} | State#state.q ], mode = binary}};
+request(getopts, State) ->
+ {ok, case State#state.mode of
+ list -> [{binary,false}];
+ binary -> [{binary, true}]
+ end, State#state{q=[getopts | State#state.q ]}};
+request({requests, Reqs}, State) ->
+ multi_request(Reqs, {ok, ok, State}).
+
+multi_request([R|Rs], {ok, _Res, State}) ->
+ multi_request(Rs, request(R, State));
+multi_request([_|_], Error) ->
+ Error;
+multi_request([], State) ->
+ State.
+
+convert(Atom,_,_) when is_atom(Atom) ->
+ Atom;
+convert(Data, unicode, list) ->
+ unicode:characters_to_list(Data,unicode);
+convert(Data, latin1, list) ->
+ try
+ L = unicode:characters_to_list(Data, unicode),
+ [ true = Ch =< 255 || Ch <- L ],
+ L
+ catch
+ _:_ ->
+ {error, {cannot_convert, unicode, latin1}}
+ end;
+convert(Data, unicode, binary) ->
+ unicode:characters_to_binary(Data,unicode,unicode);
+convert(Data, latin1, binary) ->
+ case unicode:characters_to_binary(Data, unicode, latin1) of
+ Bin when is_binary(Bin) ->
+ Bin;
+ _ ->
+ {error, {cannot_convert, unicode, latin1}}
+ end.
+
+hostname() ->
+ from($@, atom_to_list(node())).
+
+from(H, [H | T]) -> T;
+from(H, [_ | T]) -> from(H, T);
+from(_, []) -> [].
+
+chomp([]) ->
+ [];
+chomp([$\n]) ->
+ [];
+chomp([H|T]) ->
+ [H|chomp(T)];
+chomp(<<>>) ->
+ <<>>;
+chomp(<<$\n>>) ->
+ <<>>;
+chomp(<<Ch,Rest/binary>>) ->
+ X = chomp(Rest),
+ <<Ch,X/binary>>;
+chomp(Atom) ->
+ Atom.
diff --git a/lib/stdlib/test/io_proto_SUITE_data/external_utf16_big_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/external_utf16_big_bom.dat
new file mode 100644
index 0000000000..82f99cbded
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/external_utf16_big_bom.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/external_utf16_little_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/external_utf16_little_bom.dat
new file mode 100644
index 0000000000..99b3970950
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/external_utf16_little_bom.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/external_utf8_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/external_utf8_bom.dat
new file mode 100644
index 0000000000..3c87ce519e
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/external_utf8_bom.dat
@@ -0,0 +1 @@
+Återerövra \ No newline at end of file
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_latin1.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_latin1.dat
new file mode 100644
index 0000000000..a9ce7d28e1
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_latin1.dat
@@ -0,0 +1,2 @@
+���
+���
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big.dat
new file mode 100644
index 0000000000..c611508766
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big_bom.dat
new file mode 100644
index 0000000000..e35436aa97
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_big_bom.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little.dat
new file mode 100644
index 0000000000..4e2bdfa7cd
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little_bom.dat
new file mode 100644
index 0000000000..e1960d2261
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf16_little_bom.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big.dat
new file mode 100644
index 0000000000..c9170fba93
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big_bom.dat
new file mode 100644
index 0000000000..e95dcec297
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_big_bom.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little.dat
new file mode 100644
index 0000000000..8eb6eb43bb
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little_bom.dat
new file mode 100644
index 0000000000..816f19c0ba
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf32_little_bom.dat
Binary files differ
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf8.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf8.dat
new file mode 100644
index 0000000000..1fcb997763
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf8.dat
@@ -0,0 +1 @@
+тхе биг ред фоь хас соь Τηε βιγ ρεδ σοχ αρε φοχεσ \ No newline at end of file
diff --git a/lib/stdlib/test/io_proto_SUITE_data/testdata_utf8_bom.dat b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf8_bom.dat
new file mode 100644
index 0000000000..5a38c1519e
--- /dev/null
+++ b/lib/stdlib/test/io_proto_SUITE_data/testdata_utf8_bom.dat
@@ -0,0 +1 @@
+тхе биг ред фоь хас соь Τηε βιγ ρεδ σοχ αρε φοχεσ \ No newline at end of file
diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl
new file mode 100644
index 0000000000..0089e874c8
--- /dev/null
+++ b/lib/stdlib/test/lists_SUITE.erl
@@ -0,0 +1,2657 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------
+%%% Purpose: Test suite for the 'lists' module.
+%%%-----------------------------------------------------------------
+
+-module(lists_SUITE).
+-include("test_server.hrl").
+
+
+% Default timetrap timeout (set in init_per_testcase).
+% This should be set relatively high (10-15 times the expected
+% max testcasetime).
+-define(default_timeout, ?t:minutes(4)).
+
+% Test server specific exports
+-export([all/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+% Test cases must be exported.
+-export([member/1, reverse/1,
+ keymember/1, keysearch_keyfind/1,
+ keystore/1, keytake/1,
+ append/1, append_1/1, append_2/1,
+ seq/1, seq_loop/1, seq_2/1, seq_3/1, seq_2_e/1, seq_3_e/1,
+ sublist/1, flatten/1,
+ sublist_2/1, sublist_3/1, sublist_2_e/1, sublist_3_e/1,
+ flatten_1/1, flatten_2/1, flatten_1_e/1, flatten_2_e/1,
+ dropwhile/1,
+ sort/1, sort_1/1, sort_stable/1, merge/1, rmerge/1, sort_rand/1,
+ usort/1, usort_1/1, usort_stable/1, umerge/1, rumerge/1,usort_rand/1,
+ keymerge/1, rkeymerge/1,
+ keysort/1, keysort_1/1, keysort_i/1, keysort_stable/1,
+ keysort_rand/1, keysort_error/1,
+ ukeymerge/1, rukeymerge/1,
+ ukeysort/1, ukeysort_1/1, ukeysort_i/1, ukeysort_stable/1,
+ ukeysort_rand/1, ukeysort_error/1,
+ funmerge/1, rfunmerge/1,
+ funsort/1, funsort_1/1, funsort_stable/1, funsort_rand/1,
+ funsort_error/1,
+ ufunmerge/1, rufunmerge/1,
+ ufunsort/1, ufunsort_1/1, ufunsort_stable/1, ufunsort_rand/1,
+ ufunsort_error/1,
+ zip_unzip/1, zip_unzip3/1, zipwith/1, zipwith3/1,
+ filter_partition/1,
+ tickets/1, otp_5939/1, otp_6023/1, otp_6606/1, otp_7230/1,
+ suffix/1, subtract/1]).
+
+%% Sort randomized lists until stopped.
+%%
+%% If you update some of the sort or merge functions, you should
+%% definitely let sort_loop work for a couple of hours or days. Try
+%% both sort_loop/0 and sort_loop/1 with a small argument (30-50 say).
+
+-export([sort_loop/0, sort_loop/1, sloop/1]).
+
+%% Internal export.
+-export([make_fun/1]).
+
+%%
+%% all/1
+%%
+all(doc) ->
+ [];
+all(suite) ->
+ [append, reverse, member, keymember, keysearch_keyfind, keystore, keytake,
+ dropwhile,
+ sort, usort, keysort, ukeysort,
+ funsort, ufunsort, sublist, flatten, seq,
+ zip_unzip, zip_unzip3, zipwith, zipwith3,
+ filter_partition, tickets, suffix, subtract].
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%
+% Test cases starts here.
+%
+append(doc) ->
+ ["Tests lists:append/1 & lists:append/2"];
+append(suite) ->
+ [append_1, append_2].
+
+append_1(doc) -> [];
+append_1(suite) -> [];
+append_1(Config) when is_list(Config) ->
+ ?line "abcdef"=lists:append(["abc","def"]),
+ ?line [hej, du,[glade, [bagare]]]=
+ lists:append([[hej], [du], [[glade, [bagare]]]]),
+ ?line [10, [elem]]=lists:append([[10], [[elem]]]),
+ ok.
+
+append_2(doc) -> [];
+append_2(suite) -> [];
+append_2(Config) when is_list(Config) ->
+ ?line "abcdef"=lists:append("abc", "def"),
+ ?line [hej, du]=lists:append([hej], [du]),
+ ?line [10, [elem]]=lists:append([10], [[elem]]),
+ ok.
+
+reverse(suite) ->
+ [];
+reverse(doc) ->
+ ["Tests the lists:reverse() implementation. The function is "
+ "`non-blocking', and only processes a fixed number of elements "
+ "at a time."];
+reverse(Config) when is_list(Config) ->
+ ?line reverse_test(0),
+ ?line reverse_test(1),
+ ?line reverse_test(2),
+ ?line reverse_test(128),
+ ?line reverse_test(256),
+ ?line reverse_test(1000),
+ ?line reverse_test(1998),
+ ?line reverse_test(1999),
+ ?line reverse_test(2000),
+ ?line reverse_test(2001),
+ ?line reverse_test(3998),
+ ?line reverse_test(3999),
+ ?line reverse_test(4000),
+ ?line reverse_test(4001),
+ ?line reverse_test(60001),
+ ?line reverse_test(100007),
+ ok.
+
+reverse_test(0) ->
+ case lists:reverse([]) of
+ [] ->
+ ok;
+ _Other ->
+ error
+ end;
+reverse_test(Num) ->
+ List0 = ['The Element'|lists:duplicate(Num, 'Ele')],
+ List = lists:reverse(List0),
+ ['Ele'|_] = List,
+ 'The Element' = lists:last(List),
+ List0 = lists:reverse(List),
+ ok.
+
+member(doc) ->
+ ["Tests the lists:member() implementation."
+ "This test case depends on lists:reverse() to work, "
+ "wich is tested in a separate test case."];
+member(Config) when is_list(Config) ->
+ ?line {'EXIT',{badarg,_}} = (catch lists:member(45, {a,b,c})),
+ ?line {'EXIT',{badarg,_}} = (catch lists:member(45, [0|non_list_tail])),
+ ?line false = lists:member(4233, []),
+ ?line member_test(1),
+ ?line member_test(100),
+ ?line member_test(256),
+ ?line member_test(1000),
+ ?line member_test(1998),
+ ?line member_test(1999),
+ ?line member_test(2000),
+ ?line member_test(2001),
+ ?line member_test(3998),
+ ?line member_test(3999),
+ ?line member_test(4000),
+ ?line member_test(4001),
+ ?line member_test(100008),
+ ok.
+
+member_test(Num) ->
+ List0 = ['The Element'|lists:duplicate(Num, 'Elem')],
+ true = lists:member('The Element', List0),
+ true = lists:member('Elem', List0),
+ false = lists:member(arne_anka, List0),
+ false = lists:member({a,b,c}, List0),
+ List = lists:reverse(List0),
+ true = lists:member('The Element', List),
+ true = lists:member('Elem', List),
+ false = lists:member(arne_anka, List),
+ false = lists:member({a,b,c}, List).
+
+keymember(Config) when is_list(Config) ->
+ ?line false = lists:keymember(anything_goes, 1, []),
+ ?line {'EXIT',{badarg,_}} = (catch lists:keymember(anything_goes, -1, [])),
+ ?line {'EXIT',{badarg,_}} = (catch lists:keymember(anything_goes, 0, [])),
+ ?line {'EXIT',{badarg,_}} = (catch lists:keymember(anything_goes, 1, {1,2,3})),
+ List = [{52.0,a},{-19,b,c},{37.5,d},an_atom,42.0,{39},{45,{x,y,z}}],
+
+ ?line false = lists:keymember(333, 5, List),
+ ?line false = lists:keymember(333, 999, List),
+ ?line false = lists:keymember(37, 1, List),
+
+ ?line true = lists:keymember(52.0, 1, List),
+ ?line true = lists:keymember(52, 1, List),
+ ?line true = lists:keymember(-19, 1, List),
+ ?line true = lists:keymember(-19.0, 1, List),
+ ?line true = lists:keymember(37.5, 1, List),
+ ?line true = lists:keymember(39, 1, List),
+ ?line true = lists:keymember(39.0, 1, List),
+ ?line true = lists:keymember(45, 1, List),
+ ?line true = lists:keymember(45.0, 1, List),
+
+ ?line true = lists:keymember(a, 2, List),
+ ?line true = lists:keymember(b, 2, List),
+ ?line true = lists:keymember(c, 3, List),
+ ?line true = lists:keymember(d, 2, List),
+ ?line true = lists:keymember({x,y,z}, 2, List),
+
+ ?line Long0 = lists:seq(1, 100007),
+ ?line false = lists:keymember(kalle, 1, Long0),
+ ?line Long = lists:foldl(fun(E, A) -> [{1/E,E}|A] end, [], Long0),
+ ?line true = lists:keymember(1, 2, Long),
+ ?line true = lists:keymember(2, 2, Long),
+ ?line true = lists:keymember(1.0, 2, Long),
+ ?line true = lists:keymember(2.0, 2, Long),
+ ?line true = lists:keymember(100006, 2, Long),
+ ok.
+
+keysearch_keyfind(Config) when is_list(Config) ->
+ ?line false = key_search_find(anything_goes, 1, []),
+ ?line {'EXIT',{badarg,_}} = (catch key_search_find(anything_goes, -1, [])),
+ ?line {'EXIT',{badarg,_}} = (catch key_search_find(anything_goes, 0, [])),
+ ?line {'EXIT',{badarg,_}} = (catch key_search_find(anything_goes, 1, {1,2,3})),
+
+ First = {x,42.0},
+ Second = {y,-77},
+ Third = {z,[a,b,c],{5.0}},
+ List = [First,Second,Third],
+
+ ?line false = key_search_find(333, 1, []),
+ ?line false = key_search_find(333, 5, List),
+ ?line false = key_search_find(333, 999, List),
+ ?line false = key_search_find(37, 1, List),
+
+ ?line {value,First} = key_search_find(42, 2, List),
+ ?line {value,First} = key_search_find(42.0, 2, List),
+
+ ?line {value,Second} = key_search_find(-77, 2, List),
+ ?line {value,Second} = key_search_find(-77.0, 2, List),
+
+ ?line {value,Third} = key_search_find(z, 1, List),
+ ?line {value,Third} = key_search_find([a,b,c], 2, List),
+ ?line {value,Third} = key_search_find({5}, 3, List),
+ ?line {value,Third} = key_search_find({5.0}, 3, List),
+
+ ?line Long0 = lists:seq(1, 100007),
+ ?line false = key_search_find(kalle, 1, Long0),
+ ?line Long = lists:foldl(fun(E, A) -> [{1/E,float(E)}|A] end, [], Long0),
+ ?line {value,{_,1.0}} = key_search_find(1, 2, Long),
+ ?line {value,{_,1.0}} = key_search_find(1.0, 2, Long),
+ ?line {value,{_,2.0}} = key_search_find(2, 2, Long),
+ ?line {value,{_,2.0}} = key_search_find(2.0, 2, Long),
+ ?line {value,{_,33988.0}} = key_search_find(33988, 2, Long),
+ ?line {value,{_,33988.0}} = key_search_find(33988.0, 2, Long),
+ ok.
+
+%% Test both lists:keysearch/3 and lists:keyfind/3. The only
+%% difference between these two functions is that lists:keysearch/3
+%% wraps a successfully returned tuple in a value tuple.
+%%
+key_search_find(Key, Pos, List) ->
+ case lists:keyfind(Key, Pos, List) of
+ false ->
+ false = lists:keysearch(Key, Pos, List);
+ Tuple when is_tuple(Tuple) ->
+ {value,Tuple} = lists:keysearch(Key, Pos, List)
+ end.
+
+dropwhile(Config) when is_list(Config) ->
+ ?line F = fun(C) -> C =:= $@ end,
+
+ ?line [] = lists:dropwhile(F, []),
+ ?line [a] = lists:dropwhile(F, [a]),
+ ?line [a,b] = lists:dropwhile(F, [a,b]),
+ ?line [a,b,c] = lists:dropwhile(F, [a,b,c]),
+
+ ?line [] = lists:dropwhile(F, [$@]),
+ ?line [] = lists:dropwhile(F, [$@,$@]),
+ ?line [a,$@] = lists:dropwhile(F, [$@,a,$@]),
+
+ ?line [$k] = lists:dropwhile(F, [$@,$k]),
+ ?line [$k,$l] = lists:dropwhile(F, [$@,$@,$k,$l]),
+ ?line [a] = lists:dropwhile(F, [$@,$@,$@,a]),
+
+ ?line [a,$@,b] = lists:dropwhile(F, [$@,a,$@,b]),
+ ?line [a,$@,b] = lists:dropwhile(F, [$@,$@,a,$@,b]),
+ ?line [a,$@,b] = lists:dropwhile(F, [$@,$@,$@,a,$@,b]),
+
+ Long = lists:seq(1, 1024),
+ Shorter = lists:seq(800, 1024),
+
+ ?line Shorter = lists:dropwhile(fun(E) -> E < 800 end, Long),
+
+ ok.
+
+keystore(doc) ->
+ ["OTP-XXX."];
+keystore(suite) -> [];
+keystore(Config) when is_list(Config) ->
+ ?line {'EXIT',_} = (catch lists:keystore(key, 0, [], {1})),
+ ?line {'EXIT',_} = (catch lists:keystore(key, 1, {}, {})),
+ ?line {'EXIT',_} = (catch lists:keystore(key, 1, {a,b}, {})),
+ ?line {'EXIT', _} = (catch lists:keystore(a, 2, [{1,a}], b)),
+ T = {k,17},
+ ?line [T] = lists:keystore(a, 2, [], T),
+ ?line [{1,a},{2,b},{k,17}] = lists:keystore(c, 2, [{1,a},{2,b}],T),
+ L = [{1,a},{2,b},{3,c}],
+ ?line [{k,17},{2,b},{3,c}] = lists:keystore(a, 2, L, T),
+ ?line [{1,a},{k,17},{3,c}] = lists:keystore(b, 2, L, T),
+ ?line [{1,a},{2,b},{k,17}] = lists:keystore(c, 2, L, T),
+ ?line [{2,b}] = lists:keystore(a, 2, [{1,a}], {2,b}),
+ ?line [{1,a}] = lists:keystore(foo, 1, [], {1,a}),
+ ok.
+
+keytake(doc) ->
+ ["OTP-XXX."];
+keytake(suite) -> [];
+keytake(Config) when is_list(Config) ->
+ ?line {'EXIT',_} = (catch lists:keytake(key, 0, [])),
+ ?line {'EXIT',_} = (catch lists:keytake(key, 1, {})),
+ ?line {'EXIT',_} = (catch lists:keytake(key, 1, {a,b})),
+ ?line false = lists:keytake(key, 2, [{a}]),
+ ?line false = lists:keytake(key, 1, [a]),
+ ?line false = lists:keytake(k, 1, []),
+ ?line false = lists:keytake(k, 1, [{a},{b},{c}]),
+ L = [{a,1},{b,2},{c,3}],
+ ?line {value,{a,1},[{b,2},{c,3}]} = lists:keytake(1, 2, L),
+ ?line {value,{b,2},[{a,1},{c,3}]} = lists:keytake(2, 2, L),
+ ?line {value,{c,3},[{a,1},{b,2}]} = lists:keytake(3, 2, L),
+ ?line false = lists:keytake(4, 2, L),
+ ok.
+
+sort(doc) ->
+ ["Tests merge functions and lists:sort/1"];
+sort(suite) ->
+ %% [merge, rmerge, sort_1, sort_rand, sort_stable].
+ [merge, rmerge, sort_1, sort_rand].
+
+merge(doc) -> ["merge functions"];
+merge(suite) -> [];
+merge(Config) when is_list(Config) ->
+
+ %% merge list of lists
+ ?line [] = lists:merge([]),
+ ?line [] = lists:merge([[]]),
+ ?line [] = lists:merge([[],[]]),
+ ?line [] = lists:merge([[],[],[]]),
+ ?line [1] = lists:merge([[1]]),
+ ?line [1,1,2,2] = lists:merge([[1,2],[1,2]]),
+ ?line [1] = lists:merge([[1],[],[]]),
+ ?line [1] = lists:merge([[],[1],[]]),
+ ?line [1] = lists:merge([[],[],[1]]),
+ ?line [1,2] = lists:merge([[1],[2],[]]),
+ ?line [1,2] = lists:merge([[1],[],[2]]),
+ ?line [1,2] = lists:merge([[],[1],[2]]),
+ ?line [1,2,3,4,5,6] = lists:merge([[1,2],[],[5,6],[],[3,4],[]]),
+ ?line [1,2,3,4] = lists:merge([[4],[3],[2],[1]]),
+ ?line [1,2,3,4,5] = lists:merge([[1],[2],[3],[4],[5]]),
+ ?line [1,2,3,4,5,6] = lists:merge([[1],[2],[3],[4],[5],[6]]),
+ ?line [1,2,3,4,5,6,7,8,9] =
+ lists:merge([[1],[2],[3],[4],[5],[6],[7],[8],[9]]),
+ Seq = lists:seq(1,100),
+ ?line true = Seq == lists:merge(lists:map(fun(E) -> [E] end, Seq)),
+
+ Two = [1,2],
+ Six = [1,2,3,4,5,6],
+
+ %% 2-way merge
+ ?line [] = lists:merge([], []),
+ ?line Two = lists:merge(Two, []),
+ ?line Two = lists:merge([], Two),
+ ?line Six = lists:merge([1,3,5], [2,4,6]),
+ ?line Six = lists:merge([2,4,6], [1,3,5]),
+ ?line Six = lists:merge([1,2,3], [4,5,6]),
+ ?line Six = lists:merge([4,5,6], [1,2,3]),
+ ?line Six = lists:merge([1,2,5],[3,4,6]),
+ ?line [1,2,3,5,7] = lists:merge([1,3,5,7], [2]),
+ ?line [1,2,3,4,5,7] = lists:merge([1,3,5,7], [2,4]),
+ ?line [1,2,3,4,5,6,7] = lists:merge([1,3,5,7], [2,4,6]),
+ ?line [1,2,3,5,7] = lists:merge([2], [1,3,5,7]),
+ ?line [1,2,3,4,5,7] = lists:merge([2,4], [1,3,5,7]),
+ ?line [1,2,3,4,5,6,7] = lists:merge([2,4,6], [1,3,5,7]),
+
+ %% 3-way merge
+ ?line [] = lists:merge3([], [], []),
+ ?line Two = lists:merge3([], [], Two),
+ ?line Two = lists:merge3([], Two, []),
+ ?line Two = lists:merge3(Two, [], []),
+ ?line Six = lists:merge3([], [1,3,5], [2,4,6]),
+ ?line Six = lists:merge3([1,3,5], [], [2,4,6]),
+ ?line Six = lists:merge3([1,3,5], [2,4,6], []),
+ ?line Nine = lists:merge3([1,4,7],[2,5,8],[3,6,9]),
+ ?line Nine = lists:merge3([1,4,7],[3,6,9],[2,5,8]),
+ ?line Nine = lists:merge3([3,6,9],[1,4,7],[2,5,8]),
+ ?line Nine = lists:merge3([4,5,6],[1,2,3],[7,8,9]),
+ ?line Nine = lists:merge3([1,2,3],[4,5,6],[7,8,9]),
+ ?line Nine = lists:merge3([7,8,9],[4,5,6],[1,2,3]),
+ ?line Nine = lists:merge3([4,5,6],[7,8,9],[1,2,3]),
+
+ ok.
+
+rmerge(doc) -> ["reverse merge functions"];
+rmerge(suite) -> [];
+rmerge(Config) when is_list(Config) ->
+
+ Two = [2,1],
+ Six = [6,5,4,3,2,1],
+
+ %% 2-way reversed merge
+ ?line [] = lists:rmerge([], []),
+ ?line Two = lists:rmerge(Two, []),
+ ?line Two = lists:rmerge([], Two),
+ ?line Six = lists:rmerge([5,3,1], [6,4,2]),
+ ?line Six = lists:rmerge([6,4,2], [5,3,1]),
+ ?line Six = lists:rmerge([3,2,1], [6,5,4]),
+ ?line Six = lists:rmerge([6,5,4], [3,2,1]),
+ ?line Six = lists:rmerge([4,3,2],[6,5,1]),
+ ?line [7,6,5,3,1] = lists:rmerge([7,5,3,1], [6]),
+ ?line [7,6,5,4,3,1] = lists:rmerge([7,5,3,1], [6,4]),
+ ?line [7,6,5,4,3,2,1] = lists:rmerge([7,5,3,1], [6,4,2]),
+ ?line [7,5,3,2,1] = lists:rmerge([2], [7,5,3,1]),
+ ?line [7,5,4,3,2,1] = lists:rmerge([4,2], [7,5,3,1]),
+ ?line [7,6,5,4,3,2,1] = lists:rmerge([6,4,2], [7,5,3,1]),
+
+ Nine = [9,8,7,6,5,4,3,2,1],
+
+ %% 3-way reversed merge
+ ?line [] = lists:rmerge3([], [], []),
+ ?line Two = lists:rmerge3([], [], Two),
+ ?line Two = lists:rmerge3([], Two, []),
+ ?line Two = lists:rmerge3(Two, [], []),
+ ?line Six = lists:rmerge3([], [5,3,1], [6,4,2]),
+ ?line Six = lists:rmerge3([5,3,1], [], [6,4,2]),
+ ?line Six = lists:rmerge3([5,3,1], [6,4,2], []),
+ ?line Nine = lists:rmerge3([7,4,1],[8,5,2],[9,6,3]),
+ ?line Nine = lists:rmerge3([7,4,1],[9,6,3],[8,5,2]),
+ ?line Nine = lists:rmerge3([9,6,3],[7,4,1],[8,5,2]),
+ ?line Nine = lists:rmerge3([6,5,4],[3,2,1],[9,8,7]),
+ ?line Nine = lists:rmerge3([3,2,1],[6,5,4],[9,8,7]),
+ ?line Nine = lists:rmerge3([9,8,7],[6,5,4],[3,2,1]),
+ ?line Nine = lists:rmerge3([6,5,4],[9,8,7],[3,2,1]),
+
+ ok.
+
+sort_1(doc) -> ["sort/1"];
+sort_1(suite) -> [];
+sort_1(Config) when is_list(Config) ->
+ ?line [] = lists:sort([]),
+ ?line [a] = lists:sort([a]),
+ ?line [a,a] = lists:sort([a,a]),
+ ?line [a,b] = lists:sort([a,b]),
+ ?line [a,b] = lists:sort([b,a]),
+ ?line [1,1] = lists:sort([1,1]),
+ ?line [1,1,2,3] = lists:sort([1,1,3,2]),
+ ?line [1,2,3,3] = lists:sort([3,3,1,2]),
+ ?line [1,1,1,1] = lists:sort([1,1,1,1]),
+ ?line [1,1,1,2,2,2,3,3,3] = lists:sort([3,3,3,2,2,2,1,1,1]),
+ ?line [1,1,1,2,2,2,3,3,3] = lists:sort([1,1,1,2,2,2,3,3,3]),
+
+ ?line lists:foreach(fun check/1, perms([1,2,3])),
+ ?line lists:foreach(fun check/1, perms([1,2,3,4,5,6,7,8])),
+ ok.
+
+sort_rand(doc) -> ["sort/1 on big randomized lists"];
+sort_rand(suite) -> [];
+sort_rand(Config) when is_list(Config) ->
+ ?line ok = check(biglist(10)),
+ ?line ok = check(biglist(100)),
+ ?line ok = check(biglist(1000)),
+ ?line ok = check(biglist(10000)),
+ ok.
+
+%% sort/1 was really stable for a while - the order of equal elements
+%% was kept - but since the performance suffered a bit, this "feature"
+%% was removed.
+sort_stable(doc) -> ["sort/1 should be stable for equal terms."];
+sort_stable(suite) -> [];
+sort_stable(Config) when is_list(Config) ->
+ ?line ok = check_stability(bigfunlist(10)),
+ ?line ok = check_stability(bigfunlist(100)),
+ ?line ok = check_stability(bigfunlist(1000)),
+ ?line case erlang:system_info(modified_timing_level) of
+ undefined -> ok = check_stability(bigfunlist(10000));
+ _ -> ok
+ end,
+ ok.
+
+check([]) ->
+ ok;
+check(L) ->
+ S = lists:sort(L),
+ case {length(L) == length(S), check(hd(S), tl(S))} of
+ {true,ok} ->
+ ok;
+ _ ->
+ io:format("~w~n", [L]),
+ erlang:error(check)
+ end.
+
+check(_A, []) ->
+ ok;
+check(A, [B | L]) when A =< B ->
+ check(B, L);
+check(_A, _L) ->
+ no.
+
+%% The check that sort/1 is stable is no longer used.
+%% Equal elements are no longer always kept in order.
+check_stability(L) ->
+ S = lists:sort(L),
+ LP = explicit_pid(L),
+ SP = explicit_pid(S),
+ check_sorted(1, 2, LP, SP).
+
+explicit_pid(L) ->
+ lists:reverse(expl_pid(L, [])).
+
+expl_pid([{I,F} | T], L) when is_function(F) ->
+ expl_pid(T, [{I,fun_pid(F)} | L]);
+expl_pid([], L) ->
+ L.
+
+usort(doc) ->
+ ["Tests unique merge functions and lists:usort/1"];
+usort(suite) ->
+ [umerge, rumerge, usort_1, usort_rand, usort_stable].
+
+usort_1(suite) -> [];
+usort_1(doc) -> [""];
+usort_1(Conf) when is_list(Conf) ->
+ ?line [] = lists:usort([]),
+ ?line [1] = lists:usort([1]),
+ ?line [1] = lists:usort([1,1]),
+ ?line [1] = lists:usort([1,1,1,1,1]),
+ ?line [1,2] = lists:usort([1,2]),
+ ?line [1,2] = lists:usort([1,2,1]),
+ ?line [1,2] = lists:usort([1,2,2]),
+ ?line [1,2,3] = lists:usort([1,3,2]),
+ ?line [1,3] = lists:usort([3,1,3]),
+ ?line [0,1,3] = lists:usort([3,1,0]),
+ ?line [1,2,3] = lists:usort([3,1,2]),
+ ?line [1,2] = lists:usort([2,1,1]),
+ ?line [1,2] = lists:usort([2,1]),
+ ?line [0,3,4,8,9] = lists:usort([3,8,9,0,9,4]),
+
+ ?line lists:foreach(fun ucheck/1, perms([1,2,3])),
+ ?line lists:foreach(fun ucheck/1, perms([1,2,3,4,5,6,2,1])),
+
+ ok.
+
+umerge(suite) -> [];
+umerge(doc) -> [""];
+umerge(Conf) when is_list(Conf) ->
+ %% merge list of lists
+ ?line [] = lists:umerge([]),
+ ?line [] = lists:umerge([[]]),
+ ?line [] = lists:umerge([[],[]]),
+ ?line [] = lists:umerge([[],[],[]]),
+ ?line [1] = lists:umerge([[1]]),
+ ?line [1,2] = lists:umerge([[1,2],[1,2]]),
+ ?line [1] = lists:umerge([[1],[],[]]),
+ ?line [1] = lists:umerge([[],[1],[]]),
+ ?line [1] = lists:umerge([[],[],[1]]),
+ ?line [1,2] = lists:umerge([[1],[2],[]]),
+ ?line [1,2] = lists:umerge([[1],[],[2]]),
+ ?line [1,2] = lists:umerge([[],[1],[2]]),
+ ?line [1,2,3,4,5,6] = lists:umerge([[1,2],[],[5,6],[],[3,4],[]]),
+ ?line [1,2,3,4] = lists:umerge([[4],[3],[2],[1]]),
+ ?line [1,2,3,4,5] = lists:umerge([[1],[2],[3],[4],[5]]),
+ ?line [1,2,3,4,5,6] = lists:umerge([[1],[2],[3],[4],[5],[6]]),
+ ?line [1,2,3,4,5,6,7,8,9] =
+ lists:umerge([[1],[2],[3],[4],[5],[6],[7],[8],[9]]),
+ ?line [1,2,4,6,8] = lists:umerge([[1,2],[2,4,6,8]]),
+ Seq = lists:seq(1,100),
+ ?line true = Seq == lists:umerge(lists:map(fun(E) -> [E] end, Seq)),
+
+ Two = [1,2],
+ Six = [1,2,3,4,5,6],
+
+ %% 2-way unique merge
+ ?line [] = lists:umerge([], []),
+ ?line Two = lists:umerge(Two, []),
+ ?line Two = lists:umerge([], Two),
+ ?line Six = lists:umerge([1,3,5], [2,4,6]),
+ ?line Six = lists:umerge([2,4,6], [1,3,5]),
+ ?line Six = lists:umerge([1,2,3], [4,5,6]),
+ ?line Six = lists:umerge([4,5,6], [1,2,3]),
+ ?line Six = lists:umerge([1,2,5],[3,4,6]),
+ ?line [1,2,3,5,7] = lists:umerge([1,3,5,7], [2]),
+ ?line [1,2,3,4,5,7] = lists:umerge([1,3,5,7], [2,4]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge([1,3,5,7], [2,4,6]),
+ ?line [1,2,3,5,7] = lists:umerge([2], [1,3,5,7]),
+ ?line [1,2,3,4,5,7] = lists:umerge([2,4], [1,3,5,7]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge([2,4,6], [1,3,5,7]),
+
+ ?line [1,2,3,5,7] = lists:umerge([1,2,3,5,7], [2]),
+ ?line [1,2,3,4,5,7] = lists:umerge([1,2,3,4,5,7], [2,4]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge([1,2,3,4,5,6,7], [2,4,6]),
+ ?line [1,2,3,5,7] = lists:umerge([2], [1,2,3,5,7]),
+ ?line [1,2,3,4,5,7] = lists:umerge([2,4], [1,2,3,4,5,7]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge([2,4,6], [1,2,3,4,5,6,7]),
+
+ %% 3-way unique merge
+ ?line [] = lists:umerge3([], [], []),
+ ?line Two = lists:umerge3([], [], Two),
+ ?line Two = lists:umerge3([], Two, []),
+ ?line Two = lists:umerge3(Two, [], []),
+ ?line Six = lists:umerge3([], [1,3,5], [2,4,6]),
+ ?line Six = lists:umerge3([1,3,5], [], [2,4,6]),
+ ?line Six = lists:umerge3([1,3,5], [2,4,6], []),
+ ?line Nine = lists:umerge3([1,4,7],[2,5,8],[3,6,9]),
+ ?line Nine = lists:umerge3([1,4,7],[3,6,9],[2,5,8]),
+ ?line Nine = lists:umerge3([3,6,9],[1,4,7],[2,5,8]),
+ ?line Nine = lists:umerge3([4,5,6],[1,2,3],[7,8,9]),
+ ?line Nine = lists:umerge3([1,2,3],[4,5,6],[7,8,9]),
+ ?line Nine = lists:umerge3([7,8,9],[4,5,6],[1,2,3]),
+ ?line Nine = lists:umerge3([4,5,6],[7,8,9],[1,2,3]),
+
+ ?line [1,2,3] = lists:umerge3([1,2,3],[1,2,3],[1,2,3]),
+ ?line [1,2,3,4] = lists:umerge3([2,3,4],[1,2,3],[2,3,4]),
+ ?line [1,2,3] = lists:umerge3([1,2,3],[2,3],[1,2,3]),
+ ?line [1,2,3,4] = lists:umerge3([2,3,4],[3,4],[1,2,3]),
+
+ ok.
+
+rumerge(suite) -> [];
+rumerge(doc) -> [""];
+rumerge(Conf) when is_list(Conf) ->
+ Two = [2,1],
+ Six = [6,5,4,3,2,1],
+
+ %% 2-way reversed unique merge
+ ?line [] = lists:rumerge([], []),
+ ?line Two = lists:rumerge(Two, []),
+ ?line Two = lists:rumerge([], Two),
+ ?line Six = lists:rumerge([5,3,1], [6,4,2]),
+ ?line Six = lists:rumerge([6,4,2], [5,3,1]),
+ ?line Six = lists:rumerge([3,2,1], [6,5,4]),
+ ?line Six = lists:rumerge([6,5,4], [3,2,1]),
+ ?line Six = lists:rumerge([4,3,2],[6,5,1]),
+ ?line [7,6,5,3,1] = lists:rumerge([7,5,3,1], [6]),
+ ?line [7,6,5,4,3,1] = lists:rumerge([7,5,3,1], [6,4]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge([7,5,3,1], [6,4,2]),
+ ?line [7,5,3,2,1] = lists:rumerge([2], [7,5,3,1]),
+ ?line [7,5,4,3,2,1] = lists:rumerge([4,2], [7,5,3,1]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge([6,4,2], [7,5,3,1]),
+
+ ?line [7,6,5,3,1] = lists:rumerge([7,6,5,3,1], [6]),
+ ?line [7,6,5,4,3,1] = lists:rumerge([7,6,5,4,3,1], [6,4]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge([7,6,5,4,3,2,1], [6,4,2]),
+ ?line [7,5,3,2,1] = lists:rumerge([2], [7,5,3,2,1]),
+ ?line [7,5,4,3,2,1] = lists:rumerge([4,2], [7,5,4,3,2,1]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge([6,4,2], [7,6,5,4,3,2,1]),
+
+ Nine = [9,8,7,6,5,4,3,2,1],
+
+ %% 3-way reversed unique merge
+ ?line [] = lists:rumerge3([], [], []),
+ ?line Two = lists:rumerge3([], [], Two),
+ ?line Two = lists:rumerge3([], Two, []),
+ ?line Two = lists:rumerge3(Two, [], []),
+ ?line Six = lists:rumerge3([], [5,3,1], [6,4,2]),
+ ?line Six = lists:rumerge3([5,3,1], [], [6,4,2]),
+ ?line Six = lists:rumerge3([5,3,1], [6,4,2], []),
+ ?line Nine = lists:rumerge3([7,4,1],[8,5,2],[9,6,3]),
+ ?line Nine = lists:rumerge3([7,4,1],[9,6,3],[8,5,2]),
+ ?line Nine = lists:rumerge3([9,6,3],[7,4,1],[8,5,2]),
+ ?line Nine = lists:rumerge3([6,5,4],[3,2,1],[9,8,7]),
+ ?line Nine = lists:rumerge3([3,2,1],[6,5,4],[9,8,7]),
+ ?line Nine = lists:rumerge3([9,8,7],[6,5,4],[3,2,1]),
+ ?line Nine = lists:rumerge3([6,5,4],[9,8,7],[3,2,1]),
+
+ ?line [3,2,1] = lists:rumerge3([3,2,1],[3,2,1],[3,2,1]),
+ ?line [4,3,2,1] = lists:rumerge3([4,3,2],[3,2,1],[3,2,1]),
+ ?line [5,4,3,2,1] = lists:rumerge3([4,3,2],[5,4,3,2],[5,4,3,2,1]),
+ ?line [6,5,4,3,2] = lists:rumerge3([4,3,2],[5,4,3,2],[6,5,4,3]),
+
+ L1 = [c,d,e],
+ L2 = [b,c,d],
+ ?line true =
+ lists:umerge(L1, L2) ==
+ lists:reverse(lists:rumerge(lists:reverse(L1), lists:reverse(L2))),
+ ok.
+
+usort_rand(doc) -> ["usort/1 on big randomized lists"];
+usort_rand(suite) -> [];
+usort_rand(Config) when is_list(Config) ->
+ ?line ok = ucheck(biglist(10)),
+ ?line ok = ucheck(biglist(100)),
+ ?line ok = ucheck(biglist(1000)),
+ ?line ok = ucheck(biglist(10000)),
+
+ ?line ok = ucheck(ubiglist(10)),
+ ?line ok = ucheck(ubiglist(100)),
+ ?line ok = ucheck(ubiglist(1000)),
+ ?line ok = ucheck(ubiglist(10000)),
+ ok.
+
+usort_stable(doc) -> ["usort/1 should keep the first duplicate."];
+usort_stable(suite) -> [];
+usort_stable(Config) when is_list(Config) ->
+ ?line ok = ucheck_stability(bigfunlist(3)),
+ ?line ok = ucheck_stability(bigfunlist(10)),
+ ?line ok = ucheck_stability(bigfunlist(100)),
+ ?line ok = ucheck_stability(bigfunlist(1000)),
+ ?line case erlang:system_info(modified_timing_level) of
+ undefined -> ok = ucheck_stability(bigfunlist(10000));
+ _ -> ok
+ end,
+ ok.
+
+ucheck([]) ->
+ ok;
+ucheck(L) ->
+ S = lists:usort(L),
+ case ucheck(hd(S), tl(S)) of
+ ok ->
+ ok;
+ _ ->
+ io:format("~w~n", [L]),
+ erlang:error(ucheck)
+ end.
+
+ucheck(_A, []) ->
+ ok;
+ucheck(A, [B | L]) when A < B ->
+ ucheck(B, L);
+ucheck(_A, _L) ->
+ no.
+
+%% Check that usort/1 is stable and correct relative ukeysort/2.
+ucheck_stability(L) ->
+ S = no_dups(lsort(L)),
+ U = lists:usort(L),
+ check_stab(L, U, S, "usort/1", "ukeysort/2").
+
+keysort(doc) ->
+ ["Tests lists:keysort/2"];
+keysort(suite) ->
+ [keymerge, rkeymerge,
+ keysort_1, keysort_rand, keysort_i, keysort_stable, keysort_error].
+
+keymerge(doc) -> ["Key merge two lists."];
+keymerge(suite) -> [];
+keymerge(Config) when is_list(Config) ->
+
+ Two = [{1,a},{2,b}],
+ Six = [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f}],
+
+ %% 2-way keymerge
+ ?line [] = lists:keymerge(1, [], []),
+ ?line Two = lists:keymerge(1, Two, []),
+ ?line Two = lists:keymerge(1, [], Two),
+ ?line Six = lists:keymerge(1, [{1,a},{3,c},{5,e}], [{2,b},{4,d},{6,f}]),
+ ?line Six = lists:keymerge(1, [{2,b},{4,d},{6,f}], [{1,a},{3,c},{5,e}]),
+ ?line Six = lists:keymerge(1, [{1,a},{2,b},{3,c}], [{4,d},{5,e},{6,f}]),
+ ?line Six = lists:keymerge(1, [{4,d},{5,e},{6,f}], [{1,a},{2,b},{3,c}]),
+ ?line Six = lists:keymerge(1, [{1,a},{2,b},{5,e}],[{3,c},{4,d},{6,f}]),
+ ?line [{1,a},{2,b},{3,c},{5,e},{7,g}] =
+ lists:keymerge(1, [{1,a},{3,c},{5,e},{7,g}], [{2,b}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}] =
+ lists:keymerge(1, [{1,a},{3,c},{5,e},{7,g}], [{2,b},{4,d}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{7,g}] =
+ lists:keymerge(1, [{1,a},{3,c},{5,e},{7,g}], [{2,b},{4,d},{6,f}]),
+ ?line [{1,a},{2,b},{3,c},{5,e},{7,g}] =
+ lists:keymerge(1, [{2,b}], [{1,a},{3,c},{5,e},{7,g}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}] =
+ lists:keymerge(1, [{2,b},{4,d}], [{1,a},{3,c},{5,e},{7,g}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{7,g}] =
+ lists:keymerge(1, [{2,b},{4,d},{6,f}], [{1,a},{3,c},{5,e},{7,g}]),
+
+ ?line [{b,2},{c,11},{c,12},{c,21},{c,22},{e,5}] =
+ lists:keymerge(1,[{c,11},{c,12},{e,5}], [{b,2},{c,21},{c,22}]),
+
+ ok.
+
+rkeymerge(doc) -> ["Reverse key merge two lists."];
+rkeymerge(suite) -> [];
+rkeymerge(Config) when is_list(Config) ->
+
+ Two = [{2,b},{1,a}],
+ Six = [{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}],
+
+ %% 2-way reversed keymerge
+ ?line [] = lists:rkeymerge(1, [], []),
+ ?line Two = lists:rkeymerge(1, Two, []),
+ ?line Two = lists:rkeymerge(1, [], Two),
+ ?line Six = lists:rkeymerge(1, [{5,e},{3,c},{1,a}], [{6,f},{4,d},{2,b}]),
+ ?line Six = lists:rkeymerge(1, [{6,f},{4,d},{2,b}], [{5,e},{3,c},{1,a}]),
+ ?line Six = lists:rkeymerge(1, [{3,c},{2,b},{1,a}], [{6,f},{5,e},{4,d}]),
+ ?line Six = lists:rkeymerge(1, [{6,f},{5,e},{4,d}], [{3,c},{2,b},{1,a}]),
+ ?line Six = lists:rkeymerge(1, [{4,d},{3,c},{2,b}],[{6,f},{5,e},{1,a}]),
+ ?line [{7,g},{6,f},{5,e},{3,c},{1,a}] =
+ lists:rkeymerge(1, [{7,g},{5,e},{3,c},{1,a}], [{6,f}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{1,a}] =
+ lists:rkeymerge(1, [{7,g},{5,e},{3,c},{1,a}], [{6,f},{4,d}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rkeymerge(1, [{7,g},{5,e},{3,c},{1,a}], [{6,f},{4,d},{2,b}]),
+ ?line [{7,g},{5,e},{3,c},{2,b},{1,a}] =
+ lists:rkeymerge(1, [{2,b}], [{7,g},{5,e},{3,c},{1,a}]),
+ ?line [{7,g},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rkeymerge(1, [{4,d},{2,b}], [{7,g},{5,e},{3,c},{1,a}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rkeymerge(1, [{6,f},{4,d},{2,b}], [{7,g},{5,e},{3,c},{1,a}]),
+
+ L1 = [{c,11},{c,12},{e,5}],
+ L2 = [{b,2},{c,21},{c,22}],
+ ?line true =
+ lists:keymerge(1, L1, L2) ==
+ lists:reverse(lists:rkeymerge(1,lists:reverse(L1),
+ lists:reverse(L2))),
+
+ ok.
+
+keysort_1(doc) -> ["keysort"];
+keysort_1(suite) -> [];
+keysort_1(Config) when is_list(Config) ->
+ ?line ok = keysort_check(1, [], []),
+ ?line ok = keysort_check(1, [{a,b}], [{a,b}]),
+ ?line ok = keysort_check(1, [{a,b},{a,b}], [{a,b},{a,b}]),
+ ?line ok = keysort_check(1, [{a,b},{b,c}], [{a,b},{b,c}]),
+ ?line ok = keysort_check(1, [{b,c},{a,b}], [{a,b},{b,c}]),
+ ?line ok = keysort_check(1,
+ [{1,e},{3,f},{2,y},{0,z},{x,14}],
+ [{0,z},{1,e},{2,y},{3,f},{x,14}]),
+ ?line ok = keysort_check(1,
+ [{1,a},{1,a},{1,a},{1,a}],
+ [{1,a},{1,a},{1,a},{1,a}]),
+
+ ?line [{b,1},{c,1}] = lists:keysort(1, [{c,1},{b,1}]),
+ ?line [{a,0},{b,2},{c,3},{d,4}] =
+ lists:keysort(1, [{d,4},{c,3},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{b,2},{c,1}] =
+ lists:keysort(1, [{c,1},{b,1},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{b,2},{c,1},{d,4}] =
+ lists:keysort(1, [{c,1},{b,1},{b,2},{a,0},{d,4}]),
+
+ SFun = fun(L) -> fun(X) -> keysort_check(1, X, L) end end,
+ L1 = [{1,a},{2,b},{3,c}],
+ ?line lists:foreach(SFun(L1), perms(L1)),
+ L2 = [{1,a},{1,a},{2,b}],
+ ?line lists:foreach(SFun(L2), perms(L2)),
+ L3 = [{1,a},{1,a},{1,a},{2,b}],
+ ?line lists:foreach(SFun(L3), perms(L3)),
+ L4 = [{a,1},{a,1},{b,2},{b,2},{c,3},{d,4},{e,5},{f,6}],
+ ?line lists:foreach(SFun(L4), perms(L4)),
+
+ ok.
+
+keysort_stable(doc) -> ["keysort should be stable"];
+keysort_stable(suite) -> [];
+keysort_stable(Config) when is_list(Config) ->
+ ?line ok = keysort_check(1, [{1,b},{1,c}], [{1,b},{1,c}]),
+ ?line ok = keysort_check(1, [{1,c},{1,b}], [{1,c},{1,b}]),
+ ?line ok = keysort_check(1,
+ [{1,c},{1,b},{2,x},{3,p},{2,a}],
+ [{1,c},{1,b},{2,x},{2,a},{3,p}]),
+ ?line ok = keysort_check(1,
+ [{1,a},{1,b},{1,a},{1,a}],
+ [{1,a},{1,b},{1,a},{1,a}]),
+ ok.
+
+keysort_error(doc) -> ["keysort should exit when given bad arguments"];
+keysort_error(suite) -> [];
+keysort_error(Config) when is_list(Config) ->
+ ?line {'EXIT', _} = (catch lists:keysort(0, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:keysort(3, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:keysort(1.5, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:keysort(x, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:keysort(x, [])),
+ ?line {'EXIT', _} = (catch lists:keysort(x, [{1,b}])),
+ ?line {'EXIT', _} = (catch lists:keysort(1, [a,b])),
+ ?line {'EXIT', _} = (catch lists:keysort(1, [{1,b} | {1,c}])),
+ ok.
+
+keysort_i(doc) -> ["keysort with other key than first element"];
+keysort_i(suite) -> [];
+keysort_i(Config) when is_list(Config) ->
+ ?line ok = keysort_check(2, [{a,2},{b,1},{c,3}], [{b,1},{a,2},{c,3}]),
+ ok.
+
+keysort_rand(doc) -> ["keysort on big randomized lists"];
+keysort_rand(suite) -> [];
+keysort_rand(Config) when is_list(Config) ->
+ ?line ok = keysort_check3(1, biglist(10)),
+ ?line ok = keysort_check3(1, biglist(100)),
+ ?line ok = keysort_check3(1, biglist(1000)),
+ ?line ok = keysort_check3(1, biglist(10000)),
+
+ ?line ok = keysort_check3(2, biglist(10)),
+ ?line ok = keysort_check3(2, biglist(100)),
+ ?line ok = keysort_check3(2, biglist(1000)),
+ ?line ok = keysort_check3(2, biglist(10000)),
+ ok.
+
+%%% Keysort a list, check that the returned list is what we expected,
+%%% and that it is actually sorted.
+keysort_check(I, Input, Expected) ->
+ ?line Expected = lists:keysort(I, Input),
+ check_sorted(I, Input, Expected).
+
+keysort_check3(I, Input) ->
+ check_sorted(I, 3, Input, lists:keysort(I, Input)).
+
+check_sorted(I, Input, L) ->
+ check_sorted(I, I, Input, L).
+
+%%% Check that a list is keysorted by element I. Elements comparing equal
+%%% should be sorted according to element J.
+check_sorted(_I, _J, _Input, []) ->
+ ok;
+check_sorted(I, J, Input, [A | Rest]) ->
+ case catch check_sorted1(I, J, A, Rest) of
+ {'EXIT', _} ->
+ io:format("~w~n", [Input]),
+ erlang:error(check_sorted);
+ Reply ->
+ Reply
+ end.
+
+check_sorted1(_I, _J, _A, []) ->
+ ok;
+check_sorted1(I, J, A, [B | Rest]) ->
+ ok = keycompare(I, J, A, B),
+ check_sorted1(I, J, B, Rest).
+
+keycompare(I, _J, A, B) when element(I, A) < element(I, B) ->
+ ok;
+keycompare(I, J, A, B) when element(I, A) == element(I, B),
+ element(J, A) =< element(J, B) ->
+ ok.
+
+ukeysort(doc) ->
+ ["Tests lists:ukeysort/2"];
+ukeysort(suite) ->
+ [ukeymerge, rukeymerge,
+ ukeysort_1, ukeysort_rand, ukeysort_i, ukeysort_stable, ukeysort_error].
+
+ukeymerge(suite) -> [];
+ukeymerge(doc) -> ["Merge two lists while removing duplicates."];
+ukeymerge(Conf) when is_list(Conf) ->
+
+ Two = [{1,a},{2,b}],
+ Six = [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f}],
+
+ %% 2-way unique keymerge
+ ?line [] = lists:ukeymerge(1, [], []),
+ ?line Two = lists:ukeymerge(1, Two, []),
+ ?line Two = lists:ukeymerge(1, [], Two),
+ ?line [] = lists:ukeymerge(1, [], []),
+ ?line Two = lists:ukeymerge(1, Two, []),
+ ?line Two = lists:ukeymerge(1, [], Two),
+ ?line Six = lists:ukeymerge(1, [{1,a},{3,c},{5,e}], [{2,b},{4,d},{6,f}]),
+ ?line Six = lists:ukeymerge(1, [{2,b},{4,d},{6,f}], [{1,a},{3,c},{5,e}]),
+ ?line Six = lists:ukeymerge(1, [{1,a},{2,b},{3,c}], [{4,d},{5,e},{6,f}]),
+ ?line Six = lists:ukeymerge(1, [{4,d},{5,e},{6,f}], [{1,a},{2,b},{3,c}]),
+ ?line Six = lists:ukeymerge(1, [{1,a},{2,b},{5,e}],[{3,c},{4,d},{6,f}]),
+ ?line [{1,a},{2,b},{3,c},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{1,a},{3,c},{5,e},{7,g}], [{2,b}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{1,a},{3,c},{5,e},{7,g}], [{2,b},{4,d}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{7,g}] =
+ lists:ukeymerge(1, [{1,a},{3,c},{5,e},{7,g}], [{2,b},{4,d},{6,f}]),
+ ?line [{1,a},{2,b},{3,c},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{2,b}], [{1,a},{3,c},{5,e},{7,g}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{2,b},{4,d}], [{1,a},{3,c},{5,e},{7,g}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{7,g}] =
+ lists:ukeymerge(1, [{2,b},{4,d},{6,f}], [{1,a},{3,c},{5,e},{7,g}]),
+
+ ?line [{1,a},{2,b},{3,c},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{1,a},{2,b},{3,c},{5,e},{7,g}], [{2,b}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}],
+ [{2,b},{4,d}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{7,g}] =
+ lists:ukeymerge(1, [{1,a},{3,c},{5,e},{6,f},{7,g}],
+ [{2,b},{4,d},{6,f}]),
+ ?line [{1,a},{2,b},{3,c},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{2,b}], [{1,a},{2,b},{3,c},{5,e},{7,g}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}] =
+ lists:ukeymerge(1, [{2,b},{4,d}],
+ [{1,a},{2,b},{3,c},{4,d},{5,e},{7,g}]),
+ ?line [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{7,g}] =
+ lists:ukeymerge(1, [{2,b},{4,d},{6,f}],
+ [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{7,g}]),
+
+ L1 = [{a,1},{a,3},{a,5},{a,7}],
+ L2 = [{b,1},{b,3},{b,5},{b,7}],
+ ?line L1 = lists:ukeymerge(2, L1, L2),
+
+ ok.
+
+rukeymerge(suite) -> [];
+rukeymerge(doc) ->
+ ["Reverse merge two lists while removing duplicates."];
+rukeymerge(Conf) when is_list(Conf) ->
+
+ Two = [{2,b},{1,a}],
+ Six = [{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}],
+
+ %% 2-way reversed unique keymerge
+ ?line [] = lists:rukeymerge(1, [], []),
+ ?line Two = lists:rukeymerge(1, Two, []),
+ ?line Two = lists:rukeymerge(1, [], Two),
+ ?line Six = lists:rukeymerge(1, [{5,e},{3,c},{1,a}], [{6,f},{4,d},{2,b}]),
+ ?line Six = lists:rukeymerge(1, [{6,f},{4,d},{2,b}], [{5,e},{3,c},{1,a}]),
+ ?line Six = lists:rukeymerge(1, [{3,c},{2,b},{1,a}], [{6,f},{5,e},{4,d}]),
+ ?line Six = lists:rukeymerge(1, [{6,f},{5,e},{4,d}], [{3,c},{2,b},{1,a}]),
+ ?line Six = lists:rukeymerge(1, [{4,d},{3,c},{2,b}],[{6,f},{5,e},{1,a}]),
+ ?line [{7,g},{6,f},{5,e},{3,c},{1,a}] =
+ lists:rukeymerge(1, [{7,g},{5,e},{3,c},{1,a}], [{6,f}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{1,a}] =
+ lists:rukeymerge(1, [{7,g},{5,e},{3,c},{1,a}], [{6,f},{4,d}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{7,g},{5,e},{3,c},{1,a}], [{6,f},{4,d},{2,b}]),
+ ?line [{7,g},{5,e},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{2,b}], [{7,g},{5,e},{3,c},{1,a}]),
+ ?line [{7,g},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{4,d},{2,b}], [{7,g},{5,e},{3,c},{1,a}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{6,f},{4,d},{2,b}], [{7,g},{5,e},{3,c},{1,a}]),
+
+ ?line [{7,g},{6,f},{5,e},{3,c},{1,a}] =
+ lists:rukeymerge(1, [{7,g},{6,f},{5,e},{3,c},{1,a}], [{6,f}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{1,a}] =
+ lists:rukeymerge(1, [{7,g},{6,f},{5,e},{4,d},{3,c},{1,a}],
+ [{6,f},{4,d}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}],
+ [{6,f},{4,d},{2,b}]),
+ ?line [{7,g},{5,e},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{2,b}], [{7,g},{5,e},{3,c},{2,b},{1,a}]),
+ ?line [{7,g},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{4,d},{2,b}],
+ [{7,g},{5,e},{4,d},{3,c},{2,b},{1,a}]),
+ ?line [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}] =
+ lists:rukeymerge(1, [{6,f},{4,d},{2,b}],
+ [{7,g},{6,f},{5,e},{4,d},{3,c},{2,b},{1,a}]),
+
+ L1 = [{a,1},{a,3},{a,5},{a,7}],
+ L2 = [{b,1},{b,3},{b,5},{b,7}],
+ ?line true =
+ lists:ukeymerge(2, L1, L2) ==
+ lists:reverse(lists:rukeymerge(2, lists:reverse(L1),
+ lists:reverse(L2))),
+
+ ok.
+
+ukeysort_1(doc) -> ["ukeysort"];
+ukeysort_1(suite) -> [];
+ukeysort_1(Config) when is_list(Config) ->
+ ?line ok = ukeysort_check(1, [], []),
+ ?line ok = ukeysort_check(1, [{a,b}], [{a,b}]),
+ ?line ok = ukeysort_check(1, [{a,b},{a,b}], [{a,b}]),
+ ?line ok = ukeysort_check(1, [{a,b},{b,c}], [{a,b},{b,c}]),
+ ?line ok = ukeysort_check(1, [{b,c},{a,b}], [{a,b},{b,c}]),
+ ?line ok = ukeysort_check(1,
+ [{1,e},{3,f},{2,y},{0,z},{x,14}],
+ [{0,z},{1,e},{2,y},{3,f},{x,14}]),
+ ?line ok = ukeysort_check(1, [{1,a},{1,a},{1,a},{1,a}], [{1,a}]),
+
+ L1 = [{1,a},{1,b},{1,a}],
+ L1u = lists:ukeysort(1, L1),
+ L2 = [{1,a},{1,b},{1,a}],
+ L2u = lists:ukeysort(1, L2),
+ ?line ok = ukeysort_check(1, lists:keymerge(1, L1, L2),
+ lists:ukeymerge(1, L1u, L2u)),
+ L3 = [{1,a},{1,b},{1,a},{2,a}],
+ L3u = lists:ukeysort(1, L3),
+ ?line ok = ukeysort_check(1, lists:keymerge(1, L3, L2),
+ lists:ukeymerge(1, L3u, L2u)),
+ L4 = [{1,b},{1,a}],
+ L4u = lists:ukeysort(1, L4),
+ ?line ok = ukeysort_check(1, lists:keymerge(1, L1, L4),
+ lists:ukeymerge(1, L1u, L4u)),
+ L5 = [{1,a},{1,b},{1,a},{2,a}],
+ L5u = lists:ukeysort(1, L5),
+ ?line ok = ukeysort_check(1, lists:keymerge(1, [], L5),
+ lists:ukeymerge(1, [], L5u)),
+ ?line ok = ukeysort_check(1, lists:keymerge(1, L5, []),
+ lists:ukeymerge(1, L5u, [])),
+ L6 = [{3,a}],
+ L6u = lists:ukeysort(1, L6),
+ ?line ok = ukeysort_check(1, lists:keymerge(1, L5, L6),
+ lists:ukeymerge(1, L5u, L6u)),
+
+ ?line [{b,1},{c,1}] = lists:ukeysort(1, [{c,1},{c,1},{c,1},{c,1},{b,1}]),
+ ?line [{a,0},{b,2},{c,3},{d,4}] =
+ lists:ukeysort(1, [{d,4},{c,3},{b,2},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{c,1}] =
+ lists:ukeysort(1, [{c,1},{b,1},{b,1},{b,2},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{c,1},{d,4}] =
+ lists:ukeysort(1, [{c,1},{b,1},{b,2},{a,0},{a,0},{d,4},{d,4}]),
+
+ SFun = fun(L) -> fun(X) -> ukeysort_check(2, X, L) end end,
+ PL = [{a,1},{b,2},{c,3},{d,4},{e,5},{f,6}],
+ Ps = perms([{a,1},{b,2},{c,3},{d,4},{e,5},{f,6},{b,2},{a,1}]),
+ ?line lists:foreach(SFun(PL), Ps),
+
+ M1L = [{1,a},{1,a},{2,b}],
+ M1s = [{1,a},{2,b}],
+ ?line lists:foreach(SFun(M1s), perms(M1L)),
+ M2L = [{1,a},{2,b},{2,b}],
+ M2s = [{1,a},{2,b}],
+ ?line lists:foreach(SFun(M2s), perms(M2L)),
+ M3 = [{1,a},{2,b},{3,c}],
+ ?line lists:foreach(SFun(M3), perms(M3)),
+
+ ok.
+
+ukeysort_stable(doc) -> ["ukeysort should keep the first duplicate"];
+ukeysort_stable(suite) -> [];
+ukeysort_stable(Config) when is_list(Config) ->
+ ?line ok = ukeysort_check(1, [{1,b},{1,c}], [{1,b}]),
+ ?line ok = ukeysort_check(1, [{1,c},{1,b}], [{1,c}]),
+ ?line ok = ukeysort_check(1,
+ [{1,c},{1,b},{2,x},{3,p},{2,a}],
+ [{1,c},{2,x},{3,p}]),
+
+ ?line ok = ukeysort_check(1, [{1,a},{1,b},{1,b}], [{1,a}]),
+ ?line ok = ukeysort_check(1, [{2,a},{1,b},{2,a}], [{1,b},{2,a}]),
+
+ ?line ok = ukeysort_check_stability(bigfunlist(3)),
+ ?line ok = ukeysort_check_stability(bigfunlist(10)),
+ ?line ok = ukeysort_check_stability(bigfunlist(100)),
+ ?line ok = ukeysort_check_stability(bigfunlist(1000)),
+ ?line case erlang:system_info(modified_timing_level) of
+ undefined -> ok = ukeysort_check_stability(bigfunlist(10000));
+ _ -> ok
+ end,
+ ok.
+
+ukeysort_error(doc) -> ["ukeysort should exit when given bad arguments"];
+ukeysort_error(suite) -> [];
+ukeysort_error(Config) when is_list(Config) ->
+ ?line {'EXIT', _} = (catch lists:ukeysort(0, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:ukeysort(3, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:ukeysort(1.5, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:ukeysort(x, [{1,b},{1,c}])),
+ ?line {'EXIT', _} = (catch lists:ukeysort(x, [])),
+ ?line {'EXIT', _} = (catch lists:ukeysort(x, [{1,b}])),
+ ?line {'EXIT', _} = (catch lists:ukeysort(1, [a,b])),
+ ?line {'EXIT', _} = (catch lists:ukeysort(1, [{1,b} | {1,c}])),
+ ok.
+
+ukeysort_i(doc) -> ["ukeysort with other key than first element"];
+ukeysort_i(suite) -> [];
+ukeysort_i(Config) when is_list(Config) ->
+ ?line ok = ukeysort_check(2, [{a,2},{b,1},{c,3}], [{b,1},{a,2},{c,3}]),
+ ok.
+
+ukeysort_rand(doc) -> ["ukeysort on big randomized lists"];
+ukeysort_rand(suite) -> [];
+ukeysort_rand(Config) when is_list(Config) ->
+ ?line ok = ukeysort_check3(2, biglist(10)),
+ ?line ok = ukeysort_check3(2, biglist(100)),
+ ?line ok = ukeysort_check3(2, biglist(1000)),
+ ?line ok = ukeysort_check3(2, biglist(10000)),
+
+ ?line ok = gen_ukeysort_check(1, ubiglist(10)),
+ ?line ok = gen_ukeysort_check(1, ubiglist(100)),
+ ?line ok = gen_ukeysort_check(1, ubiglist(1000)),
+ ?line ok = gen_ukeysort_check(1, ubiglist(10000)),
+ ok.
+
+%% Check that ukeysort/2 is stable and correct relative keysort/2.
+%% (this is not affected by the fact that keysort/2 is no longer really
+%% stable; ucheck_stability/1 checks ukeysort/2 (and usort/1, of course))
+gen_ukeysort_check(I, Input) ->
+ U = lists:ukeysort(I, Input),
+ S = lists:keysort(I, Input),
+ case U == no_dups_keys(S, I) of
+ true ->
+ ok;
+ false ->
+ io:format("~w~n", [Input]),
+ erlang:error(gen_ukeysort_check)
+ end.
+
+%% Used for checking that the first copy is kept.
+ukeysort_check_stability(L) ->
+ I = 1,
+ U = lists:ukeysort(I, L),
+ S = no_dups_keys(lkeysort(I, L), I),
+ check_stab(L, U, S, "ukeysort/2", "usort/2").
+
+%%% Uniquely keysort a list, check that the returned list is what we
+%%% expected, and that it is actually sorted.
+ukeysort_check(I, Input, Expected) ->
+ ?line Expected = lists:ukeysort(I, Input),
+ ucheck_sorted(I, Input, Expected).
+
+ukeysort_check3(I, Input) ->
+ ucheck_sorted(I, 3, Input, lists:ukeysort(I, Input)).
+
+ucheck_sorted(I, Input, L) ->
+ ucheck_sorted(I, I, Input, L).
+
+%%% Check that a list is ukeysorted by element I. Elements comparing
+%%% equal should be sorted according to element J.
+ucheck_sorted(_I, _J, _Input, []) ->
+ ok;
+ucheck_sorted(I, J, Input, [A | Rest]) ->
+ case catch ucheck_sorted1(I, J, A, Rest) of
+ {'EXIT', _} ->
+ io:format("~w~n", [Input]),
+ erlang:error(ucheck_sorted);
+ Reply ->
+ Reply
+ end.
+
+ucheck_sorted1(_I, _J, _A, []) ->
+ ok;
+ucheck_sorted1(I, J, A, [B | Rest]) ->
+ ok = ukeycompare(I, J, A, B),
+ ucheck_sorted1(I, J, B, Rest).
+
+ukeycompare(I, _J, A, B) when element(I, A) < element(I, B) ->
+ ok;
+ukeycompare(I, J, A, B) when A =/= B,
+ element(I, A) == element(I, B),
+ element(J, A) =< element(J, B) ->
+ ok.
+
+
+funsort(doc) ->
+ ["Tests lists:sort/2"];
+funsort(suite) ->
+ [funmerge, rfunmerge,
+ funsort_1, funsort_stable, funsort_error, funsort_rand].
+
+funmerge(doc) -> ["Merge two lists using a fun."];
+funmerge(suite) -> [];
+funmerge(Config) when is_list(Config) ->
+
+ Two = [1,2],
+ Six = [1,2,3,4,5,6],
+ F = fun(X, Y) -> X =< Y end,
+
+ %% 2-way merge
+ ?line [] = lists:merge(F, [], []),
+ ?line Two = lists:merge(F, Two, []),
+ ?line Two = lists:merge(F, [], Two),
+ ?line Six = lists:merge(F, [1,3,5], [2,4,6]),
+ ?line Six = lists:merge(F, [2,4,6], [1,3,5]),
+ ?line Six = lists:merge(F, [1,2,3], [4,5,6]),
+ ?line Six = lists:merge(F, [4,5,6], [1,2,3]),
+ ?line Six = lists:merge(F, [1,2,5],[3,4,6]),
+ ?line [1,2,3,5,7] = lists:merge(F, [1,3,5,7], [2]),
+ ?line [1,2,3,4,5,7] = lists:merge(F, [1,3,5,7], [2,4]),
+ ?line [1,2,3,4,5,6,7] = lists:merge(F, [1,3,5,7], [2,4,6]),
+ ?line [1,2,3,5,7] = lists:merge(F, [2], [1,3,5,7]),
+ ?line [1,2,3,4,5,7] = lists:merge(F, [2,4], [1,3,5,7]),
+ ?line [1,2,3,4,5,6,7] = lists:merge(F, [2,4,6], [1,3,5,7]),
+
+ F2 = fun(X,Y) -> element(1,X) =< element(1,Y) end,
+ ?line [{b,2},{c,11},{c,12},{c,21},{c,22},{e,5}] =
+ lists:merge(F2,[{c,11},{c,12},{e,5}], [{b,2},{c,21},{c,22}]),
+
+ ok.
+
+rfunmerge(doc) -> ["Reverse merge two lists using a fun."];
+rfunmerge(suite) -> [];
+rfunmerge(Config) when is_list(Config) ->
+
+ Two = [2,1],
+ Six = [6,5,4,3,2,1],
+ F = fun(X, Y) -> X =< Y end,
+
+ %% 2-way reversed merge
+ ?line [] = lists:rmerge(F, [], []),
+ ?line Two = lists:rmerge(F, Two, []),
+ ?line Two = lists:rmerge(F, [], Two),
+ ?line Six = lists:rmerge(F, [5,3,1], [6,4,2]),
+ ?line Six = lists:rmerge(F, [6,4,2], [5,3,1]),
+ ?line Six = lists:rmerge(F, [3,2,1], [6,5,4]),
+ ?line Six = lists:rmerge(F, [6,5,4], [3,2,1]),
+ ?line Six = lists:rmerge(F, [4,3,2],[6,5,1]),
+ ?line [7,6,5,3,1] = lists:rmerge(F, [7,5,3,1], [6]),
+ ?line [7,6,5,4,3,1] = lists:rmerge(F, [7,5,3,1], [6,4]),
+ ?line [7,6,5,4,3,2,1] = lists:rmerge(F, [7,5,3,1], [6,4,2]),
+ ?line [7,5,3,2,1] = lists:rmerge(F, [2], [7,5,3,1]),
+ ?line [7,5,4,3,2,1] = lists:rmerge(F, [4,2], [7,5,3,1]),
+ ?line [7,6,5,4,3,2,1] = lists:rmerge(F, [6,4,2], [7,5,3,1]),
+
+ F2 = fun(X,Y) -> element(1,X) =< element(1,Y) end,
+ L1 = [{c,11},{c,12},{e,5}],
+ L2 = [{b,2},{c,21},{c,22}],
+ ?line true =
+ lists:merge(F2, L1, L2) ==
+ lists:reverse(lists:rmerge(F2,lists:reverse(L1), lists:reverse(L2))),
+
+ ok.
+
+
+funsort_1(doc) -> ["sort/2"];
+funsort_1(suite) -> [];
+funsort_1(Config) when is_list(Config) ->
+ ?line ok = funsort_check(1, [], []),
+ ?line ok = funsort_check(1, [{a,b}], [{a,b}]),
+ ?line ok = funsort_check(1, [{a,b},{a,b}], [{a,b},{a,b}]),
+ ?line ok = funsort_check(1, [{a,b},{b,c}], [{a,b},{b,c}]),
+ ?line ok = funsort_check(1, [{b,c},{a,b}], [{a,b},{b,c}]),
+ ?line ok = funsort_check(1,
+ [{1,e},{3,f},{2,y},{0,z},{x,14}],
+ [{0,z},{1,e},{2,y},{3,f},{x,14}]),
+ F = funsort_fun(1),
+
+ ?line [{b,1},{c,1}] = lists:sort(F, [{c,1},{b,1}]),
+ ?line [{a,0},{b,2},{c,3},{d,4}] =
+ lists:sort(F, [{d,4},{c,3},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{b,2},{c,1}] =
+ lists:sort(F, [{c,1},{b,1},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{b,2},{c,1},{d,4}] =
+ lists:sort(F, [{c,1},{b,1},{b,2},{a,0},{d,4}]),
+
+ SFun = fun(L) -> fun(X) -> funsort_check(1, X, L) end end,
+ L1 = [{1,a},{1,a},{2,b},{2,b},{3,c},{4,d},{5,e},{6,f}],
+ ?line lists:foreach(SFun(L1), perms(L1)),
+
+ ok.
+
+funsort_stable(doc) -> ["sort/2 should be stable"];
+funsort_stable(suite) -> [];
+funsort_stable(Config) when is_list(Config) ->
+ ?line ok = funsort_check(1, [{1,b},{1,c}], [{1,b},{1,c}]),
+ ?line ok = funsort_check(1, [{1,c},{1,b}], [{1,c},{1,b}]),
+ ?line ok = funsort_check(1,
+ [{1,c},{1,b},{2,x},{3,p},{2,a}],
+ [{1,c},{1,b},{2,x},{2,a},{3,p}]),
+ ok.
+
+funsort_error(doc) -> ["sort/2 should exit when given bad arguments"];
+funsort_error(suite) -> [];
+funsort_error(Config) when is_list(Config) ->
+ ?line {'EXIT', _} = (catch lists:sort(1, [{1,b} , {1,c}])),
+ ?line {'EXIT', _} = (catch lists:sort(fun(X,Y) -> X =< Y end,
+ [{1,b} | {1,c}])),
+ ok.
+
+funsort_rand(doc) -> ["sort/2 on big randomized lists"];
+funsort_rand(suite) -> [];
+funsort_rand(Config) when is_list(Config) ->
+ ?line ok = funsort_check3(1, biglist(10)),
+ ?line ok = funsort_check3(1, biglist(100)),
+ ?line ok = funsort_check3(1, biglist(1000)),
+ ?line ok = funsort_check3(1, biglist(10000)),
+ ok.
+
+% Do a keysort
+funsort(I, L) ->
+ lists:sort(funsort_fun(I), L).
+
+funsort_check3(I, Input) ->
+ check_sorted(I, 3, Input, funsort(I, Input)).
+
+%%% Keysort a list, check that the returned list is what we expected,
+%%% and that it is actually sorted.
+funsort_check(I, Input, Expected) ->
+ ?line Expected = funsort(I, Input),
+ check_sorted(I, Input, Expected).
+
+ufunsort(doc) ->
+ ["Tests lists:usort/2"];
+ufunsort(suite) ->
+ [ufunmerge, rufunmerge,
+ ufunsort_1, ufunsort_stable, ufunsort_error, ufunsort_rand].
+
+ufunmerge(suite) -> [];
+ufunmerge(doc) -> ["Merge two lists while removing duplicates using a fun."];
+ufunmerge(Conf) when is_list(Conf) ->
+
+ Two = [1,2],
+ Six = [1,2,3,4,5,6],
+ F = fun(X, Y) -> X =< Y end,
+
+ %% 2-way unique merge
+ ?line [] = lists:umerge(F, [], []),
+ ?line Two = lists:umerge(F, Two, []),
+ ?line Two = lists:umerge(F, [], Two),
+ ?line Six = lists:umerge(F, [1,3,5], [2,4,6]),
+ ?line Six = lists:umerge(F, [2,4,6], [1,3,5]),
+ ?line Six = lists:umerge(F, [1,2,3], [4,5,6]),
+ ?line Six = lists:umerge(F, [4,5,6], [1,2,3]),
+ ?line Six = lists:umerge(F, [1,2,5],[3,4,6]),
+ ?line [1,2,3,5,7] = lists:umerge(F, [1,3,5,7], [2]),
+ ?line [1,2,3,4,5,7] = lists:umerge(F, [1,3,5,7], [2,4]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge(F, [1,3,5,7], [2,4,6]),
+ ?line [1,2,3,5,7] = lists:umerge(F, [2], [1,3,5,7]),
+ ?line [1,2,3,4,5,7] = lists:umerge(F, [2,4], [1,3,5,7]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge(F, [2,4,6], [1,3,5,7]),
+
+ ?line [1,2,3,5,7] = lists:umerge(F, [1,2,3,5,7], [2]),
+ ?line [1,2,3,4,5,7] = lists:umerge(F, [1,2,3,4,5,7], [2,4]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge(F, [1,3,5,6,7], [2,4,6]),
+ ?line [1,2,3,5,7] = lists:umerge(F, [2], [1,2,3,5,7]),
+ ?line [1,2,3,4,5,7] = lists:umerge(F, [2,4], [1,2,3,4,5,7]),
+ ?line [1,2,3,4,5,6,7] = lists:umerge(F, [2,4,6], [1,2,3,4,5,6,7]),
+
+ L1 = [{a,1},{a,3},{a,5},{a,7}],
+ L2 = [{b,1},{b,3},{b,5},{b,7}],
+ F2 = fun(X,Y) -> element(2,X) =< element(2,Y) end,
+ ?line L1 = lists:umerge(F2, L1, L2),
+ ?line [{b,2},{e,5},{c,11},{c,12},{c,21},{c,22}] =
+ lists:umerge(F2, [{e,5},{c,11},{c,12}], [{b,2},{c,21},{c,22}]),
+
+ ok.
+
+rufunmerge(suite) -> [];
+rufunmerge(doc) ->
+ ["Reverse merge two lists while removing duplicates using a fun."];
+rufunmerge(Conf) when is_list(Conf) ->
+ Two = [2,1],
+ Six = [6,5,4,3,2,1],
+ F = fun(X, Y) -> X =< Y end,
+
+ %% 2-way reversed unique merge
+ ?line [] = lists:rumerge(F, [], []),
+ ?line Two = lists:rumerge(F, Two, []),
+ ?line Two = lists:rumerge(F, [], Two),
+ ?line Six = lists:rumerge(F, [5,3,1], [6,4,2]),
+ ?line Six = lists:rumerge(F, [6,4,2], [5,3,1]),
+ ?line Six = lists:rumerge(F, [3,2,1], [6,5,4]),
+ ?line Six = lists:rumerge(F, [6,5,4], [3,2,1]),
+ ?line Six = lists:rumerge(F, [4,3,2],[6,5,1]),
+ ?line [7,6,5,3,1] = lists:rumerge(F, [7,5,3,1], [6]),
+ ?line [7,6,5,4,3,1] = lists:rumerge(F, [7,5,3,1], [6,4]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge(F, [7,5,3,1], [6,4,2]),
+ ?line [7,5,3,2,1] = lists:rumerge(F, [2], [7,5,3,1]),
+ ?line [7,5,4,3,2,1] = lists:rumerge(F, [4,2], [7,5,3,1]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge(F, [6,4,2], [7,5,3,1]),
+
+ ?line [7,6,5,3,1] = lists:rumerge(F, [7,6,5,3,1], [6]),
+ ?line [7,6,5,4,3,1] = lists:rumerge(F, [7,6,5,4,3,1], [6,4]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge(F, [7,6,5,4,3,2,1], [6,4,2]),
+ ?line [7,5,3,2,1] = lists:rumerge(F, [2], [7,5,3,2,1]),
+ ?line [7,5,4,3,2,1] = lists:rumerge(F, [4,2], [7,5,4,3,2,1]),
+ ?line [7,6,5,4,3,2,1] = lists:rumerge(F, [6,4,2], [7,6,5,4,3,2,1]),
+
+ F2 = fun(X,Y) -> element(1,X) =< element(1,Y) end,
+ L1 = [{1,a},{1,b},{1,a}],
+ L2 = [{1,a},{1,b},{1,a}],
+ ?line true = lists:umerge(F2, L1, L2) ==
+ lists:reverse(lists:rumerge(F, lists:reverse(L2), lists:reverse(L1))),
+
+ L3 = [{c,11},{c,12},{e,5}],
+ L4 = [{b,2},{c,21},{c,22}],
+ ?line true =
+ lists:umerge(F2, L3, L4) ==
+ lists:reverse(lists:rumerge(F2,lists:reverse(L3), lists:reverse(L4))),
+
+ ok.
+
+ufunsort_1(doc) -> ["usort/2"];
+ufunsort_1(suite) -> [];
+ufunsort_1(Config) when is_list(Config) ->
+ ?line ok = ufunsort_check(1, [], []),
+ ?line ok = ufunsort_check(1, [{a,b}], [{a,b}]),
+ ?line ok = ufunsort_check(1, [{a,b},{a,b}], [{a,b}]),
+ ?line ok = ufunsort_check(1, [{a,b},{b,c}], [{a,b},{b,c}]),
+ ?line ok = ufunsort_check(1, [{b,c},{a,b}], [{a,b},{b,c}]),
+ ?line ok = ufunsort_check(1,
+ [{1,e},{3,f},{2,y},{0,z},{x,14}],
+ [{0,z},{1,e},{2,y},{3,f},{x,14}]),
+ ?line ok = ufunsort_check(1,
+ [{1,a},{2,b},{3,c},{2,b},{1,a},{2,b},{3,c},
+ {2,b},{1,a}],
+ [{1,a},{2,b},{3,c}]),
+ ?line ok = ufunsort_check(1,
+ [{1,a},{1,a},{1,b},{1,b},{1,a},{2,a}],
+ [{1,a},{2,a}]),
+
+ F = funsort_fun(1),
+ L1 = [{1,a},{1,b},{1,a}],
+ L2 = [{1,a},{1,b},{1,a}],
+ ?line ok = ufunsort_check(1, lists:keymerge(1, L1, L2),
+ lists:umerge(F, lists:usort(F, L1),
+ lists:usort(F, L2))),
+ L3 = [{1,a},{1,b},{1,a},{2,a}],
+ ?line ok = ufunsort_check(1, lists:keymerge(1, L3, L2),
+ lists:umerge(F, lists:usort(F, L3),
+ lists:usort(F, L2))),
+ L4 = [{1,b},{1,a}],
+ ?line ok = ufunsort_check(1, lists:keymerge(1, L1, L4),
+ lists:umerge(F, lists:usort(F, L1),
+ lists:usort(F, L4))),
+ L5 = [{1,a},{1,b},{1,a},{2,a}],
+ ?line ok = ufunsort_check(1, lists:keymerge(1, L5, []),
+ lists:umerge(F, lists:usort(F, L5), [])),
+ L6 = [{3,a}],
+ ?line ok = ufunsort_check(1, lists:keymerge(1, L5, L6),
+ lists:umerge(F, lists:usort(F, L5),
+ lists:usort(F, L6))),
+
+ ?line [{b,1},{c,1}] = lists:usort(F, [{c,1},{c,1},{b,1}]),
+ ?line [{a,0},{b,2},{c,3},{d,4}] =
+ lists:usort(F, [{d,4},{c,3},{b,2},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{c,1}] =
+ lists:usort(F, [{c,1},{b,1},{b,1},{b,2},{b,2},{a,0}]),
+ ?line [{a,0},{b,1},{c,1},{d,4}] =
+ lists:usort(F, [{c,1},{b,1},{b,2},{a,0},{a,0},{d,4},{d,4}]),
+
+ SFun = fun(L) -> fun(X) -> ufunsort_check(1, X, L) end end,
+ PL = [{1,a},{2,b},{3,c},{4,d},{5,e},{6,f}],
+ Ps = perms([{1,a},{2,b},{3,c},{4,d},{5,e},{6,f},{2,b},{1,a}]),
+ ?line lists:foreach(SFun(PL), Ps),
+
+ ok.
+
+ufunsort_stable(doc) -> ["usort/2 should be stable"];
+ufunsort_stable(suite) -> [];
+ufunsort_stable(Config) when is_list(Config) ->
+ ?line ok = ufunsort_check(1, [{1,b},{1,c}], [{1,b}]),
+ ?line ok = ufunsort_check(1, [{1,c},{1,b}], [{1,c}]),
+ ?line ok = ufunsort_check(1,
+ [{1,c},{1,b},{2,x},{3,p},{2,a}],
+ [{1,c},{2,x},{3,p}]),
+
+ ?line ok = ufunsort_check_stability(bigfunlist(10)),
+ ?line ok = ufunsort_check_stability(bigfunlist(100)),
+ ?line ok = ufunsort_check_stability(bigfunlist(1000)),
+ ?line case erlang:system_info(modified_timing_level) of
+ undefined -> ok = ufunsort_check_stability(bigfunlist(10000));
+ _ -> ok
+ end,
+ ok.
+
+ufunsort_error(doc) -> ["usort/2 should exit when given bad arguments"];
+ufunsort_error(suite) -> [];
+ufunsort_error(Config) when is_list(Config) ->
+ ?line {'EXIT', _} = (catch lists:usort(1, [{1,b} , {1,c}])),
+ ?line {'EXIT', _} = (catch lists:usort(fun(X,Y) -> X =< Y end,
+ [{1,b} | {1,c}])),
+ ok.
+
+ufunsort_rand(doc) -> ["usort/2 on big randomized lists"];
+ufunsort_rand(suite) -> [];
+ufunsort_rand(Config) when is_list(Config) ->
+ ?line ok = ufunsort_check3(1, biglist(10)),
+ ?line ok = ufunsort_check3(1, biglist(100)),
+ ?line ok = ufunsort_check3(1, biglist(1000)),
+ ?line ok = ufunsort_check3(1, biglist(10000)),
+
+ ?line ok = gen_ufunsort_check(1, ubiglist(100)),
+ ?line ok = gen_ufunsort_check(1, ubiglist(1000)),
+ ?line ok = gen_ufunsort_check(1, ubiglist(10000)),
+ ok.
+
+%% Check that usort/2 is stable and correct relative sort/2.
+gen_ufunsort_check(I, Input) ->
+ U = ufunsort(I, Input),
+ S = funsort(I, Input),
+ case U == no_dups_keys(S, I) of
+ true ->
+ ok;
+ false ->
+ io:format("~w~n", [Input]),
+ erlang:error(gen_ufunsort_check)
+ end.
+
+%% Used for checking that the first copy is kept.
+ufunsort_check_stability(L) ->
+ I = 1,
+ U = ufunsort(I, L),
+ S = no_dups(funsort(I, L)),
+ check_stab(L, U, S, "usort/2", "sort/2").
+
+ufunsort_check3(I, Input) ->
+ ucheck_sorted(I, 3, Input, ufunsort(I, Input)).
+
+%%% Keysort a list, check that the returned list is what we expected,
+%%% and that it is actually sorted.
+ufunsort_check(I, Input, Expected) ->
+ ?line Expected = ufunsort(I, Input),
+ ucheck_sorted(I, Input, Expected).
+
+% Do a keysort
+ufunsort(I, L) ->
+ lists:usort(funsort_fun(I), L).
+
+funsort_fun(I) ->
+ fun(A, B) when tuple_size(A) >= I, tuple_size(B) >= I ->
+ element(I, A) =< element(I, B)
+ end.
+
+check_stab(L, U, S, US, SS) ->
+ UP = explicit_pid(U),
+ SP = explicit_pid(S),
+ case UP == SP of
+ true ->
+ ok;
+ false ->
+ io:format("In: ~w~n", [explicit_pid(L)]),
+ io:format("~s: ~w~n", [US, UP]),
+ io:format("~s: ~w~n", [SS, SP]),
+ erlang:error(unstable)
+ end.
+
+%%%------------------------------------------------------------
+%%% Generate lists of given length, containing 3-tuples with
+%%% random integer elements in the range 0..44 as elements 1 and 2.
+%%% Element 3 in the tuple is the position of the tuple in the list.
+
+biglist(N) ->
+ {A, B, C} = get_seed(),
+ random:seed(A, B, C),
+ biglist(N, []).
+
+biglist(0, L) ->
+ L;
+biglist(N, L) ->
+ E = random_tuple(45, N),
+ biglist(N-1, [E|L]).
+
+%%%------------------------------------------------------------
+%%% Generate lists of given length, containing 2-tuples with
+%%% random integer elements in the range 0..10 as element 1.
+%%% Element 2 in the tuple is a random integer in the range 0..5.
+%%% No sequence number.
+
+ubiglist(N) ->
+ {A, B, C} = get_seed(),
+ random:seed(A, B, C),
+ ubiglist(N, []).
+
+ubiglist(0, L) ->
+ L;
+ubiglist(N, L) ->
+ E = urandom_tuple(11, 6),
+ ubiglist(N-1, [E|L]).
+
+urandom_tuple(N, I) ->
+ R1 = randint(N),
+ R2 = randint(I),
+ {R1, R2}.
+
+%%%------------------------------------------------------------
+%%% Generate lists of given length, containing 2-tuples with random
+%%% integer elements in the range 0..10 as elements 1. All tuples have
+%%% the same function as element 2, but every function is created in a
+%%% unique process. ==/2 will return 'true' for any pair of functions,
+%%% but erlang:fun_info(Fun, pid) can be used for distinguishing
+%%% functions created in different processes. The pid acts like a
+%%% sequence number.
+
+bigfunlist(N) ->
+ {A, B, C} = get_seed(),
+ random:seed(A, B, C),
+ bigfunlist_1(N).
+
+bigfunlist_1(N) when N < 30000 -> % Now (R8) max 32000 different pids.
+ case catch bigfunlist(N, 0, []) of
+ {'EXIT', _} ->
+ bigfunlist_1(N);
+ Reply ->
+ Reply
+ end.
+
+bigfunlist(0, _P, L) ->
+ lists:reverse(L);
+bigfunlist(N, P, L) ->
+ {E, NP} = random_funtuple(P, 11),
+ bigfunlist(N-1, NP, [E | L]).
+
+random_funtuple(P, N) ->
+ R = randint(N),
+ F = make_fun(),
+ NP = fun_pid(F),
+ true = NP > P,
+ {{R, F}, NP}.
+
+make_fun() ->
+ Pid = spawn(?MODULE, make_fun, [self()]),
+ receive {Pid, Fun} -> Fun end.
+
+make_fun(Pid) ->
+ Pid ! {self(), fun make_fun/1}.
+
+fun_pid(Fun) ->
+ erlang:fun_info(Fun, pid).
+
+get_seed() ->
+ case random:seed() of
+ undefined ->
+ now();
+ Tuple ->
+ Tuple
+ end.
+
+random_tuple(N, Seq) ->
+ R1 = randint(N),
+ R2 = randint(N),
+ {R1, R2, Seq}.
+
+randint(N) ->
+ trunc(random:uniform() * N).
+
+%% The first "duplicate" is kept.
+no_dups([]) ->
+ [];
+no_dups([H | T]) ->
+ no_dups(H, T, []).
+
+no_dups(H, [H1 | T], L) when H == H1 ->
+ no_dups(H, T, L);
+no_dups(H, [H1 | T], L) ->
+ no_dups(H1, T, [H | L]);
+no_dups(H, [], L) ->
+ lists:reverse([H | L]).
+
+%% The first "duplicate" is kept.
+no_dups_keys([], _I) ->
+ [];
+no_dups_keys([H | T], I) ->
+ no_dups_keys(H, T, [], I).
+
+no_dups_keys(H, [H1 | T], L, I) when element(I, H) == element(I, H1) ->
+ no_dups_keys(H, T, L, I);
+no_dups_keys(H, [H1 | T], L, I) ->
+ no_dups_keys(H1, T, [H | L], I);
+no_dups_keys(H, [], L, _I) ->
+ lists:reverse([H | L]).
+
+perms([]) ->
+ [[]];
+perms(L) ->
+ [[H|T] || H <- L, T <- perms(L--[H])].
+
+%%%------------------------------------------------------------
+%%% Test the sort routines with randomly generated lists.
+
+-record(state, {sort = 0, usort = 0, stable = 0}).
+
+%% Run it interactively. 'stop' or 'info' recognized commands.
+sort_loop() ->
+ sort_loop(5000).
+
+sort_loop(N) when is_integer(N), N > 0 ->
+ Pid = spawn_link(?MODULE, sloop, [N]),
+ sort_loop_1(Pid).
+
+sort_loop_1(Pid) ->
+ case io:get_line('? ') of
+ eof ->
+ ok;
+ "stop\n" ->
+ Pid ! {self(), stop},
+ receive {Pid, S} -> display_state(S) end;
+ "info\n" ->
+ Pid ! {self(), info},
+ receive {Pid, S} -> display_state(S) end,
+ sort_loop_1(Pid);
+ _Other ->
+ sort_loop_1(Pid)
+ end.
+
+sloop(N) ->
+ {A, B, C} = get_seed(),
+ random:seed(A, B, C),
+ sloop(N, #state{}).
+
+sloop(N, S) ->
+ receive
+ {From, stop} ->
+ From ! {self(), S};
+ {From, info} ->
+ From ! {self(), S},
+ sloop(N, S)
+ after 0 ->
+ Len = randint(N),
+ NS = case randint(3) of
+ 0 ->
+ BL = biglist(Len, []),
+ ok = check(BL),
+ ok = keysort_check3(1, BL),
+ ok = funsort_check3(1, BL),
+ S#state{sort = S#state.sort + 1};
+ 1 ->
+ BL = ubiglist(Len, []),
+ ok = ucheck(BL),
+ ok = gen_ukeysort_check(1, BL),
+ ok = gen_ufunsort_check(1, BL),
+ S#state{usort = S#state.usort + 1};
+ 2 ->
+ BL = bigfunlist(Len),
+ %% ok = check_stability(BL),
+ ok = ucheck_stability(BL),
+ ok = ukeysort_check_stability(BL),
+ ok = ufunsort_check_stability(BL),
+ S#state{stable = S#state.stable + 1}
+ end,
+ sloop(N, NS)
+ end.
+
+display_state(S) ->
+ io:format("sort: ~p~n", [S#state.sort]),
+ io:format("usort: ~p~n", [S#state.usort]),
+ io:format("stable: ~p~n", [S#state.stable]).
+
+%% This version of sort/1 is really stable; the order of equal
+%% elements is kept. It is used for checking the current
+%% implementation of usort/1 etc.
+
+lsort([X, Y | L] = L0) when X =< Y ->
+ case L of
+ [] ->
+ L0;
+ [Z] when Y =< Z ->
+ L0;
+ [Z] when X =< Z ->
+ [X, Z, Y];
+ [Z] ->
+ [Z, X, Y];
+ _ ->
+ split_1(X, Y, L, [], [])
+ end;
+lsort([X, Y | L]) ->
+ case L of
+ [] ->
+ [Y, X];
+ [Z] when X =< Z ->
+ [Y, X | L];
+ [Z] when Y =< Z ->
+ [Y, Z, X];
+ [Z] ->
+ [Z, Y, X];
+ _ ->
+ split_2(X, Y, L, [], [])
+ end;
+lsort([_] = L) ->
+ L;
+lsort([] = L) ->
+ L.
+
+split_1(X, Y, [Z | L], R, Rs) when Z >= Y ->
+ split_1(Y, Z, L, [X | R], Rs);
+split_1(X, Y, [Z | L], R, Rs) when Z >= X ->
+ split_1(Z, Y, L, [X | R], Rs);
+split_1(X, Y, [Z | L], [], Rs) ->
+ split_1(X, Y, L, [Z], Rs);
+split_1(X, Y, [Z | L], R, Rs) ->
+ split_1_1(X, Y, L, R, Rs, Z);
+split_1(X, Y, [], R, Rs) ->
+ rmergel([[Y, X | R] | Rs], [], asc).
+
+split_1_1(X, Y, [Z | L], R, Rs, S) when Z >= Y ->
+ split_1_1(Y, Z, L, [X | R], Rs, S);
+split_1_1(X, Y, [Z | L], R, Rs, S) when Z >= X ->
+ split_1_1(Z, Y, L, [X | R], Rs, S);
+split_1_1(X, Y, [Z | L], R, Rs, S) when S =< Z ->
+ split_1(S, Z, L, [], [[Y, X | R] | Rs]);
+split_1_1(X, Y, [Z | L], R, Rs, S) ->
+ split_1(Z, S, L, [], [[Y, X | R] | Rs]);
+split_1_1(X, Y, [], R, Rs, S) ->
+ rmergel([[S], [Y, X | R] | Rs], [], asc).
+
+split_2(X, Y, [Z | L], R, Rs) when Z < Y ->
+ split_2(Y, Z, L, [X | R], Rs);
+split_2(X, Y, [Z | L], R, Rs) when Z < X ->
+ split_2(Z, Y, L, [X | R], Rs);
+split_2(X, Y, [Z | L], [], Rs) ->
+ split_2(X, Y, L, [Z], Rs);
+split_2(X, Y, [Z | L], R, Rs) ->
+ split_2_1(X, Y, L, R, Rs, Z);
+split_2(X, Y, [], R, Rs) ->
+ mergel([[Y, X | R] | Rs], [], desc).
+
+split_2_1(X, Y, [Z | L], R, Rs, S) when Z < Y ->
+ split_2_1(Y, Z, L, [X | R], Rs, S);
+split_2_1(X, Y, [Z | L], R, Rs, S) when Z < X ->
+ split_2_1(Z, Y, L, [X | R], Rs, S);
+split_2_1(X, Y, [Z | L], R, Rs, S) when S > Z ->
+ split_2(S, Z, L, [], [[Y, X | R] | Rs]);
+split_2_1(X, Y, [Z | L], R, Rs, S) ->
+ split_2(Z, S, L, [], [[Y, X | R] | Rs]);
+split_2_1(X, Y, [], R, Rs, S) ->
+ mergel([[S], [Y, X | R] | Rs], [], desc).
+
+mergel([[] | L], Acc, O) ->
+ mergel(L, Acc, O);
+mergel([T1, [H2 | T2] | L], Acc, asc) ->
+ mergel(L, [merge2_1(T1, H2, T2, []) | Acc], asc);
+mergel([[H2 | T2], T1 | L], Acc, desc) ->
+ mergel(L, [merge2_1(T1, H2, T2, []) | Acc], desc);
+mergel([L], [], _O) ->
+ L;
+mergel([L], Acc, O) ->
+ rmergel([lists:reverse(L, []) | Acc], [], O);
+mergel([], [], _O) ->
+ [];
+mergel([], Acc, O) ->
+ rmergel(Acc, [], O);
+mergel([A, [] | L], Acc, O) ->
+ mergel([A | L], Acc, O);
+mergel([A, B, [] | L], Acc, O) ->
+ mergel([A, B | L], Acc, O).
+
+rmergel([[H2 | T2], T1 | L], Acc, asc) ->
+ rmergel(L, [rmerge2_1(T1, H2, T2, []) | Acc], asc);
+rmergel([T1, [H2 | T2] | L], Acc, desc) ->
+ rmergel(L, [rmerge2_1(T1, H2, T2, []) | Acc], desc);
+rmergel([L], Acc, O) ->
+ mergel([lists:reverse(L, []) | Acc], [], O);
+rmergel([], Acc, O) ->
+ mergel(Acc, [], O).
+
+merge2_1([H1 | T1], H2, T2, M) when H1 =< H2 ->
+ merge2_1(T1, H2, T2, [H1 | M]);
+merge2_1([H1 | T1], H2, T2, M) ->
+ merge2_2(T1, H1, T2, [H2 | M]);
+merge2_1([], H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+merge2_2(T1, H1, [H2 | T2], M) when H1 =< H2 ->
+ merge2_1(T1, H2, T2, [H1 | M]);
+merge2_2(T1, H1, [H2 | T2], M) ->
+ merge2_2(T1, H1, T2, [H2 | M]);
+merge2_2(T1, H1, [], M) ->
+ lists:reverse(T1, [H1 | M]).
+
+rmerge2_1([H1 | T1], H2, T2, M) when H1 =< H2 ->
+ rmerge2_2(T1, H1, T2, [H2 | M]);
+rmerge2_1([H1 | T1], H2, T2, M) ->
+ rmerge2_1(T1, H2, T2, [H1 | M]);
+rmerge2_1([], H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+rmerge2_2(T1, H1, [H2 | T2], M) when H1 =< H2 ->
+ rmerge2_2(T1, H1, T2, [H2 | M]);
+rmerge2_2(T1, H1, [H2 | T2], M) ->
+ rmerge2_1(T1, H2, T2, [H1 | M]);
+rmerge2_2(T1, H1, [], M) ->
+ lists:reverse(T1, [H1 | M]).
+
+
+
+%% This version of keysort/2 is really stable; the order of equal
+%% elements is kept. It is used for checking the current
+%% implementation of ukeysort/2 etc.
+
+lkeysort(Index, L) when is_integer(Index), Index > 0 ->
+ case L of
+ [] -> L;
+ [_] -> L;
+ [X, Y | T] ->
+ EX = element(Index, X),
+ EY = element(Index, Y),
+ if
+ EX =< EY ->
+ keysplit_1(Index, X, EX, Y, EY, T, [], []);
+ true ->
+ keysplit_2(Index, Y, EY, T, [X])
+ end
+ end.
+
+keysplit_1(I, X, EX, Y, EY, [Z | L], R, Rs) ->
+ EZ = element(I, Z),
+ if
+ EY =< EZ ->
+ keysplit_1(I, Y, EY, Z, EZ, L, [X | R], Rs);
+ EX =< EZ ->
+ keysplit_1(I, Z, EZ, Y, EY, L, [X | R], Rs);
+ true, R == [] ->
+ keysplit_1(I, X, EX, Y, EY, L, [Z], Rs);
+ true ->
+ keysplit_1_1(I, X, EX, Y, EY, L, R, Rs, Z, EZ)
+ end;
+keysplit_1(I, X, _EX, Y, _EY, [], R, Rs) ->
+ rkeymergel(I, [[Y, X | R] | Rs], []).
+
+%% One out-of-order element, S.
+keysplit_1_1(I, X, EX, Y, EY, [Z | L], R, Rs, S, ES) ->
+ EZ = element(I, Z),
+ if
+ EY =< EZ ->
+ keysplit_1_1(I, Y, EY, Z, EZ, L, [X | R], Rs, S, ES);
+ EX =< EZ ->
+ keysplit_1_1(I, Z, EZ, Y, EY, L, [X | R], Rs, S, ES);
+ ES =< EZ ->
+ keysplit_1(I, S, ES, Z, EZ, L, [], [[Y, X | R] | Rs]);
+ true ->
+ keysplit_1(I, Z, EZ, S, ES, L, [], [[Y, X | R] | Rs])
+ end;
+keysplit_1_1(I, X, _EX, Y, _EY, [], R, Rs, S, _ES) ->
+ rkeymergel(I, [[S], [Y, X | R] | Rs], []).
+
+%% Descending.
+keysplit_2(I, Y, EY, [Z | L], R) ->
+ EZ = element(I, Z),
+ if
+ EY =< EZ ->
+ keysplit_1(I, Y, EY, Z, EZ, L, [], [lists:reverse(R, [])]);
+ true ->
+ keysplit_2(I, Z, EZ, L, [Y | R])
+ end;
+keysplit_2(_I, Y, _EY, [], R) ->
+ [Y | R].
+
+keymergel(I, [T1, [H2 | T2] | L], Acc) ->
+ keymergel(I, L, [keymerge2_1(I, T1, element(I, H2), H2, T2, []) | Acc]);
+keymergel(_I, [L], []) ->
+ L;
+keymergel(I, [L], Acc) ->
+ rkeymergel(I, [lists:reverse(L, []) | Acc], []);
+keymergel(I, [], Acc) ->
+ rkeymergel(I, Acc, []).
+
+rkeymergel(I, [[H2 | T2], T1 | L], Acc) ->
+ rkeymergel(I, L, [rkeymerge2_1(I, T1, element(I, H2), H2, T2, []) | Acc]);
+rkeymergel(I, [L], Acc) ->
+ keymergel(I, [lists:reverse(L, []) | Acc], []);
+rkeymergel(I, [], Acc) ->
+ keymergel(I, Acc, []).
+
+keymerge2_1(I, [H1 | T1], E2, H2, T2, M) ->
+ E1 = element(I, H1),
+ if
+ E1 =< E2 ->
+ keymerge2_1(I, T1, E2, H2, T2, [H1 | M]);
+ true ->
+ keymerge2_2(I, T1, E1, H1, T2, [H2 | M])
+ end;
+keymerge2_1(_I, [], _E2, H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+keymerge2_2(I, T1, E1, H1, [H2 | T2], M) ->
+ E2 = element(I, H2),
+ if
+ E1 =< E2 ->
+ keymerge2_1(I, T1, E2, H2, T2, [H1 | M]);
+ true ->
+ keymerge2_2(I, T1, E1, H1, T2, [H2 | M])
+ end;
+keymerge2_2(_I, T1, _E1, H1, [], M) ->
+ lists:reverse(T1, [H1 | M]).
+
+rkeymerge2_1(I, [H1 | T1], E2, H2, T2, M) ->
+ E1 = element(I, H1),
+ if
+ E1 =< E2 ->
+ rkeymerge2_2(I, T1, E1, T2, [H2 | M], H1);
+ true ->
+ rkeymerge2_1(I, T1, E2, H2, T2, [H1 | M])
+ end;
+rkeymerge2_1(_I, [], _E2, H2, T2, M) ->
+ lists:reverse(T2, [H2 | M]).
+
+rkeymerge2_2(I, T1, E1, [H2 | T2], M, H1) ->
+ E2 = element(I, H2),
+ if
+ E1 =< E2 ->
+ rkeymerge2_2(I, T1, E1, T2, [H2 | M], H1);
+ true ->
+ rkeymerge2_1(I, T1, E2, H2, T2, [H1 | M])
+ end;
+rkeymerge2_2(_I, T1, _E1, [], M, H1) ->
+ lists:reverse(T1, [H1 | M]).
+
+
+%%%------------------------------------------------------------
+
+seq(doc) ->
+ ["Tests lists:seq/3"];
+seq(suite) ->
+ [
+ seq_loop,
+ seq_2, seq_3, seq_2_e, seq_3_e].
+
+seq_loop(doc) ->
+ ["Test for infinite loop (OTP-2404)."];
+seq_loop(suite) ->
+ [];
+seq_loop(Config) when is_list(Config) ->
+ ?line _ = (catch lists:seq(1, 5, -1)),
+ ok.
+
+seq_2(doc) ->
+ ["Non-error cases for seq/2"];
+seq_2(suite) ->
+ [];
+seq_2(Config) when is_list(Config) ->
+ ?line [1,2,3] = lists:seq(1,3),
+ ?line [1] = lists:seq(1,1),
+ ?line Big = 748274827583793785928592859,
+ ?line Big1 = Big+1,
+ ?line Big2 = Big+2,
+ ?line [Big, Big1, Big2] = lists:seq(Big, Big+2),
+ ok.
+
+seq_2_e(doc) ->
+ ["Error cases for seq/2"];
+seq_2_e(suite) ->
+ [];
+seq_2_e(Config) when is_list(Config) ->
+ ?line seq_error([4, 2]),
+ ?line seq_error([1, a]),
+ ?line seq_error([1.0, 2.0]),
+ ok.
+
+seq_error(Args) ->
+ {'EXIT', _} = (catch apply(lists, seq, Args)).
+
+seq_3(doc) ->
+ ["Non-error cases for seq/3"];
+seq_3(suite) ->
+ [];
+seq_3(Config) when is_list(Config) ->
+ ?line [1,2,3] = lists:seq(1,3,1),
+ ?line [1] = lists:seq(1,1,1),
+ ?line Big = 748274827583793785928592859,
+ ?line Big1 = Big+1,
+ ?line Big2 = Big+2,
+ ?line [Big, Big1, Big2] = lists:seq(Big, Big+2,1),
+
+ ?line [3,2,1] = lists:seq(3,1,-1),
+ ?line [1] = lists:seq(1,1,-1),
+
+ ?line [3,1] = lists:seq(3,1,-2),
+ ?line [1] = lists:seq(1, 10, 10),
+ ?line [1, 4, 7, 10, 13, 16, 19] = lists:seq(1, 19, 3),
+ ?line [1, 4, 7, 10, 13, 16, 19] = lists:seq(1, 20, 3),
+ ?line [1, 4, 7, 10, 13, 16, 19] = lists:seq(1, 21, 3),
+
+ ?line [1] = lists:seq(1, 1, 0), %OTP-2613
+ ok.
+
+seq_3_e(doc) ->
+ ["Error cases for seq/3"];
+seq_3_e(suite) ->
+ [];
+seq_3_e(Config) when is_list(Config) ->
+ ?line seq_error([4, 2, 1]),
+ ?line seq_error([3, 5, -1]),
+ ?line seq_error([1, a, 1]),
+ ?line seq_error([1.0, 2.0, 1]),
+
+ ?line seq_error([1, 3, 1.0]),
+ ?line seq_error([1, 3, a]),
+ ?line seq_error([1, 3, 0]),
+
+ ?line seq_error([a, a, 0]),
+ ok.
+
+otp_7230(doc) ->
+ ["OTP-7230. seq/1,2 returns the empty list"];
+otp_7230(suite) ->
+ [];
+otp_7230(Config) when is_list(Config) ->
+ From = -10,
+ To = 10,
+ StepFrom = -10,
+ StepTo = 10,
+
+ L = lists:seq(From, To),
+ SL = lists:seq(StepFrom, StepTo),
+ ?line [] =
+ [{F, T, S} ||
+ F <- L, T <- L, S <- SL,
+ not check_seq(F, T, S, catch lists:seq(F, T, S))
+ orelse
+ S =:= 1 andalso not check_seq(F, T, S, catch lists:seq(F, T))
+ ].
+
+check_seq(From, To, 0, R) ->
+ From =:= To andalso R =:= [From]
+ orelse
+ From =/= To andalso is_tuple(R) andalso element(1, R) =:= 'EXIT';
+check_seq(From, To, Step, []) when Step =/= 0 ->
+ 0 =:= property(From, To, Step)
+ andalso
+ (
+ Step > 0 andalso To < From andalso From-To =< Step
+ orelse
+ Step < 0 andalso To > From andalso To-From =< -Step
+ );
+check_seq(From, To, Step, R) when R =/= [], To < From, Step > 0 ->
+ is_tuple(R) andalso element(1, R) =:= 'EXIT';
+check_seq(From, To, Step, R) when R =/= [], To > From, Step < 0 ->
+ is_tuple(R) andalso element(1, R) =:= 'EXIT';
+check_seq(From, To, Step, L) when is_list(L), L =/= [], Step =/= 0 ->
+ First = hd(L),
+ Last = lists:last(L),
+ Min = lists:min(L),
+ Max = lists:max(L),
+
+ [] =:= [E || E <- L, not is_integer(E)]
+ andalso
+ %% The difference between two consecutive elements is Step:
+ begin
+ LS = [First-Step]++L,
+ LR = L++[Last+Step],
+ [Step] =:= lists:usort([B-A || {A,B} <- lists:zip(LS, LR)])
+ end
+ andalso
+ %% The first element of L is From:
+ From =:= First
+ andalso
+ %% No element outside the given interval:
+ Min >= lists:min([From, To])
+ andalso
+ Max =< lists:max([From, To])
+ andalso
+ %% All elements are present:
+ abs(To-Last) < abs(Step)
+ andalso
+ length(L) =:= property(From, To, Step);
+check_seq(_From, _To, _Step, _R) ->
+ false.
+
+property(From, To, Step) ->
+ ((To-From+Step) div Step).
+
+%%%------------------------------------------------------------
+
+sublist(doc) ->
+ ["Tests lists:sublist/[2,3]"];
+sublist(suite) ->
+ [sublist_2, sublist_3, sublist_2_e, sublist_3_e].
+
+-define(sublist_error2(X,Y), ?line {'EXIT', _} = (catch lists:sublist(X,Y))).
+-define(sublist_error3(X,Y,Z), ?line {'EXIT', _} = (catch lists:sublist(X,Y,Z))).
+
+sublist_2(doc) -> ["sublist/2"];
+sublist_2(suite) -> [];
+sublist_2(Config) when is_list(Config) ->
+ ?line [] = lists:sublist([], 0),
+ ?line [] = lists:sublist([], 1),
+ ?line [] = lists:sublist([a], 0),
+ ?line [a] = lists:sublist([a], 1),
+ ?line [a] = lists:sublist([a], 2),
+ ?line [a] = lists:sublist([a|b], 1),
+
+ ?line [a,b] = lists:sublist([a,b|c], 2),
+
+ ok.
+
+sublist_2_e(doc) -> ["sublist/2 error cases"];
+sublist_2_e(suite) -> [];
+sublist_2_e(Config) when is_list(Config) ->
+ ?sublist_error2([], -1),
+ ?sublist_error2(a, -1),
+ ?sublist_error2(a, 0),
+ ?sublist_error2([a|b], 2),
+ ?sublist_error2([a], x),
+ ?sublist_error2([a], 1.5),
+ ?sublist_error2([], x),
+ ?sublist_error2([], 1.5),
+ ok.
+
+sublist_3(doc) -> ["sublist/3"];
+sublist_3(suite) -> [];
+sublist_3(Config) when is_list(Config) ->
+ ?line [] = lists:sublist([], 1, 0),
+ ?line [] = lists:sublist([], 1, 1),
+ ?line [] = lists:sublist([a], 1, 0),
+ ?line [a] = lists:sublist([a], 1, 1),
+ ?line [a] = lists:sublist([a], 1, 2),
+ ?line [a] = lists:sublist([a|b], 1, 1),
+
+ ?line [] = lists:sublist([], 1, 0),
+ ?line [] = lists:sublist([], 1, 1),
+ ?line [] = lists:sublist([a], 1, 0),
+ ?line [a] = lists:sublist([a], 1, 1),
+ ?line [a] = lists:sublist([a], 1, 2),
+ ?line [] = lists:sublist([a], 2, 1),
+ ?line [] = lists:sublist([a], 2, 2),
+ ?line [] = lists:sublist([a], 2, 79),
+ ?line [] = lists:sublist([a,b|c], 1, 0),
+ ?line [] = lists:sublist([a,b|c], 2, 0),
+ ?line [a] = lists:sublist([a,b|c], 1, 1),
+ ?line [b] = lists:sublist([a,b|c], 2, 1),
+ ?line [a,b] = lists:sublist([a,b|c], 1, 2),
+
+ ?line [] = lists:sublist([a], 2, 0),
+
+ ok.
+
+sublist_3_e(doc) -> ["sublist/3 error cases"];
+sublist_3_e(suite) -> [];
+sublist_3_e(Config) when is_list(Config) ->
+ ?sublist_error3([], 1, -1),
+ ?sublist_error3(a, 1, -1),
+ ?sublist_error3(a, 1, 0),
+ ?sublist_error3([a|b], 1, 2),
+ ?sublist_error3([a], 1, x),
+ ?sublist_error3([a], 1, 1.5),
+ ?sublist_error3([], 1, x),
+ ?sublist_error3([], 1, 1.5),
+
+ ?sublist_error3([], -1, 0),
+ ?sublist_error3(a, x, -1),
+ ?sublist_error3([a,b], 0.5, 1),
+ ?sublist_error3([a,b], 1.5, 1),
+ ?sublist_error3([a], 1, x),
+ ?sublist_error3([a], 1, 1.5),
+ ?sublist_error3([], 1, x),
+ ?sublist_error3([], 1, 1.5),
+
+ ?sublist_error3([a], 0, -1),
+ ?sublist_error3([a], 1, -1),
+ ?sublist_error3([a], 2, -1),
+ ?sublist_error3([a], 0, 0),
+ ?sublist_error3([a], 0, 1),
+
+ ?sublist_error3([a,b|c], 2, 2),
+ ?sublist_error3([a,b|c], 3, 0),
+ ?sublist_error3([a,b|c], 3, 1),
+ ok.
+
+%%%------------------------------------------------------------
+
+flatten(doc) ->
+ ["Tests lists:flatten/[1,2]"];
+flatten(suite) ->
+ [flatten_1, flatten_2, flatten_1_e, flatten_2_e].
+
+-define(flatten_error1(X), ?line {'EXIT', _} = (catch lists:flatten(X))).
+-define(flatten_error2(X,Y), ?line {'EXIT', _} = (catch lists:flatten(X,Y))).
+
+flatten_1(doc) -> ["flatten/1"];
+flatten_1(suite) -> [];
+flatten_1(Config) when is_list(Config) ->
+ ?line [] = lists:flatten([]),
+ ?line [1,2] = lists:flatten([1,2]),
+ ?line [1,2] = lists:flatten([1,[2]]),
+ ?line [1,2] = lists:flatten([[1],2]),
+ ?line [1,2] = lists:flatten([[1],[2]]),
+ ?line [1,2] = lists:flatten([[1,2]]),
+ ?line [a,b,c,d] = lists:flatten([[a],[b,c,[d]]]),
+
+ ok.
+
+flatten_1_e(doc) -> ["flatten/1 error cases"];
+flatten_1_e(suite) -> [];
+flatten_1_e(Config) when is_list(Config) ->
+ ?flatten_error1(a),
+ ?flatten_error1([a|b]),
+ ?flatten_error1([[a],[b|c],[d]]),
+ ok.
+
+%%% [arndt] What if second arg isn't a proper list? This issue isn't
+%%% clear-cut. Right now, I think that any term should be allowed.
+%%% But I also wish this function didn't exist at all.
+
+flatten_2(doc) -> ["flatten/2"];
+flatten_2(suite) -> [];
+flatten_2(Config) when is_list(Config) ->
+ ?line [] = lists:flatten([]),
+ ?line [a] = lists:flatten([a]),
+ ok.
+
+flatten_2_e(doc) -> ["flatten/2 error cases"];
+flatten_2_e(suite) -> [];
+flatten_2_e(Config) when is_list(Config) ->
+ ok.
+
+%% Test lists:zip/2, lists:unzip/1.
+zip_unzip(Config) when is_list(Config) ->
+ ?line [] = lists:zip([], []),
+ ?line [{a,b}] = lists:zip([a], [b]),
+ ?line [{42.0,{kalle,nisse}},{a,b}] = lists:zip([42.0,a], [{kalle,nisse},b]),
+
+ %% Longer lists.
+ ?line SeqA = lists:seq(45, 200),
+ ?line SeqB = [A*A || A <- SeqA],
+ ?line AB = lists:zip(SeqA, SeqB),
+ ?line SeqA = [A || {A,_} <- AB],
+ ?line SeqB = [B || {_,B} <- AB],
+ ?line {SeqA,SeqB} = lists:unzip(AB),
+
+ %% Some more unzip/1.
+ ?line {[],[]} = lists:unzip([]),
+ ?line {[a],[b]} = lists:unzip([{a,b}]),
+ ?line {[a,c],[b,d]} = lists:unzip([{a,b},{c,d}]),
+
+ %% Error cases.
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zip([], [b])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zip([a], [])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zip([a], [b,c])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zip([a], [b,c])),
+ ok.
+
+%% Test lists:zip3/3, lists:unzip3/1.
+zip_unzip3(Config) when is_list(Config) ->
+ ?line [] = lists:zip3([], [], []),
+ ?line [{a,b,c}] = lists:zip3([a], [b], [c]),
+
+ %% Longer lists.
+ ?line SeqA = lists:seq(45, 200),
+ ?line SeqB = [2*A || A <- SeqA],
+ ?line SeqC = [A*A || A <- SeqA],
+ ?line ABC = lists:zip3(SeqA, SeqB, SeqC),
+ ?line SeqA = [A || {A,_,_} <- ABC],
+ ?line SeqB = [B || {_,B,_} <- ABC],
+ ?line SeqC = [C || {_,_,C} <- ABC],
+ ?line {SeqA,SeqB,SeqC} = lists:unzip3(ABC),
+
+ %% Some more unzip3/1.
+ ?line {[],[],[]} = lists:unzip3([]),
+ ?line {[a],[b],[c]} = lists:unzip3([{a,b,c}]),
+
+ %% Error cases.
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zip3([], [], [c])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zip3([], [b], [])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zip3([a], [], [])),
+
+ ok.
+
+%% Test lists:zipwith/3.
+zipwith(Config) when is_list(Config) ->
+ Zip = fun(A, B) -> [A|B] end,
+
+ ?line [] = lists:zipwith(Zip, [], []),
+ ?line [[a|b]] = lists:zipwith(Zip, [a], [b]),
+
+ %% Longer lists.
+ ?line SeqA = lists:seq(77, 300),
+ ?line SeqB = [A*A || A <- SeqA],
+ ?line AB = lists:zipwith(Zip, SeqA, SeqB),
+ ?line SeqA = [A || [A|_] <- AB],
+ ?line SeqB = [B || [_|B] <- AB],
+
+ %% Error cases.
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith(badfun, [], [])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith(Zip, [], [b])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith(Zip, [a], [])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith(Zip, [a], [b,c])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith(Zip, [a], [b,c])),
+ ok.
+
+%% Test lists:zipwith3/4.
+zipwith3(Config) when is_list(Config) ->
+ Zip = fun(A, B, C) -> [A,B,C] end,
+
+ ?line [] = lists:zipwith3(Zip, [], [], []),
+ ?line [[a,b,c]] = lists:zipwith3(Zip, [a], [b], [c]),
+
+ %% Longer lists.
+ ?line SeqA = lists:seq(45, 200),
+ ?line SeqB = [2*A || A <- SeqA],
+ ?line SeqC = [A*A || A <- SeqA],
+ ?line ABC = lists:zipwith3(Zip, SeqA, SeqB, SeqC),
+ ?line SeqA = [A || [A,_,_] <- ABC],
+ ?line SeqB = [B || [_,B,_] <- ABC],
+ ?line SeqC = [C || [_,_,C] <- ABC],
+
+ %% Error cases.
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith3(badfun, [], [], [])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith3(Zip, [], [], [c])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith3(Zip, [], [b], [])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:zipwith3(Zip, [a], [], [])),
+
+ ok.
+
+%% Test lists:filter/2, lists:partition/2.
+filter_partition(Config) when is_list(Config) ->
+ F = fun(I) -> I rem 2 =:= 0 end,
+ ?line filpart(F, [], []),
+ ?line filpart(F, [1], []),
+ ?line filpart(F, [1,3,17], []),
+ ?line filpart(F, [1,2,3,17], [2]),
+ ?line filpart(F, [6,8,1,2,3,17], [6,8,2]),
+ ?line filpart(F, [6,8,1,2,42,3,17], [6,8,2,42]),
+
+ %% Error cases.
+ ?line {'EXIT',{function_clause,_}} = (catch lists:filter(badfun, [])),
+ ?line {'EXIT',{function_clause,_}} = (catch lists:partition(badfun, [])),
+ ok.
+
+filpart(F, All, Exp) ->
+ Exp = lists:filter(F, All),
+ Other = lists:filter(fun(E) -> not F(E) end, All),
+ {Exp,Other} = lists:partition(F, All).
+
+tickets(doc) ->
+ ["Ticktes."];
+tickets(suite) ->
+ [otp_5939, otp_6023, otp_6606, otp_7230].
+
+otp_5939(doc) -> ["OTP-5939. Guard tests added."];
+otp_5939(suite) -> [];
+otp_5939(Config) when is_list(Config) ->
+ Fun1 = fun(A) -> A end,
+ Fun2 = fun(A, B) -> {A,B} end,
+ Fun3 = fun(A, B, C) -> {A,B,C} end,
+ Pred = fun(_A) -> true end,
+ Fold = fun(_E, A) -> A end,
+ MapFold = fun(E, A) -> {E,A} end,
+
+ ?line {'EXIT', _} = (catch lists:usort( [asd], [qwe])),
+
+ ?line {'EXIT', _} = (catch lists:zipwith(func, [], [])),
+ ?line [] = lists:zipwith(Fun2, [], []),
+ ?line {'EXIT', _} = (catch lists:zipwith3(func, [], [], [])),
+ ?line [] = lists:zipwith3(Fun3, [], [], []),
+ ?line {'EXIT', _} = (catch lists:keymap(func, 1, [])),
+ ?line {'EXIT', _} = (catch lists:keymap(Fun1, 0, [])),
+ ?line [] = lists:keymap(Fun1, 1, []),
+ ?line {'EXIT', _} = (catch lists:merge(func, [], [1])),
+ ?line {'EXIT', _} = (catch lists:merge(func, [1], [])),
+ ?line [] = lists:merge(Fun2, [], []),
+ ?line {'EXIT', _} = (catch lists:rmerge(func, [], [1])),
+ ?line {'EXIT', _} = (catch lists:rmerge(func, [1], [])),
+ ?line [] = lists:rmerge(Fun2, [], []),
+ ?line {'EXIT', _} = (catch lists:usort(func, [])),
+ ?line {'EXIT', _} = (catch lists:usort(func, [a])),
+ ?line {'EXIT', _} = (catch lists:usort(func, [a, b])),
+ ?line [] = lists:usort(Fun2, []),
+ ?line {'EXIT', _} = (catch lists:umerge(func, [], [1])),
+ ?line {'EXIT', _} = (catch lists:merge(func, [1], [])),
+ ?line [] = lists:umerge(Fun2, [], []),
+ ?line {'EXIT', _} = (catch lists:rumerge(func, [], [1])),
+ ?line {'EXIT', _} = (catch lists:rumerge(func, [1], [])),
+ ?line [] = lists:rumerge(Fun2, [], []),
+ ?line {'EXIT', _} = (catch lists:all(func, [])),
+ ?line true = lists:all(Pred, []),
+ ?line {'EXIT', _} = (catch lists:any(func, [])),
+ ?line false = lists:any(Pred, []),
+ ?line {'EXIT', _} = (catch lists:map(func, [])),
+ ?line [] = lists:map(Fun1, []),
+ ?line {'EXIT', _} = (catch lists:flatmap(func, [])),
+ ?line [] = lists:flatmap(Fun1, []),
+ ?line {'EXIT', _} = (catch lists:foldl(func, [], [])),
+ ?line [] = lists:foldl(Fold, [], []),
+ ?line {'EXIT', _} = (catch lists:foldr(func, [], [])),
+ ?line [] = lists:foldr(Fold, [], []),
+ ?line {'EXIT', _} = (catch lists:filter(func, [])),
+ ?line [] = lists:filter(Pred, []),
+ ?line {'EXIT', _} = (catch lists:partition(func, [])),
+ ?line {[],[]} = lists:partition(Pred, []),
+ ?line {'EXIT', _} = (catch lists:zf(func, [])),
+ ?line [] = lists:zf(Fun1, []),
+ ?line {'EXIT', _} = (catch lists:foreach(func, [])),
+ ?line ok = lists:foreach(Fun1, []),
+ ?line {'EXIT', _} = (catch lists:mapfoldl(func, [], [])),
+ ?line {[],[]} = lists:mapfoldl(MapFold, [], []),
+ ?line {'EXIT', _} = (catch lists:mapfoldr(func, [], [])),
+ ?line {[],[]} = lists:mapfoldr(MapFold, [], []),
+ ?line {'EXIT', _} = (catch lists:takewhile(func, [])),
+ ?line [] = lists:takewhile(Pred, []),
+ ?line {'EXIT', _} = (catch lists:dropwhile(func, [])),
+ ?line [] = lists:dropwhile(Pred, []),
+ ?line {'EXIT', _} = (catch lists:splitwith(func, [])),
+ ?line {[],[]} = lists:splitwith(Pred, []),
+
+ ok.
+
+otp_6023(doc) -> ["OTP-6023. lists:keyreplace/4, a typecheck."];
+otp_6023(suite) -> [];
+otp_6023(Config) when is_list(Config) ->
+ ?line {'EXIT', _} = (catch lists:keyreplace(a, 2, [{1,a}], b)),
+ ?line [{2,b}] = lists:keyreplace(a, 2, [{1,a}], {2,b}),
+
+ ok.
+
+otp_6606(doc) -> ["OTP-6606. sort and keysort bug"];
+otp_6606(suite) -> [];
+otp_6606(Config) when is_list(Config) ->
+ I = 1,
+ F = float(1),
+ L1 = [{F,I},{F,F},{I,I},{I,F}],
+ ?line L1 = lists:keysort(1, L1),
+ ?line L1 = lists:sort(L1),
+ L2 = [{I,I},{I,F},{F,I},{F,F}],
+ ?line L2 = lists:keysort(1, L2),
+ ?line L2 = lists:sort(L2),
+ ok.
+
+%% Test lists:suffix/2.
+suffix(Config) when is_list(Config) ->
+ ?line true = lists:suffix([], []),
+ ?line true = lists:suffix([], [a]),
+ ?line true = lists:suffix([], [a,b]),
+ ?line true = lists:suffix([], [a,b,c]),
+ ?line true = lists:suffix([a], lists:duplicate(200000, a)),
+ ?line true = lists:suffix(lists:seq(1, 1024),
+ lists:seq(2, 64000) ++ lists:seq(1, 1024)),
+ ?line true = lists:suffix(lists:duplicate(20000, a),
+ lists:duplicate(200000, a)),
+ ?line true = lists:suffix([2.0,3.0], [1.0,2.0,3.0]),
+
+ %% False cases.
+ ?line false = lists:suffix([a], []),
+ ?line false = lists:suffix([a,b,c], []),
+ ?line false = lists:suffix([a,b,c], [b,c]),
+ ?line false = lists:suffix([a,b,c], [a,b,c,a,b]),
+ ?line false = lists:suffix(lists:duplicate(199999, a)++[b],
+ lists:duplicate(200000, a)),
+ ?line false = lists:suffix([2.0,3.0], [1,2,3]),
+
+ %% Error cases.
+ ?line {'EXIT',_} = (catch lists:suffix({a,b,c}, [])),
+ ?line {'EXIT',_} = (catch lists:suffix([], {a,b})),
+ ?line {'EXIT',_} = (catch lists:suffix([a|b], [])),
+ ?line {'EXIT',_} = (catch lists:suffix([a,b|c], [a|b])),
+ ?line {'EXIT',_} = (catch lists:suffix([a|b], [a,b|c])),
+ ?line {'EXIT',_} = (catch lists:suffix([a|b], [a|b])),
+
+ ok.
+
+%% Test lists:subtract/2 and the '--' operator.
+subtract(Config) when is_list(Config) ->
+ ?line [] = sub([], []),
+ ?line [] = sub([], [a]),
+ ?line [] = sub([], lists:seq(1, 1024)),
+ ?line sub_non_matching([a], []),
+ ?line sub_non_matching([1,2], [make_ref()]),
+ ?line sub_non_matching(lists:seq(1, 1024), [make_ref(),make_ref()]),
+
+ %% Matching subtracts.
+ ?line [] = sub([a], [a]),
+ ?line [a] = sub([a,b], [b]),
+ ?line [a] = sub([a,b], [b,c]),
+ ?line [a] = sub([a,b,c], [b,c]),
+ ?line [a] = sub([a,b,c], [b,c]),
+ ?line [d,a,a] = sub([a,b,c,d,a,a], [a,b,c]),
+ ?line [d,x,a] = sub([a,b,c,d,a,x,a], [a,b,c,a]),
+ ?line [1,2,3,4,5,6,7,8,9,9999,10000,20,21,22] =
+ sub(lists:seq(1, 10000)++[20,21,22], lists:seq(10, 9998)),
+
+ %% Floats/integers.
+ ?line [42.0,42.0] = sub([42.0,42,42.0], [42,42,42]),
+ ?line [1,2,3,4,43.0] = sub([1,2,3,4,5,42.0,43.0], [42.0,5]),
+
+ %% Crashing subtracts.
+ ?line {'EXIT',_} = (catch sub([], [a|b])),
+ ?line {'EXIT',_} = (catch sub([a], [a|b])),
+ ?line {'EXIT',_} = (catch sub([a|b], [])),
+ ?line {'EXIT',_} = (catch sub([a|b], [])),
+ ?line {'EXIT',_} = (catch sub([a|b], [a])),
+
+ ok.
+
+sub_non_matching(A, B) ->
+ A = sub(A, B).
+
+sub(A, B) ->
+ Res = A -- B,
+ Res = lists:subtract(A, B).
+
diff --git a/lib/stdlib/test/log_mf_h_SUITE.erl b/lib/stdlib/test/log_mf_h_SUITE.erl
new file mode 100644
index 0000000000..640261f665
--- /dev/null
+++ b/lib/stdlib/test/log_mf_h_SUITE.erl
@@ -0,0 +1,92 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(log_mf_h_SUITE).
+
+-include("test_server.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-export([all/1, test/1]).
+
+all(suite) -> [test].
+
+
+%%-----------------------------------------------------------------
+%% This is actually very basic tests, maybe we could test some more
+%% in the future...
+%%-----------------------------------------------------------------
+
+test(Config) when is_list(Config) ->
+ ?line {ok, Pid} = gen_event:start_link(),
+ ?line PrivDir = ?config(priv_dir, Config),
+ Log1 = PrivDir ++ "/log1",
+ ?line ok = file:make_dir(Log1),
+ Args1 = log_mf_h:init(Log1, 500, 3),
+ gen_event:add_handler(Pid, log_mf_h, Args1),
+ generate(Pid, 200),
+ {ok, Files} = file:list_dir(Log1),
+ ?line true = lists:member("1", Files),
+ ?line true = lists:member("index", Files),
+ ?line false = lists:member("2", Files),
+ generate(Pid, 2500),
+ %% The documentation doesn't guarantee that syncing one request
+ %% causes all previous ones to be finished too, but that seems to
+ %% be the case. We need to be sure that the files exist when we
+ %% look for them with 'list_dir'.
+ gen_event:sync_notify(Pid, "end"),
+ {ok, Files2} = file:list_dir(Log1),
+ ?line true = lists:member("1", Files2),
+ ?line true = lists:member("2", Files2),
+ ?line true = lists:member("3", Files2),
+ ?line false = lists:member("4", Files2),
+ ?line true = lists:member("index", Files2),
+ ?line {ok, #file_info{size=Size1,type=regular}} = file:read_file_info(Log1 ++ "/1"),
+ ?line if Size1 > 500 -> test_server:fail({too_big, Size1});
+ true -> ok end,
+ ?line {ok, #file_info{size=Size2,type=regular}} = file:read_file_info(Log1 ++ "/2"),
+ ?line if Size2 > 500 -> test_server:fail({too_big, Size2});
+ true -> ok end,
+ ?line {ok, #file_info{size=Size3,type=regular}} = file:read_file_info(Log1 ++ "/3"),
+ ?line if Size3 > 500 -> test_server:fail({too_big, Size3});
+ true -> ok end,
+ gen_event:delete_handler(Pid, log_mf_h, []),
+ ?line {ok, Index} = read_index_file(Log1),
+ gen_event:add_handler(Pid, log_mf_h, Args1),
+ X = if Index == 3 -> 1; true -> Index + 1 end,
+ ?line {ok, X} = read_index_file(Log1).
+
+
+generate(Pid, Bytes) when Bytes > 32 ->
+ gen_event:notify(Pid, make_list(32, [])),
+ generate(Pid, Bytes - 32);
+generate(_, _) -> ok.
+
+make_list(0, Res) -> Res;
+make_list(N, Res) -> make_list(N-1, [67 | Res]).
+
+
+read_index_file(Dir) ->
+ case file:open(Dir ++ "/index", [read,raw]) of
+ {ok, Fd} ->
+ case catch file:read(Fd, 1) of
+ {ok, [Index]} -> {ok, Index};
+ _ -> error
+ end;
+ _ -> error
+ end.
+
diff --git a/lib/stdlib/test/ms_transform_SUITE.erl b/lib/stdlib/test/ms_transform_SUITE.erl
new file mode 100644
index 0000000000..cf0926b7fa
--- /dev/null
+++ b/lib/stdlib/test/ms_transform_SUITE.erl
@@ -0,0 +1,730 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ms_transform_SUITE).
+-author('[email protected]').
+
+-include("test_server.hrl").
+
+-export([all/1]).
+-export([basic_ets/1]).
+-export([basic_dbg/1]).
+-export([from_shell/1]).
+-export([records/1]).
+-export([record_index/1]).
+-export([multipass/1]).
+-export([top_match/1]).
+-export([old_guards/1]).
+-export([autoimported/1]).
+-export([semicolon/1]).
+-export([bitsyntax/1]).
+-export([record_defaults/1]).
+-export([andalso_orelse/1]).
+-export([float_1_function/1]).
+-export([action_function/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+init_per_testcase(_Func, Config) ->
+ Dog=test_server:timetrap(test_server:seconds(360)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Func, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog).
+
+all(suite) -> [from_shell,basic_ets,basic_dbg,records,record_index,multipass,
+ bitsyntax, record_defaults, andalso_orelse,
+ float_1_function, action_function,
+ top_match, old_guards, autoimported, semicolon].
+
+andalso_orelse(suite) ->
+ [];
+andalso_orelse(doc) ->
+ ["Tests that andalso and orelse are allowed in guards."];
+andalso_orelse(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line [{{'$1','$2'},
+ [{'and',{is_integer,'$1'},{'>',{'+','$1',5},'$2'}}],
+ [{'andalso','$1','$2'}]}] =
+ compile_and_run(<<"ets:fun2ms(fun({A,B}) "
+ " when is_integer(A) and (A+5 > B) -> "
+ " A andalso B "
+ " end)">>),
+ ?line [{{'$1','$2'},
+ [{'or',{is_atom,'$1'},{'>',{'+','$1',5},'$2'}}],
+ [{'orelse','$1','$2'}]}] =
+ compile_and_run(<<"ets:fun2ms(fun({A,B}) "
+ " when is_atom(A) or (A+5 > B) -> "
+ " A orelse B "
+ " end)">>),
+ ?line [{{'$1','$2'},
+ [{'andalso',{is_integer,'$1'},{'>',{'+','$1',5},'$2'}}],
+ ['$1']}] =
+ compile_and_run(
+ <<"ets:fun2ms(fun({A,B}) when is_integer(A) andalso (A+5 > B) ->"
+ " A "
+ " end)">>),
+ ?line [{{'$1','$2'},
+ [{'orelse',{is_atom,'$1'},{'>',{'+','$1',5},'$2'}}],
+ ['$1']}] =
+ compile_and_run(
+ <<"ets:fun2ms(fun({A,B}) when is_atom(A) orelse (A+5 > B) -> "
+ " A "
+ " end)">>),
+ ok.
+
+
+bitsyntax(suite) ->
+ [];
+bitsyntax(doc) ->
+ ["Tests that bitsyntax works and does not work where appropriate"];
+bitsyntax(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line [{'_',[],
+ [<<0,27,0,27>>]}] =
+ compile_and_run(<<"A = 27, "
+ "ets:fun2ms(fun(_) -> <<A:16,27:16>> end)">>),
+ ?line [{{<<15,47>>,
+ '$1',
+ '$2'},
+ [{'=:=','$1',
+ <<0,27>>},
+ {'=:=','$2',
+ <<27,28,19>>}],
+ [<<188,0,13>>]}] =
+ compile_and_run(<<"A = 27, "
+ "ets:fun2ms("
+ " fun({<<15,47>>,B,C}) "
+ " when B =:= <<A:16>>, C =:= <<27,28,19>> -> "
+ " <<A:4,12:4,13:16>> "
+ " end)">>),
+ ?line expect_failure(
+ <<>>,
+ <<"ets:fun2ms(fun({<<15,47>>,B,C}) "
+ " when B =:= <<16>>, C =:= <<27,28,19>> -> "
+ " <<B:4,12:4,13:16>> "
+ " end)">>),
+ ?line expect_failure(
+ <<>>,
+ <<"ets:fun2ms(fun({<<A:15,47>>,B,C}) "
+ " when B =:= <<16>>, C =:= <<27,28,19>> -> "
+ " <<B:4,12:4,13:16>> "
+ " end)">>),
+ ok.
+
+record_defaults(suite) ->
+ [];
+record_defaults(doc) ->
+ ["Tests that record defaults works"];
+record_defaults(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line [{{<<27>>,{a,5,'$1',hej,hej}},
+ [],
+ [{{a,hej,{'*','$1',2},flurp,flurp}}]}] =
+ compile_and_run(<<"-record(a,{a,b,c,d=foppa}).">>,
+ <<"ets:fun2ms(fun({<<27>>,#a{a=5, b=B,_=hej}}) -> "
+ "#a{a=hej,b=B*2,_=flurp} "
+ "end)">>),
+ ok.
+
+basic_ets(suite) ->
+ [];
+basic_ets(doc) ->
+ ["Tests basic ets:fun2ms"];
+basic_ets(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line [{{a,b},[],[true]}] = compile_and_run(
+ <<"ets:fun2ms(fun({a,b}) -> true end)">>),
+ ?line [{{'$1',foo},[{is_list,'$1'}],[{{{hd,'$1'},'$_'}}]},
+ {{'$1','$1'},[{is_tuple,'$1'}],[{{{element,1,'$1'},'$*'}}]}] =
+ compile_and_run(<<"ets:fun2ms(fun({X,foo}) when is_list(X) -> ",
+ "{hd(X),object()};",
+ "({X,X}) when is_tuple(X) ->",
+ "{element(1,X),bindings()}",
+ "end)">>),
+ ?line [{{'$1','$2'},[],[{{'$2','$1'}}]}] =
+ compile_and_run(<<"ets:fun2ms(fun({A,B}) -> {B,A} end)">>),
+ ?line [{{'$1','$2'},[],[['$2','$1']]}] =
+ compile_and_run(<<"ets:fun2ms(fun({A,B}) -> [B,A] end)">>),
+ ok.
+
+basic_dbg(suite) ->
+ [];
+basic_dbg(doc) ->
+ ["Tests basic ets:fun2ms"];
+basic_dbg(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line [{[a,b],[],[{message,banan},{return_trace}]}] =
+ compile_and_run(<<"dbg:fun2ms(fun([a,b]) -> message(banan), ",
+ "return_trace() end)">>),
+ ?line [{['$1','$2'],[],[{{'$2','$1'}}]}] =
+ compile_and_run(<<"dbg:fun2ms(fun([A,B]) -> {B,A} end)">>),
+ ?line [{['$1','$2'],[],[['$2','$1']]}] =
+ compile_and_run(<<"dbg:fun2ms(fun([A,B]) -> [B,A] end)">>),
+ ?line [{['$1','$2'],[],['$*']}] =
+ compile_and_run(<<"dbg:fun2ms(fun([A,B]) -> bindings() end)">>),
+ ?line [{['$1','$2'],[],['$_']}] =
+ compile_and_run(<<"dbg:fun2ms(fun([A,B]) -> object() end)">>),
+ ok.
+
+from_shell(suite) ->
+ [];
+from_shell(doc) ->
+ ["Test calling of ets/dbg:fun2ms from the shell"];
+from_shell(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line Fun = do_eval("fun({a,b}) -> true end"),
+ ?line [{{a,b},[],[true]}] = apply(ets,fun2ms,[Fun]),
+ ?line [{{a,b},[],[true]}] = do_eval("ets:fun2ms(fun({a,b}) -> true end)"),
+ ?line Fun2 = do_eval("fun([a,b]) -> message(banan), return_trace() end"),
+ ?line [{[a,b],[],[{message,banan},{return_trace}]}]
+ = apply(dbg,fun2ms,[Fun2]),
+ ?line [{[a,b],[],[{message,banan},{return_trace}]}] =
+ do_eval(
+ "dbg:fun2ms(fun([a,b]) -> message(banan), return_trace() end)"),
+ ok.
+
+records(suite) ->
+ [];
+records(doc) ->
+ ["Tests expansion of records in fun2ms"];
+records(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line RD = <<"-record(t, {"
+ "t1 = [],"
+ "t2 = foo,"
+ "t3,"
+ "t4"
+ "}).">>,
+ ?line [{{t,'$1','$2',foo,'_'},[{is_list,'$1'}],[{{{hd,'$1'},'$_'}}]},
+ {{t,'_','_','_','_'},[{'==',{element,2,'$_'},nisse}],[{{'$*'}}]}] =
+ compile_and_run(RD,<<
+ "ets:fun2ms(fun(#t{t1 = X, t2 = Y, t3 = foo}) when is_list(X) ->
+ {hd(X),object()};
+ (#t{}) when (object())#t.t1 == nisse ->
+ {bindings()}
+ end)">>),
+ ?line [{{t,'$1','$2','_',foo},
+ [{'==',{element,4,'$_'},7},{is_list,'$1'}],
+ [{{{hd,'$1'},'$_'}}]},
+ {'$1',[{is_record,'$1',t,5}],
+ [{{{element,2,'$1'},
+ {{t,'$1',foo,undefined,undefined}},
+ {{t,{element,2,'$1'},{element,3,'$1'},{element,4,'$1'},boooo}}}}]}] =
+ compile_and_run(RD,<<
+ "ets:fun2ms(fun(#t{t1 = X, t2 = Y, t4 = foo}) when
+ (object())#t.t3==7,is_list(X) ->
+ {hd(X),object()};
+ (A) when is_record(A,t) ->
+ {A#t.t1
+ ,#t{t1=A}
+ ,A#t{t4=boooo}
+ }
+ end)"
+ >>),
+ ?line [{[{t,'$1','$2',foo,'_'}],[{is_list,'$1'}],[{{{hd,'$1'},'$_'}}]},
+ {[{t,'_','_','_','_'}],[{'==',{element,2,{hd,'$_'}},nisse}],[{{'$*'}}]}]=
+ compile_and_run(RD,<<
+ "dbg:fun2ms(fun([#t{t1 = X, t2 = Y, t3 = foo}]) when is_list(X) ->
+ {hd(X),object()};
+ ([#t{}]) when (hd(object()))#t.t1 == nisse ->
+ {bindings()}
+ end)"
+ >>),
+ ok.
+
+
+record_index(suite) ->
+ [];
+record_index(doc) ->
+ ["Tests expansion of records in fun2ms, part 2"];
+record_index(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line RD = <<"-record(a,{a,b}).">>,
+ ?line [{{2},[],[true]}] = compile_and_run(RD,
+ <<"ets:fun2ms(fun({#a.a}) -> true end)">>),
+ ?line [{{2},[],[2]}] = compile_and_run(RD,
+ <<"ets:fun2ms(fun({#a.a}) -> #a.a end)">>),
+ ?line [{{2,'$1'},[{'>','$1',2}],[2]}] = compile_and_run(RD,
+ <<"ets:fun2ms(fun({#a.a,A}) when A > #a.a -> #a.a end)">>),
+ ok.
+
+top_match(suite) ->
+ [];
+top_match(doc) ->
+ ["Tests matching on top level in head to give alias for object()"];
+top_match(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line RD = <<"-record(a,{a,b}).">>,
+ ?line [{{a,3,'_'},[],['$_']}] =
+ compile_and_run(RD,
+ <<"ets:fun2ms(fun(A = #a{a=3}) -> A end)">>),
+ ?line [{{a,3,'_'},[],['$_']}] =
+ compile_and_run(RD,
+ <<"ets:fun2ms(fun(#a{a=3} = A) -> A end)">>),
+ ?line [{[a,b],[],['$_']}] =
+ compile_and_run(RD,
+ <<"dbg:fun2ms(fun(A = [a,b]) -> A end)">>),
+ ?line [{[a,b],[],['$_']}] =
+ compile_and_run(RD,
+ <<"dbg:fun2ms(fun([a,b] = A) -> A end)">>),
+ ?line expect_failure(RD,
+ <<"ets:fun2ms(fun({a,A = {_,b}}) -> A end)">>),
+ ?line expect_failure(RD,
+ <<"dbg:fun2ms(fun([a,A = {_,b}]) -> A end)">>),
+ ?line expect_failure(RD,
+ <<"ets:fun2ms(fun(A#a{a = 2}) -> A end)">>),
+ ok.
+
+multipass(suite) ->
+ [];
+multipass(doc) ->
+ ["Tests that multi-defined fields in records give errors."];
+multipass(Config) when list(Config) ->
+ ?line setup(Config),
+ ?line RD = <<"-record(a,{a,b}).">>,
+ ?line expect_failure(RD,<<"ets:fun2ms(fun(A) -> #a{a=2,a=3} end)">>),
+ ?line expect_failure(RD,<<"ets:fun2ms(fun(A) -> A#a{a=2,a=3} end)">>),
+ ?line expect_failure(RD,<<"ets:fun2ms(fun(A) when A =:= #a{a=2,a=3} ->",
+ " true end)">>),
+ ?line expect_failure(RD,<<"ets:fun2ms(fun({A,B})when A =:= B#a{a=2,a=3}->",
+ "true end)">>),
+ ?line expect_failure(RD,<<"ets:fun2ms(fun(#a{a=3,a=3}) -> true end)">>),
+ ?line compile_and_run(RD,<<"ets:fun2ms(fun(A) -> #a{a=2,b=3} end)">>),
+ ?line compile_and_run(RD,<<"ets:fun2ms(fun(A) -> A#a{a=2,b=3} end)">>),
+ ?line compile_and_run(RD,<<"ets:fun2ms(fun(A) when A =:= #a{a=2,b=3} ->",
+ " true end)">>),
+ ?line compile_and_run(RD,<<"ets:fun2ms(fun({A,B})when A=:= B#a{a=2,b=3}->",
+ "true end)">>),
+ ?line compile_and_run(RD,<<"ets:fun2ms(fun(#a{a=3,b=3}) -> true end)">>),
+ ok.
+
+
+old_guards(suite) ->
+ [];
+old_guards(doc) ->
+ ["Tests that old type tests in guards are translated"];
+old_guards(Config) when list(Config) ->
+ ?line setup(Config),
+ Tests = [
+ {atom,is_atom},
+ {constant,is_constant},
+ {float,is_float},
+ {integer,is_integer},
+ {list,is_list},
+ {number,is_number},
+ {pid,is_pid},
+ {port,is_port},
+ {reference,is_reference},
+ {tuple,is_tuple},
+ {binary,is_binary},
+ {function,is_function}],
+ ?line lists:foreach(
+ fun({Old,New}) ->
+ Bin = list_to_binary([<<"ets:fun2ms(fun(X) when ">>,
+ atom_to_list(Old),
+ <<"(X) -> true end)">>]),
+ case compile_and_run(Bin) of
+ [{'$1',[{New,'$1'}],[true]}] ->
+ ok;
+ _ ->
+ exit({bad_result_for, binary_to_list(Bin)})
+ end
+ end,
+ Tests),
+ ?line RD = <<"-record(a,{a,b}).">>,
+ ?line [{'$1',[{is_record,'$1',a,3}],[true]}] =
+ compile_and_run(RD,
+ <<"ets:fun2ms(fun(X) when record(X,a) -> true end)">>),
+ ?line expect_failure
+ (RD,
+ <<"ets:fun2ms(fun(X) when integer(X) and constant(X) -> "
+ "true end)">>),
+ ?line [{'$1',[{is_integer,'$1'},
+ {is_float,'$1'},
+ {is_atom,'$1'},
+ {is_constant,'$1'},
+ {is_list,'$1'},
+ {is_number,'$1'},
+ {is_pid,'$1'},
+ {is_port,'$1'},
+ {is_reference,'$1'},
+ {is_tuple,'$1'},
+ {is_binary,'$1'},
+ {is_record,'$1',a,3}],
+ [true]}] =
+ compile_and_run(RD, <<
+ "ets:fun2ms(fun(X) when integer(X),"
+ "float(X), atom(X), constant(X),"
+ "list(X), number(X), pid(X),"
+ "port(X), reference(X), tuple(X),"
+ "binary(X), record(X,a) -> true end)"
+ >>),
+ ok.
+
+autoimported(suite) ->
+ [];
+autoimported(doc) ->
+ ["Tests use of autoimported bif's used like erlang:'+'(A,B) in guards"
+ " and body."];
+autoimported(Config) when list(Config) ->
+ ?line setup(Config),
+ Allowed = [
+ {abs,1},
+ {element,2},
+ {hd,1},
+ {length,1},
+ {node,0},
+ {node,1},
+ {round,1},
+ {size,1},
+ {tl,1},
+ {trunc,1},
+ {self,0},
+ %{float,1}, see float_1_function/1
+ {is_atom,1},
+ {is_constant,1},
+ {is_float,1},
+ {is_integer,1},
+ {is_list,1},
+ {is_number,1},
+ {is_pid,1},
+ {is_port,1},
+ {is_reference,1},
+ {is_tuple,1},
+ {is_binary,1},
+ {is_function,1},
+ {is_record,2,magic},
+ {'and',2,infix},
+ {'or',2,infix},
+ {'xor',2,infix},
+ {'not',1},
+ %{'andalso',2,infix},
+ %{'orelse',2,infix},
+ {'+',1},
+ {'+',2,infix},
+ {'-',1},
+ {'-',2,infix},
+ {'*',2,infix},
+ {'/',2,infix},
+ {'div',2,infix},
+ {'rem',2,infix},
+ {'band',2,infix},
+ {'bor',2,infix},
+ {'bxor',2,infix},
+ {'bnot',1},
+ {'bsl',2,infix},
+ {'bsr',2,infix},
+ {'>',2,infix},
+ {'>=',2,infix},
+ {'<',2,infix},
+ {'=<',2,infix},
+ {'==',2,infix},
+ {'=:=',2,infix},
+ {'/=',2,infix},
+ {'=/=',2,infix}],
+ ?line RD = <<"-record(a,{a,b}).">>,
+ ?line lists:foreach(
+ fun({A,0}) ->
+ L = atom_to_list(A),
+ Bin1 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun(X) when ">>,
+ L,<<"() -> ">>,
+ L,<<"() end)">>
+ ]),
+ Bin2 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun(X) when erlang:'">>,
+ L,<<"'() -> erlang:'">>,
+ L,<<"'() end)">>
+ ]),
+ Res1 = compile_and_run(Bin1),
+ Res2 = compile_and_run(Bin2),
+ case Res1 =:= Res2 of
+ true ->
+ ok;
+ false ->
+ exit({not_equal,{Res1,Res2,A}})
+ end;
+ ({A,1}) ->
+ L = atom_to_list(A),
+ Bin1 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun(X) when ">>,
+ L,<<"(X) -> ">>,
+ L,<<"(X) end)">>
+ ]),
+ Bin2 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun(X) when erlang:'">>,
+ L,<<"'(X) -> erlang:'">>,
+ L,<<"'(X) end)">>
+ ]),
+ Res1 = compile_and_run(Bin1),
+ Res2 = compile_and_run(Bin2),
+ case Res1 =:= Res2 of
+ true ->
+ ok;
+ false ->
+ exit({not_equal,{Res1,Res2,A}})
+ end;
+ ({A,2}) ->
+ L = atom_to_list(A),
+ Bin1 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun({X,Y}) when ">>,
+ L,<<"(X,Y) -> ">>,
+ L,<<"(X,Y) end)">>
+ ]),
+ Bin2 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun({X,Y}) when erlang:'">>,
+ L,<<"'(X,Y) -> erlang:'">>,
+ L,<<"'(X,Y) end)">>
+ ]),
+ Res1 = compile_and_run(Bin1),
+ Res2 = compile_and_run(Bin2),
+ case Res1 =:= Res2 of
+ true ->
+ ok;
+ false ->
+ exit({not_equal,{Res1,Res2,A}})
+ end;
+ ({A,2,infix}) ->
+ L = atom_to_list(A),
+ Bin1 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun({X,Y}) when X ">>,
+ L,<<" Y -> X ">>,
+ L,<<" Y end)">>
+ ]),
+ Bin2 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun({X,Y}) when erlang:'">>,
+ L,<<"'(X,Y) -> erlang:'">>,
+ L,<<"'(X,Y) end)">>
+ ]),
+ Res1 = compile_and_run(Bin1),
+ Res2 = compile_and_run(Bin2),
+ case Res1 =:= Res2 of
+ true ->
+ ok;
+ false ->
+ exit({not_equal,{Res1,Res2,A}})
+ end;
+ ({A,2,magic}) -> %is_record
+ L = atom_to_list(A),
+ Bin1 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun(X) when ">>,
+ L,<<"(X,a) -> ">>,
+ L,<<"(X,a) end)">>
+ ]),
+ Bin2 = list_to_binary(
+ [
+ <<"ets:fun2ms(fun(X) when erlang:'">>,
+ L,<<"'(X,a) -> erlang:'">>,
+ L,<<"'(X,a) end)">>
+ ]),
+ Res1 = compile_and_run(RD,Bin1),
+ Res2 = compile_and_run(RD,Bin2),
+ case Res1 =:= Res2 of
+ true ->
+ ok;
+ false ->
+ exit({not_equal,{Res1,Res2,A}})
+ end
+ end,
+ Allowed),
+ ok.
+
+semicolon(suite) ->
+ [];
+semicolon(doc) ->
+ ["Tests semicolon in guards of match_specs."];
+semicolon(Config) when is_list(Config) ->
+ ?line setup(Config),
+ ?line Res01 = compile_and_run
+ (<<"ets:fun2ms(fun(X) when is_integer(X); "
+ "is_float(X) -> true end)">>),
+ ?line Res02 = compile_and_run
+ (<<"ets:fun2ms(fun(X) when is_integer(X) -> true; "
+ "(X) when is_float(X) -> true end)">>),
+ ?line Res01 = Res02,
+ ?line Res11 = compile_and_run
+ (<<"ets:fun2ms(fun(X) when is_integer(X); "
+ "is_float(X); atom(X) -> true end)">>),
+ ?line Res12 = compile_and_run
+ (<<"ets:fun2ms(fun(X) when is_integer(X) -> true; "
+ "(X) when is_float(X) -> true; "
+ "(X) when is_atom(X) -> true end)">>),
+ ?line Res11 = Res12,
+ ok.
+
+
+float_1_function(suite) ->
+ [];
+float_1_function(doc) ->
+ ["OTP-5297. The function float/1."];
+float_1_function(Config) when list(Config) ->
+ ?line setup(Config),
+ RunMS = fun(L, MS) ->
+ ets:match_spec_run(L, ets:match_spec_compile(MS))
+ end,
+ ?line MS1 = compile_and_run
+ (<<"ets:fun2ms(fun(X) -> float(X) end)">>),
+ ?line [F1] = RunMS([3], MS1),
+ ?line true = is_float(F1) and (F1 == 3),
+
+ ?line MS1b = compile_and_run
+ (<<"dbg:fun2ms(fun(X) -> float(X) end)">>),
+ ?line [F2] = RunMS([3], MS1b),
+ ?line true = is_float(F2) and (F2 == 3),
+
+ ?line MS2 = compile_and_run
+ (<<"ets:fun2ms(fun(X) when is_pid(X) or float(X) -> true end)">>),
+ ?line [] = RunMS([3.0], MS2),
+
+ ?line MS3 = compile_and_run
+ (<<"dbg:fun2ms(fun(X) when is_pid(X); float(X) -> true end)">>),
+ ?line [true] = RunMS([3.0], MS3),
+
+ ?line MS4 = compile_and_run
+ (<<"ets:fun2ms(fun(X) when erlang:float(X) > 1 -> big;"
+ " (_) -> small end)">>),
+ ?line [small,big] = RunMS([1.0, 3.0], MS4),
+
+ ?line MS5 = compile_and_run
+ (<<"ets:fun2ms(fun(X) when float(X) > 1 -> big;"
+ " (_) -> small end)">>),
+ ?line [small,big] = RunMS([1.0, 3.0], MS5),
+
+ %% This is the test from autoimported/1.
+ ?line [{'$1',[{is_float,'$1'}],[{float,'$1'}]}] =
+ compile_and_run
+ (<<"ets:fun2ms(fun(X) when float(X) -> float(X) end)">>),
+ ?line [{'$1',[{float,'$1'}],[{float,'$1'}]}] =
+ compile_and_run
+ (<<"ets:fun2ms(fun(X) when erlang:'float'(X) -> "
+ "erlang:'float'(X) end)">>),
+ ok.
+
+
+action_function(suite) ->
+ [];
+action_function(doc) ->
+ ["Test all 'action functions'."];
+action_function(Config) when is_list(Config) ->
+ ?line setup(Config),
+ ?line [{['$1','$2'],[],
+ [{set_seq_token,label,0},
+ {get_seq_token},
+ {message,'$1'},
+ {return_trace},
+ {exception_trace}]}] =
+ compile_and_run
+ (<<"dbg:fun2ms(fun([X,Y]) -> "
+ "set_seq_token(label, 0), "
+ "get_seq_token(), "
+ "message(X), "
+ "return_trace(), "
+ "exception_trace() end)">>),
+ ?line [{['$1','$2'],[],
+ [{process_dump},
+ {enable_trace,send},
+ {enable_trace,'$2',send},
+ {disable_trace,procs},
+ {disable_trace,'$2',procs}]}] =
+ compile_and_run
+ (<<"dbg:fun2ms(fun([X,Y]) -> "
+ "process_dump(), "
+ "enable_trace(send), "
+ "enable_trace(Y, send), "
+ "disable_trace(procs), "
+ "disable_trace(Y, procs) end)">>),
+ ?line [{['$1','$2'],
+ [],
+ [{display,'$1'},
+ {caller},
+ {set_tcw,{const,16}},
+ {silent,true},
+ {trace,[send],[procs]},
+ {trace,'$2',[procs],[send]}]}] =
+ compile_and_run
+ (<<"A = 16, dbg:fun2ms(fun([X,Y]) -> "
+ "display(X), "
+ "caller(), "
+ "set_tcw(A), "
+ "silent(true), "
+ "trace([send], [procs]), "
+ "trace(Y, [procs], [send]) end)">>),
+ ok.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Helpers
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+setup(Config) ->
+ put(mts_config,Config),
+ put(mts_tf_counter,0).
+
+temp_name() ->
+ Conf = get(mts_config),
+ C = get(mts_tf_counter),
+ put(mts_tf_counter,C+1),
+ filename:join([?config(priv_dir,Conf),
+ "tempfile"++integer_to_list(C)++".tmp"]).
+
+
+expect_failure(Recs,Code) ->
+ case (catch compile_and_run(Recs,Code)) of
+ {'EXIT',_Foo} ->
+ %erlang:display(_Foo),
+ ok;
+ Other ->
+ exit({expected,failure,got,Other})
+ end.
+
+compile_and_run(Expr) ->
+ compile_and_run(<<>>,Expr).
+compile_and_run(Records,Expr) ->
+ Prog = <<
+ "-module(tmp).\n",
+ "-include_lib(\"stdlib/include/ms_transform.hrl\").\n",
+ "-export([tmp/0]).\n",
+ Records/binary,"\n",
+ "tmp() ->\n",
+ Expr/binary,".\n">>,
+ FN=temp_name(),
+ file:write_file(FN,Prog),
+ {ok,Forms} = epp:parse_file(FN,"",""),
+ {ok,tmp,Bin} = compile:forms(Forms),
+ code:load_binary(tmp,FN,Bin),
+ tmp:tmp().
+
+do_eval(String) ->
+ {done,{ok,T,_},[]} = erl_scan:tokens(
+ [],
+ String++".\n",1),
+ {ok,Tree} = erl_parse:parse_exprs(T),
+ {value,Res,[]} = erl_eval:exprs(Tree,[]),
+ Res.
diff --git a/lib/stdlib/test/naughty_child.erl b/lib/stdlib/test/naughty_child.erl
new file mode 100644
index 0000000000..b56130929c
--- /dev/null
+++ b/lib/stdlib/test/naughty_child.erl
@@ -0,0 +1,101 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% DESCRIPTION: Implements a naughty child process that does unlink
+%%% from its supervisor. Used by the supervisor test suite.
+
+-module(naughty_child).
+
+-behaviour(gen_server).
+%%--------------------------------------------------------------------
+%% Include files
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% External exports
+-export([start_link/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {}).
+
+%%====================================================================
+%% External functions
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link/0
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link(Pid) ->
+ gen_server:start_link({local, naughty_foo}, ?MODULE, [Pid], []).
+
+%%====================================================================
+%% Server functions
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init/1
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Pid]) ->
+ unlink(Pid),
+ {ok, #state{}}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_call/3
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast/2
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info/2
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate/2
+%% Description: Shutdown the server
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
diff --git a/lib/stdlib/test/proc_lib_SUITE.erl b/lib/stdlib/test/proc_lib_SUITE.erl
new file mode 100644
index 0000000000..2fd7725335
--- /dev/null
+++ b/lib/stdlib/test/proc_lib_SUITE.erl
@@ -0,0 +1,344 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(proc_lib_SUITE).
+
+%%
+%% Define to run outside of test server
+%%
+%%-define(STANDALONE,1).
+
+-export([all/1, crash/1, sync_start/1, sync_start_nolink/1, sync_start_link/1,
+ spawn_opt/1, sp1/0, sp2/0, sp3/1, sp4/2, sp5/1,
+ hibernate/1]).
+-export([tickets/1, otp_6345/1]).
+
+-export([hib_loop/1, awaken/1]).
+
+-export([init/1,
+ handle_event/2, handle_call/2, handle_info/2,
+ terminate/2]).
+
+-export([otp_6345_init/1]).
+
+
+-ifdef(STANDALONE).
+-define(line, noop, ).
+-else.
+-include("test_server.hrl").
+-endif.
+
+all(suite) -> [crash, sync_start, spawn_opt, hibernate, tickets].
+
+tickets(suite) -> [otp_6345].
+
+%%-----------------------------------------------------------------
+%% We don't have to test that spwn and spawn_link actually spawns
+%% new processes - if they don't we can't run this suite!
+%% But we want to test that start and start_link really is
+%% synchronous, and we want to test that the crash report is ok.
+%%-----------------------------------------------------------------
+crash(Config) when is_list(Config) ->
+ error_logger:add_report_handler(?MODULE, self()),
+
+ Pid = proc_lib:spawn(?MODULE, sp1, []),
+ Pid ! die,
+ ?line Report = receive
+ {crash_report, Pid, Report0} -> Report0
+ after 2000 -> test_server:fail(no_crash_report)
+ end,
+ ?line proc_lib:format(Report),
+ ?line [PidRep, []] = Report,
+ ?line {value, {initial_call,{?MODULE,sp1,[]}}} =
+ lists:keysearch(initial_call, 1, PidRep),
+ Self = self(),
+ ?line {value, {ancestors,[Self]}} =
+ lists:keysearch(ancestors, 1, PidRep),
+ ?line {value, {error_info,{exit,die,_StackTrace1}}} =
+ lists:keysearch(error_info, 1, PidRep),
+
+ F = fun sp1/0,
+ Pid1 = proc_lib:spawn(node(), F),
+ Pid1 ! die,
+ ?line [PidRep1, []] = receive
+ {crash_report, Pid1, Report1} -> Report1
+ after 2000 -> test_server:fail(no_crash_report)
+ end,
+ ?line {value, {initial_call,{Fmod,Fname,[]}}} =
+ lists:keysearch(initial_call, 1, PidRep1),
+ ?line {module,Fmod} = erlang:fun_info(F, module),
+ ?line {name,Fname} = erlang:fun_info(F, name),
+ ?line {value, {ancestors,[Self]}} =
+ lists:keysearch(ancestors, 1, PidRep1),
+ ?line {value, {error_info,{exit,die,_StackTrace2}}} =
+ lists:keysearch(error_info, 1, PidRep1),
+
+ Pid2 = proc_lib:spawn(?MODULE, sp2, []),
+ test_server:sleep(100),
+ ?line {?MODULE,sp2,[]} = proc_lib:initial_call(Pid2),
+ ?line {?MODULE,sp2,0} = proc_lib:translate_initial_call(Pid2),
+ Pid2 ! die,
+ ?line [Pid2Rep, [{neighbour, LinkRep}]] =
+ receive
+ {crash_report, Pid2, Report2} -> Report2
+ after 2000 -> test_server:fail(no_crash_report)
+ end,
+ ?line {value, {initial_call,{?MODULE,sp2,[]}}} =
+ lists:keysearch(initial_call, 1, Pid2Rep),
+ ?line {value, {ancestors,[Self]}} =
+ lists:keysearch(ancestors, 1, Pid2Rep),
+ ?line {value, {error_info,{exit,die,_StackTrace3}}} =
+ lists:keysearch(error_info, 1, Pid2Rep),
+ ?line {value, {initial_call,{?MODULE,sp1,[]}}} =
+ lists:keysearch(initial_call, 1, LinkRep),
+
+ %% Make sure that we don't get a crash report if a process
+ %% terminates with reason 'shutdown' or reason {shutdown,Reason}.
+ ?line process_flag(trap_exit, true),
+ ?line Pid3 = proc_lib:spawn_link(erlang, apply,
+ [fun() -> exit(shutdown) end,[]]),
+
+ ?line Pid4 = proc_lib:spawn_link(erlang, apply,
+ [fun() -> exit({shutdown,{a,b,c}}) end,[]]),
+
+ ?line receive {'EXIT',Pid3,shutdown} -> ok end,
+ ?line receive {'EXIT',Pid4,{shutdown,{a,b,c}}} -> ok end,
+ ?line process_flag(trap_exit, false),
+
+ receive
+ Any ->
+ ?line ?t:fail({unexpected_message,Any})
+ after 2000 ->
+ ok
+ end.
+
+sync_start(suite) -> [sync_start_nolink, sync_start_link].
+
+sync_start_nolink(Config) when is_list(Config) ->
+ _Pid = spawn_link(?MODULE, sp5, [self()]),
+ receive
+ {sync_started, F} ->
+ exit(F, kill),
+ test_server:fail(async_start)
+ after 1000 -> ok
+ end,
+ receive
+ {Pid2, init} ->
+ Pid2 ! go_on
+ end,
+ receive
+ {sync_started, _} -> ok
+ after 1000 ->
+ exit(Pid2, kill),
+ test_server:fail(no_sync_start)
+ end,
+ ok.
+
+sync_start_link(Config) when is_list(Config) ->
+ _Pid = spawn_link(?MODULE, sp3, [self()]),
+ receive
+ {sync_started, _} -> test_server:fail(async_start)
+ after 1000 -> ok
+ end,
+ receive
+ {Pid2, init} ->
+ Pid2 ! go_on
+ end,
+ receive
+ {sync_started, _} -> ok
+ after 1000 -> test_server:fail(no_sync_start)
+ end,
+ ok.
+
+spawn_opt(Config) when is_list(Config) ->
+ F = fun sp1/0,
+ {name,Fname} = erlang:fun_info(F, name),
+ FunMFArgs = {?MODULE,Fname,[]},
+ FunMFArity = {?MODULE,Fname,0},
+ ?line Pid1 = proc_lib:spawn_opt(node(), F, [{priority,low}]),
+ ?line Pid = proc_lib:spawn_opt(F, [{priority,low}]),
+ ?line test_server:sleep(100),
+ ?line FunMFArgs = proc_lib:initial_call(Pid),
+ ?line FunMFArity = proc_lib:translate_initial_call(Pid),
+ ?line Pid ! die,
+ ?line FunMFArgs = proc_lib:initial_call(Pid1),
+ ?line FunMFArity = proc_lib:translate_initial_call(Pid1),
+ ?line Pid1 ! die,
+ ok.
+
+
+sp1() ->
+ receive
+ die -> exit(die);
+ _ -> sp1()
+ end.
+
+sp2() ->
+ _Pid = proc_lib:spawn_link(?MODULE, sp1, []),
+ receive
+ die -> exit(die);
+ _ -> sp1()
+ end.
+
+sp3(Tester) ->
+ Pid = proc_lib:start_link(?MODULE, sp4, [self(), Tester]),
+ Tester ! {sync_started, Pid}.
+
+sp5(Tester) ->
+ Pid = proc_lib:start(?MODULE, sp4, [self(), Tester]),
+ Tester ! {sync_started, Pid}.
+
+sp4(Parent, Tester) ->
+ Tester ! {self(), init},
+ receive
+ go_on -> ok
+ end,
+ proc_lib:init_ack(Parent, self()).
+
+hibernate(Config) when is_list(Config) ->
+ Ref = make_ref(),
+ Self = self(),
+ LoopData = {Ref,Self},
+ ?line Pid = proc_lib:spawn_link(?MODULE, hib_loop, [LoopData]),
+
+ %% Just check that the child process can process and answer messages.
+ ?line Pid ! {Self,loop_data},
+ receive
+ {loop_data,LoopData} -> ok;
+ Unexpected0 ->
+ ?line io:format("Unexpected: ~p\n", [Unexpected0]),
+ ?line ?t:fail()
+ after 1000 ->
+ ?line io:format("Timeout"),
+ ?line ?t:fail()
+ end,
+
+ %% Hibernate the process.
+ ?line Pid ! hibernate,
+ erlang:yield(),
+ io:format("~p\n", [process_info(Pid, heap_size)]),
+
+
+ %% Send a message to the process...
+
+ ?line Pid ! {Self,loop_data},
+
+ %% ... expect first a wake up message from the process...
+ receive
+ {awaken,LoopData} -> ok;
+ Unexpected1 ->
+ ?line io:format("Unexpected: ~p\n", [Unexpected1]),
+ ?line ?t:fail()
+ after 1000 ->
+ ?line io:format("Timeout"),
+ ?line ?t:fail()
+ end,
+
+ %% ... followed by the answer to the actual request.
+ receive
+ {loop_data,LoopData} -> ok;
+ Unexpected2 ->
+ ?line io:format("Unexpected: ~p\n", [Unexpected2]),
+ ?line ?t:fail()
+ after 1000 ->
+ ?line io:format("Timeout"),
+ ?line ?t:fail()
+ end,
+
+ %% Test that errors are handled correctly after wake up from hibernation...
+
+ ?line process_flag(trap_exit, true),
+ ?line error_logger:add_report_handler(?MODULE, self()),
+ ?line Pid ! crash,
+
+ %% We should receive two messages. Especially in the SMP emulator,
+ %% we can't be sure of the message order, so sort the messages before
+ %% matching.
+
+ Messages = lists:sort(hib_receive_messages(2)),
+ io:format("~p", [Messages]),
+ ?line [{'EXIT',Pid,i_crashed},{crash_report,Pid,[Report,[]]}] = Messages,
+
+ %% Check that the initial_call has the expected format.
+ ?line {value,{initial_call,{?MODULE,hib_loop,[_]}}} =
+ lists:keysearch(initial_call, 1, Report),
+
+ ok.
+
+hib_loop(LoopData) ->
+ receive
+ hibernate ->
+ proc_lib:hibernate(?MODULE, awaken, [LoopData]);
+ {Pid,loop_data} ->
+ Pid ! {loop_data,LoopData};
+ crash ->
+ exit(i_crashed)
+ end,
+ hib_loop(LoopData).
+
+awaken({_,Parent}=LoopData) ->
+ Parent ! {awaken,LoopData},
+ hib_loop(LoopData).
+
+hib_receive_messages(0) -> [];
+hib_receive_messages(N) ->
+ receive
+ Any -> [Any|hib_receive_messages(N-1)]
+ end.
+
+otp_6345(suite) ->
+ [];
+otp_6345(doc) ->
+ ["'monitor' spawn_opt option"];
+otp_6345(Config) when is_list(Config) ->
+ Opts = [link,monitor],
+ {'EXIT', {badarg,[{proc_lib,check_for_monitor,_}|_Stack]}} =
+ (catch proc_lib:start(?MODULE, otp_6345_init, [self()],
+ 1000, Opts)),
+ ok.
+
+otp_6345_init(Parent) ->
+ proc_lib:init_ack(Parent, {ok, self()}),
+ otp_6345_loop().
+
+otp_6345_loop() ->
+ receive
+ _Msg ->
+ otp_6345_loop()
+ end.
+
+%%-----------------------------------------------------------------
+%% The error_logger handler used.
+%%-----------------------------------------------------------------
+init(Tester) ->
+ {ok, Tester}.
+
+handle_event({error_report, _GL, {Pid, crash_report, Report}}, Tester) ->
+ io:format("~s\n", [proc_lib:format(Report)]),
+ Tester ! {crash_report, Pid, Report},
+ {ok, Tester};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(_, State) ->
+ {ok, State}.
+
+handle_call(_Query, State) -> {ok, {error, bad_query}, State}.
+
+terminate(_Reason, State) ->
+ State.
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
new file mode 100644
index 0000000000..ff11ebc6bf
--- /dev/null
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -0,0 +1,8179 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------
+%%% Purpose:Test Suite for the 'qlc' module.
+%%%-----------------------------------------------------------------
+-module(qlc_SUITE).
+
+-define(QLC, qlc).
+-define(QLCs, "qlc").
+
+%-define(debug, true).
+
+%% There are often many tests per testcase. Most tests are copied to a
+%% module, a file. The file is compiled and the test run. Should the
+%% test fail, the module file is not removed from ?privdir, but is
+%% kept for inspection. The name of the file is
+%% ?privdir/qlc_test_CASE.erl.
+-define(TESTMODULE, qlc_test).
+-define(TESTCASE, testcase_name).
+
+-ifdef(debug).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(datadir, ?QLCs ++ "_SUITE_data").
+-define(privdir, ?QLCs ++ "_SUITE_priv").
+-define(testcase, current_testcase). % don't know
+-define(t, test_server).
+-else.
+-include("test_server.hrl").
+-define(datadir, ?config(data_dir, Config)).
+-define(privdir, ?config(priv_dir, Config)).
+-define(testcase, ?config(?TESTCASE, Config)).
+-endif.
+
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export([parse_transform/1,
+ badarg/1, nested_qlc/1, unused_var/1, lc/1, fun_clauses/1,
+ filter_var/1, single/1, exported_var/1, generator_vars/1,
+ nomatch/1, errors/1, pattern/1,
+
+ evaluation/1,
+ eval/1, cursor/1, fold/1, eval_unique/1, eval_cache/1, append/1,
+ evaluator/1, string_to_handle/1, table/1, process_dies/1,
+ sort/1, keysort/1, filesort/1, cache/1, cache_list/1, filter/1,
+ info/1, nested_info/1, lookup1/1, lookup2/1, lookup_rec/1,
+ indices/1, pre_fun/1, skip_filters/1,
+
+ table_impls/1,
+ ets/1, dets/1,
+
+ join/1,
+ join_option/1, join_filter/1, join_lookup/1, join_merge/1,
+ join_sort/1, join_complex/1,
+
+ tickets/1,
+ otp_5644/1, otp_5195/1, otp_6038_bug/1, otp_6359/1, otp_6562/1,
+ otp_6590/1, otp_6673/1, otp_6964/1, otp_7114/1, otp_7238/1,
+ otp_7232/1, otp_7552/1, otp_6674/1, otp_7714/1,
+
+ manpage/1,
+
+ compat/1,
+ backward/1, forward/1]).
+
+%% Internal exports.
+-export([bad_table_throw/1, bad_table_exit/1, default_table/1, bad_table/1,
+ bad_table_format/1, bad_table_format_arity/1, bad_table_traverse/1,
+ bad_table_post/1, bad_table_lookup/1, bad_table_max_lookup/1,
+ bad_table_info_arity/1, bad_table_info_fun_n_objects/1,
+ bad_table_info_fun_indices/1, bad_table_info_fun_keypos/1,
+ bad_table_key_equality/1]).
+-export([evaluator_2/2]).
+-export([prep_scratchdir/1, truncate_tmpfile/2, crash/2, crash_tmpfile/2]).
+-export([etsc/2, etsc/3, create_ets/2, lookup_keys/1]).
+-export([strip_qlc_call/1, join_info/1, join_info_count/1]).
+-export([i/1, i/2, format_info/2]).
+
+-export([table_kill_parent/2, table_parent_throws/2,
+ table_parent_exits/2, table_bad_parent_fun/2]).
+-export([table/2, table/3, stop_list/2, table_error/2, table_error/3,
+ table_lookup_error/1]).
+
+%% error_logger
+-export([install_error_logger/0, uninstall_error_logger/0,
+ read_error_logger/0]).
+-export([init/1,
+ handle_event/2, handle_call/2, handle_info/2,
+ terminate/2]).
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(5)).
+
+init_per_testcase(Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{?TESTCASE, Case}, {watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, _Config) ->
+ Dog = ?config(watchdog, _Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [parse_transform, evaluation, table_impls, join, tickets, manpage, compat].
+
+parse_transform(suite) ->
+ [badarg, nested_qlc, unused_var, lc, fun_clauses, filter_var,
+ single, exported_var, generator_vars, nomatch, errors, pattern].
+
+badarg(doc) ->
+ "Badarg.";
+badarg(suite) -> [];
+badarg(Config) when is_list(Config) ->
+ Ts =
+ [{badarg,
+ <<"-import(qlc, [q/1, q/2]).
+ q(_, _, _) -> ok.
+
+ badarg() ->
+ qlc:q(foo),
+ qlc:q(foo, cache_all),
+ qlc:q(foo, cache_all, extra),
+ q(bar),
+ q(bar, cache_all),
+ q(bar, cache_all, extra).
+ ">>,
+ [],
+ {errors,[{5,?QLC,not_a_query_list_comprehension},
+ {6,?QLC,not_a_query_list_comprehension},
+ {8,?QLC,not_a_query_list_comprehension},
+ {9,?QLC,not_a_query_list_comprehension}],
+ []}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+nested_qlc(doc) ->
+ "Nested qlc expressions.";
+nested_qlc(suite) -> [];
+nested_qlc(Config) when is_list(Config) ->
+ %% Nested QLC expressions. X is bound before the first one; Z and X
+ %% before the second one.
+ Ts =
+ [{nested_qlc1,
+ <<"nested_qlc() ->
+ X = 3, % X unused
+ Q = qlc:q([Y ||
+ X <- % X shadowed
+ begin Z = 3,
+ qlc:q([Y ||
+ Y <- [Z],
+ X <- [1,2,3], % X shadowed
+ X < Y])
+ end,
+ Y <-
+ [y],
+ Y > X]),
+ [y, y] = qlc:e(Q),
+ ok.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{2,15},erl_lint,{unused_var,'X'}},
+ {{4,29},erl_lint,{shadowed_var,'X',generate}},
+ {{8,49},erl_lint,{shadowed_var,'X',generate}}]}},
+
+ {nested_qlc2,
+ <<"nested_qlc() ->
+ H0 = qlc:append([a,b], [c,d]),
+ qlc:q([{X,Y} ||
+ X <- H0,
+ Y <- qlc:q([{X,Y} ||
+ X <- H0, % X shadowed
+ Y <- H0])]),
+ ok.
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{6,39},erl_lint,{shadowed_var,'X',generate}}]}}
+ ],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+unused_var(doc) ->
+ "Unused variable with a name that should not be introduced.";
+unused_var(suite) -> [];
+unused_var(Config) when is_list(Config) ->
+ Ts =
+ [{unused_var,
+ <<"unused_var() ->
+ qlc:q([X || begin Y1 = 3, true end, % Y1 unused
+ Y <- [1,2,3],
+ X <- [a,b,c],
+ X < Y]).
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{2,33},erl_lint,{unused_var,'Y1'}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+lc(doc) ->
+ "Ordinary LC expression.";
+lc(suite) -> [];
+lc(Config) when is_list(Config) ->
+ Ts =
+ [{lc,
+ <<"lc() ->
+ [X || X <- [], X <- X]. % X shadowed
+ ">>,
+ [],
+ {warnings,[{{2,30},erl_lint,{shadowed_var,'X',generate}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+fun_clauses(doc) ->
+ "Fun with several clauses.";
+fun_clauses(suite) -> [];
+fun_clauses(Config) when is_list(Config) ->
+ Ts =
+ [{fun_clauses,
+ <<"fun_clauses() ->
+ {X,X1,X2} = {1,2,3},
+ F = fun({X}) -> qlc:q([X || X <- X]); % X shadowed (fun, generate)
+ ([X]) -> qlc:q([X || X <- X]) % X shadowed (fun, generate)
+ end,
+ {F,X,X1,X2}.
+ ">>,
+ [],
+ {warnings,[{{3,22},erl_lint,{shadowed_var,'X','fun'}},
+ {{3,41},erl_lint,{shadowed_var,'X',generate}},
+ {{4,22},erl_lint,{shadowed_var,'X','fun'}},
+ {{4,41},erl_lint,{shadowed_var,'X',generate}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+filter_var(doc) ->
+ "Variable introduced in filter.";
+filter_var(suite) -> [];
+filter_var(Config) when is_list(Config) ->
+ Ts =
+ [{filter_var,
+ <<"filter_var() ->
+ qlc:q([X ||
+ Y <- [X ||
+ X <- [1,2,3]],
+ begin X = Y, true end]).
+ ">>,
+ [],
+ []},
+
+ {unsafe_filter_var,
+ <<"unsafe_filter_var() ->
+ qlc:q([{X,V} || X <- [1,2],
+ case {a} of
+ {_} ->
+ true;
+ V ->
+ V
+ end]).
+ ">>,
+ [],
+ {errors,[{{2,25},erl_lint,{unsafe_var,'V',{'case',{3,19}}}}],[]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+
+single(doc) ->
+ "Unused pattern variable.";
+single(suite) -> [];
+single(Config) when is_list(Config) ->
+ Ts =
+ [{single,
+ <<"single() ->
+ qlc:q([X || {X,Y} <- [{1,2}]]), % Y unused
+ qlc:q([[] || [] <- [[]]]).
+ ">>,
+ [warn_unused_vars],
+ {warnings,[{{2,30},erl_lint,{unused_var,'Y'}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+exported_var(doc) ->
+ "Exported variable in list expression (rhs of generator).";
+exported_var(suite) -> [];
+exported_var(Config) when is_list(Config) ->
+ Ts =
+ [{exported_var,
+ <<"exported() ->
+ qlc:q([X || X <- begin
+ case foo:bar() of
+ 1 -> Z = a;
+ 2 -> Z = b
+ end,
+ [Z = 3, Z = 3] % Z exported (twice...)
+ end
+ ]).
+ ">>,
+ [warn_export_vars],
+ {warnings,[{{7,37},erl_lint,{exported_var,'Z',{'case',{3,36}}}},
+ {{7,44},erl_lint,{exported_var,'Z',{'case',{3,36}}}}]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+generator_vars(doc) ->
+ "Errors for generator variable used in list expression.";
+generator_vars(suite) -> [];
+generator_vars(Config) when is_list(Config) ->
+ Ts =
+ [{generator_vars,
+ <<"generator_vars() ->
+ qlc:q([X ||
+ Z <- [1,2],
+ X <- begin
+ case 1 of
+ 1 -> Z = a; % used_generator_variable
+ 2 -> Z = b % used_generator_variable
+ end,
+ [Z = 3, Z = 3] % used_generator_variable (2)
+ end
+ ]).
+ ">>,
+ [],
+ {errors,[{{6,41},?QLC,{used_generator_variable,'Z'}},
+ {{7,41},?QLC,{used_generator_variable,'Z'}},
+ {{9,33},?QLC,{used_generator_variable,'Z'}},
+ {{9,40},?QLC,{used_generator_variable,'Z'}}],
+ []}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+nomatch(doc) ->
+ "Unreachable clauses also found when compiling.";
+nomatch(suite) -> [];
+nomatch(Config) when is_list(Config) ->
+ Ts =
+ [{unreachable1,
+ <<"unreachable1() ->
+ qlc:q([X || X <- [1,2],
+ case X of
+ true -> false;
+ true -> true % clause cannot match
+ end]).
+ ">>,
+ [],
+ {warnings,[{5,v3_kernel,{nomatch_shadow,4}}]}},
+
+ {nomatch1,
+ <<"generator1() ->
+ qlc:q([3 || {3=4} <- []]).
+ ">>,
+ [],
+ {warnings,[{{2,27},qlc,nomatch_pattern}]}},
+
+ {nomatch2,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([3 || {3=4} <- ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{1},{2}]).
+ ">>,
+ [],
+ {warnings,[{{3,33},qlc,nomatch_pattern}]}},
+
+ {nomatch3,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} || A=B={C=D}={_,_} <-
+ ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{1,2},{2,3}]).
+ ">>,
+ [],
+ {warnings,[{{3,52},qlc,nomatch_pattern}]}},
+
+ {nomatch4,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {<<X>>} = {<<Y>>} <-
+ ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{<<34>>},{<<40>>}]).
+ ">>,
+ [],
+ {errors,[{{3,48},erl_lint,illegal_bin_pattern}],[]}},
+
+ {nomatch5,
+ <<"nomatch() ->
+ etsc(fun(E) ->
+ Q = qlc:q([t || {\"a\"++\"b\"} = {\"ac\"} <-
+ ets:table(E)]),
+ [t] = qlc:eval(Q),
+ [\"ab\"] = lookup_keys(Q)
+ end, [{\"ab\"}]).
+ ">>,
+ [],
+ {warnings,[{3,v3_core,nomatch}]}}
+
+ ],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+
+errors(doc) ->
+ "Errors within qlc expressions also found when compiling.";
+errors(suite) -> [];
+errors(Config) when is_list(Config) ->
+ Ts =
+ [{errors1,
+ <<"errors1() ->
+ qlc:q([X || X <- A]). % A unbound
+ ">>,
+ [],
+ {errors,[{{2,33},erl_lint,{unbound_var,'A'}}],[]}}],
+ ?line [] = compile(Config, Ts),
+ ok.
+
+pattern(doc) ->
+ "Patterns.";
+pattern(suite) -> [];
+pattern(Config) when is_list(Config) ->
+ Ts = [
+ <<"%% Records in patterns. No lookup.
+ L = [#a{k=#k{v=91}}],
+ H = qlc:q([Q || Q = #a{k=#k{v=91}} <- qlc_SUITE:table(L, 2, [])]),
+ {qlc,_,[{generate,_,{table,{call,_,_,_}}}], []} = i(H),
+ L = qlc:e(H),
+ {call, _, _q, [{lc,_,{var,_,'Q'},
+ [{generate,_,
+ {match,_,_,_},
+ {call,_,_,_}}]}]}
+ = i(H, {format,abstract_code})">>,
+
+ <<"%% No matchspec since there is a binary in the pattern.
+ etsc(fun(E) ->
+ Q = qlc:q([A || {<<A:3/unit:8>>} <- ets:table(E)]),
+ [_] = qlc:eval(Q),
+ {qlc,_,[{generate,_,{table,_}}], []} = i(Q)
+ end, [{<<\"hej\">>}])">>
+
+ ],
+ ?line run(Config, <<"-record(a, {k,v}).
+ -record(k, {t,v}).\n">>, Ts),
+ ok.
+
+evaluation(suite) ->
+ [eval, cursor, fold, eval_unique, eval_cache, append, evaluator,
+ string_to_handle, table, process_dies, sort, keysort, filesort, cache,
+ cache_list, filter, info, nested_info, lookup1, lookup2, lookup_rec,
+ indices, pre_fun, skip_filters].
+
+eval(doc) ->
+ "eval/2";
+eval(suite) -> [];
+eval(Config) when is_list(Config) ->
+ ScratchDir = filename:join([?privdir, "scratch","."]),
+
+ Ts = [<<"{'EXIT',{badarg,_}} = (catch qlc:eval(not_a_qlc)),
+ H = qlc:q([X || X <- [1,2]]),
+ {'EXIT',{{unsupported_qlc_handle,{qlc_handle,foo}},_}}=
+ (catch qlc:e({qlc_handle,foo})),
+ {'EXIT',{badarg,_}} = (catch qlc:eval(H, [{unique_all,badarg}])),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:eval(H, [{spawn_options,badarg}])),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:eval(H, [{unique_all,true},{bad,arg}])),
+ {throw,t} =
+ (catch {any_term,qlc:e(qlc:q([X || X <- throw({throw,t})]))}),
+ M = qlc,
+ {'EXIT',{badarg,_}} = (catch M:q(bad))">>,
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir),
+
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:truncate_tmpfile(Dir, 0)]),
+ R = qlc:eval(H),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R,
+ \"the tempo\" ++ _ = lists:flatten(qlc:format_error(R))">>],
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir),
+
+ E = ets:new(foo, []),
+ Bin = term_to_binary(lists:seq(1,20000)),
+ [true || I <- lists:seq(1, 10), not ets:insert(E, {I, I, Bin})],
+ H = qlc:q([{X,Y} || Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:crash_tmpfile(Dir, 5)]),
+ R = qlc:eval(H),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R">>],
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_throw(E),
+ Y <- ets:table(E)]),
+ R1 = (catch {any_term,qlc:eval(H, {unique_all,false})}),
+ R2 = (catch {any_term,qlc:eval(H, {unique_all,true})}),
+ ets:delete(E),
+ true = {throw,bad_pre_fun} == R1,
+ true = {throw,bad_pre_fun} == R2">>,
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_exit(E),
+ Y <- ets:table(E)]),
+ R1 = (catch qlc:eval(H, {unique_all,false})),
+ R2 = (catch qlc:eval(H, {unique_all,true})),
+ ets:delete(E),
+ {'EXIT',{bad_pre_fun,_}} = R1,
+ {'EXIT',{bad_pre_fun,_}} = R2">>,
+
+ <<"Q = qlc:q([X || X <- [4,3,2,1,0,-1], begin 3/X > 0 end]),
+ {'EXIT',{badarith,_}} = (catch qlc:eval(Q, {unique_all,false})),
+ {'EXIT',{badarith,_}} = (catch qlc:eval(Q, {unique_all,true}))
+ ">>,
+
+ <<"[1,2] = qlc:eval(qlc:q([X || X <- [1,2]])),
+ [1,2,3,4] = qlc:eval(qlc:append([1,2],[3,4])),
+ [1,2] = qlc:eval(qlc:sort([2,1])),
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ [{1},{2}] = lists:sort(qlc:eval(ets:table(E))),
+ true = ets:delete(E)">>,
+
+ <<"H = qlc:q([X || X <- [1,2],
+ begin F = fun() ->
+ qlc:e(qlc:q([Y || Y <- [1,2]])) end,
+ F() == (fun f/0)() end]),
+ [1,2] = qlc:e(H),
+ ok.
+
+ f() -> [1,2].
+ foo() -> bar">>,
+
+ <<"C1_0_1 = [1,2],
+ %% The PT cannot rename C to C1_0_1; another name is chosen.
+ [1,2] = qlc:eval(qlc:q([C || C <- C1_0_1]))">>,
+
+ <<"H = qlc:q([X || {X,X} <- [{1,a},{2,2},{b,b},{3,4}]]),
+ [2,b] = qlc:e(H),
+ H1 = qlc:q([3 || {X,X} <- [{1,a},{2,2},{b,b},{3,4}]]),
+ [3,3] = qlc:e(H1)">>,
+
+ %% Just to cover a certain line in qlc.erl (avoids returning [])
+ <<"E = ets:new(foo, []),
+ Bin = term_to_binary(lists:seq(1,20000)),
+ [true || I <- lists:seq(1, 10), not ets:insert(E, {I, I, Bin})],
+ H = qlc:q([{X,Y} || Y <- [1,2], X <- qlc:sort(ets:table(E))]),
+ R = qlc:eval(H),
+ ets:delete(E),
+ 20 = length(R)">>,
+
+ <<"H = qlc:q([{A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} ||
+ {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} <-
+ [{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w}]]),
+ [_] = qlc:e(H)">>,
+
+ <<"H = qlc:q([Y || Y <- [1,2]],
+ {unique, begin [T] = qlc:e(qlc:q([X || X <- [true]],
+ cache)),
+ T end}),
+ [1,2] = qlc:e(H)">>
+
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+cursor(doc) ->
+ "cursor/2";
+cursor(suite) -> [];
+cursor(Config) when is_list(Config) ->
+ ScratchDir = filename:join([?privdir, "scratch","."]),
+ Ts = [<<"{'EXIT',{badarg,_}} =
+ (catch qlc:cursor(fun() -> not_a_cursor end)),
+ H0 = qlc:q([X || X <- throw({throw,t})]),
+ {throw,t} = (catch {any_term,qlc:cursor(H0)}),
+ H = qlc:q([X || X <- [1,2]]),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:cursor(H,{spawn_options, [a|b]})),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:cursor(H,{bad_option,true}))">>,
+
+ <<"{'EXIT',{badarg,_}} = (catch qlc:delete_cursor(not_a_cursor))">>,
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir), % kludge
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || begin put('$qlc_tmpdir', true), true end,
+ Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:truncate_tmpfile(Dir, 0)]),
+ C = qlc:cursor(H),
+ R = qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C),
+ erase('$qlc_tmpdir'),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R">>],
+
+ <<"H1 = qlc:q([X || X <- [1,2]]),
+ C1 = qlc:cursor(H1),
+ [1,2] = qlc:next_answers(C1, all_remaining),
+ [] = qlc:next_answers(C1),
+ [] = qlc:next_answers(C1),
+ ok = qlc:delete_cursor(C1),
+
+ H2 = qlc:append([1,2],[3,4]),
+ C2 = qlc:cursor(H2),
+ [1,2,3,4] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2),
+
+ H3 = qlc:sort([2,1]),
+ C3 = qlc:cursor(H3),
+ [1,2] = qlc:next_answers(C3, all_remaining),
+ ok = qlc:delete_cursor(C3),
+
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ H4 = ets:table(E),
+ C4 = qlc:cursor(H4),
+ [{1},{2}] = lists:sort(qlc:next_answers(C4, all_remaining)),
+ ok = qlc:delete_cursor(C4),
+ true = ets:delete(E)">>,
+
+ <<"H = qlc:q([{X,Y} || X <- [1,2], Y <- [a,b]]),
+ C = qlc:cursor(H, []),
+ [{1,a},{1,b}] = qlc:next_answers(C, 2),
+ [{2,a}] = qlc:next_answers(C, 1),
+ [{2,b}] = qlc:next_answers(C, all_remaining),
+ {'EXIT',{badarg,_}} = (catch qlc:next_answers(C, -1)),
+ P = self(),
+ Pid1 = spawn_link(fun() ->
+ {'EXIT',{not_cursor_owner,_}} =
+ (catch qlc:delete_cursor(C)),
+ P ! {self(), done} end),
+ Pid2 = spawn_link(fun() ->
+ {'EXIT',{not_cursor_owner,_}} =
+ (catch qlc:next_answers(C)),
+ P ! {self(), done} end),
+ receive {Pid1, done} -> ok end,
+ receive {Pid2, done} -> ok end,
+ ok = qlc:delete_cursor(C),
+ {'EXIT',{badarg,_}} = (catch qlc:next_answers(not_a_cursor)),
+ ok = qlc:delete_cursor(C)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,true}]),
+ [1,2] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,true}]),
+ [1,2] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,true},{spawn_options, []}]),
+ [1,2] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,true},{spawn_options, default}]),
+ [1,2] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,false},{spawn_options, []}]),
+ [1,2,1,2,1] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,false},{spawn_options, []}]),
+ [1,2,1,2,1] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ C1 = qlc:cursor(Q, [{unique_all,false}]),
+ [1,2,1,2,1] = qlc:next_answers(C1, all_remaining),
+ ok = qlc:delete_cursor(C1),
+ C2 = qlc:cursor(Q, [{unique_all,false}]),
+ [1,2,1,2,1] = qlc:next_answers(C2, all_remaining),
+ ok = qlc:delete_cursor(C2)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+fold(doc) ->
+ "fold/4";
+fold(suite) -> [];
+fold(Config) when is_list(Config) ->
+ ScratchDir = filename:join([?privdir, "scratch","."]),
+ Ts = [<<"Q = qlc:q([X || X <- [1,2,1,2,1]]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], Q, {bad,arg})),
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], badarg)),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:fold(F, [], {spawn_options, [a|b]})),
+ H = qlc:q([X || X <- throw({throw,t})]),
+ {throw,t} = (catch {any_term,qlc:fold(F, [], H)}),
+ [1,2] = qlc:fold(F, [], Q, {unique_all,true}),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:fold(F, [], Q, [{unique_all,bad}])),
+ [1,2,1,2,1] =
+ qlc:fold(F, [], Q, [{unique_all,false}])">>,
+
+ [<<"Dir = \"">>,ScratchDir,<<"\",
+ qlc_SUITE:prep_scratchdir(Dir),
+
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || Y <- [1,2],
+ X <- qlc:sort(ets:table(E),{tmpdir,Dir}),
+ qlc_SUITE:truncate_tmpfile(Dir, 0)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ R = qlc:fold(F, [], H),
+ ets:delete(E),
+ {error,_,{bad_object,_}} = R">>],
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_throw(E),
+ Y <- ets:table(E)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ R1 = (catch {any_term,qlc:fold(F, [], H, {unique_all,false})}),
+ R2 = (catch {any_term,qlc:fold(F, [], H, {unique_all,true})}),
+ ets:delete(E),
+ true = {throw,bad_pre_fun} == R1,
+ true = {throw,bad_pre_fun} == R2">>,
+
+ <<"E = ets:new(test, []),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_exit(E),
+ Y <- ets:table(E)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ R1 = (catch qlc:fold(F, [], H, {unique_all,false})),
+ R2 = (catch qlc:fold(F, [], H, {unique_all,true})),
+ ets:delete(E),
+ {'EXIT',{bad_pre_fun,_}} = R1,
+ {'EXIT',{bad_pre_fun,_}} = R2">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ Q = qlc:q([X || X <- [1,2,1,2,1], throw({throw,wrong})]),
+ {throw,wrong} =
+ (catch {any_term,qlc:fold(F, [], Q, {unique_all,true})}),
+ {throw,wrong} =
+ (catch {any_term,qlc:fold(F, [], Q)})">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ Q = qlc:q([X || X <- [4,3,2,1,0,-1], begin 3/X > 0 end]),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, {unique_all,true})),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, [{unique_all,false}]))
+ ">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ [1,2] = qlc:fold(F, [], qlc:q([X || X <- [1,2]])),
+ [1,2,3,4] = qlc:fold(F, [], qlc:append([1,2],[3,4])),
+ [1,2] = qlc:fold(F, [], qlc:sort([2,1])),
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ [{1},{2}] = lists:sort(qlc:fold(F, [], ets:table(E))),
+ true = ets:delete(E)">>,
+
+ <<"F = fun(_Obj, _A) -> throw({throw,fatal}) end,
+ Q = qlc:q([X || X <- [4,3,2]]),
+ {throw,fatal} =
+ (catch {any_term,qlc:fold(F, [], Q, {unique_all,true})}),
+ {throw,fatal} =
+ (catch {any_term,qlc:fold(F, [], Q, [{unique_all,false}])})">>,
+
+ <<"G = fun(_Obj, _A, D) -> 17/D end,
+ F = fun(Obj, A) -> G(Obj, A, 0) end,
+ Q = qlc:q([X || X <- [4,3,2]]),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, {unique_all,true})),
+ {'EXIT',{badarith,_}} =
+ (catch qlc:fold(F, [], Q, [{unique_all,false}]))
+ ">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+eval_unique(doc) ->
+ "Test the unique_all option of eval.";
+eval_unique(suite) -> [];
+eval_unique(Config) when is_list(Config) ->
+ Ts = [<<"QLC1 = qlc:q([X || X <- qlc:append([[1,1,2], [1,2,3,2,3]])]),
+ [1,2,3] = qlc:eval(QLC1, {unique_all,true}),
+ QLC2 = qlc:q([X || X <- [1,2,1,2,1,2,1]]),
+ [1,2] = qlc:e(QLC2, {unique_all,true})">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([ets:table(E), ets:table(E)])]),
+ R1 = qlc:e(H, {unique_all,false}),
+ R2 = qlc:e(H, {unique_all,true}),
+ ets:delete(E),
+ true = lists:sort(R1) == [{1,a},{1,a},{2,b},{2,b},{3,c},{3,c}],
+ true = lists:sort(R2) == [{1,a},{2,b},{3,c}]
+ ">>,
+
+ <<"Q1 = qlc:q([{X,make_ref()} || X <- [1,2,1,2]]),
+ [_,_] = qlc:e(Q1, {unique_all,true}),
+ [_,_,_,_] = qlc:e(Q1, {unique_all,false}),
+ [_,_] = qlc:e(Q1, [{unique_all,true}]),
+ Q2 = qlc:q([{X,make_ref()} || X <- qlc:append([[1,2,1,2]])]),
+ [_,_] = qlc:e(Q2, {unique_all,true}),
+ [_,_,_,_] = qlc:e(Q2, {unique_all,false}),
+ [_,_] = qlc:e(Q2, [{unique_all,true}])
+ ">>,
+
+ <<"Q = qlc:q([{X,make_ref()} || X <- qlc:sort([1,2,1,2])]),
+ [_, _] = qlc:e(Q, {unique_all,true}),
+ Q1 = qlc:q([X || X <- [1,2,1,2]]),
+ Q2 = qlc:q([{X,make_ref()} || X <- qlc:sort(Q1)]),
+ [_, _] = qlc:e(Q2, {unique_all,true})
+ ">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([[1,2,1,2]])]),
+ [1,2,1,2] = qlc:e(H, {unique_all,false}),
+ [1,2] = qlc:e(H, {unique_all,true}),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(foo, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{1,a},{2,b},{3,c},{4,c},{4,d}]),
+ Q1 = qlc:q([{X,make_ref()} || {_, X} <- ets:table(E)]),
+ true = length(qlc:eval(Q1, {unique_all, true})) =:= 5,
+ Q2 = qlc:q([X || {_, X} <- ets:table(E)]),
+ true = length(qlc:eval(Q2, {unique_all, true})) =:= 4,
+ Q3 = qlc:q([element(2, X) || X <- ets:table(E)]),
+ true = length(qlc:eval(Q3, {unique_all, true})) =:= 4,
+ Q4 = qlc:q([1 || _X <- ets:table(E)]),
+ true = length(qlc:eval(Q4, {unique_all, true})) =:= 1,
+ true = ets:delete(E)
+ ">>,
+
+ <<"Q1 = qlc:q([X || X <- qlc:append([[1], [2,1]])]),
+ Q2 = qlc:q([X || X <- qlc:append([[2,1], [2]])]),
+ Q3 = qlc:q([{X,Y} || X <- Q1, Y <- Q2]),
+ [{1,2},{1,1},{2,2},{2,1}] = qlc:e(Q3, {unique_all,true}),
+ Q4 = qlc:q([{X,Y,make_ref()} || X <- Q1, Y <- Q2]),
+ [{1,2,_},{1,1,_},{2,2,_},{2,1,_}] = qlc:e(Q4, {unique_all,true})
+ ">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2,1]]),
+ Q2 = qlc:q([X || X <- [2,1,2]]),
+ Q3 = qlc:q([{X,Y} || X <- Q1, Y <- Q2]),
+ [{1,2},{1,1},{2,2},{2,1}] = qlc:e(Q3,{unique_all,true}),
+ Q4 = qlc:q([{X,Y,make_ref()} || X <- Q1, Y <- Q2]),
+ [{1,2,_},{1,1,_},{2,2,_},{2,1,_}] = qlc:e(Q4, {unique_all,true})
+ ">>,
+
+ <<"Q1 = qlc:q([X || {X,_} <- [{1,a},{1,b}]]),
+ [1] = qlc:e(Q1, {unique_all, true}),
+ Q2 = qlc:q([a || _ <- [{1,a},{1,b}]]),
+ [a] = qlc:e(Q2, {unique_all, true})
+ ">>,
+
+ <<"Q = qlc:q([SQV || SQV <- qlc:q([X || X <- [1,2,1]],unique)],
+ unique),
+ {call,_,_,[{lc,_,{var,_,'X'},[{generate,_,{var,_,'X'},_}]},_]} =
+ qlc:info(Q, [{format,abstract_code},unique_all]),
+ [1,2] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([X || X <- [1,2,1]]),
+ {call,_,_,[{lc,_,{var,_,'X'},[{generate,_,{var,_,'X'},_}]},_]} =
+ qlc:info(Q, [{format,abstract_code},unique_all]),
+ [1,2] = qlc:e(Q, unique_all)">>,
+
+ <<"Q1 = qlc:sort([{1},{2},{3},{1}], [{unique,true}]),
+ Q = qlc:sort(Q1,[{unique,true}]),
+ {sort,{sort,{list,_},[{unique,true}]},[]} = i(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+eval_cache(doc) ->
+ "Test the cache_all and unique_all options of eval.";
+eval_cache(suite) -> [];
+eval_cache(Config) when is_list(Config) ->
+ Ts = [
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- ets:table(E)]), % already unique, no cache...
+ {qlc, _,
+ [{generate, _, {qlc, _,
+ [{generate, _, {list, [3,4]}}],
+ [{unique,true}]}},
+ _,
+ {generate, _, {table,_}}],
+ [{unique,true}]} = i(H, [cache_all, unique_all]),
+ [{1},{2},{3},{4}] = qlc:e(H, [cache_all, unique_all]),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- ets:table(E)]), % no cache...
+ {qlc, _,
+ [{generate, _,{list, [3,4]}},
+ _,
+ {generate, _, {table,_}}],
+ []} = i(H, cache_all),
+ [{1},{2},{3},{1},{2},{3},{4}] = qlc:e(H, [cache_all]),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- qlc:q([X || X <- ets:table(E)], cache)]),
+ {qlc, _,
+ [{generate, _, {list, [3,4]}},
+ _,
+ {generate, _, {qlc, _,
+ [{generate, _, {table,_}}],
+ [{cache,ets}]}}],
+ []} = i(H, cache_all),
+ [{1},{2},{3},{1},{2},{3}] = qlc:e(H, [cache_all]),
+ ets:delete(E)">>,
+
+ <<"%% {cache_all,no} does not override {cache,true}.
+ E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- qlc:q([X || X <- ets:table(E)], cache)]),
+ {qlc, _,
+ [{generate, _, {list, [3,4]}},
+ _,
+ {generate, _, {qlc, _,
+ [{generate, _, {table,_}}],
+ [{cache,ets}]}}],
+ []} = i(H, {cache_all,no}),
+ [{1},{2},{3},{1},{2},{3}] = qlc:e(H, [cache_all]),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H = qlc:q([X || Y <- [3,4],
+ ets:insert(E, {Y}),
+ X <- ets:table(E)]),
+ {qlc, _,
+ [{generate, _, {qlc, _, [{generate, _,{list, [3,4]}}],
+ [{unique,true}]}},
+ _,
+ {generate, _,{table,_}}],
+ [{unique,true}]} = i(H, unique_all),
+ [{1},{2},{3},{4}] = qlc:e(H, [unique_all]),
+ ets:delete(E)">>,
+
+ %% cache_all is ignored
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2},{0}]),
+ H = qlc:q([X || X <- qlc:sort(ets:table(E))]),
+ {sort,_Table,[]} = i(H, cache_all),
+ [{0},{1},{2}] = qlc:e(H, cache_all),
+ ets:delete(E)">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{1,a}]),
+ Q = qlc:q([X || X <- ets:table(E)], cache),
+ {table, _} = i(Q, []),
+ R = qlc:fold(F, [], Q, []),
+ ets:delete(E),
+ true = [{1,a},{1,a},{2,b}] == lists:sort(R)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2},{0}]),
+ H = qlc:q([X || X <- ets:table(E)], cache),
+ {table, _} = i(H, cache_all),
+ [{0},{1},{2}]= qlc:e(H, cache_all),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(foo, []),
+ true = ets:insert(E, [{1}, {2}]),
+ Q1 = qlc:q([{X} || X <- ets:table(E)]),
+ Q2 = qlc:q([{X,Y} || {X} <- Q1, {Y} <- Q1]),
+ {qlc, _, [{generate, _, {table, _}},
+ {generate, _, {qlc, _, [{generate, _, {table, _}}],
+ [{cache,ets}]}}],
+ []} = i(Q2, cache_all),
+ [{{1},{1}},{{1},{2}},{{2},{1}},{{2},{2}}] =
+ lists:sort(qlc:e(Q2, cache_all)),
+ ets:delete(E)">>,
+
+ <<"L1 = [1,2,3],
+ L2 = [4,5,6],
+ Q1 = qlc:append(L1, L2),
+ Q2 = qlc:q([{X} || X <- Q1]),
+ {qlc, _,[{generate, _,{append, [{list, L1}, {list, L2}]}}], []} =
+ i(Q2, [cache_all]),
+ [{1},{2},{3},{4},{5},{6}] = qlc:e(Q2, [cache_all])">>,
+
+ <<"H = qlc:sort(qlc:q([1 || _ <- [a,b]])),
+ {sort, {qlc, _, [{generate, _, {qlc, _, [{generate, _,
+ {list, [a,b]}}],
+ [{unique,true}]}}],
+ [{unique,true}]},
+ []} = i(H, unique_all),
+ [1] = qlc:e(H, unique_all)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+append(doc) ->
+ "Test the append function.";
+append(suite) -> [];
+append(Config) when is_list(Config) ->
+ Ts = [<<"C = qlc:cursor(qlc:q([X || X <- [0,1,2,3], begin 10/X > 0.0 end])),
+ R = (catch qlc:next_answers(C)),
+ {'EXIT',{badarith,_}} = R">>,
+
+ <<"C = qlc:cursor(qlc:q([X || X <- [0 | fun() -> exit(bad) end]])),
+ R = (catch qlc:next_answers(C)),
+ {'EXIT',bad} = R">>,
+
+ <<"{'EXIT',{badarg,_}} = (catch qlc:append([a], a)),
+ {'EXIT',{badarg,_}} = (catch qlc:append([[a],a]))">>,
+
+ <<"C = qlc:cursor(qlc:q([X || X <- [0,1,2,3],
+ begin throw({throw,wrong}), true end])),
+ {throw,wrong} = (catch {any_term,qlc:next_answers(C)})">>,
+
+ <<"QLC = qlc:q([X || X <- [0,1,2,3],
+ begin throw({throw,wrong}), true end]),
+ {throw,wrong} = (catch {any_term,qlc:eval(QLC)}),
+ {throw,wrong} =
+ (catch {any_term,qlc:e(QLC, {unique_all,true})})">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append([H1, H2])])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = [1,2,3],
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append(H1, H2)])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append(qlc:e(H1), H2)])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = [4,5,6],
+ R = qlc:e(qlc:q([X || X <- qlc:append(H1, H2)])),
+ true = R == [1,2,3,4,5,6]">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [4,5,6]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append([H1, H2, H1]), X < 5])),
+ true = R == [1,2,3,4,1,2,3]">>,
+
+ <<"R = qlc:e(qlc:q([X || X <- qlc:append([lista(), anrop()])])),
+ true = R == [a,b,1,2],
+ ok.
+
+ lista() ->
+ [a,b].
+
+ anrop() ->
+ qlc:q([X || X <- [1,2]]).
+ foo() -> bar">>,
+
+ %% Used to work up to R11B.
+ % <<"apa = qlc:e(qlc:q([X || X <- qlc:append([[1,2,3], ugly()])])),
+ % ok.
+ %
+ % ugly() ->
+ % [a | apa].
+ % foo() -> bar">>,
+
+
+ %% Maybe this one should fail.
+ <<"[a|b] = qlc:e(qlc:q([X || X <- qlc:append([[a|b]])])),
+ ok">>,
+
+ <<"17 = qlc:e(qlc:q([X || X <- qlc:append([[1,2,3],ugly2()])])),
+ ok.
+
+ ugly2() ->
+ [a | fun() -> 17 end].
+ foo() -> bar">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([ets:table(E), apa])]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H)),
+ false = ets:info(E, safe_fixed),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H)),
+ false = ets:info(E, safe_fixed),
+ {'EXIT',{badarg,_}} = (catch qlc:cursor(H)),
+ false = ets:info(E, safe_fixed),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], H)),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"H1 = qlc:q([X || X <- [1,2,3]]),
+ H2 = qlc:q([X || X <- [a,b,c]]),
+ R = qlc:e(qlc:q([X || X <- qlc:append(H1,qlc:append(H1,H2))])),
+ true = R == [1,2,3,1,2,3,a,b,c]">>,
+
+ <<"H = qlc:q([X || X <- qlc:append([],qlc:append([[], []]))]),
+ [] = qlc:e(H)">>,
+
+ <<"Q1 = qlc:q([X || X <- [3,4,4]]),
+ Q2 = qlc:q([X || X <- qlc:sort(qlc:append([[1,2], Q1]))]),
+ [1,2,3,4,4] = qlc:e(Q2),
+ [1,2,3,4] = qlc:e(Q2, {unique_all,true})">>,
+
+ <<"[] = qlc:e(qlc:q([X || X <- qlc:append([])]))">>,
+
+ <<"Q1 = qlc:q([X || X <- [a,b]]),
+ Q2 = qlc:q([X || X <- [1,2]]),
+ Q3 = qlc:append([Q1, Q2, qlc:sort([2,1])]),
+ Q = qlc:q([X || X <- Q3]),
+ {append, [{list, [a,b]},
+ {list, [1,2]},
+ {sort,{list, [2,1]},[]}]} = i(Q),
+ [a,b,1,2,1,2] = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+evaluator(doc) ->
+ "Simple call from evaluator.";
+evaluator(suite) -> [];
+evaluator(Config) when is_list(Config) ->
+ ?line true = is_alive(),
+ evaluator_2(Config, []),
+ ?line {ok, Node} = start_node(qlc_SUITE_evaluator),
+ ?line ok = rpc:call(Node, ?MODULE, evaluator_2, [Config, [compiler]]),
+ ?line ?t:stop_node(Node),
+ ok.
+
+evaluator_2(Config, Apps) ->
+ ?line lists:foreach(fun(App) -> true = code:del_path(App) end, Apps),
+ FileName = filename:join(?privdir, "eval"),
+ ?line ok = file:write_file(FileName,
+ <<"H = qlc:q([X || X <- L]),
+ [1,2,3] = qlc:e(H).">>),
+ ?line Bs = erl_eval:add_binding('L', [1,2,3], erl_eval:new_bindings()),
+ ?line ok = file:eval(FileName, Bs),
+
+ %% The error message is "handled" a bit too much...
+ %% (no trace of erl_lint left)
+ ?line ok = file:write_file(FileName,
+ <<"H = qlc:q([X || X <- L]), qlc:e(H).">>),
+ ?line {error,_} = file:eval(FileName),
+
+ %% Ugly error message; badarg is caught by file.erl.
+ ?line ok = file:write_file(FileName,
+ <<"H = qlc:q([Z || {X,Y} <- [{a,2}], Z <- [Y]]), qlc:e(H).">>),
+ ?line {error,_} = file:eval(FileName),
+
+ _ = file:delete(FileName),
+ ok.
+
+start_node(Name) ->
+ ?line PA = filename:dirname(code:which(?MODULE)),
+ ?t:start_node(Name, slave, [{args, "-pa " ++ PA}]).
+
+string_to_handle(doc) ->
+ "string_to_handle/1,2.";
+string_to_handle(suite) -> [];
+string_to_handle(Config) when is_list(Config) ->
+ ?line {'EXIT',{badarg,_}} = (catch qlc:string_to_handle(14)),
+ ?line {'EXIT',{badarg,_}} =
+ (catch qlc:string_to_handle("[X || X <- [a].", unique_all)),
+ ?line R1 = {error, _, {_,erl_scan,_}} = qlc:string_to_handle("'"),
+ ?line "1: unterminated " ++ _ = lists:flatten(qlc:format_error(R1)),
+ ?line {error, _, {_,erl_parse,_}} = qlc:string_to_handle("foo"),
+ ?line {'EXIT',{badarg,_}} = (catch qlc:string_to_handle("foo, bar.")),
+ ?line R3 = {error, _, {_,?QLC,not_a_query_list_comprehension}} =
+ qlc:string_to_handle("bad."),
+ ?line "1: argument is not" ++ _ = lists:flatten(qlc:format_error(R3)),
+ ?line R4 = {error, _, {_,?QLC,{used_generator_variable,'Y'}}} =
+ qlc:string_to_handle("[X || begin Y = [1,2], true end, X <- Y]."),
+ ?line "1: generated variable 'Y'" ++ _ =
+ lists:flatten(qlc:format_error(R4)),
+ ?line {error, _, {_,erl_lint,_}} = qlc:string_to_handle("[X || X <- A]."),
+ ?line H1 = qlc:string_to_handle("[X || X <- [1,2]]."),
+ ?line [1,2] = qlc:e(H1),
+ ?line H2 = qlc:string_to_handle("[X || X <- qlc:append([a,b],"
+ "qlc:e(qlc:q([X || X <- [c,d,e]])))]."),
+ ?line [a,b,c,d,e] = qlc:e(H2),
+ %% The generated fun has many arguments (erl_eval has a maximum of 20).
+ ?line H3 = qlc:string_to_handle(
+ "[{A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} ||"
+ " {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W} <- []]."),
+ ?line [] = qlc:e(H3),
+ ?line Bs1 = erl_eval:add_binding('L', [1,2,3], erl_eval:new_bindings()),
+ ?line H4 = qlc:string_to_handle("[X || X <- L].", [], Bs1),
+ ?line [1,2,3] = qlc:e(H4),
+ ?line H5 = qlc:string_to_handle("[X || X <- [1,2,1,2]].", [unique, cache]),
+ ?line [1,2] = qlc:e(H5),
+
+ ?line Ets = ets:new(test, []),
+ ?line true = ets:insert(Ets, [{1}]),
+ ?line Bs2 = erl_eval:add_binding('E', Ets, erl_eval:new_bindings()),
+ ?line Q = "[X || {X} <- ets:table(E)].",
+ ?line [1] = qlc:e(qlc:string_to_handle(Q, [], Bs2)),
+ ?line [1] = qlc:e(qlc:string_to_handle(Q, {max_lookup,1000}, Bs2)),
+ ?line [1] = qlc:e(qlc:string_to_handle(Q, {max_lookup,infinity}, Bs2)),
+ ?line {'EXIT',{badarg,_}} =
+ (catch qlc:string_to_handle(Q, {max_lookup,-1}, Bs2)),
+ ?line {'EXIT', {no_lookup_to_carry_out, _}} =
+ (catch qlc:e(qlc:string_to_handle(Q, {lookup,true}, Bs2))),
+ ?line ets:delete(Ets),
+ ok.
+
+table(doc) ->
+ "table";
+table(suite) -> [];
+table(Config) when is_list(Config) ->
+ dets:start(),
+ Ts = [
+ <<"E = ets:new(test, []),
+ {'EXIT',{badarg,_}} =
+ (catch qlc:e(qlc:q([X || X <- ets:table(E, [badarg])]))),
+ [] = qlc:e(qlc:q([X || X <- ets:table(E)])),
+ ets:delete(E)">>,
+
+ <<"{'EXIT',{badarg,_}} = (catch qlc:table(not_a_fun, []))">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([{X,Y} || X <- ets:table(E), Y <- ets:table(E)]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [{{1,a},{1,a}},{{1,a},{2,b}},{{1,a},{3,c}},
+ {{2,b},{1,a}},{{2,b},{2,b}},{{2,b},{3,c}},
+
+ {{3,c},{1,a}},{{3,c},{2,b}},{{3,c},{3,c}}] = lists:sort(R)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc:append([ets:table(E), [a,b,c],
+ ets:table(E)])]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [a,b,c,{1,a},{1,a},{2,b},{2,b},{3,c},{3,c}] = lists:sort(R)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ H = qlc:q([{X,Y} || X <- ets:table(E, {n_objects, default}),
+ Y <- ets:table(E, {n_objects, 2}),
+ false =/= ets:info(E, safe_fixed),
+ throw({throw,apa})]),
+ {throw,apa} = (catch {any_term,qlc:e(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ H = qlc:q([{X,Y} || X <- ets:table(E), Y <- ets:table(E),
+ false =/= ets:info(E, safe_fixed), exit(apa)]),
+ {'EXIT',apa} = (catch {any_term,qlc:e(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_throw(E),
+ Y <- ets:table(E)]),
+ R = (catch {any_term,qlc:cursor(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E),
+ {throw,bad_pre_fun} = R">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([{X,Y} || X <- qlc_SUITE:bad_table_exit(E),
+ Y <- ets:table(E)]),
+ R = (catch {any_term,qlc:cursor(H)}),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E),
+ {'EXIT',{bad_pre_fun,_}} = R">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc_SUITE:default_table(E)]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [{1,a},{2,b},{3,c}] = R">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([X || X <- qlc_SUITE:bad_table(E)]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(H)),
+ ets:delete(E)">>,
+
+ %% The info tag num_of_objects is currently not used.
+% <<"E = ets:new(test, [ordered_set]),
+% true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+% H = qlc:q([X || X <- qlc_SUITE:bad_table_info_fun_n_objects(E)]),
+% {'EXIT', finito} = (catch {any_term,qlc:e(H)}),
+% ets:delete(E)">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([Y || {X,Y} <- qlc_SUITE:bad_table_info_fun_indices(E),
+ X =:= a]),
+ %% This is due to lookup. If the table were traversed there
+ %% would be no failure.
+ {throw, apa} = (catch {any_term,qlc:e(H)}),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(test, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ H = qlc:q([Y || {X,Y} <- qlc_SUITE:bad_table_info_fun_keypos(E),
+ X =:= a]),
+ {'EXIT',{keypos,_}} = (catch {any_term,qlc:info(H)}),
+ {'EXIT',{keypos,_}} = (catch {any_term,qlc:e(H)}),
+ ets:delete(E)">>,
+
+ begin
+ MS = ets:fun2ms(fun(X) when element(1, X) > 1 -> X end),
+ [<<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ H = qlc:q([{X,Y} || X <- ets:table(E,{traverse,{select, MS}}),
+ Y <- ets:table(E)]),
+ R = qlc:e(H),
+ ets:delete(E),
+ [{{2,b},{1,a}},{{2,b},{2,b}},{{2,b},{3,c}},
+ {{3,c},{1,a}},{{3,c},{2,b}},{{3,c},{3,c}}] = lists:sort(R)">>]
+ end,
+
+ begin % a short table
+ MS = ets:fun2ms(fun(X) when element(1, X) > 1 -> X end),
+ [<<"E = ets:new(test, []),
+ true = ets:insert(E, [{0,b}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ H1 = qlc:q([X || X <- ets:table(E)]),
+ R1 = qlc:e(H1),
+ H2 = qlc:q([X || X <- ets:table(E, {traverse, {select, MS}})]),
+ R2 = qlc:e(H2),
+ ets:delete(E),
+ [_] = R1,
+ [] = R2">>]
+ end,
+
+ begin
+ File = filename:join(?privdir, "detsfile"),
+ _ = file:delete(File),
+ [<<"{ok, Tab} = dets:open_file(apa, [{file,\"">>, File, <<"\"},
+ {type,bag}]),
+ ok = dets:insert(Tab, [{1,a},{1,b}]),
+ R = qlc:e(qlc:q([X || X <- dets:table(Tab)])),
+ dets:close(Tab),
+ file:delete(\"">>, File, <<"\"),
+ R">>]
+ end,
+
+ %% [T || P <- Table, F] turned into a match spec.
+ <<"E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c},{4,d}]),
+ QH = qlc:q([X || {X,_} <- ets:table(E), X > 2], unique),
+ {qlc, _, [{generate, _, {table, _}}], [{unique,true}]} = i(QH),
+ [3,4] = lists:sort(qlc:e(QH)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_format(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_format_arity(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_info_arity(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_traverse(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_post(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_max_lookup(E)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} = (catch qlc_SUITE:bad_table_lookup(E)),
+ ets:delete(E)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(2, X) || X <- qlc_SUITE:table(L, [2]),
+ (element(1, X) =:= 1)
+ or (2 =:= element(1, X))]),
+ [a,b] = lists:sort(qlc:e(QH))">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B} || {A,B} <-
+ qlc:q([{B,A} || {A,B} <- ets:table(E),
+ (A =:= 1) or (A =:= 2),
+ math:sqrt(B) < A])]),
+ [{2,2}] = qlc:eval(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{1,1},{2,2}])">>
+ ],
+ ?line run(Config, Ts),
+
+ Ts2 = [
+ %% [T || P <- Table, F] turned into a match spec. Records needed.
+ <<"E = ets:new(foo, [bag]),
+ ets:insert(E, [{a,1,2},#a{b=3,c=4},{a,3}]),
+ QH = qlc:q([X || X <- ets:table(E), is_record(X, a)]),
+ {list,{table,_}, _} = i(QH),
+ [{a,1,2},{a,3,4}] = lists:sort(qlc:eval(QH)),
+ ets:delete(E)">>
+ ],
+ ?line run(Config, <<"-record(a, {b,c}).\n">>, Ts2),
+
+ ok.
+
+process_dies(doc) ->
+ "Caller or cursor process dies.";
+process_dies(suite) -> [];
+process_dies(Config) when is_list(Config) ->
+ Ts = [
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ Parent = self(),
+ F = fun() ->
+ H = qlc:q([X || X <- ets:table(E)]),
+ qlc:cursor(H),
+ Parent ! {self(),ok}
+ end,
+ Pid = spawn_link(F),
+ receive {Pid,ok} -> ok end,
+ timer:sleep(10),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"%% This is not nice. The cursor's monitor kicks in.
+ E = ets:new(test, []),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ false = ets:info(E, safe_fixed),
+ Parent = self(),
+ F = fun() ->
+ H = qlc:q([X || begin
+ process_flag(trap_exit, false),
+ {links, [Pid]} =
+ process_info(self(), links),
+ unlink(Pid),
+ timer:sleep(1),
+ {links, []} =
+ process_info(self(), links),
+ true
+ end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H),
+ qlc:next_answers(C),
+ Parent ! {self(),ok}
+ end,
+ Pid = spawn_link(F),
+ receive {Pid,ok} -> ok end,
+ timer:sleep(10),
+ false = ets:info(E, safe_fixed),
+ ets:delete(E)">>,
+
+ <<"H = qlc:q([X || X <- [1,2]]),
+ {qlc_cursor, Term} = C = qlc:cursor(H),
+ PF = process_flag(trap_exit, true),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ exit(Pid, kill),
+ timer:sleep(1),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, Pid}, _}} =
+ (catch qlc:next_answers(C)),
+ process_flag(trap_exit, PF)">>,
+ <<"H = qlc:q([X || begin process_flag(trap_exit, true), true end,
+ X <- [1,2]]),
+ {qlc_cursor, Term} = C = qlc:cursor(H),
+ PF = process_flag(trap_exit, true),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ [1] = qlc:next_answers(C, 1),
+ exit(Pid, stop),
+ timer:sleep(1),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, Pid}, _}} =
+ (catch qlc:next_answers(C)),
+ process_flag(trap_exit, PF)">>,
+ <<"%% This is not nice. No cleanup is done...
+ H = qlc:q([X || begin process_flag(trap_exit, false), true end,
+ X <- [1,2]]),
+ {qlc_cursor, Term} = C = qlc:cursor(H),
+ PF = process_flag(trap_exit, true),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ [1] = qlc:next_answers(C, 1),
+ exit(Pid, stop),
+ timer:sleep(1),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, Pid}, _}} =
+ (catch qlc:next_answers(C)),
+ process_flag(trap_exit, PF)">>,
+
+ <<"PF = process_flag(trap_exit, true),
+ E = ets:new(test, []),
+ %% Hard kill. No cleanup will be done.
+ H = qlc:q([X || begin exit(self(), kill), true end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, _}, _}} =
+ (catch qlc:next_answers(C)),
+ false = ets:info(E, safe_fixed), % - but Ets cleans up anyway.
+ true = ets:delete(E),
+ process_flag(trap_exit, PF)">>,
+
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a}]),
+ %% The signal is caught by trap_exit. No process dies...
+ H = qlc:q([X || begin exit(self(), normal), true end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H, {spawn_options, []}),
+ [{1,a}] = qlc:next_answers(C),
+ qlc:delete_cursor(C),
+ false = ets:info(E, safe_fixed),
+ true = ets:delete(E)">>,
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a}]),
+ %% The same as last example.
+ H = qlc:q([X || begin
+ process_flag(trap_exit, true),
+ exit(self(), normal), true
+ end,
+ X <- ets:table(E)]),
+ C = qlc:cursor(H, {spawn_options, []}),
+ [{1,a}] = qlc:next_answers(C),
+ qlc:delete_cursor(C),
+ false = ets:info(E, safe_fixed),
+ true = ets:delete(E), ok">>,
+ <<"E = ets:new(test, []),
+ true = ets:insert(E, [{1,a}]),
+ H = qlc:q([X || X <- ets:table(E)]),
+ SpawnOpts = [link, monitor], % monitor is ignored
+ {qlc_cursor, Term} = C = qlc:cursor(H, {spawn_options, SpawnOpts}),
+ F = fun(T) -> not is_pid(T) end,
+ [Pid|_] = lists:dropwhile(F, tuple_to_list(Term)),
+ Me = self(),
+ qlc_SUITE:install_error_logger(),
+ Tuple = {this, tuple, is, writton, onto, the, error_logger},
+ SP = spawn(fun() ->
+ Pid ! Tuple,
+ Me ! {self(), done}
+ end),
+ receive {SP, done} -> ok end,
+ [{1,a}] = qlc:next_answers(C),
+ qlc:delete_cursor(C),
+ {error, _Pid, Tuple} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger(),
+ false = ets:info(E, safe_fixed),
+ true = ets:delete(E), ok">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+sort(doc) ->
+ "The sort option.";
+sort(suite) -> [];
+sort(Config) when is_list(Config) ->
+ Ts = [
+ <<"H = qlc:q([X || X <- qlc:sort([1,2,3,2], {unique,true})]),
+ [1,2,3] = qlc:e(H),
+ C1 = qlc:cursor(H),
+ [1,2,3] = qlc:next_answers(C1, all_remaining),
+ qlc:delete_cursor(C1)">>,
+
+ <<"H = qlc:q([{X,Y} || X <- qlc:sort(qlc:q([X || X <- [1,2,3,2]]),
+ {unique,true}),
+ Y <- [a,b]]),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] = qlc:e(H),
+ C = qlc:cursor(H),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] =
+ qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C)">>,
+
+ <<"H = qlc:q([X || X <- qlc:sort(qlc:q([X || X <- apa]))]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H))">>,
+
+ %% An example with a side effect. The result may vary...
+ <<"E = ets:new(test, [duplicate_bag]),
+ true = ets:insert(E, [{1,17},{1,a}]),
+ H_1 = qlc:q([X || X <- ets:table(E)]),
+ H = qlc:q([X || X <- [1,2,3], ets:insert(E, {1,-X}),
+ {_,Y} <- H_1,
+ X > Y]),
+ true = lists:sort(qlc:e(H)) == [1,2,2,3,3,3],
+ true = ets:delete(E)">>,
+
+ <<"E = ets:new(test, [duplicate_bag]),
+ true = ets:insert(E, [{1,17}]),
+ H_1 = qlc:q([X || X <- qlc:sort(ets:tab2list(E))]),
+ %% Note: side effect in filter!
+ H = qlc:q([X || X <- [1,2,3], ets:insert(E, {1,-X}),
+ {_,Y} <- H_1, X > Y]),
+ [] = qlc:e(H),
+ true = ets:delete(E)">>,
+
+ <<"H = qlc:q([X || X <- qlc:sort([1,2,3], {fopp,la})]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H)),
+ {'EXIT',{badarg,_}} = (catch qlc:cursor(H)),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {'EXIT',{badarg,_}} = (catch qlc:fold(F, [], H))">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2]]),
+ AL = [Q1, [1,2,3]],
+ Q2 = qlc:q([X || X <- qlc:sort(qlc:append(AL))]),
+ [1,1,2,2,3] = qlc:e(Q2)">>,
+
+ <<"H = qlc:q([{X,Y} || X <- qlc:sort(qlc:q([X || X <- [1,2,3,2]]),
+ {unique,true}),
+ Y <- [a,b]]),
+ {qlc, _,
+ [{generate, _, {sort, {qlc, _, [{generate, _, {list, [1,2,3,2]}}],
+ [{unique,true}]},
+ []}},
+ {generate, _, {qlc, _, [{generate, _, {list, [a,b]}}],
+ [{unique,true}]}}],
+ [{unique,true}]} = i(H, unique_all),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] = qlc:e(H, unique_all)">>,
+
+ <<"L = [1,2,1,3,4,3,1],
+ true = lists:sort(L) == qlc:e(qlc:q([X || X <- qlc:sort(L)])),
+ true = lists:usort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, {unique,true})])),
+ true = lists:reverse(lists:sort(L)) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, {order, descending})])),
+ true = lists:reverse(lists:usort(L)) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, [{order, descending},
+ {unique, true}])])),
+ CF = fun(X, Y) -> X =< Y end,
+ true = lists:sort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, {order, CF})])),
+ true = lists:usort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:sort(L, [{order, CF},
+ {unique, true}])]))">>,
+
+ <<"E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ H = qlc:q([{X,Y} || X <- [a,b], Y <- qlc:sort(ets:table(E))]),
+ 100000 = length(qlc:e(H)),
+ ets:delete(E)">>,
+
+ begin
+ TmpDir = ?privdir,
+ [<<"TE = process_flag(trap_exit, true),
+ E = ets:new(foo, []),
+ [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
+ Ports = erlang:ports(),
+ H = qlc:q([{X,Y} || X <- [a,b],
+ begin
+ [P] = erlang:ports() -- Ports,
+ exit(P, port_exit),
+ true
+ end,
+ Y <- qlc:sort(ets:table(E),
+ [{tmpdir,\"">>,
+ TmpDir, <<"\"}])]),
+ {error, qlc, {file_error, _, _}} = (catch qlc:e(H)),
+ receive {'EXIT', _, port_exit} -> ok end,
+ ets:delete(E),
+ process_flag(trap_exit, TE)">>]
+ end
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+keysort(doc) ->
+ "The sort option.";
+keysort(suite) -> [];
+keysort(Config) when is_list(Config) ->
+
+ Ts = [
+ <<"OF = fun(X, Y) -> X =< Y end,
+ F = fun(Obj, A) -> A++[Obj] end,
+ H = qlc:q([X || X <- qlc:keysort(1, [{2,a},{1,b}], {order,OF})]),
+ {'EXIT',{{badarg,order},_}} = (catch qlc:e(H)),
+ {'EXIT',{{badarg,order},_}} = (catch qlc:fold(F, [], H)),
+ {'EXIT',{{badarg,order},_}} = (catch qlc:cursor(H))">>,
+
+ <<"E = create_ets(1, 2),
+ H = qlc:q([X || X <- qlc:keysort([1], ets:table(E),
+ [{size,1},{tmpdir,\"/a/b/c\"}])]),
+ H1 = qlc:q([X || {X,_} <- qlc:e(H), X < 4]),
+ {error,_,{file_error,_,_}} = qlc:info(H1),
+ {error,_,{file_error,_,_}} = qlc:e(H1),
+ ets:delete(E)">>,
+
+ <<"L = [{1,a},{2,b},{3,c},{2,b}],
+ H = qlc:q([{X,Y} || {X,_} <- qlc:keysort(1, qlc:q([X || X <- L]),
+ {unique,true}),
+ Y <- [a,b]]),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] = qlc:e(H),
+ C = qlc:cursor(H),
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] =
+ qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C)">>,
+
+ <<"H1 = qlc:q([X || X <- qlc:keysort(0, [])]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H1)),
+ H2 = qlc:q([X || X <- qlc:keysort(1, [], {bad,arg})]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H2)),
+ H3 = qlc:q([X || X <- qlc:keysort([], [])]),
+ {'EXIT',{badarg,_}} = (catch qlc:e(H3))">>,
+
+ <<"H = qlc:q([X || X <- qlc:keysort(1, [{1,a},{2,b}],
+ [{order,descending},
+ {compressed,true}])]),
+ [{2,b},{1,a}] = qlc:e(H),
+ H2 = qlc:q([X || X <- qlc:keysort(1, [{1},{2}], compressed)]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(H2))">>,
+
+ <<"H = qlc:q([X || X <- qlc:keysort(1, [{1,a},{2,b}], {compressed,false})]),
+ [{1,a},{2,b}] = qlc:e(H)">>,
+
+ <<"E = create_ets(1, 2),
+ H = qlc:q([X || X <- qlc:keysort([1], ets:table(E),
+ [{size,1},{tmpdir,\"/a/b/c\"}])]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ {error,_,{file_error,_,_}} = qlc:e(H),
+ \" \\\"no such\" ++ _ = lists:dropwhile(fun(A) -> A =/= $\s end,
+ lists:flatten(qlc:format_error(qlc:e(H)))),
+ {error,_,{file_error,_,_}} = qlc:e(H, {unique_all,true}),
+ {error,_,{file_error,_,_}} = qlc:cursor(H),
+ {error,_,{file_error,_,_}} = qlc:cursor(H, {unique_all,true}),
+ {error,_,{file_error,_,_}} = qlc:cursor(H, {spawn_options, []}),
+ {error,_,{file_error,_,_}} = qlc:cursor(H, {spawn_options,default}),
+ {error,_,{file_error,_,_}} =
+ qlc:cursor(H, [{unique_all,true},{spawn_options, []}]),
+ {error,_,{file_error,_,_}} = qlc:fold(F, [], H),
+ {error,_,{file_error,_,_}} = qlc:fold(F, [],H, {unique_all,true}),
+ ets:delete(E)">>,
+
+ <<"L = [{1,b,a},{1,b,b},{1,a,a}],
+ H = qlc:q([X || X <- qlc:keysort([4,1], L)]),
+ {error,_,bad_object} = qlc:e(H),
+ \"the keys\" ++ _ = qlc:format_error(qlc:e(H))">>,
+
+ begin
+ File = filename:join(?privdir, "afile"),
+ ok = file:write_file(File, <<>>),
+ [<<"H = qlc:q([X || X <- qlc:keysort([1], [{1},{2},{1}],
+ [{tmpdir,\"">>, File, <<"\"},
+ {size,1}])]),
+ {error,_,{file_error,_,_}} = qlc:e(H),
+ file:delete(\"">>, File, <<"\")">>]
+ end,
+
+ <<"H0 = qlc:q([X || X <- [1,2,3]]),
+ H = qlc:q([X || X <- qlc:sort(H0,{tmpdir,\".\"})]),
+ [1,2,3] = qlc:e(H)">>,
+
+ %% The global option 'tmpdir' takes precedence.
+ begin
+ PrivDir = ?privdir,
+ [<<"L = [{1,a},{2,b}],
+ H = qlc:q([X || X <- qlc:keysort([1], L, {tmpdir,\"/a/b/c\"})]),
+ H1 = qlc:q([X || X <- H, X > 3]),
+ Dir = \"">>, PrivDir, <<"\",
+ Options = [{tmpdir, Dir}],
+ {qlc,_,[{generate,_,{keysort,{list,L},[1],[{tmpdir,Dir}]}},_],[]} =
+ i(H1, Options),
+ [{1,a},{2,b}] = qlc:e(H1, Options)">>] % no check of "/a/b/c"
+ end,
+
+ <<"L = [{2,c},{1,b},{1,a},{3,a},{1,c},{2,b}],
+ true = lists:sort(L) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort([1,2], L)]))">>,
+
+ <<"L = [{1,b},{2,c},{1,a},{3,e},{4,f},{3,d}],
+ true = lists:keysort(1, L) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1,L)])),
+ true = lists:ukeysort(1, L) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1, L, {unique,true})])),
+ true = lists:reverse(lists:keysort(1, L)) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1,L,
+ {order, descending})])),
+ true = lists:reverse(lists:ukeysort(1, L)) ==
+ qlc:e(qlc:q([X || X <- qlc:keysort(1, L, [{unique,true},
+ {order, descending}])]))">>,
+
+ <<"L = [{X} || X <- lists:seq(1,100000)],
+ H1 = qlc:append([L,[{1,2},{2,3},{3,4}]]),
+ H = qlc:keysort([1], qlc:keysort([1], H1, [{compressed,true}])),
+ R = qlc:e(H),
+ 100003 = length(R)">>
+
+ ],
+ ?line run(Config, Ts),
+
+ ok.
+
+filesort(doc) ->
+ "keysort/1,2, using a file.";
+filesort(suite) -> [];
+filesort(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q = qlc:q([X || X <- [{3},{1},{2}]]),
+ Opts = [{size,10},{no_files,3}],
+ Q2 = qlc:q([{X,Y} || Y <- [1,2], X <- qlc:keysort([1],Q,Opts)]),
+ [{{1},1},{{2},1},{{3},1},{{1},2},{{2},2},{{3},2}] = qlc:e(Q2)">>
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+
+cache(doc) ->
+ "The cache option.";
+cache(suite) -> [];
+cache(Config) when is_list(Config) ->
+ Ts = [
+ <<"{'EXIT', {badarg, _}} = (catch qlc:q([X || X <- [1,2]], badarg))">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2,1,2,1]], {unique,true}),
+ [1,2] = qlc:e(Q1),
+ [1,2] = qlc:e(Q1, {unique_all,true}),
+ Q2 = qlc:q([X || X <- qlc:q([X || X <- [1,2,1,2,1]],
+ {unique,true})]),
+ [1,2] = qlc:e(Q2),
+ [1,2] = qlc:e(Q2, {unique_all,true}),
+ Q3 = qlc:q([X || X <- qlc:append([[1,2,3], [4,5,6]])]),
+ [1,2,3,4,5,6] = qlc:e(Q3)">>,
+
+ <<"Q1 = qlc:q([X || {X,_} <- [{1,a},{2,a},{1,b},{2,b}]]),
+ Q2 = qlc:q([{X,make_ref()} || X <- Q1]),
+ [{1,_},{2,_},{1,_},{2,_}] = qlc:e(Q2, {unique_all,false}),
+ [{1,_},{2,_}] = qlc:e(Q2, {unique_all,true})">>,
+
+ <<"E = ets:new(test, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,a},{1,b},{2,b}]),
+ Q1 = qlc:q([X || {X,_} <- ets:table(E)]),
+ Q2 = qlc:q([{X,make_ref()} || X <- Q1]),
+ [{1,_},{1,_},{2,_},{2,_}] = lists:sort(qlc:e(Q2, {unique_all,false})),
+ [{1,_},{2,_}] = lists:sort(qlc:e(Q2, {unique_all,true})),
+ ets:delete(E)">>,
+
+ <<"Q1 = qlc:q([X || {X,_} <- [{1,a},{2,a},{1,b},{2,b}]]),
+ Q2 = qlc:q([{X,make_ref()} || X <- qlc:append([Q1, Q1])]),
+ [{1,_},{2,_},{1,_},{2,_},{1,_},{2,_},{1,_},{2,_}] =
+ qlc:e(Q2, {unique_all,false}),
+ [{1,_},{2,_}] = qlc:e(Q2, {unique_all,true})">>,
+
+ <<"[] = qlc:e(qlc:q([X || X <- []], {unique, true})),
+ [] = qlc:e(qlc:q([X || X <- qlc:q([X || X <- qlc:append([])],
+ {unique,true})]))">>,
+
+ <<"Q1 = qlc:q([{X,make_ref()} || {X,_} <- [{1,a},{1,b}]]),
+ [{1,_},{1,_}] = qlc:e(Q1, {unique_all, true}),
+ Q2 = qlc:q([Y || {X,_} <- [{1,a},{1,b}],
+ begin Y = {X,make_ref()}, true end]),
+ [{1,_},{1,_}] = qlc:e(Q2, {unique_all,true}),
+ Q3 = qlc:q([Y || X <- [{1,a},{2,a}],
+ begin {_,Z} = X, Y = {Z,make_ref()}, true end]),
+ [{a,_},{a,_}] = qlc:e(Q3, {unique_all, true})">>,
+
+ <<"E = ets:new(apa, [duplicate_bag]),
+ ets:insert(E, [{1,a},{2,a},{1,a}]),
+ H1 = qlc:q([X || X <- qlc:append(ets:table(E),[7,3,5])],
+ {cache, true}),
+ [{_,a},{_,a},{_,a},7,3,5] = qlc:e(H1),
+ ets:delete(E)">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ H = qlc:q([X || X <- [1,3,2,4]], cache),
+ Q = qlc:q([X || X <- H]),
+ [1,3,2,4] = qlc:fold(F, [], Q, [])">>,
+
+ <<"F = fun(Obj, A) -> A++[Obj] end,
+ E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{1,a}]),
+ Q1 = qlc:q([X || X <- ets:table(E)], [cache, unique]),
+ Q = qlc:q([X || X <- Q1], [cache, unique]),
+ {qlc, _, [{generate, _,{table,_}}], [{unique,true}]} = i(Q),
+ R = qlc:fold(F, [], Q, []),
+ ets:delete(E),
+ true = [{1,a},{2,b}] == lists:sort(R)">>,
+
+ <<"E = ets:new(apa, [duplicate_bag]),
+ ets:insert(E, [{1,a},{2,b},{1,a}]),
+ H1 = qlc:q([X || X <- qlc:append(ets:table(E),[7,3])], cache),
+ H2 = qlc:q([{Y,X} || Y <- [2,1,3], X <- H1]),
+ [{2,_},{2,_},{2,_},{2,7},{2,3},
+ {1,_},{1,_},{1,_},{1,7},{1,3},
+ {3,_},{3,_},{3,_},{3,7},{3,3}] = qlc:e(H2),
+ ets:delete(E)">>,
+
+ %% This case is not 100 percent determined. An Ets table
+ %% is updated in a filter and later used in a generator.
+ <<"E = ets:new(apa, [bag]),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ H1 = qlc:q([Y || Y <- ets:table(E)],
+ [{cache, no}, {unique, true}]),
+ H = qlc:q([{X,Y} || X <- [{1,a},{2,d},{3,e}],
+ ets:insert(E, X),
+ Y <- H1]),
+ [{{1,a},_}, {{1,a},_}, {{2,d},_}, {{2,d},_}, {{2,d},_},
+ {{3,e},_}, {{3,e},_}, {{3,e},_}, {{3,e},_}] = qlc:e(H),
+ ets:delete(E)">>,
+
+ %% This case is not 100 percent determined. An Ets table
+ %% is updated in a filter and later used in a generator.
+ <<"E = ets:new(apa, [bag]),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ H1 = qlc:q([Y || Y <- ets:table(E)],
+ [{cache, true}, {unique, true}]),
+ H = qlc:q([{X,Y} || X <- [{1,a},{2,d},{3,e}],
+ ets:insert(E, X),
+ Y <- H1]),
+ [{{1,a},_}, {{1,a},_}, {{2,d},_}, {{2,d},_}, {{3,e},_}, {{3,e},_}] =
+ qlc:e(H),
+ ets:delete(E)">>,
+
+ <<"%% {5979} and {5549} are both hashed to 28244 by phash2/1
+ E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{5979},{5549},{5979},{5549},{0}]),
+ H1 = qlc:q([X || X <- ets:table(E)],
+ [{cache, true}, {unique, true}]),
+ H = qlc:q([Y || _ <- [1,2], Y <- H1]),
+ {qlc, _, [{generate, _, {list, [1,2]}},
+ {generate, _, {qlc, _, [{generate, _, {table,_}}],
+ [{cache,ets},{unique,true}]}}],
+ []} = i(H),
+ [{0},{0},{5549},{5549},{5979},{5979}] = lists:sort(qlc:e(H)),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H1 = qlc:q([X || X <- ets:table(E)], [cache, unique]),
+ H2 = qlc:q([X || Y <- [3,4], ets:insert(E, {Y}), X <- H1]),
+ {qlc, _, [{generate, _, {list, [3,4]}}, _,
+ {generate, _, {qlc, _, [{generate, _,
+ {table, _}}],
+ [{cache, ets}]}}],
+ []} = i(H2),
+ [{1},{2},{3},{1},{2},{3}] = qlc:e(H2),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(apa, [ordered_set]),
+ ets:insert(E, [{1},{2}]),
+ H1 = qlc:q([X || X <- ets:table(E)], [unique]),
+ H2 = qlc:q([X || Y <- [3,4], ets:insert(E, {Y}), X <- H1]),
+ [{1},{2},{3},{1},{2},{3},{4}] = qlc:e(H2),
+ ets:delete(E)">>,
+
+ <<"H0 = qlc:append([a,b], [c,d]),
+ H = qlc:q([{X,Y} ||
+ X <- H0,
+ Y <- qlc:q([{X1,Y} ||
+ X1 <- H0,
+ Y <- qlc:q([{X2,Y} ||
+ X2 <- H0,
+ Y <- H0])])]),
+ {qlc, _,
+ [{generate, _,{append, [{list, [a,b]}, {list, [c,d]}]}},
+ {generate, _,
+ {qlc, _,
+ [{generate, _, {append,[{list, [a,b]},{list, [c,d]}]}},
+ {generate, _,
+ {qlc, _,
+ [{generate, _,{append,[{list, [a,b]}, {list, [c,d]}]}},
+ {generate, _,{append,[{list, [a,b]}, {list, [c,d]}]}}],
+ [{cache,ets}]}}],
+ [{cache,ets}]}}],
+ []} = i(H, cache_all)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+cache_list(doc) ->
+ "OTP-6038. The {cache,list} option.";
+cache_list(suite) -> [];
+cache_list(Config) when is_list(Config) ->
+ Ts = [
+ begin
+ PrivDir = ?privdir,
+ [<<"%% unique, cache list. A simple qlc.
+ Options = [{cache,list}, unique],
+ L0 = [1,2,3,4,1,2,3,4],
+ L = qlc_SUITE:table(L0, []),
+ Q1 = qlc:q([X || X <- L], Options),
+ Q = qlc:q([{X,Y} || X <- [a,b], Y <- Q1]),
+ GOptions = [{tmpdir,\"">>, PrivDir, <<"\"}],
+ {qlc,_,[{generate,_,{list,[a,b]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{table,_}}],
+ [{cache,list},{unique,true}]}}],
+ []} = i(Q, GOptions),
+ true = [{X,Y} || X <- [a,b], Y <- [1,2,3,4]] =:=
+ qlc:e(Q, GOptions)">>]
+ end,
+
+ begin
+ MS = ets:fun2ms(fun({X,_}) when X > 1 -> X end),
+ [<<"%% No cache, even if explicit match specification.
+ etsc(fun(E) ->
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ Options = [{cache,list}, unique],
+ Q = qlc:q([{X,Y} ||
+ X <- ets:table(E, {traverse, {select, MS}}),
+ Y <- [1,2,3]],
+ Options),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{list,[1,2,3]}}],
+ [{unique,true}]} = i(Q),
+ true = [{X,Y} || X <- lists:seq(2,10), Y <- [1,2,3]] =:=
+ lists:sort(qlc:e(Q))
+ end, [{keypos,1}], [{I,a} || I <- lists:seq(1, 10)])">>]
+ end,
+
+ <<"%% Temporary files.
+ %% Remove list expression caches when possible. (no visible effect)
+ T = lists:seq(1, 100000), % Huge terms on file
+ F = fun(C) ->
+ Q0 = qlc:q([{X} || X <- [T,T,T], begin X > 0 end],
+ {cache,C}),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ {cache,C}),
+ qlc:q([{X, Y} || Y <- [1], X <- Q1])
+ end,
+ Ql = F(list),
+ Rl = qlc:e(Ql, {max_list_size, 64*1024}),
+ Qe = F(ets),
+ Re = qlc:e(Qe),
+ Qf = F(no),
+ Rf = qlc:e(Qf),
+ Ri = qlc:e(Ql, {max_list_size, 1 bsl 35}), % heavy
+ {27,27,27,27,true,true,true} =
+ {length(Rl), length(Re), length(Rf), length(Ri),
+ Rl =:= Re, Re =:= Rf, Rf =:= Ri}">>,
+
+ <<"%% File sorter.
+ T = lists:seq(1, 10000),
+ F = fun(C) ->
+ Q0 = qlc:q([{X} || X <- [T,T,T], begin X > 0 end],
+ [{cache,C},unique]),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ [{cache,C},unique]),
+ qlc:q([{X, Y} || Y <- [1], X <- Q1])
+ end,
+ GOpt = [{max_list_size, 10000}],
+ Ql = F(list),
+ Rl = qlc:e(Ql, GOpt),
+ Qe = F(ets),
+ Re = qlc:e(Qe, GOpt),
+ Qf = F(no),
+ Rf = qlc:e(Qf, GOpt),
+ {1,1,1,true,true} =
+ {length(Rl), length(Re), length(Rf), Rl =:= Re, Re =:= Rf}">>,
+
+ <<"%% Remove list expression caches when possible. (no visible effect)
+ Q0 = qlc:q([{X} || X <- [1,2,3], begin X > 0 end], {cache,list}),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ {cache,list}),
+ Q = qlc:q([{X, Y} || Y <- [1], X <- Q1]),
+ R = qlc:e(Q),
+ L0 = [{X} || X <- [1,2,3], begin X > 0 end],
+ L1 = [{X,Y,Z} ||
+ X <- L0,
+ Y <- L0,
+ Z <- L0],
+ L = [{X, Y} || Y <- [1], X <- L1],
+ true = R =:= L">>,
+
+ <<"%% No temporary file.
+ L = [{I,a} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table_error(L, 1, err),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ err = qlc:e(Q)">>,
+
+ <<"%% Sort internally.
+ L = [{I,a} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table_error(L, 1, err),
+ begin element(1, X) > 5 end],
+ [unique,{cache,list}]),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ err = qlc:e(Q, {max_list_size,0})">>,
+
+ <<"%% No temporary file.
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ R = lists:sort(qlc:e(Q))
+ end, [{keypos,1}], [{I,a} || I <- lists:seq(1, 10)])">>,
+
+ <<"%% Sort internally
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ [{cache,list},unique]),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ R = lists:sort(qlc:e(Q))
+ end, [{keypos,1}], [{I,a} || I <- lists:seq(1, 10)])">>,
+
+ <<"%% A few more tests of unique and {cache,list}.
+ F = fun(CU) ->
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ X <- [1,2]],
+ CU),
+ qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1])
+ end,
+ Q1 = F([]),
+ Q2 = F([{cache,list}, unique]),
+ R1 = qlc:e(Q1),
+ R2 = qlc:e(Q2),
+ R3 = qlc:e(Q2, {max_list_size, 0}), % still in RAM
+ true = R1 =:= R2,
+ true = R2 =:= R3">>,
+
+ <<"E = ets:new(t, [duplicate_bag]),
+ true = ets:insert(E, [{2},{1},{2}]),
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X} <- ets:table(E)],
+ [{cache,list}, unique]),
+ H2 = qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1]),
+ {qlc,_,[{generate,_,{list,[3,4]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,[a,b]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{table,{ets,table,_}}}],
+ [{cache,list},{unique,true}]}}],
+ [{cache,list},{unique,true}]}}], []} = i(H2),
+ L1s = [[{X,Y} || Y <- [a,b], X <- Xs] || Xs <- [[1,2], [2,1]]],
+ L2s = [[{X,Y,Z} || X <- [3,4], {Y,Z} <- L1] || L1 <- L1s],
+ R1 = qlc:e(H2),
+ R2 = qlc:e(H2, {max_list_size, 0}), % on temporary file
+ ets:delete(E),
+ true = lists:member(R1, L2s),
+ true = R1 =:= R2">>,
+
+ <<"E = ets:new(t, [duplicate_bag]),
+ true = ets:insert(E, [{2},{1},{2}]),
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X} <- ets:table(E)],
+ [{cache,list}]),
+ H2 = qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1]),
+ L1s = [[{X,Y} || Y <- [a,b], X <- Xs] || Xs <- [[1,2,2], [2,2,1]]],
+ L2s = [[{X,Y,Z} || X <- [3,4], {Y,Z} <- L1] || L1 <- L1s],
+ R1 = qlc:e(H2),
+ R2 = qlc:e(H2, {max_list_size, 0}), % on temporary file
+ ets:delete(E),
+ true = lists:member(R1, L2s),
+ true = R1 =:= R2">>,
+
+ <<"Q1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X,_} <- qlc_SUITE:table_error([{a,1}], 2, err)],
+ [{cache,list}, unique]),
+ Q = qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- Q1]),
+ {qlc,_,[{generate,_,{list,[3,4]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,[a,b]}},
+ {generate,_,{table,_}}],
+ [{cache,list},{unique,true}]}}],
+ []} = i(Q),
+ err = qlc:e(Q,{max_list_size,0})">>,
+
+ begin
+ Privdir = ?privdir,
+ [<<"
+ E = ets:new(t, [duplicate_bag]),
+ N = 17000,
+ true = ets:insert(E, [{X,X} || X <- lists:seq(1, N)]),
+ N = ets:info(E, size),
+ RF = fun(GOpts) ->
+ F = fun(CU) ->
+ H1 = qlc:q([{X,Y} ||
+ Y <- [a,b],
+ {X,_} <- ets:table(E)],
+ CU),
+ qlc:q([{X,Y,Z} || X <- [3,4], {Y,Z} <- H1])
+ end,
+ Q1 = F([{cache,list}, unique]),
+ _ = qlc:info(Q1, GOpts),
+ R1 = qlc:e(Q1, GOpts),
+ Q2 = F([unique]),
+ R2 = qlc:e(Q2, GOpts),
+ true = lists:sort(R1) =:= lists:sort(R2)
+ end,
+ GOpts = [{tmpdir,\"">>,Privdir,<<"\"}],
+ RF([{max_list_size, 1 bsl 35} | GOpts]),
+ RF(GOpts),
+ RF([{max_list_size, 40000} | GOpts]),
+ true = ets:insert(E, [{X,X} || X <- lists:seq(1, N)]),
+ true = N+N =:= ets:info(E, size),
+ RF([{max_list_size, 1 bsl 30} | GOpts]),
+ RF(GOpts),
+ RF([{max_list_size, 40000} | GOpts]),
+ ets:delete(E)">>]
+ end,
+
+ <<"%% Temporary file employed.
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ R = lists:sort(qlc:e(Q, {max_list_size, 100*1024}))
+ end, [{keypos,1}], [{I,a,lists:duplicate(100000,1)} ||
+ I <- lists:seq(1, 10)])">>,
+
+ <<"%% Temporary file employed. The file is removed after error.
+ L = [{I,a,lists:duplicate(100000,1)} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table_error(L, 1, err),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ err = qlc:e(Q)">>,
+
+ <<"%% Temporary file employed. The file is removed after error.
+ L = [{I,a,lists:duplicate(100000,1)} || I <- lists:seq(1, 10)],
+ Q0 = qlc:q([X || X <- qlc_SUITE:table(L, 1, []),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} ||
+ X <- lists:seq(1, 5),
+ Y <- Q0]),
+ {error, _, {file_error,_,_}} = qlc:e(Q, {tmpdir, \"/a/b/c\"})">>,
+
+ <<"Q = qlc:q([X || X <- [1,2]]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(Q, {max_list_size, -1}))">>,
+
+ <<"Q = qlc:q([X || X <- [1,2]]),
+ {'EXIT', {badarg, _}} = (catch qlc:e(Q, {max_list_size, foo}))">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+filter(doc) ->
+ "Filters and match specs.";
+filter(suite) -> [];
+filter(Config) when is_list(Config) ->
+ Ts = [
+ <<"L = [1,2,3,4,5],
+ QH1 = qlc:q([X || X <- L, X > 1, X < 4]),
+ [2,3] = qlc:e(QH1),
+ {list,{list,L},_MS} = i(QH1)
+ ">>,
+
+ <<"L = [1,2,3,4,5],
+ QH2 = qlc:q([X || X <- L, X > 1, X < 4, X > 2]),
+ [3] = qlc:e(QH2),
+ {list,{list,L},_MS} = i(QH2)
+ ">>,
+
+ %% "X > 1" is skipped since the matchspec does the job
+ <<"QH3 = qlc:q([X || X <- [1,2,3,4,5], X > 1, begin X < 4 end]),
+ [2,3] = qlc:e(QH3),
+ {qlc,_,[{generate,_,{list,{list,[1,2,3,4,5]},_MS}},_],[]} = i(QH3)
+ ">>,
+
+ <<"QH4 = qlc:q([{X,Y} || X <- [1,2], Y <- [1,2]]),
+ [{1,1},{1,2},{2,1},{2,2}] = qlc:e(QH4),
+ {qlc,_,[{generate,_,{list,[1,2]}},{generate,_,{list,[1,2]}}],[]} =
+ i(QH4)">>,
+
+ %% "X > 1" is skipped since the matchspec does the job
+ <<"QH5 = qlc:q([{X,Y} || X <- [1,2], X > 1, Y <- [1,2]]),
+ [{2,1},{2,2}] = qlc:e(QH5),
+ {qlc,_,[{generate,_,{list,{list,[1,2]},_MS}},
+ {generate,_,{list,[1,2]}}],[]} =
+ i(QH5)">>,
+
+ <<"%% Binaries are not handled at all when trying to find lookup values
+ etsc(fun(E) ->
+ A = 2,
+ Q = qlc:q([X || {X} <- ets:table(E), <<A>> =:= <<X>>]),
+ [2] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{1},{2},{3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,_} <- ets:table(E),
+ qlc:e(qlc:q([Y || {Y,_} <- ets:table(E),
+ Y > X])) == []]),
+ [3] = qlc:e(Q)
+ end, [{1,a},{2,b},{3,c}])">>,
+
+ <<"Q = qlc:q([X || {X} <- [], (false or (X/0 > 3))]),
+ \"[]\" = qlc:info(Q),
+ [] = qlc:e(Q)">>,
+
+ <<"%% match spec
+ [] = qlc:e(qlc:q([X || {X} <- [{1},{2}],
+ (false orelse (X/0 > 3))])),
+ %% generated code
+ {'EXIT', {badarith, _}} =
+ (catch qlc:e(qlc:q([X || {X} <- [{1}],
+ begin (false orelse (X/0 > 3)) end])))">>,
+
+ <<"%% Partial evaluation in filter.
+ etsc(fun(E) ->
+ QH = qlc:q([{X+1,Y} || {X,Y} <- ets:table(E),
+ X =:= 1-1+1+(+1)]),
+ [{3,2}] = qlc:e(QH),
+ [2] = lookup_keys(QH)
+ end, [{1,1},{2,2},{3,3}])">>,
+
+ <<"%% =/2 in filters must not be recognized when 'badmatch' is
+ %% possible.
+ etsc(fun(E) ->
+ QH = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ ((Y = X) =:= 3)]),
+ {'EXIT', {{badmatch,4},_}} = (catch qlc:e(QH)),
+ false = lookup_keys(QH)
+ end, [{3,3},{4,true}])">>,
+
+ <<"%% One more of the same kind.
+ etsc(fun(E) ->
+ QH = qlc:q([{X,Y} || {X,_} <- ets:table(E),
+ (Y=X) =:= (Y=1+1)]),
+ {'EXIT', {{badmatch,2},_}} = (catch qlc:e(QH)),
+ false = lookup_keys(QH)
+ end, [{1,1},{2,2},{3,3}])">>,
+
+ <<"%% OTP-5195. Used to return a value, but badarith is correct.
+ etsc(fun(E) ->
+ QH = qlc:q([X || {X,_} <- ets:table(E),
+ (X =:= 1) and
+ if X =:= 1 -> true;
+ true -> X/0
+ end]),
+ {'EXIT',{badarith,_}} = (catch qlc:e(QH)),
+ false = lookup_keys(QH)
+ end, [{1,1},{2,2},{3,3}])">>,
+
+ <<"fun(Z) ->
+ Q = qlc:q([X || Z < 2, X <- [1,2,3]]),
+ [] = qlc:e(Q)
+ end(3)">>,
+
+ <<"H = qlc:q([{P1,A,P2,B,P3,C} ||
+ P1={A,_} <- [{1,a},{2,b}],
+ {_,B}=P2 <- [{1,a},{2,b}],
+ C=P3 <- [1],
+ {[X,_],{_,X}} <- [{[1,2],{3,1}}, {[a,b],{3,4}}],
+ A > 0,
+ B =/= c,
+ C > 0]),
+ L = [{{1,a},1,{1,a},a,1,1}, {{1,a},1,{2,b},b,1,1},
+ {{2,b},2,{1,a},a,1,1}, {{2,b},2,{2,b},b,1,1}],
+ L = qlc:e(H)">>,
+
+ <<"H = qlc:q([{X,Y} ||
+ X = _ <- [1,2,3],
+ _ = Y <- [a,b,c],
+ _ = _ <- [foo],
+ X > 1,
+ Y =/= a]),
+ [{2,b},{2,c},{3,b},{3,c}] = qlc:e(H)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+info(doc) ->
+ "info/2.";
+info(suite) -> [];
+info(Config) when is_list(Config) ->
+ Ts = [
+ <<"{list, [1,2]} = i(qlc:q([X || X <- [1,2]])),
+ {append,[{list, [1,2]}, {list, [3,4]}]} =
+ i(qlc:append([1,2],[3,4])),
+ {sort,{list, [1,2]},[]} = i(qlc:sort([1,2])),
+ E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ {table, _} = i(ets:table(E)),
+ true = ets:delete(E),
+ {list, [1,2]} = i([1,2]),
+ {append, [{list, [1,2]}, {list, [3,4]}]} =
+ i(qlc:q([X || X <- qlc:append([1,2],[3,4])])),
+
+ H0 = qlc:q([X || X <- throw({throw,t})]),
+ {throw,t} = (catch {any_term,qlc:info(H0)}),
+ {'EXIT', {badarg, _}} =
+ (catch qlc:info(foobar)),
+ {'EXIT', {badarg, _}} =
+ (catch qlc:info(qlc:q([X || X <- [1,2]]), badarg))">>,
+
+ <<"{'EXIT', {badarg, _}} =
+ (catch qlc:info([X || {X} <- []], {n_elements, 0})),
+ L = lists:seq(1, 1000),
+ \"[1,2,3,4,5,6,7,8,9,10|'...']\" = qlc:info(L, {n_elements, 10}),
+ {cons,1,{integer,1,1},{atom,1,'...'}} =
+ qlc:info(L, [{n_elements, 1},{format,abstract_code}]),
+ Q = qlc:q([{X} || X <- [a,b,c,d,e,f]]),
+ {call,_,_,[{cons,_,{atom,_,a},{cons,_,{atom,_,b},{cons,_,{atom,_,c},
+ {atom,_,'...'}}}},
+ {call,_,_,_}]} =
+ qlc:info(Q, [{n_elements, 3},{format,abstract_code}]),
+ \"ets:match_spec_run([a,b,c,d,e,f],\n\"
+ \" ets:match_spec_compile([{'$1',[true],\"
+ \"[{{'$1'}}]}]))\" =
+ qlc:info(Q, [{n_elements, infinity}])">>,
+
+ <<"Q1 = qlc:q([{X} || X <- qlc:q([X || X <- [1,2]])]),
+ {qlc, _, [{generate, _, {list, [1,2]}}],[]} = i(Q1),
+ Q2 = qlc:q([X || X <- qlc:q([{X} || X <- [1,2]])]),
+ {list,{list,[1,2]},_} = i(Q2),
+ [{1},{2}] = qlc:eval(Q2),
+ Q3 = qlc:q([{X,Y} || X <- qlc:q([X || X <- [a,b]]),
+ Y <- qlc:q([Z || Z <- [a,b]])]),
+ {qlc, _, [{generate, _, {list, [a,b]}},
+ {generate, _, {list, [a,b]}}], []} = i(Q3),
+ Q4 = qlc:q([X || X <- [a]]),
+ {list, [a]} = i(Q4),
+ Q5 = qlc:q([X || X <- qlc:q([Y || Y <- [a,b]], unique)]),
+ {qlc, _, [{generate, _, {list, [a,b]}}], [{unique,true}]} =
+ i(Q5)">>,
+
+ <<"H = qlc:q([X || X <- qlc:append([qlc:q([X || X <- [1,2]]),[1,2]])]),
+ {append, [{list, [1,2]},{list, [1,2]}]} = i(H),
+ [1,2,1,2] = qlc:e(H)">>,
+
+ <<"H = qlc:q([{X} || X <- [], X > 1]),
+ {list, []} = i(H),
+ [] = qlc:e(H)">>,
+
+ <<"H1 = qlc:q([{X} || X <- [], X > 1]),
+ H = qlc:q([{X} || X <- H1, X < 10]),
+ {list, []} = i(H),
+ [] = qlc:e(H)">>,
+
+ <<"L = [1,2,3],
+ QH1 = qlc:q([{X} || X <- L, X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1]),
+ [{{2}},{{3}}] = qlc:e(QH2),
+ {list,{list,{list,L},_},_} = i(QH2)">>,
+
+ <<"H = qlc:q([X || X <- qlc:q([Y || Y <- qlc:q([Z || Z <-[1,2,1]])])]),
+ {list, [1,2,1]} = i(H),
+ [1,2,1] = qlc:eval(H)">>,
+
+ <<"%% Used to replace empty ETS tables with [], but that won't work.
+ E = ets:new(apa,[]),
+ QH1 = qlc:q([{X} || X <- ets:table(E), X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1], cache),
+ [] = qlc:e(QH2),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}}],[]} = i(QH2),
+ ets:delete(E)">>,
+
+ <<"Q1 = qlc:q([W || W <- [a,b]]),
+ Q2 = qlc:q([Z || Z <- qlc:sort([1,2,300])], unique),
+ Q3 = qlc:q([{X,Y} || X <- qlc:keysort([2], [{1,a}]),
+ Y <- qlc:append([Q1, Q2]),
+ X > Y]),
+ {qlc, T1,
+ [{generate, P1, {list, [{1,a}]}},
+ {generate, P2, {append, [{list, [a,b]},
+ {qlc, T2, [{generate, P3,
+ {sort, {list,[1,2,300]},[]}}],
+ [{cache,ets},{unique,true}]}]}},F],
+ []} = i(Q3, cache_all),
+ {tuple, _, [{var,_,'X'}, {var,_,'Y'}]} = binary_to_term(T1),
+ {var, _, 'X'} = binary_to_term(P1),
+ {var, _, 'Y'} = binary_to_term(P2),
+ {var, _, 'Z'} = binary_to_term(P3),
+ {var, _, 'Z'} = binary_to_term(T2),
+ {op, _, '>', {var, _, 'X'}, {var, _, 'Y'}} = binary_to_term(F),
+ true = binary_to_list(<<
+ \"beginV1=qlc:q([Z||Z<-qlc:sort([1,2,300],[])],[{unique,true}]),\"
+ \"qlc:q([{X,Y}||X<-[{1,a}],Y<-qlc:append([[a,b],V1]),X>Y])end\"
+ >>) == format_info(Q3, true)">>,
+
+ <<"Q1 = qlc:q([{X} || X <- qlc:q([X || X <- [a,b]])]),
+ {qlc, _, [{generate, _, {list, [a,b]}}], []} = i(Q1),
+ Q2 = qlc:q([X || X <- qlc:q([{X} || X <- [a,b]])]),
+ {list,{list,[a,b]},_} = i(Q2),
+ [{a},{b}] = qlc:eval(Q2)">>,
+
+ <<"Q = qlc:keysort(2, [{1,a,b},{2,b,c},{3,4,c}]),
+ {keysort,{list,[{1,a,b},{2,b,c},{3,4,c}]},2,[]} = i(Q),
+ true = binary_to_list(<<
+ \"qlc:keysort(2,[{1,a,b},{2,b,c},{3,4,c}],[])\">>)
+ == format_info(Q, true),
+ [{3,4,c},{1,a,b},{2,b,c}] = qlc:e(Q)">>,
+
+ <<"E = ets:new(foo, []),
+ ets:insert(E, [{1},{2}]),
+ Q = qlc_SUITE:default_table(E),
+ {table,{'$MOD','$FUN',[]}} = i(Q),
+ true = binary_to_list(<<\"'$MOD':'$FUN'()\">>)
+ == format_info(Q, true),
+ true = ets:delete(E)">>,
+
+ <<"\"[]\" = qlc:info([], flat),
+ \"[]\" = qlc:info([]),
+ \"[]\" = qlc:info([], {flat, true})">>,
+
+ <<"H = qlc:q([{X} || X <- [a,b]]),
+ \"ets:match_spec_run([a,b],ets:match_spec_compile(\" ++ _ =
+ format_info(H, true),
+ \"ets:match_spec_run([a,b],ets:match_spec_compile(\" ++ _ =
+ format_info(H, false)">>,
+
+ <<"H = qlc:q([{X} || X <- [a,b], begin true end]),
+ true = binary_to_list(<<\"qlc:q([{X}||X<-[a,b],begintrueend])\">>)
+ == format_info(H, true),
+ true = binary_to_list(<<\"qlc:q([{X}||X<-[a,b],begintrueend])\">>)
+ == format_info(H, false)">>,
+
+ <<"H = qlc:q([A || {A} <- [{1},{2}], (A =:= 2) andalso true]),
+ {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_run}},_} =
+ qlc:info(H, {format,abstract_code})">>,
+
+ <<"H = qlc:q([{X} || X <- qlc:q([{X} || X <- [a,b], begin true end],
+ unique),
+ begin true end]),
+ true = binary_to_list(<<
+ \"beginV1=qlc:q([{X}||X<-[a,b],begintrueend],[{unique,true}]),\"
+ \"qlc:q([{X}||X<-V1,begintrueend])end\">>) ==
+ format_info(H, true),
+ true = binary_to_list(<<
+ \"qlc:q([{X}||X<-qlc:q([{X}||X<-[a,b],begintrueend],\"
+ \"[{unique,true}]),begintrueend])\">>) == format_info(H, false)">>,
+
+ <<"H0 = qlc:q([{V3} || V3 <- qlc:q([{V1} || V1 <- [a,b],
+ begin true end], unique),
+ begin true end]),
+ H = qlc:sort(H0),
+ true = binary_to_list(<<
+ \"qlc:sort(qlc:q([{V3}||V3<-qlc:q([{V1}||\"
+ \"V1<-[a,b],begintrueend],[{unique,true}]),begintrueend]),[])\">>)
+ == format_info(H, false),
+ true = binary_to_list(<<
+ \"beginV2=qlc:q([{V1}||V1<-[a,b],begintrueend],[{unique,true}]),\"
+ \"V4=qlc:q([{V3}||V3<-V2,begintrueend]),qlc:sort(V4,[])end\">>)
+ == format_info(H, true)">>,
+
+ <<"H0 = qlc:q([X || X <- [true], begin true end]),
+ H1 = qlc:q([{X} || X <- [a,b], begin true end],
+ [{unique,begin [T] = qlc:e(H0), T end}]),
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},
+ [{lc,_,{tuple,_,[{var,_,'X'}]},
+ [{generate,_,{var,_,'X'},
+ {cons,_,{atom,_,a},_}},
+ {block, _, [{atom, _, true}]}]},
+ {cons,_,_,_}]} = i(H1, {format, abstract_code})">>,
+
+ <<"E = ets:new(apa, [duplicate_bag]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c},{4,d}]),
+ QH = qlc:q([X || {X,_} <- ets:tab2list(E), X > 2], unique),
+ {qlc, _, [{generate, _, {list, _, _MS}}], [{unique, true}]} =
+ i(QH),
+ [3,4] = lists:sort(qlc:e(QH)),
+ ets:delete(E)">>,
+
+ %% "Imported" variable.
+ <<"F = fun(U) -> qlc:q([{X} || X <- [1,2,3,4,5,6], X > U]) end,
+ QH = F(4),
+ {call, _ ,
+ {remote, _, {atom, _, ets},{atom, _, match_spec_run}},
+ [{string, _, [1,2,3,4,5,6]},
+ {call, _,
+ _compile,
+ [{cons, _,
+ {tuple, _,
+ [{atom, _,'$1'},
+ {cons, _,
+ {tuple,
+ _,
+ [{atom, _,'>'},
+ {atom, _,'$1'},
+ {tuple,
+ _,
+ [{atom, _,const},
+ {integer, _,4}]}]},
+ _},
+ {cons, _, _, _}]},
+ {nil,_}}]}]} = i(QH, {format, abstract_code}),
+ [{5},{6}] = qlc:e(QH),
+ [{4},{5},{6}] = qlc:e(F(3))">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+nested_info(doc) ->
+ "Nested QLC expressions. QLC expressions in filter and template.";
+nested_info(suite) -> [];
+nested_info(Config) when is_list(Config) ->
+ Ts = [
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,R} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ begin % X imported
+ R = qlc:e(qlc:q([{X,Y} || {Y,_}
+ <- qlc_SUITE:table(L, []),
+ Y > X])),
+ true
+ end]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,R}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}]),\"
+ \"beginR=qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),Y>X]))\"
+ \",trueend])\">>) == format_info(Q, true),
+ [{1,[{1,2},{1,3}]},{2,[{2,3}]},{3,[]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q( % X imported
+ [{X,qlc:e(qlc:q([{X,Y} || {Y,_} <- qlc_SUITE:table(L, []),
+ Y > X]))} ||
+ {X,_} <- qlc_SUITE:table(L, [])]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),\"
+ \"Y>X]))}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}])])\">>)
+ == format_info(Q, true),
+ [{1,[{1,2},{1,3}]},{2,[{2,3}]},{3,[]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,R} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ begin % X imported
+ R = qlc:e(qlc:q([{X,Y} || {Y,_}
+ <- qlc_SUITE:table(L, []),
+ Y =:= X])),
+ true
+ end]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,R}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}]),\"
+ \"beginR=qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),\"
+ \"Y=:=X])),trueend])\">>) == format_info(Q, true),
+ [{1,[{1,1}]},{2,[{2,2}]},{3,[{3,3}]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X, % X imported
+ qlc:e(qlc:q([{X,Y} || {Y,_} <- qlc_SUITE:table(L, []),
+ Y =:= X]))} ||
+ {X,_} <- qlc_SUITE:table(L, [])]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,qlc:e(qlc:q([{X,Y}||{Y,_}<-qlc_SUITE:table(L,[]),\"
+ \"Y=:=X]))}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}])])\">>)
+ == format_info(Q, true),
+ [{1,[{1,1}]},{2,[{2,2}]},{3,[{3,3}]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,R} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ begin
+ R = qlc:e(qlc:q([Y || Y <- [X]])),
+ true
+ end]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,R}||{X,_}<-qlc_SUITE:the_list([{1,a},{2,b},{3,c}]),\"
+ \"beginR=qlc:e(qlc:q([Y||Y<-[X]])),trueend])\">>)
+ == format_info(Q, true),
+ [{1,[1]},{2,[2]},{3,[3]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ Q = qlc:q(
+ [{X,qlc:e(qlc:q([Y || Y <- [X]]))} ||
+ {X,_} <- qlc_SUITE:table(L, [])]),
+ true = binary_to_list(<<
+ \"qlc:q([{X,qlc:e(qlc:q([Y||Y<-[X]]))}||{X,_}<-qlc_SUITE:\"
+ \"the_list([{1,a},{2,b},{3,c}])])\">>) == format_info(Q, true),
+ [{1,[1]},{2,[2]},{3,[3]}] = qlc:e(Q)">>,
+
+ <<"L = [{1,a},{2,b}],
+ Q = qlc:q(
+ [{X,Y} ||
+ {X,_} <- qlc_SUITE:table(L, []),
+ {Y,_} <- qlc:q(
+ [{Z,V} ||
+ {Z,_} <- qlc_SUITE:table(L, []),
+ {V} <- qlc:q(
+ [{W} || W
+ <- qlc_SUITE:table(L, [])])
+ ])
+ ]),
+ true = binary_to_list(<<
+ \"beginV1=qlc:q([{W}||W<-qlc_SUITE:the_list([{1,a},{2,b}])]),\"
+ \"V2=qlc:q([{Z,V}||{Z,_}<-qlc_SUITE:the_list([{1,a},{2,b}]),\"
+ \"{V}<-V1]),qlc:q([{X,Y}||{X,_}<-qlc_SUITE:the_list([{1,a},\"
+ \"{2,b}]),{Y,_}<-V2])end\">>) == format_info(Q, true),
+ [{1,1},{1,1},{1,2},{1,2},{2,1},{2,1},{2,2},{2,2}] = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+
+lookup1(doc) ->
+ "Lookup keys. Mostly test of patterns.";
+lookup1(suite) -> [];
+lookup1(Config) when is_list(Config) ->
+ Ts = [
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || {A=3} <- ets:table(E)]),
+ [3] = qlc:eval(Q),
+ [3] = lookup_keys(Q)
+ end, [{1},{2},{3},{4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || {A=3} <- ets:table(E)],{max_lookup,0}),
+ [3] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{1},{2},{3},{4}])">>,
+
+ <<"%% The lookup and max_lookup options interact.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ (X =:= 1) or (X =:= 2)],
+ [{lookup,true},{max_lookup,1}]),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch qlc:e(Q))
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} || {A,B}={C,D} <- ets:table(E)]),
+ [{1,2,1,2},{3,4,3,4}] = lists:sort(qlc:eval(Q)),
+ false = lookup_keys(Q)
+ end, [{1,2},{3,4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E)]),
+ [{1,1,1},{2,2,2}] = lists:sort(qlc:eval(Q)),
+ false = lookup_keys(Q)
+ end, [{1,2},{2,2},{1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E),
+ (D =:= 2) or (B =:= 1)],
+ {max_lookup,infinity}),
+ [{1,1,1},{2,2,2}] = qlc:eval(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{1,2},{2,2},{1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E),
+ (D =:= 2) xor (B =:= 1)]),
+ [{1,1,1},{2,2,2}] = qlc:eval(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{1,2},{2,2},{1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([3 || {{[3,4],apa}} <- ets:table(E)]),
+ [3] = qlc:e(Q),
+ [{[3,4],apa}] = lookup_keys(Q)
+ end, [{{[4,3],foo}},{{[3,4],apa}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([3 || {3} <- ets:table(E)]),
+ [3] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{2},{3},{4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y,Z} || {{X,_},Y,Y={_,Z},X,Y} <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{1,1},1,{1,1},1,1}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,X=2} <- ets:table(E)]),
+ [2] = qlc:e(Q),
+ [2] = lookup_keys(Q)
+ end, [{2,2},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{_,3}={4,_}=X} <- ets:table(E)]),
+ [{4,3}] = qlc:e(Q),
+ [{4,3}] = lookup_keys(Q)
+ end, [{{2,3}},{{4,3}}])">>,
+
+ <<"U = 17.0,
+ etsc(fun(E) ->
+ Q = qlc:q([X || {_=X=_} <- ets:table(E)]),
+ [U] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{U},{U+U,U}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y,Z,W} || {X=Y}=Z={V=W} <- ets:table(E),
+ V == {h,g}]),
+ [{{h,g},{h,g},{{h,g}},{h,g}}] = qlc:e(Q),
+ [{h,g}] = lookup_keys(Q)
+ end, [{h,g},{{h,g}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{C,Y,Z,X} || {{X=Y}=Z}={{A=B}=C} <- ets:table(E),
+ A == a, B =/= c]),
+ [{{a},a,{a},a}] = qlc:e(Q),
+ [{a}] = lookup_keys(Q)
+ end, [{{1}},{{a}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,D,Y,X} ||
+ {{A={B=C}},{D={C}}} = {X,Y} <- ets:table(E),
+ [] == B]),
+ [{{[]},{[]},{{[]}},{{[]}}}] = qlc:e(Q),
+ [{{[]}}] = lookup_keys(Q)
+ end, [{{{[]}},{{[]}}}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X}=X <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1},{a}])">>,
+ {warnings,[{{2,37},qlc,nomatch_pattern}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X=X,Y=Y}={Y=Y,X=X} <- ets:table(E),
+ {} == X]),
+ [{}] = qlc:e(Q),
+ [{}] = lookup_keys(Q)
+ end, [{{},{}},{[],[]}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {3+4=7,X} <- ets:table(E),
+ X =:= 3+997]),
+ [1000] = qlc:e(Q),
+ [7] = lookup_keys(Q)
+ end, [{7,1000},{8,1000}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X, Y} || [X]=[Y] <- ets:table(E)]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end, [{a}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X={1,2,3,X,5} <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+ {warnings,[{{2,35},qlc,nomatch_pattern}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X=[1,2,3,X,5] <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+ {warnings,[{{2,35},qlc,nomatch_pattern}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X = <<X>> <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+
+ <<"Tre = 3.0,
+ etsc(fun(E) ->
+ Q = qlc:q([{A,B} || {A,B}={{a,C},{a,C}} <- ets:table(E),
+ C =:= Tre]),
+ [] = qlc:e(Q),
+ [{a,Tre}] = lookup_keys(Q)
+ end, [{a,b}])">>,
+
+ <<"A = 3,
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E), A =:= element(1, X)]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,a},{3,3}])">>,
+
+ <<"A = 3,
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E), A =:= erlang:element(1, X)]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,a},{3,3}])">>,
+
+ <<"A = 3,
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E), A =:= {erlang,element}(1, X)]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,a},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ A = 3,
+ Q = qlc:q([X || X <- ets:table(E),
+ A == element(1,X),
+ element(1,X) =:= a]),
+ [] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a},{b},{c}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X = {[a,Z]},
+ Z = [foo, {[Y]}],
+ Y = {{foo,[X]}}} <- ets:table(E)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a,b,c},{d,e,f}])">>,
+ {warnings,[{{2,34},qlc,nomatch_pattern}]}}
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+lookup2(doc) ->
+ "Lookup keys. Mostly test of filters.";
+lookup2(suite) -> [];
+lookup2(Config) when is_list(Config) ->
+ Ts = [
+ <<"%% Only guards are inspected. No lookup.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ ((Y = X) =:= 3)]),
+ {'EXIT', {{badmatch,4},_}} = (catch qlc:e(Q))
+ end, [{3,3},{4,true}])">>,
+
+ <<"%% Only guards are inspected. No lookup.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ Y = (X =:= 3)]),
+ {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q))
+ end, [{false,3},{true,3}])">>,
+
+ <<"%% Only guards are inspected. No lookup.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ Y = (X =:= 3)]),
+ {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q))
+ end, [{3,true},{4,true}])">>,
+
+ <<"%% Only guards are inspected. No lookup.
+ E1 = create_ets(1, 10),
+ E2 = ets:new(join, []),
+ true = ets:insert(E2, [{true,1},{false,2}]),
+ Q = qlc:q([{X,Z} || {_,X} <- ets:table(E1),
+ {Y,Z} <- ets:table(E2),
+ Y = (X =:= 3)]),
+ {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,D} || {A,B}={D,A} <- ets:table(E),
+ (A =:= 3) or (4 =:= D)]),
+ [{3,3,3},{4,4,4}] = lists:sort(qlc:e(Q)),
+ [3,4] = lookup_keys(Q)
+ end, [{2,2},{3,3},{4,4}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,U} <- ets:table(E), X =:= U]),
+ [1] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,1}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E),
+ {[X],4} =:= {[3],X}]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+ {warnings,[{{3,46},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X == 1, X =:= 2]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+ {warnings,[{{3,43},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E),
+ {[X,Y],4} =:= {[3,X],X}]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+ {warnings,[{{3,48},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E),
+ ({X,3} =:= {Y,Y}) or (X =:= 4)]),
+ [{3,3},{4,4}] = lists:sort(qlc:e(Q)),
+ [3,4] = lookup_keys(Q)
+ end, [{2,2},{3,3},{4,4},{5,5}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), {[X]} =:= {[3,4]}]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{[3]},{[3,4]}])">>,
+ {warnings,[{{2,61},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ U = 18,
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E), [X|a] =:= [3|U]]),
+ [] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{2}, {3}])">>,
+
+ <<"etsc(fun(E) ->
+ U = 18, V = 19,
+ Q = qlc:q([{X,Y} || {X=Y} <- ets:table(E),
+ [X|V] =:= [3|U+1]]),
+ [{3,3}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{2},{3}])">>,
+
+ <<"%% Blocks are not handled.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), begin X == a end]),
+ [a] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{a},{b}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ (3 =:= X) or (X =:= 12),
+ (8 =:= X) or (X =:= 10)]),
+ [] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{2},{3},{4},{8}])">>,
+ {warnings,[{{4,44},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ ((3 =:= X) or (X =:= 12))
+ and ((8 =:= X) or (X =:= 10))]),
+ [] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{2},{3},{4},{8}])">>,
+ {warnings,[{{4,35},qlc,nomatch_filter}]}},
+
+ <<"F = fun(U) ->
+ Q = qlc:q([X || {X} <- [a,b,c],
+ X =:= if U -> true; true -> false end]),
+ [] = qlc:eval(Q),
+ false = lookup_keys(Q)
+ end,
+ F(apa)">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X=1,X} <- ets:table(E), X =:= 2]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,1},{2,1}])">>,
+ {warnings,[{{2,61},qlc,nomatch_filter}]}},
+
+ <<"Two = 2.0,
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), X =:= Two]),
+ [Two] = qlc:e(Q),
+ [Two] = lookup_keys(Q)
+ end, [{2.0},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ %% This float is equal (==) to an integer. Not a constant!
+ Q = qlc:q([X || {X} <- ets:table(E), X == {a,b,c,[2.0]}]),
+ [_,_] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{a,b,c,[2]}},{{a,b,c,[2.0]}}])">>,
+
+ <<"%% Must _not_ regard floats as constants. Check imported variables
+ %% in runtime.
+ etsc(fun(E) ->
+ U = 3.0,
+ QH = qlc:q([X || {X,_} <- ets:table(E), X =:= U]),
+ [] = qlc:e(QH),
+ [U] = lookup_keys(QH)
+ end, [{1,a},{2,b},{3,c},{4,d}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ length(X) =:= 1]),
+ [[1]] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{[1]},{[2,3]}])">>,
+
+ <<"etsc(fun(E) ->
+ A=3,
+ Q = qlc:q([X || {X,Y} <- ets:table(E), X =:= A, Y =:= 3]),
+ [3] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{3,3},{4,3}])">>,
+
+ <<"etsc(fun(E) ->
+ A = 1,
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 1, <<X>> =:= <<A>>]),
+ [1] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), X == a]),
+ [a] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a},{b},{c}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X}=Y <- ets:table(E),
+ element(2, Y) == b,
+ X =:= 1]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,b},{2,3}])">>,
+ {warnings,[{{3,48},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), element(1,{X}) =:= 1]),
+ [1] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E), 1 =:= element(1,{X})]),
+ [1] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1},
+ element(1,X) =:= 2]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+ {warnings,[{{4,47},qlc,nomatch_filter}]}},
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1},
+ element(1,X) =:= element(1, {2})]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+ {warnings,[{{4,47},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ element(1,X) =:= 1, X =:= {1}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ {{element(1,element(1,{{1}}))}} =:= {X}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}},{{2}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ {element(1,element(1, {{1}}))} =:=
+ {element(1,X)}]),
+ [{1}] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ (X =:= 1) and (Y =:= 2)
+ or (X =:= 3) and (Y =:= 4)]),
+ [1,3] = lists:sort(qlc:e(Q)),
+ [{1,2}, {3,4}] = lookup_keys(Q)
+ end, [{{1,2}}, {{3,4}}, {{2,3}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,a}} <- ets:table(E), X =:= 3]),
+ [3] = qlc:e(Q),
+ [{3,a}] = lookup_keys(Q)
+ end, [{{3,a}},{{3,b}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y},_Z} <- ets:table(E),
+ X =:= 3, Y =:= a]),
+ [3] = qlc:e(Q),
+ [{3,a}] = lookup_keys(Q)
+ end, [{{3,a},3}, {{4,a},3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y},_Z} <- ets:table(E),
+ (X =:= 3) and (Y =:= a)
+ or (X =:= 4) and (Y =:= a)]),
+ [3,4] = qlc:e(Q),
+ [{3,a}, {4,a}] = lookup_keys(Q)
+ end, [{{3,a},3}, {{4,a},3}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ (X =:= 3) and (X =:= a)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{3}, {4}])">>,
+ {warnings,[{{3,44},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ X =:= 3, ((Y =:= a) or (Y =:= b))]),
+ [3,3] = qlc:e(Q),
+ [{3,a},{3,b}] = lists:sort(lookup_keys(Q))
+ end, [{{3,a}},{{2,b}},{{3,b}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ ((X =:= 3) or (Y =:= 4)) and (X == a)]),
+ [a] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a,4},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ (X =:= 3) or ((Y =:= 4) and (X == a))]),
+ [3,a] = lists:sort(qlc:e(Q)),
+ [3,a] = lookup_keys(Q)
+ end, [{a,4},{3,3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ (X =:= 3) or ((Y =:= 4) and (X == a))]),
+ [3,a] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{{3,a}},{{2,b}},{{a,4}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ ((X =:= 3) or (Y =:= 4)) and (X == a)]),
+ [a] = lists:sort(qlc:e(Q)),
+ [{a,4}] = lookup_keys(Q)
+ end, [{{3,a}},{{2,b}},{{a,4}}])">>,
+
+ <<"etsc(fun(E) ->
+ NoAnswers = 3*3*3+2*2*2,
+ Q = qlc:q([{X,Y,Z} ||
+ {{X,Y,Z}} <- ets:table(E),
+ (((X =:= 4) or (X =:= 5)) and
+ ((Y =:= 4) or (Y =:= 5)) and
+ ((Z =:= 4) or (Z =:= 5))) or
+ (((X =:= 1) or (X =:= 2) or (X =:= 3)) and
+ ((Y =:= 1) or (Y =:= 2) or (Y =:= 3)) and
+ ((Z =:= 1) or (Z =:= 2) or (Z =:= 3)))],
+ {max_lookup, NoAnswers}),
+ {list, {table, _}, _} = i(Q),
+ [{1,1,1},{2,2,2},{3,3,3}] = lists:sort(qlc:e(Q)),
+ true = NoAnswers =:= length(lookup_keys(Q))
+ end, [{{1,1,1}},{{2,2,2}},{{3,3,3}},{{3,3,4}},{{4,1,1}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y,Z} ||
+ {{X,Y,Z}} <- ets:table(E),
+ (((X =:= 4) or (X =:= 5)) and
+ ((Y =:= 4) or (Y =:= 5)) and
+ ((Z =:= 4) or (Z =:= 5))) or
+ (((X =:= 1) or (X =:= 2) or (X =:= 3)) and
+ ((Y =:= 1) or (Y =:= 2) or (Y =:= 3)) and
+ ((Z =:= 1) or (Z =:= 2) or (Z =:= 3)))],
+ {max_lookup, 10}),
+ [{1,1,1},{2,2,2},{3,3,3}] = lists:sort(qlc:e(Q)),
+ {table,{ets,table,[_,[{traverse,{select,_}}]]}} = i(Q)
+ end, [{{1,1,1}},{{2,2,2}},{{3,3,3}},{{3,3,4}},{{4,1,1}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X={_,_,_} <- ets:table(E),
+ element(1, X) =:= 3, element(2, X) == a]),
+ [{3,a,s}] = qlc:e(Q),
+ [3] = lookup_keys(Q)
+ end, [{1,c,q},{2,b,r},{3,a,s}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(0, X) =:= 3]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ F = fun(_) -> 3 end,
+ %% No occurs check; X =:= F(X) is ignored.
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 3, X =:= F(X)]),
+ {qlc,_,[{generate,_,{list,{table,_},_}},_],[]} = i(Q),
+ [3] = lists:sort(qlc:e(Q)),
+ [3] = lookup_keys(Q)
+ end, [{2},{3},{4}])">>,
+
+ <<"etsc(fun(E) ->
+ A = a, B = a,
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ ((X =:= A) and (Y =:= B))
+ or ((X =:= B) and (Y =:= A))]),
+ [a] = qlc:e(Q),
+ %% keys are usorted, duplicate removed:
+ [{a,a}] = lookup_keys(Q)
+ end, [{{a,a}},{{b,b}}])">>,
+
+ <<"etsc(fun(E) ->
+ A = a, B = b,
+ Q = qlc:q([X || {{X,Y}} <- ets:table(E),
+ ((X =:= A) and (Y =:= B))
+ or ((X =:= B) and (Y =:= A))]),
+ [a,b] = lists:sort(qlc:e(Q)),
+ [{a,b},{b,a}] = lookup_keys(Q)
+ end, [{{a,b}},{{b,a}},{{c,a}},{{d,b}}])">>,
+
+ %% The atom 'fail' is recognized - lookup.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || {A} <- ets:table(E),
+ (A =:= 2)
+ orelse fail
+ ]),
+ [2] = lists:sort(qlc:e(Q)),
+ [2] = lookup_keys(Q)
+ end, [{1},{2}])">>
+
+ ],
+ ?line run(Config, Ts),
+
+ TsR = [
+ %% is_record/2,3:
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ erlang:is_record(X, r, 2)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ is_record(X, r, 2)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ {erlang,is_record}(X, r, 2)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ record(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ {warnings,[{{4,45},erl_lint,{obsolete_guard,{record,2}}}]}},
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ erlang:is_record(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ is_record(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([element(1, X) || X <- ets:table(E),
+ {erlang,is_record}(X, r)]),
+ [r] = qlc:e(Q),
+ [r] = lookup_keys(Q)
+ end, [{keypos,1}], [#r{}])">>
+
+ ],
+ ?line run(Config, <<"-record(r, {a}).\n">>, TsR),
+
+ Ts2 = [
+ <<"etsc(fun(E) ->
+ Q0 = qlc:q([X ||
+ X <- ets:table(E),
+ (element(1, X) =:= 1) or
+ (element(1, X) =:= 2)],
+ {cache,ets}),
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- Q0]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}], []} = i(Q),
+ [{1,{1}},{1,{2}},{2,{1}},{2,{2}}] = lists:sort(qlc:e(Q)),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q0 = qlc:q([X ||
+ X <- ets:table(E),
+ (element(1, X) =:= 1) or
+ (element(1, X) =:= 2)]),
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- Q0],
+ {cache,true}),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% One introduced QLC expression (join, ms), and the cache option.
+ <<"%% Match spec and lookup. The lookup is done twice, which might
+ %% be confusing...
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ (Y =:= 1) or (Y =:= 2)],
+ []),
+ [{1,1},{1,2},{2,1},{2,2}] = qlc:e(Q),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{list,{table,_},_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% The same as last example, but with cache.
+ %% No cache needed (always one lookup only).
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ (Y =:= 1) or (Y =:= 2)],
+ [cache]),
+ [{1,1},{1,2},{2,1},{2,2}] = qlc:e(Q),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{list,{table,_},_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+
+ <<"%% And again, this time only lookup, no mach spec.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- ets:table(E),
+ (element(1, Y) =:= 1)
+ or (element(1, Y) =:= 2)],
+ []),
+ [{1,{1}},{1,{2}},{2,{1}},{2,{2}}] = qlc:e(Q),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% As last one, but with cache.
+ %% No cache needed (always one lookup only).
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ Y <- ets:table(E),
+ (element(1, Y) =:= 1)
+ or (element(1, Y) =:= 2)],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [{1,{1}},{1,{2}},{2,{1}},{2,{2}}] = qlc:e(Q),
+ [1,2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+
+ <<"%% Lookup only. No cache.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y=2} <- ets:table(E)],
+ []),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [{1,2},{2,2}] = qlc:e(Q),
+ [2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% Lookup only. No cache.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y=2} <- ets:table(E)],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{table,_}}],[]} = i(Q),
+ [{1,2},{2,2}] = qlc:e(Q),
+ [2] = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+
+ <<"%% Matchspec only. No cache.
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ Y > 1],
+ []),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,
+ {table,{ets,_,[_,[{traverse,_}]]}}}],[]} =
+ i(Q),
+ [{1,2},{1,3},{2,2},{2,3}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% Matchspec only. Cache
+ etsc(fun(E) ->
+ Q = qlc:q([{X,Y} ||
+ X <- [1,2],
+ {Y} <- ets:table(E),
+ Y > 1],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{table,{ets,_,[_,[{traverse,_}]]}}}],
+ [{cache,ets}]}}],[]} = i(Q),
+ [{1,2},{1,3},{2,2},{2,3}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{keypos,1}], [{1},{2},{3}])">>,
+ <<"%% An empty list. Always unique and cached.
+ Q = qlc:q([X || {X} <- [], X =:= 1, begin X > 0 end],
+ [{cache,true},{unique,true}]),
+ {qlc,_,[{generate,_,{list,[]}},_],[{unique,true}]} = i(Q),
+ _ = qlc:info(Q),
+ [] = qlc:e(Q)">>,
+ <<"%% A list is always cached.
+ Q = qlc:q([{X,Y} || Y <- [1,2], X <- [2,1,2]],
+ [cache]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{list,[2,1,2]}}],[]} = i(Q),
+ [{2,1},{1,1},{2,1},{2,2},{1,2},{2,2}] = qlc:e(Q)">>,
+ <<"%% But a processed list is never cached.
+ Q = qlc:q([{X,Y} || Y <- [1,2], X <- [2,1,2], X > 1],
+ [cache]),
+ {qlc,_,[{generate,_, {list,[1,2]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,{list,[2,1,2]},_}}],
+ [{cache,ets}]}}],[]} = i(Q),
+ [{2,1},{2,1},{2,2},{2,2}] = qlc:e(Q)">>,
+ <<"%% A bug fixed in R11B-2: coalescing simple_qlc:s works now.
+ Q0 = qlc:q([X || {X} <- [{1},{2},{3}]], {cache, ets}),
+ Q1 = qlc:q([X || X <- Q0], {cache, list}),
+ Q = qlc:q([{Y,X} || Y <- [1,2], X <- Q1, X < 2], {cache, list}),
+ {qlc,_,[{generate,_,{list,_}},
+ {generate,_,{qlc,_,[{generate,_,{list,{list,_},_}}],
+ [{cache,ets}]}},_],[]} = i(Q),
+ [{1,1},{2,1}] = qlc:e(Q)">>,
+ <<"Q = qlc:q([{X,Y} || Y <- [1,2], X <- [1,2], X > 1],
+ [cache,unique]),
+ {qlc,_,[{generate,_,{list,[1,2]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,{list,[1,2]},_}}],
+ [{cache,ets},{unique,true}]}}],
+ [{unique,true}]} = i(Q),
+ [{2,1},{2,2}] = qlc:e(Q)">>,
+ <<"L = [1,2,3],
+ QH1 = qlc:q([{X} || X <- L, X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1, X > 0], [cache]),
+ [{{2}},{{3}}] = qlc:e(QH2),
+ {list,{list,{list,L},_},_} = i(QH2)">>,
+ <<"L = [1,2,3,1,2,3],
+ QH1 = qlc:q([{X} || X <- L, X > 1]),
+ QH2 = qlc:q([{X} || X <- QH1, X > 0], [cache,unique]),
+ [{{2}},{{3}}] = qlc:e(QH2),
+ {qlc,_,[{generate,_,{list,{list,{list,L},_},_}}],
+ [{unique,true}]} = i(QH2)">>
+
+ ],
+
+ ?line run(Config, Ts2),
+
+ LTs = [
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1],
+ {lookup,true}),
+ {table,L} = i(Q),
+ true = is_list(L),
+ [{1,a}] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1,a},{2,b}])">>,
+ <<"%% No lookup, use the match spec for traversal instead.
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1],
+ {lookup,false}),
+ {table,{ets,table,_}} = i(Q),
+ [{1,a}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,a},{2,b}])">>,
+ <<"%% As last one. {max_lookup,0} has the same effect.
+ etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1],
+ {max_lookup,0}),
+ {table,{ets,table,_}} = i(Q),
+ [{1,a}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,a},{2,b}])">>
+
+ ],
+ ?line run(Config, LTs),
+
+ ok.
+
+lookup_rec(doc) ->
+ "Lookup keys. With records.";
+lookup_rec(suite) -> [];
+lookup_rec(Config) when is_list(Config) ->
+ Ts = [
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = A} <- ets:table(E),
+ (A =:= 3) or (4 =:= A)]),
+ [3] = qlc:e(Q),
+ [3,4] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = a}, #r{a = 3}, #r{a = 5}])">>,
+
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17 = A} <- ets:table(E),
+ (A =:= 3) or (4 =:= A)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17}, #r{a = 3}, #r{a = 5}])">>,
+ {warnings,[{{4,44},qlc,nomatch_filter}]}},
+
+ <<"%% Compares an integer and a float.
+ etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17 = A} <- ets:table(E),
+ (A == 17) or (17.0 == A)]),
+ [_] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"%% Compares an integer and a float.
+ %% There is a test in qlc_pt.erl (Op =:= '=:=', C1 =:= C2), but
+ %% that case is handled in an earlier clause (unify ... E, E).
+ etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17.0 = A} <- ets:table(E),
+ (A =:= 17) or (17.0 =:= A)]),
+ [_] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17.0}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"%% Matches an integer and a float.
+ etsc(fun(E) ->
+ Q = qlc:q([A || #r{a = 17 = A} <- ets:table(E),
+ (A =:= 17) or (17.0 =:= A)]),
+ [_] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a = 17}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"etsc(fun(E) ->
+ F = fun(_) -> 17 end,
+ Q = qlc:q([A || #r{a = A} <- ets:table(E),
+ (F(A) =:= 3) and (A =:= 4)]),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q) % F(A) could fail
+ end, [{keypos,2}], [#r{a = 4}, #r{a = 3}, #r{a = 5}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ #r{} == X]),
+ [#r{}] = lists:sort(qlc:e(Q)),
+ {call,_,_,[_,_]} = i(Q, {format, abstract_code}),
+ [#r{}] = lookup_keys(Q)
+ end, [{#r{}},{#r{a=foo}}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([R#r.a || R <- ets:table(E), R#r.a =:= foo]),
+ [foo] = qlc:e(Q),
+ [_] = lookup_keys(Q)
+ end, [{keypos,2}], [#r{a=foo}])">>
+ ],
+ ?line run(Config, <<"-record(r, {a}).\n">>, Ts),
+ ok.
+
+indices(doc) ->
+ "Using indices for lookup.";
+indices(suite) -> [];
+indices(Config) when is_list(Config) ->
+ Ts = [
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ {list, {table,{qlc_SUITE,list_keys,[[a,b],2,L]}}, _MS} = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, [2]),
+ begin (element(2, X) =:= a)
+ or (b =:= element(2, X)) end]),
+ {qlc,_,[{generate,_,{table,{call,_,
+ {remote,_,_,{atom,_,the_list}},_}}},_],[]}
+ = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{1,a,q},{2,b,r},{3,c,s}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, [2,3]),
+ (element(3, X) =:= q)
+ or (r =:= element(3, X))]),
+ {list, {table,{qlc_SUITE,list_keys, [[q,r],3,L]}}, _MS} = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{1,a,q},{2,b,r},{3,c,s}],
+ QH = qlc:q([element(1, X) || X <- qlc_SUITE:table(L, 1, [2]),
+ (element(3, X) =:= q)
+ or (r =:= element(3, X))]),
+ {qlc,_,[{generate,_,{table,{call,_,_,_}}},
+ _],[]} = i(QH),
+ [1,2] = qlc:eval(QH)">>,
+
+ <<"L = [{a,1},{b,2},{c,3}],
+ QH = qlc:q([E || {K,I}=E <- qlc_SUITE:table(L, 1, [2]),
+ ((K =:= a) or (K =:= b) or (K =:= c))
+ and ((I =:= 1) or (I =:= 2))],
+ {max_lookup, 3}),
+ {list, {table,{qlc_SUITE,list_keys,[[a,b,c],1,L]}}, _MS} = i(QH),
+ [{a,1},{b,2}] = qlc:eval(QH)">>,
+
+ <<"L = [{a,1},{b,2},{c,3}],
+ QH = qlc:q([E || {K,I}=E <- qlc_SUITE:table(L, 1, [2]),
+ ((K =:= a) or (K =:= b) or (K =:= c))
+ and ((I =:= 1) or (I =:= 2))],
+ {max_lookup, 2}),
+ {list, {table,{qlc_SUITE,list_keys, [[1,2],2,L]}}, _MS} = i(QH),
+ [{a,1},{b,2}] = qlc:eval(QH)">>,
+
+ <<"L = [{a,1,x,u},{b,2,y,v},{c,3,z,w}],
+ QH = qlc:q([E || {K,I1,I2,I3}=E <- qlc_SUITE:table(L, 1, [2,3,4]),
+ ((K =/= a) or (K =/= b) or (K =/= c))
+ and ((I1 =:= 1) or (I1 =:= 2) or
+ (I1 =:= 3) or (I1 =:= 4))
+ and ((I2 =:= x) or (I2 =:= z))
+ and ((I3 =:= v) or (I3 =:= w))],
+ {max_lookup, 5}),
+ {list, {table,{qlc_SUITE,list_keys, [[x,z],3,L]}}, _MS} = i(QH),
+ [{c,3,z,w}] = qlc:eval(QH)">>
+
+ ],
+ ?line run(Config, <<"-record(r, {a}).\n">>, Ts),
+ ok.
+
+pre_fun(doc) ->
+ "Test the table/2 callback functions parent_fun and stop_fun.";
+pre_fun(suite) -> [];
+pre_fun(Config) when is_list(Config) ->
+ Ts = [
+ <<"PF = process_flag(trap_exit, true),
+ %% cursor: table killing parent
+ L = [{1,a},{2,b},{3,c}],
+ F1 = fun() ->
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_kill_parent(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:info(QH),
+ _ = qlc:cursor(QH)
+ end,
+ Pid1 = spawn_link(F1),
+ receive {'EXIT', Pid1, killed} ->
+ ok
+ end,
+ timer:sleep(1),
+ process_flag(trap_exit, PF)">>,
+
+ <<"PF = process_flag(trap_exit, true),
+ %% eval without cursor: table killing parent
+ L = [{1,a},{2,b},{3,c}],
+ F2 = fun() ->
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_kill_parent(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:eval(QH)
+ end,
+ Pid2 = spawn_link(F2),
+ receive {'EXIT', Pid2, killed} ->
+ ok
+ end,
+ process_flag(trap_exit, PF)">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_parent_throws(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:info(QH),
+ {throw,thrown} = (catch {any_term,qlc:cursor(QH)}),
+ {throw,thrown} = (catch {any_term,qlc:eval(QH)})">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_parent_exits(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ _ = qlc:info(QH),
+ {'EXIT', {badarith,_}} = (catch qlc:cursor(QH)),
+ {'EXIT', {badarith,_}} = (catch qlc:eval(QH))">>,
+
+ <<"L = [{1,a},{2,b},{3,c}],
+ QH = qlc:q([element(1, X) ||
+ X <- qlc_SUITE:table_bad_parent_fun(L, [2]),
+ (element(2, X) =:= a)
+ or (b =:= element(2, X))]),
+ {'EXIT', {badarg,_}} = (catch qlc:cursor(QH)),
+ {'EXIT', {badarg,_}} = (catch qlc:eval(QH))">>,
+
+ <<"%% Very simple test of stop_fun.
+ Ets = ets:new(apa, [public]),
+ L = [{1,a},{2,b},{3,c}],
+ H = qlc:q([X || {X,_} <- qlc_SUITE:stop_list(L, Ets)]),
+ C = qlc:cursor(H),
+ [{stop_fun,StopFun}] = ets:lookup(Ets, stop_fun),
+ StopFun(),
+ {'EXIT', {{qlc_cursor_pid_no_longer_exists, _}, _}} =
+ (catch qlc:next_answers(C, all_remaining)),
+ ets:delete(Ets)">>
+
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+skip_filters(doc) ->
+ "Lookup keys. With records.";
+skip_filters(suite) -> [];
+skip_filters(Config) when is_list(Config) ->
+ %% Skipped filters
+ TsS = [
+ %% Cannot skip the filter.
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1, X) =:= 1) xor (element(1, X) =:= 1)]),
+ [] = qlc:eval(H),
+ [1] = lookup_keys(H)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% The filter can be skipped. Just a lookup remains.
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1, X) =:= 1) or (element(1, X) =:= 1)]),
+ [{1}] = qlc:eval(H),
+ {table, _} = i(H),
+ [1] = lookup_keys(H)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% safe_unify fails on 3 and <<X:32>>
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1, X) =:= 1) and (3 =:= <<X:32>>)]),
+ [] = qlc:eval(H),
+ [1] = lookup_keys(H)
+ end, [{keypos,1}], [{1},{2}])">>,
+
+ %% Two filters are skipped.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{B,C,D} || {A={C},B} <- ets:table(E),
+ (A =:= {1}) or (A =:= {2}),
+ (C =:= 1) or (C =:= 2),
+ D <- [1,2]]),
+ {qlc,_,[{generate,_,{table,_}},{generate,_,{list,[1,2]}}],[]}
+ = i(Q),
+ [{1,1,1},{1,1,2},{2,2,1},{2,2,2}] = lists:sort(qlc:eval(Q)),
+ [{1},{2}] = lookup_keys(Q)
+ end, [{{1},1},{{2},2},{{3},3}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{B,C} || {A={C},B} <- ets:table(E),
+ (A =:= {1}) or (A =:= {2}),
+ (C =:= 1) or (C =:= 2)]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(Q),
+ [{1,1},{2,2}] = lists:sort(qlc:eval(Q)),
+ [{1},{2}] = lookup_keys(Q)
+ end, [{{1},1},{{2},2},{{3},3}])">>,
+
+ %% Lookup. No match spec, no filter.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || X <- ets:table(E),
+ element(1, X) =:= 1]),
+ {table, _} = i(Q),
+ [{1}] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || X <- ets:table(E),
+ element(1, X) =:= 1,
+ Y <- [1,2]]),
+ {qlc,_,[{generate,_,{table,_}},{generate,_,{list,_}}],[]}
+ = i(Q),
+ [{{1},1},{{1},2}] = lists:sort(qlc:e(Q)),
+ [1] = lookup_keys(Q)
+ end, [{1},{2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ X =:= a,
+ X =:= Y]),
+ {list,{table,_},_} = i(Q),
+ [a] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a,a},{b,c},{c,a}])">>,
+
+ %% The imported variable (A) is never looked up in the current
+ %% implementation. This means that the first filter cannot be skipped;
+ %% the constant 'a' is looked up, and then the first filter evaluates
+ %% to false.
+ <<"etsc(fun(E) ->
+ A = 3,
+ Q = qlc:q([X || X <- ets:table(E),
+ A == element(1,X),
+ element(1,X) =:= a]),
+ [] = qlc:e(Q),
+ [a] = lookup_keys(Q)
+ end, [{a},{b},{c}])">>,
+
+ %% No lookup.
+ {cres,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 1,
+ X =:= 2]),
+ {table, _} = i(Q),
+ [] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [{1,1},{2,0}])">>,
+ {warnings,[{{4,37},qlc,nomatch_filter}]}},
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C} ||
+ {A} <- ets:table(E),
+ A =:= 1,
+ {B} <- ets:table(E),
+ B =:= 2,
+ {C} <- ets:table(E),
+ C =:= 3]),
+ {qlc,_,[{generate,_,{list,{table,_},_}},
+ {generate,_,{list,{table,_},_}},
+ {generate,_,{list,{table,_},_}}],[]} = i(Q),
+ [{1,2,3}] = qlc:e(Q),
+ [1,2,3] = lookup_keys(Q)
+ end, [{0},{1},{2},{3},{4}])">>
+
+ ],
+ ?line run(Config, TsS),
+
+ Ts = [
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= 2]),
+ {list,{table,_},_} = i(H),
+ [2] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ ((X =:= 2) or (X =:= 1)) and (X > 1)]),
+ {list,{table,_},_} = i(H),
+ [2] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,Y} <- ets:table(E),
+ (X =:= 2) and (Y =:= b)]),
+ {list,{table,_},_} = i(H),
+ [2] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || X <- ets:table(E),
+ (element(1,X) =:= 2) and (X =:= {2,b})]),
+ {list,{table,_},_} = i(H),
+ [{2,b}] = qlc:e(H)
+ end, [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([{X,Y,Z,W} ||
+ {X,Y} <- ets:table(E),
+ {Z,W} <- ets:table(E),
+ (Y =:= 3) or (Y =:= 4)]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}}],[]} = i(H),
+ [{a,3,a,3},{a,3,b,5}] = lists:sort(qlc:e(H))
+ end, [{a,3},{b,5}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([{X,Y} ||
+ {X,Y=3} <- ets:table(E), % no matchspec
+ %% Two columns restricted, but lookup anyway
+ (X =:= a)]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(H),
+ [{a,3}] = qlc:e(H)
+ end, [{a,3},{b,4}])">>,
+
+ <<"etsc(fun(E) ->
+ V = 3,
+ H = qlc:q([{X,Y} ||
+ {X,Y} <- ets:table(E),
+ (Y =:= V)]), % imported variable, no lookup
+ {table,{ets,table,_}} = i(H),
+ [{a,3}] = qlc:e(H)
+ end, [{a,3},{b,4}])">>,
+
+ <<"etsc(fun(E) ->
+ V = b,
+ H = qlc:q([{X,Y} ||
+ {X,Y} <- ets:table(E),
+ (X =:= V)]), % imported variable, lookup
+ {list,{table,_},_} = i(H),
+ [{b,4}] = qlc:e(H)
+ end, [{a,3},{b,4}])">>,
+
+ <<"H = qlc:q([{A,B} || {{A,B}} <- [{{1,a}},{{2,b}}],
+ A =:= 1,
+ B =:= a]),
+ {list,{list,[_,_]},_} = i(H),
+ [{1,a}] = qlc:e(H)">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([{A,B} || {{A,B}} <- ets:table(E),
+ A =:= 1,
+ B =:= a]),
+ {list,{table,_},_} = i(H),
+ [{1,a}] = qlc:e(H)
+ end, [{{1,a}},{{2,b}}])">>,
+
+ %% The filters are skipped, and the guards of the match specifications
+ %% are skipped as well. Only the transformations of the matchspecs
+ %% are kept.
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ H = qlc:q([{X,Y,Z,W} ||
+ {X,_}=Z <- ets:table(E1),
+ W={Y} <- ets:table(E2),
+ (X =:= 1) or (X =:= 2),
+ (Y =:= a) or (Y =:= b)]
+ ,{lookup,true}
+ ),
+ {qlc,_,[{generate,_,{list,{table,_},
+ [{{'$1','_'},[],['$_']}]}},
+ {generate,_,{list,{table,_},
+ [{{'$1'},[],['$_']}]}}],[]}
+ = i(H),
+ [{1,a,{1,a},{a}},
+ {1,b,{1,a},{b}},
+ {2,a,{2,b},{a}},
+ {2,b,{2,b},{b}}] = qlc:e(H)
+ end, [{a},{b}])
+ end, [{1,a},{2,b}])">>,
+
+ %% The same example again, but this time no match specs are run.
+ <<"fun(Z) ->
+ etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ H = qlc:q([{X,Y} ||
+ Z > 2,
+ X <- ets:table(E1),
+ Y <- ets:table(E2),
+ (element(1, X) =:= 1) or
+ (element(1, X) =:= 2),
+ (element(1, Y) =:= a) or
+ (element(1, Y) =:= b)]
+ ,{lookup,true}
+ ),
+ {qlc,_,[_,{generate,_,{table,_}},
+ {generate,_,{table,_}}],[]} = i(H),
+ [{{1,a},{a}},
+ {{1,a},{b}},
+ {{2,b},{a}},
+ {{2,b},{b}}] = qlc:e(H)
+ end, [{a},{b}])
+ end, [{1,a},{2,b}])
+ end(4)">>,
+
+ %% Once again, this time with a join.
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ H = qlc:q([{X,Y,Z,W} ||
+ {X,V}=Z <- ets:table(E1),
+ W={Y} <- ets:table(E2),
+ (X =:= 1) or (X =:= 2),
+ (Y =:= a) or (Y =:= b),
+ Y =:= V]
+ ,[{lookup,true},{join,merge}]
+ ),
+ {qlc,_,[{generate,_,{qlc,_,
+ [{generate,_,{qlc,_,[{generate,_,
+ {keysort,{list,{table,_},_},2,[]}},
+ _C1,_C2],[]}},
+ {generate,_,
+ {qlc,_,[{generate, _,
+ {keysort,{list,{table,_},_},1,[]}},
+ _C3],
+ []}},
+ _],
+ [{join,merge}]}},_],[]} = i(H),
+ [{1,a,{1,a},{a}},{2,b,{2,b},{b}}] =
+ lists:sort(qlc:e(H))
+ end, [{a},{b}])
+ end, [{1,a},{2,b}])">>,
+
+ %% Filters 2 and 3 are not skipped.
+ %% (Only one filter at a time is tried by the parse transform.)
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {{A,B}=X,Y} <- ets:table(E), % no matchspec
+ Y =:= 3,
+ A =:= 1,
+ B =:= a]),
+
+ {qlc,_,[{generate,_,{table,_}},_,_,_],[]}= i(H),
+ [{1,a}] = qlc:e(H)
+ end, [{{1,a},3},{{2,b},4}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= 3) and (X > 3)]),
+ {qlc,_,[{generate,_,{table,_}},_],[]} = i(H),
+ [] = qlc:e(H)
+ end, [{3,a},{4,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= 3) or true]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},_],[]} = i(H),
+ [3,4] = lists:sort(qlc:e(H))
+ end, [{3,a},{4,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= 3) or false]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(H),
+ [3] = lists:sort(qlc:e(H))
+ end, [{3,a},{4,b}])">>,
+
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X=_,_} <- ets:table(E), % no matchspec
+ (X =:= X) and (X =:= 3)]),
+ {qlc,_,[{generate,_,{table,_}}],[]} = i(H),
+ [3] = lists:sort(qlc:e(H))
+ end, [{3,a},{4,b}])">>,
+
+ %% The order of filters matters. A guard filter cannot be used
+ %% unless there are no non-guard filter placed before the guard
+ %% filter that uses the guard filter's generator. There is
+ %% more examples in join_filter().
+ <<"etsc(fun(E) ->
+ %% Lookup.
+ Q = qlc:q([{A,B,A} ||
+ {A=_,B} <- ets:table(E), % no match spec
+ A =:= 1,
+ begin 1/B > 0 end]),
+ [{1,1,1}] = lists:sort(qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"etsc(fun(E) ->
+ %% No lookup.
+ Q = qlc:q([{A,B,A} ||
+ {A=_,B} <- ets:table(E), % no match spec
+ begin 1/B > 0 end,
+ A =:= 1]),
+ {'EXIT', _} = (catch qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ %% The same thing, with a match specification.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,A} ||
+ {A,B} <- ets:table(E), % match spec
+ A < 2,
+ begin 1/B > 0 end]),
+ [{1,1,1}] = lists:sort(qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,A} ||
+ {A,B} <- ets:table(E), % match spec
+ begin 1/B > 0 end,
+ A < 2]),
+ {'EXIT', _} = (catch qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ %% More examples, this time two tables.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E), % match spec
+ A < 2,
+ {C,D} <- ets:table(E),
+ begin 1/B > 0 end, %\"invalidates\" next filter
+ C =:= 1,
+ begin 1/D > 0 end]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}},
+ _,_,_],[]} = i(Q),
+ [{1,1,1,1}] = lists:sort(qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E),
+ {C,D} <- ets:table(E),
+ begin 1/B > 0 end, % \"invalidates\" two filters
+ A < 2,
+ C =:= 1,
+ begin 1/D > 0 end]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}},_,_,_,_],[]} = i(Q),
+ {'EXIT', _} = (catch qlc:e(Q))
+ end, [{1,1},{2,0}])">>,
+ <<"%% There are objects in the ETS table, but none passes the filter.
+ %% F() would not be run if it did not \"invalidate\" the following
+ %% guards.
+ etsc(fun(E) ->
+ F = fun() -> [foo || A <- [0], 1/A] end,
+ Q1 = qlc:q([X || {X} <- ets:table(E),
+ F(), % \"invalidates\" next guard
+ X =:= 17]),
+ {'EXIT', _} = (catch qlc:e(Q1))
+ end, [{1},{2},{3}])">>,
+ <<"%% The last example works just like this one:
+ etsc(fun(E) ->
+ F = fun() -> [foo || A <- [0], 1/A] end,
+ Q1 = qlc:q([X || {X} <- ets:table(E),
+ F(),
+ begin X =:= 17 end]),
+ {'EXIT', _} = (catch qlc:e(Q1))
+ end, [{1},{2},{3}])">>
+
+ ],
+ ?line run(Config, Ts),
+
+ ok.
+
+table_impls(suite) ->
+ [ets, dets].
+
+ets(doc) ->
+ "ets:table/1,2.";
+ets(suite) -> [];
+ets(Config) when is_list(Config) ->
+ Ts = [
+ <<"E = ets:new(t, [ordered_set]),
+ true = ets:insert(E, [{1},{2}]),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- ets:table(E, bad_option)]))),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- ets:table(E,{traverse,bad})]))),
+ All = [{'$1',[],['$1']}],
+ TravAll = {traverse,{select,All}},
+ [_, _] = qlc:e(qlc:q([X || {X} <- ets:table(E, TravAll)])),
+ [_, _] = qlc:e(qlc:q([X || {X} <- ets:table(E,{traverse,select})])),
+ [1,2] =
+ qlc:e(qlc:q([X || {X} <- ets:table(E, {traverse, first_next})])),
+ [2,1] =
+ qlc:e(qlc:q([X || {X} <- ets:table(E, {traverse, last_prev})])),
+ {table,{ets,table,[_,[{traverse,{select,_}},{n_objects,1}]]}} =
+ i(qlc:q([X || {X} <- ets:table(E, {n_objects,1})])),
+ {qlc,_,[{generate,_,{table,{ets,table,[_,{n_objects,1}]}}},_],[]} =
+ i(qlc:q([X || {X} <- ets:table(E,{n_objects,1}),
+ begin (X >= 1) or (X < 1) end])),
+ {qlc,_,[{generate,_,{table,{ets,table,[_]}}},_],[]} =
+ i(qlc:q([X || {X} <- ets:table(E),
+ begin (X >= 1) or (X < 1) end])),
+ ets:delete(E)">>,
+
+ begin
+ MS = ets:fun2ms(fun({X,Y}) when X > 1 -> {X,Y} end),
+ [<<"E = ets:new(apa,[]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ Q = qlc:q([X || {X,_} <- ets:table(E, {traverse, {select, MS}}),
+ X =:= 1]),
+ R = qlc:e(Q),
+ ets:delete(E),
+ [] = R">>]
+ end
+
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+dets(doc) ->
+ "dets:table/1,2.";
+dets(suite) -> [];
+dets(Config) when is_list(Config) ->
+ dets:start(),
+ T = t,
+ Fname = filename(T, Config),
+ Ts = [
+ [<<"T = t, Fname = \"">>, Fname, <<"\",
+ file:delete(Fname),
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ ok = dets:insert(T, [{1},{2}]),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- dets:table(T, bad_option)]))),
+ {'EXIT', _} =
+ (catch qlc:e(qlc:q([X || {X} <- dets:table(T,{traverse,bad})]))),
+ {'EXIT', _} =
+ (catch
+ qlc:e(qlc:q([X || {X} <- dets:table(T,{traverse,last_prev})]))),
+ All = [{'$1',[],['$1']}],
+ TravAll = {traverse,{select,All}},
+ [_,_] = qlc:e(qlc:q([X || {X} <- dets:table(T, TravAll)])),
+ [_,_] = qlc:e(qlc:q([X || {X} <- dets:table(T,{traverse,select})])),
+ [_,_] =
+ qlc:e(qlc:q([X || {X} <- dets:table(T, {traverse, first_next})])),
+ {table,{dets,table,[T,[{traverse,{select,_}},{n_objects,1}]]}} =
+ i(qlc:q([X || {X} <- dets:table(T, {n_objects,1})])),
+ {qlc,_,[{generate,_,{table,{dets,table,[t,{n_objects,1}]}}},_],[]}=
+ i(qlc:q([X || {X} <- dets:table(T,{n_objects,1}),
+ begin (X >= 1) or (X < 1) end])),
+ {qlc,_,[{generate,_,{table,{dets,table,[_]}}},_],[]} =
+ i(qlc:q([X || {X} <- dets:table(T),
+ begin (X >= 1) or (X < 1) end])),
+ H = qlc:q([X || {X} <- dets:table(T, {n_objects, default}),
+ begin (X =:= 1) or (X =:= 2) or (X =:= 3) end]),
+ [1,2] = lists:sort(qlc:e(H)),
+ {qlc,_,[{generate,_,{table,_}},_],[]} = i(H),
+
+ H2 = qlc:q([X || {X} <- dets:table(T), (X =:= 1) or (X =:= 2)]),
+ [1,2] = lists:sort(qlc:e(H2)),
+ {list,{table,_},_} = i(H2),
+ true = binary_to_list(<<
+ \"ets:match_spec_run(lists:flatmap(fun(V)->dets:lookup(t,V)end,\"
+ \"[1,2]),ets:match_spec_compile([{{'$1'},[],['$1']}]))\">>)
+ == format_info(H2, true),
+
+ H3 = qlc:q([X || {X} <- dets:table(T), (X =:= 1)]),
+ [1] = qlc:e(H3),
+ {list,{table,_},_} = i(H3),
+
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\"),
+ ok">>],
+
+ begin
+ MS = ets:fun2ms(fun({X,Y}) when X > 1 -> {X,Y} end),
+ [<<"T = t, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ ok = dets:insert(T, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([X || {X,_} <- dets:table(T, {traverse, {select, MS}}),
+ X =:= 1]),
+ R = qlc:e(Q),
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\"),
+ [] = R">>]
+ end,
+
+ [<<"T = t, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ Objs = [{X} || X <- lists:seq(1,10)],
+ ok = dets:insert(T, Objs),
+ {ok, Where} = dets:where(T, {2}),
+ ok = dets:close(T),
+ qlc_SUITE:crash(Fname, Where),
+
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ HT = qlc:q([X || {X} <- dets:table(T, {traverse, first_next})]),
+ {'EXIT',{error,{{bad_object,_},_}}} = (catch qlc:e(HT)),
+ _ = dets:close(T),
+
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ HMS = qlc:q([X || {X} <- dets:table(T, {traverse, select})]),
+ {error,{{bad_object,_},_}} = qlc:e(HMS),
+ _ = dets:close(T),
+
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ HLU = qlc:q([X || {X} <- dets:table(T), X =:= 2]),
+ {error,{{bad_object,_},_}} = qlc:e(HLU),
+ _ = dets:close(T),
+
+ file:delete(Fname)">>]
+
+ ],
+
+ ?line run(Config, Ts),
+ _ = file:delete(Fname),
+ ok.
+
+join(suite) ->
+ [join_option, join_filter, join_lookup, join_merge,
+ join_sort, join_complex].
+
+join_option(doc) ->
+ "The 'join' option (any, lookup, merge, nested_loop). Also cache/unique.";
+join_option(suite) -> [];
+join_option(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q1 = qlc:q([X || X <- [1,2,3]],{join,merge}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:info(Q1)}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:e(Q1)}),
+
+ Q2 = qlc:q([X || X <- [1,2,3], X > 1],{join,merge}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:info(Q2)}),
+ {'EXIT', {no_join_to_carry_out,_}} = (catch {foo, qlc:e(Q2)}),
+
+ Q3 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{a},{b},{c}],
+ X =:= Y],
+ {join, merge}),
+
+ {1,0,0,2} = join_info(Q3),
+ [] = qlc:e(Q3),
+
+ Q4 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{a},{b},{c}],
+ X > Y],
+ {join, lookup}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:info(Q4)}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:e(Q4)}),
+
+ Q5 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X == Y],
+ {join, merge}),
+ [{3,3}] = qlc:e(Q5),
+
+ Q6 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X == Y],
+ {join, lookup}),
+ {'EXIT', {cannot_carry_out_join, _}} = (catch {foo, qlc:info(Q6)}),
+ {'EXIT', {cannot_carry_out_join, _}} = (catch {foo, qlc:e(Q6)}),
+
+ Q7 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X == Y],
+ {join, nested_loop}),
+ {0,0,1,0} = join_info(Q7),
+ [{3,3}] = qlc:e(Q7),
+
+ Q8 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X =:= Y],
+ {join, nested_loop}),
+ {0,0,1,0} = join_info(Q8),
+ [{3,3}] = qlc:e(Q8),
+
+ %% Only guards are inspected...
+ Q9 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ begin X =:= Y end],
+ {join, nested_loop}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:info(Q9)}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:e(Q9)}),
+
+ Q10 = qlc:q([{X,Y} ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{3},{4},{5}],
+ X < Y],
+ {join, nested_loop}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:info(Q10)}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch {foo, qlc:e(Q10)}),
+
+ F = fun(J) -> qlc:q([X || X <- [1,2]], {join,J}) end,
+ {'EXIT', {no_join_to_carry_out, _}} =
+ (catch {foo, qlc:e(F(merge))}),
+ {'EXIT', {no_join_to_carry_out, _}} =
+ (catch {foo, qlc:e(F(lookup))}),
+ {'EXIT', {no_join_to_carry_out, _}} =
+ (catch {foo, qlc:e(F(nested_loop))}),
+ [1,2] = qlc:e(F(any)),
+
+ %% No join of columns in the same table.
+ Q11 = qlc:q([{X,Y} || {a = X, X = Y} <- [{a,1},{a,a},{a,3},{a,a}]],
+ {join,merge}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch qlc:e(Q11)),
+ Q12 = qlc:q([{X,Y} || {X = a, X = Y} <- [{a,1},{a,a},{a,3},{a,a}]],
+ {join,merge}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch qlc:e(Q12)),
+ %% X and Y are \"equal\" (same constants), but must not be joined.
+ Q13 = qlc:q([{X,Y} || {X,_Z} <- [{a,1},{a,2},{b,1},{b,2}],
+ {Y} <- [{a}],
+ (X =:= a) and (Y =:= b) or
+ (X =:= b) and (Y =:= a)],
+ {join,merge}),
+ {'EXIT', {no_join_to_carry_out, _}} = (catch qlc:e(Q13))
+
+">>,
+
+ <<"Q1 = qlc:q([X || X <- [1,2,3]], {lookup,true}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:info(Q1)}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:e(Q1)}),
+ Q2 = qlc:q([{X,Y} || X <- [1,2,3], Y <- [x,y,z]], lookup),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:info(Q2)}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:e(Q2)}),
+ Q3 = qlc:q([X || {X} <- [{1},{2},{3}]], {lookup,true}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:e(Q3)}),
+ {'EXIT', {no_lookup_to_carry_out, _}} = (catch {foo, qlc:info(Q3)}),
+
+ E1 = create_ets(1, 10),
+ Q4 = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), X =:= 3], lookup),
+ {match_spec, _} = strip_qlc_call(Q4),
+ [{3,3}] = qlc:e(Q4),
+ Q5 = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), X =:= 3], {lookup,false}),
+ {table, ets, _} = strip_qlc_call(Q5),
+ [{3,3}] = qlc:e(Q5),
+ Q6 = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), X =:= 3], {lookup,any}),
+ {match_spec, _} = strip_qlc_call(Q6),
+ [{3,3}] = qlc:e(Q6),
+ ets:delete(E1)">>
+
+ ],
+ ?line run(Config, Ts),
+
+ %% The 'cache' and 'unique' options of qlc/2 affects join.
+ CUTs = [
+ <<"L1 = [1,2],
+ L2 = [{1,a},{2,b}],
+ L3 = [{a,1},{b,2}],
+ Q = qlc:q([{X,Y,Z} ||
+ Z <- L1,
+ {X,_} <- L2,
+ {_,Y} <- L3,
+ X =:= Y],
+ [cache, unique]),
+ {qlc,_,
+ [{generate,_,{list,L1}},
+ {generate,_,{qlc,_,[{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,L2},1,[]}}],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{keysort,{list,L3},2,[]}}],[]}},_],
+ [{join,merge},{cache,ets},{unique,true}]}},_],
+ [{unique,true}]} = i(Q),
+ [{1,1,1},{2,2,1},{1,1,2},{2,2,2}] = qlc:e(Q)">>,
+ <<"L1 = [1,2],
+ L2 = [{1,a},{2,b}],
+ L3 = [{a,1},{b,2}],
+ Q = qlc:q([{X,Y,Z} ||
+ Z <- L1,
+ {X,_} <- L2,
+ {_,Y} <- L3,
+ X =:= Y],
+ []),
+ Options = [{cache_all,ets}, unique_all],
+ {qlc,_,[{generate,_,{qlc,_,[{generate,_,{list,L1}}],
+ [{unique,true}]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{qlc,_,[{generate,_,
+ {keysort,{qlc,_,[{generate,_,{list,L2}}],
+ [{cache,ets},{unique,true}]},
+ 1,[]}}],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{keysort,
+ {qlc,_,[{generate,_,{list,L3}}],
+ [{cache,ets},{unique,true}]},
+ 2,[]}}],[]}},_],
+ [{join,merge},{cache,ets},{unique,true}]}},
+ _],[{unique,true}]} = i(Q, Options),
+ [{1,1,1},{2,2,1},{1,1,2},{2,2,2}] = qlc:e(Q, Options)">>
+ ],
+ ?line run(Config, CUTs),
+
+ ok.
+
+join_filter(doc) ->
+ "Various aspects of filters and join.";
+join_filter(suite) -> [];
+join_filter(Config) when is_list(Config) ->
+ Ts = [
+ <<"E1 = create_ets(1, 10),
+ Q = qlc:q([X || {X,_} <- ets:table(E1),
+ begin A = X * X end, % ej true (?)
+ X >= A]),
+ {'EXIT', _} = (catch qlc:e(Q)),
+ ets:delete(E1)">>,
+
+ %% The order of filters matters. See also skip_filters().
+ <<"Q = qlc:q([{X,Y} || {X,Y} <- [{a,1},{b,2}],
+ {Z,W} <- [{a,1},{c,0}],
+ X =:= Z,
+ begin Y/W > 0 end]),
+ [{a,1}] = qlc:e(Q)">>,
+ <<"Q = qlc:q([{X,Y} || {X,Y} <- [{a,1},{b,2}],
+ {Z,W} <- [{a,1},{c,0}],
+ begin Y/W > 0 end,
+ X =:= Z]),
+ {'EXIT', _} = (catch qlc:e(Q))">>,
+
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ F = fun() -> [foo || A <- [0], 1/A] end,
+ Q1 = qlc:q([X || {X} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ F(), % invalidates next filter
+ X =:= Y]),
+ {qlc,_,[{generate,_,{table,{ets,table,_}}},
+ {generate,_,{table,{ets,table,_}}},_,_],
+ []} = i(Q1),
+ {'EXIT', _} = (catch qlc:e(Q1))
+ end, [{1},{2},{3}])
+ end, [{a},{b},{c}])">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+join_lookup(doc) ->
+ "Lookup join.";
+join_lookup(suite) -> [];
+join_lookup(Config) when is_list(Config) ->
+ Ts = [
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ Q = qlc:q([{X,Y} || {_,Y} <- ets:table(E2),
+ {X,_} <- ets:table(E1),
+ X =:= Y], [{join,lookup}]),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2),
+ [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}] = lists:sort(R)">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ F = fun(J) -> qlc:q([{X,Y} || {X,_} <- ets:table(E1),
+ {_,Y} <- ets:table(E2),
+ X =:= Y], {join, J})
+ end,
+ Q = F(lookup),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2),
+ [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}] = lists:sort(R)">>,
+
+ <<"etsc(fun(E1) ->
+ E2 = qlc_SUITE:table([{1,a},{a},{1,b},{b}], 2, []),
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), % (1)
+ {_,Z} <- E2, % (2)
+ (Z =:= Y) and (X =:= a)
+ or
+ (Z =:= Y) and (X =:= b)]),
+ %% Cannot look up in (1) (X is keypos). Can look up (2).
+ %% Lookup-join: traverse (1), look up in (2).
+ {0,1,0,0} = join_info_count(Q),
+ [{a,a},{b,a}] = qlc:e(Q)
+ end, [{a,a},{b,a},{c,3},{d,4}])">>,
+
+ <<"%% The pattern {X,_} is used to filter out looked up objects.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {X,_} <- ets:table(E),
+ Y <- [{a,b},{c,d},{1,2},{3,4}],
+ X =:= element(1, Y)]),
+ {0,1,0,0} = join_info_count(Q),
+ [1] = qlc:e(Q)
+ end, [{1,2},{3}])">>,
+
+ <<"E = ets:new(e, [bag,{keypos,2}]),
+ L = lists:sort([{a,1},{b,1},{c,1},{d,1},
+ {aa,2},{bb,2},{cc,2},{dd,2}]),
+ true = ets:insert(E, L ++ [{aaa,1,1},{bbb,2,2},{ccc,3,3}]),
+ Q = qlc:q([Z || {_,Y}=Z <- ets:table(E),
+ {X} <- [{X} || X <- lists:seq(0, 10)],
+ X =:= Y]),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E),
+ L = lists:sort(R)">>,
+
+ <<"E = ets:new(e, [bag,{keypos,2}]),
+ L = lists:sort([{a,1},{b,1},{c,1},{d,1},
+ {aa,2},{bb,2},{cc,2},{dd,2}]),
+ true = ets:insert(E, L ++ [{aaa,1,1},{bbb,2,2},{ccc,3,3}]),
+ Q = qlc:q([Z || {X} <- [{X} || X <- lists:seq(0, 10)],
+ {_,Y}=Z <- ets:table(E),
+ X =:= Y]),
+ {0,1,0,0} = join_info_count(Q),
+ R = qlc:e(Q),
+ ets:delete(E),
+ L = lists:sort(R)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{b,1},{c,3}],
+ {Y,YY} <- qlc_SUITE:table_lookup_error([{1,a}]),
+ X =:= Y],
+ {join,lookup}),
+ {error, lookup, failed} = qlc:e(Q)">>,
+
+ <<"E = create_ets(1, 10),
+ Q = qlc:q([{X,Y} ||
+ {X,_} <- ets:table(E),
+ {_,Y} <- qlc_SUITE:table_error([{a,1}], 1, err),
+ X =:= Y]),
+ {0,1,0,0} = join_info_count(Q),
+ err = qlc:e(Q),
+ ets:delete(E)">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+join_merge(doc) ->
+ "Merge join.";
+join_merge(suite) -> [];
+join_merge(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q = qlc:q([{X,Y} || {X} <- [], {Y} <- [{1}], X =:= Y],
+ {join,merge}),
+ [] = qlc:e(Q)
+ ">>,
+
+ <<"Q = qlc:q([{X,Y} || {X} <- [{1}], {Y} <- [], X =:= Y],
+ {join,merge}),
+ [] = qlc:e(Q)
+ ">>,
+
+ <<"Q = qlc:q([{X,Y} || {X} <- [{1},{1},{1}],
+ {Y} <- [{1},{1},{1}], X =:= Y],
+ {join,merge}),
+ 9 = length(qlc:e(Q))
+ ">>,
+
+ <<"%% Two merge joins possible.
+ Q = qlc:q([{X,Y,Z,W} || {X,Y} <- [{1,a},{1,b},{1,c}],
+ {Z,W} <- [{1,a},{1,b},{1,c}],
+ X =:= Z,
+ Y =:= W]),
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},C,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},C,[]}}],[]}},
+ _],
+ [{join,merge}]}},
+ _,_],[]} = qlc:info(Q, {format,debug}),
+ [{1,a,1,a},{1,b,1,b},{1,c,1,c}] = qlc:e(Q)">>,
+
+ <<"%% As the last one, but comparison.
+ Q = qlc:q([{X,Y,Z,W} || {X,Y} <- [{1,a},{1,b},{1,c}],
+ {Z,W} <- [{1,a},{1,b},{1,c}],
+ X == Z, % skipped
+ Y =:= W]),
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},1,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,_},1,[]}}],[]}},
+ _],
+ [{join,merge}]}},
+ _],[]} = qlc:info(Q, {format,debug}),
+ [{1,a,1,a},{1,b,1,b},{1,c,1,c}] = qlc:e(Q)">>,
+
+ <<"%% This is no join.
+ Q = qlc:q([{X,Y,Z,W} || {X,Y} <- [], {Z,W} <- [],
+ X =:= Y, Z =:= W]),
+ {0,0,0,0} = join_info_count(Q)">>,
+
+ <<"%% Used to replace empty ETS tables with [], but that won't work.
+ E1 = ets:new(e1, []),
+ E2 = ets:new(e2, []),
+ Q = qlc:q([{X,Z,W} ||
+ {X, Z} <- ets:table(E1),
+ {W, Y} <- ets:table(E2),
+ X =:= Y],
+ {join, lookup}),
+ [] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"Q = qlc:q([{X,Y} || {X} <- [{3},{1},{0}],
+ {Y} <- [{1},{2},{3}],
+ X =:= Y]),
+ {1,0,0,2} = join_info_count(Q),
+ [{1,1},{3,3}] = qlc:e(Q)">>,
+
+ <<"QH = qlc:q([{X,Y,Z,W} || {X,Y} <- [{3,c},{2,b},{1,a}],
+ {Z,W} <- [{2,b},{4,d},{5,e},{3,c}],
+ X =:= Z,
+ Y =:= W]),
+ {1,0,0,2} = join_info_count(QH),
+ [{2,b,2,b},{3,c,3,c}] = qlc:e(QH)">>,
+
+ <<"%% QLC finds no join column at run time...
+ QH = qlc:q([1 || X <- [{1,2,3},{4,5,6}],
+ Y <- [{1,2},{3,4}],
+ X =:= Y]),
+ {0,0,0,0} = join_info_count(QH),
+ [] = qlc:e(QH)">>,
+
+ <<"QH = qlc:q([X || X <- [{1,2,3},{4,5,6}],
+ Y <- [{1,2},{3,4}],
+ element(1, X) =:= element(2, Y)]),
+ {1,0,0,2} = join_info_count(QH),
+ [{4,5,6}] = qlc:e(QH)">>,
+
+ <<"Q = qlc:q([{A,X,Z,W} ||
+ A <- [a,b,c],
+ {X,Z} <- [{a,1},{b,4},{c,6}],
+ {W,Y} <- [{2,a},{3,b},{4,c}],
+ X =:= Y],
+ {cache, list}),
+ _ = qlc:info(Q),
+ [{a,a,1,2},{a,b,4,3},{a,c,6,4},{b,a,1,2},{b,b,4,3},
+ {b,c,6,4},{c,a,1,2},{c,b,4,3},{c,c,6,4}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{X,Y} ||
+ {X,Z} <- [{a,1},{b,4},{c,6}],
+ {W,Y} <- [{2,a},{3,b},{4,c}],
+ Z > W,
+ X =:= Y],
+ {join,merge}),
+ {qlc,_,[{generate,_,{qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,_,1,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,_,2,[]}}],
+ []}},_],[{join,merge}]}},
+ _,_],[]} = i(Q),
+ [{b,b},{c,c}] = qlc:e(Q)">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ %% A match spec.; Q does not see Q1 and Q2 as lookup-tables.
+ Q1 = qlc:q([X || X <- ets:table(E1)]),
+ Q2 = qlc:q([X || X <- ets:table(E2)]),
+ F = fun(J) -> qlc:q([{X,Y} || X <- Q1,
+ Y <- Q2,
+ element(1,X) =:= element(1,Y)],
+ [{join,J}])
+ end,
+ {'EXIT',{cannot_carry_out_join,_}} = (catch qlc:e(F(lookup))),
+ Q = F(merge),
+ {1,0,0,2} = join_info(Q),
+ R = lists:sort(qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2),
+ true = [{Y,Y} || X <- lists:seq(5, 10), {} =/= (Y = {X,X})] =:= R
+ ">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ Q = qlc:q([{X,Y} || X <- ets:table(E1),
+ Y <- ets:table(E2),
+ element(1,X) =:= element(1,Y)],
+ [{join,merge}]),
+ {1,0,0,2} = join_info(Q),
+ R = lists:sort(qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2),
+ true = [{Y,Y} || X <- lists:seq(5, 10), {} =/= (Y = {X,X})] =:= R
+ ">>,
+
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 15),
+ Q1 = qlc:q([Y || X <- ets:table(E1), begin Y = {X}, true end]),
+ %% A match spec.; Q does not see Q2 as a lookup-table.
+ %%
+ %% OTP-6673: lookup join is considered but since there is no
+ %% filter that can do the job of Q2, lookup join is not an option..
+ Q2 = qlc:q([{X} || X <- ets:table(E2)]),
+ F = fun(J) ->
+ qlc:q([{X,Y} || X <- Q1,
+ Y <- Q2,
+ element(1,X) =:= element(1,Y)],
+ [{join,J}])
+ end,
+ {'EXIT',{cannot_carry_out_join,_}} = (catch qlc:e(F(lookup))),
+ Q = F(any),
+ {1,0,0,2} = join_info(Q),
+ R = lists:sort(qlc:e(Q)),
+ ets:delete(E1),
+ ets:delete(E2),
+ true = [{Y,Y} || X <- lists:seq(5, 10), {} =/= (Y = {{X,X}})] =:= R
+ ">>,
+
+ <<"L1 = [{1,a},{2,a},{1,b},{2,b},{1,c},{2,c}],
+ L2 = [{b,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ Q = qlc:q([{XX,YY} ||
+ {X,XX} <- L1,
+ {YY,Y} <- L2,
+ X == Y],
+ {join,J}),
+ qlc:q([{XX1,YY1,XX2,YY2} ||
+ {XX1,YY1} <- Q,
+ {XX2,YY2} <- Q])
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = lists:sort(qlc:e(Qm)) =:= lists:sort(qlc:e(Qn))">>,
+
+ <<"L1 = [{{1,a},2},{{3,c},4}],
+ L2 = [{a,{1,a}},{c,{4,d}}],
+ Q = qlc:q([{X,Y} || {X,_} <- L1,
+ {_,{Y,Z}} <- L2,
+ X == {Y,Z}
+ ]),
+ {qlc,_,[{generate,_,{qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,L1},1,[]}}],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{keysort,{list,L2},2,[]}}],[]}},
+ _],
+ [{join,merge}]}}],[]} = i(Q),
+ [{{1,a},1}] = qlc:e(Q)">>,
+
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E1), % (1)
+ {Z} <- ets:table(E2), % (2)
+ (Z =:= X) and
+ (Y =:= a) and
+ (X =:= Y) or
+ (Y =:= b) and
+ (Z =:= Y)]),
+ %% Cannot look up in (1) (X is keypos). Can look up (2).
+ %% Lookup join not possible (cannot look up in (1)).
+ %% Merge join is possible (after lookup in (2)).
+ {1,0,0,2} = join_info_count(Q),
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,
+ {table,{ets,table,_}},
+ 2,[]}},_C1],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,{table,_},1,[]}},_C2],
+ []}},
+ _],[{join,merge}]}},_],[]} = i(Q),
+ [{a,a}] = qlc:e(Q)
+ end, [{a}])
+ end, [{a,1},{a,a},{b,1},{b,2}])">>,
+
+ <<"Q = qlc:q([{G1,G2} ||
+ G1<- [{1}],
+ G2 <- [{1}],
+ element(1, G1) =:= element(1, G2)]),
+ {1,0,0,2} = join_info(Q),
+ [{{1},{1}}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{X,Y} ||
+ X <- [{1}],
+ Y <- [{1}],
+ element(1, X) =:= element(1, Y)],
+ {join,merge}),
+ {1,0,0,2} = join_info(Q),
+ [{{1},{1}}] = qlc:e(Q)">>,
+
+ <<"%% Generator after the join-filter.
+ Q = qlc:q([Z ||
+ {X} <- [{1},{2},{3}],
+ {Y} <- [{2},{3},{4}],
+ X =:= Y,
+ Z <- [1,2]]),
+ {qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{keysort,{list,[{1},{2},{3}]},1,[]}}],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{keysort,{list,_},1,[]}}],[]}},_],
+ [{join,merge}]}}, _,{generate,_,{list,_}}],[]} = i(Q),
+ [1,2,1,2] = qlc:e(Q)">>,
+
+ <<"%% X and W occur twice in the pattern of the extra join handle.
+ Q = qlc:q([{Z,W} ||
+ {X,Z,X} <- [{1,2,1},{1,2,2}],
+ {W,Y,W} <- [{a,1,a}],
+ X =:= Y]),
+ [{2,a}] = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, Ts),
+
+ %% Small examples. Returning an error term.
+ ETs = [
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- [{0,a},{1,a},{1,aa},{2,b},{2,bb},{2,bbb},
+ {3,c},{3,cc}],
+ X =:= Y],
+ {join,M})
+ end,
+ R = qlc:e(F(nested_loop)),
+ R = qlc:e(F(merge))">>,
+
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- [{0,a},{1,a},{1,aa},{2,b},{2,bb},{2,bbb},
+ {4,d}],
+ X =:= Y],
+ {join,M})
+ end,
+ R = qlc:e(F(nested_loop)),
+ R = qlc:e(F(merge))">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{b,1},{c,3}],
+ {Y,YY} <- [{1,a}],
+ X =:= Y],
+ {join,merge}),
+ [{b,a}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{b,1},{c,3}],
+ {Y,YY} <- qlc_SUITE:table_error([{1,a}], 1, err),
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{aa,1}],
+ {Y,YY} <- [{1,a}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{aa,a}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([{a,1},{aa,1}],
+ 2, err),
+ {Y,YY} <- [{1,a}],
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1}],
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa}]= qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([{a,1}], 2, err),
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ C = qlc:cursor(Q),
+ [{a,a}] = qlc:next_answers(C, 1),
+ qlc:delete_cursor(C),
+ err = qlc:e(Q)">>,
+
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- [{0,a},{1,a},{1,aa},{2,b},
+ {2,bb},{2,bbb}],
+ X =:= Y],
+ {join,M})
+ end,
+ %% [{a,a},{a,aa},{b,b},{b,bb},{b,bbb},{bb,b},{bb,bb},{bb,bbb}]
+ R = qlc:e(F(nested_loop)),
+ R = qlc:e(F(merge))">>,
+
+
+ <<"F = fun(M) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2},{bb,2},{c,3},{cc,3}],
+ {Y,YY} <- qlc_SUITE:table_error([{0,a},{1,a},{1,aa},
+ {2,b},{2,bb},
+ {2,bbb}],
+ 1, err),
+ X =:= Y],
+ {join,M})
+ end,
+ %% [{a,a},{a,aa},{b,b},{b,bb},{b,bbb},{bb,b},{bb,bb},{bb,bbb}]
+ err = qlc:e(F(nested_loop)),
+ err = qlc:e(F(merge))">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([], 2, err),
+ {Y,YY} <- [{2,b},{3,c}],
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{c,3}],
+ {Y,YY} <- [{2,b},{3,c}],
+ X =:= Y],
+ {join,merge}),
+ [{c,c}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{aa,1}],
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa},{aa,a},{aa,aa}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2}],
+ {Y,YY} <- [{1,a},{1,aa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2}],
+ {Y,YY} <- qlc_SUITE:table_error([{1,a},{1,aa}],
+ 1, err),
+ X =:= Y],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{a,1},{b,2}],
+ {Y,YY} <- [{1,a},{1,aa},{1,aaa},{1,aaaa}],
+ X =:= Y],
+ {join,merge}),
+ [{a,a},{a,aa},{a,aaa},{a,aaaa}]= qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{element(1, X), element(2, Y)} ||
+ X <- [{a,1},{aa,1}],
+ Y <- [{1,a},{1,aa}],
+ element(2, X) =:= element(1, Y)],
+ {join,merge}),
+ [{a,a},{a,aa},{aa,a},{aa,aa}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{element(1, X), element(2, Y)} ||
+ X <- [{a,1},{aa,1}],
+ Y <- qlc_SUITE:table_error([], 1, err),
+ element(2, X) =:= element(1, Y)],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{element(1, X), element(2, Y)} ||
+ X <- qlc_SUITE:table_error([{a,1}], 2, err),
+ Y <- [{2,b}],
+ element(2, X) =:= element(1, Y)],
+ {join,merge}),
+ err = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- [{1,a},{'1b',b},{2,b}],
+ {Y,YY} <- [{a,1},{b,'1b'},{c,1}],
+ X == Y],
+ {join,merge}),
+ [{1,1},{'1b','1b'},{2,'1b'}] = qlc:e(Q)">>,
+
+ <<"Q = qlc:q([{XX,YY} ||
+ {XX,X} <- qlc_SUITE:table_error([{1,a},{'1b',b},{2,b}],
+ 2, err),
+ {Y,YY} <- [{a,1},{b,'1b'},{c,1}],
+ X == Y],
+ {join,merge}),
+ err = qlc:e(Q)">>
+
+ ],
+ ?line run(Config, ETs),
+
+ %% Mostly examples where temporary files are needed while merging.
+ FTs = [
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = qlc:e(Qm,{max_list_size, 0}) =:= qlc:e(Qn)">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,merge}),
+ {error,_,{file_error,_,_}} =
+ qlc:e(Q, [{max_list_size,64*1024},{tmpdir,\"/a/b/c\"}])">>,
+
+ <<"L1 = qlc_SUITE:table_error([{1,a},{2,a}], 2, err),
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ err = qlc:e(Qm, {max_list_size,64*1024}),
+ err = qlc:e(Qn)">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = qlc_SUITE:table_error([{a,Y} || Y <- lists:seq(1, 10000)],
+ 1, err),
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ err = qlc:e(Qm, {max_list_size,64*1024}),
+ err = qlc:e(Qn)">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)] ++
+ [{'1b',b},{2,b}] ++ [{Y,d} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)] ++
+ [{b,'1b'}] ++ [{c,1}] ++ [{d,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = lists:sort(qlc:e(Qm, {max_list_size,64*1024})) =:=
+ lists:sort(qlc:e(Qn))">>,
+
+ <<"F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- [{Y,a} || Y <- lists:seq(1, 2)],
+ {Y,YY} <- [{a,Y} || Y <- lists:seq(1,100000)],
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = qlc:e(Qm, {max_list_size,64*1024}) =:= qlc:e(Qn)">>,
+
+ %% More than one join in one QLC expression.
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y,
+ begin XX > 1 end,
+ begin YY > 9999 end],
+ {join,J}),
+ qlc:q([{XX1,YY1,XX2,YY2} ||
+ {XX1,YY1} <- Q,
+ {XX2,YY2} <- Q])
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ R1 = lists:sort(qlc:e(Qm, {max_list_size,64*1024})),
+ R2 = lists:sort(qlc:e(Qm, {max_list_size,1 bsl 31})),
+ true = R1 =:= lists:sort(qlc:e(Qn)),
+ true = R1 =:= R2">>,
+
+ <<"L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ F = fun(J) ->
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y,
+ begin XX > 1 end,
+ begin YY > 9999 end],
+ {join,J}),
+ qlc:q([{XX1,YY1,XX2,YY2} ||
+ {XX1,YY1} <- Q,
+ {XX2,YY2} <- Q,
+ throw(thrown)])
+ end,
+ Qm = F(merge),
+ thrown = (catch {any_term, qlc:e(Qm, {max_list_size,64*1024})})">>,
+
+ <<"%% Bigger than 64*1024.
+ T1 = {1, lists:seq(1, 20000)},
+ L1 = [{a,T1},{b,T1}],
+ L2 = [{T1,a},{T1,b}],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ R = [{a,a},{a,b},{b,a},{b,b}],
+ R = qlc:e(Qm, {max_list_size,64*1024}),
+ R = qlc:e(Qn)">>,
+
+ <<"%% Bigger than 64*1024. No temporary files.
+ T1 = {1, lists:seq(1, 20000)},
+ L1 = [{a,T1},{b,T1}],
+ L2 = [{T1,a},{T1,b}],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ R = [{a,a},{a,b},{b,a},{b,b}],
+ R = qlc:e(Qm, {max_list_size,1 bsl 31}),
+ R = qlc:e(Qn)">>
+
+
+ ],
+ ?line run(Config, FTs),
+
+ ok.
+
+join_sort(doc) ->
+ "Merge join optimizations (avoid unnecessary sorting).";
+join_sort(suite) -> [];
+join_sort(Config) when is_list(Config) ->
+ Ts = [
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}]),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {X,_,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ {1,0,0,2} = join_info(H3),
+ [{4,4}] = qlc:e(H3)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}]),
+ H1 = qlc:q([X || X <- H1_1], unique), % keeps the order
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {X,_,_} <- H1, % no extra keysort
+ {Y,_} <- H2, % an extra keysort
+ X =:= Y]),
+ {1,0,0,3} = join_info(H3),
+ [{1,1}] = qlc:e(H3)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}], {tmpdir,\"\"}),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {_,X,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ {1,0,0,3} = join_info(H3),
+ [{2,2}] = qlc:e(H3)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6}], {tmpdir,\"\"}),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{1,2},{3,4}]),
+ H3 = qlc:q([{X,Y} || {_,X,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ {1,0,0,3} = join_info(H3),
+ [{2,2}] = qlc:e(H3)">>,
+
+ <<"H1 = qlc:sort([{1,a},{2,b},{3,c}]),
+ %% Since H1 is sorted it is also keysorted on the first column.
+ Q = qlc:q([{X, Y} || {X,_} <- H1,
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:sort([{r,a,1},{r,b,2},{r,c,3}]),
+ Q = qlc:q([{X, Y} || {r,_,X} <- H1, % needs keysort(3)
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,2} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"QH = qlc:q([X || X <- [{1,2,3},{4,5,6}],
+ Y <- qlc:sort([{1,2},{3,4}]),
+ element(1, X) =:= element(2, Y)]),
+ {1,0,0,2} = join_info_count(QH),
+ [{4,5,6}] = qlc:e(QH)">>,
+
+ <<"H1_1 = qlc:keysort(1, [{1,2,3},{4,5,6},{1,2,3}]),
+ H1 = qlc:q([X || X <- H1_1], unique),
+ H2 = qlc:keysort(2, [{2,1},{3,4}]),
+ H3 = qlc:q([{X,Y} || {X,_,_} <- H1,
+ {_,Y} <- H2,
+ X =:= Y]),
+ H4 = qlc:keysort(1, [{1,2},{3,4},{4,a}]),
+ H5 = qlc:q([{X,Y} || {X,_} <- H4,
+ {_,Y} <- H3,
+ X =:= Y]),
+ {2,0,0,3} = join_info_count(H5),
+ [{1,1},{4,4}]= qlc:e(H5)">>,
+
+ <<"
+ H1 = qlc:keysort(2, [{1,a,u},{2,b,k},{3,c,l}]),
+ H2 = qlc:q([{a,X,Y,a} || {1,X,u} <- H1,
+ {2,Y,k} <- H1]),
+ %% Neither H1 nor H2 need to be key-sorted
+ %% (the columns are constant).
+ H3 = qlc:q([{A,B,C,D,E,F,G,H} ||
+ {A,B,C,D} <- H2,
+ {E,F,G,H} <- H2,
+ A =:= H],
+ {join,merge}),
+ {1,0,0,4} = join_info_count(H3),
+ [{a,a,b,a,a,a,b,a}] = qlc:e(H3)">>,
+
+ <<"%% Q1 is sorted on X or Y.
+ Q1 = qlc:q([{X,Y} ||
+ {X,_} <- qlc:keysort(1, [{1,a},{2,b}]),
+ {_,Y} <- qlc:keysort(2, [{aa,11},{bb,22}]),
+ X < Y]),
+ [{1,11},{1,22},{2,11},{2,22}] = qlc:e(Q1),
+ Q = qlc:q([{X,Y} ||
+ {X,_} <- Q1, % no need to sort Q1
+ {Y} <- [{0},{1},{2},{3}],
+ X =:= Y]),
+ {1,0,0,3} = join_info_count(Q),
+ [{1,1},{1,1},{2,2},{2,2}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort([2], [{r,1},{r,2},{r,3}]),
+ %% H1 is actually sorted, but this info is not captured.
+ Q = qlc:q([{X, Y} || {r,X} <- H1,
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,2} = join_info_count(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"%% Two leading constants columns and sorted objects
+ %% implies keysorted on column 3.
+ H1 = qlc:sort(qlc:q([{a,X,Y} || {X,Y} <- [{1,2},{2,3},{3,3}]])),
+ H2 = qlc:q([{X,Y} ||
+ {a,3,X} <- H1,
+ {a,2,Y} <- H1,
+ X =:= Y]),
+ {1,0,0,0} = join_info_count(H2),
+ [{3,3}] = qlc:e(H2)">>,
+
+ <<"QH = qlc:q([{X,Y} || {X,Y} <- [{1,4},{1,3}],
+ {Z} <- [{1}],
+ X =:= Z, (Y =:= 3) or (Y =:= 4)]),
+ {1,0,0,1} = join_info_count(QH),
+ [{1,4},{1,3}] = qlc:e(QH)">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E), % no need to sort
+ {Y} <- [{0},{1},{2}],
+ X == Y], {join,merge}),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"H1 = qlc:sort([{r,1,a},{r,2,b},{r,3,c}]),
+ Q = qlc:q([{X, Y} || {r,X,_} <- H1, % does not need keysort(3)
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = [{a},{b}],
+ %% Several columns in different qualifiers have initial
+ %% constant columns.
+ H3 = qlc:keysort(1,[{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ Q = qlc:q([{r,X,Y,Z} || {r,X} <- H1,
+ {Y} <- H2,
+ {c1,c2,Z} <- H3,
+ X =:= Z], {join,merge}),
+ {1,0,0,3} = join_info(Q),
+ [{r,1,a,1},{r,1,b,1},{r,2,a,2},{r,2,b,2},{r,3,a,3},{r,3,b,3}] =
+ qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = [{a},{b}],
+ %% As the last one, but one keysort less.
+ H3 = qlc:keysort(3,[{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ Q = qlc:q([{r,X,Y,Z} || {r,X} <- H1,
+ {Y} <- H2,
+ {c1,c2,Z} <- H3,
+ X =:= Z], {join,merge}),
+ {1,0,0,2} = join_info(Q),
+ [{r,1,a,1},{r,1,b,1},{r,2,a,2},{r,2,b,2},{r,3,a,3},{r,3,b,3}] =
+ qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = [{a},{b}],
+ H3 = qlc:keysort(1,[{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ %% One generator before the joined generators.
+ Q = qlc:q([{r,X,Y,Z} || {Y} <- H2,
+ {r,X} <- H1,
+ {c1,c2,Z} <- H3,
+ X =:= Z], {join,merge}),
+ {1,0,0,3} = join_info(Q),
+ [{r,1,a,1},{r,2,a,2},{r,3,a,3},{r,1,b,1},{r,2,b,2},{r,3,b,3}] =
+ qlc:e(Q)">>,
+
+ <<"H1 = [{a,1},{b,2},{c,3},{d,4}],
+ H2 = [{a},{b}],
+ H3 = [{c1,c2,a},{foo,bar,b},{c1,c2,c},{c1,c2,d}],
+ %% A couple of \"extra\" filters and generators.
+ Q = qlc:q([{X,Y,Z} || {X,_} <- H1,
+ {Y} <- H2,
+ X > Y,
+ {c1,c2,Z} <- H3,
+ {W} <- [{a},{b}],
+ W > a,
+ X =:= Z]),
+ {1,0,0,2} = join_info(Q),
+ [{c,a,c},{c,b,c},{d,a,d},{d,b,d}] = qlc:e(Q)">>,
+
+ <<"H1 = qlc:keysort(2,[{r,1},{r,2},{r,3}]),
+ H2 = qlc:sort([{c1,c2,1},{foo,bar,2},{c1,c2,3},{c1,c2,2}]),
+ %% H2 is sorted, no keysort necessary.
+ %% This example shows that the 'filter-part' of the pattern
+ %% ({c1,c2,Z}) should be evaluated _before_ the join.
+ %% Otherwise the objects cannot be assumed to be keysort:ed on the
+ %% third column (if merge join), and lookup-join would lookup
+ %% more keys than necessary.
+ Q = qlc:q([{r,X,Z} || {r,X} <- H1,
+ {c1,c2,Z} <- H2,
+ X =:= Z] ,{join,merge}),
+ {1,0,0,1} = join_info(Q),
+ [{r,1,1},{r,2,2},{r,3,3}] = qlc:e(Q)">>,
+
+ <<"H1 = [{1,a},{2,b},{3,c}],
+ H2 = [{0,0},{1,1},{2,2}],
+ H3 = qlc:q([{A,C,D} ||
+ {A,_B} <- H1,
+ {C,D} <- H2,
+ A == D, C == D]),
+ H4 = [{1,1},{2,2},{3,3}],
+ H5 = qlc:q([{X,Y} ||
+ {X,_,_} <- H3, % no need to sort this one (merge join)
+ {_,Y} <- H4,
+ X == Y]),
+ Q = qlc:q([{X,Y} ||
+ {X,_} <- H5, % no need to sort this one
+ {Y,_} <- H4,
+ X == Y]),
+ {{3,0,0,4},{3,0,0,6}} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ <<"%% There is an extra test (_C1, element(1, X) =:= 1) that is not
+ %% necessary since the match spec does the same check. This can be
+ %% improved upon.
+ Q = qlc:q([{X,Y} ||
+ X <- [{2},{1}],
+ element(1, X) =:= 1,
+ Y=_ <- [{2},{1}],
+ element(1, X) =:= element(1, Y)]),
+ {qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{qlc,_,
+ [{generate,_,{list,{list,_},_}},
+ _C1],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,{list,[{2},{1}]}},
+ _C2],[]}},_],
+ [{join,merge}]}},_],[]} = i(Q),
+ {1,0,0,0} = join_info_count(Q),
+ [{{1},{1}}] = qlc:e(Q)">>,
+
+ <<"etsc(fun(E) ->
+ L = [{a,b,a},{c,d,b},{1,2,a},{3,4,b}],
+ Q = qlc:q([P1 || {X,2,Z}=P1 <- ets:table(E),
+ Y <- L,
+ X =:= 1,
+ Z =:= a,
+ P1 =:= Y,
+ X =:= element(1, Y)]),
+ {1,0,0,0} = join_info_count(Q),
+ [{1,2,a}] = qlc:e(Q)
+ end, [{1,2,a},{3,4,b}])">>,
+
+ %% Merge join on Z and element(3, Y). No need to sort!
+ <<"etsc(fun(E) ->
+ L = [{a,b,a},{c,d,b},{1,2,a},{3,4,b}],
+ Q = qlc:q([P1 || {X,2,Z}=P1 <- ets:table(E),
+ Y <- L,
+ (X =:= 1) or (X =:= 2),
+ Z =:= a,
+ P1 =:= Y,
+ X =:= element(1, Y)]),
+ {1,0,0,0} = join_info_count(Q),
+ [{1,2,a}] = qlc:e(Q)
+ end, [{1,2,a},{3,4,b}])">>,
+
+ <<"%% Y is constant as well as X. No keysort, which means that
+ %% Y must be filtered before merge join.
+ etsc(fun(E) ->
+ Q = qlc:q([X || {1,2}=X <- ets:table(E),
+ Y <- [{a,b},{c,d},{1,2},{3,4}],
+ X =:= Y,
+ element(1, X) =:= element(1, Y)]),
+ {1,0,0,0} = join_info_count(Q),
+ [{1,2}] = qlc:e(Q)
+ end, [{1,2},{3,4}])">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+join_complex(doc) ->
+ "Join of more than two columns.";
+join_complex(suite) -> [];
+join_complex(Config) when is_list(Config) ->
+ Ts = [{three,
+ <<"three() ->
+ L = [],
+ Q = qlc:q([{X,Y,Z} || {X,_} <- L,
+ {_,Y} <- L,
+ {Z,_} <- L,
+ X =:= Y, Y == Z
+ ]),
+ qlc:e(Q).">>,
+ [],
+ {warnings,[{3,qlc,too_complex_join}]}},
+
+ {two,
+ <<"two() ->
+ Q = qlc:q([{X,Y,Z,W} ||
+ {X} <- [],
+ {Y} <- [],
+ {Z} <- [],
+ {W} <- [],
+ X =:= Y,
+ Z =:= W],{join,merge}),
+ qlc:e(Q).">>,
+ [],
+ {warnings,[{2,qlc,too_many_joins}]}}
+ ],
+
+ ?line compile(Config, Ts),
+
+ Ts2 = [{three,
+ <<"three() ->
+ L = [],
+ Q = qlc:q([{X,Y,Z} || {X,_} <- L,
+ {_,Y} <- L,
+ {Z,_} <- L,
+ X =:= Y, Y == Z
+ ]),
+ qlc:e(Q).">>,
+ [],
+ {[],
+ ["cannot handle join of three or more generators efficiently"]}},
+
+ {two,
+ <<"two() ->
+ Q = qlc:q([{X,Y,Z,W} ||
+ {X} <- [],
+ {Y} <- [],
+ {Z} <- [],
+ {W} <- [],
+ X =:= Y,
+ Z =:= W],{join,merge}),
+ qlc:e(Q).">>,
+ [],
+ {[],["cannot handle more than one join efficiently"]}}
+ ],
+
+ ?line compile_format(Config, Ts2),
+
+ ok.
+
+tickets(suite) ->
+ [otp_5644, otp_5195, otp_6038_bug, otp_6359, otp_6562, otp_6590,
+ otp_6673, otp_6964, otp_7114, otp_7232, otp_7238, otp_7552, otp_6674,
+ otp_7714].
+
+otp_5644(doc) ->
+ "OTP-5644. Handle the new language element M:F/A.";
+otp_5644(suite) -> [];
+otp_5644(Config) when is_list(Config) ->
+ Ts = [
+ <<"Q = qlc:q([fun modul:mfa/0 || _ <- [1,2],
+ is_function(fun modul:mfa/0, 0)]),
+ [_,_] = qlc:eval(Q)">>
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+otp_5195(doc) ->
+ "OTP-5195. Allow traverse functions returning terms.";
+otp_5195(suite) -> [];
+otp_5195(Config) when is_list(Config) ->
+ %% Several minor improvements have been implemented in OTP-5195.
+ %% The test cases are spread all over... except these.
+ %%
+ %% Traverse functions returning terms.
+
+ Ts = [<<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ Err = qlc:e(H)">>,
+
+ <<"Err = {error,modul,err},
+ TravFun = fun() -> Err end,
+ H1 = qlc:sort(qlc:q([X || X <- qlc:table(TravFun, [])])),
+ H = qlc:q([{X} || X <- H1]),
+ Err = qlc:e(H)">>,
+
+ <<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ C = qlc:cursor(H),
+ R = qlc:next_answers(C, all_remaining),
+ qlc:delete_cursor(C),
+ Err = R">>,
+
+ <<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ Err = qlc:fold(F, [], H)">>,
+
+ <<"Err = {error,modul,err},
+ TravFun = fun() -> Err end,
+ H1 = qlc:sort(qlc:q([X || X <- qlc:table(TravFun, [])])),
+ H = qlc:q([{X} || X <- H1]),
+ F = fun(Obj, A) -> A++[Obj] end,
+ Err = qlc:fold(F, [], H)">>,
+
+ <<"Q1 = qlc:append([qlc:append([ugly()]),[3]]),
+ Q = qlc:q([X || X <- Q1]),
+ 42 = qlc:e(Q),
+ ok.
+
+ ugly() ->
+ [apa | fun() -> 42 end].
+ foo() -> bar">>,
+
+ <<"L = [1,2,3],
+ Err = {error,modul,err},
+ H = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ H1 = qlc:q([X || X <- H], unique),
+ Err = qlc:e(H1)">>,
+
+ <<"Err = {error, module, err},
+ L = [1,2,3],
+ H1 = qlc:q([{X} || X <- qlc_SUITE:table_error(L, Err)]),
+ H = qlc:q([{X,Y,Z} || X <- H1, Y <- H1, Z <- L], cache),
+ qlc:e(H, cache_all)">>,
+
+ <<"Err = {error, module, err},
+ L = [1,2,3],
+ H1 = qlc:q([X || X <- qlc_SUITE:table_error(L, Err)]),
+ H = qlc:q([{X,Y,Z} || X <- H1, Y <- H1, Z <- L], cache),
+ qlc:e(H, [cache_all,unique_all])">>,
+
+ <<"L = [{1},{2},{3}],
+ H = qlc:q([X || {X} <- qlc_SUITE:table_lookup_error(L),
+ X =:= 2]),
+ {error, lookup, failed} = qlc:e(H)">>,
+
+ %% The traverse function can return any value, but it must not
+ %% return an improper list. Improper lists must not be given anyway.
+ <<"{'EXIT', {{badfun,a},_}} =
+ (catch qlc:e(qlc:q([{X} || X <- [1 | a], begin true end])))">>
+
+ ],
+
+ ?line run(Config, Ts),
+
+ Ts2 = [<<"Q = qlc:q([{X,Y} || {X} <- [{1},{2},{3}],
+ begin
+ %% Used to generate a badly formed file
+ Y = 3, true
+ end,
+ X =:= Y]),
+ [{3,3}] = qlc:e(Q)">>],
+ ?line run(Config, Ts2),
+
+ ok.
+
+otp_6038_bug(doc) ->
+ "OTP-6038. Bug fixes: unique and keysort; cache.";
+otp_6038_bug(suite) -> [];
+otp_6038_bug(Config) when is_list(Config) ->
+ %% The 'unique' option can no longer be merged with the keysort options.
+ %% This used to return [{1,a},{1,c},{2,b},{2,d}], but since
+ %% file_sorter:keysort now removes duplicates based on keys, the
+ %% correct return value is [{1,a},{2,b}].
+ Ts = [<<"H1 = qlc:q([X || X <- [{1,a},{2,b},{1,c},{2,d}]], unique),
+ H2 = qlc:keysort(1, H1, [{unique,true}]),
+ [{1,a},{2,b}] = qlc:e(H2)">>],
+
+ ?line run(Config, Ts),
+
+ %% Sometimes the cache options did not empty the correct tables.
+ CTs = [
+ <<"Options = [cache,unique],
+ V1 = qlc:q([{X,Y} || X <- [1,2], Y <- [3]], Options),
+ V2 = qlc:q([{X,Y} || X <- [a,b], Y <- V1]),
+ V3 = qlc:q([{X,Y} || X <- [5,6], Y <- [7]], Options),
+ Q = qlc:q([{X,Y} || X <- V2, Y <- V3]),
+ R = qlc:e(Q),
+ L1 = [{X,Y} || X <- [1,2], Y <- [3]],
+ L2 = [{X,Y} || X <- [a,b], Y <- L1],
+ L3 = [{X,Y} || X <- [5,6], Y <- [7]],
+ L = [{X,Y} || X <- L2, Y <- L3],
+ true = R =:= L">>,
+ <<"Options = [cache,unique],
+ V1 = qlc:q([{X,Y} || X <- [1,2], Y <- [3]], Options),
+ V2 = qlc:q([{X,Y} || X <- [a,b], Y <- V1]),
+ V3 = qlc:q([{X,Y} || X <- [5,6], Y <- [7]], Options),
+ V4 = qlc:q([{X,Y} || X <- V2, Y <- V3], Options),
+ Q = qlc:q([{X,Y} || X <- [1,2], Y <- V4]),
+ R = qlc:e(Q),
+ L1 = [{X,Y} || X <- [1,2], Y <- [3]],
+ L2 = [{X,Y} || X <- [a,b], Y <- L1],
+ L3 = [{X,Y} || X <- [5,6], Y <- [7]],
+ L4 = [{X,Y} || X <- L2, Y <- L3],
+ L = [{X,Y} || X <- [1,2], Y <- L4],
+ true = R =:= L">>
+ ],
+ ?line run(Config, CTs),
+
+ ok.
+
+otp_6359(doc) ->
+ "OTP-6359. dets:select() never returns the empty list.";
+otp_6359(suite) -> [];
+otp_6359(Config) when is_list(Config) ->
+ dets:start(),
+ T = luna,
+ Fname = filename(T, Config),
+
+ Ts = [
+ [<<"T = luna, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ Q = qlc:q([F ||
+ F <- dets:table(T),
+ (F band ((1 bsl 0)) =/= 0),
+ true]),
+ [] = qlc:eval(Q),
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\"),
+ ok">>]
+ ],
+
+ ?line run(Config, Ts),
+ ok.
+
+otp_6562(doc) ->
+ "OTP-6562. compressed = false (should be []) when sorting before join.";
+otp_6562(suite) -> [];
+otp_6562(Config) when is_list(Config) ->
+ Bug = [
+ %% This example uses a file to sort E2 on the second column. It is
+ %% not easy to verify that this happens; the file_sorter module's
+ %% size option cannot be set in this case. But it is not likely
+ %% that the default size (512 KiB) will ever change, so it should
+ %% be future safe.
+ <<"E1 = create_ets(1, 10),
+ E2 = create_ets(5, 150000),
+ Q = qlc:q([{XX,YY} ||
+ {X,XX} <- ets:table(E1),
+ {YY,Y} <- ets:table(E2),
+ X == Y],
+ {join,merge}),
+ [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>
+ ],
+ ?line run(Config, Bug),
+
+ Bits = [
+ {otp_6562_1,
+ <<"otp_6562_1() ->
+ Q = qlc:q([X || <<X:8>> <= <<\"hej\">>]),
+ qlc:info(Q).
+ ">>,
+ [],
+ {errors,[{2,qlc,binary_generator}],
+ []}}
+ ],
+ ?line [] = compile(Config, Bits),
+
+ ?line R1 = {error,qlc,{1,qlc,binary_generator}}
+ = qlc:string_to_handle("[X || <<X:8>> <= <<\"hej\">>]."),
+ ?line "1: cannot handle binary generators\n" =
+ lists:flatten(qlc:format_error(R1)),
+
+ ok.
+
+otp_6590(doc) ->
+ "OTP-6590. Bug fix (join info).";
+otp_6590(suite) -> [];
+otp_6590(Config) when is_list(Config) ->
+ Ts = [<<"fun(Tab1Value) ->
+ Q = qlc:q([T1#tab1.id || T1 <- [#tab1{id = id1,
+ value = v,
+ tab2_id = id}],
+ T2 <- [#tab2{id = id}],
+ T1#tab1.value =:= Tab1Value,
+ T1#tab1.tab2_id =:= T2#tab2.id]),
+ [id1] = qlc:e(Q)
+ end(v)">>],
+
+ ?line run(Config, <<"-record(tab1, {id, tab2_id, value}).
+ -record(tab2, {id, value}).\n">>, Ts),
+ ok.
+
+otp_6673(doc) ->
+ "OTP-6673. Optimizations and fixes.";
+otp_6673(suite) -> [];
+otp_6673(Config) when is_list(Config) ->
+ Ts_PT =
+ [<<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E1),
+ {C,D} <- ets:table(E2),
+ A =:= 2, % lookup
+ B =:= D, % join
+ C =:= g]), % lookup invalidated by join
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,
+ {list,{table,_},
+ [{{'$1','$2'},[],['$_']}]},
+ 2,[]}},_],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,
+ {keysort,{table,_},2,[]}}],
+ []}},_],
+ [{join,merge}]}},_,_],[]} = i(Q),
+ [{2,y,g,y}] = qlc:e(Q)
+ end, [{f,x},{g,y},{h,z}])
+ end,
+ [{1,x},{2,y},{3,z}])">>,
+ <<"etsc(fun(E1) ->
+ etsc(fun(E2) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E1),
+ {C,D} <- ets:table(E2),
+ A =:= 2, % lookup
+ C =:= g, % lookup
+ B =:= D]), % join
+ {qlc,_,[{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,
+ {keysort,
+ {list,{table,_},
+ [{{'$1','$2'},[],['$_']}]},
+ 2,[]}},_],[]}},
+ {generate,_,{qlc,_,
+ [{generate,_,
+ {keysort,
+ {list,{table,_},
+ [{{'$1','$2'},[],['$_']}]},
+ 2,[]}},_],[]}},_],
+ [{join,merge}]}},_],[]} = i(Q),
+ [{2,y,g,y}] = qlc:e(Q)
+ end, [{f,x},{g,y},{h,z}])
+ end,
+ [{1,x},{2,y},{3,z}])">>],
+
+ ?line run(Config, Ts_PT),
+
+ MS = ets:fun2ms(fun({X,_Y}=T) when X > 1 -> T end),
+ Ts_RT = [
+ [<<"%% Explicit match-spec. ets:table() ensures there is no lookup
+ %% function, which means that lookup join will not be considered.
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ etsc(fun(E) ->
+ F = fun(J) ->
+ qlc:q([{X,W} ||
+ {X,_Y} <-
+ ets:table(E,{traverse,
+ {select,MS}}),
+ {Z,W} <- [{1,1},{2,2},{3,3}],
+ X =:= Z], {join,J})
+ end,
+ Qm = F(any),
+ [{2,2},{3,3}] = qlc:e(Qm),
+ {'EXIT',{cannot_carry_out_join,_}} =
+ (catch qlc:e(F(lookup)))
+ end, [{1,a},{2,b},{3,c}])">>],
+
+ <<"%% The filter 'A =< y' can be evaluated by traversing E1 using a
+ %% match specification, but then lookup join cannot use E1 for
+ %% looking up keys. This example shows that the filter is kept if
+ %% lookup join is employed (otherwise it is optimized away since
+ %% the match spec is used).
+ etsc(fun(E1) ->
+ Q = qlc:q([{A,B,C,D} ||
+ {A,B} <- ets:table(E1),
+ {C,D} <- [{x,f},{y,g},{z,h}],
+ A =< y, % kept
+ A =:= C], {join,lookup}),
+ [{x,1,x,f},{y,2,y,g}] = lists:sort(qlc:e(Q))
+ end, [{x,1},{y,2},{z,3}])">>
+
+ ],
+ ?line run(Config, Ts_RT),
+
+ %% Ulf Wiger provided a patch that makes QLC work with packages:
+ Dir = filename:join(?privdir, "p"),
+ ?line ok = filelib:ensure_dir(filename:join(Dir, ".")),
+ File = filename:join(Dir, "p.erl"),
+ ?line ok = file:write_file(File,
+ <<"-module(p.p).\n"
+ "-export([q/0]).\n"
+ "-include_lib(\"stdlib/include/qlc.hrl\").\n"
+ "q() ->\n"
+ " .qlc:q([X || X <- [1,2]]).">>),
+ ?line {ok, 'p.p'} = compile:file(File, [{outdir,Dir}]),
+ ?line code:purge('p.p'),
+ ?line {module, 'p.p'} = code:load_abs(filename:rootname(File), 'p.p'),
+ ?line [1,2] = qlc:e(p.p:q()),
+
+ ok.
+
+otp_6964(doc) ->
+ "OTP-6964. New option 'tmpdir_usage'.";
+otp_6964(suite) -> [];
+otp_6964(Config) when is_list(Config) ->
+ T1 = [
+ <<"Q1 = qlc:q([{X} || X <- [1,2]]),
+ {'EXIT', {badarg,_}} = (catch qlc:e(Q1, {tmpdir_usage,bad})),
+ %% merge join
+ F = fun(Use) ->
+ L1 = [{Y,a} || Y <- lists:seq(1, 2)],
+ L2 = [{a,Y} || Y <- lists:seq(1, 10000)],
+ Q = qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,merge}),
+ qlc:e(Q, [{max_list_size,64*1024},{tmpdir_usage,Use}])
+ end,
+ D = erlang:system_flag(backtrace_depth, 0),
+ 20000 = length(F(allowed)),
+ ErrReply = F(not_allowed),
+ {error, qlc, {tmpdir_usage,joining}} = ErrReply,
+ \"temporary file was needed for joining\n\" =
+ lists:flatten(qlc:format_error(ErrReply)),
+ qlc_SUITE:install_error_logger(),
+ 20000 = length(F(warning_msg)),
+ {error, joining} = qlc_SUITE:read_error_logger(),
+ 20000 = length(F(info_msg)),
+ {info, joining} = qlc_SUITE:read_error_logger(),
+ 20000 = length(F(error_msg)),
+ {error, joining} = qlc_SUITE:read_error_logger(),
+ _ = erlang:system_flag(backtrace_depth, D),
+ qlc_SUITE:uninstall_error_logger()">>],
+ ?line run(Config, T1),
+
+ T2 = [
+ <<"%% File sorter.
+ T = lists:seq(1, 10000),
+ Q0 = qlc:q([{X} || X <- [T,T,T], begin X > 0 end],
+ [{cache,list},unique]),
+ Q1 = qlc:q([{X,Y,Z} ||
+ X <- Q0,
+ Y <- Q0,
+ Z <- Q0],
+ [{cache,list},unique]),
+ Q = qlc:q([{X, Y} || Y <- [1], X <- Q1]),
+ F = fun(Use) ->
+ qlc:e(Q, [{max_list_size,10000},{tmpdir_usage,Use}])
+ end,
+ 1 = length(F(allowed)),
+ ErrReply = F(not_allowed),
+ {error, qlc, {tmpdir_usage,caching}} = ErrReply,
+ \"temporary file was needed for caching\n\" =
+ lists:flatten(qlc:format_error(ErrReply)),
+ qlc_SUITE:install_error_logger(),
+ 1 = length(F(error_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ 1 = length(F(warning_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ 1 = length(F(info_msg)),
+ {info, caching} = qlc_SUITE:read_error_logger(),
+ {info, caching} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger()">>],
+
+ ?line run(Config, T2),
+
+ T3 = [
+ <<"%% sort/keysort
+ E1 = create_ets(1, 10),
+ E2 = create_ets(5, 50000),
+ Q = qlc:q([{XX,YY} ||
+ {X,XX} <- ets:table(E1),
+ {YY,Y} <- ets:table(E2),
+ X == Y],
+ {join,merge}),
+ F = fun(Use) ->
+ qlc:e(Q, {tmpdir_usage,Use})
+ end,
+ ErrReply = F(not_allowed),
+ {error,qlc,{tmpdir_usage,sorting}} = ErrReply,
+ \"temporary file was needed for sorting\n\" =
+ lists:flatten(qlc:format_error(ErrReply)),
+ qlc_SUITE:install_error_logger(),
+ L = [{5,5},{6,6},{7,7},{8,8},{9,9},{10,10}],
+ L = F(allowed),
+ L = F(error_msg),
+ {error, sorting} = qlc_SUITE:read_error_logger(),
+ L = F(info_msg),
+ {info, sorting} = qlc_SUITE:read_error_logger(),
+ L = F(warning_msg),
+ {error, sorting} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger(),
+ ets:delete(E1),
+ ets:delete(E2)">>],
+ ?line run(Config, T3),
+
+ T4 = [
+ <<"%% cache list
+ etsc(fun(E) ->
+ Q0 = qlc:q([X || X <- ets:table(E),
+ begin element(1, X) > 5 end],
+ {cache,list}),
+ Q = qlc:q([{X, element(1,Y)} || X <- lists:seq(1, 5),
+ Y <- Q0]),
+ R = [{X,Y} || X <- lists:seq(1, 5),
+ Y <- lists:seq(6, 10)],
+ F = fun(Use) ->
+ qlc:e(Q, [{max_list_size, 100*1024},
+ {tmpdir_usage, Use}])
+ end,
+ R = lists:sort(F(allowed)),
+ qlc_SUITE:install_error_logger(),
+ R = lists:sort(F(info_msg)),
+ {info, caching} = qlc_SUITE:read_error_logger(),
+ R = lists:sort(F(error_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ R = lists:sort(F(warning_msg)),
+ {error, caching} = qlc_SUITE:read_error_logger(),
+ qlc_SUITE:uninstall_error_logger(),
+ ErrReply = F(not_allowed),
+ {error,qlc,{tmpdir_usage,caching}} = ErrReply,
+ \"temporary file was needed for caching\n\" =
+ lists:flatten(qlc:format_error(ErrReply))
+ end, [{keypos,1}], [{I,a,lists:duplicate(100000,1)} ||
+ I <- lists:seq(1, 10)])">>],
+ ?line run(Config, T4),
+ ok.
+
+otp_7238(doc) ->
+ "OTP-7238. info-option 'depth', &c.";
+otp_7238(suite) -> [];
+otp_7238(Config) when is_list(Config) ->
+ dets:start(),
+ T = otp_7238,
+ Fname = filename(T, Config),
+
+ ?line ok = compile_gb_table(Config),
+
+ %% A few more warnings.
+ T1 = [
+ %% The same error message string, but with different tags
+ %% (the strings are not compared :-(
+ {nomatch_1,
+ <<"nomatch_1() ->
+ {qlc:q([X || X={X} <- []]), [t || \"a\"=\"b\" <- []]}.">>,
+ [],
+ {warnings,[{{2,30},qlc,nomatch_pattern},
+ {{2,44},v3_core,nomatch}]}},
+
+ %% Not found by qlc...
+ {nomatch_2,
+ <<"nomatch_2() ->
+ qlc:q([t || {\"a\"++\"b\"} = {\"ac\"} <- []]).">>,
+ [],
+ {warnings,[{{2,22},v3_core,nomatch}]}},
+
+ {nomatch_3,
+ <<"nomatch_3() ->
+ qlc:q([t || [$a, $b] = \"ba\" <- []]).">>,
+ [],
+ {warnings,[{{2,37},qlc,nomatch_pattern}]}},
+
+ %% Not found by qlc...
+ {nomatch_4,
+ <<"nomatch_4() ->
+ qlc:q([t || \"a\"++_=\"b\" <- []]).">>,
+ [],
+ {warnings,[{{2,22},v3_core,nomatch}]}},
+
+ %% Found neither by the compiler nor by qlc...
+ {nomatch_5,
+ <<"nomatch_5() ->
+ qlc:q([X || X = <<X>> <- [3]]).">>,
+ [],
+ []},
+
+ {nomatch_6,
+ <<"nomatch_6() ->
+ qlc:q([X || X <- [],
+ X =:= {X}]).">>,
+ [],
+ {warnings,[{{3,30},qlc,nomatch_filter}]}},
+
+ {nomatch_7,
+ <<"nomatch_7() ->
+ qlc:q([X || {X=Y,{Y}=X} <- []]).">>,
+ [],
+ {warnings,[{{2,28},qlc,nomatch_pattern}]}},
+
+ {nomatch_8,
+ <<"nomatch_8() ->
+ qlc:q([X || {X={},X=[]} <- []]).">>,
+ [],
+ {warnings,[{{2,28},qlc,nomatch_pattern}]}},
+
+ {nomatch_9,
+ <<"nomatch_9() ->
+ qlc:q([X || X <- [], X =:= {}, X =:= []]).">>,
+ [],
+ {warnings,[{{2,49},qlc,nomatch_filter}]}},
+
+ {nomatch_10,
+ <<"nomatch_10() ->
+ qlc:q([X || X <- [],
+ ((X =:= 1) or (X =:= 2)) and (X =:= 3)]).">>,
+ [],
+ {warnings,[{{3,53},qlc,nomatch_filter}]}},
+
+ {nomatch_11,
+ <<"nomatch_11() ->
+ qlc:q([X || X <- [], x =:= []]).">>,
+ [],
+ {warnings,[{{2,39},qlc,nomatch_filter}]}},
+
+ {nomatch_12,
+ <<"nomatch_12() ->
+ qlc:q([X || X={} <- [], X =:= []]).">>,
+ [],
+ {warnings,[{{2,42},qlc,nomatch_filter}]}},
+
+ {nomatch_13,
+ <<"nomatch_13() ->
+ qlc:q([Z || Z <- [],
+ X={X} <- [],
+ Y={Y} <- []]).">>,
+ [],
+ {warnings,[{{3,29},qlc,nomatch_pattern},
+ {{4,29},qlc,nomatch_pattern}]}},
+
+ {nomatch_14,
+ <<"nomatch_14() ->
+ qlc:q([X || X={X} <- [],
+ 1 > 0,
+ 1 > X]).">>,
+ [],
+ {warnings,[{{2,29},qlc,nomatch_pattern}]}},
+
+ {nomatch_15,
+ <<"nomatch_15() ->
+ qlc:q([{X,Y} || X={X} <- [1],
+ Y <- [1],
+ 1 > 0,
+ 1 > X]).">>,
+ [],
+ {warnings,[{{2,32},qlc,nomatch_pattern}]}},
+
+ %% Template warning.
+ {nomatch_template1,
+ <<"nomatch_template1() ->
+ qlc:q([{X} = {} || X <- []]).">>,
+ [],
+ {warnings,[{2,sys_core_fold,no_clause_match}]}}
+ ],
+ ?line [] = compile(Config, T1),
+
+ %% 'depth' is a new option used by info()
+ T2 = [
+ %% Firstly: lists
+ <<"L = [[a,b,c],{a,b,c},[],<<\"foobar\">>],
+ Q = qlc:q([{X} || X <- L]),
+ {call, _,
+ {remote,_,{atom,_,ets},{atom,_,match_spec_run}},
+ [{cons,_,{atom,_,'...'},
+ {cons,_,{atom,_,'...'},
+ {cons,_,{nil,_},{cons,_,{atom,_,'...'},{nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,0}]),
+
+ {call,_,_,
+ [{cons,_,{cons,_,{atom,_,'...'},{nil,_}},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,1}]),
+
+ {call,_,
+ _,
+ [{cons,_,{cons,_,{atom,_,a},{atom,_,'...'}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,'...'}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_},
+ {_,_,{_,_,$.},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,2}]),
+
+ {call,_,_,
+ [{cons,_,
+ {cons,_,{atom,_,a},{cons,_,{atom,_,b},{atom,_,'...'}}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,b},{atom,_,'...'}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$o},_,_},_,_,_]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,3}]),
+
+ {call,_,_,
+ [{cons,_,
+ {cons,_,
+ {atom,_,a},{cons,_,{atom,_,b},{cons,_,{atom,_,c},{nil,_}}}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,b},{atom,_,c}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$b},_,_},
+ {_,_,{_,_,$a},_,_},
+ {_,_,{_,_,$r},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,10}]),
+
+ {call,_,_,
+ [{cons,_,
+ {cons,_,
+ {atom,_,a},{cons,_,{atom,_,b},{cons,_,{atom,_,c},{nil,_}}}},
+ {cons,_,
+ {tuple,_,[{atom,_,a},{atom,_,b},{atom,_,c}]},
+ {cons,_,{nil,_},
+ {cons,_,
+ {bin,_,
+ [{_,_,{_,_,$f},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$o},_,_},
+ {_,_,{_,_,$b},_,_},
+ {_,_,{_,_,$a},_,_},
+ {_,_,{_,_,$r},_,_}]},
+ {nil,_}}}}},
+ _]} = qlc:info(Q, [{format,abstract_code},{depth,infinity}])">>,
+
+ %% Secondly: looked up keys
+ <<"F = fun(D) ->
+ etsc(fun(E) ->
+ Q = qlc:q([C || {N,C} <- ets:table(E),
+ (N =:= {2,2}) or (N =:= {3,3})]),
+ F = qlc:info(Q, [{format,abstract_code},{depth,D}]),
+ {call,_,_,[{call,_,_,[_Fun,Values]},_]} = F,
+ [b,c] = lists:sort(qlc:eval(Q)),
+ Values
+ end, [{{1,1},a},{{2,2},b},{{3,3},c},{{4,4},d}])
+ end,
+
+ [{cons,_,{atom,_,'...'},{cons,_,{atom,_,'...'},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{integer,_,2},{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{integer,_,3},{atom,_,'...'}]},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{integer,_,2},{integer,_,2}]},
+ {cons,_,{tuple,_,[{integer,_,3},{integer,_,3}]},{nil,_}}},
+ {cons,_,
+ {tuple,_,[{integer,_,2},{integer,_,2}]},
+ {cons,_,{tuple,_,[{integer,_,3},{integer,_,3}]},{nil,_}}}] =
+ lists:map(F, [0,1,2,3,infinity])">>,
+ [<<"T = otp_7238, Fname = \"">>, Fname, <<"\",
+ {ok, _} = dets:open_file(T, [{file,Fname}]),
+ ok = dets:insert(T, [{{1,1},a},{{2,2},b},{{3,3},c},{{4,4},d}]),
+ Q = qlc:q([C || {N,C} <- dets:table(T),
+ (N =:= {2,2}) or (N =:= {3,3})]),
+ F = qlc:info(Q, [{format,abstract_code},{depth,1}]),
+ [b,c] = lists:sort(qlc:eval(Q)),
+ {call,_,_,
+ [{call,_,_,
+ [_,
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}}]},
+ _]} = F,
+ ok = dets:close(T),
+ file:delete(\"">>, Fname, <<"\")">>],
+
+ %% Thirdly: format_fun has been extended (in particular: gb_table)
+ <<"T = gb_trees:from_orddict([{{1,a},w},{{2,b},v},{{3,c},u}]),
+ QH = qlc:q([X || {{X,Y},_} <- gb_table:table(T),
+ ((X =:= 1) or (X =:= 2)),
+ ((Y =:= a) or (Y =:= b) or (Y =:= c))]),
+ {call,_,_,
+ [{call,_,_,
+ [{'fun',_,
+ {clauses,
+ [{clause,_,_,[],
+ [{'case',_,
+ {call,_,_,
+ [_,
+ {call,_,_,
+ [{cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}}}]}]},
+ [_,_]}]}]}},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}}}}}}]},
+ {call,_,_,
+ [{cons,_,{tuple,_,[{atom,_,'...'}]},{nil,_}}]}]} =
+ qlc:info(QH, [{format,abstract_code},{depth,1}])">>,
+ <<"T1 = [{1,1,a},{2,2,b},{3,3,c},{4,4,d}],
+ T2 = [{x,1},{y,1},{z,2}],
+ QH1 = T1,
+ T = gb_trees:from_orddict(T2),
+ QH2 = qlc:q([X || {_,X} <- gb_table:table(T)], cache),
+ Q = qlc:q([{X1,X2,X3} || {X1,X2,X3} <- QH1,
+ Y2 <- QH2,
+ X2 =:= Y2]),
+ {block,_,
+ [{match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [{generate,_,_,
+ {call,_,_,
+ [{call,_,_,
+ [{cons,_,
+ {tuple,_,[{atom,_,'...'}]},
+ {atom,_,'...'}}]}]}}]},
+ _]}},
+ {call,_,_,
+ [{lc,_,_,
+ [{generate,_,_,
+ {cons,_,{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}}},
+ _,_]}]}]} =
+ qlc:info(Q, [{format,abstract_code},{depth, 1},
+ {n_elements,1}])">>,
+ <<"L = [{{key,1},a},{{key,2},b},{{key,3},c}],
+ T = gb_trees:from_orddict(orddict:from_list(L)),
+ Q = qlc:q([K || {K,_} <- gb_table:table(T),
+ (K =:= {key,1}) or (K =:= {key,2})]),
+{call,_,_,
+ [{call,_,_,
+ [{'fun',_,
+ {clauses,
+ [{clause,_,_,[],
+ [{'case',_,
+ {call,_,_,
+ [_,
+ {call,_,_,
+ [{cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {nil,_}}}}]}]},
+ _}]}]}},
+ {cons,_,
+ {tuple,_,[{atom,_,key},{atom,_,'...'}]},
+ {cons,_,{tuple,_,[{atom,_,key},{atom,_,'...'}]},{nil,_}}}]},
+ {call,_,
+ {remote,_,{atom,_,ets},{atom,_,match_spec_compile}},
+ [{cons,_,
+ {tuple,_,[{tuple,_,[{atom,_,'...'}]},{atom,_,'...'}]},
+ {nil,_}}]}]} =
+ qlc:info(Q, [{format,abstract_code},{depth, 2}])">>
+
+ ],
+ ?line run(Config, T2),
+
+ T3 = [
+ {nomatch_6,
+ <<"nomatch_6() ->
+ qlc:q([X || X <- [],
+ X =:= {X}]).">>,
+ [],
+ {[],["filter evaluates to 'false'"]}},
+
+ {nomatch_7,
+ <<"nomatch_7() ->
+ qlc:q([X || {X=Y,{Y}=X} <- []]).">>,
+ [],
+ {[],["pattern cannot possibly match"]}}],
+ ?line compile_format(Config, T3),
+
+ %% *Very* simple test - just check that it doesn't crash.
+ Type = [{cres,
+ <<"Q = qlc:q([X || {X} <- []]),
+ {'EXIT',{{badfun,_},_}} = (catch qlc:e(Q))">>,
+ [type_checker],
+ []}],
+ ?line run(Config, Type),
+
+ ok.
+
+otp_7114(doc) ->
+ "OTP-7114. Match spec, table and duplicated objects..";
+otp_7114(suite) -> [];
+otp_7114(Config) when is_list(Config) ->
+ Ts = [<<"T = ets:new(t, [bag]),
+ [ets:insert(T, {t, I, I div 2}) || I <- lists:seq(1,10)],
+ Q1 = qlc:q([element(3, E) || E <- ets:table(T)]),
+ [0,1,1,2,2,3,3,4,4,5] = lists:sort(qlc:e(Q1)),
+ [0,1,2,3,4,5] = qlc:e(Q1, unique_all),
+ [0,1,2,3,4,5] = qlc:e(qlc:sort(Q1), unique_all),
+ [0,1,2,3,4,5] = qlc:e(qlc:sort(qlc:e(Q1)), unique_all),
+ ets:delete(T),
+ ok">>],
+ ?line run(Config, Ts).
+
+otp_7232(doc) ->
+ "OTP-7232. qlc:info() bug (pids, ports, refs, funs).";
+otp_7232(suite) -> [];
+otp_7232(Config) when is_list(Config) ->
+ Ts = [<<"L = [fun math:sqrt/1, list_to_pid(\"<0.4.1>\"),
+ erlang:make_ref()],
+ \"[fun math:sqrt/1,<0.4.1>,#Ref<\" ++ _ = qlc:info(L),
+ {call,_,
+ {remote,_,{atom,_,qlc},{atom,_,sort}},
+ [{cons,_,
+ {'fun',_,{function,math,sqrt,_}},
+ {cons,_,
+ {string,_,\"<0.4.1>\"}, % could use list_to_pid..
+ {cons,_,{string,_,\"#Ref<\"++_},{nil,_}}}},
+ {nil,_}]} =
+ qlc:info(qlc:sort(L),{format,abstract_code})">>,
+
+ <<"Q1 = qlc:q([X || X <- [1000,2000]]),
+ Q = qlc:sort(Q1, {order, fun(A,B)-> A>B end}),
+ \"qlc:sort([1000,2000],[{order,fun'-function/0-fun-2-'/2}])\" =
+ format_info(Q, true),
+ AC = qlc:info(Q, {format, abstract_code}),
+ \"qlc:sort([1000,2000], [{order,fun '-function/0-fun-2-'/2}])\" =
+ binary_to_list(iolist_to_binary(erl_pp:expr(AC)))">>,
+
+ %% OTP-7234. erl_parse:abstract() handles bit strings
+ <<"Q = qlc:sort([<<17:9>>]),
+ \"[<<8,1:1>>]\" = qlc:info(Q)">>
+
+ ],
+ ?line run(Config, Ts).
+
+otp_7552(doc) ->
+ "OTP-7552. Merge join bug.";
+otp_7552(suite) -> [];
+otp_7552(Config) when is_list(Config) ->
+ %% The poor performance cannot be observed unless the
+ %% (redundant) join filter is skipped.
+ Ts = [<<"Few = lists:seq(1, 2),
+ Many = lists:seq(1, 10),
+ S = [d,e],
+ L1 = [{Y,a} || Y <- Few] ++ [{'1b',b},{2,b}] ++
+ [{Y,X} || X <- S, Y <- Few],
+ L2 = [{a,Y} || Y <- Many] ++
+ [{b,'1b'}] ++ [{c,1}] ++
+ [{X,Y} || X <- S, Y <- Many],
+ F = fun(J) ->
+ qlc:q([{XX,YY} ||
+ {XX,X} <- L1,
+ {Y,YY} <- L2,
+ X == Y],
+ {join,J})
+ end,
+ Qm = F(merge),
+ Qn = F(nested_loop),
+ true = lists:sort(qlc:e(Qm, {max_list_size,20})) =:=
+ lists:sort(qlc:e(Qn))">>],
+ ?line run(Config, Ts).
+
+otp_7714(doc) ->
+ "OTP-7714. Merge join bug.";
+otp_7714(suite) -> [];
+otp_7714(Config) when is_list(Config) ->
+ %% The original example uses Mnesia. This one does not.
+ Ts = [<<"E1 = ets:new(set,[]),
+ true = ets:insert(E1, {a,1}),
+ E2 = ets:new(set,[]),
+ _ = [true = ets:insert(E2, {I, 1}) ||
+ I <- lists:seq(1, 3)],
+ Q = qlc:q([{A,B} ||
+ {A,I1} <- ets:table(E1),
+ {B,I2} <- ets:table(E2),
+ I1 =:= I2],{join,merge}),
+ [{a,1},{a,2},{a,3}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>],
+ ?line run(Config, Ts).
+
+otp_6674(doc) ->
+ "OTP-6674. match/comparison.";
+otp_6674(suite) -> [];
+otp_6674(Config) when is_list(Config) ->
+
+ ?line ok = compile_gb_table(Config),
+
+ Ts = [%% lookup join
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {0,1,0,0} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X =:= Y]),
+ {0,1,0,0} = join_info(Q),
+ {block,_,
+ [_,
+ {match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,_,{op,_,'==',_,_}]},
+ {cons,_,
+ {tuple,_,[{atom,_,join},{atom,_,lookup}]},_}]}},
+ _]} = qlc:info(Q, {format, abstract_code}),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"E = ets:new(join, [set]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X == Y], {join, lookup}),
+ {'EXIT',{cannot_carry_out_join,_}} = (catch qlc:e(Q)),
+ ets:delete(E)">>,
+
+ %% Lookup join possible in both directions.
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ X == Y],{join,lookup}), % skipped
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{table,{ets,table,[_]}}}],[]}},
+ {generate,_,{table,{ets,table,[_]}}},
+ _],
+ [{join,lookup}]}}],
+ []} = qlc:info(Q, {format,debug}),
+ {0,1,0,0} = join_info(Q),
+ [{1.0,1},{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ X =:= Y],{join,merge}), % not skipped
+ {1,0,0,1} = join_info(Q),
+ [{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ X =:= Y],{join,lookup}), % skipped
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,{table,{ets,table,[_]}}}],
+ []}},
+ {generate,_,{table,{ets,table,[_]}}},
+ _],
+ [{join,lookup}]}}],
+ []} = qlc:info(Q, {format,debug}),
+ {0,1,0,0} = join_info(Q),
+ [{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+ <<"E1 = ets:new(join, [ordered_set]),
+ E2 = ets:new(join, [set]),
+ true = ets:insert(E1, [{1.0,a},{2,b},{3,c}]),
+ true = ets:insert(E2, [{0},{1},{2}]),
+ Q = qlc:q([{X, Y} || {X,_} <- ets:table(E1),
+ {Y} <- ets:table(E2),
+ %% Independent of term comparison:
+ X =:= Y, X == Y]),
+ {0,1,0,0} = join_info(Q),
+ [{2,2}] = qlc:e(Q),
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,1},{2,2},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,Z} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X == Z, Y == Z]), % cannot skip (yet)
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[_,_,_],[{join,lookup}]}},
+ _,_],[]} = qlc:info(Q,{format,debug}),
+ {0,1,0,0} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ %% The following moved here from skip_filters. It was buggy!
+ <<"etsc(fun(E) ->
+ A = 3,
+ Q = qlc:q([X || X <- ets:table(E),
+ A == element(1,X)]),
+ {table, _} = i(Q),
+ case qlc:e(Q) of
+ [{3},{3.0}] -> ok;
+ [{3.0},{3}] -> ok
+ end,
+ false = lookup_keys(Q)
+ end, [{3},{3.0},{c}])">>,
+ <<"H1 = qlc:sort([{{192,192.0},1,a},{{192.0,192.0},2,b},{{192,192.0},3,c}]),
+ Q = qlc:q([{X, Y} || {{A,B},X,_} <- H1, % does not need keysort(3)
+ A == 192, B =:= 192.0,
+ {Y} <- [{0},{1},{2}],
+ X == Y]),
+ {block,0,
+ [{match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ %% Has to compare extra constant:
+ {op,_,'==',
+ {tuple,_,[{integer,_,192},{float,_,192.0}]},
+ {call,_,{atom,_,element},[{integer,_,1},{var,_,'P0'}]}}]}]}},
+ _,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ %% The join filter has been skipped.
+ {op,_,'==',{var,_,'A'},{integer,_,192}},
+ {op,_,'=:=',{var,_,'B'},{float,_,192.0}}]}]}]}
+ = qlc:info(Q, {format,abstract_code}),
+ {1,0,0,1} = join_info(Q),
+ [{1,1},{2,2}] = qlc:e(Q)">>,
+
+ %% Does not handle more than one lookup value (conjunctive).
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= 1 andalso X == 1.0]),
+ false = lookup_keys(H),
+ [1] = qlc:e(H)">>,
+
+ %% EqualConstants...
+ <<"etsc(fun(E) ->
+ Q = qlc:q([{X,Y} || {X} <- ets:table(E),
+ {Y} <- [{{1}},{{2}},{{1.0}},{{2.0}}],
+ X =:= {1}, X == {1.0},
+ X == Y], {join, merge}),
+ [{{1},{1}},{{1},{1.0}}] = lists:sort(qlc:e(Q)),
+ false = lookup_keys(Q)
+ end, [{{1}}, {{2}}])">>,
+
+ <<"T = gb_trees:from_orddict([{foo,{1}}, {bar,{2}}]),
+ Q = qlc:q([{X,Y} || {_,X} <- gb_table:table(T),
+ {Y} <- [{{1}},{{2}},{{1.0}},{{2.0}}],
+ (X =:= {1}) or (X == {2}),
+ (X == {1.0}) or (X =:= {2.0}),
+ X == Y], {join, merge}),
+ [{{1},{1}},{{1},{1.0}}] = qlc:e(Q)">>,
+
+ %% Compare key
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X == 1]),
+ [1] = lookup_keys(H),
+ [1] = qlc:e(H)">>,
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X == 1.0]),
+ [1.0] = lookup_keys(H), % this is how gb_table works...
+ [1.0] = qlc:e(H)">>,
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X == 1.0]),
+ [1] = qlc:e(H), % and this is how ETS works.
+ [1.0] = lookup_keys(H)
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= 2]),
+ [2] = lookup_keys(H),
+ %% Cannot (generally) remove the matching filter (the table
+ %% compares the key). But note that gb_table returns the given
+ %% term as key, so in this case the filter _could_ have been removed.
+ %% However, there is no callback to inform qlc about that.
+ {call,_,_,
+ [_,{call,_,_,
+ [{cons,_,{tuple,_,
+ [_,{cons,_,
+ {tuple,_,[{atom,_,'=:='},{atom,_,'$1'},{integer,_,2}]},
+ _},_]},_}]}]} = qlc:info(H, {format,abstract_code}),
+ [2] = qlc:e(H)">>,
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= 2.0]),
+ %% Just shows that the term (not the key) is returned.
+ [2.0] = lookup_keys(H),
+ [2.0] = qlc:e(H)">>,
+
+ <<"I = 1,
+ T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X == I]), % imported variable
+ [1] = lookup_keys(H),
+ {call,_,_,
+ [_,{call,_,_,
+ [{cons,_,
+ {tuple,_,
+ [{tuple,_,[{atom,_,'$1'},{atom,_,'_'}]},
+ {nil,_}, % the filter has been skipped
+ {cons,_,{atom,_,'$1'},_}]},
+ _}]}]} = qlc:info(H, {format, abstract_code}),
+ [1] = qlc:e(H)">>,
+ <<"I = 2,
+ T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,_} <- gb_table:table(T),
+ X =:= I]),
+ [2] = lookup_keys(H),
+ {call,_,_,
+ [_,{call,_,_,
+ [{cons,_,{tuple,_,
+ [_,{cons,_,
+ {tuple,_,
+ [{atom,_,'=:='},
+ {atom,_,'$1'},
+ {tuple,_,[{atom,_,const},{integer,_,2}]}]},
+ _},_]},
+ _}]}]} = qlc:info(H, {format, abstract_code}),
+ [2] = qlc:e(H)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= a]), % skipped
+ [a] = qlc:e(Q),
+ {list,{table,_},_} = i(Q),
+ [a] = lookup_keys(Q)
+ end, [ordered_set], [{a,1},{b,2},{3,c}])">>,
+
+ %% Does not find that if for instance X =:= {1} then the filter
+ %% X == {1} can be removed.
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1}, X == {1.0}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}}, {{2}}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= {1}, X == {1.0}]),
+ [{1}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [ordered_set], [{{1}}, {{2}}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X == {1.0}, X =:= {1}]),
+ [{1}] = qlc:e(Q),
+ [{1}] = lookup_keys(Q)
+ end, [{{1}}, {{2}}])">>,
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X == {1.0}, X =:= {1}]),
+ [{1}] = qlc:e(Q),
+ false = lookup_keys(Q)
+ end, [ordered_set], [{{1}}, {{2}}])">>,
+
+ <<"E = ets:new(apa, []),
+ true = ets:insert(E, [{1,a},{2,b}]),
+ {'EXIT', {badarg, _}} =
+ (catch qlc_SUITE:bad_table_key_equality(E)),
+ ets:delete(E)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X} <- ets:table(E),
+ X =:= 1, X =:= is_integer(X)]),
+ [] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X=1} <- ets:table(E),
+ X =:= is_integer(X)]),
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ {op,_,'=:=',
+ {var,_,'X'},
+ {call,_,
+ {atom,_,is_integer},
+ [{var,_,'X'}]}}]}]} =
+ qlc:info(Q, {format, abstract_code}),
+ [] = qlc:e(Q),
+ [1] = lookup_keys(Q)
+ end, [{1}, {2}])">>,
+
+ <<"T = gb_trees:from_orddict([{1,a},{2,b}]),
+ H = qlc:q([X || {X,Y} <- gb_table:table(T),
+ Y =:= a, true, X =:= 1]),
+ [1] = lookup_keys(H),
+ [1] = qlc:e(H)">>,
+
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B == 1]), % skipped
+ [{1.0, 1}] = qlc:e(H),
+ {qlc,_,[{generate,_,{table,_}}], []} = qlc:info(H, {format,debug})">>,
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B == 1.0]), % skipped
+ [{1.0, 1.0}] = qlc:e(H), % this is how gb_table works...
+ {qlc,_,[{generate,_,{table,_}}], []} = qlc:info(H, {format,debug})">>,
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B =:= 1.0]), % not skipped
+ [{1.0, 1.0}] = qlc:e(H),
+ {qlc,_,[{generate,_,{table,_}},_], []} = qlc:info(H,{format,debug})">>,
+ <<"T = gb_trees:from_orddict([{{1.0,1},a},{{2.0,2},b}]),
+ H = qlc:q([X || {{1.0,B}=X,_} <- gb_table:table(T),
+ B =:= 1]), % not skipped
+ [{1.0, 1}] = qlc:e(H),
+ {qlc,_,[{generate,_,{table,_}},_], []} = qlc:info(H,{format,debug})">>,
+
+ <<"%% The imported variables do not interfere with join.
+ E = ets:new(join, [ordered_set]),
+ {A, B} = {1,1},
+ true = ets:insert(E, [{1,a},{2,b},{3,c}]),
+ Q = qlc:q([{X, Y} || {X,_Z} <- ets:table(E),
+ {Y} <- [{0},{1},{2}],
+ X =:= A, Y =:= B,
+ Y == X], % skipped
+ {join, merge}),
+ [{1,1}] = qlc:e(Q),
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,
+ [{generate,_,
+ {qlc,_,[{generate,_,{list,{table,_},_}},_],[]}},
+ {generate,_,
+ {qlc,_,[{generate,_,{list,_,_}},_],[]}},
+ _],
+ [{join,merge}]}}],
+ []} = qlc:info(Q, {format, debug}),
+ ets:delete(E)">>,
+
+ <<"% An old bug: usort() should not be used when matching values
+ etsc(fun(E) ->
+ I = 1,
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= 1.0 orelse X =:= I]),
+ [1] = qlc:e(H),
+ [1.0] = lookup_keys(H) % do not look up twice
+ end, [set], [{1,a},{2,b}])">>,
+ <<"etsc(fun(E) ->
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= 1.0 orelse X == 1]),
+ [1] = qlc:e(H),
+ false = lookup_keys(H) % doesn't handle this case
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ I1 = 1, I2 = 1,
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ X =:= I1 orelse X == I2]),
+ [1] = qlc:e(H), % do not look up twice
+ [1] = lookup_keys(H)
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"etsc(fun(E) ->
+ I1 = 1, I2 = 1, I3 = 1,
+ H = qlc:q([X || {X,_} <- ets:table(E),
+ I1 == I2, I1 =:= I3, I3 == I2, I2 =:= I3,
+ X =:= I1 orelse X == I2
+ ]),
+ [1] = qlc:e(H),
+ [1] = lookup_keys(H)
+ end, [ordered_set], [{1,a},{2,b}])">>,
+
+ <<"E = ets:new(join, [ordered_set]),
+ true = ets:insert(E, [{1,a},{2,b,x},{3,c}]),
+ Q = qlc:q([P || P <- ets:table(E),
+ P =:= {1,a} orelse P =:= {2,b,x}]),
+ [{1,a},{2,b,x}] = qlc:e(Q),
+ ets:delete(E)">>,
+
+ <<"etsc(fun(E) ->
+ Q = qlc:q([X || {X,Y} <- ets:table(E),
+ ((X =:= 3) or (Y =:= 4)) and (X == a)]),
+ {list,{table,_},_} = i(Q),
+ [] = qlc:e(Q), % a is not an answer
+ [a] = lookup_keys(Q)
+ end, [{keypos,1},ordered_set], [{a,3},{b,4}])">>,
+
+ <<"Q = qlc:q([{X,Y} ||
+ {X} <- [{<<3:4>>}],
+ {Y} <- [{<<3:4>>}],
+ X =:= <<1:3,1:1>>, % <<3:4>>
+ Y =:= <<0:2,1:1,1:1>>, % <<3:4>>
+ X =:= Y]),
+ [{<<3:4>>,<<3:4>>}] = qlc:e(Q)">>
+
+
+ ],
+
+ ?line run(Config, Ts).
+
+manpage(doc) ->
+ "Examples from qlc(3).";
+manpage(suite) -> [];
+manpage(Config) when is_list(Config) ->
+
+ ?line ok = compile_gb_table(Config),
+
+ Ts = [
+ <<"QH = qlc:q([{X,Y} || X <- [a,b], Y <- [1,2]]),
+ QC = qlc:cursor(QH),
+ [{a,1}] = qlc:next_answers(QC, 1),
+ [{a,2}] = qlc:next_answers(QC, 1),
+ [{b,1},{b,2}] = qlc:next_answers(QC, all_remaining),
+ ok = qlc:delete_cursor(QC)">>,
+
+ <<"QH = qlc:q([{X,Y} || X <- [a,b], Y <- [1,2]]),
+ [{a,1},{a,2},{b,1},{b,2}] = qlc:eval(QH)">>,
+
+ <<"QH = [1,2,3,4,5,6],
+ 21 = qlc:fold(fun(X, Sum) -> X + Sum end, 0, QH)">>,
+
+ <<"QH = qlc:q([{X,Y} || X <- [x,y], Y <- [a,b]]),
+ B = \"begin\n\"
+ \" V1 =\n\"
+ \" qlc:q([ \n\"
+ \" SQV ||\n\"
+ \" SQV <- [x,y]\n\"
+ \" ],\n\"
+ \" [{unique,true}]),\n\"
+ \" V2 =\n\"
+ \" qlc:q([ \n\"
+ \" SQV ||\n\"
+ \" SQV <- [a,b]\n\"
+ \" ],\n\"
+ \" [{unique,true}]),\n\"
+ \" qlc:q([ \n\"
+ \" {X,Y} ||\n\"
+ \" X <- V1,\n\"
+ \" Y <- V2\n\"
+ \" ],\n\"
+ \" [{unique,true}])\n\"
+ \"end\",
+ true = B =:= qlc:info(QH, unique_all)">>,
+
+ <<"E1 = ets:new(e1, []),
+ E2 = ets:new(e2, []),
+ true = ets:insert(E1, [{1,a},{2,b}]),
+ true = ets:insert(E2, [{a,1},{b,2}]),
+ Q = qlc:q([{X,Z,W} ||
+ {X, Z} <- ets:table(E1),
+ {W, Y} <- ets:table(E2),
+ X =:= Y]),
+ L = \"begin\n\"
+ \" V1 =\n\"
+ \" qlc:q([ \n\"
+ \" P0 ||\n\"
+ \" P0 = {W,Y} <- ets:table(_)\n\"
+ \" ]),\n\"
+ \" V2 =\n\"
+ \" qlc:q([ \n\"
+ \" [G1|G2] ||\n\"
+ \" G2 <- V1,\n\"
+ \" G1 <- ets:table(_),\n\"
+ \" element(2, G1) =:= element(1, G2)\n\"
+ \" ],\n\"
+ \" [{join,lookup}]),\n\"
+ \" qlc:q([ \n\"
+ \" {X,Z,W} ||\n\"
+ \" [{X,Z}|{W,Y}] <- V2\n\"
+ \" ])\n\"
+ \"end\",
+ Info =
+ re:replace(qlc:info(Q),
+ \"table\\\\(-*[0-9]*\",
+ \"table(_\", [{return,list},global]),
+ L = Info,
+ ets:delete(E1),
+ ets:delete(E2)">>,
+
+ <<"Q = qlc:q([{A,X,Z,W} ||
+ A <- [a,b,c],
+ {X,Z} <- [{a,1},{b,4},{c,6}],
+ {W,Y} <- [{2,a},{3,b},{4,c}],
+ X =:= Y],
+ {cache, list}),
+ L =
+ \"begin\n\"
+ \" V1 =\n\"
+ \" qlc:q([ \n\"
+ \" P0 ||\n\"
+ \" P0 = {X,Z} <- qlc:keysort(1, [{a,1},{b,4},{c,6}], [])\n\"
+ \" ]),\n\"
+ \" V2 =\n\"
+ \" qlc:q([ \n\"
+ \" P0 ||\n\"
+ \" P0 = {W,Y} <- qlc:keysort(2, [{2,a},{3,b},{4,c}], [])\n\"
+ \" ]),\n\"
+ \" V3 =\n\"
+ \" qlc:q([ \n\"
+ \" [G1|G2] ||\n\"
+ \" G1 <- V1,\n\"
+ \" G2 <- V2,\n\"
+ \" element(1, G1) == element(2, G2)\n\"
+ \" ],\n\"
+ \" [{join,merge},{cache,list}]),\n\"
+ \" qlc:q([ \n\"
+ \" {A,X,Z,W} ||\n\"
+ \" A <- [a,b,c],\n\"
+ \" [{X,Z}|{W,Y}] <- V3,\n\"
+ \" X =:= Y\n\"
+ \" ])\n\"
+ \"end\",
+ L = qlc:info(Q)">>,
+
+ <<"E1 = ets:new(t, [set]), % uses =:=/2
+ Q1 = qlc:q([K ||
+ {K} <- ets:table(E1),
+ K == 2.71 orelse K == a]),
+ {list,{table,_}, [{{'$1'},[],['$1']}]} = i(Q1),
+ true = ets:delete(E1)">>,
+
+ <<"F = fun(E, I) ->
+ qlc:q([V || {K,V} <- ets:table(E), K == I])
+ end,
+ E2 = ets:new(t, [set]),
+ true = ets:insert(E2, [{{2,2},a},{{2,2.0},b},{{2.0,2},c}]),
+ Q2 = F(E2, {2,2}),
+ {table,{ets,table,[_,
+ [{traverse,{select,[{{'$1','$2'},
+ [{'==','$1',{const,{2,2}}}],
+ ['$2']}]}}]]}} = i(Q2),
+ [a,b,c] = lists:sort(qlc:e(Q2)),
+ true = ets:delete(E2),
+
+ E3 = ets:new(t, [ordered_set]), % uses ==/2
+ true = ets:insert(E3, [{{2,2.0},b}]),
+ Q3 = F(E3,{2,2}),
+ {list,{table,_},[{{'$1','$2'},[],['$2']}]} = i(Q3),
+ [b] = qlc:e(Q3),
+ true = ets:delete(E3)">>,
+
+ <<"T = gb_trees:empty(),
+ QH = qlc:q([X || {{X,Y},_} <- gb_table:table(T),
+ ((X == 1) or (X == 2)) andalso
+ ((Y == a) or (Y == b) or (Y == c))]),
+ L = \"ets:match_spec_run(lists:flatmap(fun(K) ->
+ case
+ gb_trees:lookup(K,
+ gb_trees:from_orddict([]))
+ of
+ {value,V} ->
+ [{K,V}];
+ none ->
+ []
+ end
+ end,
+ [{1,a},{1,b},{1,c},{2,a},{2,b},{2,c}]),
+ ets:match_spec_compile([{{{'$1','$2'},'_'},[],['$1']}]))\",
+ L = qlc:info(QH)">>
+ ],
+ ?line run(Config, Ts),
+
+ L = [1,2,3],
+ Bs = erl_eval:add_binding('L', L, erl_eval:new_bindings()),
+ QH = qlc:string_to_handle("[X+1 || X <- L].", [], Bs),
+ [2,3,4] = qlc:eval(QH),
+
+ %% ets(3)
+ MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y} end),
+ ETs = [
+ [<<"true = ets:insert(Tab = ets:new(t, []),[{1,a},{2,b},{3,c},{4,d}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ QH1 = ets:table(Tab, [{traverse, {select, MS}}]),
+
+ QH2 = qlc:q([{Y} || {X,Y} <- ets:table(Tab), (X > 1) or (X < 5)]),
+
+ true = qlc:info(QH1) =:= qlc:info(QH2),
+ true = ets:delete(Tab)">>]],
+ ?line run(Config, ETs),
+
+ %% dets(3)
+ DTs = [
+ [<<"{ok, T} = dets:open_file(t, []),
+ ok = dets:insert(T, [{1,a},{2,b},{3,c},{4,d}]),
+ MS = ">>, io_lib:format("~w", [MS]), <<",
+ QH1 = dets:table(T, [{traverse, {select, MS}}]),
+
+ QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t), (X > 1) or (X < 5)]),
+
+ true = qlc:info(QH1) =:= qlc:info(QH2),
+ ok = dets:close(T)">>]],
+ ?line run(Config, DTs),
+
+ ok.
+
+compile_gb_table(Config) ->
+ GB_table_file = filename("gb_table.erl", Config),
+ ?line ok = file:write_file(GB_table_file, gb_table()),
+ ?line {ok, gb_table} = compile:file(GB_table_file, [{outdir,?privdir}]),
+ ?line code:purge(gb_table),
+ ?line {module, gb_table} =
+ code:load_abs(filename:rootname(GB_table_file)),
+ ok.
+
+gb_table() ->
+ <<"
+-module(gb_table).
+
+-export([table/1]).
+
+table(T) ->
+ TF = fun() -> qlc_next(gb_trees:next(gb_trees:iterator(T))) end,
+ InfoFun = fun(num_of_objects) -> gb_trees:size(T);
+ (keypos) -> 1;
+ (is_sorted_key) -> true;
+ (is_unique_objects) -> true;
+ (_) -> undefined
+ end,
+ LookupFun =
+ fun(1, Ks) ->
+ lists:flatmap(fun(K) ->
+ case gb_trees:lookup(K, T) of
+ {value, V} -> [{K,V}];
+ none -> []
+ end
+ end, Ks)
+ end,
+ FormatFun =
+ fun({all, NElements, ElementFun}) ->
+ ValsS = io_lib:format(\"gb_trees:from_orddict(~w)\",
+ [gb_nodes(T, NElements, ElementFun)]),
+ io_lib:format(\"gb_table:table(~s)\", [ValsS]);
+ ({lookup, 1, KeyValues, _NElements, ElementFun}) ->
+ ValsS = io_lib:format(\"gb_trees:from_orddict(~w)\",
+ [gb_nodes(T, infinity, ElementFun)]),
+ io_lib:format(\"lists:flatmap(fun(K) -> \"
+ \"case gb_trees:lookup(K, ~s) of \"
+ \"{value, V} -> [{K,V}];none -> [] end \"
+ \"end, ~w)\",
+ [ValsS, [ElementFun(KV) || KV <- KeyValues]])
+ end,
+ qlc:table(TF, [{info_fun, InfoFun}, {format_fun, FormatFun},
+ {lookup_fun, LookupFun},{key_equality,'=='}]).
+
+qlc_next({X, V, S}) ->
+ [{X,V} | fun() -> qlc_next(gb_trees:next(S)) end];
+qlc_next(none) ->
+ [].
+
+gb_nodes(T, infinity, ElementFun) ->
+ gb_nodes(T, -1, ElementFun);
+gb_nodes(T, NElements, ElementFun) ->
+ gb_iter(gb_trees:iterator(T), NElements, ElementFun).
+
+gb_iter(_I, 0, _EFun) ->
+ '...';
+gb_iter(I0, N, EFun) ->
+ case gb_trees:next(I0) of
+ {X, V, I} ->
+ [EFun({X,V}) | gb_iter(I, N-1, EFun)];
+ none ->
+ []
+ end.
+ ">>.
+
+compat(suite) ->
+ [backward, forward].
+
+backward(doc) ->
+ "OTP-6674. Join info and extra constants.";
+backward(suite) -> [];
+backward(Config) when is_list(Config) ->
+ case try_old_join_info(Config) of
+ ok ->
+ ok;
+ Reply ->
+ Reply
+ end.
+
+-ifdef(debug).
+try_old_join_info(_Config) ->
+ ok.
+-else.
+try_old_join_info(Config) ->
+ case ?t:is_release_available("r12b") of
+ true ->
+ %% Check join info for handlers of extra constants. Start R12B-0.
+ ?line {ok, R12} = start_node_rel(r12, r12b, slave),
+ File = filename("handle.erl", Config),
+ ?line file:write_file(File,
+ <<"-module(handle).\n"
+ "-export([create_handle/0, lookup_handle/0]).\n"
+ "-include_lib(\"stdlib/include/qlc.hrl\").\n"
+ "create_handle() ->\n"
+ " H1 = qlc:sort([{192.0,1,a},{192.0,2,b},{192.0,3,c}]),\n"
+ " qlc:q([{X, Y} || {B,X,_} <- H1,\n"
+ " B =:= 192.0,\n"
+ " {Y} <- [{0},{1},{2}],\n"
+ " X == Y]).\n",
+ "\n",
+ "lookup_handle() ->\n"
+ " E = qlc_SUITE:table([{1,a},{2,b},{3,c}], 1, [1]),\n"
+ " qlc:q([{X, Y} || {X,_} <- E,\n"
+ " {Y} <- [{0},{1},{2}],\n"
+ " X =:= Y]).\n">>),
+ ?line {ok, handle} = rpc:call(R12, compile, file,
+ [File, [{outdir,?privdir}]]),
+ ?line {module, handle} = rpc:call(R12, code, load_abs,
+ [filename:rootname(File)]),
+ ?line H = rpc:call(R12, handle, create_handle, []),
+ ?line {module, handle} = code:load_abs(filename:rootname(File)),
+ ?line {block,0,
+ [{match,_,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ {op,_,'=:=',
+ {float,_,192.0},
+ {call,_,{atom,_,element},[{integer,_,1},_]}}]}]}},
+ _,_,
+ {call,_,_,
+ [{lc,_,_,
+ [_,
+ {op,_,'=:=',{var,_,'B'},{float,_,192.0}},
+ {op,_,'==',{var,_,'X'},{var,_,'Y'}}]}]}]}
+ = qlc:info(H,{format,abstract_code}),
+ ?line [{1,1},{2,2}] = qlc:e(H),
+ ?line H2 = rpc:call(R12, handle, lookup_handle, []),
+ ?line {qlc,_,[{generate,_,{qlc,_,_,[{join,lookup}]}},_],[]} =
+ qlc:info(H2, {format,debug}),
+ ?line [{1,1},{2,2}] = qlc:e(H2),
+ stop_node(R12);
+ false ->
+ ?line {skipped, "No support for old node"}
+ end.
+-endif.
+
+forward(doc) ->
+ "";
+forward(suite) -> [];
+forward(Config) when is_list(Config) ->
+ Ts = [
+ %% LC_fun() returns something unknown.
+ <<"FakeH = {qlc_handle,{qlc_lc,fun() -> {foo,bar} end,
+ {qlc_opt,false,false,-1,any,[],any,524288}}},
+ {'EXIT', {{unsupported_qlc_handle,_},_}} = (catch qlc:e(FakeH))">>,
+
+%% 'f1' should be used for new stuff that does not interfer with old behavior
+% %% The unused element 'f1' of #qlc_table seems to be used.
+% <<"DF = fun() -> foo end,
+% FakeH = {qlc_handle,{qlc_table,DF,
+% true,DF,DF,DF,DF,DF,
+% undefined,not_undefined,undefined,no_match_spec}},
+% {'EXIT', {{unsupported_qlc_handle,_},_}} = (catch qlc:e(FakeH))">>,
+
+ %% #qlc_opt has changed.
+ <<"H = qlc:q([X || X <- []]),
+ {qlc_handle, {qlc_lc, Fun, _Opt}} = H,
+ FakeH = {qlc_handle, {qlc_lc, Fun, {new_qlc_opt, a,b,c}}},
+ {'EXIT', {{unsupported_qlc_handle,_},_}} = (catch qlc:e(FakeH))">>
+
+ ],
+ ?line run(Config, Ts),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+bad_table_throw(Tab) ->
+ Limit = 1,
+ Select = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ PreFun = fun(_) -> throw({throw,bad_pre_fun}) end,
+ PostFun = fun() -> throw({throw,bad_post_fun}) end,
+ InfoFun = fun(Tag) -> info(Tab, Tag) end,
+ qlc:table(Select, [{pre_fun,PreFun}, {post_fun, PostFun},
+ {info_fun, InfoFun}]).
+
+bad_table_exit(Tab) ->
+ Limit = 1,
+ Select = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ PreFun = fun(_) -> erlang:error(bad_pre_fun) end,
+ PostFun = fun() -> erlang:error(bad_post_fun) end,
+ InfoFun = fun(Tag) -> info(Tab, Tag) end,
+ qlc:table(Select, [{pre_fun,PreFun}, {post_fun, PostFun},
+ {info_fun, InfoFun}]).
+
+info(_Tab, is_unique_objects) ->
+ false;
+info(Tab, Tag) ->
+ try ets:info(Tab, Tag) catch _:_ -> undefined end.
+
+create_ets(S, E) ->
+ create_ets(lists:seq(S, E)).
+
+create_ets(L) ->
+ E1 = ets:new(e, []),
+ true = ets:insert(E1, [{X,X} || X <- L]),
+ E1.
+
+etsc(F, Objs) ->
+ etsc(F, [{keypos,1}], Objs).
+
+etsc(F, Opts, Objs) ->
+ E = ets:new(test, Opts),
+ true = ets:insert(E, Objs),
+ V = F(E),
+ ets:delete(E),
+ V.
+
+join_info(H) ->
+ {qlc, S, Options} = strip_qlc_call(H),
+ %% "Hide" the call to qlc_pt from the test in run_test().
+ LoadedPT = code:is_loaded(qlc_pt),
+ QH = qlc:string_to_handle(S, Options),
+ _ = [unload_pt() || false <- [LoadedPT]], % doesn't take long...
+ case {join_info_count(H), join_info_count(QH)} of
+ {N, N} ->
+ N;
+ Ns ->
+ Ns
+ end.
+
+strip_qlc_call(H) ->
+ S = qlc:info(H, {flat, false}),
+ {ok, Tokens, _EndLine} = erl_scan:string(S++"."),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ case Expr of
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC]} ->
+ {qlc, lists:flatten([erl_pp:expr(LC), "."]), []};
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC, Opts]} ->
+ {qlc, lists:flatten([erl_pp:expr(LC), "."]),
+ erl_parse:normalise(Opts)};
+ {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_run}},_} ->
+ {match_spec, Expr};
+ {call,_,{remote,_,{atom,_,M},{atom,_,table}},_} ->
+ {table, M, Expr};
+ _ ->
+ []
+ end.
+
+-record(ji, {nmerge = 0, nlookup = 0, nnested_loop = 0, nkeysort = 0}).
+
+%% Counts join options and (all) calls to qlc:keysort().
+join_info_count(H) ->
+ S = qlc:info(H, {flat, false}),
+ {ok, Tokens, _EndLine} = erl_scan:string(S++"."),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ #ji{nmerge = Nmerge, nlookup = Nlookup,
+ nkeysort = NKeysort, nnested_loop = Nnested_loop} =
+ ji(Expr, #ji{}),
+ {Nmerge, Nlookup, Nnested_loop, NKeysort}.
+
+ji({call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC,Options]}, JI) ->
+ NJI = case lists:keysearch(join, 1, erl_parse:normalise(Options)) of
+ {value, {join, merge}} ->
+ JI#ji{nmerge = JI#ji.nmerge + 1};
+ {value, {join, lookup}} ->
+ JI#ji{nlookup = JI#ji.nlookup + 1};
+ {value, {join, nested_loop}} ->
+ JI#ji{nnested_loop = JI#ji.nnested_loop + 1};
+ _ ->
+ JI
+ end,
+ ji(LC, NJI);
+ji({call,_,{remote,_,{atom,_,qlc},{atom,_,keysort}},[_KP,H,_Options]}, JI) ->
+ ji(H, JI#ji{nkeysort = JI#ji.nkeysort + 1});
+ji(T, JI) when is_tuple(T) ->
+ ji(tuple_to_list(T), JI);
+ji([E | Es], JI) ->
+ ji(Es, ji(E, JI));
+ji(_, JI) ->
+ JI.
+
+%% Designed for ETS' and gb_table's format funs.
+lookup_keys(Q) ->
+ case lists:flatten(lookup_keys(i(Q), [])) of
+ [] -> false;
+ L -> lists:usort(L)
+ end.
+
+lookup_keys([Q | Qs], L) ->
+ lookup_keys(Qs, lookup_keys(Q, L));
+lookup_keys({qlc,_,Quals,_}, L) ->
+ lookup_keys(Quals, L);
+lookup_keys({list,Q,_}, L) ->
+ lookup_keys(Q, L);
+lookup_keys({generate,_,Q}, L) ->
+ lookup_keys(Q, L);
+lookup_keys({table,Chars}, L) when is_list(Chars) ->
+ {ok, Tokens, _} = erl_scan:string(lists:flatten(Chars++".")),
+ {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ case Expr of
+ {call,_,_,[_fun,AKs]} ->
+ case erl_parse:normalise(AKs) of
+ Ks when is_list(Ks) ->
+ [lists:sort(Ks) | L];
+ K -> % assume keys are never lists (ets only)
+ [K | L]
+ end;
+ _ -> % gb_table
+ L
+ end;
+lookup_keys(_Q, L) ->
+ L.
+
+bad_table_format(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ FormatFun = {is, no, good},
+ qlc:table(SelectFun, [{format_fun, FormatFun}]).
+
+bad_table_format_arity(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ FormatFun = fun() -> {?MODULE, bad_table_format_arity, [Tab]} end,
+ qlc:table(SelectFun, [{format_fun, FormatFun}]).
+
+bad_table_traverse(Tab) ->
+ Limit = 1,
+ Select = fun(MS, _) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(Select, []).
+
+bad_table_post(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, [{pre_fun,undefined},
+ {post_fun, fun(X) -> X end},
+ {info_fun, undefined}]).
+
+bad_table_lookup(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, {lookup_fun, fun(X) -> X end}).
+
+bad_table_max_lookup(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, {max_lookup, -2}).
+
+bad_table_info_arity(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ InfoFun = fun() -> {?MODULE, bad_table_info_arity, [Tab]} end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}]).
+
+default_table(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, [{format_fun, undefined},
+ {info_fun, undefined},
+ {lookup_fun, undefined},
+ {parent_fun, undefined},
+ {pre_fun,undefined},
+ {post_fun, undefined}]).
+
+bad_table(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ qlc:table(SelectFun, [{info, fun() -> ok end}]).
+
+bad_table_info_fun_n_objects(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ InfoFun = fun(num_of_objects) -> exit(finito);
+ (_) -> undefined
+ end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}, {lookup_fun, LookupFun}]).
+
+bad_table_info_fun_indices(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ InfoFun = fun(indices) -> throw({throw,apa});
+ (_) -> undefined
+ end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}, {lookup_fun, LookupFun}]).
+
+bad_table_info_fun_keypos(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ InfoFun = fun(indices) -> erlang:error(keypos);
+ (_) -> undefined
+ end,
+ qlc:table(SelectFun, [{info_fun, InfoFun}, {lookup_fun, LookupFun}]).
+
+bad_table_key_equality(Tab) ->
+ Limit = 1,
+ SelectFun = fun(MS) -> cb(ets:select(Tab, MS, Limit)) end,
+ LookupFun = fun(_Pos, Ks) ->
+ lists:flatmap(fun(K) -> ets:lookup(Tab, K) end, Ks)
+ end,
+ qlc:table(SelectFun, [{lookup_fun, LookupFun},{key_equality,'=/='}]).
+
+cb('$end_of_table') ->
+ [];
+cb({Objects,Cont}) ->
+ Objects ++ fun() -> cb(ets:select(Cont)) end.
+
+i(H) ->
+ i(H, []).
+
+i(H, Options) when is_list(Options) ->
+ case has_format(Options) of
+ true -> qlc:info(H, Options);
+ false -> qlc:info(H, [{format, debug} | Options])
+ end;
+i(H, Option) ->
+ i(H, [Option]).
+
+has_format({format,_}) ->
+ true;
+has_format([E | Es]) ->
+ has_format(E) or has_format(Es);
+has_format(_) ->
+ false.
+
+format_info(H, Flat) ->
+ L = qlc:info(H, [{flat, Flat}, {format,string}]),
+ re:replace(L, "\s|\n|\t|\f|\r|\v", "", [{return,list},global]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% A list turned into a table...
+
+table_kill_parent(List, Indices) ->
+ ParentFun = fun() -> exit(self(), kill) end,
+ table_i(List, Indices, ParentFun).
+
+table_parent_throws(List, Indices) ->
+ ParentFun = fun() -> throw({throw,thrown}) end,
+ table_i(List, Indices, ParentFun).
+
+table_parent_exits(List, Indices) ->
+ ParentFun = fun() -> 1 + Indices end,
+ table_i(List, Indices, ParentFun).
+
+table_bad_parent_fun(List, Indices) ->
+ ParentFun = fun(X) -> X end, % parent_fun should be nullary
+ table_i(List, Indices, ParentFun).
+
+table(List, Indices) ->
+ ParentFun = fun() -> self() end,
+ table_i(List, Indices, ParentFun).
+
+table(List, KeyPos, Indices) ->
+ ParentFun = fun() -> self() end,
+ table(List, Indices, KeyPos, ParentFun).
+
+table_i(List, Indices, ParentFun) ->
+ table(List, Indices, undefined, ParentFun).
+
+table(List, Indices, KeyPos, ParentFun) ->
+ TraverseFun = fun() -> list_traverse(List) end,
+ PreFun = fun(PreArgs) ->
+ {value, {parent_value, Pid}} =
+ lists:keysearch(parent_value, 1, PreArgs),
+ true = is_pid(Pid)
+ end,
+ PostFun = fun() -> ok end,
+ InfoFun = fun(indices) ->
+ Indices;
+ (is_unique_objects) ->
+ undefined;
+ (keypos) ->
+ KeyPos;
+ (num_of_objects) ->
+ undefined;
+ (_) ->
+ undefined
+ end,
+ LookupFun =
+ fun(Column, Values) ->
+ lists:flatmap(fun(V) ->
+ case lists:keysearch(V, Column, List) of
+ false -> [];
+ {value,Val} -> [Val]
+ end
+ end, Values)
+
+ end,
+ FormatFun = fun(all) ->
+ L = 17,
+ {call,L,{remote,L,{atom,1,?MODULE},{atom,L,the_list}},
+ [erl_parse:abstract(List, 17)]};
+ ({lookup, Column, Values}) ->
+ {?MODULE, list_keys, [Values, Column, List]}
+ end,
+ qlc:table(TraverseFun, [{info_fun,InfoFun}, {pre_fun, PreFun},
+ {post_fun, PostFun}, {lookup_fun, LookupFun},
+ {format_fun, FormatFun},
+ {parent_fun, ParentFun}]).
+
+stop_list(List, Ets) ->
+ Traverse = fun() -> list_traverse(List) end,
+ PV = a_sample_parent_value,
+ ParentFun = fun() -> PV end,
+ Pre = fun(PreArgs) ->
+ {value, {parent_value, PV}} =
+ lists:keysearch(parent_value, 1, PreArgs),
+ {value, {stop_fun, Fun}} =
+ lists:keysearch(stop_fun, 1, PreArgs),
+ true = ets:insert(Ets, {stop_fun, Fun})
+ end,
+ qlc:table(Traverse, [{pre_fun, Pre}, {parent_fun, ParentFun}]).
+
+list_traverse([]) ->
+ [];
+list_traverse([E | Es]) ->
+ [E | fun() -> list_traverse(Es) end].
+
+table_error(List, Error) ->
+ table_error(List, undefined, Error).
+
+table_error(List, KeyPos, Error) ->
+ TraverseFun = fun() -> list_traverse2(lists:sort(List), Error) end,
+ InfoFun = fun(is_sorted_key) -> true;
+ (keypos) -> KeyPos;
+ (_) -> undefined
+ end,
+ qlc:table(TraverseFun, [{info_fun,InfoFun}]).
+
+list_traverse2([], Err) ->
+ Err;
+list_traverse2([E | Es], Err) ->
+ [E | fun() -> list_traverse2(Es, Err) end].
+
+table_lookup_error(List) ->
+ TraverseFun = fun() -> list_traverse(List) end,
+ LookupFun = fun(_Column, _Values) -> {error,lookup,failed} end,
+ InfoFun = fun(keypos) -> 1;
+ (_) -> undefined
+ end,
+ qlc:table(TraverseFun, [{lookup_fun,LookupFun},{info_fun,InfoFun}]).
+
+prep_scratchdir(Dir) ->
+ put('$qlc_tmpdir', true),
+ _ = filelib:ensure_dir(Dir),
+ lists:foreach(fun(F) -> file:delete(F)
+ end, filelib:wildcard(filename:join(Dir, "*"))),
+ true.
+
+%% Truncate just once.
+truncate_tmpfile(Dir, Where) ->
+ case get('$qlc_tmpdir') of
+ true ->
+ {ok, [TmpFile0 | _]} = file:list_dir(Dir),
+ TmpFile = filename:join(Dir, TmpFile0),
+ truncate(TmpFile, Where),
+ erase('$qlc_tmpdir');
+ _ ->
+ true
+ end.
+
+truncate(File, Where) ->
+ {ok, Fd} = file:open(File, [read, write]),
+ {ok, _} = file:position(Fd, Where),
+ ok = file:truncate(Fd),
+ ok = file:close(Fd).
+
+%% Crash just once.
+crash_tmpfile(Dir, Where) ->
+ case get('$qlc_tmpdir') of
+ true ->
+ {ok, [TmpFile0 | _]} = file:list_dir(Dir),
+ TmpFile = filename:join(Dir, TmpFile0),
+ crash(TmpFile, Where),
+ erase('$qlc_tmpdir');
+ _ ->
+ true
+ end.
+
+crash(File, Where) ->
+ {ok, Fd} = file:open(File, [read, write]),
+ {ok, _} = file:position(Fd, Where),
+ ok = file:write(Fd, [10]),
+ ok = file:close(Fd).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+run(Config, Tests) ->
+ run(Config, [], Tests).
+
+run(Config, Extra, Tests) ->
+ lists:foreach(fun(Body) -> run_test(Config, Extra, Body) end, Tests).
+
+run_test(Config, Extra, {cres, Body, ExpectedCompileReturn}) ->
+ run_test(Config, Extra, {cres, Body, _Opts = [], ExpectedCompileReturn});
+run_test(Config, Extra, {cres, Body, Opts, ExpectedCompileReturn}) ->
+ {SourceFile, Mod} = compile_file_mod(Config),
+ P = [Extra,<<"function() -> ">>, Body, <<", ok. ">>],
+ CompileReturn = compile_file(Config, P, Opts),
+ case comp_compare(ExpectedCompileReturn, CompileReturn) of
+ true -> ok;
+ false -> expected(ExpectedCompileReturn, CompileReturn, SourceFile)
+ end,
+ AbsFile = filename:rootname(SourceFile, ".erl"),
+ _ = code:purge(Mod),
+ {module, _} = code:load_abs(AbsFile, Mod),
+
+ Ms0 = erlang:process_info(self(),messages),
+ Before = {get(), pps(), ets:all(), Ms0},
+
+ %% Prepare the check that the qlc module does not call qlc_pt.
+ _ = [unload_pt() || {file, Name} <- [code:is_loaded(qlc_pt)],
+ Name =/= cover_compiled],
+
+ R = case catch Mod:function() of
+ {'EXIT', _Reason} = Error ->
+ ?t:format("failed, got ~p~n", [Error]),
+ fail(SourceFile);
+ Reply ->
+ Reply
+ end,
+
+ %% Check that the qlc module does not call qlc_pt:
+ case code:is_loaded(qlc_pt) of
+ {file, cover_compiled} ->
+ ok;
+ {file, _} ->
+ ?t:format("qlc_pt was loaded in runtime~n", []),
+ fail(SourceFile);
+ false ->
+ ok
+ end,
+
+ Ms = erlang:process_info(self(),messages),
+ After = {get(), pps(), ets:all(), Ms},
+ code:purge(Mod),
+ case {R, After} of
+ {ok, Before} -> ok;
+ _ -> expected({ok,Before}, {R,After}, SourceFile)
+ end;
+run_test(Config, Extra, Body) ->
+ run_test(Config, Extra, {cres,Body,[]}).
+
+unload_pt() ->
+ erlang:garbage_collect(), % get rid of references to qlc_pt...
+ _ = code:purge(qlc_pt),
+ _ = code:delete(qlc_pt).
+
+compile_format(Config, Tests) ->
+ Fun = fun(Test, Opts) ->
+ Return = compile_file(Config, Test, Opts),
+ format_messages(Return)
+ end,
+ compile(Config, Tests, Fun).
+
+format_messages({warnings,Ws}) ->
+ format_messages({errors,[],Ws});
+format_messages({errors,Es,Ws}) ->
+ {[format_msg(E, Mod) || {_Line,Mod,E} <- Es],
+ [format_msg(W, Mod) || {_Line,Mod,W} <- Ws]}.
+
+format_msg(Msg, Mod) ->
+ IOlist = Mod:format_error(Msg),
+ binary_to_list(iolist_to_binary(IOlist)).
+
+compile(Config, Tests) ->
+ Fun = fun(Test, Opts) -> catch compile_file(Config, Test, Opts) end,
+ compile(Config, Tests, Fun).
+
+compile(Config, Tests, Fun) ->
+ F = fun({TestName,Test,Opts,Expected}, BadL) ->
+ Return = Fun(Test, Opts),
+ case comp_compare(Expected, Return) of
+ true ->
+ BadL;
+ false ->
+ {File, _Mod} = compile_file_mod(Config),
+ expected(TestName, Expected, Return, File)
+ end
+ end,
+ lists:foldl(F, [], Tests).
+
+%% Compiles a test module and returns the list of errors and warnings.
+
+compile_file(Config, Test0, Opts0) ->
+ {File, Mod} = compile_file_mod(Config),
+ Test = list_to_binary(["-module(", atom_to_list(Mod), "). "
+ "-compile(export_all). "
+ "-import(qlc_SUITE, [i/1,i/2,format_info/2]). "
+ "-import(qlc_SUITE, [etsc/2, etsc/3]). "
+ "-import(qlc_SUITE, [create_ets/2]). "
+ "-import(qlc_SUITE, [strip_qlc_call/1]). "
+ "-import(qlc_SUITE, [join_info/1]). "
+ "-import(qlc_SUITE, [join_info_count/1]). "
+ "-import(qlc_SUITE, [lookup_keys/1]). "
+ "-include_lib(\"stdlib/include/qlc.hrl\"). ",
+ Test0]),
+ Opts = [export_all,return,nowarn_unused_record,{outdir,?privdir}|Opts0],
+ ok = file:write_file(File, Test),
+ case compile:file(File, Opts) of
+ {ok, _M, Ws} -> warnings(File, Ws);
+ {error, [{File,Es}], []} -> {errors, Es, []};
+ {error, [{File,Es}], [{File,Ws}]} -> {error, Es, Ws}
+ end.
+
+comp_compare(T, T) ->
+ true;
+comp_compare(T1, T2_0) ->
+ T2 = wskip(T2_0),
+ T1 =:= T2
+ %% This clause should eventually be removed.
+ orelse ln(T1) =:= T2 orelse T1 =:= ln(T2).
+
+wskip([]) ->
+ [];
+wskip([{_,sys_core_fold,{eval_failure,badarg}}|L]) ->
+ wskip(L);
+wskip([{{L,_C},sys_core_fold,M}|L]) ->
+ [{L,sys_core_fold,M}|wskip(L)];
+wskip({T,L}) ->
+ {T,wskip(L)};
+wskip([M|L]) ->
+ [M|wskip(L)];
+wskip(T) ->
+ T.
+
+%% Replaces locations like {Line,Column} with Line.
+ln({warnings,L}) ->
+ {warnings,ln0(L)};
+ln({errors,EL,WL}) ->
+ {errors,ln0(EL),ln0(WL)};
+ln(L) ->
+ ln0(L).
+
+ln0(L) ->
+ lists:sort(ln1(L)).
+
+ln1([]) ->
+ [];
+ln1([{File,Ms}|MsL]) when is_list(File) ->
+ [{File,ln0(Ms)}|ln1(MsL)];
+ln1([{{L,_C},Mod,Mess0}|Ms]) ->
+ Mess = case Mess0 of
+ {exported_var,V,{Where,{L1,_C1}}} ->
+ {exported_var,V,{Where,L1}};
+ {unsafe_var,V,{Where,{L1,_C1}}} ->
+ {unsafe_var,V,{Where,L1}};
+ %% There are more...
+ M ->
+ M
+ end,
+ [{L,Mod,Mess}|ln1(Ms)];
+ln1([M|Ms]) ->
+ [M|ln1(Ms)].
+
+%% -> {FileName, Module}; {string(), atom()}
+compile_file_mod(Config) ->
+ NameL = lists:concat([?TESTMODULE, "_", ?testcase]),
+ Name = list_to_atom(NameL),
+ File = filename(NameL ++ ".erl", Config),
+ {File, Name}.
+
+filename(Name, Config) when is_atom(Name) ->
+ filename(atom_to_list(Name), Config);
+filename(Name, Config) ->
+ filename:join(?privdir, Name).
+
+pps() ->
+ {port_list(), process_list()}.
+
+port_list() ->
+ [{P,safe_second_element(erlang:port_info(P, name))} ||
+ P <- erlang:ports()].
+
+process_list() ->
+ [{P,process_info(P, registered_name),
+ safe_second_element(process_info(P, initial_call))} ||
+ P <- processes(), is_process_alive(P)].
+
+safe_second_element({_,Info}) -> Info;
+safe_second_element(Other) -> Other.
+
+warnings(File, Ws) ->
+ case lists:append([W || {F, W} <- Ws, F =:= File]) of
+ [] -> [];
+ L -> {warnings, L}
+ end.
+
+expected(Test, Expected, Got, File) ->
+ ?t:format("~nTest ~p failed. ", [Test]),
+ expected(Expected, Got, File).
+
+expected(Expected, Got, File) ->
+ ?t:format("Expected~n ~p~n, but got~n ~p~n", [Expected, Got]),
+ fail(File).
+
+fail(Source) ->
+ io:format("failed~n"),
+ ?t:fail({failed,testcase,on,Source}).
+
+%% Copied from global_SUITE.erl.
+
+start_node_rel(Name, Rel, How) ->
+ {Release, Compat} = case Rel of
+ this ->
+ {[this], "+R8"};
+ Rel when is_atom(Rel) ->
+ {[{release, atom_to_list(Rel)}], ""};
+ RelList ->
+ {RelList, ""}
+ end,
+ ?line Pa = filename:dirname(code:which(?MODULE)),
+ ?line Res = test_server:start_node(Name, How,
+ [{args,
+ Compat ++
+ " -kernel net_setuptime 100 "
+ " -pa " ++ Pa},
+ {erl, Release}]),
+ Res.
+
+stop_node(Node) ->
+ ?line ?t:stop_node(Node).
+
+install_error_logger() ->
+ error_logger:add_report_handler(?MODULE, self()).
+
+uninstall_error_logger() ->
+ error_logger:delete_report_handler(?MODULE).
+
+read_error_logger() ->
+ receive
+ {error, Why} ->
+ {error, Why};
+ {info, Why} ->
+ {info, Why};
+ {error, Pid, Tuple} ->
+ {error, Pid, Tuple}
+ after 1000 ->
+ ?line io:format("No reply after 1 s\n", []),
+ ?line ?t:fail()
+ end.
+
+%%-----------------------------------------------------------------
+%% The error_logger handler used.
+%% (Copied from stdlib/test/proc_lib_SUITE.erl.)
+%%-----------------------------------------------------------------
+init(Tester) ->
+ {ok, Tester}.
+
+handle_event({error, _GL, {_Pid, _Msg, [Why, _]}}, Tester)
+ when is_atom(Why) ->
+ Tester ! {error, Why},
+ {ok, Tester};
+handle_event({error, _GL, {_Pid, _Msg, [P, T]}}, Tester) when is_pid(P) ->
+ Tester ! {error, P, T},
+ {ok, Tester};
+handle_event({info_msg, _GL, {_Pid, _Msg, [Why, _]}}, Tester) ->
+ Tester ! {info, Why},
+ {ok, Tester};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(_, State) ->
+ {ok, State}.
+
+handle_call(_Query, State) -> {ok, {error, bad_query}, State}.
+
+terminate(_Reason, State) ->
+ State.
diff --git a/lib/stdlib/test/queue_SUITE.erl b/lib/stdlib/test/queue_SUITE.erl
new file mode 100644
index 0000000000..ec3080baa0
--- /dev/null
+++ b/lib/stdlib/test/queue_SUITE.erl
@@ -0,0 +1,604 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(queue_SUITE).
+-export([all/1]).
+
+-export([do/1, to_list/1, io_test/1, op_test/1, error/1, oops/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-include("test_server.hrl").
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(doc) ->
+ ["Test cases for queue."];
+all(suite) ->
+ [do, to_list, io_test, op_test, error, oops].
+
+do(doc) ->
+ [""];
+do(suite) ->
+ [];
+do(Config) when list(Config) ->
+ ?line L = [{in, 1},
+ {in, 2},
+ {out, {value, 1}},
+ {in, 3},
+ {out, {value, 2}},
+ {out, {value, 3}},
+ {out, empty}
+ ],
+
+ ?line E = queue:new(),
+ ?line [] = queue:to_list(E),
+ ?line Q = do_queue(E, L),
+ ?line true = queue:is_empty(Q),
+ ?line 0 = queue:len(Q),
+ ok.
+
+to_list(doc) ->
+ ["OTP-2701"];
+to_list(suite) ->
+ [];
+to_list(Config) when list(Config) ->
+ ?line E = queue:new(),
+ ?line Q = do_queue(E, [{in, 1},
+ {in, 2},
+ {in, 3},
+ {out, {value, 1}},
+ {in, 4},
+ {in, 5}]),
+ ?line true = queue:is_queue(Q),
+ ?line 4 = queue:len(Q),
+ ?line case queue:to_list(Q) of
+ [2,3,4,5] ->
+ ok;
+ Other1 ->
+ test_server:fail(Other1)
+ end,
+ ok.
+
+do_queue(Q, []) ->
+ Q;
+do_queue(Q, [E | Rest]) ->
+ do_queue(do_queue_1(E, Q), Rest).
+
+do_queue_1({in, E}, Q) ->
+ queue:in(E, Q);
+do_queue_1({out, E}, Q) ->
+ case queue:out(Q) of
+ {E, Q1} ->
+ Q1;
+ Other ->
+ test_server:fail({"out failed", E, Q, Other})
+ end.
+
+
+io_test(doc) ->
+ "Test input and output";
+io_test(suite) ->
+ [];
+io_test(Config) when list(Config) ->
+ E = queue:new(),
+ do_io_test(E),
+ ok.
+
+do_io_test(E) ->
+ ?line [4,3,5] =
+ io([snoc,snoc,head,head,head,cons,cons,snoc], E, 1),
+ ?line [5,3,4] =
+ io([cons,cons,daeh,daeh,daeh,snoc,snoc,cons], E, 1),
+ ?line [4,3,5] =
+ io([in,in,out,out,out,in_r,in_r,in], E, 1),
+ ?line [5,3,4] =
+ io([in_r,in_r,out_r,out_r,out_r,in,in,in_r], E, 1),
+ %%
+ ?line [] =
+ io([snoc,snoc,head,snoc,snoc,head,head,snoc,head,head], E, 1),
+ ?line [] =
+ io([cons,cons,daeh,cons,cons,daeh,daeh,cons,daeh,daeh], E, 1),
+ ?line [] =
+ io([in,in,out,in,in,out,out,in,out,out], E, 1),
+ ?line [] =
+ io([in_r,in_r,out_r,in_r,in_r,out_r,out_r,in_r,out_r,out_r],
+ E, 1),
+ %%
+ ?line [5,6] =
+ io([snoc,snoc,snoc,head,head,snoc,snoc,snoc,head,head], E, 1),
+ ?line [6,5] =
+ io([cons,cons,cons,daeh,daeh,cons,cons,cons,daeh,daeh], E, 1),
+ ?line [5,6] =
+ io([in,in,in,out,out,in,in,in,out,out], E, 1),
+ ?line [6,5] =
+ io([in_r,in_r,in_r,out_r,out_r,in_r,in_r,in_r,out_r,out_r],
+ E, 1),
+ %%
+ ?line [5] =
+ io([snoc,head,head,snoc,head,snoc,head,snoc,head,snoc], E, 1),
+ ?line [5] =
+ io([cons,daeh,daeh,cons,daeh,cons,daeh,cons,daeh,cons], E, 1),
+ ?line [5] =
+ io([in,out,out,in,out,in,out,in,out,in], E, 1),
+ ?line [5] =
+ io([in_r,out_r,out_r,in_r,out_r,in_r,out_r,in_r,out_r,in_r],
+ E, 1),
+ %%
+ ?line [] =
+ io([snoc,head,snoc,snoc,head,head,snoc,snoc,snoc,head,head,head],
+ E, 1),
+ ?line [] =
+ io([cons,daeh,cons,cons,daeh,daeh,cons,cons,cons,daeh,daeh,daeh],
+ E, 1),
+ ?line [] =
+ io([in,out,in,in,out,out,in,in,in,out,out,out],
+ E, 1),
+ ?line [] =
+ io([in_r,out_r,in_r,in_r,out_r,out_r,in_r,in_r,in_r,out_r,out_r,out_r],
+ E, 1),
+ %%
+ ?line [3] = io([cons,cons,cons,snoc,daeh,daeh,daeh], E, 1),
+ ?line [3] = io([snoc,snoc,snoc,cons,head,head,head], E, 1),
+ ?line [3] = io([in,in,in,in_r,out,out,out], E, 1),
+ ?line [3] = io([in_r,in_r,in_r,in,out_r,out_r,out_r], E, 1),
+ %%
+ ?line Q2 = queue:join(queue:cons(1, E),queue:cons(2, E)),
+ ?line Q1 = queue:reverse(Q2),
+ ?line [1] = io([head], Q1, 3),
+ ?line [1] = io([out], Q1, 3),
+ ?line [1] = io([daeh], Q2, 3),
+ ?line [1] = io([out_r], Q2, 3),
+% ?line [2] = io([cons,cons,snoc,daeh,daeh], [], 1),
+% ?line [2] = io([snoc,snoc,cons,head,head], [], 1),
+% ?line [2] = io([in,in,in_r,out,out], [], 1),
+% ?line [2] = io([in_r,in_r,in,out_r,out_r], [], 1),
+ %%
+ ?line [2] =
+ io([in,peek,peek_r,drop,in_r,peek,peek_r,in,peek,peek_r,drop_r], E, 1),
+ %% Malformed queues UGLY-GUTS-ALL-OVER-THE-PLACE
+ ?line [2,1] = io([peek], {[1,2],[]}, 1),
+ ?line [1,2] = io([peek_r], {[],[1,2]}, 1),
+ %%
+ ok.
+
+%% Perform a list of operations to a queue.
+%% Keep a reference queue on the side; just a list.
+%% Compare the read values between the queues.
+%% Return the resulting queue as a list.
+%% Inserted values are increments of the previously inserted.
+io(Ops, Q, X) ->
+ io(Ops, Q, queue:to_list(Q), X).
+
+io([head | Tail], Q, [], X) ->
+ true = queue:is_empty(Q),
+ {'EXIT',{empty,_}} = (catch {ok,queue:head(Q)}),
+ {'EXIT',{empty,_}} = (catch {ok,queue:tail(Q)}),
+ io(Tail, Q, [], X);
+io([head | Tail], Q, [H | T], X) ->
+ H = queue:head(Q),
+ false = queue:is_empty(Q),
+ io(Tail, queue:tail(Q), T, X);
+io([daeh | Tail], Q, [], X) ->
+ true = queue:is_empty(Q),
+ {'EXIT',{empty,_}} = (catch {ok,queue:daeh(Q)}),
+ {'EXIT',{empty,_}} = (catch {ok,queue:liat(Q)}),
+ {'EXIT',{empty,_}} = (catch {ok,queue:lait(Q)}),
+ io(Tail, Q, [], X);
+io([daeh | Tail], Q, QQ, X) ->
+ H = queue:daeh(Q),
+ false = queue:is_empty(Q),
+ [H | T] = lists:reverse(QQ),
+ io(Tail, queue:liat(Q), lists:reverse(T), X);
+io([out | Tail], Q, [], X) ->
+ {empty, Q1} = queue:out(Q),
+ io(Tail, Q1, [], X);
+io([out | Tail], Q, [H | T], X) ->
+ {{value,H}, Q1} = queue:out(Q),
+ io(Tail, Q1, T, X);
+io([out_r | Tail], Q, [], X) ->
+ {empty, Q1} = queue:out_r(Q),
+ io(Tail, Q1, [], X);
+io([out_r | Tail], Q, QQ, X) ->
+ {{value,H}, Q1} = queue:out_r(Q),
+ [H | T] = lists:reverse(QQ),
+ io(Tail, Q1, lists:reverse(T), X);
+io([cons | Tail], Q, QQ, X) ->
+ io(Tail, queue:cons(X,Q), [X|QQ], X+1);
+io([snoc | Tail], Q, QQ, X) ->
+ io(Tail, queue:snoc(Q,X), QQ++[X], X+1);
+io([in_r | Tail], Q, QQ, X) ->
+ io(Tail, queue:in_r(X,Q), [X|QQ], X+1);
+io([in | Tail], Q, QQ, X) ->
+ io(Tail, queue:in(X,Q), QQ++[X], X+1);
+io([peek | Tail], Q, [], X) ->
+ empty = queue:peek(Q),
+ io(Tail, Q, [], X);
+io([peek | Tail], Q, [H|_]=Q0, X) ->
+ {value,H} = queue:peek(Q),
+ io(Tail, Q, Q0, X);
+io([peek_r | Tail], Q, [], X) ->
+ empty = queue:peek_r(Q),
+ io(Tail, Q, [], X);
+io([peek_r | Tail], Q, Q0, X) ->
+ E = lists:last(Q0),
+ {value,E} = queue:peek_r(Q),
+ io(Tail, Q, Q0, X);
+io([drop | Tail], Q, [], X) ->
+ try queue:drop(Q) of
+ V ->
+ test_server:fail({?MODULE,?LINE,V})
+ catch
+ error:empty ->
+ io(Tail, Q, [], X)
+ end;
+io([drop | Tail], Q, [_ | T], X) ->
+ Q1 = queue:drop(Q),
+ io(Tail, Q1, T, X);
+io([drop_r | Tail], Q, [], X) ->
+ try queue:drop_r(Q) of
+ V ->
+ test_server:fail({?MODULE,?LINE,V})
+ catch
+ error:empty ->
+ io(Tail, Q, [], X)
+ end;
+io([drop_r | Tail], Q, L, X) ->
+ io:format("~p~n", [{drop_r,Tail,Q,L,X}]),
+ Q1 = queue:drop_r(Q),
+ [_ | T] = lists:reverse(L),
+ io:format("~p~n", [{drop_r,Q1,T}]),
+ io(Tail, Q1, lists:reverse(T), X);
+io([], Q, QQ, _X) ->
+ QQ = queue:to_list(Q),
+ Length = length(QQ),
+ Length = queue:len(Q),
+ QQ.
+
+
+op_test(doc) ->
+ "Test operations on whole queues";
+op_test(suite) ->
+ [];
+op_test(Config) when list(Config) ->
+ do_op_test(fun id/1),
+ ok.
+
+do_op_test(F) ->
+ ?line Len = 50,
+ ?line Len2 = 2*Len,
+ ?line L1 = lists:seq(1, Len),
+ ?line L1r = lists:reverse(L1),
+ ?line L2 = lists:seq(Len+1, Len2),
+ ?line L2r = lists:reverse(L2),
+ ?line L3 = L1++L2,
+ ?line L3r = L2r++L1r,
+ ?line Q0 = F(queue:new()),
+ ?line [] = queue:to_list(Q0),
+ ?line Q0 = F(queue:from_list([])),
+ ?line Q1 = F(queue:from_list(L1)),
+ ?line Q2 = F(queue:from_list(L2)),
+ ?line Q3 = F(queue:from_list(L3)),
+ ?line Len = queue:len(Q1),
+ ?line Len = queue:len(Q2),
+ ?line Len2 = queue:len(Q3),
+ ?line L1 = queue:to_list(Q1),
+ ?line L2 = queue:to_list(Q2),
+ ?line L3 = queue:to_list(Q3),
+ ?line Q3b = queue:join(Q0, queue:join(queue:join(Q1, Q2), Q0)),
+ ?line L3 = queue:to_list(Q3b),
+ ?line {Q0, Q3New1} = queue:split(0, Q3),
+ ?line L3 = queue:to_list(Q3New1),
+ ?line {Q3New2, Q0} = queue:split(Len2, Q3),
+ ?line L3 = queue:to_list(Q3New2),
+ ?line {Q1a, Q2a} = queue:split(Len, Q3),
+ ?line L1 = queue:to_list(Q1a),
+ ?line L2 = queue:to_list(Q2a),
+ ?line {Q3c, Q3d} = queue:split(2, Q3),
+ ?line L3 = queue:to_list(Q3c) ++ queue:to_list(Q3d),
+ ?line {Q1b, Q2b} = queue:split(Len, Q3b),
+ ?line L1 = queue:to_list(Q1b),
+ ?line L2 = queue:to_list(Q2b),
+ ?line Len = queue:len(Q1b),
+ ?line Len = queue:len(Q2b),
+ ?line Len2 = queue:len(Q3b),
+ ?line Q1r = queue:reverse(Q1),
+ ?line Q2r = queue:reverse(Q2),
+ ?line Q1ar = queue:reverse(Q1a),
+ ?line Q2ar = queue:reverse(Q2a),
+ ?line Q1br = queue:reverse(Q1b),
+ ?line Q2br = queue:reverse(Q2b),
+ ?line Q3br = queue:reverse(Q3b),
+ ?line L1r = queue:to_list(Q1r),
+ ?line L1r = queue:to_list(Q1ar),
+ ?line L1r = queue:to_list(Q1br),
+ ?line L2r = queue:to_list(Q2r),
+ ?line L2r = queue:to_list(Q2ar),
+ ?line L2r = queue:to_list(Q2br),
+ ?line L3r = queue:to_list(Q3br),
+ ?line Len = queue:len(Q1br),
+ ?line Len = queue:len(Q2br),
+ ?line Len2 = queue:len(Q3br),
+ ?line false = queue:member([], Q0),
+ ?line false = queue:member(0, Q0),
+ ?line false = queue:member(0, Q1),
+ ?line false = queue:member([], Q1),
+ ?line true = queue:member(1, Q1),
+ ?line false = queue:member(1.0, Q1),
+ ?line true = queue:member(Len, Q1),
+ %%
+ %% Additional coverage.
+ ?line {MyL1r,MyL2r} = lists:split(Len-2, L1r),
+ ?line MyQ0r = queue:reverse(F(queue:from_list(L1))),
+ ?line {MyQ1r,MyQ2r} = queue:split(Len-2, MyQ0r),
+ ?line MyL1r = queue:to_list(MyQ1r),
+ ?line MyL2r = queue:to_list(MyQ2r),
+ ?line MyQ3r = queue:filter(
+ fun (X) when X rem 4 >= 2 -> false;
+ (X) when X rem 8 == 0 -> [float(X),{X}];
+ (X) when X rem 2 >= 1 -> [{X}];
+ (_) -> true
+ end, MyQ1r),
+ ?line MyL3r = lists:flatten(
+ [if X rem 8 == 0 -> [float(X),{X}];
+ X rem 2 >= 1 -> {X};
+ true -> X
+ end || X <- MyL1r,
+ X rem 4 < 2]),
+ ?line MyL3r = queue:to_list(MyQ3r),
+ ?line MyQ4 = F(queue:from_list([11,22,33,44])),
+ ?line [11,22] = queue:to_list(queue:filter(fun(X) when X < 27 -> true;
+ (_) -> [] end, MyQ4)),
+ ?line [33,44] = queue:to_list(queue:filter(fun(X) when X < 27 -> false;
+ (X) -> [X] end, MyQ4)),
+ %%
+ ok.
+
+error(doc) ->
+ "Test queue errors";
+error(suite) ->
+ [];
+error(Config) when list(Config) ->
+ do_error(fun id/1, illegal_queue),
+ do_error(fun id/1, {[],illegal_queue}),
+ do_error(fun id/1, {illegal_queue,[17]}),
+ ok.
+
+trycatch(F, Args) ->
+ trycatch(queue, F, Args).
+
+trycatch(M, F, Args) ->
+ try apply(M, F, Args) of
+ V -> {value,V}
+ catch
+ C:R -> {C,R}
+ end.
+
+do_error(F, IQ) ->
+ ?line io:format("Illegal Queue: ~p~n", [IQ]),
+ %%
+ ?line {error,badarg} = trycatch(in, [1, IQ]),
+ ?line {error,badarg} = trycatch(out, [IQ]),
+ ?line {error,badarg} = trycatch(in_r ,[1, IQ]),
+ ?line {error,badarg} = trycatch(out_r ,[IQ]),
+ ?line {error,badarg} = trycatch(to_list ,[IQ]),
+ %%
+ ?line {error,badarg} = trycatch(from_list, [no_list]),
+ ?line {error,badarg} = trycatch(is_empty, [IQ]),
+ ?line {error,badarg} = trycatch(len, [IQ]),
+ %%
+ ?line {error,badarg} = trycatch(cons, [1, IQ]),
+ ?line {error,badarg} = trycatch(head, [IQ]),
+ ?line {error,badarg} = trycatch(tail, [IQ]),
+ %%
+ ?line {error,badarg} = trycatch(snoc, [IQ, 1]),
+ ?line {error,badarg} = trycatch(last, [IQ]),
+ ?line {error,badarg} = trycatch(daeh, [IQ]),
+ ?line {error,badarg} = trycatch(liat, [IQ]),
+ ?line {error,badarg} = trycatch(lait, [IQ]),
+ ?line {error,badarg} = trycatch(init, [IQ]),
+ %%
+ ?line {error,badarg} = trycatch(reverse, [IQ]),
+ ?line {error,badarg} = trycatch(join, [F(queue:new()), IQ]),
+ ?line {error,badarg} = trycatch(join, [IQ, F(queue:new())]),
+ ?line {error,badarg} = trycatch(split, [17, IQ]),
+ ?line {error,badarg} = trycatch(head, [IQ]),
+ %%
+ ?line Q0 = F(queue:new()),
+ ?line {error,badarg} = trycatch(split, [1, Q0]),
+ ?line {error,badarg} = trycatch(split, [2, queue:snoc(Q0, 1)]),
+ %%
+ ?line {value,false} = trycatch(is_queue, [IQ]),
+ ?line {error,badarg} = trycatch(get, [IQ]),
+ ?line {error,badarg} = trycatch(peek, [IQ]),
+ ?line {error,badarg} = trycatch(peek_r, [IQ]),
+ ?line {error,badarg} = trycatch(filter, [fun id/1, IQ]),
+ ?line {error,badarg} = trycatch(filter, [no_fun, Q0]),
+ %%
+ ?line {error,badarg} = trycatch(member, [1, IQ]),
+ ok.
+
+id(X) ->
+ X.
+
+oops(doc) ->
+ "Test queue errors";
+oops(suite) ->
+ [];
+oops(Config) when list(Config) ->
+ ?line N = 3142,
+ ?line Optab = optab(),
+ ?line Seed0 = random:seed0(),
+ ?line {Is,Seed} = random_list(N, tuple_size(Optab), Seed0, []),
+ ?line io:format("~p ", [Is]),
+ ?line QA = queue:new(),
+ ?line QB = {[]},
+ ?line emul([QA], [QB], Seed, [element(I, Optab) || I <- Is]).
+
+optab() ->
+ {{new,[], q, fun () -> {[]} end},
+ {is_queue,[q], v, fun (_) -> true end},
+ {is_empty,[q], v, fun (Q) ->
+ case Q of
+ {[]} -> true;
+ _ -> false
+ end end},
+ {len,[q], v, fun ({L}) -> length(L) end},
+ {to_list,[q], v, fun ({L}) -> L end},
+ {from_list,[l], q, fun (L) -> {L} end},
+ {in,[t,q], q, fun (X,{L}) -> {L++[X]} end},
+ {in_r,[t,q], q, fun (X,{L}) -> {[X|L]} end},
+ {out,[q], {v,q}, fun ({L}=Q) ->
+ case L of
+ [] -> {empty,Q};
+ [X|T] -> {{value,X},{T}}
+ end
+ end},
+ {out_r,[q], {v,q}, fun ({L}=Q) ->
+ case L of
+ [] -> {empty,Q};
+ _ ->
+ [X|R] = lists:reverse(L),
+ T = lists:reverse(R),
+ {{value,X},{T}}
+ end
+ end},
+ {get,[q], v, fun ({[]}) -> erlang:error(empty);
+ ({[H|_]}) -> H
+ end},
+ {get_r,[q], v, fun ({[]}) -> erlang:error(empty);
+ ({L}) -> lists:last(L)
+ end},
+ {peek,[q], v, fun ({[]}) -> empty;
+ ({[H|_]}) -> {value,H}
+ end},
+ {peek_r,[q], v, fun ({[]}) -> empty;
+ ({L}) -> {value,lists:last(L)}
+ end},
+ {drop,[q], q, fun ({[]}) -> erlang:error(empty);
+ ({[_|T]}) -> {T}
+ end},
+ {drop_r,[q], q, fun ({[]}) -> erlang:error(empty);
+ ({L}) -> [_|R] = lists:reverse(L),
+ {lists:reverse(R)}
+ end},
+ {reverse,[q], q, fun ({L}) -> {lists:reverse(L)} end},
+ {join,[q,q], q, fun ({L1}, {L2}) -> {L1++L2} end},
+ {split,[n,q], {q,q}, fun (N, {L}) -> {L1,L2} = lists:split(N, L),
+ {{L1},{L2}} end},
+ {member,[t,q], v, fun (X, {L}) -> lists:member(X, L) end}
+ }.
+
+emul(_, _, _, []) ->
+ ok;
+emul(QsA0, QsB0, Seed0, [{Op,Ts,S,Fun}|Ops]) ->
+ {AsA,Seed} = args(Ts, QsA0, Seed0, []),
+ {AsB,Seed} = args(Ts, QsB0, Seed0, []),
+ io:format("~n% ~w % ~p ", [Op,AsA]),
+ io:format("% ~p :", [AsB]),
+ XX = call({queue,Op}, AsA),
+ YY = call(Fun, AsB),
+ case {XX,YY} of
+ {{value,X},{value,Y}} ->
+ {[Qa|_]=QsA,[{Lb}|_]=QsB} = chk(QsA0, QsB0, S, X, Y),
+ case queue:to_list(Qa) of
+ Lb ->
+ io:format("|~p| ", [Lb]),
+ emul(QsA, QsB, Seed, Ops);
+ La ->
+ throw({to_list,[XX,YY,Op,AsA,AsB,La,Lb]})
+ end;
+ {Exception,Exception} ->
+ io:format("!~p! ", [Exception]),
+ emul(QsA0, QsB0, Seed, Ops);
+ _ ->
+ throw({diff,[XX,YY,Op,AsA,AsB]})
+ end.
+
+args([], _, Seed, R) ->
+ {lists:reverse(R),Seed};
+args([q|Ts], [Q|Qs]=Qss, Seed, R) ->
+ args(Ts, if Qs =:= [] -> Qss; true -> Qs end, Seed, [Q|R]);
+args([l|Ts], Qs, Seed0, R) ->
+ {N,Seed1} = random:uniform_s(17, Seed0),
+ {L,Seed} = random_list(N, 4711, Seed1, []),
+ args(Ts, Qs, Seed, [L|R]);
+args([t|Ts], Qs, Seed0, R) ->
+ {T,Seed} = random:uniform_s(4711, Seed0),
+ args(Ts, Qs, Seed, [T|R]);
+args([n|Ts], Qs, Seed0, R) ->
+ {N,Seed} = random:uniform_s(17, Seed0),
+ args(Ts, Qs, Seed, [N|R]).
+
+random_list(0, _, Seed, R) ->
+ {R,Seed};
+random_list(N, M, Seed0, R) ->
+ {X,Seed} = random:uniform_s(M, Seed0),
+ random_list(N-1, M, Seed, [X|R]).
+
+call(Func, As) ->
+ try case Func of
+ {M,F} -> apply(M, F, As);
+ _ -> apply(Func, As)
+ end of
+ V ->
+ {value,V}
+ catch
+ Class:Reason ->
+ {Class,Reason}
+ end.
+
+chk(QsA, QsB, v, X, X) ->
+ io:format("<~p> ", [X]),
+ {QsA,QsB};
+chk(_, _, v, X, Y) ->
+ throw({diff,v,[X,Y]});
+chk(QsA, QsB, q, Qa, {Lb}=Qb) ->
+ case queue:to_list(Qa) of
+ Lb ->
+ io:format("|~p| ", [Lb]),
+ {[Qa|QsA],[Qb|QsB]};
+ La ->
+ throw({diff,q,[Qa,La,Lb]})
+ end;
+chk(QsA, QsB, T, X, Y)
+ when tuple_size(T) =:= tuple_size(X), tuple_size(T) =:= tuple_size(Y) ->
+ io:format("{"),
+ try
+ chk_tuple(QsA, QsB, T, X, Y, 1)
+ after
+ io:format("}")
+ end;
+chk(_, _, T, X, Y)
+ when is_tuple(T), is_tuple(X), is_tuple(Y) ->
+ throw({diff,T,[X,Y]}).
+
+chk_tuple(QsA, QsB, T, _, _, N) when N > tuple_size(T) ->
+ {QsA,QsB};
+chk_tuple(QsA0, QsB0, T, X, Y, N) ->
+ {QsA,QsB} = chk(QsA0, QsB0, element(N, T), element(N, X), element(N, Y)),
+ chk_tuple(QsA, QsB, T, X, Y, N+1).
diff --git a/lib/stdlib/test/random_SUITE.erl b/lib/stdlib/test/random_SUITE.erl
new file mode 100644
index 0000000000..8f1c304705
--- /dev/null
+++ b/lib/stdlib/test/random_SUITE.erl
@@ -0,0 +1,110 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(random_SUITE).
+-export([all/1]).
+
+-export([interval_1/1, seed0/1, seed/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-include("test_server.hrl").
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(doc) ->
+ ["Test cases for random."];
+all(suite) ->
+ [interval_1, seed0, seed].
+
+seed0(doc) ->
+ ["Test that seed is set implicitly, and always the same."];
+seed0(suite) ->
+ [];
+seed0(Config) when is_list(Config) ->
+ ?line Self = self(),
+ ?line _ = spawn(fun() -> Self ! random:uniform() end),
+ ?line F1 = receive
+ Fa -> Fa
+ end,
+ ?line _ = spawn(fun() -> random:seed(),
+ Self ! random:uniform() end),
+ ?line F2 = receive
+ Fb -> Fb
+ end,
+ ?line F1 = F2,
+ ok.
+
+seed(doc) ->
+ ["Test that seed/1 and seed/3 is equivalent."];
+seed(suite) ->
+ [];
+seed(Config) when is_list(Config) ->
+ ?line Self = self(),
+ ?line Seed = {S1, S2, S3} = now(),
+ ?line _ = spawn(fun() ->
+ random:seed(S1,S2,S3),
+ Rands = lists:foldl(fun
+ (_, Out) -> [random:uniform(10000)|Out]
+ end, [], lists:seq(1,100)),
+ Self ! {seed_test, Rands}
+ end),
+ ?line Rands1 = receive {seed_test, R1s} -> R1s end,
+ ?line _ = spawn(fun() ->
+ random:seed(Seed),
+ Rands = lists:foldl(fun
+ (_, Out) -> [random:uniform(10000)|Out]
+ end, [], lists:seq(1,100)),
+ Self ! {seed_test, Rands}
+ end),
+ ?line Rands2 = receive {seed_test, R2s} -> R2s end,
+ ?line Rands1 = Rands2,
+ ok.
+
+
+interval_1(doc) ->
+ ["Check that uniform/1 returns values within the proper interval."];
+interval_1(suite) ->
+ [];
+interval_1(Config) when is_list(Config) ->
+ ?line Top = 7,
+ ?line N = 10,
+ ?line check_interval(N, Top),
+ ok.
+
+check_interval(0, _) -> ok;
+check_interval(N, Top) ->
+ X = random:uniform(Top),
+ if
+ X < 1 ->
+ test_server:fail(too_small);
+ X > Top ->
+ test_server:fail(too_large);
+ true ->
+ ok
+ end,
+ check_interval(N-1, Top).
diff --git a/lib/stdlib/test/random_iolist.erl b/lib/stdlib/test/random_iolist.erl
new file mode 100644
index 0000000000..4bce347d9a
--- /dev/null
+++ b/lib/stdlib/test/random_iolist.erl
@@ -0,0 +1,195 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% Generate random iolists to be used by crypto_SUITE.erl
+%%
+
+-module(random_iolist).
+
+-export([run/3, run2/3, standard_seed/0, compare/3, compare2/3,
+ random_iolist/1]).
+
+run(Iter,Fun1,Fun2) ->
+ standard_seed(),
+ compare(Iter,Fun1,Fun2).
+
+run2(Iter,Fun1,Fun2) ->
+ standard_seed(),
+ compare2(Iter,Fun1,Fun2).
+
+random_byte() ->
+ random:uniform(256) - 1.
+
+random_list(0,Acc) ->
+ Acc;
+random_list(N,Acc) ->
+ random_list(N-1,[random_byte() | Acc]).
+
+random_binary(N) ->
+ B = list_to_binary(random_list(N,[])),
+ case {random:uniform(2),size(B)} of
+ {2,M} when M > 1 ->
+ S = M-1,
+ <<_:3,C:S/binary,_:5>> = B,
+ C;
+ _ ->
+ B
+ end.
+random_list(N) ->
+ random_list(N,[]).
+
+front() ->
+ case random:uniform(10) of
+ 10 ->
+ false;
+ _ ->
+ true
+ end.
+
+any_type() ->
+ case random:uniform(10) of
+ 1 ->
+ list;
+ 2 ->
+ binary;
+ 3 ->
+ iolist;
+ _ ->
+ byte
+ end.
+
+tail_type() ->
+ case random:uniform(5) of
+ 1 ->
+ list;
+ 2 ->
+ iolist;
+ _ ->
+ binary
+ end.
+
+random_length(N) ->
+ UpperLimit = 255,
+ case N of
+ M when M > UpperLimit ->
+ random:uniform(UpperLimit+1) - 1;
+ _ ->
+ random:uniform(N+1) - 1
+ end.
+
+random_iolist(0,Acc) ->
+ Acc;
+random_iolist(N,Acc) ->
+ case front() of
+ true ->
+ case any_type() of
+ list ->
+ X = random_length(N),
+ L = random_list(X),
+ random_iolist(N-X,[L|Acc]);
+ binary ->
+ X = random_length(N),
+ B = random_binary(X),
+ random_iolist(N-X,[B|Acc]);
+ iolist ->
+ X = random_length(N),
+ B = random_iolist(X),
+ random_iolist(N-X,[B|Acc]);
+ byte ->
+ C = random_byte(),
+ random_iolist(N-1,[C|Acc])
+ end;
+ false ->
+ case tail_type() of
+ list ->
+ X = random_length(N),
+ L = random_list(X),
+ random_iolist(N-X,[Acc|L]);
+ binary ->
+ X = random_length(N),
+ B = random_binary(X),
+ random_iolist(N-X,[Acc|B]);
+ iolist ->
+ X = random_length(N),
+ B = random_iolist(X),
+ random_iolist(N-X,[Acc|B])
+ end
+ end.
+
+random_iolist(N) ->
+ random_iolist(N,[]).
+
+
+standard_seed() ->
+ random:seed(1201,855653,380975).
+
+do_comp(List,F1,F2) ->
+ X = F1(List),
+ Y = F2(List),
+ case X =:= Y of
+ false ->
+ exit({not_matching,List,X,Y});
+ _ ->
+ true
+ end.
+
+do_comp(List,List2,F1,F2) ->
+ X = F1(List,List2),
+ Y = F2(List,List2),
+ case X =:= Y of
+ false ->
+ exit({not_matching,List,List2,X,Y});
+ _ ->
+ true
+ end.
+
+compare(0,Fun1,Fun2) ->
+ do_comp(<<>>,Fun1,Fun2),
+ do_comp([],Fun1,Fun2),
+ do_comp([[]|<<>>],Fun1,Fun2),
+ do_comp([<<>>,[]|<<>>],Fun1,Fun2),
+ true;
+
+compare(N,Fun1,Fun2) ->
+ L = random_iolist(N),
+ do_comp(L,Fun1,Fun2),
+ compare(N-1,Fun1,Fun2).
+
+compare2(0,Fun1,Fun2) ->
+ L = random_iolist(100),
+ do_comp(<<>>,L,Fun1,Fun2),
+ do_comp(L,<<>>,Fun1,Fun2),
+ do_comp(<<>>,<<>>,Fun1,Fun2),
+ do_comp([],L,Fun1,Fun2),
+ do_comp(L,[],Fun1,Fun2),
+ do_comp([],[],Fun1,Fun2),
+ do_comp([[]|<<>>],L,Fun1,Fun2),
+ do_comp(L,[[]|<<>>],Fun1,Fun2),
+ do_comp([[]|<<>>],[[]|<<>>],Fun1,Fun2),
+ do_comp([<<>>,[]|<<>>],L,Fun1,Fun2),
+ do_comp(L,[<<>>,[]|<<>>],Fun1,Fun2),
+ do_comp([<<>>,[]|<<>>],[<<>>,[]|<<>>],Fun1,Fun2),
+ true;
+
+compare2(N,Fun1,Fun2) ->
+ L = random_iolist(N),
+ L2 = random_iolist(N),
+ do_comp(L,L2,Fun1,Fun2),
+ compare2(N-1,Fun1,Fun2).
diff --git a/lib/stdlib/test/random_unicode_list.erl b/lib/stdlib/test/random_unicode_list.erl
new file mode 100644
index 0000000000..3e83383b08
--- /dev/null
+++ b/lib/stdlib/test/random_unicode_list.erl
@@ -0,0 +1,270 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% Generate random iolists to be used by crypto_SUITE.erl
+%%
+
+-module(random_unicode_list).
+
+-export([run/3, run/4, run2/3, standard_seed/0, compare/4, compare2/3,
+ random_unicode_list/2]).
+
+run(I,F1,F2) ->
+ run(I,F1,F2,utf8).
+run(Iter,Fun1,Fun2,Enc) ->
+ standard_seed(),
+ compare(Iter,Fun1,Fun2,Enc).
+
+run2(Iter,Fun1,Fun2) ->
+ standard_seed(),
+ compare2(Iter,Fun1,Fun2).
+
+int_to_utf8(I) when I =< 16#7F ->
+ <<I>>;
+int_to_utf8(I) when I =< 16#7FF ->
+ B2 = I,
+ B1 = (I bsr 6),
+ <<1:1,1:1,0:1,B1:5,1:1,0:1,B2:6>>;
+int_to_utf8(I) when I =< 16#FFFF ->
+ B3 = I,
+ B2 = (I bsr 6),
+ B1 = (I bsr 12),
+ <<1:1,1:1,1:1,0:1,B1:4,1:1,0:1,B2:6,1:1,0:1,B3:6>>;
+int_to_utf8(I) when I =< 16#3FFFFF ->
+ B4 = I,
+ B3 = (I bsr 6),
+ B2 = (I bsr 12),
+ B1 = (I bsr 18),
+ <<1:1,1:1,1:1,1:1,0:1,B1:3,1:1,0:1,B2:6,1:1,0:1,B3:6,1:1,0:1,B4:6>>;
+int_to_utf8(I) when I =< 16#3FFFFFF ->
+ B5 = I,
+ B4 = (I bsr 6),
+ B3 = (I bsr 12),
+ B2 = (I bsr 18),
+ B1 = (I bsr 24),
+ <<1:1,1:1,1:1,1:1,1:1,0:1,B1:2,1:1,0:1,B2:6,1:1,0:1,B3:6,1:1,0:1,B4:6,
+ 1:1,0:1,B5:6>>.
+
+int_to_utf16_big(I) when I < 16#10000 ->
+ <<I:16/big>>;
+int_to_utf16_big(I) ->
+ I2 = I - 16#10000,
+ B1 = 16#D800 bor (I2 bsr 10),
+ B2 = 16#DC00 bor (I2 band 16#3FF),
+ <<B1:16/big,B2:16/big>>.
+int_to_utf16_little(I) when I < 16#10000 ->
+ <<I:16/little>>;
+int_to_utf16_little(I) ->
+ I2 = I - 16#10000,
+ B1 = 16#D800 bor (I2 bsr 10),
+ B2 = 16#DC00 bor (I2 band 16#3FF),
+ <<B1:16/little,B2:16/little>>.
+int_to_utf32_big(I) ->
+ <<I:32/big>>.
+int_to_utf32_little(I) ->
+ <<I:32/little>>.
+
+id(I) -> I.
+
+random_char() ->
+ case random:uniform(16#10FFFF+1) - 1 of
+ X when X >= 16#D800,
+ X =< 16#DFFF ->
+ random_char();
+ Y ->
+ Y
+ end.
+
+random_list(0,Acc) ->
+ Acc;
+random_list(N,Acc) ->
+ random_list(N-1,[random_char() | Acc]).
+
+int_to(utf8,X) ->
+ int_to_utf8(X);
+int_to({utf16,big},X) ->
+ int_to_utf16_big(X);
+int_to({utf16,little},X) ->
+ int_to_utf16_little(X);
+int_to({utf32,big},X) ->
+ int_to_utf32_big(X);
+int_to({utf32,little},X) ->
+ int_to_utf32_little(X).
+
+
+random_binary(N,Enc) ->
+ L = random_list(N,[]),
+ B = iolist_to_binary(lists:map(fun(X) ->
+ int_to(Enc,X)
+ end,
+ L)),
+ case {random:uniform(3),size(B)} of
+ {2,M} when M > 1 ->
+ B2 = id(<<1:3,B/binary,1:5>>),
+ <<_:3,C:M/binary,_:5>> = B2,
+ C;
+ {3,M} when M > 1 ->
+ X = random:uniform(M+1)-1,
+ <<B1:X/binary,B2/binary>> = B,
+ [B1,B2];
+ _ ->
+ B
+ end.
+random_list(N) ->
+ random_list(N,[]).
+
+front() ->
+ case random:uniform(10) of
+ 10 ->
+ false;
+ _ ->
+ true
+ end.
+
+any_type() ->
+ case random:uniform(10) of
+ 1 ->
+ list;
+ 2 ->
+ binary;
+ 3 ->
+ iolist;
+ _ ->
+ char
+ end.
+
+tail_type() ->
+ case random:uniform(5) of
+ 1 ->
+ list;
+ 2 ->
+ iolist;
+ _ ->
+ binary
+ end.
+
+random_length(N) ->
+ UpperLimit = 255,
+ case N of
+ M when M > UpperLimit ->
+ random:uniform(UpperLimit+1) - 1;
+ _ ->
+ random:uniform(N+1) - 1
+ end.
+
+random_unicode_list(0,Acc,_Enc) ->
+ Acc;
+random_unicode_list(N,Acc,Enc) ->
+ case front() of
+ true ->
+ case any_type() of
+ list ->
+ X = random_length(N),
+ L = random_list(X),
+ random_unicode_list(N-X,[L|Acc],Enc);
+ binary ->
+ X = random_length(N),
+ B = random_binary(X,Enc),
+ random_unicode_list(N-X,[B|Acc],Enc);
+ iolist ->
+ X = random_length(N),
+ B = random_unicode_list(X,Enc),
+ random_unicode_list(N-X,[B|Acc],Enc);
+ char ->
+ C = random_char(),
+ random_unicode_list(N-1,[C|Acc],Enc)
+ end;
+ false ->
+ case tail_type() of
+ list ->
+ X = random_length(N),
+ L = random_list(X),
+ random_unicode_list(N-X,[Acc|L],Enc);
+ binary ->
+ X = random_length(N),
+ B = random_binary(X,Enc),
+ random_unicode_list(N-X,[Acc|B],Enc);
+ iolist ->
+ X = random_length(N),
+ B = random_unicode_list(X,Enc),
+ random_unicode_list(N-X,[Acc|B],Enc)
+ end
+ end.
+
+random_unicode_list(N,Enc) ->
+ random_unicode_list(N,[],Enc).
+
+
+standard_seed() ->
+ random:seed(1201,855653,380975).
+
+do_comp(List,F1,F2) ->
+ X = F1(List),
+ Y = F2(List),
+ case X =:= Y of
+ false ->
+ exit({not_matching,List,X,Y});
+ _ ->
+ true
+ end.
+
+do_comp(List,List2,F1,F2) ->
+ X = F1(List,List2),
+ Y = F2(List,List2),
+ case X =:= Y of
+ false ->
+ exit({not_matching,List,List2,X,Y});
+ _ ->
+ true
+ end.
+
+compare(0,Fun1,Fun2,_Enc) ->
+ do_comp(<<>>,Fun1,Fun2),
+ do_comp([],Fun1,Fun2),
+ do_comp([[]|<<>>],Fun1,Fun2),
+ do_comp([<<>>,[]|<<>>],Fun1,Fun2),
+ true;
+
+compare(N,Fun1,Fun2,Enc) ->
+ L = random_unicode_list(N,Enc),
+ do_comp(L,Fun1,Fun2),
+ compare(N-1,Fun1,Fun2,Enc).
+
+compare2(0,Fun1,Fun2) ->
+ L = random_unicode_list(100,utf8),
+ do_comp(<<>>,L,Fun1,Fun2),
+ do_comp(L,<<>>,Fun1,Fun2),
+ do_comp(<<>>,<<>>,Fun1,Fun2),
+ do_comp([],L,Fun1,Fun2),
+ do_comp(L,[],Fun1,Fun2),
+ do_comp([],[],Fun1,Fun2),
+ do_comp([[]|<<>>],L,Fun1,Fun2),
+ do_comp(L,[[]|<<>>],Fun1,Fun2),
+ do_comp([[]|<<>>],[[]|<<>>],Fun1,Fun2),
+ do_comp([<<>>,[]|<<>>],L,Fun1,Fun2),
+ do_comp(L,[<<>>,[]|<<>>],Fun1,Fun2),
+ do_comp([<<>>,[]|<<>>],[<<>>,[]|<<>>],Fun1,Fun2),
+ true;
+
+compare2(N,Fun1,Fun2) ->
+ L = random_unicode_list(N,utf8),
+ L2 = random_unicode_list(N,utf8),
+ do_comp(L,L2,Fun1,Fun2),
+ compare2(N-1,Fun1,Fun2).
diff --git a/lib/stdlib/test/re_SUITE.erl b/lib/stdlib/test/re_SUITE.erl
new file mode 100644
index 0000000000..98eb66d1fb
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE.erl
@@ -0,0 +1,526 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(re_SUITE).
+
+-export([all/1, pcre/1,compile_options/1,run_options/1,combined_options/1,replace_autogen/1,global_capture/1,replace_return/1,split_autogen/1,split_options/1,split_specials/1,error_handling/1]).
+
+-include("test_server.hrl").
+-include_lib("kernel/include/file.hrl").
+
+all(suite) -> [pcre,compile_options,run_options,combined_options,replace_autogen,global_capture,replace_return,split_autogen,split_options,split_specials,error_handling].
+
+pcre(doc) ->
+ ["Run all applicable tests from the PCRE testsuites."];
+pcre(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(3)),
+ RootDir = ?config(data_dir, Config),
+ Res = run_pcre_tests:test(RootDir),
+ 0 = lists:sum([ X || {X,_,_} <- Res ]),
+ ?t:timetrap_cancel(Dog),
+ {comment,Res}.
+
+compile_options(doc) ->
+ ["Test all documented compile options"];
+compile_options(Config) when is_list(Config) ->
+ ?line ok = ctest("ABDabcdABCD","abcd",[],true,{match,[{3,4}]}),
+ ?line ok = ctest("ABDabcdABCD","abcd",[anchored],true,nomatch),
+ ?line ok = ctest("ABDabcdABCD",".*abcd",[anchored],true,{match,[{0,7}]}),
+ ?line ok = ctest("ABCabcdABC","ABCD",[],true,nomatch),
+ ?line ok = ctest("ABCabcdABC","ABCD",[caseless],true,{match,[{3,4}]}),
+ ?line ok = ctest("abcdABC\n","ABC$",[],true,{match,[{4,3}]}),
+ ?line ok = ctest("abcdABC\n","ABC$",[dollar_endonly],true,nomatch),
+ ?line ok = ctest("abcdABC\n","ABC.",[],true,nomatch),
+ ?line ok = ctest("abcdABC\n","ABC.",[dotall],true,{match,[{4,4}]}),
+ ?line ok = ctest("abcdABCD","ABC .",[],true,nomatch),
+ ?line ok = ctest("abcdABCD","ABC .",[extended],true,{match,[{4,4}]}),
+ ?line ok = ctest("abcd\nABCD","ABC",[],true,{match,[{5,3}]}),
+ ?line ok = ctest("abcd\nABCD","ABC",[firstline],true,nomatch),
+ ?line ok = ctest("abcd\nABCD","^ABC",[],true,nomatch),
+ ?line ok = ctest("abcd\nABCD","^ABC",[multiline],true,{match,[{5,3}]}),
+ ?line ok = ctest("abcdABCD","(ABC)",[],true,{match,[{4,3},{4,3}]}),
+ ?line ok = ctest("abcdABCD","(ABC)",[no_auto_capture],true,{match,[{4,3}]}),
+ ?line ok = ctest(notused,"(?<FOO>ABC)|(?<FOO>DEF)",[],false,notused),
+ ?line ok = ctest("abcdABCD","(?<FOO>ABC)|(?<FOO>DEF)",[dupnames],true,{match,[{4,3},{4,3}]}),
+ ?line ok = ctest("abcdABCDabcABCD","abcd.*D",[],true,{match,[{0,15}]}),
+ ?line ok = ctest("abcdABCDabcABCD","abcd.*D",[ungreedy],true,{match,[{0,8}]}),
+ ?line ok = ctest("abcdABCabcABC\nD","abcd.*D",[],true,nomatch),
+ ?line ok = ctest("abcdABCabcABC\nD","abcd.*D",[{newline,cr}],true,{match,[{0,15}]}),
+ ?line ok = ctest("abcdABCabcABC\rD","abcd.*D",[],true,{match,[{0,15}]}),
+ ?line ok = ctest("abcdABCabcABC\rD","abcd.*D",[{newline,lf}],true,{match,[{0,15}]}),
+ ?line ok = ctest("abcdABCabcd\r\n","abcd$",[{newline,lf}],true,nomatch),
+ ?line ok = ctest("abcdABCabcd\r\n","abcd$",[{newline,cr}],true,nomatch),
+ ?line ok = ctest("abcdABCabcd\r\n","abcd$",[{newline,crlf}],true,{match,[{7,4}]}),
+
+ ?line ok = ctest("abcdABCabcd\r","abcd$",[{newline,crlf}],true,nomatch),
+ ?line ok = ctest("abcdABCabcd\n","abcd$",[{newline,crlf}],true,nomatch),
+ ?line ok = ctest("abcdABCabcd\r\n","abcd$",[{newline,anycrlf}],true,{match,[{7,4}]}),
+
+ ?line ok = ctest("abcdABCabcd\r","abcd$",[{newline,anycrlf}],true,{match,[{7,4}]}),
+ ?line ok = ctest("abcdABCabcd\n","abcd$",[{newline,anycrlf}],true,{match,[{7,4}]}),
+ ok.
+
+run_options(doc) ->
+ ["Test all documented run specific options"];
+run_options(Config) when is_list(Config) ->
+ ?line rtest("ABCabcdABC","abc",[],[],true),
+ ?line rtest("ABCabcdABC","abc",[anchored],[],false),
+ % Anchored in run overrides unanchored in compilation
+ ?line rtest("ABCabcdABC","abc",[],[anchored],false),
+
+ ?line rtest("","a?b?",[],[],true),
+ ?line rtest("","a?b?",[],[notempty],false),
+
+ ?line rtest("abc","^a",[],[],true),
+ ?line rtest("abc","^a",[],[notbol],false),
+ ?line rtest("ab\nc","^a",[multiline],[],true),
+ ?line rtest("ab\nc","^a",[multiline],[notbol],false),
+ ?line rtest("ab\nc","^c",[multiline],[notbol],true),
+
+ ?line rtest("abc","c$",[],[],true),
+ ?line rtest("abc","c$",[],[noteol],false),
+
+ ?line rtest("ab\nc","b$",[multiline],[],true),
+ ?line rtest("ab\nc","c$",[multiline],[],true),
+ ?line rtest("ab\nc","b$",[multiline],[noteol],true),
+ ?line rtest("ab\nc","c$",[multiline],[noteol],false),
+
+ ?line rtest("abc","ab",[],[{offset,0}],true),
+ ?line rtest("abc","ab",[],[{offset,1}],false),
+
+ ?line rtest("abcdABCabcABC\nD","abcd.*D",[],[],false),
+ ?line rtest("abcdABCabcABC\nD","abcd.*D",[],[{newline,cr}],true),
+ ?line rtest("abcdABCabcABC\rD","abcd.*D",[],[],true),
+ ?line rtest("abcdABCabcABC\rD","abcd.*D",[{newline,cr}],[{newline,lf}],true),
+ ?line rtest("abcdABCabcd\r\n","abcd$",[],[{newline,lf}],false),
+ ?line rtest("abcdABCabcd\r\n","abcd$",[],[{newline,cr}],false),
+ ?line rtest("abcdABCabcd\r\n","abcd$",[],[{newline,crlf}],true),
+
+ ?line rtest("abcdABCabcd\r","abcd$",[],[{newline,crlf}],false),
+ ?line rtest("abcdABCabcd\n","abcd$",[],[{newline,crlf}],false),
+ ?line rtest("abcdABCabcd\r\n","abcd$",[],[{newline,anycrlf}],true),
+
+ ?line rtest("abcdABCabcd\r","abcd$",[],[{newline,anycrlf}],true),
+ ?line rtest("abcdABCabcd\n","abcd$",[],[{newline,anycrlf}],true),
+
+ ?line {ok,MP} = re:compile(".*(abcd).*"),
+ ?line {match,[{0,10},{3,4}]} = re:run("ABCabcdABC",MP,[]),
+ ?line {match,[{0,10},{3,4}]} = re:run("ABCabcdABC",MP,[{capture,all}]),
+ ?line {match,[{0,10},{3,4}]} = re:run("ABCabcdABC",MP,[{capture,all,index}]),
+ ?line {match,["ABCabcdABC","abcd"]} = re:run("ABCabcdABC",MP,[{capture,all,list}]),
+ ?line {match,[<<"ABCabcdABC">>,<<"abcd">>]} = re:run("ABCabcdABC",MP,[{capture,all,binary}]),
+ ?line {match,[{0,10}]} = re:run("ABCabcdABC",MP,[{capture,first}]),
+ ?line {match,[{0,10}]} = re:run("ABCabcdABC",MP,[{capture,first,index}]), ?line {match,["ABCabcdABC"]} = re:run("ABCabcdABC",MP,[{capture,first,list}]),
+ ?line {match,[<<"ABCabcdABC">>]} = re:run("ABCabcdABC",MP,[{capture,first,binary}]),
+
+ ?line {match,[{3,4}]} = re:run("ABCabcdABC",MP,[{capture,all_but_first}]),
+ ?line {match,[{3,4}]} = re:run("ABCabcdABC",MP,[{capture,all_but_first,index}]),
+ ?line {match,["abcd"]} = re:run("ABCabcdABC",MP,[{capture,all_but_first,list}]),
+ ?line {match,[<<"abcd">>]} = re:run("ABCabcdABC",MP,[{capture,all_but_first,binary}]),
+
+ ?line match = re:run("ABCabcdABC",MP,[{capture,none}]),
+ ?line match = re:run("ABCabcdABC",MP,[{capture,none,index}]),
+ ?line match = re:run("ABCabcdABC",MP,[{capture,none,list}]),
+ ?line match = re:run("ABCabcdABC",MP,[{capture,none,binary}]),
+
+ ?line {ok,MP2} = re:compile(".*(?<FOO>abcd).*"),
+ ?line {match,[{3,4}]} = re:run("ABCabcdABC",MP2,[{capture,[1]}]),
+ ?line {match,[{3,4}]} = re:run("ABCabcdABC",MP2,[{capture,['FOO']}]),
+ ?line {match,[{3,4}]} = re:run("ABCabcdABC",MP2,[{capture,["FOO"]}]),
+ ?line {match,["abcd"]} = re:run("ABCabcdABC",MP2,[{capture,["FOO"],list}]),
+ ?line {match,[<<"abcd">>]} = re:run("ABCabcdABC",MP2,[{capture,["FOO"],binary}]),
+
+ ?line {match,[{-1,0}]} = re:run("ABCabcdABC",MP2,[{capture,[200]}]),
+ ?line {match,[{-1,0}]} = re:run("ABCabcdABC",MP2,[{capture,['BAR']}]),
+ ?line {match,[""]} = re:run("ABCabcdABC",MP2,[{capture,[200],list}]),
+ ?line {match,[""]} = re:run("ABCabcdABC",MP2,[{capture,['BAR'],list}]),
+ ?line {match,[<<>>]} = re:run("ABCabcdABC",MP2,[{capture,[200],binary}]),
+ ?line {match,[<<>>]} = re:run("ABCabcdABC",MP2,[{capture,['BAR'],binary}]),
+
+ ?line {ok, MP3} = re:compile(".*((?<FOO>abdd)|a(..d)).*"),
+ ?line {match,[{0,10},{3,4},{-1,0},{4,3}]} = re:run("ABCabcdABC",MP3,[]),
+ ?line {match,[{0,10},{3,4},{-1,0},{4,3}]} = re:run("ABCabcdABC",MP3,[{capture,all,index}]),
+ ?line {match,[<<"ABCabcdABC">>,<<"abcd">>,<<>>,<<"bcd">>]} = re:run("ABCabcdABC",MP3,[{capture,all,binary}]),
+ ?line {match,["ABCabcdABC","abcd",[],"bcd"]} = re:run("ABCabcdABC",MP3,[{capture,all,list}]),
+ ok.
+
+
+
+combined_options(doc) ->
+ ["Test compile options given directly to run"];
+combined_options(Config) when is_list(Config) ->
+ ?line ok = crtest("ABDabcdABCD","abcd",[],true,{match,[{3,4}]}),
+ ?line ok = crtest("ABDabcdABCD","abcd",[anchored],true,nomatch),
+ ?line ok = crtest("ABDabcdABCD",".*abcd",[anchored],true,{match,[{0,7}]}),
+ ?line ok = crtest("ABCabcdABC","ABCD",[],true,nomatch),
+ ?line ok = crtest("ABCabcdABC","ABCD",[caseless],true,{match,[{3,4}]}),
+ ?line ok = crtest("abcdABC\n","ABC$",[],true,{match,[{4,3}]}),
+ ?line ok = crtest("abcdABC\n","ABC$",[dollar_endonly],true,nomatch),
+ ?line ok = crtest("abcdABC\n","ABC.",[],true,nomatch),
+ ?line ok = crtest("abcdABC\n","ABC.",[dotall],true,{match,[{4,4}]}),
+ ?line ok = crtest("abcdABCD","ABC .",[],true,nomatch),
+ ?line ok = crtest("abcdABCD","ABC .",[extended],true,{match,[{4,4}]}),
+ ?line ok = crtest("abcd\nABCD","ABC",[],true,{match,[{5,3}]}),
+ ?line ok = crtest("abcd\nABCD","ABC",[firstline],true,nomatch),
+ ?line ok = crtest("abcd\nABCD","^ABC",[],true,nomatch),
+ ?line ok = crtest("abcd\nABCD","^ABC",[multiline],true,{match,[{5,3}]}),
+ ?line ok = crtest("abcdABCD","(ABC)",[],true,{match,[{4,3},{4,3}]}),
+ ?line ok = crtest("abcdABCD","(ABC)",[no_auto_capture],true,{match,[{4,3}]}),
+ ?line ok = crtest(notused,"(?<FOO>ABC)|(?<FOO>DEF)",[],false,notused),
+ ?line ok = crtest("abcdABCD","(?<FOO>ABC)|(?<FOO>DEF)",[dupnames],true,{match,[{4,3},{4,3}]}),
+ ?line ok = crtest("abcdABCDabcABCD","abcd.*D",[],true,{match,[{0,15}]}),
+ ?line ok = crtest("abcdABCDabcABCD","abcd.*D",[ungreedy],true,{match,[{0,8}]}),
+ ?line ok = ctest("abcdABCabcABC\nD","abcd.*D",[],true,nomatch),
+ ?line ok = crtest("abcdABCabcABC\nD","abcd.*D",[{newline,cr}],true,{match,[{0,15}]}),
+ ?line ok = crtest("abcdABCabcABC\rD","abcd.*D",[],true,{match,[{0,15}]}),
+ ?line ok = crtest("abcdABCabcABC\rD","abcd.*D",[{newline,lf}],true,{match,[{0,15}]}),
+ ?line ok = crtest("abcdABCabcd\r\n","abcd$",[{newline,lf}],true,nomatch),
+ ?line ok = crtest("abcdABCabcd\r\n","abcd$",[{newline,cr}],true,nomatch),
+ ?line ok = crtest("abcdABCabcd\r\n","abcd$",[{newline,crlf}],true,{match,[{7,4}]}),
+
+ ?line ok = crtest("abcdABCabcd\r","abcd$",[{newline,crlf}],true,nomatch),
+ ?line ok = crtest("abcdABCabcd\n","abcd$",[{newline,crlf}],true,nomatch),
+ ?line ok = crtest("abcdABCabcd\r\n","abcd$",[{newline,anycrlf}],true,{match,[{7,4}]}),
+
+ ?line ok = crtest("abcdABCabcd\r","abcd$",[{newline,anycrlf}],true,{match,[{7,4}]}),
+ ?line ok = crtest("abcdABCabcd\n","abcd$",[{newline,anycrlf}],true,{match,[{7,4}]}),
+
+ ?line ok = crtest("abcdABCabcd\r\n","abcd$",[{newline,anycrlf},{capture,all,list}],true,{match,["abcd"]}),
+
+ ?line ok = crtest("abcdABCabcd\r","abcd$",[{newline,anycrlf},{capture,all,list}],true,{match,["abcd"]}),
+
+ ?line ok = crtest("abcdABCabcd\n","abcd$",[{newline,anycrlf},{capture,all,list}],true,{match,["abcd"]}),
+
+ ?line ok = crtest("abcdABCabcd\r\n","abcd$",[{newline,anycrlf},{capture,all,binary}],true,{match,[<<"abcd">>]}),
+
+ ?line ok = crtest("abcdABCabcd\r","abcd$",[{newline,anycrlf},{capture,all,binary}],true,{match,[<<"abcd">>]}),
+ ?line ok = crtest("abcdABCabcd\n","abcd$",[{newline,anycrlf},{capture,all,binary}],true,{match,[<<"abcd">>]}),
+
+ % Check that unique run-options fail in compile only case:
+ ?line {'EXIT',{badarg,_}} = (catch re:compile("abcd$",[{newline,anycrlf},{capture,all,binary}])),
+ ?line {'EXIT',{badarg,_}} = (catch re:compile("abcd$",[{newline,anycrlf},{offset,3}])),
+ ?line {'EXIT',{badarg,_}} = (catch re:compile("abcd$",[{newline,anycrlf},notempty])),
+ ?line {'EXIT',{badarg,_}} = (catch re:compile("abcd$",[{newline,anycrlf},notbol])),
+ ?line {'EXIT',{badarg,_}} = (catch re:compile("abcd$",[{newline,anycrlf},noteol])),
+
+
+ ?line {match,_} = re:run("abcdABCabcd\r\n","abcd$",[{newline,crlf}]),
+ ?line nomatch = re:run("abcdABCabcd\r\nefgh","abcd$",[{newline,crlf}]),
+ ?line {match,_} = re:run("abcdABCabcd\r\nefgh","abcd$",[{newline,crlf},multiline]),
+ ?line nomatch = re:run("abcdABCabcd\r\nefgh","efgh$",[{newline,crlf},multiline,noteol]),
+ ?line {match,_} = re:run("abcdABCabcd\r\nefgh","abcd$",[{newline,crlf},multiline,noteol]),
+ ?line {match,_} = re:run("abcdABCabcd\r\nefgh","^abcd",[{newline,crlf},multiline,noteol]),
+ ?line nomatch = re:run("abcdABCabcd\r\nefgh","^abcd",[{newline,crlf},multiline,notbol]),
+ ?line {match,_} = re:run("abcdABCabcd\r\nefgh","^efgh",[{newline,crlf},multiline,notbol]),
+ ?line {match,_} = re:run("ABC\nD","[a-z]*",[{newline,crlf}]),
+ ?line nomatch = re:run("ABC\nD","[a-z]*",[{newline,crlf},notempty]),
+ ok.
+
+replace_autogen(doc) ->
+ ["Test replace with autogenerated erlang module"];
+replace_autogen(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(3)),
+ re_testoutput1_replacement_test:run(),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+global_capture(doc) ->
+ ["Tests capture options together with global searching"];
+global_capture(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(3)),
+ ?line {match,[{3,4}]} = re:run("ABCabcdABC",".*(?<FOO>abcd).*",[{capture,[1]}]),
+ ?line {match,[{10,4}]} = re:run("ABCabcdABCabcdA",".*(?<FOO>abcd).*",[{capture,[1]}]),
+ ?line {match,[[{10,4}]]} = re:run("ABCabcdABCabcdA",".*(?<FOO>abcd).*",[global,{capture,[1]}]),
+ ?line {match,[{3,4}]} = re:run("ABCabcdABC",".*(?<FOO>abcd).*",[{capture,['FOO']}]),
+ ?line {match,[{10,4}]} = re:run("ABCabcdABCabcdA",".*(?<FOO>abcd).*",[{capture,['FOO']}]),
+ ?line {match,[[{10,4}]]} = re:run("ABCabcdABCabcdA",".*(?<FOO>abcd).*",[global,{capture,['FOO']}]),
+ ?line {match,[[{3,4},{3,4}],[{10,4},{10,4}]]} = re:run("ABCabcdABCabcdA","(?<FOO>abcd)",[global]),
+ ?line {match,[[{3,4},{3,4}],[{10,4},{10,4}]]} = re:run("ABCabcdABCabcdA","(?<FOO>abcd)",[global,{capture,all}]),
+ ?line {match,[[{3,4},{3,4}],[{10,4},{10,4}]]} = re:run("ABCabcdABCabcdA","(?<FOO>abcd)",[global,{capture,all,index}]),
+ ?line {match,[[{3,4}],[{10,4}]]} = re:run("ABCabcdABCabcdA","(?<FOO>abcd)",[global,{capture,first}]),
+ ?line {match,[[{3,4}],[{10,4}]]} = re:run("ABCabcdABCabcdA","(?<FOO>abcd)",[global,{capture,all_but_first}]),
+ ?line {match,[[<<"bcd">>],[<<"bcd">>]]} = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,all_but_first,binary}]),
+ ?line {match,[["bcd"],["bcd"]]} = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,all_but_first,list}]),
+ ?line {match,[["abcd","bcd"],["abcd","bcd"]]} = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,all,list}]),
+ ?line {match,[[<<"abcd">>,<<"bcd">>],[<<"abcd">>,<<"bcd">>]]} = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,all,binary}]),
+ ?line {match,[[{3,4},{4,3}],[{10,4},{11,3}]]} = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,all,index}]),
+ ?line match = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,none,index}]),
+ ?line match = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,none,binary}]),
+ ?line match = re:run("ABCabcdABCabcdA","a(?<FOO>bcd)",[global,{capture,none,list}]),
+ ?line {match,[[<<195,133,98,99,100>>,<<"bcd">>],[<<"abcd">>,<<"bcd">>]]} = re:run("ABC�bcdABCabcdA",".(?<FOO>bcd)",[global,{capture,all,binary},unicode]),
+ ?line {match,[["�bcd","bcd"],["abcd","bcd"]]} = re:run(<<"ABC",8#303,8#205,"bcdABCabcdA">>,".(?<FOO>bcd)",[global,{capture,all,list},unicode]),
+ ?line {match,[["�bcd","bcd"],["abcd","bcd"]]} = re:run("ABC�bcdABCabcdA",".(?<FOO>bcd)",[global,{capture,all,list},unicode]),
+ ?line {match,[[{3,5},{5,3}],[{11,4},{12,3}]]} = re:run("ABC�bcdABCabcdA",".(?<FOO>bcd)",[global,{capture,all,index},unicode]),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+replace_return(doc) ->
+ ["Tests return options of replace together with global searching"];
+replace_return(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(3)),
+ ?line {'EXIT',{badarg,_}} = (catch re:replace("na","(a","")),
+ ?line <<"nasse">> = re:replace(<<"nisse">>,"i","a",[{return,binary}]),
+ ?line <<"ABC�XABCXA">> = re:replace("ABC\305abcdABCabcdA","a(?<FOO>bcd)","X",[global,{return,binary}]),
+
+ ?line [<<"ABC�">>,
+ <<"X">>,
+ <<"ABC">>,
+ <<"X">> |
+ <<"A">> ] =
+ re:replace("ABC�abcdABCabcdA","a(?<FOO>bcd)","X",[global,{return,iodata}]),
+ ?line "ABC�XABCXA" = re:replace("ABC�abcdABCabcdA","a(?<FOO>bcd)","X",[global,{return,list},unicode]),
+ ?line <<65,66,67,195,133,88,65,66,67,88,65>> = re:replace("ABC�abcdABCabcdA","a(?<FOO>bcd)","X",[global,{return,binary},unicode]),
+ ?line <<65,66,67,195,133,88,65,66,67,97,98,99,100,65>> = re:replace("ABC�abcdABCabcdA","a(?<FOO>bcd)","X",[{return,binary},unicode]),
+ ?line <<"iXk">> = re:replace("abcdefghijk","(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)","\\9X",[{return,binary}]),
+ ?line <<"jXk">> = re:replace("abcdefghijk","(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)","\\10X",[{return,binary}]),
+ ?line <<"Xk">> = re:replace("abcdefghijk","(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)","\\11X",[{return,binary}]),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+rtest(Subj, RE, Copt, Ropt, true) ->
+ {ok,MP} = re:compile(RE,Copt),
+ {match,_} = re:run(Subj,MP,Ropt),
+ ok;
+rtest(Subj, RE, Copt, Ropt, false) ->
+ {ok,MP} = re:compile(RE,Copt),
+ nomatch = re:run(Subj,MP,Ropt),
+ ok.
+
+ctest(_,RE,Options,false,_) ->
+ case re:compile(RE,Options) of
+ {ok,_} ->
+ error;
+ {error,_} ->
+ ok
+ end;
+ctest(Subject,RE,Options,true,Result) ->
+ try
+ {ok, Prog} = re:compile(RE,Options),
+ Result = re:run(Subject,Prog,[]),
+ ok
+ catch
+ _:_ ->
+ error
+ end.
+crtest(_,RE,Options,false,_) ->
+ case (catch re:run("",RE,Options)) of
+ {'EXIT',{badarg,_}} ->
+ ok;
+ _ ->
+ error
+ end;
+crtest(Subject,RE,Options,true,Result) ->
+ try
+ Result = re:run(Subject,RE,Options),
+ ok
+ catch
+ _:_ ->
+ error
+ end.
+
+split_autogen(doc) ->
+ ["Test split with autogenerated erlang module"];
+split_autogen(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(3)),
+ re_testoutput1_split_test:run(),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+split_options(doc) ->
+ ["Test special options to split."];
+split_options(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(1)),
+ ?line [[<<"a">>,<<" ">>],[<<"b">>,<<" ">>],[<<"c">>,<<" ">>]] = re:split("a b c ","( )",[group,trim]),
+ ?line [[<<"a">>,<<" ">>],[<<"b">>,<<" ">>],[<<"c">>,<<" ">>]] = re:split("a b c ","( )",[group,{parts,0}]),
+ ?line [[<<"a">>,<<" ">>],[<<"b">>,<<" ">>],[<<"c">>,<<" ">>],[<<>>]] =
+ re:split("a b c ","( )",[{parts,infinity},group]),
+ ?line [[<<"a">>,<<" ">>],[<<"b">>,<<" ">>],[<<"c">>,<<" ">>],[<<>>]] =
+ re:split("a b c ","( )",[group]),
+ ?line [[<<>>,<<" ">>],[<<"a">>,<<" ">>],[<<"b">>,<<" ">>],
+ [<<"c">>,<<" ">>],[<<"d">>,<<" ">>]] =
+ re:split(" a b c d ","( +)",[group,trim]),
+ ?line [[<<>>,<<" ">>],[<<"a">>,<<" ">>],[<<"b">>,<<" ">>],
+ [<<"c">>,<<" ">>],[<<"d">>,<<" ">>]] =
+ re:split(" a b c d ","( +)",[{parts,0},group]),
+ ?line [[<<>>,<<" ">>],[<<"a">>,<<" ">>],[<<"b">>,<<" ">>],
+ [<<"c">>,<<" ">>],[<<"d">>,<<" ">>],[<<>>]] =
+ re:split(" a b c d ","( +)",[{parts,infinity},group]),
+ ?line [[<<"a">>,<<" ">>],[<<"b c d">>]] =
+ re:split("a b c d","( +)",[{parts,2},group]),
+ ?line [[[967]," "],["b c d"]] =
+ re:split([967]++" b c d","( +)",
+ [{parts,2},group,{return,list},unicode]),
+ ?line [[<<207,135>>,<<" ">>],[<<"b c d">>]] =
+ re:split([967]++" b c d","( +)",
+ [{parts,2},group,{return,binary},unicode]),
+ ?line {'EXIT',{badarg,_}} =
+ (catch re:split([967]++" b c d","( +)",
+ [{parts,2},group,{return,binary}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch re:split("a b c d","( +)",[{parts,-2}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch re:split("a b c d","( +)",[{parts,banan}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch re:split("a b c d","( +)",[{capture,all}])),
+ ?line {'EXIT',{badarg,_}} =
+ (catch re:split("a b c d","( +)",[{capture,[],binary}])),
+ % Parts 0 is equal to no parts specification (implicit strip)
+ ?line ["a"," ","b"," ","c"," ","d"] =
+ re:split("a b c d","( *)",[{parts,0},{return,list}]),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+join([]) -> [];
+join([A]) -> [A];
+join([H|T]) -> [H,<<":">>|join(T)].
+
+split_specials(doc) ->
+ ["Some special cases of split that are easy to get wrong."];
+split_specials(Config) when is_list(Config) ->
+ %% More or less just to remember these icky cases
+ Dog = ?t:timetrap(?t:minutes(1)),
+ ?line <<"::abd:f">> =
+ iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[trim]))),
+ ?line <<":abc2xyzabc3">> =
+ iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[trim]))),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
+
+error_handling(doc) ->
+ ["Test that errors are handled correctly by the erlang code."];
+error_handling(Config) when is_list(Config) ->
+ % This test checks the exception tuples manufactured in the erlang
+ % code to hide the trapping from the user at least when it comes to errors
+ Dog = ?t:timetrap(?t:minutes(1)),
+ % The malformed precomiled RE is detected after
+ % the trap to re:grun from grun, in the grun function clause
+ % that handles precompiled expressions
+ ?line {'EXIT',{badarg,[{re,run,["apa",{1,2,3,4},[global]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:run("apa",{1,2,3,4},[global])),
+ % An invalid capture list will also cause a badarg late,
+ % but with a non pre compiled RE, the exception should be thrown by the
+ % grun function clause that handles RE's compiled implicitly by
+ % the run/3 BIF before trapping.
+ ?line {'EXIT',{badarg,[{re,run,["apa","p",[{capture,[1,{a}]},global]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:run("apa","p",[{capture,[1,{a}]},global])),
+ % And so the case of a precompiled expression together with
+ % a compile-option (binary and list subject):
+ ?line {ok,RE} = re:compile("(p)"),
+ ?line {match,[[{1,1},{1,1}]]} = re:run(<<"apa">>,RE,[global]),
+ ?line {match,[[{1,1},{1,1}]]} = re:run("apa",RE,[global]),
+ {'EXIT',{badarg,[{re,run,
+ [<<"apa">>,
+ {re_pattern,1,0,_},
+ [global,unicode]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:run(<<"apa">>,RE,[global,unicode])),
+ {'EXIT',{badarg,[{re,run,
+ ["apa",
+ {re_pattern,1,0,_},
+ [global,unicode]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:run("apa",RE,[global,unicode])),
+ ?line {'EXIT',{badarg,_}} = (catch re:run("apa","(p",[])),
+ ?line {'EXIT',{badarg,_}} = (catch re:run("apa","(p",[global])),
+ % The replace errors:
+ ?line {'EXIT',{badarg,[{re,replace,["apa",{1,2,3,4},"X",[]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:replace("apa",{1,2,3,4},"X",[])),
+ ?line {'EXIT',{badarg,[{re,replace,["apa",{1,2,3,4},"X",[global]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:replace("apa",{1,2,3,4},"X",[global])),
+ ?line {'EXIT',{badarg,[{re,replace,
+ ["apa",
+ {re_pattern,1,0,_},
+ "X",
+ [unicode]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:replace("apa",RE,"X",[unicode])),
+ ?line <<"aXa">> = iolist_to_binary(re:replace("apa","p","X",[])),
+ ?line {'EXIT',{badarg,[{re,replace,
+ ["apa","p","X",[{capture,all,binary}]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch iolist_to_binary(re:replace("apa","p","X",
+ [{capture,all,binary}]))),
+ ?line {'EXIT',{badarg,[{re,replace,
+ ["apa","p","X",[{capture,all}]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch iolist_to_binary(re:replace("apa","p","X",
+ [{capture,all}]))),
+ ?line {'EXIT',{badarg,[{re,replace,
+ ["apa","p","X",[{return,banana}]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch iolist_to_binary(re:replace("apa","p","X",
+ [{return,banana}]))),
+ ?line {'EXIT',{badarg,_}} = (catch re:replace("apa","(p","X",[])),
+ % Badarg, not compile error.
+ ?line {'EXIT',{badarg,[{re,replace,
+ ["apa","(p","X",[{return,banana}]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch iolist_to_binary(re:replace("apa","(p","X",
+ [{return,banana}]))),
+ % And the split errors:
+ ?line [<<"a">>,<<"a">>] = (catch re:split("apa","p",[])),
+ ?line [<<"a">>,<<"p">>,<<"a">>] = (catch re:split("apa",RE,[])),
+ ?line {'EXIT',{badarg,[{re,split,["apa","p",[global]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa","p",[global])),
+ ?line {'EXIT',{badarg,[{re,split,["apa","p",[{capture,all}]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa","p",[{capture,all}])),
+ ?line {'EXIT',{badarg,[{re,split,["apa","p",[{capture,all,binary}]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa","p",[{capture,all,binary}])),
+ ?line {'EXIT',{badarg,[{re,split,["apa",{1,2,3,4},[]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa",{1,2,3,4})),
+ ?line {'EXIT',{badarg,[{re,split,["apa",{1,2,3,4},[]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa",{1,2,3,4},[])),
+ ?line {'EXIT',{badarg,[{re,split,
+ ["apa",
+ RE,
+ [unicode]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa",RE,[unicode])),
+ ?line {'EXIT',{badarg,[{re,split,
+ ["apa",
+ RE,
+ [{return,banana}]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa",RE,[{return,banana}])),
+ ?line {'EXIT',{badarg,[{re,split,
+ ["apa",
+ RE,
+ [banana]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa",RE,[banana])),
+ ?line {'EXIT',{badarg,_}} = (catch re:split("apa","(p")),
+ %Exception on bad argument, not compilation error
+ ?line {'EXIT',{badarg,[{re,split,
+ ["apa",
+ "(p",
+ [banana]]},
+ {?MODULE, error_handling,1} | _]}} =
+ (catch re:split("apa","(p",[banana])),
+ ?t:timetrap_cancel(Dog),
+ ok.
+
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput1 b/lib/stdlib/test/re_SUITE_data/testoutput1
new file mode 100644
index 0000000000..3bf4ffbeee
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput1
@@ -0,0 +1,6608 @@
+/the quick brown fox/
+ the quick brown fox
+ 0: the quick brown fox
+ The quick brown FOX
+No match
+ What do you know about the quick brown fox?
+ 0: the quick brown fox
+ What do you know about THE QUICK BROWN FOX?
+No match
+
+/The quick brown fox/i
+ the quick brown fox
+ 0: the quick brown fox
+ The quick brown FOX
+ 0: The quick brown FOX
+ What do you know about the quick brown fox?
+ 0: the quick brown fox
+ What do you know about THE QUICK BROWN FOX?
+ 0: THE QUICK BROWN FOX
+
+/abcd\t\n\r\f\a\e\071\x3b\$\\\?caxyz/
+ abcd\t\n\r\f\a\e9;\$\\?caxyz
+ 0: abcd\x09\x0a\x0d\x0c\x07\x1b9;$\?caxyz
+
+/a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz/
+ abxyzpqrrrabbxyyyypqAzz
+ 0: abxyzpqrrrabbxyyyypqAzz
+ abxyzpqrrrabbxyyyypqAzz
+ 0: abxyzpqrrrabbxyyyypqAzz
+ aabxyzpqrrrabbxyyyypqAzz
+ 0: aabxyzpqrrrabbxyyyypqAzz
+ aaabxyzpqrrrabbxyyyypqAzz
+ 0: aaabxyzpqrrrabbxyyyypqAzz
+ aaaabxyzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzpqrrrabbxyyyypqAzz
+ abcxyzpqrrrabbxyyyypqAzz
+ 0: abcxyzpqrrrabbxyyyypqAzz
+ aabcxyzpqrrrabbxyyyypqAzz
+ 0: aabcxyzpqrrrabbxyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypAzz
+ 0: aaabcxyzpqrrrabbxyyyypAzz
+ aaabcxyzpqrrrabbxyyyypqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqqqAzz
+ aaaabcxyzpqrrrabbxyyyypqAzz
+ 0: aaaabcxyzpqrrrabbxyyyypqAzz
+ abxyzzpqrrrabbxyyyypqAzz
+ 0: abxyzzpqrrrabbxyyyypqAzz
+ aabxyzzzpqrrrabbxyyyypqAzz
+ 0: aabxyzzzpqrrrabbxyyyypqAzz
+ aaabxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaabxyzzzzpqrrrabbxyyyypqAzz
+ aaaabxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzzzzpqrrrabbxyyyypqAzz
+ abcxyzzpqrrrabbxyyyypqAzz
+ 0: abcxyzzpqrrrabbxyyyypqAzz
+ aabcxyzzzpqrrrabbxyyyypqAzz
+ 0: aabcxyzzzpqrrrabbxyyyypqAzz
+ aaabcxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaabcxyzzzzpqrrrabbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbbxyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbbxyyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbbxyyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypABzz
+ 0: aaabcxyzpqrrrabbxyyyypABzz
+ aaabcxyzpqrrrabbxyyyypABBzz
+ 0: aaabcxyzpqrrrabbxyyyypABBzz
+ >>>aaabxyzpqrrrabbxyyyypqAzz
+ 0: aaabxyzpqrrrabbxyyyypqAzz
+ >aaaabxyzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzpqrrrabbxyyyypqAzz
+ >>>>abcxyzpqrrrabbxyyyypqAzz
+ 0: abcxyzpqrrrabbxyyyypqAzz
+ *** Failers
+No match
+ abxyzpqrrabbxyyyypqAzz
+No match
+ abxyzpqrrrrabbxyyyypqAzz
+No match
+ abxyzpqrrrabxyyyypqAzz
+No match
+ aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz
+No match
+ aaaabcxyzzzzpqrrrabbbxyyypqAzz
+No match
+ aaabcxyzpqrrrabbxyyyypqqqqqqqAzz
+No match
+
+/^(abc){1,2}zz/
+ abczz
+ 0: abczz
+ 1: abc
+ abcabczz
+ 0: abcabczz
+ 1: abc
+ *** Failers
+No match
+ zz
+No match
+ abcabcabczz
+No match
+ >>abczz
+No match
+
+/^(b+?|a){1,2}?c/
+ bc
+ 0: bc
+ 1: b
+ bbc
+ 0: bbc
+ 1: b
+ bbbc
+ 0: bbbc
+ 1: bb
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ aac
+ 0: aac
+ 1: a
+ abbbbbbbbbbbc
+ 0: abbbbbbbbbbbc
+ 1: bbbbbbbbbbb
+ bbbbbbbbbbbac
+ 0: bbbbbbbbbbbac
+ 1: a
+ *** Failers
+No match
+ aaac
+No match
+ abbbbbbbbbbbac
+No match
+
+/^(b+|a){1,2}c/
+ bc
+ 0: bc
+ 1: b
+ bbc
+ 0: bbc
+ 1: bb
+ bbbc
+ 0: bbbc
+ 1: bbb
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ aac
+ 0: aac
+ 1: a
+ abbbbbbbbbbbc
+ 0: abbbbbbbbbbbc
+ 1: bbbbbbbbbbb
+ bbbbbbbbbbbac
+ 0: bbbbbbbbbbbac
+ 1: a
+ *** Failers
+No match
+ aaac
+No match
+ abbbbbbbbbbbac
+No match
+
+/^(b+|a){1,2}?bc/
+ bbc
+ 0: bbc
+ 1: b
+
+/^(b*|ba){1,2}?bc/
+ babc
+ 0: babc
+ 1: ba
+ bbabc
+ 0: bbabc
+ 1: ba
+ bababc
+ 0: bababc
+ 1: ba
+ *** Failers
+No match
+ bababbc
+No match
+ babababc
+No match
+
+/^(ba|b*){1,2}?bc/
+ babc
+ 0: babc
+ 1: ba
+ bbabc
+ 0: bbabc
+ 1: ba
+ bababc
+ 0: bababc
+ 1: ba
+ *** Failers
+No match
+ bababbc
+No match
+ babababc
+No match
+
+/^\ca\cA\c[\c{\c:/
+ \x01\x01\e;z
+ 0: \x01\x01\x1b;z
+
+/^[ab\]cde]/
+ athing
+ 0: a
+ bthing
+ 0: b
+ ]thing
+ 0: ]
+ cthing
+ 0: c
+ dthing
+ 0: d
+ ething
+ 0: e
+ *** Failers
+No match
+ fthing
+No match
+ [thing
+No match
+ \\thing
+No match
+
+/^[]cde]/
+ ]thing
+ 0: ]
+ cthing
+ 0: c
+ dthing
+ 0: d
+ ething
+ 0: e
+ *** Failers
+No match
+ athing
+No match
+ fthing
+No match
+
+/^[^ab\]cde]/
+ fthing
+ 0: f
+ [thing
+ 0: [
+ \\thing
+ 0: \
+ *** Failers
+ 0: *
+ athing
+No match
+ bthing
+No match
+ ]thing
+No match
+ cthing
+No match
+ dthing
+No match
+ ething
+No match
+
+/^[^]cde]/
+ athing
+ 0: a
+ fthing
+ 0: f
+ *** Failers
+ 0: *
+ ]thing
+No match
+ cthing
+No match
+ dthing
+No match
+ ething
+No match
+
+/^\�/
+ �
+ 0: \x81
+
+/^�/
+ �
+ 0: \xff
+
+/^[0-9]+$/
+ 0
+ 0: 0
+ 1
+ 0: 1
+ 2
+ 0: 2
+ 3
+ 0: 3
+ 4
+ 0: 4
+ 5
+ 0: 5
+ 6
+ 0: 6
+ 7
+ 0: 7
+ 8
+ 0: 8
+ 9
+ 0: 9
+ 10
+ 0: 10
+ 100
+ 0: 100
+ *** Failers
+No match
+ abc
+No match
+
+/^.*nter/
+ enter
+ 0: enter
+ inter
+ 0: inter
+ uponter
+ 0: uponter
+
+/^xxx[0-9]+$/
+ xxx0
+ 0: xxx0
+ xxx1234
+ 0: xxx1234
+ *** Failers
+No match
+ xxx
+No match
+
+/^.+[0-9][0-9][0-9]$/
+ x123
+ 0: x123
+ xx123
+ 0: xx123
+ 123456
+ 0: 123456
+ *** Failers
+No match
+ 123
+No match
+ x1234
+ 0: x1234
+
+/^.+?[0-9][0-9][0-9]$/
+ x123
+ 0: x123
+ xx123
+ 0: xx123
+ 123456
+ 0: 123456
+ *** Failers
+No match
+ 123
+No match
+ x1234
+ 0: x1234
+
+/^([^!]+)!(.+)=apquxz\.ixr\.zzz\.ac\.uk$/
+ abc!pqr=apquxz.ixr.zzz.ac.uk
+ 0: abc!pqr=apquxz.ixr.zzz.ac.uk
+ 1: abc
+ 2: pqr
+ *** Failers
+No match
+ !pqr=apquxz.ixr.zzz.ac.uk
+No match
+ abc!=apquxz.ixr.zzz.ac.uk
+No match
+ abc!pqr=apquxz:ixr.zzz.ac.uk
+No match
+ abc!pqr=apquxz.ixr.zzz.ac.ukk
+No match
+
+/:/
+ Well, we need a colon: somewhere
+ 0: :
+ *** Fail if we don't
+No match
+
+/([\da-f:]+)$/i
+ 0abc
+ 0: 0abc
+ 1: 0abc
+ abc
+ 0: abc
+ 1: abc
+ fed
+ 0: fed
+ 1: fed
+ E
+ 0: E
+ 1: E
+ ::
+ 0: ::
+ 1: ::
+ 5f03:12C0::932e
+ 0: 5f03:12C0::932e
+ 1: 5f03:12C0::932e
+ fed def
+ 0: def
+ 1: def
+ Any old stuff
+ 0: ff
+ 1: ff
+ *** Failers
+No match
+ 0zzz
+No match
+ gzzz
+No match
+ fed\x20
+No match
+ Any old rubbish
+No match
+
+/^.*\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
+ .1.2.3
+ 0: .1.2.3
+ 1: 1
+ 2: 2
+ 3: 3
+ A.12.123.0
+ 0: A.12.123.0
+ 1: 12
+ 2: 123
+ 3: 0
+ *** Failers
+No match
+ .1.2.3333
+No match
+ 1.2.3
+No match
+ 1234.2.3
+No match
+
+/^(\d+)\s+IN\s+SOA\s+(\S+)\s+(\S+)\s*\(\s*$/
+ 1 IN SOA non-sp1 non-sp2(
+ 0: 1 IN SOA non-sp1 non-sp2(
+ 1: 1
+ 2: non-sp1
+ 3: non-sp2
+ 1 IN SOA non-sp1 non-sp2 (
+ 0: 1 IN SOA non-sp1 non-sp2 (
+ 1: 1
+ 2: non-sp1
+ 3: non-sp2
+ *** Failers
+No match
+ 1IN SOA non-sp1 non-sp2(
+No match
+
+/^[a-zA-Z\d][a-zA-Z\d\-]*(\.[a-zA-Z\d][a-zA-z\d\-]*)*\.$/
+ a.
+ 0: a.
+ Z.
+ 0: Z.
+ 2.
+ 0: 2.
+ ab-c.pq-r.
+ 0: ab-c.pq-r.
+ 1: .pq-r
+ sxk.zzz.ac.uk.
+ 0: sxk.zzz.ac.uk.
+ 1: .uk
+ x-.y-.
+ 0: x-.y-.
+ 1: .y-
+ *** Failers
+No match
+ -abc.peq.
+No match
+
+/^\*\.[a-z]([a-z\-\d]*[a-z\d]+)?(\.[a-z]([a-z\-\d]*[a-z\d]+)?)*$/
+ *.a
+ 0: *.a
+ *.b0-a
+ 0: *.b0-a
+ 1: 0-a
+ *.c3-b.c
+ 0: *.c3-b.c
+ 1: 3-b
+ 2: .c
+ *.c-a.b-c
+ 0: *.c-a.b-c
+ 1: -a
+ 2: .b-c
+ 3: -c
+ *** Failers
+No match
+ *.0
+No match
+ *.a-
+No match
+ *.a-b.c-
+No match
+ *.c-a.0-c
+No match
+
+/^(?=ab(de))(abd)(e)/
+ abde
+ 0: abde
+ 1: de
+ 2: abd
+ 3: e
+
+/^(?!(ab)de|x)(abd)(f)/
+ abdf
+ 0: abdf
+ 1: <unset>
+ 2: abd
+ 3: f
+
+/^(?=(ab(cd)))(ab)/
+ abcd
+ 0: ab
+ 1: abcd
+ 2: cd
+ 3: ab
+
+/^[\da-f](\.[\da-f])*$/i
+ a.b.c.d
+ 0: a.b.c.d
+ 1: .d
+ A.B.C.D
+ 0: A.B.C.D
+ 1: .D
+ a.b.c.1.2.3.C
+ 0: a.b.c.1.2.3.C
+ 1: .C
+
+/^\".*\"\s*(;.*)?$/
+ \"1234\"
+ 0: "1234"
+ \"abcd\" ;
+ 0: "abcd" ;
+ 1: ;
+ \"\" ; rhubarb
+ 0: "" ; rhubarb
+ 1: ; rhubarb
+ *** Failers
+No match
+ \"1234\" : things
+No match
+
+/^$/
+ \
+ 0:
+ *** Failers
+No match
+
+/ ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/x
+ ab c
+ 0: ab c
+ *** Failers
+No match
+ abc
+No match
+ ab cde
+No match
+
+/(?x) ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/
+ ab c
+ 0: ab c
+ *** Failers
+No match
+ abc
+No match
+ ab cde
+No match
+
+/^ a\ b[c ]d $/x
+ a bcd
+ 0: a bcd
+ a b d
+ 0: a b d
+ *** Failers
+No match
+ abcd
+No match
+ ab d
+No match
+
+/^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$/
+ abcdefhijklm
+ 0: abcdefhijklm
+ 1: abc
+ 2: bc
+ 3: c
+ 4: def
+ 5: ef
+ 6: f
+ 7: hij
+ 8: ij
+ 9: j
+10: klm
+11: lm
+12: m
+
+/^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$/
+ abcdefhijklm
+ 0: abcdefhijklm
+ 1: bc
+ 2: c
+ 3: ef
+ 4: f
+ 5: ij
+ 6: j
+ 7: lm
+ 8: m
+
+/^[\w][\W][\s][\S][\d][\D][\b][\n][\c]][\022]/
+ a+ Z0+\x08\n\x1d\x12
+ 0: a+ Z0+\x08\x0a\x1d\x12
+
+/^[.^$|()*+?{,}]+/
+ .^\$(*+)|{?,?}
+ 0: .^$(*+)|{?,?}
+
+/^a*\w/
+ z
+ 0: z
+ az
+ 0: az
+ aaaz
+ 0: aaaz
+ a
+ 0: a
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+ a+
+ 0: a
+ aa+
+ 0: aa
+
+/^a*?\w/
+ z
+ 0: z
+ az
+ 0: a
+ aaaz
+ 0: a
+ a
+ 0: a
+ aa
+ 0: a
+ aaaa
+ 0: a
+ a+
+ 0: a
+ aa+
+ 0: a
+
+/^a+\w/
+ az
+ 0: az
+ aaaz
+ 0: aaaz
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+ aa+
+ 0: aa
+
+/^a+?\w/
+ az
+ 0: az
+ aaaz
+ 0: aa
+ aa
+ 0: aa
+ aaaa
+ 0: aa
+ aa+
+ 0: aa
+
+/^\d{8}\w{2,}/
+ 1234567890
+ 0: 1234567890
+ 12345678ab
+ 0: 12345678ab
+ 12345678__
+ 0: 12345678__
+ *** Failers
+No match
+ 1234567
+No match
+
+/^[aeiou\d]{4,5}$/
+ uoie
+ 0: uoie
+ 1234
+ 0: 1234
+ 12345
+ 0: 12345
+ aaaaa
+ 0: aaaaa
+ *** Failers
+No match
+ 123456
+No match
+
+/^[aeiou\d]{4,5}?/
+ uoie
+ 0: uoie
+ 1234
+ 0: 1234
+ 12345
+ 0: 1234
+ aaaaa
+ 0: aaaa
+ 123456
+ 0: 1234
+
+/\A(abc|def)=(\1){2,3}\Z/
+ abc=abcabc
+ 0: abc=abcabc
+ 1: abc
+ 2: abc
+ def=defdefdef
+ 0: def=defdefdef
+ 1: def
+ 2: def
+ *** Failers
+No match
+ abc=defdef
+No match
+
+/^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\11*(\3\4)\1(?#)2$/
+ abcdefghijkcda2
+ 0: abcdefghijkcda2
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+12: cd
+ abcdefghijkkkkcda2
+ 0: abcdefghijkkkkcda2
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+12: cd
+
+/(cat(a(ract|tonic)|erpillar)) \1()2(3)/
+ cataract cataract23
+ 0: cataract cataract23
+ 1: cataract
+ 2: aract
+ 3: ract
+ 4:
+ 5: 3
+ catatonic catatonic23
+ 0: catatonic catatonic23
+ 1: catatonic
+ 2: atonic
+ 3: tonic
+ 4:
+ 5: 3
+ caterpillar caterpillar23
+ 0: caterpillar caterpillar23
+ 1: caterpillar
+ 2: erpillar
+ 3: <unset>
+ 4:
+ 5: 3
+
+
+/^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]/
+ From abcd Mon Sep 01 12:33:02 1997
+ 0: From abcd Mon Sep 01 12:33
+ 1: abcd
+
+/^From\s+\S+\s+([a-zA-Z]{3}\s+){2}\d{1,2}\s+\d\d:\d\d/
+ From abcd Mon Sep 01 12:33:02 1997
+ 0: From abcd Mon Sep 01 12:33
+ 1: Sep
+ From abcd Mon Sep 1 12:33:02 1997
+ 0: From abcd Mon Sep 1 12:33
+ 1: Sep
+ *** Failers
+No match
+ From abcd Sep 01 12:33:02 1997
+No match
+
+/^12.34/s
+ 12\n34
+ 0: 12\x0a34
+ 12\r34
+ 0: 12\x0d34
+
+/\w+(?=\t)/
+ the quick brown\t fox
+ 0: brown
+
+/foo(?!bar)(.*)/
+ foobar is foolish see?
+ 0: foolish see?
+ 1: lish see?
+
+/(?:(?!foo)...|^.{0,2})bar(.*)/
+ foobar crowbar etc
+ 0: rowbar etc
+ 1: etc
+ barrel
+ 0: barrel
+ 1: rel
+ 2barrel
+ 0: 2barrel
+ 1: rel
+ A barrel
+ 0: A barrel
+ 1: rel
+
+/^(\D*)(?=\d)(?!123)/
+ abc456
+ 0: abc
+ 1: abc
+ *** Failers
+No match
+ abc123
+No match
+
+/^1234(?# test newlines
+ inside)/
+ 1234
+ 0: 1234
+
+/^1234 #comment in extended re
+ /x
+ 1234
+ 0: 1234
+
+/#rhubarb
+ abcd/x
+ abcd
+ 0: abcd
+
+/^abcd#rhubarb/x
+ abcd
+ 0: abcd
+
+/^(a)\1{2,3}(.)/
+ aaab
+ 0: aaab
+ 1: a
+ 2: b
+ aaaab
+ 0: aaaab
+ 1: a
+ 2: b
+ aaaaab
+ 0: aaaaa
+ 1: a
+ 2: a
+ aaaaaab
+ 0: aaaaa
+ 1: a
+ 2: a
+
+/(?!^)abc/
+ the abc
+ 0: abc
+ *** Failers
+No match
+ abc
+No match
+
+/(?=^)abc/
+ abc
+ 0: abc
+ *** Failers
+No match
+ the abc
+No match
+
+/^[ab]{1,3}(ab*|b)/
+ aabbbbb
+ 0: aabb
+ 1: b
+
+/^[ab]{1,3}?(ab*|b)/
+ aabbbbb
+ 0: aabbbbb
+ 1: abbbbb
+
+/^[ab]{1,3}?(ab*?|b)/
+ aabbbbb
+ 0: aa
+ 1: a
+
+/^[ab]{1,3}(ab*?|b)/
+ aabbbbb
+ 0: aabb
+ 1: b
+
+/ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # optional leading comment
+(?: (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # initial word
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) )* # further okay, if led by a period
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] | # atom and space parts, or...
+\(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) | # comments, or...
+
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+# quoted strings
+)*
+< (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # leading <
+(?: @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* , (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* )? # optional route
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # initial word
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) )* # further okay, if led by a period
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* > # trailing >
+# name and address
+) (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # optional trailing comment
+/x
+ Alan Other <user\@dom.ain>
+ 0: Alan Other <[email protected]>
+ <user\@dom.ain>
+ user\@dom.ain
+ \"A. Other\" <user.1234\@dom.ain> (a comment)
+ 0: "A. Other" <[email protected]> (a comment)
+ A. Other <user.1234\@dom.ain> (a comment)
+ 0: Other <[email protected]> (a comment)
+ \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay
+ 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay
+ A missing angle <user\@some.where
+ *** Failers
+No match
+ The quick brown fox
+No match
+
+/[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+# leading word
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # "normal" atoms and or spaces
+(?:
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+|
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+) # "special" comment or quoted string
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # more "normal"
+)*
+<
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)
+/x
+ Alan Other <user\@dom.ain>
+ 0: Alan Other <[email protected]>
+ <user\@dom.ain>
+ user\@dom.ain
+ \"A. Other\" <user.1234\@dom.ain> (a comment)
+ 0: "A. Other" <[email protected]>
+ A. Other <user.1234\@dom.ain> (a comment)
+ 0: Other <[email protected]>
+ \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay
+ 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay
+ A missing angle <user\@some.where
+ *** Failers
+No match
+ The quick brown fox
+No match
+
+/abc\0def\00pqr\000xyz\0000AB/
+ abc\0def\00pqr\000xyz\0000AB
+ 0: abc\x00def\x00pqr\x00xyz\x000AB
+ abc456 abc\0def\00pqr\000xyz\0000ABCDE
+ 0: abc\x00def\x00pqr\x00xyz\x000AB
+
+/abc\x0def\x00pqr\x000xyz\x0000AB/
+ abc\x0def\x00pqr\x000xyz\x0000AB
+ 0: abc\x0def\x00pqr\x000xyz\x0000AB
+ abc456 abc\x0def\x00pqr\x000xyz\x0000ABCDE
+ 0: abc\x0def\x00pqr\x000xyz\x0000AB
+
+/^[\000-\037]/
+ \0A
+ 0: \x00
+ \01B
+ 0: \x01
+ \037C
+ 0: \x1f
+
+/\0*/
+ \0\0\0\0
+ 0: \x00\x00\x00\x00
+
+/A\x0{2,3}Z/
+ The A\x0\x0Z
+ 0: A\x00\x00Z
+ An A\0\x0\0Z
+ 0: A\x00\x00\x00Z
+ *** Failers
+No match
+ A\0Z
+No match
+ A\0\x0\0\x0Z
+No match
+
+/^(cow|)\1(bell)/
+ cowcowbell
+ 0: cowcowbell
+ 1: cow
+ 2: bell
+ bell
+ 0: bell
+ 1:
+ 2: bell
+ *** Failers
+No match
+ cowbell
+No match
+
+/^\s/
+ \040abc
+ 0:
+ \x0cabc
+ 0: \x0c
+ \nabc
+ 0: \x0a
+ \rabc
+ 0: \x0d
+ \tabc
+ 0: \x09
+ *** Failers
+No match
+ abc
+No match
+
+/^a b
+ c/x
+ abc
+ 0: abc
+
+/^(a|)\1*b/
+ ab
+ 0: ab
+ 1: a
+ aaaab
+ 0: aaaab
+ 1: a
+ b
+ 0: b
+ 1:
+ *** Failers
+No match
+ acb
+No match
+
+/^(a|)\1+b/
+ aab
+ 0: aab
+ 1: a
+ aaaab
+ 0: aaaab
+ 1: a
+ b
+ 0: b
+ 1:
+ *** Failers
+No match
+ ab
+No match
+
+/^(a|)\1?b/
+ ab
+ 0: ab
+ 1: a
+ aab
+ 0: aab
+ 1: a
+ b
+ 0: b
+ 1:
+ *** Failers
+No match
+ acb
+No match
+
+/^(a|)\1{2}b/
+ aaab
+ 0: aaab
+ 1: a
+ b
+ 0: b
+ 1:
+ *** Failers
+No match
+ ab
+No match
+ aab
+No match
+ aaaab
+No match
+
+/^(a|)\1{2,3}b/
+ aaab
+ 0: aaab
+ 1: a
+ aaaab
+ 0: aaaab
+ 1: a
+ b
+ 0: b
+ 1:
+ *** Failers
+No match
+ ab
+No match
+ aab
+No match
+ aaaaab
+No match
+
+/ab{1,3}bc/
+ abbbbc
+ 0: abbbbc
+ abbbc
+ 0: abbbc
+ abbc
+ 0: abbc
+ *** Failers
+No match
+ abc
+No match
+ abbbbbc
+No match
+
+/([^.]*)\.([^:]*):[T ]+(.*)/
+ track1.title:TBlah blah blah
+ 0: track1.title:TBlah blah blah
+ 1: track1
+ 2: title
+ 3: Blah blah blah
+
+/([^.]*)\.([^:]*):[T ]+(.*)/i
+ track1.title:TBlah blah blah
+ 0: track1.title:TBlah blah blah
+ 1: track1
+ 2: title
+ 3: Blah blah blah
+
+/([^.]*)\.([^:]*):[t ]+(.*)/i
+ track1.title:TBlah blah blah
+ 0: track1.title:TBlah blah blah
+ 1: track1
+ 2: title
+ 3: Blah blah blah
+
+/^[W-c]+$/
+ WXY_^abc
+ 0: WXY_^abc
+ *** Failers
+No match
+ wxy
+No match
+
+/^[W-c]+$/i
+ WXY_^abc
+ 0: WXY_^abc
+ wxy_^ABC
+ 0: wxy_^ABC
+
+/^[\x3f-\x5F]+$/i
+ WXY_^abc
+ 0: WXY_^abc
+ wxy_^ABC
+ 0: wxy_^ABC
+
+/^abc$/m
+ abc
+ 0: abc
+ qqq\nabc
+ 0: abc
+ abc\nzzz
+ 0: abc
+ qqq\nabc\nzzz
+ 0: abc
+
+/^abc$/
+ abc
+ 0: abc
+ *** Failers
+No match
+ qqq\nabc
+No match
+ abc\nzzz
+No match
+ qqq\nabc\nzzz
+No match
+
+/\Aabc\Z/m
+ abc
+ 0: abc
+ abc\n
+ 0: abc
+ *** Failers
+No match
+ qqq\nabc
+No match
+ abc\nzzz
+No match
+ qqq\nabc\nzzz
+No match
+
+/\A(.)*\Z/s
+ abc\ndef
+ 0: abc\x0adef
+ 1: f
+
+/\A(.)*\Z/m
+ *** Failers
+ 0: *** Failers
+ 1: s
+ abc\ndef
+No match
+
+/(?:b)|(?::+)/
+ b::c
+ 0: b
+ c::b
+ 0: ::
+
+/[-az]+/
+ az-
+ 0: az-
+ *** Failers
+ 0: a
+ b
+No match
+
+/[az-]+/
+ za-
+ 0: za-
+ *** Failers
+ 0: a
+ b
+No match
+
+/[a\-z]+/
+ a-z
+ 0: a-z
+ *** Failers
+ 0: a
+ b
+No match
+
+/[a-z]+/
+ abcdxyz
+ 0: abcdxyz
+
+/[\d-]+/
+ 12-34
+ 0: 12-34
+ *** Failers
+No match
+ aaa
+No match
+
+/[\d-z]+/
+ 12-34z
+ 0: 12-34z
+ *** Failers
+No match
+ aaa
+No match
+
+/\x5c/
+ \\
+ 0: \
+
+/\x20Z/
+ the Zoo
+ 0: Z
+ *** Failers
+No match
+ Zulu
+No match
+
+/(abc)\1/i
+ abcabc
+ 0: abcabc
+ 1: abc
+ ABCabc
+ 0: ABCabc
+ 1: ABC
+ abcABC
+ 0: abcABC
+ 1: abc
+
+/ab{3cd/
+ ab{3cd
+ 0: ab{3cd
+
+/ab{3,cd/
+ ab{3,cd
+ 0: ab{3,cd
+
+/ab{3,4a}cd/
+ ab{3,4a}cd
+ 0: ab{3,4a}cd
+
+/{4,5a}bc/
+ {4,5a}bc
+ 0: {4,5a}bc
+
+/abc$/
+ abc
+ 0: abc
+ abc\n
+ 0: abc
+ *** Failers
+No match
+ abc\ndef
+No match
+
+/(abc)\123/
+ abc\x53
+ 0: abcS
+ 1: abc
+
+/(abc)\223/
+ abc\x93
+ 0: abc\x93
+ 1: abc
+
+/(abc)\323/
+ abc\xd3
+ 0: abc\xd3
+ 1: abc
+
+/(abc)\100/
+ abc\x40
+ 0: abc@
+ 1: abc
+ abc\100
+ 0: abc@
+ 1: abc
+
+/(abc)\1000/
+ abc\x400
+ 0: abc@0
+ 1: abc
+ abc\x40\x30
+ 0: abc@0
+ 1: abc
+ abc\1000
+ 0: abc@0
+ 1: abc
+ abc\100\x30
+ 0: abc@0
+ 1: abc
+ abc\100\060
+ 0: abc@0
+ 1: abc
+ abc\100\60
+ 0: abc@0
+ 1: abc
+
+/abc\81/
+ abc\081
+ 0: abc\x0081
+ abc\0\x38\x31
+ 0: abc\x0081
+
+/abc\91/
+ abc\091
+ 0: abc\x0091
+ abc\0\x39\x31
+ 0: abc\x0091
+
+/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\12\123/
+ abcdefghijkllS
+ 0: abcdefghijkllS
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+12: l
+
+/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\12\123/
+ abcdefghijk\12S
+ 0: abcdefghijk\x0aS
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+
+/ab\idef/
+ abidef
+ 0: abidef
+
+/a{0}bc/
+ bc
+ 0: bc
+
+/(a|(bc)){0,0}?xyz/
+ xyz
+ 0: xyz
+
+/abc[\10]de/
+ abc\010de
+ 0: abc\x08de
+
+/abc[\1]de/
+ abc\1de
+ 0: abc\x01de
+
+/(abc)[\1]de/
+ abc\1de
+ 0: abc\x01de
+ 1: abc
+
+/(?s)a.b/
+ a\nb
+ 0: a\x0ab
+
+/^([^a])([^\b])([^c]*)([^d]{3,4})/
+ baNOTccccd
+ 0: baNOTcccc
+ 1: b
+ 2: a
+ 3: NOT
+ 4: cccc
+ baNOTcccd
+ 0: baNOTccc
+ 1: b
+ 2: a
+ 3: NOT
+ 4: ccc
+ baNOTccd
+ 0: baNOTcc
+ 1: b
+ 2: a
+ 3: NO
+ 4: Tcc
+ bacccd
+ 0: baccc
+ 1: b
+ 2: a
+ 3:
+ 4: ccc
+ *** Failers
+ 0: *** Failers
+ 1: *
+ 2: *
+ 3: * Fail
+ 4: ers
+ anything
+No match
+ b\bc
+No match
+ baccd
+No match
+
+/[^a]/
+ Abc
+ 0: A
+
+/[^a]/i
+ Abc
+ 0: b
+
+/[^a]+/
+ AAAaAbc
+ 0: AAA
+
+/[^a]+/i
+ AAAaAbc
+ 0: bc
+
+/[^a]+/
+ bbb\nccc
+ 0: bbb\x0accc
+
+/[^k]$/
+ abc
+ 0: c
+ *** Failers
+ 0: s
+ abk
+No match
+
+/[^k]{2,3}$/
+ abc
+ 0: abc
+ kbc
+ 0: bc
+ kabc
+ 0: abc
+ *** Failers
+ 0: ers
+ abk
+No match
+ akb
+No match
+ akk
+No match
+
+/^\d{8,}\@.+[^k]$/
+ 12345678\@a.b.c.d
+ 123456789\@x.y.z
+ *** Failers
+No match
+ 12345678\@x.y.uk
+No match
+ 1234567\@a.b.c.d
+No match
+
+/(a)\1{8,}/
+ aaaaaaaaa
+ 0: aaaaaaaaa
+ 1: a
+ aaaaaaaaaa
+ 0: aaaaaaaaaa
+ 1: a
+ *** Failers
+No match
+ aaaaaaa
+No match
+
+/[^a]/
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: A
+
+/[^a]/i
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: b
+
+/[^az]/
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: A
+
+/[^az]/i
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: b
+
+/\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103\104\105\106\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377/
+ \000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103\104\105\106\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377
+ 0: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff
+
+/P[^*]TAIRE[^*]{1,6}?LL/
+ xxxxxxxxxxxPSTAIREISLLxxxxxxxxx
+ 0: PSTAIREISLL
+
+/P[^*]TAIRE[^*]{1,}?LL/
+ xxxxxxxxxxxPSTAIREISLLxxxxxxxxx
+ 0: PSTAIREISLL
+
+/(\.\d\d[1-9]?)\d+/
+ 1.230003938
+ 0: .230003938
+ 1: .23
+ 1.875000282
+ 0: .875000282
+ 1: .875
+ 1.235
+ 0: .235
+ 1: .23
+
+/(\.\d\d((?=0)|\d(?=\d)))/
+ 1.230003938
+ 0: .23
+ 1: .23
+ 2:
+ 1.875000282
+ 0: .875
+ 1: .875
+ 2: 5
+ *** Failers
+No match
+ 1.235
+No match
+
+/a(?)b/
+ ab
+ 0: ab
+
+/\b(foo)\s+(\w+)/i
+ Food is on the foo table
+ 0: foo table
+ 1: foo
+ 2: table
+
+/foo(.*)bar/
+ The food is under the bar in the barn.
+ 0: food is under the bar in the bar
+ 1: d is under the bar in the
+
+/foo(.*?)bar/
+ The food is under the bar in the barn.
+ 0: food is under the bar
+ 1: d is under the
+
+/(.*)(\d*)/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 53147
+ 2:
+
+/(.*)(\d+)/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: 7
+
+/(.*?)(\d*)/
+ I have 2 numbers: 53147
+ 0:
+ 1:
+ 2:
+
+/(.*?)(\d+)/
+ I have 2 numbers: 53147
+ 0: I have 2
+ 1: I have
+ 2: 2
+
+/(.*)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: 7
+
+/(.*?)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers:
+ 2: 53147
+
+/(.*)\b(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers:
+ 2: 53147
+
+/(.*\D)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers:
+ 2: 53147
+
+/^\D*(?!123)/
+ ABC123
+ 0: AB
+
+/^(\D*)(?=\d)(?!123)/
+ ABC445
+ 0: ABC
+ 1: ABC
+ *** Failers
+No match
+ ABC123
+No match
+
+/^[W-]46]/
+ W46]789
+ 0: W46]
+ -46]789
+ 0: -46]
+ *** Failers
+No match
+ Wall
+No match
+ Zebra
+No match
+ 42
+No match
+ [abcd]
+No match
+ ]abcd[
+No match
+
+/^[W-\]46]/
+ W46]789
+ 0: W
+ Wall
+ 0: W
+ Zebra
+ 0: Z
+ Xylophone
+ 0: X
+ 42
+ 0: 4
+ [abcd]
+ 0: [
+ ]abcd[
+ 0: ]
+ \\backslash
+ 0: \
+ *** Failers
+No match
+ -46]789
+No match
+ well
+No match
+
+/\d\d\/\d\d\/\d\d\d\d/
+ 01/01/2000
+ 0: 01/01/2000
+
+/word (?:[a-zA-Z0-9]+ ){0,10}otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ word cat dog elephant mussel cow horse canary baboon snake shark
+No match
+
+/word (?:[a-zA-Z0-9]+ ){0,300}otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope
+No match
+
+/^(a){0,0}/
+ bcd
+ 0:
+ abc
+ 0:
+ aab
+ 0:
+
+/^(a){0,1}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: a
+ 1: a
+
+/^(a){0,2}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+
+/^(a){0,3}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+
+/^(a){0,}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+ aaaaaaaa
+ 0: aaaaaaaa
+ 1: a
+
+/^(a){1,1}/
+ bcd
+No match
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: a
+ 1: a
+
+/^(a){1,2}/
+ bcd
+No match
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+
+/^(a){1,3}/
+ bcd
+No match
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+
+/^(a){1,}/
+ bcd
+No match
+ abc
+ 0: a
+ 1: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: a
+ aaaaaaaa
+ 0: aaaaaaaa
+ 1: a
+
+/.*\.gif/
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.{0,}\.gif/
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.*\.gif/m
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.*\.gif/s
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif
+
+/.*\.gif/ms
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif
+
+/.*$/
+ borfle\nbib.gif\nno
+ 0: no
+
+/.*$/m
+ borfle\nbib.gif\nno
+ 0: borfle
+
+/.*$/s
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif\x0ano
+
+/.*$/ms
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif\x0ano
+
+/.*$/
+ borfle\nbib.gif\nno\n
+ 0: no
+
+/.*$/m
+ borfle\nbib.gif\nno\n
+ 0: borfle
+
+/.*$/s
+ borfle\nbib.gif\nno\n
+ 0: borfle\x0abib.gif\x0ano\x0a
+
+/.*$/ms
+ borfle\nbib.gif\nno\n
+ 0: borfle\x0abib.gif\x0ano\x0a
+
+/(.*X|^B)/
+ abcde\n1234Xyz
+ 0: 1234X
+ 1: 1234X
+ BarFoo
+ 0: B
+ 1: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/(.*X|^B)/m
+ abcde\n1234Xyz
+ 0: 1234X
+ 1: 1234X
+ BarFoo
+ 0: B
+ 1: B
+ abcde\nBar
+ 0: B
+ 1: B
+
+/(.*X|^B)/s
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ 1: abcde\x0a1234X
+ BarFoo
+ 0: B
+ 1: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/(.*X|^B)/ms
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ 1: abcde\x0a1234X
+ BarFoo
+ 0: B
+ 1: B
+ abcde\nBar
+ 0: B
+ 1: B
+
+/(?s)(.*X|^B)/
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ 1: abcde\x0a1234X
+ BarFoo
+ 0: B
+ 1: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/(?s:.*X|^B)/
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ BarFoo
+ 0: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/^.*B/
+ **** Failers
+No match
+ abc\nB
+No match
+
+/(?s)^.*B/
+ abc\nB
+ 0: abc\x0aB
+
+/(?m)^.*B/
+ abc\nB
+ 0: B
+
+/(?ms)^.*B/
+ abc\nB
+ 0: abc\x0aB
+
+/(?ms)^B/
+ abc\nB
+ 0: B
+
+/(?s)B$/
+ B\n
+ 0: B
+
+/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/
+ 123456654321
+ 0: 123456654321
+
+/^\d\d\d\d\d\d\d\d\d\d\d\d/
+ 123456654321
+ 0: 123456654321
+
+/^[\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d]/
+ 123456654321
+ 0: 123456654321
+
+/^[abc]{12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+
+/^[a-c]{12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+
+/^(a|b|c){12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+ 1: c
+
+/^[abcdefghijklmnopqrstuvwxy0123456789]/
+ n
+ 0: n
+ *** Failers
+No match
+ z
+No match
+
+/abcde{0,0}/
+ abcd
+ 0: abcd
+ *** Failers
+No match
+ abce
+No match
+
+/ab[cd]{0,0}e/
+ abe
+ 0: abe
+ *** Failers
+No match
+ abcde
+No match
+
+/ab(c){0,0}d/
+ abd
+ 0: abd
+ *** Failers
+No match
+ abcd
+No match
+
+/a(b*)/
+ a
+ 0: a
+ 1:
+ ab
+ 0: ab
+ 1: b
+ abbbb
+ 0: abbbb
+ 1: bbbb
+ *** Failers
+ 0: a
+ 1:
+ bbbbb
+No match
+
+/ab\d{0}e/
+ abe
+ 0: abe
+ *** Failers
+No match
+ ab1e
+No match
+
+/"([^\\"]+|\\.)*"/
+ the \"quick\" brown fox
+ 0: "quick"
+ 1: quick
+ \"the \\\"quick\\\" brown fox\"
+ 0: "the \"quick\" brown fox"
+ 1: brown fox
+
+/.*?/g+
+ abc
+ 0:
+ 0+ abc
+ 0: a
+ 0+ bc
+ 0:
+ 0+ bc
+ 0: b
+ 0+ c
+ 0:
+ 0+ c
+ 0: c
+ 0+
+ 0:
+ 0+
+
+/\b/g+
+ abc
+ 0:
+ 0+ abc
+ 0:
+ 0+
+
+/\b/+g
+ abc
+ 0:
+ 0+ abc
+ 0:
+ 0+
+
+//g
+ abc
+ 0:
+ 0:
+ 0:
+ 0:
+
+/<tr([\w\W\s\d][^<>]{0,})><TD([\w\W\s\d][^<>]{0,})>([\d]{0,}\.)(.*)((<BR>([\w\W\s\d][^<>]{0,})|[\s]{0,}))<\/a><\/TD><TD([\w\W\s\d][^<>]{0,})>([\w\W\s\d][^<>]{0,})<\/TD><TD([\w\W\s\d][^<>]{0,})>([\w\W\s\d][^<>]{0,})<\/TD><\/TR>/is
+ <TR BGCOLOR='#DBE9E9'><TD align=left valign=top>43.<a href='joblist.cfm?JobID=94 6735&Keyword='>Word Processor<BR>(N-1286)</a></TD><TD align=left valign=top>Lega lstaff.com</TD><TD align=left valign=top>CA - Statewide</TD></TR>
+ 0: <TR BGCOLOR='#DBE9E9'><TD align=left valign=top>43.<a href='joblist.cfm?JobID=94 6735&Keyword='>Word Processor<BR>(N-1286)</a></TD><TD align=left valign=top>Lega lstaff.com</TD><TD align=left valign=top>CA - Statewide</TD></TR>
+ 1: BGCOLOR='#DBE9E9'
+ 2: align=left valign=top
+ 3: 43.
+ 4: <a href='joblist.cfm?JobID=94 6735&Keyword='>Word Processor<BR>(N-1286)
+ 5:
+ 6:
+ 7: <unset>
+ 8: align=left valign=top
+ 9: Lega lstaff.com
+10: align=left valign=top
+11: CA - Statewide
+
+/a[^a]b/
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/a.b/
+ acb
+ 0: acb
+ *** Failers
+No match
+ a\nb
+No match
+
+/a[^a]b/s
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/a.b/s
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/^(b+?|a){1,2}?c/
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ bbbac
+ 0: bbbac
+ 1: a
+ bbbbac
+ 0: bbbbac
+ 1: a
+ bbbbbac
+ 0: bbbbbac
+ 1: a
+
+/^(b+|a){1,2}?c/
+ bac
+ 0: bac
+ 1: a
+ bbac
+ 0: bbac
+ 1: a
+ bbbac
+ 0: bbbac
+ 1: a
+ bbbbac
+ 0: bbbbac
+ 1: a
+ bbbbbac
+ 0: bbbbbac
+ 1: a
+
+/(?!\A)x/m
+ x\nb\n
+No match
+ a\bx\n
+ 0: x
+
+/\x0{ab}/
+ \0{ab}
+ 0: \x00{ab}
+
+/(A|B)*?CD/
+ CD
+ 0: CD
+
+/(A|B)*CD/
+ CD
+ 0: CD
+
+/(AB)*?\1/
+ ABABAB
+ 0: ABAB
+ 1: AB
+
+/(AB)*\1/
+ ABABAB
+ 0: ABABAB
+ 1: AB
+
+/(?<!bar)foo/
+ foo
+ 0: foo
+ catfood
+ 0: foo
+ arfootle
+ 0: foo
+ rfoosh
+ 0: foo
+ *** Failers
+No match
+ barfoo
+No match
+ towbarfoo
+No match
+
+/\w{3}(?<!bar)foo/
+ catfood
+ 0: catfoo
+ *** Failers
+No match
+ foo
+No match
+ barfoo
+No match
+ towbarfoo
+No match
+
+/(?<=(foo)a)bar/
+ fooabar
+ 0: bar
+ 1: foo
+ *** Failers
+No match
+ bar
+No match
+ foobbar
+No match
+
+/\Aabc\z/m
+ abc
+ 0: abc
+ *** Failers
+No match
+ abc\n
+No match
+ qqq\nabc
+No match
+ abc\nzzz
+No match
+ qqq\nabc\nzzz
+No match
+
+"(?>.*/)foo"
+ /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/it/you/see/
+No match
+
+"(?>.*/)foo"
+ /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo
+ 0: /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo
+
+/(?>(\.\d\d[1-9]?))\d+/
+ 1.230003938
+ 0: .230003938
+ 1: .23
+ 1.875000282
+ 0: .875000282
+ 1: .875
+ *** Failers
+No match
+ 1.235
+No match
+
+/^((?>\w+)|(?>\s+))*$/
+ now is the time for all good men to come to the aid of the party
+ 0: now is the time for all good men to come to the aid of the party
+ 1: party
+ *** Failers
+No match
+ this is not a line with only words and spaces!
+No match
+
+/(\d+)(\w)/
+ 12345a
+ 0: 12345a
+ 1: 12345
+ 2: a
+ 12345+
+ 0: 12345
+ 1: 1234
+ 2: 5
+
+/((?>\d+))(\w)/
+ 12345a
+ 0: 12345a
+ 1: 12345
+ 2: a
+ *** Failers
+No match
+ 12345+
+No match
+
+/(?>a+)b/
+ aaab
+ 0: aaab
+
+/((?>a+)b)/
+ aaab
+ 0: aaab
+ 1: aaab
+
+/(?>(a+))b/
+ aaab
+ 0: aaab
+ 1: aaa
+
+/(?>b)+/
+ aaabbbccc
+ 0: bbb
+
+/(?>a+|b+|c+)*c/
+ aaabbbbccccd
+ 0: aaabbbbc
+
+/((?>[^()]+)|\([^()]*\))+/
+ ((abc(ade)ufh()()x
+ 0: abc(ade)ufh()()x
+ 1: x
+
+/\(((?>[^()]+)|\([^()]+\))+\)/
+ (abc)
+ 0: (abc)
+ 1: abc
+ (abc(def)xyz)
+ 0: (abc(def)xyz)
+ 1: xyz
+ *** Failers
+No match
+ ((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/a(?-i)b/i
+ ab
+ 0: ab
+ Ab
+ 0: Ab
+ *** Failers
+No match
+ aB
+No match
+ AB
+No match
+
+/(a (?x)b c)d e/
+ a bcd e
+ 0: a bcd e
+ 1: a bc
+ *** Failers
+No match
+ a b cd e
+No match
+ abcd e
+No match
+ a bcde
+No match
+
+/(a b(?x)c d (?-x)e f)/
+ a bcde f
+ 0: a bcde f
+ 1: a bcde f
+ *** Failers
+No match
+ abcdef
+No match
+
+/(a(?i)b)c/
+ abc
+ 0: abc
+ 1: ab
+ aBc
+ 0: aBc
+ 1: aB
+ *** Failers
+No match
+ abC
+No match
+ aBC
+No match
+ Abc
+No match
+ ABc
+No match
+ ABC
+No match
+ AbC
+No match
+
+/a(?i:b)c/
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+ *** Failers
+No match
+ ABC
+No match
+ abC
+No match
+ aBC
+No match
+
+/a(?i:b)*c/
+ aBc
+ 0: aBc
+ aBBc
+ 0: aBBc
+ *** Failers
+No match
+ aBC
+No match
+ aBBC
+No match
+
+/a(?=b(?i)c)\w\wd/
+ abcd
+ 0: abcd
+ abCd
+ 0: abCd
+ *** Failers
+No match
+ aBCd
+No match
+ abcD
+No match
+
+/(?s-i:more.*than).*million/i
+ more than million
+ 0: more than million
+ more than MILLION
+ 0: more than MILLION
+ more \n than Million
+ 0: more \x0a than Million
+ *** Failers
+No match
+ MORE THAN MILLION
+No match
+ more \n than \n million
+No match
+
+/(?:(?s-i)more.*than).*million/i
+ more than million
+ 0: more than million
+ more than MILLION
+ 0: more than MILLION
+ more \n than Million
+ 0: more \x0a than Million
+ *** Failers
+No match
+ MORE THAN MILLION
+No match
+ more \n than \n million
+No match
+
+/(?>a(?i)b+)+c/
+ abc
+ 0: abc
+ aBbc
+ 0: aBbc
+ aBBc
+ 0: aBBc
+ *** Failers
+No match
+ Abc
+No match
+ abAb
+No match
+ abbC
+No match
+
+/(?=a(?i)b)\w\wc/
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+ *** Failers
+No match
+ Ab
+No match
+ abC
+No match
+ aBC
+No match
+
+/(?<=a(?i)b)(\w\w)c/
+ abxxc
+ 0: xxc
+ 1: xx
+ aBxxc
+ 0: xxc
+ 1: xx
+ *** Failers
+No match
+ Abxxc
+No match
+ ABxxc
+No match
+ abxxC
+No match
+
+/(?:(a)|b)(?(1)A|B)/
+ aA
+ 0: aA
+ 1: a
+ bB
+ 0: bB
+ *** Failers
+No match
+ aB
+No match
+ bA
+No match
+
+/^(a)?(?(1)a|b)+$/
+ aa
+ 0: aa
+ 1: a
+ b
+ 0: b
+ bb
+ 0: bb
+ *** Failers
+No match
+ ab
+No match
+
+/^(?(?=abc)\w{3}:|\d\d)$/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+ *** Failers
+No match
+ 123
+No match
+ xyz
+No match
+
+/^(?(?!abc)\d\d|\w{3}:)$/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+ *** Failers
+No match
+ 123
+No match
+ xyz
+No match
+
+/(?(?<=foo)bar|cat)/
+ foobar
+ 0: bar
+ cat
+ 0: cat
+ fcat
+ 0: cat
+ focat
+ 0: cat
+ *** Failers
+No match
+ foocat
+No match
+
+/(?(?<!foo)cat|bar)/
+ foobar
+ 0: bar
+ cat
+ 0: cat
+ fcat
+ 0: cat
+ focat
+ 0: cat
+ *** Failers
+No match
+ foocat
+No match
+
+/( \( )? [^()]+ (?(1) \) |) /x
+ abcd
+ 0: abcd
+ (abcd)
+ 0: (abcd)
+ 1: (
+ the quick (abcd) fox
+ 0: the quick
+ (abcd
+ 0: abcd
+
+/( \( )? [^()]+ (?(1) \) ) /x
+ abcd
+ 0: abcd
+ (abcd)
+ 0: (abcd)
+ 1: (
+ the quick (abcd) fox
+ 0: the quick
+ (abcd
+ 0: abcd
+
+/^(?(2)a|(1)(2))+$/
+ 12
+ 0: 12
+ 1: 1
+ 2: 2
+ 12a
+ 0: 12a
+ 1: 1
+ 2: 2
+ 12aa
+ 0: 12aa
+ 1: 1
+ 2: 2
+ *** Failers
+No match
+ 1234
+No match
+
+/((?i)blah)\s+\1/
+ blah blah
+ 0: blah blah
+ 1: blah
+ BLAH BLAH
+ 0: BLAH BLAH
+ 1: BLAH
+ Blah Blah
+ 0: Blah Blah
+ 1: Blah
+ blaH blaH
+ 0: blaH blaH
+ 1: blaH
+ *** Failers
+No match
+ blah BLAH
+No match
+ Blah blah
+No match
+ blaH blah
+No match
+
+/((?i)blah)\s+(?i:\1)/
+ blah blah
+ 0: blah blah
+ 1: blah
+ BLAH BLAH
+ 0: BLAH BLAH
+ 1: BLAH
+ Blah Blah
+ 0: Blah Blah
+ 1: Blah
+ blaH blaH
+ 0: blaH blaH
+ 1: blaH
+ blah BLAH
+ 0: blah BLAH
+ 1: blah
+ Blah blah
+ 0: Blah blah
+ 1: Blah
+ blaH blah
+ 0: blaH blah
+ 1: blaH
+
+/(?>a*)*/
+ a
+ 0: a
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+
+/(abc|)+/
+ abc
+ 0: abc
+ 1:
+ abcabc
+ 0: abcabc
+ 1:
+ abcabcabc
+ 0: abcabcabc
+ 1:
+ xyz
+ 0:
+ 1:
+
+/([a]*)*/
+ a
+ 0: a
+ 1:
+ aaaaa
+ 0: aaaaa
+ 1:
+
+/([ab]*)*/
+ a
+ 0: a
+ 1:
+ b
+ 0: b
+ 1:
+ ababab
+ 0: ababab
+ 1:
+ aaaabcde
+ 0: aaaab
+ 1:
+ bbbb
+ 0: bbbb
+ 1:
+
+/([^a]*)*/
+ b
+ 0: b
+ 1:
+ bbbb
+ 0: bbbb
+ 1:
+ aaa
+ 0:
+ 1:
+
+/([^ab]*)*/
+ cccc
+ 0: cccc
+ 1:
+ abab
+ 0:
+ 1:
+
+/([a]*?)*/
+ a
+ 0:
+ 1:
+ aaaa
+ 0:
+ 1:
+
+/([ab]*?)*/
+ a
+ 0:
+ 1:
+ b
+ 0:
+ 1:
+ abab
+ 0:
+ 1:
+ baba
+ 0:
+ 1:
+
+/([^a]*?)*/
+ b
+ 0:
+ 1:
+ bbbb
+ 0:
+ 1:
+ aaa
+ 0:
+ 1:
+
+/([^ab]*?)*/
+ c
+ 0:
+ 1:
+ cccc
+ 0:
+ 1:
+ baba
+ 0:
+ 1:
+
+/(?>a*)*/
+ a
+ 0: a
+ aaabcde
+ 0: aaa
+
+/((?>a*))*/
+ aaaaa
+ 0: aaaaa
+ 1:
+ aabbaa
+ 0: aa
+ 1:
+
+/((?>a*?))*/
+ aaaaa
+ 0:
+ 1:
+ aabbaa
+ 0:
+ 1:
+
+/(?(?=[^a-z]+[a-z]) \d{2}-[a-z]{3}-\d{2} | \d{2}-\d{2}-\d{2} ) /x
+ 12-sep-98
+ 0: 12-sep-98
+ 12-09-98
+ 0: 12-09-98
+ *** Failers
+No match
+ sep-12-98
+No match
+
+/(?<=(foo))bar\1/
+ foobarfoo
+ 0: barfoo
+ 1: foo
+ foobarfootling
+ 0: barfoo
+ 1: foo
+ *** Failers
+No match
+ foobar
+No match
+ barfoo
+No match
+
+/(?i:saturday|sunday)/
+ saturday
+ 0: saturday
+ sunday
+ 0: sunday
+ Saturday
+ 0: Saturday
+ Sunday
+ 0: Sunday
+ SATURDAY
+ 0: SATURDAY
+ SUNDAY
+ 0: SUNDAY
+ SunDay
+ 0: SunDay
+
+/(a(?i)bc|BB)x/
+ abcx
+ 0: abcx
+ 1: abc
+ aBCx
+ 0: aBCx
+ 1: aBC
+ bbx
+ 0: bbx
+ 1: bb
+ BBx
+ 0: BBx
+ 1: BB
+ *** Failers
+No match
+ abcX
+No match
+ aBCX
+No match
+ bbX
+No match
+ BBX
+No match
+
+/^([ab](?i)[cd]|[ef])/
+ ac
+ 0: ac
+ 1: ac
+ aC
+ 0: aC
+ 1: aC
+ bD
+ 0: bD
+ 1: bD
+ elephant
+ 0: e
+ 1: e
+ Europe
+ 0: E
+ 1: E
+ frog
+ 0: f
+ 1: f
+ France
+ 0: F
+ 1: F
+ *** Failers
+No match
+ Africa
+No match
+
+/^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)/
+ ab
+ 0: ab
+ 1: ab
+ aBd
+ 0: aBd
+ 1: aBd
+ xy
+ 0: xy
+ 1: xy
+ xY
+ 0: xY
+ 1: xY
+ zebra
+ 0: z
+ 1: z
+ Zambesi
+ 0: Z
+ 1: Z
+ *** Failers
+No match
+ aCD
+No match
+ XY
+No match
+
+/(?<=foo\n)^bar/m
+ foo\nbar
+ 0: bar
+ *** Failers
+No match
+ bar
+No match
+ baz\nbar
+No match
+
+/(?<=(?<!foo)bar)baz/
+ barbaz
+ 0: baz
+ barbarbaz
+ 0: baz
+ koobarbaz
+ 0: baz
+ *** Failers
+No match
+ baz
+No match
+ foobarbaz
+No match
+
+/The case of aaaaaa is missed out below because I think Perl 5.005_02 gets/
+/it wrong; it sets $1 to aaa rather than aa. Compare the following test,/
+No match
+/where it does set $1 to aa when matching aaaaaa./
+No match
+
+/^(a\1?){4}$/
+ a
+No match
+ aa
+No match
+ aaa
+No match
+ aaaa
+ 0: aaaa
+ 1: a
+ aaaaa
+ 0: aaaaa
+ 1: a
+ aaaaaaa
+ 0: aaaaaaa
+ 1: a
+ aaaaaaaa
+No match
+ aaaaaaaaa
+No match
+ aaaaaaaaaa
+ 0: aaaaaaaaaa
+ 1: aaaa
+ aaaaaaaaaaa
+No match
+ aaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaa
+No match
+
+/^(a\1?)(a\1?)(a\2?)(a\3?)$/
+ a
+No match
+ aa
+No match
+ aaa
+No match
+ aaaa
+ 0: aaaa
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ aaaaa
+ 0: aaaaa
+ 1: a
+ 2: aa
+ 3: a
+ 4: a
+ aaaaaa
+ 0: aaaaaa
+ 1: a
+ 2: aa
+ 3: a
+ 4: aa
+ aaaaaaa
+ 0: aaaaaaa
+ 1: a
+ 2: aa
+ 3: aaa
+ 4: a
+ aaaaaaaa
+No match
+ aaaaaaaaa
+No match
+ aaaaaaaaaa
+ 0: aaaaaaaaaa
+ 1: a
+ 2: aa
+ 3: aaa
+ 4: aaaa
+ aaaaaaaaaaa
+No match
+ aaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaa
+No match
+
+/The following tests are taken from the Perl 5.005 test suite; some of them/
+/are compatible with 5.004, but I'd rather not have to sort them out./
+No match
+
+/abc/
+ abc
+ 0: abc
+ xabcy
+ 0: abc
+ ababc
+ 0: abc
+ *** Failers
+No match
+ xbc
+No match
+ axc
+No match
+ abx
+No match
+
+/ab*c/
+ abc
+ 0: abc
+
+/ab*bc/
+ abc
+ 0: abc
+ abbc
+ 0: abbc
+ abbbbc
+ 0: abbbbc
+
+/.{1}/
+ abbbbc
+ 0: a
+
+/.{3,4}/
+ abbbbc
+ 0: abbb
+
+/ab{0,}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab+bc/
+ abbc
+ 0: abbc
+ *** Failers
+No match
+ abc
+No match
+ abq
+No match
+
+/ab{1,}bc/
+
+/ab+bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{1,}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{1,3}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{3,4}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{4,5}bc/
+ *** Failers
+No match
+ abq
+No match
+ abbbbc
+No match
+
+/ab?bc/
+ abbc
+ 0: abbc
+ abc
+ 0: abc
+
+/ab{0,1}bc/
+ abc
+ 0: abc
+
+/ab?bc/
+
+/ab?c/
+ abc
+ 0: abc
+
+/ab{0,1}c/
+ abc
+ 0: abc
+
+/^abc$/
+ abc
+ 0: abc
+ *** Failers
+No match
+ abbbbc
+No match
+ abcc
+No match
+
+/^abc/
+ abcc
+ 0: abc
+
+/^abc$/
+
+/abc$/
+ aabc
+ 0: abc
+ *** Failers
+No match
+ aabc
+ 0: abc
+ aabcd
+No match
+
+/^/
+ abc
+ 0:
+
+/$/
+ abc
+ 0:
+
+/a.c/
+ abc
+ 0: abc
+ axc
+ 0: axc
+
+/a.*c/
+ axyzc
+ 0: axyzc
+
+/a[bc]d/
+ abd
+ 0: abd
+ *** Failers
+No match
+ axyzd
+No match
+ abc
+No match
+
+/a[b-d]e/
+ ace
+ 0: ace
+
+/a[b-d]/
+ aac
+ 0: ac
+
+/a[-b]/
+ a-
+ 0: a-
+
+/a[b-]/
+ a-
+ 0: a-
+
+/a]/
+ a]
+ 0: a]
+
+/a[]]b/
+ a]b
+ 0: a]b
+
+/a[^bc]d/
+ aed
+ 0: aed
+ *** Failers
+No match
+ abd
+No match
+ abd
+No match
+
+/a[^-b]c/
+ adc
+ 0: adc
+
+/a[^]b]c/
+ adc
+ 0: adc
+ *** Failers
+No match
+ a-c
+ 0: a-c
+ a]c
+No match
+
+/\ba\b/
+ a-
+ 0: a
+ -a
+ 0: a
+ -a-
+ 0: a
+
+/\by\b/
+ *** Failers
+No match
+ xy
+No match
+ yz
+No match
+ xyz
+No match
+
+/\Ba\B/
+ *** Failers
+ 0: a
+ a-
+No match
+ -a
+No match
+ -a-
+No match
+
+/\By\b/
+ xy
+ 0: y
+
+/\by\B/
+ yz
+ 0: y
+
+/\By\B/
+ xyz
+ 0: y
+
+/\w/
+ a
+ 0: a
+
+/\W/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ a
+No match
+
+/a\sb/
+ a b
+ 0: a b
+
+/a\Sb/
+ a-b
+ 0: a-b
+ *** Failers
+No match
+ a-b
+ 0: a-b
+ a b
+No match
+
+/\d/
+ 1
+ 0: 1
+
+/\D/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ 1
+No match
+
+/[\w]/
+ a
+ 0: a
+
+/[\W]/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ a
+No match
+
+/a[\s]b/
+ a b
+ 0: a b
+
+/a[\S]b/
+ a-b
+ 0: a-b
+ *** Failers
+No match
+ a-b
+ 0: a-b
+ a b
+No match
+
+/[\d]/
+ 1
+ 0: 1
+
+/[\D]/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ 1
+No match
+
+/ab|cd/
+ abc
+ 0: ab
+ abcd
+ 0: ab
+
+/()ef/
+ def
+ 0: ef
+ 1:
+
+/$b/
+
+/a\(b/
+ a(b
+ 0: a(b
+
+/a\(*b/
+ ab
+ 0: ab
+ a((b
+ 0: a((b
+
+/a\\b/
+ a\b
+No match
+
+/((a))/
+ abc
+ 0: a
+ 1: a
+ 2: a
+
+/(a)b(c)/
+ abc
+ 0: abc
+ 1: a
+ 2: c
+
+/a+b+c/
+ aabbabc
+ 0: abc
+
+/a{1,}b{1,}c/
+ aabbabc
+ 0: abc
+
+/a.+?c/
+ abcabc
+ 0: abc
+
+/(a+|b)*/
+ ab
+ 0: ab
+ 1: b
+
+/(a+|b){0,}/
+ ab
+ 0: ab
+ 1: b
+
+/(a+|b)+/
+ ab
+ 0: ab
+ 1: b
+
+/(a+|b){1,}/
+ ab
+ 0: ab
+ 1: b
+
+/(a+|b)?/
+ ab
+ 0: a
+ 1: a
+
+/(a+|b){0,1}/
+ ab
+ 0: a
+ 1: a
+
+/[^ab]*/
+ cde
+ 0: cde
+
+/abc/
+ *** Failers
+No match
+ b
+No match
+
+
+/a*/
+
+
+/([abc])*d/
+ abbbcd
+ 0: abbbcd
+ 1: c
+
+/([abc])*bcd/
+ abcd
+ 0: abcd
+ 1: a
+
+/a|b|c|d|e/
+ e
+ 0: e
+
+/(a|b|c|d|e)f/
+ ef
+ 0: ef
+ 1: e
+
+/abcd*efg/
+ abcdefg
+ 0: abcdefg
+
+/ab*/
+ xabyabbbz
+ 0: ab
+ xayabbbz
+ 0: a
+
+/(ab|cd)e/
+ abcde
+ 0: cde
+ 1: cd
+
+/[abhgefdc]ij/
+ hij
+ 0: hij
+
+/^(ab|cd)e/
+
+/(abc|)ef/
+ abcdef
+ 0: ef
+ 1:
+
+/(a|b)c*d/
+ abcd
+ 0: bcd
+ 1: b
+
+/(ab|ab*)bc/
+ abc
+ 0: abc
+ 1: a
+
+/a([bc]*)c*/
+ abc
+ 0: abc
+ 1: bc
+
+/a([bc]*)(c*d)/
+ abcd
+ 0: abcd
+ 1: bc
+ 2: d
+
+/a([bc]+)(c*d)/
+ abcd
+ 0: abcd
+ 1: bc
+ 2: d
+
+/a([bc]*)(c+d)/
+ abcd
+ 0: abcd
+ 1: b
+ 2: cd
+
+/a[bcd]*dcdcde/
+ adcdcde
+ 0: adcdcde
+
+/a[bcd]+dcdcde/
+ *** Failers
+No match
+ abcde
+No match
+ adcdcde
+No match
+
+/(ab|a)b*c/
+ abc
+ 0: abc
+ 1: ab
+
+/((a)(b)c)(d)/
+ abcd
+ 0: abcd
+ 1: abc
+ 2: a
+ 3: b
+ 4: d
+
+/[a-zA-Z_][a-zA-Z0-9_]*/
+ alpha
+ 0: alpha
+
+/^a(bc+|b[eh])g|.h$/
+ abh
+ 0: bh
+
+/(bc+d$|ef*g.|h?i(j|k))/
+ effgz
+ 0: effgz
+ 1: effgz
+ ij
+ 0: ij
+ 1: ij
+ 2: j
+ reffgz
+ 0: effgz
+ 1: effgz
+ *** Failers
+No match
+ effg
+No match
+ bcdd
+No match
+
+/((((((((((a))))))))))/
+ a
+ 0: a
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+ 9: a
+10: a
+
+/((((((((((a))))))))))\10/
+ aa
+ 0: aa
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+ 9: a
+10: a
+
+/(((((((((a)))))))))/
+ a
+ 0: a
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+ 9: a
+
+/multiple words of text/
+ *** Failers
+No match
+ aa
+No match
+ uh-uh
+No match
+
+/multiple words/
+ multiple words, yeah
+ 0: multiple words
+
+/(.*)c(.*)/
+ abcde
+ 0: abcde
+ 1: ab
+ 2: de
+
+/\((.*), (.*)\)/
+ (a, b)
+ 0: (a, b)
+ 1: a
+ 2: b
+
+/[k]/
+
+/abcd/
+ abcd
+ 0: abcd
+
+/a(bc)d/
+ abcd
+ 0: abcd
+ 1: bc
+
+/a[-]?c/
+ ac
+ 0: ac
+
+/(abc)\1/
+ abcabc
+ 0: abcabc
+ 1: abc
+
+/([a-c]*)\1/
+ abcabc
+ 0: abcabc
+ 1: abc
+
+/(a)|\1/
+ a
+ 0: a
+ 1: a
+ *** Failers
+ 0: a
+ 1: a
+ ab
+ 0: a
+ 1: a
+ x
+No match
+
+/(([a-c])b*?\2)*/
+ ababbbcbc
+ 0: ababb
+ 1: bb
+ 2: b
+
+/(([a-c])b*?\2){3}/
+ ababbbcbc
+ 0: ababbbcbc
+ 1: cbc
+ 2: c
+
+/((\3|b)\2(a)x)+/
+ aaaxabaxbaaxbbax
+ 0: bbax
+ 1: bbax
+ 2: b
+ 3: a
+
+/((\3|b)\2(a)){2,}/
+ bbaababbabaaaaabbaaaabba
+ 0: bbaaaabba
+ 1: bba
+ 2: b
+ 3: a
+
+/abc/i
+ ABC
+ 0: ABC
+ XABCY
+ 0: ABC
+ ABABC
+ 0: ABC
+ *** Failers
+No match
+ aaxabxbaxbbx
+No match
+ XBC
+No match
+ AXC
+No match
+ ABX
+No match
+
+/ab*c/i
+ ABC
+ 0: ABC
+
+/ab*bc/i
+ ABC
+ 0: ABC
+ ABBC
+ 0: ABBC
+
+/ab*?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{0,}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab+?bc/i
+ ABBC
+ 0: ABBC
+
+/ab+bc/i
+ *** Failers
+No match
+ ABC
+No match
+ ABQ
+No match
+
+/ab{1,}bc/i
+
+/ab+bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{1,}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{1,3}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{3,4}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{4,5}?bc/i
+ *** Failers
+No match
+ ABQ
+No match
+ ABBBBC
+No match
+
+/ab??bc/i
+ ABBC
+ 0: ABBC
+ ABC
+ 0: ABC
+
+/ab{0,1}?bc/i
+ ABC
+ 0: ABC
+
+/ab??bc/i
+
+/ab??c/i
+ ABC
+ 0: ABC
+
+/ab{0,1}?c/i
+ ABC
+ 0: ABC
+
+/^abc$/i
+ ABC
+ 0: ABC
+ *** Failers
+No match
+ ABBBBC
+No match
+ ABCC
+No match
+
+/^abc/i
+ ABCC
+ 0: ABC
+
+/^abc$/i
+
+/abc$/i
+ AABC
+ 0: ABC
+
+/^/i
+ ABC
+ 0:
+
+/$/i
+ ABC
+ 0:
+
+/a.c/i
+ ABC
+ 0: ABC
+ AXC
+ 0: AXC
+
+/a.*?c/i
+ AXYZC
+ 0: AXYZC
+
+/a.*c/i
+ *** Failers
+No match
+ AABC
+ 0: AABC
+ AXYZD
+No match
+
+/a[bc]d/i
+ ABD
+ 0: ABD
+
+/a[b-d]e/i
+ ACE
+ 0: ACE
+ *** Failers
+No match
+ ABC
+No match
+ ABD
+No match
+
+/a[b-d]/i
+ AAC
+ 0: AC
+
+/a[-b]/i
+ A-
+ 0: A-
+
+/a[b-]/i
+ A-
+ 0: A-
+
+/a]/i
+ A]
+ 0: A]
+
+/a[]]b/i
+ A]B
+ 0: A]B
+
+/a[^bc]d/i
+ AED
+ 0: AED
+
+/a[^-b]c/i
+ ADC
+ 0: ADC
+ *** Failers
+No match
+ ABD
+No match
+ A-C
+No match
+
+/a[^]b]c/i
+ ADC
+ 0: ADC
+
+/ab|cd/i
+ ABC
+ 0: AB
+ ABCD
+ 0: AB
+
+/()ef/i
+ DEF
+ 0: EF
+ 1:
+
+/$b/i
+ *** Failers
+No match
+ A]C
+No match
+ B
+No match
+
+/a\(b/i
+ A(B
+ 0: A(B
+
+/a\(*b/i
+ AB
+ 0: AB
+ A((B
+ 0: A((B
+
+/a\\b/i
+ A\B
+No match
+
+/((a))/i
+ ABC
+ 0: A
+ 1: A
+ 2: A
+
+/(a)b(c)/i
+ ABC
+ 0: ABC
+ 1: A
+ 2: C
+
+/a+b+c/i
+ AABBABC
+ 0: ABC
+
+/a{1,}b{1,}c/i
+ AABBABC
+ 0: ABC
+
+/a.+?c/i
+ ABCABC
+ 0: ABC
+
+/a.*?c/i
+ ABCABC
+ 0: ABC
+
+/a.{0,5}?c/i
+ ABCABC
+ 0: ABC
+
+/(a+|b)*/i
+ AB
+ 0: AB
+ 1: B
+
+/(a+|b){0,}/i
+ AB
+ 0: AB
+ 1: B
+
+/(a+|b)+/i
+ AB
+ 0: AB
+ 1: B
+
+/(a+|b){1,}/i
+ AB
+ 0: AB
+ 1: B
+
+/(a+|b)?/i
+ AB
+ 0: A
+ 1: A
+
+/(a+|b){0,1}/i
+ AB
+ 0: A
+ 1: A
+
+/(a+|b){0,1}?/i
+ AB
+ 0:
+
+/[^ab]*/i
+ CDE
+ 0: CDE
+
+/abc/i
+
+/a*/i
+
+
+/([abc])*d/i
+ ABBBCD
+ 0: ABBBCD
+ 1: C
+
+/([abc])*bcd/i
+ ABCD
+ 0: ABCD
+ 1: A
+
+/a|b|c|d|e/i
+ E
+ 0: E
+
+/(a|b|c|d|e)f/i
+ EF
+ 0: EF
+ 1: E
+
+/abcd*efg/i
+ ABCDEFG
+ 0: ABCDEFG
+
+/ab*/i
+ XABYABBBZ
+ 0: AB
+ XAYABBBZ
+ 0: A
+
+/(ab|cd)e/i
+ ABCDE
+ 0: CDE
+ 1: CD
+
+/[abhgefdc]ij/i
+ HIJ
+ 0: HIJ
+
+/^(ab|cd)e/i
+ ABCDE
+No match
+
+/(abc|)ef/i
+ ABCDEF
+ 0: EF
+ 1:
+
+/(a|b)c*d/i
+ ABCD
+ 0: BCD
+ 1: B
+
+/(ab|ab*)bc/i
+ ABC
+ 0: ABC
+ 1: A
+
+/a([bc]*)c*/i
+ ABC
+ 0: ABC
+ 1: BC
+
+/a([bc]*)(c*d)/i
+ ABCD
+ 0: ABCD
+ 1: BC
+ 2: D
+
+/a([bc]+)(c*d)/i
+ ABCD
+ 0: ABCD
+ 1: BC
+ 2: D
+
+/a([bc]*)(c+d)/i
+ ABCD
+ 0: ABCD
+ 1: B
+ 2: CD
+
+/a[bcd]*dcdcde/i
+ ADCDCDE
+ 0: ADCDCDE
+
+/a[bcd]+dcdcde/i
+
+/(ab|a)b*c/i
+ ABC
+ 0: ABC
+ 1: AB
+
+/((a)(b)c)(d)/i
+ ABCD
+ 0: ABCD
+ 1: ABC
+ 2: A
+ 3: B
+ 4: D
+
+/[a-zA-Z_][a-zA-Z0-9_]*/i
+ ALPHA
+ 0: ALPHA
+
+/^a(bc+|b[eh])g|.h$/i
+ ABH
+ 0: BH
+
+/(bc+d$|ef*g.|h?i(j|k))/i
+ EFFGZ
+ 0: EFFGZ
+ 1: EFFGZ
+ IJ
+ 0: IJ
+ 1: IJ
+ 2: J
+ REFFGZ
+ 0: EFFGZ
+ 1: EFFGZ
+ *** Failers
+No match
+ ADCDCDE
+No match
+ EFFG
+No match
+ BCDD
+No match
+
+/((((((((((a))))))))))/i
+ A
+ 0: A
+ 1: A
+ 2: A
+ 3: A
+ 4: A
+ 5: A
+ 6: A
+ 7: A
+ 8: A
+ 9: A
+10: A
+
+/((((((((((a))))))))))\10/i
+ AA
+ 0: AA
+ 1: A
+ 2: A
+ 3: A
+ 4: A
+ 5: A
+ 6: A
+ 7: A
+ 8: A
+ 9: A
+10: A
+
+/(((((((((a)))))))))/i
+ A
+ 0: A
+ 1: A
+ 2: A
+ 3: A
+ 4: A
+ 5: A
+ 6: A
+ 7: A
+ 8: A
+ 9: A
+
+/(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))/i
+ A
+ 0: A
+ 1: A
+
+/(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))/i
+ C
+ 0: C
+ 1: C
+
+/multiple words of text/i
+ *** Failers
+No match
+ AA
+No match
+ UH-UH
+No match
+
+/multiple words/i
+ MULTIPLE WORDS, YEAH
+ 0: MULTIPLE WORDS
+
+/(.*)c(.*)/i
+ ABCDE
+ 0: ABCDE
+ 1: AB
+ 2: DE
+
+/\((.*), (.*)\)/i
+ (A, B)
+ 0: (A, B)
+ 1: A
+ 2: B
+
+/[k]/i
+
+/abcd/i
+ ABCD
+ 0: ABCD
+
+/a(bc)d/i
+ ABCD
+ 0: ABCD
+ 1: BC
+
+/a[-]?c/i
+ AC
+ 0: AC
+
+/(abc)\1/i
+ ABCABC
+ 0: ABCABC
+ 1: ABC
+
+/([a-c]*)\1/i
+ ABCABC
+ 0: ABCABC
+ 1: ABC
+
+/a(?!b)./
+ abad
+ 0: ad
+
+/a(?=d)./
+ abad
+ 0: ad
+
+/a(?=c|d)./
+ abad
+ 0: ad
+
+/a(?:b|c|d)(.)/
+ ace
+ 0: ace
+ 1: e
+
+/a(?:b|c|d)*(.)/
+ ace
+ 0: ace
+ 1: e
+
+/a(?:b|c|d)+?(.)/
+ ace
+ 0: ace
+ 1: e
+ acdbcdbe
+ 0: acd
+ 1: d
+
+/a(?:b|c|d)+(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: e
+
+/a(?:b|c|d){2}(.)/
+ acdbcdbe
+ 0: acdb
+ 1: b
+
+/a(?:b|c|d){4,5}(.)/
+ acdbcdbe
+ 0: acdbcdb
+ 1: b
+
+/a(?:b|c|d){4,5}?(.)/
+ acdbcdbe
+ 0: acdbcd
+ 1: d
+
+/((foo)|(bar))*/
+ foobar
+ 0: foobar
+ 1: bar
+ 2: foo
+ 3: bar
+
+/a(?:b|c|d){6,7}(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: e
+
+/a(?:b|c|d){6,7}?(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: e
+
+/a(?:b|c|d){5,6}(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: e
+
+/a(?:b|c|d){5,6}?(.)/
+ acdbcdbe
+ 0: acdbcdb
+ 1: b
+
+/a(?:b|c|d){5,7}(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: e
+
+/a(?:b|c|d){5,7}?(.)/
+ acdbcdbe
+ 0: acdbcdb
+ 1: b
+
+/a(?:b|(c|e){1,2}?|d)+?(.)/
+ ace
+ 0: ace
+ 1: c
+ 2: e
+
+/^(.+)?B/
+ AB
+ 0: AB
+ 1: A
+
+/^([^a-z])|(\^)$/
+ .
+ 0: .
+ 1: .
+
+/^[<>]&/
+ <&OUT
+ 0: <&
+
+/^(a\1?){4}$/
+ aaaaaaaaaa
+ 0: aaaaaaaaaa
+ 1: aaaa
+ *** Failers
+No match
+ AB
+No match
+ aaaaaaaaa
+No match
+ aaaaaaaaaaa
+No match
+
+/^(a(?(1)\1)){4}$/
+ aaaaaaaaaa
+ 0: aaaaaaaaaa
+ 1: aaaa
+ *** Failers
+No match
+ aaaaaaaaa
+No match
+ aaaaaaaaaaa
+No match
+
+/(?:(f)(o)(o)|(b)(a)(r))*/
+ foobar
+ 0: foobar
+ 1: f
+ 2: o
+ 3: o
+ 4: b
+ 5: a
+ 6: r
+
+/(?<=a)b/
+ ab
+ 0: b
+ *** Failers
+No match
+ cb
+No match
+ b
+No match
+
+/(?<!c)b/
+ ab
+ 0: b
+ b
+ 0: b
+ b
+ 0: b
+
+/(?:..)*a/
+ aba
+ 0: aba
+
+/(?:..)*?a/
+ aba
+ 0: a
+
+/^(?:b|a(?=(.)))*\1/
+ abc
+ 0: ab
+ 1: b
+
+/^(){3,5}/
+ abc
+ 0:
+ 1:
+
+/^(a+)*ax/
+ aax
+ 0: aax
+ 1: a
+
+/^((a|b)+)*ax/
+ aax
+ 0: aax
+ 1: a
+ 2: a
+
+/^((a|bc)+)*ax/
+ aax
+ 0: aax
+ 1: a
+ 2: a
+
+/(a|x)*ab/
+ cab
+ 0: ab
+
+/(a)*ab/
+ cab
+ 0: ab
+
+/(?:(?i)a)b/
+ ab
+ 0: ab
+
+/((?i)a)b/
+ ab
+ 0: ab
+ 1: a
+
+/(?:(?i)a)b/
+ Ab
+ 0: Ab
+
+/((?i)a)b/
+ Ab
+ 0: Ab
+ 1: A
+
+/(?:(?i)a)b/
+ *** Failers
+No match
+ cb
+No match
+ aB
+No match
+
+/((?i)a)b/
+
+/(?i:a)b/
+ ab
+ 0: ab
+
+/((?i:a))b/
+ ab
+ 0: ab
+ 1: a
+
+/(?i:a)b/
+ Ab
+ 0: Ab
+
+/((?i:a))b/
+ Ab
+ 0: Ab
+ 1: A
+
+/(?i:a)b/
+ *** Failers
+No match
+ aB
+No match
+ aB
+No match
+
+/((?i:a))b/
+
+/(?:(?-i)a)b/i
+ ab
+ 0: ab
+
+/((?-i)a)b/i
+ ab
+ 0: ab
+ 1: a
+
+/(?:(?-i)a)b/i
+ aB
+ 0: aB
+
+/((?-i)a)b/i
+ aB
+ 0: aB
+ 1: a
+
+/(?:(?-i)a)b/i
+ *** Failers
+No match
+ aB
+ 0: aB
+ Ab
+No match
+
+/((?-i)a)b/i
+
+/(?:(?-i)a)b/i
+ aB
+ 0: aB
+
+/((?-i)a)b/i
+ aB
+ 0: aB
+ 1: a
+
+/(?:(?-i)a)b/i
+ *** Failers
+No match
+ Ab
+No match
+ AB
+No match
+
+/((?-i)a)b/i
+
+/(?-i:a)b/i
+ ab
+ 0: ab
+
+/((?-i:a))b/i
+ ab
+ 0: ab
+ 1: a
+
+/(?-i:a)b/i
+ aB
+ 0: aB
+
+/((?-i:a))b/i
+ aB
+ 0: aB
+ 1: a
+
+/(?-i:a)b/i
+ *** Failers
+No match
+ AB
+No match
+ Ab
+No match
+
+/((?-i:a))b/i
+
+/(?-i:a)b/i
+ aB
+ 0: aB
+
+/((?-i:a))b/i
+ aB
+ 0: aB
+ 1: a
+
+/(?-i:a)b/i
+ *** Failers
+No match
+ Ab
+No match
+ AB
+No match
+
+/((?-i:a))b/i
+
+/((?-i:a.))b/i
+ *** Failers
+No match
+ AB
+No match
+ a\nB
+No match
+
+/((?s-i:a.))b/i
+ a\nB
+ 0: a\x0aB
+ 1: a\x0a
+
+/(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))/
+ cabbbb
+ 0: cabbbb
+
+/(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))/
+ caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ 0: caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+/(ab)\d\1/i
+ Ab4ab
+ 0: Ab4ab
+ 1: Ab
+ ab4Ab
+ 0: ab4Ab
+ 1: ab
+
+/foo\w*\d{4}baz/
+ foobar1234baz
+ 0: foobar1234baz
+
+/x(~~)*(?:(?:F)?)?/
+ x~~
+ 0: x~~
+ 1: ~~
+
+/^a(?#xxx){3}c/
+ aaac
+ 0: aaac
+
+/^a (?#xxx) (?#yyy) {3}c/x
+ aaac
+ 0: aaac
+
+/(?<![cd])b/
+ *** Failers
+No match
+ B\nB
+No match
+ dbcb
+No match
+
+/(?<![cd])[ab]/
+ dbaacb
+ 0: a
+
+/(?<!(c|d))b/
+
+/(?<!(c|d))[ab]/
+ dbaacb
+ 0: a
+
+/(?<!cd)[ab]/
+ cdaccb
+ 0: b
+
+/^(?:a?b?)*$/
+ \
+ 0:
+ a
+ 0: a
+ ab
+ 0: ab
+ aaa
+ 0: aaa
+ *** Failers
+No match
+ dbcb
+No match
+ a--
+No match
+ aa--
+No match
+
+/((?s)^a(.))((?m)^b$)/
+ a\nb\nc\n
+ 0: a\x0ab
+ 1: a\x0a
+ 2: \x0a
+ 3: b
+
+/((?m)^b$)/
+ a\nb\nc\n
+ 0: b
+ 1: b
+
+/(?m)^b/
+ a\nb\n
+ 0: b
+
+/(?m)^(b)/
+ a\nb\n
+ 0: b
+ 1: b
+
+/((?m)^b)/
+ a\nb\n
+ 0: b
+ 1: b
+
+/\n((?m)^b)/
+ a\nb\n
+ 0: \x0ab
+ 1: b
+
+/((?s).)c(?!.)/
+ a\nb\nc\n
+ 0: \x0ac
+ 1: \x0a
+ a\nb\nc\n
+ 0: \x0ac
+ 1: \x0a
+
+/((?s)b.)c(?!.)/
+ a\nb\nc\n
+ 0: b\x0ac
+ 1: b\x0a
+ a\nb\nc\n
+ 0: b\x0ac
+ 1: b\x0a
+
+/^b/
+
+/()^b/
+ *** Failers
+No match
+ a\nb\nc\n
+No match
+ a\nb\nc\n
+No match
+
+/((?m)^b)/
+ a\nb\nc\n
+ 0: b
+ 1: b
+
+/(x)?(?(1)a|b)/
+ *** Failers
+No match
+ a
+No match
+ a
+No match
+
+/(x)?(?(1)b|a)/
+ a
+ 0: a
+
+/()?(?(1)b|a)/
+ a
+ 0: a
+
+/()(?(1)b|a)/
+
+/()?(?(1)a|b)/
+ a
+ 0: a
+ 1:
+
+/^(\()?blah(?(1)(\)))$/
+ (blah)
+ 0: (blah)
+ 1: (
+ 2: )
+ blah
+ 0: blah
+ *** Failers
+No match
+ a
+No match
+ blah)
+No match
+ (blah
+No match
+
+/^(\(+)?blah(?(1)(\)))$/
+ (blah)
+ 0: (blah)
+ 1: (
+ 2: )
+ blah
+ 0: blah
+ *** Failers
+No match
+ blah)
+No match
+ (blah
+No match
+
+/(?(?!a)a|b)/
+
+/(?(?!a)b|a)/
+ a
+ 0: a
+
+/(?(?=a)b|a)/
+ *** Failers
+No match
+ a
+No match
+ a
+No match
+
+/(?(?=a)a|b)/
+ a
+ 0: a
+
+/(?=(a+?))(\1ab)/
+ aaab
+ 0: aab
+ 1: a
+ 2: aab
+
+/^(?=(a+?))\1ab/
+
+/(\w+:)+/
+ one:
+ 0: one:
+ 1: one:
+
+/$(?<=^(a))/
+ a
+ 0:
+ 1: a
+
+/(?=(a+?))(\1ab)/
+ aaab
+ 0: aab
+ 1: a
+ 2: aab
+
+/^(?=(a+?))\1ab/
+ *** Failers
+No match
+ aaab
+No match
+ aaab
+No match
+
+/([\w:]+::)?(\w+)$/
+ abcd
+ 0: abcd
+ 1: <unset>
+ 2: abcd
+ xy:z:::abcd
+ 0: xy:z:::abcd
+ 1: xy:z:::
+ 2: abcd
+
+/^[^bcd]*(c+)/
+ aexycd
+ 0: aexyc
+ 1: c
+
+/(a*)b+/
+ caab
+ 0: aab
+ 1: aa
+
+/([\w:]+::)?(\w+)$/
+ abcd
+ 0: abcd
+ 1: <unset>
+ 2: abcd
+ xy:z:::abcd
+ 0: xy:z:::abcd
+ 1: xy:z:::
+ 2: abcd
+ *** Failers
+ 0: Failers
+ 1: <unset>
+ 2: Failers
+ abcd:
+No match
+ abcd:
+No match
+
+/^[^bcd]*(c+)/
+ aexycd
+ 0: aexyc
+ 1: c
+
+/(>a+)ab/
+
+/(?>a+)b/
+ aaab
+ 0: aaab
+
+/([[:]+)/
+ a:[b]:
+ 0: :[
+ 1: :[
+
+/([[=]+)/
+ a=[b]=
+ 0: =[
+ 1: =[
+
+/([[.]+)/
+ a.[b].
+ 0: .[
+ 1: .[
+
+/((?>a+)b)/
+ aaab
+ 0: aaab
+ 1: aaab
+
+/(?>(a+))b/
+ aaab
+ 0: aaab
+ 1: aaa
+
+/((?>[^()]+)|\([^()]*\))+/
+ ((abc(ade)ufh()()x
+ 0: abc(ade)ufh()()x
+ 1: x
+
+/a\Z/
+ *** Failers
+No match
+ aaab
+No match
+ a\nb\n
+No match
+
+/b\Z/
+ a\nb\n
+ 0: b
+
+/b\z/
+
+/b\Z/
+ a\nb
+ 0: b
+
+/b\z/
+ a\nb
+ 0: b
+ *** Failers
+No match
+
+/^(?>(?(1)\.|())[^\W_](?>[a-z0-9-]*[^\W_])?)+$/
+ a
+ 0: a
+ 1:
+ abc
+ 0: abc
+ 1:
+ a-b
+ 0: a-b
+ 1:
+ 0-9
+ 0: 0-9
+ 1:
+ a.b
+ 0: a.b
+ 1:
+ 5.6.7
+ 0: 5.6.7
+ 1:
+ the.quick.brown.fox
+ 0: the.quick.brown.fox
+ 1:
+ a100.b200.300c
+ 0: a100.b200.300c
+ 1:
+ 12-ab.1245
+ 0: 12-ab.1245
+ 1:
+ *** Failers
+No match
+ \
+No match
+ .a
+No match
+ -a
+No match
+ a-
+No match
+ a.
+No match
+ a_b
+No match
+ a.-
+No match
+ a..
+No match
+ ab..bc
+No match
+ the.quick.brown.fox-
+No match
+ the.quick.brown.fox.
+No match
+ the.quick.brown.fox_
+No match
+ the.quick.brown.fox+
+No match
+
+/(?>.*)(?<=(abcd|wxyz))/
+ alphabetabcd
+ 0: alphabetabcd
+ 1: abcd
+ endingwxyz
+ 0: endingwxyz
+ 1: wxyz
+ *** Failers
+No match
+ a rather long string that doesn't end with one of them
+No match
+
+/word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ word cat dog elephant mussel cow horse canary baboon snake shark
+No match
+
+/word (?>[a-zA-Z0-9]+ ){0,30}otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope
+No match
+
+/(?<=\d{3}(?!999))foo/
+ 999foo
+ 0: foo
+ 123999foo
+ 0: foo
+ *** Failers
+No match
+ 123abcfoo
+No match
+
+/(?<=(?!...999)\d{3})foo/
+ 999foo
+ 0: foo
+ 123999foo
+ 0: foo
+ *** Failers
+No match
+ 123abcfoo
+No match
+
+/(?<=\d{3}(?!999)...)foo/
+ 123abcfoo
+ 0: foo
+ 123456foo
+ 0: foo
+ *** Failers
+No match
+ 123999foo
+No match
+
+/(?<=\d{3}...)(?<!999)foo/
+ 123abcfoo
+ 0: foo
+ 123456foo
+ 0: foo
+ *** Failers
+No match
+ 123999foo
+No match
+
+/<a[\s]+href[\s]*=[\s]* # find <a href=
+ ([\"\'])? # find single or double quote
+ (?(1) (.*?)\1 | ([^\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space
+/isx
+ <a href=abcd xyz
+ 0: <a href=abcd
+ 1: <unset>
+ 2: <unset>
+ 3: abcd
+ <a href=\"abcd xyz pqr\" cats
+ 0: <a href="abcd xyz pqr"
+ 1: "
+ 2: abcd xyz pqr
+ <a href=\'abcd xyz pqr\' cats
+ 0: <a href='abcd xyz pqr'
+ 1: '
+ 2: abcd xyz pqr
+
+/<a\s+href\s*=\s* # find <a href=
+ (["'])? # find single or double quote
+ (?(1) (.*?)\1 | (\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space
+/isx
+ <a href=abcd xyz
+ 0: <a href=abcd
+ 1: <unset>
+ 2: <unset>
+ 3: abcd
+ <a href=\"abcd xyz pqr\" cats
+ 0: <a href="abcd xyz pqr"
+ 1: "
+ 2: abcd xyz pqr
+ <a href = \'abcd xyz pqr\' cats
+ 0: <a href = 'abcd xyz pqr'
+ 1: '
+ 2: abcd xyz pqr
+
+/<a\s+href(?>\s*)=(?>\s*) # find <a href=
+ (["'])? # find single or double quote
+ (?(1) (.*?)\1 | (\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space
+/isx
+ <a href=abcd xyz
+ 0: <a href=abcd
+ 1: <unset>
+ 2: <unset>
+ 3: abcd
+ <a href=\"abcd xyz pqr\" cats
+ 0: <a href="abcd xyz pqr"
+ 1: "
+ 2: abcd xyz pqr
+ <a href = \'abcd xyz pqr\' cats
+ 0: <a href = 'abcd xyz pqr'
+ 1: '
+ 2: abcd xyz pqr
+
+/((Z)+|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: A
+ 2: Z
+
+/(Z()|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: A
+ 2:
+
+/(Z(())|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: A
+ 2:
+ 3:
+
+/((?>Z)+|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: A
+
+/((?>)+|A)*/
+ ZABCDEFG
+ 0:
+ 1:
+
+/a*/g
+ abbab
+ 0: a
+ 0:
+ 0:
+ 0: a
+ 0:
+ 0:
+
+/^[a-\d]/
+ abcde
+ 0: a
+ -things
+ 0: -
+ 0digit
+ 0: 0
+ *** Failers
+No match
+ bcdef
+No match
+
+/^[\d-a]/
+ abcde
+ 0: a
+ -things
+ 0: -
+ 0digit
+ 0: 0
+ *** Failers
+No match
+ bcdef
+No match
+
+/[[:space:]]+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d\x0b
+
+/[[:blank:]]+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09
+
+/[\s]+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d
+
+/\s+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d
+
+/a b/x
+ ab
+No match
+
+/(?!\A)x/m
+ a\nxb\n
+ 0: x
+
+/(?!^)x/m
+ a\nxb\n
+No match
+
+/abc\Qabc\Eabc/
+ abcabcabc
+ 0: abcabcabc
+
+/abc\Q(*+|\Eabc/
+ abc(*+|abc
+ 0: abc(*+|abc
+
+/ abc\Q abc\Eabc/x
+ abc abcabc
+ 0: abc abcabc
+ *** Failers
+No match
+ abcabcabc
+No match
+
+/abc#comment
+ \Q#not comment
+ literal\E/x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/abc#comment
+ \Q#not comment
+ literal/x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/abc#comment
+ \Q#not comment
+ literal\E #more comment
+ /x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/abc#comment
+ \Q#not comment
+ literal\E #more comment/x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/\Qabc\$xyz\E/
+ abc\\\$xyz
+ 0: abc\$xyz
+
+/\Qabc\E\$\Qxyz\E/
+ abc\$xyz
+ 0: abc$xyz
+
+/\Gabc/
+ abc
+ 0: abc
+ *** Failers
+No match
+ xyzabc
+No match
+
+/\Gabc./g
+ abc1abc2xyzabc3
+ 0: abc1
+ 0: abc2
+
+/abc./g
+ abc1abc2xyzabc3
+ 0: abc1
+ 0: abc2
+ 0: abc3
+
+/a(?x: b c )d/
+ XabcdY
+ 0: abcd
+ *** Failers
+No match
+ Xa b c d Y
+No match
+
+/((?x)x y z | a b c)/
+ XabcY
+ 0: abc
+ 1: abc
+ AxyzB
+ 0: xyz
+ 1: xyz
+
+/(?i)AB(?-i)C/
+ XabCY
+ 0: abC
+ *** Failers
+No match
+ XabcY
+No match
+
+/((?i)AB(?-i)C|D)E/
+ abCE
+ 0: abCE
+ 1: abC
+ DE
+ 0: DE
+ 1: D
+ *** Failers
+No match
+ abcE
+No match
+ abCe
+No match
+ dE
+No match
+ De
+No match
+
+/(.*)\d+\1/
+ abc123abc
+ 0: abc123abc
+ 1: abc
+ abc123bc
+ 0: bc123bc
+ 1: bc
+
+/(.*)\d+\1/s
+ abc123abc
+ 0: abc123abc
+ 1: abc
+ abc123bc
+ 0: bc123bc
+ 1: bc
+
+/((.*))\d+\1/
+ abc123abc
+ 0: abc123abc
+ 1: abc
+ 2: abc
+ abc123bc
+ 0: bc123bc
+ 1: bc
+ 2: bc
+
+/-- This tests for an IPv6 address in the form where it can have up to --/
+/-- eight components, one and only one of which is empty. This must be --/
+No match
+/-- an internal component. --/
+No match
+
+/^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ /xi
+ a123::a123
+ 0: a123::a123
+ 1:
+ a123:b342::abcd
+ 0: a123:b342::abcd
+ 1:
+ a123:b342::324e:abcd
+ 0: a123:b342::324e:abcd
+ 1:
+ a123:ddde:b342::324e:abcd
+ 0: a123:ddde:b342::324e:abcd
+ 1:
+ a123:ddde:b342::324e:dcba:abcd
+ 0: a123:ddde:b342::324e:dcba:abcd
+ 1:
+ a123:ddde:9999:b342::324e:dcba:abcd
+ 0: a123:ddde:9999:b342::324e:dcba:abcd
+ 1:
+ *** Failers
+No match
+ 1:2:3:4:5:6:7:8
+No match
+ a123:bce:ddde:9999:b342::324e:dcba:abcd
+No match
+ a123::9999:b342::324e:dcba:abcd
+No match
+ abcde:2:3:4:5:6:7:8
+No match
+ ::1
+No match
+ abcd:fee0:123::
+No match
+ :1
+No match
+ 1:
+No match
+
+/[z\Qa-d]\E]/
+ z
+ 0: z
+ a
+ 0: a
+ -
+ 0: -
+ d
+ 0: d
+ ]
+ 0: ]
+ *** Failers
+ 0: a
+ b
+No match
+
+/[\z\C]/
+ z
+ 0: z
+ C
+ 0: C
+
+/\M/
+ M
+ 0: M
+
+/(a+)*b/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/(?i)reg(?:ul(?:[a�]|ae)r|ex)/
+ REGular
+ 0: REGular
+ regulaer
+ 0: regulaer
+ Regex
+ 0: Regex
+ regul�r
+ 0: regul\xe4r
+
+/����[�-��-�]+/
+ �����
+ 0: \xc5\xe6\xe5\xe4\xe0
+ �����
+ 0: \xc5\xe6\xe5\xe4\xff
+ �����
+ 0: \xc5\xe6\xe5\xe4\xc0
+ �����
+ 0: \xc5\xe6\xe5\xe4\xdf
+
+/(?<=Z)X./
+ \x84XAZXB
+ 0: XB
+
+/ab cd (?x) de fg/
+ ab cd defg
+ 0: ab cd defg
+
+/ab cd(?x) de fg/
+ ab cddefg
+ 0: ab cddefg
+ ** Failers
+No match
+ abcddefg
+No match
+
+/(?<![^f]oo)(bar)/
+ foobarX
+ 0: bar
+ 1: bar
+ ** Failers
+No match
+ boobarX
+No match
+
+/(?<![^f])X/
+ offX
+ 0: X
+ ** Failers
+No match
+ onyX
+No match
+
+/(?<=[^f])X/
+ onyX
+ 0: X
+ ** Failers
+No match
+ offX
+No match
+
+/^/mg
+ a\nb\nc\n
+ 0:
+ 0:
+ 0:
+ \
+ 0:
+
+/(?<=C\n)^/mg
+ A\nC\nC\n
+ 0:
+
+/(?:(?(1)a|b)(X))+/
+ bXaX
+ 0: bXaX
+ 1: X
+
+/(?:(?(1)\1a|b)(X|Y))+/
+ bXXaYYaY
+ 0: bXXaYYaY
+ 1: Y
+ bXYaXXaX
+ 0: bX
+ 1: X
+
+/()()()()()()()()()(?:(?(10)\10a|b)(X|Y))+/
+ bXXaYYaY
+ 0: bX
+ 1:
+ 2:
+ 3:
+ 4:
+ 5:
+ 6:
+ 7:
+ 8:
+ 9:
+10: X
+
+/[[,abc,]+]/
+ abc]
+ 0: abc]
+ a,b]
+ 0: a,b]
+ [a,b,c]
+ 0: [a,b,c]
+
+/(?-x: )/x
+ A\x20B
+ 0:
+
+"(?x)(?-x: \s*#\s*)"
+ A # B
+ 0: #
+ ** Failers
+No match
+ #
+No match
+
+"(?x-is)(?:(?-ixs) \s*#\s*) include"
+ A #include
+ 0: #include
+ ** Failers
+No match
+ A#include
+No match
+ A #Include
+No match
+
+/a*b*\w/
+ aaabbbb
+ 0: aaabbbb
+ aaaa
+ 0: aaaa
+ a
+ 0: a
+
+/a*b?\w/
+ aaabbbb
+ 0: aaabb
+ aaaa
+ 0: aaaa
+ a
+ 0: a
+
+/a*b{0,4}\w/
+ aaabbbb
+ 0: aaabbbb
+ aaaa
+ 0: aaaa
+ a
+ 0: a
+
+/a*b{0,}\w/
+ aaabbbb
+ 0: aaabbbb
+ aaaa
+ 0: aaaa
+ a
+ 0: a
+
+/a*\d*\w/
+ 0a
+ 0: 0a
+ a
+ 0: a
+
+/a*b *\w/x
+ a
+ 0: a
+
+/a*b#comment
+ *\w/x
+ a
+ 0: a
+
+/a* b *\w/x
+ a
+ 0: a
+
+/^\w+=.*(\\\n.*)*/
+ abc=xyz\\\npqr
+ 0: abc=xyz\
+
+/(?=(\w+))\1:/
+ abcd:
+ 0: abcd:
+ 1: abcd
+
+/^(?=(\w+))\1:/
+ abcd:
+ 0: abcd:
+ 1: abcd
+
+/^\Eabc/
+ abc
+ 0: abc
+
+/^[\Eabc]/
+ a
+ 0: a
+ ** Failers
+No match
+ E
+No match
+
+/^[a-\Ec]/
+ b
+ 0: b
+ ** Failers
+No match
+ -
+No match
+ E
+No match
+
+/^[a\E\E-\Ec]/
+ b
+ 0: b
+ ** Failers
+No match
+ -
+No match
+ E
+No match
+
+/^[\E\Qa\E-\Qz\E]+/
+ b
+ 0: b
+ ** Failers
+No match
+ -
+No match
+
+/^[a\Q]bc\E]/
+ a
+ 0: a
+ ]
+ 0: ]
+ c
+ 0: c
+
+/^[a-\Q\E]/
+ a
+ 0: a
+ -
+ 0: -
+
+/^(a()*)*/
+ aaaa
+ 0: aaaa
+ 1: a
+ 2:
+
+/^(?:a(?:(?:))*)*/
+ aaaa
+ 0: aaaa
+
+/^(a()+)+/
+ aaaa
+ 0: aaaa
+ 1: a
+ 2:
+
+/^(?:a(?:(?:))+)+/
+ aaaa
+ 0: aaaa
+
+/(a){0,3}(?(1)b|(c|))*D/
+ abbD
+ 0: abbD
+ 1: a
+ ccccD
+ 0: ccccD
+ 1: <unset>
+ 2:
+ D
+ 0: D
+ 1: <unset>
+ 2:
+
+/(a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 1:
+
+/(?>a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+
+/(?:a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+
+/\Z/g
+ abc\n
+ 0:
+ 0:
+
+/^(?s)(?>.*)(?<!\n)/
+ abc
+ 0: abc
+ abc\n
+No match
+
+/^(?![^\n]*\n\z)/
+ abc
+ 0:
+ abc\n
+No match
+
+/\z(?<!\n)/
+ abc
+ 0:
+ abc\n
+No match
+
+/(.*(.)?)*/
+ abcd
+ 0: abcd
+ 1:
+
+/( (A | (?(1)0|) )* )/x
+ abcd
+ 0:
+ 1:
+ 2:
+
+/( ( (?(1)0|) )* )/x
+ abcd
+ 0:
+ 1:
+ 2:
+
+/( (?(1)0|)* )/x
+ abcd
+ 0:
+ 1:
+
+/[[:abcd:xyz]]/
+ a]
+ 0: a]
+ :]
+ 0: :]
+
+/[abc[:x\]pqr]/
+ a
+ 0: a
+ [
+ 0: [
+ :
+ 0: :
+ ]
+ 0: ]
+ p
+ 0: p
+
+/ End of testinput1 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput10 b/lib/stdlib/test/re_SUITE_data/testoutput10
new file mode 100644
index 0000000000..dbd59241ad
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput10
@@ -0,0 +1,669 @@
+/-- These are a few representative patterns whose lengths and offsets are to be
+shown when the link size is 2. This is just a doublecheck test to ensure the
+sizes don't go horribly wrong when something is changed. The pattern contents
+are all themselves checked in other tests. --/
+
+/((?i)b)/BM
+Memory allocation (code space): 21
+------------------------------------------------------------------
+ 0 17 Bra
+ 3 9 CBra 1
+ 8 01 Opt
+ 10 NC b
+ 12 9 Ket
+ 15 00 Opt
+ 17 17 Ket
+ 20 End
+------------------------------------------------------------------
+
+/(?s)(.*X|^B)/BM
+Memory allocation (code space): 25
+------------------------------------------------------------------
+ 0 21 Bra
+ 3 9 CBra 1
+ 8 Any*
+ 10 X
+ 12 6 Alt
+ 15 ^
+ 16 B
+ 18 15 Ket
+ 21 21 Ket
+ 24 End
+------------------------------------------------------------------
+
+/(?s:.*X|^B)/BM
+Memory allocation (code space): 29
+------------------------------------------------------------------
+ 0 25 Bra
+ 3 9 Bra
+ 6 04 Opt
+ 8 Any*
+ 10 X
+ 12 8 Alt
+ 15 04 Opt
+ 17 ^
+ 18 B
+ 20 17 Ket
+ 23 00 Opt
+ 25 25 Ket
+ 28 End
+------------------------------------------------------------------
+
+/^[[:alnum:]]/BM
+Memory allocation (code space): 41
+------------------------------------------------------------------
+ 0 37 Bra
+ 3 ^
+ 4 [0-9A-Za-z]
+ 37 37 Ket
+ 40 End
+------------------------------------------------------------------
+
+/#/IxMD
+Memory allocation (code space): 7
+------------------------------------------------------------------
+ 0 3 Bra
+ 3 3 Ket
+ 6 End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: extended
+No first char
+No need char
+
+/a#/IxMD
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 a
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: extended
+First char = 'a'
+No need char
+
+/x?+/BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 x?+
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/x++/BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 x++
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/x{1,3}+/BM
+Memory allocation (code space): 19
+------------------------------------------------------------------
+ 0 15 Bra
+ 3 9 Once
+ 6 x
+ 8 x{0,2}
+ 12 9 Ket
+ 15 15 Ket
+ 18 End
+------------------------------------------------------------------
+
+/(x)*+/BM
+Memory allocation (code space): 24
+------------------------------------------------------------------
+ 0 20 Bra
+ 3 14 Once
+ 6 Brazero
+ 7 7 CBra 1
+ 12 x
+ 14 7 KetRmax
+ 17 14 Ket
+ 20 20 Ket
+ 23 End
+------------------------------------------------------------------
+
+/^((a+)(?U)([ab]+)(?-U)([bc]+)(\w*))/BM
+Memory allocation (code space): 120
+------------------------------------------------------------------
+ 0 116 Bra
+ 3 ^
+ 4 109 CBra 1
+ 9 7 CBra 2
+ 14 a+
+ 16 7 Ket
+ 19 39 CBra 3
+ 24 [ab]+?
+ 58 39 Ket
+ 61 39 CBra 4
+ 66 [bc]+
+100 39 Ket
+103 7 CBra 5
+108 \w*
+110 7 Ket
+113 109 Ket
+116 116 Ket
+119 End
+------------------------------------------------------------------
+
+|8J\$WE\<\.rX\+ix\[d1b\!H\#\?vV0vrK\:ZH1\=2M\>iV\;\?aPhFB\<\*vW\@QW\@sO9\}cfZA\-i\'w\%hKd6gt1UJP\,15_\#QY\$M\^Mss_U\/\]\&LK9\[5vQub\^w\[KDD\<EjmhUZ\?\.akp2dF\>qmj\;2\}YWFdYx\.Ap\]hjCPTP\(n28k\+3\;o\&WXqs\/gOXdr\$\:r\'do0\;b4c\(f_Gr\=\"\\4\)\[01T7ajQJvL\$W\~mL_sS\/4h\:x\*\[ZN\=KLs\&L5zX\/\/\>it\,o\:aU\(\;Z\>pW\&T7oP\'2K\^E\:x9\'c\[\%z\-\,64JQ5AeH_G\#KijUKghQw\^\\vea3a\?kka_G\$8\#\`\*kynsxzBLru\'\]k_\[7FrVx\}\^\=\$blx\>s\-N\%j\;D\*aZDnsw\:YKZ\%Q\.Kne9\#hP\?\+b3\(SOvL\,\^\;\&u5\@\?5C5Bhb\=m\-vEh_L15Jl\]U\)0RP6\{q\%L\^_z5E\'Dw6X\b|BM
+Memory allocation (code space): 826
+------------------------------------------------------------------
+ 0 822 Bra
+ 3 8J$WE<.rX+ix[d1b!H#?vV0vrK:ZH1=2M>iV;?aPhFB<*vW@QW@sO9}cfZA-i'w%hKd6gt1UJP,15_#QY$M^Mss_U/]&LK9[5vQub^w[KDD<EjmhUZ?.akp2dF>qmj;2}YWFdYx.Ap]hjCPTP(n28k+3;o&WXqs/gOXdr$:r'do0;b4c(f_Gr="\4)[01T7ajQJvL$W~mL_sS/4h:x*[ZN=KLs&L5zX//>it,o:aU(;Z>pW&T7oP'2K^E:x9'c[%z-,64JQ5AeH_G#KijUKghQw^\vea3a?kka_G$8#`*kynsxzBLru']k_[7FrVx}^=$blx>s-N%j;D*aZDnsw:YKZ%Q.Kne9#hP?+b3(SOvL,^;&u5@?5C5Bhb=m-vEh_L15Jl]U)0RP6{q%L^_z5E'Dw6X
+821 \b
+822 822 Ket
+825 End
+------------------------------------------------------------------
+
+|\$\<\.X\+ix\[d1b\!H\#\?vV0vrK\:ZH1\=2M\>iV\;\?aPhFB\<\*vW\@QW\@sO9\}cfZA\-i\'w\%hKd6gt1UJP\,15_\#QY\$M\^Mss_U\/\]\&LK9\[5vQub\^w\[KDD\<EjmhUZ\?\.akp2dF\>qmj\;2\}YWFdYx\.Ap\]hjCPTP\(n28k\+3\;o\&WXqs\/gOXdr\$\:r\'do0\;b4c\(f_Gr\=\"\\4\)\[01T7ajQJvL\$W\~mL_sS\/4h\:x\*\[ZN\=KLs\&L5zX\/\/\>it\,o\:aU\(\;Z\>pW\&T7oP\'2K\^E\:x9\'c\[\%z\-\,64JQ5AeH_G\#KijUKghQw\^\\vea3a\?kka_G\$8\#\`\*kynsxzBLru\'\]k_\[7FrVx\}\^\=\$blx\>s\-N\%j\;D\*aZDnsw\:YKZ\%Q\.Kne9\#hP\?\+b3\(SOvL\,\^\;\&u5\@\?5C5Bhb\=m\-vEh_L15Jl\]U\)0RP6\{q\%L\^_z5E\'Dw6X\b|BM
+Memory allocation (code space): 816
+------------------------------------------------------------------
+ 0 812 Bra
+ 3 $<.X+ix[d1b!H#?vV0vrK:ZH1=2M>iV;?aPhFB<*vW@QW@sO9}cfZA-i'w%hKd6gt1UJP,15_#QY$M^Mss_U/]&LK9[5vQub^w[KDD<EjmhUZ?.akp2dF>qmj;2}YWFdYx.Ap]hjCPTP(n28k+3;o&WXqs/gOXdr$:r'do0;b4c(f_Gr="\4)[01T7ajQJvL$W~mL_sS/4h:x*[ZN=KLs&L5zX//>it,o:aU(;Z>pW&T7oP'2K^E:x9'c[%z-,64JQ5AeH_G#KijUKghQw^\vea3a?kka_G$8#`*kynsxzBLru']k_[7FrVx}^=$blx>s-N%j;D*aZDnsw:YKZ%Q.Kne9#hP?+b3(SOvL,^;&u5@?5C5Bhb=m-vEh_L15Jl]U)0RP6{q%L^_z5E'Dw6X
+811 \b
+812 812 Ket
+815 End
+------------------------------------------------------------------
+
+/(a(?1)b)/BM
+Memory allocation (code space): 28
+------------------------------------------------------------------
+ 0 24 Bra
+ 3 18 CBra 1
+ 8 a
+ 10 6 Once
+ 13 3 Recurse
+ 16 6 Ket
+ 19 b
+ 21 18 Ket
+ 24 24 Ket
+ 27 End
+------------------------------------------------------------------
+
+/(a(?1)+b)/BM
+Memory allocation (code space): 28
+------------------------------------------------------------------
+ 0 24 Bra
+ 3 18 CBra 1
+ 8 a
+ 10 6 Once
+ 13 3 Recurse
+ 16 6 KetRmax
+ 19 b
+ 21 18 Ket
+ 24 24 Ket
+ 27 End
+------------------------------------------------------------------
+
+/a(?P<name1>b|c)d(?P<longername2>e)/BM
+Memory allocation (code space): 42
+------------------------------------------------------------------
+ 0 32 Bra
+ 3 a
+ 5 7 CBra 1
+ 10 b
+ 12 5 Alt
+ 15 c
+ 17 12 Ket
+ 20 d
+ 22 7 CBra 2
+ 27 e
+ 29 7 Ket
+ 32 32 Ket
+ 35 End
+------------------------------------------------------------------
+
+/(?:a(?P<c>c(?P<d>d)))(?P<a>a)/BM
+Memory allocation (code space): 54
+------------------------------------------------------------------
+ 0 41 Bra
+ 3 25 Bra
+ 6 a
+ 8 17 CBra 1
+ 13 c
+ 15 7 CBra 2
+ 20 d
+ 22 7 Ket
+ 25 17 Ket
+ 28 25 Ket
+ 31 7 CBra 3
+ 36 a
+ 38 7 Ket
+ 41 41 Ket
+ 44 End
+------------------------------------------------------------------
+
+/(?P<a>a)...(?P=a)bbb(?P>a)d/BM
+Memory allocation (code space): 43
+------------------------------------------------------------------
+ 0 36 Bra
+ 3 7 CBra 1
+ 8 a
+ 10 7 Ket
+ 13 Any
+ 14 Any
+ 15 Any
+ 16 \1
+ 19 bbb
+ 25 6 Once
+ 28 3 Recurse
+ 31 6 Ket
+ 34 d
+ 36 36 Ket
+ 39 End
+------------------------------------------------------------------
+
+/abc(?C255)de(?C)f/BM
+Memory allocation (code space): 31
+------------------------------------------------------------------
+ 0 27 Bra
+ 3 abc
+ 9 Callout 255 10 1
+ 15 de
+ 19 Callout 0 16 1
+ 25 f
+ 27 27 Ket
+ 30 End
+------------------------------------------------------------------
+
+/abcde/CBM
+Memory allocation (code space): 53
+------------------------------------------------------------------
+ 0 49 Bra
+ 3 Callout 255 0 1
+ 9 a
+ 11 Callout 255 1 1
+ 17 b
+ 19 Callout 255 2 1
+ 25 c
+ 27 Callout 255 3 1
+ 33 d
+ 35 Callout 255 4 1
+ 41 e
+ 43 Callout 255 5 0
+ 49 49 Ket
+ 52 End
+------------------------------------------------------------------
+
+/\x{100}/8BM
+Memory allocation (code space): 10
+------------------------------------------------------------------
+ 0 6 Bra
+ 3 \x{100}
+ 6 6 Ket
+ 9 End
+------------------------------------------------------------------
+
+/\x{1000}/8BM
+Memory allocation (code space): 11
+------------------------------------------------------------------
+ 0 7 Bra
+ 3 \x{1000}
+ 7 7 Ket
+ 10 End
+------------------------------------------------------------------
+
+/\x{10000}/8BM
+Memory allocation (code space): 12
+------------------------------------------------------------------
+ 0 8 Bra
+ 3 \x{10000}
+ 8 8 Ket
+ 11 End
+------------------------------------------------------------------
+
+/\x{100000}/8BM
+Memory allocation (code space): 12
+------------------------------------------------------------------
+ 0 8 Bra
+ 3 \x{100000}
+ 8 8 Ket
+ 11 End
+------------------------------------------------------------------
+
+/\x{1000000}/8BM
+Memory allocation (code space): 13
+------------------------------------------------------------------
+ 0 9 Bra
+ 3 \x{1000000}
+ 9 9 Ket
+ 12 End
+------------------------------------------------------------------
+
+/\x{4000000}/8BM
+Memory allocation (code space): 14
+------------------------------------------------------------------
+ 0 10 Bra
+ 3 \x{4000000}
+ 10 10 Ket
+ 13 End
+------------------------------------------------------------------
+
+/\x{7fffFFFF}/8BM
+Memory allocation (code space): 14
+------------------------------------------------------------------
+ 0 10 Bra
+ 3 \x{7fffffff}
+ 10 10 Ket
+ 13 End
+------------------------------------------------------------------
+
+/[\x{ff}]/8BM
+Memory allocation (code space): 10
+------------------------------------------------------------------
+ 0 6 Bra
+ 3 \x{ff}
+ 6 6 Ket
+ 9 End
+------------------------------------------------------------------
+
+/[\x{100}]/8BM
+Memory allocation (code space): 15
+------------------------------------------------------------------
+ 0 11 Bra
+ 3 [\x{100}]
+ 11 11 Ket
+ 14 End
+------------------------------------------------------------------
+
+/\x80/8BM
+Memory allocation (code space): 10
+------------------------------------------------------------------
+ 0 6 Bra
+ 3 \x{80}
+ 6 6 Ket
+ 9 End
+------------------------------------------------------------------
+
+/\xff/8BM
+Memory allocation (code space): 10
+------------------------------------------------------------------
+ 0 6 Bra
+ 3 \x{ff}
+ 6 6 Ket
+ 9 End
+------------------------------------------------------------------
+
+/\x{0041}\x{2262}\x{0391}\x{002e}/D8M
+Memory allocation (code space): 18
+------------------------------------------------------------------
+ 0 14 Bra
+ 3 A\x{2262}\x{391}.
+ 14 14 Ket
+ 17 End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 'A'
+Need char = '.'
+
+/\x{D55c}\x{ad6d}\x{C5B4}/D8M
+Memory allocation (code space): 19
+------------------------------------------------------------------
+ 0 15 Bra
+ 3 \x{d55c}\x{ad6d}\x{c5b4}
+ 15 15 Ket
+ 18 End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 237
+Need char = 180
+
+/\x{65e5}\x{672c}\x{8a9e}/D8M
+Memory allocation (code space): 19
+------------------------------------------------------------------
+ 0 15 Bra
+ 3 \x{65e5}\x{672c}\x{8a9e}
+ 15 15 Ket
+ 18 End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 230
+Need char = 158
+
+/[\x{100}]/8BM
+Memory allocation (code space): 15
+------------------------------------------------------------------
+ 0 11 Bra
+ 3 [\x{100}]
+ 11 11 Ket
+ 14 End
+------------------------------------------------------------------
+
+/[Z\x{100}]/8BM
+Memory allocation (code space): 47
+------------------------------------------------------------------
+ 0 43 Bra
+ 3 [Z\x{100}]
+ 43 43 Ket
+ 46 End
+------------------------------------------------------------------
+
+/^[\x{100}\E-\Q\E\x{150}]/B8M
+Memory allocation (code space): 18
+------------------------------------------------------------------
+ 0 14 Bra
+ 3 ^
+ 4 [\x{100}-\x{150}]
+ 14 14 Ket
+ 17 End
+------------------------------------------------------------------
+
+/^[\QĀ\E-\QŐ\E]/B8M
+Memory allocation (code space): 18
+------------------------------------------------------------------
+ 0 14 Bra
+ 3 ^
+ 4 [\x{100}-\x{150}]
+ 14 14 Ket
+ 17 End
+------------------------------------------------------------------
+
+/^[\QĀ\E-\QŐ\E/B8M
+Failed: missing terminating ] for character class at offset 15
+
+/[\p{L}]/BM
+Memory allocation (code space): 15
+------------------------------------------------------------------
+ 0 11 Bra
+ 3 [\p{L}]
+ 11 11 Ket
+ 14 End
+------------------------------------------------------------------
+
+/[\p{^L}]/BM
+Memory allocation (code space): 15
+------------------------------------------------------------------
+ 0 11 Bra
+ 3 [\P{L}]
+ 11 11 Ket
+ 14 End
+------------------------------------------------------------------
+
+/[\P{L}]/BM
+Memory allocation (code space): 15
+------------------------------------------------------------------
+ 0 11 Bra
+ 3 [\P{L}]
+ 11 11 Ket
+ 14 End
+------------------------------------------------------------------
+
+/[\P{^L}]/BM
+Memory allocation (code space): 15
+------------------------------------------------------------------
+ 0 11 Bra
+ 3 [\p{L}]
+ 11 11 Ket
+ 14 End
+------------------------------------------------------------------
+
+/[abc\p{L}\x{0660}]/8BM
+Memory allocation (code space): 50
+------------------------------------------------------------------
+ 0 46 Bra
+ 3 [a-c\p{L}\x{660}]
+ 46 46 Ket
+ 49 End
+------------------------------------------------------------------
+
+/[\p{Nd}]/8BM
+Memory allocation (code space): 15
+------------------------------------------------------------------
+ 0 11 Bra
+ 3 [\p{Nd}]
+ 11 11 Ket
+ 14 End
+------------------------------------------------------------------
+
+/[\p{Nd}+-]+/8BM
+Memory allocation (code space): 48
+------------------------------------------------------------------
+ 0 44 Bra
+ 3 [+\-\p{Nd}]+
+ 44 44 Ket
+ 47 End
+------------------------------------------------------------------
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8iBM
+Memory allocation (code space): 25
+------------------------------------------------------------------
+ 0 21 Bra
+ 3 NC A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 21 21 Ket
+ 24 End
+------------------------------------------------------------------
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8BM
+Memory allocation (code space): 25
+------------------------------------------------------------------
+ 0 21 Bra
+ 3 A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 21 21 Ket
+ 24 End
+------------------------------------------------------------------
+
+/[\x{105}-\x{109}]/8iBM
+Memory allocation (code space): 17
+------------------------------------------------------------------
+ 0 13 Bra
+ 3 [\x{104}-\x{109}]
+ 13 13 Ket
+ 16 End
+------------------------------------------------------------------
+
+/( ( (?(1)0|) )* )/xBM
+Memory allocation (code space): 38
+------------------------------------------------------------------
+ 0 34 Bra
+ 3 28 CBra 1
+ 8 Brazero
+ 9 19 SCBra 2
+ 14 8 Cond
+ 17 1 Cond ref
+ 20 0
+ 22 3 Alt
+ 25 11 Ket
+ 28 19 KetRmax
+ 31 28 Ket
+ 34 34 Ket
+ 37 End
+------------------------------------------------------------------
+
+/( (?(1)0|)* )/xBM
+Memory allocation (code space): 30
+------------------------------------------------------------------
+ 0 26 Bra
+ 3 20 CBra 1
+ 8 Brazero
+ 9 8 SCond
+ 12 1 Cond ref
+ 15 0
+ 17 3 Alt
+ 20 11 KetRmax
+ 23 20 Ket
+ 26 26 Ket
+ 29 End
+------------------------------------------------------------------
+
+/[a]/BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 a
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/[a]/8BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 a
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/[\xaa]/BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 \xaa
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/[\xaa]/8BM
+Memory allocation (code space): 10
+------------------------------------------------------------------
+ 0 6 Bra
+ 3 \x{aa}
+ 6 6 Ket
+ 9 End
+------------------------------------------------------------------
+
+/[^a]/BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 [^a]
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/[^a]/8BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 [^a]
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/[^\xaa]/BM
+Memory allocation (code space): 9
+------------------------------------------------------------------
+ 0 5 Bra
+ 3 [^\xaa]
+ 5 5 Ket
+ 8 End
+------------------------------------------------------------------
+
+/[^\xaa]/8BM
+Memory allocation (code space): 40
+------------------------------------------------------------------
+ 0 36 Bra
+ 3 [\x00-\xa9\xab-\xff] (neg)
+ 36 36 Ket
+ 39 End
+------------------------------------------------------------------
+
+/ End of testinput10 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput2 b/lib/stdlib/test/re_SUITE_data/testoutput2
new file mode 100644
index 0000000000..dba227f503
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput2
@@ -0,0 +1,9388 @@
+/(a)b|/I
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+
+/abc/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+ abc
+ 0: abc
+ defabc
+ 0: abc
+ \Aabc
+ 0: abc
+ *** Failers
+No match
+ \Adefabc
+No match
+ ABC
+No match
+
+/^abc/I
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+ abc
+ 0: abc
+ \Aabc
+ 0: abc
+ *** Failers
+No match
+ defabc
+No match
+ \Adefabc
+No match
+
+/a+bc/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'c'
+
+/a*bc/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = 'c'
+
+/a{3}bc/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'c'
+
+/(abc|a+z)/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/^abc$/I
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+ abc
+ 0: abc
+ *** Failers
+No match
+ def\nabc
+No match
+
+/ab\idef/X
+Failed: unrecognized character follows \ at offset 3
+
+/(?X)ab\idef/X
+Failed: unrecognized character follows \ at offset 7
+
+/x{5,4}/
+Failed: numbers out of order in {} quantifier at offset 5
+
+/z{65536}/
+Failed: number too big in {} quantifier at offset 7
+
+/[abcd/
+Failed: missing terminating ] for character class at offset 5
+
+/(?X)[\B]/
+Failed: invalid escape sequence in character class at offset 6
+
+/[z-a]/
+Failed: range out of order in character class at offset 3
+
+/^*/
+Failed: nothing to repeat at offset 1
+
+/(abc/
+Failed: missing ) at offset 4
+
+/(?# abc/
+Failed: missing ) after comment at offset 7
+
+/(?z)abc/
+Failed: unrecognized character after (? or (?- at offset 2
+
+/.*b/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char at start or follows newline
+Need char = 'b'
+
+/.*?b/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char at start or follows newline
+Need char = 'b'
+
+/cat|dog|elephant/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+ this sentence eventually mentions a cat
+ 0: cat
+ this sentences rambles on and on for a while and then reaches elephant
+ 0: elephant
+
+/cat|dog|elephant/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: c d e
+ this sentence eventually mentions a cat
+ 0: cat
+ this sentences rambles on and on for a while and then reaches elephant
+ 0: elephant
+
+/cat|dog|elephant/IiS
+Capturing subpattern count = 0
+Options: caseless
+No first char
+No need char
+Starting byte set: C D E c d e
+ this sentence eventually mentions a CAT cat
+ 0: CAT
+ this sentences rambles on and on for a while to elephant ElePhant
+ 0: elephant
+
+/a|[bcd]/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: a b c d
+
+/(a|[^\dZ])/IS
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+Starting byte set: \x00 \x01 \x02 \x03 \x04 \x05 \x06 \x07 \x08 \x09 \x0a
+ \x0b \x0c \x0d \x0e \x0f \x10 \x11 \x12 \x13 \x14 \x15 \x16 \x17 \x18 \x19
+ \x1a \x1b \x1c \x1d \x1e \x1f \x20 ! " # $ % & ' ( ) * + , - . / : ; < = >
+ ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y [ \ ] ^ _ ` a b c d
+ e f g h i j k l m n o p q r s t u v w x y z { | } ~ \x7f \x80 \x81 \x82 \x83
+ \x84 \x85 \x86 \x87 \x88 \x89 \x8a \x8b \x8c \x8d \x8e \x8f \x90 \x91 \x92
+ \x93 \x94 \x95 \x96 \x97 \x98 \x99 \x9a \x9b \x9c \x9d \x9e \x9f \xa0 \xa1
+ \xa2 \xa3 \xa4 \xa5 \xa6 \xa7 \xa8 \xa9 \xaa \xab \xac \xad \xae \xaf \xb0
+ \xb1 \xb2 \xb3 \xb4 \xb5 \xb6 \xb7 \xb8 \xb9 \xba \xbb \xbc \xbd \xbe \xbf
+ \xc0 \xc1 \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 \xc8 \xc9 \xca \xcb \xcc \xcd \xce
+ \xcf \xd0 \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 \xd8 \xd9 \xda \xdb \xdc \xdd
+ \xde \xdf \xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec
+ \xed \xee \xef \xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7 \xf8 \xf9 \xfa \xfb
+ \xfc \xfd \xfe \xff
+
+/(a|b)*[\s]/IS
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+Starting byte set: \x09 \x0a \x0c \x0d \x20 a b
+
+/(ab\2)/
+Failed: reference to non-existent subpattern at offset 6
+
+/{4,5}abc/
+Failed: nothing to repeat at offset 4
+
+/(a)(b)(c)\2/I
+Capturing subpattern count = 3
+Max back reference = 2
+No options
+First char = 'a'
+Need char = 'c'
+ abcb
+ 0: abcb
+ 1: a
+ 2: b
+ 3: c
+ \O0abcb
+Matched, but too many substrings
+ \O3abcb
+Matched, but too many substrings
+ 0: abcb
+ \O6abcb
+Matched, but too many substrings
+ 0: abcb
+ 1: a
+ \O9abcb
+Matched, but too many substrings
+ 0: abcb
+ 1: a
+ 2: b
+ \O12abcb
+ 0: abcb
+ 1: a
+ 2: b
+ 3: c
+
+/(a)bc|(a)(b)\2/I
+Capturing subpattern count = 3
+Max back reference = 2
+No options
+First char = 'a'
+No need char
+ abc
+ 0: abc
+ 1: a
+ \O0abc
+Matched, but too many substrings
+ \O3abc
+Matched, but too many substrings
+ 0: abc
+ \O6abc
+ 0: abc
+ 1: a
+ aba
+ 0: aba
+ 1: <unset>
+ 2: a
+ 3: b
+ \O0aba
+Matched, but too many substrings
+ \O3aba
+Matched, but too many substrings
+ 0: aba
+ \O6aba
+Matched, but too many substrings
+ 0: aba
+ 1: <unset>
+ \O9aba
+Matched, but too many substrings
+ 0: aba
+ 1: <unset>
+ 2: a
+ \O12aba
+ 0: aba
+ 1: <unset>
+ 2: a
+ 3: b
+
+/abc$/IE
+Capturing subpattern count = 0
+Options: dollar_endonly
+First char = 'a'
+Need char = 'c'
+ abc
+ 0: abc
+ *** Failers
+No match
+ abc\n
+No match
+ abc\ndef
+No match
+
+/(a)(b)(c)(d)(e)\6/
+Failed: reference to non-existent subpattern at offset 17
+
+/the quick brown fox/I
+Capturing subpattern count = 0
+No options
+First char = 't'
+Need char = 'x'
+ the quick brown fox
+ 0: the quick brown fox
+ this is a line with the quick brown fox
+ 0: the quick brown fox
+
+/the quick brown fox/IA
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+ the quick brown fox
+ 0: the quick brown fox
+ *** Failers
+No match
+ this is a line with the quick brown fox
+No match
+
+/ab(?z)cd/
+Failed: unrecognized character after (? or (?- at offset 4
+
+/^abc|def/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+ abcdef
+ 0: abc
+ abcdef\B
+ 0: def
+
+/.*((abc)$|(def))/I
+Capturing subpattern count = 3
+Partial matching not supported
+No options
+First char at start or follows newline
+No need char
+ defabc
+ 0: defabc
+ 1: abc
+ 2: abc
+ \Zdefabc
+ 0: def
+ 1: def
+ 2: <unset>
+ 3: def
+
+/abc/IP
+ abc
+ 0: abc
+ *** Failers
+No match: POSIX code 17: match failed
+
+/^abc|def/IP
+ abcdef
+ 0: abc
+ abcdef\B
+ 0: def
+
+/.*((abc)$|(def))/IP
+ defabc
+ 0: defabc
+ 1: abc
+ 2: abc
+ \Zdefabc
+ 0: def
+ 1: def
+ 3: def
+
+/the quick brown fox/IP
+ the quick brown fox
+ 0: the quick brown fox
+ *** Failers
+No match: POSIX code 17: match failed
+ The Quick Brown Fox
+No match: POSIX code 17: match failed
+
+/the quick brown fox/IPi
+ the quick brown fox
+ 0: the quick brown fox
+ The Quick Brown Fox
+ 0: The Quick Brown Fox
+
+/abc.def/IP
+ *** Failers
+No match: POSIX code 17: match failed
+ abc\ndef
+No match: POSIX code 17: match failed
+
+/abc$/IP
+ abc
+ 0: abc
+ abc\n
+ 0: abc
+
+/(abc)\2/IP
+Failed: POSIX code 15: bad back reference at offset 7
+
+/(abc\1)/IP
+ abc
+No match: POSIX code 17: match failed
+
+/)/
+Failed: unmatched parentheses at offset 0
+
+/a[]b/
+Failed: missing terminating ] for character class at offset 4
+
+/[^aeiou ]{3,}/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ co-processors, and for
+ 0: -pr
+
+/<.*>/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = '<'
+Need char = '>'
+ abc<def>ghi<klm>nop
+ 0: <def>ghi<klm>
+
+/<.*?>/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = '<'
+Need char = '>'
+ abc<def>ghi<klm>nop
+ 0: <def>
+
+/<.*>/IU
+Capturing subpattern count = 0
+Partial matching not supported
+Options: ungreedy
+First char = '<'
+Need char = '>'
+ abc<def>ghi<klm>nop
+ 0: <def>
+
+/(?U)<.*>/I
+Capturing subpattern count = 0
+Partial matching not supported
+Options: ungreedy
+First char = '<'
+Need char = '>'
+ abc<def>ghi<klm>nop
+ 0: <def>
+
+/<.*?>/IU
+Capturing subpattern count = 0
+Partial matching not supported
+Options: ungreedy
+First char = '<'
+Need char = '>'
+ abc<def>ghi<klm>nop
+ 0: <def>ghi<klm>
+
+/={3,}/IU
+Capturing subpattern count = 0
+Partial matching not supported
+Options: ungreedy
+First char = '='
+Need char = '='
+ abc========def
+ 0: ===
+
+/(?U)={3,}?/I
+Capturing subpattern count = 0
+Partial matching not supported
+Options: ungreedy
+First char = '='
+Need char = '='
+ abc========def
+ 0: ========
+
+/(?<!bar|cattle)foo/I
+Capturing subpattern count = 0
+No options
+First char = 'f'
+Need char = 'o'
+ foo
+ 0: foo
+ catfoo
+ 0: foo
+ *** Failers
+No match
+ the barfoo
+No match
+ and cattlefoo
+No match
+
+/(?<=a+)b/
+Failed: lookbehind assertion is not fixed length at offset 6
+
+/(?<=aaa|b{0,3})b/
+Failed: lookbehind assertion is not fixed length at offset 14
+
+/(?<!(foo)a\1)bar/
+Failed: lookbehind assertion is not fixed length at offset 12
+
+/(?i)abc/I
+Capturing subpattern count = 0
+Options: caseless
+First char = 'a' (caseless)
+Need char = 'c' (caseless)
+
+/(a|(?m)a)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/(?i)^1234/I
+Capturing subpattern count = 0
+Options: anchored caseless
+No first char
+No need char
+
+/(^b|(?i)^d)/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+
+/(?s).*/I
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored dotall
+No first char
+No need char
+
+/[abcd]/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: a b c d
+
+/(?i)[abcd]/IS
+Capturing subpattern count = 0
+Options: caseless
+No first char
+No need char
+Starting byte set: A B C D a b c d
+
+/(?m)[xy]|(b|c)/IS
+Capturing subpattern count = 1
+Options: multiline
+No first char
+No need char
+Starting byte set: b c x y
+
+/(^a|^b)/Im
+Capturing subpattern count = 1
+Options: multiline
+First char at start or follows newline
+No need char
+
+/(?i)(^a|^b)/Im
+Capturing subpattern count = 1
+Options: caseless multiline
+First char at start or follows newline
+No need char
+
+/(a)(?(1)a|b|c)/
+Failed: conditional group contains more than two branches at offset 13
+
+/(?(?=a)a|b|c)/
+Failed: conditional group contains more than two branches at offset 12
+
+/(?(1a)/
+Failed: missing ) at offset 6
+
+/(?(1a))/
+Failed: reference to non-existent subpattern at offset 6
+
+/(?(?i))/
+Failed: assertion expected after (?( at offset 3
+
+/(?(abc))/
+Failed: reference to non-existent subpattern at offset 7
+
+/(?(?<ab))/
+Failed: syntax error in subpattern name (missing terminator) at offset 7
+
+/((?s)blah)\s+\1/I
+Capturing subpattern count = 1
+Max back reference = 1
+Partial matching not supported
+No options
+First char = 'b'
+Need char = 'h'
+
+/((?i)blah)\s+\1/I
+Capturing subpattern count = 1
+Max back reference = 1
+Partial matching not supported
+No options
+First char = 'b' (caseless)
+Need char = 'h' (caseless)
+
+/((?i)b)/IDZS
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ 01 Opt
+ NC b
+ Ket
+ 00 Opt
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+No options
+First char = 'b' (caseless)
+No need char
+Study returned NULL
+
+/(a*b|(?i:c*(?-i)d))/IS
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+Starting byte set: C a b c d
+
+/a$/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+No need char
+ a
+ 0: a
+ a\n
+ 0: a
+ *** Failers
+No match
+ \Za
+No match
+ \Za\n
+No match
+
+/a$/Im
+Capturing subpattern count = 0
+Options: multiline
+First char = 'a'
+No need char
+ a
+ 0: a
+ a\n
+ 0: a
+ \Za\n
+ 0: a
+ *** Failers
+No match
+ \Za
+No match
+
+/\Aabc/Im
+Capturing subpattern count = 0
+Options: anchored multiline
+No first char
+No need char
+
+/^abc/Im
+Capturing subpattern count = 0
+Options: multiline
+First char at start or follows newline
+Need char = 'c'
+
+/^((a+)(?U)([ab]+)(?-U)([bc]+)(\w*))/I
+Capturing subpattern count = 5
+Partial matching not supported
+Options: anchored
+No first char
+No need char
+ aaaaabbbbbcccccdef
+ 0: aaaaabbbbbcccccdef
+ 1: aaaaabbbbbcccccdef
+ 2: aaaaa
+ 3: b
+ 4: bbbbccccc
+ 5: def
+
+/(?<=foo)[ab]/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: a b
+
+/(?<!foo)(alpha|omega)/IS
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'a'
+Starting byte set: a o
+
+/(?!alphabet)[ab]/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: a b
+
+/(?<=foo\n)^bar/Im
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+Options: multiline
+No first char
+Need char = 'r'
+ foo\nbarbar
+ 0: bar
+ ***Failers
+No match
+ rhubarb
+No match
+ barbell
+No match
+ abc\nbarton
+No match
+
+/^(?<=foo\n)bar/Im
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+Options: multiline
+First char at start or follows newline
+Need char = 'r'
+ foo\nbarbar
+ 0: bar
+ ***Failers
+No match
+ rhubarb
+No match
+ barbell
+No match
+ abc\nbarton
+No match
+
+/(?>^abc)/Im
+Capturing subpattern count = 0
+Options: multiline
+First char at start or follows newline
+Need char = 'c'
+ abc
+ 0: abc
+ def\nabc
+ 0: abc
+ *** Failers
+No match
+ defabc
+No match
+
+/(?<=ab(c+)d)ef/
+Failed: lookbehind assertion is not fixed length at offset 11
+
+/(?<=ab(?<=c+)d)ef/
+Failed: lookbehind assertion is not fixed length at offset 12
+
+/(?<=ab(c|de)f)g/
+Failed: lookbehind assertion is not fixed length at offset 13
+
+/The next three are in testinput2 because they have variable length branches/
+
+/(?<=bullock|donkey)-cart/I
+Capturing subpattern count = 0
+No options
+First char = '-'
+Need char = 't'
+ the bullock-cart
+ 0: -cart
+ a donkey-cart race
+ 0: -cart
+ *** Failers
+No match
+ cart
+No match
+ horse-and-cart
+No match
+
+/(?<=ab(?i)x|y|z)/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/(?>.*)(?<=(abcd)|(xyz))/I
+Capturing subpattern count = 2
+Partial matching not supported
+No options
+First char at start or follows newline
+No need char
+ alphabetabcd
+ 0: alphabetabcd
+ 1: abcd
+ endingxyz
+ 0: endingxyz
+ 1: <unset>
+ 2: xyz
+
+/(?<=ab(?i)x(?-i)y|(?i)z|b)ZZ/I
+Capturing subpattern count = 0
+No options
+First char = 'Z'
+Need char = 'Z'
+ abxyZZ
+ 0: ZZ
+ abXyZZ
+ 0: ZZ
+ ZZZ
+ 0: ZZ
+ zZZ
+ 0: ZZ
+ bZZ
+ 0: ZZ
+ BZZ
+ 0: ZZ
+ *** Failers
+No match
+ ZZ
+No match
+ abXYZZ
+No match
+ zzz
+No match
+ bzz
+No match
+
+/(?<!(foo)a)bar/I
+Capturing subpattern count = 1
+No options
+First char = 'b'
+Need char = 'r'
+ bar
+ 0: bar
+ foobbar
+ 0: bar
+ *** Failers
+No match
+ fooabar
+No match
+
+/This one is here because Perl 5.005_02 doesn't fail it/I
+Capturing subpattern count = 0
+No options
+First char = 'T'
+Need char = 't'
+
+/^(a)?(?(1)a|b)+$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ *** Failers
+No match
+ a
+No match
+
+/This one is here because I think Perl 5.005_02 gets the setting of $1 wrong/I
+Capturing subpattern count = 0
+No options
+First char = 'T'
+Need char = 'g'
+
+/^(a\1?){4}$/I
+Capturing subpattern count = 1
+Max back reference = 1
+Options: anchored
+No first char
+No need char
+ aaaaaa
+ 0: aaaaaa
+ 1: aa
+
+/These are syntax tests from Perl 5.005/I
+Capturing subpattern count = 0
+No options
+First char = 'T'
+Need char = '5'
+
+/a[b-a]/
+Failed: range out of order in character class at offset 4
+
+/a[]b/
+Failed: missing terminating ] for character class at offset 4
+
+/a[/
+Failed: missing terminating ] for character class at offset 2
+
+/*a/
+Failed: nothing to repeat at offset 0
+
+/(*)b/
+Failed: nothing to repeat at offset 1
+
+/abc)/
+Failed: unmatched parentheses at offset 3
+
+/(abc/
+Failed: missing ) at offset 4
+
+/a**/
+Failed: nothing to repeat at offset 2
+
+/)(/
+Failed: unmatched parentheses at offset 0
+
+/\1/
+Failed: reference to non-existent subpattern at offset 2
+
+/\2/
+Failed: reference to non-existent subpattern at offset 2
+
+/(a)|\2/
+Failed: reference to non-existent subpattern at offset 6
+
+/a[b-a]/Ii
+Failed: range out of order in character class at offset 4
+
+/a[]b/Ii
+Failed: missing terminating ] for character class at offset 4
+
+/a[/Ii
+Failed: missing terminating ] for character class at offset 2
+
+/*a/Ii
+Failed: nothing to repeat at offset 0
+
+/(*)b/Ii
+Failed: nothing to repeat at offset 1
+
+/abc)/Ii
+Failed: unmatched parentheses at offset 3
+
+/(abc/Ii
+Failed: missing ) at offset 4
+
+/a**/Ii
+Failed: nothing to repeat at offset 2
+
+/)(/Ii
+Failed: unmatched parentheses at offset 0
+
+/:(?:/
+Failed: missing ) at offset 4
+
+/(?<%)b/
+Failed: unrecognized character after (?< at offset 3
+
+/a(?{)b/
+Failed: unrecognized character after (? or (?- at offset 3
+
+/a(?{{})b/
+Failed: unrecognized character after (? or (?- at offset 3
+
+/a(?{}})b/
+Failed: unrecognized character after (? or (?- at offset 3
+
+/a(?{"{"})b/
+Failed: unrecognized character after (? or (?- at offset 3
+
+/a(?{"{"}})b/
+Failed: unrecognized character after (? or (?- at offset 3
+
+/(?(1?)a|b)/
+Failed: malformed number or name after (?( at offset 4
+
+/[a[:xyz:/
+Failed: missing terminating ] for character class at offset 8
+
+/(?<=x+)y/
+Failed: lookbehind assertion is not fixed length at offset 6
+
+/a{37,17}/
+Failed: numbers out of order in {} quantifier at offset 7
+
+/abc/\
+Failed: \ at end of pattern at offset 4
+
+/abc/\P
+Failed: POSIX code 9: bad escape sequence at offset 4
+
+/abc/\i
+Failed: \ at end of pattern at offset 4
+
+/(a)bc(d)/I
+Capturing subpattern count = 2
+No options
+First char = 'a'
+Need char = 'd'
+ abcd
+ 0: abcd
+ 1: a
+ 2: d
+ abcd\C2
+ 0: abcd
+ 1: a
+ 2: d
+ 2C d (1)
+ abcd\C5
+ 0: abcd
+ 1: a
+ 2: d
+copy substring 5 failed -7
+
+/(.{20})/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+ abcdefghijklmnopqrstuvwxyz
+ 0: abcdefghijklmnopqrst
+ 1: abcdefghijklmnopqrst
+ abcdefghijklmnopqrstuvwxyz\C1
+ 0: abcdefghijklmnopqrst
+ 1: abcdefghijklmnopqrst
+ 1C abcdefghijklmnopqrst (20)
+ abcdefghijklmnopqrstuvwxyz\G1
+ 0: abcdefghijklmnopqrst
+ 1: abcdefghijklmnopqrst
+ 1G abcdefghijklmnopqrst (20)
+
+/(.{15})/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+ abcdefghijklmnopqrstuvwxyz
+ 0: abcdefghijklmno
+ 1: abcdefghijklmno
+ abcdefghijklmnopqrstuvwxyz\C1\G1
+ 0: abcdefghijklmno
+ 1: abcdefghijklmno
+ 1C abcdefghijklmno (15)
+ 1G abcdefghijklmno (15)
+
+/(.{16})/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+ abcdefghijklmnopqrstuvwxyz
+ 0: abcdefghijklmnop
+ 1: abcdefghijklmnop
+ abcdefghijklmnopqrstuvwxyz\C1\G1\L
+ 0: abcdefghijklmnop
+ 1: abcdefghijklmnop
+ 1C abcdefghijklmnop (16)
+ 1G abcdefghijklmnop (16)
+ 0L abcdefghijklmnop
+ 1L abcdefghijklmnop
+
+/^(a|(bc))de(f)/I
+Capturing subpattern count = 3
+Options: anchored
+No first char
+No need char
+ adef\G1\G2\G3\G4\L
+ 0: adef
+ 1: a
+ 2: <unset>
+ 3: f
+ 1G a (1)
+ 2G (0)
+ 3G f (1)
+get substring 4 failed -7
+ 0L adef
+ 1L a
+ 2L
+ 3L f
+ bcdef\G1\G2\G3\G4\L
+ 0: bcdef
+ 1: bc
+ 2: bc
+ 3: f
+ 1G bc (2)
+ 2G bc (2)
+ 3G f (1)
+get substring 4 failed -7
+ 0L bcdef
+ 1L bc
+ 2L bc
+ 3L f
+ adefghijk\C0
+ 0: adef
+ 1: a
+ 2: <unset>
+ 3: f
+ 0C adef (4)
+
+/^abc\00def/I
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+ abc\00def\L\C0
+ 0: abc\x00def
+ 0C abc (7)
+ 0L abc
+
+/word ((?:[a-zA-Z0-9]+ )((?:[a-zA-Z0-9]+ )((?:[a-zA-Z0-9]+ )((?:[a-zA-Z0-9]+
+)((?:[a-zA-Z0-9]+ )((?:[a-zA-Z0-9]+ )((?:[a-zA-Z0-9]+ )((?:[a-zA-Z0-9]+
+)?)?)?)?)?)?)?)?)?otherword/I
+Capturing subpattern count = 8
+Partial matching not supported
+Contains explicit CR or LF match
+No options
+First char = 'w'
+Need char = 'd'
+
+/.*X/IDZ
+------------------------------------------------------------------
+ Bra
+ Any*
+ X
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char at start or follows newline
+Need char = 'X'
+
+/.*X/IDZs
+------------------------------------------------------------------
+ Bra
+ Any*
+ X
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored dotall
+No first char
+Need char = 'X'
+
+/(.*X|^B)/IDZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ Any*
+ X
+ Alt
+ ^
+ B
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+First char at start or follows newline
+No need char
+
+/(.*X|^B)/IDZs
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ Any*
+ X
+ Alt
+ ^
+ B
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options: anchored dotall
+No first char
+No need char
+
+/(?s)(.*X|^B)/IDZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ Any*
+ X
+ Alt
+ ^
+ B
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options: anchored dotall
+No first char
+No need char
+
+/(?s:.*X|^B)/IDZ
+------------------------------------------------------------------
+ Bra
+ Bra
+ 04 Opt
+ Any*
+ X
+ Alt
+ 04 Opt
+ ^
+ B
+ Ket
+ 00 Opt
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char at start or follows newline
+No need char
+
+/\Biss\B/I+
+Capturing subpattern count = 0
+No options
+First char = 'i'
+Need char = 's'
+ Mississippi
+ 0: iss
+ 0+ issippi
+
+/\Biss\B/I+P
+ Mississippi
+ 0: iss
+ 0+ issippi
+
+/iss/IG+
+Capturing subpattern count = 0
+No options
+First char = 'i'
+Need char = 's'
+ Mississippi
+ 0: iss
+ 0+ issippi
+ 0: iss
+ 0+ ippi
+
+/\Biss\B/IG+
+Capturing subpattern count = 0
+No options
+First char = 'i'
+Need char = 's'
+ Mississippi
+ 0: iss
+ 0+ issippi
+
+/\Biss\B/Ig+
+Capturing subpattern count = 0
+No options
+First char = 'i'
+Need char = 's'
+ Mississippi
+ 0: iss
+ 0+ issippi
+ 0: iss
+ 0+ ippi
+ *** Failers
+No match
+ Mississippi\A
+No match
+
+/(?<=[Ms])iss/Ig+
+Capturing subpattern count = 0
+No options
+First char = 'i'
+Need char = 's'
+ Mississippi
+ 0: iss
+ 0+ issippi
+ 0: iss
+ 0+ ippi
+
+/(?<=[Ms])iss/IG+
+Capturing subpattern count = 0
+No options
+First char = 'i'
+Need char = 's'
+ Mississippi
+ 0: iss
+ 0+ issippi
+
+/^iss/Ig+
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+ ississippi
+ 0: iss
+ 0+ issippi
+
+/.*iss/Ig+
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char at start or follows newline
+Need char = 's'
+ abciss\nxyzisspqr
+ 0: abciss
+ 0+ \x0axyzisspqr
+ 0: xyziss
+ 0+ pqr
+
+/.i./I+g
+Capturing subpattern count = 0
+No options
+No first char
+Need char = 'i'
+ Mississippi
+ 0: Mis
+ 0+ sissippi
+ 0: sis
+ 0+ sippi
+ 0: sip
+ 0+ pi
+ Mississippi\A
+ 0: Mis
+ 0+ sissippi
+ 0: sis
+ 0+ sippi
+ 0: sip
+ 0+ pi
+ Missouri river
+ 0: Mis
+ 0+ souri river
+ 0: ri
+ 0+ river
+ 0: riv
+ 0+ er
+ Missouri river\A
+ 0: Mis
+ 0+ souri river
+
+/^.is/I+g
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+ Mississippi
+ 0: Mis
+ 0+ sissippi
+
+/^ab\n/Ig+
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+Options: anchored
+No first char
+No need char
+ ab\nab\ncd
+ 0: ab\x0a
+ 0+ ab\x0acd
+
+/^ab\n/Img+
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+Options: multiline
+First char at start or follows newline
+Need char = 10
+ ab\nab\ncd
+ 0: ab\x0a
+ 0+ ab\x0acd
+ 0: ab\x0a
+ 0+ cd
+
+/abc/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+
+/abc|bac/I
+Capturing subpattern count = 0
+No options
+No first char
+Need char = 'c'
+
+/(abc|bac)/I
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'c'
+
+/(abc|(c|dc))/I
+Capturing subpattern count = 2
+No options
+No first char
+Need char = 'c'
+
+/(abc|(d|de)c)/I
+Capturing subpattern count = 2
+No options
+No first char
+Need char = 'c'
+
+/a*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+
+/a+/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/(baa|a+)/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+Need char = 'a'
+
+/a{0,3}/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+
+/baa{3,}/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'b'
+Need char = 'a'
+
+/"([^\\"]+|\\.)*"/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+First char = '"'
+Need char = '"'
+
+/(abc|ab[cd])/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/(a|.)/I
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+
+/a|ba|\w/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/abc(?=pqr)/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'r'
+
+/...(?<=abc)/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/abc(?!pqr)/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+
+/ab./I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'b'
+
+/ab[xyz]/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'b'
+
+/abc*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+
+/ab.c*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+
+/a.c*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/.c*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+
+/ac*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/(a.c*|b.c*)/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+
+/a.c*|aba/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/.+a/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = 'a'
+
+/(?=abcda)a.*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'a'
+
+/(?=a)a.*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/a(b)*/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/a\d*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/ab\d*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+
+/a(\d)*/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/abcde{0,0}/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'd'
+
+/ab\d+/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+
+/a(?(1)b)(.)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/a(?(1)bag|big)(.)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'g'
+
+/a(?(1)bag|big)*(.)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/a(?(1)bag|big)+(.)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'g'
+
+/a(?(1)b..|b..)(.)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'b'
+
+/ab\d{0}e/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'e'
+
+/a?b?/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+ a
+ 0: a
+ b
+ 0: b
+ ab
+ 0: ab
+ \
+ 0:
+ *** Failers
+ 0:
+ \N
+No match
+
+/|-/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+ abcd
+ 0:
+ -abc
+ 0:
+ \Nab-c
+ 0: -
+ *** Failers
+ 0:
+ \Nabc
+No match
+
+/a*(b+)(z)(z)/IP
+ aaaabbbbzzzz
+ 0: aaaabbbbzz
+ 1: bbbb
+ 2: z
+ 3: z
+ aaaabbbbzzzz\O0
+ aaaabbbbzzzz\O1
+ 0: aaaabbbbzz
+ aaaabbbbzzzz\O2
+ 0: aaaabbbbzz
+ 1: bbbb
+ aaaabbbbzzzz\O3
+ 0: aaaabbbbzz
+ 1: bbbb
+ 2: z
+ aaaabbbbzzzz\O4
+ 0: aaaabbbbzz
+ 1: bbbb
+ 2: z
+ 3: z
+ aaaabbbbzzzz\O5
+ 0: aaaabbbbzz
+ 1: bbbb
+ 2: z
+ 3: z
+
+/^.?abcd/IS
+Capturing subpattern count = 0
+Options: anchored
+No first char
+Need char = 'd'
+Study returned NULL
+
+/\( # ( at start
+ (?: # Non-capturing bracket
+ (?>[^()]+) # Either a sequence of non-brackets (no backtracking)
+ | # Or
+ (?R) # Recurse - i.e. nested bracketed string
+ )* # Zero or more contents
+ \) # Closing )
+ /Ix
+Capturing subpattern count = 0
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (abcd)
+ 0: (abcd)
+ (abcd)xyz
+ 0: (abcd)
+ xyz(abcd)
+ 0: (abcd)
+ (ab(xy)cd)pqr
+ 0: (ab(xy)cd)
+ (ab(xycd)pqr
+ 0: (xycd)
+ () abc ()
+ 0: ()
+ 12(abcde(fsh)xyz(foo(bar))lmno)89
+ 0: (abcde(fsh)xyz(foo(bar))lmno)
+ *** Failers
+No match
+ abcd
+No match
+ abcd)
+No match
+ (abcd
+No match
+
+/\( ( (?>[^()]+) | (?R) )* \) /Ixg
+Capturing subpattern count = 1
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (ab(xy)cd)pqr
+ 0: (ab(xy)cd)
+ 1: cd
+ 1(abcd)(x(y)z)pqr
+ 0: (abcd)
+ 1: abcd
+ 0: (x(y)z)
+ 1: z
+
+/\( (?: (?>[^()]+) | (?R) ) \) /Ix
+Capturing subpattern count = 0
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (abcd)
+ 0: (abcd)
+ (ab(xy)cd)
+ 0: (xy)
+ (a(b(c)d)e)
+ 0: (c)
+ ((ab))
+ 0: ((ab))
+ *** Failers
+No match
+ ()
+No match
+
+/\( (?: (?>[^()]+) | (?R) )? \) /Ix
+Capturing subpattern count = 0
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ ()
+ 0: ()
+ 12(abcde(fsh)xyz(foo(bar))lmno)89
+ 0: (fsh)
+
+/\( ( (?>[^()]+) | (?R) )* \) /Ix
+Capturing subpattern count = 1
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (ab(xy)cd)
+ 0: (ab(xy)cd)
+ 1: cd
+
+/\( ( ( (?>[^()]+) | (?R) )* ) \) /Ix
+Capturing subpattern count = 2
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (ab(xy)cd)
+ 0: (ab(xy)cd)
+ 1: ab(xy)cd
+ 2: cd
+
+/\( (123)? ( ( (?>[^()]+) | (?R) )* ) \) /Ix
+Capturing subpattern count = 3
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (ab(xy)cd)
+ 0: (ab(xy)cd)
+ 1: <unset>
+ 2: ab(xy)cd
+ 3: cd
+ (123ab(xy)cd)
+ 0: (123ab(xy)cd)
+ 1: 123
+ 2: ab(xy)cd
+ 3: cd
+
+/\( ( (123)? ( (?>[^()]+) | (?R) )* ) \) /Ix
+Capturing subpattern count = 3
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (ab(xy)cd)
+ 0: (ab(xy)cd)
+ 1: ab(xy)cd
+ 2: <unset>
+ 3: cd
+ (123ab(xy)cd)
+ 0: (123ab(xy)cd)
+ 1: 123ab(xy)cd
+ 2: 123
+ 3: cd
+
+/\( (((((((((( ( (?>[^()]+) | (?R) )* )))))))))) \) /Ix
+Capturing subpattern count = 11
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (ab(xy)cd)
+ 0: (ab(xy)cd)
+ 1: ab(xy)cd
+ 2: ab(xy)cd
+ 3: ab(xy)cd
+ 4: ab(xy)cd
+ 5: ab(xy)cd
+ 6: ab(xy)cd
+ 7: ab(xy)cd
+ 8: ab(xy)cd
+ 9: ab(xy)cd
+10: ab(xy)cd
+11: cd
+
+/\( ( ( (?>[^()<>]+) | ((?>[^()]+)) | (?R) )* ) \) /Ix
+Capturing subpattern count = 3
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (abcd(xyz<p>qrs)123)
+ 0: (abcd(xyz<p>qrs)123)
+ 1: abcd(xyz<p>qrs)123
+ 2: 123
+ 3: <unset>
+
+/\( ( ( (?>[^()]+) | ((?R)) )* ) \) /Ix
+Capturing subpattern count = 3
+Partial matching not supported
+Options: extended
+First char = '('
+Need char = ')'
+ (ab(cd)ef)
+ 0: (ab(cd)ef)
+ 1: ab(cd)ef
+ 2: ef
+ 3: (cd)
+ (ab(cd(ef)gh)ij)
+ 0: (ab(cd(ef)gh)ij)
+ 1: ab(cd(ef)gh)ij
+ 2: ij
+ 3: (cd(ef)gh)
+
+/^[[:alnum:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [0-9A-Za-z]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:^alnum:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-/:-@[-`{-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:alpha:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [A-Za-z]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:^alpha:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-@[-`{-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/[_[:alpha:]]/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+ _ a b c d e f g h i j k l m n o p q r s t u v w x y z
+
+/^[[:ascii:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-\x7f]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:^ascii:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x80-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:blank:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x09 ]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:^blank:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-\x08\x0a-\x1f!-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/[\n\x0b\x0c\x0d[:blank:]]/IS
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+No options
+No first char
+No need char
+Starting byte set: \x09 \x0a \x0b \x0c \x0d \x20
+
+/^[[:cntrl:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-\x1f\x7f]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:digit:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [0-9]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:graph:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [!-~]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:lower:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [a-z]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:print:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [ -~]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:punct:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [!-/:-@[-`{-~]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:space:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x09-\x0d ]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:upper:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [A-Z]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:xdigit:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [0-9A-Fa-f]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:word:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [0-9A-Z_a-z]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:^cntrl:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [ -~\x80-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[12[:^digit:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-/12:-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/^[[:^blank:]]/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-\x08\x0a-\x1f!-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/[01[:alpha:]%]/DZ
+------------------------------------------------------------------
+ Bra
+ [%01A-Za-z]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[[.ch.]]/I
+Failed: POSIX collating elements are not supported at offset 1
+
+/[[=ch=]]/I
+Failed: POSIX collating elements are not supported at offset 1
+
+/[[:rhubarb:]]/I
+Failed: unknown POSIX class name at offset 3
+
+/[[:upper:]]/Ii
+Capturing subpattern count = 0
+Options: caseless
+No first char
+No need char
+ A
+ 0: A
+ a
+ 0: a
+
+/[[:lower:]]/Ii
+Capturing subpattern count = 0
+Options: caseless
+No first char
+No need char
+ A
+ 0: A
+ a
+ 0: a
+
+/((?-i)[[:lower:]])[[:lower:]]/Ii
+Capturing subpattern count = 1
+Options: caseless
+No first char
+No need char
+ ab
+ 0: ab
+ 1: a
+ aB
+ 0: aB
+ 1: a
+ *** Failers
+ 0: ai
+ 1: a
+ Ab
+No match
+ AB
+No match
+
+/[\200-\110]/I
+Failed: range out of order in character class at offset 9
+
+/^(?(0)f|b)oo/I
+Failed: invalid condition (?(0) at offset 6
+
+/This one's here because of the large output vector needed/I
+Capturing subpattern count = 0
+No options
+First char = 'T'
+Need char = 'd'
+
+/(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\d+(?:\s|$))(\w+)\s+(\270)/I
+Capturing subpattern count = 271
+Max back reference = 270
+Partial matching not supported
+No options
+No first char
+No need char
+ \O900 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 ABC ABC
+ 0: 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 ABC ABC
+ 1: 1
+ 2: 2
+ 3: 3
+ 4: 4
+ 5: 5
+ 6: 6
+ 7: 7
+ 8: 8
+ 9: 9
+10: 10
+11: 11
+12: 12
+13: 13
+14: 14
+15: 15
+16: 16
+17: 17
+18: 18
+19: 19
+20: 20
+21: 21
+22: 22
+23: 23
+24: 24
+25: 25
+26: 26
+27: 27
+28: 28
+29: 29
+30: 30
+31: 31
+32: 32
+33: 33
+34: 34
+35: 35
+36: 36
+37: 37
+38: 38
+39: 39
+40: 40
+41: 41
+42: 42
+43: 43
+44: 44
+45: 45
+46: 46
+47: 47
+48: 48
+49: 49
+50: 50
+51: 51
+52: 52
+53: 53
+54: 54
+55: 55
+56: 56
+57: 57
+58: 58
+59: 59
+60: 60
+61: 61
+62: 62
+63: 63
+64: 64
+65: 65
+66: 66
+67: 67
+68: 68
+69: 69
+70: 70
+71: 71
+72: 72
+73: 73
+74: 74
+75: 75
+76: 76
+77: 77
+78: 78
+79: 79
+80: 80
+81: 81
+82: 82
+83: 83
+84: 84
+85: 85
+86: 86
+87: 87
+88: 88
+89: 89
+90: 90
+91: 91
+92: 92
+93: 93
+94: 94
+95: 95
+96: 96
+97: 97
+98: 98
+99: 99
+100: 100
+101: 101
+102: 102
+103: 103
+104: 104
+105: 105
+106: 106
+107: 107
+108: 108
+109: 109
+110: 110
+111: 111
+112: 112
+113: 113
+114: 114
+115: 115
+116: 116
+117: 117
+118: 118
+119: 119
+120: 120
+121: 121
+122: 122
+123: 123
+124: 124
+125: 125
+126: 126
+127: 127
+128: 128
+129: 129
+130: 130
+131: 131
+132: 132
+133: 133
+134: 134
+135: 135
+136: 136
+137: 137
+138: 138
+139: 139
+140: 140
+141: 141
+142: 142
+143: 143
+144: 144
+145: 145
+146: 146
+147: 147
+148: 148
+149: 149
+150: 150
+151: 151
+152: 152
+153: 153
+154: 154
+155: 155
+156: 156
+157: 157
+158: 158
+159: 159
+160: 160
+161: 161
+162: 162
+163: 163
+164: 164
+165: 165
+166: 166
+167: 167
+168: 168
+169: 169
+170: 170
+171: 171
+172: 172
+173: 173
+174: 174
+175: 175
+176: 176
+177: 177
+178: 178
+179: 179
+180: 180
+181: 181
+182: 182
+183: 183
+184: 184
+185: 185
+186: 186
+187: 187
+188: 188
+189: 189
+190: 190
+191: 191
+192: 192
+193: 193
+194: 194
+195: 195
+196: 196
+197: 197
+198: 198
+199: 199
+200: 200
+201: 201
+202: 202
+203: 203
+204: 204
+205: 205
+206: 206
+207: 207
+208: 208
+209: 209
+210: 210
+211: 211
+212: 212
+213: 213
+214: 214
+215: 215
+216: 216
+217: 217
+218: 218
+219: 219
+220: 220
+221: 221
+222: 222
+223: 223
+224: 224
+225: 225
+226: 226
+227: 227
+228: 228
+229: 229
+230: 230
+231: 231
+232: 232
+233: 233
+234: 234
+235: 235
+236: 236
+237: 237
+238: 238
+239: 239
+240: 240
+241: 241
+242: 242
+243: 243
+244: 244
+245: 245
+246: 246
+247: 247
+248: 248
+249: 249
+250: 250
+251: 251
+252: 252
+253: 253
+254: 254
+255: 255
+256: 256
+257: 257
+258: 258
+259: 259
+260: 260
+261: 261
+262: 262
+263: 263
+264: 264
+265: 265
+266: 266
+267: 267
+268: 268
+269: 269
+270: ABC
+271: ABC
+
+/This one's here because Perl does this differently and PCRE can't at present/I
+Capturing subpattern count = 0
+No options
+First char = 'T'
+Need char = 't'
+
+/(main(O)?)+/I
+Capturing subpattern count = 2
+No options
+First char = 'm'
+Need char = 'n'
+ mainmain
+ 0: mainmain
+ 1: main
+ mainOmain
+ 0: mainOmain
+ 1: main
+ 2: O
+
+/These are all cases where Perl does it differently (nested captures)/I
+Capturing subpattern count = 1
+No options
+First char = 'T'
+Need char = 's'
+
+/^(a(b)?)+$/I
+Capturing subpattern count = 2
+Options: anchored
+No first char
+No need char
+ aba
+ 0: aba
+ 1: a
+ 2: b
+
+/^(aa(bb)?)+$/I
+Capturing subpattern count = 2
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: aa
+ 2: bb
+
+/^(aa|aa(bb))+$/I
+Capturing subpattern count = 2
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: aa
+ 2: bb
+
+/^(aa(bb)??)+$/I
+Capturing subpattern count = 2
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: aa
+ 2: bb
+
+/^(?:aa(bb)?)+$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: bb
+
+/^(aa(b(b))?)+$/I
+Capturing subpattern count = 3
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: aa
+ 2: bb
+ 3: b
+
+/^(?:aa(b(b))?)+$/I
+Capturing subpattern count = 2
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: bb
+ 2: b
+
+/^(?:aa(b(?:b))?)+$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: bb
+
+/^(?:aa(bb(?:b))?)+$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ aabbbaa
+ 0: aabbbaa
+ 1: bbb
+
+/^(?:aa(b(?:bb))?)+$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ aabbbaa
+ 0: aabbbaa
+ 1: bbb
+
+/^(?:aa(?:b(b))?)+$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ aabbaa
+ 0: aabbaa
+ 1: b
+
+/^(?:aa(?:b(bb))?)+$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ aabbbaa
+ 0: aabbbaa
+ 1: bb
+
+/^(aa(b(bb))?)+$/I
+Capturing subpattern count = 3
+Options: anchored
+No first char
+No need char
+ aabbbaa
+ 0: aabbbaa
+ 1: aa
+ 2: bbb
+ 3: bb
+
+/^(aa(bb(bb))?)+$/I
+Capturing subpattern count = 3
+Options: anchored
+No first char
+No need char
+ aabbbbaa
+ 0: aabbbbaa
+ 1: aa
+ 2: bbbb
+ 3: bb
+
+/--------------------------------------------------------------------/I
+Capturing subpattern count = 0
+No options
+First char = '-'
+Need char = '-'
+
+/#/IxDZ
+------------------------------------------------------------------
+ Bra
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: extended
+No first char
+No need char
+
+/a#/IxDZ
+------------------------------------------------------------------
+ Bra
+ a
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: extended
+First char = 'a'
+No need char
+
+/[\s]/DZ
+------------------------------------------------------------------
+ Bra
+ [\x09\x0a\x0c\x0d ]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[\S]/DZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\x08\x0b\x0e-\x1f!-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/a(?i)b/DZ
+------------------------------------------------------------------
+ Bra
+ a
+ 01 Opt
+ NC b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'b' (caseless)
+ ab
+ 0: ab
+ aB
+ 0: aB
+ *** Failers
+No match
+ AB
+No match
+
+/(a(?i)b)/DZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ a
+ 01 Opt
+ NC b
+ Ket
+ 00 Opt
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'b' (caseless)
+ ab
+ 0: ab
+ 1: ab
+ aB
+ 0: aB
+ 1: aB
+ *** Failers
+No match
+ AB
+No match
+
+/ (?i)abc/IxDZ
+------------------------------------------------------------------
+ Bra
+ NC abc
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: caseless extended
+First char = 'a' (caseless)
+Need char = 'c' (caseless)
+
+/#this is a comment
+ (?i)abc/IxDZ
+------------------------------------------------------------------
+ Bra
+ NC abc
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: caseless extended
+First char = 'a' (caseless)
+Need char = 'c' (caseless)
+
+/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/DZ
+------------------------------------------------------------------
+ Bra
+ 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = '1'
+Need char = '0'
+
+/\Q123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890/DZ
+------------------------------------------------------------------
+ Bra
+ 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = '1'
+Need char = '0'
+
+/\Q\E/DZ
+------------------------------------------------------------------
+ Bra
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+ \
+ 0:
+
+/\Q\Ex/DZ
+------------------------------------------------------------------
+ Bra
+ x
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = 'x'
+No need char
+
+/ \Q\E/DZ
+------------------------------------------------------------------
+ Bra
+
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = ' '
+No need char
+
+/a\Q\E/DZ
+------------------------------------------------------------------
+ Bra
+ a
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = 'a'
+No need char
+ abc
+ 0: a
+ bca
+ 0: a
+ bac
+ 0: a
+
+/a\Q\Eb/DZ
+------------------------------------------------------------------
+ Bra
+ ab
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'b'
+ abc
+ 0: ab
+
+/\Q\Eabc/DZ
+------------------------------------------------------------------
+ Bra
+ abc
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+
+/x*+\w/DZ
+------------------------------------------------------------------
+ Bra
+ x*+
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ *** Failers
+ 0: F
+ xxxxx
+No match
+
+/x?+/DZ
+------------------------------------------------------------------
+ Bra
+ x?+
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/x++/DZ
+------------------------------------------------------------------
+ Bra
+ x++
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'x'
+No need char
+
+/x{1,3}+/DZ
+------------------------------------------------------------------
+ Bra
+ Once
+ x
+ x{0,2}
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'x'
+No need char
+
+/(x)*+/DZ
+------------------------------------------------------------------
+ Bra
+ Once
+ Brazero
+ CBra 1
+ x
+ KetRmax
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+
+/^(\w++|\s++)*$/I
+Capturing subpattern count = 1
+Partial matching not supported
+Options: anchored
+No first char
+No need char
+ now is the time for all good men to come to the aid of the party
+ 0: now is the time for all good men to come to the aid of the party
+ 1: party
+ *** Failers
+No match
+ this is not a line with only words and spaces!
+No match
+
+/(\d++)(\w)/I
+Capturing subpattern count = 2
+Partial matching not supported
+No options
+No first char
+No need char
+ 12345a
+ 0: 12345a
+ 1: 12345
+ 2: a
+ *** Failers
+No match
+ 12345+
+No match
+
+/a++b/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+ aaab
+ 0: aaab
+
+/(a++b)/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+ aaab
+ 0: aaab
+ 1: aaab
+
+/(a++)b/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+ aaab
+ 0: aaab
+ 1: aaa
+
+/([^()]++|\([^()]*\))+/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+ ((abc(ade)ufh()()x
+ 0: abc(ade)ufh()()x
+ 1: x
+
+/\(([^()]++|\([^()]+\))+\)/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+First char = '('
+Need char = ')'
+ (abc)
+ 0: (abc)
+ 1: abc
+ (abc(def)xyz)
+ 0: (abc(def)xyz)
+ 1: xyz
+ *** Failers
+No match
+ ((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/(abc){1,3}+/DZ
+------------------------------------------------------------------
+ Bra
+ Once
+ CBra 1
+ abc
+ Ket
+ Brazero
+ Bra
+ CBra 1
+ abc
+ Ket
+ Brazero
+ CBra 1
+ abc
+ Ket
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'c'
+
+/a+?+/I
+Failed: nothing to repeat at offset 3
+
+/a{2,3}?+b/I
+Failed: nothing to repeat at offset 7
+
+/(?U)a+?+/I
+Failed: nothing to repeat at offset 7
+
+/a{2,3}?+b/IU
+Failed: nothing to repeat at offset 7
+
+/x(?U)a++b/DZ
+------------------------------------------------------------------
+ Bra
+ x
+ a++
+ b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'x'
+Need char = 'b'
+ xaaaab
+ 0: xaaaab
+
+/(?U)xa++b/DZ
+------------------------------------------------------------------
+ Bra
+ x
+ a++
+ b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: ungreedy
+First char = 'x'
+Need char = 'b'
+ xaaaab
+ 0: xaaaab
+
+/^((a+)(?U)([ab]+)(?-U)([bc]+)(\w*))/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ CBra 1
+ CBra 2
+ a+
+ Ket
+ CBra 3
+ [ab]+?
+ Ket
+ CBra 4
+ [bc]+
+ Ket
+ CBra 5
+ \w*
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 5
+Partial matching not supported
+Options: anchored
+No first char
+No need char
+
+/^x(?U)a+b/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ x
+ a++
+ b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored
+No first char
+Need char = 'b'
+
+/^x(?U)(a+)b/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ x
+ CBra 1
+ a+?
+ Ket
+ b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options: anchored
+No first char
+Need char = 'b'
+
+/[.x.]/I
+Failed: POSIX collating elements are not supported at offset 0
+
+/[=x=]/I
+Failed: POSIX collating elements are not supported at offset 0
+
+/[:x:]/I
+Failed: POSIX named classes are supported only within a class at offset 0
+
+/\l/I
+Failed: PCRE does not support \L, \l, \N, \U, or \u at offset 1
+
+/\L/I
+Failed: PCRE does not support \L, \l, \N, \U, or \u at offset 1
+
+/\N{name}/I
+Failed: PCRE does not support \L, \l, \N, \U, or \u at offset 1
+
+/\u/I
+Failed: PCRE does not support \L, \l, \N, \U, or \u at offset 1
+
+/\U/I
+Failed: PCRE does not support \L, \l, \N, \U, or \u at offset 1
+
+/[/I
+Failed: missing terminating ] for character class at offset 1
+
+/[a-/I
+Failed: missing terminating ] for character class at offset 3
+
+/[[:space:]/I
+Failed: missing terminating ] for character class at offset 10
+
+/[\s]/IDZ
+------------------------------------------------------------------
+ Bra
+ [\x09\x0a\x0c\x0d ]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[[:space:]]/IDZ
+------------------------------------------------------------------
+ Bra
+ [\x09-\x0d ]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[[:space:]abcde]/IDZ
+------------------------------------------------------------------
+ Bra
+ [\x09-\x0d a-e]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/< (?: (?(R) \d++ | [^<>]*+) | (?R)) * >/Ix
+Capturing subpattern count = 0
+Partial matching not supported
+Options: extended
+First char = '<'
+Need char = '>'
+ <>
+ 0: <>
+ <abcd>
+ 0: <abcd>
+ <abc <123> hij>
+ 0: <abc <123> hij>
+ <abc <def> hij>
+ 0: <def>
+ <abc<>def>
+ 0: <abc<>def>
+ <abc<>
+ 0: <>
+ *** Failers
+No match
+ <abc
+No match
+
+|8J\$WE\<\.rX\+ix\[d1b\!H\#\?vV0vrK\:ZH1\=2M\>iV\;\?aPhFB\<\*vW\@QW\@sO9\}cfZA\-i\'w\%hKd6gt1UJP\,15_\#QY\$M\^Mss_U\/\]\&LK9\[5vQub\^w\[KDD\<EjmhUZ\?\.akp2dF\>qmj\;2\}YWFdYx\.Ap\]hjCPTP\(n28k\+3\;o\&WXqs\/gOXdr\$\:r\'do0\;b4c\(f_Gr\=\"\\4\)\[01T7ajQJvL\$W\~mL_sS\/4h\:x\*\[ZN\=KLs\&L5zX\/\/\>it\,o\:aU\(\;Z\>pW\&T7oP\'2K\^E\:x9\'c\[\%z\-\,64JQ5AeH_G\#KijUKghQw\^\\vea3a\?kka_G\$8\#\`\*kynsxzBLru\'\]k_\[7FrVx\}\^\=\$blx\>s\-N\%j\;D\*aZDnsw\:YKZ\%Q\.Kne9\#hP\?\+b3\(SOvL\,\^\;\&u5\@\?5C5Bhb\=m\-vEh_L15Jl\]U\)0RP6\{q\%L\^_z5E\'Dw6X\b|IDZ
+------------------------------------------------------------------
+ Bra
+ 8J$WE<.rX+ix[d1b!H#?vV0vrK:ZH1=2M>iV;?aPhFB<*vW@QW@sO9}cfZA-i'w%hKd6gt1UJP,15_#QY$M^Mss_U/]&LK9[5vQub^w[KDD<EjmhUZ?.akp2dF>qmj;2}YWFdYx.Ap]hjCPTP(n28k+3;o&WXqs/gOXdr$:r'do0;b4c(f_Gr="\4)[01T7ajQJvL$W~mL_sS/4h:x*[ZN=KLs&L5zX//>it,o:aU(;Z>pW&T7oP'2K^E:x9'c[%z-,64JQ5AeH_G#KijUKghQw^\vea3a?kka_G$8#`*kynsxzBLru']k_[7FrVx}^=$blx>s-N%j;D*aZDnsw:YKZ%Q.Kne9#hP?+b3(SOvL,^;&u5@?5C5Bhb=m-vEh_L15Jl]U)0RP6{q%L^_z5E'Dw6X
+ \b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = '8'
+Need char = 'X'
+
+|\$\<\.X\+ix\[d1b\!H\#\?vV0vrK\:ZH1\=2M\>iV\;\?aPhFB\<\*vW\@QW\@sO9\}cfZA\-i\'w\%hKd6gt1UJP\,15_\#QY\$M\^Mss_U\/\]\&LK9\[5vQub\^w\[KDD\<EjmhUZ\?\.akp2dF\>qmj\;2\}YWFdYx\.Ap\]hjCPTP\(n28k\+3\;o\&WXqs\/gOXdr\$\:r\'do0\;b4c\(f_Gr\=\"\\4\)\[01T7ajQJvL\$W\~mL_sS\/4h\:x\*\[ZN\=KLs\&L5zX\/\/\>it\,o\:aU\(\;Z\>pW\&T7oP\'2K\^E\:x9\'c\[\%z\-\,64JQ5AeH_G\#KijUKghQw\^\\vea3a\?kka_G\$8\#\`\*kynsxzBLru\'\]k_\[7FrVx\}\^\=\$blx\>s\-N\%j\;D\*aZDnsw\:YKZ\%Q\.Kne9\#hP\?\+b3\(SOvL\,\^\;\&u5\@\?5C5Bhb\=m\-vEh_L15Jl\]U\)0RP6\{q\%L\^_z5E\'Dw6X\b|IDZ
+------------------------------------------------------------------
+ Bra
+ $<.X+ix[d1b!H#?vV0vrK:ZH1=2M>iV;?aPhFB<*vW@QW@sO9}cfZA-i'w%hKd6gt1UJP,15_#QY$M^Mss_U/]&LK9[5vQub^w[KDD<EjmhUZ?.akp2dF>qmj;2}YWFdYx.Ap]hjCPTP(n28k+3;o&WXqs/gOXdr$:r'do0;b4c(f_Gr="\4)[01T7ajQJvL$W~mL_sS/4h:x*[ZN=KLs&L5zX//>it,o:aU(;Z>pW&T7oP'2K^E:x9'c[%z-,64JQ5AeH_G#KijUKghQw^\vea3a?kka_G$8#`*kynsxzBLru']k_[7FrVx}^=$blx>s-N%j;D*aZDnsw:YKZ%Q.Kne9#hP?+b3(SOvL,^;&u5@?5C5Bhb=m-vEh_L15Jl]U)0RP6{q%L^_z5E'Dw6X
+ \b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = '$'
+Need char = 'X'
+
+/(.*)\d+\1/I
+Capturing subpattern count = 1
+Max back reference = 1
+Partial matching not supported
+No options
+No first char
+No need char
+
+/(.*)\d+/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+First char at start or follows newline
+No need char
+
+/(.*)\d+\1/Is
+Capturing subpattern count = 1
+Max back reference = 1
+Partial matching not supported
+Options: dotall
+No first char
+No need char
+
+/(.*)\d+/Is
+Capturing subpattern count = 1
+Partial matching not supported
+Options: anchored dotall
+No first char
+No need char
+
+/(.*(xyz))\d+\2/I
+Capturing subpattern count = 2
+Max back reference = 2
+Partial matching not supported
+No options
+First char at start or follows newline
+Need char = 'z'
+
+/((.*))\d+\1/I
+Capturing subpattern count = 2
+Max back reference = 1
+Partial matching not supported
+No options
+No first char
+No need char
+ abc123bc
+ 0: bc123bc
+ 1: bc
+ 2: bc
+
+/a[b]/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'b'
+
+/(?=a).*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+First char = 'a'
+No need char
+
+/(?=abc).xyz/IiI
+Capturing subpattern count = 0
+Options: caseless
+First char = 'a' (caseless)
+Need char = 'z' (caseless)
+
+/(?=abc)(?i).xyz/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'z' (caseless)
+
+/(?=a)(?=b)/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+No need char
+
+/(?=.)a/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+No need char
+
+/((?=abcda)a)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'a'
+
+/((?=abcda)ab)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'b'
+
+/()a/I
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'a'
+
+/(?(1)ab|ac)(.)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/(?(1)abz|acz)(.)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'z'
+
+/(?(1)abz)(.)/I
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+
+/(?(1)abz)(1)23/I
+Capturing subpattern count = 1
+No options
+No first char
+Need char = '3'
+
+/(a)+/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+No need char
+
+/(a){2,3}/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'a'
+
+/(a)*/I
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+
+/[a]/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+No need char
+
+/[ab]/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[ab]/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: a b
+
+/[^a]/I
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/\d456/I
+Capturing subpattern count = 0
+No options
+No first char
+Need char = '6'
+
+/\d456/IS
+Capturing subpattern count = 0
+No options
+No first char
+Need char = '6'
+Starting byte set: 0 1 2 3 4 5 6 7 8 9
+
+/a^b/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'b'
+
+/^a/Im
+Capturing subpattern count = 0
+Options: multiline
+First char at start or follows newline
+Need char = 'a'
+ abcde
+ 0: a
+ xy\nabc
+ 0: a
+ *** Failers
+No match
+ xyabc
+No match
+
+/c|abc/I
+Capturing subpattern count = 0
+No options
+No first char
+Need char = 'c'
+
+/(?i)[ab]/IS
+Capturing subpattern count = 0
+Options: caseless
+No first char
+No need char
+Starting byte set: A B a b
+
+/[ab](?i)cd/IS
+Capturing subpattern count = 0
+No options
+No first char
+Need char = 'd' (caseless)
+Starting byte set: a b
+
+/abc(?C)def/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'f'
+ abcdef
+--->abcdef
+ 0 ^ ^ d
+ 0: abcdef
+ 1234abcdef
+--->1234abcdef
+ 0 ^ ^ d
+ 0: abcdef
+ *** Failers
+No match
+ abcxyz
+No match
+ abcxyzf
+--->abcxyzf
+ 0 ^ ^ d
+No match
+
+/abc(?C)de(?C1)f/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'f'
+ 123abcdef
+--->123abcdef
+ 0 ^ ^ d
+ 1 ^ ^ f
+ 0: abcdef
+
+/(?C1)\dabc(?C2)def/I
+Capturing subpattern count = 0
+No options
+No first char
+Need char = 'f'
+ 1234abcdef
+--->1234abcdef
+ 1 ^ \d
+ 1 ^ \d
+ 1 ^ \d
+ 1 ^ \d
+ 2 ^ ^ d
+ 0: 4abcdef
+ *** Failers
+No match
+ abcdef
+--->abcdef
+ 1 ^ \d
+ 1 ^ \d
+ 1 ^ \d
+ 1 ^ \d
+ 1 ^ \d
+ 1 ^ \d
+No match
+
+/(?C255)ab/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'b'
+
+/(?C256)ab/I
+Failed: number after (?C is > 255 at offset 6
+
+/(?Cab)xx/I
+Failed: closing ) for (?C expected at offset 3
+
+/(?C12vr)x/I
+Failed: closing ) for (?C expected at offset 5
+
+/abc(?C)def/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'f'
+ *** Failers
+No match
+ \x83\x0\x61bcdef
+--->\x83\x00abcdef
+ 0 ^ ^ d
+ 0: abcdef
+
+/(abc)(?C)de(?C1)f/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'f'
+ 123abcdef
+--->123abcdef
+ 0 ^ ^ d
+ 1 ^ ^ f
+ 0: abcdef
+ 1: abc
+ 123abcdef\C+
+Callout 0: last capture = 1
+ 0: <unset>
+ 1: abc
+--->123abcdef
+ ^ ^ d
+Callout 1: last capture = 1
+ 0: <unset>
+ 1: abc
+--->123abcdef
+ ^ ^ f
+ 0: abcdef
+ 1: abc
+ 123abcdef\C-
+ 0: abcdef
+ 1: abc
+ *** Failers
+No match
+ 123abcdef\C!1
+--->123abcdef
+ 0 ^ ^ d
+ 1 ^ ^ f
+No match
+
+/(?C0)(abc(?C1))*/I
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+ abcabcabc
+--->abcabcabc
+ 0 ^ (abc(?C1))*
+ 1 ^ ^ )
+ 1 ^ ^ )
+ 1 ^ ^ )
+ 0: abcabcabc
+ 1: abc
+ abcabc\C!1!3
+--->abcabc
+ 0 ^ (abc(?C1))*
+ 1 ^ ^ )
+ 1 ^ ^ )
+ 0: abcabc
+ 1: abc
+ *** Failers
+--->*** Failers
+ 0 ^ (abc(?C1))*
+ 0:
+ abcabcabc\C!1!3
+--->abcabcabc
+ 0 ^ (abc(?C1))*
+ 1 ^ ^ )
+ 1 ^ ^ )
+ 1 ^ ^ )
+ 0: abcabc
+ 1: abc
+
+/(\d{3}(?C))*/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+ 123\C+
+Callout 0: last capture = -1
+ 0: <unset>
+--->123
+ ^ ^ )
+ 0: 123
+ 1: 123
+ 123456\C+
+Callout 0: last capture = -1
+ 0: <unset>
+--->123456
+ ^ ^ )
+Callout 0: last capture = 1
+ 0: <unset>
+ 1: 123
+--->123456
+ ^ ^ )
+ 0: 123456
+ 1: 456
+ 123456789\C+
+Callout 0: last capture = -1
+ 0: <unset>
+--->123456789
+ ^ ^ )
+Callout 0: last capture = 1
+ 0: <unset>
+ 1: 123
+--->123456789
+ ^ ^ )
+Callout 0: last capture = 1
+ 0: <unset>
+ 1: 456
+--->123456789
+ ^ ^ )
+ 0: 123456789
+ 1: 789
+
+/((xyz)(?C)p|(?C1)xyzabc)/I
+Capturing subpattern count = 2
+No options
+First char = 'x'
+No need char
+ xyzabc\C+
+Callout 0: last capture = 2
+ 0: <unset>
+ 1: <unset>
+ 2: xyz
+--->xyzabc
+ ^ ^ p
+Callout 1: last capture = -1
+ 0: <unset>
+--->xyzabc
+ ^ x
+ 0: xyzabc
+ 1: xyzabc
+
+/(X)((xyz)(?C)p|(?C1)xyzabc)/I
+Capturing subpattern count = 3
+No options
+First char = 'X'
+Need char = 'x'
+ Xxyzabc\C+
+Callout 0: last capture = 3
+ 0: <unset>
+ 1: X
+ 2: <unset>
+ 3: xyz
+--->Xxyzabc
+ ^ ^ p
+Callout 1: last capture = 1
+ 0: <unset>
+ 1: X
+--->Xxyzabc
+ ^^ x
+ 0: Xxyzabc
+ 1: X
+ 2: xyzabc
+
+/(?=(abc))(?C)abcdef/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'f'
+ abcdef\C+
+Callout 0: last capture = 1
+ 0: <unset>
+ 1: abc
+--->abcdef
+ ^ a
+ 0: abcdef
+ 1: abc
+
+/(?!(abc)(?C1)d)(?C2)abcxyz/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'z'
+ abcxyz\C+
+Callout 1: last capture = 1
+ 0: <unset>
+ 1: abc
+--->abcxyz
+ ^ ^ d
+Callout 2: last capture = -1
+ 0: <unset>
+--->abcxyz
+ ^ a
+ 0: abcxyz
+
+/(?<=(abc)(?C))xyz/I
+Capturing subpattern count = 1
+No options
+First char = 'x'
+Need char = 'z'
+ abcxyz\C+
+Callout 0: last capture = 1
+ 0: <unset>
+ 1: abc
+--->abcxyz
+ ^ )
+ 0: xyz
+ 1: abc
+
+/a(b+)(c*)(?C1)/I
+Capturing subpattern count = 2
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+ abbbbbccc\C*1
+--->abbbbbccc
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+No match
+
+/a(b+?)(c*?)(?C1)/I
+Capturing subpattern count = 2
+Partial matching not supported
+No options
+First char = 'a'
+Need char = 'b'
+ abbbbbccc\C*1
+--->abbbbbccc
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+ 1 ^ ^
+Callout data = 1
+No match
+
+/(?C)abc/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+
+/(?C)^abc/I
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/(?C)a|b/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: a b
+
+/(?R)/I
+Failed: recursive call could loop indefinitely at offset 3
+
+/(a|(?R))/I
+Failed: recursive call could loop indefinitely at offset 6
+
+/(ab|(bc|(de|(?R))))/I
+Failed: recursive call could loop indefinitely at offset 15
+
+/x(ab|(bc|(de|(?R))))/I
+Capturing subpattern count = 3
+No options
+First char = 'x'
+No need char
+ xab
+ 0: xab
+ 1: ab
+ xbc
+ 0: xbc
+ 1: bc
+ 2: bc
+ xde
+ 0: xde
+ 1: de
+ 2: de
+ 3: de
+ xxab
+ 0: xxab
+ 1: xab
+ 2: xab
+ 3: xab
+ xxxab
+ 0: xxxab
+ 1: xxab
+ 2: xxab
+ 3: xxab
+ *** Failers
+No match
+ xyab
+No match
+
+/(ab|(bc|(de|(?1))))/I
+Failed: recursive call could loop indefinitely at offset 15
+
+/x(ab|(bc|(de|(?1)x)x)x)/I
+Failed: recursive call could loop indefinitely at offset 16
+
+/^([^()]|\((?1)*\))*$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ abc
+ 0: abc
+ 1: c
+ a(b)c
+ 0: a(b)c
+ 1: c
+ a(b(c))d
+ 0: a(b(c))d
+ 1: d
+ *** Failers)
+No match
+ a(b(c)d
+No match
+
+/^>abc>([^()]|\((?1)*\))*<xyz<$/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+Need char = '<'
+ >abc>123<xyz<
+ 0: >abc>123<xyz<
+ 1: 3
+ >abc>1(2)3<xyz<
+ 0: >abc>1(2)3<xyz<
+ 1: 3
+ >abc>(1(2)3)<xyz<
+ 0: >abc>(1(2)3)<xyz<
+ 1: (1(2)3)
+
+/(a(?1)b)/DZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ a
+ Once
+ Recurse
+ Ket
+ b
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'b'
+
+/(a(?1)+b)/DZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ a
+ Once
+ Recurse
+ KetRmax
+ b
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'b'
+
+/^\W*(?:((.)\W*(?1)\W*\2|)|((.)\W*(?3)\W*\4|\W*.\W*))\W*$/Ii
+Capturing subpattern count = 4
+Max back reference = 4
+Partial matching not supported
+Options: anchored caseless
+No first char
+No need char
+ 1221
+ 0: 1221
+ 1: 1221
+ 2: 1
+ Satan, oscillate my metallic sonatas!
+ 0: Satan, oscillate my metallic sonatas!
+ 1: <unset>
+ 2: <unset>
+ 3: Satan, oscillate my metallic sonatas
+ 4: S
+ A man, a plan, a canal: Panama!
+ 0: A man, a plan, a canal: Panama!
+ 1: <unset>
+ 2: <unset>
+ 3: A man, a plan, a canal: Panama
+ 4: A
+ Able was I ere I saw Elba.
+ 0: Able was I ere I saw Elba.
+ 1: <unset>
+ 2: <unset>
+ 3: Able was I ere I saw Elba
+ 4: A
+ *** Failers
+No match
+ The quick brown fox
+No match
+
+/^(\d+|\((?1)([+*-])(?1)\)|-(?1))$/I
+Capturing subpattern count = 2
+Partial matching not supported
+Options: anchored
+No first char
+No need char
+ 12
+ 0: 12
+ 1: 12
+ (((2+2)*-3)-7)
+ 0: (((2+2)*-3)-7)
+ 1: (((2+2)*-3)-7)
+ 2: -
+ -12
+ 0: -12
+ 1: -12
+ *** Failers
+No match
+ ((2+2)*-3)-7)
+No match
+
+/^(x(y|(?1){2})z)/I
+Capturing subpattern count = 2
+Options: anchored
+No first char
+No need char
+ xyz
+ 0: xyz
+ 1: xyz
+ 2: y
+ xxyzxyzz
+ 0: xxyzxyzz
+ 1: xxyzxyzz
+ 2: xyzxyz
+ *** Failers
+No match
+ xxyzz
+No match
+ xxyzxyzxyzz
+No match
+
+/((< (?: (?(R) \d++ | [^<>]*+) | (?2)) * >))/Ix
+Capturing subpattern count = 2
+Partial matching not supported
+Options: extended
+First char = '<'
+Need char = '>'
+ <>
+ 0: <>
+ 1: <>
+ 2: <>
+ <abcd>
+ 0: <abcd>
+ 1: <abcd>
+ 2: <abcd>
+ <abc <123> hij>
+ 0: <abc <123> hij>
+ 1: <abc <123> hij>
+ 2: <abc <123> hij>
+ <abc <def> hij>
+ 0: <def>
+ 1: <def>
+ 2: <def>
+ <abc<>def>
+ 0: <abc<>def>
+ 1: <abc<>def>
+ 2: <abc<>def>
+ <abc<>
+ 0: <>
+ 1: <>
+ 2: <>
+ *** Failers
+No match
+ <abc
+No match
+
+/(?1)/I
+Failed: reference to non-existent subpattern at offset 3
+
+/((?2)(abc)/I
+Failed: missing ) at offset 10
+
+/^(abc)def(?1)/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ abcdefabc
+ 0: abcdefabc
+ 1: abc
+
+/^(a|b|c)=(?1)+/I
+Capturing subpattern count = 1
+Options: anchored
+No first char
+No need char
+ a=a
+ 0: a=a
+ 1: a
+ a=b
+ 0: a=b
+ 1: a
+ a=bc
+ 0: a=bc
+ 1: a
+
+/^(a|b|c)=((?1))+/I
+Capturing subpattern count = 2
+Options: anchored
+No first char
+No need char
+ a=a
+ 0: a=a
+ 1: a
+ 2: a
+ a=b
+ 0: a=b
+ 1: a
+ 2: b
+ a=bc
+ 0: a=bc
+ 1: a
+ 2: c
+
+/a(?P<name1>b|c)d(?P<longername2>e)/DZ
+------------------------------------------------------------------
+ Bra
+ a
+ CBra 1
+ b
+ Alt
+ c
+ Ket
+ d
+ CBra 2
+ e
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ longername2 2
+ name1 1
+No options
+First char = 'a'
+Need char = 'e'
+ abde
+ 0: abde
+ 1: b
+ 2: e
+ acde
+ 0: acde
+ 1: c
+ 2: e
+
+/(?:a(?P<c>c(?P<d>d)))(?P<a>a)/DZ
+------------------------------------------------------------------
+ Bra
+ Bra
+ a
+ CBra 1
+ c
+ CBra 2
+ d
+ Ket
+ Ket
+ Ket
+ CBra 3
+ a
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 3
+Named capturing subpatterns:
+ a 3
+ c 1
+ d 2
+No options
+First char = 'a'
+Need char = 'a'
+
+/(?P<a>a)...(?P=a)bbb(?P>a)d/DZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ a
+ Ket
+ Any
+ Any
+ Any
+ \1
+ bbb
+ Once
+ Recurse
+ Ket
+ d
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Max back reference = 1
+Named capturing subpatterns:
+ a 1
+No options
+First char = 'a'
+Need char = 'd'
+
+/^\W*(?:(?P<one>(?P<two>.)\W*(?P>one)\W*(?P=two)|)|(?P<three>(?P<four>.)\W*(?P>three)\W*(?P=four)|\W*.\W*))\W*$/Ii
+Capturing subpattern count = 4
+Max back reference = 4
+Named capturing subpatterns:
+ four 4
+ one 1
+ three 3
+ two 2
+Partial matching not supported
+Options: anchored caseless
+No first char
+No need char
+ 1221
+ 0: 1221
+ 1: 1221
+ 2: 1
+ Satan, oscillate my metallic sonatas!
+ 0: Satan, oscillate my metallic sonatas!
+ 1: <unset>
+ 2: <unset>
+ 3: Satan, oscillate my metallic sonatas
+ 4: S
+ A man, a plan, a canal: Panama!
+ 0: A man, a plan, a canal: Panama!
+ 1: <unset>
+ 2: <unset>
+ 3: A man, a plan, a canal: Panama
+ 4: A
+ Able was I ere I saw Elba.
+ 0: Able was I ere I saw Elba.
+ 1: <unset>
+ 2: <unset>
+ 3: Able was I ere I saw Elba
+ 4: A
+ *** Failers
+No match
+ The quick brown fox
+No match
+
+/((?(R)a|b))\1(?1)?/I
+Capturing subpattern count = 1
+Max back reference = 1
+No options
+No first char
+No need char
+ bb
+ 0: bb
+ 1: b
+ bbaa
+ 0: bba
+ 1: b
+
+/(.*)a/Is
+Capturing subpattern count = 1
+Partial matching not supported
+Options: anchored dotall
+No first char
+Need char = 'a'
+
+/(.*)a\1/Is
+Capturing subpattern count = 1
+Max back reference = 1
+Partial matching not supported
+Options: dotall
+No first char
+Need char = 'a'
+
+/(.*)a(b)\2/Is
+Capturing subpattern count = 2
+Max back reference = 2
+Partial matching not supported
+Options: anchored dotall
+No first char
+Need char = 'b'
+
+/((.*)a|(.*)b)z/Is
+Capturing subpattern count = 3
+Partial matching not supported
+Options: anchored dotall
+No first char
+Need char = 'z'
+
+/((.*)a|(.*)b)z\1/Is
+Capturing subpattern count = 3
+Max back reference = 1
+Partial matching not supported
+Options: dotall
+No first char
+Need char = 'z'
+
+/((.*)a|(.*)b)z\2/Is
+Capturing subpattern count = 3
+Max back reference = 2
+Partial matching not supported
+Options: dotall
+No first char
+Need char = 'z'
+
+/((.*)a|(.*)b)z\3/Is
+Capturing subpattern count = 3
+Max back reference = 3
+Partial matching not supported
+Options: dotall
+No first char
+Need char = 'z'
+
+/((.*)a|^(.*)b)z\3/Is
+Capturing subpattern count = 3
+Max back reference = 3
+Partial matching not supported
+Options: anchored dotall
+No first char
+Need char = 'z'
+
+/(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)a/Is
+Capturing subpattern count = 31
+Partial matching not supported
+Options: anchored dotall
+No first char
+No need char
+
+/(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)a\31/Is
+Capturing subpattern count = 31
+Max back reference = 31
+Partial matching not supported
+Options: dotall
+No first char
+No need char
+
+/(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)|(.*)a\32/Is
+Capturing subpattern count = 32
+Max back reference = 32
+Partial matching not supported
+Options: dotall
+No first char
+No need char
+
+/(a)(bc)/INDZ
+------------------------------------------------------------------
+ Bra
+ Bra
+ a
+ Ket
+ Bra
+ bc
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: no_auto_capture
+First char = 'a'
+Need char = 'c'
+ abc
+ 0: abc
+
+/(?P<one>a)(bc)/INDZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ a
+ Ket
+ Bra
+ bc
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ one 1
+Options: no_auto_capture
+First char = 'a'
+Need char = 'c'
+ abc
+ 0: abc
+ 1: a
+
+/(a)(?P<named>bc)/INDZ
+------------------------------------------------------------------
+ Bra
+ Bra
+ a
+ Ket
+ CBra 1
+ bc
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ named 1
+Options: no_auto_capture
+First char = 'a'
+Need char = 'c'
+
+/(a+)*zz/I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+Need char = 'z'
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazzbbbbbb\M
+Minimum match() limit = 8
+Minimum match() recursion limit = 6
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazz
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaz\M
+Minimum match() limit = 32768
+Minimum match() recursion limit = 42
+No match
+
+/(aaa(?C1)bbb|ab)/I
+Capturing subpattern count = 1
+No options
+First char = 'a'
+Need char = 'b'
+ aaabbb
+--->aaabbb
+ 1 ^ ^ b
+ 0: aaabbb
+ 1: aaabbb
+ aaabbb\C*0
+--->aaabbb
+ 1 ^ ^ b
+ 0: aaabbb
+ 1: aaabbb
+ aaabbb\C*1
+--->aaabbb
+ 1 ^ ^ b
+Callout data = 1
+ 0: ab
+ 1: ab
+ aaabbb\C*-1
+--->aaabbb
+ 1 ^ ^ b
+Callout data = -1
+No match
+
+/ab(?P<one>cd)ef(?P<two>gh)/I
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ one 1
+ two 2
+No options
+First char = 'a'
+Need char = 'h'
+ abcdefgh
+ 0: abcdefgh
+ 1: cd
+ 2: gh
+ abcdefgh\C1\Gtwo
+ 0: abcdefgh
+ 1: cd
+ 2: gh
+ 1C cd (2)
+ G gh (2) two
+ abcdefgh\Cone\Ctwo
+ 0: abcdefgh
+ 1: cd
+ 2: gh
+ C cd (2) one
+ C gh (2) two
+ abcdefgh\Cthree
+no parentheses with name "three"
+ 0: abcdefgh
+ 1: cd
+ 2: gh
+copy substring three failed -7
+
+/(?P<Tes>)(?P<Test>)/DZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ Ket
+ CBra 2
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ Tes 1
+ Test 2
+No options
+No first char
+No need char
+
+/(?P<Test>)(?P<Tes>)/DZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ Ket
+ CBra 2
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ Tes 2
+ Test 1
+No options
+No first char
+No need char
+
+/(?P<Z>zz)(?P<A>aa)/I
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ A 2
+ Z 1
+No options
+First char = 'z'
+Need char = 'a'
+ zzaa\CZ
+ 0: zzaa
+ 1: zz
+ 2: aa
+ C zz (2) Z
+ zzaa\CA
+ 0: zzaa
+ 1: zz
+ 2: aa
+ C aa (2) A
+
+/(?P<x>eks)(?P<x>eccs)/I
+Failed: two named subpatterns have the same name at offset 15
+
+/(?P<abc>abc(?P<def>def)(?P<abc>xyz))/I
+Failed: two named subpatterns have the same name at offset 30
+
+"\[((?P<elem>\d+)(,(?P>elem))*)\]"I
+Capturing subpattern count = 3
+Named capturing subpatterns:
+ elem 2
+Partial matching not supported
+No options
+First char = '['
+Need char = ']'
+ [10,20,30,5,5,4,4,2,43,23,4234]
+ 0: [10,20,30,5,5,4,4,2,43,23,4234]
+ 1: 10,20,30,5,5,4,4,2,43,23,4234
+ 2: 10
+ 3: ,4234
+ *** Failers
+No match
+ []
+No match
+
+"\[((?P<elem>\d+)(,(?P>elem))*)?\]"I
+Capturing subpattern count = 3
+Named capturing subpatterns:
+ elem 2
+Partial matching not supported
+No options
+First char = '['
+Need char = ']'
+ [10,20,30,5,5,4,4,2,43,23,4234]
+ 0: [10,20,30,5,5,4,4,2,43,23,4234]
+ 1: 10,20,30,5,5,4,4,2,43,23,4234
+ 2: 10
+ 3: ,4234
+ []
+ 0: []
+
+/(a(b(?2)c))?/DZ
+------------------------------------------------------------------
+ Bra
+ Brazero
+ CBra 1
+ a
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+No options
+No first char
+No need char
+
+/(a(b(?2)c))*/DZ
+------------------------------------------------------------------
+ Bra
+ Brazero
+ CBra 1
+ a
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ KetRmax
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+No options
+No first char
+No need char
+
+/(a(b(?2)c)){0,2}/DZ
+------------------------------------------------------------------
+ Bra
+ Brazero
+ Bra
+ CBra 1
+ a
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Brazero
+ CBra 1
+ a
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+No options
+No first char
+No need char
+
+/[ab]{1}+/DZ
+------------------------------------------------------------------
+ Bra
+ Once
+ [ab]{1,1}
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/((w\/|-|with)*(free|immediate)*.*?shipping\s*[!.-]*)/Ii
+Capturing subpattern count = 3
+Partial matching not supported
+Options: caseless
+No first char
+Need char = 'g' (caseless)
+ Baby Bjorn Active Carrier - With free SHIPPING!!
+ 0: Baby Bjorn Active Carrier - With free SHIPPING!!
+ 1: Baby Bjorn Active Carrier - With free SHIPPING!!
+
+/((w\/|-|with)*(free|immediate)*.*?shipping\s*[!.-]*)/IiS
+Capturing subpattern count = 3
+Partial matching not supported
+Options: caseless
+No first char
+Need char = 'g' (caseless)
+Study returned NULL
+ Baby Bjorn Active Carrier - With free SHIPPING!!
+ 0: Baby Bjorn Active Carrier - With free SHIPPING!!
+ 1: Baby Bjorn Active Carrier - With free SHIPPING!!
+
+/a*.*b/ISDZ
+------------------------------------------------------------------
+ Bra
+ a*
+ Any*
+ b
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = 'b'
+Study returned NULL
+
+/(a|b)*.?c/ISDZ
+------------------------------------------------------------------
+ Bra
+ Brazero
+ CBra 1
+ a
+ Alt
+ b
+ KetRmax
+ Any?
+ c
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'c'
+Study returned NULL
+
+/abc(?C255)de(?C)f/DZ
+------------------------------------------------------------------
+ Bra
+ abc
+ Callout 255 10 1
+ de
+ Callout 0 16 1
+ f
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'f'
+
+/abcde/ICDZ
+------------------------------------------------------------------
+ Bra
+ Callout 255 0 1
+ a
+ Callout 255 1 1
+ b
+ Callout 255 2 1
+ c
+ Callout 255 3 1
+ d
+ Callout 255 4 1
+ e
+ Callout 255 5 0
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options:
+First char = 'a'
+Need char = 'e'
+ abcde
+--->abcde
+ +0 ^ a
+ +1 ^^ b
+ +2 ^ ^ c
+ +3 ^ ^ d
+ +4 ^ ^ e
+ +5 ^ ^
+ 0: abcde
+ abcdfe
+--->abcdfe
+ +0 ^ a
+ +1 ^^ b
+ +2 ^ ^ c
+ +3 ^ ^ d
+ +4 ^ ^ e
+No match
+
+/a*b/ICDZ
+------------------------------------------------------------------
+ Bra
+ Callout 255 0 2
+ a*+
+ Callout 255 2 1
+ b
+ Callout 255 3 0
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options:
+No first char
+Need char = 'b'
+ ab
+--->ab
+ +0 ^ a*
+ +2 ^^ b
+ +3 ^ ^
+ 0: ab
+ aaaab
+--->aaaab
+ +0 ^ a*
+ +2 ^ ^ b
+ +3 ^ ^
+ 0: aaaab
+ aaaacb
+--->aaaacb
+ +0 ^ a*
+ +2 ^ ^ b
+ +0 ^ a*
+ +2 ^ ^ b
+ +0 ^ a*
+ +2 ^ ^ b
+ +0 ^ a*
+ +2 ^^ b
+ +0 ^ a*
+ +2 ^ b
+ +0 ^ a*
+ +2 ^ b
+ +3 ^^
+ 0: b
+
+/a+b/ICDZ
+------------------------------------------------------------------
+ Bra
+ Callout 255 0 2
+ a++
+ Callout 255 2 1
+ b
+ Callout 255 3 0
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options:
+First char = 'a'
+Need char = 'b'
+ ab
+--->ab
+ +0 ^ a+
+ +2 ^^ b
+ +3 ^ ^
+ 0: ab
+ aaaab
+--->aaaab
+ +0 ^ a+
+ +2 ^ ^ b
+ +3 ^ ^
+ 0: aaaab
+ aaaacb
+--->aaaacb
+ +0 ^ a+
+ +2 ^ ^ b
+ +0 ^ a+
+ +2 ^ ^ b
+ +0 ^ a+
+ +2 ^ ^ b
+ +0 ^ a+
+ +2 ^^ b
+No match
+
+/(abc|def)x/ICDZ
+------------------------------------------------------------------
+ Bra
+ Callout 255 0 9
+ CBra 1
+ Callout 255 1 1
+ a
+ Callout 255 2 1
+ b
+ Callout 255 3 1
+ c
+ Callout 255 4 0
+ Alt
+ Callout 255 5 1
+ d
+ Callout 255 6 1
+ e
+ Callout 255 7 1
+ f
+ Callout 255 8 0
+ Ket
+ Callout 255 9 1
+ x
+ Callout 255 10 0
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Options:
+No first char
+Need char = 'x'
+ abcx
+--->abcx
+ +0 ^ (abc|def)
+ +1 ^ a
+ +2 ^^ b
+ +3 ^ ^ c
+ +4 ^ ^ |
+ +9 ^ ^ x
++10 ^ ^
+ 0: abcx
+ 1: abc
+ defx
+--->defx
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+ +6 ^^ e
+ +7 ^ ^ f
+ +8 ^ ^ )
+ +9 ^ ^ x
++10 ^ ^
+ 0: defx
+ 1: def
+ abcdefzx
+--->abcdefzx
+ +0 ^ (abc|def)
+ +1 ^ a
+ +2 ^^ b
+ +3 ^ ^ c
+ +4 ^ ^ |
+ +9 ^ ^ x
+ +5 ^ d
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+ +6 ^^ e
+ +7 ^ ^ f
+ +8 ^ ^ )
+ +9 ^ ^ x
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+ +0 ^ (abc|def)
+ +1 ^ a
+ +5 ^ d
+No match
+
+/(ab|cd){3,4}/IC
+Capturing subpattern count = 1
+Options:
+No first char
+No need char
+ ababab
+--->ababab
+ +0 ^ (ab|cd){3,4}
+ +1 ^ a
+ +2 ^^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +2 ^ ^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +2 ^ ^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
++12 ^ ^
+ 0: ababab
+ 1: ab
+ abcdabcd
+--->abcdabcd
+ +0 ^ (ab|cd){3,4}
+ +1 ^ a
+ +2 ^^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
+ +1 ^ ^ a
+ +2 ^ ^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
++12 ^ ^
+ 0: abcdabcd
+ 1: cd
+ abcdcdcdcdcd
+--->abcdcdcdcdcd
+ +0 ^ (ab|cd){3,4}
+ +1 ^ a
+ +2 ^^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
++12 ^ ^
+ 0: abcdcdcd
+ 1: cd
+
+/([ab]{,4}c|xy)/ICDZ
+------------------------------------------------------------------
+ Bra
+ Callout 255 0 14
+ CBra 1
+ Callout 255 1 4
+ [ab]
+ Callout 255 5 1
+ {
+ Callout 255 6 1
+ ,
+ Callout 255 7 1
+ 4
+ Callout 255 8 1
+ }
+ Callout 255 9 1
+ c
+ Callout 255 10 0
+ Alt
+ Callout 255 11 1
+ x
+ Callout 255 12 1
+ y
+ Callout 255 13 0
+ Ket
+ Callout 255 14 0
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Options:
+No first char
+No need char
+ Note: that { does NOT introduce a quantifier
+--->Note: that { does NOT introduce a quantifier
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
+ +5 ^^ {
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
+ +5 ^^ {
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
+ +5 ^^ {
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+ +0 ^ ([ab]{,4}c|xy)
+ +1 ^ [ab]
++11 ^ x
+No match
+
+/([ab]{1,4}c|xy){4,5}?123/ICDZ
+------------------------------------------------------------------
+ Bra
+ Callout 255 0 21
+ CBra 1
+ Callout 255 1 9
+ [ab]{1,4}
+ Callout 255 10 1
+ c
+ Callout 255 11 0
+ Alt
+ Callout 255 12 1
+ x
+ Callout 255 13 1
+ y
+ Callout 255 14 0
+ Ket
+ CBra 1
+ Callout 255 1 9
+ [ab]{1,4}
+ Callout 255 10 1
+ c
+ Callout 255 11 0
+ Alt
+ Callout 255 12 1
+ x
+ Callout 255 13 1
+ y
+ Callout 255 14 0
+ Ket
+ CBra 1
+ Callout 255 1 9
+ [ab]{1,4}
+ Callout 255 10 1
+ c
+ Callout 255 11 0
+ Alt
+ Callout 255 12 1
+ x
+ Callout 255 13 1
+ y
+ Callout 255 14 0
+ Ket
+ CBra 1
+ Callout 255 1 9
+ [ab]{1,4}
+ Callout 255 10 1
+ c
+ Callout 255 11 0
+ Alt
+ Callout 255 12 1
+ x
+ Callout 255 13 1
+ y
+ Callout 255 14 0
+ Ket
+ Braminzero
+ CBra 1
+ Callout 255 1 9
+ [ab]{1,4}
+ Callout 255 10 1
+ c
+ Callout 255 11 0
+ Alt
+ Callout 255 12 1
+ x
+ Callout 255 13 1
+ y
+ Callout 255 14 0
+ Ket
+ Callout 255 21 1
+ 1
+ Callout 255 22 1
+ 2
+ Callout 255 23 1
+ 3
+ Callout 255 24 0
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options:
+No first char
+Need char = '3'
+ aacaacaacaacaac123
+--->aacaacaacaacaac123
+ +0 ^ ([ab]{1,4}c|xy){4,5}?
+ +1 ^ [ab]{1,4}
++10 ^ ^ c
++11 ^ ^ |
+ +1 ^ ^ [ab]{1,4}
++10 ^ ^ c
++11 ^ ^ |
+ +1 ^ ^ [ab]{1,4}
++10 ^ ^ c
++11 ^ ^ |
+ +1 ^ ^ [ab]{1,4}
++10 ^ ^ c
++11 ^ ^ |
++21 ^ ^ 1
+ +1 ^ ^ [ab]{1,4}
++10 ^ ^ c
++11 ^ ^ |
++21 ^ ^ 1
++22 ^ ^ 2
++23 ^ ^ 3
++24 ^ ^
+ 0: aacaacaacaacaac123
+ 1: aac
+
+/\b.*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ ab cd\>1
+ 0: cd
+
+/\b.*/Is
+Capturing subpattern count = 0
+Partial matching not supported
+Options: dotall
+No first char
+No need char
+ ab cd\>1
+ 0: cd
+
+/(?!.bcd).*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ Xbcd12345
+ 0: bcd12345
+
+/abcde/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'e'
+ ab\P
+Partial match
+ abc\P
+Partial match
+ abcd\P
+Partial match
+ abcde\P
+ 0: abcde
+ the quick brown abc\P
+Partial match
+ ** Failers\P
+No match
+ the quick brown abxyz fox\P
+No match
+
+"^(0?[1-9]|[12][0-9]|3[01])/(0?[1-9]|1[012])/(20)?\d\d$"I
+Capturing subpattern count = 3
+Options: anchored
+No first char
+Need char = '/'
+ 13/05/04\P
+ 0: 13/05/04
+ 1: 13
+ 2: 05
+ 13/5/2004\P
+ 0: 13/5/2004
+ 1: 13
+ 2: 5
+ 3: 20
+ 02/05/09\P
+ 0: 02/05/09
+ 1: 02
+ 2: 05
+ 1\P
+Partial match
+ 1/2\P
+Partial match
+ 1/2/0\P
+Partial match
+ 1/2/04\P
+ 0: 1/2/04
+ 1: 1
+ 2: 2
+ 0\P
+Partial match
+ 02/\P
+Partial match
+ 02/0\P
+Partial match
+ 02/1\P
+Partial match
+ ** Failers\P
+No match
+ \P
+No match
+ 123\P
+No match
+ 33/4/04\P
+No match
+ 3/13/04\P
+No match
+ 0/1/2003\P
+No match
+ 0/\P
+No match
+ 02/0/\P
+No match
+ 02/13\P
+No match
+
+/0{0,2}ABC/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = 'C'
+
+/\d{3,}ABC/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = 'C'
+
+/\d*ABC/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = 'C'
+
+/[abc]+DE/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = 'E'
+
+/[abc]?123/I
+Capturing subpattern count = 0
+No options
+No first char
+Need char = '3'
+ 123\P
+ 0: 123
+ a\P
+Partial match
+ b\P
+Partial match
+ c\P
+Partial match
+ c12\P
+Partial match
+ c123\P
+ 0: c123
+
+/^(?:\d){3,5}X/I
+Capturing subpattern count = 0
+Options: anchored
+No first char
+Need char = 'X'
+ 1\P
+Partial match
+ 123\P
+Partial match
+ 123X
+ 0: 123X
+ 1234\P
+Partial match
+ 1234X
+ 0: 1234X
+ 12345\P
+Partial match
+ 12345X
+ 0: 12345X
+ *** Failers
+No match
+ 1X
+No match
+ 123456\P
+No match
+
+/abc/I>testsavedregex
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+Compiled regex written to testsavedregex
+<testsavedregex
+Compiled regex loaded from testsavedregex
+No study data
+ abc
+ 0: abc
+ ** Failers
+No match
+ bca
+No match
+
+/abc/IF>testsavedregex
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+Compiled regex written to testsavedregex
+<testsavedregex
+Compiled regex (byte-inverted) loaded from testsavedregex
+No study data
+ abc
+ 0: abc
+ ** Failers
+No match
+ bca
+No match
+
+/(a|b)/IS>testsavedregex
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+Starting byte set: a b
+Compiled regex written to testsavedregex
+Study data written to testsavedregex
+<testsavedregex
+Compiled regex loaded from testsavedregex
+Study data loaded from testsavedregex
+ abc
+ 0: a
+ 1: a
+ ** Failers
+ 0: a
+ 1: a
+ def
+No match
+
+/(a|b)/ISF>testsavedregex
+Capturing subpattern count = 1
+No options
+No first char
+No need char
+Starting byte set: a b
+Compiled regex written to testsavedregex
+Study data written to testsavedregex
+<testsavedregex
+Compiled regex (byte-inverted) loaded from testsavedregex
+Study data loaded from testsavedregex
+ abc
+ 0: a
+ 1: a
+ ** Failers
+ 0: a
+ 1: a
+ def
+No match
+
+~<(\w+)/?>(.)*</(\1)>~smgI
+Capturing subpattern count = 3
+Max back reference = 1
+Partial matching not supported
+Options: multiline dotall
+First char = '<'
+Need char = '>'
+ <!DOCTYPE seite SYSTEM "http://www.lco.lineas.de/xmlCms.dtd">\n<seite>\n<dokumenteninformation>\n<seitentitel>Partner der LCO</seitentitel>\n<sprache>de</sprache>\n<seitenbeschreibung>Partner der LINEAS Consulting\nGmbH</seitenbeschreibung>\n<schluesselworte>LINEAS Consulting GmbH Hamburg\nPartnerfirmen</schluesselworte>\n<revisit>30 days</revisit>\n<robots>index,follow</robots>\n<menueinformation>\n<aktiv>ja</aktiv>\n<menueposition>3</menueposition>\n<menuetext>Partner</menuetext>\n</menueinformation>\n<lastedited>\n<autor>LCO</autor>\n<firma>LINEAS Consulting</firma>\n<datum>15.10.2003</datum>\n</lastedited>\n</dokumenteninformation>\n<inhalt>\n\n<absatzueberschrift>Die Partnerfirmen der LINEAS Consulting\nGmbH</absatzueberschrift>\n\n<absatz><link ziel="http://www.ca.com/" zielfenster="_blank">\n<bild name="logo_ca.gif" rahmen="no"/></link> <link\nziel="http://www.ey.com/" zielfenster="_blank"><bild\nname="logo_euy.gif" rahmen="no"/></link>\n</absatz>\n\n<absatz><link ziel="http://www.cisco.de/" zielfenster="_blank">\n<bild name="logo_cisco.gif" rahmen="ja"/></link></absatz>\n\n<absatz><link ziel="http://www.atelion.de/"\nzielfenster="_blank"><bild\nname="logo_atelion.gif" rahmen="no"/></link>\n</absatz>\n\n<absatz><link ziel="http://www.line-information.de/"\nzielfenster="_blank">\n<bild name="logo_line_information.gif" rahmen="no"/></link>\n</absatz>\n\n<absatz><bild name="logo_aw.gif" rahmen="no"/></absatz>\n\n<absatz><link ziel="http://www.incognis.de/"\nzielfenster="_blank"><bild\nname="logo_incognis.gif" rahmen="no"/></link></absatz>\n\n<absatz><link ziel="http://www.addcraft.com/"\nzielfenster="_blank"><bild\nname="logo_addcraft.gif" rahmen="no"/></link></absatz>\n\n<absatz><link ziel="http://www.comendo.com/"\nzielfenster="_blank"><bild\nname="logo_comendo.gif" rahmen="no"/></link></absatz>\n\n</inhalt>\n</seite>
+ 0: <seite>\x0a<dokumenteninformation>\x0a<seitentitel>Partner der LCO</seitentitel>\x0a<sprache>de</sprache>\x0a<seitenbeschreibung>Partner der LINEAS Consulting\x0aGmbH</seitenbeschreibung>\x0a<schluesselworte>LINEAS Consulting GmbH Hamburg\x0aPartnerfirmen</schluesselworte>\x0a<revisit>30 days</revisit>\x0a<robots>index,follow</robots>\x0a<menueinformation>\x0a<aktiv>ja</aktiv>\x0a<menueposition>3</menueposition>\x0a<menuetext>Partner</menuetext>\x0a</menueinformation>\x0a<lastedited>\x0a<autor>LCO</autor>\x0a<firma>LINEAS Consulting</firma>\x0a<datum>15.10.2003</datum>\x0a</lastedited>\x0a</dokumenteninformation>\x0a<inhalt>\x0a\x0a<absatzueberschrift>Die Partnerfirmen der LINEAS Consulting\x0aGmbH</absatzueberschrift>\x0a\x0a<absatz><link ziel="http://www.ca.com/" zielfenster="_blank">\x0a<bild name="logo_ca.gif" rahmen="no"/></link> <link\x0aziel="http://www.ey.com/" zielfenster="_blank"><bild\x0aname="logo_euy.gif" rahmen="no"/></link>\x0a</absatz>\x0a\x0a<absatz><link ziel="http://www.cisco.de/" zielfenster="_blank">\x0a<bild name="logo_cisco.gif" rahmen="ja"/></link></absatz>\x0a\x0a<absatz><link ziel="http://www.atelion.de/"\x0azielfenster="_blank"><bild\x0aname="logo_atelion.gif" rahmen="no"/></link>\x0a</absatz>\x0a\x0a<absatz><link ziel="http://www.line-information.de/"\x0azielfenster="_blank">\x0a<bild name="logo_line_information.gif" rahmen="no"/></link>\x0a</absatz>\x0a\x0a<absatz><bild name="logo_aw.gif" rahmen="no"/></absatz>\x0a\x0a<absatz><link ziel="http://www.incognis.de/"\x0azielfenster="_blank"><bild\x0aname="logo_incognis.gif" rahmen="no"/></link></absatz>\x0a\x0a<absatz><link ziel="http://www.addcraft.com/"\x0azielfenster="_blank"><bild\x0aname="logo_addcraft.gif" rahmen="no"/></link></absatz>\x0a\x0a<absatz><link ziel="http://www.comendo.com/"\x0azielfenster="_blank"><bild\x0aname="logo_comendo.gif" rahmen="no"/></link></absatz>\x0a\x0a</inhalt>\x0a</seite>
+ 1: seite
+ 2: \x0a
+ 3: seite
+
+/^a/IF
+Capturing subpattern count = 0
+Options: anchored
+No first char
+No need char
+
+/line\nbreak/I
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+No options
+First char = 'l'
+Need char = 'k'
+ this is a line\nbreak
+ 0: line\x0abreak
+ line one\nthis is a line\nbreak in the second line
+ 0: line\x0abreak
+
+/line\nbreak/If
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+Options: firstline
+First char = 'l'
+Need char = 'k'
+ this is a line\nbreak
+ 0: line\x0abreak
+ ** Failers
+No match
+ line one\nthis is a line\nbreak in the second line
+No match
+
+/line\nbreak/Imf
+Capturing subpattern count = 0
+Contains explicit CR or LF match
+Options: multiline firstline
+First char = 'l'
+Need char = 'k'
+ this is a line\nbreak
+ 0: line\x0abreak
+ ** Failers
+No match
+ line one\nthis is a line\nbreak in the second line
+No match
+
+/ab.cd/IP
+ ab-cd
+ 0: ab-cd
+ ab=cd
+ 0: ab=cd
+ ** Failers
+No match: POSIX code 17: match failed
+ ab\ncd
+No match: POSIX code 17: match failed
+
+/ab.cd/IPs
+ ab-cd
+ 0: ab-cd
+ ab=cd
+ 0: ab=cd
+ ab\ncd
+ 0: ab\x0acd
+
+/(?i)(?-i)AbCd/I
+Capturing subpattern count = 0
+No options
+First char = 'A'
+Need char = 'd'
+ AbCd
+ 0: AbCd
+ ** Failers
+No match
+ abcd
+No match
+
+/a{11111111111111111111}/I
+Failed: number too big in {} quantifier at offset 22
+
+/(){64294967295}/I
+Failed: number too big in {} quantifier at offset 14
+
+/(){2,4294967295}/I
+Failed: number too big in {} quantifier at offset 15
+
+"(?i:a)(?i:b)(?i:c)(?i:d)(?i:e)(?i:f)(?i:g)(?i:h)(?i:i)(?i:j)(k)(?i:l)A\1B"I
+Capturing subpattern count = 1
+Max back reference = 1
+No options
+First char = 'a' (caseless)
+Need char = 'B'
+ abcdefghijklAkB
+ 0: abcdefghijklAkB
+ 1: k
+
+"(?P<n0>a)(?P<n1>b)(?P<n2>c)(?P<n3>d)(?P<n4>e)(?P<n5>f)(?P<n6>g)(?P<n7>h)(?P<n8>i)(?P<n9>j)(?P<n10>k)(?P<n11>l)A\11B"I
+Capturing subpattern count = 12
+Max back reference = 11
+Named capturing subpatterns:
+ n0 1
+ n1 2
+ n10 11
+ n11 12
+ n2 3
+ n3 4
+ n4 5
+ n5 6
+ n6 7
+ n7 8
+ n8 9
+ n9 10
+No options
+First char = 'a'
+Need char = 'B'
+ abcdefghijklAkB
+ 0: abcdefghijklAkB
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+12: l
+
+"(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)A\11B"I
+Capturing subpattern count = 12
+Max back reference = 11
+No options
+First char = 'a'
+Need char = 'B'
+ abcdefghijklAkB
+ 0: abcdefghijklAkB
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ 5: e
+ 6: f
+ 7: g
+ 8: h
+ 9: i
+10: j
+11: k
+12: l
+
+"(?P<name0>a)(?P<name1>a)(?P<name2>a)(?P<name3>a)(?P<name4>a)(?P<name5>a)(?P<name6>a)(?P<name7>a)(?P<name8>a)(?P<name9>a)(?P<name10>a)(?P<name11>a)(?P<name12>a)(?P<name13>a)(?P<name14>a)(?P<name15>a)(?P<name16>a)(?P<name17>a)(?P<name18>a)(?P<name19>a)(?P<name20>a)(?P<name21>a)(?P<name22>a)(?P<name23>a)(?P<name24>a)(?P<name25>a)(?P<name26>a)(?P<name27>a)(?P<name28>a)(?P<name29>a)(?P<name30>a)(?P<name31>a)(?P<name32>a)(?P<name33>a)(?P<name34>a)(?P<name35>a)(?P<name36>a)(?P<name37>a)(?P<name38>a)(?P<name39>a)(?P<name40>a)(?P<name41>a)(?P<name42>a)(?P<name43>a)(?P<name44>a)(?P<name45>a)(?P<name46>a)(?P<name47>a)(?P<name48>a)(?P<name49>a)(?P<name50>a)(?P<name51>a)(?P<name52>a)(?P<name53>a)(?P<name54>a)(?P<name55>a)(?P<name56>a)(?P<name57>a)(?P<name58>a)(?P<name59>a)(?P<name60>a)(?P<name61>a)(?P<name62>a)(?P<name63>a)(?P<name64>a)(?P<name65>a)(?P<name66>a)(?P<name67>a)(?P<name68>a)(?P<name69>a)(?P<name70>a)(?P<name71>a)(?P<name72>a)(?P<name73>a)(?P<name74>a)(?P<name75>a)(?P<name76>a)(?P<name77>a)(?P<name78>a)(?P<name79>a)(?P<name80>a)(?P<name81>a)(?P<name82>a)(?P<name83>a)(?P<name84>a)(?P<name85>a)(?P<name86>a)(?P<name87>a)(?P<name88>a)(?P<name89>a)(?P<name90>a)(?P<name91>a)(?P<name92>a)(?P<name93>a)(?P<name94>a)(?P<name95>a)(?P<name96>a)(?P<name97>a)(?P<name98>a)(?P<name99>a)(?P<name100>a)"I
+Capturing subpattern count = 101
+Named capturing subpatterns:
+ name0 1
+ name1 2
+ name10 11
+ name100 101
+ name11 12
+ name12 13
+ name13 14
+ name14 15
+ name15 16
+ name16 17
+ name17 18
+ name18 19
+ name19 20
+ name2 3
+ name20 21
+ name21 22
+ name22 23
+ name23 24
+ name24 25
+ name25 26
+ name26 27
+ name27 28
+ name28 29
+ name29 30
+ name3 4
+ name30 31
+ name31 32
+ name32 33
+ name33 34
+ name34 35
+ name35 36
+ name36 37
+ name37 38
+ name38 39
+ name39 40
+ name4 5
+ name40 41
+ name41 42
+ name42 43
+ name43 44
+ name44 45
+ name45 46
+ name46 47
+ name47 48
+ name48 49
+ name49 50
+ name5 6
+ name50 51
+ name51 52
+ name52 53
+ name53 54
+ name54 55
+ name55 56
+ name56 57
+ name57 58
+ name58 59
+ name59 60
+ name6 7
+ name60 61
+ name61 62
+ name62 63
+ name63 64
+ name64 65
+ name65 66
+ name66 67
+ name67 68
+ name68 69
+ name69 70
+ name7 8
+ name70 71
+ name71 72
+ name72 73
+ name73 74
+ name74 75
+ name75 76
+ name76 77
+ name77 78
+ name78 79
+ name79 80
+ name8 9
+ name80 81
+ name81 82
+ name82 83
+ name83 84
+ name84 85
+ name85 86
+ name86 87
+ name87 88
+ name88 89
+ name89 90
+ name9 10
+ name90 91
+ name91 92
+ name92 93
+ name93 94
+ name94 95
+ name95 96
+ name96 97
+ name97 98
+ name98 99
+ name99 100
+No options
+First char = 'a'
+Need char = 'a'
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many substrings
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+ 9: a
+10: a
+11: a
+12: a
+13: a
+14: a
+
+"(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)(a)"I
+Capturing subpattern count = 101
+No options
+First char = 'a'
+Need char = 'a'
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many substrings
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+ 9: a
+10: a
+11: a
+12: a
+13: a
+14: a
+
+/[^()]*(?:\((?R)\)[^()]*)*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ (this(and)that
+ 0:
+ (this(and)that)
+ 0: (this(and)that)
+ (this(and)that)stuff
+ 0: (this(and)that)stuff
+
+/[^()]*(?:\((?>(?R))\)[^()]*)*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ (this(and)that
+ 0:
+ (this(and)that)
+ 0: (this(and)that)
+
+/[^()]*(?:\((?R)\))*[^()]*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ (this(and)that
+ 0:
+ (this(and)that)
+ 0: (this(and)that)
+
+/(?:\((?R)\))*[^()]*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ (this(and)that
+ 0:
+ (this(and)that)
+ 0:
+ ((this))
+ 0: ((this))
+
+/(?:\((?R)\))|[^()]*/I
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+No need char
+ (this(and)that
+ 0:
+ (this(and)that)
+ 0:
+ (this)
+ 0: (this)
+ ((this))
+ 0: ((this))
+
+/a(b)c/IPN
+ abc
+Matched with REG_NOSUB
+
+/a(?P<name>b)c/IPN
+ abc
+Matched with REG_NOSUB
+
+/\x{100}/I
+Failed: character value in \x{...} sequence is too large at offset 6
+
+/\x{0000ff}/I
+Capturing subpattern count = 0
+No options
+First char = 255
+No need char
+
+/^((?P<A>a1)|(?P<A>a2)b)/I
+Failed: two named subpatterns have the same name at offset 17
+
+/^((?P<A>a1)|(?P<A>a2)b)/IJ
+Capturing subpattern count = 3
+Named capturing subpatterns:
+ A 2
+ A 3
+Options: anchored dupnames
+No first char
+No need char
+ a1b\CA
+ 0: a1
+ 1: a1
+ 2: a1
+ C a1 (2) A
+ a2b\CA
+ 0: a2b
+ 1: a2b
+ 2: <unset>
+ 3: a2
+ C a2 (2) A
+ ** Failers
+No match
+ a1b\CZ\CA
+no parentheses with name "Z"
+ 0: a1
+ 1: a1
+ 2: a1
+copy substring Z failed -7
+ C a1 (2) A
+
+/^(?P<A>a)(?P<A>b)/IJ
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ A 1
+ A 2
+Options: anchored dupnames
+No first char
+No need char
+ ab\CA
+ 0: ab
+ 1: a
+ 2: b
+ C a (1) A
+
+/^(?P<A>a)(?P<A>b)|cd/IJ
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ A 1
+ A 2
+Options: dupnames
+No first char
+No need char
+ ab\CA
+ 0: ab
+ 1: a
+ 2: b
+ C a (1) A
+ cd\CA
+ 0: cd
+copy substring A failed -7
+
+/^(?P<A>a)(?P<A>b)|cd(?P<A>ef)(?P<A>gh)/IJ
+Capturing subpattern count = 4
+Named capturing subpatterns:
+ A 1
+ A 2
+ A 3
+ A 4
+Options: dupnames
+No first char
+No need char
+ cdefgh\CA
+ 0: cdefgh
+ 1: <unset>
+ 2: <unset>
+ 3: ef
+ 4: gh
+ C ef (2) A
+
+/^((?P<A>a1)|(?P<A>a2)b)/IJ
+Capturing subpattern count = 3
+Named capturing subpatterns:
+ A 2
+ A 3
+Options: anchored dupnames
+No first char
+No need char
+ a1b\GA
+ 0: a1
+ 1: a1
+ 2: a1
+ G a1 (2) A
+ a2b\GA
+ 0: a2b
+ 1: a2b
+ 2: <unset>
+ 3: a2
+ G a2 (2) A
+ ** Failers
+No match
+ a1b\GZ\GA
+no parentheses with name "Z"
+ 0: a1
+ 1: a1
+ 2: a1
+copy substring Z failed -7
+ G a1 (2) A
+
+/^(?P<A>a)(?P<A>b)/IJ
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ A 1
+ A 2
+Options: anchored dupnames
+No first char
+No need char
+ ab\GA
+ 0: ab
+ 1: a
+ 2: b
+ G a (1) A
+
+/^(?P<A>a)(?P<A>b)|cd/IJ
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ A 1
+ A 2
+Options: dupnames
+No first char
+No need char
+ ab\GA
+ 0: ab
+ 1: a
+ 2: b
+ G a (1) A
+ cd\GA
+ 0: cd
+copy substring A failed -7
+
+/^(?P<A>a)(?P<A>b)|cd(?P<A>ef)(?P<A>gh)/IJ
+Capturing subpattern count = 4
+Named capturing subpatterns:
+ A 1
+ A 2
+ A 3
+ A 4
+Options: dupnames
+No first char
+No need char
+ cdefgh\GA
+ 0: cdefgh
+ 1: <unset>
+ 2: <unset>
+ 3: ef
+ 4: gh
+ G ef (2) A
+
+/(?J)^((?P<A>a1)|(?P<A>a2)b)/I
+Capturing subpattern count = 3
+Named capturing subpatterns:
+ A 2
+ A 3
+Options: anchored dupnames
+Duplicate name status changes
+No first char
+No need char
+ a1b\CA
+ 0: a1
+ 1: a1
+ 2: a1
+ C a1 (2) A
+ a2b\CA
+ 0: a2b
+ 1: a2b
+ 2: <unset>
+ 3: a2
+ C a2 (2) A
+
+/^(?P<A>a) (?J:(?P<B>b)(?P<B>c)) (?P<A>d)/I
+Failed: two named subpatterns have the same name at offset 37
+
+/ In this next test, J is not set at the outer level; consequently it isn't
+set in the pattern's options; consequently pcre_get_named_substring() produces
+a random value. /Ix
+Capturing subpattern count = 1
+Options: extended
+First char = 'I'
+Need char = 'e'
+
+/^(?P<A>a) (?J:(?P<B>b)(?P<B>c)) (?P<C>d)/I
+Capturing subpattern count = 4
+Named capturing subpatterns:
+ A 1
+ B 2
+ B 3
+ C 4
+Options: anchored
+Duplicate name status changes
+No first char
+No need char
+ a bc d\CA\CB\CC
+ 0: a bc d
+ 1: a
+ 2: b
+ 3: c
+ 4: d
+ C a (1) A
+ C b (1) B
+ C d (1) C
+
+/^(?P<A>a)?(?(A)a|b)/I
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ A 1
+Options: anchored
+No first char
+No need char
+ aabc
+ 0: aa
+ 1: a
+ bc
+ 0: b
+ ** Failers
+No match
+ abc
+No match
+
+/(?:(?(ZZ)a|b)(?P<ZZ>X))+/I
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ ZZ 1
+No options
+No first char
+Need char = 'X'
+ bXaX
+ 0: bXaX
+ 1: X
+
+/(?:(?(2y)a|b)(X))+/I
+Failed: reference to non-existent subpattern at offset 9
+
+/(?:(?(ZA)a|b)(?P<ZZ>X))+/I
+Failed: reference to non-existent subpattern at offset 9
+
+/(?:(?(ZZ)a|b)(?(ZZ)a|b)(?P<ZZ>X))+/I
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ ZZ 1
+No options
+No first char
+Need char = 'X'
+ bbXaaX
+ 0: bbXaaX
+ 1: X
+
+/(?:(?(ZZ)a|\(b\))\\(?P<ZZ>X))+/I
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ ZZ 1
+No options
+No first char
+Need char = 'X'
+ (b)\\Xa\\X
+ 0: (b)\Xa\X
+ 1: X
+
+/(?P<ABC/I
+Failed: syntax error in subpattern name (missing terminator) at offset 7
+
+/(?:(?(A)(?P=A)a|b)(?P<A>X|Y))+/I
+Capturing subpattern count = 1
+Max back reference = 1
+Named capturing subpatterns:
+ A 1
+No options
+No first char
+No need char
+ bXXaYYaY
+ 0: bXXaYYaY
+ 1: Y
+ bXYaXXaX
+ 0: bX
+ 1: X
+
+/()()()()()()()()()(?:(?(A)(?P=A)a|b)(?P<A>X|Y))+/I
+Capturing subpattern count = 10
+Max back reference = 10
+Named capturing subpatterns:
+ A 10
+No options
+No first char
+No need char
+ bXXaYYaY
+ 0: bXXaYYaY
+ 1:
+ 2:
+ 3:
+ 4:
+ 5:
+ 6:
+ 7:
+ 8:
+ 9:
+10: Y
+
+/\777/I
+Failed: octal value is greater than \377 (not in UTF-8 mode) at offset 3
+
+/\s*,\s*/IS
+Capturing subpattern count = 0
+Partial matching not supported
+No options
+No first char
+Need char = ','
+Starting byte set: \x09 \x0a \x0c \x0d \x20 ,
+ \x0b,\x0b
+ 0: ,
+ \x0c,\x0d
+ 0: \x0c,\x0d
+
+/^abc/Im
+Capturing subpattern count = 0
+Options: multiline
+First char at start or follows newline
+Need char = 'c'
+ xyz\nabc
+ 0: abc
+ xyz\nabc\<lf>
+ 0: abc
+ xyz\r\nabc\<lf>
+ 0: abc
+ xyz\rabc\<cr>
+ 0: abc
+ xyz\r\nabc\<crlf>
+ 0: abc
+ ** Failers
+No match
+ xyz\nabc\<cr>
+No match
+ xyz\r\nabc\<cr>
+No match
+ xyz\nabc\<crlf>
+No match
+ xyz\rabc\<crlf>
+No match
+ xyz\rabc\<lf>
+No match
+
+/abc$/Im<lf>
+Capturing subpattern count = 0
+Options: multiline
+Forced newline sequence: LF
+First char = 'a'
+Need char = 'c'
+ xyzabc
+ 0: abc
+ xyzabc\n
+ 0: abc
+ xyzabc\npqr
+ 0: abc
+ xyzabc\r\<cr>
+ 0: abc
+ xyzabc\rpqr\<cr>
+ 0: abc
+ xyzabc\r\n\<crlf>
+ 0: abc
+ xyzabc\r\npqr\<crlf>
+ 0: abc
+ ** Failers
+No match
+ xyzabc\r
+No match
+ xyzabc\rpqr
+No match
+ xyzabc\r\n
+No match
+ xyzabc\r\npqr
+No match
+
+/^abc/Im<cr>
+Capturing subpattern count = 0
+Options: multiline
+Forced newline sequence: CR
+First char at start or follows newline
+Need char = 'c'
+ xyz\rabcdef
+ 0: abc
+ xyz\nabcdef\<lf>
+ 0: abc
+ ** Failers
+No match
+ xyz\nabcdef
+No match
+
+/^abc/Im<lf>
+Capturing subpattern count = 0
+Options: multiline
+Forced newline sequence: LF
+First char at start or follows newline
+Need char = 'c'
+ xyz\nabcdef
+ 0: abc
+ xyz\rabcdef\<cr>
+ 0: abc
+ ** Failers
+No match
+ xyz\rabcdef
+No match
+
+/^abc/Im<crlf>
+Capturing subpattern count = 0
+Options: multiline
+Forced newline sequence: CRLF
+First char at start or follows newline
+Need char = 'c'
+ xyz\r\nabcdef
+ 0: abc
+ xyz\rabcdef\<cr>
+ 0: abc
+ ** Failers
+No match
+ xyz\rabcdef
+No match
+
+/^abc/Im<bad>
+Unknown newline type at: <bad>
+
+
+/abc/I
+Capturing subpattern count = 0
+No options
+First char = 'a'
+Need char = 'c'
+ xyz\rabc\<bad>
+Unknown newline type at: <bad>
+ abc
+ 0: abc
+
+/.*/I<lf>
+Capturing subpattern count = 0
+Partial matching not supported
+Options:
+Forced newline sequence: LF
+First char at start or follows newline
+No need char
+ abc\ndef
+ 0: abc
+ abc\rdef
+ 0: abc\x0ddef
+ abc\r\ndef
+ 0: abc\x0d
+ \<cr>abc\ndef
+ 0: abc\x0adef
+ \<cr>abc\rdef
+ 0: abc
+ \<cr>abc\r\ndef
+ 0: abc
+ \<crlf>abc\ndef
+ 0: abc\x0adef
+ \<crlf>abc\rdef
+ 0: abc\x0ddef
+ \<crlf>abc\r\ndef
+ 0: abc
+
+/\w+(.)(.)?def/Is
+Capturing subpattern count = 2
+Partial matching not supported
+Options: dotall
+No first char
+Need char = 'f'
+ abc\ndef
+ 0: abc\x0adef
+ 1: \x0a
+ abc\rdef
+ 0: abc\x0ddef
+ 1: \x0d
+ abc\r\ndef
+ 0: abc\x0d\x0adef
+ 1: \x0d
+ 2: \x0a
+
++((?:\s|//.*\\n|/[*](?:\\n|.)*?[*]/)*)+I
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+ /* this is a C style comment */\M
+Minimum match() limit = 120
+Minimum match() recursion limit = 6
+ 0: /* this is a C style comment */
+ 1: /* this is a C style comment */
+
+/(?P<B>25[0-5]|2[0-4]\d|[01]?\d?\d)(?:\.(?P>B)){3}/I
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ B 1
+No options
+No first char
+Need char = '.'
+
+/()()()()()()()()()()()()()()()()()()()()
+ ()()()()()()()()()()()()()()()()()()()()
+ ()()()()()()()()()()()()()()()()()()()()
+ ()()()()()()()()()()()()()()()()()()()()
+ ()()()()()()()()()()()()()()()()()()()()
+ (.(.))/Ix
+Capturing subpattern count = 102
+Options: extended
+No first char
+No need char
+ XY\O400
+ 0: XY
+ 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: XY
+102: Y
+
+/(a*b|(?i:c*(?-i)d))/IS
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+Starting byte set: C a b c d
+
+/()[ab]xyz/IS
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'z'
+Starting byte set: a b
+
+/(|)[ab]xyz/IS
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'z'
+Starting byte set: a b
+
+/(|c)[ab]xyz/IS
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'z'
+Starting byte set: a b c
+
+/(|c?)[ab]xyz/IS
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'z'
+Starting byte set: a b c
+
+/(d?|c?)[ab]xyz/IS
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'z'
+Starting byte set: a b c d
+
+/(d?|c)[ab]xyz/IS
+Capturing subpattern count = 1
+No options
+No first char
+Need char = 'z'
+Starting byte set: a b c d
+
+/^a*b\d/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ a*+
+ b
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored
+No first char
+Need char = 'b'
+
+/^a*+b\d/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ a*+
+ b
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored
+No first char
+Need char = 'b'
+
+/^a*?b\d/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ a*+
+ b
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored
+No first char
+Need char = 'b'
+
+/^a+A\d/DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ a++
+ A
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored
+No first char
+Need char = 'A'
+ aaaA5
+ 0: aaaA5
+ ** Failers
+No match
+ aaaa5
+No match
+
+/^a*A\d/IiDZ
+------------------------------------------------------------------
+ Bra
+ ^
+ a*
+ NC A
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: anchored caseless
+No first char
+Need char = 'A' (caseless)
+ aaaA5
+ 0: aaaA5
+ aaaa5
+ 0: aaaa5
+
+/(a*|b*)[cd]/IS
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+Starting byte set: a b c d
+
+/(a+|b*)[cd]/IS
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+Starting byte set: a b c d
+
+/(a*|b+)[cd]/IS
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+Starting byte set: a b c d
+
+/(a+|b+)[cd]/IS
+Capturing subpattern count = 1
+Partial matching not supported
+No options
+No first char
+No need char
+Starting byte set: a b
+
+/((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+ ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
+ (((
+ a
+ ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+ ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
+ )))
+/Ix
+Capturing subpattern count = 203
+Options: extended
+First char = 'a'
+No need char
+ large nest
+Matched, but too many substrings
+ 0: a
+ 1: a
+ 2: a
+ 3: a
+ 4: a
+ 5: a
+ 6: a
+ 7: a
+ 8: a
+ 9: a
+10: a
+11: a
+12: a
+13: a
+14: a
+
+/a*\d/BZ
+------------------------------------------------------------------
+ Bra
+ a*+
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/a*\D/BZ
+------------------------------------------------------------------
+ Bra
+ a*
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/0*\d/BZ
+------------------------------------------------------------------
+ Bra
+ 0*
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/0*\D/BZ
+------------------------------------------------------------------
+ Bra
+ 0*+
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/a*\s/BZ
+------------------------------------------------------------------
+ Bra
+ a*+
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/a*\S/BZ
+------------------------------------------------------------------
+ Bra
+ a*
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/ *\s/BZ
+------------------------------------------------------------------
+ Bra
+ *
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/ *\S/BZ
+------------------------------------------------------------------
+ Bra
+ *+
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/a*\w/BZ
+------------------------------------------------------------------
+ Bra
+ a*
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/a*\W/BZ
+------------------------------------------------------------------
+ Bra
+ a*+
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/=*\w/BZ
+------------------------------------------------------------------
+ Bra
+ =*+
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/=*\W/BZ
+------------------------------------------------------------------
+ Bra
+ =*
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*a/BZ
+------------------------------------------------------------------
+ Bra
+ \d*+
+ a
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*2/BZ
+------------------------------------------------------------------
+ Bra
+ \d*
+ 2
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*\d/BZ
+------------------------------------------------------------------
+ Bra
+ \d*
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*\D/BZ
+------------------------------------------------------------------
+ Bra
+ \d*+
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*\s/BZ
+------------------------------------------------------------------
+ Bra
+ \d*+
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*\S/BZ
+------------------------------------------------------------------
+ Bra
+ \d*
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*\w/BZ
+------------------------------------------------------------------
+ Bra
+ \d*
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*\W/BZ
+------------------------------------------------------------------
+ Bra
+ \d*+
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*a/BZ
+------------------------------------------------------------------
+ Bra
+ \D*
+ a
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*2/BZ
+------------------------------------------------------------------
+ Bra
+ \D*+
+ 2
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*\d/BZ
+------------------------------------------------------------------
+ Bra
+ \D*+
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*\D/BZ
+------------------------------------------------------------------
+ Bra
+ \D*
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*\s/BZ
+------------------------------------------------------------------
+ Bra
+ \D*
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*\S/BZ
+------------------------------------------------------------------
+ Bra
+ \D*
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*\w/BZ
+------------------------------------------------------------------
+ Bra
+ \D*
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/\D*\W/BZ
+------------------------------------------------------------------
+ Bra
+ \D*
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*a/BZ
+------------------------------------------------------------------
+ Bra
+ \s*+
+ a
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*2/BZ
+------------------------------------------------------------------
+ Bra
+ \s*+
+ 2
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*\d/BZ
+------------------------------------------------------------------
+ Bra
+ \s*+
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*\D/BZ
+------------------------------------------------------------------
+ Bra
+ \s*
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*\s/BZ
+------------------------------------------------------------------
+ Bra
+ \s*
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*\S/BZ
+------------------------------------------------------------------
+ Bra
+ \s*+
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*\w/BZ
+------------------------------------------------------------------
+ Bra
+ \s*+
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/\s*\W/BZ
+------------------------------------------------------------------
+ Bra
+ \s*
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*a/BZ
+------------------------------------------------------------------
+ Bra
+ \S*
+ a
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*2/BZ
+------------------------------------------------------------------
+ Bra
+ \S*
+ 2
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*\d/BZ
+------------------------------------------------------------------
+ Bra
+ \S*
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*\D/BZ
+------------------------------------------------------------------
+ Bra
+ \S*
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*\s/BZ
+------------------------------------------------------------------
+ Bra
+ \S*+
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*\S/BZ
+------------------------------------------------------------------
+ Bra
+ \S*
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*\w/BZ
+------------------------------------------------------------------
+ Bra
+ \S*
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/\S*\W/BZ
+------------------------------------------------------------------
+ Bra
+ \S*
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*a/BZ
+------------------------------------------------------------------
+ Bra
+ \w*
+ a
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*2/BZ
+------------------------------------------------------------------
+ Bra
+ \w*
+ 2
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*\d/BZ
+------------------------------------------------------------------
+ Bra
+ \w*
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*\D/BZ
+------------------------------------------------------------------
+ Bra
+ \w*
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*\s/BZ
+------------------------------------------------------------------
+ Bra
+ \w*+
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*\S/BZ
+------------------------------------------------------------------
+ Bra
+ \w*
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*\w/BZ
+------------------------------------------------------------------
+ Bra
+ \w*
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w*\W/BZ
+------------------------------------------------------------------
+ Bra
+ \w*+
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*a/BZ
+------------------------------------------------------------------
+ Bra
+ \W*+
+ a
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*2/BZ
+------------------------------------------------------------------
+ Bra
+ \W*+
+ 2
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*\d/BZ
+------------------------------------------------------------------
+ Bra
+ \W*+
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*\D/BZ
+------------------------------------------------------------------
+ Bra
+ \W*
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*\s/BZ
+------------------------------------------------------------------
+ Bra
+ \W*
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*\S/BZ
+------------------------------------------------------------------
+ Bra
+ \W*
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*\w/BZ
+------------------------------------------------------------------
+ Bra
+ \W*+
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/\W*\W/BZ
+------------------------------------------------------------------
+ Bra
+ \W*
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+
+/[^a]+a/BZ
+------------------------------------------------------------------
+ Bra
+ [^a]++
+ a
+ Ket
+ End
+------------------------------------------------------------------
+
+/[^a]+a/BZi
+------------------------------------------------------------------
+ Bra
+ [^A]++
+ NC a
+ Ket
+ End
+------------------------------------------------------------------
+
+/[^a]+A/BZi
+------------------------------------------------------------------
+ Bra
+ [^A]++
+ NC A
+ Ket
+ End
+------------------------------------------------------------------
+
+/[^a]+b/BZ
+------------------------------------------------------------------
+ Bra
+ [^a]+
+ b
+ Ket
+ End
+------------------------------------------------------------------
+
+/[^a]+\d/BZ
+------------------------------------------------------------------
+ Bra
+ [^a]+
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/a*[^a]/BZ
+------------------------------------------------------------------
+ Bra
+ a*
+ [^a]
+ Ket
+ End
+------------------------------------------------------------------
+
+/(?P<abc>x)(?P<xyz>y)/I
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ abc 1
+ xyz 2
+No options
+First char = 'x'
+Need char = 'y'
+ xy\Cabc\Cxyz
+ 0: xy
+ 1: x
+ 2: y
+ C x (1) abc
+ C y (1) xyz
+
+/(?<abc>x)(?'xyz'y)/I
+Capturing subpattern count = 2
+Named capturing subpatterns:
+ abc 1
+ xyz 2
+No options
+First char = 'x'
+Need char = 'y'
+ xy\Cabc\Cxyz
+ 0: xy
+ 1: x
+ 2: y
+ C x (1) abc
+ C y (1) xyz
+
+/(?<abc'x)(?'xyz'y)/I
+Failed: syntax error in subpattern name (missing terminator) at offset 6
+
+/(?<abc>x)(?'xyz>y)/I
+Failed: syntax error in subpattern name (missing terminator) at offset 15
+
+/(?P'abc'x)(?P<xyz>y)/I
+Failed: unrecognized character after (?P at offset 3
+
+/^(?:(?(ZZ)a|b)(?<ZZ>X))+/
+ bXaX
+ 0: bXaX
+ 1: X
+ bXbX
+ 0: bX
+ 1: X
+ ** Failers
+No match
+ aXaX
+No match
+ aXbX
+No match
+
+/^(?P>abc)(?<abcd>xxx)/
+Failed: reference to non-existent subpattern at offset 8
+
+/^(?P>abc)(?<abc>x|y)/
+ xx
+ 0: xx
+ 1: x
+ xy
+ 0: xy
+ 1: y
+ yy
+ 0: yy
+ 1: y
+ yx
+ 0: yx
+ 1: x
+
+/^(?P>abc)(?P<abc>x|y)/
+ xx
+ 0: xx
+ 1: x
+ xy
+ 0: xy
+ 1: y
+ yy
+ 0: yy
+ 1: y
+ yx
+ 0: yx
+ 1: x
+
+/^((?(abc)a|b)(?<abc>x|y))+/
+ bxay
+ 0: bxay
+ 1: ay
+ 2: y
+ bxby
+ 0: bx
+ 1: bx
+ 2: x
+ ** Failers
+No match
+ axby
+No match
+
+/^(((?P=abc)|X)(?<abc>x|y))+/
+ XxXxxx
+ 0: XxXxxx
+ 1: xx
+ 2: x
+ 3: x
+ XxXyyx
+ 0: XxXyyx
+ 1: yx
+ 2: y
+ 3: x
+ XxXyxx
+ 0: XxXy
+ 1: Xy
+ 2: X
+ 3: y
+ ** Failers
+No match
+ x
+No match
+
+/^(?1)(abc)/
+ abcabc
+ 0: abcabc
+ 1: abc
+
+/^(?:(?:\1|X)(a|b))+/
+ Xaaa
+ 0: Xaaa
+ 1: a
+ Xaba
+ 0: Xa
+ 1: a
+
+/^[\E\Qa\E-\Qz\E]+/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [a-z]+
+ Ket
+ End
+------------------------------------------------------------------
+
+/^[a\Q]bc\E]/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\]a-c]
+ Ket
+ End
+------------------------------------------------------------------
+
+/^[a-\Q\E]/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\-a]
+ Ket
+ End
+------------------------------------------------------------------
+
+/^(?P>abc)[()](?<abc>)/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ Once
+ Recurse
+ Ket
+ [()]
+ CBra 1
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+
+/^((?(abc)y)[()](?P<abc>x))+/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ CBra 1
+ Cond
+ 2 Cond ref
+ y
+ Ket
+ [()]
+ CBra 2
+ x
+ Ket
+ KetRmax
+ Ket
+ End
+------------------------------------------------------------------
+ (xy)x
+ 0: (xy)x
+ 1: y)x
+ 2: x
+
+/^(?P>abc)\Q()\E(?<abc>)/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ Once
+ Recurse
+ Ket
+ ()
+ CBra 1
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+
+/^(?P>abc)[a\Q(]\E(](?<abc>)/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ Once
+ Recurse
+ Ket
+ [(\]a]
+ CBra 1
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+
+/^(?P>abc) # this is (a comment)
+ (?<abc>)/BZx
+------------------------------------------------------------------
+ Bra
+ ^
+ Once
+ Recurse
+ Ket
+ CBra 1
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+
+/^\W*(?:(?<one>(?<two>.)\W*(?&one)\W*\k<two>|)|(?<three>(?<four>.)\W*(?&three)\W*\k'four'|\W*.\W*))\W*$/Ii
+Capturing subpattern count = 4
+Max back reference = 4
+Named capturing subpatterns:
+ four 4
+ one 1
+ three 3
+ two 2
+Partial matching not supported
+Options: anchored caseless
+No first char
+No need char
+ 1221
+ 0: 1221
+ 1: 1221
+ 2: 1
+ Satan, oscillate my metallic sonatas!
+ 0: Satan, oscillate my metallic sonatas!
+ 1: <unset>
+ 2: <unset>
+ 3: Satan, oscillate my metallic sonatas
+ 4: S
+ A man, a plan, a canal: Panama!
+ 0: A man, a plan, a canal: Panama!
+ 1: <unset>
+ 2: <unset>
+ 3: A man, a plan, a canal: Panama
+ 4: A
+ Able was I ere I saw Elba.
+ 0: Able was I ere I saw Elba.
+ 1: <unset>
+ 2: <unset>
+ 3: Able was I ere I saw Elba
+ 4: A
+ *** Failers
+No match
+ The quick brown fox
+No match
+
+/(?=(\w+))\1:/I
+Capturing subpattern count = 1
+Max back reference = 1
+Partial matching not supported
+No options
+No first char
+Need char = ':'
+ abcd:
+ 0: abcd:
+ 1: abcd
+
+/(?=(?'abc'\w+))\k<abc>:/I
+Capturing subpattern count = 1
+Max back reference = 1
+Named capturing subpatterns:
+ abc 1
+Partial matching not supported
+No options
+No first char
+Need char = ':'
+ abcd:
+ 0: abcd:
+ 1: abcd
+
+/(?'abc'\w+):\k<abc>{2}/
+ a:aaxyz
+ 0: a:aa
+ 1: a
+ ab:ababxyz
+ 0: ab:abab
+ 1: ab
+ ** Failers
+No match
+ a:axyz
+No match
+ ab:abxyz
+No match
+
+/(?'abc'a|b)(?<abc>d|e)\k<abc>{2}/J
+ adaa
+ 0: adaa
+ 1: a
+ 2: d
+ ** Failers
+No match
+ addd
+No match
+ adbb
+No match
+
+/(?'abc'a|b)(?<abc>d|e)(?&abc){2}/J
+ bdaa
+ 0: bdaa
+ 1: b
+ 2: d
+ bdab
+ 0: bdab
+ 1: b
+ 2: d
+ ** Failers
+No match
+ bddd
+No match
+
+/^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)/x
+ abd
+ 0: abd
+ 1: a
+ ce
+ 0: ce
+
+/(?(<bc))/
+Failed: malformed number or name after (?( at offset 6
+
+/(?(''))/
+Failed: assertion expected after (?( at offset 4
+
+/(?('R')stuff)/
+Failed: reference to non-existent subpattern at offset 7
+
+/((abc (?(R) (?(R1)1) (?(R2)2) X | (?1) (?2) (?R) ))) /x
+ abcabc1Xabc2XabcXabcabc
+ 0: abcabc1Xabc2XabcX
+ 1: abcabc1Xabc2XabcX
+ 2: abcabc1Xabc2XabcX
+
+/(?<A> (?'B' abc (?(R) (?(R&A)1) (?(R&B)2) X | (?1) (?2) (?R) ))) /x
+ abcabc1Xabc2XabcXabcabc
+ 0: abcabc1Xabc2XabcX
+ 1: abcabc1Xabc2XabcX
+ 2: abcabc1Xabc2XabcX
+
+/(?<A> (?'B' abc (?(R) (?(R&1)1) (?(R&B)2) X | (?1) (?2) (?R) ))) /x
+Failed: reference to non-existent subpattern at offset 29
+
+/(?<1> (?'B' abc (?(R) (?(R&1)1) (?(R&B)2) X | (?1) (?2) (?R) ))) /x
+ abcabc1Xabc2XabcXabcabc
+ 0: abcabc1Xabc2XabcX
+ 1: abcabc1Xabc2XabcX
+ 2: abcabc1Xabc2XabcX
+
+/^(?(DEFINE) (?<A> a) (?<B> b) ) (?&A) (?&B) /x
+ abcd
+ 0: ab
+ 1: <unset>
+ 2: <unset>
+
+/(?<NAME>(?&NAME_PAT))\s+(?<ADDR>(?&ADDRESS_PAT))
+ (?(DEFINE)
+ (?<NAME_PAT>[a-z]+)
+ (?<ADDRESS_PAT>\d+)
+ )/x
+ metcalfe 33
+ 0: metcalfe 33
+ 1: metcalfe
+ 2: 33
+ 3: <unset>
+ 4: <unset>
+
+/^(?(DEFINE) abc | xyz ) /x
+Failed: DEFINE group contains more than one branch at offset 22
+
+/(?(DEFINE) abc) xyz/xI
+Capturing subpattern count = 0
+Options: extended
+First char = 'x'
+Need char = 'z'
+
+/(?(DEFINE) abc){3} xyz/x
+Failed: repeating a DEFINE group is not allowed at offset 17
+
+/(a|)*\d/
+ \O0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+ \O0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+Matched, but too many substrings
+
+/^a.b/<lf>
+ a\rb
+ 0: a\x0db
+ a\nb\<cr>
+ 0: a\x0ab
+ a\x85b\<anycrlf>
+ 0: a\x85b
+ ** Failers
+No match
+ a\nb
+No match
+ a\nb\<any>
+No match
+ a\rb\<cr>
+No match
+ a\rb\<any>
+No match
+ a\x85b\<any>
+No match
+ a\rb\<anycrlf>
+No match
+
+/^abc./mgx<any>
+ abc1 \x0aabc2 \x0babc3xx \x0cabc4 \x0dabc5xx \x0d\x0aabc6 \x85abc7 \x{2028}abc8 \x{2029}abc9 JUNK
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc7
+
+/abc.$/mgx<any>
+ abc1\x0a abc2\x0b abc3\x0c abc4\x0d abc5\x0d\x0a abc6\x85 abc7\x{2028} abc8\x{2029} abc9
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc9
+
+/a/<cr><any>
+
+/a/<any><crlf>
+Failed: inconsistent NEWLINE options at offset 0
+
+/^a\Rb/<bsr_unicode>
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x0bb
+ 0: a\x0bb
+ a\x0cb
+ 0: a\x0cb
+ a\x85b
+ 0: a\x85b
+ ** Failers
+No match
+ a\n\rb
+No match
+
+/^a\R*b/<bsr_unicode>
+ ab
+ 0: ab
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x0bb
+ 0: a\x0bb
+ a\x0cb
+ 0: a\x0cb
+ a\x85b
+ 0: a\x85b
+ a\n\rb
+ 0: a\x0a\x0db
+ a\n\r\x85\x0cb
+ 0: a\x0a\x0d\x85\x0cb
+
+/^a\R+b/<bsr_unicode>
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x0bb
+ 0: a\x0bb
+ a\x0cb
+ 0: a\x0cb
+ a\x85b
+ 0: a\x85b
+ a\n\rb
+ 0: a\x0a\x0db
+ a\n\r\x85\x0cb
+ 0: a\x0a\x0d\x85\x0cb
+ ** Failers
+No match
+ ab
+No match
+
+/^a\R{1,3}b/<bsr_unicode>
+ a\nb
+ 0: a\x0ab
+ a\n\rb
+ 0: a\x0a\x0db
+ a\n\r\x85b
+ 0: a\x0a\x0d\x85b
+ a\r\n\r\nb
+ 0: a\x0d\x0a\x0d\x0ab
+ a\r\n\r\n\r\nb
+ 0: a\x0d\x0a\x0d\x0a\x0d\x0ab
+ a\n\r\n\rb
+ 0: a\x0a\x0d\x0a\x0db
+ a\n\n\r\nb
+ 0: a\x0a\x0a\x0d\x0ab
+ ** Failers
+No match
+ a\n\n\n\rb
+No match
+ a\r
+No match
+
+/^a[\R]b/<bsr_unicode>
+ aRb
+ 0: aRb
+ ** Failers
+No match
+ a\nb
+No match
+
+/(?&abc)X(?<abc>P)/I
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ abc 1
+No options
+No first char
+Need char = 'P'
+ abcPXP123
+ 0: PXP
+ 1: P
+
+/(?1)X(?<abc>P)/I
+Capturing subpattern count = 1
+Named capturing subpatterns:
+ abc 1
+No options
+No first char
+Need char = 'P'
+ abcPXP123
+ 0: PXP
+ 1: P
+
+/(?(DEFINE)(?<byte>2[0-4]\d|25[0-5]|1\d\d|[1-9]?\d))\b(?&byte)(\.(?&byte)){3}/
+ 1.2.3.4
+ 0: 1.2.3.4
+ 1: <unset>
+ 2: .4
+ 131.111.10.206
+ 0: 131.111.10.206
+ 1: <unset>
+ 2: .206
+ 10.0.0.0
+ 0: 10.0.0.0
+ 1: <unset>
+ 2: .0
+ ** Failers
+No match
+ 10.6
+No match
+ 455.3.4.5
+No match
+
+/\b(?&byte)(\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\d|25[0-5]|1\d\d|[1-9]?\d))/
+ 1.2.3.4
+ 0: 1.2.3.4
+ 1: .4
+ 2: <unset>
+ 131.111.10.206
+ 0: 131.111.10.206
+ 1: .206
+ 2: <unset>
+ 10.0.0.0
+ 0: 10.0.0.0
+ 1: .0
+ 2: <unset>
+ ** Failers
+No match
+ 10.6
+No match
+ 455.3.4.5
+No match
+
+/(?:a(?&abc)b)*(?<abc>x)/
+ 123axbaxbaxbx456
+ 0: axbaxbaxbx
+ 1: x
+ 123axbaxbaxb456
+ 0: x
+ 1: x
+
+/(?:a(?&abc)b){1,5}(?<abc>x)/
+ 123axbaxbaxbx456
+ 0: axbaxbaxbx
+ 1: x
+
+/(?:a(?&abc)b){2,5}(?<abc>x)/
+ 123axbaxbaxbx456
+ 0: axbaxbaxbx
+ 1: x
+
+/(?:a(?&abc)b){2,}(?<abc>x)/
+ 123axbaxbaxbx456
+ 0: axbaxbaxbx
+ 1: x
+
+/(abc)(?i:(?1))/
+ defabcabcxyz
+ 0: abcabc
+ 1: abc
+ DEFabcABCXYZ
+No match
+
+/(abc)(?:(?i)(?1))/
+ defabcabcxyz
+ 0: abcabc
+ 1: abc
+ DEFabcABCXYZ
+No match
+
+/^(a(b))\1\g1\g{1}\g-1\g{-1}\g{-02}Z/
+ ababababbbabZXXXX
+ 0: ababababbbabZ
+ 1: ab
+ 2: b
+
+/^(a)\g-2/
+Failed: reference to non-existent subpattern at offset 7
+
+/^(a)\g/
+Failed: \g is not followed by a braced name or an optionally braced non-zero number at offset 5
+
+/^(a)\g{0}/
+Failed: \g is not followed by a braced name or an optionally braced non-zero number at offset 7
+
+/^(a)\g{3/
+Failed: \g is not followed by a braced name or an optionally braced non-zero number at offset 8
+
+/^(a)\g{4a}/
+Failed: reference to non-existent subpattern at offset 9
+
+/^a.b/<lf>
+ a\rb
+ 0: a\x0db
+ *** Failers
+No match
+ a\nb
+No match
+
+/.+foo/
+ afoo
+ 0: afoo
+ ** Failers
+No match
+ \r\nfoo
+No match
+ \nfoo
+No match
+
+/.+foo/<crlf>
+ afoo
+ 0: afoo
+ \nfoo
+ 0: \x0afoo
+ ** Failers
+No match
+ \r\nfoo
+No match
+
+/.+foo/<any>
+ afoo
+ 0: afoo
+ ** Failers
+No match
+ \nfoo
+No match
+ \r\nfoo
+No match
+
+/.+foo/s
+ afoo
+ 0: afoo
+ \r\nfoo
+ 0: \x0d\x0afoo
+ \nfoo
+ 0: \x0afoo
+
+/^$/mg<any>
+ abc\r\rxyz
+ 0:
+ abc\n\rxyz
+ 0:
+ ** Failers
+No match
+ abc\r\nxyz
+No match
+
+/(?m)^$/<any>g+
+ abc\r\n\r\n
+ 0:
+ 0+ \x0d\x0a
+
+/(?m)^$|^\r\n/<any>g+
+ abc\r\n\r\n
+ 0:
+ 0+ \x0d\x0a
+ 0: \x0d\x0a
+ 0+
+
+/(?m)$/<any>g+
+ abc\r\n\r\n
+ 0:
+ 0+ \x0d\x0a\x0d\x0a
+ 0:
+ 0+ \x0d\x0a
+ 0:
+ 0+
+
+/abc.$/mgx<anycrlf>
+ abc1\x0a abc2\x0b abc3\x0c abc4\x0d abc5\x0d\x0a abc6\x85 abc7\x{2028} abc8\x{2029} abc9
+ 0: abc1
+ 0: abc4
+ 0: abc5
+ 0: abc9
+
+/^X/m
+ XABC
+ 0: X
+ ** Failers
+No match
+ XABC\B
+No match
+
+/(ab|c)(?-1)/BZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ ab
+ Alt
+ c
+ Ket
+ Once
+ Recurse
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+ abc
+ 0: abc
+ 1: ab
+
+/xy(?+1)(abc)/BZ
+------------------------------------------------------------------
+ Bra
+ xy
+ Once
+ Recurse
+ Ket
+ CBra 1
+ abc
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+ xyabcabc
+ 0: xyabcabc
+ 1: abc
+ ** Failers
+No match
+ xyabc
+No match
+
+/x(?-0)y/
+Failed: (?+ or (?- or (?(+ or (?(- must be followed by a non-zero number at offset 5
+
+/x(?-1)y/
+Failed: reference to non-existent subpattern at offset 5
+
+/x(?+0)y/
+Failed: (?+ or (?- or (?(+ or (?(- must be followed by a non-zero number at offset 5
+
+/x(?+1)y/
+Failed: reference to non-existent subpattern at offset 5
+
+/^(abc)?(?(-1)X|Y)/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ Brazero
+ CBra 1
+ abc
+ Ket
+ Cond
+ 1 Cond ref
+ X
+ Alt
+ Y
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+ abcX
+ 0: abcX
+ 1: abc
+ Y
+ 0: Y
+ ** Failers
+No match
+ abcY
+No match
+
+/^((?(+1)X|Y)(abc))+/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ CBra 1
+ Cond
+ 2 Cond ref
+ X
+ Alt
+ Y
+ Ket
+ CBra 2
+ abc
+ Ket
+ KetRmax
+ Ket
+ End
+------------------------------------------------------------------
+ YabcXabc
+ 0: YabcXabc
+ 1: Xabc
+ 2: abc
+ YabcXabcXabc
+ 0: YabcXabcXabc
+ 1: Xabc
+ 2: abc
+ ** Failers
+No match
+ XabcXabc
+No match
+
+/(?(-1)a)/BZ
+Failed: reference to non-existent subpattern at offset 6
+
+/((?(-1)a))/BZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ Cond
+ 1 Cond ref
+ a
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+
+/((?(-2)a))/BZ
+Failed: reference to non-existent subpattern at offset 7
+
+/^(?(+1)X|Y)(.)/BZ
+------------------------------------------------------------------
+ Bra
+ ^
+ Cond
+ 1 Cond ref
+ X
+ Alt
+ Y
+ Ket
+ CBra 1
+ Any
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+ Y!
+ 0: Y!
+ 1: !
+
+/(foo)\Kbar/
+ foobar
+ 0: bar
+ 1: foo
+
+/(foo)(\Kbar|baz)/
+ foobar
+ 0: bar
+ 1: foo
+ 2: bar
+ foobaz
+ 0: foobaz
+ 1: foo
+ 2: baz
+
+/(foo\Kbar)baz/
+ foobarbaz
+ 0: barbaz
+ 1: foobar
+
+/(?<A>tom|bon)-\k{A}/
+ tom-tom
+ 0: tom-tom
+ 1: tom
+ bon-bon
+ 0: bon-bon
+ 1: bon
+ ** Failers
+No match
+ tom-bon
+No match
+
+/(?<A>tom|bon)-\g{A}/
+ tom-tom
+ 0: tom-tom
+ 1: tom
+ bon-bon
+ 0: bon-bon
+ 1: bon
+
+/\g{A/
+Failed: syntax error in subpattern name (missing terminator) at offset 4
+
+/(?|(abc)|(xyz))/BZ
+------------------------------------------------------------------
+ Bra
+ Bra
+ CBra 1
+ abc
+ Ket
+ Alt
+ CBra 1
+ xyz
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+ >abc<
+ 0: abc
+ 1: abc
+ >xyz<
+ 0: xyz
+ 1: xyz
+
+/(x)(?|(abc)|(xyz))(x)/BZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ x
+ Ket
+ Bra
+ CBra 2
+ abc
+ Ket
+ Alt
+ CBra 2
+ xyz
+ Ket
+ Ket
+ CBra 3
+ x
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+ xabcx
+ 0: xabcx
+ 1: x
+ 2: abc
+ 3: x
+ xxyzx
+ 0: xxyzx
+ 1: x
+ 2: xyz
+ 3: x
+
+/(x)(?|(abc)(pqr)|(xyz))(x)/BZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ x
+ Ket
+ Bra
+ CBra 2
+ abc
+ Ket
+ CBra 3
+ pqr
+ Ket
+ Alt
+ CBra 2
+ xyz
+ Ket
+ Ket
+ CBra 4
+ x
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+ xabcpqrx
+ 0: xabcpqrx
+ 1: x
+ 2: abc
+ 3: pqr
+ 4: x
+ xxyzx
+ 0: xxyzx
+ 1: x
+ 2: xyz
+ 3: <unset>
+ 4: x
+
+/(?|(abc)|(xyz))\1/
+ abcabc
+ 0: abcabc
+ 1: abc
+ xyzxyz
+ 0: xyzxyz
+ 1: xyz
+ ** Failers
+No match
+ abcxyz
+No match
+ xyzabc
+No match
+
+/(?|(abc)|(xyz))(?1)/
+ abcabc
+ 0: abcabc
+ 1: abc
+ xyzabc
+ 0: xyzabc
+ 1: xyz
+ ** Failers
+No match
+ xyzxyz
+No match
+
+/\H\h\V\v/
+ X X\x0a
+ 0: X X\x0a
+ X\x09X\x0b
+ 0: X\x09X\x0b
+ ** Failers
+No match
+ \xa0 X\x0a
+No match
+
+/\H*\h+\V?\v{3,4}/
+ \x09\x20\xa0X\x0a\x0b\x0c\x0d\x0a
+ 0: \x09 \xa0X\x0a\x0b\x0c\x0d
+ \x09\x20\xa0\x0a\x0b\x0c\x0d\x0a
+ 0: \x09 \xa0\x0a\x0b\x0c\x0d
+ \x09\x20\xa0\x0a\x0b\x0c
+ 0: \x09 \xa0\x0a\x0b\x0c
+ ** Failers
+No match
+ \x09\x20\xa0\x0a\x0b
+No match
+
+/\H{3,4}/
+ XY ABCDE
+ 0: ABCD
+ XY PQR ST
+ 0: PQR
+
+/.\h{3,4}./
+ XY AB PQRS
+ 0: B P
+
+/\h*X\h?\H+Y\H?Z/
+ >XNNNYZ
+ 0: XNNNYZ
+ > X NYQZ
+ 0: X NYQZ
+ ** Failers
+No match
+ >XYZ
+No match
+ > X NY Z
+No match
+
+/\v*X\v?Y\v+Z\V*\x0a\V+\x0b\V{2,3}\x0c/
+ >XY\x0aZ\x0aA\x0bNN\x0c
+ 0: XY\x0aZ\x0aA\x0bNN\x0c
+ >\x0a\x0dX\x0aY\x0a\x0bZZZ\x0aAAA\x0bNNN\x0c
+ 0: \x0a\x0dX\x0aY\x0a\x0bZZZ\x0aAAA\x0bNNN\x0c
+
+/[\h]/BZ
+------------------------------------------------------------------
+ Bra
+ [\x09 \xa0]
+ Ket
+ End
+------------------------------------------------------------------
+ >\x09<
+ 0: \x09
+
+/[\h]+/BZ
+------------------------------------------------------------------
+ Bra
+ [\x09 \xa0]+
+ Ket
+ End
+------------------------------------------------------------------
+ >\x09\x20\xa0<
+ 0: \x09 \xa0
+
+/[\v]/BZ
+------------------------------------------------------------------
+ Bra
+ [\x0a-\x0d\x85]
+ Ket
+ End
+------------------------------------------------------------------
+
+/[\H]/BZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\x08\x0a-\x1f!-\x9f\xa1-\xff]
+ Ket
+ End
+------------------------------------------------------------------
+
+/[^\h]/BZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\x08\x0a-\x1f!-\x9f\xa1-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+
+/[\V]/BZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\x09\x0e-\x84\x86-\xff]
+ Ket
+ End
+------------------------------------------------------------------
+
+/[\x0a\V]/BZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\x0a\x0e-\x84\x86-\xff]
+ Ket
+ End
+------------------------------------------------------------------
+
+/\H++X/BZ
+------------------------------------------------------------------
+ Bra
+ \H++
+ X
+ Ket
+ End
+------------------------------------------------------------------
+ ** Failers
+No match
+ XXXX
+No match
+
+/\H+\hY/BZ
+------------------------------------------------------------------
+ Bra
+ \H++
+ \h
+ Y
+ Ket
+ End
+------------------------------------------------------------------
+ XXXX Y
+ 0: XXXX Y
+
+/\H+ Y/BZ
+------------------------------------------------------------------
+ Bra
+ \H++
+ Y
+ Ket
+ End
+------------------------------------------------------------------
+
+/\h+A/BZ
+------------------------------------------------------------------
+ Bra
+ \h++
+ A
+ Ket
+ End
+------------------------------------------------------------------
+
+/\v*B/BZ
+------------------------------------------------------------------
+ Bra
+ \v*+
+ B
+ Ket
+ End
+------------------------------------------------------------------
+
+/\V+\x0a/BZ
+------------------------------------------------------------------
+ Bra
+ \V++
+ \x0a
+ Ket
+ End
+------------------------------------------------------------------
+
+/A+\h/BZ
+------------------------------------------------------------------
+ Bra
+ A++
+ \h
+ Ket
+ End
+------------------------------------------------------------------
+
+/ *\H/BZ
+------------------------------------------------------------------
+ Bra
+ *+
+ \H
+ Ket
+ End
+------------------------------------------------------------------
+
+/A*\v/BZ
+------------------------------------------------------------------
+ Bra
+ A*+
+ \v
+ Ket
+ End
+------------------------------------------------------------------
+
+/\x0b*\V/BZ
+------------------------------------------------------------------
+ Bra
+ \x0b*+
+ \V
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d+\h/BZ
+------------------------------------------------------------------
+ Bra
+ \d++
+ \h
+ Ket
+ End
+------------------------------------------------------------------
+
+/\d*\v/BZ
+------------------------------------------------------------------
+ Bra
+ \d*+
+ \v
+ Ket
+ End
+------------------------------------------------------------------
+
+/S+\h\S+\v/BZ
+------------------------------------------------------------------
+ Bra
+ S++
+ \h
+ \S++
+ \v
+ Ket
+ End
+------------------------------------------------------------------
+
+/\w{3,}\h\w+\v/BZ
+------------------------------------------------------------------
+ Bra
+ \w{3}
+ \w*+
+ \h
+ \w++
+ \v
+ Ket
+ End
+------------------------------------------------------------------
+
+/\h+\d\h+\w\h+\S\h+\H/BZ
+------------------------------------------------------------------
+ Bra
+ \h++
+ \d
+ \h++
+ \w
+ \h++
+ \S
+ \h++
+ \H
+ Ket
+ End
+------------------------------------------------------------------
+
+/\v+\d\v+\w\v+\S\v+\V/BZ
+------------------------------------------------------------------
+ Bra
+ \v++
+ \d
+ \v++
+ \w
+ \v+
+ \S
+ \v++
+ \V
+ Ket
+ End
+------------------------------------------------------------------
+
+/\H+\h\H+\d/BZ
+------------------------------------------------------------------
+ Bra
+ \H++
+ \h
+ \H+
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+
+/\V+\v\V+\w/BZ
+------------------------------------------------------------------
+ Bra
+ \V++
+ \v
+ \V+
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+
+/\( (?: [^()]* | (?R) )* \)/x
+(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(00)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)
+ 0: (0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(0(00)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)0)
+
+/[\E]AAA/
+Failed: missing terminating ] for character class at offset 7
+
+/[\Q\E]AAA/
+Failed: missing terminating ] for character class at offset 9
+
+/[^\E]AAA/
+Failed: missing terminating ] for character class at offset 8
+
+/[^\Q\E]AAA/
+Failed: missing terminating ] for character class at offset 10
+
+/[\E^]AAA/
+Failed: missing terminating ] for character class at offset 8
+
+/[\Q\E^]AAA/
+Failed: missing terminating ] for character class at offset 10
+
+/A(*PRUNE)B(*SKIP)C(*THEN)D(*COMMIT)E(*F)F(*FAIL)G(?!)H(*ACCEPT)I/BZ
+------------------------------------------------------------------
+ Bra
+ A
+ *PRUNE
+ B
+ *SKIP
+ C
+ *THEN
+ D
+ *COMMIT
+ E
+ *FAIL
+ F
+ *FAIL
+ G
+ *FAIL
+ H
+ *ACCEPT
+ I
+ Ket
+ End
+------------------------------------------------------------------
+
+/^a+(*FAIL)/
+ aaaaaa
+No match
+
+/a+b?c+(*FAIL)/
+ aaabccc
+No match
+
+/a+b?(*PRUNE)c+(*FAIL)/
+ aaabccc
+No match
+
+/a+b?(*COMMIT)c+(*FAIL)/
+ aaabccc
+No match
+
+/a+b?(*SKIP)c+(*FAIL)/
+ aaabcccaaabccc
+No match
+
+/^(?:aaa(*THEN)\w{6}|bbb(*THEN)\w{5}|ccc(*THEN)\w{4}|\w{3})/
+ aaaxxxxxx
+ 0: aaaxxxxxx
+ aaa++++++
+ 0: aaa
+ bbbxxxxx
+ 0: bbbxxxxx
+ bbb+++++
+ 0: bbb
+ cccxxxx
+ 0: cccxxxx
+ ccc++++
+ 0: ccc
+ dddddddd
+ 0: ddd
+
+/^(aaa(*THEN)\w{6}|bbb(*THEN)\w{5}|ccc(*THEN)\w{4}|\w{3})/
+ aaaxxxxxx
+ 0: aaaxxxxxx
+ 1: aaaxxxxxx
+ aaa++++++
+ 0: aaa
+ 1: aaa
+ bbbxxxxx
+ 0: bbbxxxxx
+ 1: bbbxxxxx
+ bbb+++++
+ 0: bbb
+ 1: bbb
+ cccxxxx
+ 0: cccxxxx
+ 1: cccxxxx
+ ccc++++
+ 0: ccc
+ 1: ccc
+ dddddddd
+ 0: ddd
+ 1: ddd
+
+/a+b?(*THEN)c+(*FAIL)/
+ aaabccc
+No match
+
+/(A (A|B(*ACCEPT)|C) D)(E)/x
+ ABX
+ 0: AB
+ AADE
+ 0: AADE
+ 1: AAD
+ 2: A
+ 3: E
+ ACDE
+ 0: ACDE
+ 1: ACD
+ 2: C
+ 3: E
+ ** Failers
+No match
+ AD
+No match
+
+/^a+(*FAIL)/C
+ aaaaaa
+--->aaaaaa
+ +0 ^ ^
+ +1 ^ a+
+ +3 ^ ^ (*FAIL)
+ +3 ^ ^ (*FAIL)
+ +3 ^ ^ (*FAIL)
+ +3 ^ ^ (*FAIL)
+ +3 ^ ^ (*FAIL)
+ +3 ^^ (*FAIL)
+No match
+
+/a+b?c+(*FAIL)/C
+ aaabccc
+--->aaabccc
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ c+
+ +6 ^ ^ (*FAIL)
+ +6 ^ ^ (*FAIL)
+ +6 ^ ^ (*FAIL)
+ +4 ^ ^ c+
+ +2 ^ ^ b?
+ +4 ^ ^ c+
+ +2 ^^ b?
+ +4 ^^ c+
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ c+
+ +6 ^ ^ (*FAIL)
+ +6 ^ ^ (*FAIL)
+ +6 ^ ^ (*FAIL)
+ +4 ^ ^ c+
+ +2 ^^ b?
+ +4 ^^ c+
+ +0 ^ a+
+ +2 ^^ b?
+ +4 ^ ^ c+
+ +6 ^ ^ (*FAIL)
+ +6 ^ ^ (*FAIL)
+ +6 ^ ^ (*FAIL)
+ +4 ^^ c+
+No match
+
+/a+b?(*PRUNE)c+(*FAIL)/C
+ aaabccc
+--->aaabccc
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ (*PRUNE)
++12 ^ ^ c+
++14 ^ ^ (*FAIL)
++14 ^ ^ (*FAIL)
++14 ^ ^ (*FAIL)
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ (*PRUNE)
++12 ^ ^ c+
++14 ^ ^ (*FAIL)
++14 ^ ^ (*FAIL)
++14 ^ ^ (*FAIL)
+ +0 ^ a+
+ +2 ^^ b?
+ +4 ^ ^ (*PRUNE)
++12 ^ ^ c+
++14 ^ ^ (*FAIL)
++14 ^ ^ (*FAIL)
++14 ^ ^ (*FAIL)
+No match
+
+/a+b?(*COMMIT)c+(*FAIL)/C
+ aaabccc
+--->aaabccc
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ (*COMMIT)
++13 ^ ^ c+
++15 ^ ^ (*FAIL)
++15 ^ ^ (*FAIL)
++15 ^ ^ (*FAIL)
+No match
+
+/a+b?(*SKIP)c+(*FAIL)/C
+ aaabcccaaabccc
+--->aaabcccaaabccc
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ (*SKIP)
++11 ^ ^ c+
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ (*SKIP)
++11 ^ ^ c+
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
+No match
+
+/a+b?(*THEN)c+(*FAIL)/C
+ aaabccc
+--->aaabccc
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ (*THEN)
++11 ^ ^ c+
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
+ +0 ^ a+
+ +2 ^ ^ b?
+ +4 ^ ^ (*THEN)
++11 ^ ^ c+
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
+ +0 ^ a+
+ +2 ^^ b?
+ +4 ^ ^ (*THEN)
++11 ^ ^ c+
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
++13 ^ ^ (*FAIL)
+No match
+
+/a(*PRUNE:XXX)b/
+Failed: (*VERB) with an argument is not supported at offset 8
+
+/a(*MARK)b/
+Failed: (*VERB) not recognized at offset 7
+
+/(?i:A{1,}\6666666666)/
+Failed: number is too big at offset 19
+
+/\g6666666666/
+Failed: number is too big at offset 11
+
+/[\g6666666666]/
+Failed: number is too big at offset 12
+
+/(?1)\c[/
+Failed: reference to non-existent subpattern at offset 3
+
+/.+A/<crlf>
+ \r\nA
+No match
+
+/\nA/<crlf>
+ \r\nA
+ 0: \x0aA
+
+/[\r\n]A/<crlf>
+ \r\nA
+ 0: \x0aA
+
+/(\r|\n)A/<crlf>
+ \r\nA
+ 0: \x0aA
+ 1: \x0a
+
+/a(*CR)b/
+Failed: (*VERB) not recognized at offset 5
+
+/(*CR)a.b/
+ a\nb
+ 0: a\x0ab
+ ** Failers
+No match
+ a\rb
+No match
+
+/(*CR)a.b/<lf>
+ a\nb
+ 0: a\x0ab
+ ** Failers
+No match
+ a\rb
+No match
+
+/(*LF)a.b/<CRLF>
+ a\rb
+ 0: a\x0db
+ ** Failers
+No match
+ a\nb
+No match
+
+/(*CRLF)a.b/
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ ** Failers
+No match
+ a\r\nb
+No match
+
+/(*ANYCRLF)a.b/<CR>
+ ** Failers
+No match
+ a\rb
+No match
+ a\nb
+No match
+ a\r\nb
+No match
+
+/(*ANY)a.b/<cr>
+ ** Failers
+No match
+ a\rb
+No match
+ a\nb
+No match
+ a\r\nb
+No match
+ a\x85b
+No match
+
+/a\Rb/I<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ ** Failers
+No match
+ a\x85b
+No match
+ a\x0bb
+No match
+
+/a\Rb/I<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x85b
+ 0: a\x85b
+ a\x0bb
+ 0: a\x0bb
+ ** Failers
+No match
+ a\x85b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/a\R?b/I<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ ** Failers
+No match
+ a\x85b
+No match
+ a\x0bb
+No match
+
+/a\R?b/I<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x85b
+ 0: a\x85b
+ a\x0bb
+ 0: a\x0bb
+ ** Failers
+No match
+ a\x85b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/a\R{2,4}b/I<bsr_anycrlf>
+Capturing subpattern count = 0
+Partial matching not supported
+Options: bsr_anycrlf
+First char = 'a'
+Need char = 'b'
+ a\r\n\nb
+ 0: a\x0d\x0a\x0ab
+ a\n\r\rb
+ 0: a\x0a\x0d\x0db
+ a\r\n\r\n\r\n\r\nb
+ 0: a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0ab
+ ** Failers
+No match
+ a\x85\85b
+No match
+ a\x0b\0bb
+No match
+
+/a\R{2,4}b/I<bsr_unicode>
+Capturing subpattern count = 0
+Partial matching not supported
+Options: bsr_unicode
+First char = 'a'
+Need char = 'b'
+ a\r\rb
+ 0: a\x0d\x0db
+ a\n\n\nb
+ 0: a\x0a\x0a\x0ab
+ a\r\n\n\r\rb
+ 0: a\x0d\x0a\x0a\x0d\x0db
+ a\x85\85b
+No match
+ a\x0b\0bb
+No match
+ ** Failers
+No match
+ a\r\r\r\r\rb
+No match
+ a\x85\85b\<bsr_anycrlf>
+No match
+ a\x0b\0bb\<bsr_anycrlf>
+No match
+
+/(*BSR_ANYCRLF)a\Rb/I
+Capturing subpattern count = 0
+Options: bsr_anycrlf
+First char = 'a'
+Need char = 'b'
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+
+/(*BSR_UNICODE)a\Rb/I
+Capturing subpattern count = 0
+Options: bsr_unicode
+First char = 'a'
+Need char = 'b'
+ a\x85b
+ 0: a\x85b
+
+/(*BSR_ANYCRLF)(*CRLF)a\Rb/I
+Capturing subpattern count = 0
+Options: bsr_anycrlf
+Forced newline sequence: CRLF
+First char = 'a'
+Need char = 'b'
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+
+/(*CRLF)(*BSR_UNICODE)a\Rb/I
+Capturing subpattern count = 0
+Options: bsr_unicode
+Forced newline sequence: CRLF
+First char = 'a'
+Need char = 'b'
+ a\x85b
+ 0: a\x85b
+
+/(*CRLF)(*BSR_ANYCRLF)(*CR)ab/I
+Capturing subpattern count = 0
+Options: bsr_anycrlf
+Forced newline sequence: CR
+First char = 'a'
+Need char = 'b'
+
+/(?<a>)(?&)/
+Failed: subpattern name expected at offset 9
+
+/(?<abc>)(?&a)/
+Failed: reference to non-existent subpattern at offset 12
+
+/(?<a>)(?&aaaaaaaaaaaaaaaaaaaaaaa)/
+Failed: reference to non-existent subpattern at offset 32
+
+/(?+-a)/
+Failed: digit expected after (?+ at offset 3
+
+/(?-+a)/
+Failed: unrecognized character after (? or (?- at offset 3
+
+/(?(-1))/
+Failed: reference to non-existent subpattern at offset 6
+
+/(?(+10))/
+Failed: reference to non-existent subpattern at offset 7
+
+/(?(10))/
+Failed: reference to non-existent subpattern at offset 6
+
+/(?(+2))()()/
+
+/(?(2))()()/
+
+/\k''/
+Failed: subpattern name expected at offset 3
+
+/\k<>/
+Failed: subpattern name expected at offset 3
+
+/\k{}/
+Failed: subpattern name expected at offset 3
+
+/(?P=)/
+Failed: subpattern name expected at offset 4
+
+/(?P>)/
+Failed: subpattern name expected at offset 4
+
+/(?!\w)(?R)/
+Failed: recursive call could loop indefinitely at offset 9
+
+/(?=\w)(?R)/
+Failed: recursive call could loop indefinitely at offset 9
+
+/(?<!\w)(?R)/
+Failed: recursive call could loop indefinitely at offset 10
+
+/(?<=\w)(?R)/
+Failed: recursive call could loop indefinitely at offset 10
+
+/[[:foo:]]/
+Failed: unknown POSIX class name at offset 3
+
+/[[:1234:]]/
+Failed: unknown POSIX class name at offset 3
+
+/[[:f\oo:]]/
+Failed: unknown POSIX class name at offset 3
+
+/[[: :]]/
+Failed: unknown POSIX class name at offset 3
+
+/[[:...:]]/
+Failed: unknown POSIX class name at offset 3
+
+/[[:l\ower:]]/
+Failed: unknown POSIX class name at offset 3
+
+/[[:abc\:]]/
+Failed: unknown POSIX class name at offset 3
+
+/[abc[:x\]pqr:]]/
+Failed: unknown POSIX class name at offset 6
+
+/[[:a\dz:]]/
+Failed: unknown POSIX class name at offset 3
+
+/ End of testinput2 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput3 b/lib/stdlib/test/re_SUITE_data/testoutput3
new file mode 100644
index 0000000000..28b1c3aaaf
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput3
@@ -0,0 +1,163 @@
+/^[\w]+/
+ *** Failers
+No match
+ �cole
+No match
+
+/^[\w]+/Lfr_FR
+ �cole
+ 0: �cole
+
+/^[\w]+/
+ *** Failers
+No match
+ �cole
+No match
+
+/^[\W]+/
+ �cole
+ 0: \xc9
+
+/^[\W]+/Lfr_FR
+ *** Failers
+ 0: ***
+ �cole
+No match
+
+/[\b]/
+ \b
+ 0: \x08
+ *** Failers
+No match
+ a
+No match
+
+/[\b]/Lfr_FR
+ \b
+ 0: \x08
+ *** Failers
+No match
+ a
+No match
+
+/^\w+/
+ *** Failers
+No match
+ �cole
+No match
+
+/^\w+/Lfr_FR
+ �cole
+ 0: �cole
+
+/(.+)\b(.+)/
+ �cole
+ 0: \xc9cole
+ 1: \xc9
+ 2: cole
+
+/(.+)\b(.+)/Lfr_FR
+ *** Failers
+ 0: *** Failers
+ 1: ***
+ 2: Failers
+ �cole
+No match
+
+/�cole/i
+ �cole
+ 0: \xc9cole
+ *** Failers
+No match
+ �cole
+No match
+
+/�cole/iLfr_FR
+ �cole
+ 0: �cole
+ �cole
+ 0: �cole
+
+/\w/IS
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P
+ Q R S T U V W X Y Z _ a b c d e f g h i j k l m n o p q r s t u v w x y z
+
+/\w/ISLfr_FR
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+Starting byte set: 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P
+ Q R S T U V W X Y Z _ a b c d e f g h i j k l m n o p q r s t u v w x y z
+ � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �
+ � � � � � � � � � � � � � � � � � � � � � � � � � � � �
+
+/^[\xc8-\xc9]/iLfr_FR
+ �cole
+ 0: �
+ �cole
+ 0: �
+
+/^[\xc8-\xc9]/Lfr_FR
+ �cole
+ 0: �
+ *** Failers
+No match
+ �cole
+No match
+
+/\W+/Lfr_FR
+ >>>\xaa<<<
+ 0: >>>
+ >>>\xba<<<
+ 0: >>>
+
+/[\W]+/Lfr_FR
+ >>>\xaa<<<
+ 0: >>>
+ >>>\xba<<<
+ 0: >>>
+
+/[^[:alpha:]]+/Lfr_FR
+ >>>\xaa<<<
+ 0: >>>
+ >>>\xba<<<
+ 0: >>>
+
+/\w+/Lfr_FR
+ >>>\xaa<<<
+ 0: �
+ >>>\xba<<<
+ 0: �
+
+/[\w]+/Lfr_FR
+ >>>\xaa<<<
+ 0: �
+ >>>\xba<<<
+ 0: �
+
+/[[:alpha:]]+/Lfr_FR
+ >>>\xaa<<<
+ 0: �
+ >>>\xba<<<
+ 0: �
+
+/[[:alpha:]][[:lower:]][[:upper:]]/DZLfr_FR
+------------------------------------------------------------------
+ Bra
+ [A-Za-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\xff]
+ [a-z\xb5\xdf-\xf6\xf8-\xff]
+ [A-Z\xc0-\xd6\xd8-\xde]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/ End of testinput3 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput4 b/lib/stdlib/test/re_SUITE_data/testoutput4
new file mode 100644
index 0000000000..d87ea4bcc4
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput4
@@ -0,0 +1,1074 @@
+/-- Do not use the \x{} construct except with patterns that have the --/
+/-- /8 option set, because PCRE doesn't recognize them as UTF-8 unless --/
+No match
+/-- that option is set. However, the latest Perls recognize them always. --/
+No match
+
+/a.b/8
+ acb
+ 0: acb
+ a\x7fb
+ 0: a\x{7f}b
+ a\x{100}b
+ 0: a\x{100}b
+ *** Failers
+No match
+ a\nb
+No match
+
+/a(.{3})b/8
+ a\x{4000}xyb
+ 0: a\x{4000}xyb
+ 1: \x{4000}xy
+ a\x{4000}\x7fyb
+ 0: a\x{4000}\x{7f}yb
+ 1: \x{4000}\x{7f}y
+ a\x{4000}\x{100}yb
+ 0: a\x{4000}\x{100}yb
+ 1: \x{4000}\x{100}y
+ *** Failers
+No match
+ a\x{4000}b
+No match
+ ac\ncb
+No match
+
+/a(.*?)(.)/
+ a\xc0\x88b
+ 0: a\xc0
+ 1:
+ 2: \xc0
+
+/a(.*?)(.)/8
+ a\x{100}b
+ 0: a\x{100}
+ 1:
+ 2: \x{100}
+
+/a(.*)(.)/
+ a\xc0\x88b
+ 0: a\xc0\x88b
+ 1: \xc0\x88
+ 2: b
+
+/a(.*)(.)/8
+ a\x{100}b
+ 0: a\x{100}b
+ 1: \x{100}
+ 2: b
+
+/a(.)(.)/
+ a\xc0\x92bcd
+ 0: a\xc0\x92
+ 1: \xc0
+ 2: \x92
+
+/a(.)(.)/8
+ a\x{240}bcd
+ 0: a\x{240}b
+ 1: \x{240}
+ 2: b
+
+/a(.?)(.)/
+ a\xc0\x92bcd
+ 0: a\xc0\x92
+ 1: \xc0
+ 2: \x92
+
+/a(.?)(.)/8
+ a\x{240}bcd
+ 0: a\x{240}b
+ 1: \x{240}
+ 2: b
+
+/a(.??)(.)/
+ a\xc0\x92bcd
+ 0: a\xc0
+ 1:
+ 2: \xc0
+
+/a(.??)(.)/8
+ a\x{240}bcd
+ 0: a\x{240}
+ 1:
+ 2: \x{240}
+
+/a(.{3})b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ 1: \x{1234}xy
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ 1: \x{1234}\x{4321}y
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ 1: \x{1234}\x{4321}\x{3412}
+ *** Failers
+No match
+ a\x{1234}b
+No match
+ ac\ncb
+No match
+
+/a(.{3,})b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ 1: \x{1234}xy
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ 1: \x{1234}\x{4321}y
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ 1: \x{1234}\x{4321}\x{3412}
+ axxxxbcdefghijb
+ 0: axxxxbcdefghijb
+ 1: xxxxbcdefghij
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 1: \x{1234}\x{4321}\x{3412}\x{3421}
+ *** Failers
+No match
+ a\x{1234}b
+No match
+
+/a(.{3,}?)b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ 1: \x{1234}xy
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ 1: \x{1234}\x{4321}y
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ 1: \x{1234}\x{4321}\x{3412}
+ axxxxbcdefghijb
+ 0: axxxxb
+ 1: xxxx
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 1: \x{1234}\x{4321}\x{3412}\x{3421}
+ *** Failers
+No match
+ a\x{1234}b
+No match
+
+/a(.{3,5})b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ 1: \x{1234}xy
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ 1: \x{1234}\x{4321}y
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ 1: \x{1234}\x{4321}\x{3412}
+ axxxxbcdefghijb
+ 0: axxxxb
+ 1: xxxx
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 1: \x{1234}\x{4321}\x{3412}\x{3421}
+ axbxxbcdefghijb
+ 0: axbxxb
+ 1: xbxx
+ axxxxxbcdefghijb
+ 0: axxxxxb
+ 1: xxxxx
+ *** Failers
+No match
+ a\x{1234}b
+No match
+ axxxxxxbcdefghijb
+No match
+
+/a(.{3,5}?)b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ 1: \x{1234}xy
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ 1: \x{1234}\x{4321}y
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ 1: \x{1234}\x{4321}\x{3412}
+ axxxxbcdefghijb
+ 0: axxxxb
+ 1: xxxx
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 1: \x{1234}\x{4321}\x{3412}\x{3421}
+ axbxxbcdefghijb
+ 0: axbxxb
+ 1: xbxx
+ axxxxxbcdefghijb
+ 0: axxxxxb
+ 1: xxxxx
+ *** Failers
+No match
+ a\x{1234}b
+No match
+ axxxxxxbcdefghijb
+No match
+
+/^[a\x{c0}]/8
+ *** Failers
+No match
+ \x{100}
+No match
+
+/(?<=aXb)cd/8
+ aXbcd
+ 0: cd
+
+/(?<=a\x{100}b)cd/8
+ a\x{100}bcd
+ 0: cd
+
+/(?<=a\x{100000}b)cd/8
+ a\x{100000}bcd
+ 0: cd
+
+/(?:\x{100}){3}b/8
+ \x{100}\x{100}\x{100}b
+ 0: \x{100}\x{100}\x{100}b
+ *** Failers
+No match
+ \x{100}\x{100}b
+No match
+
+/\x{ab}/8
+ \x{ab}
+ 0: \x{ab}
+ \xc2\xab
+ 0: \x{ab}
+ *** Failers
+No match
+ \x00{ab}
+No match
+
+/(?<=(.))X/8
+ WXYZ
+ 0: X
+ 1: W
+ \x{256}XYZ
+ 0: X
+ 1: \x{256}
+ *** Failers
+No match
+ XYZ
+No match
+
+/X(\C{3})/8
+ X\x{1234}
+ 0: X\x{1234}
+ 1: \x{1234}
+
+/X(\C{4})/8
+ X\x{1234}YZ
+ 0: X\x{1234}Y
+ 1: \x{1234}Y
+
+/X\C*/8
+ XYZabcdce
+ 0: XYZabcdce
+
+/X\C*?/8
+ XYZabcde
+ 0: X
+
+/X\C{3,5}/8
+ Xabcdefg
+ 0: Xabcde
+ X\x{1234}
+ 0: X\x{1234}
+ X\x{1234}YZ
+ 0: X\x{1234}YZ
+ X\x{1234}\x{512}
+ 0: X\x{1234}\x{512}
+ X\x{1234}\x{512}YZ
+ 0: X\x{1234}\x{512}
+
+/X\C{3,5}?/8
+ Xabcdefg
+ 0: Xabc
+ X\x{1234}
+ 0: X\x{1234}
+ X\x{1234}YZ
+ 0: X\x{1234}
+ X\x{1234}\x{512}
+ 0: X\x{1234}
+
+/[^a]+/8g
+ bcd
+ 0: bcd
+ \x{100}aY\x{256}Z
+ 0: \x{100}
+ 0: Y\x{256}Z
+
+/^[^a]{2}/8
+ \x{100}bc
+ 0: \x{100}b
+
+/^[^a]{2,}/8
+ \x{100}bcAa
+ 0: \x{100}bcA
+
+/^[^a]{2,}?/8
+ \x{100}bca
+ 0: \x{100}b
+
+/[^a]+/8ig
+ bcd
+ 0: bcd
+ \x{100}aY\x{256}Z
+ 0: \x{100}
+ 0: Y\x{256}Z
+
+/^[^a]{2}/8i
+ \x{100}bc
+ 0: \x{100}b
+
+/^[^a]{2,}/8i
+ \x{100}bcAa
+ 0: \x{100}bc
+
+/^[^a]{2,}?/8i
+ \x{100}bca
+ 0: \x{100}b
+
+/\x{100}{0,0}/8
+ abcd
+ 0:
+
+/\x{100}?/8
+ abcd
+ 0:
+ \x{100}\x{100}
+ 0: \x{100}
+
+/\x{100}{0,3}/8
+ \x{100}\x{100}
+ 0: \x{100}\x{100}
+ \x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}
+
+/\x{100}*/8
+ abce
+ 0:
+ \x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}\x{100}
+
+/\x{100}{1,1}/8
+ abcd\x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}
+
+/\x{100}{1,3}/8
+ abcd\x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}
+
+/\x{100}+/8
+ abcd\x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}\x{100}
+
+/\x{100}{3}/8
+ abcd\x{100}\x{100}\x{100}XX
+ 0: \x{100}\x{100}\x{100}
+
+/\x{100}{3,5}/8
+ abcd\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}XX
+ 0: \x{100}\x{100}\x{100}\x{100}\x{100}
+
+/\x{100}{3,}/8
+ abcd\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}XX
+ 0: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+
+/(?<=a\x{100}{2}b)X/8+
+ Xyyya\x{100}\x{100}bXzzz
+ 0: X
+ 0+ zzz
+
+/\D*/8
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/\D*/8
+ \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+
+/\D/8
+ 1X2
+ 0: X
+ 1\x{100}2
+ 0: \x{100}
+
+/>\S/8
+ > >X Y
+ 0: >X
+ > >\x{100} Y
+ 0: >\x{100}
+
+/\d/8
+ \x{100}3
+ 0: 3
+
+/\s/8
+ \x{100} X
+ 0:
+
+/\D+/8
+ 12abcd34
+ 0: abcd
+ *** Failers
+ 0: *** Failers
+ 1234
+No match
+
+/\D{2,3}/8
+ 12abcd34
+ 0: abc
+ 12ab34
+ 0: ab
+ *** Failers
+ 0: ***
+ 1234
+No match
+ 12a34
+No match
+
+/\D{2,3}?/8
+ 12abcd34
+ 0: ab
+ 12ab34
+ 0: ab
+ *** Failers
+ 0: **
+ 1234
+No match
+ 12a34
+No match
+
+/\d+/8
+ 12abcd34
+ 0: 12
+ *** Failers
+No match
+
+/\d{2,3}/8
+ 12abcd34
+ 0: 12
+ 1234abcd
+ 0: 123
+ *** Failers
+No match
+ 1.4
+No match
+
+/\d{2,3}?/8
+ 12abcd34
+ 0: 12
+ 1234abcd
+ 0: 12
+ *** Failers
+No match
+ 1.4
+No match
+
+/\S+/8
+ 12abcd34
+ 0: 12abcd34
+ *** Failers
+ 0: ***
+ \ \
+No match
+
+/\S{2,3}/8
+ 12abcd34
+ 0: 12a
+ 1234abcd
+ 0: 123
+ *** Failers
+ 0: ***
+ \ \
+No match
+
+/\S{2,3}?/8
+ 12abcd34
+ 0: 12
+ 1234abcd
+ 0: 12
+ *** Failers
+ 0: **
+ \ \
+No match
+
+/>\s+</8+
+ 12> <34
+ 0: > <
+ 0+ 34
+ *** Failers
+No match
+
+/>\s{2,3}</8+
+ ab> <cd
+ 0: > <
+ 0+ cd
+ ab> <ce
+ 0: > <
+ 0+ ce
+ *** Failers
+No match
+ ab> <cd
+No match
+
+/>\s{2,3}?</8+
+ ab> <cd
+ 0: > <
+ 0+ cd
+ ab> <ce
+ 0: > <
+ 0+ ce
+ *** Failers
+No match
+ ab> <cd
+No match
+
+/\w+/8
+ 12 34
+ 0: 12
+ *** Failers
+ 0: Failers
+ +++=*!
+No match
+
+/\w{2,3}/8
+ ab cd
+ 0: ab
+ abcd ce
+ 0: abc
+ *** Failers
+ 0: Fai
+ a.b.c
+No match
+
+/\w{2,3}?/8
+ ab cd
+ 0: ab
+ abcd ce
+ 0: ab
+ *** Failers
+ 0: Fa
+ a.b.c
+No match
+
+/\W+/8
+ 12====34
+ 0: ====
+ *** Failers
+ 0: ***
+ abcd
+No match
+
+/\W{2,3}/8
+ ab====cd
+ 0: ===
+ ab==cd
+ 0: ==
+ *** Failers
+ 0: ***
+ a.b.c
+No match
+
+/\W{2,3}?/8
+ ab====cd
+ 0: ==
+ ab==cd
+ 0: ==
+ *** Failers
+ 0: **
+ a.b.c
+No match
+
+/[\x{100}]/8
+ \x{100}
+ 0: \x{100}
+ Z\x{100}
+ 0: \x{100}
+ \x{100}Z
+ 0: \x{100}
+ *** Failers
+No match
+
+/[Z\x{100}]/8
+ Z\x{100}
+ 0: Z
+ \x{100}
+ 0: \x{100}
+ \x{100}Z
+ 0: \x{100}
+ *** Failers
+No match
+
+/[\x{100}\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ *** Failers
+No match
+
+/[\x{100}-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ *** Failers
+No match
+
+/[z-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ abzcd
+ 0: z
+ ab|cd
+ 0: |
+ *** Failers
+No match
+
+/[Q\x{100}\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ Q?
+ 0: Q
+ *** Failers
+No match
+
+/[Q\x{100}-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ Q?
+ 0: Q
+ *** Failers
+No match
+
+/[Qz-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ abzcd
+ 0: z
+ ab|cd
+ 0: |
+ Q?
+ 0: Q
+ *** Failers
+No match
+
+/[\x{100}\x{200}]{1,3}/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}\x{100}\x{200}
+ *** Failers
+No match
+
+/[\x{100}\x{200}]{1,3}?/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}
+ *** Failers
+No match
+
+/[Q\x{100}\x{200}]{1,3}/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}\x{100}\x{200}
+ *** Failers
+No match
+
+/[Q\x{100}\x{200}]{1,3}?/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}
+ *** Failers
+No match
+
+/(?<=[\x{100}\x{200}])X/8
+ abc\x{200}X
+ 0: X
+ abc\x{100}X
+ 0: X
+ *** Failers
+No match
+ X
+No match
+
+/(?<=[Q\x{100}\x{200}])X/8
+ abc\x{200}X
+ 0: X
+ abc\x{100}X
+ 0: X
+ abQX
+ 0: X
+ *** Failers
+No match
+ X
+No match
+
+/(?<=[\x{100}\x{200}]{3})X/8
+ abc\x{100}\x{200}\x{100}X
+ 0: X
+ *** Failers
+No match
+ abc\x{200}X
+No match
+ X
+No match
+
+/[^\x{100}\x{200}]X/8
+ AX
+ 0: AX
+ \x{150}X
+ 0: \x{150}X
+ \x{500}X
+ 0: \x{500}X
+ *** Failers
+No match
+ \x{100}X
+No match
+ \x{200}X
+No match
+
+/[^Q\x{100}\x{200}]X/8
+ AX
+ 0: AX
+ \x{150}X
+ 0: \x{150}X
+ \x{500}X
+ 0: \x{500}X
+ *** Failers
+No match
+ \x{100}X
+No match
+ \x{200}X
+No match
+ QX
+No match
+
+/[^\x{100}-\x{200}]X/8
+ AX
+ 0: AX
+ \x{500}X
+ 0: \x{500}X
+ *** Failers
+No match
+ \x{100}X
+No match
+ \x{150}X
+No match
+ \x{200}X
+No match
+
+/a\Cb/
+ aXb
+ 0: aXb
+ a\nb
+ 0: a\x0ab
+
+/a\Cb/8
+ aXb
+ 0: aXb
+ a\nb
+ 0: a\x{0a}b
+ *** Failers
+No match
+ a\x{100}b
+No match
+
+/[z-\x{100}]/8i
+ z
+ 0: z
+ Z
+ 0: Z
+ \x{100}
+ 0: \x{100}
+ *** Failers
+No match
+ \x{102}
+No match
+ y
+No match
+
+/[\xFF]/
+ >\xff<
+ 0: \xff
+
+/[\xff]/8
+ >\x{ff}<
+ 0: \x{ff}
+
+/[^\xFF]/
+ XYZ
+ 0: X
+
+/[^\xff]/8
+ XYZ
+ 0: X
+ \x{123}
+ 0: \x{123}
+
+/^[ac]*b/8
+ xb
+No match
+
+/^[ac\x{100}]*b/8
+ xb
+No match
+
+/^[^x]*b/8i
+ xb
+No match
+
+/^[^x]*b/8
+ xb
+No match
+
+/^\d*b/8
+ xb
+No match
+
+/(|a)/g8
+ catac
+ 0:
+ 1:
+ 0:
+ 1:
+ 0: a
+ 1: a
+ 0:
+ 1:
+ 0:
+ 1:
+ 0: a
+ 1: a
+ 0:
+ 1:
+ 0:
+ 1:
+ a\x{256}a
+ 0:
+ 1:
+ 0: a
+ 1: a
+ 0:
+ 1:
+ 0:
+ 1:
+ 0: a
+ 1: a
+ 0:
+ 1:
+
+/^\x{85}$/8i
+ \x{85}
+ 0: \x{85}
+
+/^ሴ/8
+ ሴ
+ 0: \x{1234}
+
+/^\ሴ/8
+ ሴ
+ 0: \x{1234}
+
+"(?s)(.{1,5})"8
+ abcdefg
+ 0: abcde
+ 1: abcde
+ ab
+ 0: ab
+ 1: ab
+
+/a*\x{100}*\w/8
+ a
+ 0: a
+
+/\S\S/8g
+ A\x{a3}BC
+ 0: A\x{a3}
+ 0: BC
+
+/\S{2}/8g
+ A\x{a3}BC
+ 0: A\x{a3}
+ 0: BC
+
+/\W\W/8g
+ +\x{a3}==
+ 0: +\x{a3}
+ 0: ==
+
+/\W{2}/8g
+ +\x{a3}==
+ 0: +\x{a3}
+ 0: ==
+
+/\S/8g
+ \x{442}\x{435}\x{441}\x{442}
+ 0: \x{442}
+ 0: \x{435}
+ 0: \x{441}
+ 0: \x{442}
+
+/[\S]/8g
+ \x{442}\x{435}\x{441}\x{442}
+ 0: \x{442}
+ 0: \x{435}
+ 0: \x{441}
+ 0: \x{442}
+
+/\D/8g
+ \x{442}\x{435}\x{441}\x{442}
+ 0: \x{442}
+ 0: \x{435}
+ 0: \x{441}
+ 0: \x{442}
+
+/[\D]/8g
+ \x{442}\x{435}\x{441}\x{442}
+ 0: \x{442}
+ 0: \x{435}
+ 0: \x{441}
+ 0: \x{442}
+
+/\W/8g
+ \x{2442}\x{2435}\x{2441}\x{2442}
+ 0: \x{2442}
+ 0: \x{2435}
+ 0: \x{2441}
+ 0: \x{2442}
+
+/[\W]/8g
+ \x{2442}\x{2435}\x{2441}\x{2442}
+ 0: \x{2442}
+ 0: \x{2435}
+ 0: \x{2441}
+ 0: \x{2442}
+
+/[\S\s]*/8
+ abc\n\r\x{442}\x{435}\x{441}\x{442}xyz
+ 0: abc\x{0a}\x{0d}\x{442}\x{435}\x{441}\x{442}xyz
+
+/[\x{41f}\S]/8g
+ \x{442}\x{435}\x{441}\x{442}
+ 0: \x{442}
+ 0: \x{435}
+ 0: \x{441}
+ 0: \x{442}
+
+/.[^\S]./8g
+ abc def\x{442}\x{443}xyz\npqr
+ 0: c d
+ 0: z\x{0a}p
+
+/.[^\S\n]./8g
+ abc def\x{442}\x{443}xyz\npqr
+ 0: c d
+
+/[[:^alnum:]]/8g
+ +\x{2442}
+ 0: +
+ 0: \x{2442}
+
+/[[:^alpha:]]/8g
+ +\x{2442}
+ 0: +
+ 0: \x{2442}
+
+/[[:^ascii:]]/8g
+ A\x{442}
+ 0: \x{442}
+
+/[[:^blank:]]/8g
+ A\x{442}
+ 0: A
+ 0: \x{442}
+
+/[[:^cntrl:]]/8g
+ A\x{442}
+ 0: A
+ 0: \x{442}
+
+/[[:^digit:]]/8g
+ A\x{442}
+ 0: A
+ 0: \x{442}
+
+/[[:^graph:]]/8g
+ \x19\x{e01ff}
+ 0: \x{19}
+ 0: \x{e01ff}
+
+/[[:^lower:]]/8g
+ A\x{422}
+ 0: A
+ 0: \x{422}
+
+/[[:^print:]]/8g
+ \x{19}\x{e01ff}
+ 0: \x{19}
+ 0: \x{e01ff}
+
+/[[:^punct:]]/8g
+ A\x{442}
+ 0: A
+ 0: \x{442}
+
+/[[:^space:]]/8g
+ A\x{442}
+ 0: A
+ 0: \x{442}
+
+/[[:^upper:]]/8g
+ a\x{442}
+ 0: a
+ 0: \x{442}
+
+/[[:^word:]]/8g
+ +\x{2442}
+ 0: +
+ 0: \x{2442}
+
+/[[:^xdigit:]]/8g
+ M\x{442}
+ 0: M
+ 0: \x{442}
+
+/[^ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸŹŻŽƁƂƄƆƇƉƊƋƎƏƐƑƓƔƖƗƘƜƝƟƠƢƤƦƧƩƬƮƯƱƲƳƵƷƸƼDŽLJNJǍǏǑǓǕǗǙǛǞǠǢǤǦǨǪǬǮDZǴǶǷǸǺǼǾȀȂȄȆȈȊȌȎȐȒȔȖȘȚȜȞȠȢȤȦȨȪȬȮȰȲȺȻȽȾɁΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫϒϓϔϘϚϜϞϠϢϤϦϨϪϬϮϴϷϹϺϽϾϿЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎҐҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾӀӁӃӅӇӉӋӍӐӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӶӸԀԂԄԆԈԊԌԎԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՎՏՐՑՒՓՔՕՖႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅḀḂḄḆḈḊḌḎḐḒḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎṐṒṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎẐẒẔẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎỐỒỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸἈἉἊἋἌἍἎἏἘἙἚἛἜἝἨἩἪἫἬἭἮἯἸἹἺἻἼἽἾἿὈὉὊὋὌὍὙὛὝὟὨὩὪὫὬὭὮὯᾸᾹᾺΆῈΈῊΉῘῙῚΊῨῩῪΎῬῸΌῺΏabcdefghijklmnopqrstuvwxyzªµºßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıijĵķĸĺļľŀłńņňʼnŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżžſƀƃƅƈƌƍƒƕƙƚƛƞơƣƥƨƪƫƭưƴƶƹƺƽƾƿdžljnjǎǐǒǔǖǘǚǜǝǟǡǣǥǧǩǫǭǯǰdzǵǹǻǽǿȁȃȅȇȉȋȍȏȑȓȕȗșțȝȟȡȣȥȧȩȫȭȯȱȳȴȵȶȷȸȹȼȿɀɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀʁʂʃʄʅʆʇʈʉʊʋʌʍʎʏʐʑʒʓʔʕʖʗʘʙʚʛʜʝʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯΐάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϐϑϕϖϗϙϛϝϟϡϣϥϧϩϫϭϯϰϱϲϳϵϸϻϼабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿҁҋҍҏґғҕҗҙқҝҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӂӄӆӈӊӌӎӑӓӕӗәӛӝӟӡӣӥӧөӫӭӯӱӳӵӷӹԁԃԅԇԉԋԍԏաբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆևᴀᴁᴂᴃᴄᴅᴆᴇᴈᴉᴊᴋᴌᴍᴎᴏᴐᴑᴒᴓᴔᴕᴖᴗᴘᴙᴚᴛᴜᴝᴞᴟᴠᴡᴢᴣᴤᴥᴦᴧᴨᴩᴪᴫᵢᵣᵤᵥᵦᵧᵨᵩᵪᵫᵬᵭᵮᵯᵰᵱᵲᵳᵴᵵᵶᵷᵹᵺᵻᵼᵽᵾᵿᶀᶁᶂᶃᶄᶅᶆᶇᶈᶉᶊᶋᶌᶍᶎᶏᶐᶑᶒᶓᶔᶕᶖᶗᶘᶙᶚḁḃḅḇḉḋḍḏḑḓḕḗḙḛḝḟḡḣḥḧḩḫḭḯḱḳḵḷḹḻḽḿṁṃṅṇṉṋṍṏṑṓṕṗṙṛṝṟṡṣṥṧṩṫṭṯṱṳṵṷṹṻṽṿẁẃẅẇẉẋẍẏẑẓẕẖẗẘẙẚẛạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹἀἁἂἃἄἅἆἇἐἑἒἓἔἕἠἡἢἣἤἥἦἧἰἱἲἳἴἵἶἷὀὁὂὃὄὅὐὑὒὓὔὕὖὗὠὡὢὣὤὥὦὧὰάὲέὴήὶίὸόὺύὼώᾀᾁᾂᾃᾄᾅᾆᾇᾐᾑᾒᾓᾔᾕᾖᾗᾠᾡᾢᾣᾤᾥᾦᾧᾰᾱᾲᾳᾴᾶᾷιῂῃῄῆῇῐῑῒΐῖῗῠῡῢΰῤῥῦῧῲῳῴῶῷⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⲳⲵⲷⲹⲻⲽⲿⳁⳃⳅⳇⳉⳋⳍⳏⳑⳓⳕⳗⳙⳛⳝⳟⳡⳣⳤⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥfffiflffifflſtstﬓﬔﬕﬖﬗ\d-_^]/8
+
+/ End of testinput4 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput5 b/lib/stdlib/test/re_SUITE_data/testoutput5
new file mode 100644
index 0000000000..abbe1c87de
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput5
@@ -0,0 +1,1611 @@
+/\x{100}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 196
+Need char = 128
+
+/\x{1000}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{1000}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 225
+Need char = 128
+
+/\x{10000}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{10000}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 240
+Need char = 128
+
+/\x{100000}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100000}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 244
+Need char = 128
+
+/\x{1000000}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{1000000}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 249
+Need char = 128
+
+/\x{4000000}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{4000000}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 252
+Need char = 128
+
+/\x{7fffFFFF}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{7fffffff}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 253
+Need char = 191
+
+/[\x{ff}]/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{ff}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 195
+Need char = 191
+
+/[\x{100}]/8DZ
+------------------------------------------------------------------
+ Bra
+ [\x{100}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+
+/\x{ffffffff}/8
+Failed: character value in \x{...} sequence is too large at offset 11
+
+/\x{100000000}/8
+Failed: character value in \x{...} sequence is too large at offset 12
+
+/^\x{100}a\x{1234}/8
+ \x{100}a\x{1234}bcd
+ 0: \x{100}a\x{1234}
+
+/\x80/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{80}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 194
+Need char = 128
+
+/\xff/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{ff}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 195
+Need char = 191
+
+/\x{0041}\x{2262}\x{0391}\x{002e}/DZ8
+------------------------------------------------------------------
+ Bra
+ A\x{2262}\x{391}.
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 'A'
+Need char = '.'
+ \x{0041}\x{2262}\x{0391}\x{002e}
+ 0: A\x{2262}\x{391}.
+
+/\x{D55c}\x{ad6d}\x{C5B4}/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{d55c}\x{ad6d}\x{c5b4}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 237
+Need char = 180
+ \x{D55c}\x{ad6d}\x{C5B4}
+ 0: \x{d55c}\x{ad6d}\x{c5b4}
+
+/\x{65e5}\x{672c}\x{8a9e}/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{65e5}\x{672c}\x{8a9e}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 230
+Need char = 158
+ \x{65e5}\x{672c}\x{8a9e}
+ 0: \x{65e5}\x{672c}\x{8a9e}
+
+/\x{80}/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{80}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 194
+Need char = 128
+
+/\x{084}/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{84}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 194
+Need char = 132
+
+/\x{104}/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{104}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 196
+Need char = 132
+
+/\x{861}/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{861}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 224
+Need char = 161
+
+/\x{212ab}/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{212ab}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 240
+Need char = 171
+
+/.{3,5}X/DZ8
+------------------------------------------------------------------
+ Bra
+ Any{3}
+ Any{0,2}
+ X
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+Need char = 'X'
+ \x{212ab}\x{212ab}\x{212ab}\x{861}X
+ 0: \x{212ab}\x{212ab}\x{212ab}\x{861}X
+
+
+/.{3,5}?/DZ8
+------------------------------------------------------------------
+ Bra
+ Any{3}
+ Any{0,2}?
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+ \x{212ab}\x{212ab}\x{212ab}\x{861}
+ 0: \x{212ab}\x{212ab}\x{212ab}
+
+/-- These tests are here rather than in testinput4 because Perl 5.6 has some
+problems with UTF-8 support, in the area of \x{..} where the value is < 255.
+It grumbles about invalid UTF-8 strings. --/
+
+/^[a\x{c0}]b/8
+ \x{c0}b
+ 0: \x{c0}b
+
+/^([a\x{c0}]*?)aa/8
+ a\x{c0}aaaa/
+ 0: a\x{c0}aa
+ 1: a\x{c0}
+
+/^([a\x{c0}]*?)aa/8
+ a\x{c0}aaaa/
+ 0: a\x{c0}aa
+ 1: a\x{c0}
+ a\x{c0}a\x{c0}aaa/
+ 0: a\x{c0}a\x{c0}aa
+ 1: a\x{c0}a\x{c0}
+
+/^([a\x{c0}]*)aa/8
+ a\x{c0}aaaa/
+ 0: a\x{c0}aaaa
+ 1: a\x{c0}aa
+ a\x{c0}a\x{c0}aaa/
+ 0: a\x{c0}a\x{c0}aaa
+ 1: a\x{c0}a\x{c0}a
+
+/^([a\x{c0}]*)a\x{c0}/8
+ a\x{c0}aaaa/
+ 0: a\x{c0}
+ 1:
+ a\x{c0}a\x{c0}aaa/
+ 0: a\x{c0}a\x{c0}
+ 1: a\x{c0}
+
+/-- --/
+
+/(?<=\C)X/8
+Failed: \C not allowed in lookbehind assertion at offset 6
+
+/-- This one is here not because it's different to Perl, but because the way
+the captured single-byte is displayed. (In Perl it becomes a character, and you
+can't tell the difference.) --/
+
+/X(\C)(.*)/8
+ X\x{1234}
+ 0: X\x{1234}
+ 1: \xe1
+ 2: \x88\xb4
+ X\nabc
+ 0: X\x{0a}abc
+ 1: \x{0a}
+ 2: abc
+
+/^[ab]/8DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [ab]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored utf8
+No first char
+No need char
+ bar
+ 0: b
+ *** Failers
+No match
+ c
+No match
+ \x{ff}
+No match
+ \x{100}
+No match
+
+/^[^ab]/8DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x00-`c-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored utf8
+No first char
+No need char
+ c
+ 0: c
+ \x{ff}
+ 0: \x{ff}
+ \x{100}
+ 0: \x{100}
+ *** Failers
+ 0: *
+ aaa
+No match
+
+/[^ab\xC0-\xF0]/8SDZ
+------------------------------------------------------------------
+ Bra
+ [\x00-`c-\xbf\xf1-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+Starting byte set: \x00 \x01 \x02 \x03 \x04 \x05 \x06 \x07 \x08 \x09 \x0a
+ \x0b \x0c \x0d \x0e \x0f \x10 \x11 \x12 \x13 \x14 \x15 \x16 \x17 \x18 \x19
+ \x1a \x1b \x1c \x1d \x1e \x1f \x20 ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4
+ 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y
+ Z [ \ ] ^ _ ` c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ \x7f
+ \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 \xc8 \xc9 \xca \xcb \xcc \xcd \xce \xcf \xd0
+ \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 \xd8 \xd9 \xda \xdb \xdc \xdd \xde \xdf
+ \xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee
+ \xef \xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7 \xf8 \xf9 \xfa \xfb \xfc \xfd
+ \xfe \xff
+ \x{f1}
+ 0: \x{f1}
+ \x{bf}
+ 0: \x{bf}
+ \x{100}
+ 0: \x{100}
+ \x{1000}
+ 0: \x{1000}
+ *** Failers
+ 0: *
+ \x{c0}
+No match
+ \x{f0}
+No match
+
+/Ā{3,4}/8SDZ
+------------------------------------------------------------------
+ Bra
+ \x{100}{3}
+ \x{100}?
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 196
+Need char = 128
+Study returned NULL
+ \x{100}\x{100}\x{100}\x{100\x{100}
+ 0: \x{100}\x{100}\x{100}
+
+/(\x{100}+|x)/8SDZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ \x{100}+
+ Alt
+ x
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+Starting byte set: x \xc4
+
+/(\x{100}*a|x)/8SDZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ \x{100}*+
+ a
+ Alt
+ x
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+Starting byte set: a x \xc4
+
+/(\x{100}{0,2}a|x)/8SDZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ \x{100}{0,2}
+ a
+ Alt
+ x
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+Starting byte set: a x \xc4
+
+/(\x{100}{1,2}a|x)/8SDZ
+------------------------------------------------------------------
+ Bra
+ CBra 1
+ \x{100}
+ \x{100}{0,1}
+ a
+ Alt
+ x
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+Starting byte set: x \xc4
+
+/\x{100}*(\d+|"(?1)")/8
+ 1234
+ 0: 1234
+ 1: 1234
+ "1234"
+ 0: "1234"
+ 1: "1234"
+ \x{100}1234
+ 0: \x{100}1234
+ 1: 1234
+ "\x{100}1234"
+ 0: \x{100}1234
+ 1: 1234
+ \x{100}\x{100}12ab
+ 0: \x{100}\x{100}12
+ 1: 12
+ \x{100}\x{100}"12"
+ 0: \x{100}\x{100}"12"
+ 1: "12"
+ *** Failers
+No match
+ \x{100}\x{100}abcd
+No match
+
+/\x{100}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 196
+Need char = 128
+
+/\x{100}*/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/a\x{100}*/8DZ
+------------------------------------------------------------------
+ Bra
+ a
+ \x{100}*
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 'a'
+No need char
+
+/ab\x{100}*/8DZ
+------------------------------------------------------------------
+ Bra
+ ab
+ \x{100}*
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 'a'
+Need char = 'b'
+
+/a\x{100}\x{101}*/8DZ
+------------------------------------------------------------------
+ Bra
+ a\x{100}
+ \x{101}*
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 'a'
+Need char = 128
+
+/a\x{100}\x{101}+/8DZ
+------------------------------------------------------------------
+ Bra
+ a\x{100}
+ \x{101}+
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 'a'
+Need char = 129
+
+/\x{100}*A/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*+
+ A
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+Need char = 'A'
+ A
+ 0: A
+
+/\x{100}*\d(?R)/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*+
+ \d
+ Once
+ Recurse
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/[^\x{c4}]/DZ
+------------------------------------------------------------------
+ Bra
+ [^\xc4]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[^\x{c4}]/8DZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\xc3\xc5-\xff] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+
+/[\x{100}]/8DZ
+------------------------------------------------------------------
+ Bra
+ [\x{100}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+ \x{100}
+ 0: \x{100}
+ Z\x{100}
+ 0: \x{100}
+ \x{100}Z
+ 0: \x{100}
+ *** Failers
+No match
+
+/[Z\x{100}]/8DZ
+------------------------------------------------------------------
+ Bra
+ [Z\x{100}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+ Z\x{100}
+ 0: Z
+ \x{100}
+ 0: \x{100}
+ \x{100}Z
+ 0: \x{100}
+ *** Failers
+No match
+
+/[\x{200}-\x{100}]/8
+Failed: range out of order in character class at offset 15
+
+/[Ā-Ą]/8
+ \x{100}
+ 0: \x{100}
+ \x{104}
+ 0: \x{104}
+ *** Failers
+No match
+ \x{105}
+No match
+ \x{ff}
+No match
+
+/[z-\x{100}]/8DZ
+------------------------------------------------------------------
+ Bra
+ [z-\x{100}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+
+/[z\Qa-d]Ā\E]/8DZ
+------------------------------------------------------------------
+ Bra
+ [\-\]adz\x{100}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+ \x{100}
+ 0: \x{100}
+ Ā
+ 0: \x{100}
+
+/[\xFF]/DZ
+------------------------------------------------------------------
+ Bra
+ \xff
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+First char = 255
+No need char
+ >\xff<
+ 0: \xff
+
+/[\xff]/DZ8
+------------------------------------------------------------------
+ Bra
+ \x{ff}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 195
+Need char = 191
+ >\x{ff}<
+ 0: \x{ff}
+
+/[^\xFF]/DZ
+------------------------------------------------------------------
+ Bra
+ [^\xff]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[^\xff]/8DZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\xfe] (neg)
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+
+/[Ä-Ü]/8
+ Ö # Matches without Study
+ 0: \x{d6}
+ \x{d6}
+ 0: \x{d6}
+
+/[Ä-Ü]/8S
+ Ö <-- Same with Study
+ 0: \x{d6}
+ \x{d6}
+ 0: \x{d6}
+
+/[\x{c4}-\x{dc}]/8
+ Ö # Matches without Study
+ 0: \x{d6}
+ \x{d6}
+ 0: \x{d6}
+
+/[\x{c4}-\x{dc}]/8S
+ Ö <-- Same with Study
+ 0: \x{d6}
+ \x{d6}
+ 0: \x{d6}
+
+/[�]/8
+Failed: invalid UTF-8 string at offset 2
+
+/�/8
+Failed: invalid UTF-8 string at offset 0
+
+/���xxx/8
+Failed: invalid UTF-8 string at offset 1
+
+/���xxx/8?DZ
+------------------------------------------------------------------
+ Bra
+ \X{c0}\X{c0}\X{c0}xxx
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8 no_utf8_check
+First char = 195
+Need char = 'x'
+
+/abc/8
+ �]
+Error -10
+ �
+Error -10
+ ���
+Error -10
+ ���\?
+No match
+
+/anything/8
+ \xc0\x80
+Error -10
+ \xc1\x8f
+Error -10
+ \xe0\x9f\x80
+Error -10
+ \xf0\x8f\x80\x80
+Error -10
+ \xf8\x87\x80\x80\x80
+Error -10
+ \xfc\x83\x80\x80\x80\x80
+Error -10
+ \xfe\x80\x80\x80\x80\x80
+Error -10
+ \xff\x80\x80\x80\x80\x80
+Error -10
+ \xc3\x8f
+No match
+ \xe0\xaf\x80
+No match
+ \xe1\x80\x80
+No match
+ \xf0\x9f\x80\x80
+No match
+ \xf1\x8f\x80\x80
+No match
+ \xf8\x88\x80\x80\x80
+Error -10
+ \xf9\x87\x80\x80\x80
+Error -10
+ \xfc\x84\x80\x80\x80\x80
+Error -10
+ \xfd\x83\x80\x80\x80\x80
+Error -10
+ \?\xf8\x88\x80\x80\x80
+No match
+ \?\xf9\x87\x80\x80\x80
+No match
+ \?\xfc\x84\x80\x80\x80\x80
+No match
+ \?\xfd\x83\x80\x80\x80\x80
+No match
+
+/\x{100}abc(xyz(?1))/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}abc
+ CBra 1
+ xyz
+ Once
+ Recurse
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Options: utf8
+First char = 196
+Need char = 'z'
+
+/[^\x{100}]abc(xyz(?1))/8DZ
+------------------------------------------------------------------
+ Bra
+ [^\x{100}]
+ abc
+ CBra 1
+ xyz
+ Once
+ Recurse
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Options: utf8
+No first char
+Need char = 'z'
+
+/[ab\x{100}]abc(xyz(?1))/8DZ
+------------------------------------------------------------------
+ Bra
+ [ab\x{100}]
+ abc
+ CBra 1
+ xyz
+ Once
+ Recurse
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 1
+Options: utf8
+No first char
+Need char = 'z'
+
+/(\x{100}(b(?2)c))?/DZ8
+------------------------------------------------------------------
+ Bra
+ Brazero
+ CBra 1
+ \x{100}
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+Options: utf8
+No first char
+No need char
+
+/(\x{100}(b(?2)c)){0,2}/DZ8
+------------------------------------------------------------------
+ Bra
+ Brazero
+ Bra
+ CBra 1
+ \x{100}
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Brazero
+ CBra 1
+ \x{100}
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+Options: utf8
+No first char
+No need char
+
+/(\x{100}(b(?1)c))?/DZ8
+------------------------------------------------------------------
+ Bra
+ Brazero
+ CBra 1
+ \x{100}
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+Options: utf8
+No first char
+No need char
+
+/(\x{100}(b(?1)c)){0,2}/DZ8
+------------------------------------------------------------------
+ Bra
+ Brazero
+ Bra
+ CBra 1
+ \x{100}
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Brazero
+ CBra 1
+ \x{100}
+ CBra 2
+ b
+ Once
+ Recurse
+ Ket
+ c
+ Ket
+ Ket
+ Ket
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 2
+Options: utf8
+No first char
+No need char
+
+/\W/8
+ A.B
+ 0: .
+ A\x{100}B
+ 0: \x{100}
+
+/\w/8
+ \x{100}X
+ 0: X
+
+/a\x{1234}b/P8
+ a\x{1234}b
+ 0: a\x{1234}b
+
+/^\ሴ/8DZ
+------------------------------------------------------------------
+ Bra
+ ^
+ \x{1234}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: anchored utf8
+No first char
+No need char
+
+/\777/I
+Failed: octal value is greater than \377 (not in UTF-8 mode) at offset 3
+
+/\777/8I
+Capturing subpattern count = 0
+Options: utf8
+First char = 199
+Need char = 191
+ \x{1ff}
+ 0: \x{1ff}
+ \777
+ 0: \x{1ff}
+
+/\x{100}*\d/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*+
+ \d
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/\x{100}*\s/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*+
+ \s
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/\x{100}*\w/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*+
+ \w
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/\x{100}*\D/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*
+ \D
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/\x{100}*\S/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*
+ \S
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/\x{100}*\W/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}*
+ \W
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+
+/\x{100}+\x{200}/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}++
+ \x{200}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 196
+Need char = 128
+
+/\x{100}+X/8DZ
+------------------------------------------------------------------
+ Bra
+ \x{100}++
+ X
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 196
+Need char = 'X'
+
+/X+\x{200}/8DZ
+------------------------------------------------------------------
+ Bra
+ X++
+ \x{200}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+First char = 'X'
+Need char = 128
+
+/()()()()()()()()()()
+ ()()()()()()()()()()
+ ()()()()()()()()()()
+ ()()()()()()()()()()
+ A (x) (?41) B/8x
+ AxxB
+Matched, but too many substrings
+ 0: AxxB
+ 1:
+ 2:
+ 3:
+ 4:
+ 5:
+ 6:
+ 7:
+ 8:
+ 9:
+10:
+11:
+12:
+13:
+14:
+
+/^[\x{100}\E-\Q\E\x{150}]/BZ8
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x{100}-\x{150}]
+ Ket
+ End
+------------------------------------------------------------------
+
+/^[\QĀ\E-\QŐ\E]/BZ8
+------------------------------------------------------------------
+ Bra
+ ^
+ [\x{100}-\x{150}]
+ Ket
+ End
+------------------------------------------------------------------
+
+/^[\QĀ\E-\QŐ\E/BZ8
+Failed: missing terminating ] for character class at offset 15
+
+/^abc./mgx8<any>
+ abc1 \x0aabc2 \x0babc3xx \x0cabc4 \x0dabc5xx \x0d\x0aabc6 \x{0085}abc7 \x{2028}abc8 \x{2029}abc9 JUNK
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc7
+ 0: abc8
+ 0: abc9
+
+/abc.$/mgx8<any>
+ abc1\x0a abc2\x0b abc3\x0c abc4\x0d abc5\x0d\x0a abc6\x{0085} abc7\x{2028} abc8\x{2029} abc9
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc7
+ 0: abc8
+ 0: abc9
+
+/^a\Rb/8<bsr_unicode>
+ a\nb
+ 0: a\x{0a}b
+ a\rb
+ 0: a\x{0d}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x0bb
+ 0: a\x{0b}b
+ a\x0cb
+ 0: a\x{0c}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\x{2028}b
+ 0: a\x{2028}b
+ a\x{2029}b
+ 0: a\x{2029}b
+ ** Failers
+No match
+ a\n\rb
+No match
+
+/^a\R*b/8<bsr_unicode>
+ ab
+ 0: ab
+ a\nb
+ 0: a\x{0a}b
+ a\rb
+ 0: a\x{0d}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x0bb
+ 0: a\x{0b}b
+ a\x0c\x{2028}\x{2029}b
+ 0: a\x{0c}\x{2028}\x{2029}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\n\rb
+ 0: a\x{0a}\x{0d}b
+ a\n\r\x{85}\x0cb
+ 0: a\x{0a}\x{0d}\x{85}\x{0c}b
+
+/^a\R+b/8<bsr_unicode>
+ a\nb
+ 0: a\x{0a}b
+ a\rb
+ 0: a\x{0d}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x0bb
+ 0: a\x{0b}b
+ a\x0c\x{2028}\x{2029}b
+ 0: a\x{0c}\x{2028}\x{2029}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\n\rb
+ 0: a\x{0a}\x{0d}b
+ a\n\r\x{85}\x0cb
+ 0: a\x{0a}\x{0d}\x{85}\x{0c}b
+ ** Failers
+No match
+ ab
+No match
+
+/^a\R{1,3}b/8<bsr_unicode>
+ a\nb
+ 0: a\x{0a}b
+ a\n\rb
+ 0: a\x{0a}\x{0d}b
+ a\n\r\x{85}b
+ 0: a\x{0a}\x{0d}\x{85}b
+ a\r\n\r\nb
+ 0: a\x{0d}\x{0a}\x{0d}\x{0a}b
+ a\r\n\r\n\r\nb
+ 0: a\x{0d}\x{0a}\x{0d}\x{0a}\x{0d}\x{0a}b
+ a\n\r\n\rb
+ 0: a\x{0a}\x{0d}\x{0a}\x{0d}b
+ a\n\n\r\nb
+ 0: a\x{0a}\x{0a}\x{0d}\x{0a}b
+ ** Failers
+No match
+ a\n\n\n\rb
+No match
+ a\r
+No match
+
+/\H\h\V\v/8
+ X X\x0a
+ 0: X X\x{0a}
+ X\x09X\x0b
+ 0: X\x{09}X\x{0b}
+ ** Failers
+No match
+ \x{a0} X\x0a
+No match
+
+/\H*\h+\V?\v{3,4}/8
+ \x09\x20\x{a0}X\x0a\x0b\x0c\x0d\x0a
+ 0: \x{09} \x{a0}X\x{0a}\x{0b}\x{0c}\x{0d}
+ \x09\x20\x{a0}\x0a\x0b\x0c\x0d\x0a
+ 0: \x{09} \x{a0}\x{0a}\x{0b}\x{0c}\x{0d}
+ \x09\x20\x{a0}\x0a\x0b\x0c
+ 0: \x{09} \x{a0}\x{0a}\x{0b}\x{0c}
+ ** Failers
+No match
+ \x09\x20\x{a0}\x0a\x0b
+No match
+
+/\H\h\V\v/8
+ \x{3001}\x{3000}\x{2030}\x{2028}
+ 0: \x{3001}\x{3000}\x{2030}\x{2028}
+ X\x{180e}X\x{85}
+ 0: X\x{180e}X\x{85}
+ ** Failers
+No match
+ \x{2009} X\x0a
+No match
+
+/\H*\h+\V?\v{3,4}/8
+ \x{1680}\x{180e}\x{2007}X\x{2028}\x{2029}\x0c\x0d\x0a
+ 0: \x{1680}\x{180e}\x{2007}X\x{2028}\x{2029}\x{0c}\x{0d}
+ \x09\x{205f}\x{a0}\x0a\x{2029}\x0c\x{2028}\x0a
+ 0: \x{09}\x{205f}\x{a0}\x{0a}\x{2029}\x{0c}\x{2028}
+ \x09\x20\x{202f}\x0a\x0b\x0c
+ 0: \x{09} \x{202f}\x{0a}\x{0b}\x{0c}
+ ** Failers
+No match
+ \x09\x{200a}\x{a0}\x{2028}\x0b
+No match
+
+/[\h]/8BZ
+------------------------------------------------------------------
+ Bra
+ [\x09 \xa0\x{1680}\x{180e}\x{2000}-\x{200a}\x{202f}\x{205f}\x{3000}]
+ Ket
+ End
+------------------------------------------------------------------
+ >\x{1680}
+ 0: \x{1680}
+
+/[\h]{3,}/8BZ
+------------------------------------------------------------------
+ Bra
+ [\x09 \xa0\x{1680}\x{180e}\x{2000}-\x{200a}\x{202f}\x{205f}\x{3000}]{3,}
+ Ket
+ End
+------------------------------------------------------------------
+ >\x{1680}\x{180e}\x{2000}\x{2003}\x{200a}\x{202f}\x{205f}\x{3000}<
+ 0: \x{1680}\x{180e}\x{2000}\x{2003}\x{200a}\x{202f}\x{205f}\x{3000}
+
+/[\v]/8BZ
+------------------------------------------------------------------
+ Bra
+ [\x0a-\x0d\x85\x{2028}-\x{2029}]
+ Ket
+ End
+------------------------------------------------------------------
+
+/[\H]/8BZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\x08\x0a-\x1f!-\x9f\xa1-\xff\x{100}-\x{167f}\x{1681}-\x{180d}\x{180f}-\x{1fff}\x{200b}-\x{202e}\x{2030}-\x{205e}\x{2060}-\x{2fff}\x{3001}-\x{7fffffff}]
+ Ket
+ End
+------------------------------------------------------------------
+
+/[\V]/8BZ
+------------------------------------------------------------------
+ Bra
+ [\x00-\x09\x0e-\x84\x86-\xff\x{100}-\x{2027}\x{2029}-\x{7fffffff}]
+ Ket
+ End
+------------------------------------------------------------------
+
+/.*$/8<any>
+ \x{1ec5}
+ 0: \x{1ec5}
+
+/-- This tests the stricter UTF-8 check according to RFC 3629. --/
+
+/X/8
+ \x{0}\x{d7ff}\x{e000}\x{10ffff}
+No match
+ \x{d800}
+Error -10
+ \x{d800}\?
+No match
+ \x{da00}
+Error -10
+ \x{da00}\?
+No match
+ \x{dfff}
+Error -10
+ \x{dfff}\?
+No match
+ \x{110000}
+Error -10
+ \x{110000}\?
+No match
+ \x{2000000}
+Error -10
+ \x{2000000}\?
+No match
+ \x{7fffffff}
+Error -10
+ \x{7fffffff}\?
+No match
+
+/a\Rb/I8<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ ** Failers
+No match
+ a\x{85}b
+No match
+ a\x0bb
+No match
+
+/a\Rb/I8<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\x0bb
+ 0: a\x{0b}b
+ ** Failers
+No match
+ a\x{85}b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/a\R?b/I8<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ ** Failers
+No match
+ a\x{85}b
+No match
+ a\x0bb
+No match
+
+/a\R?b/I8<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\x0bb
+ 0: a\x{0b}b
+ ** Failers
+No match
+ a\x{85}b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/.*a.*=.b.*/8<ANY>
+ QQQ\x{2029}ABCaXYZ=!bPQR
+ 0: ABCaXYZ=!bPQR
+ ** Failers
+No match
+ a\x{2029}b
+No match
+ \x61\xe2\x80\xa9\x62
+No match
+
+/[[:a\x{100}b:]]/8
+Failed: unknown POSIX class name at offset 3
+
+/ End of testinput5 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput6 b/lib/stdlib/test/re_SUITE_data/testoutput6
new file mode 100644
index 0000000000..db825b08c1
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput6
@@ -0,0 +1,1682 @@
+/^\pC\pL\pM\pN\pP\pS\pZ</8
+ \x7f\x{c0}\x{30f}\x{660}\x{66c}\x{f01}\x{1680}<
+ 0: \x{7f}\x{c0}\x{30f}\x{660}\x{66c}\x{f01}\x{1680}<
+ \np\x{300}9!\$ <
+ 0: \x{0a}p\x{300}9!$ <
+ ** Failers
+No match
+ ap\x{300}9!\$ <
+No match
+
+/^\PC/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x7f
+No match
+
+/^\PL/8
+ 9
+ 0: 9
+ ** Failers
+ 0: *
+ \x{c0}
+No match
+
+/^\PM/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{30f}
+No match
+
+/^\PN/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{660}
+No match
+
+/^\PP/8
+ X
+ 0: X
+ ** Failers
+No match
+ \x{66c}
+No match
+
+/^\PS/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{f01}
+No match
+
+/^\PZ/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{1680}
+No match
+
+/^\p{Cc}/8
+ \x{017}
+ 0: \x{17}
+ \x{09f}
+ 0: \x{9f}
+ ** Failers
+No match
+ \x{0600}
+No match
+
+/^\p{Cf}/8
+ \x{601}
+ 0: \x{601}
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Cn}/8
+ \x{e0000}
+ 0: \x{e0000}
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Co}/8
+ \x{f8ff}
+ 0: \x{f8ff}
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Cs}/8
+ \?\x{dfff}
+ 0: \x{dfff}
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Ll}/8
+ a
+ 0: a
+ ** Failers
+No match
+ Z
+No match
+ \x{e000}
+No match
+
+/^\p{Lm}/8
+ \x{2b0}
+ 0: \x{2b0}
+ ** Failers
+No match
+ a
+No match
+
+/^\p{Lo}/8
+ \x{1bb}
+ 0: \x{1bb}
+ \x{3400}
+ 0: \x{3400}
+ \x{3401}
+ 0: \x{3401}
+ \x{4d00}
+ 0: \x{4d00}
+ \x{4db4}
+ 0: \x{4db4}
+ \x{4db5}
+ 0: \x{4db5}
+ ** Failers
+No match
+ a
+No match
+ \x{2b0}
+No match
+ \x{4db6}
+No match
+
+/^\p{Lt}/8
+ \x{1c5}
+ 0: \x{1c5}
+ ** Failers
+No match
+ a
+No match
+ \x{2b0}
+No match
+
+/^\p{Lu}/8
+ A
+ 0: A
+ ** Failers
+No match
+ \x{2b0}
+No match
+
+/^\p{Mc}/8
+ \x{903}
+ 0: \x{903}
+ ** Failers
+No match
+ X
+No match
+ \x{300}
+No match
+
+/^\p{Me}/8
+ \x{488}
+ 0: \x{488}
+ ** Failers
+No match
+ X
+No match
+ \x{903}
+No match
+ \x{300}
+No match
+
+/^\p{Mn}/8
+ \x{300}
+ 0: \x{300}
+ ** Failers
+No match
+ X
+No match
+ \x{903}
+No match
+
+/^\p{Nd}+/8
+ 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}\x{666}\x{667}\x{668}\x{669}\x{66a}
+ 0: 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}\x{666}\x{667}\x{668}\x{669}
+ \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}\x{6f6}\x{6f7}\x{6f8}\x{6f9}\x{6fa}
+ 0: \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}\x{6f6}\x{6f7}\x{6f8}\x{6f9}
+ \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}\x{96c}\x{96d}\x{96e}\x{96f}\x{970}
+ 0: \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}\x{96c}\x{96d}\x{96e}\x{96f}
+ ** Failers
+No match
+ X
+No match
+
+/^\p{Nl}/8
+ \x{16ee}
+ 0: \x{16ee}
+ ** Failers
+No match
+ X
+No match
+ \x{966}
+No match
+
+/^\p{No}/8
+ \x{b2}
+ 0: \x{b2}
+ \x{b3}
+ 0: \x{b3}
+ ** Failers
+No match
+ X
+No match
+ \x{16ee}
+No match
+
+/^\p{Pc}/8
+ \x5f
+ 0: _
+ \x{203f}
+ 0: \x{203f}
+ ** Failers
+No match
+ X
+No match
+ -
+No match
+ \x{58a}
+No match
+
+/^\p{Pd}/8
+ -
+ 0: -
+ \x{58a}
+ 0: \x{58a}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Pe}/8
+ )
+ 0: )
+ ]
+ 0: ]
+ }
+ 0: }
+ \x{f3b}
+ 0: \x{f3b}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+ (
+No match
+ [
+No match
+ {
+No match
+ \x{f3c}
+No match
+
+/^\p{Pf}/8
+ \x{bb}
+ 0: \x{bb}
+ \x{2019}
+ 0: \x{2019}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Pi}/8
+ \x{ab}
+ 0: \x{ab}
+ \x{2018}
+ 0: \x{2018}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Po}/8
+ !
+ 0: !
+ \x{37e}
+ 0: \x{37e}
+ ** Failers
+ 0: *
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Ps}/8
+ (
+ 0: (
+ [
+ 0: [
+ {
+ 0: {
+ \x{f3c}
+ 0: \x{f3c}
+ ** Failers
+No match
+ X
+No match
+ )
+No match
+ ]
+No match
+ }
+No match
+ \x{f3b}
+No match
+
+/^\p{Sc}+/8
+ $\x{a2}\x{a3}\x{a4}\x{a5}\x{a6}
+ 0: $\x{a2}\x{a3}\x{a4}\x{a5}
+ \x{9f2}
+ 0: \x{9f2}
+ ** Failers
+No match
+ X
+No match
+ \x{2c2}
+No match
+
+/^\p{Sk}/8
+ \x{2c2}
+ 0: \x{2c2}
+ ** Failers
+No match
+ X
+No match
+ \x{9f2}
+No match
+
+/^\p{Sm}+/8
+ +<|~\x{ac}\x{2044}
+ 0: +<|~\x{ac}\x{2044}
+ ** Failers
+No match
+ X
+No match
+ \x{9f2}
+No match
+
+/^\p{So}/8
+ \x{a6}
+ 0: \x{a6}
+ \x{482}
+ 0: \x{482}
+ ** Failers
+No match
+ X
+No match
+ \x{9f2}
+No match
+
+/^\p{Zl}/8
+ \x{2028}
+ 0: \x{2028}
+ ** Failers
+No match
+ X
+No match
+ \x{2029}
+No match
+
+/^\p{Zp}/8
+ \x{2029}
+ 0: \x{2029}
+ ** Failers
+No match
+ X
+No match
+ \x{2028}
+No match
+
+/^\p{Zs}/8
+ \ \
+ 0:
+ \x{a0}
+ 0: \x{a0}
+ \x{1680}
+ 0: \x{1680}
+ \x{180e}
+ 0: \x{180e}
+ \x{2000}
+ 0: \x{2000}
+ \x{2001}
+ 0: \x{2001}
+ ** Failers
+No match
+ \x{2028}
+No match
+ \x{200d}
+No match
+
+/\p{Nd}+(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: AB
+
+/\p{Nd}+?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}
+ 1: \x{661}\x{662}
+
+/\p{Nd}{2,}(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: AB
+
+/\p{Nd}{2,}?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}A
+ 1: \x{662}A
+
+/\p{Nd}*(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: AB
+
+/\p{Nd}*?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}
+ 1: \x{660}\x{661}
+
+/\p{Nd}{2}(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}A
+ 1: \x{662}A
+
+/\p{Nd}{2,3}(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: AB
+
+/\p{Nd}{2,3}?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}A
+ 1: \x{662}A
+
+/\p{Nd}?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}
+ 1: \x{661}\x{662}
+
+/\p{Nd}??(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}
+ 1: \x{660}\x{661}
+
+/\p{Nd}*+(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: AB
+
+/\p{Nd}*+(...)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}ABC
+ 1: ABC
+
+/\p{Nd}*+(....)/8
+ ** Failers
+ 0: ** F
+ 1: ** F
+ \x{660}\x{661}\x{662}ABC
+No match
+
+/\p{Lu}/8i
+ A
+ 0: A
+ a\x{10a0}B
+ 0: \x{10a0}
+ ** Failers
+ 0: F
+ a
+No match
+ \x{1d00}
+No match
+
+/\p{^Lu}/8i
+ 1234
+ 0: 1
+ ** Failers
+ 0: *
+ ABC
+No match
+
+/\P{Lu}/8i
+ 1234
+ 0: 1
+ ** Failers
+ 0: *
+ ABC
+No match
+
+/(?<=A\p{Nd})XYZ/8
+ A2XYZ
+ 0: XYZ
+ 123A5XYZPQR
+ 0: XYZ
+ ABA\x{660}XYZpqr
+ 0: XYZ
+ ** Failers
+No match
+ AXYZ
+No match
+ XYZ
+No match
+
+/(?<!\pL)XYZ/8
+ 1XYZ
+ 0: XYZ
+ AB=XYZ..
+ 0: XYZ
+ XYZ
+ 0: XYZ
+ ** Failers
+No match
+ WXYZ
+No match
+
+/[\p{L}]/DZ
+------------------------------------------------------------------
+ Bra
+ [\p{L}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[\p{^L}]/DZ
+------------------------------------------------------------------
+ Bra
+ [\P{L}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[\P{L}]/DZ
+------------------------------------------------------------------
+ Bra
+ [\P{L}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[\P{^L}]/DZ
+------------------------------------------------------------------
+ Bra
+ [\p{L}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+No options
+No first char
+No need char
+
+/[abc\p{L}\x{0660}]/8DZ
+------------------------------------------------------------------
+ Bra
+ [a-c\p{L}\x{660}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+
+/[\p{Nd}]/8DZ
+------------------------------------------------------------------
+ Bra
+ [\p{Nd}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+No first char
+No need char
+ 1234
+ 0: 1
+
+/[\p{Nd}+-]+/8DZ
+------------------------------------------------------------------
+ Bra
+ [+\-\p{Nd}]+
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Partial matching not supported
+Options: utf8
+No first char
+No need char
+ 1234
+ 0: 1234
+ 12-34
+ 0: 12-34
+ 12+\x{661}-34
+ 0: 12+\x{661}-34
+ ** Failers
+No match
+ abcd
+No match
+
+/[\P{Nd}]+/8
+ abcd
+ 0: abcd
+ ** Failers
+ 0: ** Failers
+ 1234
+No match
+
+/\D+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/\P{Nd}+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/[\D]+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/[\P{Nd}]+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/[\D\P{Nd}]+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/\pL/8
+ a
+ 0: a
+ A
+ 0: A
+
+/\pL/8i
+ a
+ 0: a
+ A
+ 0: A
+
+/\p{Lu}/8
+ A
+ 0: A
+ aZ
+ 0: Z
+ ** Failers
+ 0: F
+ abc
+No match
+
+/\p{Lu}/8i
+ A
+ 0: A
+ aZ
+ 0: Z
+ ** Failers
+ 0: F
+ abc
+No match
+
+/\p{Ll}/8
+ a
+ 0: a
+ Az
+ 0: z
+ ** Failers
+ 0: a
+ ABC
+No match
+
+/\p{Ll}/8i
+ a
+ 0: a
+ Az
+ 0: z
+ ** Failers
+ 0: a
+ ABC
+No match
+
+/^\x{c0}$/8i
+ \x{c0}
+ 0: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+
+/^\x{e0}$/8i
+ \x{c0}
+ 0: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8
+ A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 0: A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ ** Failers
+No match
+ a\x{391}\x{10427}\x{ff3a}\x{1fb0}
+No match
+ A\x{3b1}\x{10427}\x{ff3a}\x{1fb0}
+No match
+ A\x{391}\x{1044F}\x{ff3a}\x{1fb0}
+No match
+ A\x{391}\x{10427}\x{ff5a}\x{1fb0}
+No match
+ A\x{391}\x{10427}\x{ff3a}\x{1fb8}
+No match
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8i
+ A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 0: A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ a\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 0: a\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ A\x{3b1}\x{10427}\x{ff3a}\x{1fb0}
+ 0: A\x{3b1}\x{10427}\x{ff3a}\x{1fb0}
+ A\x{391}\x{1044F}\x{ff3a}\x{1fb0}
+ 0: A\x{391}\x{1044f}\x{ff3a}\x{1fb0}
+ A\x{391}\x{10427}\x{ff5a}\x{1fb0}
+ 0: A\x{391}\x{10427}\x{ff5a}\x{1fb0}
+ A\x{391}\x{10427}\x{ff3a}\x{1fb8}
+ 0: A\x{391}\x{10427}\x{ff3a}\x{1fb8}
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8iDZ
+------------------------------------------------------------------
+ Bra
+ NC A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: caseless utf8
+First char = 'A' (caseless)
+No need char
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8DZ
+------------------------------------------------------------------
+ Bra
+ A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 'A'
+Need char = 176
+
+/AB\x{1fb0}/8DZ
+------------------------------------------------------------------
+ Bra
+ AB\x{1fb0}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: utf8
+First char = 'A'
+Need char = 176
+
+/AB\x{1fb0}/8DZi
+------------------------------------------------------------------
+ Bra
+ NC AB\x{1fb0}
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: caseless utf8
+First char = 'A' (caseless)
+Need char = 'B' (caseless)
+
+/\x{391}+/8i
+ \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}
+ 0: \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}
+
+/\x{391}{3,5}(.)/8i
+ \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}X
+ 0: \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}X
+ 1: X
+
+/\x{391}{3,5}?(.)/8i
+ \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}X
+ 0: \x{391}\x{3b1}\x{3b1}\x{3b1}
+ 1: \x{3b1}
+
+/[\x{391}\x{ff3a}]/8i
+ \x{391}
+ 0: \x{391}
+ \x{ff3a}
+ 0: \x{ff3a}
+ \x{3b1}
+ 0: \x{3b1}
+ \x{ff5a}
+ 0: \x{ff5a}
+
+/[\x{c0}\x{391}]/8i
+ \x{c0}
+ 0: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+
+/[\x{105}-\x{109}]/8iDZ
+------------------------------------------------------------------
+ Bra
+ [\x{104}-\x{109}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: caseless utf8
+No first char
+No need char
+ \x{104}
+ 0: \x{104}
+ \x{105}
+ 0: \x{105}
+ \x{109}
+ 0: \x{109}
+ ** Failers
+No match
+ \x{100}
+No match
+ \x{10a}
+No match
+
+/[z-\x{100}]/8iDZ
+------------------------------------------------------------------
+ Bra
+ [Z\x{39c}\x{178}z-\x{101}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: caseless utf8
+No first char
+No need char
+ Z
+ 0: Z
+ z
+ 0: z
+ \x{39c}
+ 0: \x{39c}
+ \x{178}
+ 0: \x{178}
+ |
+ 0: |
+ \x{80}
+ 0: \x{80}
+ \x{ff}
+ 0: \x{ff}
+ \x{100}
+ 0: \x{100}
+ \x{101}
+ 0: \x{101}
+ ** Failers
+No match
+ \x{102}
+No match
+ Y
+No match
+ y
+No match
+
+/[z-\x{100}]/8DZi
+------------------------------------------------------------------
+ Bra
+ [Z\x{39c}\x{178}z-\x{101}]
+ Ket
+ End
+------------------------------------------------------------------
+Capturing subpattern count = 0
+Options: caseless utf8
+No first char
+No need char
+
+/^\X/8
+ A
+ 0: A
+ A\x{300}BC
+ 0: A\x{300}
+ A\x{300}\x{301}\x{302}BC
+ 0: A\x{300}\x{301}\x{302}
+ *** Failers
+ 0: *
+ \x{300}
+No match
+
+/^[\X]/8
+ X123
+ 0: X
+ *** Failers
+No match
+ AXYZ
+No match
+
+/^(\X*)C/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A\x{300}\x{301}\x{302}BC
+ 1: A\x{300}\x{301}\x{302}B
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 1: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+
+/^(\X*?)C/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A\x{300}\x{301}\x{302}BC
+ 1: A\x{300}\x{301}\x{302}B
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A\x{300}\x{301}\x{302}BC
+ 1: A\x{300}\x{301}\x{302}B
+
+/^(\X*)(.)/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A\x{300}\x{301}\x{302}BCA
+ 1: A\x{300}\x{301}\x{302}BC
+ 2: A
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 1: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 2: C
+
+/^(\X*?)(.)/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A
+ 1:
+ 2: A
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A
+ 1:
+ 2: A
+
+/^\X(.)/8
+ *** Failers
+ 0: **
+ 1: *
+ A\x{300}\x{301}\x{302}
+No match
+
+/^\X{2,3}(.)/8
+ A\x{300}\x{301}B\x{300}X
+ 0: A\x{300}\x{301}B\x{300}X
+ 1: X
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}
+ 0: A\x{300}\x{301}B\x{300}C
+ 1: C
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}X
+ 0: A\x{300}\x{301}B\x{300}C\x{300}\x{301}X
+ 1: X
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}DA\x{300}X
+ 0: A\x{300}\x{301}B\x{300}C\x{300}\x{301}D
+ 1: D
+
+/^\X{2,3}?(.)/8
+ A\x{300}\x{301}B\x{300}X
+ 0: A\x{300}\x{301}B\x{300}X
+ 1: X
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}
+ 0: A\x{300}\x{301}B\x{300}C
+ 1: C
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}X
+ 0: A\x{300}\x{301}B\x{300}C
+ 1: C
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}DA\x{300}X
+ 0: A\x{300}\x{301}B\x{300}C
+ 1: C
+
+/^\p{Han}+/8
+ \x{2e81}\x{3007}\x{2f804}\x{31a0}
+ 0: \x{2e81}\x{3007}\x{2f804}
+ ** Failers
+No match
+ \x{2e7f}
+No match
+
+/^\P{Katakana}+/8
+ \x{3105}
+ 0: \x{3105}
+ ** Failers
+ 0: ** Failers
+ \x{30ff}
+No match
+
+/^[\p{Arabic}]/8
+ \x{06e9}
+ 0: \x{6e9}
+ \x{060b}
+ 0: \x{60b}
+ ** Failers
+No match
+ X\x{06e9}
+No match
+
+/^[\P{Yi}]/8
+ \x{2f800}
+ 0: \x{2f800}
+ ** Failers
+ 0: *
+ \x{a014}
+No match
+ \x{a4c6}
+No match
+
+/^\p{Any}X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ X
+No match
+
+/^\P{Any}X/8
+ ** Failers
+No match
+ AX
+No match
+
+/^\p{Any}?X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ ABXYZ
+No match
+
+/^\P{Any}?X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ ABXYZ
+No match
+
+/^\p{Any}+X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+ XYZ
+No match
+
+/^\P{Any}+X/8
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+ XYZ
+No match
+
+/^\p{Any}*X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+
+/^\P{Any}*X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+
+/^[\p{Any}]X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ X
+No match
+
+/^[\P{Any}]X/8
+ ** Failers
+No match
+ AX
+No match
+
+/^[\p{Any}]?X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ ABXYZ
+No match
+
+/^[\P{Any}]?X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ ABXYZ
+No match
+
+/^[\p{Any}]+X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+ XYZ
+No match
+
+/^[\P{Any}]+X/8
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+ XYZ
+No match
+
+/^[\p{Any}]*X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+
+/^[\P{Any}]*X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+
+/^\p{Any}{3,5}?/8
+ abcdefgh
+ 0: abc
+ \x{1234}\n\r\x{3456}xyz
+ 0: \x{1234}\x{0a}\x{0d}
+
+/^\p{Any}{3,5}/8
+ abcdefgh
+ 0: abcde
+ \x{1234}\n\r\x{3456}xyz
+ 0: \x{1234}\x{0a}\x{0d}\x{3456}x
+
+/^\P{Any}{3,5}?/8
+ ** Failers
+No match
+ abcdefgh
+No match
+ \x{1234}\n\r\x{3456}xyz
+No match
+
+/^\p{L&}X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ \x{1c5}XY
+ 0: \x{1c5}X
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^[\p{L&}]X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ \x{1c5}XY
+ 0: \x{1c5}X
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^\p{L&}+X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEXypqreX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^[\p{L&}]+X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEXypqreX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^\p{L&}+?X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^[\p{L&}]+?X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^\P{L&}X/8
+ !XY
+ 0: !X
+ \x{1bb}XY
+ 0: \x{1bb}X
+ \x{2b0}XY
+ 0: \x{2b0}X
+ ** Failers
+No match
+ \x{1c5}XY
+No match
+ AXY
+No match
+
+/^[\P{L&}]X/8
+ !XY
+ 0: !X
+ \x{1bb}XY
+ 0: \x{1bb}X
+ \x{2b0}XY
+ 0: \x{2b0}X
+ ** Failers
+No match
+ \x{1c5}XY
+No match
+ AXY
+No match
+
+/^(\p{Z}[^\p{C}\p{Z}]+)*$/
+ \xa0!
+ 0: \xa0!
+ 1: \xa0!
+
+/^[\pL](abc)(?1)/
+ AabcabcYZ
+ 0: Aabcabc
+ 1: abc
+
+/([\pL]=(abc))*X/
+ L=abcX
+ 0: L=abcX
+ 1: L=abc
+ 2: abc
+
+/The next two should be Perl-compatible, but it fails to match \x{e0}. PCRE
+will match it only with UCP support, because without that it has no notion
+of case for anything other than the ASCII letters. /
+
+/((?i)[\x{c0}])/8
+ \x{c0}
+ 0: \x{c0}
+ 1: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+ 1: \x{e0}
+
+/(?i:[\x{c0}])/8
+ \x{c0}
+ 0: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+
+/^\p{Balinese}\p{Cuneiform}\p{Nko}\p{Phags_Pa}\p{Phoenician}/8
+ \x{1b00}\x{12000}\x{7c0}\x{a840}\x{10900}
+ 0: \x{1b00}\x{12000}\x{7c0}\x{a840}\x{10900}
+
+/The next two are special cases where the lengths of the different cases of the
+same character differ. The first went wrong with heap fram storage; the 2nd
+was broken in all cases./
+
+/^\x{023a}+?(\x{0130}+)/8i
+ \x{023a}\x{2c65}\x{0130}
+ 0: \x{23a}\x{2c65}\x{130}
+ 1: \x{130}
+
+/^\x{023a}+([^X])/8i
+ \x{023a}\x{2c65}X
+ 0: \x{23a}\x{2c65}
+ 1: \x{2c65}
+
+/Check property support in non-UTF-8 mode/
+
+/\p{L}{4}/
+ 123abcdefg
+ 0: abcd
+ 123abc\xc4\xc5zz
+ 0: abc\xc4
+
+/\X{1,3}\d/
+ \x8aBCD
+No match
+
+/\X?\d/
+ \x8aBCD
+No match
+
+/\P{L}?\d/
+ \x8aBCD
+No match
+
+/[\PPP\x8a]{1,}\x80/
+ A\x80
+ 0: A\x80
+
+/(?:[\PPa*]*){8,}/
+
+/[\P{Any}]/BZ
+------------------------------------------------------------------
+ Bra
+ [\P{Any}]
+ Ket
+ End
+------------------------------------------------------------------
+
+/[\P{Any}\E]/BZ
+------------------------------------------------------------------
+ Bra
+ [\P{Any}]
+ Ket
+ End
+------------------------------------------------------------------
+
+/(\P{Yi}+\277)/
+
+/(\P{Yi}+\277)?/
+
+/(?<=\P{Yi}{3}A)X/
+
+/\p{Yi}+(\P{Yi}+)(?1)/
+
+/(\P{Yi}{2}\277)?/
+
+/[\P{Yi}A]/
+
+/[\P{Yi}\P{Yi}\P{Yi}A]/
+
+/[^\P{Yi}A]/
+
+/[^\P{Yi}\P{Yi}\P{Yi}A]/
+
+/(\P{Yi}*\277)*/
+
+/(\P{Yi}*?\277)*/
+
+/(\p{Yi}*+\277)*/
+
+/(\P{Yi}?\277)*/
+
+/(\P{Yi}??\277)*/
+
+/(\p{Yi}?+\277)*/
+
+/(\P{Yi}{0,3}\277)*/
+
+/(\P{Yi}{0,3}?\277)*/
+
+/(\p{Yi}{0,3}+\277)*/
+
+/^[\p{Arabic}]/8
+ \x{60e}
+ 0: \x{60e}
+ \x{656}
+ 0: \x{656}
+ \x{657}
+ 0: \x{657}
+ \x{658}
+ 0: \x{658}
+ \x{659}
+ 0: \x{659}
+ \x{65a}
+ 0: \x{65a}
+ \x{65b}
+ 0: \x{65b}
+ \x{65c}
+ 0: \x{65c}
+ \x{65d}
+ 0: \x{65d}
+ \x{65e}
+ 0: \x{65e}
+ \x{66a}
+ 0: \x{66a}
+ \x{6e9}
+ 0: \x{6e9}
+ \x{6ef}
+ 0: \x{6ef}
+ \x{6fa}
+ 0: \x{6fa}
+ ** Failers
+No match
+ \x{600}
+No match
+ \x{650}
+No match
+ \x{651}
+No match
+ \x{652}
+No match
+ \x{653}
+No match
+ \x{654}
+No match
+ \x{655}
+No match
+ \x{65f}
+No match
+
+/^\p{Cyrillic}/8
+ \x{1d2b}
+ 0: \x{1d2b}
+
+/^\p{Common}/8
+ \x{589}
+ 0: \x{589}
+ \x{60c}
+ 0: \x{60c}
+ \x{61f}
+ 0: \x{61f}
+ \x{964}
+ 0: \x{964}
+ \x{965}
+ 0: \x{965}
+ \x{970}
+ 0: \x{970}
+
+/^\p{Inherited}/8
+ \x{64b}
+ 0: \x{64b}
+ \x{654}
+ 0: \x{654}
+ \x{655}
+ 0: \x{655}
+ \x{200c}
+ 0: \x{200c}
+ ** Failers
+No match
+ \x{64a}
+No match
+ \x{656}
+No match
+
+/^\p{Shavian}/8
+ \x{10450}
+ 0: \x{10450}
+ \x{1047f}
+ 0: \x{1047f}
+
+/^\p{Deseret}/8
+ \x{10400}
+ 0: \x{10400}
+ \x{1044f}
+ 0: \x{1044f}
+
+/^\p{Osmanya}/8
+ \x{10480}
+ 0: \x{10480}
+ \x{1049d}
+ 0: \x{1049d}
+ \x{104a0}
+ 0: \x{104a0}
+ \x{104a9}
+ 0: \x{104a9}
+ ** Failers
+No match
+ \x{1049e}
+No match
+ \x{1049f}
+No match
+ \x{104aa}
+No match
+
+/\p{Zl}{2,3}+/8BZ
+------------------------------------------------------------------
+ Bra
+ prop Zl {2}
+ prop Zl ?+
+ Ket
+ End
+------------------------------------------------------------------
+ \xe2\x80\xa8\xe2\x80\xa8
+ 0: \x{2028}\x{2028}
+ \x{2028}\x{2028}\x{2028}
+ 0: \x{2028}\x{2028}\x{2028}
+
+/\p{Zl}/8BZ
+------------------------------------------------------------------
+ Bra
+ prop Zl
+ Ket
+ End
+------------------------------------------------------------------
+
+/\p{Lu}{3}+/8BZ
+------------------------------------------------------------------
+ Bra
+ prop Lu {3}
+ Ket
+ End
+------------------------------------------------------------------
+
+/\pL{2}+/8BZ
+------------------------------------------------------------------
+ Bra
+ prop L {2}
+ Ket
+ End
+------------------------------------------------------------------
+
+/\p{Cc}{2}+/8BZ
+------------------------------------------------------------------
+ Bra
+ prop Cc {2}
+ Ket
+ End
+------------------------------------------------------------------
+
+/ End of testinput6 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput7 b/lib/stdlib/test/re_SUITE_data/testoutput7
new file mode 100644
index 0000000000..d8e3833f39
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput7
@@ -0,0 +1,7215 @@
+/abc/
+ abc
+ 0: abc
+
+/ab*c/
+ abc
+ 0: abc
+ abbbbc
+ 0: abbbbc
+ ac
+ 0: ac
+
+/ab+c/
+ abc
+ 0: abc
+ abbbbbbc
+ 0: abbbbbbc
+ *** Failers
+No match
+ ac
+No match
+ ab
+No match
+
+/a*/
+ a
+ 0: a
+ 1:
+ aaaaaaaaaaaaaaaaa
+ 0: aaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaa
+ 5: aaaaaaaaaaaa
+ 6: aaaaaaaaaaa
+ 7: aaaaaaaaaa
+ 8: aaaaaaaaa
+ 9: aaaaaaaa
+10: aaaaaaa
+11: aaaaaa
+12: aaaaa
+13: aaaa
+14: aaa
+15: aa
+16: a
+17:
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many subsidiary matches
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaaaaaaaaaaaaaaa
+ 5: aaaaaaaaaaaaaaaaaaaaaaaaa
+ 6: aaaaaaaaaaaaaaaaaaaaaaaa
+ 7: aaaaaaaaaaaaaaaaaaaaaaa
+ 8: aaaaaaaaaaaaaaaaaaaaaa
+ 9: aaaaaaaaaaaaaaaaaaaaa
+10: aaaaaaaaaaaaaaaaaaaa
+11: aaaaaaaaaaaaaaaaaaa
+12: aaaaaaaaaaaaaaaaaa
+13: aaaaaaaaaaaaaaaaa
+14: aaaaaaaaaaaaaaaa
+15: aaaaaaaaaaaaaaa
+16: aaaaaaaaaaaaaa
+17: aaaaaaaaaaaaa
+18: aaaaaaaaaaaa
+19: aaaaaaaaaaa
+20: aaaaaaaaaa
+21: aaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\F
+ 0:
+
+/(a|abcd|african)/
+ a
+ 0: a
+ abcd
+ 0: abcd
+ 1: a
+ african
+ 0: african
+ 1: a
+
+/^abc/
+ abcdef
+ 0: abc
+ *** Failers
+No match
+ xyzabc
+No match
+ xyz\nabc
+No match
+
+/^abc/m
+ abcdef
+ 0: abc
+ xyz\nabc
+ 0: abc
+ *** Failers
+No match
+ xyzabc
+No match
+
+/\Aabc/
+ abcdef
+ 0: abc
+ *** Failers
+No match
+ xyzabc
+No match
+ xyz\nabc
+No match
+
+/\Aabc/m
+ abcdef
+ 0: abc
+ *** Failers
+No match
+ xyzabc
+No match
+ xyz\nabc
+No match
+
+/\Gabc/
+ abcdef
+ 0: abc
+ xyzabc\>3
+ 0: abc
+ *** Failers
+No match
+ xyzabc
+No match
+ xyzabc\>2
+No match
+
+/x\dy\Dz/
+ x9yzz
+ 0: x9yzz
+ x0y+z
+ 0: x0y+z
+ *** Failers
+No match
+ xyz
+No match
+ xxy0z
+No match
+
+/x\sy\Sz/
+ x yzz
+ 0: x yzz
+ x y+z
+ 0: x y+z
+ *** Failers
+No match
+ xyz
+No match
+ xxyyz
+No match
+
+/x\wy\Wz/
+ xxy+z
+ 0: xxy+z
+ *** Failers
+No match
+ xxy0z
+No match
+ x+y+z
+No match
+
+/x.y/
+ x+y
+ 0: x+y
+ x-y
+ 0: x-y
+ *** Failers
+No match
+ x\ny
+No match
+
+/x.y/s
+ x+y
+ 0: x+y
+ x-y
+ 0: x-y
+ x\ny
+ 0: x\x0ay
+
+/(a.b(?s)c.d|x.y)p.q/
+ a+bc+dp+q
+ 0: a+bc+dp+q
+ a+bc\ndp+q
+ 0: a+bc\x0adp+q
+ x\nyp+q
+ 0: x\x0ayp+q
+ *** Failers
+No match
+ a\nbc\ndp+q
+No match
+ a+bc\ndp\nq
+No match
+ x\nyp\nq
+No match
+
+/a\d\z/
+ ba0
+ 0: a0
+ *** Failers
+No match
+ ba0\n
+No match
+ ba0\ncd
+No match
+
+/a\d\z/m
+ ba0
+ 0: a0
+ *** Failers
+No match
+ ba0\n
+No match
+ ba0\ncd
+No match
+
+/a\d\Z/
+ ba0
+ 0: a0
+ ba0\n
+ 0: a0
+ *** Failers
+No match
+ ba0\ncd
+No match
+
+/a\d\Z/m
+ ba0
+ 0: a0
+ ba0\n
+ 0: a0
+ *** Failers
+No match
+ ba0\ncd
+No match
+
+/a\d$/
+ ba0
+ 0: a0
+ ba0\n
+ 0: a0
+ *** Failers
+No match
+ ba0\ncd
+No match
+
+/a\d$/m
+ ba0
+ 0: a0
+ ba0\n
+ 0: a0
+ ba0\ncd
+ 0: a0
+ *** Failers
+No match
+
+/abc/i
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+ ABC
+ 0: ABC
+
+/[^a]/
+ abcd
+ 0: b
+
+/ab?\w/
+ abz
+ 0: abz
+ 1: ab
+ abbz
+ 0: abb
+ 1: ab
+ azz
+ 0: az
+
+/x{0,3}yz/
+ ayzq
+ 0: yz
+ axyzq
+ 0: xyz
+ axxyz
+ 0: xxyz
+ axxxyzq
+ 0: xxxyz
+ axxxxyzq
+ 0: xxxyz
+ *** Failers
+No match
+ ax
+No match
+ axx
+No match
+
+/x{3}yz/
+ axxxyzq
+ 0: xxxyz
+ axxxxyzq
+ 0: xxxyz
+ *** Failers
+No match
+ ax
+No match
+ axx
+No match
+ ayzq
+No match
+ axyzq
+No match
+ axxyz
+No match
+
+/x{2,3}yz/
+ axxyz
+ 0: xxyz
+ axxxyzq
+ 0: xxxyz
+ axxxxyzq
+ 0: xxxyz
+ *** Failers
+No match
+ ax
+No match
+ axx
+No match
+ ayzq
+No match
+ axyzq
+No match
+
+/[^a]+/
+ bac
+ 0: b
+ bcdefax
+ 0: bcdef
+ 1: bcde
+ 2: bcd
+ 3: bc
+ 4: b
+ *** Failers
+ 0: *** F
+ 1: ***
+ 2: ***
+ 3: **
+ 4: *
+ aaaaa
+No match
+
+/[^a]*/
+ bac
+ 0: b
+ 1:
+ bcdefax
+ 0: bcdef
+ 1: bcde
+ 2: bcd
+ 3: bc
+ 4: b
+ 5:
+ *** Failers
+ 0: *** F
+ 1: ***
+ 2: ***
+ 3: **
+ 4: *
+ 5:
+ aaaaa
+ 0:
+
+/[^a]{3,5}/
+ xyz
+ 0: xyz
+ awxyza
+ 0: wxyz
+ 1: wxy
+ abcdefa
+ 0: bcdef
+ 1: bcde
+ 2: bcd
+ abcdefghijk
+ 0: bcdef
+ 1: bcde
+ 2: bcd
+ *** Failers
+ 0: *** F
+ 1: ***
+ 2: ***
+ axya
+No match
+ axa
+No match
+ aaaaa
+No match
+
+/\d*/
+ 1234b567
+ 0: 1234
+ 1: 123
+ 2: 12
+ 3: 1
+ 4:
+ xyz
+ 0:
+
+/\D*/
+ a1234b567
+ 0: a
+ 1:
+ xyz
+ 0: xyz
+ 1: xy
+ 2: x
+ 3:
+
+/\d+/
+ ab1234c56
+ 0: 1234
+ 1: 123
+ 2: 12
+ 3: 1
+ *** Failers
+No match
+ xyz
+No match
+
+/\D+/
+ ab123c56
+ 0: ab
+ 1: a
+ *** Failers
+ 0: *** Failers
+ 1: *** Failer
+ 2: *** Faile
+ 3: *** Fail
+ 4: *** Fai
+ 5: *** Fa
+ 6: *** F
+ 7: ***
+ 8: ***
+ 9: **
+10: *
+ 789
+No match
+
+/\d?A/
+ 045ABC
+ 0: 5A
+ ABC
+ 0: A
+ *** Failers
+No match
+ XYZ
+No match
+
+/\D?A/
+ ABC
+ 0: A
+ BAC
+ 0: BA
+ 9ABC
+ 0: A
+ *** Failers
+No match
+
+/a+/
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+
+/^.*xyz/
+ xyz
+ 0: xyz
+ ggggggggxyz
+ 0: ggggggggxyz
+
+/^.+xyz/
+ abcdxyz
+ 0: abcdxyz
+ axyz
+ 0: axyz
+ *** Failers
+No match
+ xyz
+No match
+
+/^.?xyz/
+ xyz
+ 0: xyz
+ cxyz
+ 0: cxyz
+
+/^\d{2,3}X/
+ 12X
+ 0: 12X
+ 123X
+ 0: 123X
+ *** Failers
+No match
+ X
+No match
+ 1X
+No match
+ 1234X
+No match
+
+/^[abcd]\d/
+ a45
+ 0: a4
+ b93
+ 0: b9
+ c99z
+ 0: c9
+ d04
+ 0: d0
+ *** Failers
+No match
+ e45
+No match
+ abcd
+No match
+ abcd1234
+No match
+ 1234
+No match
+
+/^[abcd]*\d/
+ a45
+ 0: a4
+ b93
+ 0: b9
+ c99z
+ 0: c9
+ d04
+ 0: d0
+ abcd1234
+ 0: abcd1
+ 1234
+ 0: 1
+ *** Failers
+No match
+ e45
+No match
+ abcd
+No match
+
+/^[abcd]+\d/
+ a45
+ 0: a4
+ b93
+ 0: b9
+ c99z
+ 0: c9
+ d04
+ 0: d0
+ abcd1234
+ 0: abcd1
+ *** Failers
+No match
+ 1234
+No match
+ e45
+No match
+ abcd
+No match
+
+/^a+X/
+ aX
+ 0: aX
+ aaX
+ 0: aaX
+
+/^[abcd]?\d/
+ a45
+ 0: a4
+ b93
+ 0: b9
+ c99z
+ 0: c9
+ d04
+ 0: d0
+ 1234
+ 0: 1
+ *** Failers
+No match
+ abcd1234
+No match
+ e45
+No match
+
+/^[abcd]{2,3}\d/
+ ab45
+ 0: ab4
+ bcd93
+ 0: bcd9
+ *** Failers
+No match
+ 1234
+No match
+ a36
+No match
+ abcd1234
+No match
+ ee45
+No match
+
+/^(abc)*\d/
+ abc45
+ 0: abc4
+ abcabcabc45
+ 0: abcabcabc4
+ 42xyz
+ 0: 4
+ *** Failers
+No match
+
+/^(abc)+\d/
+ abc45
+ 0: abc4
+ abcabcabc45
+ 0: abcabcabc4
+ *** Failers
+No match
+ 42xyz
+No match
+
+/^(abc)?\d/
+ abc45
+ 0: abc4
+ 42xyz
+ 0: 4
+ *** Failers
+No match
+ abcabcabc45
+No match
+
+/^(abc){2,3}\d/
+ abcabc45
+ 0: abcabc4
+ abcabcabc45
+ 0: abcabcabc4
+ *** Failers
+No match
+ abcabcabcabc45
+No match
+ abc45
+No match
+ 42xyz
+No match
+
+/1(abc|xyz)2(?1)3/
+ 1abc2abc3456
+ 0: 1abc2abc3
+ 1abc2xyz3456
+ 0: 1abc2xyz3
+
+/^(a*\w|ab)=(a*\w|ab)/
+ ab=ab
+ 0: ab=ab
+ 1: ab=a
+
+/^(a*\w|ab)=(?1)/
+ ab=ab
+ 0: ab=ab
+
+/^([^()]|\((?1)*\))*$/
+ abc
+ 0: abc
+ a(b)c
+ 0: a(b)c
+ a(b(c))d
+ 0: a(b(c))d
+ *** Failers)
+No match
+ a(b(c)d
+No match
+
+/^>abc>([^()]|\((?1)*\))*<xyz<$/
+ >abc>123<xyz<
+ 0: >abc>123<xyz<
+ >abc>1(2)3<xyz<
+ 0: >abc>1(2)3<xyz<
+ >abc>(1(2)3)<xyz<
+ 0: >abc>(1(2)3)<xyz<
+
+/^(?>a*)\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9876
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9
+ *** Failers
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/< (?: (?(R) \d++ | [^<>]*+) | (?R)) * >/x
+ <>
+ 0: <>
+ <abcd>
+ 0: <abcd>
+ <abc <123> hij>
+ 0: <abc <123> hij>
+ <abc <def> hij>
+ 0: <def>
+ <abc<>def>
+ 0: <abc<>def>
+ <abc<>
+ 0: <>
+ *** Failers
+No match
+ <abc
+No match
+
+/^(?(?=abc)\w{3}:|\d\d)$/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+ *** Failers
+No match
+ 123
+No match
+ xyz
+No match
+
+/^(?(?!abc)\d\d|\w{3}:)$/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+ *** Failers
+No match
+ 123
+No match
+ xyz
+No match
+
+/^(?=abc)\w{5}:$/
+ abcde:
+ 0: abcde:
+ *** Failers
+No match
+ abc..
+No match
+ 123
+No match
+ vwxyz
+No match
+
+/^(?!abc)\d\d$/
+ 12
+ 0: 12
+ *** Failers
+No match
+ abcde:
+No match
+ abc..
+No match
+ 123
+No match
+ vwxyz
+No match
+
+/(?<=abc|xy)123/
+ abc12345
+ 0: 123
+ wxy123z
+ 0: 123
+ *** Failers
+No match
+ 123abc
+No match
+
+/(?<!abc|xy)123/
+ 123abc
+ 0: 123
+ mno123456
+ 0: 123
+ *** Failers
+No match
+ abc12345
+No match
+ wxy123z
+No match
+
+/abc(?C1)xyz/
+ abcxyz
+--->abcxyz
+ 1 ^ ^ x
+ 0: abcxyz
+ 123abcxyz999
+--->123abcxyz999
+ 1 ^ ^ x
+ 0: abcxyz
+
+/(ab|cd){3,4}/C
+ ababab
+--->ababab
+ +0 ^ (ab|cd){3,4}
+ +1 ^ a
+ +4 ^ c
+ +2 ^^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +2 ^ ^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +2 ^ ^ b
+ +3 ^ ^ |
++12 ^ ^
+ +1 ^ ^ a
+ +4 ^ ^ c
+ 0: ababab
+ abcdabcd
+--->abcdabcd
+ +0 ^ (ab|cd){3,4}
+ +1 ^ a
+ +4 ^ c
+ +2 ^^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +2 ^ ^ b
+ +3 ^ ^ |
++12 ^ ^
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
++12 ^ ^
+ 0: abcdabcd
+ 1: abcdab
+ abcdcdcdcdcd
+--->abcdcdcdcdcd
+ +0 ^ (ab|cd){3,4}
+ +1 ^ a
+ +4 ^ c
+ +2 ^^ b
+ +3 ^ ^ |
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
++12 ^ ^
+ +1 ^ ^ a
+ +4 ^ ^ c
+ +5 ^ ^ d
+ +6 ^ ^ )
++12 ^ ^
+ 0: abcdcdcd
+ 1: abcdcd
+
+/^abc/
+ abcdef
+ 0: abc
+ *** Failers
+No match
+ abcdef\B
+No match
+
+/^(a*|xyz)/
+ bcd
+ 0:
+ aaabcd
+ 0: aaa
+ 1: aa
+ 2: a
+ 3:
+ xyz
+ 0: xyz
+ 1:
+ xyz\N
+ 0: xyz
+ *** Failers
+ 0:
+ bcd\N
+No match
+
+/xyz$/
+ xyz
+ 0: xyz
+ xyz\n
+ 0: xyz
+ *** Failers
+No match
+ xyz\Z
+No match
+ xyz\n\Z
+No match
+
+/xyz$/m
+ xyz
+ 0: xyz
+ xyz\n
+ 0: xyz
+ abcxyz\npqr
+ 0: xyz
+ abcxyz\npqr\Z
+ 0: xyz
+ xyz\n\Z
+ 0: xyz
+ *** Failers
+No match
+ xyz\Z
+No match
+
+/\Gabc/
+ abcdef
+ 0: abc
+ defabcxyz\>3
+ 0: abc
+ *** Failers
+No match
+ defabcxyz
+No match
+
+/^abcdef/
+ ab\P
+Partial match: ab
+ abcde\P
+Partial match: abcde
+ abcdef\P
+ 0: abcdef
+ *** Failers
+No match
+ abx\P
+No match
+
+/^a{2,4}\d+z/
+ a\P
+Partial match: a
+ aa\P
+Partial match: aa
+ aa2\P
+Partial match: aa2
+ aaa\P
+Partial match: aaa
+ aaa23\P
+Partial match: aaa23
+ aaaa12345\P
+Partial match: aaaa12345
+ aa0z\P
+ 0: aa0z
+ aaaa4444444444444z\P
+ 0: aaaa4444444444444z
+ *** Failers
+No match
+ az\P
+No match
+ aaaaa\P
+No match
+ a56\P
+No match
+
+/^abcdef/
+ abc\P
+Partial match: abc
+ def\R
+ 0: def
+
+/(?<=foo)bar/
+ xyzfo\P
+No match
+ foob\P\>2
+Partial match: b
+ foobar...\R\P\>4
+ 0: ar
+ xyzfo\P
+No match
+ foobar\>2
+ 0: bar
+ *** Failers
+No match
+ xyzfo\P
+No match
+ obar\R
+No match
+
+/(ab*(cd|ef))+X/
+ adfadadaklhlkalkajhlkjahdfasdfasdfladsfjkj\P\Z
+No match
+ lkjhlkjhlkjhlkjhabbbbbbcdaefabbbbbbbefa\P\B\Z
+Partial match: abbbbbbcdaefabbbbbbbefa
+ cdabbbbbbbb\P\R\B\Z
+Partial match: cdabbbbbbbb
+ efabbbbbbbbbbbbbbbb\P\R\B\Z
+Partial match: efabbbbbbbbbbbbbbbb
+ bbbbbbbbbbbbcdXyasdfadf\P\R\B\Z
+ 0: bbbbbbbbbbbbcdX
+
+/(a|b)/SF>testsavedregex
+Compiled regex written to testsavedregex
+Study data written to testsavedregex
+<testsavedregex
+Compiled regex (byte-inverted) loaded from testsavedregex
+Study data loaded from testsavedregex
+ abc
+ 0: a
+ ** Failers
+ 0: a
+ def
+No match
+
+/the quick brown fox/
+ the quick brown fox
+ 0: the quick brown fox
+ The quick brown FOX
+No match
+ What do you know about the quick brown fox?
+ 0: the quick brown fox
+ What do you know about THE QUICK BROWN FOX?
+No match
+
+/The quick brown fox/i
+ the quick brown fox
+ 0: the quick brown fox
+ The quick brown FOX
+ 0: The quick brown FOX
+ What do you know about the quick brown fox?
+ 0: the quick brown fox
+ What do you know about THE QUICK BROWN FOX?
+ 0: THE QUICK BROWN FOX
+
+/abcd\t\n\r\f\a\e\071\x3b\$\\\?caxyz/
+ abcd\t\n\r\f\a\e9;\$\\?caxyz
+ 0: abcd\x09\x0a\x0d\x0c\x07\x1b9;$\?caxyz
+
+/a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz/
+ abxyzpqrrrabbxyyyypqAzz
+ 0: abxyzpqrrrabbxyyyypqAzz
+ abxyzpqrrrabbxyyyypqAzz
+ 0: abxyzpqrrrabbxyyyypqAzz
+ aabxyzpqrrrabbxyyyypqAzz
+ 0: aabxyzpqrrrabbxyyyypqAzz
+ aaabxyzpqrrrabbxyyyypqAzz
+ 0: aaabxyzpqrrrabbxyyyypqAzz
+ aaaabxyzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzpqrrrabbxyyyypqAzz
+ abcxyzpqrrrabbxyyyypqAzz
+ 0: abcxyzpqrrrabbxyyyypqAzz
+ aabcxyzpqrrrabbxyyyypqAzz
+ 0: aabcxyzpqrrrabbxyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypAzz
+ 0: aaabcxyzpqrrrabbxyyyypAzz
+ aaabcxyzpqrrrabbxyyyypqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqqAzz
+ aaabcxyzpqrrrabbxyyyypqqqqqqAzz
+ 0: aaabcxyzpqrrrabbxyyyypqqqqqqAzz
+ aaaabcxyzpqrrrabbxyyyypqAzz
+ 0: aaaabcxyzpqrrrabbxyyyypqAzz
+ abxyzzpqrrrabbxyyyypqAzz
+ 0: abxyzzpqrrrabbxyyyypqAzz
+ aabxyzzzpqrrrabbxyyyypqAzz
+ 0: aabxyzzzpqrrrabbxyyyypqAzz
+ aaabxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaabxyzzzzpqrrrabbxyyyypqAzz
+ aaaabxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzzzzpqrrrabbxyyyypqAzz
+ abcxyzzpqrrrabbxyyyypqAzz
+ 0: abcxyzzpqrrrabbxyyyypqAzz
+ aabcxyzzzpqrrrabbxyyyypqAzz
+ 0: aabcxyzzzpqrrrabbxyyyypqAzz
+ aaabcxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaabcxyzzzzpqrrrabbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbxyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbbxyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbbxyyyypqAzz
+ aaaabcxyzzzzpqrrrabbbxyyyyypqAzz
+ 0: aaaabcxyzzzzpqrrrabbbxyyyyypqAzz
+ aaabcxyzpqrrrabbxyyyypABzz
+ 0: aaabcxyzpqrrrabbxyyyypABzz
+ aaabcxyzpqrrrabbxyyyypABBzz
+ 0: aaabcxyzpqrrrabbxyyyypABBzz
+ >>>aaabxyzpqrrrabbxyyyypqAzz
+ 0: aaabxyzpqrrrabbxyyyypqAzz
+ >aaaabxyzpqrrrabbxyyyypqAzz
+ 0: aaaabxyzpqrrrabbxyyyypqAzz
+ >>>>abcxyzpqrrrabbxyyyypqAzz
+ 0: abcxyzpqrrrabbxyyyypqAzz
+ *** Failers
+No match
+ abxyzpqrrabbxyyyypqAzz
+No match
+ abxyzpqrrrrabbxyyyypqAzz
+No match
+ abxyzpqrrrabxyyyypqAzz
+No match
+ aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz
+No match
+ aaaabcxyzzzzpqrrrabbbxyyypqAzz
+No match
+ aaabcxyzpqrrrabbxyyyypqqqqqqqAzz
+No match
+
+/^(abc){1,2}zz/
+ abczz
+ 0: abczz
+ abcabczz
+ 0: abcabczz
+ *** Failers
+No match
+ zz
+No match
+ abcabcabczz
+No match
+ >>abczz
+No match
+
+/^(b+?|a){1,2}?c/
+ bc
+ 0: bc
+ bbc
+ 0: bbc
+ bbbc
+ 0: bbbc
+ bac
+ 0: bac
+ bbac
+ 0: bbac
+ aac
+ 0: aac
+ abbbbbbbbbbbc
+ 0: abbbbbbbbbbbc
+ bbbbbbbbbbbac
+ 0: bbbbbbbbbbbac
+ *** Failers
+No match
+ aaac
+No match
+ abbbbbbbbbbbac
+No match
+
+/^(b+|a){1,2}c/
+ bc
+ 0: bc
+ bbc
+ 0: bbc
+ bbbc
+ 0: bbbc
+ bac
+ 0: bac
+ bbac
+ 0: bbac
+ aac
+ 0: aac
+ abbbbbbbbbbbc
+ 0: abbbbbbbbbbbc
+ bbbbbbbbbbbac
+ 0: bbbbbbbbbbbac
+ *** Failers
+No match
+ aaac
+No match
+ abbbbbbbbbbbac
+No match
+
+/^(b+|a){1,2}?bc/
+ bbc
+ 0: bbc
+
+/^(b*|ba){1,2}?bc/
+ babc
+ 0: babc
+ bbabc
+ 0: bbabc
+ bababc
+ 0: bababc
+ *** Failers
+No match
+ bababbc
+No match
+ babababc
+No match
+
+/^(ba|b*){1,2}?bc/
+ babc
+ 0: babc
+ bbabc
+ 0: bbabc
+ bababc
+ 0: bababc
+ *** Failers
+No match
+ bababbc
+No match
+ babababc
+No match
+
+/^\ca\cA\c[\c{\c:/
+ \x01\x01\e;z
+ 0: \x01\x01\x1b;z
+
+/^[ab\]cde]/
+ athing
+ 0: a
+ bthing
+ 0: b
+ ]thing
+ 0: ]
+ cthing
+ 0: c
+ dthing
+ 0: d
+ ething
+ 0: e
+ *** Failers
+No match
+ fthing
+No match
+ [thing
+No match
+ \\thing
+No match
+
+/^[]cde]/
+ ]thing
+ 0: ]
+ cthing
+ 0: c
+ dthing
+ 0: d
+ ething
+ 0: e
+ *** Failers
+No match
+ athing
+No match
+ fthing
+No match
+
+/^[^ab\]cde]/
+ fthing
+ 0: f
+ [thing
+ 0: [
+ \\thing
+ 0: \
+ *** Failers
+ 0: *
+ athing
+No match
+ bthing
+No match
+ ]thing
+No match
+ cthing
+No match
+ dthing
+No match
+ ething
+No match
+
+/^[^]cde]/
+ athing
+ 0: a
+ fthing
+ 0: f
+ *** Failers
+ 0: *
+ ]thing
+No match
+ cthing
+No match
+ dthing
+No match
+ ething
+No match
+
+/^\�/
+ �
+ 0: \x81
+
+/^�/
+ �
+ 0: \xff
+
+/^[0-9]+$/
+ 0
+ 0: 0
+ 1
+ 0: 1
+ 2
+ 0: 2
+ 3
+ 0: 3
+ 4
+ 0: 4
+ 5
+ 0: 5
+ 6
+ 0: 6
+ 7
+ 0: 7
+ 8
+ 0: 8
+ 9
+ 0: 9
+ 10
+ 0: 10
+ 100
+ 0: 100
+ *** Failers
+No match
+ abc
+No match
+
+/^.*nter/
+ enter
+ 0: enter
+ inter
+ 0: inter
+ uponter
+ 0: uponter
+
+/^xxx[0-9]+$/
+ xxx0
+ 0: xxx0
+ xxx1234
+ 0: xxx1234
+ *** Failers
+No match
+ xxx
+No match
+
+/^.+[0-9][0-9][0-9]$/
+ x123
+ 0: x123
+ xx123
+ 0: xx123
+ 123456
+ 0: 123456
+ *** Failers
+No match
+ 123
+No match
+ x1234
+ 0: x1234
+
+/^.+?[0-9][0-9][0-9]$/
+ x123
+ 0: x123
+ xx123
+ 0: xx123
+ 123456
+ 0: 123456
+ *** Failers
+No match
+ 123
+No match
+ x1234
+ 0: x1234
+
+/^([^!]+)!(.+)=apquxz\.ixr\.zzz\.ac\.uk$/
+ abc!pqr=apquxz.ixr.zzz.ac.uk
+ 0: abc!pqr=apquxz.ixr.zzz.ac.uk
+ *** Failers
+No match
+ !pqr=apquxz.ixr.zzz.ac.uk
+No match
+ abc!=apquxz.ixr.zzz.ac.uk
+No match
+ abc!pqr=apquxz:ixr.zzz.ac.uk
+No match
+ abc!pqr=apquxz.ixr.zzz.ac.ukk
+No match
+
+/:/
+ Well, we need a colon: somewhere
+ 0: :
+ *** Fail if we don't
+No match
+
+/([\da-f:]+)$/i
+ 0abc
+ 0: 0abc
+ abc
+ 0: abc
+ fed
+ 0: fed
+ E
+ 0: E
+ ::
+ 0: ::
+ 5f03:12C0::932e
+ 0: 5f03:12C0::932e
+ fed def
+ 0: def
+ Any old stuff
+ 0: ff
+ *** Failers
+No match
+ 0zzz
+No match
+ gzzz
+No match
+ fed\x20
+No match
+ Any old rubbish
+No match
+
+/^.*\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
+ .1.2.3
+ 0: .1.2.3
+ A.12.123.0
+ 0: A.12.123.0
+ *** Failers
+No match
+ .1.2.3333
+No match
+ 1.2.3
+No match
+ 1234.2.3
+No match
+
+/^(\d+)\s+IN\s+SOA\s+(\S+)\s+(\S+)\s*\(\s*$/
+ 1 IN SOA non-sp1 non-sp2(
+ 0: 1 IN SOA non-sp1 non-sp2(
+ 1 IN SOA non-sp1 non-sp2 (
+ 0: 1 IN SOA non-sp1 non-sp2 (
+ *** Failers
+No match
+ 1IN SOA non-sp1 non-sp2(
+No match
+
+/^[a-zA-Z\d][a-zA-Z\d\-]*(\.[a-zA-Z\d][a-zA-z\d\-]*)*\.$/
+ a.
+ 0: a.
+ Z.
+ 0: Z.
+ 2.
+ 0: 2.
+ ab-c.pq-r.
+ 0: ab-c.pq-r.
+ sxk.zzz.ac.uk.
+ 0: sxk.zzz.ac.uk.
+ x-.y-.
+ 0: x-.y-.
+ *** Failers
+No match
+ -abc.peq.
+No match
+
+/^\*\.[a-z]([a-z\-\d]*[a-z\d]+)?(\.[a-z]([a-z\-\d]*[a-z\d]+)?)*$/
+ *.a
+ 0: *.a
+ *.b0-a
+ 0: *.b0-a
+ *.c3-b.c
+ 0: *.c3-b.c
+ *.c-a.b-c
+ 0: *.c-a.b-c
+ *** Failers
+No match
+ *.0
+No match
+ *.a-
+No match
+ *.a-b.c-
+No match
+ *.c-a.0-c
+No match
+
+/^(?=ab(de))(abd)(e)/
+ abde
+ 0: abde
+
+/^(?!(ab)de|x)(abd)(f)/
+ abdf
+ 0: abdf
+
+/^(?=(ab(cd)))(ab)/
+ abcd
+ 0: ab
+
+/^[\da-f](\.[\da-f])*$/i
+ a.b.c.d
+ 0: a.b.c.d
+ A.B.C.D
+ 0: A.B.C.D
+ a.b.c.1.2.3.C
+ 0: a.b.c.1.2.3.C
+
+/^\".*\"\s*(;.*)?$/
+ \"1234\"
+ 0: "1234"
+ \"abcd\" ;
+ 0: "abcd" ;
+ \"\" ; rhubarb
+ 0: "" ; rhubarb
+ *** Failers
+No match
+ \"1234\" : things
+No match
+
+/^$/
+ \
+ 0:
+ *** Failers
+No match
+
+/ ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/x
+ ab c
+ 0: ab c
+ *** Failers
+No match
+ abc
+No match
+ ab cde
+No match
+
+/(?x) ^ a (?# begins with a) b\sc (?# then b c) $ (?# then end)/
+ ab c
+ 0: ab c
+ *** Failers
+No match
+ abc
+No match
+ ab cde
+No match
+
+/^ a\ b[c ]d $/x
+ a bcd
+ 0: a bcd
+ a b d
+ 0: a b d
+ *** Failers
+No match
+ abcd
+No match
+ ab d
+No match
+
+/^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$/
+ abcdefhijklm
+ 0: abcdefhijklm
+
+/^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$/
+ abcdefhijklm
+ 0: abcdefhijklm
+
+/^[\w][\W][\s][\S][\d][\D][\b][\n][\c]][\022]/
+ a+ Z0+\x08\n\x1d\x12
+ 0: a+ Z0+\x08\x0a\x1d\x12
+
+/^[.^$|()*+?{,}]+/
+ .^\$(*+)|{?,?}
+ 0: .^$(*+)|{?,?}
+ 1: .^$(*+)|{?,?
+ 2: .^$(*+)|{?,
+ 3: .^$(*+)|{?
+ 4: .^$(*+)|{
+ 5: .^$(*+)|
+ 6: .^$(*+)
+ 7: .^$(*+
+ 8: .^$(*
+ 9: .^$(
+10: .^$
+11: .^
+12: .
+
+/^a*\w/
+ z
+ 0: z
+ az
+ 0: az
+ 1: a
+ aaaz
+ 0: aaaz
+ 1: aaa
+ 2: aa
+ 3: a
+ a
+ 0: a
+ aa
+ 0: aa
+ 1: a
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+ a+
+ 0: a
+ aa+
+ 0: aa
+ 1: a
+
+/^a*?\w/
+ z
+ 0: z
+ az
+ 0: az
+ 1: a
+ aaaz
+ 0: aaaz
+ 1: aaa
+ 2: aa
+ 3: a
+ a
+ 0: a
+ aa
+ 0: aa
+ 1: a
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+ a+
+ 0: a
+ aa+
+ 0: aa
+ 1: a
+
+/^a+\w/
+ az
+ 0: az
+ aaaz
+ 0: aaaz
+ 1: aaa
+ 2: aa
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ aa+
+ 0: aa
+
+/^a+?\w/
+ az
+ 0: az
+ aaaz
+ 0: aaaz
+ 1: aaa
+ 2: aa
+ aa
+ 0: aa
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ aa+
+ 0: aa
+
+/^\d{8}\w{2,}/
+ 1234567890
+ 0: 1234567890
+ 12345678ab
+ 0: 12345678ab
+ 12345678__
+ 0: 12345678__
+ *** Failers
+No match
+ 1234567
+No match
+
+/^[aeiou\d]{4,5}$/
+ uoie
+ 0: uoie
+ 1234
+ 0: 1234
+ 12345
+ 0: 12345
+ aaaaa
+ 0: aaaaa
+ *** Failers
+No match
+ 123456
+No match
+
+/^[aeiou\d]{4,5}?/
+ uoie
+ 0: uoie
+ 1234
+ 0: 1234
+ 12345
+ 0: 12345
+ 1: 1234
+ aaaaa
+ 0: aaaaa
+ 1: aaaa
+ 123456
+ 0: 12345
+ 1: 1234
+
+/^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]/
+ From abcd Mon Sep 01 12:33:02 1997
+ 0: From abcd Mon Sep 01 12:33
+
+/^From\s+\S+\s+([a-zA-Z]{3}\s+){2}\d{1,2}\s+\d\d:\d\d/
+ From abcd Mon Sep 01 12:33:02 1997
+ 0: From abcd Mon Sep 01 12:33
+ From abcd Mon Sep 1 12:33:02 1997
+ 0: From abcd Mon Sep 1 12:33
+ *** Failers
+No match
+ From abcd Sep 01 12:33:02 1997
+No match
+
+/^12.34/s
+ 12\n34
+ 0: 12\x0a34
+ 12\r34
+ 0: 12\x0d34
+
+/\w+(?=\t)/
+ the quick brown\t fox
+ 0: brown
+
+/foo(?!bar)(.*)/
+ foobar is foolish see?
+ 0: foolish see?
+ 1: foolish see
+ 2: foolish se
+ 3: foolish s
+ 4: foolish
+ 5: foolish
+ 6: foolis
+ 7: fooli
+ 8: fool
+ 9: foo
+
+/(?:(?!foo)...|^.{0,2})bar(.*)/
+ foobar crowbar etc
+ 0: rowbar etc
+ 1: rowbar et
+ 2: rowbar e
+ 3: rowbar
+ 4: rowbar
+ barrel
+ 0: barrel
+ 1: barre
+ 2: barr
+ 3: bar
+ 2barrel
+ 0: 2barrel
+ 1: 2barre
+ 2: 2barr
+ 3: 2bar
+ A barrel
+ 0: A barrel
+ 1: A barre
+ 2: A barr
+ 3: A bar
+
+/^(\D*)(?=\d)(?!123)/
+ abc456
+ 0: abc
+ *** Failers
+No match
+ abc123
+No match
+
+/^1234(?# test newlines
+ inside)/
+ 1234
+ 0: 1234
+
+/^1234 #comment in extended re
+ /x
+ 1234
+ 0: 1234
+
+/#rhubarb
+ abcd/x
+ abcd
+ 0: abcd
+
+/^abcd#rhubarb/x
+ abcd
+ 0: abcd
+
+/(?!^)abc/
+ the abc
+ 0: abc
+ *** Failers
+No match
+ abc
+No match
+
+/(?=^)abc/
+ abc
+ 0: abc
+ *** Failers
+No match
+ the abc
+No match
+
+/^[ab]{1,3}(ab*|b)/
+ aabbbbb
+ 0: aabbbbb
+ 1: aabbbb
+ 2: aabbb
+ 3: aabb
+ 4: aab
+ 5: aa
+
+/^[ab]{1,3}?(ab*|b)/
+ aabbbbb
+ 0: aabbbbb
+ 1: aabbbb
+ 2: aabbb
+ 3: aabb
+ 4: aab
+ 5: aa
+
+/^[ab]{1,3}?(ab*?|b)/
+ aabbbbb
+ 0: aabbbbb
+ 1: aabbbb
+ 2: aabbb
+ 3: aabb
+ 4: aab
+ 5: aa
+
+/^[ab]{1,3}(ab*?|b)/
+ aabbbbb
+ 0: aabbbbb
+ 1: aabbbb
+ 2: aabbb
+ 3: aabb
+ 4: aab
+ 5: aa
+
+/ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # optional leading comment
+(?: (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # initial word
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) )* # further okay, if led by a period
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] | # atom and space parts, or...
+\(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) | # comments, or...
+
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+# quoted strings
+)*
+< (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # leading <
+(?: @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* , (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* )? # optional route
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) # initial word
+(?: (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+" (?: # opening quote...
+[^\\\x80-\xff\n\015"] # Anything except backslash and quote
+| # or
+\\ [^\x80-\xff] # Escaped something (something != CR)
+)* " # closing quote
+) )* # further okay, if led by a period
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* @ (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # initial subdomain
+(?: #
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* \. # if led by a period...
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* (?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+| \[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* > # trailing >
+# name and address
+) (?: [\040\t] | \(
+(?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] | \( (?: [^\\\x80-\xff\n\015()] | \\ [^\x80-\xff] )* \) )*
+\) )* # optional trailing comment
+/x
+ Alan Other <user\@dom.ain>
+ 0: Alan Other <[email protected]>
+ <user\@dom.ain>
+ 1: user@dom
+ user\@dom.ain
+ 1: user@dom
+ \"A. Other\" <user.1234\@dom.ain> (a comment)
+ 0: "A. Other" <[email protected]> (a comment)
+ 1: "A. Other" <[email protected]>
+ 2: "A. Other" <[email protected]>
+ A. Other <user.1234\@dom.ain> (a comment)
+ 0: Other <[email protected]> (a comment)
+ 1: Other <[email protected]>
+ 2: Other <[email protected]>
+ \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay
+ 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay
+ 1: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re
+ A missing angle <user\@some.where
+ 1: user@some
+ *** Failers
+No match
+ The quick brown fox
+No match
+
+/[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+# leading word
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # "normal" atoms and or spaces
+(?:
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+|
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+) # "special" comment or quoted string
+[^()<>@,;:".\\\[\]\x80-\xff\000-\010\012-\037] * # more "normal"
+)*
+<
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+" # "
+[^\\\x80-\xff\n\015"] * # normal
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015"] * )* # ( special normal* )*
+" # "
+# Quoted string
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\.
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+ # some number of atom characters...
+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]) # ..not followed by something that could be part of an atom
+|
+\[ # [
+(?: [^\\\x80-\xff\n\015\[\]] | \\ [^\x80-\xff] )* # stuff
+\] # ]
+)
+[\040\t]* # Nab whitespace.
+(?:
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: # (
+(?: \\ [^\x80-\xff] |
+\( # (
+[^\\\x80-\xff\n\015()] * # normal*
+(?: \\ [^\x80-\xff] [^\\\x80-\xff\n\015()] * )* # (special normal*)*
+\) # )
+) # special
+[^\\\x80-\xff\n\015()] * # normal*
+)* # )*
+\) # )
+[\040\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)
+/x
+ Alan Other <user\@dom.ain>
+ 0: Alan Other <[email protected]>
+ <user\@dom.ain>
+ 1: user@dom
+ user\@dom.ain
+ 1: user@dom
+ \"A. Other\" <user.1234\@dom.ain> (a comment)
+ 0: "A. Other" <[email protected]>
+ A. Other <user.1234\@dom.ain> (a comment)
+ 0: Other <[email protected]>
+ \"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"\@x400-re.lay
+ 0: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re.lay
+ 1: "/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/"@x400-re
+ A missing angle <user\@some.where
+ 1: user@some
+ *** Failers
+No match
+ The quick brown fox
+No match
+
+/abc\0def\00pqr\000xyz\0000AB/
+ abc\0def\00pqr\000xyz\0000AB
+ 0: abc\x00def\x00pqr\x00xyz\x000AB
+ abc456 abc\0def\00pqr\000xyz\0000ABCDE
+ 0: abc\x00def\x00pqr\x00xyz\x000AB
+
+/abc\x0def\x00pqr\x000xyz\x0000AB/
+ abc\x0def\x00pqr\x000xyz\x0000AB
+ 0: abc\x0def\x00pqr\x000xyz\x0000AB
+ abc456 abc\x0def\x00pqr\x000xyz\x0000ABCDE
+ 0: abc\x0def\x00pqr\x000xyz\x0000AB
+
+/^[\000-\037]/
+ \0A
+ 0: \x00
+ \01B
+ 0: \x01
+ \037C
+ 0: \x1f
+
+/\0*/
+ \0\0\0\0
+ 0: \x00\x00\x00\x00
+ 1: \x00\x00\x00
+ 2: \x00\x00
+ 3: \x00
+ 4:
+
+/A\x0{2,3}Z/
+ The A\x0\x0Z
+ 0: A\x00\x00Z
+ An A\0\x0\0Z
+ 0: A\x00\x00\x00Z
+ *** Failers
+No match
+ A\0Z
+No match
+ A\0\x0\0\x0Z
+No match
+
+/^\s/
+ \040abc
+ 0:
+ \x0cabc
+ 0: \x0c
+ \nabc
+ 0: \x0a
+ \rabc
+ 0: \x0d
+ \tabc
+ 0: \x09
+ *** Failers
+No match
+ abc
+No match
+
+/^a b
+ c/x
+ abc
+ 0: abc
+
+/ab{1,3}bc/
+ abbbbc
+ 0: abbbbc
+ abbbc
+ 0: abbbc
+ abbc
+ 0: abbc
+ *** Failers
+No match
+ abc
+No match
+ abbbbbc
+No match
+
+/([^.]*)\.([^:]*):[T ]+(.*)/
+ track1.title:TBlah blah blah
+ 0: track1.title:TBlah blah blah
+ 1: track1.title:TBlah blah bla
+ 2: track1.title:TBlah blah bl
+ 3: track1.title:TBlah blah b
+ 4: track1.title:TBlah blah
+ 5: track1.title:TBlah blah
+ 6: track1.title:TBlah bla
+ 7: track1.title:TBlah bl
+ 8: track1.title:TBlah b
+ 9: track1.title:TBlah
+10: track1.title:TBlah
+11: track1.title:TBla
+12: track1.title:TBl
+13: track1.title:TB
+14: track1.title:T
+
+/([^.]*)\.([^:]*):[T ]+(.*)/i
+ track1.title:TBlah blah blah
+ 0: track1.title:TBlah blah blah
+ 1: track1.title:TBlah blah bla
+ 2: track1.title:TBlah blah bl
+ 3: track1.title:TBlah blah b
+ 4: track1.title:TBlah blah
+ 5: track1.title:TBlah blah
+ 6: track1.title:TBlah bla
+ 7: track1.title:TBlah bl
+ 8: track1.title:TBlah b
+ 9: track1.title:TBlah
+10: track1.title:TBlah
+11: track1.title:TBla
+12: track1.title:TBl
+13: track1.title:TB
+14: track1.title:T
+
+/([^.]*)\.([^:]*):[t ]+(.*)/i
+ track1.title:TBlah blah blah
+ 0: track1.title:TBlah blah blah
+ 1: track1.title:TBlah blah bla
+ 2: track1.title:TBlah blah bl
+ 3: track1.title:TBlah blah b
+ 4: track1.title:TBlah blah
+ 5: track1.title:TBlah blah
+ 6: track1.title:TBlah bla
+ 7: track1.title:TBlah bl
+ 8: track1.title:TBlah b
+ 9: track1.title:TBlah
+10: track1.title:TBlah
+11: track1.title:TBla
+12: track1.title:TBl
+13: track1.title:TB
+14: track1.title:T
+
+/^[W-c]+$/
+ WXY_^abc
+ 0: WXY_^abc
+ *** Failers
+No match
+ wxy
+No match
+
+/^[W-c]+$/i
+ WXY_^abc
+ 0: WXY_^abc
+ wxy_^ABC
+ 0: wxy_^ABC
+
+/^[\x3f-\x5F]+$/i
+ WXY_^abc
+ 0: WXY_^abc
+ wxy_^ABC
+ 0: wxy_^ABC
+
+/^abc$/m
+ abc
+ 0: abc
+ qqq\nabc
+ 0: abc
+ abc\nzzz
+ 0: abc
+ qqq\nabc\nzzz
+ 0: abc
+
+/^abc$/
+ abc
+ 0: abc
+ *** Failers
+No match
+ qqq\nabc
+No match
+ abc\nzzz
+No match
+ qqq\nabc\nzzz
+No match
+
+/\Aabc\Z/m
+ abc
+ 0: abc
+ abc\n
+ 0: abc
+ *** Failers
+No match
+ qqq\nabc
+No match
+ abc\nzzz
+No match
+ qqq\nabc\nzzz
+No match
+
+/\A(.)*\Z/s
+ abc\ndef
+ 0: abc\x0adef
+
+/\A(.)*\Z/m
+ *** Failers
+ 0: *** Failers
+ abc\ndef
+No match
+
+/(?:b)|(?::+)/
+ b::c
+ 0: b
+ c::b
+ 0: ::
+ 1: :
+
+/[-az]+/
+ az-
+ 0: az-
+ 1: az
+ 2: a
+ *** Failers
+ 0: a
+ b
+No match
+
+/[az-]+/
+ za-
+ 0: za-
+ 1: za
+ 2: z
+ *** Failers
+ 0: a
+ b
+No match
+
+/[a\-z]+/
+ a-z
+ 0: a-z
+ 1: a-
+ 2: a
+ *** Failers
+ 0: a
+ b
+No match
+
+/[a-z]+/
+ abcdxyz
+ 0: abcdxyz
+ 1: abcdxy
+ 2: abcdx
+ 3: abcd
+ 4: abc
+ 5: ab
+ 6: a
+
+/[\d-]+/
+ 12-34
+ 0: 12-34
+ 1: 12-3
+ 2: 12-
+ 3: 12
+ 4: 1
+ *** Failers
+No match
+ aaa
+No match
+
+/[\d-z]+/
+ 12-34z
+ 0: 12-34z
+ 1: 12-34
+ 2: 12-3
+ 3: 12-
+ 4: 12
+ 5: 1
+ *** Failers
+No match
+ aaa
+No match
+
+/\x5c/
+ \\
+ 0: \
+
+/\x20Z/
+ the Zoo
+ 0: Z
+ *** Failers
+No match
+ Zulu
+No match
+
+/ab{3cd/
+ ab{3cd
+ 0: ab{3cd
+
+/ab{3,cd/
+ ab{3,cd
+ 0: ab{3,cd
+
+/ab{3,4a}cd/
+ ab{3,4a}cd
+ 0: ab{3,4a}cd
+
+/{4,5a}bc/
+ {4,5a}bc
+ 0: {4,5a}bc
+
+/^a.b/<lf>
+ a\rb
+ 0: a\x0db
+ *** Failers
+No match
+ a\nb
+No match
+
+/abc$/
+ abc
+ 0: abc
+ abc\n
+ 0: abc
+ *** Failers
+No match
+ abc\ndef
+No match
+
+/(abc)\123/
+ abc\x53
+ 0: abcS
+
+/(abc)\223/
+ abc\x93
+ 0: abc\x93
+
+/(abc)\323/
+ abc\xd3
+ 0: abc\xd3
+
+/(abc)\100/
+ abc\x40
+ 0: abc@
+ abc\100
+ 0: abc@
+
+/(abc)\1000/
+ abc\x400
+ 0: abc@0
+ abc\x40\x30
+ 0: abc@0
+ abc\1000
+ 0: abc@0
+ abc\100\x30
+ 0: abc@0
+ abc\100\060
+ 0: abc@0
+ abc\100\60
+ 0: abc@0
+
+/abc\81/
+ abc\081
+ 0: abc\x0081
+ abc\0\x38\x31
+ 0: abc\x0081
+
+/abc\91/
+ abc\091
+ 0: abc\x0091
+ abc\0\x39\x31
+ 0: abc\x0091
+
+/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\12\123/
+ abcdefghijk\12S
+ 0: abcdefghijk\x0aS
+
+/ab\idef/
+ abidef
+ 0: abidef
+
+/a{0}bc/
+ bc
+ 0: bc
+
+/(a|(bc)){0,0}?xyz/
+ xyz
+ 0: xyz
+
+/abc[\10]de/
+ abc\010de
+ 0: abc\x08de
+
+/abc[\1]de/
+ abc\1de
+ 0: abc\x01de
+
+/(abc)[\1]de/
+ abc\1de
+ 0: abc\x01de
+
+/(?s)a.b/
+ a\nb
+ 0: a\x0ab
+
+/^([^a])([^\b])([^c]*)([^d]{3,4})/
+ baNOTccccd
+ 0: baNOTcccc
+ 1: baNOTccc
+ 2: baNOTcc
+ 3: baNOTc
+ 4: baNOT
+ baNOTcccd
+ 0: baNOTccc
+ 1: baNOTcc
+ 2: baNOTc
+ 3: baNOT
+ baNOTccd
+ 0: baNOTcc
+ 1: baNOTc
+ 2: baNOT
+ bacccd
+ 0: baccc
+ *** Failers
+ 0: *** Failers
+ 1: *** Failer
+ 2: *** Faile
+ 3: *** Fail
+ 4: *** Fai
+ 5: *** Fa
+ 6: *** F
+ anything
+No match
+ b\bc
+No match
+ baccd
+No match
+
+/[^a]/
+ Abc
+ 0: A
+
+/[^a]/i
+ Abc
+ 0: b
+
+/[^a]+/
+ AAAaAbc
+ 0: AAA
+ 1: AA
+ 2: A
+
+/[^a]+/i
+ AAAaAbc
+ 0: bc
+ 1: b
+
+/[^a]+/
+ bbb\nccc
+ 0: bbb\x0accc
+ 1: bbb\x0acc
+ 2: bbb\x0ac
+ 3: bbb\x0a
+ 4: bbb
+ 5: bb
+ 6: b
+
+/[^k]$/
+ abc
+ 0: c
+ *** Failers
+ 0: s
+ abk
+No match
+
+/[^k]{2,3}$/
+ abc
+ 0: abc
+ kbc
+ 0: bc
+ kabc
+ 0: abc
+ *** Failers
+ 0: ers
+ abk
+No match
+ akb
+No match
+ akk
+No match
+
+/^\d{8,}\@.+[^k]$/
+ 12345678\@a.b.c.d
+ 123456789\@x.y.z
+ *** Failers
+No match
+ 12345678\@x.y.uk
+No match
+ 1234567\@a.b.c.d
+No match
+
+/[^a]/
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: A
+
+/[^a]/i
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: b
+
+/[^az]/
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: A
+
+/[^az]/i
+ aaaabcd
+ 0: b
+ aaAabcd
+ 0: b
+
+/\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103\104\105\106\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377/
+ \000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\047\050\051\052\053\054\055\056\057\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103\104\105\106\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377
+ 0: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff
+
+/P[^*]TAIRE[^*]{1,6}?LL/
+ xxxxxxxxxxxPSTAIREISLLxxxxxxxxx
+ 0: PSTAIREISLL
+
+/P[^*]TAIRE[^*]{1,}?LL/
+ xxxxxxxxxxxPSTAIREISLLxxxxxxxxx
+ 0: PSTAIREISLL
+
+/(\.\d\d[1-9]?)\d+/
+ 1.230003938
+ 0: .230003938
+ 1: .23000393
+ 2: .2300039
+ 3: .230003
+ 4: .23000
+ 5: .2300
+ 6: .230
+ 1.875000282
+ 0: .875000282
+ 1: .87500028
+ 2: .8750002
+ 3: .875000
+ 4: .87500
+ 5: .8750
+ 6: .875
+ 1.235
+ 0: .235
+
+/(\.\d\d((?=0)|\d(?=\d)))/
+ 1.230003938
+ 0: .230
+ 1: .23
+ 1.875000282
+ 0: .875
+ *** Failers
+No match
+ 1.235
+No match
+
+/a(?)b/
+ ab
+ 0: ab
+
+/\b(foo)\s+(\w+)/i
+ Food is on the foo table
+ 0: foo table
+ 1: foo tabl
+ 2: foo tab
+ 3: foo ta
+ 4: foo t
+
+/foo(.*)bar/
+ The food is under the bar in the barn.
+ 0: food is under the bar in the bar
+ 1: food is under the bar
+
+/foo(.*?)bar/
+ The food is under the bar in the barn.
+ 0: food is under the bar in the bar
+ 1: food is under the bar
+
+/(.*)(\d*)/
+ I have 2 numbers: 53147
+Matched, but too many subsidiary matches
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: I have 2 numbers: 531
+ 3: I have 2 numbers: 53
+ 4: I have 2 numbers: 5
+ 5: I have 2 numbers:
+ 6: I have 2 numbers:
+ 7: I have 2 numbers
+ 8: I have 2 number
+ 9: I have 2 numbe
+10: I have 2 numb
+11: I have 2 num
+12: I have 2 nu
+13: I have 2 n
+14: I have 2
+15: I have 2
+16: I have
+17: I have
+18: I hav
+19: I ha
+20: I h
+21: I
+
+/(.*)(\d+)/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: I have 2 numbers: 531
+ 3: I have 2 numbers: 53
+ 4: I have 2 numbers: 5
+ 5: I have 2
+
+/(.*?)(\d*)/
+ I have 2 numbers: 53147
+Matched, but too many subsidiary matches
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: I have 2 numbers: 531
+ 3: I have 2 numbers: 53
+ 4: I have 2 numbers: 5
+ 5: I have 2 numbers:
+ 6: I have 2 numbers:
+ 7: I have 2 numbers
+ 8: I have 2 number
+ 9: I have 2 numbe
+10: I have 2 numb
+11: I have 2 num
+12: I have 2 nu
+13: I have 2 n
+14: I have 2
+15: I have 2
+16: I have
+17: I have
+18: I hav
+19: I ha
+20: I h
+21: I
+
+/(.*?)(\d+)/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+ 1: I have 2 numbers: 5314
+ 2: I have 2 numbers: 531
+ 3: I have 2 numbers: 53
+ 4: I have 2 numbers: 5
+ 5: I have 2
+
+/(.*)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+
+/(.*?)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+
+/(.*)\b(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+
+/(.*\D)(\d+)$/
+ I have 2 numbers: 53147
+ 0: I have 2 numbers: 53147
+
+/^\D*(?!123)/
+ ABC123
+ 0: AB
+ 1: A
+ 2:
+
+/^(\D*)(?=\d)(?!123)/
+ ABC445
+ 0: ABC
+ *** Failers
+No match
+ ABC123
+No match
+
+/^[W-]46]/
+ W46]789
+ 0: W46]
+ -46]789
+ 0: -46]
+ *** Failers
+No match
+ Wall
+No match
+ Zebra
+No match
+ 42
+No match
+ [abcd]
+No match
+ ]abcd[
+No match
+
+/^[W-\]46]/
+ W46]789
+ 0: W
+ Wall
+ 0: W
+ Zebra
+ 0: Z
+ Xylophone
+ 0: X
+ 42
+ 0: 4
+ [abcd]
+ 0: [
+ ]abcd[
+ 0: ]
+ \\backslash
+ 0: \
+ *** Failers
+No match
+ -46]789
+No match
+ well
+No match
+
+/\d\d\/\d\d\/\d\d\d\d/
+ 01/01/2000
+ 0: 01/01/2000
+
+/word (?:[a-zA-Z0-9]+ ){0,10}otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ word cat dog elephant mussel cow horse canary baboon snake shark
+No match
+
+/word (?:[a-zA-Z0-9]+ ){0,300}otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope
+No match
+
+/^(a){0,0}/
+ bcd
+ 0:
+ abc
+ 0:
+ aab
+ 0:
+
+/^(a){0,1}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1:
+ aab
+ 0: a
+ 1:
+
+/^(a){0,2}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1:
+ aab
+ 0: aa
+ 1: a
+ 2:
+
+/^(a){0,3}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1:
+ aab
+ 0: aa
+ 1: a
+ 2:
+ aaa
+ 0: aaa
+ 1: aa
+ 2: a
+ 3:
+
+/^(a){0,}/
+ bcd
+ 0:
+ abc
+ 0: a
+ 1:
+ aab
+ 0: aa
+ 1: a
+ 2:
+ aaa
+ 0: aaa
+ 1: aa
+ 2: a
+ 3:
+ aaaaaaaa
+ 0: aaaaaaaa
+ 1: aaaaaaa
+ 2: aaaaaa
+ 3: aaaaa
+ 4: aaaa
+ 5: aaa
+ 6: aa
+ 7: a
+ 8:
+
+/^(a){1,1}/
+ bcd
+No match
+ abc
+ 0: a
+ aab
+ 0: a
+
+/^(a){1,2}/
+ bcd
+No match
+ abc
+ 0: a
+ aab
+ 0: aa
+ 1: a
+
+/^(a){1,3}/
+ bcd
+No match
+ abc
+ 0: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: aa
+ 2: a
+
+/^(a){1,}/
+ bcd
+No match
+ abc
+ 0: a
+ aab
+ 0: aa
+ 1: a
+ aaa
+ 0: aaa
+ 1: aa
+ 2: a
+ aaaaaaaa
+ 0: aaaaaaaa
+ 1: aaaaaaa
+ 2: aaaaaa
+ 3: aaaaa
+ 4: aaaa
+ 5: aaa
+ 6: aa
+ 7: a
+
+/.*\.gif/
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.{0,}\.gif/
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.*\.gif/m
+ borfle\nbib.gif\nno
+ 0: bib.gif
+
+/.*\.gif/s
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif
+
+/.*\.gif/ms
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif
+
+/.*$/
+ borfle\nbib.gif\nno
+ 0: no
+
+/.*$/m
+ borfle\nbib.gif\nno
+ 0: borfle
+
+/.*$/s
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif\x0ano
+
+/.*$/ms
+ borfle\nbib.gif\nno
+ 0: borfle\x0abib.gif\x0ano
+ 1: borfle\x0abib.gif
+ 2: borfle
+
+/.*$/
+ borfle\nbib.gif\nno\n
+ 0: no
+
+/.*$/m
+ borfle\nbib.gif\nno\n
+ 0: borfle
+
+/.*$/s
+ borfle\nbib.gif\nno\n
+ 0: borfle\x0abib.gif\x0ano\x0a
+ 1: borfle\x0abib.gif\x0ano
+
+/.*$/ms
+ borfle\nbib.gif\nno\n
+ 0: borfle\x0abib.gif\x0ano\x0a
+ 1: borfle\x0abib.gif\x0ano
+ 2: borfle\x0abib.gif
+ 3: borfle
+
+/(.*X|^B)/
+ abcde\n1234Xyz
+ 0: 1234X
+ BarFoo
+ 0: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/(.*X|^B)/m
+ abcde\n1234Xyz
+ 0: 1234X
+ BarFoo
+ 0: B
+ abcde\nBar
+ 0: B
+
+/(.*X|^B)/s
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ BarFoo
+ 0: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/(.*X|^B)/ms
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ BarFoo
+ 0: B
+ abcde\nBar
+ 0: B
+
+/(?s)(.*X|^B)/
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ BarFoo
+ 0: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/(?s:.*X|^B)/
+ abcde\n1234Xyz
+ 0: abcde\x0a1234X
+ BarFoo
+ 0: B
+ *** Failers
+No match
+ abcde\nBar
+No match
+
+/^.*B/
+ **** Failers
+No match
+ abc\nB
+No match
+
+/(?s)^.*B/
+ abc\nB
+ 0: abc\x0aB
+
+/(?m)^.*B/
+ abc\nB
+ 0: B
+
+/(?ms)^.*B/
+ abc\nB
+ 0: abc\x0aB
+
+/(?ms)^B/
+ abc\nB
+ 0: B
+
+/(?s)B$/
+ B\n
+ 0: B
+
+/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/
+ 123456654321
+ 0: 123456654321
+
+/^\d\d\d\d\d\d\d\d\d\d\d\d/
+ 123456654321
+ 0: 123456654321
+
+/^[\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d][\d]/
+ 123456654321
+ 0: 123456654321
+
+/^[abc]{12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+
+/^[a-c]{12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+
+/^(a|b|c){12}/
+ abcabcabcabc
+ 0: abcabcabcabc
+
+/^[abcdefghijklmnopqrstuvwxy0123456789]/
+ n
+ 0: n
+ *** Failers
+No match
+ z
+No match
+
+/abcde{0,0}/
+ abcd
+ 0: abcd
+ *** Failers
+No match
+ abce
+No match
+
+/ab[cd]{0,0}e/
+ abe
+ 0: abe
+ *** Failers
+No match
+ abcde
+No match
+
+/ab(c){0,0}d/
+ abd
+ 0: abd
+ *** Failers
+No match
+ abcd
+No match
+
+/a(b*)/
+ a
+ 0: a
+ ab
+ 0: ab
+ 1: a
+ abbbb
+ 0: abbbb
+ 1: abbb
+ 2: abb
+ 3: ab
+ 4: a
+ *** Failers
+ 0: a
+ bbbbb
+No match
+
+/ab\d{0}e/
+ abe
+ 0: abe
+ *** Failers
+No match
+ ab1e
+No match
+
+/"([^\\"]+|\\.)*"/
+ the \"quick\" brown fox
+ 0: "quick"
+ \"the \\\"quick\\\" brown fox\"
+ 0: "the \"quick\" brown fox"
+
+/.*?/g+
+ abc
+ 0: abc
+ 0+
+ 1: ab
+ 2: a
+ 3:
+ 0:
+ 0+
+
+/\b/g+
+ abc
+ 0:
+ 0+ abc
+ 0:
+ 0+
+
+/\b/+g
+ abc
+ 0:
+ 0+ abc
+ 0:
+ 0+
+
+//g
+ abc
+ 0:
+ 0:
+ 0:
+ 0:
+
+/<tr([\w\W\s\d][^<>]{0,})><TD([\w\W\s\d][^<>]{0,})>([\d]{0,}\.)(.*)((<BR>([\w\W\s\d][^<>]{0,})|[\s]{0,}))<\/a><\/TD><TD([\w\W\s\d][^<>]{0,})>([\w\W\s\d][^<>]{0,})<\/TD><TD([\w\W\s\d][^<>]{0,})>([\w\W\s\d][^<>]{0,})<\/TD><\/TR>/is
+ <TR BGCOLOR='#DBE9E9'><TD align=left valign=top>43.<a href='joblist.cfm?JobID=94 6735&Keyword='>Word Processor<BR>(N-1286)</a></TD><TD align=left valign=top>Lega lstaff.com</TD><TD align=left valign=top>CA - Statewide</TD></TR>
+ 0: <TR BGCOLOR='#DBE9E9'><TD align=left valign=top>43.<a href='joblist.cfm?JobID=94 6735&Keyword='>Word Processor<BR>(N-1286)</a></TD><TD align=left valign=top>Lega lstaff.com</TD><TD align=left valign=top>CA - Statewide</TD></TR>
+
+/a[^a]b/
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/a.b/
+ acb
+ 0: acb
+ *** Failers
+No match
+ a\nb
+No match
+
+/a[^a]b/s
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/a.b/s
+ acb
+ 0: acb
+ a\nb
+ 0: a\x0ab
+
+/^(b+?|a){1,2}?c/
+ bac
+ 0: bac
+ bbac
+ 0: bbac
+ bbbac
+ 0: bbbac
+ bbbbac
+ 0: bbbbac
+ bbbbbac
+ 0: bbbbbac
+
+/^(b+|a){1,2}?c/
+ bac
+ 0: bac
+ bbac
+ 0: bbac
+ bbbac
+ 0: bbbac
+ bbbbac
+ 0: bbbbac
+ bbbbbac
+ 0: bbbbbac
+
+/(?!\A)x/m
+ x\nb\n
+No match
+ a\bx\n
+ 0: x
+
+/\x0{ab}/
+ \0{ab}
+ 0: \x00{ab}
+
+/(A|B)*?CD/
+ CD
+ 0: CD
+
+/(A|B)*CD/
+ CD
+ 0: CD
+
+/(?<!bar)foo/
+ foo
+ 0: foo
+ catfood
+ 0: foo
+ arfootle
+ 0: foo
+ rfoosh
+ 0: foo
+ *** Failers
+No match
+ barfoo
+No match
+ towbarfoo
+No match
+
+/\w{3}(?<!bar)foo/
+ catfood
+ 0: catfoo
+ *** Failers
+No match
+ foo
+No match
+ barfoo
+No match
+ towbarfoo
+No match
+
+/(?<=(foo)a)bar/
+ fooabar
+ 0: bar
+ *** Failers
+No match
+ bar
+No match
+ foobbar
+No match
+
+/\Aabc\z/m
+ abc
+ 0: abc
+ *** Failers
+No match
+ abc\n
+No match
+ qqq\nabc
+No match
+ abc\nzzz
+No match
+ qqq\nabc\nzzz
+No match
+
+"(?>.*/)foo"
+ /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/it/you/see/
+No match
+
+"(?>.*/)foo"
+ /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo
+ 0: /this/is/a/very/long/line/in/deed/with/very/many/slashes/in/and/foo
+
+/(?>(\.\d\d[1-9]?))\d+/
+ 1.230003938
+ 0: .230003938
+ 1: .23000393
+ 2: .2300039
+ 3: .230003
+ 4: .23000
+ 5: .2300
+ 6: .230
+ 1.875000282
+ 0: .875000282
+ 1: .87500028
+ 2: .8750002
+ 3: .875000
+ 4: .87500
+ 5: .8750
+ *** Failers
+No match
+ 1.235
+No match
+
+/^((?>\w+)|(?>\s+))*$/
+ now is the time for all good men to come to the aid of the party
+ 0: now is the time for all good men to come to the aid of the party
+ *** Failers
+No match
+ this is not a line with only words and spaces!
+No match
+
+/(\d+)(\w)/
+ 12345a
+ 0: 12345a
+ 1: 12345
+ 2: 1234
+ 3: 123
+ 4: 12
+ 12345+
+ 0: 12345
+ 1: 1234
+ 2: 123
+ 3: 12
+
+/((?>\d+))(\w)/
+ 12345a
+ 0: 12345a
+ *** Failers
+No match
+ 12345+
+No match
+
+/(?>a+)b/
+ aaab
+ 0: aaab
+
+/((?>a+)b)/
+ aaab
+ 0: aaab
+
+/(?>(a+))b/
+ aaab
+ 0: aaab
+
+/(?>b)+/
+ aaabbbccc
+ 0: bbb
+ 1: bb
+ 2: b
+
+/(?>a+|b+|c+)*c/
+ aaabbbbccccd
+ 0: aaabbbbcccc
+ 1: aaabbbbc
+
+/(a+|b+|c+)*c/
+ aaabbbbccccd
+ 0: aaabbbbcccc
+ 1: aaabbbbccc
+ 2: aaabbbbcc
+ 3: aaabbbbc
+
+/((?>[^()]+)|\([^()]*\))+/
+ ((abc(ade)ufh()()x
+ 0: abc(ade)ufh()()x
+ 1: abc(ade)ufh()()
+ 2: abc(ade)ufh()
+ 3: abc(ade)ufh
+ 4: abc(ade)
+ 5: abc
+
+/\(((?>[^()]+)|\([^()]+\))+\)/
+ (abc)
+ 0: (abc)
+ (abc(def)xyz)
+ 0: (abc(def)xyz)
+ *** Failers
+No match
+ ((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/a(?-i)b/i
+ ab
+ 0: ab
+ Ab
+ 0: Ab
+ *** Failers
+No match
+ aB
+No match
+ AB
+No match
+
+/(a (?x)b c)d e/
+ a bcd e
+ 0: a bcd e
+ *** Failers
+No match
+ a b cd e
+No match
+ abcd e
+No match
+ a bcde
+No match
+
+/(a b(?x)c d (?-x)e f)/
+ a bcde f
+ 0: a bcde f
+ *** Failers
+No match
+ abcdef
+No match
+
+/(a(?i)b)c/
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+ *** Failers
+No match
+ abC
+No match
+ aBC
+No match
+ Abc
+No match
+ ABc
+No match
+ ABC
+No match
+ AbC
+No match
+
+/a(?i:b)c/
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+ *** Failers
+No match
+ ABC
+No match
+ abC
+No match
+ aBC
+No match
+
+/a(?i:b)*c/
+ aBc
+ 0: aBc
+ aBBc
+ 0: aBBc
+ *** Failers
+No match
+ aBC
+No match
+ aBBC
+No match
+
+/a(?=b(?i)c)\w\wd/
+ abcd
+ 0: abcd
+ abCd
+ 0: abCd
+ *** Failers
+No match
+ aBCd
+No match
+ abcD
+No match
+
+/(?s-i:more.*than).*million/i
+ more than million
+ 0: more than million
+ more than MILLION
+ 0: more than MILLION
+ more \n than Million
+ 0: more \x0a than Million
+ *** Failers
+No match
+ MORE THAN MILLION
+No match
+ more \n than \n million
+No match
+
+/(?:(?s-i)more.*than).*million/i
+ more than million
+ 0: more than million
+ more than MILLION
+ 0: more than MILLION
+ more \n than Million
+ 0: more \x0a than Million
+ *** Failers
+No match
+ MORE THAN MILLION
+No match
+ more \n than \n million
+No match
+
+/(?>a(?i)b+)+c/
+ abc
+ 0: abc
+ aBbc
+ 0: aBbc
+ aBBc
+ 0: aBBc
+ *** Failers
+No match
+ Abc
+No match
+ abAb
+No match
+ abbC
+No match
+
+/(?=a(?i)b)\w\wc/
+ abc
+ 0: abc
+ aBc
+ 0: aBc
+ *** Failers
+No match
+ Ab
+No match
+ abC
+No match
+ aBC
+No match
+
+/(?<=a(?i)b)(\w\w)c/
+ abxxc
+ 0: xxc
+ aBxxc
+ 0: xxc
+ *** Failers
+No match
+ Abxxc
+No match
+ ABxxc
+No match
+ abxxC
+No match
+
+/^(?(?=abc)\w{3}:|\d\d)$/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+ *** Failers
+No match
+ 123
+No match
+ xyz
+No match
+
+/^(?(?!abc)\d\d|\w{3}:)$/
+ abc:
+ 0: abc:
+ 12
+ 0: 12
+ *** Failers
+No match
+ 123
+No match
+ xyz
+No match
+
+/(?(?<=foo)bar|cat)/
+ foobar
+ 0: bar
+ cat
+ 0: cat
+ fcat
+ 0: cat
+ focat
+ 0: cat
+ *** Failers
+No match
+ foocat
+No match
+
+/(?(?<!foo)cat|bar)/
+ foobar
+ 0: bar
+ cat
+ 0: cat
+ fcat
+ 0: cat
+ focat
+ 0: cat
+ *** Failers
+No match
+ foocat
+No match
+
+/(?>a*)*/
+ a
+ 0: a
+ 1:
+ aa
+ 0: aa
+ 1:
+ aaaa
+ 0: aaaa
+ 1:
+
+/(abc|)+/
+ abc
+ 0: abc
+ 1:
+ abcabc
+ 0: abcabc
+ 1: abc
+ 2:
+ abcabcabc
+ 0: abcabcabc
+ 1: abcabc
+ 2: abc
+ 3:
+ xyz
+ 0:
+
+/([a]*)*/
+ a
+ 0: a
+ 1:
+ aaaaa
+ 0: aaaaa
+ 1: aaaa
+ 2: aaa
+ 3: aa
+ 4: a
+ 5:
+
+/([ab]*)*/
+ a
+ 0: a
+ 1:
+ b
+ 0: b
+ 1:
+ ababab
+ 0: ababab
+ 1: ababa
+ 2: abab
+ 3: aba
+ 4: ab
+ 5: a
+ 6:
+ aaaabcde
+ 0: aaaab
+ 1: aaaa
+ 2: aaa
+ 3: aa
+ 4: a
+ 5:
+ bbbb
+ 0: bbbb
+ 1: bbb
+ 2: bb
+ 3: b
+ 4:
+
+/([^a]*)*/
+ b
+ 0: b
+ 1:
+ bbbb
+ 0: bbbb
+ 1: bbb
+ 2: bb
+ 3: b
+ 4:
+ aaa
+ 0:
+
+/([^ab]*)*/
+ cccc
+ 0: cccc
+ 1: ccc
+ 2: cc
+ 3: c
+ 4:
+ abab
+ 0:
+
+/([a]*?)*/
+ a
+ 0: a
+ 1:
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+ 4:
+
+/([ab]*?)*/
+ a
+ 0: a
+ 1:
+ b
+ 0: b
+ 1:
+ abab
+ 0: abab
+ 1: aba
+ 2: ab
+ 3: a
+ 4:
+ baba
+ 0: baba
+ 1: bab
+ 2: ba
+ 3: b
+ 4:
+
+/([^a]*?)*/
+ b
+ 0: b
+ 1:
+ bbbb
+ 0: bbbb
+ 1: bbb
+ 2: bb
+ 3: b
+ 4:
+ aaa
+ 0:
+
+/([^ab]*?)*/
+ c
+ 0: c
+ 1:
+ cccc
+ 0: cccc
+ 1: ccc
+ 2: cc
+ 3: c
+ 4:
+ baba
+ 0:
+
+/(?>a*)*/
+ a
+ 0: a
+ 1:
+ aaabcde
+ 0: aaa
+ 1:
+
+/((?>a*))*/
+ aaaaa
+ 0: aaaaa
+ 1:
+ aabbaa
+ 0: aa
+ 1:
+
+/((?>a*?))*/
+ aaaaa
+ 0: aaaaa
+ 1:
+ aabbaa
+ 0: aa
+ 1:
+
+/(?(?=[^a-z]+[a-z]) \d{2}-[a-z]{3}-\d{2} | \d{2}-\d{2}-\d{2} ) /x
+ 12-sep-98
+ 0: 12-sep-98
+ 12-09-98
+ 0: 12-09-98
+ *** Failers
+No match
+ sep-12-98
+No match
+
+/(?i:saturday|sunday)/
+ saturday
+ 0: saturday
+ sunday
+ 0: sunday
+ Saturday
+ 0: Saturday
+ Sunday
+ 0: Sunday
+ SATURDAY
+ 0: SATURDAY
+ SUNDAY
+ 0: SUNDAY
+ SunDay
+ 0: SunDay
+
+/(a(?i)bc|BB)x/
+ abcx
+ 0: abcx
+ aBCx
+ 0: aBCx
+ bbx
+ 0: bbx
+ BBx
+ 0: BBx
+ *** Failers
+No match
+ abcX
+No match
+ aBCX
+No match
+ bbX
+No match
+ BBX
+No match
+
+/^([ab](?i)[cd]|[ef])/
+ ac
+ 0: ac
+ aC
+ 0: aC
+ bD
+ 0: bD
+ elephant
+ 0: e
+ Europe
+ 0: E
+ frog
+ 0: f
+ France
+ 0: F
+ *** Failers
+No match
+ Africa
+No match
+
+/^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)/
+ ab
+ 0: ab
+ aBd
+ 0: aBd
+ xy
+ 0: xy
+ xY
+ 0: xY
+ zebra
+ 0: z
+ Zambesi
+ 0: Z
+ *** Failers
+No match
+ aCD
+No match
+ XY
+No match
+
+/(?<=foo\n)^bar/m
+ foo\nbar
+ 0: bar
+ *** Failers
+No match
+ bar
+No match
+ baz\nbar
+No match
+
+/(?<=(?<!foo)bar)baz/
+ barbaz
+ 0: baz
+ barbarbaz
+ 0: baz
+ koobarbaz
+ 0: baz
+ *** Failers
+No match
+ baz
+No match
+ foobarbaz
+No match
+
+/The following tests are taken from the Perl 5.005 test suite; some of them/
+/are compatible with 5.004, but I'd rather not have to sort them out./
+No match
+
+/abc/
+ abc
+ 0: abc
+ xabcy
+ 0: abc
+ ababc
+ 0: abc
+ *** Failers
+No match
+ xbc
+No match
+ axc
+No match
+ abx
+No match
+
+/ab*c/
+ abc
+ 0: abc
+
+/ab*bc/
+ abc
+ 0: abc
+ abbc
+ 0: abbc
+ abbbbc
+ 0: abbbbc
+
+/.{1}/
+ abbbbc
+ 0: a
+
+/.{3,4}/
+ abbbbc
+ 0: abbb
+ 1: abb
+
+/ab{0,}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab+bc/
+ abbc
+ 0: abbc
+ *** Failers
+No match
+ abc
+No match
+ abq
+No match
+
+/ab{1,}bc/
+
+/ab+bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{1,}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{1,3}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{3,4}bc/
+ abbbbc
+ 0: abbbbc
+
+/ab{4,5}bc/
+ *** Failers
+No match
+ abq
+No match
+ abbbbc
+No match
+
+/ab?bc/
+ abbc
+ 0: abbc
+ abc
+ 0: abc
+
+/ab{0,1}bc/
+ abc
+ 0: abc
+
+/ab?bc/
+
+/ab?c/
+ abc
+ 0: abc
+
+/ab{0,1}c/
+ abc
+ 0: abc
+
+/^abc$/
+ abc
+ 0: abc
+ *** Failers
+No match
+ abbbbc
+No match
+ abcc
+No match
+
+/^abc/
+ abcc
+ 0: abc
+
+/^abc$/
+
+/abc$/
+ aabc
+ 0: abc
+ *** Failers
+No match
+ aabc
+ 0: abc
+ aabcd
+No match
+
+/^/
+ abc
+ 0:
+
+/$/
+ abc
+ 0:
+
+/a.c/
+ abc
+ 0: abc
+ axc
+ 0: axc
+
+/a.*c/
+ axyzc
+ 0: axyzc
+
+/a[bc]d/
+ abd
+ 0: abd
+ *** Failers
+No match
+ axyzd
+No match
+ abc
+No match
+
+/a[b-d]e/
+ ace
+ 0: ace
+
+/a[b-d]/
+ aac
+ 0: ac
+
+/a[-b]/
+ a-
+ 0: a-
+
+/a[b-]/
+ a-
+ 0: a-
+
+/a]/
+ a]
+ 0: a]
+
+/a[]]b/
+ a]b
+ 0: a]b
+
+/a[^bc]d/
+ aed
+ 0: aed
+ *** Failers
+No match
+ abd
+No match
+ abd
+No match
+
+/a[^-b]c/
+ adc
+ 0: adc
+
+/a[^]b]c/
+ adc
+ 0: adc
+ *** Failers
+No match
+ a-c
+ 0: a-c
+ a]c
+No match
+
+/\ba\b/
+ a-
+ 0: a
+ -a
+ 0: a
+ -a-
+ 0: a
+
+/\by\b/
+ *** Failers
+No match
+ xy
+No match
+ yz
+No match
+ xyz
+No match
+
+/\Ba\B/
+ *** Failers
+ 0: a
+ a-
+No match
+ -a
+No match
+ -a-
+No match
+
+/\By\b/
+ xy
+ 0: y
+
+/\by\B/
+ yz
+ 0: y
+
+/\By\B/
+ xyz
+ 0: y
+
+/\w/
+ a
+ 0: a
+
+/\W/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ a
+No match
+
+/a\sb/
+ a b
+ 0: a b
+
+/a\Sb/
+ a-b
+ 0: a-b
+ *** Failers
+No match
+ a-b
+ 0: a-b
+ a b
+No match
+
+/\d/
+ 1
+ 0: 1
+
+/\D/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ 1
+No match
+
+/[\w]/
+ a
+ 0: a
+
+/[\W]/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ a
+No match
+
+/a[\s]b/
+ a b
+ 0: a b
+
+/a[\S]b/
+ a-b
+ 0: a-b
+ *** Failers
+No match
+ a-b
+ 0: a-b
+ a b
+No match
+
+/[\d]/
+ 1
+ 0: 1
+
+/[\D]/
+ -
+ 0: -
+ *** Failers
+ 0: *
+ -
+ 0: -
+ 1
+No match
+
+/ab|cd/
+ abc
+ 0: ab
+ abcd
+ 0: ab
+
+/()ef/
+ def
+ 0: ef
+
+/$b/
+
+/a\(b/
+ a(b
+ 0: a(b
+
+/a\(*b/
+ ab
+ 0: ab
+ a((b
+ 0: a((b
+
+/a\\b/
+ a\b
+No match
+
+/((a))/
+ abc
+ 0: a
+
+/(a)b(c)/
+ abc
+ 0: abc
+
+/a+b+c/
+ aabbabc
+ 0: abc
+
+/a{1,}b{1,}c/
+ aabbabc
+ 0: abc
+
+/a.+?c/
+ abcabc
+ 0: abcabc
+ 1: abc
+
+/(a+|b)*/
+ ab
+ 0: ab
+ 1: a
+ 2:
+
+/(a+|b){0,}/
+ ab
+ 0: ab
+ 1: a
+ 2:
+
+/(a+|b)+/
+ ab
+ 0: ab
+ 1: a
+
+/(a+|b){1,}/
+ ab
+ 0: ab
+ 1: a
+
+/(a+|b)?/
+ ab
+ 0: a
+ 1:
+
+/(a+|b){0,1}/
+ ab
+ 0: a
+ 1:
+
+/[^ab]*/
+ cde
+ 0: cde
+ 1: cd
+ 2: c
+ 3:
+
+/abc/
+ *** Failers
+No match
+ b
+No match
+
+
+/a*/
+
+
+/([abc])*d/
+ abbbcd
+ 0: abbbcd
+
+/([abc])*bcd/
+ abcd
+ 0: abcd
+
+/a|b|c|d|e/
+ e
+ 0: e
+
+/(a|b|c|d|e)f/
+ ef
+ 0: ef
+
+/abcd*efg/
+ abcdefg
+ 0: abcdefg
+
+/ab*/
+ xabyabbbz
+ 0: ab
+ 1: a
+ xayabbbz
+ 0: a
+
+/(ab|cd)e/
+ abcde
+ 0: cde
+
+/[abhgefdc]ij/
+ hij
+ 0: hij
+
+/^(ab|cd)e/
+
+/(abc|)ef/
+ abcdef
+ 0: ef
+
+/(a|b)c*d/
+ abcd
+ 0: bcd
+
+/(ab|ab*)bc/
+ abc
+ 0: abc
+
+/a([bc]*)c*/
+ abc
+ 0: abc
+ 1: ab
+ 2: a
+
+/a([bc]*)(c*d)/
+ abcd
+ 0: abcd
+
+/a([bc]+)(c*d)/
+ abcd
+ 0: abcd
+
+/a([bc]*)(c+d)/
+ abcd
+ 0: abcd
+
+/a[bcd]*dcdcde/
+ adcdcde
+ 0: adcdcde
+
+/a[bcd]+dcdcde/
+ *** Failers
+No match
+ abcde
+No match
+ adcdcde
+No match
+
+/(ab|a)b*c/
+ abc
+ 0: abc
+
+/((a)(b)c)(d)/
+ abcd
+ 0: abcd
+
+/[a-zA-Z_][a-zA-Z0-9_]*/
+ alpha
+ 0: alpha
+ 1: alph
+ 2: alp
+ 3: al
+ 4: a
+
+/^a(bc+|b[eh])g|.h$/
+ abh
+ 0: bh
+
+/(bc+d$|ef*g.|h?i(j|k))/
+ effgz
+ 0: effgz
+ ij
+ 0: ij
+ reffgz
+ 0: effgz
+ *** Failers
+No match
+ effg
+No match
+ bcdd
+No match
+
+/((((((((((a))))))))))/
+ a
+ 0: a
+
+/(((((((((a)))))))))/
+ a
+ 0: a
+
+/multiple words of text/
+ *** Failers
+No match
+ aa
+No match
+ uh-uh
+No match
+
+/multiple words/
+ multiple words, yeah
+ 0: multiple words
+
+/(.*)c(.*)/
+ abcde
+ 0: abcde
+ 1: abcd
+ 2: abc
+
+/\((.*), (.*)\)/
+ (a, b)
+ 0: (a, b)
+
+/[k]/
+
+/abcd/
+ abcd
+ 0: abcd
+
+/a(bc)d/
+ abcd
+ 0: abcd
+
+/a[-]?c/
+ ac
+ 0: ac
+
+/abc/i
+ ABC
+ 0: ABC
+ XABCY
+ 0: ABC
+ ABABC
+ 0: ABC
+ *** Failers
+No match
+ aaxabxbaxbbx
+No match
+ XBC
+No match
+ AXC
+No match
+ ABX
+No match
+
+/ab*c/i
+ ABC
+ 0: ABC
+
+/ab*bc/i
+ ABC
+ 0: ABC
+ ABBC
+ 0: ABBC
+
+/ab*?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{0,}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab+?bc/i
+ ABBC
+ 0: ABBC
+
+/ab+bc/i
+ *** Failers
+No match
+ ABC
+No match
+ ABQ
+No match
+
+/ab{1,}bc/i
+
+/ab+bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{1,}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{1,3}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{3,4}?bc/i
+ ABBBBC
+ 0: ABBBBC
+
+/ab{4,5}?bc/i
+ *** Failers
+No match
+ ABQ
+No match
+ ABBBBC
+No match
+
+/ab??bc/i
+ ABBC
+ 0: ABBC
+ ABC
+ 0: ABC
+
+/ab{0,1}?bc/i
+ ABC
+ 0: ABC
+
+/ab??bc/i
+
+/ab??c/i
+ ABC
+ 0: ABC
+
+/ab{0,1}?c/i
+ ABC
+ 0: ABC
+
+/^abc$/i
+ ABC
+ 0: ABC
+ *** Failers
+No match
+ ABBBBC
+No match
+ ABCC
+No match
+
+/^abc/i
+ ABCC
+ 0: ABC
+
+/^abc$/i
+
+/abc$/i
+ AABC
+ 0: ABC
+
+/^/i
+ ABC
+ 0:
+
+/$/i
+ ABC
+ 0:
+
+/a.c/i
+ ABC
+ 0: ABC
+ AXC
+ 0: AXC
+
+/a.*?c/i
+ AXYZC
+ 0: AXYZC
+
+/a.*c/i
+ *** Failers
+No match
+ AABC
+ 0: AABC
+ AXYZD
+No match
+
+/a[bc]d/i
+ ABD
+ 0: ABD
+
+/a[b-d]e/i
+ ACE
+ 0: ACE
+ *** Failers
+No match
+ ABC
+No match
+ ABD
+No match
+
+/a[b-d]/i
+ AAC
+ 0: AC
+
+/a[-b]/i
+ A-
+ 0: A-
+
+/a[b-]/i
+ A-
+ 0: A-
+
+/a]/i
+ A]
+ 0: A]
+
+/a[]]b/i
+ A]B
+ 0: A]B
+
+/a[^bc]d/i
+ AED
+ 0: AED
+
+/a[^-b]c/i
+ ADC
+ 0: ADC
+ *** Failers
+No match
+ ABD
+No match
+ A-C
+No match
+
+/a[^]b]c/i
+ ADC
+ 0: ADC
+
+/ab|cd/i
+ ABC
+ 0: AB
+ ABCD
+ 0: AB
+
+/()ef/i
+ DEF
+ 0: EF
+
+/$b/i
+ *** Failers
+No match
+ A]C
+No match
+ B
+No match
+
+/a\(b/i
+ A(B
+ 0: A(B
+
+/a\(*b/i
+ AB
+ 0: AB
+ A((B
+ 0: A((B
+
+/a\\b/i
+ A\B
+No match
+
+/((a))/i
+ ABC
+ 0: A
+
+/(a)b(c)/i
+ ABC
+ 0: ABC
+
+/a+b+c/i
+ AABBABC
+ 0: ABC
+
+/a{1,}b{1,}c/i
+ AABBABC
+ 0: ABC
+
+/a.+?c/i
+ ABCABC
+ 0: ABCABC
+ 1: ABC
+
+/a.*?c/i
+ ABCABC
+ 0: ABCABC
+ 1: ABC
+
+/a.{0,5}?c/i
+ ABCABC
+ 0: ABCABC
+ 1: ABC
+
+/(a+|b)*/i
+ AB
+ 0: AB
+ 1: A
+ 2:
+
+/(a+|b){0,}/i
+ AB
+ 0: AB
+ 1: A
+ 2:
+
+/(a+|b)+/i
+ AB
+ 0: AB
+ 1: A
+
+/(a+|b){1,}/i
+ AB
+ 0: AB
+ 1: A
+
+/(a+|b)?/i
+ AB
+ 0: A
+ 1:
+
+/(a+|b){0,1}/i
+ AB
+ 0: A
+ 1:
+
+/(a+|b){0,1}?/i
+ AB
+ 0: A
+ 1:
+
+/[^ab]*/i
+ CDE
+ 0: CDE
+ 1: CD
+ 2: C
+ 3:
+
+/abc/i
+
+/a*/i
+
+
+/([abc])*d/i
+ ABBBCD
+ 0: ABBBCD
+
+/([abc])*bcd/i
+ ABCD
+ 0: ABCD
+
+/a|b|c|d|e/i
+ E
+ 0: E
+
+/(a|b|c|d|e)f/i
+ EF
+ 0: EF
+
+/abcd*efg/i
+ ABCDEFG
+ 0: ABCDEFG
+
+/ab*/i
+ XABYABBBZ
+ 0: AB
+ 1: A
+ XAYABBBZ
+ 0: A
+
+/(ab|cd)e/i
+ ABCDE
+ 0: CDE
+
+/[abhgefdc]ij/i
+ HIJ
+ 0: HIJ
+
+/^(ab|cd)e/i
+ ABCDE
+No match
+
+/(abc|)ef/i
+ ABCDEF
+ 0: EF
+
+/(a|b)c*d/i
+ ABCD
+ 0: BCD
+
+/(ab|ab*)bc/i
+ ABC
+ 0: ABC
+
+/a([bc]*)c*/i
+ ABC
+ 0: ABC
+ 1: AB
+ 2: A
+
+/a([bc]*)(c*d)/i
+ ABCD
+ 0: ABCD
+
+/a([bc]+)(c*d)/i
+ ABCD
+ 0: ABCD
+
+/a([bc]*)(c+d)/i
+ ABCD
+ 0: ABCD
+
+/a[bcd]*dcdcde/i
+ ADCDCDE
+ 0: ADCDCDE
+
+/a[bcd]+dcdcde/i
+
+/(ab|a)b*c/i
+ ABC
+ 0: ABC
+
+/((a)(b)c)(d)/i
+ ABCD
+ 0: ABCD
+
+/[a-zA-Z_][a-zA-Z0-9_]*/i
+ ALPHA
+ 0: ALPHA
+ 1: ALPH
+ 2: ALP
+ 3: AL
+ 4: A
+
+/^a(bc+|b[eh])g|.h$/i
+ ABH
+ 0: BH
+
+/(bc+d$|ef*g.|h?i(j|k))/i
+ EFFGZ
+ 0: EFFGZ
+ IJ
+ 0: IJ
+ REFFGZ
+ 0: EFFGZ
+ *** Failers
+No match
+ ADCDCDE
+No match
+ EFFG
+No match
+ BCDD
+No match
+
+/((((((((((a))))))))))/i
+ A
+ 0: A
+
+/(((((((((a)))))))))/i
+ A
+ 0: A
+
+/(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))/i
+ A
+ 0: A
+
+/(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))/i
+ C
+ 0: C
+
+/multiple words of text/i
+ *** Failers
+No match
+ AA
+No match
+ UH-UH
+No match
+
+/multiple words/i
+ MULTIPLE WORDS, YEAH
+ 0: MULTIPLE WORDS
+
+/(.*)c(.*)/i
+ ABCDE
+ 0: ABCDE
+ 1: ABCD
+ 2: ABC
+
+/\((.*), (.*)\)/i
+ (A, B)
+ 0: (A, B)
+
+/[k]/i
+
+/abcd/i
+ ABCD
+ 0: ABCD
+
+/a(bc)d/i
+ ABCD
+ 0: ABCD
+
+/a[-]?c/i
+ AC
+ 0: AC
+
+/a(?!b)./
+ abad
+ 0: ad
+
+/a(?=d)./
+ abad
+ 0: ad
+
+/a(?=c|d)./
+ abad
+ 0: ad
+
+/a(?:b|c|d)(.)/
+ ace
+ 0: ace
+
+/a(?:b|c|d)*(.)/
+ ace
+ 0: ace
+ 1: ac
+
+/a(?:b|c|d)+?(.)/
+ ace
+ 0: ace
+ acdbcdbe
+ 0: acdbcdbe
+ 1: acdbcdb
+ 2: acdbcd
+ 3: acdbc
+ 4: acdb
+ 5: acd
+
+/a(?:b|c|d)+(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: acdbcdb
+ 2: acdbcd
+ 3: acdbc
+ 4: acdb
+ 5: acd
+
+/a(?:b|c|d){2}(.)/
+ acdbcdbe
+ 0: acdb
+
+/a(?:b|c|d){4,5}(.)/
+ acdbcdbe
+ 0: acdbcdb
+ 1: acdbcd
+
+/a(?:b|c|d){4,5}?(.)/
+ acdbcdbe
+ 0: acdbcdb
+ 1: acdbcd
+
+/((foo)|(bar))*/
+ foobar
+ 0: foobar
+ 1: foo
+ 2:
+
+/a(?:b|c|d){6,7}(.)/
+ acdbcdbe
+ 0: acdbcdbe
+
+/a(?:b|c|d){6,7}?(.)/
+ acdbcdbe
+ 0: acdbcdbe
+
+/a(?:b|c|d){5,6}(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: acdbcdb
+
+/a(?:b|c|d){5,6}?(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: acdbcdb
+
+/a(?:b|c|d){5,7}(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: acdbcdb
+
+/a(?:b|c|d){5,7}?(.)/
+ acdbcdbe
+ 0: acdbcdbe
+ 1: acdbcdb
+
+/a(?:b|(c|e){1,2}?|d)+?(.)/
+ ace
+ 0: ace
+
+/^(.+)?B/
+ AB
+ 0: AB
+
+/^([^a-z])|(\^)$/
+ .
+ 0: .
+
+/^[<>]&/
+ <&OUT
+ 0: <&
+
+/(?:(f)(o)(o)|(b)(a)(r))*/
+ foobar
+ 0: foobar
+ 1: foo
+ 2:
+
+/(?<=a)b/
+ ab
+ 0: b
+ *** Failers
+No match
+ cb
+No match
+ b
+No match
+
+/(?<!c)b/
+ ab
+ 0: b
+ b
+ 0: b
+ b
+ 0: b
+
+/(?:..)*a/
+ aba
+ 0: aba
+ 1: a
+
+/(?:..)*?a/
+ aba
+ 0: aba
+ 1: a
+
+/^(){3,5}/
+ abc
+ 0:
+
+/^(a+)*ax/
+ aax
+ 0: aax
+
+/^((a|b)+)*ax/
+ aax
+ 0: aax
+
+/^((a|bc)+)*ax/
+ aax
+ 0: aax
+
+/(a|x)*ab/
+ cab
+ 0: ab
+
+/(a)*ab/
+ cab
+ 0: ab
+
+/(?:(?i)a)b/
+ ab
+ 0: ab
+
+/((?i)a)b/
+ ab
+ 0: ab
+
+/(?:(?i)a)b/
+ Ab
+ 0: Ab
+
+/((?i)a)b/
+ Ab
+ 0: Ab
+
+/(?:(?i)a)b/
+ *** Failers
+No match
+ cb
+No match
+ aB
+No match
+
+/((?i)a)b/
+
+/(?i:a)b/
+ ab
+ 0: ab
+
+/((?i:a))b/
+ ab
+ 0: ab
+
+/(?i:a)b/
+ Ab
+ 0: Ab
+
+/((?i:a))b/
+ Ab
+ 0: Ab
+
+/(?i:a)b/
+ *** Failers
+No match
+ aB
+No match
+ aB
+No match
+
+/((?i:a))b/
+
+/(?:(?-i)a)b/i
+ ab
+ 0: ab
+
+/((?-i)a)b/i
+ ab
+ 0: ab
+
+/(?:(?-i)a)b/i
+ aB
+ 0: aB
+
+/((?-i)a)b/i
+ aB
+ 0: aB
+
+/(?:(?-i)a)b/i
+ *** Failers
+No match
+ aB
+ 0: aB
+ Ab
+No match
+
+/((?-i)a)b/i
+
+/(?:(?-i)a)b/i
+ aB
+ 0: aB
+
+/((?-i)a)b/i
+ aB
+ 0: aB
+
+/(?:(?-i)a)b/i
+ *** Failers
+No match
+ Ab
+No match
+ AB
+No match
+
+/((?-i)a)b/i
+
+/(?-i:a)b/i
+ ab
+ 0: ab
+
+/((?-i:a))b/i
+ ab
+ 0: ab
+
+/(?-i:a)b/i
+ aB
+ 0: aB
+
+/((?-i:a))b/i
+ aB
+ 0: aB
+
+/(?-i:a)b/i
+ *** Failers
+No match
+ AB
+No match
+ Ab
+No match
+
+/((?-i:a))b/i
+
+/(?-i:a)b/i
+ aB
+ 0: aB
+
+/((?-i:a))b/i
+ aB
+ 0: aB
+
+/(?-i:a)b/i
+ *** Failers
+No match
+ Ab
+No match
+ AB
+No match
+
+/((?-i:a))b/i
+
+/((?-i:a.))b/i
+ *** Failers
+No match
+ AB
+No match
+ a\nB
+No match
+
+/((?s-i:a.))b/i
+ a\nB
+ 0: a\x0aB
+
+/(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))/
+ cabbbb
+ 0: cabbbb
+
+/(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))/
+ caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ 0: caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+/foo\w*\d{4}baz/
+ foobar1234baz
+ 0: foobar1234baz
+
+/x(~~)*(?:(?:F)?)?/
+ x~~
+ 0: x~~
+ 1: x
+
+/^a(?#xxx){3}c/
+ aaac
+ 0: aaac
+
+/^a (?#xxx) (?#yyy) {3}c/x
+ aaac
+ 0: aaac
+
+/(?<![cd])b/
+ *** Failers
+No match
+ B\nB
+No match
+ dbcb
+No match
+
+/(?<![cd])[ab]/
+ dbaacb
+ 0: a
+
+/(?<!(c|d))b/
+
+/(?<!(c|d))[ab]/
+ dbaacb
+ 0: a
+
+/(?<!cd)[ab]/
+ cdaccb
+ 0: b
+
+/^(?:a?b?)*$/
+ *** Failers
+No match
+ dbcb
+No match
+ a--
+No match
+
+/((?s)^a(.))((?m)^b$)/
+ a\nb\nc\n
+ 0: a\x0ab
+
+/((?m)^b$)/
+ a\nb\nc\n
+ 0: b
+
+/(?m)^b/
+ a\nb\n
+ 0: b
+
+/(?m)^(b)/
+ a\nb\n
+ 0: b
+
+/((?m)^b)/
+ a\nb\n
+ 0: b
+
+/\n((?m)^b)/
+ a\nb\n
+ 0: \x0ab
+
+/((?s).)c(?!.)/
+ a\nb\nc\n
+ 0: \x0ac
+ a\nb\nc\n
+ 0: \x0ac
+
+/((?s)b.)c(?!.)/
+ a\nb\nc\n
+ 0: b\x0ac
+ a\nb\nc\n
+ 0: b\x0ac
+
+/^b/
+
+/()^b/
+ *** Failers
+No match
+ a\nb\nc\n
+No match
+ a\nb\nc\n
+No match
+
+/((?m)^b)/
+ a\nb\nc\n
+ 0: b
+
+/(?(?!a)a|b)/
+
+/(?(?!a)b|a)/
+ a
+ 0: a
+
+/(?(?=a)b|a)/
+ *** Failers
+No match
+ a
+No match
+ a
+No match
+
+/(?(?=a)a|b)/
+ a
+ 0: a
+
+/(\w+:)+/
+ one:
+ 0: one:
+
+/$(?<=^(a))/
+ a
+ 0:
+
+/([\w:]+::)?(\w+)$/
+ abcd
+ 0: abcd
+ xy:z:::abcd
+ 0: xy:z:::abcd
+
+/^[^bcd]*(c+)/
+ aexycd
+ 0: aexyc
+
+/(a*)b+/
+ caab
+ 0: aab
+
+/([\w:]+::)?(\w+)$/
+ abcd
+ 0: abcd
+ xy:z:::abcd
+ 0: xy:z:::abcd
+ *** Failers
+ 0: Failers
+ abcd:
+No match
+ abcd:
+No match
+
+/^[^bcd]*(c+)/
+ aexycd
+ 0: aexyc
+
+/(>a+)ab/
+
+/(?>a+)b/
+ aaab
+ 0: aaab
+
+/([[:]+)/
+ a:[b]:
+ 0: :[
+ 1: :
+
+/([[=]+)/
+ a=[b]=
+ 0: =[
+ 1: =
+
+/([[.]+)/
+ a.[b].
+ 0: .[
+ 1: .
+
+/((?>a+)b)/
+ aaab
+ 0: aaab
+
+/(?>(a+))b/
+ aaab
+ 0: aaab
+
+/((?>[^()]+)|\([^()]*\))+/
+ ((abc(ade)ufh()()x
+ 0: abc(ade)ufh()()x
+ 1: abc(ade)ufh()()
+ 2: abc(ade)ufh()
+ 3: abc(ade)ufh
+ 4: abc(ade)
+ 5: abc
+
+/a\Z/
+ *** Failers
+No match
+ aaab
+No match
+ a\nb\n
+No match
+
+/b\Z/
+ a\nb\n
+ 0: b
+
+/b\z/
+
+/b\Z/
+ a\nb
+ 0: b
+
+/b\z/
+ a\nb
+ 0: b
+ *** Failers
+No match
+
+/(?>.*)(?<=(abcd|wxyz))/
+ alphabetabcd
+ 0: alphabetabcd
+ endingwxyz
+ 0: endingwxyz
+ *** Failers
+No match
+ a rather long string that doesn't end with one of them
+No match
+
+/word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ 0: word cat dog elephant mussel cow horse canary baboon snake shark otherword
+ word cat dog elephant mussel cow horse canary baboon snake shark
+No match
+
+/word (?>[a-zA-Z0-9]+ ){0,30}otherword/
+ word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope
+No match
+
+/(?<=\d{3}(?!999))foo/
+ 999foo
+ 0: foo
+ 123999foo
+ 0: foo
+ *** Failers
+No match
+ 123abcfoo
+No match
+
+/(?<=(?!...999)\d{3})foo/
+ 999foo
+ 0: foo
+ 123999foo
+ 0: foo
+ *** Failers
+No match
+ 123abcfoo
+No match
+
+/(?<=\d{3}(?!999)...)foo/
+ 123abcfoo
+ 0: foo
+ 123456foo
+ 0: foo
+ *** Failers
+No match
+ 123999foo
+No match
+
+/(?<=\d{3}...)(?<!999)foo/
+ 123abcfoo
+ 0: foo
+ 123456foo
+ 0: foo
+ *** Failers
+No match
+ 123999foo
+No match
+
+/((Z)+|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: Z
+ 2:
+
+/(Z()|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: Z
+ 2:
+
+/(Z(())|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: Z
+ 2:
+
+/((?>Z)+|A)*/
+ ZABCDEFG
+ 0: ZA
+ 1: Z
+ 2:
+
+/((?>)+|A)*/
+ ZABCDEFG
+ 0:
+
+/a*/g
+ abbab
+ 0: a
+ 1:
+ 0:
+ 0:
+ 0: a
+ 1:
+ 0:
+ 0:
+
+/^[a-\d]/
+ abcde
+ 0: a
+ -things
+ 0: -
+ 0digit
+ 0: 0
+ *** Failers
+No match
+ bcdef
+No match
+
+/^[\d-a]/
+ abcde
+ 0: a
+ -things
+ 0: -
+ 0digit
+ 0: 0
+ *** Failers
+No match
+ bcdef
+No match
+
+/[[:space:]]+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d\x0b
+ 1: \x09\x0a\x0c\x0d
+ 2: \x09\x0a\x0c
+ 3: \x09\x0a
+ 4: \x09
+ 5:
+
+/[[:blank:]]+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09
+ 1:
+
+/[\s]+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d
+ 1: \x09\x0a\x0c
+ 2: \x09\x0a
+ 3: \x09
+ 4:
+
+/\s+/
+ > \x09\x0a\x0c\x0d\x0b<
+ 0: \x09\x0a\x0c\x0d
+ 1: \x09\x0a\x0c
+ 2: \x09\x0a
+ 3: \x09
+ 4:
+
+/a b/x
+ ab
+No match
+
+/(?!\A)x/m
+ a\nxb\n
+ 0: x
+
+/(?!^)x/m
+ a\nxb\n
+No match
+
+/abc\Qabc\Eabc/
+ abcabcabc
+ 0: abcabcabc
+
+/abc\Q(*+|\Eabc/
+ abc(*+|abc
+ 0: abc(*+|abc
+
+/ abc\Q abc\Eabc/x
+ abc abcabc
+ 0: abc abcabc
+ *** Failers
+No match
+ abcabcabc
+No match
+
+/abc#comment
+ \Q#not comment
+ literal\E/x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/abc#comment
+ \Q#not comment
+ literal/x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/abc#comment
+ \Q#not comment
+ literal\E #more comment
+ /x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/abc#comment
+ \Q#not comment
+ literal\E #more comment/x
+ abc#not comment\n literal
+ 0: abc#not comment\x0a literal
+
+/\Qabc\$xyz\E/
+ abc\\\$xyz
+ 0: abc\$xyz
+
+/\Qabc\E\$\Qxyz\E/
+ abc\$xyz
+ 0: abc$xyz
+
+/\Gabc/
+ abc
+ 0: abc
+ *** Failers
+No match
+ xyzabc
+No match
+
+/\Gabc./g
+ abc1abc2xyzabc3
+ 0: abc1
+ 0: abc2
+
+/abc./g
+ abc1abc2xyzabc3
+ 0: abc1
+ 0: abc2
+ 0: abc3
+
+/a(?x: b c )d/
+ XabcdY
+ 0: abcd
+ *** Failers
+No match
+ Xa b c d Y
+No match
+
+/((?x)x y z | a b c)/
+ XabcY
+ 0: abc
+ AxyzB
+ 0: xyz
+
+/(?i)AB(?-i)C/
+ XabCY
+ 0: abC
+ *** Failers
+No match
+ XabcY
+No match
+
+/((?i)AB(?-i)C|D)E/
+ abCE
+ 0: abCE
+ DE
+ 0: DE
+ *** Failers
+No match
+ abcE
+No match
+ abCe
+No match
+ dE
+No match
+ De
+No match
+
+/[z\Qa-d]\E]/
+ z
+ 0: z
+ a
+ 0: a
+ -
+ 0: -
+ d
+ 0: d
+ ]
+ 0: ]
+ *** Failers
+ 0: a
+ b
+No match
+
+/[\z\C]/
+ z
+ 0: z
+ C
+ 0: C
+
+/\M/
+ M
+ 0: M
+
+/(a+)*b/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+
+/(?i)reg(?:ul(?:[a�]|ae)r|ex)/
+ REGular
+ 0: REGular
+ regulaer
+ 0: regulaer
+ Regex
+ 0: Regex
+ regul�r
+ 0: regul\xe4r
+
+/����[�-��-�]+/
+ �����
+ 0: \xc5\xe6\xe5\xe4\xe0
+ �����
+ 0: \xc5\xe6\xe5\xe4\xff
+ �����
+ 0: \xc5\xe6\xe5\xe4\xc0
+ �����
+ 0: \xc5\xe6\xe5\xe4\xdf
+
+/(?<=Z)X./
+ \x84XAZXB
+ 0: XB
+
+/^(?(2)a|(1)(2))+$/
+ 123a
+Error -17
+
+/(?<=a|bbbb)c/
+ ac
+ 0: c
+ bbbbc
+ 0: c
+
+/abc/>testsavedregex
+Compiled regex written to testsavedregex
+<testsavedregex
+Compiled regex loaded from testsavedregex
+No study data
+ abc
+ 0: abc
+ *** Failers
+No match
+ bca
+No match
+
+/abc/F>testsavedregex
+Compiled regex written to testsavedregex
+<testsavedregex
+Compiled regex (byte-inverted) loaded from testsavedregex
+No study data
+ abc
+ 0: abc
+ *** Failers
+No match
+ bca
+No match
+
+/(a|b)/S>testsavedregex
+Compiled regex written to testsavedregex
+Study data written to testsavedregex
+<testsavedregex
+Compiled regex loaded from testsavedregex
+Study data loaded from testsavedregex
+ abc
+ 0: a
+ *** Failers
+ 0: a
+ def
+No match
+
+/(a|b)/SF>testsavedregex
+Compiled regex written to testsavedregex
+Study data written to testsavedregex
+<testsavedregex
+Compiled regex (byte-inverted) loaded from testsavedregex
+Study data loaded from testsavedregex
+ abc
+ 0: a
+ *** Failers
+ 0: a
+ def
+No match
+
+/line\nbreak/
+ this is a line\nbreak
+ 0: line\x0abreak
+ line one\nthis is a line\nbreak in the second line
+ 0: line\x0abreak
+
+/line\nbreak/f
+ this is a line\nbreak
+ 0: line\x0abreak
+ ** Failers
+No match
+ line one\nthis is a line\nbreak in the second line
+No match
+
+/line\nbreak/mf
+ this is a line\nbreak
+ 0: line\x0abreak
+ ** Failers
+No match
+ line one\nthis is a line\nbreak in the second line
+No match
+
+/1234/
+ 123\P
+Partial match: 123
+ a4\P\R
+No match
+
+/1234/
+ 123\P
+Partial match: 123
+ 4\P\R
+ 0: 4
+
+/^/mg
+ a\nb\nc\n
+ 0:
+ 0:
+ 0:
+ \
+ 0:
+
+/(?<=C\n)^/mg
+ A\nC\nC\n
+ 0:
+
+/(?s)A?B/
+ AB
+ 0: AB
+ aB
+ 0: B
+
+/(?s)A*B/
+ AB
+ 0: AB
+ aB
+ 0: B
+
+/(?m)A?B/
+ AB
+ 0: AB
+ aB
+ 0: B
+
+/(?m)A*B/
+ AB
+ 0: AB
+ aB
+ 0: B
+
+/Content-Type\x3A[^\r\n]{6,}/
+ Content-Type:xxxxxyyy
+ 0: Content-Type:xxxxxyyy
+ 1: Content-Type:xxxxxyy
+ 2: Content-Type:xxxxxy
+
+/Content-Type\x3A[^\r\n]{6,}z/
+ Content-Type:xxxxxyyyz
+ 0: Content-Type:xxxxxyyyz
+
+/Content-Type\x3A[^a]{6,}/
+ Content-Type:xxxyyy
+ 0: Content-Type:xxxyyy
+
+/Content-Type\x3A[^a]{6,}z/
+ Content-Type:xxxyyyz
+ 0: Content-Type:xxxyyyz
+
+/^abc/m
+ xyz\nabc
+ 0: abc
+ xyz\nabc\<lf>
+ 0: abc
+ xyz\r\nabc\<lf>
+ 0: abc
+ xyz\rabc\<cr>
+ 0: abc
+ xyz\r\nabc\<crlf>
+ 0: abc
+ ** Failers
+No match
+ xyz\nabc\<cr>
+No match
+ xyz\r\nabc\<cr>
+No match
+ xyz\nabc\<crlf>
+No match
+ xyz\rabc\<crlf>
+No match
+ xyz\rabc\<lf>
+No match
+
+/abc$/m<lf>
+ xyzabc
+ 0: abc
+ xyzabc\n
+ 0: abc
+ xyzabc\npqr
+ 0: abc
+ xyzabc\r\<cr>
+ 0: abc
+ xyzabc\rpqr\<cr>
+ 0: abc
+ xyzabc\r\n\<crlf>
+ 0: abc
+ xyzabc\r\npqr\<crlf>
+ 0: abc
+ ** Failers
+No match
+ xyzabc\r
+No match
+ xyzabc\rpqr
+No match
+ xyzabc\r\n
+No match
+ xyzabc\r\npqr
+No match
+
+/^abc/m<cr>
+ xyz\rabcdef
+ 0: abc
+ xyz\nabcdef\<lf>
+ 0: abc
+ ** Failers
+No match
+ xyz\nabcdef
+No match
+
+/^abc/m<lf>
+ xyz\nabcdef
+ 0: abc
+ xyz\rabcdef\<cr>
+ 0: abc
+ ** Failers
+No match
+ xyz\rabcdef
+No match
+
+/^abc/m<crlf>
+ xyz\r\nabcdef
+ 0: abc
+ xyz\rabcdef\<cr>
+ 0: abc
+ ** Failers
+No match
+ xyz\rabcdef
+No match
+
+/.*/<lf>
+ abc\ndef
+ 0: abc
+ 1: ab
+ 2: a
+ 3:
+ abc\rdef
+ 0: abc\x0ddef
+ 1: abc\x0dde
+ 2: abc\x0dd
+ 3: abc\x0d
+ 4: abc
+ 5: ab
+ 6: a
+ 7:
+ abc\r\ndef
+ 0: abc\x0d
+ 1: abc
+ 2: ab
+ 3: a
+ 4:
+ \<cr>abc\ndef
+ 0: abc\x0adef
+ 1: abc\x0ade
+ 2: abc\x0ad
+ 3: abc\x0a
+ 4: abc
+ 5: ab
+ 6: a
+ 7:
+ \<cr>abc\rdef
+ 0: abc
+ 1: ab
+ 2: a
+ 3:
+ \<cr>abc\r\ndef
+ 0: abc
+ 1: ab
+ 2: a
+ 3:
+ \<crlf>abc\ndef
+ 0: abc\x0adef
+ 1: abc\x0ade
+ 2: abc\x0ad
+ 3: abc\x0a
+ 4: abc
+ 5: ab
+ 6: a
+ 7:
+ \<crlf>abc\rdef
+ 0: abc\x0ddef
+ 1: abc\x0dde
+ 2: abc\x0dd
+ 3: abc\x0d
+ 4: abc
+ 5: ab
+ 6: a
+ 7:
+ \<crlf>abc\r\ndef
+ 0: abc
+ 1: ab
+ 2: a
+ 3:
+
+/\w+(.)(.)?def/s
+ abc\ndef
+ 0: abc\x0adef
+ abc\rdef
+ 0: abc\x0ddef
+ abc\r\ndef
+ 0: abc\x0d\x0adef
+
+/^\w+=.*(\\\n.*)*/
+ abc=xyz\\\npqr
+ 0: abc=xyz\\x0apqr
+ 1: abc=xyz\\x0apq
+ 2: abc=xyz\\x0ap
+ 3: abc=xyz\\x0a
+ 4: abc=xyz\
+ 5: abc=xyz
+ 6: abc=xy
+ 7: abc=x
+ 8: abc=
+
+/^(a()*)*/
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+ 4:
+
+/^(?:a(?:(?:))*)*/
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+ 4:
+
+/^(a()+)+/
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+
+/^(?:a(?:(?:))+)+/
+ aaaa
+ 0: aaaa
+ 1: aaa
+ 2: aa
+ 3: a
+
+/(a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+
+/(?>a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+
+/(?:a|)*\d/
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4
+
+/^a.b/<lf>
+ a\rb
+ 0: a\x0db
+ a\nb\<cr>
+ 0: a\x0ab
+ ** Failers
+No match
+ a\nb
+No match
+ a\nb\<any>
+No match
+ a\rb\<cr>
+No match
+ a\rb\<any>
+No match
+
+/^abc./mgx<any>
+ abc1 \x0aabc2 \x0babc3xx \x0cabc4 \x0dabc5xx \x0d\x0aabc6 \x85abc7 \x{2028}abc8 \x{2029}abc9 JUNK
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc7
+
+/abc.$/mgx<any>
+ abc1\x0a abc2\x0b abc3\x0c abc4\x0d abc5\x0d\x0a abc6\x85 abc7\x{2028} abc8\x{2029} abc9
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc9
+
+/^a\Rb/<bsr_unicode>
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x0bb
+ 0: a\x0bb
+ a\x0cb
+ 0: a\x0cb
+ a\x85b
+ 0: a\x85b
+ ** Failers
+No match
+ a\n\rb
+No match
+
+/^a\R*b/<bsr_unicode>
+ ab
+ 0: ab
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x0bb
+ 0: a\x0bb
+ a\x0cb
+ 0: a\x0cb
+ a\x85b
+ 0: a\x85b
+ a\n\rb
+ 0: a\x0a\x0db
+ a\n\r\x85\x0cb
+ 0: a\x0a\x0d\x85\x0cb
+
+/^a\R+b/<bsr_unicode>
+ a\nb
+ 0: a\x0ab
+ a\rb
+ 0: a\x0db
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x0bb
+ 0: a\x0bb
+ a\x0cb
+ 0: a\x0cb
+ a\x85b
+ 0: a\x85b
+ a\n\rb
+ 0: a\x0a\x0db
+ a\n\r\x85\x0cb
+ 0: a\x0a\x0d\x85\x0cb
+ ** Failers
+No match
+ ab
+No match
+
+/^a\R{1,3}b/<bsr_unicode>
+ a\nb
+ 0: a\x0ab
+ a\n\rb
+ 0: a\x0a\x0db
+ a\n\r\x85b
+ 0: a\x0a\x0d\x85b
+ a\r\n\r\nb
+ 0: a\x0d\x0a\x0d\x0ab
+ a\r\n\r\n\r\nb
+ 0: a\x0d\x0a\x0d\x0a\x0d\x0ab
+ a\n\r\n\rb
+ 0: a\x0a\x0d\x0a\x0db
+ a\n\n\r\nb
+ 0: a\x0a\x0a\x0d\x0ab
+ ** Failers
+No match
+ a\n\n\n\rb
+No match
+ a\r
+No match
+
+/^a[\R]b/<bsr_unicode>
+ aRb
+ 0: aRb
+ ** Failers
+No match
+ a\nb
+No match
+
+/.+foo/
+ afoo
+ 0: afoo
+ ** Failers
+No match
+ \r\nfoo
+No match
+ \nfoo
+No match
+
+/.+foo/<crlf>
+ afoo
+ 0: afoo
+ \nfoo
+ 0: \x0afoo
+ ** Failers
+No match
+ \r\nfoo
+No match
+
+/.+foo/<any>
+ afoo
+ 0: afoo
+ ** Failers
+No match
+ \nfoo
+No match
+ \r\nfoo
+No match
+
+/.+foo/s
+ afoo
+ 0: afoo
+ \r\nfoo
+ 0: \x0d\x0afoo
+ \nfoo
+ 0: \x0afoo
+
+/^$/mg<any>
+ abc\r\rxyz
+ 0:
+ abc\n\rxyz
+ 0:
+ ** Failers
+No match
+ abc\r\nxyz
+No match
+
+/^X/m
+ XABC
+ 0: X
+ ** Failers
+No match
+ XABC\B
+No match
+
+/(?m)^$/<any>g+
+ abc\r\n\r\n
+ 0:
+ 0+ \x0d\x0a
+
+/(?m)^$|^\r\n/<any>g+
+ abc\r\n\r\n
+ 0: \x0d\x0a
+ 0+
+ 1:
+
+/(?m)$/<any>g+
+ abc\r\n\r\n
+ 0:
+ 0+ \x0d\x0a\x0d\x0a
+ 0:
+ 0+ \x0d\x0a
+ 0:
+ 0+
+
+/(?|(abc)|(xyz))/
+ >abc<
+ 0: abc
+ >xyz<
+ 0: xyz
+
+/(x)(?|(abc)|(xyz))(x)/
+ xabcx
+ 0: xabcx
+ xxyzx
+ 0: xxyzx
+
+/(x)(?|(abc)(pqr)|(xyz))(x)/
+ xabcpqrx
+ 0: xabcpqrx
+ xxyzx
+ 0: xxyzx
+
+/(?|(abc)|(xyz))(?1)/
+ abcabc
+ 0: abcabc
+ xyzabc
+ 0: xyzabc
+ ** Failers
+No match
+ xyzxyz
+No match
+
+/\H\h\V\v/
+ X X\x0a
+ 0: X X\x0a
+ X\x09X\x0b
+ 0: X\x09X\x0b
+ ** Failers
+No match
+ \xa0 X\x0a
+No match
+
+/\H*\h+\V?\v{3,4}/
+ \x09\x20\xa0X\x0a\x0b\x0c\x0d\x0a
+ 0: \x09 \xa0X\x0a\x0b\x0c\x0d
+ 1: \x09 \xa0X\x0a\x0b\x0c
+ \x09\x20\xa0\x0a\x0b\x0c\x0d\x0a
+ 0: \x09 \xa0\x0a\x0b\x0c\x0d
+ 1: \x09 \xa0\x0a\x0b\x0c
+ \x09\x20\xa0\x0a\x0b\x0c
+ 0: \x09 \xa0\x0a\x0b\x0c
+ ** Failers
+No match
+ \x09\x20\xa0\x0a\x0b
+No match
+
+/\H{3,4}/
+ XY ABCDE
+ 0: ABCD
+ 1: ABC
+ XY PQR ST
+ 0: PQR
+
+/.\h{3,4}./
+ XY AB PQRS
+ 0: B P
+ 1: B
+
+/\h*X\h?\H+Y\H?Z/
+ >XNNNYZ
+ 0: XNNNYZ
+ > X NYQZ
+ 0: X NYQZ
+ ** Failers
+No match
+ >XYZ
+No match
+ > X NY Z
+No match
+
+/\v*X\v?Y\v+Z\V*\x0a\V+\x0b\V{2,3}\x0c/
+ >XY\x0aZ\x0aA\x0bNN\x0c
+ 0: XY\x0aZ\x0aA\x0bNN\x0c
+ >\x0a\x0dX\x0aY\x0a\x0bZZZ\x0aAAA\x0bNNN\x0c
+ 0: \x0a\x0dX\x0aY\x0a\x0bZZZ\x0aAAA\x0bNNN\x0c
+
+/.+A/<crlf>
+ \r\nA
+No match
+
+/\nA/<crlf>
+ \r\nA
+ 0: \x0aA
+
+/[\r\n]A/<crlf>
+ \r\nA
+ 0: \x0aA
+
+/(\r|\n)A/<crlf>
+ \r\nA
+ 0: \x0aA
+
+/a\Rb/I<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ ** Failers
+No match
+ a\x85b
+No match
+ a\x0bb
+No match
+
+/a\Rb/I<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x85b
+ 0: a\x85b
+ a\x0bb
+ 0: a\x0bb
+ ** Failers
+No match
+ a\x85b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/a\R?b/I<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ ** Failers
+No match
+ a\x85b
+No match
+ a\x0bb
+No match
+
+/a\R?b/I<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x0db
+ a\nb
+ 0: a\x0ab
+ a\r\nb
+ 0: a\x0d\x0ab
+ a\x85b
+ 0: a\x85b
+ a\x0bb
+ 0: a\x0bb
+ ** Failers
+No match
+ a\x85b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/a\R{2,4}b/I<bsr_anycrlf>
+Capturing subpattern count = 0
+Partial matching not supported
+Options: bsr_anycrlf
+First char = 'a'
+Need char = 'b'
+ a\r\n\nb
+ 0: a\x0d\x0a\x0ab
+ a\n\r\rb
+ 0: a\x0a\x0d\x0db
+ a\r\n\r\n\r\n\r\nb
+ 0: a\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0ab
+ ** Failers
+No match
+ a\x85\85b
+No match
+ a\x0b\0bb
+No match
+
+/a\R{2,4}b/I<bsr_unicode>
+Capturing subpattern count = 0
+Partial matching not supported
+Options: bsr_unicode
+First char = 'a'
+Need char = 'b'
+ a\r\rb
+ 0: a\x0d\x0db
+ a\n\n\nb
+ 0: a\x0a\x0a\x0ab
+ a\r\n\n\r\rb
+ 0: a\x0d\x0a\x0a\x0d\x0db
+ a\x85\85b
+No match
+ a\x0b\0bb
+No match
+ ** Failers
+No match
+ a\r\r\r\r\rb
+No match
+ a\x85\85b\<bsr_anycrlf>
+No match
+ a\x0b\0bb\<bsr_anycrlf>
+No match
+
+/ End of testinput7 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput8 b/lib/stdlib/test/re_SUITE_data/testoutput8
new file mode 100644
index 0000000000..631e5b82f9
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput8
@@ -0,0 +1,1287 @@
+/-- Do not use the \x{} construct except with patterns that have the --/
+/-- /8 option set, because PCRE doesn't recognize them as UTF-8 unless --/
+No match
+/-- that option is set. However, the latest Perls recognize them always. --/
+No match
+
+/\x{100}ab/8
+ \x{100}ab
+ 0: \x{100}ab
+
+/a\x{100}*b/8
+ ab
+ 0: ab
+ a\x{100}b
+ 0: a\x{100}b
+ a\x{100}\x{100}b
+ 0: a\x{100}\x{100}b
+
+/a\x{100}+b/8
+ a\x{100}b
+ 0: a\x{100}b
+ a\x{100}\x{100}b
+ 0: a\x{100}\x{100}b
+ *** Failers
+No match
+ ab
+No match
+
+/\bX/8
+ Xoanon
+ 0: X
+ +Xoanon
+ 0: X
+ \x{300}Xoanon
+ 0: X
+ *** Failers
+No match
+ YXoanon
+No match
+
+/\BX/8
+ YXoanon
+ 0: X
+ *** Failers
+No match
+ Xoanon
+No match
+ +Xoanon
+No match
+ \x{300}Xoanon
+No match
+
+/X\b/8
+ X+oanon
+ 0: X
+ ZX\x{300}oanon
+ 0: X
+ FAX
+ 0: X
+ *** Failers
+No match
+ Xoanon
+No match
+
+/X\B/8
+ Xoanon
+ 0: X
+ *** Failers
+No match
+ X+oanon
+No match
+ ZX\x{300}oanon
+No match
+ FAX
+No match
+
+/[^a]/8
+ abcd
+ 0: b
+ a\x{100}
+ 0: \x{100}
+
+/^[abc\x{123}\x{400}-\x{402}]{2,3}\d/8
+ ab99
+ 0: ab9
+ \x{123}\x{123}45
+ 0: \x{123}\x{123}4
+ \x{400}\x{401}\x{402}6
+ 0: \x{400}\x{401}\x{402}6
+ *** Failers
+No match
+ d99
+No match
+ \x{123}\x{122}4
+No match
+ \x{400}\x{403}6
+No match
+ \x{400}\x{401}\x{402}\x{402}6
+No match
+
+/abc/8
+ �]
+Error -10
+ �
+Error -10
+ ���
+Error -10
+ ���\?
+No match
+
+/a.b/8
+ acb
+ 0: acb
+ a\x7fb
+ 0: a\x{7f}b
+ a\x{100}b
+ 0: a\x{100}b
+ *** Failers
+No match
+ a\nb
+No match
+
+/a(.{3})b/8
+ a\x{4000}xyb
+ 0: a\x{4000}xyb
+ a\x{4000}\x7fyb
+ 0: a\x{4000}\x{7f}yb
+ a\x{4000}\x{100}yb
+ 0: a\x{4000}\x{100}yb
+ *** Failers
+No match
+ a\x{4000}b
+No match
+ ac\ncb
+No match
+
+/a(.*?)(.)/
+ a\xc0\x88b
+ 0: a\xc0\x88b
+ 1: a\xc0\x88
+ 2: a\xc0
+
+/a(.*?)(.)/8
+ a\x{100}b
+ 0: a\x{100}b
+ 1: a\x{100}
+
+/a(.*)(.)/
+ a\xc0\x88b
+ 0: a\xc0\x88b
+ 1: a\xc0\x88
+ 2: a\xc0
+
+/a(.*)(.)/8
+ a\x{100}b
+ 0: a\x{100}b
+ 1: a\x{100}
+
+/a(.)(.)/
+ a\xc0\x92bcd
+ 0: a\xc0\x92
+
+/a(.)(.)/8
+ a\x{240}bcd
+ 0: a\x{240}b
+
+/a(.?)(.)/
+ a\xc0\x92bcd
+ 0: a\xc0\x92
+ 1: a\xc0
+
+/a(.?)(.)/8
+ a\x{240}bcd
+ 0: a\x{240}b
+ 1: a\x{240}
+
+/a(.??)(.)/
+ a\xc0\x92bcd
+ 0: a\xc0\x92
+ 1: a\xc0
+
+/a(.??)(.)/8
+ a\x{240}bcd
+ 0: a\x{240}b
+ 1: a\x{240}
+
+/a(.{3})b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ *** Failers
+No match
+ a\x{1234}b
+No match
+ ac\ncb
+No match
+
+/a(.{3,})b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ axxxxbcdefghijb
+ 0: axxxxbcdefghijb
+ 1: axxxxb
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ *** Failers
+No match
+ a\x{1234}b
+No match
+
+/a(.{3,}?)b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ axxxxbcdefghijb
+ 0: axxxxbcdefghijb
+ 1: axxxxb
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ *** Failers
+No match
+ a\x{1234}b
+No match
+
+/a(.{3,5})b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ axxxxbcdefghijb
+ 0: axxxxb
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ axbxxbcdefghijb
+ 0: axbxxb
+ axxxxxbcdefghijb
+ 0: axxxxxb
+ *** Failers
+No match
+ a\x{1234}b
+No match
+ axxxxxxbcdefghijb
+No match
+
+/a(.{3,5}?)b/8
+ a\x{1234}xyb
+ 0: a\x{1234}xyb
+ a\x{1234}\x{4321}yb
+ 0: a\x{1234}\x{4321}yb
+ a\x{1234}\x{4321}\x{3412}b
+ 0: a\x{1234}\x{4321}\x{3412}b
+ axxxxbcdefghijb
+ 0: axxxxb
+ a\x{1234}\x{4321}\x{3412}\x{3421}b
+ 0: a\x{1234}\x{4321}\x{3412}\x{3421}b
+ axbxxbcdefghijb
+ 0: axbxxb
+ axxxxxbcdefghijb
+ 0: axxxxxb
+ *** Failers
+No match
+ a\x{1234}b
+No match
+ axxxxxxbcdefghijb
+No match
+
+/^[a\x{c0}]/8
+ *** Failers
+No match
+ \x{100}
+No match
+
+/(?<=aXb)cd/8
+ aXbcd
+ 0: cd
+
+/(?<=a\x{100}b)cd/8
+ a\x{100}bcd
+ 0: cd
+
+/(?<=a\x{100000}b)cd/8
+ a\x{100000}bcd
+ 0: cd
+
+/(?:\x{100}){3}b/8
+ \x{100}\x{100}\x{100}b
+ 0: \x{100}\x{100}\x{100}b
+ *** Failers
+No match
+ \x{100}\x{100}b
+No match
+
+/\x{ab}/8
+ \x{ab}
+ 0: \x{ab}
+ \xc2\xab
+ 0: \x{ab}
+ *** Failers
+No match
+ \x00{ab}
+No match
+
+/(?<=(.))X/8
+ WXYZ
+ 0: X
+ \x{256}XYZ
+ 0: X
+ *** Failers
+No match
+ XYZ
+No match
+
+/[^a]+/8g
+ bcd
+ 0: bcd
+ 1: bc
+ 2: b
+ \x{100}aY\x{256}Z
+ 0: \x{100}
+ 0: Y\x{256}Z
+ 1: Y\x{256}
+ 2: Y
+
+/^[^a]{2}/8
+ \x{100}bc
+ 0: \x{100}b
+
+/^[^a]{2,}/8
+ \x{100}bcAa
+ 0: \x{100}bcA
+ 1: \x{100}bc
+ 2: \x{100}b
+
+/^[^a]{2,}?/8
+ \x{100}bca
+ 0: \x{100}bc
+ 1: \x{100}b
+
+/[^a]+/8ig
+ bcd
+ 0: bcd
+ 1: bc
+ 2: b
+ \x{100}aY\x{256}Z
+ 0: \x{100}
+ 0: Y\x{256}Z
+ 1: Y\x{256}
+ 2: Y
+
+/^[^a]{2}/8i
+ \x{100}bc
+ 0: \x{100}b
+
+/^[^a]{2,}/8i
+ \x{100}bcAa
+ 0: \x{100}bc
+ 1: \x{100}b
+
+/^[^a]{2,}?/8i
+ \x{100}bca
+ 0: \x{100}bc
+ 1: \x{100}b
+
+/\x{100}{0,0}/8
+ abcd
+ 0:
+
+/\x{100}?/8
+ abcd
+ 0:
+ \x{100}\x{100}
+ 0: \x{100}
+ 1:
+
+/\x{100}{0,3}/8
+ \x{100}\x{100}
+ 0: \x{100}\x{100}
+ 1: \x{100}
+ 2:
+ \x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}
+ 1: \x{100}\x{100}
+ 2: \x{100}
+ 3:
+
+/\x{100}*/8
+ abce
+ 0:
+ \x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}\x{100}
+ 1: \x{100}\x{100}\x{100}
+ 2: \x{100}\x{100}
+ 3: \x{100}
+ 4:
+
+/\x{100}{1,1}/8
+ abcd\x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}
+
+/\x{100}{1,3}/8
+ abcd\x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}
+ 1: \x{100}\x{100}
+ 2: \x{100}
+
+/\x{100}+/8
+ abcd\x{100}\x{100}\x{100}\x{100}
+ 0: \x{100}\x{100}\x{100}\x{100}
+ 1: \x{100}\x{100}\x{100}
+ 2: \x{100}\x{100}
+ 3: \x{100}
+
+/\x{100}{3}/8
+ abcd\x{100}\x{100}\x{100}XX
+ 0: \x{100}\x{100}\x{100}
+
+/\x{100}{3,5}/8
+ abcd\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}XX
+ 0: \x{100}\x{100}\x{100}\x{100}\x{100}
+ 1: \x{100}\x{100}\x{100}\x{100}
+ 2: \x{100}\x{100}\x{100}
+
+/\x{100}{3,}/8
+ abcd\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}XX
+ 0: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 1: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 2: \x{100}\x{100}\x{100}\x{100}\x{100}
+ 3: \x{100}\x{100}\x{100}\x{100}
+ 4: \x{100}\x{100}\x{100}
+
+/(?<=a\x{100}{2}b)X/8
+ Xyyya\x{100}\x{100}bXzzz
+ 0: X
+
+/\D*/8
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many subsidiary matches
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 5: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 6: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 7: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 8: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 9: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+10: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+13: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+14: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+15: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+16: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+17: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+18: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+20: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+21: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/\D*/8
+ \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+Matched, but too many subsidiary matches
+ 0: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 1: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 2: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 3: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 4: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 5: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 6: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 7: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 8: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+ 9: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+10: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+11: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+12: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+13: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+14: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+15: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+16: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+17: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+18: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+19: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+20: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+21: \x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}\x{100}
+
+/\D/8
+ 1X2
+ 0: X
+ 1\x{100}2
+ 0: \x{100}
+
+/>\S/8
+ > >X Y
+ 0: >X
+ > >\x{100} Y
+ 0: >\x{100}
+
+/\d/8
+ \x{100}3
+ 0: 3
+
+/\s/8
+ \x{100} X
+ 0:
+
+/\D+/8
+ 12abcd34
+ 0: abcd
+ 1: abc
+ 2: ab
+ 3: a
+ *** Failers
+ 0: *** Failers
+ 1: *** Failer
+ 2: *** Faile
+ 3: *** Fail
+ 4: *** Fai
+ 5: *** Fa
+ 6: *** F
+ 7: ***
+ 8: ***
+ 9: **
+10: *
+ 1234
+No match
+
+/\D{2,3}/8
+ 12abcd34
+ 0: abc
+ 1: ab
+ 12ab34
+ 0: ab
+ *** Failers
+ 0: ***
+ 1: **
+ 1234
+No match
+ 12a34
+No match
+
+/\D{2,3}?/8
+ 12abcd34
+ 0: abc
+ 1: ab
+ 12ab34
+ 0: ab
+ *** Failers
+ 0: ***
+ 1: **
+ 1234
+No match
+ 12a34
+No match
+
+/\d+/8
+ 12abcd34
+ 0: 12
+ 1: 1
+ *** Failers
+No match
+
+/\d{2,3}/8
+ 12abcd34
+ 0: 12
+ 1234abcd
+ 0: 123
+ 1: 12
+ *** Failers
+No match
+ 1.4
+No match
+
+/\d{2,3}?/8
+ 12abcd34
+ 0: 12
+ 1234abcd
+ 0: 123
+ 1: 12
+ *** Failers
+No match
+ 1.4
+No match
+
+/\S+/8
+ 12abcd34
+ 0: 12abcd34
+ 1: 12abcd3
+ 2: 12abcd
+ 3: 12abc
+ 4: 12ab
+ 5: 12a
+ 6: 12
+ 7: 1
+ *** Failers
+ 0: ***
+ 1: **
+ 2: *
+ \ \
+No match
+
+/\S{2,3}/8
+ 12abcd34
+ 0: 12a
+ 1: 12
+ 1234abcd
+ 0: 123
+ 1: 12
+ *** Failers
+ 0: ***
+ 1: **
+ \ \
+No match
+
+/\S{2,3}?/8
+ 12abcd34
+ 0: 12a
+ 1: 12
+ 1234abcd
+ 0: 123
+ 1: 12
+ *** Failers
+ 0: ***
+ 1: **
+ \ \
+No match
+
+/>\s+</8
+ 12> <34
+ 0: > <
+ *** Failers
+No match
+
+/>\s{2,3}</8
+ ab> <cd
+ 0: > <
+ ab> <ce
+ 0: > <
+ *** Failers
+No match
+ ab> <cd
+No match
+
+/>\s{2,3}?</8
+ ab> <cd
+ 0: > <
+ ab> <ce
+ 0: > <
+ *** Failers
+No match
+ ab> <cd
+No match
+
+/\w+/8
+ 12 34
+ 0: 12
+ 1: 1
+ *** Failers
+ 0: Failers
+ 1: Failer
+ 2: Faile
+ 3: Fail
+ 4: Fai
+ 5: Fa
+ 6: F
+ +++=*!
+No match
+
+/\w{2,3}/8
+ ab cd
+ 0: ab
+ abcd ce
+ 0: abc
+ 1: ab
+ *** Failers
+ 0: Fai
+ 1: Fa
+ a.b.c
+No match
+
+/\w{2,3}?/8
+ ab cd
+ 0: ab
+ abcd ce
+ 0: abc
+ 1: ab
+ *** Failers
+ 0: Fai
+ 1: Fa
+ a.b.c
+No match
+
+/\W+/8
+ 12====34
+ 0: ====
+ 1: ===
+ 2: ==
+ 3: =
+ *** Failers
+ 0: ***
+ 1: ***
+ 2: **
+ 3: *
+ abcd
+No match
+
+/\W{2,3}/8
+ ab====cd
+ 0: ===
+ 1: ==
+ ab==cd
+ 0: ==
+ *** Failers
+ 0: ***
+ 1: **
+ a.b.c
+No match
+
+/\W{2,3}?/8
+ ab====cd
+ 0: ===
+ 1: ==
+ ab==cd
+ 0: ==
+ *** Failers
+ 0: ***
+ 1: **
+ a.b.c
+No match
+
+/[\x{100}]/8
+ \x{100}
+ 0: \x{100}
+ Z\x{100}
+ 0: \x{100}
+ \x{100}Z
+ 0: \x{100}
+ *** Failers
+No match
+
+/[Z\x{100}]/8
+ Z\x{100}
+ 0: Z
+ \x{100}
+ 0: \x{100}
+ \x{100}Z
+ 0: \x{100}
+ *** Failers
+No match
+
+/[\x{100}\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ *** Failers
+No match
+
+/[\x{100}-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ *** Failers
+No match
+
+/[z-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ abzcd
+ 0: z
+ ab|cd
+ 0: |
+ *** Failers
+No match
+
+/[Q\x{100}\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ Q?
+ 0: Q
+ *** Failers
+No match
+
+/[Q\x{100}-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ Q?
+ 0: Q
+ *** Failers
+No match
+
+/[Qz-\x{200}]/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{111}cd
+ 0: \x{111}
+ abzcd
+ 0: z
+ ab|cd
+ 0: |
+ Q?
+ 0: Q
+ *** Failers
+No match
+
+/[\x{100}\x{200}]{1,3}/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}\x{100}\x{200}
+ 1: \x{200}\x{100}
+ 2: \x{200}
+ *** Failers
+No match
+
+/[\x{100}\x{200}]{1,3}?/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}\x{100}\x{200}
+ 1: \x{200}\x{100}
+ 2: \x{200}
+ *** Failers
+No match
+
+/[Q\x{100}\x{200}]{1,3}/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}\x{100}\x{200}
+ 1: \x{200}\x{100}
+ 2: \x{200}
+ *** Failers
+No match
+
+/[Q\x{100}\x{200}]{1,3}?/8
+ ab\x{100}cd
+ 0: \x{100}
+ ab\x{200}cd
+ 0: \x{200}
+ ab\x{200}\x{100}\x{200}\x{100}cd
+ 0: \x{200}\x{100}\x{200}
+ 1: \x{200}\x{100}
+ 2: \x{200}
+ *** Failers
+No match
+
+/(?<=[\x{100}\x{200}])X/8
+ abc\x{200}X
+ 0: X
+ abc\x{100}X
+ 0: X
+ *** Failers
+No match
+ X
+No match
+
+/(?<=[Q\x{100}\x{200}])X/8
+ abc\x{200}X
+ 0: X
+ abc\x{100}X
+ 0: X
+ abQX
+ 0: X
+ *** Failers
+No match
+ X
+No match
+
+/(?<=[\x{100}\x{200}]{3})X/8
+ abc\x{100}\x{200}\x{100}X
+ 0: X
+ *** Failers
+No match
+ abc\x{200}X
+No match
+ X
+No match
+
+/[^\x{100}\x{200}]X/8
+ AX
+ 0: AX
+ \x{150}X
+ 0: \x{150}X
+ \x{500}X
+ 0: \x{500}X
+ *** Failers
+No match
+ \x{100}X
+No match
+ \x{200}X
+No match
+
+/[^Q\x{100}\x{200}]X/8
+ AX
+ 0: AX
+ \x{150}X
+ 0: \x{150}X
+ \x{500}X
+ 0: \x{500}X
+ *** Failers
+No match
+ \x{100}X
+No match
+ \x{200}X
+No match
+ QX
+No match
+
+/[^\x{100}-\x{200}]X/8
+ AX
+ 0: AX
+ \x{500}X
+ 0: \x{500}X
+ *** Failers
+No match
+ \x{100}X
+No match
+ \x{150}X
+No match
+ \x{200}X
+No match
+
+/[z-\x{100}]/8i
+ z
+ 0: z
+ Z
+ 0: Z
+ \x{100}
+ 0: \x{100}
+ *** Failers
+No match
+ \x{102}
+No match
+ y
+No match
+
+/[\xFF]/
+ >\xff<
+ 0: \xff
+
+/[\xff]/8
+ >\x{ff}<
+ 0: \x{ff}
+
+/[^\xFF]/
+ XYZ
+ 0: X
+
+/[^\xff]/8
+ XYZ
+ 0: X
+ \x{123}
+ 0: \x{123}
+
+/^[ac]*b/8
+ xb
+No match
+
+/^[ac\x{100}]*b/8
+ xb
+No match
+
+/^[^x]*b/8i
+ xb
+No match
+
+/^[^x]*b/8
+ xb
+No match
+
+/^\d*b/8
+ xb
+No match
+
+/(|a)/g8
+ catac
+ 0:
+ 0: a
+ 1:
+ 0:
+ 0: a
+ 1:
+ 0:
+ 0:
+ a\x{256}a
+ 0: a
+ 1:
+ 0:
+ 0: a
+ 1:
+ 0:
+
+/^\x{85}$/8i
+ \x{85}
+ 0: \x{85}
+
+/^abc./mgx8<any>
+ abc1 \x0aabc2 \x0babc3xx \x0cabc4 \x0dabc5xx \x0d\x0aabc6 \x{0085}abc7 \x{2028}abc8 \x{2029}abc9 JUNK
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc7
+ 0: abc8
+ 0: abc9
+
+/abc.$/mgx8<any>
+ abc1\x0a abc2\x0b abc3\x0c abc4\x0d abc5\x0d\x0a abc6\x{0085} abc7\x{2028} abc8\x{2029} abc9
+ 0: abc1
+ 0: abc2
+ 0: abc3
+ 0: abc4
+ 0: abc5
+ 0: abc6
+ 0: abc7
+ 0: abc8
+ 0: abc9
+
+/^a\Rb/8<bsr_unicode>
+ a\nb
+ 0: a\x{0a}b
+ a\rb
+ 0: a\x{0d}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x0bb
+ 0: a\x{0b}b
+ a\x0cb
+ 0: a\x{0c}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\x{2028}b
+ 0: a\x{2028}b
+ a\x{2029}b
+ 0: a\x{2029}b
+ ** Failers
+No match
+ a\n\rb
+No match
+
+/^a\R*b/8<bsr_unicode>
+ ab
+ 0: ab
+ a\nb
+ 0: a\x{0a}b
+ a\rb
+ 0: a\x{0d}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x0bb
+ 0: a\x{0b}b
+ a\x0c\x{2028}\x{2029}b
+ 0: a\x{0c}\x{2028}\x{2029}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\n\rb
+ 0: a\x{0a}\x{0d}b
+ a\n\r\x{85}\x0cb
+ 0: a\x{0a}\x{0d}\x{85}\x{0c}b
+
+/^a\R+b/8<bsr_unicode>
+ a\nb
+ 0: a\x{0a}b
+ a\rb
+ 0: a\x{0d}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x0bb
+ 0: a\x{0b}b
+ a\x0c\x{2028}\x{2029}b
+ 0: a\x{0c}\x{2028}\x{2029}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\n\rb
+ 0: a\x{0a}\x{0d}b
+ a\n\r\x{85}\x0cb
+ 0: a\x{0a}\x{0d}\x{85}\x{0c}b
+ ** Failers
+No match
+ ab
+No match
+
+/^a\R{1,3}b/8<bsr_unicode>
+ a\nb
+ 0: a\x{0a}b
+ a\n\rb
+ 0: a\x{0a}\x{0d}b
+ a\n\r\x{85}b
+ 0: a\x{0a}\x{0d}\x{85}b
+ a\r\n\r\nb
+ 0: a\x{0d}\x{0a}\x{0d}\x{0a}b
+ a\r\n\r\n\r\nb
+ 0: a\x{0d}\x{0a}\x{0d}\x{0a}\x{0d}\x{0a}b
+ a\n\r\n\rb
+ 0: a\x{0a}\x{0d}\x{0a}\x{0d}b
+ a\n\n\r\nb
+ 0: a\x{0a}\x{0a}\x{0d}\x{0a}b
+ ** Failers
+No match
+ a\n\n\n\rb
+No match
+ a\r
+No match
+
+/\h+\V?\v{3,4}/8
+ \x09\x20\x{a0}X\x0a\x0b\x0c\x0d\x0a
+ 0: \x{09} \x{a0}X\x{0a}\x{0b}\x{0c}\x{0d}
+ 1: \x{09} \x{a0}X\x{0a}\x{0b}\x{0c}
+
+/\V?\v{3,4}/8
+ \x20\x{a0}X\x0a\x0b\x0c\x0d\x0a
+ 0: X\x{0a}\x{0b}\x{0c}\x{0d}
+ 1: X\x{0a}\x{0b}\x{0c}
+
+/\h+\V?\v{3,4}/8
+ >\x09\x20\x{a0}X\x0a\x0a\x0a<
+ 0: \x{09} \x{a0}X\x{0a}\x{0a}\x{0a}
+
+/\V?\v{3,4}/8
+ >\x09\x20\x{a0}X\x0a\x0a\x0a<
+ 0: X\x{0a}\x{0a}\x{0a}
+
+/\H\h\V\v/8
+ X X\x0a
+ 0: X X\x{0a}
+ X\x09X\x0b
+ 0: X\x{09}X\x{0b}
+ ** Failers
+No match
+ \x{a0} X\x0a
+No match
+
+/\H*\h+\V?\v{3,4}/8
+ \x09\x20\x{a0}X\x0a\x0b\x0c\x0d\x0a
+ 0: \x{09} \x{a0}X\x{0a}\x{0b}\x{0c}\x{0d}
+ 1: \x{09} \x{a0}X\x{0a}\x{0b}\x{0c}
+ \x09\x20\x{a0}\x0a\x0b\x0c\x0d\x0a
+ 0: \x{09} \x{a0}\x{0a}\x{0b}\x{0c}\x{0d}
+ 1: \x{09} \x{a0}\x{0a}\x{0b}\x{0c}
+ \x09\x20\x{a0}\x0a\x0b\x0c
+ 0: \x{09} \x{a0}\x{0a}\x{0b}\x{0c}
+ ** Failers
+No match
+ \x09\x20\x{a0}\x0a\x0b
+No match
+
+/\H\h\V\v/8
+ \x{3001}\x{3000}\x{2030}\x{2028}
+ 0: \x{3001}\x{3000}\x{2030}\x{2028}
+ X\x{180e}X\x{85}
+ 0: X\x{180e}X\x{85}
+ ** Failers
+No match
+ \x{2009} X\x0a
+No match
+
+/\H*\h+\V?\v{3,4}/8
+ \x{1680}\x{180e}\x{2007}X\x{2028}\x{2029}\x0c\x0d\x0a
+ 0: \x{1680}\x{180e}\x{2007}X\x{2028}\x{2029}\x{0c}\x{0d}
+ 1: \x{1680}\x{180e}\x{2007}X\x{2028}\x{2029}\x{0c}
+ \x09\x{205f}\x{a0}\x0a\x{2029}\x0c\x{2028}\x0a
+ 0: \x{09}\x{205f}\x{a0}\x{0a}\x{2029}\x{0c}\x{2028}
+ 1: \x{09}\x{205f}\x{a0}\x{0a}\x{2029}\x{0c}
+ \x09\x20\x{202f}\x0a\x0b\x0c
+ 0: \x{09} \x{202f}\x{0a}\x{0b}\x{0c}
+ ** Failers
+No match
+ \x09\x{200a}\x{a0}\x{2028}\x0b
+No match
+
+/a\Rb/I8<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ ** Failers
+No match
+ a\x{85}b
+No match
+ a\x0bb
+No match
+
+/a\Rb/I8<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\x0bb
+ 0: a\x{0b}b
+ ** Failers
+No match
+ a\x{85}b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/a\R?b/I8<bsr_anycrlf>
+Capturing subpattern count = 0
+Options: bsr_anycrlf utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ ** Failers
+No match
+ a\x{85}b
+No match
+ a\x0bb
+No match
+
+/a\R?b/I8<bsr_unicode>
+Capturing subpattern count = 0
+Options: bsr_unicode utf8
+First char = 'a'
+Need char = 'b'
+ a\rb
+ 0: a\x{0d}b
+ a\nb
+ 0: a\x{0a}b
+ a\r\nb
+ 0: a\x{0d}\x{0a}b
+ a\x{85}b
+ 0: a\x{85}b
+ a\x0bb
+ 0: a\x{0b}b
+ ** Failers
+No match
+ a\x{85}b\<bsr_anycrlf>
+No match
+ a\x0bb\<bsr_anycrlf>
+No match
+
+/ End of testinput 8 /
diff --git a/lib/stdlib/test/re_SUITE_data/testoutput9 b/lib/stdlib/test/re_SUITE_data/testoutput9
new file mode 100644
index 0000000000..acaeb398dd
--- /dev/null
+++ b/lib/stdlib/test/re_SUITE_data/testoutput9
@@ -0,0 +1,1643 @@
+/\pL\P{Nd}/8
+ AB
+ 0: AB
+ *** Failers
+ 0: Fa
+ A0
+No match
+ 00
+No match
+
+/\X./8
+ AB
+ 0: AB
+ A\x{300}BC
+ 0: A\x{300}B
+ A\x{300}\x{301}\x{302}BC
+ 0: A\x{300}\x{301}\x{302}B
+ *** Failers
+ 0: **
+ \x{300}
+No match
+
+/\X\X/8
+ ABC
+ 0: AB
+ A\x{300}B\x{300}\x{301}C
+ 0: A\x{300}B\x{300}\x{301}
+ A\x{300}\x{301}\x{302}BC
+ 0: A\x{300}\x{301}\x{302}B
+ *** Failers
+ 0: **
+ \x{300}
+No match
+
+/^\pL+/8
+ abcd
+ 0: abcd
+ 1: abc
+ 2: ab
+ 3: a
+ a
+ 0: a
+ *** Failers
+No match
+
+/^\PL+/8
+ 1234
+ 0: 1234
+ 1: 123
+ 2: 12
+ 3: 1
+ =
+ 0: =
+ *** Failers
+ 0: ***
+ 1: ***
+ 2: **
+ 3: *
+ abcd
+No match
+
+/^\X+/8
+ abcdA\x{300}\x{301}\x{302}
+ 0: abcdA\x{300}\x{301}\x{302}
+ 1: abcd
+ 2: abc
+ 3: ab
+ 4: a
+ A\x{300}\x{301}\x{302}
+ 0: A\x{300}\x{301}\x{302}
+ A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}
+ 0: A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}
+ 1: A\x{300}\x{301}\x{302}
+ a
+ 0: a
+ *** Failers
+ 0: *** Failers
+ 1: *** Failer
+ 2: *** Faile
+ 3: *** Fail
+ 4: *** Fai
+ 5: *** Fa
+ 6: *** F
+ 7: ***
+ 8: ***
+ 9: **
+10: *
+ \x{300}\x{301}\x{302}
+No match
+
+/\X?abc/8
+ abc
+ 0: abc
+ A\x{300}abc
+ 0: A\x{300}abc
+ A\x{300}\x{301}\x{302}A\x{300}A\x{300}A\x{300}abcxyz
+ 0: A\x{300}abc
+ \x{300}abc
+ 0: abc
+ *** Failers
+No match
+
+/^\X?abc/8
+ abc
+ 0: abc
+ A\x{300}abc
+ 0: A\x{300}abc
+ *** Failers
+No match
+ A\x{300}\x{301}\x{302}A\x{300}A\x{300}A\x{300}abcxyz
+No match
+ \x{300}abc
+No match
+
+/\X*abc/8
+ abc
+ 0: abc
+ A\x{300}abc
+ 0: A\x{300}abc
+ A\x{300}\x{301}\x{302}A\x{300}A\x{300}A\x{300}abcxyz
+ 0: A\x{300}\x{301}\x{302}A\x{300}A\x{300}A\x{300}abc
+ \x{300}abc
+ 0: abc
+ *** Failers
+No match
+
+/^\X*abc/8
+ abc
+ 0: abc
+ A\x{300}abc
+ 0: A\x{300}abc
+ A\x{300}\x{301}\x{302}A\x{300}A\x{300}A\x{300}abcxyz
+ 0: A\x{300}\x{301}\x{302}A\x{300}A\x{300}A\x{300}abc
+ *** Failers
+No match
+ \x{300}abc
+No match
+
+/^\pL?=./8
+ A=b
+ 0: A=b
+ =c
+ 0: =c
+ *** Failers
+No match
+ 1=2
+No match
+ AAAA=b
+No match
+
+/^\pL*=./8
+ AAAA=b
+ 0: AAAA=b
+ =c
+ 0: =c
+ *** Failers
+No match
+ 1=2
+No match
+
+/^\X{2,3}X/8
+ A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}X
+ 0: A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}X
+ A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}X
+ 0: A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}X
+ *** Failers
+No match
+ X
+No match
+ A\x{300}\x{301}\x{302}X
+No match
+ A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}A\x{300}\x{301}\x{302}X
+No match
+
+/^\pC\pL\pM\pN\pP\pS\pZ</8
+ \x7f\x{c0}\x{30f}\x{660}\x{66c}\x{f01}\x{1680}<
+ 0: \x{7f}\x{c0}\x{30f}\x{660}\x{66c}\x{f01}\x{1680}<
+ \np\x{300}9!\$ <
+ 0: \x{0a}p\x{300}9!$ <
+ ** Failers
+No match
+ ap\x{300}9!\$ <
+No match
+
+/^\PC/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x7f
+No match
+
+/^\PL/8
+ 9
+ 0: 9
+ ** Failers
+ 0: *
+ \x{c0}
+No match
+
+/^\PM/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{30f}
+No match
+
+/^\PN/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{660}
+No match
+
+/^\PP/8
+ X
+ 0: X
+ ** Failers
+No match
+ \x{66c}
+No match
+
+/^\PS/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{f01}
+No match
+
+/^\PZ/8
+ X
+ 0: X
+ ** Failers
+ 0: *
+ \x{1680}
+No match
+
+/^\p{Cc}/8
+ \x{017}
+ 0: \x{17}
+ \x{09f}
+ 0: \x{9f}
+ ** Failers
+No match
+ \x{0600}
+No match
+
+/^\p{Cf}/8
+ \x{601}
+ 0: \x{601}
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Cn}/8
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Co}/8
+ \x{f8ff}
+ 0: \x{f8ff}
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Cs}/8
+ \?\x{dfff}
+ 0: \x{dfff}
+ ** Failers
+No match
+ \x{09f}
+No match
+
+/^\p{Ll}/8
+ a
+ 0: a
+ ** Failers
+No match
+ Z
+No match
+ \x{e000}
+No match
+
+/^\p{Lm}/8
+ \x{2b0}
+ 0: \x{2b0}
+ ** Failers
+No match
+ a
+No match
+
+/^\p{Lo}/8
+ \x{1bb}
+ 0: \x{1bb}
+ ** Failers
+No match
+ a
+No match
+ \x{2b0}
+No match
+
+/^\p{Lt}/8
+ \x{1c5}
+ 0: \x{1c5}
+ ** Failers
+No match
+ a
+No match
+ \x{2b0}
+No match
+
+/^\p{Lu}/8
+ A
+ 0: A
+ ** Failers
+No match
+ \x{2b0}
+No match
+
+/^\p{Mc}/8
+ \x{903}
+ 0: \x{903}
+ ** Failers
+No match
+ X
+No match
+ \x{300}
+No match
+
+/^\p{Me}/8
+ \x{488}
+ 0: \x{488}
+ ** Failers
+No match
+ X
+No match
+ \x{903}
+No match
+ \x{300}
+No match
+
+/^\p{Mn}/8
+ \x{300}
+ 0: \x{300}
+ ** Failers
+No match
+ X
+No match
+ \x{903}
+No match
+
+/^\p{Nd}+/8
+ 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}\x{666}\x{667}\x{668}\x{669}\x{66a}
+ 0: 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}\x{666}\x{667}\x{668}\x{669}
+ 1: 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}\x{666}\x{667}\x{668}
+ 2: 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}\x{666}\x{667}
+ 3: 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}\x{666}
+ 4: 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}\x{665}
+ 5: 0123456789\x{660}\x{661}\x{662}\x{663}\x{664}
+ 6: 0123456789\x{660}\x{661}\x{662}\x{663}
+ 7: 0123456789\x{660}\x{661}\x{662}
+ 8: 0123456789\x{660}\x{661}
+ 9: 0123456789\x{660}
+10: 0123456789
+11: 012345678
+12: 01234567
+13: 0123456
+14: 012345
+15: 01234
+16: 0123
+17: 012
+18: 01
+19: 0
+ \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}\x{6f6}\x{6f7}\x{6f8}\x{6f9}\x{6fa}
+ 0: \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}\x{6f6}\x{6f7}\x{6f8}\x{6f9}
+ 1: \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}\x{6f6}\x{6f7}\x{6f8}
+ 2: \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}\x{6f6}\x{6f7}
+ 3: \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}\x{6f6}
+ 4: \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}\x{6f5}
+ 5: \x{6f0}\x{6f1}\x{6f2}\x{6f3}\x{6f4}
+ 6: \x{6f0}\x{6f1}\x{6f2}\x{6f3}
+ 7: \x{6f0}\x{6f1}\x{6f2}
+ 8: \x{6f0}\x{6f1}
+ 9: \x{6f0}
+ \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}\x{96c}\x{96d}\x{96e}\x{96f}\x{970}
+ 0: \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}\x{96c}\x{96d}\x{96e}\x{96f}
+ 1: \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}\x{96c}\x{96d}\x{96e}
+ 2: \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}\x{96c}\x{96d}
+ 3: \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}\x{96c}
+ 4: \x{966}\x{967}\x{968}\x{969}\x{96a}\x{96b}
+ 5: \x{966}\x{967}\x{968}\x{969}\x{96a}
+ 6: \x{966}\x{967}\x{968}\x{969}
+ 7: \x{966}\x{967}\x{968}
+ 8: \x{966}\x{967}
+ 9: \x{966}
+ ** Failers
+No match
+ X
+No match
+
+/^\p{Nl}/8
+ \x{16ee}
+ 0: \x{16ee}
+ ** Failers
+No match
+ X
+No match
+ \x{966}
+No match
+
+/^\p{No}/8
+ \x{b2}
+ 0: \x{b2}
+ \x{b3}
+ 0: \x{b3}
+ ** Failers
+No match
+ X
+No match
+ \x{16ee}
+No match
+
+/^\p{Pc}/8
+ \x5f
+ 0: _
+ \x{203f}
+ 0: \x{203f}
+ ** Failers
+No match
+ X
+No match
+ -
+No match
+ \x{58a}
+No match
+
+/^\p{Pd}/8
+ -
+ 0: -
+ \x{58a}
+ 0: \x{58a}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Pe}/8
+ )
+ 0: )
+ ]
+ 0: ]
+ }
+ 0: }
+ \x{f3b}
+ 0: \x{f3b}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+ (
+No match
+ [
+No match
+ {
+No match
+ \x{f3c}
+No match
+
+/^\p{Pf}/8
+ \x{bb}
+ 0: \x{bb}
+ \x{2019}
+ 0: \x{2019}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Pi}/8
+ \x{ab}
+ 0: \x{ab}
+ \x{2018}
+ 0: \x{2018}
+ ** Failers
+No match
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Po}/8
+ !
+ 0: !
+ \x{37e}
+ 0: \x{37e}
+ ** Failers
+ 0: *
+ X
+No match
+ \x{203f}
+No match
+
+/^\p{Ps}/8
+ (
+ 0: (
+ [
+ 0: [
+ {
+ 0: {
+ \x{f3c}
+ 0: \x{f3c}
+ ** Failers
+No match
+ X
+No match
+ )
+No match
+ ]
+No match
+ }
+No match
+ \x{f3b}
+No match
+
+/^\p{Sc}+/8
+ $\x{a2}\x{a3}\x{a4}\x{a5}\x{a6}
+ 0: $\x{a2}\x{a3}\x{a4}\x{a5}
+ 1: $\x{a2}\x{a3}\x{a4}
+ 2: $\x{a2}\x{a3}
+ 3: $\x{a2}
+ 4: $
+ \x{9f2}
+ 0: \x{9f2}
+ ** Failers
+No match
+ X
+No match
+ \x{2c2}
+No match
+
+/^\p{Sk}/8
+ \x{2c2}
+ 0: \x{2c2}
+ ** Failers
+No match
+ X
+No match
+ \x{9f2}
+No match
+
+/^\p{Sm}+/8
+ +<|~\x{ac}\x{2044}
+ 0: +<|~\x{ac}\x{2044}
+ 1: +<|~\x{ac}
+ 2: +<|~
+ 3: +<|
+ 4: +<
+ 5: +
+ ** Failers
+No match
+ X
+No match
+ \x{9f2}
+No match
+
+/^\p{So}/8
+ \x{a6}
+ 0: \x{a6}
+ \x{482}
+ 0: \x{482}
+ ** Failers
+No match
+ X
+No match
+ \x{9f2}
+No match
+
+/^\p{Zl}/8
+ \x{2028}
+ 0: \x{2028}
+ ** Failers
+No match
+ X
+No match
+ \x{2029}
+No match
+
+/^\p{Zp}/8
+ \x{2029}
+ 0: \x{2029}
+ ** Failers
+No match
+ X
+No match
+ \x{2028}
+No match
+
+/^\p{Zs}/8
+ \ \
+ 0:
+ \x{a0}
+ 0: \x{a0}
+ \x{1680}
+ 0: \x{1680}
+ \x{180e}
+ 0: \x{180e}
+ \x{2000}
+ 0: \x{2000}
+ \x{2001}
+ 0: \x{2001}
+ ** Failers
+No match
+ \x{2028}
+No match
+ \x{200d}
+No match
+
+/\p{Nd}+(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+ 2: \x{660}\x{661}\x{662}
+
+/\p{Nd}+?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+ 2: \x{660}\x{661}\x{662}
+
+/\p{Nd}{2,}(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+
+/\p{Nd}{2,}?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+
+/\p{Nd}*(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+ 2: \x{660}\x{661}\x{662}
+ 3: \x{660}\x{661}
+
+/\p{Nd}*?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+ 2: \x{660}\x{661}\x{662}
+ 3: \x{660}\x{661}
+
+/\p{Nd}{2}(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}A
+
+/\p{Nd}{2,3}(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+
+/\p{Nd}{2,3}?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+ 1: \x{660}\x{661}\x{662}A
+
+/\p{Nd}?(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}
+ 1: \x{660}\x{661}
+
+/\p{Nd}??(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}
+ 1: \x{660}\x{661}
+
+/\p{Nd}*+(..)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}AB
+
+/\p{Nd}*+(...)/8
+ \x{660}\x{661}\x{662}ABC
+ 0: \x{660}\x{661}\x{662}ABC
+
+/\p{Nd}*+(....)/8
+ ** Failers
+ 0: ** F
+ \x{660}\x{661}\x{662}ABC
+No match
+
+/\p{Lu}/8i
+ A
+ 0: A
+ a\x{10a0}B
+ 0: \x{10a0}
+ ** Failers
+ 0: F
+ a
+No match
+ \x{1d00}
+No match
+
+/\p{^Lu}/8i
+ 1234
+ 0: 1
+ ** Failers
+ 0: *
+ ABC
+No match
+
+/\P{Lu}/8i
+ 1234
+ 0: 1
+ ** Failers
+ 0: *
+ ABC
+No match
+
+/(?<=A\p{Nd})XYZ/8
+ A2XYZ
+ 0: XYZ
+ 123A5XYZPQR
+ 0: XYZ
+ ABA\x{660}XYZpqr
+ 0: XYZ
+ ** Failers
+No match
+ AXYZ
+No match
+ XYZ
+No match
+
+/(?<!\pL)XYZ/8
+ 1XYZ
+ 0: XYZ
+ AB=XYZ..
+ 0: XYZ
+ XYZ
+ 0: XYZ
+ ** Failers
+No match
+ WXYZ
+No match
+
+/[\p{Nd}]/8
+ 1234
+ 0: 1
+
+/[\p{Nd}+-]+/8
+ 1234
+ 0: 1234
+ 1: 123
+ 2: 12
+ 3: 1
+ 12-34
+ 0: 12-34
+ 1: 12-3
+ 2: 12-
+ 3: 12
+ 4: 1
+ 12+\x{661}-34
+ 0: 12+\x{661}-34
+ 1: 12+\x{661}-3
+ 2: 12+\x{661}-
+ 3: 12+\x{661}
+ 4: 12+
+ 5: 12
+ 6: 1
+ ** Failers
+No match
+ abcd
+No match
+
+/[\P{Nd}]+/8
+ abcd
+ 0: abcd
+ 1: abc
+ 2: ab
+ 3: a
+ ** Failers
+ 0: ** Failers
+ 1: ** Failer
+ 2: ** Faile
+ 3: ** Fail
+ 4: ** Fai
+ 5: ** Fa
+ 6: ** F
+ 7: **
+ 8: **
+ 9: *
+ 1234
+No match
+
+/\D+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many subsidiary matches
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 5: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 6: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 7: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 8: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 9: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+10: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+13: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+14: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+15: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+16: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+17: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+18: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+20: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+21: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/\P{Nd}+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many subsidiary matches
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 5: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 6: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 7: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 8: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 9: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+10: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+13: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+14: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+15: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+16: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+17: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+18: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+20: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+21: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/[\D]+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many subsidiary matches
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 5: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 6: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 7: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 8: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 9: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+10: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+13: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+14: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+15: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+16: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+17: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+18: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+20: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+21: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/[\P{Nd}]+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many subsidiary matches
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 5: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 6: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 7: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 8: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 9: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+10: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+13: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+14: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+15: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+16: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+17: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+18: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+20: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+21: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/[\D\P{Nd}]+/8
+ 11111111111111111111111111111111111111111111111111111111111111111111111
+No match
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+Matched, but too many subsidiary matches
+ 0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 2: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 3: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 4: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 5: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 6: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 7: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 8: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ 9: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+10: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+13: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+14: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+15: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+16: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+17: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+18: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+20: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+21: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+/\pL/8
+ a
+ 0: a
+ A
+ 0: A
+
+/\pL/8i
+ a
+ 0: a
+ A
+ 0: A
+
+/\p{Lu}/8
+ A
+ 0: A
+ aZ
+ 0: Z
+ ** Failers
+ 0: F
+ abc
+No match
+
+/\p{Lu}/8i
+ A
+ 0: A
+ aZ
+ 0: Z
+ ** Failers
+ 0: F
+ abc
+No match
+
+/\p{Ll}/8
+ a
+ 0: a
+ Az
+ 0: z
+ ** Failers
+ 0: a
+ ABC
+No match
+
+/\p{Ll}/8i
+ a
+ 0: a
+ Az
+ 0: z
+ ** Failers
+ 0: a
+ ABC
+No match
+
+/^\x{c0}$/8i
+ \x{c0}
+ 0: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+
+/^\x{e0}$/8i
+ \x{c0}
+ 0: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8
+ A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 0: A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ ** Failers
+No match
+ a\x{391}\x{10427}\x{ff3a}\x{1fb0}
+No match
+ A\x{3b1}\x{10427}\x{ff3a}\x{1fb0}
+No match
+ A\x{391}\x{1044F}\x{ff3a}\x{1fb0}
+No match
+ A\x{391}\x{10427}\x{ff5a}\x{1fb0}
+No match
+ A\x{391}\x{10427}\x{ff3a}\x{1fb8}
+No match
+
+/A\x{391}\x{10427}\x{ff3a}\x{1fb0}/8i
+ A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 0: A\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ a\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ 0: a\x{391}\x{10427}\x{ff3a}\x{1fb0}
+ A\x{3b1}\x{10427}\x{ff3a}\x{1fb0}
+ 0: A\x{3b1}\x{10427}\x{ff3a}\x{1fb0}
+ A\x{391}\x{1044F}\x{ff3a}\x{1fb0}
+ 0: A\x{391}\x{1044f}\x{ff3a}\x{1fb0}
+ A\x{391}\x{10427}\x{ff5a}\x{1fb0}
+ 0: A\x{391}\x{10427}\x{ff5a}\x{1fb0}
+ A\x{391}\x{10427}\x{ff3a}\x{1fb8}
+ 0: A\x{391}\x{10427}\x{ff3a}\x{1fb8}
+
+/\x{391}+/8i
+ \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}
+ 0: \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}
+ 1: \x{391}\x{3b1}\x{3b1}\x{3b1}
+ 2: \x{391}\x{3b1}\x{3b1}
+ 3: \x{391}\x{3b1}
+ 4: \x{391}
+
+/\x{391}{3,5}(.)/8i
+ \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}X
+ 0: \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}X
+ 1: \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}
+ 2: \x{391}\x{3b1}\x{3b1}\x{3b1}
+
+/\x{391}{3,5}?(.)/8i
+ \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}X
+ 0: \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}X
+ 1: \x{391}\x{3b1}\x{3b1}\x{3b1}\x{391}
+ 2: \x{391}\x{3b1}\x{3b1}\x{3b1}
+
+/[\x{391}\x{ff3a}]/8i
+ \x{391}
+ 0: \x{391}
+ \x{ff3a}
+ 0: \x{ff3a}
+ \x{3b1}
+ 0: \x{3b1}
+ \x{ff5a}
+ 0: \x{ff5a}
+
+/[\x{c0}\x{391}]/8i
+ \x{c0}
+ 0: \x{c0}
+ \x{e0}
+ 0: \x{e0}
+
+/[\x{105}-\x{109}]/8i
+ \x{104}
+ 0: \x{104}
+ \x{105}
+ 0: \x{105}
+ \x{109}
+ 0: \x{109}
+ ** Failers
+No match
+ \x{100}
+No match
+ \x{10a}
+No match
+
+/[z-\x{100}]/8i
+ Z
+ 0: Z
+ z
+ 0: z
+ \x{39c}
+ 0: \x{39c}
+ \x{178}
+ 0: \x{178}
+ |
+ 0: |
+ \x{80}
+ 0: \x{80}
+ \x{ff}
+ 0: \x{ff}
+ \x{100}
+ 0: \x{100}
+ \x{101}
+ 0: \x{101}
+ ** Failers
+No match
+ \x{102}
+No match
+ Y
+No match
+ y
+No match
+
+/[z-\x{100}]/8i
+
+/^\X/8
+ A
+ 0: A
+ A\x{300}BC
+ 0: A\x{300}
+ A\x{300}\x{301}\x{302}BC
+ 0: A\x{300}\x{301}\x{302}
+ *** Failers
+ 0: *
+ \x{300}
+No match
+
+/^[\X]/8
+ X123
+ 0: X
+ *** Failers
+No match
+ AXYZ
+No match
+
+/^(\X*)C/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A\x{300}\x{301}\x{302}BC
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 1: A\x{300}\x{301}\x{302}BC
+
+/^(\X*?)C/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A\x{300}\x{301}\x{302}BC
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 1: A\x{300}\x{301}\x{302}BC
+
+/^(\X*)(.)/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A\x{300}\x{301}\x{302}BCA
+ 1: A\x{300}\x{301}\x{302}BC
+ 2: A\x{300}\x{301}\x{302}B
+ 3: A
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 1: A\x{300}\x{301}\x{302}BCA
+ 2: A\x{300}\x{301}\x{302}BC
+ 3: A\x{300}\x{301}\x{302}B
+ 4: A
+
+/^(\X*?)(.)/8
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}
+ 0: A\x{300}\x{301}\x{302}BCA
+ 1: A\x{300}\x{301}\x{302}BC
+ 2: A\x{300}\x{301}\x{302}B
+ 3: A
+ A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 0: A\x{300}\x{301}\x{302}BCA\x{300}\x{301}C
+ 1: A\x{300}\x{301}\x{302}BCA
+ 2: A\x{300}\x{301}\x{302}BC
+ 3: A\x{300}\x{301}\x{302}B
+ 4: A
+
+/^\X(.)/8
+ *** Failers
+ 0: **
+ A\x{300}\x{301}\x{302}
+No match
+
+/^\X{2,3}(.)/8
+ A\x{300}\x{301}B\x{300}X
+ 0: A\x{300}\x{301}B\x{300}X
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}
+ 0: A\x{300}\x{301}B\x{300}C
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}X
+ 0: A\x{300}\x{301}B\x{300}C\x{300}\x{301}X
+ 1: A\x{300}\x{301}B\x{300}C
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}DA\x{300}X
+ 0: A\x{300}\x{301}B\x{300}C\x{300}\x{301}D
+ 1: A\x{300}\x{301}B\x{300}C
+
+/^\X{2,3}?(.)/8
+ A\x{300}\x{301}B\x{300}X
+ 0: A\x{300}\x{301}B\x{300}X
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}
+ 0: A\x{300}\x{301}B\x{300}C
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}X
+ 0: A\x{300}\x{301}B\x{300}C\x{300}\x{301}X
+ 1: A\x{300}\x{301}B\x{300}C
+ A\x{300}\x{301}B\x{300}C\x{300}\x{301}DA\x{300}X
+ 0: A\x{300}\x{301}B\x{300}C\x{300}\x{301}D
+ 1: A\x{300}\x{301}B\x{300}C
+
+/^\pN{2,3}X/
+ 12X
+ 0: 12X
+ 123X
+ 0: 123X
+ *** Failers
+No match
+ X
+No match
+ 1X
+No match
+ 1234X
+No match
+
+/\x{100}/i8
+ \x{100}
+ 0: \x{100}
+ \x{101}
+ 0: \x{101}
+
+/^\p{Han}+/8
+ \x{2e81}\x{3007}\x{2f804}\x{31a0}
+ 0: \x{2e81}\x{3007}\x{2f804}
+ 1: \x{2e81}\x{3007}
+ 2: \x{2e81}
+ ** Failers
+No match
+ \x{2e7f}
+No match
+
+/^\P{Katakana}+/8
+ \x{3105}
+ 0: \x{3105}
+ ** Failers
+ 0: ** Failers
+ 1: ** Failer
+ 2: ** Faile
+ 3: ** Fail
+ 4: ** Fai
+ 5: ** Fa
+ 6: ** F
+ 7: **
+ 8: **
+ 9: *
+ \x{30ff}
+No match
+
+/^[\p{Arabic}]/8
+ \x{06e9}
+ 0: \x{6e9}
+ \x{060b}
+ 0: \x{60b}
+ ** Failers
+No match
+ X\x{06e9}
+No match
+
+/^[\P{Yi}]/8
+ \x{2f800}
+ 0: \x{2f800}
+ ** Failers
+ 0: *
+ \x{a014}
+No match
+ \x{a4c6}
+No match
+
+/^\p{Any}X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ X
+No match
+
+/^\P{Any}X/8
+ ** Failers
+No match
+ AX
+No match
+
+/^\p{Any}?X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ ABXYZ
+No match
+
+/^\P{Any}?X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ ABXYZ
+No match
+
+/^\p{Any}+X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+ XYZ
+No match
+
+/^\P{Any}+X/8
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+ XYZ
+No match
+
+/^\p{Any}*X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+
+/^\P{Any}*X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+
+/^[\p{Any}]X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ X
+No match
+
+/^[\P{Any}]X/8
+ ** Failers
+No match
+ AX
+No match
+
+/^[\p{Any}]?X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ ** Failers
+No match
+ ABXYZ
+No match
+
+/^[\P{Any}]?X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ ABXYZ
+No match
+
+/^[\p{Any}]+X/8
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+ XYZ
+No match
+
+/^[\P{Any}]+X/8
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+ XYZ
+No match
+
+/^[\p{Any}]*X/8
+ XYZ
+ 0: X
+ AXYZ
+ 0: AX
+ \x{1234}XYZ
+ 0: \x{1234}X
+ A\x{1234}XYZ
+ 0: A\x{1234}X
+ ** Failers
+No match
+
+/^[\P{Any}]*X/8
+ XYZ
+ 0: X
+ ** Failers
+No match
+ AXYZ
+No match
+ \x{1234}XYZ
+No match
+ A\x{1234}XYZ
+No match
+
+/^\p{Any}{3,5}?/8
+ abcdefgh
+ 0: abcde
+ 1: abcd
+ 2: abc
+ \x{1234}\n\r\x{3456}xyz
+ 0: \x{1234}\x{0a}\x{0d}\x{3456}x
+ 1: \x{1234}\x{0a}\x{0d}\x{3456}
+ 2: \x{1234}\x{0a}\x{0d}
+
+/^\p{Any}{3,5}/8
+ abcdefgh
+ 0: abcde
+ 1: abcd
+ 2: abc
+ \x{1234}\n\r\x{3456}xyz
+ 0: \x{1234}\x{0a}\x{0d}\x{3456}x
+ 1: \x{1234}\x{0a}\x{0d}\x{3456}
+ 2: \x{1234}\x{0a}\x{0d}
+
+/^\P{Any}{3,5}?/8
+ ** Failers
+No match
+ abcdefgh
+No match
+ \x{1234}\n\r\x{3456}xyz
+No match
+
+/^\p{L&}X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ \x{1c5}XY
+ 0: \x{1c5}X
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^[\p{L&}]X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ \x{1c5}XY
+ 0: \x{1c5}X
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^\p{L&}+X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEXypqreX
+ 1: abcDEX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^[\p{L&}]+X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEXypqreX
+ 1: abcDEX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^\p{L&}+?X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEXypqreX
+ 1: abcDEX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^[\p{L&}]+?X/8
+ AXY
+ 0: AX
+ aXY
+ 0: aX
+ AbcdeXyz
+ 0: AbcdeX
+ \x{1c5}AbXY
+ 0: \x{1c5}AbX
+ abcDEXypqreXlmn
+ 0: abcDEXypqreX
+ 1: abcDEX
+ ** Failers
+No match
+ \x{1bb}XY
+No match
+ \x{2b0}XY
+No match
+ !XY
+No match
+
+/^\P{L&}X/8
+ !XY
+ 0: !X
+ \x{1bb}XY
+ 0: \x{1bb}X
+ \x{2b0}XY
+ 0: \x{2b0}X
+ ** Failers
+No match
+ \x{1c5}XY
+No match
+ AXY
+No match
+
+/^[\P{L&}]X/8
+ !XY
+ 0: !X
+ \x{1bb}XY
+ 0: \x{1bb}X
+ \x{2b0}XY
+ 0: \x{2b0}X
+ ** Failers
+No match
+ \x{1c5}XY
+No match
+ AXY
+No match
+
+/^\x{023a}+?(\x{0130}+)/8i
+ \x{023a}\x{2c65}\x{0130}
+ 0: \x{23a}\x{2c65}\x{130}
+
+/^\x{023a}+([^X])/8i
+ \x{023a}\x{2c65}X
+ 0: \x{23a}\x{2c65}
+
+/Check property support in non-UTF-8 mode/
+
+/\p{L}{4}/
+ 123abcdefg
+ 0: abcd
+ 123abc\xc4\xc5zz
+ 0: abc\xc4
+
+/ End /
diff --git a/lib/stdlib/test/re_testoutput1_replacement_test.erl b/lib/stdlib/test/re_testoutput1_replacement_test.erl
new file mode 100644
index 0000000000..b20db3f9c3
--- /dev/null
+++ b/lib/stdlib/test/re_testoutput1_replacement_test.erl
@@ -0,0 +1,18596 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(re_testoutput1_replacement_test).
+-compile(export_all).
+-include("test_server.hrl").
+%% This file is generated by running run_pcre_tests:gen_repl_test("re_SUITE_data/testoutput1")
+run() ->
+?line <<"WkCthe quick brown foxtthe quick brown foxjthe quick brown foxPpfmthe quick brown foxthe quick brown foxy">> = iolist_to_binary(re:replace("the quick brown fox","the quick brown fox","WkC&t\\1\\1&j&Ppfm&&y",[])),
+?line <<"WkCthe quick brown foxtthe quick brown foxjthe quick brown foxPpfmthe quick brown foxthe quick brown foxy">> = iolist_to_binary(re:replace("the quick brown fox","the quick brown fox","WkC&t\\1\\1&j&Ppfm&&y",[global])),
+?line <<"The quick brown FOX">> = iolist_to_binary(re:replace("The quick brown FOX","the quick brown fox","in&&CSwx",[])),
+?line <<"The quick brown FOX">> = iolist_to_binary(re:replace("The quick brown FOX","the quick brown fox","in&&CSwx",[global])),
+?line <<"What do you know about ORtMvuTRHtLthe quick brown foxiYthe quick brown foxGi?">> = iolist_to_binary(re:replace("What do you know about the quick brown fox?","the quick brown fox","ORtMvuTRHtL&iY&Gi",[])),
+?line <<"What do you know about ORtMvuTRHtLthe quick brown foxiYthe quick brown foxGi?">> = iolist_to_binary(re:replace("What do you know about the quick brown fox?","the quick brown fox","ORtMvuTRHtL&iY&Gi",[global])),
+?line <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(re:replace("What do you know about THE QUICK BROWN FOX?","the quick brown fox","\\1nfTnvooMaxHdXgGO",[])),
+?line <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(re:replace("What do you know about THE QUICK BROWN FOX?","the quick brown fox","\\1nfTnvooMaxHdXgGO",[global])),
+?line <<"hSniFQTqBU">> = iolist_to_binary(re:replace("the quick brown fox","The quick brown fox","hSniFQTqBU",[caseless])),
+?line <<"hSniFQTqBU">> = iolist_to_binary(re:replace("the quick brown fox","The quick brown fox","hSniFQTqBU",[caseless,
+ global])),
+?line <<"q">> = iolist_to_binary(re:replace("The quick brown FOX","The quick brown fox","q",[caseless])),
+?line <<"q">> = iolist_to_binary(re:replace("The quick brown FOX","The quick brown fox","q",[caseless,
+ global])),
+?line <<"What do you know about uJnke?">> = iolist_to_binary(re:replace("What do you know about the quick brown fox?","The quick brown fox","uJ\\1nke",[caseless])),
+?line <<"What do you know about uJnke?">> = iolist_to_binary(re:replace("What do you know about the quick brown fox?","The quick brown fox","uJ\\1nke",[caseless,
+ global])),
+?line <<"What do you know about VRUTHE QUICK BROWN FOXYgJqUVfiTHE QUICK BROWN FOXqb?">> = iolist_to_binary(re:replace("What do you know about THE QUICK BROWN FOX?","The quick brown fox","VRU&YgJqUVfi&\\1qb",[caseless])),
+?line <<"What do you know about VRUTHE QUICK BROWN FOXYgJqUVfiTHE QUICK BROWN FOXqb?">> = iolist_to_binary(re:replace("What do you know about THE QUICK BROWN FOX?","The quick brown fox","VRU&YgJqUVfi&\\1qb",[caseless,
+ global])),
+?line <<"jeUmEaUYOfHpPURCabcd
+ 9;$\\?caxyz">> = iolist_to_binary(re:replace("abcd
+ 9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz","jeUmEaUYOfHpPURC&",[])),
+?line <<"jeUmEaUYOfHpPURCabcd
+ 9;$\\?caxyz">> = iolist_to_binary(re:replace("abcd
+ 9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz","jeUmEaUYOfHpPURC&",[global])),
+?line <<"YVh">> = iolist_to_binary(re:replace("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","YVh",[])),
+?line <<"YVh">> = iolist_to_binary(re:replace("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","YVh",[global])),
+?line <<"wDCxbqXSqpabxyzpqrrrabbxyyyypqAzzX">> = iolist_to_binary(re:replace("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","wDCxbqXSqp&X",[])),
+?line <<"wDCxbqXSqpabxyzpqrrrabbxyyyypqAzzX">> = iolist_to_binary(re:replace("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","wDCxbqXSqp&X",[global])),
+?line <<"XOnDbhuPYPfGm">> = iolist_to_binary(re:replace("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1XOnDbhuP\\1Y\\1PfGm",[])),
+?line <<"XOnDbhuPYPfGm">> = iolist_to_binary(re:replace("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1XOnDbhuP\\1Y\\1PfGm",[global])),
+?line <<"vgswmIcA">> = iolist_to_binary(re:replace("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","vgswmI\\1cA",[])),
+?line <<"vgswmIcA">> = iolist_to_binary(re:replace("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","vgswmI\\1cA",[global])),
+?line <<"YaaaabxyzpqrrrabbxyyyypqAzzOXXRaa">> = iolist_to_binary(re:replace("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","Y&OXXRaa",[])),
+?line <<"YaaaabxyzpqrrrabbxyyyypqAzzOXXRaa">> = iolist_to_binary(re:replace("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","Y&OXXRaa",[global])),
+?line <<"CAeqsXe">> = iolist_to_binary(re:replace("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1CAeq\\1s\\1Xe",[])),
+?line <<"CAeqsXe">> = iolist_to_binary(re:replace("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1CAeq\\1s\\1Xe",[global])),
+?line <<"cDLaApdgW">> = iolist_to_binary(re:replace("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","cDLaApdgW",[])),
+?line <<"cDLaApdgW">> = iolist_to_binary(re:replace("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","cDLaApdgW",[global])),
+?line <<"aLfXiUYS">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","aLf\\1XiUYS",[])),
+?line <<"aLfXiUYS">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","aLf\\1XiUYS",[global])),
+?line <<"aaabcxyzpqrrrabbxyyyypqAzzBcaaabcxyzpqrrrabbxyyyypqAzzDAyoYqGn">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1&Bc&DAyoYqGn",[])),
+?line <<"aaabcxyzpqrrrabbxyyyypqAzzBcaaabcxyzpqrrrabbxyyyypqAzzDAyoYqGn">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1&Bc&DAyoYqGn",[global])),
+?line <<"aaabcxyzpqrrrabbxyyyypqqAzzijaaabcxyzpqrrrabbxyyyypqqAzzdIBcB">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&ij&dI\\1BcB",[])),
+?line <<"aaabcxyzpqrrrabbxyyyypqqAzzijaaabcxyzpqrrrabbxyyyypqqAzzdIBcB">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&ij&dI\\1BcB",[global])),
+?line <<"qrxTuPSgEjNvkaaabcxyzpqrrrabbxyyyypqqqAzz">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1qrx\\1\\1TuPSgEjNvk&",[])),
+?line <<"qrxTuPSgEjNvkaaabcxyzpqrrrabbxyyyypqqqAzz">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1qrx\\1\\1TuPSgEjNvk&",[global])),
+?line <<"oWxyrN">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1oWx\\1y\\1rN",[])),
+?line <<"oWxyrN">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1oWx\\1y\\1rN",[global])),
+?line <<"TPbeAcarX">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1T\\1PbeAcarX",[])),
+?line <<"TPbeAcarX">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1T\\1PbeAcarX",[global])),
+?line <<"xWhhgaaabcxyzpqrrrabbxyyyypqqqqqqAzzsHcQaaabcxyzpqrrrabbxyyyypqqqqqqAzzAeU">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","xWh\\1hg&sHcQ&AeU",[])),
+?line <<"xWhhgaaabcxyzpqrrrabbxyyyypqqqqqqAzzsHcQaaabcxyzpqrrrabbxyyyypqqqqqqAzzAeU">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","xWh\\1hg&sHcQ&AeU",[global])),
+?line <<"HasuDgVdEpaaaabcxyzpqrrrabbxyyyypqAzz">> = iolist_to_binary(re:replace("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","Hasu\\1\\1DgV\\1dEp&",[])),
+?line <<"HasuDgVdEpaaaabcxyzpqrrrabbxyyyypqAzz">> = iolist_to_binary(re:replace("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","Hasu\\1\\1DgV\\1dEp&",[global])),
+?line <<"XWMcabxyzzpqrrrabbxyyyypqAzzIUK">> = iolist_to_binary(re:replace("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","XWMc&\\1IUK",[])),
+?line <<"XWMcabxyzzpqrrrabbxyyyypqAzzIUK">> = iolist_to_binary(re:replace("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","XWMc&\\1IUK",[global])),
+?line <<"UBljDAPnposGdT">> = iolist_to_binary(re:replace("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","UBljDAPnposGdT",[])),
+?line <<"UBljDAPnposGdT">> = iolist_to_binary(re:replace("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","UBljDAPnposGdT",[global])),
+?line <<"boTxGt">> = iolist_to_binary(re:replace("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","boTxGt",[])),
+?line <<"boTxGt">> = iolist_to_binary(re:replace("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","boTxGt",[global])),
+?line <<"mnBWBx">> = iolist_to_binary(re:replace("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","mnB\\1\\1WBx",[])),
+?line <<"mnBWBx">> = iolist_to_binary(re:replace("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","mnB\\1\\1WBx",[global])),
+?line <<"lcgIVpnY">> = iolist_to_binary(re:replace("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","lcgIVpnY\\1",[])),
+?line <<"lcgIVpnY">> = iolist_to_binary(re:replace("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","lcgIVpnY\\1",[global])),
+?line <<"aabcxyzzzpqrrrabbxyyyypqAzznutiQsQaabcxyzzzpqrrrabbxyyyypqAzzokm">> = iolist_to_binary(re:replace("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&n\\1\\1utiQsQ&o\\1km",[])),
+?line <<"aabcxyzzzpqrrrabbxyyyypqAzznutiQsQaabcxyzzzpqrrrabbxyyyypqAzzokm">> = iolist_to_binary(re:replace("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&n\\1\\1utiQsQ&o\\1km",[global])),
+?line <<"rshbaaabcxyzzzzpqrrrabbxyyyypqAzzyCaaabcxyzzzzpqrrrabbxyyyypqAzzFuphTaaabcxyzzzzpqrrrabbxyyyypqAzzb">> = iolist_to_binary(re:replace("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","rshb&yC&FuphT&b",[])),
+?line <<"rshbaaabcxyzzzzpqrrrabbxyyyypqAzzyCaaabcxyzzzzpqrrrabbxyyyypqAzzFuphTaaabcxyzzzzpqrrrabbxyyyypqAzzb">> = iolist_to_binary(re:replace("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","rshb&yC&FuphT&b",[global])),
+?line <<"aaaabcxyzzzzpqrrrabbxyyyypqAzzDpUaaaabcxyzzzzpqrrrabbxyyyypqAzzWxWLwIQUnS">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&DpU&WxW\\1LwIQUnS",[])),
+?line <<"aaaabcxyzzzzpqrrrabbxyyyypqAzzDpUaaaabcxyzzzzpqrrrabbxyyyypqAzzWxWLwIQUnS">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&DpU&WxW\\1LwIQUnS",[global])),
+?line <<"maaaabcxyzzzzpqrrrabbbxyyyypqAzzaaaabcxyzzzzpqrrrabbbxyyyypqAzzdV">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","m\\1&&dV",[])),
+?line <<"maaaabcxyzzzzpqrrrabbbxyyyypqAzzaaaabcxyzzzzpqrrrabbbxyyyypqAzzdV">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","m\\1&&dV",[global])),
+?line <<"qABAquMpjbGrEQl">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","qAB\\1A\\1quMpjbGrEQl",[])),
+?line <<"qABAquMpjbGrEQl">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","qAB\\1A\\1quMpjbGrEQl",[global])),
+?line <<"XEmwtsQHVhnjgxANa">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","XEmw\\1tsQHVhn\\1jgx\\1ANa",[])),
+?line <<"XEmwtsQHVhnjgxANa">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","XEmw\\1tsQHVhn\\1jgx\\1ANa",[global])),
+?line <<"agMMGdMqblL">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","agMMGdMq\\1blL\\1",[])),
+?line <<"agMMGdMqblL">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","agMMGdMq\\1blL\\1",[global])),
+?line <<">>>EFCLJKUGJXH">> = iolist_to_binary(re:replace(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","EFCLJKUGJXH",[])),
+?line <<">>>EFCLJKUGJXH">> = iolist_to_binary(re:replace(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","EFCLJKUGJXH",[global])),
+?line <<">IW">> = iolist_to_binary(re:replace(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1IW",[])),
+?line <<">IW">> = iolist_to_binary(re:replace(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1IW",[global])),
+?line <<">>>>uiixDteuEA">> = iolist_to_binary(re:replace(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1uiixD\\1teuEA",[])),
+?line <<">>>>uiixDteuEA">> = iolist_to_binary(re:replace(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","\\1uiixD\\1teuEA",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","vayXo\\1eo\\1H",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","vayXo\\1eo\\1H",[global])),
+?line <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(re:replace("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","nJK",[])),
+?line <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(re:replace("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","nJK",[global])),
+?line <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(re:replace("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","msrV\\1",[])),
+?line <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(re:replace("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","msrV\\1",[global])),
+?line <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(re:replace("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","nVAVmEdY&rfTu",[])),
+?line <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(re:replace("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","nVAVmEdY&rfTu",[global])),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&G&\\1eyiM",[])),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","&G&\\1eyiM",[global])),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","AIYq\\1nFUePr&s\\1s",[])),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(re:replace("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","AIYq\\1nFUePr&s\\1s",[global])),
+?line <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","G\\1\\1XF\\1XcTk&D&Vd",[])),
+?line <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(re:replace("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz","G\\1\\1XF\\1XcTk&D&Vd",[global])),
+?line <<"NMabcYpabcatqabczzabczzReBo">> = iolist_to_binary(re:replace("abczz","^(abc){1,2}zz","NM\\1Yp\\1atq&&ReBo",[])),
+?line <<"NMabcYpabcatqabczzabczzReBo">> = iolist_to_binary(re:replace("abczz","^(abc){1,2}zz","NM\\1Yp\\1atq&&ReBo",[global])),
+?line <<"PabcabczzabcsubxWpWrabcCabcabczzBDsb">> = iolist_to_binary(re:replace("abcabczz","^(abc){1,2}zz","P&\\1subxWpWr\\1C&BDsb",[])),
+?line <<"PabcabczzabcsubxWpWrabcCabcabczzBDsb">> = iolist_to_binary(re:replace("abcabczz","^(abc){1,2}zz","P&\\1subxWpWr\\1C&BDsb",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(abc){1,2}zz","u&MSQ\\1MwaXNEFxKb\\1v\\1r",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(abc){1,2}zz","u&MSQ\\1MwaXNEFxKb\\1v\\1r",[global])),
+?line <<"zz">> = iolist_to_binary(re:replace("zz","^(abc){1,2}zz","&XIfLMiKJsG&X",[])),
+?line <<"zz">> = iolist_to_binary(re:replace("zz","^(abc){1,2}zz","&XIfLMiKJsG&X",[global])),
+?line <<"abcabcabczz">> = iolist_to_binary(re:replace("abcabcabczz","^(abc){1,2}zz","k\\1S&UT&HR\\1\\1MHKIh&mv",[])),
+?line <<"abcabcabczz">> = iolist_to_binary(re:replace("abcabcabczz","^(abc){1,2}zz","k\\1S&UT&HR\\1\\1MHKIh&mv",[global])),
+?line <<">>abczz">> = iolist_to_binary(re:replace(">>abczz","^(abc){1,2}zz","R\\1GKCdWtC&\\1ULoV",[])),
+?line <<">>abczz">> = iolist_to_binary(re:replace(">>abczz","^(abc){1,2}zz","R\\1GKCdWtC&\\1ULoV",[global])),
+?line <<"bcbcvRuVbMbcbcOSuQfOJbc">> = iolist_to_binary(re:replace("bc","^(b+?|a){1,2}?c","&bcvRuV\\1M&&OSuQfOJ\\1c",[])),
+?line <<"bcbcvRuVbMbcbcOSuQfOJbc">> = iolist_to_binary(re:replace("bc","^(b+?|a){1,2}?c","&bcvRuV\\1M&&OSuQfOJ\\1c",[global])),
+?line <<"L">> = iolist_to_binary(re:replace("bbc","^(b+?|a){1,2}?c","L",[])),
+?line <<"L">> = iolist_to_binary(re:replace("bbc","^(b+?|a){1,2}?c","L",[global])),
+?line <<"pFFAeA">> = iolist_to_binary(re:replace("bbbc","^(b+?|a){1,2}?c","pFFAeA",[])),
+?line <<"pFFAeA">> = iolist_to_binary(re:replace("bbbc","^(b+?|a){1,2}?c","pFFAeA",[global])),
+?line <<"OpEK">> = iolist_to_binary(re:replace("bac","^(b+?|a){1,2}?c","OpEK",[])),
+?line <<"OpEK">> = iolist_to_binary(re:replace("bac","^(b+?|a){1,2}?c","OpEK",[global])),
+?line <<"bbacQeabbactAVaalybbacdBwbbac">> = iolist_to_binary(re:replace("bbac","^(b+?|a){1,2}?c","&Qe\\1&tAV\\1\\1ly&dBw&",[])),
+?line <<"bbacQeabbactAVaalybbacdBwbbac">> = iolist_to_binary(re:replace("bbac","^(b+?|a){1,2}?c","&Qe\\1&tAV\\1\\1ly&dBw&",[global])),
+?line <<"atVuxqLMNgBtlattKaT">> = iolist_to_binary(re:replace("aac","^(b+?|a){1,2}?c","\\1tVuxqLMNgBtl\\1ttKaT",[])),
+?line <<"atVuxqLMNgBtlattKaT">> = iolist_to_binary(re:replace("aac","^(b+?|a){1,2}?c","\\1tVuxqLMNgBtl\\1ttKaT",[global])),
+?line <<"Y">> = iolist_to_binary(re:replace("abbbbbbbbbbbc","^(b+?|a){1,2}?c","Y",[])),
+?line <<"Y">> = iolist_to_binary(re:replace("abbbbbbbbbbbc","^(b+?|a){1,2}?c","Y",[global])),
+?line <<"bbbbbbbbbbbactDhmKI">> = iolist_to_binary(re:replace("bbbbbbbbbbbac","^(b+?|a){1,2}?c","&tDhmKI",[])),
+?line <<"bbbbbbbbbbbactDhmKI">> = iolist_to_binary(re:replace("bbbbbbbbbbbac","^(b+?|a){1,2}?c","&tDhmKI",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(b+?|a){1,2}?c","qVVVR&C\\1&etAsmWh",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(b+?|a){1,2}?c","qVVVR&C\\1&etAsmWh",[global])),
+?line <<"aaac">> = iolist_to_binary(re:replace("aaac","^(b+?|a){1,2}?c","jD\\1&q&KCdV&RhT",[])),
+?line <<"aaac">> = iolist_to_binary(re:replace("aaac","^(b+?|a){1,2}?c","jD\\1&q&KCdV&RhT",[global])),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(re:replace("abbbbbbbbbbbac","^(b+?|a){1,2}?c","\\1jT&nRG",[])),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(re:replace("abbbbbbbbbbbac","^(b+?|a){1,2}?c","\\1jT&nRG",[global])),
+?line <<"bcXfvbIubUfhmIRev">> = iolist_to_binary(re:replace("bc","^(b+|a){1,2}c","&Xfv\\1IubUfhmIRev",[])),
+?line <<"bcXfvbIubUfhmIRev">> = iolist_to_binary(re:replace("bc","^(b+|a){1,2}c","&Xfv\\1IubUfhmIRev",[global])),
+?line <<"EbbbbcwbbWbbcVuAOqROLkbbcwbbc">> = iolist_to_binary(re:replace("bbc","^(b+|a){1,2}c","E\\1&w\\1W&VuAOqROLk&w&",[])),
+?line <<"EbbbbcwbbWbbcVuAOqROLkbbcwbbc">> = iolist_to_binary(re:replace("bbc","^(b+|a){1,2}c","E\\1&w\\1W&VuAOqROLk&w&",[global])),
+?line <<"I">> = iolist_to_binary(re:replace("bbbc","^(b+|a){1,2}c","I",[])),
+?line <<"I">> = iolist_to_binary(re:replace("bbbc","^(b+|a){1,2}c","I",[global])),
+?line <<"dctSELQIPb">> = iolist_to_binary(re:replace("bac","^(b+|a){1,2}c","dctSELQIPb",[])),
+?line <<"dctSELQIPb">> = iolist_to_binary(re:replace("bac","^(b+|a){1,2}c","dctSELQIPb",[global])),
+?line <<"kbdarKarpbbacbbacbDO">> = iolist_to_binary(re:replace("bbac","^(b+|a){1,2}c","kbd\\1rK\\1rp&&bDO",[])),
+?line <<"kbdarKarpbbacbbacbDO">> = iolist_to_binary(re:replace("bbac","^(b+|a){1,2}c","kbd\\1rK\\1rp&&bDO",[global])),
+?line <<"aFIlpaasKQWsFRadP">> = iolist_to_binary(re:replace("aac","^(b+|a){1,2}c","\\1FIlp\\1\\1sKQWsFR\\1dP",[])),
+?line <<"aFIlpaasKQWsFRadP">> = iolist_to_binary(re:replace("aac","^(b+|a){1,2}c","\\1FIlp\\1\\1sKQWsFR\\1dP",[global])),
+?line <<"bbbbbbbbbbbfbbbbbbbbbbbHo">> = iolist_to_binary(re:replace("abbbbbbbbbbbc","^(b+|a){1,2}c","\\1f\\1Ho",[])),
+?line <<"bbbbbbbbbbbfbbbbbbbbbbbHo">> = iolist_to_binary(re:replace("abbbbbbbbbbbc","^(b+|a){1,2}c","\\1f\\1Ho",[global])),
+?line <<"bbbbbbbbbbbacOuqvbbbbbbbbbbbaclVwIa">> = iolist_to_binary(re:replace("bbbbbbbbbbbac","^(b+|a){1,2}c","&Ouqv&lVwI\\1",[])),
+?line <<"bbbbbbbbbbbacOuqvbbbbbbbbbbbaclVwIa">> = iolist_to_binary(re:replace("bbbbbbbbbbbac","^(b+|a){1,2}c","&Ouqv&lVwI\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(b+|a){1,2}c","\\1sSSP\\1Tw&R&byI\\1TN",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(b+|a){1,2}c","\\1sSSP\\1Tw&R&byI\\1TN",[global])),
+?line <<"aaac">> = iolist_to_binary(re:replace("aaac","^(b+|a){1,2}c","lBeqV\\1ygD\\1oXXqs",[])),
+?line <<"aaac">> = iolist_to_binary(re:replace("aaac","^(b+|a){1,2}c","lBeqV\\1ygD\\1oXXqs",[global])),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(re:replace("abbbbbbbbbbbac","^(b+|a){1,2}c","HVL\\1kIEVrx\\1hyh\\1&eY\\1R",[])),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(re:replace("abbbbbbbbbbbac","^(b+|a){1,2}c","HVL\\1kIEVrx\\1hyh\\1&eY\\1R",[global])),
+?line <<"ScHjJbbcrs">> = iolist_to_binary(re:replace("bbc","^(b+|a){1,2}?bc","ScHjJ&rs",[])),
+?line <<"ScHjJbbcrs">> = iolist_to_binary(re:replace("bbc","^(b+|a){1,2}?bc","ScHjJ&rs",[global])),
+?line <<"xbabcCeyVbabcbaXLUCoov">> = iolist_to_binary(re:replace("babc","^(b*|ba){1,2}?bc","x&CeyV&\\1XLUCoov",[])),
+?line <<"xbabcCeyVbabcbaXLUCoov">> = iolist_to_binary(re:replace("babc","^(b*|ba){1,2}?bc","x&CeyV&\\1XLUCoov",[global])),
+?line <<"HbPsbrWbbabcba">> = iolist_to_binary(re:replace("bbabc","^(b*|ba){1,2}?bc","HbPsbrW&\\1",[])),
+?line <<"HbPsbrWbbabcba">> = iolist_to_binary(re:replace("bbabc","^(b*|ba){1,2}?bc","HbPsbrW&\\1",[global])),
+?line <<"IpbababcRBSkmAw">> = iolist_to_binary(re:replace("bababc","^(b*|ba){1,2}?bc","Ip&RBSkmAw",[])),
+?line <<"IpbababcRBSkmAw">> = iolist_to_binary(re:replace("bababc","^(b*|ba){1,2}?bc","Ip&RBSkmAw",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(b*|ba){1,2}?bc","e\\1\\1Tx",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(b*|ba){1,2}?bc","e\\1\\1Tx",[global])),
+?line <<"bababbc">> = iolist_to_binary(re:replace("bababbc","^(b*|ba){1,2}?bc","llyNxYhfjNKiNYM\\1&Ko",[])),
+?line <<"bababbc">> = iolist_to_binary(re:replace("bababbc","^(b*|ba){1,2}?bc","llyNxYhfjNKiNYM\\1&Ko",[global])),
+?line <<"babababc">> = iolist_to_binary(re:replace("babababc","^(b*|ba){1,2}?bc","R&TKD\\1JpYJGqtjf",[])),
+?line <<"babababc">> = iolist_to_binary(re:replace("babababc","^(b*|ba){1,2}?bc","R&TKD\\1JpYJGqtjf",[global])),
+?line <<"babcfHFubaafbYLoJba">> = iolist_to_binary(re:replace("babc","^(ba|b*){1,2}?bc","&fHFu\\1afbYLoJ\\1",[])),
+?line <<"babcfHFubaafbYLoJba">> = iolist_to_binary(re:replace("babc","^(ba|b*){1,2}?bc","&fHFu\\1afbYLoJ\\1",[global])),
+?line <<"ewhbbabc">> = iolist_to_binary(re:replace("bbabc","^(ba|b*){1,2}?bc","ewh&",[])),
+?line <<"ewhbbabc">> = iolist_to_binary(re:replace("bbabc","^(ba|b*){1,2}?bc","ewh&",[global])),
+?line <<"L">> = iolist_to_binary(re:replace("bababc","^(ba|b*){1,2}?bc","L",[])),
+?line <<"L">> = iolist_to_binary(re:replace("bababc","^(ba|b*){1,2}?bc","L",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(ba|b*){1,2}?bc","\\1wv",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(ba|b*){1,2}?bc","\\1wv",[global])),
+?line <<"bababbc">> = iolist_to_binary(re:replace("bababbc","^(ba|b*){1,2}?bc","&F",[])),
+?line <<"bababbc">> = iolist_to_binary(re:replace("bababbc","^(ba|b*){1,2}?bc","&F",[global])),
+?line <<"babababc">> = iolist_to_binary(re:replace("babababc","^(ba|b*){1,2}?bc","Yk",[])),
+?line <<"babababc">> = iolist_to_binary(re:replace("babababc","^(ba|b*){1,2}?bc","Yk",[global])),
+?line <<"rdI;zTYuI;zcdx">> = iolist_to_binary(re:replace(";z","^\\ca\\cA\\c[\\c{\\c:","rdI&TYuI&cdx\\1",[])),
+?line <<"rdI;zTYuI;zcdx">> = iolist_to_binary(re:replace(";z","^\\ca\\cA\\c[\\c{\\c:","rdI&TYuI&cdx\\1",[global])),
+?line <<"XgOhHATXLthing">> = iolist_to_binary(re:replace("athing","^[ab\\]cde]","XgO\\1hHATXL",[])),
+?line <<"XgOhHATXLthing">> = iolist_to_binary(re:replace("athing","^[ab\\]cde]","XgO\\1hHATXL",[global])),
+?line <<"xIBYFthing">> = iolist_to_binary(re:replace("bthing","^[ab\\]cde]","xIBYF",[])),
+?line <<"xIBYFthing">> = iolist_to_binary(re:replace("bthing","^[ab\\]cde]","xIBYF",[global])),
+?line <<"]lthing">> = iolist_to_binary(re:replace("]thing","^[ab\\]cde]","&l",[])),
+?line <<"]lthing">> = iolist_to_binary(re:replace("]thing","^[ab\\]cde]","&l",[global])),
+?line <<"qbsthing">> = iolist_to_binary(re:replace("cthing","^[ab\\]cde]","\\1qbs",[])),
+?line <<"qbsthing">> = iolist_to_binary(re:replace("cthing","^[ab\\]cde]","\\1qbs",[global])),
+?line <<"gyOCYsthing">> = iolist_to_binary(re:replace("dthing","^[ab\\]cde]","gyOCYs",[])),
+?line <<"gyOCYsthing">> = iolist_to_binary(re:replace("dthing","^[ab\\]cde]","gyOCYs",[global])),
+?line <<"DrUmPIeSUthing">> = iolist_to_binary(re:replace("ething","^[ab\\]cde]","DrUmP\\1IeSU",[])),
+?line <<"DrUmPIeSUthing">> = iolist_to_binary(re:replace("ething","^[ab\\]cde]","DrUmP\\1IeSU",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[ab\\]cde]","Xi\\1luACtdK",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[ab\\]cde]","Xi\\1luACtdK",[global])),
+?line <<"fthing">> = iolist_to_binary(re:replace("fthing","^[ab\\]cde]","&u&Y\\1obNLU\\1tyonhH",[])),
+?line <<"fthing">> = iolist_to_binary(re:replace("fthing","^[ab\\]cde]","&u&Y\\1obNLU\\1tyonhH",[global])),
+?line <<"[thing">> = iolist_to_binary(re:replace("[thing","^[ab\\]cde]","TVEAE&ooeuGQJgKnrqW",[])),
+?line <<"[thing">> = iolist_to_binary(re:replace("[thing","^[ab\\]cde]","TVEAE&ooeuGQJgKnrqW",[global])),
+?line <<"\\thing">> = iolist_to_binary(re:replace("\\thing","^[ab\\]cde]","vRby\\1&",[])),
+?line <<"\\thing">> = iolist_to_binary(re:replace("\\thing","^[ab\\]cde]","vRby\\1&",[global])),
+?line <<"]n]ExaxasbKqYi]CHthing">> = iolist_to_binary(re:replace("]thing","^[]cde]","&n&Ex\\1axa\\1sbKqYi&CH",[])),
+?line <<"]n]ExaxasbKqYi]CHthing">> = iolist_to_binary(re:replace("]thing","^[]cde]","&n&Ex\\1axa\\1sbKqYi&CH",[global])),
+?line <<"nLnsthing">> = iolist_to_binary(re:replace("cthing","^[]cde]","nLns",[])),
+?line <<"nLnsthing">> = iolist_to_binary(re:replace("cthing","^[]cde]","nLns",[global])),
+?line <<"dOETLdnanQKLkkVthing">> = iolist_to_binary(re:replace("dthing","^[]cde]","\\1&OET\\1L&nanQKLkkV",[])),
+?line <<"dOETLdnanQKLkkVthing">> = iolist_to_binary(re:replace("dthing","^[]cde]","\\1&OET\\1L&nanQKLkkV",[global])),
+?line <<"UKthing">> = iolist_to_binary(re:replace("ething","^[]cde]","UK",[])),
+?line <<"UKthing">> = iolist_to_binary(re:replace("ething","^[]cde]","UK",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[]cde]","OEN&h&RDky",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[]cde]","OEN&h&RDky",[global])),
+?line <<"athing">> = iolist_to_binary(re:replace("athing","^[]cde]","ADIYuoD\\1PwBWBN",[])),
+?line <<"athing">> = iolist_to_binary(re:replace("athing","^[]cde]","ADIYuoD\\1PwBWBN",[global])),
+?line <<"fthing">> = iolist_to_binary(re:replace("fthing","^[]cde]","H&UGGJFd\\1Ys\\1xgEg",[])),
+?line <<"fthing">> = iolist_to_binary(re:replace("fthing","^[]cde]","H&UGGJFd\\1Ys\\1xgEg",[global])),
+?line <<"yipHChvHfthing">> = iolist_to_binary(re:replace("fthing","^[^ab\\]cde]","yi\\1pHC\\1hvH&",[])),
+?line <<"yipHChvHfthing">> = iolist_to_binary(re:replace("fthing","^[^ab\\]cde]","yi\\1pHC\\1hvH&",[global])),
+?line <<"pthing">> = iolist_to_binary(re:replace("[thing","^[^ab\\]cde]","p",[])),
+?line <<"pthing">> = iolist_to_binary(re:replace("[thing","^[^ab\\]cde]","p",[global])),
+?line <<"nvbthing">> = iolist_to_binary(re:replace("\\thing","^[^ab\\]cde]","n\\1v\\1b",[])),
+?line <<"nvbthing">> = iolist_to_binary(re:replace("\\thing","^[^ab\\]cde]","n\\1v\\1b",[global])),
+?line <<"o*r*CQayoALTVo** Failers">> = iolist_to_binary(re:replace("*** Failers","^[^ab\\]cde]","o&r&CQa\\1yoALTVo\\1",[])),
+?line <<"o*r*CQayoALTVo** Failers">> = iolist_to_binary(re:replace("*** Failers","^[^ab\\]cde]","o&r&CQa\\1yoALTVo\\1",[global])),
+?line <<"athing">> = iolist_to_binary(re:replace("athing","^[^ab\\]cde]","k\\1&MCQ",[])),
+?line <<"athing">> = iolist_to_binary(re:replace("athing","^[^ab\\]cde]","k\\1&MCQ",[global])),
+?line <<"bthing">> = iolist_to_binary(re:replace("bthing","^[^ab\\]cde]","XKeFQEPnv",[])),
+?line <<"bthing">> = iolist_to_binary(re:replace("bthing","^[^ab\\]cde]","XKeFQEPnv",[global])),
+?line <<"]thing">> = iolist_to_binary(re:replace("]thing","^[^ab\\]cde]","\\1",[])),
+?line <<"]thing">> = iolist_to_binary(re:replace("]thing","^[^ab\\]cde]","\\1",[global])),
+?line <<"cthing">> = iolist_to_binary(re:replace("cthing","^[^ab\\]cde]","NU",[])),
+?line <<"cthing">> = iolist_to_binary(re:replace("cthing","^[^ab\\]cde]","NU",[global])),
+?line <<"dthing">> = iolist_to_binary(re:replace("dthing","^[^ab\\]cde]","GVUo\\1m&I",[])),
+?line <<"dthing">> = iolist_to_binary(re:replace("dthing","^[^ab\\]cde]","GVUo\\1m&I",[global])),
+?line <<"ething">> = iolist_to_binary(re:replace("ething","^[^ab\\]cde]","Ms\\1&GwiawlCHng&EEX",[])),
+?line <<"ething">> = iolist_to_binary(re:replace("ething","^[^ab\\]cde]","Ms\\1&GwiawlCHng&EEX",[global])),
+?line <<"lqtFwcAYthing">> = iolist_to_binary(re:replace("athing","^[^]cde]","lqtFwcAY",[])),
+?line <<"lqtFwcAYthing">> = iolist_to_binary(re:replace("athing","^[^]cde]","lqtFwcAY",[global])),
+?line <<"Fxtpjthing">> = iolist_to_binary(re:replace("fthing","^[^]cde]","Fxt\\1pj",[])),
+?line <<"Fxtpjthing">> = iolist_to_binary(re:replace("fthing","^[^]cde]","Fxt\\1pj",[global])),
+?line <<"xT*oD*U** Failers">> = iolist_to_binary(re:replace("*** Failers","^[^]cde]","xT&oD\\1&U",[])),
+?line <<"xT*oD*U** Failers">> = iolist_to_binary(re:replace("*** Failers","^[^]cde]","xT&oD\\1&U",[global])),
+?line <<"]thing">> = iolist_to_binary(re:replace("]thing","^[^]cde]","R\\1M&\\1m",[])),
+?line <<"]thing">> = iolist_to_binary(re:replace("]thing","^[^]cde]","R\\1M&\\1m",[global])),
+?line <<"cthing">> = iolist_to_binary(re:replace("cthing","^[^]cde]","\\1tF\\1WOFN&fB",[])),
+?line <<"cthing">> = iolist_to_binary(re:replace("cthing","^[^]cde]","\\1tF\\1WOFN&fB",[global])),
+?line <<"dthing">> = iolist_to_binary(re:replace("dthing","^[^]cde]","y\\1I&MoqRPG&GQa\\1l",[])),
+?line <<"dthing">> = iolist_to_binary(re:replace("dthing","^[^]cde]","y\\1I&MoqRPG&GQa\\1l",[global])),
+?line <<"ething">> = iolist_to_binary(re:replace("ething","^[^]cde]","AsxwUn\\1GqkWNdgRJk",[])),
+?line <<"ething">> = iolist_to_binary(re:replace("ething","^[^]cde]","AsxwUn\\1GqkWNdgRJk",[global])),
+?line <<"RornKmOnaFr�tWgtW">> = iolist_to_binary(re:replace("�","^\\�","R\\1o\\1r\\1nKmOnaFr&tWgtW",[])),
+?line <<"RornKmOnaFr�tWgtW">> = iolist_to_binary(re:replace("�","^\\�","R\\1o\\1r\\1nKmOnaFr&tWgtW",[global])),
+?line <<"ufbmbfOYuK�wf�E�dx">> = iolist_to_binary(re:replace("�","^�","ufbmbfOYuK&wf&E&\\1dx",[])),
+?line <<"ufbmbfOYuK�wf�E�dx">> = iolist_to_binary(re:replace("�","^�","ufbmbfOYuK&wf&E&\\1dx",[global])),
+?line <<"oAdJme0jw">> = iolist_to_binary(re:replace("0","^[0-9]+$","oAdJme\\1&jw",[])),
+?line <<"oAdJme0jw">> = iolist_to_binary(re:replace("0","^[0-9]+$","oAdJme\\1&jw",[global])),
+?line <<"1aoKN">> = iolist_to_binary(re:replace("1","^[0-9]+$","&aoKN",[])),
+?line <<"1aoKN">> = iolist_to_binary(re:replace("1","^[0-9]+$","&aoKN",[global])),
+?line <<"tIHn">> = iolist_to_binary(re:replace("2","^[0-9]+$","tIHn\\1",[])),
+?line <<"tIHn">> = iolist_to_binary(re:replace("2","^[0-9]+$","tIHn\\1",[global])),
+?line <<"wgA3cJbrrCyMvMXM3">> = iolist_to_binary(re:replace("3","^[0-9]+$","wgA&cJbrrCyMv\\1M\\1XM&",[])),
+?line <<"wgA3cJbrrCyMvMXM3">> = iolist_to_binary(re:replace("3","^[0-9]+$","wgA&cJbrrCyMv\\1M\\1XM&",[global])),
+?line <<"huUpJ">> = iolist_to_binary(re:replace("4","^[0-9]+$","huUpJ",[])),
+?line <<"huUpJ">> = iolist_to_binary(re:replace("4","^[0-9]+$","huUpJ",[global])),
+?line <<"Fe5F5">> = iolist_to_binary(re:replace("5","^[0-9]+$","F\\1e&F&",[])),
+?line <<"Fe5F5">> = iolist_to_binary(re:replace("5","^[0-9]+$","F\\1e&F&",[global])),
+?line <<"HJ">> = iolist_to_binary(re:replace("6","^[0-9]+$","HJ",[])),
+?line <<"HJ">> = iolist_to_binary(re:replace("6","^[0-9]+$","HJ",[global])),
+?line <<"e">> = iolist_to_binary(re:replace("7","^[0-9]+$","e",[])),
+?line <<"e">> = iolist_to_binary(re:replace("7","^[0-9]+$","e",[global])),
+?line <<"Fmds88NtMX">> = iolist_to_binary(re:replace("8","^[0-9]+$","F\\1mds&&Nt\\1MX",[])),
+?line <<"Fmds88NtMX">> = iolist_to_binary(re:replace("8","^[0-9]+$","F\\1mds&&Nt\\1MX",[global])),
+?line <<"99cE9SqMch">> = iolist_to_binary(re:replace("9","^[0-9]+$","&&cE&\\1SqMch",[])),
+?line <<"99cE9SqMch">> = iolist_to_binary(re:replace("9","^[0-9]+$","&&cE&\\1SqMch",[global])),
+?line <<"xhR">> = iolist_to_binary(re:replace("10","^[0-9]+$","xhR",[])),
+?line <<"xhR">> = iolist_to_binary(re:replace("10","^[0-9]+$","xhR",[global])),
+?line <<"j100">> = iolist_to_binary(re:replace("100","^[0-9]+$","j&",[])),
+?line <<"j100">> = iolist_to_binary(re:replace("100","^[0-9]+$","j&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[0-9]+$","SR&tOYsEgJid&hfCF",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[0-9]+$","SR&tOYsEgJid&hfCF",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","^[0-9]+$","JK&",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","^[0-9]+$","JK&",[global])),
+?line <<"rXjkB">> = iolist_to_binary(re:replace("enter","^.*nter","rXjkB",[])),
+?line <<"rXjkB">> = iolist_to_binary(re:replace("enter","^.*nter","rXjkB",[global])),
+?line <<"oOEtqV">> = iolist_to_binary(re:replace("inter","^.*nter","oO\\1EtqV",[])),
+?line <<"oOEtqV">> = iolist_to_binary(re:replace("inter","^.*nter","oO\\1EtqV",[global])),
+?line <<"">> = iolist_to_binary(re:replace("uponter","^.*nter","\\1",[])),
+?line <<"">> = iolist_to_binary(re:replace("uponter","^.*nter","\\1",[global])),
+?line <<"SODUcOgFnbuQEN">> = iolist_to_binary(re:replace("xxx0","^xxx[0-9]+$","\\1\\1SODU\\1\\1cOgFnbuQEN",[])),
+?line <<"SODUcOgFnbuQEN">> = iolist_to_binary(re:replace("xxx0","^xxx[0-9]+$","\\1\\1SODU\\1\\1cOgFnbuQEN",[global])),
+?line <<"hsacOxxx1234kudxxx1234sEIrIdI">> = iolist_to_binary(re:replace("xxx1234","^xxx[0-9]+$","hsacO\\1&kud&s\\1EIrIdI\\1",[])),
+?line <<"hsacOxxx1234kudxxx1234sEIrIdI">> = iolist_to_binary(re:replace("xxx1234","^xxx[0-9]+$","hsacO\\1&kud&s\\1EIrIdI\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^xxx[0-9]+$","e",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^xxx[0-9]+$","e",[global])),
+?line <<"xxx">> = iolist_to_binary(re:replace("xxx","^xxx[0-9]+$","oSBYD&M",[])),
+?line <<"xxx">> = iolist_to_binary(re:replace("xxx","^xxx[0-9]+$","oSBYD&M",[global])),
+?line <<"x123HgGUYCx123PowSBtYb">> = iolist_to_binary(re:replace("x123","^.+[0-9][0-9][0-9]$","&HgGUYC&PowSBtY\\1b",[])),
+?line <<"x123HgGUYCx123PowSBtYb">> = iolist_to_binary(re:replace("x123","^.+[0-9][0-9][0-9]$","&HgGUYC&PowSBtY\\1b",[global])),
+?line <<"mEVxx123SNuYPQIaJ">> = iolist_to_binary(re:replace("xx123","^.+[0-9][0-9][0-9]$","mE\\1V\\1&SNuYPQIa\\1J",[])),
+?line <<"mEVxx123SNuYPQIaJ">> = iolist_to_binary(re:replace("xx123","^.+[0-9][0-9][0-9]$","mE\\1V\\1&SNuYPQIa\\1J",[global])),
+?line <<"l123456O123456">> = iolist_to_binary(re:replace("123456","^.+[0-9][0-9][0-9]$","l&O&",[])),
+?line <<"l123456O123456">> = iolist_to_binary(re:replace("123456","^.+[0-9][0-9][0-9]$","l&O&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^.+[0-9][0-9][0-9]$","MX&hxvs",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^.+[0-9][0-9][0-9]$","MX&hxvs",[global])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^.+[0-9][0-9][0-9]$","RBYgTXkgO&TLdWqjEUps",[])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^.+[0-9][0-9][0-9]$","RBYgTXkgO&TLdWqjEUps",[global])),
+?line <<"fgx1234">> = iolist_to_binary(re:replace("x1234","^.+[0-9][0-9][0-9]$","fg&",[])),
+?line <<"fgx1234">> = iolist_to_binary(re:replace("x1234","^.+[0-9][0-9][0-9]$","fg&",[global])),
+?line <<"FLbkgx123RdPrD">> = iolist_to_binary(re:replace("x123","^.+?[0-9][0-9][0-9]$","FLbkg&R\\1dPrD",[])),
+?line <<"FLbkgx123RdPrD">> = iolist_to_binary(re:replace("x123","^.+?[0-9][0-9][0-9]$","FLbkg&R\\1dPrD",[global])),
+?line <<"C">> = iolist_to_binary(re:replace("xx123","^.+?[0-9][0-9][0-9]$","C",[])),
+?line <<"C">> = iolist_to_binary(re:replace("xx123","^.+?[0-9][0-9][0-9]$","C",[global])),
+?line <<"oWjVDKTAoaLU">> = iolist_to_binary(re:replace("123456","^.+?[0-9][0-9][0-9]$","oW\\1jVDK\\1TAoaLU",[])),
+?line <<"oWjVDKTAoaLU">> = iolist_to_binary(re:replace("123456","^.+?[0-9][0-9][0-9]$","oW\\1jVDK\\1TAoaLU",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^.+?[0-9][0-9][0-9]$","xA&\\1sIV",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^.+?[0-9][0-9][0-9]$","xA&\\1sIV",[global])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^.+?[0-9][0-9][0-9]$","ONX&",[])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^.+?[0-9][0-9][0-9]$","ONX&",[global])),
+?line <<"oLgQtiSmGx1234wqbuoRB">> = iolist_to_binary(re:replace("x1234","^.+?[0-9][0-9][0-9]$","\\1oLgQtiSm\\1\\1G&wqbuoRB",[])),
+?line <<"oLgQtiSmGx1234wqbuoRB">> = iolist_to_binary(re:replace("x1234","^.+?[0-9][0-9][0-9]$","\\1oLgQtiSm\\1\\1G&wqbuoRB",[global])),
+?line <<"mcpuCvaabc!pqr=apquxz.ixr.zzz.ac.ukgabc!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","mcpuCva&g&",[])),
+?line <<"mcpuCvaabc!pqr=apquxz.ixr.zzz.ac.ukgabc!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","mcpuCva&g&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","J&a\\1HaapJjylMMyeA\\1e",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","J&a\\1HaapJjylMMyeA\\1e",[global])),
+?line <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","FOOFh&nQLU\\1c",[])),
+?line <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","FOOFh&nQLU\\1c",[global])),
+?line <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","H\\1G",[])),
+?line <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","H\\1G",[global])),
+?line <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","&tBS&",[])),
+?line <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(re:replace("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","&tBS&",[global])),
+?line <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(re:replace("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","Gd&\\1CN\\1",[])),
+?line <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(re:replace("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$","Gd&\\1CN\\1",[global])),
+?line <<"Well, we need a colonGTdcJbUips: somewhere">> = iolist_to_binary(re:replace("Well, we need a colon: somewhere",":","GTdc\\1J\\1bUips\\1&",[])),
+?line <<"Well, we need a colonGTdcJbUips: somewhere">> = iolist_to_binary(re:replace("Well, we need a colon: somewhere",":","GTdc\\1J\\1bUips\\1&",[global])),
+?line <<"*** Fail if we don't">> = iolist_to_binary(re:replace("*** Fail if we don't",":","d",[])),
+?line <<"*** Fail if we don't">> = iolist_to_binary(re:replace("*** Fail if we don't",":","d",[global])),
+?line <<"0abcDqVs0abc0abcptNR">> = iolist_to_binary(re:replace("0abc","([\\da-f:]+)$","\\1DqVs\\1\\1ptNR",[caseless])),
+?line <<"0abcDqVs0abc0abcptNR">> = iolist_to_binary(re:replace("0abc","([\\da-f:]+)$","\\1DqVs\\1\\1ptNR",[caseless,
+ global])),
+?line <<"abctJK">> = iolist_to_binary(re:replace("abc","([\\da-f:]+)$","&tJK",[caseless])),
+?line <<"abctJK">> = iolist_to_binary(re:replace("abc","([\\da-f:]+)$","&tJK",[caseless,
+ global])),
+?line <<"quighClnfedRB">> = iolist_to_binary(re:replace("fed","([\\da-f:]+)$","quighCln\\1RB",[caseless])),
+?line <<"quighClnfedRB">> = iolist_to_binary(re:replace("fed","([\\da-f:]+)$","quighCln\\1RB",[caseless,
+ global])),
+?line <<"ENd">> = iolist_to_binary(re:replace("E","([\\da-f:]+)$","\\1Nd",[caseless])),
+?line <<"ENd">> = iolist_to_binary(re:replace("E","([\\da-f:]+)$","\\1Nd",[caseless,
+ global])),
+?line <<"o::U::lkIj::XoRWPah::s">> = iolist_to_binary(re:replace("::","([\\da-f:]+)$","o&U&lkIj\\1XoRWPah&s",[caseless])),
+?line <<"o::U::lkIj::XoRWPah::s">> = iolist_to_binary(re:replace("::","([\\da-f:]+)$","o&U&lkIj\\1XoRWPah&s",[caseless,
+ global])),
+?line <<"ab5f03:12C0::932eONbt5f03:12C0::932ehnfLI5f03:12C0::932esqYx5f03:12C0::932e">> = iolist_to_binary(re:replace("5f03:12C0::932e","([\\da-f:]+)$","ab&ONbt\\1hnfLI\\1sqYx&",[caseless])),
+?line <<"ab5f03:12C0::932eONbt5f03:12C0::932ehnfLI5f03:12C0::932esqYx5f03:12C0::932e">> = iolist_to_binary(re:replace("5f03:12C0::932e","([\\da-f:]+)$","ab&ONbt\\1hnfLI\\1sqYx&",[caseless,
+ global])),
+?line <<"fed OAMdefijvdef">> = iolist_to_binary(re:replace("fed def","([\\da-f:]+)$","OAM\\1ijv&",[caseless])),
+?line <<"fed OAMdefijvdef">> = iolist_to_binary(re:replace("fed def","([\\da-f:]+)$","OAM\\1ijv&",[caseless,
+ global])),
+?line <<"Any old stuSVffaffeYffCjDlYffhWTMo">> = iolist_to_binary(re:replace("Any old stuff","([\\da-f:]+)$","SV&a\\1eY&CjDlY&hWTMo",[caseless])),
+?line <<"Any old stuSVffaffeYffCjDlYffhWTMo">> = iolist_to_binary(re:replace("Any old stuff","([\\da-f:]+)$","SV&a\\1eY&CjDlY&hWTMo",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","([\\da-f:]+)$","j&R\\1oXiR",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","([\\da-f:]+)$","j&R\\1oXiR",[caseless,
+ global])),
+?line <<"0zzz">> = iolist_to_binary(re:replace("0zzz","([\\da-f:]+)$","&HUuWH\\1&VEg",[caseless])),
+?line <<"0zzz">> = iolist_to_binary(re:replace("0zzz","([\\da-f:]+)$","&HUuWH\\1&VEg",[caseless,
+ global])),
+?line <<"gzzz">> = iolist_to_binary(re:replace("gzzz","([\\da-f:]+)$","l&n&p&DKUsLBFC",[caseless])),
+?line <<"gzzz">> = iolist_to_binary(re:replace("gzzz","([\\da-f:]+)$","l&n&p&DKUsLBFC",[caseless,
+ global])),
+?line <<"fed ">> = iolist_to_binary(re:replace("fed ","([\\da-f:]+)$","KuLffd&Y",[caseless])),
+?line <<"fed ">> = iolist_to_binary(re:replace("fed ","([\\da-f:]+)$","KuLffd&Y",[caseless,
+ global])),
+?line <<"Any old rubbish">> = iolist_to_binary(re:replace("Any old rubbish","([\\da-f:]+)$","Mid\\1",[caseless])),
+?line <<"Any old rubbish">> = iolist_to_binary(re:replace("Any old rubbish","([\\da-f:]+)$","Mid\\1",[caseless,
+ global])),
+?line <<"g.1.2.3WLxQ1s">> = iolist_to_binary(re:replace(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","g&WLxQ\\1s",[])),
+?line <<"g.1.2.3WLxQ1s">> = iolist_to_binary(re:replace(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","g&WLxQ\\1s",[global])),
+?line <<"12Oy">> = iolist_to_binary(re:replace("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","\\1Oy",[])),
+?line <<"12Oy">> = iolist_to_binary(re:replace("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","\\1Oy",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","eX\\1nRbXcnyEu",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","eX\\1nRbXcnyEu",[global])),
+?line <<".1.2.3333">> = iolist_to_binary(re:replace(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","&iIBylLhNq",[])),
+?line <<".1.2.3333">> = iolist_to_binary(re:replace(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","&iIBylLhNq",[global])),
+?line <<"1.2.3">> = iolist_to_binary(re:replace("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","\\1G&pFELr\\1\\1&\\1HPX",[])),
+?line <<"1.2.3">> = iolist_to_binary(re:replace("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","\\1G&pFELr\\1\\1&\\1HPX",[global])),
+?line <<"1234.2.3">> = iolist_to_binary(re:replace("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","&qtoJnh\\1h",[])),
+?line <<"1234.2.3">> = iolist_to_binary(re:replace("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$","&qtoJnh\\1h",[global])),
+?line <<"eaSBc111LbN">> = iolist_to_binary(re:replace("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","eaSBc\\1\\1\\1LbN",[])),
+?line <<"eaSBc111LbN">> = iolist_to_binary(re:replace("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","eaSBc\\1\\1\\1LbN",[global])),
+?line <<"1 IN SOA non-sp1 non-sp2 (yranl1 IN SOA non-sp1 non-sp2 (kU">> = iolist_to_binary(re:replace("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","&yranl&kU",[])),
+?line <<"1 IN SOA non-sp1 non-sp2 (yranl1 IN SOA non-sp1 non-sp2 (kU">> = iolist_to_binary(re:replace("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","&yranl&kU",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","W",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","W",[global])),
+?line <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(re:replace("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","UyNVyrR",[])),
+?line <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(re:replace("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$","UyNVyrR",[global])),
+?line <<"pbeGIhIlhvhv">> = iolist_to_binary(re:replace("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","pbeGIhIl\\1hvhv",[])),
+?line <<"pbeGIhIlhvhv">> = iolist_to_binary(re:replace("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","pbeGIhIl\\1hvhv",[global])),
+?line <<"gFDyTxSYOppWiRv">> = iolist_to_binary(re:replace("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","gFDyTxSYOppWiR\\1v",[])),
+?line <<"gFDyTxSYOppWiRv">> = iolist_to_binary(re:replace("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","gFDyTxSYOppWiR\\1v",[global])),
+?line <<"HSc2.vOMD">> = iolist_to_binary(re:replace("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","HSc&\\1vO\\1MD\\1",[])),
+?line <<"HSc2.vOMD">> = iolist_to_binary(re:replace("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","HSc&\\1vO\\1MD\\1",[global])),
+?line <<".pq-rd.pq-rG.pq-rUab-c.pq-r.HCGaTI">> = iolist_to_binary(re:replace("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","\\1d\\1G\\1U&HCGaTI",[])),
+?line <<".pq-rd.pq-rG.pq-rUab-c.pq-r.HCGaTI">> = iolist_to_binary(re:replace("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","\\1d\\1G\\1U&HCGaTI",[global])),
+?line <<"RwF">> = iolist_to_binary(re:replace("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","RwF",[])),
+?line <<"RwF">> = iolist_to_binary(re:replace("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","RwF",[global])),
+?line <<"oAJdUx-.y-.GMx-.y-.iBCRNx-.y-.wBW">> = iolist_to_binary(re:replace("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","oAJdU&GM&iBCRN&wBW",[])),
+?line <<"oAJdUx-.y-.GMx-.y-.iBCRNx-.y-.wBW">> = iolist_to_binary(re:replace("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","oAJdU&GM&iBCRN&wBW",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","wwA&\\1snkRSCcfnG&S",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","wwA&\\1snkRSCcfnG&S",[global])),
+?line <<"-abc.peq.">> = iolist_to_binary(re:replace("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","YaFu&cvVYqVy",[])),
+?line <<"-abc.peq.">> = iolist_to_binary(re:replace("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$","YaFu&cvVYqVy",[global])),
+?line <<"*.aXojEWCW">> = iolist_to_binary(re:replace("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","&\\1XojEWCW",[])),
+?line <<"*.aXojEWCW">> = iolist_to_binary(re:replace("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","&\\1XojEWCW",[global])),
+?line <<"djXR0-a0-aFXv*.b0-awEtv">> = iolist_to_binary(re:replace("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","djXR\\1\\1FXv&wEtv",[])),
+?line <<"djXR0-a0-aFXv*.b0-awEtv">> = iolist_to_binary(re:replace("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","djXR\\1\\1FXv&wEtv",[global])),
+?line <<"*.c3-b.cregb">> = iolist_to_binary(re:replace("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","&regb",[])),
+?line <<"*.c3-b.cregb">> = iolist_to_binary(re:replace("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","&regb",[global])),
+?line <<"Vlm">> = iolist_to_binary(re:replace("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","Vlm",[])),
+?line <<"Vlm">> = iolist_to_binary(re:replace("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","Vlm",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","Vxm\\1SKnM",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","Vxm\\1SKnM",[global])),
+?line <<"*.0">> = iolist_to_binary(re:replace("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","n\\1",[])),
+?line <<"*.0">> = iolist_to_binary(re:replace("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","n\\1",[global])),
+?line <<"*.a-">> = iolist_to_binary(re:replace("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","SbmRQSxio",[])),
+?line <<"*.a-">> = iolist_to_binary(re:replace("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","SbmRQSxio",[global])),
+?line <<"*.a-b.c-">> = iolist_to_binary(re:replace("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","xINKaLRww\\1&\\1",[])),
+?line <<"*.a-b.c-">> = iolist_to_binary(re:replace("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","xINKaLRww\\1&\\1",[global])),
+?line <<"*.c-a.0-c">> = iolist_to_binary(re:replace("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","akuY\\1pMWNGWjJ\\1oFTV\\1t",[])),
+?line <<"*.c-a.0-c">> = iolist_to_binary(re:replace("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$","akuY\\1pMWNGWjJ\\1oFTV\\1t",[global])),
+?line <<"Dy">> = iolist_to_binary(re:replace("abde","^(?=ab(de))(abd)(e)","Dy",[])),
+?line <<"Dy">> = iolist_to_binary(re:replace("abde","^(?=ab(de))(abd)(e)","Dy",[global])),
+?line <<"Sf">> = iolist_to_binary(re:replace("abdf","^(?!(ab)de|x)(abd)(f)","Sf",[])),
+?line <<"Sf">> = iolist_to_binary(re:replace("abdf","^(?!(ab)de|x)(abd)(f)","Sf",[global])),
+?line <<"IabcdJcd">> = iolist_to_binary(re:replace("abcd","^(?=(ab(cd)))(ab)","I\\1J",[])),
+?line <<"IabcdJcd">> = iolist_to_binary(re:replace("abcd","^(?=(ab(cd)))(ab)","I\\1J",[global])),
+?line <<"rvnE.d">> = iolist_to_binary(re:replace("a.b.c.d","^[\\da-f](\\.[\\da-f])*$","rvnE\\1",[caseless])),
+?line <<"rvnE.d">> = iolist_to_binary(re:replace("a.b.c.d","^[\\da-f](\\.[\\da-f])*$","rvnE\\1",[caseless,
+ global])),
+?line <<"niA.B.C.DL.DCGxjcuA.B.C.DhA.B.C.DT.DA.B.C.D.DM">> = iolist_to_binary(re:replace("A.B.C.D","^[\\da-f](\\.[\\da-f])*$","ni&L\\1CGxjcu&h&T\\1&\\1M",[caseless])),
+?line <<"niA.B.C.DL.DCGxjcuA.B.C.DhA.B.C.DT.DA.B.C.D.DM">> = iolist_to_binary(re:replace("A.B.C.D","^[\\da-f](\\.[\\da-f])*$","ni&L\\1CGxjcu&h&T\\1&\\1M",[caseless,
+ global])),
+?line <<"wARa.b.c.1.2.3.Ca.b.c.1.2.3.C.CpUAa.b.c.1.2.3.Cg">> = iolist_to_binary(re:replace("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$","wAR&&\\1pUA&g",[caseless])),
+?line <<"wARa.b.c.1.2.3.Ca.b.c.1.2.3.C.CpUAa.b.c.1.2.3.Cg">> = iolist_to_binary(re:replace("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$","wAR&&\\1pUA&g",[caseless,
+ global])),
+?line <<"a">> = iolist_to_binary(re:replace("\"1234\"","^\\\".*\\\"\\s*(;.*)?$","\\1a",[])),
+?line <<"a">> = iolist_to_binary(re:replace("\"1234\"","^\\\".*\\\"\\s*(;.*)?$","\\1a",[global])),
+?line <<"CIv">> = iolist_to_binary(re:replace("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$","CIv",[])),
+?line <<"CIv">> = iolist_to_binary(re:replace("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$","CIv",[global])),
+?line <<"; rhubarbK">> = iolist_to_binary(re:replace("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$","\\1K",[])),
+?line <<"; rhubarbK">> = iolist_to_binary(re:replace("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$","\\1K",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\\".*\\\"\\s*(;.*)?$","aWrdsUS\\1QSjQ&dFoGfF",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\\".*\\\"\\s*(;.*)?$","aWrdsUS\\1QSjQ&dFoGfF",[global])),
+?line <<"\"1234\" : things">> = iolist_to_binary(re:replace("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$","khMGlmiUM&d\\1\\1GGb",[])),
+?line <<"\"1234\" : things">> = iolist_to_binary(re:replace("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$","khMGlmiUM&d\\1\\1GGb",[global])),
+?line <<"ddsaVnvP">> = iolist_to_binary(re:replace("","^$","ddsaVnvP",[])),
+?line <<"ddsaVnvP">> = iolist_to_binary(re:replace("","^$","ddsaVnvP",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^$","d\\1yl\\1chv&DYUrRBp",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^$","d\\1yl\\1chv&DYUrRBp",[global])),
+?line <<"ubIEeu">> = iolist_to_binary(re:replace("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","ubIEeu",[extended])),
+?line <<"ubIEeu">> = iolist_to_binary(re:replace("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","ubIEeu",[extended,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","hTyFd&e\\1&PttRSXjwggW",[extended])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","hTyFd&e\\1&PttRSXjwggW",[extended,
+ global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","RUKr",[extended])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","RUKr",[extended,
+ global])),
+?line <<"ab cde">> = iolist_to_binary(re:replace("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","&wELTyPo&oJqp&vLg\\1T",[extended])),
+?line <<"ab cde">> = iolist_to_binary(re:replace("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","&wELTyPo&oJqp&vLg\\1T",[extended,
+ global])),
+?line <<"NNDSkqab cNfQkVqmWj">> = iolist_to_binary(re:replace("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","NN\\1DSk\\1q&NfQkVqmWj",[])),
+?line <<"NNDSkqab cNfQkVqmWj">> = iolist_to_binary(re:replace("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","NN\\1DSk\\1q&NfQkVqmWj",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","vkwk\\1js\\1pepFK\\1\\1Wa&g",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","vkwk\\1js\\1pepFK\\1\\1Wa&g",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","\\1&fYSPEB",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","\\1&fYSPEB",[global])),
+?line <<"ab cde">> = iolist_to_binary(re:replace("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","\\1D\\1\\1\\1by&lUP\\1jTlVc",[])),
+?line <<"ab cde">> = iolist_to_binary(re:replace("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)","\\1D\\1\\1\\1by&lUP\\1jTlVc",[global])),
+?line <<"Pca bcdDXLbDTDa bcdja bcdTa bcdX">> = iolist_to_binary(re:replace("a bcd","^ a\\ b[c ]d $","Pc&DXLbDTD&j&T&X\\1",[extended])),
+?line <<"Pca bcdDXLbDTDa bcdja bcdTa bcdX">> = iolist_to_binary(re:replace("a bcd","^ a\\ b[c ]d $","Pc&DXLbDTD&j&T&X\\1",[extended,
+ global])),
+?line <<"cbruWa b dWvF">> = iolist_to_binary(re:replace("a b d","^ a\\ b[c ]d $","cbruW&WvF\\1",[extended])),
+?line <<"cbruWa b dWvF">> = iolist_to_binary(re:replace("a b d","^ a\\ b[c ]d $","cbruW&WvF\\1",[extended,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^ a\\ b[c ]d $","xLsLvmNGGbWjEqU\\1q",[extended])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^ a\\ b[c ]d $","xLsLvmNGGbWjEqU\\1q",[extended,
+ global])),
+?line <<"abcd">> = iolist_to_binary(re:replace("abcd","^ a\\ b[c ]d $","QvID",[extended])),
+?line <<"abcd">> = iolist_to_binary(re:replace("abcd","^ a\\ b[c ]d $","QvID",[extended,
+ global])),
+?line <<"ab d">> = iolist_to_binary(re:replace("ab d","^ a\\ b[c ]d $","CEUu&Jt",[extended])),
+?line <<"ab d">> = iolist_to_binary(re:replace("ab d","^ a\\ b[c ]d $","CEUu&Jt",[extended,
+ global])),
+?line <<"ohu">> = iolist_to_binary(re:replace("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$","ohu",[])),
+?line <<"ohu">> = iolist_to_binary(re:replace("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$","ohu",[global])),
+?line <<"rbcmNabcdefhijklmabcdefhijklmNGkLNbcabcdefhijklmxnbclO">> = iolist_to_binary(re:replace("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$","r\\1mN&&NGkLN\\1&xn\\1lO",[])),
+?line <<"rbcmNabcdefhijklmabcdefhijklmNGkLNbcabcdefhijklmxnbclO">> = iolist_to_binary(re:replace("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$","r\\1mN&&NGkLN\\1&xn\\1lO",[global])),
+?line <<"I">> = iolist_to_binary(re:replace("a+ Z0+
+","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]","I",[])),
+?line <<"I">> = iolist_to_binary(re:replace("a+ Z0+
+","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]","I",[global])),
+?line <<"ANdwkyHnc">> = iolist_to_binary(re:replace(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+","ANdwkyHnc",[])),
+?line <<"ANdwkyHnc">> = iolist_to_binary(re:replace(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+","ANdwkyHnc",[global])),
+?line <<"Dx">> = iolist_to_binary(re:replace("z","^a*\\w","Dx",[])),
+?line <<"Dx">> = iolist_to_binary(re:replace("z","^a*\\w","Dx",[global])),
+?line <<"OpVTjQkTaz">> = iolist_to_binary(re:replace("az","^a*\\w","OpVTjQkT&",[])),
+?line <<"OpVTjQkTaz">> = iolist_to_binary(re:replace("az","^a*\\w","OpVTjQkT&",[global])),
+?line <<"LexbuooAmFDifW">> = iolist_to_binary(re:replace("aaaz","^a*\\w","LexbuooAm\\1FDifW",[])),
+?line <<"LexbuooAmFDifW">> = iolist_to_binary(re:replace("aaaz","^a*\\w","LexbuooAm\\1FDifW",[global])),
+?line <<"arpLCDAadYpPkb">> = iolist_to_binary(re:replace("a","^a*\\w","arpLCDAadYpPk\\1b",[])),
+?line <<"arpLCDAadYpPkb">> = iolist_to_binary(re:replace("a","^a*\\w","arpLCDAadYpPk\\1b",[global])),
+?line <<"CyElaaTHfV">> = iolist_to_binary(re:replace("aa","^a*\\w","CyEl&T\\1HfV",[])),
+?line <<"CyElaaTHfV">> = iolist_to_binary(re:replace("aa","^a*\\w","CyEl&T\\1HfV",[global])),
+?line <<"v">> = iolist_to_binary(re:replace("aaaa","^a*\\w","v\\1\\1",[])),
+?line <<"v">> = iolist_to_binary(re:replace("aaaa","^a*\\w","v\\1\\1",[global])),
+?line <<"COSWtMTXCLic+">> = iolist_to_binary(re:replace("a+","^a*\\w","COSWtMTXCLic",[])),
+?line <<"COSWtMTXCLic+">> = iolist_to_binary(re:replace("a+","^a*\\w","COSWtMTXCLic",[global])),
+?line <<"kaaDuaauKGaaIaaFUK+">> = iolist_to_binary(re:replace("aa+","^a*\\w","k&Du&uKG\\1&I&FU\\1\\1K\\1",[])),
+?line <<"kaaDuaauKGaaIaaFUK+">> = iolist_to_binary(re:replace("aa+","^a*\\w","k&Du&uKG\\1&I&FU\\1\\1K\\1",[global])),
+?line <<"Pttz">> = iolist_to_binary(re:replace("z","^a*?\\w","Ptt&",[])),
+?line <<"Pttz">> = iolist_to_binary(re:replace("z","^a*?\\w","Ptt&",[global])),
+?line <<"QEHxyFSkkaz">> = iolist_to_binary(re:replace("az","^a*?\\w","QEHxyFSkk&",[])),
+?line <<"QEHxyFSkkaz">> = iolist_to_binary(re:replace("az","^a*?\\w","QEHxyFSkk&",[global])),
+?line <<"atHEjpaKrKaaz">> = iolist_to_binary(re:replace("aaaz","^a*?\\w","&tHE\\1jp&KrK",[])),
+?line <<"atHEjpaKrKaaz">> = iolist_to_binary(re:replace("aaaz","^a*?\\w","&tHE\\1jp&KrK",[global])),
+?line <<"YaSeqaaeBiLO">> = iolist_to_binary(re:replace("a","^a*?\\w","Y&Seq\\1&&eBiLO",[])),
+?line <<"YaSeqaaeBiLO">> = iolist_to_binary(re:replace("a","^a*?\\w","Y&Seq\\1&&eBiLO",[global])),
+?line <<"UlaGeGnImSoQaHibARka">> = iolist_to_binary(re:replace("aa","^a*?\\w","Ul&GeGnImSoQ&HibARk",[])),
+?line <<"UlaGeGnImSoQaHibARka">> = iolist_to_binary(re:replace("aa","^a*?\\w","Ul&GeGnImSoQ&HibARk",[global])),
+?line <<"upDbxncvqbwvaaa">> = iolist_to_binary(re:replace("aaaa","^a*?\\w","upDbxncvqb\\1wv",[])),
+?line <<"upDbxncvqbwvaaa">> = iolist_to_binary(re:replace("aaaa","^a*?\\w","upDbxncvqb\\1wv",[global])),
+?line <<"MalqdUPrj+">> = iolist_to_binary(re:replace("a+","^a*?\\w","M&\\1lqdUPr\\1j\\1\\1",[])),
+?line <<"MalqdUPrj+">> = iolist_to_binary(re:replace("a+","^a*?\\w","M&\\1lqdUPr\\1j\\1\\1",[global])),
+?line <<"JTa+">> = iolist_to_binary(re:replace("aa+","^a*?\\w","JT",[])),
+?line <<"JTa+">> = iolist_to_binary(re:replace("aa+","^a*?\\w","JT",[global])),
+?line <<"nnvJUazdlV">> = iolist_to_binary(re:replace("az","^a+\\w","\\1nnvJU&dlV",[])),
+?line <<"nnvJUazdlV">> = iolist_to_binary(re:replace("az","^a+\\w","\\1nnvJU&dlV",[global])),
+?line <<"aaaz">> = iolist_to_binary(re:replace("aaaz","^a+\\w","\\1&",[])),
+?line <<"aaaz">> = iolist_to_binary(re:replace("aaaz","^a+\\w","\\1&",[global])),
+?line <<"daaRBaauOlL">> = iolist_to_binary(re:replace("aa","^a+\\w","d&RB&uO\\1lL",[])),
+?line <<"daaRBaauOlL">> = iolist_to_binary(re:replace("aa","^a+\\w","d&RB&uO\\1lL",[global])),
+?line <<"uaaaajkPXJqfBddNNYaaaa">> = iolist_to_binary(re:replace("aaaa","^a+\\w","u&jkPXJqfBddNNY&",[])),
+?line <<"uaaaajkPXJqfBddNNYaaaa">> = iolist_to_binary(re:replace("aaaa","^a+\\w","u&jkPXJqfBddNNY&",[global])),
+?line <<"YAJlePYgQb+">> = iolist_to_binary(re:replace("aa+","^a+\\w","YAJlePYgQb",[])),
+?line <<"YAJlePYgQb+">> = iolist_to_binary(re:replace("aa+","^a+\\w","YAJlePYgQb",[global])),
+?line <<"MXsW">> = iolist_to_binary(re:replace("az","^a+?\\w","MXsW",[])),
+?line <<"MXsW">> = iolist_to_binary(re:replace("az","^a+?\\w","MXsW",[global])),
+?line <<"GaaMGsaaPiMScaz">> = iolist_to_binary(re:replace("aaaz","^a+?\\w","G&MGs&P\\1i\\1MSc",[])),
+?line <<"GaaMGsaaPiMScaz">> = iolist_to_binary(re:replace("aaaz","^a+?\\w","G&MGs&P\\1i\\1MSc",[global])),
+?line <<"aaYUHgT">> = iolist_to_binary(re:replace("aa","^a+?\\w","&YUHgT",[])),
+?line <<"aaYUHgT">> = iolist_to_binary(re:replace("aa","^a+?\\w","&YUHgT",[global])),
+?line <<"ePknVhaa">> = iolist_to_binary(re:replace("aaaa","^a+?\\w","ePknVh\\1",[])),
+?line <<"ePknVhaa">> = iolist_to_binary(re:replace("aaaa","^a+?\\w","ePknVh\\1",[global])),
+?line <<"+">> = iolist_to_binary(re:replace("aa+","^a+?\\w","\\1",[])),
+?line <<"+">> = iolist_to_binary(re:replace("aa+","^a+?\\w","\\1",[global])),
+?line <<"AFRGuhtn1234567890rEtjU">> = iolist_to_binary(re:replace("1234567890","^\\d{8}\\w{2,}","\\1AFRGuhtn&rEtjU",[])),
+?line <<"AFRGuhtn1234567890rEtjU">> = iolist_to_binary(re:replace("1234567890","^\\d{8}\\w{2,}","\\1AFRGuhtn&rEtjU",[global])),
+?line <<"nNSL12345678abRY">> = iolist_to_binary(re:replace("12345678ab","^\\d{8}\\w{2,}","nNSL&RY",[])),
+?line <<"nNSL12345678abRY">> = iolist_to_binary(re:replace("12345678ab","^\\d{8}\\w{2,}","nNSL&RY",[global])),
+?line <<"12345678__JUDy">> = iolist_to_binary(re:replace("12345678__","^\\d{8}\\w{2,}","&JUDy",[])),
+?line <<"12345678__JUDy">> = iolist_to_binary(re:replace("12345678__","^\\d{8}\\w{2,}","&JUDy",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\d{8}\\w{2,}","&OcK\\1P&XNvgrP",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\d{8}\\w{2,}","&OcK\\1P&XNvgrP",[global])),
+?line <<"1234567">> = iolist_to_binary(re:replace("1234567","^\\d{8}\\w{2,}","&Mo",[])),
+?line <<"1234567">> = iolist_to_binary(re:replace("1234567","^\\d{8}\\w{2,}","&Mo",[global])),
+?line <<"O">> = iolist_to_binary(re:replace("uoie","^[aeiou\\d]{4,5}$","O",[])),
+?line <<"O">> = iolist_to_binary(re:replace("uoie","^[aeiou\\d]{4,5}$","O",[global])),
+?line <<"1234b1234n">> = iolist_to_binary(re:replace("1234","^[aeiou\\d]{4,5}$","&b&n",[])),
+?line <<"1234b1234n">> = iolist_to_binary(re:replace("1234","^[aeiou\\d]{4,5}$","&b&n",[global])),
+?line <<"YBp12345Ul">> = iolist_to_binary(re:replace("12345","^[aeiou\\d]{4,5}$","YBp&Ul",[])),
+?line <<"YBp12345Ul">> = iolist_to_binary(re:replace("12345","^[aeiou\\d]{4,5}$","YBp&Ul",[global])),
+?line <<"SVHQqBtcrMAtQxy">> = iolist_to_binary(re:replace("aaaaa","^[aeiou\\d]{4,5}$","SVHQqBtcrMAtQxy\\1",[])),
+?line <<"SVHQqBtcrMAtQxy">> = iolist_to_binary(re:replace("aaaaa","^[aeiou\\d]{4,5}$","SVHQqBtcrMAtQxy\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[aeiou\\d]{4,5}$","ne",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[aeiou\\d]{4,5}$","ne",[global])),
+?line <<"123456">> = iolist_to_binary(re:replace("123456","^[aeiou\\d]{4,5}$","QA&F\\1LJ",[])),
+?line <<"123456">> = iolist_to_binary(re:replace("123456","^[aeiou\\d]{4,5}$","QA&F\\1LJ",[global])),
+?line <<"gKGpFvTeUK">> = iolist_to_binary(re:replace("uoie","^[aeiou\\d]{4,5}?","gKGpFvTeUK",[])),
+?line <<"gKGpFvTeUK">> = iolist_to_binary(re:replace("uoie","^[aeiou\\d]{4,5}?","gKGpFvTeUK",[global])),
+?line <<"LCEqvLxHGWXWrexD">> = iolist_to_binary(re:replace("1234","^[aeiou\\d]{4,5}?","LCE\\1qvL\\1xHGWXWrexD",[])),
+?line <<"LCEqvLxHGWXWrexD">> = iolist_to_binary(re:replace("1234","^[aeiou\\d]{4,5}?","LCE\\1qvL\\1xHGWXWrexD",[global])),
+?line <<"QWV1234vhndnkkT1234l5">> = iolist_to_binary(re:replace("12345","^[aeiou\\d]{4,5}?","\\1QWV&\\1vhndnkk\\1T&l",[])),
+?line <<"QWV1234vhndnkkT1234l5">> = iolist_to_binary(re:replace("12345","^[aeiou\\d]{4,5}?","\\1QWV&\\1vhndnkk\\1T&l",[global])),
+?line <<"aaaaIaaaayaaaaEoAPYra">> = iolist_to_binary(re:replace("aaaaa","^[aeiou\\d]{4,5}?","&I&y&E\\1oAPYr",[])),
+?line <<"aaaaIaaaayaaaaEoAPYra">> = iolist_to_binary(re:replace("aaaaa","^[aeiou\\d]{4,5}?","&I&y&E\\1oAPYr",[global])),
+?line <<"xKUWgRs56">> = iolist_to_binary(re:replace("123456","^[aeiou\\d]{4,5}?","xKUWgRs",[])),
+?line <<"xKUWgRs56">> = iolist_to_binary(re:replace("123456","^[aeiou\\d]{4,5}?","xKUWgRs",[global])),
+?line <<"JlUywcabcvRabcPFPabcYSXE">> = iolist_to_binary(re:replace("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z","JlUywc\\1vR\\1PFP\\1YSXE",[])),
+?line <<"JlUywcabcvRabcPFPabcYSXE">> = iolist_to_binary(re:replace("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z","JlUywc\\1vR\\1PFP\\1YSXE",[global])),
+?line <<"def=defdefdefbucdef=defdefdef">> = iolist_to_binary(re:replace("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z","&buc&",[])),
+?line <<"def=defdefdefbucdef=defdefdef">> = iolist_to_binary(re:replace("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z","&buc&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z","AYuyHUCDlwjQc\\1OS",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z","AYuyHUCDlwjQc\\1OS",[global])),
+?line <<"abc=defdef">> = iolist_to_binary(re:replace("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z","PM&h\\1\\1xD\\1&",[])),
+?line <<"abc=defdef">> = iolist_to_binary(re:replace("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z","PM&h\\1\\1xD\\1&",[global])),
+?line <<"UFhaagoHObvFc">> = iolist_to_binary(re:replace("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$","UFh\\1\\1goHObvFc",[])),
+?line <<"UFhaagoHObvFc">> = iolist_to_binary(re:replace("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$","UFh\\1\\1goHObvFc",[global])),
+?line <<"aabcdefghijkkkkcda2abcdefghijkkkkcda2abcdefghijkkkkcda2vuYabcdefghijkkkkcda2xdabcdefghijkkkkcda2njaJQEF">> = iolist_to_binary(re:replace("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$","\\1&&&vuY&xd&nj\\1JQEF",[])),
+?line <<"aabcdefghijkkkkcda2abcdefghijkkkkcda2abcdefghijkkkkcda2vuYabcdefghijkkkkcda2xdabcdefghijkkkkcda2njaJQEF">> = iolist_to_binary(re:replace("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$","\\1&&&vuY&xd&nj\\1JQEF",[global])),
+?line <<"mcataractcataract cataract23YTkMcataract cataract23">> = iolist_to_binary(re:replace("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)","m\\1&YTkM&",[])),
+?line <<"mcataractcataract cataract23YTkMcataract cataract23">> = iolist_to_binary(re:replace("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)","m\\1&YTkM&",[global])),
+?line <<"OXcatatonicoHKaXHQpryKFyhVcatatonic">> = iolist_to_binary(re:replace("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)","OX\\1oHKaXHQpryKFyhV\\1",[])),
+?line <<"OXcatatonicoHKaXHQpryKFyhVcatatonic">> = iolist_to_binary(re:replace("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)","OX\\1oHKaXHQpryKFyhV\\1",[global])),
+?line <<"caterpillarcaterpillar caterpillar23foVlhvplLSebcaterpillar">> = iolist_to_binary(re:replace("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)","\\1&foVlhvplLSeb\\1",[])),
+?line <<"caterpillarcaterpillar caterpillar23foVlhvplLSebcaterpillar">> = iolist_to_binary(re:replace("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)","\\1&foVlhvplLSeb\\1",[global])),
+?line <<"From abcd Mon Sep 01 12:33TNVlANgYabcdabcdFrom abcd Mon Sep 01 12:33aIFrom abcd Mon Sep 01 12:33TqsabcdQ:02 1997">> = iolist_to_binary(re:replace("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]","&TNVlANgY\\1\\1&aI&Tqs\\1Q",[])),
+?line <<"From abcd Mon Sep 01 12:33TNVlANgYabcdabcdFrom abcd Mon Sep 01 12:33aIFrom abcd Mon Sep 01 12:33TqsabcdQ:02 1997">> = iolist_to_binary(re:replace("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]","&TNVlANgY\\1\\1&aI&Tqs\\1Q",[global])),
+?line <<"isKrFrom abcd Mon Sep 01 12:33:02 1997">> = iolist_to_binary(re:replace("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","isKr&",[])),
+?line <<"isKrFrom abcd Mon Sep 01 12:33:02 1997">> = iolist_to_binary(re:replace("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","isKr&",[global])),
+?line <<"lNtEJS:02 1997">> = iolist_to_binary(re:replace("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","lNtEJS",[])),
+?line <<"lNtEJS:02 1997">> = iolist_to_binary(re:replace("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","lNtEJS",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","aR\\1i&Fxe",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","aR\\1i&Fxe",[global])),
+?line <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(re:replace("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","\\1NsrJ&\\1&PtWlXT",[])),
+?line <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(re:replace("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d","\\1NsrJ&\\1&PtWlXT",[global])),
+?line <<"nGixKixjMO12
+34BhoReSp">> = iolist_to_binary(re:replace("12
+34","^12.34","nGixKixjMO&BhoReSp",[dotall])),
+?line <<"nGixKixjMO12
+34BhoReSp">> = iolist_to_binary(re:replace("12
+34","^12.34","nGixKixjMO&BhoReSp",[dotall,global])),
+?line <<"12 34OBfpF12 3412 34fh">> = iolist_to_binary(re:replace("12 34","^12.34","&OBfpF&&fh",[dotall])),
+?line <<"12 34OBfpF12 3412 34fh">> = iolist_to_binary(re:replace("12 34","^12.34","&OBfpF&&fh",[dotall,
+ global])),
+?line <<"the quick brownhubrownbrownubrownQ fox">> = iolist_to_binary(re:replace("the quick brown fox","\\w+(?=\\t)","&hu&&u&Q",[])),
+?line <<"the quick brownhubrownbrownubrownQ fox">> = iolist_to_binary(re:replace("the quick brown fox","\\w+(?=\\t)","&hu&&u&Q",[global])),
+?line <<"foobar is uqH">> = iolist_to_binary(re:replace("foobar is foolish see?","foo(?!bar)(.*)","uqH",[])),
+?line <<"foobar is uqH">> = iolist_to_binary(re:replace("foobar is foolish see?","foo(?!bar)(.*)","uqH",[global])),
+?line <<"foobar cn">> = iolist_to_binary(re:replace("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)","n",[])),
+?line <<"foobar cn">> = iolist_to_binary(re:replace("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)","n",[global])),
+?line <<"CpDjibh">> = iolist_to_binary(re:replace("barrel","(?:(?!foo)...|^.{0,2})bar(.*)","CpDjibh",[])),
+?line <<"CpDjibh">> = iolist_to_binary(re:replace("barrel","(?:(?!foo)...|^.{0,2})bar(.*)","CpDjibh",[global])),
+?line <<"wlcfLrelprelgrelD">> = iolist_to_binary(re:replace("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)","wlcfL\\1p\\1g\\1D",[])),
+?line <<"wlcfLrelprelgrelD">> = iolist_to_binary(re:replace("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)","wlcfL\\1p\\1g\\1D",[global])),
+?line <<"wbDA barrelYA barrelcQA barrelplrelA barrelXPrel">> = iolist_to_binary(re:replace("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)","wbD&Y&cQ&pl\\1&XP\\1",[])),
+?line <<"wbDA barrelYA barrelcQA barrelplrelA barrelXPrel">> = iolist_to_binary(re:replace("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)","wbD&Y&cQ&pl\\1&XP\\1",[global])),
+?line <<"PJMqUabctmoSPC456">> = iolist_to_binary(re:replace("abc456","^(\\D*)(?=\\d)(?!123)","PJMqU\\1tmoSPC",[])),
+?line <<"PJMqUabctmoSPC456">> = iolist_to_binary(re:replace("abc456","^(\\D*)(?=\\d)(?!123)","PJMqU\\1tmoSPC",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\D*)(?=\\d)(?!123)","k\\1&lgXOi",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\D*)(?=\\d)(?!123)","k\\1&lgXOi",[global])),
+?line <<"abc123">> = iolist_to_binary(re:replace("abc123","^(\\D*)(?=\\d)(?!123)","UgE&XTVNX&ygyaQdYO&",[])),
+?line <<"abc123">> = iolist_to_binary(re:replace("abc123","^(\\D*)(?=\\d)(?!123)","UgE&XTVNX&ygyaQdYO&",[global])),
+?line <<"CByu1234Oj12341234">> = iolist_to_binary(re:replace("1234","^1234(?# test newlines
+ inside)","CByu&Oj&&",[])),
+?line <<"CByu1234Oj12341234">> = iolist_to_binary(re:replace("1234","^1234(?# test newlines
+ inside)","CByu&Oj&&",[global])),
+?line <<"AVF1234DSEvHi">> = iolist_to_binary(re:replace("1234","^1234 #comment in extended re
+ ","AVF&DSEvHi",[extended])),
+?line <<"AVF1234DSEvHi">> = iolist_to_binary(re:replace("1234","^1234 #comment in extended re
+ ","AVF&DSEvHi",[extended,global])),
+?line <<"wiUNsYJdROkkkabcdSabcdjvS">> = iolist_to_binary(re:replace("abcd","#rhubarb
+ abcd","wiUNsYJdROkkk&S&jvS",[extended])),
+?line <<"wiUNsYJdROkkkabcdSabcdjvS">> = iolist_to_binary(re:replace("abcd","#rhubarb
+ abcd","wiUNsYJdROkkk&S&jvS",[extended,global])),
+?line <<"wxabcdyOpSDe">> = iolist_to_binary(re:replace("abcd","^abcd#rhubarb","wx&yOpSDe",[extended])),
+?line <<"wxabcdyOpSDe">> = iolist_to_binary(re:replace("abcd","^abcd#rhubarb","wx&yOpSDe",[extended,
+ global])),
+?line <<"aqfaaaab">> = iolist_to_binary(re:replace("aaab","^(a)\\1{2,3}(.)","\\1qf\\1&",[])),
+?line <<"aqfaaaab">> = iolist_to_binary(re:replace("aaab","^(a)\\1{2,3}(.)","\\1qf\\1&",[global])),
+?line <<"aLGaaaabhavaLLxaIaaaabJaaaabidaaaab">> = iolist_to_binary(re:replace("aaaab","^(a)\\1{2,3}(.)","\\1LG&h\\1v\\1LLx\\1I&J&id&",[])),
+?line <<"aLGaaaabhavaLLxaIaaaabJaaaabidaaaab">> = iolist_to_binary(re:replace("aaaab","^(a)\\1{2,3}(.)","\\1LG&h\\1v\\1LLx\\1I&J&id&",[global])),
+?line <<"aaaaaEaaaaagaawPOaNaaaaab">> = iolist_to_binary(re:replace("aaaaab","^(a)\\1{2,3}(.)","&E&g\\1\\1wPO\\1N&",[])),
+?line <<"aaaaaEaaaaagaawPOaNaaaaab">> = iolist_to_binary(re:replace("aaaaab","^(a)\\1{2,3}(.)","&E&g\\1\\1wPO\\1N&",[global])),
+?line <<"QwegtaHxecVVVaaaaaab">> = iolist_to_binary(re:replace("aaaaaab","^(a)\\1{2,3}(.)","QwegtaHxecVVV&",[])),
+?line <<"QwegtaHxecVVVaaaaaab">> = iolist_to_binary(re:replace("aaaaaab","^(a)\\1{2,3}(.)","QwegtaHxecVVV&",[global])),
+?line <<"the EcabcmU">> = iolist_to_binary(re:replace("the abc","(?!^)abc","Ec&mU",[])),
+?line <<"the EcabcmU">> = iolist_to_binary(re:replace("the abc","(?!^)abc","Ec&mU",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?!^)abc","NA",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?!^)abc","NA",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(?!^)abc","yjhaoMMFW\\1",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(?!^)abc","yjhaoMMFW\\1",[global])),
+?line <<"PKCfTNYlWMooD">> = iolist_to_binary(re:replace("abc","(?=^)abc","\\1PK\\1CfTNYlWMooD",[])),
+?line <<"PKCfTNYlWMooD">> = iolist_to_binary(re:replace("abc","(?=^)abc","\\1PK\\1CfTNYlWMooD",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?=^)abc","fR\\1ltffBHNVYixMX",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?=^)abc","fR\\1ltffBHNVYixMX",[global])),
+?line <<"the abc">> = iolist_to_binary(re:replace("the abc","(?=^)abc","L\\1JJFtgfU&l",[])),
+?line <<"the abc">> = iolist_to_binary(re:replace("the abc","(?=^)abc","L\\1JJFtgfU&l",[global])),
+?line <<"aabbvbOdaabbyVtUjIbqObbbb">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}(ab*|b)","&v\\1Od&yVtUjI\\1qO\\1",[])),
+?line <<"aabbvbOdaabbyVtUjIbqObbbb">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}(ab*|b)","&v\\1Od&yVtUjI\\1qO\\1",[global])),
+?line <<"PLPJVxaabbbbbYrEOEywwHFp">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}?(ab*|b)","PLPJVxa\\1YrEOEywwHFp",[])),
+?line <<"PLPJVxaabbbbbYrEOEywwHFp">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}?(ab*|b)","PLPJVxa\\1YrEOEywwHFp",[global])),
+?line <<"VmghaaMBBitDaaaRnWKaaaAbbbbb">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}?(ab*?|b)","Vmgh&MBBitD&\\1RnWK&\\1A",[])),
+?line <<"VmghaaMBBitDaaaRnWKaaaAbbbbb">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}?(ab*?|b)","Vmgh&MBBitD&\\1RnWK&\\1A",[global])),
+?line <<"baabblNbbLhaabbtbbb">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}(ab*?|b)","\\1&lNb\\1Lh&t",[])),
+?line <<"baabblNbbLhaabbtbbb">> = iolist_to_binary(re:replace("aabbbbb","^[ab]{1,3}(ab*?|b)","\\1&lNb\\1Lh&t",[global])),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(re:replace("Alan Other <user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","Ag",[extended])),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(re:replace("Alan Other <user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","Ag",[extended,
+ global])),
+?line <<"<user.ain>">> = iolist_to_binary(re:replace("<user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","OcN\\1",[extended])),
+?line <<"<user.ain>">> = iolist_to_binary(re:replace("<user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","OcN\\1",[extended,
+ global])),
+?line <<"user.ain">> = iolist_to_binary(re:replace("user.ain"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","P\\1\\1",[extended])),
+?line <<"user.ain">> = iolist_to_binary(re:replace("user.ain"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","P\\1\\1",[extended,
+ global])),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","tFTD&XMAPNeq",[extended])),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","tFTD&XMAPNeq",[extended,
+ global])),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","c\\1&C&YTXnfnhWs\\1g",[extended])),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","c\\1&C&YTXnfnhWs\\1g",[extended,
+ global])),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(re:replace("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","\\1R&P&\\1aCnlgH",[extended])),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(re:replace("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","\\1R&P&\\1aCnlgH",[extended,
+ global])),
+?line <<"A missing angle <user.where">> = iolist_to_binary(re:replace("A missing angle <user.where"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","p\\1u\\1REy&",[extended])),
+?line <<"A missing angle <user.where">> = iolist_to_binary(re:replace("A missing angle <user.where"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","p\\1u\\1REy&",[extended,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","qytCjuWj\\1fpNNv\\1&ya",[extended])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","qytCjuWj\\1fpNNv\\1&ya",[extended,
+ global])),
+?line <<"The quick brown fox">> = iolist_to_binary(re:replace("The quick brown fox"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","L",[extended])),
+?line <<"The quick brown fox">> = iolist_to_binary(re:replace("The quick brown fox"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment","L",[extended,
+ global])),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(re:replace("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","\\1",[extended])),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(re:replace("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","\\1",[extended,global])),
+?line <<"<user.ain>">> = iolist_to_binary(re:replace("<user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","On",[extended])),
+?line <<"<user.ain>">> = iolist_to_binary(re:replace("<user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","On",[extended,global])),
+?line <<"user.ain">> = iolist_to_binary(re:replace("user.ain","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","OLL\\1TqepmsD\\1V\\1\\1h\\1DsD",[extended])),
+?line <<"user.ain">> = iolist_to_binary(re:replace("user.ain","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","OLL\\1TqepmsD\\1V\\1\\1h\\1DsD",[extended,global])),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","SHuKuC\\1Td",[extended])),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","SHuKuC\\1Td",[extended,global])),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","D&t&LSQGMfQpSXj",[extended])),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(re:replace("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","D&t&LSQGMfQpSXj",[extended,global])),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(re:replace("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","qDSGw",[extended])),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(re:replace("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","qDSGw",[extended,global])),
+?line <<"A missing angle <user.where">> = iolist_to_binary(re:replace("A missing angle <user.where","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","SOYovQniOUVJIil\\1",[extended])),
+?line <<"A missing angle <user.where">> = iolist_to_binary(re:replace("A missing angle <user.where","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","SOYovQniOUVJIil\\1",[extended,global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","D\\1",[extended])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","D\\1",[extended,global])),
+?line <<"The quick brown fox">> = iolist_to_binary(re:replace("The quick brown fox","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","X&ayUGYrNMDenjwrkvT",[extended])),
+?line <<"The quick brown fox">> = iolist_to_binary(re:replace("The quick brown fox","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)","X&ayUGYrNMDenjwrkvT",[extended,global])),
+?line <<"abcdefpqrxyz0AB">> = iolist_to_binary(re:replace("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB","bYVVTfV",[])),
+?line <<"abcdefpqrxyz0AB">> = iolist_to_binary(re:replace("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB","bYVVTfV",[global])),
+?line <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(re:replace("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB","ALc",[])),
+?line <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(re:replace("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB","ALc",[global])),
+?line <<"abc efpqr0xyz00AB">> = iolist_to_binary(re:replace("abc efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB","FJjJa&MUN",[])),
+?line <<"abc efpqr0xyz00AB">> = iolist_to_binary(re:replace("abc efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB","FJjJa&MUN",[global])),
+?line <<"abc456 abc efpqr0xyz00ABCDE">> = iolist_to_binary(re:replace("abc456 abc efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB","IsJK",[])),
+?line <<"abc456 abc efpqr0xyz00ABCDE">> = iolist_to_binary(re:replace("abc456 abc efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB","IsJK",[global])),
+?line <<"A">> = iolist_to_binary(re:replace("A","^[\\000-\\037]","&&&l&oFiYRb&dwnRVIB",[])),
+?line <<"A">> = iolist_to_binary(re:replace("A","^[\\000-\\037]","&&&l&oFiYRb&dwnRVIB",[global])),
+?line <<"pJINBIVHkbsQpNB">> = iolist_to_binary(re:replace("B","^[\\000-\\037]","p\\1JINBIVHkbs&QpN",[])),
+?line <<"pJINBIVHkbsQpNB">> = iolist_to_binary(re:replace("B","^[\\000-\\037]","p\\1JINBIVHkbs&QpN",[global])),
+?line <<"qnaxlKtxgBKMC">> = iolist_to_binary(re:replace("C","^[\\000-\\037]","qnax&lKtxgB&KM",[])),
+?line <<"qnaxlKtxgBKMC">> = iolist_to_binary(re:replace("C","^[\\000-\\037]","qnax&lKtxgB&KM",[global])),
+?line <<"WuuRxIy">> = iolist_to_binary(re:replace("","\\0*","Wu&uRxIy",[])),
+?line <<"WuuRxIy">> = iolist_to_binary(re:replace("","\\0*","Wu&uRxIy",[global])),
+?line <<"The AZ">> = iolist_to_binary(re:replace("The AZ","A\\x0{2,3}Z","ggJSniphIbt",[])),
+?line <<"The AZ">> = iolist_to_binary(re:replace("The AZ","A\\x0{2,3}Z","ggJSniphIbt",[global])),
+?line <<"An AZ">> = iolist_to_binary(re:replace("An AZ","A\\x0{2,3}Z","s&\\1Bwkjj",[])),
+?line <<"An AZ">> = iolist_to_binary(re:replace("An AZ","A\\x0{2,3}Z","s&\\1Bwkjj",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","A\\x0{2,3}Z","\\1eJyYpl",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","A\\x0{2,3}Z","\\1eJyYpl",[global])),
+?line <<"AZ">> = iolist_to_binary(re:replace("AZ","A\\x0{2,3}Z","aK\\1qVDC\\1uB",[])),
+?line <<"AZ">> = iolist_to_binary(re:replace("AZ","A\\x0{2,3}Z","aK\\1qVDC\\1uB",[global])),
+?line <<"AZ">> = iolist_to_binary(re:replace("AZ","A\\x0{2,3}Z","DMN&CNdjTe",[])),
+?line <<"AZ">> = iolist_to_binary(re:replace("AZ","A\\x0{2,3}Z","DMN&CNdjTe",[global])),
+?line <<"cowcowbelldrlcowNSbcowcowbelladivdcowcowbell">> = iolist_to_binary(re:replace("cowcowbell","^(cow|)\\1(bell)","&drl\\1NSb&adivd&",[])),
+?line <<"cowcowbelldrlcowNSbcowcowbelladivdcowcowbell">> = iolist_to_binary(re:replace("cowcowbell","^(cow|)\\1(bell)","&drl\\1NSb&adivd&",[global])),
+?line <<"UUUBpbellPtC">> = iolist_to_binary(re:replace("bell","^(cow|)\\1(bell)","UUUBp&P\\1tC",[])),
+?line <<"UUUBpbellPtC">> = iolist_to_binary(re:replace("bell","^(cow|)\\1(bell)","UUUBp&P\\1tC",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(cow|)\\1(bell)","\\1\\1L\\1foe&LDaKY",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(cow|)\\1(bell)","\\1\\1L\\1foe&LDaKY",[global])),
+?line <<"cowbell">> = iolist_to_binary(re:replace("cowbell","^(cow|)\\1(bell)","&pE",[])),
+?line <<"cowbell">> = iolist_to_binary(re:replace("cowbell","^(cow|)\\1(bell)","&pE",[global])),
+?line <<"KEPMMarMefAQoabc">> = iolist_to_binary(re:replace(" abc","^\\s","KEP\\1MMa\\1rMefAQ\\1\\1\\1o",[])),
+?line <<"KEPMMarMefAQoabc">> = iolist_to_binary(re:replace(" abc","^\\s","KEP\\1MMa\\1rMefAQ\\1\\1\\1o",[global])),
+?line <<"mdPwbKbGabc">> = iolist_to_binary(re:replace(" abc","^\\s","mdPwbKbG",[])),
+?line <<"mdPwbKbGabc">> = iolist_to_binary(re:replace(" abc","^\\s","mdPwbKbG",[global])),
+?line <<"Ed
+FNgfabc">> = iolist_to_binary(re:replace("
+abc","^\\s","Ed&FNgf",[])),
+?line <<"Ed
+FNgfabc">> = iolist_to_binary(re:replace("
+abc","^\\s","Ed&FNgf",[global])),
+?line <<"iYCabc">> = iolist_to_binary(re:replace(" abc","^\\s","iYC",[])),
+?line <<"iYCabc">> = iolist_to_binary(re:replace(" abc","^\\s","iYC",[global])),
+?line <<"Y KyKjBtWUscEoeabc">> = iolist_to_binary(re:replace(" abc","^\\s","Y\\1&\\1KyKjBtWUscEoe",[])),
+?line <<"Y KyKjBtWUscEoeabc">> = iolist_to_binary(re:replace(" abc","^\\s","Y\\1&\\1KyKjBtWUscEoe",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\s","fMXHNBeT",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\s","fMXHNBeT",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","^\\s","GF\\1s&cS\\1yGC",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","^\\s","GF\\1s&cS\\1yGC",[global])),
+?line <<"Ur">> = iolist_to_binary(re:replace("abc","^a b
+ c","Ur",[extended])),
+?line <<"Ur">> = iolist_to_binary(re:replace("abc","^a b
+ c","Ur",[extended,global])),
+?line <<"UQqfOFVevBwaga">> = iolist_to_binary(re:replace("ab","^(a|)\\1*b","UQqfOFVevBwag\\1",[])),
+?line <<"UQqfOFVevBwaga">> = iolist_to_binary(re:replace("ab","^(a|)\\1*b","UQqfOFVevBwag\\1",[global])),
+?line <<"cAc">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1*b","cAc",[])),
+?line <<"cAc">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1*b","cAc",[global])),
+?line <<"QGbTLPFbbYYwpIhdW">> = iolist_to_binary(re:replace("b","^(a|)\\1*b","QG&TLPF&&YYwp\\1Ih\\1dW",[])),
+?line <<"QGbTLPFbbYYwpIhdW">> = iolist_to_binary(re:replace("b","^(a|)\\1*b","QG&TLPF&&YYwp\\1Ih\\1dW",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1*b","wY",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1*b","wY",[global])),
+?line <<"acb">> = iolist_to_binary(re:replace("acb","^(a|)\\1*b","IpD\\1tRUS",[])),
+?line <<"acb">> = iolist_to_binary(re:replace("acb","^(a|)\\1*b","IpD\\1tRUS",[global])),
+?line <<"PMaabaRKsKVgnxCiJtgp">> = iolist_to_binary(re:replace("aab","^(a|)\\1+b","PM&\\1RKsKVgnxCiJtgp",[])),
+?line <<"PMaabaRKsKVgnxCiJtgp">> = iolist_to_binary(re:replace("aab","^(a|)\\1+b","PM&\\1RKsKVgnxCiJtgp",[global])),
+?line <<"quaJPXxLfMHlVxH">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1+b","qu\\1JPXxLfMHlVxH",[])),
+?line <<"quaJPXxLfMHlVxH">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1+b","qu\\1JPXxLfMHlVxH",[global])),
+?line <<"b">> = iolist_to_binary(re:replace("b","^(a|)\\1+b","&",[])),
+?line <<"b">> = iolist_to_binary(re:replace("b","^(a|)\\1+b","&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1+b","ywXwC",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1+b","ywXwC",[global])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a|)\\1+b","hwwbvhOrVEaVOsD\\1",[])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a|)\\1+b","hwwbvhOrVEaVOsD\\1",[global])),
+?line <<"tNvaWAg">> = iolist_to_binary(re:replace("ab","^(a|)\\1?b","tNv\\1WAg",[])),
+?line <<"tNvaWAg">> = iolist_to_binary(re:replace("ab","^(a|)\\1?b","tNv\\1WAg",[global])),
+?line <<"K">> = iolist_to_binary(re:replace("aab","^(a|)\\1?b","K",[])),
+?line <<"K">> = iolist_to_binary(re:replace("aab","^(a|)\\1?b","K",[global])),
+?line <<"bRibsTbLcleUeb">> = iolist_to_binary(re:replace("b","^(a|)\\1?b","&Ri\\1&sT&L\\1cl\\1e\\1Ue&",[])),
+?line <<"bRibsTbLcleUeb">> = iolist_to_binary(re:replace("b","^(a|)\\1?b","&Ri\\1&sT&L\\1cl\\1e\\1Ue&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1?b","&CGRslcRfjatPWbOMT",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1?b","&CGRslcRfjatPWbOMT",[global])),
+?line <<"acb">> = iolist_to_binary(re:replace("acb","^(a|)\\1?b","&k\\1aoVMtug&hJsI",[])),
+?line <<"acb">> = iolist_to_binary(re:replace("acb","^(a|)\\1?b","&k\\1aoVMtug&hJsI",[global])),
+?line <<"gaaabFGtJRckPahi">> = iolist_to_binary(re:replace("aaab","^(a|)\\1{2}b","g&FGtJRckP\\1hi",[])),
+?line <<"gaaabFGtJRckPahi">> = iolist_to_binary(re:replace("aaab","^(a|)\\1{2}b","g&FGtJRckP\\1hi",[global])),
+?line <<"bINnRM">> = iolist_to_binary(re:replace("b","^(a|)\\1{2}b","bINnR\\1M",[])),
+?line <<"bINnRM">> = iolist_to_binary(re:replace("b","^(a|)\\1{2}b","bINnR\\1M",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1{2}b","cm",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1{2}b","cm",[global])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a|)\\1{2}b","yT\\1\\1NlFQVleuHkXnE\\1",[])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a|)\\1{2}b","yT\\1\\1NlFQVleuHkXnE\\1",[global])),
+?line <<"aab">> = iolist_to_binary(re:replace("aab","^(a|)\\1{2}b","PwBRhyP\\1txXQhbjE\\1a",[])),
+?line <<"aab">> = iolist_to_binary(re:replace("aab","^(a|)\\1{2}b","PwBRhyP\\1txXQhbjE\\1a",[global])),
+?line <<"aaaab">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1{2}b","cxo",[])),
+?line <<"aaaab">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1{2}b","cxo",[global])),
+?line <<"aaabXaaabMaaabdHhnqsiti">> = iolist_to_binary(re:replace("aaab","^(a|)\\1{2,3}b","&X&M&dHhnqsiti",[])),
+?line <<"aaabXaaabMaaabdHhnqsiti">> = iolist_to_binary(re:replace("aaab","^(a|)\\1{2,3}b","&X&M&dHhnqsiti",[global])),
+?line <<"QaaaablaaaabNnVdaaaabpaQEaaaaab">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1{2,3}b","Q&l&NnVd&p\\1QE\\1&",[])),
+?line <<"QaaaablaaaabNnVdaaaabpaQEaaaaab">> = iolist_to_binary(re:replace("aaaab","^(a|)\\1{2,3}b","Q&l&NnVd&p\\1QE\\1&",[global])),
+?line <<"bCw">> = iolist_to_binary(re:replace("b","^(a|)\\1{2,3}b","&Cw",[])),
+?line <<"bCw">> = iolist_to_binary(re:replace("b","^(a|)\\1{2,3}b","&Cw",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1{2,3}b","\\1&puY",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a|)\\1{2,3}b","\\1&puY",[global])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a|)\\1{2,3}b","pbextvQnRWgXs",[])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a|)\\1{2,3}b","pbextvQnRWgXs",[global])),
+?line <<"aab">> = iolist_to_binary(re:replace("aab","^(a|)\\1{2,3}b","fHRaaYYIr\\1l\\1",[])),
+?line <<"aab">> = iolist_to_binary(re:replace("aab","^(a|)\\1{2,3}b","fHRaaYYIr\\1l\\1",[global])),
+?line <<"aaaaab">> = iolist_to_binary(re:replace("aaaaab","^(a|)\\1{2,3}b","nXt&Aw\\1XCfLg\\1GGPmN",[])),
+?line <<"aaaaab">> = iolist_to_binary(re:replace("aaaaab","^(a|)\\1{2,3}b","nXt&Aw\\1XCfLg\\1GGPmN",[global])),
+?line <<"eRwvgLU">> = iolist_to_binary(re:replace("abbbbc","ab{1,3}bc","eRwvgLU",[])),
+?line <<"eRwvgLU">> = iolist_to_binary(re:replace("abbbbc","ab{1,3}bc","eRwvgLU",[global])),
+?line <<"mDnlkabbbcfTJ">> = iolist_to_binary(re:replace("abbbc","ab{1,3}bc","m\\1Dnl\\1k&fTJ",[])),
+?line <<"mDnlkabbbcfTJ">> = iolist_to_binary(re:replace("abbbc","ab{1,3}bc","m\\1Dnl\\1k&fTJ",[global])),
+?line <<"QabbcqIjjWabbchabbcBrTp">> = iolist_to_binary(re:replace("abbc","ab{1,3}bc","Q&qIjjW&h\\1&BrTp",[])),
+?line <<"QabbcqIjjWabbchabbcBrTp">> = iolist_to_binary(re:replace("abbc","ab{1,3}bc","Q&qIjjW&h\\1&BrTp",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab{1,3}bc","\\1Wj&Y&ML\\1RBiGiweww",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab{1,3}bc","\\1Wj&Y&ML\\1RBiGiweww",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","ab{1,3}bc","HmlRU&NUwwokL",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","ab{1,3}bc","HmlRU&NUwwokL",[global])),
+?line <<"abbbbbc">> = iolist_to_binary(re:replace("abbbbbc","ab{1,3}bc","FUyCCDShGVXhEHX\\1V\\1bK",[])),
+?line <<"abbbbbc">> = iolist_to_binary(re:replace("abbbbbc","ab{1,3}bc","FUyCCDShGVXhEHX\\1V\\1bK",[global])),
+?line <<"track1astrack1track1tIDhtrack1.title:TBlah blah blahiA">> = iolist_to_binary(re:replace("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)","\\1as\\1\\1tIDh&iA",[])),
+?line <<"track1astrack1track1tIDhtrack1.title:TBlah blah blahiA">> = iolist_to_binary(re:replace("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)","\\1as\\1\\1tIDh&iA",[global])),
+?line <<"wUVSR">> = iolist_to_binary(re:replace("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)","wUVSR",[caseless])),
+?line <<"wUVSR">> = iolist_to_binary(re:replace("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)","wUVSR",[caseless,
+ global])),
+?line <<"htrack1TCNtrack1.title:TBlah blah blahpLtrack1.title:TBlah blah blahtrack1.title:TBlah blah blahFnNtrack1jOBdd">> = iolist_to_binary(re:replace("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)","h\\1TCN&pL&&FnN\\1jOBdd",[caseless])),
+?line <<"htrack1TCNtrack1.title:TBlah blah blahpLtrack1.title:TBlah blah blahtrack1.title:TBlah blah blahFnNtrack1jOBdd">> = iolist_to_binary(re:replace("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)","h\\1TCN&pL&&FnN\\1jOBdd",[caseless,
+ global])),
+?line <<"OKnYPU">> = iolist_to_binary(re:replace("WXY_^abc","^[W-c]+$","OKnYPU",[])),
+?line <<"OKnYPU">> = iolist_to_binary(re:replace("WXY_^abc","^[W-c]+$","OKnYPU",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[W-c]+$","\\1GX\\1YVV&\\1WF",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[W-c]+$","\\1GX\\1YVV&\\1WF",[global])),
+?line <<"wxy">> = iolist_to_binary(re:replace("wxy","^[W-c]+$","Uax\\1F",[])),
+?line <<"wxy">> = iolist_to_binary(re:replace("wxy","^[W-c]+$","Uax\\1F",[global])),
+?line <<"WXY_^abcARKQ">> = iolist_to_binary(re:replace("WXY_^abc","^[W-c]+$","&ARK\\1Q",[caseless])),
+?line <<"WXY_^abcARKQ">> = iolist_to_binary(re:replace("WXY_^abc","^[W-c]+$","&ARK\\1Q",[caseless,
+ global])),
+?line <<"fIwxy_^ABCwxy_^ABCGwxy_^ABCwAHLMA">> = iolist_to_binary(re:replace("wxy_^ABC","^[W-c]+$","fI&&G&w\\1AHL\\1MA",[caseless])),
+?line <<"fIwxy_^ABCwxy_^ABCGwxy_^ABCwAHLMA">> = iolist_to_binary(re:replace("wxy_^ABC","^[W-c]+$","fI&&G&w\\1AHL\\1MA",[caseless,
+ global])),
+?line <<"WXY_^abcrgsgXuYrmtfAuS">> = iolist_to_binary(re:replace("WXY_^abc","^[\\x3f-\\x5F]+$","&rgsgXuYrmtfAuS",[caseless])),
+?line <<"WXY_^abcrgsgXuYrmtfAuS">> = iolist_to_binary(re:replace("WXY_^abc","^[\\x3f-\\x5F]+$","&rgsgXuYrmtfAuS",[caseless,
+ global])),
+?line <<"FmwJKwxy_^ABCyluQcjQVnwQ">> = iolist_to_binary(re:replace("wxy_^ABC","^[\\x3f-\\x5F]+$","FmwJK&yluQcjQVnwQ",[caseless])),
+?line <<"FmwJKwxy_^ABCyluQcjQVnwQ">> = iolist_to_binary(re:replace("wxy_^ABC","^[\\x3f-\\x5F]+$","FmwJK&yluQcjQVnwQ",[caseless,
+ global])),
+?line <<"abcabcMObxlD">> = iolist_to_binary(re:replace("abc","^abc$","&\\1&MObxlD",[multiline])),
+?line <<"abcabcMObxlD">> = iolist_to_binary(re:replace("abc","^abc$","&\\1&MObxlD",[multiline,
+ global])),
+?line <<"qqq
+wgabc">> = iolist_to_binary(re:replace("qqq
+abc","^abc$","wg&",[multiline])),
+?line <<"qqq
+wgabc">> = iolist_to_binary(re:replace("qqq
+abc","^abc$","wg&",[multiline,global])),
+?line <<"abcwXeRgabcKmklKpYiE
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","^abc$","&wXe\\1Rg&K\\1mklKpYiE",[multiline])),
+?line <<"abcwXeRgabcKmklKpYiE
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","^abc$","&wXe\\1Rg&K\\1mklKpYiE",[multiline,global])),
+?line <<"qqq
+CabcCKxlJamS
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","^abc$","C&CKxlJam\\1S",[multiline])),
+?line <<"qqq
+CabcCKxlJamS
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","^abc$","C&CKxlJam\\1S",[multiline,global])),
+?line <<"MLKPlabcY">> = iolist_to_binary(re:replace("abc","^abc$","MLKPl&\\1Y",[])),
+?line <<"MLKPlabcY">> = iolist_to_binary(re:replace("abc","^abc$","MLKPl&\\1Y",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^abc$","\\1uRnMNc&\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^abc$","\\1uRnMNc&\\1",[global])),
+?line <<"qqq
+abc">> = iolist_to_binary(re:replace("qqq
+abc","^abc$","b\\1",[])),
+?line <<"qqq
+abc">> = iolist_to_binary(re:replace("qqq
+abc","^abc$","b\\1",[global])),
+?line <<"abc
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","^abc$","RAJ\\1a&Mvoue\\1d",[])),
+?line <<"abc
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","^abc$","RAJ\\1a&Mvoue\\1d",[global])),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","^abc$","\\1NghvSn\\1GSQvu&&grYN",[])),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","^abc$","\\1NghvSn\\1GSQvu&&grYN",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","\\Aabc\\Z","&",[multiline])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","\\Aabc\\Z","&",[multiline,
+ global])),
+?line <<"XQevmabcHXD">> = iolist_to_binary(re:replace("abc","\\Aabc\\Z","XQevm&HXD",[multiline])),
+?line <<"XQevmabcHXD">> = iolist_to_binary(re:replace("abc","\\Aabc\\Z","XQevm&HXD",[multiline,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\Aabc\\Z","plNA&&\\1Myw&e",[multiline])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\Aabc\\Z","plNA&&\\1Myw&e",[multiline,
+ global])),
+?line <<"qqq
+abc">> = iolist_to_binary(re:replace("qqq
+abc","\\Aabc\\Z","tActj",[multiline])),
+?line <<"qqq
+abc">> = iolist_to_binary(re:replace("qqq
+abc","\\Aabc\\Z","tActj",[multiline,global])),
+?line <<"abc
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","\\Aabc\\Z","&PAnUmSADdk",[multiline])),
+?line <<"abc
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","\\Aabc\\Z","&PAnUmSADdk",[multiline,global])),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","\\Aabc\\Z","O&&GYJA",[multiline])),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","\\Aabc\\Z","O&&GYJA",[multiline,global])),
+?line <<"ejfGpamjOGidXfWabc
+defiXJg">> = iolist_to_binary(re:replace("abc
+def","\\A(.)*\\Z","ej\\1GpamjOGidX\\1W&iXJg",[dotall])),
+?line <<"ejfGpamjOGidXfWabc
+defiXJg">> = iolist_to_binary(re:replace("abc
+def","\\A(.)*\\Z","ej\\1GpamjOGidX\\1W&iXJg",[dotall,global])),
+?line <<"UTI*** FailersagVgtIFYe*** Failerss*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\A(.)*\\Z","UTI&agVgtIFYe&\\1&",[multiline])),
+?line <<"UTI*** FailersagVgtIFYe*** Failerss*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\A(.)*\\Z","UTI&agVgtIFYe&\\1&",[multiline,
+ global])),
+?line <<"abc
+def">> = iolist_to_binary(re:replace("abc
+def","\\A(.)*\\Z","&PHW&rh&xcxs\\1Cy&Chhd",[multiline])),
+?line <<"abc
+def">> = iolist_to_binary(re:replace("abc
+def","\\A(.)*\\Z","&PHW&rh&xcxs\\1Cy&Chhd",[multiline,global])),
+?line <<"cjOwaTTW::c">> = iolist_to_binary(re:replace("b::c","(?:b)|(?::+)","cjOwaTTW",[])),
+?line <<"cjOwaTTWcjOwaTTWc">> = iolist_to_binary(re:replace("b::c","(?:b)|(?::+)","cjOwaTTW",[global])),
+?line <<"c::ReJbWufqTUdDqlXQb">> = iolist_to_binary(re:replace("c::b","(?:b)|(?::+)","\\1&ReJbWufqTUdDqlXQ",[])),
+?line <<"c::ReJbWufqTUdDqlXQbReJbWufqTUdDqlXQ">> = iolist_to_binary(re:replace("c::b","(?:b)|(?::+)","\\1&ReJbWufqTUdDqlXQ",[global])),
+?line <<"az-uaz-obYOlaz-DVbrqaz-y">> = iolist_to_binary(re:replace("az-","[-az]+","&u&obYOl&DVbrq&y",[])),
+?line <<"az-uaz-obYOlaz-DVbrqaz-y">> = iolist_to_binary(re:replace("az-","[-az]+","&u&obYOl&DVbrq&y",[global])),
+?line <<"*** FAlNlPvDnUXilers">> = iolist_to_binary(re:replace("*** Failers","[-az]+","AlNlPvDn\\1UX",[])),
+?line <<"*** FAlNlPvDnUXilers">> = iolist_to_binary(re:replace("*** Failers","[-az]+","AlNlPvDn\\1UX",[global])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[-az]+","&xa\\1Q\\1BSaQG",[])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[-az]+","&xa\\1Q\\1BSaQG",[global])),
+?line <<"rJXv">> = iolist_to_binary(re:replace("za-","[az-]+","\\1rJXv",[])),
+?line <<"rJXv">> = iolist_to_binary(re:replace("za-","[az-]+","\\1rJXv",[global])),
+?line <<"*** FIQWDsCilers">> = iolist_to_binary(re:replace("*** Failers","[az-]+","IQWD\\1\\1sC",[])),
+?line <<"*** FIQWDsCilers">> = iolist_to_binary(re:replace("*** Failers","[az-]+","IQWD\\1\\1sC",[global])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[az-]+","G\\1TcEO\\1EAeKKLc&eOBg",[])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[az-]+","G\\1TcEO\\1EAeKKLc&eOBg",[global])),
+?line <<"DsdWoREvsWCDpa-z">> = iolist_to_binary(re:replace("a-z","[a\\-z]+","Dsd\\1WoREvsWCD\\1p&",[])),
+?line <<"DsdWoREvsWCDpa-z">> = iolist_to_binary(re:replace("a-z","[a\\-z]+","Dsd\\1WoREvsWCD\\1p&",[global])),
+?line <<"*** FaUqEUvbeKTpilers">> = iolist_to_binary(re:replace("*** Failers","[a\\-z]+","&U\\1qEUvbeKTp",[])),
+?line <<"*** FaUqEUvbeKTpilers">> = iolist_to_binary(re:replace("*** Failers","[a\\-z]+","&U\\1qEUvbeKTp",[global])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[a\\-z]+","IdIH&",[])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[a\\-z]+","IdIH&",[global])),
+?line <<"CHIiEabcdxyzAXhIPVabcdxyz">> = iolist_to_binary(re:replace("abcdxyz","[a-z]+","CHIiE&AXhIPV&",[])),
+?line <<"CHIiEabcdxyzAXhIPVabcdxyz">> = iolist_to_binary(re:replace("abcdxyz","[a-z]+","CHIiE&AXhIPV&",[global])),
+?line <<"Tk12-34ptREc12-34dF">> = iolist_to_binary(re:replace("12-34","[\\d-]+","Tk&ptREc&dF\\1",[])),
+?line <<"Tk12-34ptREc12-34dF">> = iolist_to_binary(re:replace("12-34","[\\d-]+","Tk&ptREc&dF\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\d-]+","PO\\1\\1HuRnqA\\1miVVsKv",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\d-]+","PO\\1\\1HuRnqA\\1miVVsKv",[global])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","[\\d-]+","QMUbdeC\\1fKgUg",[])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","[\\d-]+","QMUbdeC\\1fKgUg",[global])),
+?line <<"YypJ">> = iolist_to_binary(re:replace("12-34z","[\\d-z]+","YypJ",[])),
+?line <<"YypJ">> = iolist_to_binary(re:replace("12-34z","[\\d-z]+","YypJ",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\d-z]+","d&ErDHl\\1&GDjyQy",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\d-z]+","d&ErDHl\\1&GDjyQy",[global])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","[\\d-z]+","EVkS",[])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","[\\d-z]+","EVkS",[global])),
+?line <<"cHd\\ltkGr\\BqrhP ">> = iolist_to_binary(re:replace("\\ ","\\x5c","cHd&ltkGr&Bqr\\1hP",[])),
+?line <<"cHd\\ltkGr\\BqrhP ">> = iolist_to_binary(re:replace("\\ ","\\x5c","cHd&ltkGr&Bqr\\1hP",[global])),
+?line <<"the Z Z ZciyfJLvoo">> = iolist_to_binary(re:replace("the Zoo","\\x20Z","&&&ciyf\\1JLv",[])),
+?line <<"the Z Z ZciyfJLvoo">> = iolist_to_binary(re:replace("the Zoo","\\x20Z","&&&ciyf\\1JLv",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\x20Z","ry\\1S\\1&\\1\\1MkYc",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\x20Z","ry\\1S\\1&\\1\\1MkYc",[global])),
+?line <<"Zulu">> = iolist_to_binary(re:replace("Zulu","\\x20Z","b\\1Wc&RB\\1&&pO&Dd\\1&A",[])),
+?line <<"Zulu">> = iolist_to_binary(re:replace("Zulu","\\x20Z","b\\1Wc&RB\\1&&pO&Dd\\1&A",[global])),
+?line <<"aabcabcUxryabcAPabcabcDGgL">> = iolist_to_binary(re:replace("abcabc","(abc)\\1","a&Uxry\\1AP&DGgL",[caseless])),
+?line <<"aabcabcUxryabcAPabcabcDGgL">> = iolist_to_binary(re:replace("abcabc","(abc)\\1","a&Uxry\\1AP&DGgL",[caseless,
+ global])),
+?line <<"oStOWABCvVHVfFqojojfABC">> = iolist_to_binary(re:replace("ABCabc","(abc)\\1","oStOW\\1vVHVfFqojojf\\1",[caseless])),
+?line <<"oStOWABCvVHVfFqojojfABC">> = iolist_to_binary(re:replace("ABCabc","(abc)\\1","oStOW\\1vVHVfFqojojf\\1",[caseless,
+ global])),
+?line <<"GabcABCrGLdSabcABCWnHabcabcABCP">> = iolist_to_binary(re:replace("abcABC","(abc)\\1","G&rGLdS&WnH\\1&P",[caseless])),
+?line <<"GabcABCrGLdSabcABCWnHabcabcABCP">> = iolist_to_binary(re:replace("abcABC","(abc)\\1","G&rGLdS&WnH\\1&P",[caseless,
+ global])),
+?line <<"KVwWyab{3cdEWMab{3cdPr">> = iolist_to_binary(re:replace("ab{3cd","ab{3cd","KVwWy&\\1EWM&Pr",[])),
+?line <<"KVwWyab{3cdEWMab{3cdPr">> = iolist_to_binary(re:replace("ab{3cd","ab{3cd","KVwWy&\\1EWM&Pr",[global])),
+?line <<"KuJab{3,cd">> = iolist_to_binary(re:replace("ab{3,cd","ab{3,cd","KuJ&",[])),
+?line <<"KuJab{3,cd">> = iolist_to_binary(re:replace("ab{3,cd","ab{3,cd","KuJ&",[global])),
+?line <<"squJfab{3,4a}cd">> = iolist_to_binary(re:replace("ab{3,4a}cd","ab{3,4a}cd","squ\\1Jf&",[])),
+?line <<"squJfab{3,4a}cd">> = iolist_to_binary(re:replace("ab{3,4a}cd","ab{3,4a}cd","squ\\1Jf&",[global])),
+?line <<"{4,5a}bc{4,5a}bcH">> = iolist_to_binary(re:replace("{4,5a}bc","{4,5a}bc","&&H",[])),
+?line <<"{4,5a}bc{4,5a}bcH">> = iolist_to_binary(re:replace("{4,5a}bc","{4,5a}bc","&&H",[global])),
+?line <<"nb">> = iolist_to_binary(re:replace("abc","abc$","nb",[])),
+?line <<"nb">> = iolist_to_binary(re:replace("abc","abc$","nb",[global])),
+?line <<"aabcTslrEK">> = iolist_to_binary(re:replace("abc","abc$","a&TslrEK",[])),
+?line <<"aabcTslrEK">> = iolist_to_binary(re:replace("abc","abc$","a&TslrEK",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc$","rVkXRL&nq&w\\1NDuHM\\1dj",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc$","rVkXRL&nq&w\\1NDuHM\\1dj",[global])),
+?line <<"abc
+def">> = iolist_to_binary(re:replace("abc
+def","abc$","M",[])),
+?line <<"abc
+def">> = iolist_to_binary(re:replace("abc
+def","abc$","M",[global])),
+?line <<"abcWCabcSYXGPjRugTabcVGabcSX">> = iolist_to_binary(re:replace("abcS","(abc)\\123","\\1WC&YXGPjRugT\\1VG&X",[])),
+?line <<"abcWCabcSYXGPjRugTabcVGabcSX">> = iolist_to_binary(re:replace("abcS","(abc)\\123","\\1WC&YXGPjRugT\\1VG&X",[global])),
+?line <<"fabc�Uabc�UmiqabceCsabcabc�">> = iolist_to_binary(re:replace("abc�","(abc)\\223","f&U&Umiq\\1eCs\\1&",[])),
+?line <<"fabc�Uabc�UmiqabceCsabcabc�">> = iolist_to_binary(re:replace("abc�","(abc)\\223","f&U&Umiq\\1eCs\\1&",[global])),
+?line <<"JRFabcxnbabc�Vkabc�fWigQMuaY">> = iolist_to_binary(re:replace("abc�","(abc)\\323","JRF\\1xnb&Vk&fWigQMuaY",[])),
+?line <<"JRFabcxnbabc�Vkabc�fWigQMuaY">> = iolist_to_binary(re:replace("abc�","(abc)\\323","JRF\\1xnb&Vk&fWigQMuaY",[global])),
+?line <<"vgabc@QQ">> = iolist_to_binary(re:replace("abc@","(abc)\\100","vg&QQ",[])),
+?line <<"vgabc@QQ">> = iolist_to_binary(re:replace("abc@","(abc)\\100","vg&QQ",[global])),
+?line <<"abc@OkvNytabc@abcabc@a">> = iolist_to_binary(re:replace("abc@","(abc)\\100","&OkvNyt&\\1&a",[])),
+?line <<"abc@OkvNytabc@abcabc@a">> = iolist_to_binary(re:replace("abc@","(abc)\\100","&OkvNyt&\\1&a",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","P&kRot\\1ILA",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","P&kRot\\1ILA",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","iULjBTiVDW&K\\1p&bj",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","iULjBTiVDW&K\\1p&bj",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","cplYw\\1iuv\\1Okstb\\1p",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","cplYw\\1iuv\\1Okstb\\1p",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","LDaRV&lAu\\1i",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","LDaRV&lAu\\1i",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","K\\1",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","K\\1",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","cXHTTaJLMXvR&\\1",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","(abc)\\1000","cXHTTaJLMXvR&\\1",[global])),
+?line <<"abc81">> = iolist_to_binary(re:replace("abc81","abc\\81","\\1kB&oGS\\1\\1lPn",[])),
+?line <<"abc81">> = iolist_to_binary(re:replace("abc81","abc\\81","\\1kB&oGS\\1\\1lPn",[global])),
+?line <<"abc81">> = iolist_to_binary(re:replace("abc81","abc\\81","\\1",[])),
+?line <<"abc81">> = iolist_to_binary(re:replace("abc81","abc\\81","\\1",[global])),
+?line <<"abc91">> = iolist_to_binary(re:replace("abc91","abc\\91","\\1GKLIQYEGVpGIxagx&&",[])),
+?line <<"abc91">> = iolist_to_binary(re:replace("abc91","abc\\91","\\1GKLIQYEGVpGIxagx&&",[global])),
+?line <<"abc91">> = iolist_to_binary(re:replace("abc91","abc\\91","og&&Tlj&jCLkmrllagN",[])),
+?line <<"abc91">> = iolist_to_binary(re:replace("abc91","abc\\91","og&&Tlj&jCLkmrllagN",[global])),
+?line <<"xMFEgabcdefghijkllSDGabcdefghijkllSN">> = iolist_to_binary(re:replace("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123","xMFEg&DG&N",[])),
+?line <<"xMFEgabcdefghijkllSDGabcdefghijkllSN">> = iolist_to_binary(re:replace("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123","xMFEg&DG&N",[global])),
+?line <<"WyroFQabcdefghijk
+SNfabcdefghijk
+SahyoPabcdefghijk
+Sqfbs">> = iolist_to_binary(re:replace("abcdefghijk
+S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123","WyroFQ&Nf&ahyoP&qfbs",[])),
+?line <<"WyroFQabcdefghijk
+SNfabcdefghijk
+SahyoPabcdefghijk
+Sqfbs">> = iolist_to_binary(re:replace("abcdefghijk
+S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123","WyroFQ&Nf&ahyoP&qfbs",[global])),
+?line <<"fCnqsFE">> = iolist_to_binary(re:replace("abidef","ab\\idef","fCnqs\\1FE",[])),
+?line <<"fCnqsFE">> = iolist_to_binary(re:replace("abidef","ab\\idef","fCnqs\\1FE",[global])),
+?line <<"QFATItqr">> = iolist_to_binary(re:replace("bc","a{0}bc","Q\\1FAT\\1Itqr",[])),
+?line <<"QFATItqr">> = iolist_to_binary(re:replace("bc","a{0}bc","Q\\1FAT\\1Itqr",[global])),
+?line <<"DnNn">> = iolist_to_binary(re:replace("xyz","(a|(bc)){0,0}?xyz","DnNn",[])),
+?line <<"DnNn">> = iolist_to_binary(re:replace("xyz","(a|(bc)){0,0}?xyz","DnNn",[global])),
+?line <<"GlvWIgK">> = iolist_to_binary(re:replace("abcde","abc[\\10]de","GlvW\\1\\1IgK",[])),
+?line <<"GlvWIgK">> = iolist_to_binary(re:replace("abcde","abc[\\10]de","GlvW\\1\\1IgK",[global])),
+?line <<"KDyabcdeXgxxbvI">> = iolist_to_binary(re:replace("abcde","abc[\\1]de","KDy&XgxxbvI",[])),
+?line <<"KDyabcdeXgxxbvI">> = iolist_to_binary(re:replace("abcde","abc[\\1]de","KDy&XgxxbvI",[global])),
+?line <<"abcSYuXabcUabckewfJS">> = iolist_to_binary(re:replace("abcde","(abc)[\\1]de","\\1SYuX\\1U\\1kewfJS",[])),
+?line <<"abcSYuXabcUabckewfJS">> = iolist_to_binary(re:replace("abcde","(abc)[\\1]de","\\1SYuX\\1U\\1kewfJS",[global])),
+?line <<"a
+b">> = iolist_to_binary(re:replace("a
+b","(?s)a.b","&",[])),
+?line <<"a
+b">> = iolist_to_binary(re:replace("a
+b","(?s)a.b","&",[global])),
+?line <<"sILYgbGPUbaNOTcccceynxed">> = iolist_to_binary(re:replace("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","sILYg\\1GPU&eynxe",[])),
+?line <<"sILYgbGPUbaNOTcccceynxed">> = iolist_to_binary(re:replace("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","sILYg\\1GPU&eynxe",[global])),
+?line <<"bURwd">> = iolist_to_binary(re:replace("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","\\1URw",[])),
+?line <<"bURwd">> = iolist_to_binary(re:replace("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","\\1URw",[global])),
+?line <<"rRcqtdVUmd">> = iolist_to_binary(re:replace("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","rRcqtdVUm",[])),
+?line <<"rRcqtdVUmd">> = iolist_to_binary(re:replace("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","rRcqtdVUm",[global])),
+?line <<"lYsStJbdoraarRd">> = iolist_to_binary(re:replace("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","lYsStJ\\1doraarR",[])),
+?line <<"lYsStJbdoraarRd">> = iolist_to_binary(re:replace("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","lYsStJ\\1doraarR",[global])),
+?line <<"*** Failersyu**c*** Failers*KH">> = iolist_to_binary(re:replace("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})","&yu\\1\\1c&\\1KH",[])),
+?line <<"*** Failersyu**c*** Failers*KH">> = iolist_to_binary(re:replace("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})","&yu\\1\\1c&\\1KH",[global])),
+?line <<"anything">> = iolist_to_binary(re:replace("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})","&xLcPYkjD\\1YuJHCYWAIc",[])),
+?line <<"anything">> = iolist_to_binary(re:replace("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})","&xLcPYkjD\\1YuJHCYWAIc",[global])),
+?line <<"bc">> = iolist_to_binary(re:replace("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})","l\\1RIA&evjlHaNPGsYSx",[])),
+?line <<"bc">> = iolist_to_binary(re:replace("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})","l\\1RIA&evjlHaNPGsYSx",[global])),
+?line <<"baccd">> = iolist_to_binary(re:replace("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","SfTsSTMDCrU",[])),
+?line <<"baccd">> = iolist_to_binary(re:replace("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})","SfTsSTMDCrU",[global])),
+?line <<"KqAssSsdXronRAsbc">> = iolist_to_binary(re:replace("Abc","[^a]","Kq&s\\1sSsdXronR&s\\1",[])),
+?line <<"KqAssSsdXronRAsKqbssSsdXronRbsKqcssSsdXronRcs">> = iolist_to_binary(re:replace("Abc","[^a]","Kq&s\\1sSsdXronR&s\\1",[global])),
+?line <<"AXxbehbnMtJMOjc">> = iolist_to_binary(re:replace("Abc","[^a]","Xx&eh&nMtJMOj",[caseless])),
+?line <<"AXxbehbnMtJMOjXxcehcnMtJMOj">> = iolist_to_binary(re:replace("Abc","[^a]","Xx&eh&nMtJMOj",[caseless,
+ global])),
+?line <<"owGHnKmdtjnrJgSkEfaAbc">> = iolist_to_binary(re:replace("AAAaAbc","[^a]+","owGHnKmdtjnrJgSkEf",[])),
+?line <<"owGHnKmdtjnrJgSkEfaowGHnKmdtjnrJgSkEf">> = iolist_to_binary(re:replace("AAAaAbc","[^a]+","owGHnKmdtjnrJgSkEf",[global])),
+?line <<"AAAaAkm">> = iolist_to_binary(re:replace("AAAaAbc","[^a]+","\\1km",[caseless])),
+?line <<"AAAaAkm">> = iolist_to_binary(re:replace("AAAaAbc","[^a]+","\\1km",[caseless,
+ global])),
+?line <<"iBIdW">> = iolist_to_binary(re:replace("bbb
+ccc","[^a]+","iB\\1I\\1d\\1\\1W",[])),
+?line <<"iBIdW">> = iolist_to_binary(re:replace("bbb
+ccc","[^a]+","iB\\1I\\1d\\1\\1W",[global])),
+?line <<"abDIvvGgnrbcKkaSCbuiv">> = iolist_to_binary(re:replace("abc","[^k]$","DIvvGgnrb&KkaSCbuiv\\1",[])),
+?line <<"abDIvvGgnrbcKkaSCbuiv">> = iolist_to_binary(re:replace("abc","[^k]$","DIvvGgnrb&KkaSCbuiv\\1",[global])),
+?line <<"*** FailerbswvPHxEss">> = iolist_to_binary(re:replace("*** Failers","[^k]$","b&wvPH\\1xE&&",[])),
+?line <<"*** FailerbswvPHxEss">> = iolist_to_binary(re:replace("*** Failers","[^k]$","b&wvPH\\1xE&&",[global])),
+?line <<"abk">> = iolist_to_binary(re:replace("abk","[^k]$","nWvCPNUa\\1iDnbay",[])),
+?line <<"abk">> = iolist_to_binary(re:replace("abk","[^k]$","nWvCPNUa\\1iDnbay",[global])),
+?line <<"HWSo">> = iolist_to_binary(re:replace("abc","[^k]{2,3}$","HWSo\\1",[])),
+?line <<"HWSo">> = iolist_to_binary(re:replace("abc","[^k]{2,3}$","HWSo\\1",[global])),
+?line <<"kNKYkjbsvgnUOybcsuN">> = iolist_to_binary(re:replace("kbc","[^k]{2,3}$","NKYkjbsvgn\\1UO\\1y&suN",[])),
+?line <<"kNKYkjbsvgnUOybcsuN">> = iolist_to_binary(re:replace("kbc","[^k]{2,3}$","NKYkjbsvgn\\1UO\\1y&suN",[global])),
+?line <<"kDhBIRhnabc">> = iolist_to_binary(re:replace("kabc","[^k]{2,3}$","DhBIRhn&",[])),
+?line <<"kDhBIRhnabc">> = iolist_to_binary(re:replace("kabc","[^k]{2,3}$","DhBIRhn&",[global])),
+?line <<"*** FailQmVy">> = iolist_to_binary(re:replace("*** Failers","[^k]{2,3}$","QmVy",[])),
+?line <<"*** FailQmVy">> = iolist_to_binary(re:replace("*** Failers","[^k]{2,3}$","QmVy",[global])),
+?line <<"abk">> = iolist_to_binary(re:replace("abk","[^k]{2,3}$","L&e",[])),
+?line <<"abk">> = iolist_to_binary(re:replace("abk","[^k]{2,3}$","L&e",[global])),
+?line <<"akb">> = iolist_to_binary(re:replace("akb","[^k]{2,3}$","\\1aD\\1u&",[])),
+?line <<"akb">> = iolist_to_binary(re:replace("akb","[^k]{2,3}$","\\1aD\\1u&",[global])),
+?line <<"akk">> = iolist_to_binary(re:replace("akk","[^k]{2,3}$","lPwfbQvWcRAypQ&",[])),
+?line <<"akk">> = iolist_to_binary(re:replace("akk","[^k]{2,3}$","lPwfbQvWcRAypQ&",[global])),
+?line <<"12345678.b.c.d">> = iolist_to_binary(re:replace("12345678.b.c.d","^\\d{8,}\\@.+[^k]$","VKe&\\1iCfITU\\1&nqEh",[])),
+?line <<"12345678.b.c.d">> = iolist_to_binary(re:replace("12345678.b.c.d","^\\d{8,}\\@.+[^k]$","VKe&\\1iCfITU\\1&nqEh",[global])),
+?line <<"123456789.y.z">> = iolist_to_binary(re:replace("123456789.y.z","^\\d{8,}\\@.+[^k]$","aW\\1Jg&g",[])),
+?line <<"123456789.y.z">> = iolist_to_binary(re:replace("123456789.y.z","^\\d{8,}\\@.+[^k]$","aW\\1Jg&g",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\d{8,}\\@.+[^k]$","lHO",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^\\d{8,}\\@.+[^k]$","lHO",[global])),
+?line <<"12345678.y.uk">> = iolist_to_binary(re:replace("12345678.y.uk","^\\d{8,}\\@.+[^k]$","uVLRBqgT\\1c&\\1",[])),
+?line <<"12345678.y.uk">> = iolist_to_binary(re:replace("12345678.y.uk","^\\d{8,}\\@.+[^k]$","uVLRBqgT\\1c&\\1",[global])),
+?line <<"1234567.b.c.d">> = iolist_to_binary(re:replace("1234567.b.c.d","^\\d{8,}\\@.+[^k]$","VRE&uJDdtsECL",[])),
+?line <<"1234567.b.c.d">> = iolist_to_binary(re:replace("1234567.b.c.d","^\\d{8,}\\@.+[^k]$","VRE&uJDdtsECL",[global])),
+?line <<"XJnEEHYdC">> = iolist_to_binary(re:replace("aaaaaaaaa","(a)\\1{8,}","XJnEEHYdC",[])),
+?line <<"XJnEEHYdC">> = iolist_to_binary(re:replace("aaaaaaaaa","(a)\\1{8,}","XJnEEHYdC",[global])),
+?line <<"eviBaaaaaaaaaaaKMfaysHkyJyKi">> = iolist_to_binary(re:replace("aaaaaaaaaa","(a)\\1{8,}","eviB\\1&KMfaysHkyJyKi",[])),
+?line <<"eviBaaaaaaaaaaaKMfaysHkyJyKi">> = iolist_to_binary(re:replace("aaaaaaaaaa","(a)\\1{8,}","eviB\\1&KMfaysHkyJyKi",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a)\\1{8,}","DjllScfItkb&J&&Nv\\1\\1C",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a)\\1{8,}","DjllScfItkb&J&&Nv\\1\\1C",[global])),
+?line <<"aaaaaaa">> = iolist_to_binary(re:replace("aaaaaaa","(a)\\1{8,}","W&Bp\\1Bem",[])),
+?line <<"aaaaaaa">> = iolist_to_binary(re:replace("aaaaaaa","(a)\\1{8,}","W&Bp\\1Bem",[global])),
+?line <<"aaaaIEWbcd">> = iolist_to_binary(re:replace("aaaabcd","[^a]","IEW\\1&",[])),
+?line <<"aaaaIEWbIEWcIEWd">> = iolist_to_binary(re:replace("aaaabcd","[^a]","IEW\\1&",[global])),
+?line <<"aarhjHFaNDBbwVabcd">> = iolist_to_binary(re:replace("aaAabcd","[^a]","r\\1hjHFaNDBbwV",[])),
+?line <<"aarhjHFaNDBbwVarhjHFaNDBbwVrhjHFaNDBbwVrhjHFaNDBbwV">> = iolist_to_binary(re:replace("aaAabcd","[^a]","r\\1hjHFaNDBbwV",[global])),
+?line <<"aaaaUVFcd">> = iolist_to_binary(re:replace("aaaabcd","[^a]","U\\1V\\1F",[caseless])),
+?line <<"aaaaUVFUVFUVF">> = iolist_to_binary(re:replace("aaaabcd","[^a]","U\\1V\\1F",[caseless,
+ global])),
+?line <<"aaAacbLmqbDepcd">> = iolist_to_binary(re:replace("aaAabcd","[^a]","c&Lmq&De\\1p",[caseless])),
+?line <<"aaAacbLmqbDepccLmqcDepcdLmqdDep">> = iolist_to_binary(re:replace("aaAabcd","[^a]","c&Lmq&De\\1p",[caseless,
+ global])),
+?line <<"aaaaFtrLcd">> = iolist_to_binary(re:replace("aaaabcd","[^az]","Ftr\\1L",[])),
+?line <<"aaaaFtrLFtrLFtrL">> = iolist_to_binary(re:replace("aaaabcd","[^az]","Ftr\\1L",[global])),
+?line <<"aaxQoACFyabcd">> = iolist_to_binary(re:replace("aaAabcd","[^az]","xQo&C\\1Fy",[])),
+?line <<"aaxQoACFyaxQobCFyxQocCFyxQodCFy">> = iolist_to_binary(re:replace("aaAabcd","[^az]","xQo&C\\1Fy",[global])),
+?line <<"aaaawiJAqfcd">> = iolist_to_binary(re:replace("aaaabcd","[^az]","wiJ\\1Aq\\1f",[caseless])),
+?line <<"aaaawiJAqfwiJAqfwiJAqf">> = iolist_to_binary(re:replace("aaaabcd","[^az]","wiJ\\1Aq\\1f",[caseless,
+ global])),
+?line <<"aaAabheGsmSFcd">> = iolist_to_binary(re:replace("aaAabcd","[^az]","bheGsmSF",[caseless])),
+?line <<"aaAabheGsmSFbheGsmSFbheGsmSF">> = iolist_to_binary(re:replace("aaAabcd","[^az]","bheGsmSF",[caseless,
+ global])),
+?line <<"xxxxxxxxxxxINrCehGlgxfQWogKhXjxxxxxxxxx">> = iolist_to_binary(re:replace("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL","INrCe\\1hGlgxfQWogKhXj",[])),
+?line <<"xxxxxxxxxxxINrCehGlgxfQWogKhXjxxxxxxxxx">> = iolist_to_binary(re:replace("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL","INrCe\\1hGlgxfQWogKhXj",[global])),
+?line <<"xxxxxxxxxxxSxxxxxxxxx">> = iolist_to_binary(re:replace("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL","S",[])),
+?line <<"xxxxxxxxxxxSxxxxxxxxx">> = iolist_to_binary(re:replace("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL","S",[global])),
+?line <<"1yO.230003938DJNUSE">> = iolist_to_binary(re:replace("1.230003938","(\\.\\d\\d[1-9]?)\\d+","yO&DJNUSE",[])),
+?line <<"1yO.230003938DJNUSE">> = iolist_to_binary(re:replace("1.230003938","(\\.\\d\\d[1-9]?)\\d+","yO&DJNUSE",[global])),
+?line <<"1J.875RCmc.875000282SWmWrLgf">> = iolist_to_binary(re:replace("1.875000282","(\\.\\d\\d[1-9]?)\\d+","J\\1RCmc&SWmWrLgf",[])),
+?line <<"1J.875RCmc.875000282SWmWrLgf">> = iolist_to_binary(re:replace("1.875000282","(\\.\\d\\d[1-9]?)\\d+","J\\1RCmc&SWmWrLgf",[global])),
+?line <<"1QhH.23A.235j">> = iolist_to_binary(re:replace("1.235","(\\.\\d\\d[1-9]?)\\d+","QhH\\1A&j",[])),
+?line <<"1QhH.23A.235j">> = iolist_to_binary(re:replace("1.235","(\\.\\d\\d[1-9]?)\\d+","QhH\\1A&j",[global])),
+?line <<"1ASoXGLgq.23gkJkohg.23pCC0003938">> = iolist_to_binary(re:replace("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))","ASoXGLgq\\1gkJkohg\\1pCC",[])),
+?line <<"1ASoXGLgq.23gkJkohg.23pCC0003938">> = iolist_to_binary(re:replace("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))","ASoXGLgq\\1gkJkohg\\1pCC",[global])),
+?line <<"1rmMcAlqV000282">> = iolist_to_binary(re:replace("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))","rmMcAlqV",[])),
+?line <<"1rmMcAlqV000282">> = iolist_to_binary(re:replace("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))","rmMcAlqV",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))","wpfYj&AA",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))","wpfYj&AA",[global])),
+?line <<"1.235">> = iolist_to_binary(re:replace("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))","Kq&VgFL",[])),
+?line <<"1.235">> = iolist_to_binary(re:replace("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))","Kq&VgFL",[global])),
+?line <<"JabwbScGoababcabE">> = iolist_to_binary(re:replace("ab","a(?)b","J&wbSc\\1Go&\\1\\1&c&E",[])),
+?line <<"JabwbScGoababcabE">> = iolist_to_binary(re:replace("ab","a(?)b","J&wbSc\\1Go&\\1\\1&c&E",[global])),
+?line <<"Food is on the Qfoo tableygvPjujxbaDigfoo tablegCefoo table">> = iolist_to_binary(re:replace("Food is on the foo table","\\b(foo)\\s+(\\w+)","Q&ygvPjujxbaDig&gCe&",[caseless])),
+?line <<"Food is on the Qfoo tableygvPjujxbaDigfoo tablegCefoo table">> = iolist_to_binary(re:replace("Food is on the foo table","\\b(foo)\\s+(\\w+)","Q&ygvPjujxbaDig&gCe&",[caseless,
+ global])),
+?line <<"The pBETCXLfood is under the bar in the barWwWn.">> = iolist_to_binary(re:replace("The food is under the bar in the barn.","foo(.*)bar","pBETCXL&WwW",[])),
+?line <<"The pBETCXLfood is under the bar in the barWwWn.">> = iolist_to_binary(re:replace("The food is under the bar in the barn.","foo(.*)bar","pBETCXL&WwW",[global])),
+?line <<"The nfood is under the barnSrtWBfood is under the barXapfood is under the barr in the barn.">> = iolist_to_binary(re:replace("The food is under the bar in the barn.","foo(.*?)bar","n&nSrtWB&Xap&r",[])),
+?line <<"The nfood is under the barnSrtWBfood is under the barXapfood is under the barr in the barn.">> = iolist_to_binary(re:replace("The food is under the bar in the barn.","foo(.*?)bar","n&nSrtWB&Xap&r",[global])),
+?line <<"GI have 2 numbers: 53147mNBu">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)(\\d*)","G&mNBu",[])),
+?line <<"GI have 2 numbers: 53147mNBuGmNBu">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)(\\d*)","G&mNBu",[global])),
+?line <<"SI have 2 numbers: 53147">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)(\\d+)","S&",[])),
+?line <<"SI have 2 numbers: 53147">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)(\\d+)","S&",[global])),
+?line <<"bGeEvMOYIVDQHJHuI have 2 numbers: 53147">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*?)(\\d*)","bGeEvMO\\1YIVDQ&HJHu",[])),
+?line <<"bGeEvMOYIVDQHJHubGeEvMOIYIVDQIHJHubGeEvMOYIVDQHJHubGeEvMO YIVDQ HJHubGeEvMOYIVDQHJHubGeEvMOhYIVDQhHJHubGeEvMOYIVDQHJHubGeEvMOaYIVDQaHJHubGeEvMOYIVDQHJHubGeEvMOvYIVDQvHJHubGeEvMOYIVDQHJHubGeEvMOeYIVDQeHJHubGeEvMOYIVDQHJHubGeEvMO YIVDQ 2HJHubGeEvMOYIVDQHJHubGeEvMO YIVDQ HJHubGeEvMOYIVDQHJHubGeEvMOnYIVDQnHJHubGeEvMOYIVDQHJHubGeEvMOuYIVDQuHJHubGeEvMOYIVDQHJHubGeEvMOmYIVDQmHJHubGeEvMOYIVDQHJHubGeEvMObYIVDQbHJHubGeEvMOYIVDQHJHubGeEvMOeYIVDQeHJHubGeEvMOYIVDQHJHubGeEvMOrYIVDQrHJHubGeEvMOYIVDQHJHubGeEvMOsYIVDQsHJHubGeEvMOYIVDQHJHubGeEvMO:YIVDQ:HJHubGeEvMOYIVDQHJHubGeEvMO YIVDQ 53147HJHubGeEvMOYIVDQHJHu">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*?)(\\d*)","bGeEvMO\\1YIVDQ&HJHu",[global])),
+?line <<"I have tsI have cgpNHsI have I have U numbers: 53147">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*?)(\\d+)","\\1ts\\1cgpNHs\\1\\1U",[])),
+?line <<"I have tsI have cgpNHsI have I have U numbers: ts numbers: cgpNHs numbers: numbers: U">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*?)(\\d+)","\\1ts\\1cgpNHs\\1\\1U",[global])),
+?line <<"mwinCVPlFdkqGucU">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)(\\d+)$","mwinCVPlFdkqGucU",[])),
+?line <<"mwinCVPlFdkqGucU">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)(\\d+)$","mwinCVPlFdkqGucU",[global])),
+?line <<"I have 2 numbers: 53147I have 2 numbers: 53147I have 2 numbers: 53147pEI have 2 numbers: 53147o">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*?)(\\d+)$","&&&pE&o",[])),
+?line <<"I have 2 numbers: 53147I have 2 numbers: 53147I have 2 numbers: 53147pEI have 2 numbers: 53147o">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*?)(\\d+)$","&&&pE&o",[global])),
+?line <<"ym">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)\\b(\\d+)$","ym",[])),
+?line <<"ym">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*)\\b(\\d+)$","ym",[global])),
+?line <<"FI have 2 numbers: 53147QSb">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*\\D)(\\d+)$","F&QSb",[])),
+?line <<"FI have 2 numbers: 53147QSb">> = iolist_to_binary(re:replace("I have 2 numbers: 53147","(.*\\D)(\\d+)$","F&QSb",[global])),
+?line <<"ABDGFHjyNAtuJTC123">> = iolist_to_binary(re:replace("ABC123","^\\D*(?!123)","&DGFHjyNAtu\\1JT",[])),
+?line <<"ABDGFHjyNAtuJTC123">> = iolist_to_binary(re:replace("ABC123","^\\D*(?!123)","&DGFHjyNAtu\\1JT",[global])),
+?line <<"ABCUxCRnmWFAQVrawlJ445">> = iolist_to_binary(re:replace("ABC445","^(\\D*)(?=\\d)(?!123)","\\1UxCRnmWFAQVrawlJ",[])),
+?line <<"ABCUxCRnmWFAQVrawlJ445">> = iolist_to_binary(re:replace("ABC445","^(\\D*)(?=\\d)(?!123)","\\1UxCRnmWFAQVrawlJ",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\D*)(?=\\d)(?!123)","X\\1uOv\\1PUbsw&IOcqB",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\D*)(?=\\d)(?!123)","X\\1uOv\\1PUbsw&IOcqB",[global])),
+?line <<"ABC123">> = iolist_to_binary(re:replace("ABC123","^(\\D*)(?=\\d)(?!123)","&efpcH\\1vtp",[])),
+?line <<"ABC123">> = iolist_to_binary(re:replace("ABC123","^(\\D*)(?=\\d)(?!123)","&efpcH\\1vtp",[global])),
+?line <<"X789">> = iolist_to_binary(re:replace("W46]789","^[W-]46]","X",[])),
+?line <<"X789">> = iolist_to_binary(re:replace("W46]789","^[W-]46]","X",[global])),
+?line <<"-46]GqowvnKBMivOCTQ789">> = iolist_to_binary(re:replace("-46]789","^[W-]46]","&GqowvnKBMiv\\1\\1OCTQ",[])),
+?line <<"-46]GqowvnKBMivOCTQ789">> = iolist_to_binary(re:replace("-46]789","^[W-]46]","&GqowvnKBMiv\\1\\1OCTQ",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[W-]46]","VkkVbQHsQJe\\1Oqgp",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[W-]46]","VkkVbQHsQJe\\1Oqgp",[global])),
+?line <<"Wall">> = iolist_to_binary(re:replace("Wall","^[W-]46]","A\\1",[])),
+?line <<"Wall">> = iolist_to_binary(re:replace("Wall","^[W-]46]","A\\1",[global])),
+?line <<"Zebra">> = iolist_to_binary(re:replace("Zebra","^[W-]46]","D\\1Gh&rRMY",[])),
+?line <<"Zebra">> = iolist_to_binary(re:replace("Zebra","^[W-]46]","D\\1Gh&rRMY",[global])),
+?line <<"42">> = iolist_to_binary(re:replace("42","^[W-]46]","&NkVKe",[])),
+?line <<"42">> = iolist_to_binary(re:replace("42","^[W-]46]","&NkVKe",[global])),
+?line <<"[abcd]">> = iolist_to_binary(re:replace("[abcd]","^[W-]46]","UhUNReBRM\\1AIyjJpNT",[])),
+?line <<"[abcd]">> = iolist_to_binary(re:replace("[abcd]","^[W-]46]","UhUNReBRM\\1AIyjJpNT",[global])),
+?line <<"]abcd[">> = iolist_to_binary(re:replace("]abcd[","^[W-]46]","&GSivk\\1PUgboDgD\\1Q&\\1",[])),
+?line <<"]abcd[">> = iolist_to_binary(re:replace("]abcd[","^[W-]46]","&GSivk\\1PUgboDgD\\1Q&\\1",[global])),
+?line <<"CiPKWjJuYp46]789">> = iolist_to_binary(re:replace("W46]789","^[W-\\]46]","CiPK&jJuYp",[])),
+?line <<"CiPKWjJuYp46]789">> = iolist_to_binary(re:replace("W46]789","^[W-\\]46]","CiPK&jJuYp",[global])),
+?line <<"WWGQUxNBall">> = iolist_to_binary(re:replace("Wall","^[W-\\]46]","&&G\\1QUxN\\1\\1B",[])),
+?line <<"WWGQUxNBall">> = iolist_to_binary(re:replace("Wall","^[W-\\]46]","&&G\\1QUxN\\1\\1B",[global])),
+?line <<"BBmKOIEOebra">> = iolist_to_binary(re:replace("Zebra","^[W-\\]46]","BBmKOI\\1E\\1O",[])),
+?line <<"BBmKOIEOebra">> = iolist_to_binary(re:replace("Zebra","^[W-\\]46]","BBmKOI\\1E\\1O",[global])),
+?line <<"fsXBGOoQmDJroEwPIXEylophone">> = iolist_to_binary(re:replace("Xylophone","^[W-\\]46]","f\\1s&BGOoQmDJroEwPI&E",[])),
+?line <<"fsXBGOoQmDJroEwPIXEylophone">> = iolist_to_binary(re:replace("Xylophone","^[W-\\]46]","f\\1s&BGOoQmDJroEwPI&E",[global])),
+?line <<"rtkSPUv4c2">> = iolist_to_binary(re:replace("42","^[W-\\]46]","rtkSPUv&c",[])),
+?line <<"rtkSPUv4c2">> = iolist_to_binary(re:replace("42","^[W-\\]46]","rtkSPUv&c",[global])),
+?line <<"AqCR[dTqabcd]">> = iolist_to_binary(re:replace("[abcd]","^[W-\\]46]","AqCR\\1&dTq",[])),
+?line <<"AqCR[dTqabcd]">> = iolist_to_binary(re:replace("[abcd]","^[W-\\]46]","AqCR\\1&dTq",[global])),
+?line <<"XG]JRsPEtL]eoabcd[">> = iolist_to_binary(re:replace("]abcd[","^[W-\\]46]","XG&JRsPEtL&e\\1o",[])),
+?line <<"XG]JRsPEtL]eoabcd[">> = iolist_to_binary(re:replace("]abcd[","^[W-\\]46]","XG&JRsPEtL&e\\1o",[global])),
+?line <<"YrokgJhnnbackslash">> = iolist_to_binary(re:replace("\\backslash","^[W-\\]46]","YrokgJhnn\\1",[])),
+?line <<"YrokgJhnnbackslash">> = iolist_to_binary(re:replace("\\backslash","^[W-\\]46]","YrokgJhnn\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[W-\\]46]","\\1l",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[W-\\]46]","\\1l",[global])),
+?line <<"-46]789">> = iolist_to_binary(re:replace("-46]789","^[W-\\]46]","lUctT",[])),
+?line <<"-46]789">> = iolist_to_binary(re:replace("-46]789","^[W-\\]46]","lUctT",[global])),
+?line <<"well">> = iolist_to_binary(re:replace("well","^[W-\\]46]","xELX&QH\\1AsnFr&SH",[])),
+?line <<"well">> = iolist_to_binary(re:replace("well","^[W-\\]46]","xELX&QH\\1AsnFr&SH",[global])),
+?line <<"vword cat dog elephant mussel cow horse canary baboon snake shark otherwordword cat dog elephant mussel cow horse canary baboon snake shark otherwordKteyKVE">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword","v&&KteyKVE\\1",[])),
+?line <<"vword cat dog elephant mussel cow horse canary baboon snake shark otherwordword cat dog elephant mussel cow horse canary baboon snake shark otherwordKteyKVE">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword","v&&KteyKVE\\1",[global])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword","wUEYJgw",[])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword","wUEYJgw",[global])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword","ABdTK\\1Y\\1",[])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword","ABdTK\\1Y\\1",[global])),
+?line <<"CahYoKbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,0}","\\1CahYoK",[])),
+?line <<"CahYoKbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,0}","\\1CahYoK",[global])),
+?line <<"QcFabc">> = iolist_to_binary(re:replace("abc","^(a){0,0}","&QcF",[])),
+?line <<"QcFabc">> = iolist_to_binary(re:replace("abc","^(a){0,0}","&QcF",[global])),
+?line <<"BvdvcHaab">> = iolist_to_binary(re:replace("aab","^(a){0,0}","BvdvcH",[])),
+?line <<"BvdvcHaab">> = iolist_to_binary(re:replace("aab","^(a){0,0}","BvdvcH",[global])),
+?line <<"mtLIyIhdbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,1}","mtL\\1IyIhd",[])),
+?line <<"mtLIyIhdbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,1}","mtL\\1IyIhd",[global])),
+?line <<"oaDoeSPhHaNhncDqkiabc">> = iolist_to_binary(re:replace("abc","^(a){0,1}","o\\1DoeSPhH&NhncDqki\\1",[])),
+?line <<"oaDoeSPhHaNhncDqkiabc">> = iolist_to_binary(re:replace("abc","^(a){0,1}","o\\1DoeSPhH&NhncDqki\\1",[global])),
+?line <<"ahaXsxVhaFHSKPhapJQab">> = iolist_to_binary(re:replace("aab","^(a){0,1}","\\1h\\1XsxVh\\1FHSKPhapJQ",[])),
+?line <<"ahaXsxVhaFHSKPhapJQab">> = iolist_to_binary(re:replace("aab","^(a){0,1}","\\1h\\1XsxVh\\1FHSKPhapJQ",[global])),
+?line <<"iMdtvIhKruTIdObcd">> = iolist_to_binary(re:replace("bcd","^(a){0,2}","iMd\\1\\1\\1tvIhK&r\\1uTIdO",[])),
+?line <<"iMdtvIhKruTIdObcd">> = iolist_to_binary(re:replace("bcd","^(a){0,2}","iMd\\1\\1\\1tvIhK&r\\1uTIdO",[global])),
+?line <<"WYRQMjbc">> = iolist_to_binary(re:replace("abc","^(a){0,2}","WYRQMj",[])),
+?line <<"WYRQMjbc">> = iolist_to_binary(re:replace("abc","^(a){0,2}","WYRQMj",[global])),
+?line <<"XaKbiaaaEub">> = iolist_to_binary(re:replace("aab","^(a){0,2}","XaKbi\\1&Eu",[])),
+?line <<"XaKbiaaaEub">> = iolist_to_binary(re:replace("aab","^(a){0,2}","XaKbi\\1&Eu",[global])),
+?line <<"cFVbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,3}","&c&&FV&",[])),
+?line <<"cFVbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,3}","&c&&FV&",[global])),
+?line <<"gEwbc">> = iolist_to_binary(re:replace("abc","^(a){0,3}","gEw",[])),
+?line <<"gEwbc">> = iolist_to_binary(re:replace("abc","^(a){0,3}","gEw",[global])),
+?line <<"aaSuagMaaaaIaaKb">> = iolist_to_binary(re:replace("aab","^(a){0,3}","&Su\\1gM&&I&K",[])),
+?line <<"aaSuagMaaaaIaaKb">> = iolist_to_binary(re:replace("aab","^(a){0,3}","&Su\\1gM&&I&K",[global])),
+?line <<"GaaahIAJLaMaaaaaSBlaaaTU">> = iolist_to_binary(re:replace("aaa","^(a){0,3}","G&hIAJL\\1M\\1&aSBl&TU",[])),
+?line <<"GaaahIAJLaMaaaaaSBlaaaTU">> = iolist_to_binary(re:replace("aaa","^(a){0,3}","G&hIAJL\\1M\\1&aSBl&TU",[global])),
+?line <<"gcHyvqMebtbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,}","g&cHyvqMebt",[])),
+?line <<"gcHyvqMebtbcd">> = iolist_to_binary(re:replace("bcd","^(a){0,}","g&cHyvqMebt",[global])),
+?line <<"atJaOxHRjOnDbc">> = iolist_to_binary(re:replace("abc","^(a){0,}","\\1tJaOxHRjOnD",[])),
+?line <<"atJaOxHRjOnDbc">> = iolist_to_binary(re:replace("abc","^(a){0,}","\\1tJaOxHRjOnD",[global])),
+?line <<"RlAewSMFrb">> = iolist_to_binary(re:replace("aab","^(a){0,}","RlAewSMFr",[])),
+?line <<"RlAewSMFrb">> = iolist_to_binary(re:replace("aab","^(a){0,}","RlAewSMFr",[global])),
+?line <<"FaaaBoIPaaaLaaaYfapavgA">> = iolist_to_binary(re:replace("aaa","^(a){0,}","F&BoIP&L&Yf\\1p\\1vgA",[])),
+?line <<"FaaaBoIPaaaLaaaYfapavgA">> = iolist_to_binary(re:replace("aaa","^(a){0,}","F&BoIP&L&Yf\\1p\\1vgA",[global])),
+?line <<"baaaaaaaacUvaaaaaaaaCgeDoaRaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaa","^(a){0,}","b&cUv&CgeDo\\1R&",[])),
+?line <<"baaaaaaaacUvaaaaaaaaCgeDoaRaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaa","^(a){0,}","b&cUv&CgeDo\\1R&",[global])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,1}","F&vqwCbrndilc&Wb\\1LRH",[])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,1}","F&vqwCbrndilc&Wb\\1LRH",[global])),
+?line <<"BaaYanfjPxbaabc">> = iolist_to_binary(re:replace("abc","^(a){1,1}","B\\1\\1Y\\1nfjPxb\\1&",[])),
+?line <<"BaaYanfjPxbaabc">> = iolist_to_binary(re:replace("abc","^(a){1,1}","B\\1\\1Y\\1nfjPxb\\1&",[global])),
+?line <<"Pab">> = iolist_to_binary(re:replace("aab","^(a){1,1}","P",[])),
+?line <<"Pab">> = iolist_to_binary(re:replace("aab","^(a){1,1}","P",[global])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,2}","eK\\1B&ypECvCJqtlr&",[])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,2}","eK\\1B&ypECvCJqtlr&",[global])),
+?line <<"QYbc">> = iolist_to_binary(re:replace("abc","^(a){1,2}","QY",[])),
+?line <<"QYbc">> = iolist_to_binary(re:replace("abc","^(a){1,2}","QY",[global])),
+?line <<"MbnVXaaaXhaab">> = iolist_to_binary(re:replace("aab","^(a){1,2}","MbnVX&\\1Xh&",[])),
+?line <<"MbnVXaaaXhaab">> = iolist_to_binary(re:replace("aab","^(a){1,2}","MbnVX&\\1Xh&",[global])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,3}","UKOH",[])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,3}","UKOH",[global])),
+?line <<"TJxKYHFmaaadWlbc">> = iolist_to_binary(re:replace("abc","^(a){1,3}","TJxKYHFm&&\\1dWl",[])),
+?line <<"TJxKYHFmaaadWlbc">> = iolist_to_binary(re:replace("abc","^(a){1,3}","TJxKYHFm&&\\1dWl",[global])),
+?line <<"ub">> = iolist_to_binary(re:replace("aab","^(a){1,3}","u",[])),
+?line <<"ub">> = iolist_to_binary(re:replace("aab","^(a){1,3}","u",[global])),
+?line <<"FOX">> = iolist_to_binary(re:replace("aaa","^(a){1,3}","FOX",[])),
+?line <<"FOX">> = iolist_to_binary(re:replace("aaa","^(a){1,3}","FOX",[global])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,}","W\\1Urn\\1O",[])),
+?line <<"bcd">> = iolist_to_binary(re:replace("bcd","^(a){1,}","W\\1Urn\\1O",[global])),
+?line <<"awalKDgaLeMbc">> = iolist_to_binary(re:replace("abc","^(a){1,}","\\1w\\1lKDg&LeM",[])),
+?line <<"awalKDgaLeMbc">> = iolist_to_binary(re:replace("abc","^(a){1,}","\\1w\\1lKDg&LeM",[global])),
+?line <<"qHVYNjismoeaasSpb">> = iolist_to_binary(re:replace("aab","^(a){1,}","qHVYNjismoe&sSp",[])),
+?line <<"qHVYNjismoeaasSpb">> = iolist_to_binary(re:replace("aab","^(a){1,}","qHVYNjismoe&sSp",[global])),
+?line <<"laa">> = iolist_to_binary(re:replace("aaa","^(a){1,}","la\\1",[])),
+?line <<"laa">> = iolist_to_binary(re:replace("aaa","^(a){1,}","la\\1",[global])),
+?line <<"UJqaaaaaaaarcaaaaaaaaQHavwaGaaaaaaaaoX">> = iolist_to_binary(re:replace("aaaaaaaa","^(a){1,}","UJq&rc&QH\\1vw\\1G&oX",[])),
+?line <<"UJqaaaaaaaarcaaaaaaaaQHavwaGaaaaaaaaoX">> = iolist_to_binary(re:replace("aaaaaaaa","^(a){1,}","UJq&rc&QH\\1vw\\1G&oX",[global])),
+?line <<"borfle
+bib.gifs
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","&s",[])),
+?line <<"borfle
+bib.gifs
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","&s",[global])),
+?line <<"borfle
+BD
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".{0,}\\.gif","BD",[])),
+?line <<"borfle
+BD
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".{0,}\\.gif","BD",[global])),
+?line <<"borfle
+vDNsbib.gif
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","vDNs&",[multiline])),
+?line <<"borfle
+vDNsbib.gif
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","vDNs&",[multiline,global])),
+?line <<"dPgeQVbGVD
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","dPge\\1QVbGVD",[dotall])),
+?line <<"dPgeQVbGVD
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","dPge\\1QVbGVD",[dotall,global])),
+?line <<"SGyevborfle
+bib.gifCborfle
+bib.gifWUWborfle
+bib.gifHd
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","SGyev&C\\1&\\1\\1WUW&Hd\\1",[multiline,dotall])),
+?line <<"SGyevborfle
+bib.gifCborfle
+bib.gifWUWborfle
+bib.gifHd
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*\\.gif","SGyev&C\\1&\\1\\1WUW&Hd\\1",[multiline,dotall,
+ global])),
+?line <<"borfle
+bib.gif
+BKNYIMcbaV">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","BKNYIMcbaV",[])),
+?line <<"borfle
+bib.gif
+BKNYIMcbaVBKNYIMcbaV">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","BKNYIMcbaV",[global])),
+?line <<"borflevLMilNh
+bib.gif
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","&vLM\\1ilNh\\1",[multiline])),
+?line <<"borflevLMilNhvLMilNh
+bib.gifvLMilNhvLMilNh
+novLMilNhvLMilNh">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","&vLM\\1ilNh\\1",[multiline,global])),
+?line <<"borfle
+bib.gif
+noMtTxgborfle
+bib.gif
+noXborfle
+bib.gif
+noqKCRborfle
+bib.gif
+noRFb">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","&MtTxg&X&qKCR&RFb",[dotall])),
+?line <<"borfle
+bib.gif
+noMtTxgborfle
+bib.gif
+noXborfle
+bib.gif
+noqKCRborfle
+bib.gif
+noRFbMtTxgXqKCRRFb">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","&MtTxg&X&qKCR&RFb",[dotall,global])),
+?line <<"rb">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","rb",[multiline,dotall])),
+?line <<"rbrb">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","rb",[multiline,dotall,global])),
+?line <<"borfle
+bib.gif
+dTAVnoxiWSnoiuGHA">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","dTAV&xiWS&iuGH\\1A\\1",[])),
+?line <<"borfle
+bib.gif
+dTAVnoxiWSnoiuGHAdTAVxiWSiuGHA">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","dTAV&xiWS&iuGH\\1A\\1",[global])),
+?line <<"bcnFLv
+bib.gif
+no">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","bc\\1nFLv",[multiline])),
+?line <<"bcnFLvbcnFLv
+bcnFLvbcnFLv
+bcnFLvbcnFLv">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","bc\\1nFLv",[multiline,global])),
+?line <<"LIQosdWborfle
+bib.gif
+nogcg">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","L\\1IQosd\\1W&gcg",[dotall])),
+?line <<"LIQosdWborfle
+bib.gif
+nogcgLIQosdWgcg">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","L\\1IQosd\\1W&gcg",[dotall,global])),
+?line <<"EcPBKTVborfle
+bib.gif
+noPrborfle
+bib.gif
+noXxQSc">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","EcPBKTV&Pr&XxQSc",[multiline,dotall])),
+?line <<"EcPBKTVborfle
+bib.gif
+noPrborfle
+bib.gif
+noXxQScEcPBKTVPrXxQSc">> = iolist_to_binary(re:replace("borfle
+bib.gif
+no",".*$","EcPBKTV&Pr&XxQSc",[multiline,dotall,global])),
+?line <<"abcde
+uhjjD1234XGTHcATIgH1234X1234XfbByz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","uhjjD&GTHcATIgH\\1\\1fbB",[])),
+?line <<"abcde
+uhjjD1234XGTHcATIgH1234X1234XfbByz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","uhjjD&GTHcATIgH\\1\\1fbB",[global])),
+?line <<"dbqarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","dbq",[])),
+?line <<"dbqarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","dbq",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(.*X|^B)","tdwOa\\1DQul",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(.*X|^B)","tdwOa\\1DQul",[global])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","j\\1&fFKu\\1tLJjDOmjkU",[])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","j\\1&fFKu\\1tLJjDOmjkU",[global])),
+?line <<"abcde
+cKv1234XFUwb1234XTPoWrycn1234XO1234Xyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","cKv&FUwb&TPoWrycn&O&",[multiline])),
+?line <<"abcde
+cKv1234XFUwb1234XTPoWrycn1234XO1234Xyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","cKv&FUwb&TPoWrycn&O&",[multiline,global])),
+?line <<"FBHWgBBNiGBkIarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","F&HWgB\\1NiG\\1kI",[multiline])),
+?line <<"FBHWgBBNiGBkIarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","F&HWgB\\1NiG\\1kI",[multiline,
+ global])),
+?line <<"abcde
+oArar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","oAr",[multiline])),
+?line <<"abcde
+oArar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","oAr",[multiline,global])),
+?line <<"abcde
+1234XpqJuWOcKJabcde
+1234XyXyvjQwPyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","\\1pqJuWOcKJ&yXyvjQwP",[dotall])),
+?line <<"abcde
+1234XpqJuWOcKJabcde
+1234XyXyvjQwPyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","\\1pqJuWOcKJ&yXyvjQwP",[dotall,global])),
+?line <<"BHBBBLbBBgBBoarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","BH\\1&&Lb&&g\\1&o",[dotall])),
+?line <<"BHBBBLbBBgBBoarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","BH\\1&&Lb&&g\\1&o",[dotall,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(.*X|^B)","&LH",[dotall])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(.*X|^B)","&LH",[dotall,
+ global])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","HvNOg&qrhICiO",[dotall])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","HvNOg&qrhICiO",[dotall,global])),
+?line <<"eabcde
+1234Xabcde
+1234XIRtabcde
+1234Xabcde
+1234XCMGsyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","e\\1&IRt\\1\\1CMGs",[multiline,dotall])),
+?line <<"eabcde
+1234Xabcde
+1234XIRtabcde
+1234Xabcde
+1234XCMGsyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(.*X|^B)","e\\1&IRt\\1\\1CMGs",[multiline,dotall,global])),
+?line <<"MBMpwYiMLarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","M\\1MpwYiML",[multiline,
+ dotall])),
+?line <<"MBMpwYiMLarFoo">> = iolist_to_binary(re:replace("BarFoo","(.*X|^B)","M\\1MpwYiML",[multiline,
+ dotall,
+ global])),
+?line <<"abcde
+SOfar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","SOf",[multiline,dotall])),
+?line <<"abcde
+SOfar">> = iolist_to_binary(re:replace("abcde
+Bar","(.*X|^B)","SOf",[multiline,dotall,global])),
+?line <<"Xabcde
+1234XIabcde
+1234XJyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(?s)(.*X|^B)","X\\1I\\1J",[])),
+?line <<"Xabcde
+1234XIabcde
+1234XJyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(?s)(.*X|^B)","X\\1I\\1J",[global])),
+?line <<"ckBBgDwBNcECcmswBGrarFoo">> = iolist_to_binary(re:replace("BarFoo","(?s)(.*X|^B)","ck\\1&gDw\\1NcECcmsw\\1Gr",[])),
+?line <<"ckBBgDwBNcECcmswBGrarFoo">> = iolist_to_binary(re:replace("BarFoo","(?s)(.*X|^B)","ck\\1&gDw\\1NcECcmsw\\1Gr",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?s)(.*X|^B)","dC\\1SBGDJPgKye\\1iTO",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?s)(.*X|^B)","dC\\1SBGDJPgKye\\1iTO",[global])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(?s)(.*X|^B)","hL&HrGi&",[])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(?s)(.*X|^B)","hL&HrGi&",[global])),
+?line <<"abcde
+1234Xabcde
+1234XBpOYYilckyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(?s:.*X|^B)","&&\\1BpOYYilck",[])),
+?line <<"abcde
+1234Xabcde
+1234XBpOYYilckyz">> = iolist_to_binary(re:replace("abcde
+1234Xyz","(?s:.*X|^B)","&&\\1BpOYYilck",[global])),
+?line <<"ThDpSQKkSSwHarFoo">> = iolist_to_binary(re:replace("BarFoo","(?s:.*X|^B)","ThDpSQ\\1KkSSwH",[])),
+?line <<"ThDpSQKkSSwHarFoo">> = iolist_to_binary(re:replace("BarFoo","(?s:.*X|^B)","ThDpSQ\\1KkSSwH",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?s:.*X|^B)","H&KaW\\1iKok\\1x&gEh&",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?s:.*X|^B)","H&KaW\\1iKok\\1x&gEh&",[global])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(?s:.*X|^B)","DFJoEUQUCdLTM\\1ETp",[])),
+?line <<"abcde
+Bar">> = iolist_to_binary(re:replace("abcde
+Bar","(?s:.*X|^B)","DFJoEUQUCdLTM\\1ETp",[global])),
+?line <<"**** Failers">> = iolist_to_binary(re:replace("**** Failers","^.*B","N&QUc\\1IX&",[])),
+?line <<"**** Failers">> = iolist_to_binary(re:replace("**** Failers","^.*B","N&QUc\\1IX&",[global])),
+?line <<"abc
+B">> = iolist_to_binary(re:replace("abc
+B","^.*B","XtHO\\1dFCGIat\\1\\1A\\1yj\\1",[])),
+?line <<"abc
+B">> = iolist_to_binary(re:replace("abc
+B","^.*B","XtHO\\1dFCGIat\\1\\1A\\1yj\\1",[global])),
+?line <<"yKkFabc
+B">> = iolist_to_binary(re:replace("abc
+B","(?s)^.*B","yKkF&",[])),
+?line <<"yKkFabc
+B">> = iolist_to_binary(re:replace("abc
+B","(?s)^.*B","yKkF&",[global])),
+?line <<"abc
+AQ">> = iolist_to_binary(re:replace("abc
+B","(?m)^.*B","AQ",[])),
+?line <<"abc
+AQ">> = iolist_to_binary(re:replace("abc
+B","(?m)^.*B","AQ",[global])),
+?line <<"abc
+BDabc
+BNabc
+BxoUHHabc
+Bo">> = iolist_to_binary(re:replace("abc
+B","(?ms)^.*B","\\1&D&N&xoUHH&o",[])),
+?line <<"abc
+BDabc
+BNabc
+BxoUHHabc
+Bo">> = iolist_to_binary(re:replace("abc
+B","(?ms)^.*B","\\1&D&N&xoUHH&o",[global])),
+?line <<"abc
+GHgiVrjlVxqskBLR">> = iolist_to_binary(re:replace("abc
+B","(?ms)^B","GHgiVrj\\1lVxqsk&LR",[])),
+?line <<"abc
+GHgiVrjlVxqskBLR">> = iolist_to_binary(re:replace("abc
+B","(?ms)^B","GHgiVrj\\1lVxqsk&LR",[global])),
+?line <<"RBtqaBce">> = iolist_to_binary(re:replace("B","(?s)B$","R&tqaBce",[])),
+?line <<"RBtqaBce">> = iolist_to_binary(re:replace("B","(?s)B$","R&tqaBce",[global])),
+?line <<"kG">> = iolist_to_binary(re:replace("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]","kG",[])),
+?line <<"kG">> = iolist_to_binary(re:replace("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]","kG",[global])),
+?line <<"Tx">> = iolist_to_binary(re:replace("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d","Tx",[])),
+?line <<"Tx">> = iolist_to_binary(re:replace("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d","Tx",[global])),
+?line <<"xB">> = iolist_to_binary(re:replace("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]","xB",[])),
+?line <<"xB">> = iolist_to_binary(re:replace("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]","xB",[global])),
+?line <<"OpPIBabcabcabcabcDcxDvlIdSoa">> = iolist_to_binary(re:replace("abcabcabcabc","^[abc]{12}","OpPIB&DcxDvlIdSoa",[])),
+?line <<"OpPIBabcabcabcabcDcxDvlIdSoa">> = iolist_to_binary(re:replace("abcabcabcabc","^[abc]{12}","OpPIB&DcxDvlIdSoa",[global])),
+?line <<"LGEMfnjAabcabcabcabcuAotOabcabcabcabcs">> = iolist_to_binary(re:replace("abcabcabcabc","^[a-c]{12}","LGEMfnj\\1A&uAotO&s",[])),
+?line <<"LGEMfnjAabcabcabcabcuAotOabcabcabcabcs">> = iolist_to_binary(re:replace("abcabcabcabc","^[a-c]{12}","LGEMfnj\\1A&uAotO&s",[global])),
+?line <<"PNcwcmOyx">> = iolist_to_binary(re:replace("abcabcabcabc","^(a|b|c){12}","PN\\1w\\1mOyx",[])),
+?line <<"PNcwcmOyx">> = iolist_to_binary(re:replace("abcabcabcabc","^(a|b|c){12}","PN\\1w\\1mOyx",[global])),
+?line <<"hVfRbFrAh">> = iolist_to_binary(re:replace("n","^[abcdefghijklmnopqrstuvwxy0123456789]","hVfRbFrAh",[])),
+?line <<"hVfRbFrAh">> = iolist_to_binary(re:replace("n","^[abcdefghijklmnopqrstuvwxy0123456789]","hVfRbFrAh",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]","R\\1gE\\1TmOo&B&\\1EaaIWLL",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]","R\\1gE\\1TmOo&B&\\1EaaIWLL",[global])),
+?line <<"z">> = iolist_to_binary(re:replace("z","^[abcdefghijklmnopqrstuvwxy0123456789]","dPAGng",[])),
+?line <<"z">> = iolist_to_binary(re:replace("z","^[abcdefghijklmnopqrstuvwxy0123456789]","dPAGng",[global])),
+?line <<"GalPHuLJkabcdJ">> = iolist_to_binary(re:replace("abcd","abcde{0,0}","Gal\\1PHu\\1LJk&J",[])),
+?line <<"GalPHuLJkabcdJ">> = iolist_to_binary(re:replace("abcd","abcde{0,0}","Gal\\1PHu\\1LJk&J",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abcde{0,0}","nRSqklns\\1RNQ",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abcde{0,0}","nRSqklns\\1RNQ",[global])),
+?line <<"abce">> = iolist_to_binary(re:replace("abce","abcde{0,0}","aYgl\\1WJ",[])),
+?line <<"abce">> = iolist_to_binary(re:replace("abce","abcde{0,0}","aYgl\\1WJ",[global])),
+?line <<"JSDhTpxGUy">> = iolist_to_binary(re:replace("abe","ab[cd]{0,0}e","J\\1SDhT\\1pxGUy",[])),
+?line <<"JSDhTpxGUy">> = iolist_to_binary(re:replace("abe","ab[cd]{0,0}e","J\\1SDhT\\1pxGUy",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab[cd]{0,0}e","K\\1VVqQdlOY\\1IFC",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab[cd]{0,0}e","K\\1VVqQdlOY\\1IFC",[global])),
+?line <<"abcde">> = iolist_to_binary(re:replace("abcde","ab[cd]{0,0}e","CBmXc&",[])),
+?line <<"abcde">> = iolist_to_binary(re:replace("abcde","ab[cd]{0,0}e","CBmXc&",[global])),
+?line <<"YKTMKcsKabd">> = iolist_to_binary(re:replace("abd","ab(c){0,0}d","YKT\\1MKcsK&",[])),
+?line <<"YKTMKcsKabd">> = iolist_to_binary(re:replace("abd","ab(c){0,0}d","YKT\\1MKcsK&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab(c){0,0}d","&j&dtuA\\1A&vS",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab(c){0,0}d","&j&dtuA\\1A&vS",[global])),
+?line <<"abcd">> = iolist_to_binary(re:replace("abcd","ab(c){0,0}d","&ceeJC\\1HRtWmI",[])),
+?line <<"abcd">> = iolist_to_binary(re:replace("abcd","ab(c){0,0}d","&ceeJC\\1HRtWmI",[global])),
+?line <<"k">> = iolist_to_binary(re:replace("a","a(b*)","\\1k",[])),
+?line <<"k">> = iolist_to_binary(re:replace("a","a(b*)","\\1k",[global])),
+?line <<"abnwnAb">> = iolist_to_binary(re:replace("ab","a(b*)","&nwnA\\1",[])),
+?line <<"abnwnAb">> = iolist_to_binary(re:replace("ab","a(b*)","&nwnA\\1",[global])),
+?line <<"nLIbbbbd">> = iolist_to_binary(re:replace("abbbb","a(b*)","nLI\\1d",[])),
+?line <<"nLIbbbbd">> = iolist_to_binary(re:replace("abbbb","a(b*)","nLI\\1d",[global])),
+?line <<"*** FMxgxBanxQaNgAilers">> = iolist_to_binary(re:replace("*** Failers","a(b*)","Mxgx\\1\\1B&nxQ&NgA",[])),
+?line <<"*** FMxgxBanxQaNgAilers">> = iolist_to_binary(re:replace("*** Failers","a(b*)","Mxgx\\1\\1B&nxQ&NgA",[global])),
+?line <<"bbbbb">> = iolist_to_binary(re:replace("bbbbb","a(b*)","Yho\\1QKp",[])),
+?line <<"bbbbb">> = iolist_to_binary(re:replace("bbbbb","a(b*)","Yho\\1QKp",[global])),
+?line <<"c">> = iolist_to_binary(re:replace("abe","ab\\d{0}e","c",[])),
+?line <<"c">> = iolist_to_binary(re:replace("abe","ab\\d{0}e","c",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab\\d{0}e","e\\1Nu\\1BSg\\1Rxb",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab\\d{0}e","e\\1Nu\\1BSg\\1Rxb",[global])),
+?line <<"ab1e">> = iolist_to_binary(re:replace("ab1e","ab\\d{0}e","C\\1xDlVAxYcYUK\\1fMs",[])),
+?line <<"ab1e">> = iolist_to_binary(re:replace("ab1e","ab\\d{0}e","C\\1xDlVAxYcYUK\\1fMs",[global])),
+?line <<"the MipoEquickBcm brown fox">> = iolist_to_binary(re:replace("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"","MipoE\\1Bcm",[])),
+?line <<"the MipoEquickBcm brown fox">> = iolist_to_binary(re:replace("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"","MipoE\\1Bcm",[global])),
+?line <<"\"the \\\"quick\\\" brown fox\"\"the \\\"quick\\\" brown fox\"K brown fox\"the \\\"quick\\\" brown fox\"twdFEi\"the \\\"quick\\\" brown fox\"Tf">> = iolist_to_binary(re:replace("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"","&&K\\1&twdFEi&Tf",[])),
+?line <<"\"the \\\"quick\\\" brown fox\"\"the \\\"quick\\\" brown fox\"K brown fox\"the \\\"quick\\\" brown fox\"twdFEi\"the \\\"quick\\\" brown fox\"Tf">> = iolist_to_binary(re:replace("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"","&&K\\1&twdFEi&Tf",[global])),
+?line <<"uOiYpObvYvnabc">> = iolist_to_binary(re:replace("abc","","uOiYp&\\1Obv&Yvn",[])),
+?line <<"uOiYpObvYvnauOiYpObvYvnbuOiYpObvYvncuOiYpObvYvn">> = iolist_to_binary(re:replace("abc","","uOiYp&\\1Obv&Yvn",[global])),
+?line <<"cacbJUVL">> = iolist_to_binary(re:replace("acb","a[^a]b","c&JUVL",[])),
+?line <<"cacbJUVL">> = iolist_to_binary(re:replace("acb","a[^a]b","c&JUVL",[global])),
+?line <<"a
+bnnciOUFa
+bttqb">> = iolist_to_binary(re:replace("a
+b","a[^a]b","&nnciOUF&ttq\\1b",[])),
+?line <<"a
+bnnciOUFa
+bttqb">> = iolist_to_binary(re:replace("a
+b","a[^a]b","&nnciOUF&ttq\\1b",[global])),
+?line <<"acbrJrBLmacbnacb">> = iolist_to_binary(re:replace("acb","a.b","&rJrBLm&n&",[])),
+?line <<"acbrJrBLmacbnacb">> = iolist_to_binary(re:replace("acb","a.b","&rJrBLm&n&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a.b","lUyaU",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a.b","lUyaU",[global])),
+?line <<"a
+b">> = iolist_to_binary(re:replace("a
+b","a.b","&etys",[])),
+?line <<"a
+b">> = iolist_to_binary(re:replace("a
+b","a.b","&etys",[global])),
+?line <<"BRCBYDDacbacbacbLfVw">> = iolist_to_binary(re:replace("acb","a[^a]b","BRCBY\\1D\\1D&&\\1&LfVw",[dotall])),
+?line <<"BRCBYDDacbacbacbLfVw">> = iolist_to_binary(re:replace("acb","a[^a]b","BRCBY\\1D\\1D&&\\1&LfVw",[dotall,
+ global])),
+?line <<"a
+bQkfhPqa
+bka
+byK">> = iolist_to_binary(re:replace("a
+b","a[^a]b","&Qk\\1fhPq&k&yK",[dotall])),
+?line <<"a
+bQkfhPqa
+bka
+byK">> = iolist_to_binary(re:replace("a
+b","a[^a]b","&Qk\\1fhPq&k&yK",[dotall,global])),
+?line <<"nKFpFMMacbmxur">> = iolist_to_binary(re:replace("acb","a.b","\\1nKF\\1\\1pF\\1MM\\1&mxur",[dotall])),
+?line <<"nKFpFMMacbmxur">> = iolist_to_binary(re:replace("acb","a.b","\\1nKF\\1\\1pF\\1MM\\1&mxur",[dotall,
+ global])),
+?line <<"RESrDVpIxqsoAT">> = iolist_to_binary(re:replace("a
+b","a.b","\\1RES\\1rDVpIxqso\\1AT",[dotall])),
+?line <<"RESrDVpIxqsoAT">> = iolist_to_binary(re:replace("a
+b","a.b","\\1RES\\1rDVpIxqso\\1AT",[dotall,global])),
+?line <<"aNchRRBHywejNvbacE">> = iolist_to_binary(re:replace("bac","^(b+?|a){1,2}?c","\\1NchRRBHywejNv&E",[])),
+?line <<"aNchRRBHywejNvbacE">> = iolist_to_binary(re:replace("bac","^(b+?|a){1,2}?c","\\1NchRRBHywejNv&E",[global])),
+?line <<"tOamCbbacgsmxbbac">> = iolist_to_binary(re:replace("bbac","^(b+?|a){1,2}?c","tO\\1mC&gsmx&",[])),
+?line <<"tOamCbbacgsmxbbac">> = iolist_to_binary(re:replace("bbac","^(b+?|a){1,2}?c","tO\\1mC&gsmx&",[global])),
+?line <<"xHTFdauecabbbacGpaol">> = iolist_to_binary(re:replace("bbbac","^(b+?|a){1,2}?c","xHTFd\\1uec\\1&Gp\\1ol",[])),
+?line <<"xHTFdauecabbbacGpaol">> = iolist_to_binary(re:replace("bbbac","^(b+?|a){1,2}?c","xHTFd\\1uec\\1&Gp\\1ol",[global])),
+?line <<"baapjPEIagYbbbbacvbbbbacUPkPJ">> = iolist_to_binary(re:replace("bbbbac","^(b+?|a){1,2}?c","b\\1\\1pjPEI\\1gY&v&UPkPJ",[])),
+?line <<"baapjPEIagYbbbbacvbbbbacUPkPJ">> = iolist_to_binary(re:replace("bbbbac","^(b+?|a){1,2}?c","b\\1\\1pjPEI\\1gY&v&UPkPJ",[global])),
+?line <<"atqCLSTaKnviKn">> = iolist_to_binary(re:replace("bbbbbac","^(b+?|a){1,2}?c","\\1tqCLST\\1KnviKn",[])),
+?line <<"atqCLSTaKnviKn">> = iolist_to_binary(re:replace("bbbbbac","^(b+?|a){1,2}?c","\\1tqCLST\\1KnviKn",[global])),
+?line <<"x">> = iolist_to_binary(re:replace("bac","^(b+|a){1,2}?c","x",[])),
+?line <<"x">> = iolist_to_binary(re:replace("bac","^(b+|a){1,2}?c","x",[global])),
+?line <<"ysaxGbbachYTP">> = iolist_to_binary(re:replace("bbac","^(b+|a){1,2}?c","ysaxG&hYTP",[])),
+?line <<"ysaxGbbachYTP">> = iolist_to_binary(re:replace("bbac","^(b+|a){1,2}?c","ysaxG&hYTP",[global])),
+?line <<"IxpOsaoUxbbbacCEcBow">> = iolist_to_binary(re:replace("bbbac","^(b+|a){1,2}?c","IxpOs\\1oUx&CEcBow",[])),
+?line <<"IxpOsaoUxbbbacCEcBow">> = iolist_to_binary(re:replace("bbbac","^(b+|a){1,2}?c","IxpOs\\1oUx&CEcBow",[global])),
+?line <<"bbbbacDeITf">> = iolist_to_binary(re:replace("bbbbac","^(b+|a){1,2}?c","&DeITf",[])),
+?line <<"bbbbacDeITf">> = iolist_to_binary(re:replace("bbbbac","^(b+|a){1,2}?c","&DeITf",[global])),
+?line <<"XlWabAqKnj">> = iolist_to_binary(re:replace("bbbbbac","^(b+|a){1,2}?c","XlW\\1bAqKnj",[])),
+?line <<"XlWabAqKnj">> = iolist_to_binary(re:replace("bbbbbac","^(b+|a){1,2}?c","XlW\\1bAqKnj",[global])),
+?line <<"x
+b">> = iolist_to_binary(re:replace("x
+b","(?!\\A)x","DxdEs\\1&\\1LKts&",[multiline])),
+?line <<"x
+b">> = iolist_to_binary(re:replace("x
+b","(?!\\A)x","DxdEs\\1&\\1LKts&",[multiline,global])),
+?line <<"aAx">> = iolist_to_binary(re:replace("ax","(?!\\A)x","Ax",[multiline])),
+?line <<"aAx">> = iolist_to_binary(re:replace("ax","(?!\\A)x","Ax",[multiline,
+ global])),
+?line <<"{ab}">> = iolist_to_binary(re:replace("{ab}","\\x0{ab}","aOVgpiCu\\1P\\1xjYe\\1",[])),
+?line <<"{ab}">> = iolist_to_binary(re:replace("{ab}","\\x0{ab}","aOVgpiCu\\1P\\1xjYe\\1",[global])),
+?line <<"PilCDnyDeI">> = iolist_to_binary(re:replace("CD","(A|B)*?CD","Pil&\\1nyDeI",[])),
+?line <<"PilCDnyDeI">> = iolist_to_binary(re:replace("CD","(A|B)*?CD","Pil&\\1nyDeI",[global])),
+?line <<"WrpDiffmnCDPINGCDSe">> = iolist_to_binary(re:replace("CD","(A|B)*CD","WrpDiffmn&PING&Se",[])),
+?line <<"WrpDiffmnCDPINGCDSe">> = iolist_to_binary(re:replace("CD","(A|B)*CD","WrpDiffmn&PING&Se",[global])),
+?line <<"mIeAB">> = iolist_to_binary(re:replace("ABABAB","(AB)*?\\1","mIe",[])),
+?line <<"mIeAB">> = iolist_to_binary(re:replace("ABABAB","(AB)*?\\1","mIe",[global])),
+?line <<"JThaowd">> = iolist_to_binary(re:replace("ABABAB","(AB)*\\1","JThaowd",[])),
+?line <<"JThaowd">> = iolist_to_binary(re:replace("ABABAB","(AB)*\\1","JThaowd",[global])),
+?line <<"mxivbdfooiW">> = iolist_to_binary(re:replace("foo","(?<!bar)foo","m\\1xi\\1\\1vbd&iW",[])),
+?line <<"mxivbdfooiW">> = iolist_to_binary(re:replace("foo","(?<!bar)foo","m\\1xi\\1\\1vbd&iW",[global])),
+?line <<"cathTPTYtGud">> = iolist_to_binary(re:replace("catfood","(?<!bar)foo","hTPTYtGu",[])),
+?line <<"cathTPTYtGud">> = iolist_to_binary(re:replace("catfood","(?<!bar)foo","hTPTYtGu",[global])),
+?line <<"arTMXuVvEElifooRfooXqctle">> = iolist_to_binary(re:replace("arfootle","(?<!bar)foo","TMXu\\1VvEEli&R&Xqc",[])),
+?line <<"arTMXuVvEElifooRfooXqctle">> = iolist_to_binary(re:replace("arfootle","(?<!bar)foo","TMXu\\1VvEEli&R&Xqc",[global])),
+?line <<"rCCfnfoopTfbpOVUOFBsh">> = iolist_to_binary(re:replace("rfoosh","(?<!bar)foo","CCfn&pT\\1fbpOVUOFB",[])),
+?line <<"rCCfnfoopTfbpOVUOFBsh">> = iolist_to_binary(re:replace("rfoosh","(?<!bar)foo","CCfn&pT\\1fbpOVUOFB",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<!bar)foo","bxdEUE&SEpWXK&",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<!bar)foo","bxdEUE&SEpWXK&",[global])),
+?line <<"barfoo">> = iolist_to_binary(re:replace("barfoo","(?<!bar)foo","ghAth&\\1XCvK&&",[])),
+?line <<"barfoo">> = iolist_to_binary(re:replace("barfoo","(?<!bar)foo","ghAth&\\1XCvK&&",[global])),
+?line <<"towbarfoo">> = iolist_to_binary(re:replace("towbarfoo","(?<!bar)foo","soY&O&QNmTi\\1xspnti&T",[])),
+?line <<"towbarfoo">> = iolist_to_binary(re:replace("towbarfoo","(?<!bar)foo","soY&O&QNmTi\\1xspnti&T",[global])),
+?line <<"JBHNVNd">> = iolist_to_binary(re:replace("catfood","\\w{3}(?<!bar)foo","J\\1BHN\\1VN\\1",[])),
+?line <<"JBHNVNd">> = iolist_to_binary(re:replace("catfood","\\w{3}(?<!bar)foo","J\\1BHN\\1VN\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\w{3}(?<!bar)foo","&ucJ",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\w{3}(?<!bar)foo","&ucJ",[global])),
+?line <<"foo">> = iolist_to_binary(re:replace("foo","\\w{3}(?<!bar)foo","uasVxSucjJKX",[])),
+?line <<"foo">> = iolist_to_binary(re:replace("foo","\\w{3}(?<!bar)foo","uasVxSucjJKX",[global])),
+?line <<"barfoo">> = iolist_to_binary(re:replace("barfoo","\\w{3}(?<!bar)foo","u&&Su\\1&YWVvwAftwcE&",[])),
+?line <<"barfoo">> = iolist_to_binary(re:replace("barfoo","\\w{3}(?<!bar)foo","u&&Su\\1&YWVvwAftwcE&",[global])),
+?line <<"towbarfoo">> = iolist_to_binary(re:replace("towbarfoo","\\w{3}(?<!bar)foo","IH",[])),
+?line <<"towbarfoo">> = iolist_to_binary(re:replace("towbarfoo","\\w{3}(?<!bar)foo","IH",[global])),
+?line <<"fooabarSWfoobar">> = iolist_to_binary(re:replace("fooabar","(?<=(foo)a)bar","&SW\\1&",[])),
+?line <<"fooabarSWfoobar">> = iolist_to_binary(re:replace("fooabar","(?<=(foo)a)bar","&SW\\1&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(foo)a)bar","&ShYlKf",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(foo)a)bar","&ShYlKf",[global])),
+?line <<"bar">> = iolist_to_binary(re:replace("bar","(?<=(foo)a)bar","Usk\\1PJ&",[])),
+?line <<"bar">> = iolist_to_binary(re:replace("bar","(?<=(foo)a)bar","Usk\\1PJ&",[global])),
+?line <<"foobbar">> = iolist_to_binary(re:replace("foobbar","(?<=(foo)a)bar","\\1pXSA",[])),
+?line <<"foobbar">> = iolist_to_binary(re:replace("foobbar","(?<=(foo)a)bar","\\1pXSA",[global])),
+?line <<"gWabcqJMuvLrGqnPLJK">> = iolist_to_binary(re:replace("abc","\\Aabc\\z","gW&qJ\\1MuvLrGqnPLJK",[multiline])),
+?line <<"gWabcqJMuvLrGqnPLJK">> = iolist_to_binary(re:replace("abc","\\Aabc\\z","gW&qJ\\1MuvLrGqnPLJK",[multiline,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\Aabc\\z","tcIt&YpWtGEy\\1p",[multiline])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\Aabc\\z","tcIt&YpWtGEy\\1p",[multiline,
+ global])),
+?line <<"abcM">> = iolist_to_binary(re:replace("abc","\\Aabc\\z","&M",[multiline])),
+?line <<"abcM">> = iolist_to_binary(re:replace("abc","\\Aabc\\z","&M",[multiline,
+ global])),
+?line <<"qqq
+abc">> = iolist_to_binary(re:replace("qqq
+abc","\\Aabc\\z","B\\1B\\1fqK\\1nocSHwGCCiM",[multiline])),
+?line <<"qqq
+abc">> = iolist_to_binary(re:replace("qqq
+abc","\\Aabc\\z","B\\1B\\1fqK\\1nocSHwGCCiM",[multiline,global])),
+?line <<"abc
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","\\Aabc\\z","P",[multiline])),
+?line <<"abc
+zzz">> = iolist_to_binary(re:replace("abc
+zzz","\\Aabc\\z","P",[multiline,global])),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","\\Aabc\\z","\\1adD\\1",[multiline])),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(re:replace("qqq
+abc
+zzz","\\Aabc\\z","\\1adD\\1",[multiline,global])),
+?line <<"1U">> = iolist_to_binary(re:replace("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+","U",[])),
+?line <<"1U">> = iolist_to_binary(re:replace("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+","U",[global])),
+?line <<"1bJ.875fwFYOodDWXyr.875tm.875000282.875000282">> = iolist_to_binary(re:replace("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+","bJ\\1fwFYOodDWXyr\\1tm&&",[])),
+?line <<"1bJ.875fwFYOodDWXyr.875tm.875000282.875000282">> = iolist_to_binary(re:replace("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+","bJ\\1fwFYOodDWXyr\\1tm&&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+","e\\1JPFf&",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+","e\\1JPFf&",[global])),
+?line <<"1.235">> = iolist_to_binary(re:replace("1.235","(?>(\\.\\d\\d[1-9]?))\\d+","yOPhp&Vy&fKXLBmiJx\\1a",[])),
+?line <<"1.235">> = iolist_to_binary(re:replace("1.235","(?>(\\.\\d\\d[1-9]?))\\d+","yOPhp&Vy&fKXLBmiJx\\1a",[global])),
+?line <<"partynow is the time for all good men to come to the aid of the partycnow is the time for all good men to come to the aid of the partynow is the time for all good men to come to the aid of the partypartyyecj">> = iolist_to_binary(re:replace("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$","\\1&c&&\\1yecj",[])),
+?line <<"partynow is the time for all good men to come to the aid of the partycnow is the time for all good men to come to the aid of the partynow is the time for all good men to come to the aid of the partypartyyecj">> = iolist_to_binary(re:replace("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$","\\1&c&&\\1yecj",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^((?>\\w+)|(?>\\s+))*$","\\1u",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^((?>\\w+)|(?>\\s+))*$","\\1u",[global])),
+?line <<"this is not a line with only words and spaces!">> = iolist_to_binary(re:replace("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$","gH&CX\\1&vnufnGXYAun",[])),
+?line <<"this is not a line with only words and spaces!">> = iolist_to_binary(re:replace("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$","gH&CX\\1&vnufnGXYAun",[global])),
+?line <<"K1234512345aH12345a">> = iolist_to_binary(re:replace("12345a","(\\d+)(\\w)","K\\1&H&",[])),
+?line <<"K1234512345aH12345a">> = iolist_to_binary(re:replace("12345a","(\\d+)(\\w)","K\\1&H&",[global])),
+?line <<"IdNK+">> = iolist_to_binary(re:replace("12345+","(\\d+)(\\w)","IdNK",[])),
+?line <<"IdNK+">> = iolist_to_binary(re:replace("12345+","(\\d+)(\\w)","IdNK",[global])),
+?line <<"jvjjhmthsl">> = iolist_to_binary(re:replace("12345a","((?>\\d+))(\\w)","jvjjhmthsl",[])),
+?line <<"jvjjhmthsl">> = iolist_to_binary(re:replace("12345a","((?>\\d+))(\\w)","jvjjhmthsl",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?>\\d+))(\\w)","eK&NTn&\\1T",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?>\\d+))(\\w)","eK&NTn&\\1T",[global])),
+?line <<"12345+">> = iolist_to_binary(re:replace("12345+","((?>\\d+))(\\w)","&&TXSquRgMqkDs&K&",[])),
+?line <<"12345+">> = iolist_to_binary(re:replace("12345+","((?>\\d+))(\\w)","&&TXSquRgMqkDs&K&",[global])),
+?line <<"NeYcvQCaaabtbOkx">> = iolist_to_binary(re:replace("aaab","(?>a+)b","NeYcvQC&tbOkx\\1",[])),
+?line <<"NeYcvQCaaabtbOkx">> = iolist_to_binary(re:replace("aaab","(?>a+)b","NeYcvQC&tbOkx\\1",[global])),
+?line <<"aaabbaaabFOSaaabKKtRUNaaabLDTCH">> = iolist_to_binary(re:replace("aaab","((?>a+)b)","&b&FOS&KKtRUN\\1LDTCH",[])),
+?line <<"aaabbaaabFOSaaabKKtRUNaaabLDTCH">> = iolist_to_binary(re:replace("aaab","((?>a+)b)","&b&FOS&KKtRUN\\1LDTCH",[global])),
+?line <<"xaaabaaabaaaiaaabCaGaaaaaabaaaaaaaaabaaabaaaF">> = iolist_to_binary(re:replace("aaab","(?>(a+))b","x\\1b&\\1i&CaG\\1&\\1\\1&&\\1F",[])),
+?line <<"xaaabaaabaaaiaaabCaGaaaaaabaaaaaaaaabaaabaaaF">> = iolist_to_binary(re:replace("aaab","(?>(a+))b","x\\1b&\\1i&CaG\\1&\\1\\1&&\\1F",[global])),
+?line <<"aaaLDSAYvgtbbbotccc">> = iolist_to_binary(re:replace("aaabbbccc","(?>b)+","LDSAYvgt&ot",[])),
+?line <<"aaaLDSAYvgtbbbotccc">> = iolist_to_binary(re:replace("aaabbbccc","(?>b)+","LDSAYvgt&ot",[global])),
+?line <<"ABQFhQjYaaabbbbcAcccd">> = iolist_to_binary(re:replace("aaabbbbccccd","(?>a+|b+|c+)*c","ABQ\\1Fh\\1QjY&A",[])),
+?line <<"ABQFhQjYaaabbbbcAABQFhQjYcAABQFhQjYcAABQFhQjYcAd">> = iolist_to_binary(re:replace("aaabbbbccccd","(?>a+|b+|c+)*c","ABQ\\1Fh\\1QjY&A",[global])),
+?line <<"((xiabc(ade)ufh()()xpJf">> = iolist_to_binary(re:replace("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+","\\1i&pJf",[])),
+?line <<"((xiabc(ade)ufh()()xpJf">> = iolist_to_binary(re:replace("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+","\\1i&pJf",[global])),
+?line <<"NBF">> = iolist_to_binary(re:replace("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)","NBF",[])),
+?line <<"NBF">> = iolist_to_binary(re:replace("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)","NBF",[global])),
+?line <<"(abc(def)xyz)AFLTv">> = iolist_to_binary(re:replace("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)","&AFLTv",[])),
+?line <<"(abc(def)xyz)AFLTv">> = iolist_to_binary(re:replace("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)","&AFLTv",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)","P\\1OC",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)","P\\1OC",[global])),
+?line <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)","u\\1\\1H\\1TOt\\1fVI&X",[])),
+?line <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)","u\\1\\1H\\1TOt\\1fVI&X",[global])),
+?line <<"E">> = iolist_to_binary(re:replace("ab","a(?-i)b","E",[caseless])),
+?line <<"E">> = iolist_to_binary(re:replace("ab","a(?-i)b","E",[caseless,
+ global])),
+?line <<"hAboOovpk">> = iolist_to_binary(re:replace("Ab","a(?-i)b","h&oOovpk",[caseless])),
+?line <<"hAboOovpk">> = iolist_to_binary(re:replace("Ab","a(?-i)b","h&oOovpk",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?-i)b","liOu",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?-i)b","liOu",[caseless,
+ global])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","a(?-i)b","P",[caseless])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","a(?-i)b","P",[caseless,
+ global])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","a(?-i)b","GdSF&e&",[caseless])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","a(?-i)b","GdSF&e&",[caseless,
+ global])),
+?line <<"a bcd eSPWrUhOwa">> = iolist_to_binary(re:replace("a bcd e","(a (?x)b c)d e","&SPWrUhOwa",[])),
+?line <<"a bcd eSPWrUhOwa">> = iolist_to_binary(re:replace("a bcd e","(a (?x)b c)d e","&SPWrUhOwa",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a (?x)b c)d e","IgDGvOUoEIi",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a (?x)b c)d e","IgDGvOUoEIi",[global])),
+?line <<"a b cd e">> = iolist_to_binary(re:replace("a b cd e","(a (?x)b c)d e","C\\1Lora&uj\\1nePq",[])),
+?line <<"a b cd e">> = iolist_to_binary(re:replace("a b cd e","(a (?x)b c)d e","C\\1Lora&uj\\1nePq",[global])),
+?line <<"abcd e">> = iolist_to_binary(re:replace("abcd e","(a (?x)b c)d e","M",[])),
+?line <<"abcd e">> = iolist_to_binary(re:replace("abcd e","(a (?x)b c)d e","M",[global])),
+?line <<"a bcde">> = iolist_to_binary(re:replace("a bcde","(a (?x)b c)d e","mNwUc&",[])),
+?line <<"a bcde">> = iolist_to_binary(re:replace("a bcde","(a (?x)b c)d e","mNwUc&",[global])),
+?line <<"a bcde fTra bcde fnDatbuPn">> = iolist_to_binary(re:replace("a bcde f","(a b(?x)c d (?-x)e f)","&Tr\\1nDatbuPn",[])),
+?line <<"a bcde fTra bcde fnDatbuPn">> = iolist_to_binary(re:replace("a bcde f","(a b(?x)c d (?-x)e f)","&Tr\\1nDatbuPn",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a b(?x)c d (?-x)e f)","\\1AXE&qrGnJBeg",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a b(?x)c d (?-x)e f)","\\1AXE&qrGnJBeg",[global])),
+?line <<"abcdef">> = iolist_to_binary(re:replace("abcdef","(a b(?x)c d (?-x)e f)","YXaHuSXEMrxHLIUXuNu\\1",[])),
+?line <<"abcdef">> = iolist_to_binary(re:replace("abcdef","(a b(?x)c d (?-x)e f)","YXaHuSXEMrxHLIUXuNu\\1",[global])),
+?line <<"AmT">> = iolist_to_binary(re:replace("abc","(a(?i)b)c","AmT",[])),
+?line <<"AmT">> = iolist_to_binary(re:replace("abc","(a(?i)b)c","AmT",[global])),
+?line <<"CYjrwVi">> = iolist_to_binary(re:replace("aBc","(a(?i)b)c","CYjrwVi",[])),
+?line <<"CYjrwVi">> = iolist_to_binary(re:replace("aBc","(a(?i)b)c","CYjrwVi",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a(?i)b)c","STYFgfjhE&dcc",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a(?i)b)c","STYFgfjhE&dcc",[global])),
+?line <<"abC">> = iolist_to_binary(re:replace("abC","(a(?i)b)c","BAcmfc\\1fAfr&o",[])),
+?line <<"abC">> = iolist_to_binary(re:replace("abC","(a(?i)b)c","BAcmfc\\1fAfr&o",[global])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","(a(?i)b)c","NGkAgBCJxbCgR",[])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","(a(?i)b)c","NGkAgBCJxbCgR",[global])),
+?line <<"Abc">> = iolist_to_binary(re:replace("Abc","(a(?i)b)c","aS&kKYGwkOB&YhadhLX",[])),
+?line <<"Abc">> = iolist_to_binary(re:replace("Abc","(a(?i)b)c","aS&kKYGwkOB&YhadhLX",[global])),
+?line <<"ABc">> = iolist_to_binary(re:replace("ABc","(a(?i)b)c","hP\\1RiAIeDp",[])),
+?line <<"ABc">> = iolist_to_binary(re:replace("ABc","(a(?i)b)c","hP\\1RiAIeDp",[global])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","(a(?i)b)c","Jj&j\\1&I&\\1&CR\\1qDG",[])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","(a(?i)b)c","Jj&j\\1&I&\\1&CR\\1qDG",[global])),
+?line <<"AbC">> = iolist_to_binary(re:replace("AbC","(a(?i)b)c","\\1h\\1XCNuqcxa",[])),
+?line <<"AbC">> = iolist_to_binary(re:replace("AbC","(a(?i)b)c","\\1h\\1XCNuqcxa",[global])),
+?line <<"hsDxrqJjHQabc">> = iolist_to_binary(re:replace("abc","a(?i:b)c","hsDxrqJjHQ&",[])),
+?line <<"hsDxrqJjHQabc">> = iolist_to_binary(re:replace("abc","a(?i:b)c","hsDxrqJjHQ&",[global])),
+?line <<"QltXULOSWaBcFaBcS">> = iolist_to_binary(re:replace("aBc","a(?i:b)c","Qlt\\1XU\\1L\\1OSW&F&S",[])),
+?line <<"QltXULOSWaBcFaBcS">> = iolist_to_binary(re:replace("aBc","a(?i:b)c","Qlt\\1XU\\1L\\1OSW&F&S",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?i:b)c","owyoniUsut",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?i:b)c","owyoniUsut",[global])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","a(?i:b)c","unB&TlphDWAD",[])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","a(?i:b)c","unB&TlphDWAD",[global])),
+?line <<"abC">> = iolist_to_binary(re:replace("abC","a(?i:b)c","\\1XaX&kAbe&\\1V",[])),
+?line <<"abC">> = iolist_to_binary(re:replace("abC","a(?i:b)c","\\1XaX&kAbe&\\1V",[global])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","a(?i:b)c","IY\\1aQ&tM\\1",[])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","a(?i:b)c","IY\\1aQ&tM\\1",[global])),
+?line <<"aBcOHQouBOaBc">> = iolist_to_binary(re:replace("aBc","a(?i:b)*c","&O\\1HQo\\1uBO&",[])),
+?line <<"aBcOHQouBOaBc">> = iolist_to_binary(re:replace("aBc","a(?i:b)*c","&O\\1HQo\\1uBO&",[global])),
+?line <<"lvjhpFaBBceaBBciWQNaBBcr">> = iolist_to_binary(re:replace("aBBc","a(?i:b)*c","lvjhpF&e&i\\1WQN\\1&r",[])),
+?line <<"lvjhpFaBBceaBBciWQNaBBcr">> = iolist_to_binary(re:replace("aBBc","a(?i:b)*c","lvjhpF&e&i\\1WQN\\1&r",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?i:b)*c","\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?i:b)*c","\\1",[global])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","a(?i:b)*c","OaR&&ytX",[])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","a(?i:b)*c","OaR&&ytX",[global])),
+?line <<"aBBC">> = iolist_to_binary(re:replace("aBBC","a(?i:b)*c","Su",[])),
+?line <<"aBBC">> = iolist_to_binary(re:replace("aBBC","a(?i:b)*c","Su",[global])),
+?line <<"abcdabcdalFfabcdsPxC">> = iolist_to_binary(re:replace("abcd","a(?=b(?i)c)\\w\\wd","&&alFf&sPxC",[])),
+?line <<"abcdabcdalFfabcdsPxC">> = iolist_to_binary(re:replace("abcd","a(?=b(?i)c)\\w\\wd","&&alFf&sPxC",[global])),
+?line <<"hNuQEFvcSEabCdQTlwEabCd">> = iolist_to_binary(re:replace("abCd","a(?=b(?i)c)\\w\\wd","hNuQEFvcSE\\1&QTlwE&",[])),
+?line <<"hNuQEFvcSEabCdQTlwEabCd">> = iolist_to_binary(re:replace("abCd","a(?=b(?i)c)\\w\\wd","hNuQEFvcSE\\1&QTlwE&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?=b(?i)c)\\w\\wd","YLT",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?=b(?i)c)\\w\\wd","YLT",[global])),
+?line <<"aBCd">> = iolist_to_binary(re:replace("aBCd","a(?=b(?i)c)\\w\\wd","u",[])),
+?line <<"aBCd">> = iolist_to_binary(re:replace("aBCd","a(?=b(?i)c)\\w\\wd","u",[global])),
+?line <<"abcD">> = iolist_to_binary(re:replace("abcD","a(?=b(?i)c)\\w\\wd","PIaL\\1rCHujWQjtBluw",[])),
+?line <<"abcD">> = iolist_to_binary(re:replace("abcD","a(?=b(?i)c)\\w\\wd","PIaL\\1rCHujWQjtBluw",[global])),
+?line <<"JklClgvcUHdcbpmore than million">> = iolist_to_binary(re:replace("more than million","(?s-i:more.*than).*million","JklCl\\1gvc\\1UHdcbp&",[caseless])),
+?line <<"JklClgvcUHdcbpmore than million">> = iolist_to_binary(re:replace("more than million","(?s-i:more.*than).*million","JklCl\\1gvc\\1UHdcbp&",[caseless,
+ global])),
+?line <<"RaiCEcYVVcTj">> = iolist_to_binary(re:replace("more than MILLION","(?s-i:more.*than).*million","R\\1\\1\\1aiCEcYVVcTj",[caseless])),
+?line <<"RaiCEcYVVcTj">> = iolist_to_binary(re:replace("more than MILLION","(?s-i:more.*than).*million","R\\1\\1\\1aiCEcYVVcTj",[caseless,
+ global])),
+?line <<"AGtSe">> = iolist_to_binary(re:replace("more
+ than Million","(?s-i:more.*than).*million","AGtSe",[caseless])),
+?line <<"AGtSe">> = iolist_to_binary(re:replace("more
+ than Million","(?s-i:more.*than).*million","AGtSe",[caseless,global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?s-i:more.*than).*million","vIAbYe&Vw\\1&VD",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?s-i:more.*than).*million","vIAbYe&Vw\\1&VD",[caseless,
+ global])),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(re:replace("MORE THAN MILLION","(?s-i:more.*than).*million","LhBYPjwD\\1kJ\\1",[caseless])),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(re:replace("MORE THAN MILLION","(?s-i:more.*than).*million","LhBYPjwD\\1kJ\\1",[caseless,
+ global])),
+?line <<"more
+ than
+ million">> = iolist_to_binary(re:replace("more
+ than
+ million","(?s-i:more.*than).*million","SSp\\1ttOwEGO&H",[caseless])),
+?line <<"more
+ than
+ million">> = iolist_to_binary(re:replace("more
+ than
+ million","(?s-i:more.*than).*million","SSp\\1ttOwEGO&H",[caseless,
+ global])),
+?line <<"dmore than millionhFexWTaTmclBHK">> = iolist_to_binary(re:replace("more than million","(?:(?s-i)more.*than).*million","d&hFexWTaT\\1\\1mc\\1\\1lBHK",[caseless])),
+?line <<"dmore than millionhFexWTaTmclBHK">> = iolist_to_binary(re:replace("more than million","(?:(?s-i)more.*than).*million","d&hFexWTaT\\1\\1mc\\1\\1lBHK",[caseless,
+ global])),
+?line <<"wcVAikDmore than MILLIONFsmore than MILLIONIceKiuDm">> = iolist_to_binary(re:replace("more than MILLION","(?:(?s-i)more.*than).*million","wcVAikD&Fs&Ice\\1KiuDm",[caseless])),
+?line <<"wcVAikDmore than MILLIONFsmore than MILLIONIceKiuDm">> = iolist_to_binary(re:replace("more than MILLION","(?:(?s-i)more.*than).*million","wcVAikD&Fs&Ice\\1KiuDm",[caseless,
+ global])),
+?line <<"BMxnsX">> = iolist_to_binary(re:replace("more
+ than Million","(?:(?s-i)more.*than).*million","BMx\\1nsX",[caseless])),
+?line <<"BMxnsX">> = iolist_to_binary(re:replace("more
+ than Million","(?:(?s-i)more.*than).*million","BMx\\1nsX",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?s-i)more.*than).*million","e\\1KeC\\1LrMA",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?s-i)more.*than).*million","e\\1KeC\\1LrMA",[caseless,
+ global])),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(re:replace("MORE THAN MILLION","(?:(?s-i)more.*than).*million","Oo&U&\\1O\\1royLyh\\1Uj\\1&e",[caseless])),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(re:replace("MORE THAN MILLION","(?:(?s-i)more.*than).*million","Oo&U&\\1O\\1royLyh\\1Uj\\1&e",[caseless,
+ global])),
+?line <<"more
+ than
+ million">> = iolist_to_binary(re:replace("more
+ than
+ million","(?:(?s-i)more.*than).*million","rsd\\1UhwpU\\1&S",[caseless])),
+?line <<"more
+ than
+ million">> = iolist_to_binary(re:replace("more
+ than
+ million","(?:(?s-i)more.*than).*million","rsd\\1UhwpU\\1&S",[caseless,
+ global])),
+?line <<"rTdtycUabcoabcVaVEt">> = iolist_to_binary(re:replace("abc","(?>a(?i)b+)+c","r\\1\\1T\\1dtycU&o&V\\1aV\\1Et",[])),
+?line <<"rTdtycUabcoabcVaVEt">> = iolist_to_binary(re:replace("abc","(?>a(?i)b+)+c","r\\1\\1T\\1dtycU&o&V\\1aV\\1Et",[global])),
+?line <<"geFTAcqhWJc">> = iolist_to_binary(re:replace("aBbc","(?>a(?i)b+)+c","geFTAc\\1qhWJ\\1c",[])),
+?line <<"geFTAcqhWJc">> = iolist_to_binary(re:replace("aBbc","(?>a(?i)b+)+c","geFTAc\\1qhWJ\\1c",[global])),
+?line <<"lHStMaBBcnaBBcjCaBBcgdfm">> = iolist_to_binary(re:replace("aBBc","(?>a(?i)b+)+c","lHStM&n&jC&g\\1df\\1m",[])),
+?line <<"lHStMaBBcnaBBcjCaBBcgdfm">> = iolist_to_binary(re:replace("aBBc","(?>a(?i)b+)+c","lHStM&n&jC&g\\1df\\1m",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?>a(?i)b+)+c","U\\1pOiN&FCXl",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?>a(?i)b+)+c","U\\1pOiN&FCXl",[global])),
+?line <<"Abc">> = iolist_to_binary(re:replace("Abc","(?>a(?i)b+)+c","h",[])),
+?line <<"Abc">> = iolist_to_binary(re:replace("Abc","(?>a(?i)b+)+c","h",[global])),
+?line <<"abAb">> = iolist_to_binary(re:replace("abAb","(?>a(?i)b+)+c","f\\1\\1DwX\\1Y&y\\1dgv&A\\1",[])),
+?line <<"abAb">> = iolist_to_binary(re:replace("abAb","(?>a(?i)b+)+c","f\\1\\1DwX\\1Y&y\\1dgv&A\\1",[global])),
+?line <<"abbC">> = iolist_to_binary(re:replace("abbC","(?>a(?i)b+)+c","&\\1Gd&r\\1shP",[])),
+?line <<"abbC">> = iolist_to_binary(re:replace("abbC","(?>a(?i)b+)+c","&\\1Gd&r\\1shP",[global])),
+?line <<"VIaLabcabcCabcyvwsI">> = iolist_to_binary(re:replace("abc","(?=a(?i)b)\\w\\wc","VIaL&&C&yvwsI",[])),
+?line <<"VIaLabcabcCabcyvwsI">> = iolist_to_binary(re:replace("abc","(?=a(?i)b)\\w\\wc","VIaL&&C&yvwsI",[global])),
+?line <<"gaBcaBc">> = iolist_to_binary(re:replace("aBc","(?=a(?i)b)\\w\\wc","g&&",[])),
+?line <<"gaBcaBc">> = iolist_to_binary(re:replace("aBc","(?=a(?i)b)\\w\\wc","g&&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?=a(?i)b)\\w\\wc","RMcFr&",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?=a(?i)b)\\w\\wc","RMcFr&",[global])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?=a(?i)b)\\w\\wc","y\\1&kD&os\\1E",[])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?=a(?i)b)\\w\\wc","y\\1&kD&os\\1E",[global])),
+?line <<"abC">> = iolist_to_binary(re:replace("abC","(?=a(?i)b)\\w\\wc","ms\\1m",[])),
+?line <<"abC">> = iolist_to_binary(re:replace("abC","(?=a(?i)b)\\w\\wc","ms\\1m",[global])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","(?=a(?i)b)\\w\\wc","lD&&vcs",[])),
+?line <<"aBC">> = iolist_to_binary(re:replace("aBC","(?=a(?i)b)\\w\\wc","lD&&vcs",[global])),
+?line <<"abxqXfVViuPuvfMxxcvcj">> = iolist_to_binary(re:replace("abxxc","(?<=a(?i)b)(\\w\\w)c","xqXfVViuPuvfM&vcj",[])),
+?line <<"abxqXfVViuPuvfMxxcvcj">> = iolist_to_binary(re:replace("abxxc","(?<=a(?i)b)(\\w\\w)c","xqXfVViuPuvfM&vcj",[global])),
+?line <<"aBXoxxcXtiuxxXqWYfixxKxxcxxUs">> = iolist_to_binary(re:replace("aBxxc","(?<=a(?i)b)(\\w\\w)c","Xo&Xtiu\\1XqWYfi\\1K&\\1Us",[])),
+?line <<"aBXoxxcXtiuxxXqWYfixxKxxcxxUs">> = iolist_to_binary(re:replace("aBxxc","(?<=a(?i)b)(\\w\\w)c","Xo&Xtiu\\1XqWYfi\\1K&\\1Us",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=a(?i)b)(\\w\\w)c","cGWOK&O&A\\1Xq&&aF\\1G",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=a(?i)b)(\\w\\w)c","cGWOK&O&A\\1Xq&&aF\\1G",[global])),
+?line <<"Abxxc">> = iolist_to_binary(re:replace("Abxxc","(?<=a(?i)b)(\\w\\w)c","uosfXS&Drr&Nsg",[])),
+?line <<"Abxxc">> = iolist_to_binary(re:replace("Abxxc","(?<=a(?i)b)(\\w\\w)c","uosfXS&Drr&Nsg",[global])),
+?line <<"ABxxc">> = iolist_to_binary(re:replace("ABxxc","(?<=a(?i)b)(\\w\\w)c","mKjv&im",[])),
+?line <<"ABxxc">> = iolist_to_binary(re:replace("ABxxc","(?<=a(?i)b)(\\w\\w)c","mKjv&im",[global])),
+?line <<"abxxC">> = iolist_to_binary(re:replace("abxxC","(?<=a(?i)b)(\\w\\w)c","ferUg\\1J\\1T\\1kWcDAY\\1jM",[])),
+?line <<"abxxC">> = iolist_to_binary(re:replace("abxxC","(?<=a(?i)b)(\\w\\w)c","ferUg\\1J\\1T\\1kWcDAY\\1jM",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("aA","(?:(a)|b)(?(1)A|B)","\\1",[])),
+?line <<"a">> = iolist_to_binary(re:replace("aA","(?:(a)|b)(?(1)A|B)","\\1",[global])),
+?line <<"YdvbBbBsFnnbBKbB">> = iolist_to_binary(re:replace("bB","(?:(a)|b)(?(1)A|B)","Y\\1\\1dv&&\\1sFnn&K&",[])),
+?line <<"YdvbBbBsFnnbBKbB">> = iolist_to_binary(re:replace("bB","(?:(a)|b)(?(1)A|B)","Y\\1\\1dv&&\\1sFnn&K&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(a)|b)(?(1)A|B)","\\1\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(a)|b)(?(1)A|B)","\\1\\1",[global])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?:(a)|b)(?(1)A|B)","\\1To",[])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?:(a)|b)(?(1)A|B)","\\1To",[global])),
+?line <<"bA">> = iolist_to_binary(re:replace("bA","(?:(a)|b)(?(1)A|B)","K\\1UMD",[])),
+?line <<"bA">> = iolist_to_binary(re:replace("bA","(?:(a)|b)(?(1)A|B)","K\\1UMD",[global])),
+?line <<"mLbykSwT">> = iolist_to_binary(re:replace("aa","^(a)?(?(1)a|b)+$","mLbykSwT",[])),
+?line <<"mLbykSwT">> = iolist_to_binary(re:replace("aa","^(a)?(?(1)a|b)+$","mLbykSwT",[global])),
+?line <<"br">> = iolist_to_binary(re:replace("b","^(a)?(?(1)a|b)+$","&r",[])),
+?line <<"br">> = iolist_to_binary(re:replace("b","^(a)?(?(1)a|b)+$","&r",[global])),
+?line <<"yaAboE">> = iolist_to_binary(re:replace("bb","^(a)?(?(1)a|b)+$","yaAb\\1o\\1E",[])),
+?line <<"yaAboE">> = iolist_to_binary(re:replace("bb","^(a)?(?(1)a|b)+$","yaAb\\1o\\1E",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a)?(?(1)a|b)+$","QkLpQarHEu\\1Fi",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a)?(?(1)a|b)+$","QkLpQarHEu\\1Fi",[global])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a)?(?(1)a|b)+$","U",[])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","^(a)?(?(1)a|b)+$","U",[global])),
+?line <<"MoTyD">> = iolist_to_binary(re:replace("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$","M\\1oTyD",[])),
+?line <<"MoTyD">> = iolist_to_binary(re:replace("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$","M\\1oTyD",[global])),
+?line <<"kawc12B">> = iolist_to_binary(re:replace("12","^(?(?=abc)\\w{3}:|\\d\\d)$","k\\1awc&B\\1",[])),
+?line <<"kawc12B">> = iolist_to_binary(re:replace("12","^(?(?=abc)\\w{3}:|\\d\\d)$","k\\1awc&B\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$","TAqA",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$","TAqA",[global])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^(?(?=abc)\\w{3}:|\\d\\d)$","WlKxg",[])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^(?(?=abc)\\w{3}:|\\d\\d)$","WlKxg",[global])),
+?line <<"xyz">> = iolist_to_binary(re:replace("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$","O",[])),
+?line <<"xyz">> = iolist_to_binary(re:replace("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$","O",[global])),
+?line <<"kNqQd">> = iolist_to_binary(re:replace("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$","kNq\\1Q\\1d",[])),
+?line <<"kNqQd">> = iolist_to_binary(re:replace("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$","kNq\\1Q\\1d",[global])),
+?line <<"Cr">> = iolist_to_binary(re:replace("12","^(?(?!abc)\\d\\d|\\w{3}:)$","\\1C\\1r",[])),
+?line <<"Cr">> = iolist_to_binary(re:replace("12","^(?(?!abc)\\d\\d|\\w{3}:)$","\\1C\\1r",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$","&\\1\\1RHBBR",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$","&\\1\\1RHBBR",[global])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^(?(?!abc)\\d\\d|\\w{3}:)$","P",[])),
+?line <<"123">> = iolist_to_binary(re:replace("123","^(?(?!abc)\\d\\d|\\w{3}:)$","P",[global])),
+?line <<"xyz">> = iolist_to_binary(re:replace("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$","&awfl",[])),
+?line <<"xyz">> = iolist_to_binary(re:replace("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$","&awfl",[global])),
+?line <<"fooHSXSjJapIqY">> = iolist_to_binary(re:replace("foobar","(?(?<=foo)bar|cat)","HSXSjJ\\1apIqY",[])),
+?line <<"fooHSXSjJapIqY">> = iolist_to_binary(re:replace("foobar","(?(?<=foo)bar|cat)","HSXSjJ\\1apIqY",[global])),
+?line <<"IouuQfOcatNN">> = iolist_to_binary(re:replace("cat","(?(?<=foo)bar|cat)","Iouu\\1QfO&NN\\1",[])),
+?line <<"IouuQfOcatNN">> = iolist_to_binary(re:replace("cat","(?(?<=foo)bar|cat)","Iouu\\1QfO&NN\\1",[global])),
+?line <<"fcatxAWTu">> = iolist_to_binary(re:replace("fcat","(?(?<=foo)bar|cat)","&\\1xAWTu",[])),
+?line <<"fcatxAWTu">> = iolist_to_binary(re:replace("fcat","(?(?<=foo)bar|cat)","&\\1xAWTu",[global])),
+?line <<"fodYcNREMccatAcatyvscatidk">> = iolist_to_binary(re:replace("focat","(?(?<=foo)bar|cat)","dYc\\1N\\1REMc&A&yvs&idk",[])),
+?line <<"fodYcNREMccatAcatyvscatidk">> = iolist_to_binary(re:replace("focat","(?(?<=foo)bar|cat)","dYc\\1N\\1REMc&A&yvs&idk",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?<=foo)bar|cat)","FmDOEt&",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?<=foo)bar|cat)","FmDOEt&",[global])),
+?line <<"foocat">> = iolist_to_binary(re:replace("foocat","(?(?<=foo)bar|cat)","Jj&OFdGEDut\\1HjNDH",[])),
+?line <<"foocat">> = iolist_to_binary(re:replace("foocat","(?(?<=foo)bar|cat)","Jj&OFdGEDut\\1HjNDH",[global])),
+?line <<"fooEGbarEwDYbarCNxYJbarbD">> = iolist_to_binary(re:replace("foobar","(?(?<!foo)cat|bar)","EG&Ew\\1DY&CN\\1xYJ&bD",[])),
+?line <<"fooEGbarEwDYbarCNxYJbarbD">> = iolist_to_binary(re:replace("foobar","(?(?<!foo)cat|bar)","EG&Ew\\1DY&CN\\1xYJ&bD",[global])),
+?line <<"jHKKpLcatBtkhMt">> = iolist_to_binary(re:replace("cat","(?(?<!foo)cat|bar)","jHKKpL&BtkhMt",[])),
+?line <<"jHKKpLcatBtkhMt">> = iolist_to_binary(re:replace("cat","(?(?<!foo)cat|bar)","jHKKpL&BtkhMt",[global])),
+?line <<"fiJOnynFk">> = iolist_to_binary(re:replace("fcat","(?(?<!foo)cat|bar)","iJ\\1O\\1ny\\1nFk",[])),
+?line <<"fiJOnynFk">> = iolist_to_binary(re:replace("fcat","(?(?<!foo)cat|bar)","iJ\\1O\\1ny\\1nFk",[global])),
+?line <<"foWCSvhChIBqicatcatLjMieK">> = iolist_to_binary(re:replace("focat","(?(?<!foo)cat|bar)","WCSvh\\1ChIBqi&&LjMieK",[])),
+?line <<"foWCSvhChIBqicatcatLjMieK">> = iolist_to_binary(re:replace("focat","(?(?<!foo)cat|bar)","WCSvh\\1ChIBqi&&LjMieK",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?<!foo)cat|bar)","v&yn",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?<!foo)cat|bar)","v&yn",[global])),
+?line <<"foocat">> = iolist_to_binary(re:replace("foocat","(?(?<!foo)cat|bar)","\\1&\\1\\1&dvNYDwrfFA\\1&d",[])),
+?line <<"foocat">> = iolist_to_binary(re:replace("foocat","(?(?<!foo)cat|bar)","\\1&\\1\\1&dvNYDwrfFA\\1&d",[global])),
+?line <<"TQKeRLoqn">> = iolist_to_binary(re:replace("abcd","( \\( )? [^()]+ (?(1) \\) |) ","T\\1\\1QK\\1e\\1RLoqn",[extended])),
+?line <<"TQKeRLoqn">> = iolist_to_binary(re:replace("abcd","( \\( )? [^()]+ (?(1) \\) |) ","T\\1\\1QK\\1e\\1RLoqn",[extended,
+ global])),
+?line <<"Qd(ovEgf(pkOYCuPDq(R">> = iolist_to_binary(re:replace("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ","Qd\\1ovEgf\\1pkOYCuPDq\\1R",[extended])),
+?line <<"Qd(ovEgf(pkOYCuPDq(R">> = iolist_to_binary(re:replace("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ","Qd\\1ovEgf\\1pkOYCuPDq\\1R",[extended,
+ global])),
+?line <<"Dmthe quick QWSbgBeLthe quick E(abcd) fox">> = iolist_to_binary(re:replace("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ","Dm\\1&QWSbgBeL&E",[extended])),
+?line <<"Dmthe quick QWSbgBeLthe quick EDm((abcd)QWSbgBeL(abcd)EDm foxQWSbgBeL foxE">> = iolist_to_binary(re:replace("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ","Dm\\1&QWSbgBeL&E",[extended,
+ global])),
+?line <<"(Ld">> = iolist_to_binary(re:replace("(abcd","( \\( )? [^()]+ (?(1) \\) |) ","Ld",[extended])),
+?line <<"(Ld">> = iolist_to_binary(re:replace("(abcd","( \\( )? [^()]+ (?(1) \\) |) ","Ld",[extended,
+ global])),
+?line <<"abcdiCLfuabcdkd">> = iolist_to_binary(re:replace("abcd","( \\( )? [^()]+ (?(1) \\) ) ","&iCLfu&kd",[extended])),
+?line <<"abcdiCLfuabcdkd">> = iolist_to_binary(re:replace("abcd","( \\( )? [^()]+ (?(1) \\) ) ","&iCLfu&kd",[extended,
+ global])),
+?line <<"uXgnII((abcd)SMj">> = iolist_to_binary(re:replace("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ","uXgnII\\1&SMj",[extended])),
+?line <<"uXgnII((abcd)SMj">> = iolist_to_binary(re:replace("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ","uXgnII\\1&SMj",[extended,
+ global])),
+?line <<"the quick the quick the quick xjaBhsFamkEL(abcd) fox">> = iolist_to_binary(re:replace("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ","&&&xjaBhsFa\\1mkEL\\1",[extended])),
+?line <<"the quick the quick the quick xjaBhsFamkEL(abcd)(abcd)(abcd)xjaBhsFa(mkEL( fox fox foxxjaBhsFamkEL">> = iolist_to_binary(re:replace("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ","&&&xjaBhsFa\\1mkEL\\1",[extended,
+ global])),
+?line <<"(KuH">> = iolist_to_binary(re:replace("(abcd","( \\( )? [^()]+ (?(1) \\) ) ","K\\1uH",[extended])),
+?line <<"(KuH">> = iolist_to_binary(re:replace("(abcd","( \\( )? [^()]+ (?(1) \\) ) ","K\\1uH",[extended,
+ global])),
+?line <<"IMNCUvkRpMGDtA">> = iolist_to_binary(re:replace("12","^(?(2)a|(1)(2))+$","IMNCUvkRpMGDtA",[])),
+?line <<"IMNCUvkRpMGDtA">> = iolist_to_binary(re:replace("12","^(?(2)a|(1)(2))+$","IMNCUvkRpMGDtA",[global])),
+?line <<"YQQcCpqUaDwGin12a1he">> = iolist_to_binary(re:replace("12a","^(?(2)a|(1)(2))+$","YQQcCpqUaDwGin&\\1he",[])),
+?line <<"YQQcCpqUaDwGin12a1he">> = iolist_to_binary(re:replace("12a","^(?(2)a|(1)(2))+$","YQQcCpqUaDwGin&\\1he",[global])),
+?line <<"LiJWEQyka">> = iolist_to_binary(re:replace("12aa","^(?(2)a|(1)(2))+$","LiJWEQyka",[])),
+?line <<"LiJWEQyka">> = iolist_to_binary(re:replace("12aa","^(?(2)a|(1)(2))+$","LiJWEQyka",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?(2)a|(1)(2))+$","jNpGudmNvPautj\\1\\1rc",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?(2)a|(1)(2))+$","jNpGudmNvPautj\\1\\1rc",[global])),
+?line <<"1234">> = iolist_to_binary(re:replace("1234","^(?(2)a|(1)(2))+$","TKb&DSqQCtNBSjto\\1",[])),
+?line <<"1234">> = iolist_to_binary(re:replace("1234","^(?(2)a|(1)(2))+$","TKb&DSqQCtNBSjto\\1",[global])),
+?line <<"OKbblah blahmEblah blahcAEblah">> = iolist_to_binary(re:replace("blah blah","((?i)blah)\\s+\\1","OKb&mE&cAE\\1",[])),
+?line <<"OKbblah blahmEblah blahcAEblah">> = iolist_to_binary(re:replace("blah blah","((?i)blah)\\s+\\1","OKb&mE&cAE\\1",[global])),
+?line <<"sHRABLAH BLAHDBLAH BLAHofBLAHMonBF">> = iolist_to_binary(re:replace("BLAH BLAH","((?i)blah)\\s+\\1","sHRA&D&of\\1MonBF",[])),
+?line <<"sHRABLAH BLAHDBLAH BLAHofBLAHMonBF">> = iolist_to_binary(re:replace("BLAH BLAH","((?i)blah)\\s+\\1","sHRA&D&of\\1MonBF",[global])),
+?line <<"rBlah BlahowtRMgXJKNBlah Blah">> = iolist_to_binary(re:replace("Blah Blah","((?i)blah)\\s+\\1","r&owtRMgXJKN&",[])),
+?line <<"rBlah BlahowtRMgXJKNBlah Blah">> = iolist_to_binary(re:replace("Blah Blah","((?i)blah)\\s+\\1","r&owtRMgXJKN&",[global])),
+?line <<"JblaHeNvblaHaSeblaHblaH blaHg">> = iolist_to_binary(re:replace("blaH blaH","((?i)blah)\\s+\\1","J\\1eNv\\1aSe\\1&g",[])),
+?line <<"JblaHeNvblaHaSeblaHblaH blaHg">> = iolist_to_binary(re:replace("blaH blaH","((?i)blah)\\s+\\1","J\\1eNv\\1aSe\\1&g",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?i)blah)\\s+\\1","\\1LVUknDVfgj\\1ij\\1eDhAE",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?i)blah)\\s+\\1","\\1LVUknDVfgj\\1ij\\1eDhAE",[global])),
+?line <<"blah BLAH">> = iolist_to_binary(re:replace("blah BLAH","((?i)blah)\\s+\\1","auAYHQ\\1S&PKut",[])),
+?line <<"blah BLAH">> = iolist_to_binary(re:replace("blah BLAH","((?i)blah)\\s+\\1","auAYHQ\\1S&PKut",[global])),
+?line <<"Blah blah">> = iolist_to_binary(re:replace("Blah blah","((?i)blah)\\s+\\1","\\1&N\\1tNui&w&CgfgFCbPk",[])),
+?line <<"Blah blah">> = iolist_to_binary(re:replace("Blah blah","((?i)blah)\\s+\\1","\\1&N\\1tNui&w&CgfgFCbPk",[global])),
+?line <<"blaH blah">> = iolist_to_binary(re:replace("blaH blah","((?i)blah)\\s+\\1","rLouaVXAOeWe",[])),
+?line <<"blaH blah">> = iolist_to_binary(re:replace("blaH blah","((?i)blah)\\s+\\1","rLouaVXAOeWe",[global])),
+?line <<"CXblahSqUjfblah blahblah blahblah">> = iolist_to_binary(re:replace("blah blah","((?i)blah)\\s+(?i:\\1)","CX\\1SqUjf&&\\1",[])),
+?line <<"CXblahSqUjfblah blahblah blahblah">> = iolist_to_binary(re:replace("blah blah","((?i)blah)\\s+(?i:\\1)","CX\\1SqUjf&&\\1",[global])),
+?line <<"XBLAHTkBLAHXUjMhbiRBLAH BLAHgXxxti">> = iolist_to_binary(re:replace("BLAH BLAH","((?i)blah)\\s+(?i:\\1)","X\\1Tk\\1XUjMhbiR&gXxxti",[])),
+?line <<"XBLAHTkBLAHXUjMhbiRBLAH BLAHgXxxti">> = iolist_to_binary(re:replace("BLAH BLAH","((?i)blah)\\s+(?i:\\1)","X\\1Tk\\1XUjMhbiR&gXxxti",[global])),
+?line <<"SBlahlpSqiBlah BlahLvYBlahJBlahxSbx">> = iolist_to_binary(re:replace("Blah Blah","((?i)blah)\\s+(?i:\\1)","S\\1lpSqi&LvY\\1J\\1xSbx",[])),
+?line <<"SBlahlpSqiBlah BlahLvYBlahJBlahxSbx">> = iolist_to_binary(re:replace("Blah Blah","((?i)blah)\\s+(?i:\\1)","S\\1lpSqi&LvY\\1J\\1xSbx",[global])),
+?line <<"oIFblaHCV">> = iolist_to_binary(re:replace("blaH blaH","((?i)blah)\\s+(?i:\\1)","oIF\\1CV",[])),
+?line <<"oIFblaHCV">> = iolist_to_binary(re:replace("blaH blaH","((?i)blah)\\s+(?i:\\1)","oIF\\1CV",[global])),
+?line <<"blahblah BLAHnOGRLblahgvVwkgjq">> = iolist_to_binary(re:replace("blah BLAH","((?i)blah)\\s+(?i:\\1)","\\1&nOGRL\\1gvVwkgjq",[])),
+?line <<"blahblah BLAHnOGRLblahgvVwkgjq">> = iolist_to_binary(re:replace("blah BLAH","((?i)blah)\\s+(?i:\\1)","\\1&nOGRL\\1gvVwkgjq",[global])),
+?line <<"PrLBlahTtgBlah blahpNNDBlah blahBlah blahBlah blah">> = iolist_to_binary(re:replace("Blah blah","((?i)blah)\\s+(?i:\\1)","PrL\\1Ttg&pNND&&&",[])),
+?line <<"PrLBlahTtgBlah blahpNNDBlah blahBlah blahBlah blah">> = iolist_to_binary(re:replace("Blah blah","((?i)blah)\\s+(?i:\\1)","PrL\\1Ttg&pNND&&&",[global])),
+?line <<"blaH blahGnDblaHFhNOIOmBhblaH blahfblaH blahKt">> = iolist_to_binary(re:replace("blaH blah","((?i)blah)\\s+(?i:\\1)","&GnD\\1FhNOIOmBh&f&Kt",[])),
+?line <<"blaH blahGnDblaHFhNOIOmBhblaH blahfblaH blahKt">> = iolist_to_binary(re:replace("blaH blah","((?i)blah)\\s+(?i:\\1)","&GnD\\1FhNOIOmBh&f&Kt",[global])),
+?line <<"aW">> = iolist_to_binary(re:replace("a","(?>a*)*","&W",[])),
+?line <<"aWW">> = iolist_to_binary(re:replace("a","(?>a*)*","&W",[global])),
+?line <<"Seaa">> = iolist_to_binary(re:replace("aa","(?>a*)*","Se&",[])),
+?line <<"SeaaSe">> = iolist_to_binary(re:replace("aa","(?>a*)*","Se&",[global])),
+?line <<"itILgCmaaaaioxBkDkO">> = iolist_to_binary(re:replace("aaaa","(?>a*)*","itIL\\1gCm&ioxBkDkO",[])),
+?line <<"itILgCmaaaaioxBkDkOitILgCmioxBkDkO">> = iolist_to_binary(re:replace("aaaa","(?>a*)*","itIL\\1gCm&ioxBkDkO",[global])),
+?line <<"aqm">> = iolist_to_binary(re:replace("abc","(abc|)+","\\1aqm",[])),
+?line <<"aqmaqm">> = iolist_to_binary(re:replace("abc","(abc|)+","\\1aqm",[global])),
+?line <<"tSWTMOLPTnvvJTwabcabcJwE">> = iolist_to_binary(re:replace("abcabc","(abc|)+","tSWTMOLPTnvvJTw\\1&JwE",[])),
+?line <<"tSWTMOLPTnvvJTwabcabcJwEtSWTMOLPTnvvJTwJwE">> = iolist_to_binary(re:replace("abcabc","(abc|)+","tSWTMOLPTnvvJTw\\1&JwE",[global])),
+?line <<"eMhIJbMyp">> = iolist_to_binary(re:replace("abcabcabc","(abc|)+","\\1eMh\\1\\1IJb\\1Myp",[])),
+?line <<"eMhIJbMypeMhIJbMyp">> = iolist_to_binary(re:replace("abcabcabc","(abc|)+","\\1eMh\\1\\1IJb\\1Myp",[global])),
+?line <<"dBxyz">> = iolist_to_binary(re:replace("xyz","(abc|)+","dB",[])),
+?line <<"dBxdBydBzdB">> = iolist_to_binary(re:replace("xyz","(abc|)+","dB",[global])),
+?line <<"wCrHnihkSygW">> = iolist_to_binary(re:replace("a","([a]*)*","wCrHnihkSygW",[])),
+?line <<"wCrHnihkSygWwCrHnihkSygW">> = iolist_to_binary(re:replace("a","([a]*)*","wCrHnihkSygW",[global])),
+?line <<"GjjjYNVefTgBaaaaafMw">> = iolist_to_binary(re:replace("aaaaa","([a]*)*","\\1Gjj\\1jYNVefTgB&fMw",[])),
+?line <<"GjjjYNVefTgBaaaaafMwGjjjYNVefTgBfMw">> = iolist_to_binary(re:replace("aaaaa","([a]*)*","\\1Gjj\\1jYNVefTgB&fMw",[global])),
+?line <<"pXFqCJUNaNU">> = iolist_to_binary(re:replace("a","([ab]*)*","pXFqCJUN&NU",[])),
+?line <<"pXFqCJUNaNUpXFqCJUNNU">> = iolist_to_binary(re:replace("a","([ab]*)*","pXFqCJUN&NU",[global])),
+?line <<"qbGbaTAUHHibHnl">> = iolist_to_binary(re:replace("b","([ab]*)*","q&\\1GbaTAUHHi&Hn\\1l",[])),
+?line <<"qbGbaTAUHHibHnlqGbaTAUHHiHnl">> = iolist_to_binary(re:replace("b","([ab]*)*","q&\\1GbaTAUHHi&Hn\\1l",[global])),
+?line <<"l">> = iolist_to_binary(re:replace("ababab","([ab]*)*","\\1l",[])),
+?line <<"ll">> = iolist_to_binary(re:replace("ababab","([ab]*)*","\\1l",[global])),
+?line <<"scde">> = iolist_to_binary(re:replace("aaaabcde","([ab]*)*","s\\1\\1",[])),
+?line <<"sscsdses">> = iolist_to_binary(re:replace("aaaabcde","([ab]*)*","s\\1\\1",[global])),
+?line <<"cLbwWRDkdHt">> = iolist_to_binary(re:replace("bbbb","([ab]*)*","cL\\1bwWRDkdHt",[])),
+?line <<"cLbwWRDkdHtcLbwWRDkdHt">> = iolist_to_binary(re:replace("bbbb","([ab]*)*","cL\\1bwWRDkdHt",[global])),
+?line <<"nmytevammko">> = iolist_to_binary(re:replace("b","([^a]*)*","nmytevammk\\1o",[])),
+?line <<"nmytevammkonmytevammko">> = iolist_to_binary(re:replace("b","([^a]*)*","nmytevammk\\1o",[global])),
+?line <<"bbbbqIbAMyI">> = iolist_to_binary(re:replace("bbbb","([^a]*)*","&qIbAMyI",[])),
+?line <<"bbbbqIbAMyIqIbAMyI">> = iolist_to_binary(re:replace("bbbb","([^a]*)*","&qIbAMyI",[global])),
+?line <<"ItRSFsauaaa">> = iolist_to_binary(re:replace("aaa","([^a]*)*","ItRS\\1Fs&a&&u",[])),
+?line <<"ItRSFsauaItRSFsauaItRSFsauaItRSFsau">> = iolist_to_binary(re:replace("aaa","([^a]*)*","ItRS\\1Fs&a&&u",[global])),
+?line <<"PKlYUFjsxy">> = iolist_to_binary(re:replace("cccc","([^ab]*)*","PKlYUFjsxy",[])),
+?line <<"PKlYUFjsxyPKlYUFjsxy">> = iolist_to_binary(re:replace("cccc","([^ab]*)*","PKlYUFjsxy",[global])),
+?line <<"eBumQabab">> = iolist_to_binary(re:replace("abab","([^ab]*)*","e\\1BumQ",[])),
+?line <<"eBumQaeBumQbeBumQaeBumQbeBumQ">> = iolist_to_binary(re:replace("abab","([^ab]*)*","e\\1BumQ",[global])),
+?line <<"VsNPa">> = iolist_to_binary(re:replace("a","([a]*?)*","&Vs\\1N&P",[])),
+?line <<"VsNPaVsNaPVsNP">> = iolist_to_binary(re:replace("a","([a]*?)*","&Vs\\1N&P",[global])),
+?line <<"oJTmjRIMESSEdaaaa">> = iolist_to_binary(re:replace("aaaa","([a]*?)*","o&JT\\1\\1\\1&m&jR&IMESSEd",[])),
+?line <<"oJTmjRIMESSEdoaJTamajRaIMESSEdoJTmjRIMESSEdoaJTamajRaIMESSEdoJTmjRIMESSEdoaJTamajRaIMESSEdoJTmjRIMESSEdoaJTamajRaIMESSEdoJTmjRIMESSEd">> = iolist_to_binary(re:replace("aaaa","([a]*?)*","o&JT\\1\\1\\1&m&jR&IMESSEd",[global])),
+?line <<"uSOa">> = iolist_to_binary(re:replace("a","([ab]*?)*","&\\1&u&SO",[])),
+?line <<"uSOaauaSOuSO">> = iolist_to_binary(re:replace("a","([ab]*?)*","&\\1&u&SO",[global])),
+?line <<"BfDNwUAfjUb">> = iolist_to_binary(re:replace("b","([ab]*?)*","B&fDNwU\\1Af&j\\1U\\1",[])),
+?line <<"BfDNwUAfjUBbfDNwUAfbjUBfDNwUAfjU">> = iolist_to_binary(re:replace("b","([ab]*?)*","B&fDNwU\\1Af&j\\1U\\1",[global])),
+?line <<"kTOkbSeRnabab">> = iolist_to_binary(re:replace("abab","([ab]*?)*","k&TOkb\\1\\1S&eR&n&",[])),
+?line <<"kTOkbSeRnkaTOkbSaeRanakTOkbSeRnkbTOkbSbeRbnbkTOkbSeRnkaTOkbSaeRanakTOkbSeRnkbTOkbSbeRbnbkTOkbSeRn">> = iolist_to_binary(re:replace("abab","([ab]*?)*","k&TOkb\\1\\1S&eR&n&",[global])),
+?line <<"Sbaba">> = iolist_to_binary(re:replace("baba","([ab]*?)*","S",[])),
+?line <<"SSSSSSSSS">> = iolist_to_binary(re:replace("baba","([ab]*?)*","S",[global])),
+?line <<"Aotb">> = iolist_to_binary(re:replace("b","([^a]*?)*","A\\1ot",[])),
+?line <<"AotAotAot">> = iolist_to_binary(re:replace("b","([^a]*?)*","A\\1ot",[global])),
+?line <<"CbPnbbbb">> = iolist_to_binary(re:replace("bbbb","([^a]*?)*","CbPn&&",[])),
+?line <<"CbPnCbPnbbCbPnCbPnbbCbPnCbPnbbCbPnCbPnbbCbPn">> = iolist_to_binary(re:replace("bbbb","([^a]*?)*","CbPn&&",[global])),
+?line <<"DUteaaa">> = iolist_to_binary(re:replace("aaa","([^a]*?)*","D\\1\\1Ute",[])),
+?line <<"DUteaDUteaDUteaDUte">> = iolist_to_binary(re:replace("aaa","([^a]*?)*","D\\1\\1Ute",[global])),
+?line <<"ATgsBhAkPic">> = iolist_to_binary(re:replace("c","([^ab]*?)*","ATg\\1sB\\1hAkP&i",[])),
+?line <<"ATgsBhAkPiATgsBhAkPciATgsBhAkPi">> = iolist_to_binary(re:replace("c","([^ab]*?)*","ATg\\1sB\\1hAkP&i",[global])),
+?line <<"lwFoWkRIxUcccc">> = iolist_to_binary(re:replace("cccc","([^ab]*?)*","l&wFoWk\\1RIxU",[])),
+?line <<"lwFoWkRIxUlcwFoWkRIxUlwFoWkRIxUlcwFoWkRIxUlwFoWkRIxUlcwFoWkRIxUlwFoWkRIxUlcwFoWkRIxUlwFoWkRIxU">> = iolist_to_binary(re:replace("cccc","([^ab]*?)*","l&wFoWk\\1RIxU",[global])),
+?line <<"QXSXCbaba">> = iolist_to_binary(re:replace("baba","([^ab]*?)*","&QXSXC",[])),
+?line <<"QXSXCbQXSXCaQXSXCbQXSXCaQXSXC">> = iolist_to_binary(re:replace("baba","([^ab]*?)*","&QXSXC",[global])),
+?line <<"epgEBpyDja">> = iolist_to_binary(re:replace("a","(?>a*)*","e\\1\\1pgEB\\1\\1pyD\\1j&",[])),
+?line <<"epgEBpyDjaepgEBpyDj">> = iolist_to_binary(re:replace("a","(?>a*)*","e\\1\\1pgEB\\1\\1pyD\\1j&",[global])),
+?line <<"SekThCelBbcde">> = iolist_to_binary(re:replace("aaabcde","(?>a*)*","SekT\\1hC\\1\\1elB",[])),
+?line <<"SekThCelBSekThCelBbSekThCelBcSekThCelBdSekThCelBeSekThCelB">> = iolist_to_binary(re:replace("aaabcde","(?>a*)*","SekT\\1hC\\1\\1elB",[global])),
+?line <<"goPCaaaaawO">> = iolist_to_binary(re:replace("aaaaa","((?>a*))*","go\\1PC\\1&\\1wO",[])),
+?line <<"goPCaaaaawOgoPCwO">> = iolist_to_binary(re:replace("aaaaa","((?>a*))*","go\\1PC\\1&\\1wO",[global])),
+?line <<"SCaadJliaaKfxRbbbaa">> = iolist_to_binary(re:replace("aabbaa","((?>a*))*","\\1S\\1C&\\1dJ\\1\\1li&KfxR\\1b",[])),
+?line <<"SCaadJliaaKfxRbSCdJliKfxRbbSCdJliKfxRbbSCaadJliaaKfxRbSCdJliKfxRb">> = iolist_to_binary(re:replace("aabbaa","((?>a*))*","\\1S\\1C&\\1dJ\\1\\1li&KfxR\\1b",[global])),
+?line <<"ssMfBjQEIebjdsmPRaaaaa">> = iolist_to_binary(re:replace("aaaaa","((?>a*?))*","s&s&MfBjQEIebjdsmPR",[])),
+?line <<"ssMfBjQEIebjdsmPRassMfBjQEIebjdsmPRassMfBjQEIebjdsmPRassMfBjQEIebjdsmPRassMfBjQEIebjdsmPRassMfBjQEIebjdsmPR">> = iolist_to_binary(re:replace("aaaaa","((?>a*?))*","s&s&MfBjQEIebjdsmPR",[global])),
+?line <<"VQJGaabbaa">> = iolist_to_binary(re:replace("aabbaa","((?>a*?))*","VQ\\1&J\\1G",[])),
+?line <<"VQJGaVQJGaVQJGbVQJGbVQJGaVQJGaVQJG">> = iolist_to_binary(re:replace("aabbaa","((?>a*?))*","VQ\\1&J\\1G",[global])),
+?line <<"t">> = iolist_to_binary(re:replace("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","t",[extended])),
+?line <<"t">> = iolist_to_binary(re:replace("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","t",[extended,
+ global])),
+?line <<"EIgEQmDKuIoMFts">> = iolist_to_binary(re:replace("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","EIgEQmDKuIoMFts",[extended])),
+?line <<"EIgEQmDKuIoMFts">> = iolist_to_binary(re:replace("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","EIgEQmDKuIoMFts",[extended,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","M\\1K\\1N",[extended])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","M\\1K\\1N",[extended,
+ global])),
+?line <<"sep-12-98">> = iolist_to_binary(re:replace("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","Xby\\1g&vVVPgw\\1",[extended])),
+?line <<"sep-12-98">> = iolist_to_binary(re:replace("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ","Xby\\1g&vVVPgw\\1",[extended,
+ global])),
+?line <<"foonfoobJpbIfooAbKhpTIXfoofooEN">> = iolist_to_binary(re:replace("foobarfoo","(?<=(foo))bar\\1","n\\1bJpbI\\1AbKhpTIX\\1\\1EN",[])),
+?line <<"foonfoobJpbIfooAbKhpTIXfoofooEN">> = iolist_to_binary(re:replace("foobarfoo","(?<=(foo))bar\\1","n\\1bJpbI\\1AbKhpTIX\\1\\1EN",[global])),
+?line <<"foobarfoofooUWdHfoofoocfifXWQdmKgtling">> = iolist_to_binary(re:replace("foobarfootling","(?<=(foo))bar\\1","&\\1UWdH\\1\\1cfifXWQdmKg",[])),
+?line <<"foobarfoofooUWdHfoofoocfifXWQdmKgtling">> = iolist_to_binary(re:replace("foobarfootling","(?<=(foo))bar\\1","&\\1UWdH\\1\\1cfifXWQdmKg",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(foo))bar\\1","\\1A\\1Do\\1roQX",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(foo))bar\\1","\\1A\\1Do\\1roQX",[global])),
+?line <<"foobar">> = iolist_to_binary(re:replace("foobar","(?<=(foo))bar\\1","WqywQ",[])),
+?line <<"foobar">> = iolist_to_binary(re:replace("foobar","(?<=(foo))bar\\1","WqywQ",[global])),
+?line <<"barfoo">> = iolist_to_binary(re:replace("barfoo","(?<=(foo))bar\\1","g&dx",[])),
+?line <<"barfoo">> = iolist_to_binary(re:replace("barfoo","(?<=(foo))bar\\1","g&dx",[global])),
+?line <<"EqLnXCi">> = iolist_to_binary(re:replace("saturday","(?i:saturday|sunday)","EqLnX\\1Ci\\1",[])),
+?line <<"EqLnXCi">> = iolist_to_binary(re:replace("saturday","(?i:saturday|sunday)","EqLnX\\1Ci\\1",[global])),
+?line <<"rXiCVoMssundayWVsundayg">> = iolist_to_binary(re:replace("sunday","(?i:saturday|sunday)","rXiCVoMs&WV&\\1g",[])),
+?line <<"rXiCVoMssundayWVsundayg">> = iolist_to_binary(re:replace("sunday","(?i:saturday|sunday)","rXiCVoMs&WV&\\1g",[global])),
+?line <<"DCML">> = iolist_to_binary(re:replace("Saturday","(?i:saturday|sunday)","D\\1CML",[])),
+?line <<"DCML">> = iolist_to_binary(re:replace("Saturday","(?i:saturday|sunday)","D\\1CML",[global])),
+?line <<"caYSundaylQsqBJmDFf">> = iolist_to_binary(re:replace("Sunday","(?i:saturday|sunday)","caY&l\\1Q\\1\\1sq\\1BJmD\\1Ff",[])),
+?line <<"caYSundaylQsqBJmDFf">> = iolist_to_binary(re:replace("Sunday","(?i:saturday|sunday)","caY&l\\1Q\\1\\1sq\\1BJmD\\1Ff",[global])),
+?line <<"LSATURDAYSATURDAYb">> = iolist_to_binary(re:replace("SATURDAY","(?i:saturday|sunday)","L&&b",[])),
+?line <<"LSATURDAYSATURDAYb">> = iolist_to_binary(re:replace("SATURDAY","(?i:saturday|sunday)","L&&b",[global])),
+?line <<"J">> = iolist_to_binary(re:replace("SUNDAY","(?i:saturday|sunday)","J",[])),
+?line <<"J">> = iolist_to_binary(re:replace("SUNDAY","(?i:saturday|sunday)","J",[global])),
+?line <<"cCHUgeqmaSunDaySunDayQSunDayHUJ">> = iolist_to_binary(re:replace("SunDay","(?i:saturday|sunday)","c\\1CHUgeqma&&Q&HUJ",[])),
+?line <<"cCHUgeqmaSunDaySunDayQSunDayHUJ">> = iolist_to_binary(re:replace("SunDay","(?i:saturday|sunday)","c\\1CHUgeqma&&Q&HUJ",[global])),
+?line <<"abcEbXpYQWabcxNabcxabcxVrPLd">> = iolist_to_binary(re:replace("abcx","(a(?i)bc|BB)x","\\1EbXpYQW&N&&VrPLd",[])),
+?line <<"abcEbXpYQWabcxNabcxabcxVrPLd">> = iolist_to_binary(re:replace("abcx","(a(?i)bc|BB)x","\\1EbXpYQW&N&&VrPLd",[global])),
+?line <<"jBMIviaBCvaBCxTRLe">> = iolist_to_binary(re:replace("aBCx","(a(?i)bc|BB)x","jBMIvi\\1v&TRLe",[])),
+?line <<"jBMIviaBCvaBCxTRLe">> = iolist_to_binary(re:replace("aBCx","(a(?i)bc|BB)x","jBMIvi\\1v&TRLe",[global])),
+?line <<"wDnyUbbxDgOUSgpsDtqV">> = iolist_to_binary(re:replace("bbx","(a(?i)bc|BB)x","wDnyU&DgOUSgpsDtqV",[])),
+?line <<"wDnyUbbxDgOUSgpsDtqV">> = iolist_to_binary(re:replace("bbx","(a(?i)bc|BB)x","wDnyU&DgOUSgpsDtqV",[global])),
+?line <<"Vx">> = iolist_to_binary(re:replace("BBx","(a(?i)bc|BB)x","Vx",[])),
+?line <<"Vx">> = iolist_to_binary(re:replace("BBx","(a(?i)bc|BB)x","Vx",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a(?i)bc|BB)x","vuaWcgIs\\1SRUcqMEb",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(a(?i)bc|BB)x","vuaWcgIs\\1SRUcqMEb",[global])),
+?line <<"abcX">> = iolist_to_binary(re:replace("abcX","(a(?i)bc|BB)x","V\\1",[])),
+?line <<"abcX">> = iolist_to_binary(re:replace("abcX","(a(?i)bc|BB)x","V\\1",[global])),
+?line <<"aBCX">> = iolist_to_binary(re:replace("aBCX","(a(?i)bc|BB)x","kcaH&mISces&gy\\1Mv",[])),
+?line <<"aBCX">> = iolist_to_binary(re:replace("aBCX","(a(?i)bc|BB)x","kcaH&mISces&gy\\1Mv",[global])),
+?line <<"bbX">> = iolist_to_binary(re:replace("bbX","(a(?i)bc|BB)x","hfQHW",[])),
+?line <<"bbX">> = iolist_to_binary(re:replace("bbX","(a(?i)bc|BB)x","hfQHW",[global])),
+?line <<"BBX">> = iolist_to_binary(re:replace("BBX","(a(?i)bc|BB)x","QWSdgANNG&a&hUuhv&T",[])),
+?line <<"BBX">> = iolist_to_binary(re:replace("BBX","(a(?i)bc|BB)x","QWSdgANNG&a&hUuhv&T",[global])),
+?line <<"tEacrW">> = iolist_to_binary(re:replace("ac","^([ab](?i)[cd]|[ef])","tE\\1rW",[])),
+?line <<"tEacrW">> = iolist_to_binary(re:replace("ac","^([ab](?i)[cd]|[ef])","tE\\1rW",[global])),
+?line <<"jjqwaCaCyaCpaCPWnSv">> = iolist_to_binary(re:replace("aC","^([ab](?i)[cd]|[ef])","jjqw\\1\\1y\\1p&PWnSv",[])),
+?line <<"jjqwaCaCyaCpaCPWnSv">> = iolist_to_binary(re:replace("aC","^([ab](?i)[cd]|[ef])","jjqw\\1\\1y\\1p&PWnSv",[global])),
+?line <<"pbDoK">> = iolist_to_binary(re:replace("bD","^([ab](?i)[cd]|[ef])","p&oK",[])),
+?line <<"pbDoK">> = iolist_to_binary(re:replace("bD","^([ab](?i)[cd]|[ef])","p&oK",[global])),
+?line <<"hOUSaMTfcPejGlephant">> = iolist_to_binary(re:replace("elephant","^([ab](?i)[cd]|[ef])","hOUSaMTfcP&jG",[])),
+?line <<"hOUSaMTfcPejGlephant">> = iolist_to_binary(re:replace("elephant","^([ab](?i)[cd]|[ef])","hOUSaMTfcP&jG",[global])),
+?line <<"EOnTdEESsrQXxRPurope">> = iolist_to_binary(re:replace("Europe","^([ab](?i)[cd]|[ef])","\\1OnTd&&SsrQXxRP",[])),
+?line <<"EOnTdEESsrQXxRPurope">> = iolist_to_binary(re:replace("Europe","^([ab](?i)[cd]|[ef])","\\1OnTd&&SsrQXxRP",[global])),
+?line <<"fhfBHfIDHldAwNfEfqrog">> = iolist_to_binary(re:replace("frog","^([ab](?i)[cd]|[ef])","\\1h&BH\\1IDHldAwN&E\\1q",[])),
+?line <<"fhfBHfIDHldAwNfEfqrog">> = iolist_to_binary(re:replace("frog","^([ab](?i)[cd]|[ef])","\\1h&BH\\1IDHldAwN&E\\1q",[global])),
+?line <<"FFFxSuhnFwrance">> = iolist_to_binary(re:replace("France","^([ab](?i)[cd]|[ef])","\\1&FxSuhn&w",[])),
+?line <<"FFFxSuhnFwrance">> = iolist_to_binary(re:replace("France","^([ab](?i)[cd]|[ef])","\\1&FxSuhn&w",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^([ab](?i)[cd]|[ef])","&ff\\1J\\1I",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^([ab](?i)[cd]|[ef])","&ff\\1J\\1I",[global])),
+?line <<"Africa">> = iolist_to_binary(re:replace("Africa","^([ab](?i)[cd]|[ef])","nEbgaPXOn\\1",[])),
+?line <<"Africa">> = iolist_to_binary(re:replace("Africa","^([ab](?i)[cd]|[ef])","nEbgaPXOn\\1",[global])),
+?line <<"abcabDXAiaSg">> = iolist_to_binary(re:replace("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","&c&DXAiaSg",[])),
+?line <<"abcabDXAiaSg">> = iolist_to_binary(re:replace("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","&c&DXAiaSg",[global])),
+?line <<"aBdqaBdmLoaaBdEb">> = iolist_to_binary(re:replace("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","&q&mLoa&Eb",[])),
+?line <<"aBdqaBdmLoaaBdEb">> = iolist_to_binary(re:replace("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","&q&mLoa&Eb",[global])),
+?line <<"xxyvFo">> = iolist_to_binary(re:replace("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","x&vFo",[])),
+?line <<"xxyvFo">> = iolist_to_binary(re:replace("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","x&vFo",[global])),
+?line <<"eWy">> = iolist_to_binary(re:replace("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","eWy",[])),
+?line <<"eWy">> = iolist_to_binary(re:replace("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","eWy",[global])),
+?line <<"bzVONndeqzaVKebra">> = iolist_to_binary(re:replace("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","b&VONndeq\\1aVK",[])),
+?line <<"bzVONndeqzaVKebra">> = iolist_to_binary(re:replace("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","b&VONndeq\\1aVK",[global])),
+?line <<"ZNZZjZVZJeZnZZCXZambesi">> = iolist_to_binary(re:replace("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","&N&&j&V&Je\\1n&\\1CX&",[])),
+?line <<"ZNZZjZVZJeZnZZCXZambesi">> = iolist_to_binary(re:replace("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","&N&&j&V&Je\\1n&\\1CX&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","vJ\\1\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","vJ\\1\\1",[global])),
+?line <<"aCD">> = iolist_to_binary(re:replace("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","\\1FKPhO&",[])),
+?line <<"aCD">> = iolist_to_binary(re:replace("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","\\1FKPhO&",[global])),
+?line <<"XY">> = iolist_to_binary(re:replace("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","SEE",[])),
+?line <<"XY">> = iolist_to_binary(re:replace("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)","SEE",[global])),
+?line <<"foo
+rwHxBqDqeLQ">> = iolist_to_binary(re:replace("foo
+bar","(?<=foo\\n)^bar","rwHxBqDq\\1eLQ",[multiline])),
+?line <<"foo
+rwHxBqDqeLQ">> = iolist_to_binary(re:replace("foo
+bar","(?<=foo\\n)^bar","rwHxBqDq\\1eLQ",[multiline,global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=foo\\n)^bar","\\1P&&W&\\1oN",[multiline])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=foo\\n)^bar","\\1P&&W&\\1oN",[multiline,
+ global])),
+?line <<"bar">> = iolist_to_binary(re:replace("bar","(?<=foo\\n)^bar","rk\\1SSPj&JPOE",[multiline])),
+?line <<"bar">> = iolist_to_binary(re:replace("bar","(?<=foo\\n)^bar","rk\\1SSPj&JPOE",[multiline,
+ global])),
+?line <<"baz
+bar">> = iolist_to_binary(re:replace("baz
+bar","(?<=foo\\n)^bar","&JIhqO&Da",[multiline])),
+?line <<"baz
+bar">> = iolist_to_binary(re:replace("baz
+bar","(?<=foo\\n)^bar","&JIhqO&Da",[multiline,global])),
+?line <<"baruQUCmWhYKFBWj">> = iolist_to_binary(re:replace("barbaz","(?<=(?<!foo)bar)baz","uQUCmWhYKF\\1BWj\\1",[])),
+?line <<"baruQUCmWhYKFBWj">> = iolist_to_binary(re:replace("barbaz","(?<=(?<!foo)bar)baz","uQUCmWhYKF\\1BWj\\1",[global])),
+?line <<"barbarTNMlbazRYUbazYJyQER">> = iolist_to_binary(re:replace("barbarbaz","(?<=(?<!foo)bar)baz","TNMl\\1&RYU&YJyQER",[])),
+?line <<"barbarTNMlbazRYUbazYJyQER">> = iolist_to_binary(re:replace("barbarbaz","(?<=(?<!foo)bar)baz","TNMl\\1&RYU&YJyQER",[global])),
+?line <<"koobarxhTvv">> = iolist_to_binary(re:replace("koobarbaz","(?<=(?<!foo)bar)baz","xhTvv\\1",[])),
+?line <<"koobarxhTvv">> = iolist_to_binary(re:replace("koobarbaz","(?<=(?<!foo)bar)baz","xhTvv\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(?<!foo)bar)baz","uHmyIQ&yV&hQ&Di&\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(?<!foo)bar)baz","uHmyIQ&yV&hQ&Di&\\1",[global])),
+?line <<"baz">> = iolist_to_binary(re:replace("baz","(?<=(?<!foo)bar)baz","B",[])),
+?line <<"baz">> = iolist_to_binary(re:replace("baz","(?<=(?<!foo)bar)baz","B",[global])),
+?line <<"foobarbaz">> = iolist_to_binary(re:replace("foobarbaz","(?<=(?<!foo)bar)baz","BuNXYgf",[])),
+?line <<"foobarbaz">> = iolist_to_binary(re:replace("foobarbaz","(?<=(?<!foo)bar)baz","BuNXYgf",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","^(a\\1?){4}$","N\\1&Psi",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","^(a\\1?){4}$","N\\1&Psi",[global])),
+?line <<"aa">> = iolist_to_binary(re:replace("aa","^(a\\1?){4}$","&",[])),
+?line <<"aa">> = iolist_to_binary(re:replace("aa","^(a\\1?){4}$","&",[global])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","^(a\\1?){4}$","\\1OPhpdjl&J&F&j",[])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","^(a\\1?){4}$","\\1OPhpdjl&J&F&j",[global])),
+?line <<"oEaaPaaaaFyHMpKbNxCqlyG">> = iolist_to_binary(re:replace("aaaa","^(a\\1?){4}$","oE\\1\\1P&FyHMpKbNxCqlyG",[])),
+?line <<"oEaaPaaaaFyHMpKbNxCqlyG">> = iolist_to_binary(re:replace("aaaa","^(a\\1?){4}$","oE\\1\\1P&FyHMpKbNxCqlyG",[global])),
+?line <<"aaaaaawR">> = iolist_to_binary(re:replace("aaaaa","^(a\\1?){4}$","\\1&wR",[])),
+?line <<"aaaaaawR">> = iolist_to_binary(re:replace("aaaaa","^(a\\1?){4}$","\\1&wR",[global])),
+?line <<"SaWUrMlNUaaaaaaaaaaaaaan">> = iolist_to_binary(re:replace("aaaaaaa","^(a\\1?){4}$","S\\1WUrMlNU&&n",[])),
+?line <<"SaWUrMlNUaaaaaaaaaaaaaan">> = iolist_to_binary(re:replace("aaaaaaa","^(a\\1?){4}$","S\\1WUrMlNU&&n",[global])),
+?line <<"aaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaa","^(a\\1?){4}$","Vk&&RgxI\\1\\1pJ&&",[])),
+?line <<"aaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaa","^(a\\1?){4}$","Vk&&RgxI\\1\\1pJ&&",[global])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a\\1?){4}$","x",[])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a\\1?){4}$","x",[global])),
+?line <<"FEkJCFraaaaaaaaaaaaaaaaaaaaMlwosy">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a\\1?){4}$","FEkJCFr&&Mlwosy",[])),
+?line <<"FEkJCFraaaaaaaaaaaaaaaaaaaaMlwosy">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a\\1?){4}$","FEkJCFr&&Mlwosy",[global])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a\\1?){4}$","&IO",[])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a\\1?){4}$","&IO",[global])),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaa","^(a\\1?){4}$","lKw",[])),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaa","^(a\\1?){4}$","lKw",[global])),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaa","^(a\\1?){4}$","nN&bSVJh\\1J\\1d&Ko",[])),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaa","^(a\\1?){4}$","nN&bSVJh\\1J\\1d&Ko",[global])),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaa","^(a\\1?){4}$","Fut\\1X\\1",[])),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaa","^(a\\1?){4}$","Fut\\1X\\1",[global])),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaa","^(a\\1?){4}$","v",[])),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaa","^(a\\1?){4}$","v",[global])),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaa","^(a\\1?){4}$","b",[])),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaa","^(a\\1?){4}$","b",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","VC",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","VC",[global])),
+?line <<"aa">> = iolist_to_binary(re:replace("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","&&mKsMkXfPP",[])),
+?line <<"aa">> = iolist_to_binary(re:replace("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","&&mKsMkXfPP",[global])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","c\\1HhkFrF&vy&\\1bmNHPw",[])),
+?line <<"aaa">> = iolist_to_binary(re:replace("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","c\\1HhkFrF&vy&\\1bmNHPw",[global])),
+?line <<"SnJcTQRFQiat">> = iolist_to_binary(re:replace("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","SnJcTQRFQi\\1t",[])),
+?line <<"SnJcTQRFQiat">> = iolist_to_binary(re:replace("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","SnJcTQRFQi\\1t",[global])),
+?line <<"MaaaaakiYfFeaaaaaetD">> = iolist_to_binary(re:replace("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","M&kiYfFe&etD",[])),
+?line <<"MaaaaakiYfFeaaaaaetD">> = iolist_to_binary(re:replace("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","M&kiYfFe&etD",[global])),
+?line <<"WDaaaaaasEn">> = iolist_to_binary(re:replace("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","WD&sEn",[])),
+?line <<"WDaaaaaasEn">> = iolist_to_binary(re:replace("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","WD&sEn",[global])),
+?line <<"sauvrqyMaaaaaaaEvrD">> = iolist_to_binary(re:replace("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","s\\1uvrqyM&EvrD",[])),
+?line <<"sauvrqyMaaaaaaaEvrD">> = iolist_to_binary(re:replace("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","s\\1uvrqyM&EvrD",[global])),
+?line <<"aaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","WyhxEMLFTAGuL&Hymc",[])),
+?line <<"aaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","WyhxEMLFTAGuL&Hymc",[global])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","\\1WXGI&T&rPigX\\1IAQu",[])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","\\1WXGI&T&rPigX\\1IAQu",[global])),
+?line <<"HaaaaaaaaaajHUn">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","H&jHUn",[])),
+?line <<"HaaaaaaaaaajHUn">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","H&jHUn",[global])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","e",[])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","e",[global])),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","dSM\\1GGkyX&xNUIVG&",[])),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","dSM\\1GGkyX&xNUIVG&",[global])),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","p\\1T",[])),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","p\\1T",[global])),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","wOWcxD\\1e&",[])),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","wOWcxD\\1e&",[global])),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","C&CWw&kAen&",[])),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","C&CWw&kAen&",[global])),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","rkiiFEQ&WDahG&lSqR",[])),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$","rkiiFEQ&WDahG&lSqR",[global])),
+?line <<"babcoabcabcESPlWKsDDog">> = iolist_to_binary(re:replace("abc","abc","b&o&&\\1ES\\1\\1PlWKsD\\1Dog",[])),
+?line <<"babcoabcabcESPlWKsDDog">> = iolist_to_binary(re:replace("abc","abc","b&o&&\\1ES\\1\\1PlWKsD\\1Dog",[global])),
+?line <<"xFfvcJy">> = iolist_to_binary(re:replace("xabcy","abc","FfvcJ",[])),
+?line <<"xFfvcJy">> = iolist_to_binary(re:replace("xabcy","abc","FfvcJ",[global])),
+?line <<"abaGvHrWEIIXoI">> = iolist_to_binary(re:replace("ababc","abc","a\\1\\1G\\1v\\1HrWEIIXoI\\1",[])),
+?line <<"abaGvHrWEIIXoI">> = iolist_to_binary(re:replace("ababc","abc","a\\1\\1G\\1v\\1HrWEIIXoI\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc","DOtxCgk&UIBKmk\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc","DOtxCgk&UIBKmk\\1",[global])),
+?line <<"xbc">> = iolist_to_binary(re:replace("xbc","abc","qmfvVTE\\1aHl\\1BT&U",[])),
+?line <<"xbc">> = iolist_to_binary(re:replace("xbc","abc","qmfvVTE\\1aHl\\1BT&U",[global])),
+?line <<"axc">> = iolist_to_binary(re:replace("axc","abc","uN&yFKoX\\1pBswe\\1HLf",[])),
+?line <<"axc">> = iolist_to_binary(re:replace("axc","abc","uN&yFKoX\\1pBswe\\1HLf",[global])),
+?line <<"abx">> = iolist_to_binary(re:replace("abx","abc","&v\\1\\1QwMhqY",[])),
+?line <<"abx">> = iolist_to_binary(re:replace("abx","abc","&v\\1\\1QwMhqY",[global])),
+?line <<"FIOGpcHeabcNHJ">> = iolist_to_binary(re:replace("abc","ab*c","FIOGpcHe&NHJ",[])),
+?line <<"FIOGpcHeabcNHJ">> = iolist_to_binary(re:replace("abc","ab*c","FIOGpcHe&NHJ",[global])),
+?line <<"abcbnCUabcnxKKHabcXVaO">> = iolist_to_binary(re:replace("abc","ab*bc","&bnCU&n\\1xKKH&\\1XVaO",[])),
+?line <<"abcbnCUabcnxKKHabcXVaO">> = iolist_to_binary(re:replace("abc","ab*bc","&bnCU&n\\1xKKH&\\1XVaO",[global])),
+?line <<"bFtejUbnDA">> = iolist_to_binary(re:replace("abbc","ab*bc","bFtejUbn\\1DA",[])),
+?line <<"bFtejUbnDA">> = iolist_to_binary(re:replace("abbc","ab*bc","bFtejUbn\\1DA",[global])),
+?line <<"W">> = iolist_to_binary(re:replace("abbbbc","ab*bc","W",[])),
+?line <<"W">> = iolist_to_binary(re:replace("abbbbc","ab*bc","W",[global])),
+?line <<"BlaEararHKSGaabbbbc">> = iolist_to_binary(re:replace("abbbbc",".{1}","Bl&E&r&r\\1HKSG&&\\1",[])),
+?line <<"BlaEararHKSGaaBlbEbrbrHKSGbbBlbEbrbrHKSGbbBlbEbrbrHKSGbbBlbEbrbrHKSGbbBlcEcrcrHKSGcc">> = iolist_to_binary(re:replace("abbbbc",".{1}","Bl&E&r&r\\1HKSG&&\\1",[global])),
+?line <<"pabbbcRXxcEabbbIabbbXTbc">> = iolist_to_binary(re:replace("abbbbc",".{3,4}","p&cRXxcE&I&XT",[])),
+?line <<"pabbbcRXxcEabbbIabbbXTbc">> = iolist_to_binary(re:replace("abbbbc",".{3,4}","p&cRXxcE&I&XT",[global])),
+?line <<"HkIFCqCBaabbbbcyHm">> = iolist_to_binary(re:replace("abbbbc","ab{0,}bc","Hk\\1IFCq\\1CBa&yHm",[])),
+?line <<"HkIFCqCBaabbbbcyHm">> = iolist_to_binary(re:replace("abbbbc","ab{0,}bc","Hk\\1IFCq\\1CBa&yHm",[global])),
+?line <<"HBKHabbcHxdabbciuxleGabbc">> = iolist_to_binary(re:replace("abbc","ab+bc","HBKH&\\1Hx\\1d&iuxleG&",[])),
+?line <<"HBKHabbcHxdabbciuxleGabbc">> = iolist_to_binary(re:replace("abbc","ab+bc","HBKH&\\1Hx\\1d&iuxleG&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab+bc","&Pm&sRjS",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab+bc","&Pm&sRjS",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","ab+bc","IiWLkYFrIIhMk\\1D\\1vagP",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","ab+bc","IiWLkYFrIIhMk\\1D\\1vagP",[global])),
+?line <<"abq">> = iolist_to_binary(re:replace("abq","ab+bc","&\\1\\1UiX\\1&MgPB",[])),
+?line <<"abq">> = iolist_to_binary(re:replace("abq","ab+bc","&\\1\\1UiX\\1&MgPB",[global])),
+?line <<"WLvlqVMuiA">> = iolist_to_binary(re:replace("abbbbc","ab+bc","WLvlqVMuiA",[])),
+?line <<"WLvlqVMuiA">> = iolist_to_binary(re:replace("abbbbc","ab+bc","WLvlqVMuiA",[global])),
+?line <<"qQ">> = iolist_to_binary(re:replace("abbbbc","ab{1,}bc","qQ",[])),
+?line <<"qQ">> = iolist_to_binary(re:replace("abbbbc","ab{1,}bc","qQ",[global])),
+?line <<"ALwvHVhFGH">> = iolist_to_binary(re:replace("abbbbc","ab{1,3}bc","A\\1LwvHVhFGH",[])),
+?line <<"ALwvHVhFGH">> = iolist_to_binary(re:replace("abbbbc","ab{1,3}bc","A\\1LwvHVhFGH",[global])),
+?line <<"ORLsuabbbbc">> = iolist_to_binary(re:replace("abbbbc","ab{3,4}bc","ORL\\1su&",[])),
+?line <<"ORLsuabbbbc">> = iolist_to_binary(re:replace("abbbbc","ab{3,4}bc","ORL\\1su&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab{4,5}bc","APwPKjxS\\1Di&\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab{4,5}bc","APwPKjxS\\1Di&\\1",[global])),
+?line <<"abq">> = iolist_to_binary(re:replace("abq","ab{4,5}bc","FVk",[])),
+?line <<"abq">> = iolist_to_binary(re:replace("abq","ab{4,5}bc","FVk",[global])),
+?line <<"abbbbc">> = iolist_to_binary(re:replace("abbbbc","ab{4,5}bc","PO&t",[])),
+?line <<"abbbbc">> = iolist_to_binary(re:replace("abbbbc","ab{4,5}bc","PO&t",[global])),
+?line <<"EHtIXyQbabbclaCBg">> = iolist_to_binary(re:replace("abbc","ab?bc","EHtIXyQb&laCBg",[])),
+?line <<"EHtIXyQbabbclaCBg">> = iolist_to_binary(re:replace("abbc","ab?bc","EHtIXyQb&laCBg",[global])),
+?line <<"SVx">> = iolist_to_binary(re:replace("abc","ab?bc","SVx",[])),
+?line <<"SVx">> = iolist_to_binary(re:replace("abc","ab?bc","SVx",[global])),
+?line <<"KJabcWtBNRX">> = iolist_to_binary(re:replace("abc","ab{0,1}bc","KJ&WtBNRX",[])),
+?line <<"KJabcWtBNRX">> = iolist_to_binary(re:replace("abc","ab{0,1}bc","KJ&WtBNRX",[global])),
+?line <<"oabcQlOwmmeXxTySSV">> = iolist_to_binary(re:replace("abc","ab?c","o&Q\\1lOwmmeXx\\1TySSV",[])),
+?line <<"oabcQlOwmmeXxTySSV">> = iolist_to_binary(re:replace("abc","ab?c","o&Q\\1lOwmmeXx\\1TySSV",[global])),
+?line <<"abcQpPYPtQcFabcCDEcWOl">> = iolist_to_binary(re:replace("abc","ab{0,1}c","&QpPY\\1PtQcF&CDEcWOl",[])),
+?line <<"abcQpPYPtQcFabcCDEcWOl">> = iolist_to_binary(re:replace("abc","ab{0,1}c","&QpPY\\1PtQcF&CDEcWOl",[global])),
+?line <<"YbB">> = iolist_to_binary(re:replace("abc","^abc$","YbB",[])),
+?line <<"YbB">> = iolist_to_binary(re:replace("abc","^abc$","YbB",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^abc$","r&t\\1OLYfC",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^abc$","r&t\\1OLYfC",[global])),
+?line <<"abbbbc">> = iolist_to_binary(re:replace("abbbbc","^abc$","P&QwAxc\\1vYfQF",[])),
+?line <<"abbbbc">> = iolist_to_binary(re:replace("abbbbc","^abc$","P&QwAxc\\1vYfQF",[global])),
+?line <<"abcc">> = iolist_to_binary(re:replace("abcc","^abc$","d\\1j&LhAN\\1JvojhyCmSwU",[])),
+?line <<"abcc">> = iolist_to_binary(re:replace("abcc","^abc$","d\\1j&LhAN\\1JvojhyCmSwU",[global])),
+?line <<"yabcxAc">> = iolist_to_binary(re:replace("abcc","^abc","y\\1&xA",[])),
+?line <<"yabcxAc">> = iolist_to_binary(re:replace("abcc","^abc","y\\1&xA",[global])),
+?line <<"awrOx">> = iolist_to_binary(re:replace("aabc","abc$","wrOx",[])),
+?line <<"awrOx">> = iolist_to_binary(re:replace("aabc","abc$","wrOx",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc$","XLe",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc$","XLe",[global])),
+?line <<"aRthCinfyGSJTg">> = iolist_to_binary(re:replace("aabc","abc$","\\1RthCi\\1nfyGSJTg",[])),
+?line <<"aRthCinfyGSJTg">> = iolist_to_binary(re:replace("aabc","abc$","\\1RthCi\\1nfyGSJTg",[global])),
+?line <<"aabcd">> = iolist_to_binary(re:replace("aabcd","abc$","UT&CPLaUA\\1nluQPA",[])),
+?line <<"aabcd">> = iolist_to_binary(re:replace("aabcd","abc$","UT&CPLaUA\\1nluQPA",[global])),
+?line <<"hYaxcodDoHwNTLtDQoabc">> = iolist_to_binary(re:replace("abc","^","hYaxc&o&dDoHwNTLtDQo",[])),
+?line <<"hYaxcodDoHwNTLtDQoabc">> = iolist_to_binary(re:replace("abc","^","hYaxc&o&dDoHwNTLtDQo",[global])),
+?line <<"abcepxRqYNdJMdQeRKr">> = iolist_to_binary(re:replace("abc","$","epxRqYNdJ&MdQeR&Kr",[])),
+?line <<"abcepxRqYNdJMdQeRKr">> = iolist_to_binary(re:replace("abc","$","epxRqYNdJ&MdQeR&Kr",[global])),
+?line <<"vnabcXvSRrFjWv">> = iolist_to_binary(re:replace("abc","a.c","\\1vn&X\\1vSRrFj\\1Wv",[])),
+?line <<"vnabcXvSRrFjWv">> = iolist_to_binary(re:replace("abc","a.c","\\1vn&X\\1vSRrFj\\1Wv",[global])),
+?line <<"YSYAvHCLl">> = iolist_to_binary(re:replace("axc","a.c","Y\\1\\1SYAvHCLl",[])),
+?line <<"YSYAvHCLl">> = iolist_to_binary(re:replace("axc","a.c","Y\\1\\1SYAvHCLl",[global])),
+?line <<"oTi">> = iolist_to_binary(re:replace("axyzc","a.*c","oTi",[])),
+?line <<"oTi">> = iolist_to_binary(re:replace("axyzc","a.*c","oTi",[global])),
+?line <<"abdd">> = iolist_to_binary(re:replace("abd","a[bc]d","&d",[])),
+?line <<"abdd">> = iolist_to_binary(re:replace("abd","a[bc]d","&d",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[bc]d","m",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[bc]d","m",[global])),
+?line <<"axyzd">> = iolist_to_binary(re:replace("axyzd","a[bc]d","xw\\1kClblo&A&pX",[])),
+?line <<"axyzd">> = iolist_to_binary(re:replace("axyzd","a[bc]d","xw\\1kClblo&A&pX",[global])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","a[bc]d","DXVJBfd&&nWiQKDXx&p",[])),
+?line <<"abc">> = iolist_to_binary(re:replace("abc","a[bc]d","DXVJBfd&&nWiQKDXx&p",[global])),
+?line <<"qgcyq">> = iolist_to_binary(re:replace("ace","a[b-d]e","qgcyq\\1",[])),
+?line <<"qgcyq">> = iolist_to_binary(re:replace("ace","a[b-d]e","qgcyq\\1",[global])),
+?line <<"aSo">> = iolist_to_binary(re:replace("aac","a[b-d]","S\\1o",[])),
+?line <<"aSo">> = iolist_to_binary(re:replace("aac","a[b-d]","S\\1o",[global])),
+?line <<"FalJiLh">> = iolist_to_binary(re:replace("a-","a[-b]","FalJ\\1iL\\1h",[])),
+?line <<"FalJiLh">> = iolist_to_binary(re:replace("a-","a[-b]","FalJ\\1iL\\1h",[global])),
+?line <<"aea-">> = iolist_to_binary(re:replace("a-","a[b-]","ae&",[])),
+?line <<"aea-">> = iolist_to_binary(re:replace("a-","a[b-]","ae&",[global])),
+?line <<"Uxci">> = iolist_to_binary(re:replace("a]","a]","Uxci",[])),
+?line <<"Uxci">> = iolist_to_binary(re:replace("a]","a]","Uxci",[global])),
+?line <<"fuDs">> = iolist_to_binary(re:replace("a]b","a[]]b","fu\\1Ds",[])),
+?line <<"fuDs">> = iolist_to_binary(re:replace("a]b","a[]]b","fu\\1Ds",[global])),
+?line <<"S">> = iolist_to_binary(re:replace("aed","a[^bc]d","\\1S",[])),
+?line <<"S">> = iolist_to_binary(re:replace("aed","a[^bc]d","\\1S",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[^bc]d","q\\1c",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[^bc]d","q\\1c",[global])),
+?line <<"abd">> = iolist_to_binary(re:replace("abd","a[^bc]d","CwigRG\\1",[])),
+?line <<"abd">> = iolist_to_binary(re:replace("abd","a[^bc]d","CwigRG\\1",[global])),
+?line <<"abd">> = iolist_to_binary(re:replace("abd","a[^bc]d","G&sOiYoXxtvjC\\1C",[])),
+?line <<"abd">> = iolist_to_binary(re:replace("abd","a[^bc]d","G&sOiYoXxtvjC\\1C",[global])),
+?line <<"WjwradcGadcGdiadcJadcadc">> = iolist_to_binary(re:replace("adc","a[^-b]c","Wjwr&G&Gdi&J&&",[])),
+?line <<"WjwradcGadcGdiadcJadcadc">> = iolist_to_binary(re:replace("adc","a[^-b]c","Wjwr&G&Gdi&J&&",[global])),
+?line <<"sXuNQuSoADXQHaadcc">> = iolist_to_binary(re:replace("adc","a[^]b]c","sXuNQuS\\1oAD\\1XQH\\1a&c",[])),
+?line <<"sXuNQuSoADXQHaadcc">> = iolist_to_binary(re:replace("adc","a[^]b]c","sXuNQuS\\1oAD\\1XQH\\1a&c",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[^]b]c","\\1WTVFfqlY\\1I",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[^]b]c","\\1WTVFfqlY\\1I",[global])),
+?line <<"a-c">> = iolist_to_binary(re:replace("a-c","a[^]b]c","&",[])),
+?line <<"a-c">> = iolist_to_binary(re:replace("a-c","a[^]b]c","&",[global])),
+?line <<"a]c">> = iolist_to_binary(re:replace("a]c","a[^]b]c","U\\1ASb",[])),
+?line <<"a]c">> = iolist_to_binary(re:replace("a]c","a[^]b]c","U\\1ASb",[global])),
+?line <<"FhmTxP-">> = iolist_to_binary(re:replace("a-","\\ba\\b","FhmTxP",[])),
+?line <<"FhmTxP-">> = iolist_to_binary(re:replace("a-","\\ba\\b","FhmTxP",[global])),
+?line <<"-sIgAwOVeaIs">> = iolist_to_binary(re:replace("-a","\\ba\\b","sIgAwOVe&Is",[])),
+?line <<"-sIgAwOVeaIs">> = iolist_to_binary(re:replace("-a","\\ba\\b","sIgAwOVe&Is",[global])),
+?line <<"-K-">> = iolist_to_binary(re:replace("-a-","\\ba\\b","K",[])),
+?line <<"-K-">> = iolist_to_binary(re:replace("-a-","\\ba\\b","K",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\by\\b","NFs\\1N&no&v&LBhrfD",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\by\\b","NFs\\1N&no&v&LBhrfD",[global])),
+?line <<"xy">> = iolist_to_binary(re:replace("xy","\\by\\b","\\1&WFL&q",[])),
+?line <<"xy">> = iolist_to_binary(re:replace("xy","\\by\\b","\\1&WFL&q",[global])),
+?line <<"yz">> = iolist_to_binary(re:replace("yz","\\by\\b","pjeKdgSu&&",[])),
+?line <<"yz">> = iolist_to_binary(re:replace("yz","\\by\\b","pjeKdgSu&&",[global])),
+?line <<"xyz">> = iolist_to_binary(re:replace("xyz","\\by\\b","hyG",[])),
+?line <<"xyz">> = iolist_to_binary(re:replace("xyz","\\by\\b","hyG",[global])),
+?line <<"*** FCasopdebailers">> = iolist_to_binary(re:replace("*** Failers","\\Ba\\B","C\\1&sopdeb&",[])),
+?line <<"*** FCasopdebailers">> = iolist_to_binary(re:replace("*** Failers","\\Ba\\B","C\\1&sopdeb&",[global])),
+?line <<"a-">> = iolist_to_binary(re:replace("a-","\\Ba\\B","mnLWav",[])),
+?line <<"a-">> = iolist_to_binary(re:replace("a-","\\Ba\\B","mnLWav",[global])),
+?line <<"-a">> = iolist_to_binary(re:replace("-a","\\Ba\\B","X&NpSD\\1If",[])),
+?line <<"-a">> = iolist_to_binary(re:replace("-a","\\Ba\\B","X&NpSD\\1If",[global])),
+?line <<"-a-">> = iolist_to_binary(re:replace("-a-","\\Ba\\B","loFA&cpicKF&FLSfj&",[])),
+?line <<"-a-">> = iolist_to_binary(re:replace("-a-","\\Ba\\B","loFA&cpicKF&FLSfj&",[global])),
+?line <<"xyTAT">> = iolist_to_binary(re:replace("xy","\\By\\b","&TAT",[])),
+?line <<"xyTAT">> = iolist_to_binary(re:replace("xy","\\By\\b","&TAT",[global])),
+?line <<"fxz">> = iolist_to_binary(re:replace("yz","\\by\\B","fx\\1\\1",[])),
+?line <<"fxz">> = iolist_to_binary(re:replace("yz","\\by\\B","fx\\1\\1",[global])),
+?line <<"xKryOdAEz">> = iolist_to_binary(re:replace("xyz","\\By\\B","Kr&OdAE",[])),
+?line <<"xKryOdAEz">> = iolist_to_binary(re:replace("xyz","\\By\\B","Kr&OdAE",[global])),
+?line <<"IRVtaAqLDh">> = iolist_to_binary(re:replace("a","\\w","IRVt&AqLDh\\1",[])),
+?line <<"IRVtaAqLDh">> = iolist_to_binary(re:replace("a","\\w","IRVt&AqLDh\\1",[global])),
+?line <<"EckncnNqwHO">> = iolist_to_binary(re:replace("-","\\W","EckncnNq\\1wHO\\1",[])),
+?line <<"EckncnNqwHO">> = iolist_to_binary(re:replace("-","\\W","EckncnNq\\1wHO\\1",[global])),
+?line <<"Yf*VvjLGbD*h** Failers">> = iolist_to_binary(re:replace("*** Failers","\\W","Yf&VvjLGbD&h",[])),
+?line <<"Yf*VvjLGbD*hYf*VvjLGbD*hYf*VvjLGbD*hYf VvjLGbD hFailers">> = iolist_to_binary(re:replace("*** Failers","\\W","Yf&VvjLGbD&h",[global])),
+?line <<"GBjpbjk">> = iolist_to_binary(re:replace("-","\\W","GBjpbjk",[])),
+?line <<"GBjpbjk">> = iolist_to_binary(re:replace("-","\\W","GBjpbjk",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","\\W","j",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","\\W","j",[global])),
+?line <<"VAiCYGiBXpa bumnbi">> = iolist_to_binary(re:replace("a b","a\\sb","VAiCYGiBX\\1p&umn\\1bi",[])),
+?line <<"VAiCYGiBXpa bumnbi">> = iolist_to_binary(re:replace("a b","a\\sb","VAiCYGiBX\\1p&umn\\1bi",[global])),
+?line <<"Etv">> = iolist_to_binary(re:replace("a-b","a\\Sb","Etv",[])),
+?line <<"Etv">> = iolist_to_binary(re:replace("a-b","a\\Sb","Etv",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a\\Sb","EbhHjGSDvEtLT\\1cSURa",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a\\Sb","EbhHjGSDvEtLT\\1cSURa",[global])),
+?line <<"uD">> = iolist_to_binary(re:replace("a-b","a\\Sb","uD",[])),
+?line <<"uD">> = iolist_to_binary(re:replace("a-b","a\\Sb","uD",[global])),
+?line <<"a b">> = iolist_to_binary(re:replace("a b","a\\Sb","fQm&D&nJmqrWl",[])),
+?line <<"a b">> = iolist_to_binary(re:replace("a b","a\\Sb","fQm&D&nJmqrWl",[global])),
+?line <<"jti">> = iolist_to_binary(re:replace("1","\\d","jti",[])),
+?line <<"jti">> = iolist_to_binary(re:replace("1","\\d","jti",[global])),
+?line <<"-m">> = iolist_to_binary(re:replace("-","\\D","&m",[])),
+?line <<"-m">> = iolist_to_binary(re:replace("-","\\D","&m",[global])),
+?line <<"Tw** Failers">> = iolist_to_binary(re:replace("*** Failers","\\D","\\1Tw",[])),
+?line <<"TwTwTwTwTwTwTwTwTwTwTw">> = iolist_to_binary(re:replace("*** Failers","\\D","\\1Tw",[global])),
+?line <<"T">> = iolist_to_binary(re:replace("-","\\D","T",[])),
+?line <<"T">> = iolist_to_binary(re:replace("-","\\D","T",[global])),
+?line <<"1">> = iolist_to_binary(re:replace("1","\\D","QkyGdjVcibs",[])),
+?line <<"1">> = iolist_to_binary(re:replace("1","\\D","QkyGdjVcibs",[global])),
+?line <<"Vsg">> = iolist_to_binary(re:replace("a","[\\w]","Vsg",[])),
+?line <<"Vsg">> = iolist_to_binary(re:replace("a","[\\w]","Vsg",[global])),
+?line <<"AeUjdVITmvExYR">> = iolist_to_binary(re:replace("-","[\\W]","AeU\\1jdVITmvExYR\\1",[])),
+?line <<"AeUjdVITmvExYR">> = iolist_to_binary(re:replace("-","[\\W]","AeU\\1jdVITmvExYR\\1",[global])),
+?line <<"WhGTRj*eDnCm** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\W]","WhGTRj&eDnCm",[])),
+?line <<"WhGTRj*eDnCmWhGTRj*eDnCmWhGTRj*eDnCmWhGTRj eDnCmFailers">> = iolist_to_binary(re:replace("*** Failers","[\\W]","WhGTRj&eDnCm",[global])),
+?line <<"c-aqg-Be">> = iolist_to_binary(re:replace("-","[\\W]","c&aqg&Be",[])),
+?line <<"c-aqg-Be">> = iolist_to_binary(re:replace("-","[\\W]","c&aqg&Be",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","[\\W]","lS",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","[\\W]","lS",[global])),
+?line <<"aTa bLH">> = iolist_to_binary(re:replace("a b","a[\\s]b","aT&LH\\1",[])),
+?line <<"aTa bLH">> = iolist_to_binary(re:replace("a b","a[\\s]b","aT&LH\\1",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a-b","a[\\S]b","a",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a-b","a[\\S]b","a",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[\\S]b","b\\1E&yC\\1kT&CwD",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[\\S]b","b\\1E&yC\\1kT&CwD",[global])),
+?line <<"a-bfLEHcwVqa-bwEar">> = iolist_to_binary(re:replace("a-b","a[\\S]b","&fLE\\1\\1Hcw\\1V\\1q&wEar",[])),
+?line <<"a-bfLEHcwVqa-bwEar">> = iolist_to_binary(re:replace("a-b","a[\\S]b","&fLE\\1\\1Hcw\\1V\\1q&wEar",[global])),
+?line <<"a b">> = iolist_to_binary(re:replace("a b","a[\\S]b","E\\1NcSC&l",[])),
+?line <<"a b">> = iolist_to_binary(re:replace("a b","a[\\S]b","E\\1NcSC&l",[global])),
+?line <<"U1LgIwNodaqkLmOQ">> = iolist_to_binary(re:replace("1","[\\d]","U\\1&LgIwNo\\1daqkL\\1mOQ",[])),
+?line <<"U1LgIwNodaqkLmOQ">> = iolist_to_binary(re:replace("1","[\\d]","U\\1&LgIwNo\\1daqkL\\1mOQ",[global])),
+?line <<"XTGOaAL-XOENvmW">> = iolist_to_binary(re:replace("-","[\\D]","XTGOa\\1AL&XOE\\1NvmW",[])),
+?line <<"XTGOaAL-XOENvmW">> = iolist_to_binary(re:replace("-","[\\D]","XTGOa\\1AL&XOE\\1NvmW",[global])),
+?line <<"*rIhW*QnqjR*** Failers">> = iolist_to_binary(re:replace("*** Failers","[\\D]","&\\1r\\1I\\1h\\1W&Q\\1n\\1qjR&",[])),
+?line <<"*rIhW*QnqjR**rIhW*QnqjR**rIhW*QnqjR* rIhW QnqjR FrIhWFQnqjRFarIhWaQnqjRairIhWiQnqjRilrIhWlQnqjRlerIhWeQnqjRerrIhWrQnqjRrsrIhWsQnqjRs">> = iolist_to_binary(re:replace("*** Failers","[\\D]","&\\1r\\1I\\1h\\1W&Q\\1n\\1qjR&",[global])),
+?line <<"TEOmg-mrq">> = iolist_to_binary(re:replace("-","[\\D]","TE\\1Omg&\\1mrq",[])),
+?line <<"TEOmg-mrq">> = iolist_to_binary(re:replace("-","[\\D]","TE\\1Omg&\\1mrq",[global])),
+?line <<"1">> = iolist_to_binary(re:replace("1","[\\D]","YI\\1o&\\1IwvchJgD&&",[])),
+?line <<"1">> = iolist_to_binary(re:replace("1","[\\D]","YI\\1o&\\1IwvchJgD&&",[global])),
+?line <<"DKXoAolSGIabc">> = iolist_to_binary(re:replace("abc","ab|cd","DKXoAolSGI&",[])),
+?line <<"DKXoAolSGIabc">> = iolist_to_binary(re:replace("abc","ab|cd","DKXoAolSGI&",[global])),
+?line <<"tFHUIrVcd">> = iolist_to_binary(re:replace("abcd","ab|cd","tFHUIrV\\1",[])),
+?line <<"tFHUIrVtFHUIrV">> = iolist_to_binary(re:replace("abcd","ab|cd","tFHUIrV\\1",[global])),
+?line <<"doeAAefCeUJ">> = iolist_to_binary(re:replace("def","()ef","oeAA&CeUJ",[])),
+?line <<"doeAAefCeUJ">> = iolist_to_binary(re:replace("def","()ef","oeAA&CeUJ",[global])),
+?line <<"B">> = iolist_to_binary(re:replace("a(b","a\\(b","B",[])),
+?line <<"B">> = iolist_to_binary(re:replace("a(b","a\\(b","B",[global])),
+?line <<"sFcBhj">> = iolist_to_binary(re:replace("ab","a\\(*b","sFcBhj",[])),
+?line <<"sFcBhj">> = iolist_to_binary(re:replace("ab","a\\(*b","sFcBhj",[global])),
+?line <<"iTla((bUcHSjwja((ba((b">> = iolist_to_binary(re:replace("a((b","a\\(*b","iTl&UcHSjwj\\1&&",[])),
+?line <<"iTla((bUcHSjwja((ba((b">> = iolist_to_binary(re:replace("a((b","a\\(*b","iTl&UcHSjwj\\1&&",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","a\\\\b","AkoMVU&",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","a\\\\b","AkoMVU&",[global])),
+?line <<"Gbc">> = iolist_to_binary(re:replace("abc","((a))","G",[])),
+?line <<"Gbc">> = iolist_to_binary(re:replace("abc","((a))","G",[global])),
+?line <<"aaabcTualQ">> = iolist_to_binary(re:replace("abc","(a)b(c)","\\1\\1&Tu\\1lQ",[])),
+?line <<"aaabcTualQ">> = iolist_to_binary(re:replace("abc","(a)b(c)","\\1\\1&Tu\\1lQ",[global])),
+?line <<"aabbCivt">> = iolist_to_binary(re:replace("aabbabc","a+b+c","\\1Civt",[])),
+?line <<"aabbCivt">> = iolist_to_binary(re:replace("aabbabc","a+b+c","\\1Civt",[global])),
+?line <<"aabbT">> = iolist_to_binary(re:replace("aabbabc","a{1,}b{1,}c","T",[])),
+?line <<"aabbT">> = iolist_to_binary(re:replace("aabbabc","a{1,}b{1,}c","T",[global])),
+?line <<"VxJHThVabcketabcebWabc">> = iolist_to_binary(re:replace("abcabc","a.+?c","VxJHThV&ket&ebW",[])),
+?line <<"VxJHThVabcketabcebWVxJHThVabcketabcebW">> = iolist_to_binary(re:replace("abcabc","a.+?c","VxJHThV&ket&ebW",[global])),
+?line <<"LXfabNabCqMabHb">> = iolist_to_binary(re:replace("ab","(a+|b)*","LXf&N&CqM&H\\1",[])),
+?line <<"LXfabNabCqMabHbLXfNCqMH">> = iolist_to_binary(re:replace("ab","(a+|b)*","LXf&N&CqM&H\\1",[global])),
+?line <<"NNopapyUJpabVxnQ">> = iolist_to_binary(re:replace("ab","(a+|b){0,}","NNopapyUJpa\\1VxnQ",[])),
+?line <<"NNopapyUJpabVxnQNNopapyUJpaVxnQ">> = iolist_to_binary(re:replace("ab","(a+|b){0,}","NNopapyUJpa\\1VxnQ",[global])),
+?line <<"cejhccpabbAd">> = iolist_to_binary(re:replace("ab","(a+|b)+","cejhccp&\\1Ad",[])),
+?line <<"cejhccpabbAd">> = iolist_to_binary(re:replace("ab","(a+|b)+","cejhccp&\\1Ad",[global])),
+?line <<"uMqbbBaYPvPbkabNdlb">> = iolist_to_binary(re:replace("ab","(a+|b){1,}","uMqb\\1BaYPvP\\1k&Ndl\\1",[])),
+?line <<"uMqbbBaYPvPbkabNdlb">> = iolist_to_binary(re:replace("ab","(a+|b){1,}","uMqb\\1BaYPvP\\1k&Ndl\\1",[global])),
+?line <<"cyKMb">> = iolist_to_binary(re:replace("ab","(a+|b)?","cyKM",[])),
+?line <<"cyKMcyKMcyKM">> = iolist_to_binary(re:replace("ab","(a+|b)?","cyKM",[global])),
+?line <<"uaaPjgxb">> = iolist_to_binary(re:replace("ab","(a+|b){0,1}","u\\1\\1Pjgx",[])),
+?line <<"uaaPjgxubbPjgxuPjgx">> = iolist_to_binary(re:replace("ab","(a+|b){0,1}","u\\1\\1Pjgx",[global])),
+?line <<"JsG">> = iolist_to_binary(re:replace("cde","[^ab]*","\\1J\\1sG",[])),
+?line <<"JsGJsG">> = iolist_to_binary(re:replace("cde","[^ab]*","\\1J\\1sG",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc","NyHCvfJjxj\\1SrM&BdF",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc","NyHCvfJjxj\\1SrM&BdF",[global])),
+?line <<"b">> = iolist_to_binary(re:replace("b","abc","T&\\1Jd\\1tQxU\\1&\\1\\1bp",[])),
+?line <<"b">> = iolist_to_binary(re:replace("b","abc","T&\\1Jd\\1tQxU\\1&\\1\\1bp",[global])),
+?line <<"AHcabbbcdabbbcdabbbcdSCcNR">> = iolist_to_binary(re:replace("abbbcd","([abc])*d","AH\\1&&&SC\\1NR",[])),
+?line <<"AHcabbbcdabbbcdabbbcdSCcNR">> = iolist_to_binary(re:replace("abbbcd","([abc])*d","AH\\1&&&SC\\1NR",[global])),
+?line <<"jDJabcdqabcdgNaaNoyaGm">> = iolist_to_binary(re:replace("abcd","([abc])*bcd","jDJ&q&gN\\1\\1Noy\\1Gm",[])),
+?line <<"jDJabcdqabcdgNaaNoyaGm">> = iolist_to_binary(re:replace("abcd","([abc])*bcd","jDJ&q&gN\\1\\1Noy\\1Gm",[global])),
+?line <<"IHdxtFuTeF">> = iolist_to_binary(re:replace("e","a|b|c|d|e","IHdxtFuT&F",[])),
+?line <<"IHdxtFuTeF">> = iolist_to_binary(re:replace("e","a|b|c|d|e","IHdxtFuT&F",[global])),
+?line <<"RjceNtss">> = iolist_to_binary(re:replace("ef","(a|b|c|d|e)f","Rjc\\1Ntss",[])),
+?line <<"RjceNtss">> = iolist_to_binary(re:replace("ef","(a|b|c|d|e)f","Rjc\\1Ntss",[global])),
+?line <<"nViLDabcdefg">> = iolist_to_binary(re:replace("abcdefg","abcd*efg","nViL\\1D&\\1",[])),
+?line <<"nViLDabcdefg">> = iolist_to_binary(re:replace("abcdefg","abcd*efg","nViL\\1D&\\1",[global])),
+?line <<"xoJUabyHFyabbbz">> = iolist_to_binary(re:replace("xabyabbbz","ab*","oJ\\1U&yHF",[])),
+?line <<"xoJUabyHFyoJUabbbyHFz">> = iolist_to_binary(re:replace("xabyabbbz","ab*","oJ\\1U&yHF",[global])),
+?line <<"xbaEdBayabbbz">> = iolist_to_binary(re:replace("xayabbbz","ab*","b&EdBa",[])),
+?line <<"xbaEdBaybabbbEdBaz">> = iolist_to_binary(re:replace("xayabbbz","ab*","b&EdBa",[global])),
+?line <<"abHqcde">> = iolist_to_binary(re:replace("abcde","(ab|cd)e","Hq&",[])),
+?line <<"abHqcde">> = iolist_to_binary(re:replace("abcde","(ab|cd)e","Hq&",[global])),
+?line <<"lrrKIUARhij">> = iolist_to_binary(re:replace("hij","[abhgefdc]ij","l\\1\\1r\\1rKIUAR&",[])),
+?line <<"lrrKIUARhij">> = iolist_to_binary(re:replace("hij","[abhgefdc]ij","l\\1\\1r\\1rKIUAR&",[global])),
+?line <<"abcdWfgkefnnefNPAQ">> = iolist_to_binary(re:replace("abcdef","(abc|)ef","\\1Wfgk&nn&\\1NPAQ\\1",[])),
+?line <<"abcdWfgkefnnefNPAQ">> = iolist_to_binary(re:replace("abcdef","(abc|)ef","\\1Wfgk&nn&\\1NPAQ\\1",[global])),
+?line <<"aUbGqmbcdWXLMCpYbbcd">> = iolist_to_binary(re:replace("abcd","(a|b)c*d","U\\1Gqm&WXLMCpY\\1&",[])),
+?line <<"aUbGqmbcdWXLMCpYbbcd">> = iolist_to_binary(re:replace("abcd","(a|b)c*d","U\\1Gqm&WXLMCpY\\1&",[global])),
+?line <<"vYALaabcfgsaUfyDabcjOtcQ">> = iolist_to_binary(re:replace("abc","(ab|ab*)bc","vYAL\\1&fgs\\1UfyD&jOtcQ",[])),
+?line <<"vYALaabcfgsaUfyDabcjOtcQ">> = iolist_to_binary(re:replace("abc","(ab|ab*)bc","vYAL\\1&fgs\\1UfyD&jOtcQ",[global])),
+?line <<"uyabcabcp">> = iolist_to_binary(re:replace("abc","a([bc]*)c*","uy&&p",[])),
+?line <<"uyabcabcp">> = iolist_to_binary(re:replace("abc","a([bc]*)c*","uy&&p",[global])),
+?line <<"iAyJUbcM">> = iolist_to_binary(re:replace("abcd","a([bc]*)(c*d)","iAyJU\\1M",[])),
+?line <<"iAyJUbcM">> = iolist_to_binary(re:replace("abcd","a([bc]*)(c*d)","iAyJU\\1M",[global])),
+?line <<"abcdabcdabcd">> = iolist_to_binary(re:replace("abcd","a([bc]+)(c*d)","&&&",[])),
+?line <<"abcdabcdabcd">> = iolist_to_binary(re:replace("abcd","a([bc]+)(c*d)","&&&",[global])),
+?line <<"UgwJmKabcddNBBm">> = iolist_to_binary(re:replace("abcd","a([bc]*)(c+d)","UgwJmK&dNBBm",[])),
+?line <<"UgwJmKabcddNBBm">> = iolist_to_binary(re:replace("abcd","a([bc]*)(c+d)","UgwJmK&dNBBm",[global])),
+?line <<"glXDRFe">> = iolist_to_binary(re:replace("adcdcde","a[bcd]*dcdcde","glXDRFe\\1",[])),
+?line <<"glXDRFe">> = iolist_to_binary(re:replace("adcdcde","a[bcd]*dcdcde","glXDRFe\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[bcd]+dcdcde","&\\1Tw",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[bcd]+dcdcde","&\\1Tw",[global])),
+?line <<"abcde">> = iolist_to_binary(re:replace("abcde","a[bcd]+dcdcde","OaFcNB\\1AbGk\\1RcX&\\1hU",[])),
+?line <<"abcde">> = iolist_to_binary(re:replace("abcde","a[bcd]+dcdcde","OaFcNB\\1AbGk\\1RcX&\\1hU",[global])),
+?line <<"adcdcde">> = iolist_to_binary(re:replace("adcdcde","a[bcd]+dcdcde","LRCnDGeISr",[])),
+?line <<"adcdcde">> = iolist_to_binary(re:replace("adcdcde","a[bcd]+dcdcde","LRCnDGeISr",[global])),
+?line <<"ab">> = iolist_to_binary(re:replace("abc","(ab|a)b*c","\\1",[])),
+?line <<"ab">> = iolist_to_binary(re:replace("abc","(ab|a)b*c","\\1",[global])),
+?line <<"abcdYujfprabcdqmHBi">> = iolist_to_binary(re:replace("abcd","((a)(b)c)(d)","&Yujfpr&qmHBi",[])),
+?line <<"abcdYujfprabcdqmHBi">> = iolist_to_binary(re:replace("abcd","((a)(b)c)(d)","&Yujfpr&qmHBi",[global])),
+?line <<"HSalphaw">> = iolist_to_binary(re:replace("alpha","[a-zA-Z_][a-zA-Z0-9_]*","HS&w",[])),
+?line <<"HSalphaw">> = iolist_to_binary(re:replace("alpha","[a-zA-Z_][a-zA-Z0-9_]*","HS&w",[global])),
+?line <<"amKrkJTTmWxwbhbhVXebhD">> = iolist_to_binary(re:replace("abh","^a(bc+|b[eh])g|.h$","mKrkJTT\\1mWxw&&VXe&D",[])),
+?line <<"amKrkJTTmWxwbhbhVXebhD">> = iolist_to_binary(re:replace("abh","^a(bc+|b[eh])g|.h$","mKrkJTT\\1mWxw&&VXe&D",[global])),
+?line <<"gkglaeffgzeffgzwvwD">> = iolist_to_binary(re:replace("effgz","(bc+d$|ef*g.|h?i(j|k))","gkgla\\1&wvwD",[])),
+?line <<"gkglaeffgzeffgzwvwD">> = iolist_to_binary(re:replace("effgz","(bc+d$|ef*g.|h?i(j|k))","gkgla\\1&wvwD",[global])),
+?line <<"ivMGijUoGoijri">> = iolist_to_binary(re:replace("ij","(bc+d$|ef*g.|h?i(j|k))","ivMG\\1UoGo\\1ri",[])),
+?line <<"ivMGijUoGoijri">> = iolist_to_binary(re:replace("ij","(bc+d$|ef*g.|h?i(j|k))","ivMG\\1UoGo\\1ri",[global])),
+?line <<"reffgzWeffgzJeffgzeffgzFUK">> = iolist_to_binary(re:replace("reffgz","(bc+d$|ef*g.|h?i(j|k))","&W\\1J\\1&FUK",[])),
+?line <<"reffgzWeffgzJeffgzeffgzFUK">> = iolist_to_binary(re:replace("reffgz","(bc+d$|ef*g.|h?i(j|k))","&W\\1J\\1&FUK",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(bc+d$|ef*g.|h?i(j|k))","qoREgh&sKvuYfqcVSQ",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(bc+d$|ef*g.|h?i(j|k))","qoREgh&sKvuYfqcVSQ",[global])),
+?line <<"effg">> = iolist_to_binary(re:replace("effg","(bc+d$|ef*g.|h?i(j|k))","OSMK&kVLNnI",[])),
+?line <<"effg">> = iolist_to_binary(re:replace("effg","(bc+d$|ef*g.|h?i(j|k))","OSMK&kVLNnI",[global])),
+?line <<"bcdd">> = iolist_to_binary(re:replace("bcdd","(bc+d$|ef*g.|h?i(j|k))","l\\1SFH&\\1WG\\1N&\\1WpNv",[])),
+?line <<"bcdd">> = iolist_to_binary(re:replace("bcdd","(bc+d$|ef*g.|h?i(j|k))","l\\1SFH&\\1WG\\1N&\\1WpNv",[global])),
+?line <<"uCcLk">> = iolist_to_binary(re:replace("a","((((((((((a))))))))))","uCcLk",[])),
+?line <<"uCcLk">> = iolist_to_binary(re:replace("a","((((((((((a))))))))))","uCcLk",[global])),
+?line <<"VCUvbvxORiulavLRaFa">> = iolist_to_binary(re:replace("aa","((((((((((a))))))))))\\10","VCUvbvxORiul\\1vLR\\1F\\1",[])),
+?line <<"VCUvbvxORiulavLRaFa">> = iolist_to_binary(re:replace("aa","((((((((((a))))))))))\\10","VCUvbvxORiul\\1vLR\\1F\\1",[global])),
+?line <<"vFhEaarfaQfeLfaFGiV">> = iolist_to_binary(re:replace("a","(((((((((a)))))))))","vFhE\\1&rf&QfeLf\\1FGiV",[])),
+?line <<"vFhEaarfaQfeLfaFGiV">> = iolist_to_binary(re:replace("a","(((((((((a)))))))))","vFhE\\1&rf&QfeLf\\1FGiV",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","multiple words of text","jlWax\\1&H",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","multiple words of text","jlWax\\1&H",[global])),
+?line <<"aa">> = iolist_to_binary(re:replace("aa","multiple words of text","hsUw",[])),
+?line <<"aa">> = iolist_to_binary(re:replace("aa","multiple words of text","hsUw",[global])),
+?line <<"uh-uh">> = iolist_to_binary(re:replace("uh-uh","multiple words of text","ASEw\\1gOsB",[])),
+?line <<"uh-uh">> = iolist_to_binary(re:replace("uh-uh","multiple words of text","ASEw\\1gOsB",[global])),
+?line <<"dmultiple wordsxkMtmultiple words, yeah">> = iolist_to_binary(re:replace("multiple words, yeah","multiple words","d&x\\1\\1kMt&",[])),
+?line <<"dmultiple wordsxkMtmultiple words, yeah">> = iolist_to_binary(re:replace("multiple words, yeah","multiple words","d&x\\1\\1kMt&",[global])),
+?line <<"ykkVabcdefmldQabUjIJjw">> = iolist_to_binary(re:replace("abcde","(.*)c(.*)","ykkV&fmldQ\\1UjIJjw",[])),
+?line <<"ykkVabcdefmldQabUjIJjw">> = iolist_to_binary(re:replace("abcde","(.*)c(.*)","ykkV&fmldQ\\1UjIJjw",[global])),
+?line <<"efWBSCaa(a, b)Q">> = iolist_to_binary(re:replace("(a, b)","\\((.*), (.*)\\)","efWBSC\\1\\1&Q",[])),
+?line <<"efWBSCaa(a, b)Q">> = iolist_to_binary(re:replace("(a, b)","\\((.*), (.*)\\)","efWBSC\\1\\1&Q",[global])),
+?line <<"abcdBdXhwHpBabcdPC">> = iolist_to_binary(re:replace("abcd","abcd","&Bd\\1XhwHp\\1B&PC",[])),
+?line <<"abcdBdXhwHpBabcdPC">> = iolist_to_binary(re:replace("abcd","abcd","&Bd\\1XhwHp\\1B&PC",[global])),
+?line <<"SbsAruCoIPbckBgbcSyqva">> = iolist_to_binary(re:replace("abcd","a(bc)d","SbsAruCoIP\\1kBg\\1Syqva",[])),
+?line <<"SbsAruCoIPbckBgbcSyqva">> = iolist_to_binary(re:replace("abcd","a(bc)d","SbsAruCoIP\\1kBg\\1Syqva",[global])),
+?line <<"XiUVfmkDnpfY">> = iolist_to_binary(re:replace("ac","a[-]?c","XiUVfmkDnpfY",[])),
+?line <<"XiUVfmkDnpfY">> = iolist_to_binary(re:replace("ac","a[-]?c","XiUVfmkDnpfY",[global])),
+?line <<"GYmabcndabcabcabcabcCjabcabcabcabctjmn">> = iolist_to_binary(re:replace("abcabc","(abc)\\1","GYm\\1nd&&Cj&\\1\\1tjmn",[])),
+?line <<"GYmabcndabcabcabcabcCjabcabcabcabctjmn">> = iolist_to_binary(re:replace("abcabc","(abc)\\1","GYm\\1nd&&Cj&\\1\\1tjmn",[global])),
+?line <<"AabcFabcabcOw">> = iolist_to_binary(re:replace("abcabc","([a-c]*)\\1","A\\1F&Ow",[])),
+?line <<"AabcFabcabcOwAFOw">> = iolist_to_binary(re:replace("abcabc","([a-c]*)\\1","A\\1F&Ow",[global])),
+?line <<"savneuiFiA">> = iolist_to_binary(re:replace("a","(a)|\\1","s&vneuiFiA",[])),
+?line <<"savneuiFiA">> = iolist_to_binary(re:replace("a","(a)|\\1","s&vneuiFiA",[global])),
+?line <<"*** FatXvbwaxGeTrgahaailers">> = iolist_to_binary(re:replace("*** Failers","(a)|\\1","\\1tXvbw&xGeTrg\\1h&\\1",[])),
+?line <<"*** FatXvbwaxGeTrgahaailers">> = iolist_to_binary(re:replace("*** Failers","(a)|\\1","\\1tXvbw&xGeTrg\\1h&\\1",[global])),
+?line <<"ShaaIOaiKrRarjaTFxavb">> = iolist_to_binary(re:replace("ab","(a)|\\1","Sh\\1\\1IO&iKrR\\1rj\\1TFx&v",[])),
+?line <<"ShaaIOaiKrRarjaTFxavb">> = iolist_to_binary(re:replace("ab","(a)|\\1","Sh\\1\\1IO&iKrR\\1rj\\1TFx&v",[global])),
+?line <<"x">> = iolist_to_binary(re:replace("x","(a)|\\1","wG&OmupsBaCA&ULU&br",[])),
+?line <<"x">> = iolist_to_binary(re:replace("x","(a)|\\1","wG&OmupsBaCA&ULU&br",[global])),
+?line <<"ababbsaHOnababbDlUpRwMMqlababbCbbbcbc">> = iolist_to_binary(re:replace("ababbbcbc","(([a-c])b*?\\2)*","&saHOn&DlUpRwMMql&C\\1",[])),
+?line <<"ababbsaHOnababbDlUpRwMMqlababbCbbsaHOnDlUpRwMMqlCbcbcsaHOncbcDlUpRwMMqlcbcCcbcsaHOnDlUpRwMMqlC">> = iolist_to_binary(re:replace("ababbbcbc","(([a-c])b*?\\2)*","&saHOn&DlUpRwMMql&C\\1",[global])),
+?line <<"YdAFYmyyababbbcbcHpJCababbbcbcQa">> = iolist_to_binary(re:replace("ababbbcbc","(([a-c])b*?\\2){3}","YdAFYmyy&HpJC&Qa",[])),
+?line <<"YdAFYmyyababbbcbcHpJCababbbcbcQa">> = iolist_to_binary(re:replace("ababbbcbc","(([a-c])b*?\\2){3}","YdAFYmyy&HpJC&Qa",[global])),
+?line <<"aaaxabaxbaaxabbax">> = iolist_to_binary(re:replace("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+","a\\1",[])),
+?line <<"aaaxabaxbaaxabbax">> = iolist_to_binary(re:replace("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+","a\\1",[global])),
+?line <<"bbaababbabaaaaaoh">> = iolist_to_binary(re:replace("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}","oh",[])),
+?line <<"bbaababbabaaaaaoh">> = iolist_to_binary(re:replace("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}","oh",[global])),
+?line <<"fABCaeUgIABCDABCNFvst">> = iolist_to_binary(re:replace("ABC","abc","f&aeUgI&D&\\1NFvst",[caseless])),
+?line <<"fABCaeUgIABCDABCNFvst">> = iolist_to_binary(re:replace("ABC","abc","f&aeUgI&D&\\1NFvst",[caseless,
+ global])),
+?line <<"XgOY">> = iolist_to_binary(re:replace("XABCY","abc","gO",[caseless])),
+?line <<"XgOY">> = iolist_to_binary(re:replace("XABCY","abc","gO",[caseless,
+ global])),
+?line <<"ABnpaQvR">> = iolist_to_binary(re:replace("ABABC","abc","npaQvR",[caseless])),
+?line <<"ABnpaQvR">> = iolist_to_binary(re:replace("ABABC","abc","npaQvR",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc","E&beuUX&&",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","abc","E&beuUX&&",[caseless,
+ global])),
+?line <<"aaxabxbaxbbx">> = iolist_to_binary(re:replace("aaxabxbaxbbx","abc","\\1hAU\\1hg&Pk",[caseless])),
+?line <<"aaxabxbaxbbx">> = iolist_to_binary(re:replace("aaxabxbaxbbx","abc","\\1hAU\\1hg&Pk",[caseless,
+ global])),
+?line <<"XBC">> = iolist_to_binary(re:replace("XBC","abc","yqsG",[caseless])),
+?line <<"XBC">> = iolist_to_binary(re:replace("XBC","abc","yqsG",[caseless,
+ global])),
+?line <<"AXC">> = iolist_to_binary(re:replace("AXC","abc","\\1bi&Ff\\1kGVy&V",[caseless])),
+?line <<"AXC">> = iolist_to_binary(re:replace("AXC","abc","\\1bi&Ff\\1kGVy&V",[caseless,
+ global])),
+?line <<"ABX">> = iolist_to_binary(re:replace("ABX","abc","x&m\\1jtf&ptFxBd",[caseless])),
+?line <<"ABX">> = iolist_to_binary(re:replace("ABX","abc","x&m\\1jtf&ptFxBd",[caseless,
+ global])),
+?line <<"VOTkukABC">> = iolist_to_binary(re:replace("ABC","ab*c","V\\1OTkuk&\\1",[caseless])),
+?line <<"VOTkukABC">> = iolist_to_binary(re:replace("ABC","ab*c","V\\1OTkuk&\\1",[caseless,
+ global])),
+?line <<"gkYiABCuYNOFDNc">> = iolist_to_binary(re:replace("ABC","ab*bc","g\\1kYi&uYNOFDNc",[caseless])),
+?line <<"gkYiABCuYNOFDNc">> = iolist_to_binary(re:replace("ABC","ab*bc","g\\1kYi&uYNOFDNc",[caseless,
+ global])),
+?line <<"GHfaNWh">> = iolist_to_binary(re:replace("ABBC","ab*bc","G\\1HfaNWh",[caseless])),
+?line <<"GHfaNWh">> = iolist_to_binary(re:replace("ABBC","ab*bc","G\\1HfaNWh",[caseless,
+ global])),
+?line <<"IJJBqWcABBBBCmU">> = iolist_to_binary(re:replace("ABBBBC","ab*?bc","I\\1JJB\\1\\1qWc&mU",[caseless])),
+?line <<"IJJBqWcABBBBCmU">> = iolist_to_binary(re:replace("ABBBBC","ab*?bc","I\\1JJB\\1\\1qWc&mU",[caseless,
+ global])),
+?line <<"YOCTcABBBBCABBBBCbVDCpABBBBChP">> = iolist_to_binary(re:replace("ABBBBC","ab{0,}?bc","YOCTc&&bVDCp&hP",[caseless])),
+?line <<"YOCTcABBBBCABBBBCbVDCpABBBBChP">> = iolist_to_binary(re:replace("ABBBBC","ab{0,}?bc","YOCTc&&bVDCp&hP",[caseless,
+ global])),
+?line <<"j">> = iolist_to_binary(re:replace("ABBC","ab+?bc","j",[caseless])),
+?line <<"j">> = iolist_to_binary(re:replace("ABBC","ab+?bc","j",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab+bc","vLfo&Q\\1&uXHE",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab+bc","vLfo&Q\\1&uXHE",[caseless,
+ global])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","ab+bc","hPVKmARvLrX&l",[caseless])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","ab+bc","hPVKmARvLrX&l",[caseless,
+ global])),
+?line <<"ABQ">> = iolist_to_binary(re:replace("ABQ","ab+bc","&\\1OP&EAg",[caseless])),
+?line <<"ABQ">> = iolist_to_binary(re:replace("ABQ","ab+bc","&\\1OP&EAg",[caseless,
+ global])),
+?line <<"PElq">> = iolist_to_binary(re:replace("ABBBBC","ab+bc","PElq",[caseless])),
+?line <<"PElq">> = iolist_to_binary(re:replace("ABBBBC","ab+bc","PElq",[caseless,
+ global])),
+?line <<"aqDjyRpvS">> = iolist_to_binary(re:replace("ABBBBC","ab{1,}?bc","\\1a\\1\\1q\\1DjyRpvS\\1",[caseless])),
+?line <<"aqDjyRpvS">> = iolist_to_binary(re:replace("ABBBBC","ab{1,}?bc","\\1a\\1\\1q\\1DjyRpvS\\1",[caseless,
+ global])),
+?line <<"aTWqxABBBBCInABBBBCpRFpO">> = iolist_to_binary(re:replace("ABBBBC","ab{1,3}?bc","aTW\\1qx&In&\\1pRFpO",[caseless])),
+?line <<"aTWqxABBBBCInABBBBCpRFpO">> = iolist_to_binary(re:replace("ABBBBC","ab{1,3}?bc","aTW\\1qx&In&\\1pRFpO",[caseless,
+ global])),
+?line <<"ABBBBCTlrABBBBCJOlJvqwgABBBBCh">> = iolist_to_binary(re:replace("ABBBBC","ab{3,4}?bc","&T\\1lr&JOl\\1Jvqwg&h",[caseless])),
+?line <<"ABBBBCTlrABBBBCJOlJvqwgABBBBCh">> = iolist_to_binary(re:replace("ABBBBC","ab{3,4}?bc","&T\\1lr&JOl\\1Jvqwg&h",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab{4,5}?bc","QBVMd\\1eBP&j\\1Y\\1\\1",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","ab{4,5}?bc","QBVMd\\1eBP&j\\1Y\\1\\1",[caseless,
+ global])),
+?line <<"ABQ">> = iolist_to_binary(re:replace("ABQ","ab{4,5}?bc","pvlNJ\\1o\\1yAcNJhSec\\1",[caseless])),
+?line <<"ABQ">> = iolist_to_binary(re:replace("ABQ","ab{4,5}?bc","pvlNJ\\1o\\1yAcNJhSec\\1",[caseless,
+ global])),
+?line <<"ABBBBC">> = iolist_to_binary(re:replace("ABBBBC","ab{4,5}?bc","dGdgiMqW\\1X",[caseless])),
+?line <<"ABBBBC">> = iolist_to_binary(re:replace("ABBBBC","ab{4,5}?bc","dGdgiMqW\\1X",[caseless,
+ global])),
+?line <<"eqlxxIffIhfLhL">> = iolist_to_binary(re:replace("ABBC","ab??bc","eqlxxIffIhfLhL",[caseless])),
+?line <<"eqlxxIffIhfLhL">> = iolist_to_binary(re:replace("ABBC","ab??bc","eqlxxIffIhfLhL",[caseless,
+ global])),
+?line <<"bQqNAjyKOqhMXyoABC">> = iolist_to_binary(re:replace("ABC","ab??bc","b\\1QqNAjyKOqhMXyo&",[caseless])),
+?line <<"bQqNAjyKOqhMXyoABC">> = iolist_to_binary(re:replace("ABC","ab??bc","b\\1QqNAjyKOqhMXyo&",[caseless,
+ global])),
+?line <<"jJQGABCIRM">> = iolist_to_binary(re:replace("ABC","ab{0,1}?bc","jJQG&IR\\1M",[caseless])),
+?line <<"jJQGABCIRM">> = iolist_to_binary(re:replace("ABC","ab{0,1}?bc","jJQG&IR\\1M",[caseless,
+ global])),
+?line <<"gPABCCho">> = iolist_to_binary(re:replace("ABC","ab??c","gP&Cho",[caseless])),
+?line <<"gPABCCho">> = iolist_to_binary(re:replace("ABC","ab??c","gP&Cho",[caseless,
+ global])),
+?line <<"iNBxUabjwtTABCLABCvXhABC">> = iolist_to_binary(re:replace("ABC","ab{0,1}?c","iNBxUabjw\\1tT&L&vXh&",[caseless])),
+?line <<"iNBxUabjwtTABCLABCvXhABC">> = iolist_to_binary(re:replace("ABC","ab{0,1}?c","iNBxUabjw\\1tT&L&vXh&",[caseless,
+ global])),
+?line <<"iBkpxqXNa">> = iolist_to_binary(re:replace("ABC","^abc$","iBkpxqXNa",[caseless])),
+?line <<"iBkpxqXNa">> = iolist_to_binary(re:replace("ABC","^abc$","iBkpxqXNa",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^abc$","OhTwUeW&yJtn",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^abc$","OhTwUeW&yJtn",[caseless,
+ global])),
+?line <<"ABBBBC">> = iolist_to_binary(re:replace("ABBBBC","^abc$","LoawT&xKl\\1&",[caseless])),
+?line <<"ABBBBC">> = iolist_to_binary(re:replace("ABBBBC","^abc$","LoawT&xKl\\1&",[caseless,
+ global])),
+?line <<"ABCC">> = iolist_to_binary(re:replace("ABCC","^abc$","iVcopu",[caseless])),
+?line <<"ABCC">> = iolist_to_binary(re:replace("ABCC","^abc$","iVcopu",[caseless,
+ global])),
+?line <<"ABCtC">> = iolist_to_binary(re:replace("ABCC","^abc","&t",[caseless])),
+?line <<"ABCtC">> = iolist_to_binary(re:replace("ABCC","^abc","&t",[caseless,
+ global])),
+?line <<"AABCWBABCtLipJGm">> = iolist_to_binary(re:replace("AABC","abc$","&W\\1B&tLipJGm",[caseless])),
+?line <<"AABCWBABCtLipJGm">> = iolist_to_binary(re:replace("AABC","abc$","&W\\1B&tLipJGm",[caseless,
+ global])),
+?line <<"wDvJtREmkCGdgtGtgABC">> = iolist_to_binary(re:replace("ABC","^","wDvJt\\1REmk\\1CGdgtGtg",[caseless])),
+?line <<"wDvJtREmkCGdgtGtgABC">> = iolist_to_binary(re:replace("ABC","^","wDvJt\\1REmk\\1CGdgtGtg",[caseless,
+ global])),
+?line <<"ABCWiqpRnpqRRBAD">> = iolist_to_binary(re:replace("ABC","$","W\\1iqpRnpq\\1RRB&\\1AD",[caseless])),
+?line <<"ABCWiqpRnpqRRBAD">> = iolist_to_binary(re:replace("ABC","$","W\\1iqpRnpq\\1RRB&\\1AD",[caseless,
+ global])),
+?line <<"FOABCHABCuJ">> = iolist_to_binary(re:replace("ABC","a.c","FO&H&uJ",[caseless])),
+?line <<"FOABCHABCuJ">> = iolist_to_binary(re:replace("ABC","a.c","FO&H&uJ",[caseless,
+ global])),
+?line <<"mmAXCwHQUmyij">> = iolist_to_binary(re:replace("AXC","a.c","m\\1m&\\1wHQUmyi\\1j",[caseless])),
+?line <<"mmAXCwHQUmyij">> = iolist_to_binary(re:replace("AXC","a.c","m\\1m&\\1wHQUmyi\\1j",[caseless,
+ global])),
+?line <<"vKLhT">> = iolist_to_binary(re:replace("AXYZC","a.*?c","vKLhT",[caseless])),
+?line <<"vKLhT">> = iolist_to_binary(re:replace("AXYZC","a.*?c","vKLhT",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a.*c","TrBPBlhlCyS&oLwXCYxT",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a.*c","TrBPBlhlCyS&oLwXCYxT",[caseless,
+ global])),
+?line <<"oNBAABC">> = iolist_to_binary(re:replace("AABC","a.*c","oNB&",[caseless])),
+?line <<"oNBAABC">> = iolist_to_binary(re:replace("AABC","a.*c","oNB&",[caseless,
+ global])),
+?line <<"AXYZD">> = iolist_to_binary(re:replace("AXYZD","a.*c","v\\1byeTHd&vaDRL\\1",[caseless])),
+?line <<"AXYZD">> = iolist_to_binary(re:replace("AXYZD","a.*c","v\\1byeTHd&vaDRL\\1",[caseless,
+ global])),
+?line <<"puCobgfLFWkTABD">> = iolist_to_binary(re:replace("ABD","a[bc]d","puCobgf\\1LF\\1W\\1kT&",[caseless])),
+?line <<"puCobgfLFWkTABD">> = iolist_to_binary(re:replace("ABD","a[bc]d","puCobgf\\1LF\\1W\\1kT&",[caseless,
+ global])),
+?line <<"ACELM">> = iolist_to_binary(re:replace("ACE","a[b-d]e","&LM",[caseless])),
+?line <<"ACELM">> = iolist_to_binary(re:replace("ACE","a[b-d]e","&LM",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[b-d]e","YsqxG&B&NxQkv\\1RY",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[b-d]e","YsqxG&B&NxQkv\\1RY",[caseless,
+ global])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","a[b-d]e","o&WeiJHAt\\1vpj",[caseless])),
+?line <<"ABC">> = iolist_to_binary(re:replace("ABC","a[b-d]e","o&WeiJHAt\\1vpj",[caseless,
+ global])),
+?line <<"ABD">> = iolist_to_binary(re:replace("ABD","a[b-d]e","S",[caseless])),
+?line <<"ABD">> = iolist_to_binary(re:replace("ABD","a[b-d]e","S",[caseless,
+ global])),
+?line <<"AgdndBWDnHACACACVgB">> = iolist_to_binary(re:replace("AAC","a[b-d]","gdndBWDnH&&&VgB",[caseless])),
+?line <<"AgdndBWDnHACACACVgB">> = iolist_to_binary(re:replace("AAC","a[b-d]","gdndBWDnH&&&VgB",[caseless,
+ global])),
+?line <<"lpIqxA-sQA-">> = iolist_to_binary(re:replace("A-","a[-b]","lpIqx&sQ&",[caseless])),
+?line <<"lpIqxA-sQA-">> = iolist_to_binary(re:replace("A-","a[-b]","lpIqx&sQ&",[caseless,
+ global])),
+?line <<"NJrfcO">> = iolist_to_binary(re:replace("A-","a[b-]","NJrfc\\1O",[caseless])),
+?line <<"NJrfcO">> = iolist_to_binary(re:replace("A-","a[b-]","NJrfc\\1O",[caseless,
+ global])),
+?line <<"eA]OcbkaA]qQEbtfTQD">> = iolist_to_binary(re:replace("A]","a]","e&Oc\\1bka&qQEbtfTQD",[caseless])),
+?line <<"eA]OcbkaA]qQEbtfTQD">> = iolist_to_binary(re:replace("A]","a]","e&Oc\\1bka&qQEbtfTQD",[caseless,
+ global])),
+?line <<"GqJU">> = iolist_to_binary(re:replace("A]B","a[]]b","GqJU",[caseless])),
+?line <<"GqJU">> = iolist_to_binary(re:replace("A]B","a[]]b","GqJU",[caseless,
+ global])),
+?line <<"AEDdAEDiIAEDCTAEDj">> = iolist_to_binary(re:replace("AED","a[^bc]d","&d&iI&CT&j",[caseless])),
+?line <<"AEDdAEDiIAEDCTAEDj">> = iolist_to_binary(re:replace("AED","a[^bc]d","&d&iI&CT&j",[caseless,
+ global])),
+?line <<"MOKnvQDsS">> = iolist_to_binary(re:replace("ADC","a[^-b]c","MOKn\\1\\1vQDsS\\1",[caseless])),
+?line <<"MOKnvQDsS">> = iolist_to_binary(re:replace("ADC","a[^-b]c","MOKn\\1\\1vQDsS\\1",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[^-b]c","dr",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a[^-b]c","dr",[caseless,
+ global])),
+?line <<"ABD">> = iolist_to_binary(re:replace("ABD","a[^-b]c","IMKcT&fF&WXbjs\\1\\1",[caseless])),
+?line <<"ABD">> = iolist_to_binary(re:replace("ABD","a[^-b]c","IMKcT&fF&WXbjs\\1\\1",[caseless,
+ global])),
+?line <<"A-C">> = iolist_to_binary(re:replace("A-C","a[^-b]c","M&xDIfNL\\1W",[caseless])),
+?line <<"A-C">> = iolist_to_binary(re:replace("A-C","a[^-b]c","M&xDIfNL\\1W",[caseless,
+ global])),
+?line <<"wR">> = iolist_to_binary(re:replace("ADC","a[^]b]c","wR",[caseless])),
+?line <<"wR">> = iolist_to_binary(re:replace("ADC","a[^]b]c","wR",[caseless,
+ global])),
+?line <<"HKTAABllCQjRABEnXDqjC">> = iolist_to_binary(re:replace("ABC","ab|cd","HKTA&llCQjR&EnXDqj",[caseless])),
+?line <<"HKTAABllCQjRABEnXDqjC">> = iolist_to_binary(re:replace("ABC","ab|cd","HKTA&llCQjR&EnXDqj",[caseless,
+ global])),
+?line <<"ABCD">> = iolist_to_binary(re:replace("ABCD","ab|cd","&",[caseless])),
+?line <<"ABCD">> = iolist_to_binary(re:replace("ABCD","ab|cd","&",[caseless,
+ global])),
+?line <<"DmgTYsxtpkrXgnoJ">> = iolist_to_binary(re:replace("DEF","()ef","mgTYsxtpkrXgnoJ",[caseless])),
+?line <<"DmgTYsxtpkrXgnoJ">> = iolist_to_binary(re:replace("DEF","()ef","mgTYsxtpkrXgnoJ",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","$b","KN\\1&V",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","$b","KN\\1&V",[caseless,
+ global])),
+?line <<"A]C">> = iolist_to_binary(re:replace("A]C","$b","jtB&",[caseless])),
+?line <<"A]C">> = iolist_to_binary(re:replace("A]C","$b","jtB&",[caseless,
+ global])),
+?line <<"B">> = iolist_to_binary(re:replace("B","$b","&pRb",[caseless])),
+?line <<"B">> = iolist_to_binary(re:replace("B","$b","&pRb",[caseless,
+ global])),
+?line <<"lBUQxBtA(BtBeKAEeJq">> = iolist_to_binary(re:replace("A(B","a\\(b","lBUQ\\1xBt&tBeK\\1AEeJq",[caseless])),
+?line <<"lBUQxBtA(BtBeKAEeJq">> = iolist_to_binary(re:replace("A(B","a\\(b","lBUQ\\1xBt&tBeK\\1AEeJq",[caseless,
+ global])),
+?line <<"oXCRABKrhGnMTcHtq">> = iolist_to_binary(re:replace("AB","a\\(*b","oXCR&\\1KrhGnMTcHtq",[caseless])),
+?line <<"oXCRABKrhGnMTcHtq">> = iolist_to_binary(re:replace("AB","a\\(*b","oXCR&\\1KrhGnMTcHtq",[caseless,
+ global])),
+?line <<"VyDjHkfygiNMHC">> = iolist_to_binary(re:replace("A((B","a\\(*b","Vy\\1DjH\\1kfygi\\1NM\\1H\\1C",[caseless])),
+?line <<"VyDjHkfygiNMHC">> = iolist_to_binary(re:replace("A((B","a\\(*b","Vy\\1DjH\\1kfygi\\1NM\\1H\\1C",[caseless,
+ global])),
+?line <<"A">> = iolist_to_binary(re:replace("A","a\\\\b","DhmiARyCBuVi",[caseless,
+ notbol])),
+?line <<"A">> = iolist_to_binary(re:replace("A","a\\\\b","DhmiARyCBuVi",[caseless,
+ notbol,
+ global])),
+?line <<"FArKeEijRRtjoEBC">> = iolist_to_binary(re:replace("ABC","((a))","F\\1rKeEijRRtjoE",[caseless])),
+?line <<"FArKeEijRRtjoEBC">> = iolist_to_binary(re:replace("ABC","((a))","F\\1rKeEijRRtjoE",[caseless,
+ global])),
+?line <<"shGABCXUFAQfAABCy">> = iolist_to_binary(re:replace("ABC","(a)b(c)","shG&XUF\\1Qf\\1&y",[caseless])),
+?line <<"shGABCXUFAQfAABCy">> = iolist_to_binary(re:replace("ABC","(a)b(c)","shG&XUF\\1Qf\\1&y",[caseless,
+ global])),
+?line <<"AABBABCmqnIoEABCugfABCNVyK">> = iolist_to_binary(re:replace("AABBABC","a+b+c","&m\\1qnIoE&\\1ug\\1f&NVyK",[caseless])),
+?line <<"AABBABCmqnIoEABCugfABCNVyK">> = iolist_to_binary(re:replace("AABBABC","a+b+c","&m\\1qnIoE&\\1ug\\1f&NVyK",[caseless,
+ global])),
+?line <<"AABByABCrABC">> = iolist_to_binary(re:replace("AABBABC","a{1,}b{1,}c","y&r\\1&",[caseless])),
+?line <<"AABByABCrABC">> = iolist_to_binary(re:replace("AABBABC","a{1,}b{1,}c","y&r\\1&",[caseless,
+ global])),
+?line <<"tABC">> = iolist_to_binary(re:replace("ABCABC","a.+?c","t",[caseless])),
+?line <<"tt">> = iolist_to_binary(re:replace("ABCABC","a.+?c","t",[caseless,
+ global])),
+?line <<"UABC">> = iolist_to_binary(re:replace("ABCABC","a.*?c","\\1U",[caseless])),
+?line <<"UU">> = iolist_to_binary(re:replace("ABCABC","a.*?c","\\1U",[caseless,
+ global])),
+?line <<"EfjDOiBAosuABC">> = iolist_to_binary(re:replace("ABCABC","a.{0,5}?c","EfjDOiBAosu",[caseless])),
+?line <<"EfjDOiBAosuEfjDOiBAosu">> = iolist_to_binary(re:replace("ABCABC","a.{0,5}?c","EfjDOiBAosu",[caseless,
+ global])),
+?line <<"CBBliVcPcv">> = iolist_to_binary(re:replace("AB","(a+|b)*","C\\1\\1liVcPcv",[caseless])),
+?line <<"CBBliVcPcvCliVcPcv">> = iolist_to_binary(re:replace("AB","(a+|b)*","C\\1\\1liVcPcv",[caseless,
+ global])),
+?line <<"fSc">> = iolist_to_binary(re:replace("AB","(a+|b){0,}","fSc",[caseless])),
+?line <<"fScfSc">> = iolist_to_binary(re:replace("AB","(a+|b){0,}","fSc",[caseless,
+ global])),
+?line <<"sABpDnVVBAB">> = iolist_to_binary(re:replace("AB","(a+|b)+","s&pDnVV\\1&",[caseless])),
+?line <<"sABpDnVVBAB">> = iolist_to_binary(re:replace("AB","(a+|b)+","s&pDnVV\\1&",[caseless,
+ global])),
+?line <<"bnGKKf">> = iolist_to_binary(re:replace("AB","(a+|b){1,}","bnGKKf",[caseless])),
+?line <<"bnGKKf">> = iolist_to_binary(re:replace("AB","(a+|b){1,}","bnGKKf",[caseless,
+ global])),
+?line <<"LpFAjhAtClIGSIAdYAB">> = iolist_to_binary(re:replace("AB","(a+|b)?","LpF&jhAtClIGSI\\1dY\\1",[caseless])),
+?line <<"LpFAjhAtClIGSIAdYALpFBjhAtClIGSIBdYBLpFjhAtClIGSIdY">> = iolist_to_binary(re:replace("AB","(a+|b)?","LpF&jhAtClIGSI\\1dY\\1",[caseless,
+ global])),
+?line <<"qbB">> = iolist_to_binary(re:replace("AB","(a+|b){0,1}","qb",[caseless])),
+?line <<"qbqbqb">> = iolist_to_binary(re:replace("AB","(a+|b){0,1}","qb",[caseless,
+ global])),
+?line <<"vLbrTRIJAB">> = iolist_to_binary(re:replace("AB","(a+|b){0,1}?","v&L\\1brTRIJ\\1\\1",[caseless])),
+?line <<"vLbrTRIJvALAbrTRIJAAvLbrTRIJvBLBbrTRIJBBvLbrTRIJ">> = iolist_to_binary(re:replace("AB","(a+|b){0,1}?","v&L\\1brTRIJ\\1\\1",[caseless,
+ global])),
+?line <<"HtbhHKCDEwOT">> = iolist_to_binary(re:replace("CDE","[^ab]*","Ht\\1\\1bhHK&wOT",[caseless])),
+?line <<"HtbhHKCDEwOTHtbhHKwOT">> = iolist_to_binary(re:replace("CDE","[^ab]*","Ht\\1\\1bhHK&wOT",[caseless,
+ global])),
+?line <<"vOowROtABBBCD">> = iolist_to_binary(re:replace("ABBBCD","([abc])*d","vOowROt&",[caseless])),
+?line <<"vOowROtABBBCD">> = iolist_to_binary(re:replace("ABBBCD","([abc])*d","vOowROt&",[caseless,
+ global])),
+?line <<"TBIuDTLoAqOaABCDMcvVABCDPm">> = iolist_to_binary(re:replace("ABCD","([abc])*bcd","TBIuDTLo\\1qOa&McvV&Pm",[caseless])),
+?line <<"TBIuDTLoAqOaABCDMcvVABCDPm">> = iolist_to_binary(re:replace("ABCD","([abc])*bcd","TBIuDTLo\\1qOa&McvV&Pm",[caseless,
+ global])),
+?line <<"EUpqELAv">> = iolist_to_binary(re:replace("E","a|b|c|d|e","&Upq&LAv",[caseless])),
+?line <<"EUpqELAv">> = iolist_to_binary(re:replace("E","a|b|c|d|e","&Upq&LAv",[caseless,
+ global])),
+?line <<"oViRJMyEkEPtcEFEM">> = iolist_to_binary(re:replace("EF","(a|b|c|d|e)f","oViRJMy\\1k\\1Ptc&\\1M",[caseless])),
+?line <<"oViRJMyEkEPtcEFEM">> = iolist_to_binary(re:replace("EF","(a|b|c|d|e)f","oViRJMy\\1k\\1Ptc&\\1M",[caseless,
+ global])),
+?line <<"ABCDEFGeumABCDEFGxRcjHuSABCDEFGOABCDEFG">> = iolist_to_binary(re:replace("ABCDEFG","abcd*efg","&eum&xRcjHuS&O&",[caseless])),
+?line <<"ABCDEFGeumABCDEFGxRcjHuSABCDEFGOABCDEFG">> = iolist_to_binary(re:replace("ABCDEFG","abcd*efg","&eum&xRcjHuS&O&",[caseless,
+ global])),
+?line <<"XOvpxKbYuFMwABVhYABBBZ">> = iolist_to_binary(re:replace("XABYABBBZ","ab*","OvpxKbYuFMw&Vh",[caseless])),
+?line <<"XOvpxKbYuFMwABVhYOvpxKbYuFMwABBBVhZ">> = iolist_to_binary(re:replace("XABYABBBZ","ab*","OvpxKbYuFMw&Vh",[caseless,
+ global])),
+?line <<"XXbAAHhcmmXVwkYABBBZ">> = iolist_to_binary(re:replace("XAYABBBZ","ab*","Xb&&Hhcmm\\1XVwk",[caseless])),
+?line <<"XXbAAHhcmmXVwkYXbABBBABBBHhcmmXVwkZ">> = iolist_to_binary(re:replace("XAYABBBZ","ab*","Xb&&Hhcmm\\1XVwk",[caseless,
+ global])),
+?line <<"ABPWMPmhUsCDEo">> = iolist_to_binary(re:replace("ABCDE","(ab|cd)e","PWMPmhUs&o",[caseless])),
+?line <<"ABPWMPmhUsCDEo">> = iolist_to_binary(re:replace("ABCDE","(ab|cd)e","PWMPmhUs&o",[caseless,
+ global])),
+?line <<"jHIJ">> = iolist_to_binary(re:replace("HIJ","[abhgefdc]ij","j&",[caseless])),
+?line <<"jHIJ">> = iolist_to_binary(re:replace("HIJ","[abhgefdc]ij","j&",[caseless,
+ global])),
+?line <<"ABCDE">> = iolist_to_binary(re:replace("ABCDE","^(ab|cd)e","Dc&hlOsc\\1EL\\1Vl",[caseless])),
+?line <<"ABCDE">> = iolist_to_binary(re:replace("ABCDE","^(ab|cd)e","Dc&hlOsc\\1EL\\1Vl",[caseless,
+ global])),
+?line <<"ABCDBRPmLBtJGwEFEF">> = iolist_to_binary(re:replace("ABCDEF","(abc|)ef","\\1BRPmLBtJGw\\1&&",[caseless])),
+?line <<"ABCDBRPmLBtJGwEFEF">> = iolist_to_binary(re:replace("ABCDEF","(abc|)ef","\\1BRPmLBtJGw\\1&&",[caseless,
+ global])),
+?line <<"ABdnvuIvc">> = iolist_to_binary(re:replace("ABCD","(a|b)c*d","\\1dnvuIvc",[caseless])),
+?line <<"ABdnvuIvc">> = iolist_to_binary(re:replace("ABCD","(a|b)c*d","\\1dnvuIvc",[caseless,
+ global])),
+?line <<"ACKWkV">> = iolist_to_binary(re:replace("ABC","(ab|ab*)bc","ACKWkV",[caseless])),
+?line <<"ACKWkV">> = iolist_to_binary(re:replace("ABC","(ab|ab*)bc","ACKWkV",[caseless,
+ global])),
+?line <<"LepuBCXf">> = iolist_to_binary(re:replace("ABC","a([bc]*)c*","Lepu\\1Xf",[caseless])),
+?line <<"LepuBCXf">> = iolist_to_binary(re:replace("ABC","a([bc]*)c*","Lepu\\1Xf",[caseless,
+ global])),
+?line <<"NDABCDxHBMEtfBGtV">> = iolist_to_binary(re:replace("ABCD","a([bc]*)(c*d)","ND&xHBMEtfBGtV",[caseless])),
+?line <<"NDABCDxHBMEtfBGtV">> = iolist_to_binary(re:replace("ABCD","a([bc]*)(c*d)","ND&xHBMEtfBGtV",[caseless,
+ global])),
+?line <<"eIDCKdi">> = iolist_to_binary(re:replace("ABCD","a([bc]+)(c*d)","eIDCKdi",[caseless])),
+?line <<"eIDCKdi">> = iolist_to_binary(re:replace("ABCD","a([bc]+)(c*d)","eIDCKdi",[caseless,
+ global])),
+?line <<"fKBQepABCDABCDhHYaRKHjS">> = iolist_to_binary(re:replace("ABCD","a([bc]*)(c+d)","fK\\1Qep&&hHYaRKHjS",[caseless])),
+?line <<"fKBQepABCDABCDhHYaRKHjS">> = iolist_to_binary(re:replace("ABCD","a([bc]*)(c+d)","fK\\1Qep&&hHYaRKHjS",[caseless,
+ global])),
+?line <<"yYx">> = iolist_to_binary(re:replace("ADCDCDE","a[bcd]*dcdcde","yYx",[caseless])),
+?line <<"yYx">> = iolist_to_binary(re:replace("ADCDCDE","a[bcd]*dcdcde","yYx",[caseless,
+ global])),
+?line <<"gCGlfM">> = iolist_to_binary(re:replace("ABC","(ab|a)b*c","gCGlfM",[caseless])),
+?line <<"gCGlfM">> = iolist_to_binary(re:replace("ABC","(ab|a)b*c","gCGlfM",[caseless,
+ global])),
+?line <<"GJGaABCEwhSGeABC">> = iolist_to_binary(re:replace("ABCD","((a)(b)c)(d)","GJGa\\1EwhSGe\\1",[caseless])),
+?line <<"GJGaABCEwhSGeABC">> = iolist_to_binary(re:replace("ABCD","((a)(b)c)(d)","GJGa\\1EwhSGe\\1",[caseless,
+ global])),
+?line <<"GwdBxNJCuOfALPHADSnt">> = iolist_to_binary(re:replace("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*","GwdBxNJCuOf&DSnt",[caseless])),
+?line <<"GwdBxNJCuOfALPHADSnt">> = iolist_to_binary(re:replace("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*","GwdBxNJCuOf&DSnt",[caseless,
+ global])),
+?line <<"AMcmNBHqwYsXdABHJTBH">> = iolist_to_binary(re:replace("ABH","^a(bc+|b[eh])g|.h$","McmN&qwYsX\\1dA&JT&",[caseless])),
+?line <<"AMcmNBHqwYsXdABHJTBH">> = iolist_to_binary(re:replace("ABH","^a(bc+|b[eh])g|.h$","McmN&qwYsX\\1dA&JT&",[caseless,
+ global])),
+?line <<"JEFFGZWDujiGKchTEFFGZd">> = iolist_to_binary(re:replace("EFFGZ","(bc+d$|ef*g.|h?i(j|k))","J&WDujiGKchT&d",[caseless])),
+?line <<"JEFFGZWDujiGKchTEFFGZd">> = iolist_to_binary(re:replace("EFFGZ","(bc+d$|ef*g.|h?i(j|k))","J&WDujiGKchT&d",[caseless,
+ global])),
+?line <<"JIJIJVbIJOWIJT">> = iolist_to_binary(re:replace("IJ","(bc+d$|ef*g.|h?i(j|k))","J\\1\\1Vb\\1OW\\1T",[caseless])),
+?line <<"JIJIJVbIJOWIJT">> = iolist_to_binary(re:replace("IJ","(bc+d$|ef*g.|h?i(j|k))","J\\1\\1Vb\\1OW\\1T",[caseless,
+ global])),
+?line <<"REnKX">> = iolist_to_binary(re:replace("REFFGZ","(bc+d$|ef*g.|h?i(j|k))","EnKX",[caseless])),
+?line <<"REnKX">> = iolist_to_binary(re:replace("REFFGZ","(bc+d$|ef*g.|h?i(j|k))","EnKX",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(bc+d$|ef*g.|h?i(j|k))","&bQK&gYPqvKo\\1Dxq&&&",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(bc+d$|ef*g.|h?i(j|k))","&bQK&gYPqvKo\\1Dxq&&&",[caseless,
+ global])),
+?line <<"ADCDCDE">> = iolist_to_binary(re:replace("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))","\\1D&uYGYYB\\1mXY",[caseless])),
+?line <<"ADCDCDE">> = iolist_to_binary(re:replace("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))","\\1D&uYGYYB\\1mXY",[caseless,
+ global])),
+?line <<"EFFG">> = iolist_to_binary(re:replace("EFFG","(bc+d$|ef*g.|h?i(j|k))","bqLaCGT&\\1\\1StNeJTj",[caseless])),
+?line <<"EFFG">> = iolist_to_binary(re:replace("EFFG","(bc+d$|ef*g.|h?i(j|k))","bqLaCGT&\\1\\1StNeJTj",[caseless,
+ global])),
+?line <<"BCDD">> = iolist_to_binary(re:replace("BCDD","(bc+d$|ef*g.|h?i(j|k))","M\\1v\\1T\\1H&OCM\\1",[caseless])),
+?line <<"BCDD">> = iolist_to_binary(re:replace("BCDD","(bc+d$|ef*g.|h?i(j|k))","M\\1v\\1T\\1H&OCM\\1",[caseless,
+ global])),
+?line <<"teSxBAbRcV">> = iolist_to_binary(re:replace("A","((((((((((a))))))))))","teSxB\\1bRcV",[caseless])),
+?line <<"teSxBAbRcV">> = iolist_to_binary(re:replace("A","((((((((((a))))))))))","teSxB\\1bRcV",[caseless,
+ global])),
+?line <<"AAAAA">> = iolist_to_binary(re:replace("AA","((((((((((a))))))))))\\10","&&\\1",[caseless])),
+?line <<"AAAAA">> = iolist_to_binary(re:replace("AA","((((((((((a))))))))))\\10","&&\\1",[caseless,
+ global])),
+?line <<"AAPBpi">> = iolist_to_binary(re:replace("A","(((((((((a)))))))))","&\\1PBpi",[caseless])),
+?line <<"AAPBpi">> = iolist_to_binary(re:replace("A","(((((((((a)))))))))","&\\1PBpi",[caseless,
+ global])),
+?line <<"htagTvaaMIaAkgtdgA">> = iolist_to_binary(re:replace("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))","htagTvaaMIaAkgtdg&",[caseless])),
+?line <<"htagTvaaMIaAkgtdgA">> = iolist_to_binary(re:replace("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))","htagTvaaMIaAkgtdg&",[caseless,
+ global])),
+?line <<"iVCeGCCLesCClCCD">> = iolist_to_binary(re:replace("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))","iV&eG\\1CLesC&l&\\1D",[caseless])),
+?line <<"iVCeGCCLesCClCCD">> = iolist_to_binary(re:replace("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))","iV&eG\\1CLesC&l&\\1D",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","multiple words of text","hKB&NgvvVpXbuP",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","multiple words of text","hKB&NgvvVpXbuP",[caseless,
+ global])),
+?line <<"AA">> = iolist_to_binary(re:replace("AA","multiple words of text","V&\\1e&n\\1",[caseless])),
+?line <<"AA">> = iolist_to_binary(re:replace("AA","multiple words of text","V&\\1e&n\\1",[caseless,
+ global])),
+?line <<"UH-UH">> = iolist_to_binary(re:replace("UH-UH","multiple words of text","w\\1JcoWUQlAryay\\1",[caseless])),
+?line <<"UH-UH">> = iolist_to_binary(re:replace("UH-UH","multiple words of text","w\\1JcoWUQlAryay\\1",[caseless,
+ global])),
+?line <<"Ee, YEAH">> = iolist_to_binary(re:replace("MULTIPLE WORDS, YEAH","multiple words","Ee",[caseless])),
+?line <<"Ee, YEAH">> = iolist_to_binary(re:replace("MULTIPLE WORDS, YEAH","multiple words","Ee",[caseless,
+ global])),
+?line <<"uTrQvyABCDEABCDEACeABCDE">> = iolist_to_binary(re:replace("ABCDE","(.*)c(.*)","uTrQvy&&ACe&",[caseless])),
+?line <<"uTrQvyABCDEABCDEACeABCDE">> = iolist_to_binary(re:replace("ABCDE","(.*)c(.*)","uTrQvy&&ACe&",[caseless,
+ global])),
+?line <<"ba(A, B)owqjAHEJ(A, B)qP">> = iolist_to_binary(re:replace("(A, B)","\\((.*), (.*)\\)","ba&owqj\\1HEJ&qP",[caseless])),
+?line <<"ba(A, B)owqjAHEJ(A, B)qP">> = iolist_to_binary(re:replace("(A, B)","\\((.*), (.*)\\)","ba&owqj\\1HEJ&qP",[caseless,
+ global])),
+?line <<"ABCDRIpLJwyEwDArCpanW">> = iolist_to_binary(re:replace("ABCD","abcd","&RIpLJwyEwDA\\1rCpan\\1W",[caseless])),
+?line <<"ABCDRIpLJwyEwDArCpanW">> = iolist_to_binary(re:replace("ABCD","abcd","&RIpLJwyEwDA\\1rCpan\\1W",[caseless,
+ global])),
+?line <<"xykYBC">> = iolist_to_binary(re:replace("ABCD","a(bc)d","xykY\\1",[caseless])),
+?line <<"xykYBC">> = iolist_to_binary(re:replace("ABCD","a(bc)d","xykY\\1",[caseless,
+ global])),
+?line <<"UMfPSTJEqdeS">> = iolist_to_binary(re:replace("AC","a[-]?c","U\\1M\\1\\1fPSTJEqdeS",[caseless])),
+?line <<"UMfPSTJEqdeS">> = iolist_to_binary(re:replace("AC","a[-]?c","U\\1M\\1\\1fPSTJEqdeS",[caseless,
+ global])),
+?line <<"ITABCABCABCnxfDlABCpYAXQvxABCABCE">> = iolist_to_binary(re:replace("ABCABC","(abc)\\1","IT\\1&nxfDl\\1pYAXQvx\\1\\1E",[caseless])),
+?line <<"ITABCABCABCnxfDlABCpYAXQvxABCABCE">> = iolist_to_binary(re:replace("ABCABC","(abc)\\1","IT\\1&nxfDl\\1pYAXQvx\\1\\1E",[caseless,
+ global])),
+?line <<"JXRRQqcKbpvOgISABCT">> = iolist_to_binary(re:replace("ABCABC","([a-c]*)\\1","JXRRQqcKbpvOgIS\\1T",[caseless])),
+?line <<"JXRRQqcKbpvOgISABCTJXRRQqcKbpvOgIST">> = iolist_to_binary(re:replace("ABCABC","([a-c]*)\\1","JXRRQqcKbpvOgIS\\1T",[caseless,
+ global])),
+?line <<"abxuiiVt">> = iolist_to_binary(re:replace("abad","a(?!b).","xuiiVt",[])),
+?line <<"abxuiiVt">> = iolist_to_binary(re:replace("abad","a(?!b).","xuiiVt",[global])),
+?line <<"abMdtNqPOC">> = iolist_to_binary(re:replace("abad","a(?=d).","MdtNqPOC",[])),
+?line <<"abMdtNqPOC">> = iolist_to_binary(re:replace("abad","a(?=d).","MdtNqPOC",[global])),
+?line <<"abmkrHu">> = iolist_to_binary(re:replace("abad","a(?=c|d).","mkrHu",[])),
+?line <<"abmkrHu">> = iolist_to_binary(re:replace("abad","a(?=c|d).","mkrHu",[global])),
+?line <<"JfimtGueeaceUlTKvht">> = iolist_to_binary(re:replace("ace","a(?:b|c|d)(.)","JfimtGu\\1\\1&UlTKvht",[])),
+?line <<"JfimtGueeaceUlTKvht">> = iolist_to_binary(re:replace("ace","a(?:b|c|d)(.)","JfimtGu\\1\\1&UlTKvht",[global])),
+?line <<"IdKeqIicacekGQCace">> = iolist_to_binary(re:replace("ace","a(?:b|c|d)*(.)","IdK\\1qIic&kGQC&",[])),
+?line <<"IdKeqIicacekGQCace">> = iolist_to_binary(re:replace("ace","a(?:b|c|d)*(.)","IdK\\1qIic&kGQC&",[global])),
+?line <<"ovgdwiKdYGGace">> = iolist_to_binary(re:replace("ace","a(?:b|c|d)+?(.)","ovgdwiKdYGG&",[])),
+?line <<"ovgdwiKdYGGace">> = iolist_to_binary(re:replace("ace","a(?:b|c|d)+?(.)","ovgdwiKdYGG&",[global])),
+?line <<"uJgdlOhWXUJpEBdwSbcdbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d)+?(.)","uJg\\1lOhWXUJpEB\\1wS",[])),
+?line <<"uJgdlOhWXUJpEBdwSbcdbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d)+?(.)","uJg\\1lOhWXUJpEB\\1wS",[global])),
+?line <<"UMpmieMJkQH">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d)+(.)","UMpmi\\1MJkQH",[])),
+?line <<"UMpmieMJkQH">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d)+(.)","UMpmi\\1MJkQH",[global])),
+?line <<"pCjGheRqYfSacdbhlDAvcdbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){2}(.)","pCjGheRqYfS&hlDAv",[])),
+?line <<"pCjGheRqYfSacdbhlDAvcdbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){2}(.)","pCjGheRqYfS&hlDAv",[global])),
+?line <<"yxwYmacdbcdbbAbacdbcdbbGABe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){4,5}(.)","yxwYm&\\1A\\1&bGAB",[])),
+?line <<"yxwYmacdbcdbbAbacdbcdbbGABe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){4,5}(.)","yxwYm&\\1A\\1&bGAB",[global])),
+?line <<"dkoReacdbcdhXacdbcdacdbcdcakNJbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){4,5}?(.)","\\1koRe&hX&&cakNJ",[])),
+?line <<"dkoReacdbcdhXacdbcdacdbcdcakNJbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){4,5}?(.)","\\1koRe&hX&&cakNJ",[global])),
+?line <<"nVjVDfoobarOjfoobarfoobareL">> = iolist_to_binary(re:replace("foobar","((foo)|(bar))*","nVjVD&Oj&&eL",[])),
+?line <<"nVjVDfoobarOjfoobarfoobareLnVjVDOjeL">> = iolist_to_binary(re:replace("foobar","((foo)|(bar))*","nVjVD&Oj&&eL",[global])),
+?line <<"bJacdbcdbeMVacdbcdbeFAiPYieyEAI">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){6,7}(.)","bJ&MV&FAiPYi\\1yEAI",[])),
+?line <<"bJacdbcdbeMVacdbcdbeFAiPYieyEAI">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){6,7}(.)","bJ&MV&FAiPYi\\1yEAI",[global])),
+?line <<"cW">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){6,7}?(.)","cW",[])),
+?line <<"cW">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){6,7}?(.)","cW",[global])),
+?line <<"EnymbYybacdbcdbeQeCacdbcdbeacdbcdbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,6}(.)","EnymbYyb&Q\\1C&&",[])),
+?line <<"EnymbYybacdbcdbeQeCacdbcdbeacdbcdbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,6}(.)","EnymbYyb&Q\\1C&&",[global])),
+?line <<"XCacdbcdbVjuGQacdbcdbBqmsUJe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,6}?(.)","XC&VjuGQ&BqmsUJ",[])),
+?line <<"XCacdbcdbVjuGQacdbcdbBqmsUJe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,6}?(.)","XC&VjuGQ&BqmsUJ",[global])),
+?line <<"OacdbcdbeCSMacdbcdbensheuoDP">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,7}(.)","O&CSM&nsh\\1uoDP",[])),
+?line <<"OacdbcdbeCSMacdbcdbensheuoDP">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,7}(.)","O&CSM&nsh\\1uoDP",[global])),
+?line <<"YReNTbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,7}?(.)","YReNT\\1",[])),
+?line <<"YReNTbe">> = iolist_to_binary(re:replace("acdbcdbe","a(?:b|c|d){5,7}?(.)","YReNT\\1",[global])),
+?line <<"IJoaceaceHEIicnaaceaceacecq">> = iolist_to_binary(re:replace("ace","a(?:b|(c|e){1,2}?|d)+?(.)","IJo&&HEIi\\1na&&&\\1q",[])),
+?line <<"IJoaceaceHEIicnaaceaceacecq">> = iolist_to_binary(re:replace("ace","a(?:b|(c|e){1,2}?|d)+?(.)","IJo&&HEIi\\1na&&&\\1q",[global])),
+?line <<"PqnTibAldgKNACABm">> = iolist_to_binary(re:replace("AB","^(.+)?B","PqnTib\\1ldgKN\\1C&m",[])),
+?line <<"PqnTibAldgKNACABm">> = iolist_to_binary(re:replace("AB","^(.+)?B","PqnTib\\1ldgKN\\1C&m",[global])),
+?line <<"n.toDyHxNwuj.d.n..l">> = iolist_to_binary(re:replace(".","^([^a-z])|(\\^)$","n\\1toDyHxNwuj\\1d&n\\1\\1l",[])),
+?line <<"n.toDyHxNwuj.d.n..l">> = iolist_to_binary(re:replace(".","^([^a-z])|(\\^)$","n\\1toDyHxNwuj\\1d&n\\1\\1l",[global])),
+?line <<"GOUT">> = iolist_to_binary(re:replace("<&OUT","^[<>]&","G",[])),
+?line <<"GOUT">> = iolist_to_binary(re:replace("<&OUT","^[<>]&","G",[global])),
+?line <<"eQPwy">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a\\1?){4}$","eQPwy",[])),
+?line <<"eQPwy">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a\\1?){4}$","eQPwy",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a\\1?){4}$","fpysabFs",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a\\1?){4}$","fpysabFs",[global])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","^(a\\1?){4}$","iySaXMmSpF\\1wGu&i&",[])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","^(a\\1?){4}$","iySaXMmSpF\\1wGu&i&",[global])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a\\1?){4}$","&E&t",[])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a\\1?){4}$","&E&t",[global])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a\\1?){4}$","rv\\1nEUYoTcup\\1",[])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a\\1?){4}$","rv\\1nEUYoTcup\\1",[global])),
+?line <<"Grouf">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a(?(1)\\1)){4}$","Grouf",[])),
+?line <<"Grouf">> = iolist_to_binary(re:replace("aaaaaaaaaa","^(a(?(1)\\1)){4}$","Grouf",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a(?(1)\\1)){4}$","xJ\\1D",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(a(?(1)\\1)){4}$","xJ\\1D",[global])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a(?(1)\\1)){4}$","w&PQ&n&C",[])),
+?line <<"aaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaa","^(a(?(1)\\1)){4}$","w&PQ&n&C",[global])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a(?(1)\\1)){4}$","fXiC",[])),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaa","^(a(?(1)\\1)){4}$","fXiC",[global])),
+?line <<"fEfoobarodvfoobarmohu">> = iolist_to_binary(re:replace("foobar","(?:(f)(o)(o)|(b)(a)(r))*","\\1E&odv&mohu",[])),
+?line <<"fEfoobarodvfoobarmohuEodvmohu">> = iolist_to_binary(re:replace("foobar","(?:(f)(o)(o)|(b)(a)(r))*","\\1E&odv&mohu",[global])),
+?line <<"aaHgXnnrbsEWfBvCB">> = iolist_to_binary(re:replace("ab","(?<=a)b","aHgXnnr&sEWfBv\\1CB",[])),
+?line <<"aaHgXnnrbsEWfBvCB">> = iolist_to_binary(re:replace("ab","(?<=a)b","aHgXnnr&sEWfBv\\1CB",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=a)b","&\\1J\\1qs\\1\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=a)b","&\\1J\\1qs\\1\\1",[global])),
+?line <<"cb">> = iolist_to_binary(re:replace("cb","(?<=a)b","PBhSDEP\\1&fa&&FAQ",[])),
+?line <<"cb">> = iolist_to_binary(re:replace("cb","(?<=a)b","PBhSDEP\\1&fa&&FAQ",[global])),
+?line <<"b">> = iolist_to_binary(re:replace("b","(?<=a)b","VGBLJTb\\1",[])),
+?line <<"b">> = iolist_to_binary(re:replace("b","(?<=a)b","VGBLJTb\\1",[global])),
+?line <<"aAjOnQvFo">> = iolist_to_binary(re:replace("ab","(?<!c)b","Aj\\1OnQvFo",[])),
+?line <<"aAjOnQvFo">> = iolist_to_binary(re:replace("ab","(?<!c)b","Aj\\1OnQvFo",[global])),
+?line <<"EcsuQFCLbrqGgAba">> = iolist_to_binary(re:replace("b","(?<!c)b","EcsuQ\\1FCLbrqGgA&a",[])),
+?line <<"EcsuQFCLbrqGgAba">> = iolist_to_binary(re:replace("b","(?<!c)b","EcsuQ\\1FCLbrqGgA&a",[global])),
+?line <<"OLbRynrb">> = iolist_to_binary(re:replace("b","(?<!c)b","OL&R\\1\\1ynr&\\1",[])),
+?line <<"OLbRynrb">> = iolist_to_binary(re:replace("b","(?<!c)b","OL&R\\1\\1ynr&\\1",[global])),
+?line <<"YnabankVVRQtUI">> = iolist_to_binary(re:replace("aba","(?:..)*a","Yn&nkVVRQtUI",[])),
+?line <<"YnabankVVRQtUI">> = iolist_to_binary(re:replace("aba","(?:..)*a","Yn&nkVVRQtUI",[global])),
+?line <<"dtatllgba">> = iolist_to_binary(re:replace("aba","(?:..)*?a","dt&tllg",[])),
+?line <<"dtatllgbdtatllg">> = iolist_to_binary(re:replace("aba","(?:..)*?a","dt&tllg",[global])),
+?line <<"abKyOHc">> = iolist_to_binary(re:replace("abc","^(?:b|a(?=(.)))*\\1","&KyOH",[])),
+?line <<"abKyOHc">> = iolist_to_binary(re:replace("abc","^(?:b|a(?=(.)))*\\1","&KyOH",[global])),
+?line <<"olabc">> = iolist_to_binary(re:replace("abc","^(){3,5}","ol",[])),
+?line <<"olabc">> = iolist_to_binary(re:replace("abc","^(){3,5}","ol",[global])),
+?line <<"tQmIRuA">> = iolist_to_binary(re:replace("aax","^(a+)*ax","tQmIRuA",[])),
+?line <<"tQmIRuA">> = iolist_to_binary(re:replace("aax","^(a+)*ax","tQmIRuA",[global])),
+?line <<"RaaxLDgyKaII">> = iolist_to_binary(re:replace("aax","^((a|b)+)*ax","R&LDgyK\\1II",[])),
+?line <<"RaaxLDgyKaII">> = iolist_to_binary(re:replace("aax","^((a|b)+)*ax","R&LDgyK\\1II",[global])),
+?line <<"jxJdaCNaaxAUxaofKF">> = iolist_to_binary(re:replace("aax","^((a|bc)+)*ax","jxJd\\1CN&AUx\\1ofKF",[])),
+?line <<"jxJdaCNaaxAUxaofKF">> = iolist_to_binary(re:replace("aax","^((a|bc)+)*ax","jxJd\\1CN&AUx\\1ofKF",[global])),
+?line <<"cxMJiMAGvYS">> = iolist_to_binary(re:replace("cab","(a|x)*ab","xMJiMA\\1G\\1vYS",[])),
+?line <<"cxMJiMAGvYS">> = iolist_to_binary(re:replace("cab","(a|x)*ab","xMJiMA\\1G\\1vYS",[global])),
+?line <<"cwXU">> = iolist_to_binary(re:replace("cab","(a)*ab","wXU\\1",[])),
+?line <<"cwXU">> = iolist_to_binary(re:replace("cab","(a)*ab","wXU\\1",[global])),
+?line <<"y">> = iolist_to_binary(re:replace("ab","(?:(?i)a)b","y",[])),
+?line <<"y">> = iolist_to_binary(re:replace("ab","(?:(?i)a)b","y",[global])),
+?line <<"xNpTab">> = iolist_to_binary(re:replace("ab","((?i)a)b","xNpT&",[])),
+?line <<"xNpTab">> = iolist_to_binary(re:replace("ab","((?i)a)b","xNpT&",[global])),
+?line <<"UUhQk">> = iolist_to_binary(re:replace("Ab","(?:(?i)a)b","UU\\1\\1h\\1Qk",[])),
+?line <<"UUhQk">> = iolist_to_binary(re:replace("Ab","(?:(?i)a)b","UU\\1\\1h\\1Qk",[global])),
+?line <<"To">> = iolist_to_binary(re:replace("Ab","((?i)a)b","To",[])),
+?line <<"To">> = iolist_to_binary(re:replace("Ab","((?i)a)b","To",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?i)a)b","Iog\\1kPwXTNA\\1u",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?i)a)b","Iog\\1kPwXTNA\\1u",[global])),
+?line <<"cb">> = iolist_to_binary(re:replace("cb","(?:(?i)a)b","\\1eeor\\1PcpHdMT&rFUGQX",[])),
+?line <<"cb">> = iolist_to_binary(re:replace("cb","(?:(?i)a)b","\\1eeor\\1PcpHdMT&rFUGQX",[global])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?:(?i)a)b","f&&\\1Fnb&nOyDHT&Trng",[])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?:(?i)a)b","f&&\\1Fnb&nOyDHT&Trng",[global])),
+?line <<"SCyJabaabEDejJdabuabG">> = iolist_to_binary(re:replace("ab","(?i:a)b","SCyJ\\1&a&EDejJd&\\1u\\1&G",[])),
+?line <<"SCyJabaabEDejJdabuabG">> = iolist_to_binary(re:replace("ab","(?i:a)b","SCyJ\\1&a&EDejJd&\\1u\\1&G",[global])),
+?line <<"nabMYvlVqabaabvabp">> = iolist_to_binary(re:replace("ab","((?i:a))b","n&MYvlVq&\\1&v&p",[])),
+?line <<"nabMYvlVqabaabvabp">> = iolist_to_binary(re:replace("ab","((?i:a))b","n&MYvlVq&\\1&v&p",[global])),
+?line <<"hQhGlGrAbAbxEKrc">> = iolist_to_binary(re:replace("Ab","(?i:a)b","hQhGlGr&&xEKrc\\1",[])),
+?line <<"hQhGlGrAbAbxEKrc">> = iolist_to_binary(re:replace("Ab","(?i:a)b","hQhGlGr&&xEKrc\\1",[global])),
+?line <<"fSgsAnoYq">> = iolist_to_binary(re:replace("Ab","((?i:a))b","fSgs\\1noYq",[])),
+?line <<"fSgsAnoYq">> = iolist_to_binary(re:replace("Ab","((?i:a))b","fSgs\\1noYq",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?i:a)b","Lsa",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?i:a)b","Lsa",[global])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?i:a)b","&MKfO&mRWgP&yU",[])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?i:a)b","&MKfO&mRWgP&yU",[global])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?i:a)b","S",[])),
+?line <<"aB">> = iolist_to_binary(re:replace("aB","(?i:a)b","S",[global])),
+?line <<"QTabISRDlbwogmSuiJ">> = iolist_to_binary(re:replace("ab","(?:(?-i)a)b","QT&ISRDlbwogmSuiJ",[caseless])),
+?line <<"QTabISRDlbwogmSuiJ">> = iolist_to_binary(re:replace("ab","(?:(?-i)a)b","QT&ISRDlbwogmSuiJ",[caseless,
+ global])),
+?line <<"VabWOeTSwSGwOkyIabf">> = iolist_to_binary(re:replace("ab","((?-i)a)b","V&WOeTSwSGwOkyI&f",[caseless])),
+?line <<"VabWOeTSwSGwOkyIabf">> = iolist_to_binary(re:replace("ab","((?-i)a)b","V&WOeTSwSGwOkyI&f",[caseless,
+ global])),
+?line <<"pNtk">> = iolist_to_binary(re:replace("aB","(?:(?-i)a)b","\\1pNtk",[caseless])),
+?line <<"pNtk">> = iolist_to_binary(re:replace("aB","(?:(?-i)a)b","\\1pNtk",[caseless,
+ global])),
+?line <<"MpdRBTE">> = iolist_to_binary(re:replace("aB","((?-i)a)b","MpdRBTE",[caseless])),
+?line <<"MpdRBTE">> = iolist_to_binary(re:replace("aB","((?-i)a)b","MpdRBTE",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?-i)a)b","RiV",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?-i)a)b","RiV",[caseless,
+ global])),
+?line <<"tjEITLaBaBtD">> = iolist_to_binary(re:replace("aB","(?:(?-i)a)b","t\\1jEITL&&tD",[caseless])),
+?line <<"tjEITLaBaBtD">> = iolist_to_binary(re:replace("aB","(?:(?-i)a)b","t\\1jEITL&&tD",[caseless,
+ global])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?:(?-i)a)b","hbvOWn",[caseless])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?:(?-i)a)b","hbvOWn",[caseless,
+ global])),
+?line <<"s">> = iolist_to_binary(re:replace("aB","(?:(?-i)a)b","s",[caseless])),
+?line <<"s">> = iolist_to_binary(re:replace("aB","(?:(?-i)a)b","s",[caseless,
+ global])),
+?line <<"yfQaBvLFJJQMhKGx">> = iolist_to_binary(re:replace("aB","((?-i)a)b","yfQ&vLFJJQMhKGx",[caseless])),
+?line <<"yfQaBvLFJJQMhKGx">> = iolist_to_binary(re:replace("aB","((?-i)a)b","yfQ&vLFJJQMhKGx",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?-i)a)b","QM&L",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?:(?-i)a)b","QM&L",[caseless,
+ global])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?:(?-i)a)b","aVF\\1dL&\\1",[caseless])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?:(?-i)a)b","aVF\\1dL&\\1",[caseless,
+ global])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","(?:(?-i)a)b","GSb\\1bvleJ",[caseless])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","(?:(?-i)a)b","GSb\\1bvleJ",[caseless,
+ global])),
+?line <<"wAW">> = iolist_to_binary(re:replace("ab","(?-i:a)b","wAW",[caseless])),
+?line <<"wAW">> = iolist_to_binary(re:replace("ab","(?-i:a)b","wAW",[caseless,
+ global])),
+?line <<"Atl">> = iolist_to_binary(re:replace("ab","((?-i:a))b","Atl",[caseless])),
+?line <<"Atl">> = iolist_to_binary(re:replace("ab","((?-i:a))b","Atl",[caseless,
+ global])),
+?line <<"gP">> = iolist_to_binary(re:replace("aB","(?-i:a)b","gP\\1",[caseless])),
+?line <<"gP">> = iolist_to_binary(re:replace("aB","(?-i:a)b","gP\\1",[caseless,
+ global])),
+?line <<"LFcaNaJixv">> = iolist_to_binary(re:replace("aB","((?-i:a))b","LFc\\1N\\1Jixv",[caseless])),
+?line <<"LFcaNaJixv">> = iolist_to_binary(re:replace("aB","((?-i:a))b","LFc\\1N\\1Jixv",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?-i:a)b","FRPuPJIi\\1",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?-i:a)b","FRPuPJIi\\1",[caseless,
+ global])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","(?-i:a)b","TYBp\\1aMFRUwXYyGS\\1&tH",[caseless])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","(?-i:a)b","TYBp\\1aMFRUwXYyGS\\1&tH",[caseless,
+ global])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?-i:a)b","s",[caseless])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?-i:a)b","s",[caseless,
+ global])),
+?line <<"XBBaBVaBHwVHaBtFl">> = iolist_to_binary(re:replace("aB","(?-i:a)b","XBB\\1&V&H\\1wVH&t\\1Fl",[caseless])),
+?line <<"XBBaBVaBHwVHaBtFl">> = iolist_to_binary(re:replace("aB","(?-i:a)b","XBB\\1&V&H\\1wVH&t\\1Fl",[caseless,
+ global])),
+?line <<"TfbKaBvxl">> = iolist_to_binary(re:replace("aB","((?-i:a))b","TfbK&vxl",[caseless])),
+?line <<"TfbKaBvxl">> = iolist_to_binary(re:replace("aB","((?-i:a))b","TfbK&vxl",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?-i:a)b","\\1B\\1v&LjNSAy",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?-i:a)b","\\1B\\1v&LjNSAy",[caseless,
+ global])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?-i:a)b","HQTW",[caseless])),
+?line <<"Ab">> = iolist_to_binary(re:replace("Ab","(?-i:a)b","HQTW",[caseless,
+ global])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","(?-i:a)b","pG",[caseless])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","(?-i:a)b","pG",[caseless,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?-i:a.))b","U",[caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?-i:a.))b","U",[caseless,
+ global])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","((?-i:a.))b","\\1uBjM&tv&\\1tD\\1UO\\1gVY",[caseless])),
+?line <<"AB">> = iolist_to_binary(re:replace("AB","((?-i:a.))b","\\1uBjM&tv&\\1tD\\1UO\\1gVY",[caseless,
+ global])),
+?line <<"a
+B">> = iolist_to_binary(re:replace("a
+B","((?-i:a.))b","SAx&Io",[caseless])),
+?line <<"a
+B">> = iolist_to_binary(re:replace("a
+B","((?-i:a.))b","SAx&Io",[caseless,global])),
+?line <<"LPyepkdfnqsa
+oa
+a
+Ba
+M">> = iolist_to_binary(re:replace("a
+B","((?s-i:a.))b","LPyepkdfnqs\\1o\\1&\\1M",[caseless])),
+?line <<"LPyepkdfnqsa
+oa
+a
+Ba
+M">> = iolist_to_binary(re:replace("a
+B","((?s-i:a.))b","LPyepkdfnqs\\1o\\1&\\1M",[caseless,global])),
+?line <<"RG">> = iolist_to_binary(re:replace("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))","RG",[])),
+?line <<"RG">> = iolist_to_binary(re:replace("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))","RG",[global])),
+?line <<"SiMB">> = iolist_to_binary(re:replace("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))","\\1SiMB",[])),
+?line <<"SiMB">> = iolist_to_binary(re:replace("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))","\\1SiMB",[global])),
+?line <<"HNBAb4abYAb4abecqBPfYYBF">> = iolist_to_binary(re:replace("Ab4ab","(ab)\\d\\1","HNB&Y&ecqBPfYYBF",[caseless])),
+?line <<"HNBAb4abYAb4abecqBPfYYBF">> = iolist_to_binary(re:replace("Ab4ab","(ab)\\d\\1","HNB&Y&ecqBPfYYBF",[caseless,
+ global])),
+?line <<"eqbVWgJEcab4Ababe">> = iolist_to_binary(re:replace("ab4Ab","(ab)\\d\\1","eqbVWgJEc&\\1e",[caseless])),
+?line <<"eqbVWgJEcab4Ababe">> = iolist_to_binary(re:replace("ab4Ab","(ab)\\d\\1","eqbVWgJEc&\\1e",[caseless,
+ global])),
+?line <<"HvsmPfoobar1234bazCYfoobar1234bazqih">> = iolist_to_binary(re:replace("foobar1234baz","foo\\w*\\d{4}baz","HvsmP&CY&qih",[])),
+?line <<"HvsmPfoobar1234bazCYfoobar1234bazqih">> = iolist_to_binary(re:replace("foobar1234baz","foo\\w*\\d{4}baz","HvsmP&CY&qih",[global])),
+?line <<"x~~lsTkD~~qWUPtx~~wj~~R">> = iolist_to_binary(re:replace("x~~","x(~~)*(?:(?:F)?)?","&lsTkD\\1qWUPt&wj\\1R",[])),
+?line <<"x~~lsTkD~~qWUPtx~~wj~~R">> = iolist_to_binary(re:replace("x~~","x(~~)*(?:(?:F)?)?","&lsTkD\\1qWUPt&wj\\1R",[global])),
+?line <<"aaacPnBOLPeN">> = iolist_to_binary(re:replace("aaac","^a(?#xxx){3}c","&P\\1n\\1BOLPeN",[])),
+?line <<"aaacPnBOLPeN">> = iolist_to_binary(re:replace("aaac","^a(?#xxx){3}c","&P\\1n\\1BOLPeN",[global])),
+?line <<"Uh">> = iolist_to_binary(re:replace("aaac","^a (?#xxx) (?#yyy) {3}c","Uh",[extended])),
+?line <<"Uh">> = iolist_to_binary(re:replace("aaac","^a (?#xxx) (?#yyy) {3}c","Uh",[extended,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<![cd])b","\\1CWOLaTxilNg\\1W",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<![cd])b","\\1CWOLaTxilNg\\1W",[global])),
+?line <<"B
+B">> = iolist_to_binary(re:replace("B
+B","(?<![cd])b","ruaT\\1JrTa&\\1sLB&iVNCc",[])),
+?line <<"B
+B">> = iolist_to_binary(re:replace("B
+B","(?<![cd])b","ruaT\\1JrTa&\\1sLB&iVNCc",[global])),
+?line <<"dbcb">> = iolist_to_binary(re:replace("dbcb","(?<![cd])b","\\1l\\1yKVtpLoPXXvCkO",[])),
+?line <<"dbcb">> = iolist_to_binary(re:replace("dbcb","(?<![cd])b","\\1l\\1yKVtpLoPXXvCkO",[global])),
+?line <<"dbuWHWJTppUTaMYMjwHAacb">> = iolist_to_binary(re:replace("dbaacb","(?<![cd])[ab]","uWHWJTppUT&\\1MYM\\1jwHA",[])),
+?line <<"dbuWHWJTppUTaMYMjwHAuWHWJTppUTaMYMjwHAcb">> = iolist_to_binary(re:replace("dbaacb","(?<![cd])[ab]","uWHWJTppUT&\\1MYM\\1jwHA",[global])),
+?line <<"dbeacb">> = iolist_to_binary(re:replace("dbaacb","(?<!(c|d))[ab]","e",[])),
+?line <<"dbeecb">> = iolist_to_binary(re:replace("dbaacb","(?<!(c|d))[ab]","e",[global])),
+?line <<"cdaccbtNFbjDlrMmYMBg">> = iolist_to_binary(re:replace("cdaccb","(?<!cd)[ab]","\\1\\1btNF&jDlrM\\1mYMBg",[])),
+?line <<"cdaccbtNFbjDlrMmYMBg">> = iolist_to_binary(re:replace("cdaccb","(?<!cd)[ab]","\\1\\1btNF&jDlrM\\1mYMBg",[global])),
+?line <<"s">> = iolist_to_binary(re:replace("","^(?:a?b?)*$","s",[])),
+?line <<"s">> = iolist_to_binary(re:replace("","^(?:a?b?)*$","s",[global])),
+?line <<"odRhXAvKP">> = iolist_to_binary(re:replace("a","^(?:a?b?)*$","odRhXAvKP",[])),
+?line <<"odRhXAvKP">> = iolist_to_binary(re:replace("a","^(?:a?b?)*$","odRhXAvKP",[global])),
+?line <<"o">> = iolist_to_binary(re:replace("ab","^(?:a?b?)*$","\\1o",[])),
+?line <<"o">> = iolist_to_binary(re:replace("ab","^(?:a?b?)*$","\\1o",[global])),
+?line <<"d">> = iolist_to_binary(re:replace("aaa","^(?:a?b?)*$","d",[])),
+?line <<"d">> = iolist_to_binary(re:replace("aaa","^(?:a?b?)*$","d",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?:a?b?)*$","fX&M\\1FCCOYOMH\\1lR&ISP",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?:a?b?)*$","fX&M\\1FCCOYOMH\\1lR&ISP",[global])),
+?line <<"dbcb">> = iolist_to_binary(re:replace("dbcb","^(?:a?b?)*$","d\\1aeAWVouRdylpC",[])),
+?line <<"dbcb">> = iolist_to_binary(re:replace("dbcb","^(?:a?b?)*$","d\\1aeAWVouRdylpC",[global])),
+?line <<"a--">> = iolist_to_binary(re:replace("a--","^(?:a?b?)*$","uM",[])),
+?line <<"a--">> = iolist_to_binary(re:replace("a--","^(?:a?b?)*$","uM",[global])),
+?line <<"aa--">> = iolist_to_binary(re:replace("aa--","^(?:a?b?)*$","EhSvb&jryIv\\1O&oeR",[])),
+?line <<"aa--">> = iolist_to_binary(re:replace("aa--","^(?:a?b?)*$","EhSvb&jryIv\\1O&oeR",[global])),
+?line <<"yobOa
+ba
+ca
+bcItPS
+c">> = iolist_to_binary(re:replace("a
+b
+c","((?s)^a(.))((?m)^b$)","yobO&\\1c&cItPS",[])),
+?line <<"yobOa
+ba
+ca
+bcItPS
+c">> = iolist_to_binary(re:replace("a
+b
+c","((?s)^a(.))((?m)^b$)","yobO&\\1c&cItPS",[global])),
+?line <<"a
+EBbPgb
+c">> = iolist_to_binary(re:replace("a
+b
+c","((?m)^b$)","EB\\1Pg\\1",[])),
+?line <<"a
+EBbPgb
+c">> = iolist_to_binary(re:replace("a
+b
+c","((?m)^b$)","EB\\1Pg\\1",[global])),
+?line <<"a
+AybVFSWPOkP">> = iolist_to_binary(re:replace("a
+b","(?m)^b","Ay\\1&VF\\1SWPOkP\\1",[])),
+?line <<"a
+AybVFSWPOkP">> = iolist_to_binary(re:replace("a
+b","(?m)^b","Ay\\1&VF\\1SWPOkP\\1",[global])),
+?line <<"a
+bbTbKbl">> = iolist_to_binary(re:replace("a
+b","(?m)^(b)","b\\1T&K\\1l",[])),
+?line <<"a
+bbTbKbl">> = iolist_to_binary(re:replace("a
+b","(?m)^(b)","b\\1T&K\\1l",[global])),
+?line <<"a
+bsyUb">> = iolist_to_binary(re:replace("a
+b","((?m)^b)","&syU\\1",[])),
+?line <<"a
+bsyUb">> = iolist_to_binary(re:replace("a
+b","((?m)^b)","&syU\\1",[global])),
+?line <<"abh">> = iolist_to_binary(re:replace("a
+b","\\n((?m)^b)","\\1h",[])),
+?line <<"abh">> = iolist_to_binary(re:replace("a
+b","\\n((?m)^b)","\\1h",[global])),
+?line <<"a
+bEu">> = iolist_to_binary(re:replace("a
+b
+c","((?s).)c(?!.)","Eu",[])),
+?line <<"a
+bEu">> = iolist_to_binary(re:replace("a
+b
+c","((?s).)c(?!.)","Eu",[global])),
+?line <<"a
+bvKqN
+cF
+r
+n">> = iolist_to_binary(re:replace("a
+b
+c","((?s).)c(?!.)","vKqN&F\\1r\\1n",[])),
+?line <<"a
+bvKqN
+cF
+r
+n">> = iolist_to_binary(re:replace("a
+b
+c","((?s).)c(?!.)","vKqN&F\\1r\\1n",[global])),
+?line <<"a
+ctWb
+Kb
+cinb
+FvJ">> = iolist_to_binary(re:replace("a
+b
+c","((?s)b.)c(?!.)","ctW\\1K&in\\1FvJ",[])),
+?line <<"a
+ctWb
+Kb
+cinb
+FvJ">> = iolist_to_binary(re:replace("a
+b
+c","((?s)b.)c(?!.)","ctW\\1K&in\\1FvJ",[global])),
+?line <<"a
+Tb
+cnVEJvb
+Bb
+yG">> = iolist_to_binary(re:replace("a
+b
+c","((?s)b.)c(?!.)","T&nVEJv\\1B\\1yG",[])),
+?line <<"a
+Tb
+cnVEJvb
+Bb
+yG">> = iolist_to_binary(re:replace("a
+b
+c","((?s)b.)c(?!.)","T&nVEJv\\1B\\1yG",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","()^b","&\\1",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","()^b","&\\1",[global])),
+?line <<"a
+b
+c">> = iolist_to_binary(re:replace("a
+b
+c","()^b","ikT\\1",[])),
+?line <<"a
+b
+c">> = iolist_to_binary(re:replace("a
+b
+c","()^b","ikT\\1",[global])),
+?line <<"a
+b
+c">> = iolist_to_binary(re:replace("a
+b
+c","()^b","&i&frU",[])),
+?line <<"a
+b
+c">> = iolist_to_binary(re:replace("a
+b
+c","()^b","&i&frU",[global])),
+?line <<"a
+bDbfDWKbixbSbxsSN
+c">> = iolist_to_binary(re:replace("a
+b
+c","((?m)^b)","bD\\1fDWK\\1ix\\1S\\1xsSN",[])),
+?line <<"a
+bDbfDWKbixbSbxsSN
+c">> = iolist_to_binary(re:replace("a
+b
+c","((?m)^b)","bD\\1fDWK\\1ix\\1S\\1xsSN",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(x)?(?(1)a|b)","s&Rt",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(x)?(?(1)a|b)","s&Rt",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(x)?(?(1)a|b)","S\\1EQcXTxxFE",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(x)?(?(1)a|b)","S\\1EQcXTxxFE",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(x)?(?(1)a|b)","JRHc\\1hvpt&&",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(x)?(?(1)a|b)","JRHc\\1hvpt&&",[global])),
+?line <<"bQs">> = iolist_to_binary(re:replace("a","(x)?(?(1)b|a)","bQs",[])),
+?line <<"bQs">> = iolist_to_binary(re:replace("a","(x)?(?(1)b|a)","bQs",[global])),
+?line <<"PQeYprqargqfYkWQsJ">> = iolist_to_binary(re:replace("a","()?(?(1)b|a)","PQeY\\1prq&rgqfYk\\1WQsJ",[])),
+?line <<"PQeYprqargqfYkWQsJ">> = iolist_to_binary(re:replace("a","()?(?(1)b|a)","PQeY\\1prq&rgqfYk\\1WQsJ",[global])),
+?line <<"HafNdwOXAFq">> = iolist_to_binary(re:replace("a","()?(?(1)a|b)","H&\\1f\\1NdwOXA\\1Fq",[])),
+?line <<"HafNdwOXAFq">> = iolist_to_binary(re:replace("a","()?(?(1)a|b)","H&\\1f\\1NdwOXA\\1Fq",[global])),
+?line <<"m">> = iolist_to_binary(re:replace("(blah)","^(\\()?blah(?(1)(\\)))$","m",[])),
+?line <<"m">> = iolist_to_binary(re:replace("(blah)","^(\\()?blah(?(1)(\\)))$","m",[global])),
+?line <<"elEwHf">> = iolist_to_binary(re:replace("blah","^(\\()?blah(?(1)(\\)))$","elEwHf",[])),
+?line <<"elEwHf">> = iolist_to_binary(re:replace("blah","^(\\()?blah(?(1)(\\)))$","elEwHf",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\()?blah(?(1)(\\)))$","IGewW&v&qpGlghCJe\\1Y",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\()?blah(?(1)(\\)))$","IGewW&v&qpGlghCJe\\1Y",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","^(\\()?blah(?(1)(\\)))$","mxf",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","^(\\()?blah(?(1)(\\)))$","mxf",[global])),
+?line <<"blah)">> = iolist_to_binary(re:replace("blah)","^(\\()?blah(?(1)(\\)))$","LxtdV",[])),
+?line <<"blah)">> = iolist_to_binary(re:replace("blah)","^(\\()?blah(?(1)(\\)))$","LxtdV",[global])),
+?line <<"(blah">> = iolist_to_binary(re:replace("(blah","^(\\()?blah(?(1)(\\)))$","Ni\\1CkEtaxcXXYB\\1",[])),
+?line <<"(blah">> = iolist_to_binary(re:replace("(blah","^(\\()?blah(?(1)(\\)))$","Ni\\1CkEtaxcXXYB\\1",[global])),
+?line <<"UaAfN(ELb">> = iolist_to_binary(re:replace("(blah)","^(\\(+)?blah(?(1)(\\)))$","UaAfN\\1ELb",[])),
+?line <<"UaAfN(ELb">> = iolist_to_binary(re:replace("(blah)","^(\\(+)?blah(?(1)(\\)))$","UaAfN\\1ELb",[global])),
+?line <<"XrxQosMn">> = iolist_to_binary(re:replace("blah","^(\\(+)?blah(?(1)(\\)))$","Xrx\\1QosMn\\1",[])),
+?line <<"XrxQosMn">> = iolist_to_binary(re:replace("blah","^(\\(+)?blah(?(1)(\\)))$","Xrx\\1QosMn\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\(+)?blah(?(1)(\\)))$","QGGpmf",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(\\(+)?blah(?(1)(\\)))$","QGGpmf",[global])),
+?line <<"blah)">> = iolist_to_binary(re:replace("blah)","^(\\(+)?blah(?(1)(\\)))$","HDFROCUS",[])),
+?line <<"blah)">> = iolist_to_binary(re:replace("blah)","^(\\(+)?blah(?(1)(\\)))$","HDFROCUS",[global])),
+?line <<"(blah">> = iolist_to_binary(re:replace("(blah","^(\\(+)?blah(?(1)(\\)))$","AVrY",[])),
+?line <<"(blah">> = iolist_to_binary(re:replace("(blah","^(\\(+)?blah(?(1)(\\)))$","AVrY",[global])),
+?line <<"HlgBXckVbhp">> = iolist_to_binary(re:replace("a","(?(?!a)b|a)","HlgBXckV\\1bhp",[])),
+?line <<"HlgBXckVbhp">> = iolist_to_binary(re:replace("a","(?(?!a)b|a)","HlgBXckV\\1bhp",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?=a)b|a)","&&PIwfc\\1cckXSEYaB",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?(?=a)b|a)","&&PIwfc\\1cckXSEYaB",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(?(?=a)b|a)","vKjGNVI&ySCYE",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(?(?=a)b|a)","vKjGNVI&ySCYE",[global])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(?(?=a)b|a)","lAvpym&eEJad\\1RMs\\1CLu",[])),
+?line <<"a">> = iolist_to_binary(re:replace("a","(?(?=a)b|a)","lAvpym&eEJad\\1RMs\\1CLu",[global])),
+?line <<"kHAy">> = iolist_to_binary(re:replace("a","(?(?=a)a|b)","kHAy",[])),
+?line <<"kHAy">> = iolist_to_binary(re:replace("a","(?(?=a)a|b)","kHAy",[global])),
+?line <<"amAyaheaaTPJaVdTAyU">> = iolist_to_binary(re:replace("aaab","(?=(a+?))(\\1ab)","mAyahe\\1\\1TPJ\\1VdTAyU",[])),
+?line <<"amAyaheaaTPJaVdTAyU">> = iolist_to_binary(re:replace("aaab","(?=(a+?))(\\1ab)","mAyahe\\1\\1TPJ\\1VdTAyU",[global])),
+?line <<"pEvYFTwEOhhryoVdG">> = iolist_to_binary(re:replace("one:","(\\w+:)+","pEvYFTwEOhhryoVdG",[])),
+?line <<"pEvYFTwEOhhryoVdG">> = iolist_to_binary(re:replace("one:","(\\w+:)+","pEvYFTwEOhhryoVdG",[global])),
+?line <<"aHho">> = iolist_to_binary(re:replace("a","$(?<=^(a))","Hho",[])),
+?line <<"aHho">> = iolist_to_binary(re:replace("a","$(?<=^(a))","Hho",[global])),
+?line <<"ajuOkagipUraRpaQiaabv">> = iolist_to_binary(re:replace("aaab","(?=(a+?))(\\1ab)","juOk\\1gipUr\\1Rp\\1Qi&v",[])),
+?line <<"ajuOkagipUraRpaQiaabv">> = iolist_to_binary(re:replace("aaab","(?=(a+?))(\\1ab)","juOk\\1gipUr\\1Rp\\1Qi&v",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?=(a+?))\\1ab","g\\1\\1\\1v",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?=(a+?))\\1ab","g\\1\\1\\1v",[global])),
+?line <<"aaab">> = iolist_to_binary(re:replace("aaab","^(?=(a+?))\\1ab","dj\\1E&",[])),
+?line <<"aaab">> = iolist_to_binary(re:replace("aaab","^(?=(a+?))\\1ab","dj\\1E&",[global])),
+?line <<"aaab">> = iolist_to_binary(re:replace("aaab","^(?=(a+?))\\1ab","\\1UllJHtwTvaUdSmur",[])),
+?line <<"aaab">> = iolist_to_binary(re:replace("aaab","^(?=(a+?))\\1ab","\\1UllJHtwTvaUdSmur",[global])),
+?line <<"juabcdvJAqaNxcabcdrDs">> = iolist_to_binary(re:replace("abcd","([\\w:]+::)?(\\w+)$","ju&vJAqaNxc&rDs\\1",[])),
+?line <<"juabcdvJAqaNxcabcdrDs">> = iolist_to_binary(re:replace("abcd","([\\w:]+::)?(\\w+)$","ju&vJAqaNxc&rDs\\1",[global])),
+?line <<"unOWEMklEbRjSO">> = iolist_to_binary(re:replace("xy:z:::abcd","([\\w:]+::)?(\\w+)$","unOWEMklEbRjSO",[])),
+?line <<"unOWEMklEbRjSO">> = iolist_to_binary(re:replace("xy:z:::abcd","([\\w:]+::)?(\\w+)$","unOWEMklEbRjSO",[global])),
+?line <<"nGqSeaexycfAmCxmxEaexycd">> = iolist_to_binary(re:replace("aexycd","^[^bcd]*(c+)","nGqSe&fAmCxmxE&",[])),
+?line <<"nGqSeaexycfAmCxmxEaexycd">> = iolist_to_binary(re:replace("aexycd","^[^bcd]*(c+)","nGqSe&fAmCxmxE&",[global])),
+?line <<"cwK">> = iolist_to_binary(re:replace("caab","(a*)b+","wK",[])),
+?line <<"cwK">> = iolist_to_binary(re:replace("caab","(a*)b+","wK",[global])),
+?line <<"VKunDTpabcd">> = iolist_to_binary(re:replace("abcd","([\\w:]+::)?(\\w+)$","VKunD\\1Tp&",[])),
+?line <<"VKunDTpabcd">> = iolist_to_binary(re:replace("abcd","([\\w:]+::)?(\\w+)$","VKunD\\1Tp&",[global])),
+?line <<"xy:z:::SXxy:z:::cMHreuKxy:z:::">> = iolist_to_binary(re:replace("xy:z:::abcd","([\\w:]+::)?(\\w+)$","\\1SX\\1cMHreuK\\1",[])),
+?line <<"xy:z:::SXxy:z:::cMHreuKxy:z:::">> = iolist_to_binary(re:replace("xy:z:::abcd","([\\w:]+::)?(\\w+)$","\\1SX\\1cMHreuK\\1",[global])),
+?line <<"*** FhLAjVJbYFailersvFailersQgrO">> = iolist_to_binary(re:replace("*** Failers","([\\w:]+::)?(\\w+)$","FhLAjVJbY&v&QgrO\\1\\1",[])),
+?line <<"*** FhLAjVJbYFailersvFailersQgrO">> = iolist_to_binary(re:replace("*** Failers","([\\w:]+::)?(\\w+)$","FhLAjVJbY&v&QgrO\\1\\1",[global])),
+?line <<"abcd:">> = iolist_to_binary(re:replace("abcd:","([\\w:]+::)?(\\w+)$","cwV&UpGIKN",[])),
+?line <<"abcd:">> = iolist_to_binary(re:replace("abcd:","([\\w:]+::)?(\\w+)$","cwV&UpGIKN",[global])),
+?line <<"abcd:">> = iolist_to_binary(re:replace("abcd:","([\\w:]+::)?(\\w+)$","S",[])),
+?line <<"abcd:">> = iolist_to_binary(re:replace("abcd:","([\\w:]+::)?(\\w+)$","S",[global])),
+?line <<"Fd">> = iolist_to_binary(re:replace("aexycd","^[^bcd]*(c+)","F",[])),
+?line <<"Fd">> = iolist_to_binary(re:replace("aexycd","^[^bcd]*(c+)","F",[global])),
+?line <<"jBpBHQR">> = iolist_to_binary(re:replace("aaab","(?>a+)b","jBpBHQR",[])),
+?line <<"jBpBHQR">> = iolist_to_binary(re:replace("aaab","(?>a+)b","jBpBHQR",[global])),
+?line <<"a:[J:[:[UyJMIwrPUq:[b]:">> = iolist_to_binary(re:replace("a:[b]:","([[:]+)","\\1J&\\1UyJMIwrPUq\\1",[])),
+?line <<"a:[J:[:[UyJMIwrPUq:[b]:J::UyJMIwrPUq:">> = iolist_to_binary(re:replace("a:[b]:","([[:]+)","\\1J&\\1UyJMIwrPUq\\1",[global])),
+?line <<"asI=[vo=[d=[Y=[nMpb]=">> = iolist_to_binary(re:replace("a=[b]=","([[=]+)","sI\\1vo\\1d\\1Y&nMp",[])),
+?line <<"asI=[vo=[d=[Y=[nMpb]sI=vo=d=Y=nMp">> = iolist_to_binary(re:replace("a=[b]=","([[=]+)","sI\\1vo\\1d\\1Y&nMp",[global])),
+?line <<"aChCrrVW.[ed.[eo.[h.[SYkIb].">> = iolist_to_binary(re:replace("a.[b].","([[.]+)","ChCrrVW\\1ed&eo\\1h\\1SYkI",[])),
+?line <<"aChCrrVW.[ed.[eo.[h.[SYkIb]ChCrrVW.ed.eo.h.SYkI">> = iolist_to_binary(re:replace("a.[b].","([[.]+)","ChCrrVW\\1ed&eo\\1h\\1SYkI",[global])),
+?line <<"BaaabaaabGBaaabJDn">> = iolist_to_binary(re:replace("aaab","((?>a+)b)","B&\\1GB&JDn",[])),
+?line <<"BaaabaaabGBaaabJDn">> = iolist_to_binary(re:replace("aaab","((?>a+)b)","B&\\1GB&JDn",[global])),
+?line <<"kaaabsaaXdPWUBV">> = iolist_to_binary(re:replace("aaab","(?>(a+))b","k&saaXdPWUBV",[])),
+?line <<"kaaabsaaXdPWUBV">> = iolist_to_binary(re:replace("aaab","(?>(a+))b","k&saaXdPWUBV",[global])),
+?line <<"((xpOHCg">> = iolist_to_binary(re:replace("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+","\\1pOHCg",[])),
+?line <<"((xpOHCg">> = iolist_to_binary(re:replace("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+","\\1pOHCg",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a\\Z","swCtIfMPh&\\1Yr",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a\\Z","swCtIfMPh&\\1Yr",[global])),
+?line <<"aaab">> = iolist_to_binary(re:replace("aaab","a\\Z","ysA\\1",[])),
+?line <<"aaab">> = iolist_to_binary(re:replace("aaab","a\\Z","ysA\\1",[global])),
+?line <<"a
+b">> = iolist_to_binary(re:replace("a
+b","a\\Z","&ajAlqYVsnk",[])),
+?line <<"a
+b">> = iolist_to_binary(re:replace("a
+b","a\\Z","&ajAlqYVsnk",[global])),
+?line <<"a
+lKbbBrmbgrOVeW">> = iolist_to_binary(re:replace("a
+b","b\\Z","l\\1Kb&Brm&gr\\1OVeW",[])),
+?line <<"a
+lKbbBrmbgrOVeW">> = iolist_to_binary(re:replace("a
+b","b\\Z","l\\1Kb&Brm&gr\\1OVeW",[global])),
+?line <<"a
+gnI">> = iolist_to_binary(re:replace("a
+b","b\\Z","g\\1nI",[])),
+?line <<"a
+gnI">> = iolist_to_binary(re:replace("a
+b","b\\Z","g\\1nI",[global])),
+?line <<"a
+bcbdqlbVGu">> = iolist_to_binary(re:replace("a
+b","b\\z","bc&dql&VGu\\1",[])),
+?line <<"a
+bcbdqlbVGu">> = iolist_to_binary(re:replace("a
+b","b\\z","bc&dql&VGu\\1",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","b\\z","e\\1yg",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","b\\z","e\\1yg",[global])),
+?line <<"ad">> = iolist_to_binary(re:replace("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","&d",[])),
+?line <<"ad">> = iolist_to_binary(re:replace("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","&d",[global])),
+?line <<"Aq">> = iolist_to_binary(re:replace("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","Aq",[])),
+?line <<"Aq">> = iolist_to_binary(re:replace("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","Aq",[global])),
+?line <<"ca-ba-bhLfWdPLa-boe">> = iolist_to_binary(re:replace("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","c&&hLfWdPL&oe",[])),
+?line <<"ca-ba-bhLfWdPLa-boe">> = iolist_to_binary(re:replace("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","c&&hLfWdPL&oe",[global])),
+?line <<"0-90-9YeqrpXMpBjK">> = iolist_to_binary(re:replace("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","&\\1&YeqrpXMpBjK",[])),
+?line <<"0-90-9YeqrpXMpBjK">> = iolist_to_binary(re:replace("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","&\\1&YeqrpXMpBjK",[global])),
+?line <<"fa.bPwKXcUgqjJm">> = iolist_to_binary(re:replace("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","\\1f&PwKX\\1cUgqjJm",[])),
+?line <<"fa.bPwKXcUgqjJm">> = iolist_to_binary(re:replace("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","\\1f&PwKX\\1cUgqjJm",[global])),
+?line <<"um5.6.7cFpS">> = iolist_to_binary(re:replace("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","um&cFpS\\1",[])),
+?line <<"um5.6.7cFpS">> = iolist_to_binary(re:replace("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","um&cFpS\\1",[global])),
+?line <<"xqtEI">> = iolist_to_binary(re:replace("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","xqtEI",[])),
+?line <<"xqtEI">> = iolist_to_binary(re:replace("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","xqtEI",[global])),
+?line <<"sga100.b200.300cdqqwa100.b200.300cqSJINVa100.b200.300cOyeI">> = iolist_to_binary(re:replace("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","sg&dqqw&qSJINV&OyeI",[])),
+?line <<"sga100.b200.300cdqqwa100.b200.300cqSJINVa100.b200.300cOyeI">> = iolist_to_binary(re:replace("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","sg&dqqw&qSJINV&OyeI",[global])),
+?line <<"rabe12-ab.1245NInvoPCLW12-ab.1245">> = iolist_to_binary(re:replace("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","rabe&NI\\1nvo\\1PCLW&",[])),
+?line <<"rabe12-ab.1245NInvoPCLW12-ab.1245">> = iolist_to_binary(re:replace("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","rabe&NI\\1nvo\\1PCLW&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","jhvaoUW\\1Wye\\1Qkrj",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","jhvaoUW\\1Wye\\1Qkrj",[global])),
+?line <<"">> = iolist_to_binary(re:replace("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","Q&L\\1qk&QWuvQ\\1o",[])),
+?line <<"">> = iolist_to_binary(re:replace("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","Q&L\\1qk&QWuvQ\\1o",[global])),
+?line <<".a">> = iolist_to_binary(re:replace(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","j&I&\\1&myXAKjyO&Mw",[])),
+?line <<".a">> = iolist_to_binary(re:replace(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","j&I&\\1&myXAKjyO&Mw",[global])),
+?line <<"-a">> = iolist_to_binary(re:replace("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","yyHkNcANJB",[])),
+?line <<"-a">> = iolist_to_binary(re:replace("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","yyHkNcANJB",[global])),
+?line <<"a-">> = iolist_to_binary(re:replace("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","&\\1tGDVOXc&&\\1aF\\1",[])),
+?line <<"a-">> = iolist_to_binary(re:replace("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","&\\1tGDVOXc&&\\1aF\\1",[global])),
+?line <<"a.">> = iolist_to_binary(re:replace("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","\\1\\1PTJg\\1\\1x",[])),
+?line <<"a.">> = iolist_to_binary(re:replace("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","\\1\\1PTJg\\1\\1x",[global])),
+?line <<"a_b">> = iolist_to_binary(re:replace("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","w&",[])),
+?line <<"a_b">> = iolist_to_binary(re:replace("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","w&",[global])),
+?line <<"a.-">> = iolist_to_binary(re:replace("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","EYTDvAw&\\1hAJjtxh",[])),
+?line <<"a.-">> = iolist_to_binary(re:replace("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","EYTDvAw&\\1hAJjtxh",[global])),
+?line <<"a..">> = iolist_to_binary(re:replace("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","v&",[])),
+?line <<"a..">> = iolist_to_binary(re:replace("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","v&",[global])),
+?line <<"ab..bc">> = iolist_to_binary(re:replace("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","xi\\1TmyKwF&sc",[])),
+?line <<"ab..bc">> = iolist_to_binary(re:replace("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","xi\\1TmyKwF&sc",[global])),
+?line <<"the.quick.brown.fox-">> = iolist_to_binary(re:replace("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","qbJylT\\1",[])),
+?line <<"the.quick.brown.fox-">> = iolist_to_binary(re:replace("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","qbJylT\\1",[global])),
+?line <<"the.quick.brown.fox.">> = iolist_to_binary(re:replace("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","P&\\1dGVbFGwNJ",[])),
+?line <<"the.quick.brown.fox.">> = iolist_to_binary(re:replace("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","P&\\1dGVbFGwNJ",[global])),
+?line <<"the.quick.brown.fox_">> = iolist_to_binary(re:replace("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","V&e",[])),
+?line <<"the.quick.brown.fox_">> = iolist_to_binary(re:replace("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","V&e",[global])),
+?line <<"the.quick.brown.fox+">> = iolist_to_binary(re:replace("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","\\1",[])),
+?line <<"the.quick.brown.fox+">> = iolist_to_binary(re:replace("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$","\\1",[global])),
+?line <<"alphabetabcdlJabcdabcdabcdWqGabcd">> = iolist_to_binary(re:replace("alphabetabcd","(?>.*)(?<=(abcd|wxyz))","&lJ\\1\\1\\1WqG\\1",[])),
+?line <<"alphabetabcdlJabcdabcdabcdWqGabcdlJabcdabcdabcdWqGabcd">> = iolist_to_binary(re:replace("alphabetabcd","(?>.*)(?<=(abcd|wxyz))","&lJ\\1\\1\\1WqG\\1",[global])),
+?line <<"Q">> = iolist_to_binary(re:replace("endingwxyz","(?>.*)(?<=(abcd|wxyz))","Q",[])),
+?line <<"QQ">> = iolist_to_binary(re:replace("endingwxyz","(?>.*)(?<=(abcd|wxyz))","Q",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?>.*)(?<=(abcd|wxyz))","qTAxLBxW",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?>.*)(?<=(abcd|wxyz))","qTAxLBxW",[global])),
+?line <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(re:replace("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))","HY\\1",[])),
+?line <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(re:replace("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))","HY\\1",[global])),
+?line <<"KxMJnKmSMrA">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword","\\1KxMJnKmSMrA",[])),
+?line <<"KxMJnKmSMrA">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword","\\1KxMJnKmSMrA",[global])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword","TgCuE\\1Jnjdu",[])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword","TgCuE\\1Jnjdu",[global])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword","HJa&S&CWDAtFOINp\\1a",[])),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(re:replace("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword","HJa&S&CWDAtFOINp\\1a",[global])),
+?line <<"999GSL">> = iolist_to_binary(re:replace("999foo","(?<=\\d{3}(?!999))foo","GSL",[])),
+?line <<"999GSL">> = iolist_to_binary(re:replace("999foo","(?<=\\d{3}(?!999))foo","GSL",[global])),
+?line <<"123999Lxthrb">> = iolist_to_binary(re:replace("123999foo","(?<=\\d{3}(?!999))foo","Lxthrb",[])),
+?line <<"123999Lxthrb">> = iolist_to_binary(re:replace("123999foo","(?<=\\d{3}(?!999))foo","Lxthrb",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=\\d{3}(?!999))foo","K&j&h&\\1&&&nJDCN",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=\\d{3}(?!999))foo","K&j&h&\\1&&&nJDCN",[global])),
+?line <<"123abcfoo">> = iolist_to_binary(re:replace("123abcfoo","(?<=\\d{3}(?!999))foo","Y&X\\1Oe",[])),
+?line <<"123abcfoo">> = iolist_to_binary(re:replace("123abcfoo","(?<=\\d{3}(?!999))foo","Y&X\\1Oe",[global])),
+?line <<"999lwxfoov">> = iolist_to_binary(re:replace("999foo","(?<=(?!...999)\\d{3})foo","lwx&v",[])),
+?line <<"999lwxfoov">> = iolist_to_binary(re:replace("999foo","(?<=(?!...999)\\d{3})foo","lwx&v",[global])),
+?line <<"123999MgNvfoofooIVfoo">> = iolist_to_binary(re:replace("123999foo","(?<=(?!...999)\\d{3})foo","MgNv\\1&&IV&",[])),
+?line <<"123999MgNvfoofooIVfoo">> = iolist_to_binary(re:replace("123999foo","(?<=(?!...999)\\d{3})foo","MgNv\\1&&IV&",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(?!...999)\\d{3})foo","Iv&\\1uOjnSDBEaj",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=(?!...999)\\d{3})foo","Iv&\\1uOjnSDBEaj",[global])),
+?line <<"123abcfoo">> = iolist_to_binary(re:replace("123abcfoo","(?<=(?!...999)\\d{3})foo","sanP",[])),
+?line <<"123abcfoo">> = iolist_to_binary(re:replace("123abcfoo","(?<=(?!...999)\\d{3})foo","sanP",[global])),
+?line <<"123abclcAPWfoouwtMfoofDv">> = iolist_to_binary(re:replace("123abcfoo","(?<=\\d{3}(?!999)...)foo","lcAPW&uwtM&fDv",[])),
+?line <<"123abclcAPWfoouwtMfoofDv">> = iolist_to_binary(re:replace("123abcfoo","(?<=\\d{3}(?!999)...)foo","lcAPW&uwtM&fDv",[global])),
+?line <<"123456nRFsuNto">> = iolist_to_binary(re:replace("123456foo","(?<=\\d{3}(?!999)...)foo","nR\\1FsuNto",[])),
+?line <<"123456nRFsuNto">> = iolist_to_binary(re:replace("123456foo","(?<=\\d{3}(?!999)...)foo","nR\\1FsuNto",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=\\d{3}(?!999)...)foo","P&",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=\\d{3}(?!999)...)foo","P&",[global])),
+?line <<"123999foo">> = iolist_to_binary(re:replace("123999foo","(?<=\\d{3}(?!999)...)foo","BdprjW\\1hk\\1K\\1",[])),
+?line <<"123999foo">> = iolist_to_binary(re:replace("123999foo","(?<=\\d{3}(?!999)...)foo","BdprjW\\1hk\\1K\\1",[global])),
+?line <<"123abcCB">> = iolist_to_binary(re:replace("123abcfoo","(?<=\\d{3}...)(?<!999)foo","CB",[])),
+?line <<"123abcCB">> = iolist_to_binary(re:replace("123abcfoo","(?<=\\d{3}...)(?<!999)foo","CB",[global])),
+?line <<"123456g">> = iolist_to_binary(re:replace("123456foo","(?<=\\d{3}...)(?<!999)foo","g",[])),
+?line <<"123456g">> = iolist_to_binary(re:replace("123456foo","(?<=\\d{3}...)(?<!999)foo","g",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=\\d{3}...)(?<!999)foo","\\1R&",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?<=\\d{3}...)(?<!999)foo","\\1R&",[global])),
+?line <<"123999foo">> = iolist_to_binary(re:replace("123999foo","(?<=\\d{3}...)(?<!999)foo","bh&&&M",[])),
+?line <<"123999foo">> = iolist_to_binary(re:replace("123999foo","(?<=\\d{3}...)(?<!999)foo","bh&&&M",[global])),
+?line <<"E<a href=abcd<a href=abcdTofh xyz">> = iolist_to_binary(re:replace("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","E&&Tofh",[caseless,
+ dotall,
+ extended])),
+?line <<"E<a href=abcd<a href=abcdTofh xyz">> = iolist_to_binary(re:replace("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","E&&Tofh",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"<a href=\"abcd xyz pqr\"Y\"ylaR<a href=\"abcd xyz pqr\"bd cats">> = iolist_to_binary(re:replace("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","&Y\\1ylaR&bd",[caseless,
+ dotall,
+ extended])),
+?line <<"<a href=\"abcd xyz pqr\"Y\"ylaR<a href=\"abcd xyz pqr\"bd cats">> = iolist_to_binary(re:replace("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","&Y\\1ylaR&bd",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"CdADwL<a href='abcd xyz pqr'b'a'' cats">> = iolist_to_binary(re:replace("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","CdADwL&b\\1a\\1\\1",[caseless,
+ dotall,
+ extended])),
+?line <<"CdADwL<a href='abcd xyz pqr'b'a'' cats">> = iolist_to_binary(re:replace("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","CdADwL&b\\1a\\1\\1",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"IAfhYumoIpB xyz">> = iolist_to_binary(re:replace("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","\\1\\1IAfhYumoIpB\\1",[caseless,
+ dotall,
+ extended])),
+?line <<"IAfhYumoIpB xyz">> = iolist_to_binary(re:replace("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","\\1\\1IAfhYumoIpB\\1",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"xrs<a href=\"abcd xyz pqr\"\"tWWeQ\"K<a href=\"abcd xyz pqr\"LjVA\"<a href=\"abcd xyz pqr\" cats">> = iolist_to_binary(re:replace("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","xrs&\\1tWWeQ\\1K&LjVA\\1&",[caseless,
+ dotall,
+ extended])),
+?line <<"xrs<a href=\"abcd xyz pqr\"\"tWWeQ\"K<a href=\"abcd xyz pqr\"LjVA\"<a href=\"abcd xyz pqr\" cats">> = iolist_to_binary(re:replace("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","xrs&\\1tWWeQ\\1K&LjVA\\1&",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"QmbmFDyYfP cats">> = iolist_to_binary(re:replace("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","QmbmFDyYfP",[caseless,
+ dotall,
+ extended])),
+?line <<"QmbmFDyYfP cats">> = iolist_to_binary(re:replace("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","QmbmFDyYfP",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"EokGsdLETK xyz">> = iolist_to_binary(re:replace("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","E\\1okGsdLETK",[caseless,
+ dotall,
+ extended])),
+?line <<"EokGsdLETK xyz">> = iolist_to_binary(re:replace("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","E\\1okGsdLETK",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"gy cats">> = iolist_to_binary(re:replace("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","gy",[caseless,
+ dotall,
+ extended])),
+?line <<"gy cats">> = iolist_to_binary(re:replace("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","gy",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"i cats">> = iolist_to_binary(re:replace("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","i",[caseless,
+ dotall,
+ extended])),
+?line <<"i cats">> = iolist_to_binary(re:replace("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space","i",[caseless,
+ dotall,
+ extended,
+ global])),
+?line <<"EZADCywAYhLBCDEFG">> = iolist_to_binary(re:replace("ZABCDEFG","((Z)+|A)*","E&DCywAYhL",[])),
+?line <<"EZADCywAYhLEDCywAYhLBEDCywAYhLCEDCywAYhLDEDCywAYhLEEDCywAYhLFEDCywAYhLGEDCywAYhL">> = iolist_to_binary(re:replace("ZABCDEFG","((Z)+|A)*","E&DCywAYhL",[global])),
+?line <<"nyATkLcbFTgYpAunmMXBCDEFG">> = iolist_to_binary(re:replace("ZABCDEFG","(Z()|A)*","ny\\1TkLcbFTgYp\\1unmMX",[])),
+?line <<"nyATkLcbFTgYpAunmMXnyTkLcbFTgYpunmMXBnyTkLcbFTgYpunmMXCnyTkLcbFTgYpunmMXDnyTkLcbFTgYpunmMXEnyTkLcbFTgYpunmMXFnyTkLcbFTgYpunmMXGnyTkLcbFTgYpunmMX">> = iolist_to_binary(re:replace("ZABCDEFG","(Z()|A)*","ny\\1TkLcbFTgYp\\1unmMX",[global])),
+?line <<"YiJonwVPQAqACHABCDEFG">> = iolist_to_binary(re:replace("ZABCDEFG","(Z(())|A)*","YiJonwVPQ\\1q\\1CH\\1",[])),
+?line <<"YiJonwVPQAqACHAYiJonwVPQqCHBYiJonwVPQqCHCYiJonwVPQqCHDYiJonwVPQqCHEYiJonwVPQqCHFYiJonwVPQqCHGYiJonwVPQqCH">> = iolist_to_binary(re:replace("ZABCDEFG","(Z(())|A)*","YiJonwVPQ\\1q\\1CH\\1",[global])),
+?line <<"bZAEAXcNeNwfBCDEFG">> = iolist_to_binary(re:replace("ZABCDEFG","((?>Z)+|A)*","b&E\\1XcNeNwf",[])),
+?line <<"bZAEAXcNeNwfbEXcNeNwfBbEXcNeNwfCbEXcNeNwfDbEXcNeNwfEbEXcNeNwfFbEXcNeNwfGbEXcNeNwf">> = iolist_to_binary(re:replace("ZABCDEFG","((?>Z)+|A)*","b&E\\1XcNeNwf",[global])),
+?line <<"UeIRbNvamSaniIQYPZABCDEFG">> = iolist_to_binary(re:replace("ZABCDEFG","((?>)+|A)*","UeIRbNv&amSaniIQYP",[])),
+?line <<"UeIRbNvamSaniIQYPZUeIRbNvamSaniIQYPUeIRbNvAamSaniIQYPUeIRbNvamSaniIQYPBUeIRbNvamSaniIQYPCUeIRbNvamSaniIQYPDUeIRbNvamSaniIQYPEUeIRbNvamSaniIQYPFUeIRbNvamSaniIQYPGUeIRbNvamSaniIQYP">> = iolist_to_binary(re:replace("ZABCDEFG","((?>)+|A)*","UeIRbNv&amSaniIQYP",[global])),
+?line <<"AnyTcLbbab">> = iolist_to_binary(re:replace("abbab","a*","AnyTcL",[])),
+?line <<"AnyTcLAnyTcLbAnyTcLbAnyTcLAnyTcLbAnyTcL">> = iolist_to_binary(re:replace("abbab","a*","AnyTcL",[global])),
+?line <<"EfoRdQVibcde">> = iolist_to_binary(re:replace("abcde","^[a-\\d]","EfoRdQVi",[])),
+?line <<"EfoRdQVibcde">> = iolist_to_binary(re:replace("abcde","^[a-\\d]","EfoRdQVi",[global])),
+?line <<"cCcyoUi-GT--drpjthings">> = iolist_to_binary(re:replace("-things","^[a-\\d]","cCcy\\1o\\1Ui&GT&&drpj",[])),
+?line <<"cCcyoUi-GT--drpjthings">> = iolist_to_binary(re:replace("-things","^[a-\\d]","cCcy\\1o\\1Ui&GT&&drpj",[global])),
+?line <<"eddigit">> = iolist_to_binary(re:replace("0digit","^[a-\\d]","ed",[])),
+?line <<"eddigit">> = iolist_to_binary(re:replace("0digit","^[a-\\d]","ed",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[a-\\d]","\\1wCjwyJQB&COO&&Vyp\\1M",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[a-\\d]","\\1wCjwyJQB&COO&&Vyp\\1M",[global])),
+?line <<"bcdef">> = iolist_to_binary(re:replace("bcdef","^[a-\\d]","EofOaus",[])),
+?line <<"bcdef">> = iolist_to_binary(re:replace("bcdef","^[a-\\d]","EofOaus",[global])),
+?line <<"kpGVaTiadJVwJbcde">> = iolist_to_binary(re:replace("abcde","^[\\d-a]","kpG\\1V&Ti&dJVwJ",[])),
+?line <<"kpGVaTiadJVwJbcde">> = iolist_to_binary(re:replace("abcde","^[\\d-a]","kpG\\1V&Ti&dJVwJ",[global])),
+?line <<"Chuymdqthings">> = iolist_to_binary(re:replace("-things","^[\\d-a]","Chu\\1ymdq",[])),
+?line <<"Chuymdqthings">> = iolist_to_binary(re:replace("-things","^[\\d-a]","Chu\\1ymdq",[global])),
+?line <<"TpWPVwVtHJWAdigit">> = iolist_to_binary(re:replace("0digit","^[\\d-a]","TpWPVwV\\1tHJWA",[])),
+?line <<"TpWPVwVtHJWAdigit">> = iolist_to_binary(re:replace("0digit","^[\\d-a]","TpWPVwV\\1tHJWA",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[\\d-a]","WYnxbwypPj",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^[\\d-a]","WYnxbwypPj",[global])),
+?line <<"bcdef">> = iolist_to_binary(re:replace("bcdef","^[\\d-a]","bnUSwwhPJ",[])),
+?line <<"bcdef">> = iolist_to_binary(re:replace("bcdef","^[\\d-a]","bnUSwwhPJ",[global])),
+?line <<">mnfN
+ FbS
+ ghxwsq<">> = iolist_to_binary(re:replace(">
+ <","[[:space:]]+","mnfN&FbS\\1&ghxwsq",[])),
+?line <<">mnfN
+ FbS
+ ghxwsq<">> = iolist_to_binary(re:replace(">
+ <","[[:space:]]+","mnfN&FbS\\1&ghxwsq",[global])),
+?line <<">R
+ <">> = iolist_to_binary(re:replace(">
+ <","[[:blank:]]+","R",[])),
+?line <<">R
+ <">> = iolist_to_binary(re:replace(">
+ <","[[:blank:]]+","R",[global])),
+?line <<">PjX
+ <">> = iolist_to_binary(re:replace(">
+ <","[\\s]+","PjX&",[])),
+?line <<">PjX
+ <">> = iolist_to_binary(re:replace(">
+ <","[\\s]+","PjX&",[global])),
+?line <<">EO
+
+ g
+
+ DMTFYSd
+ <">> = iolist_to_binary(re:replace(">
+ <","\\s+","EO&&g&&DMT\\1FY\\1Sd&",[])),
+?line <<">EO
+
+ g
+
+ DMTFYSd
+ <">> = iolist_to_binary(re:replace(">
+ <","\\s+","EO&&g&&DMT\\1FY\\1Sd&",[global])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","a b","qihC&Vy",[extended])),
+?line <<"ab">> = iolist_to_binary(re:replace("ab","a b","qihC&Vy",[extended,
+ global])),
+?line <<"a
+dxmjb">> = iolist_to_binary(re:replace("a
+xb","(?!\\A)x","dxmj",[multiline])),
+?line <<"a
+dxmjb">> = iolist_to_binary(re:replace("a
+xb","(?!\\A)x","dxmj",[multiline,global])),
+?line <<"a
+xb">> = iolist_to_binary(re:replace("a
+xb","(?!^)x","\\1tysI\\1v\\1BVwx\\1FOWG\\1&C",[multiline])),
+?line <<"a
+xb">> = iolist_to_binary(re:replace("a
+xb","(?!^)x","\\1tysI\\1v\\1BVwx\\1FOWG\\1&C",[multiline,
+ global])),
+?line <<"MYdx">> = iolist_to_binary(re:replace("abcabcabc","abc\\Qabc\\Eabc","MYdx",[])),
+?line <<"MYdx">> = iolist_to_binary(re:replace("abcabcabc","abc\\Qabc\\Eabc","MYdx",[global])),
+?line <<"abc(*+|abc">> = iolist_to_binary(re:replace("abc(*+|abc","abc\\Q(*+|\\Eabc","&",[])),
+?line <<"abc(*+|abc">> = iolist_to_binary(re:replace("abc(*+|abc","abc\\Q(*+|\\Eabc","&",[global])),
+?line <<"abc abcabcbMSm">> = iolist_to_binary(re:replace("abc abcabc"," abc\\Q abc\\Eabc","&bM\\1Sm",[extended])),
+?line <<"abc abcabcbMSm">> = iolist_to_binary(re:replace("abc abcabc"," abc\\Q abc\\Eabc","&bM\\1Sm",[extended,
+ global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers"," abc\\Q abc\\Eabc","X\\1\\1&",[extended])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers"," abc\\Q abc\\Eabc","X\\1\\1&",[extended,
+ global])),
+?line <<"abcabcabc">> = iolist_to_binary(re:replace("abcabcabc"," abc\\Q abc\\Eabc","qbvwJpk",[extended])),
+?line <<"abcabcabc">> = iolist_to_binary(re:replace("abcabcabc"," abc\\Q abc\\Eabc","qbvwJpk",[extended,
+ global])),
+?line <<"UwGaabc#not comment
+ literal">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E","UwGa&",[extended])),
+?line <<"UwGaabc#not comment
+ literal">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E","UwGa&",[extended,global])),
+?line <<"abc#not comment
+ literalSCWsV">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal","&\\1SCWsV",[extended])),
+?line <<"abc#not comment
+ literalSCWsV">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal","&\\1SCWsV",[extended,global])),
+?line <<"T">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment
+ ","T",[extended])),
+?line <<"T">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment
+ ","T",[extended,global])),
+?line <<"abc#not comment
+ literalDFMabc#not comment
+ literaliRRuHyq">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment","&DFM&\\1\\1iR\\1RuHy\\1q",[extended])),
+?line <<"abc#not comment
+ literalDFMabc#not comment
+ literaliRRuHyq">> = iolist_to_binary(re:replace("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment","&DFM&\\1\\1iR\\1RuHy\\1q",[extended,
+ global])),
+?line <<"DkSX">> = iolist_to_binary(re:replace("abc\\$xyz","\\Qabc\\$xyz\\E","DkSX",[])),
+?line <<"DkSX">> = iolist_to_binary(re:replace("abc\\$xyz","\\Qabc\\$xyz\\E","DkSX",[global])),
+?line <<"EOaWuCabc$xyzeabc$xyzDrvLP">> = iolist_to_binary(re:replace("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E","EOaWuC&e&DrvLP",[])),
+?line <<"EOaWuCabc$xyzeabc$xyzDrvLP">> = iolist_to_binary(re:replace("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E","EOaWuC&e&DrvLP",[global])),
+?line <<"PUGabcRXWXakpQfboabcw">> = iolist_to_binary(re:replace("abc","\\Gabc","P\\1UG&R\\1XWXakpQfbo&w",[])),
+?line <<"PUGabcRXWXakpQfboabcw">> = iolist_to_binary(re:replace("abc","\\Gabc","P\\1UG&R\\1XWXakpQfbo&w",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\Gabc","sssS\\1AVaXM&Is&c",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","\\Gabc","sssS\\1AVaXM&Is&c",[global])),
+?line <<"xyzabc">> = iolist_to_binary(re:replace("xyzabc","\\Gabc","r",[])),
+?line <<"xyzabc">> = iolist_to_binary(re:replace("xyzabc","\\Gabc","r",[global])),
+?line <<"tScabc2xyzabc3">> = iolist_to_binary(re:replace("abc1abc2xyzabc3","\\Gabc.","tSc",[])),
+?line <<"tSctScxyzabc3">> = iolist_to_binary(re:replace("abc1abc2xyzabc3","\\Gabc.","tSc",[global])),
+?line <<"HcJuuopHFgbabc2xyzabc3">> = iolist_to_binary(re:replace("abc1abc2xyzabc3","abc.","HcJuuopHFgb",[])),
+?line <<"HcJuuopHFgbHcJuuopHFgbxyzHcJuuopHFgb">> = iolist_to_binary(re:replace("abc1abc2xyzabc3","abc.","HcJuuopHFgb",[global])),
+?line <<"XMSabcdaDVucwrGDirY">> = iolist_to_binary(re:replace("XabcdY","a(?x: b c )d","MS&aDVucwrGDir",[])),
+?line <<"XMSabcdaDVucwrGDirY">> = iolist_to_binary(re:replace("XabcdY","a(?x: b c )d","MS&aDVucwrGDir",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?x: b c )d","paXT\\1iPxaNPv",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","a(?x: b c )d","paXT\\1iPxaNPv",[global])),
+?line <<"Xa b c d Y">> = iolist_to_binary(re:replace("Xa b c d Y","a(?x: b c )d","FRHntJTvUtt&w\\1Mhj",[])),
+?line <<"Xa b c d Y">> = iolist_to_binary(re:replace("Xa b c d Y","a(?x: b c )d","FRHntJTvUtt&w\\1Mhj",[global])),
+?line <<"XabcY">> = iolist_to_binary(re:replace("XabcY","((?x)x y z | a b c)","\\1",[])),
+?line <<"XabcY">> = iolist_to_binary(re:replace("XabcY","((?x)x y z | a b c)","\\1",[global])),
+?line <<"AxyznocmxyzxyzxyzfWlBxyzPEB">> = iolist_to_binary(re:replace("AxyzB","((?x)x y z | a b c)","&nocm\\1&&fWlB&PE",[])),
+?line <<"AxyznocmxyzxyzxyzfWlBxyzPEB">> = iolist_to_binary(re:replace("AxyzB","((?x)x y z | a b c)","&nocm\\1&&fWlB&PE",[global])),
+?line <<"XqabCRwY">> = iolist_to_binary(re:replace("XabCY","(?i)AB(?-i)C","q&Rw",[])),
+?line <<"XqabCRwY">> = iolist_to_binary(re:replace("XabCY","(?i)AB(?-i)C","q&Rw",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?i)AB(?-i)C","b&Qx",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","(?i)AB(?-i)C","b&Qx",[global])),
+?line <<"XabcY">> = iolist_to_binary(re:replace("XabcY","(?i)AB(?-i)C","VjuCQPxKgGiffeGDHugc",[])),
+?line <<"XabcY">> = iolist_to_binary(re:replace("XabcY","(?i)AB(?-i)C","VjuCQPxKgGiffeGDHugc",[global])),
+?line <<"abCEApGxObicabCaHXabCib">> = iolist_to_binary(re:replace("abCE","((?i)AB(?-i)C|D)E","&ApGxObic\\1aHX\\1ib",[])),
+?line <<"abCEApGxObicabCaHXabCib">> = iolist_to_binary(re:replace("abCE","((?i)AB(?-i)C|D)E","&ApGxObic\\1aHX\\1ib",[global])),
+?line <<"uLoeOQwJDEyFGS">> = iolist_to_binary(re:replace("DE","((?i)AB(?-i)C|D)E","uLoeOQwJ&yFGS",[])),
+?line <<"uLoeOQwJDEyFGS">> = iolist_to_binary(re:replace("DE","((?i)AB(?-i)C|D)E","uLoeOQwJ&yFGS",[global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?i)AB(?-i)C|D)E","LUuqtYqP\\1RCe",[])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","((?i)AB(?-i)C|D)E","LUuqtYqP\\1RCe",[global])),
+?line <<"abcE">> = iolist_to_binary(re:replace("abcE","((?i)AB(?-i)C|D)E","\\1sXBxu\\1",[])),
+?line <<"abcE">> = iolist_to_binary(re:replace("abcE","((?i)AB(?-i)C|D)E","\\1sXBxu\\1",[global])),
+?line <<"abCe">> = iolist_to_binary(re:replace("abCe","((?i)AB(?-i)C|D)E","Y\\1Rgo\\1Ican\\1",[])),
+?line <<"abCe">> = iolist_to_binary(re:replace("abCe","((?i)AB(?-i)C|D)E","Y\\1Rgo\\1Ican\\1",[global])),
+?line <<"dE">> = iolist_to_binary(re:replace("dE","((?i)AB(?-i)C|D)E","XvslPbYV&&PG",[])),
+?line <<"dE">> = iolist_to_binary(re:replace("dE","((?i)AB(?-i)C|D)E","XvslPbYV&&PG",[global])),
+?line <<"De">> = iolist_to_binary(re:replace("De","((?i)AB(?-i)C|D)E","Ry\\1rxj\\1\\1dTXtld&\\1D&&",[])),
+?line <<"De">> = iolist_to_binary(re:replace("De","((?i)AB(?-i)C|D)E","Ry\\1rxj\\1\\1dTXtld&\\1D&&",[global])),
+?line <<"vyabc">> = iolist_to_binary(re:replace("abc123abc","(.*)\\d+\\1","vy\\1",[])),
+?line <<"vyabc">> = iolist_to_binary(re:replace("abc123abc","(.*)\\d+\\1","vy\\1",[global])),
+?line <<"aLB">> = iolist_to_binary(re:replace("abc123bc","(.*)\\d+\\1","LB",[])),
+?line <<"aLB">> = iolist_to_binary(re:replace("abc123bc","(.*)\\d+\\1","LB",[global])),
+?line <<"HXjXabc123abcabc123abcabc123abcfUay">> = iolist_to_binary(re:replace("abc123abc","(.*)\\d+\\1","HXjX&&&fUay",[dotall])),
+?line <<"HXjXabc123abcabc123abcabc123abcfUay">> = iolist_to_binary(re:replace("abc123abc","(.*)\\d+\\1","HXjX&&&fUay",[dotall,
+ global])),
+?line <<"aCRabcRSbc123bcbcokUUyuMbc123bcm">> = iolist_to_binary(re:replace("abc123bc","(.*)\\d+\\1","CRa\\1RS&\\1okUUyuM&m",[dotall])),
+?line <<"aCRabcRSbc123bcbcokUUyuMbc123bcm">> = iolist_to_binary(re:replace("abc123bc","(.*)\\d+\\1","CRa\\1RS&\\1okUUyuM&m",[dotall,
+ global])),
+?line <<"RvRabcJIYH">> = iolist_to_binary(re:replace("abc123abc","((.*))\\d+\\1","RvR\\1JIYH",[])),
+?line <<"RvRabcJIYH">> = iolist_to_binary(re:replace("abc123abc","((.*))\\d+\\1","RvR\\1JIYH",[global])),
+?line <<"aRbc123bcmb">> = iolist_to_binary(re:replace("abc123bc","((.*))\\d+\\1","R&mb",[])),
+?line <<"aRbc123bcmb">> = iolist_to_binary(re:replace("abc123bc","((.*))\\d+\\1","R&mb",[global])),
+?line <<"ET">> = iolist_to_binary(re:replace("a123::a123","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","ET",[extended,caseless])),
+?line <<"ET">> = iolist_to_binary(re:replace("a123::a123","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","ET",[extended,caseless,global])),
+?line <<"nYalhKtcGgINbn">> = iolist_to_binary(re:replace("a123:b342::abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","nY\\1a\\1lhKtcGgINbn",[extended,caseless])),
+?line <<"nYalhKtcGgINbn">> = iolist_to_binary(re:replace("a123:b342::abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","nY\\1a\\1lhKtcGgINbn",[extended,caseless,global])),
+?line <<"xeKa123:b342::324e:abcdRvTn">> = iolist_to_binary(re:replace("a123:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","xeK&RvTn",[extended,caseless])),
+?line <<"xeKa123:b342::324e:abcdRvTn">> = iolist_to_binary(re:replace("a123:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","xeK&RvTn",[extended,caseless,global])),
+?line <<"JHrQJuCtAvAt">> = iolist_to_binary(re:replace("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","JHrQJuCtAvAt",[extended,caseless])),
+?line <<"JHrQJuCtAvAt">> = iolist_to_binary(re:replace("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","JHrQJuCtAvAt",[extended,caseless,global])),
+?line <<"IphCja">> = iolist_to_binary(re:replace("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","Ip\\1hCj\\1a",[extended,caseless])),
+?line <<"IphCja">> = iolist_to_binary(re:replace("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","Ip\\1hCj\\1a",[extended,caseless,global])),
+?line <<"xAGmJctxEa123:ddde:9999:b342::324e:dcba:abcdjhClw">> = iolist_to_binary(re:replace("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","xAGmJctxE&jhClw",[extended,caseless])),
+?line <<"xAGmJctxEa123:ddde:9999:b342::324e:dcba:abcdjhClw">> = iolist_to_binary(re:replace("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","xAGmJctxE&jhClw",[extended,caseless,global])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","&jc&",[extended,caseless])),
+?line <<"*** Failers">> = iolist_to_binary(re:replace("*** Failers","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","&jc&",[extended,caseless,global])),
+?line <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(re:replace("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","xjuUU",[extended,caseless])),
+?line <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(re:replace("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","xjuUU",[extended,caseless,global])),
+?line <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(re:replace("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","&\\1gJElxfvxu\\1ly",[extended,caseless])),
+?line <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(re:replace("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","&\\1gJElxfvxu\\1ly",[extended,caseless,global])),
+?line <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(re:replace("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","r",[extended,caseless])),
+?line <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(re:replace("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","r",[extended,caseless,global])),
+?line <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(re:replace("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","\\1",[extended,caseless])),
+?line <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(re:replace("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","\\1",[extended,caseless,global])),
+?line <<"::1">> = iolist_to_binary(re:replace("::1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","ymau\\1\\1NVl\\1WdO\\1",[extended,caseless])),
+?line <<"::1">> = iolist_to_binary(re:replace("::1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","ymau\\1\\1NVl\\1WdO\\1",[extended,caseless,global])),
+?line <<"abcd:fee0:123::">> = iolist_to_binary(re:replace("abcd:fee0:123::","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","KUuIBK&Px&",[extended,caseless])),
+?line <<"abcd:fee0:123::">> = iolist_to_binary(re:replace("abcd:fee0:123::","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","KUuIBK&Px&",[extended,caseless,global])),
+?line <<":1">> = iolist_to_binary(re:replace(":1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","&BwNOmaJ\\1M&\\1TUCr",[extended,caseless])),
+?line <<":1">> = iolist_to_binary(re:replace(":1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","&BwNOmaJ\\1M&\\1TUCr",[extended,caseless,global])),
+?line <<"1:">> = iolist_to_binary(re:replace("1:","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","J\\1e\\1",[extended,caseless])),
+?line <<"1:">> = iolist_to_binary(re:replace("1:","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ","J\\1e\\1",[extended,caseless,global])),
+?line <<"XpmPL">> = iolist_to_binary(re:replace("z","[z\\Qa-d]\\E]","\\1X\\1pmPL",[])),
+?line <<"XpmPL">> = iolist_to_binary(re:replace("z","[z\\Qa-d]\\E]","\\1X\\1pmPL",[global])),
+?line <<"WrMTefTPBbaVhaDwab">> = iolist_to_binary(re:replace("a","[z\\Qa-d]\\E]","WrMTefTPBb&V\\1\\1h&Dwab",[])),
+?line <<"WrMTefTPBbaVhaDwab">> = iolist_to_binary(re:replace("a","[z\\Qa-d]\\E]","WrMTefTPBb&V\\1\\1h&Dwab",[global])),
+?line <<"uFU-rQ-">> = iolist_to_binary(re:replace("-","[z\\Qa-d]\\E]","uFU\\1\\1&rQ&",[])),
+?line <<"uFU-rQ-">> = iolist_to_binary(re:replace("-","[z\\Qa-d]\\E]","uFU\\1\\1&rQ&",[global])),
+?line <<"QliOKMpfH">> = iolist_to_binary(re:replace("d","[z\\Qa-d]\\E]","QliOKMpfH",[])),
+?line <<"QliOKMpfH">> = iolist_to_binary(re:replace("d","[z\\Qa-d]\\E]","QliOKMpfH",[global])),
+?line <<"t]KdBE">> = iolist_to_binary(re:replace("]","[z\\Qa-d]\\E]","t\\1&KdBE",[])),
+?line <<"t]KdBE">> = iolist_to_binary(re:replace("]","[z\\Qa-d]\\E]","t\\1&KdBE",[global])),
+?line <<"*** FNavaiaOJGqPkBilers">> = iolist_to_binary(re:replace("*** Failers","[z\\Qa-d]\\E]","N&v&i\\1&OJGqPkB",[])),
+?line <<"*** FNavaiaOJGqPkBilers">> = iolist_to_binary(re:replace("*** Failers","[z\\Qa-d]\\E]","N&v&i\\1&OJGqPkB",[global])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[z\\Qa-d]\\E]","R&ba",[])),
+?line <<"b">> = iolist_to_binary(re:replace("b","[z\\Qa-d]\\E]","R&ba",[global])),
+?line <<"sjzSKziFAAJiTVWC">> = iolist_to_binary(re:replace("z","[\\z\\C]","sj&SK&iFAAJiTVWC",[])),
+?line <<"sjzSKziFAAJiTVWC">> = iolist_to_binary(re:replace("z","[\\z\\C]","sj&SK&iFAAJiTVWC",[global])),
+?line <<"DDSbIgCmsBKCTmEuitn">> = iolist_to_binary(re:replace("C","[\\z\\C]","D\\1DSbIg&msBK&TmEuitn",[])),
+?line <<"DDSbIgCmsBKCTmEuitn">> = iolist_to_binary(re:replace("C","[\\z\\C]","D\\1DSbIg&msBK&TmEuitn",[global])),
+?line <<"cY">> = iolist_to_binary(re:replace("M","\\M","cY\\1",[])),
+?line <<"cY">> = iolist_to_binary(re:replace("M","\\M","cY\\1",[global])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b","yWOTIFhIX\\1H",[])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b","yWOTIFhIX\\1H",[global])),
+?line <<"NREGularsRREGularWEYrVRr">> = iolist_to_binary(re:replace("REGular","(?i)reg(?:ul(?:[a�]|ae)r|ex)","N&\\1sR&WEYrVRr",[])),
+?line <<"NREGularsRREGularWEYrVRr">> = iolist_to_binary(re:replace("REGular","(?i)reg(?:ul(?:[a�]|ae)r|ex)","N&\\1sR&WEYrVRr",[global])),
+?line <<"G">> = iolist_to_binary(re:replace("regulaer","(?i)reg(?:ul(?:[a�]|ae)r|ex)","G",[])),
+?line <<"G">> = iolist_to_binary(re:replace("regulaer","(?i)reg(?:ul(?:[a�]|ae)r|ex)","G",[global])),
+?line <<"PSsXtwlmy">> = iolist_to_binary(re:replace("Regex","(?i)reg(?:ul(?:[a�]|ae)r|ex)","PSsXtwlmy",[])),
+?line <<"PSsXtwlmy">> = iolist_to_binary(re:replace("Regex","(?i)reg(?:ul(?:[a�]|ae)r|ex)","PSsXtwlmy",[global])),
+?line <<"regul�rmiYTi">> = iolist_to_binary(re:replace("regul�r","(?i)reg(?:ul(?:[a�]|ae)r|ex)","&miYTi\\1\\1",[])),
+?line <<"regul�rmiYTi">> = iolist_to_binary(re:replace("regul�r","(?i)reg(?:ul(?:[a�]|ae)r|ex)","&miYTi\\1\\1",[global])),
+?line <<"W�����rxh�����yUoaLOIegmSA">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","W&rxh&yUoaL\\1OIegmS\\1A",[])),
+?line <<"W�����rxh�����yUoaLOIegmSA">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","W&rxh&yUoaL\\1OIegmS\\1A",[global])),
+?line <<"F�����gnWPyHeh�����tXTQ">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","F&gnWPyHe\\1h&tXTQ",[])),
+?line <<"F�����gnWPyHeh�����tXTQ">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","F&gnWPyHe\\1h&tXTQ",[global])),
+?line <<"sHerHnAhAdx">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","sHer\\1HnA\\1h\\1Adx",[])),
+?line <<"sHerHnAhAdx">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","sHer\\1HnA\\1h\\1Adx",[global])),
+?line <<"trobAQoU�����n">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","tr\\1obAQoU&n",[])),
+?line <<"trobAQoU�����n">> = iolist_to_binary(re:replace("�����","����[�-��-�]+","tr\\1obAQoU&n",[global])),
+?line <<"�XAZSd">> = iolist_to_binary(re:replace("�XAZXB","(?<=Z)X.","Sd",[])),
+?line <<"�XAZSd">> = iolist_to_binary(re:replace("�XAZXB","(?<=Z)X.","Sd",[global])),
+?line <<"A">> = iolist_to_binary(re:replace("ab cd defg","ab cd (?x) de fg","\\1A\\1",[])),
+?line <<"A">> = iolist_to_binary(re:replace("ab cd defg","ab cd (?x) de fg","\\1A\\1",[global])),
+?line <<"fab cddefgLdtKCtPab cddefgxvVUHDah">> = iolist_to_binary(re:replace("ab cddefg","ab cd(?x) de fg","f&LdtKC\\1\\1tP&xvVUHDah",[])),
+?line <<"fab cddefgLdtKCtPab cddefgxvVUHDah">> = iolist_to_binary(re:replace("ab cddefg","ab cd(?x) de fg","f&LdtKC\\1\\1tP&xvVUHDah",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","ab cd(?x) de fg","BkO\\1dl&WNuvnGhG&Qkl",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","ab cd(?x) de fg","BkO\\1dl&WNuvnGhG&Qkl",[global])),
+?line <<"abcddefg">> = iolist_to_binary(re:replace("abcddefg","ab cd(?x) de fg","SCJx&",[])),
+?line <<"abcddefg">> = iolist_to_binary(re:replace("abcddefg","ab cd(?x) de fg","SCJx&",[global])),
+?line <<"foobarLvX">> = iolist_to_binary(re:replace("foobarX","(?<![^f]oo)(bar)","&Lv",[])),
+?line <<"foobarLvX">> = iolist_to_binary(re:replace("foobarX","(?<![^f]oo)(bar)","&Lv",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?<![^f]oo)(bar)","dRgI\\1vl\\1\\1eC\\1RFQ",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?<![^f]oo)(bar)","dRgI\\1vl\\1\\1eC\\1RFQ",[global])),
+?line <<"boobarX">> = iolist_to_binary(re:replace("boobarX","(?<![^f]oo)(bar)","p\\1CU&Q",[])),
+?line <<"boobarX">> = iolist_to_binary(re:replace("boobarX","(?<![^f]oo)(bar)","p\\1CU&Q",[global])),
+?line <<"offmnXHaaIXuXOo">> = iolist_to_binary(re:replace("offX","(?<![^f])X","m\\1n&HaaI\\1&u&Oo",[])),
+?line <<"offmnXHaaIXuXOo">> = iolist_to_binary(re:replace("offX","(?<![^f])X","m\\1n&HaaI\\1&u&Oo",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?<![^f])X","jhEwy&w\\1mfw\\1H&VBqnX\\1",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?<![^f])X","jhEwy&w\\1mfw\\1H&VBqnX\\1",[global])),
+?line <<"onyX">> = iolist_to_binary(re:replace("onyX","(?<![^f])X","HyLQEU",[])),
+?line <<"onyX">> = iolist_to_binary(re:replace("onyX","(?<![^f])X","HyLQEU",[global])),
+?line <<"onyNXN">> = iolist_to_binary(re:replace("onyX","(?<=[^f])X","\\1N&N",[])),
+?line <<"onyNXN">> = iolist_to_binary(re:replace("onyX","(?<=[^f])X","\\1N&N",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?<=[^f])X","kt&SCnnaVhTlQMnFltwd",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?<=[^f])X","kt&SCnnaVhTlQMnFltwd",[global])),
+?line <<"offX">> = iolist_to_binary(re:replace("offX","(?<=[^f])X","UsgnsEG\\1LX&DB",[])),
+?line <<"offX">> = iolist_to_binary(re:replace("offX","(?<=[^f])X","UsgnsEG\\1LX&DB",[global])),
+?line <<"FOjOAeQxFFXja
+b
+c">> = iolist_to_binary(re:replace("a
+b
+c","^","FOjO&AeQx&FFXj",[multiline])),
+?line <<"FOjOAeQxFFXja
+FOjOAeQxFFXjb
+FOjOAeQxFFXjc">> = iolist_to_binary(re:replace("a
+b
+c","^","FOjO&AeQx&FFXj",[multiline,global])),
+?line <<"jwIVfrtVCpnVwNgju">> = iolist_to_binary(re:replace("","^","jwIVfrtVCpnVwNgju",[multiline])),
+?line <<"jwIVfrtVCpnVwNgju">> = iolist_to_binary(re:replace("","^","jwIVfrtVCpnVwNgju",[multiline,
+ global])),
+?line <<"A
+C
+sugWOwdKBC">> = iolist_to_binary(re:replace("A
+C
+C","(?<=C\\n)^","sugW&&\\1OwdKB",[multiline])),
+?line <<"A
+C
+sugWOwdKBC">> = iolist_to_binary(re:replace("A
+C
+C","(?<=C\\n)^","sugW&&\\1OwdKB",[multiline,global])),
+?line <<"dIXQXEThebXaXXcLjA">> = iolist_to_binary(re:replace("bXaX","(?:(?(1)a|b)(X))+","dI\\1Q\\1EThe&\\1cLjA",[])),
+?line <<"dIXQXEThebXaXXcLjA">> = iolist_to_binary(re:replace("bXaX","(?:(?(1)a|b)(X))+","dI\\1Q\\1EThe&\\1cLjA",[global])),
+?line <<"AvB">> = iolist_to_binary(re:replace("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+","AvB",[])),
+?line <<"AvB">> = iolist_to_binary(re:replace("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+","AvB",[global])),
+?line <<"EHtFjtbXESMPhXXYaXXaX">> = iolist_to_binary(re:replace("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+","EHtFjt&ESMPh\\1\\1",[])),
+?line <<"EHtFjtbXESMPhXXYaXXaX">> = iolist_to_binary(re:replace("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+","EHtFjt&ESMPh\\1\\1",[global])),
+?line <<"bXnIbjbXebXpecwXaYYaY">> = iolist_to_binary(re:replace("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+","&nIbj&\\1e&p\\1ecw",[])),
+?line <<"bXnIbjbXebXpecwXaYYaY">> = iolist_to_binary(re:replace("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+","&nIbj&\\1e&p\\1ecw",[global])),
+?line <<"Fv">> = iolist_to_binary(re:replace("abc]","[[,abc,]+]","Fv",[])),
+?line <<"Fv">> = iolist_to_binary(re:replace("abc]","[[,abc,]+]","Fv",[global])),
+?line <<"enGFFPGa,b]Vq">> = iolist_to_binary(re:replace("a,b]","[[,abc,]+]","enGFFPG&V\\1q",[])),
+?line <<"enGFFPGa,b]Vq">> = iolist_to_binary(re:replace("a,b]","[[,abc,]+]","enGFFPG&V\\1q",[global])),
+?line <<"SLU[a,b,c]KDFqnru[a,b,c]">> = iolist_to_binary(re:replace("[a,b,c]","[[,abc,]+]","SLU&KDFqnru&",[])),
+?line <<"SLU[a,b,c]KDFqnru[a,b,c]">> = iolist_to_binary(re:replace("[a,b,c]","[[,abc,]+]","SLU&KDFqnru&",[global])),
+?line <<"AFHpB">> = iolist_to_binary(re:replace("A B","(?-x: )","FHp",[extended])),
+?line <<"AFHpB">> = iolist_to_binary(re:replace("A B","(?-x: )","FHp",[extended,
+ global])),
+?line <<"ALptWSVme # # # B">> = iolist_to_binary(re:replace("A # B","(?x)(?-x: \\s*#\\s*)","LptWSVme&\\1&&",[])),
+?line <<"ALptWSVme # # # B">> = iolist_to_binary(re:replace("A # B","(?x)(?-x: \\s*#\\s*)","LptWSVme&\\1&&",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?x)(?-x: \\s*#\\s*)","enBY&\\1vE&\\1I\\1IhttjD\\1",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?x)(?-x: \\s*#\\s*)","enBY&\\1vE&\\1I\\1IhttjD\\1",[global])),
+?line <<"#">> = iolist_to_binary(re:replace("#","(?x)(?-x: \\s*#\\s*)","c\\1cwAsih",[])),
+?line <<"#">> = iolist_to_binary(re:replace("#","(?x)(?-x: \\s*#\\s*)","c\\1cwAsih",[global])),
+?line <<"ARtjg #includeDy #includeg">> = iolist_to_binary(re:replace("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include","Rtjg&Dy\\1&g",[])),
+?line <<"ARtjg #includeDy #includeg">> = iolist_to_binary(re:replace("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include","Rtjg&Dy\\1&g",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include","tYbpFaHd\\1GjcqHIWx\\1a",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include","tYbpFaHd\\1GjcqHIWx\\1a",[global])),
+?line <<"A#include">> = iolist_to_binary(re:replace("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include","nv\\1tgvlSHVHyKOMPNXVF",[])),
+?line <<"A#include">> = iolist_to_binary(re:replace("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include","nv\\1tgvlSHVHyKOMPNXVF",[global])),
+?line <<"A #Include">> = iolist_to_binary(re:replace("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include","kQdv",[])),
+?line <<"A #Include">> = iolist_to_binary(re:replace("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include","kQdv",[global])),
+?line <<"k">> = iolist_to_binary(re:replace("aaabbbb","a*b*\\w","k",[])),
+?line <<"k">> = iolist_to_binary(re:replace("aaabbbb","a*b*\\w","k",[global])),
+?line <<"xaaaa">> = iolist_to_binary(re:replace("aaaa","a*b*\\w","x&",[])),
+?line <<"xaaaa">> = iolist_to_binary(re:replace("aaaa","a*b*\\w","x&",[global])),
+?line <<"pOagbKtJauauHwjM">> = iolist_to_binary(re:replace("a","a*b*\\w","pOagbKtJ&uauHwjM",[])),
+?line <<"pOagbKtJauauHwjM">> = iolist_to_binary(re:replace("a","a*b*\\w","pOagbKtJ&uauHwjM",[global])),
+?line <<"tuaaabbPhWfMuDrCJEUabb">> = iolist_to_binary(re:replace("aaabbbb","a*b?\\w","tu&P\\1h\\1WfMu\\1Dr\\1CJEUa",[])),
+?line <<"tuaaabbPhWfMuDrCJEUatubbPhWfMuDrCJEUa">> = iolist_to_binary(re:replace("aaabbbb","a*b?\\w","tu&P\\1h\\1WfMu\\1Dr\\1CJEUa",[global])),
+?line <<"heEGaaaabPJaaaaaaaakUYNXaaaasNCaaaa">> = iolist_to_binary(re:replace("aaaa","a*b?\\w","heEG&bPJ&&kUYNX&sNC&",[])),
+?line <<"heEGaaaabPJaaaaaaaakUYNXaaaasNCaaaa">> = iolist_to_binary(re:replace("aaaa","a*b?\\w","heEG&bPJ&&kUYNX&sNC&",[global])),
+?line <<"cjDPFiqs">> = iolist_to_binary(re:replace("a","a*b?\\w","cjDPFiq\\1s",[])),
+?line <<"cjDPFiqs">> = iolist_to_binary(re:replace("a","a*b?\\w","cjDPFiq\\1s",[global])),
+?line <<"aaabbbbU">> = iolist_to_binary(re:replace("aaabbbb","a*b{0,4}\\w","&U",[])),
+?line <<"aaabbbbU">> = iolist_to_binary(re:replace("aaabbbb","a*b{0,4}\\w","&U",[global])),
+?line <<"kaaaavgaaaaFaaaa">> = iolist_to_binary(re:replace("aaaa","a*b{0,4}\\w","k&vg&F&",[])),
+?line <<"kaaaavgaaaaFaaaa">> = iolist_to_binary(re:replace("aaaa","a*b{0,4}\\w","k&vg&F&",[global])),
+?line <<"ahHM">> = iolist_to_binary(re:replace("a","a*b{0,4}\\w","&hHM",[])),
+?line <<"ahHM">> = iolist_to_binary(re:replace("a","a*b{0,4}\\w","&hHM",[global])),
+?line <<"aaabbbbkVyXSBUXNHaaabbbbSC">> = iolist_to_binary(re:replace("aaabbbb","a*b{0,}\\w","\\1&kVyXSBUXNH&SC\\1",[])),
+?line <<"aaabbbbkVyXSBUXNHaaabbbbSC">> = iolist_to_binary(re:replace("aaabbbb","a*b{0,}\\w","\\1&kVyXSBUXNH&SC\\1",[global])),
+?line <<"JsaaaaARjP">> = iolist_to_binary(re:replace("aaaa","a*b{0,}\\w","Js&ARjP",[])),
+?line <<"JsaaaaARjP">> = iolist_to_binary(re:replace("aaaa","a*b{0,}\\w","Js&ARjP",[global])),
+?line <<"safA">> = iolist_to_binary(re:replace("a","a*b{0,}\\w","s\\1&fA",[])),
+?line <<"safA">> = iolist_to_binary(re:replace("a","a*b{0,}\\w","s\\1&fA",[global])),
+?line <<"0aGcgVV0aXhLIJ">> = iolist_to_binary(re:replace("0a","a*\\d*\\w","&GcgVV&XhLIJ",[])),
+?line <<"0aGcgVV0aXhLIJ">> = iolist_to_binary(re:replace("0a","a*\\d*\\w","&GcgVV&XhLIJ",[global])),
+?line <<"OWJamuSoHvWtdO">> = iolist_to_binary(re:replace("a","a*\\d*\\w","OWJ&muSoHvWtdO",[])),
+?line <<"OWJamuSoHvWtdO">> = iolist_to_binary(re:replace("a","a*\\d*\\w","OWJ&muSoHvWtdO",[global])),
+?line <<"jLLQBsEdhgm">> = iolist_to_binary(re:replace("a","a*b *\\w","jLLQ\\1BsE\\1dhgm",[extended])),
+?line <<"jLLQBsEdhgm">> = iolist_to_binary(re:replace("a","a*b *\\w","jLLQ\\1BsE\\1dhgm",[extended,
+ global])),
+?line <<"JFKdkakQmYFCpg">> = iolist_to_binary(re:replace("a","a*b#comment
+ *\\w","JFKdk&kQmYFCpg",[extended])),
+?line <<"JFKdkakQmYFCpg">> = iolist_to_binary(re:replace("a","a*b#comment
+ *\\w","JFKdk&kQmYFCpg",[extended,global])),
+?line <<"UeHUaDNFkPaoa">> = iolist_to_binary(re:replace("a","a* b *\\w","UeHU&DNFkP&o&\\1",[extended])),
+?line <<"UeHUaDNFkPaoa">> = iolist_to_binary(re:replace("a","a* b *\\w","UeHU&DNFkP&o&\\1",[extended,
+ global])),
+?line <<"Qomltkg
+pqr">> = iolist_to_binary(re:replace("abc=xyz\\
+pqr","^\\w+=.*(\\\\\\n.*)*","\\1Qomltkg",[])),
+?line <<"Qomltkg
+pqr">> = iolist_to_binary(re:replace("abc=xyz\\
+pqr","^\\w+=.*(\\\\\\n.*)*","\\1Qomltkg",[global])),
+?line <<"abcdVXTDna">> = iolist_to_binary(re:replace("abcd:","(?=(\\w+))\\1:","\\1VXTDna",[])),
+?line <<"abcdVXTDna">> = iolist_to_binary(re:replace("abcd:","(?=(\\w+))\\1:","\\1VXTDna",[global])),
+?line <<"dxHUL">> = iolist_to_binary(re:replace("abcd:","^(?=(\\w+))\\1:","dxHUL",[])),
+?line <<"dxHUL">> = iolist_to_binary(re:replace("abcd:","^(?=(\\w+))\\1:","dxHUL",[global])),
+?line <<"HsivTabcLabcC">> = iolist_to_binary(re:replace("abc","^\\Eabc","H\\1\\1sivT&L&C\\1",[])),
+?line <<"HsivTabcLabcC">> = iolist_to_binary(re:replace("abc","^\\Eabc","H\\1\\1sivT&L&C\\1",[global])),
+?line <<"HaCuWiasgghyJxOoaVMR">> = iolist_to_binary(re:replace("a","^[\\Eabc]","HaCuWi&sgghyJxOo&VMR",[])),
+?line <<"HaCuWiasgghyJxOoaVMR">> = iolist_to_binary(re:replace("a","^[\\Eabc]","HaCuWi&sgghyJxOo&VMR",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[\\Eabc]","Lc&Jjl&YLfuY",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[\\Eabc]","Lc&Jjl&YLfuY",[global])),
+?line <<"E">> = iolist_to_binary(re:replace("E","^[\\Eabc]","MS\\1e",[])),
+?line <<"E">> = iolist_to_binary(re:replace("E","^[\\Eabc]","MS\\1e",[global])),
+?line <<"rtpBWkcLbtGo">> = iolist_to_binary(re:replace("b","^[a-\\Ec]","rtpBWkcL&\\1tGo",[])),
+?line <<"rtpBWkcLbtGo">> = iolist_to_binary(re:replace("b","^[a-\\Ec]","rtpBWkcL&\\1tGo",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[a-\\Ec]","IC&T\\1r",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[a-\\Ec]","IC&T\\1r",[global])),
+?line <<"-">> = iolist_to_binary(re:replace("-","^[a-\\Ec]","CoL\\1S\\1d",[])),
+?line <<"-">> = iolist_to_binary(re:replace("-","^[a-\\Ec]","CoL\\1S\\1d",[global])),
+?line <<"E">> = iolist_to_binary(re:replace("E","^[a-\\Ec]","\\1T",[])),
+?line <<"E">> = iolist_to_binary(re:replace("E","^[a-\\Ec]","\\1T",[global])),
+?line <<"nbUnfwIYbbDEgCj">> = iolist_to_binary(re:replace("b","^[a\\E\\E-\\Ec]","n&Un\\1fwIY\\1&&DEgCj",[])),
+?line <<"nbUnfwIYbbDEgCj">> = iolist_to_binary(re:replace("b","^[a\\E\\E-\\Ec]","n&Un\\1fwIY\\1&&DEgCj",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[a\\E\\E-\\Ec]","P\\1VvOTyOaT",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[a\\E\\E-\\Ec]","P\\1VvOTyOaT",[global])),
+?line <<"-">> = iolist_to_binary(re:replace("-","^[a\\E\\E-\\Ec]","XYUeR",[])),
+?line <<"-">> = iolist_to_binary(re:replace("-","^[a\\E\\E-\\Ec]","XYUeR",[global])),
+?line <<"E">> = iolist_to_binary(re:replace("E","^[a\\E\\E-\\Ec]","PeFBbxifgd",[])),
+?line <<"E">> = iolist_to_binary(re:replace("E","^[a\\E\\E-\\Ec]","PeFBbxifgd",[global])),
+?line <<"pdeFkRdPoflblrWho">> = iolist_to_binary(re:replace("b","^[\\E\\Qa\\E-\\Qz\\E]+","pdeFkRdPofl&lrWho\\1",[])),
+?line <<"pdeFkRdPoflblrWho">> = iolist_to_binary(re:replace("b","^[\\E\\Qa\\E-\\Qz\\E]+","pdeFkRdPofl&lrWho\\1",[global])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+","mXYgE\\1\\1",[])),
+?line <<"** Failers">> = iolist_to_binary(re:replace("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+","mXYgE\\1\\1",[global])),
+?line <<"-">> = iolist_to_binary(re:replace("-","^[\\E\\Qa\\E-\\Qz\\E]+","KLdkiRi",[])),
+?line <<"-">> = iolist_to_binary(re:replace("-","^[\\E\\Qa\\E-\\Qz\\E]+","KLdkiRi",[global])),
+?line <<"CAXhbVsbB">> = iolist_to_binary(re:replace("a","^[a\\Q]bc\\E]","CAXh\\1bVsbB",[])),
+?line <<"CAXhbVsbB">> = iolist_to_binary(re:replace("a","^[a\\Q]bc\\E]","CAXh\\1bVsbB",[global])),
+?line <<"YFgJL]GhQVUD]Pbp">> = iolist_to_binary(re:replace("]","^[a\\Q]bc\\E]","YFgJL&GhQVU\\1D&Pbp",[])),
+?line <<"YFgJL]GhQVUD]Pbp">> = iolist_to_binary(re:replace("]","^[a\\Q]bc\\E]","YFgJL&GhQVU\\1D&Pbp",[global])),
+?line <<"waUcGiVGxIcKiccYmjc">> = iolist_to_binary(re:replace("c","^[a\\Q]bc\\E]","waU&GiVGxI&Ki&cYmj&",[])),
+?line <<"waUcGiVGxIcKiccYmjc">> = iolist_to_binary(re:replace("c","^[a\\Q]bc\\E]","waU&GiVGxI&Ki&cYmj&",[global])),
+?line <<"yfaacSB">> = iolist_to_binary(re:replace("a","^[a-\\Q\\E]","yf&\\1acSB",[])),
+?line <<"yfaacSB">> = iolist_to_binary(re:replace("a","^[a-\\Q\\E]","yf&\\1acSB",[global])),
+?line <<"f">> = iolist_to_binary(re:replace("-","^[a-\\Q\\E]","f",[])),
+?line <<"f">> = iolist_to_binary(re:replace("-","^[a-\\Q\\E]","f",[global])),
+?line <<"S">> = iolist_to_binary(re:replace("aaaa","^(a()*)*","S",[])),
+?line <<"S">> = iolist_to_binary(re:replace("aaaa","^(a()*)*","S",[global])),
+?line <<"BPPbuaaaaufIV">> = iolist_to_binary(re:replace("aaaa","^(?:a(?:(?:))*)*","BPPbu&ufIV",[])),
+?line <<"BPPbuaaaaufIV">> = iolist_to_binary(re:replace("aaaa","^(?:a(?:(?:))*)*","BPPbu&ufIV",[global])),
+?line <<"HBMaaaafDaRVOv">> = iolist_to_binary(re:replace("aaaa","^(a()+)+","HBM&fD\\1RVOv",[])),
+?line <<"HBMaaaafDaRVOv">> = iolist_to_binary(re:replace("aaaa","^(a()+)+","HBM&fD\\1RVOv",[global])),
+?line <<"JvvaaaaaaaaXGaaaawkPaaaaNqM">> = iolist_to_binary(re:replace("aaaa","^(?:a(?:(?:))+)+","J\\1vv\\1\\1&&XG&w\\1kP&N\\1qM",[])),
+?line <<"JvvaaaaaaaaXGaaaawkPaaaaNqM">> = iolist_to_binary(re:replace("aaaa","^(?:a(?:(?:))+)+","J\\1vv\\1\\1&&XG&w\\1kP&N\\1qM",[global])),
+?line <<"GlQPoT">> = iolist_to_binary(re:replace("abbD","(a){0,3}(?(1)b|(c|))*D","GlQPoT",[])),
+?line <<"GlQPoT">> = iolist_to_binary(re:replace("abbD","(a){0,3}(?(1)b|(c|))*D","GlQPoT",[global])),
+?line <<"ApBccccD">> = iolist_to_binary(re:replace("ccccD","(a){0,3}(?(1)b|(c|))*D","\\1\\1ApB&",[])),
+?line <<"ApBccccD">> = iolist_to_binary(re:replace("ccccD","(a){0,3}(?(1)b|(c|))*D","\\1\\1ApB&",[global])),
+?line <<"BC">> = iolist_to_binary(re:replace("D","(a){0,3}(?(1)b|(c|))*D","BC",[])),
+?line <<"BC">> = iolist_to_binary(re:replace("D","(a){0,3}(?(1)b|(c|))*D","BC",[global])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d","gsB\\1",[])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d","gsB\\1",[global])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4FVLiMHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4hlau">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d","&F\\1VLiMH&hlau",[])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4FVLiMHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4hlau">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d","&F\\1VLiMH&hlau",[global])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d","PkrWG&pe\\1uUD&sBHqm",[])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d","PkrWG&pe\\1uUD&sBHqm",[global])),
+?line <<"KmL">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d","KmL",[])),
+?line <<"KmL">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d","KmL",[global])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d","&HUPIn&&&uUmDmrCoAY",[])),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d","&HUPIn&&&uUmDmrCoAY",[global])),
+?line <<"Q">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d","Q",[])),
+?line <<"Q">> = iolist_to_binary(re:replace("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d","Q",[global])),
+?line <<"abcR">> = iolist_to_binary(re:replace("abc","\\Z","R",[])),
+?line <<"abcR">> = iolist_to_binary(re:replace("abc","\\Z","R",[global])),
+?line <<"GwfabcD">> = iolist_to_binary(re:replace("abc","^(?s)(?>.*)(?<!\\n)","Gwf&D",[])),
+?line <<"GwfabcD">> = iolist_to_binary(re:replace("abc","^(?s)(?>.*)(?<!\\n)","Gwf&D",[global])),
+?line <<"EWJHblKuypyqFbP">> = iolist_to_binary(re:replace("abc","^(?s)(?>.*)(?<!\\n)","EWJHblKuypyqFbP",[])),
+?line <<"EWJHblKuypyqFbP">> = iolist_to_binary(re:replace("abc","^(?s)(?>.*)(?<!\\n)","EWJHblKuypyqFbP",[global])),
+?line <<"fabc">> = iolist_to_binary(re:replace("abc","^(?![^\\n]*\\n\\z)","f",[])),
+?line <<"fabc">> = iolist_to_binary(re:replace("abc","^(?![^\\n]*\\n\\z)","f",[global])),
+?line <<"cKyLyGabc">> = iolist_to_binary(re:replace("abc","^(?![^\\n]*\\n\\z)","cKyL\\1yG",[])),
+?line <<"cKyLyGabc">> = iolist_to_binary(re:replace("abc","^(?![^\\n]*\\n\\z)","cKyL\\1yG",[global])),
+?line <<"abcKoYH">> = iolist_to_binary(re:replace("abc","\\z(?<!\\n)","\\1\\1Ko\\1&Y&H",[])),
+?line <<"abcKoYH">> = iolist_to_binary(re:replace("abc","\\z(?<!\\n)","\\1\\1Ko\\1&Y&H",[global])),
+?line <<"abcqWjyPjUTIdbm">> = iolist_to_binary(re:replace("abc","\\z(?<!\\n)","qWjy\\1PjUTIdbm&",[])),
+?line <<"abcqWjyPjUTIdbm">> = iolist_to_binary(re:replace("abc","\\z(?<!\\n)","qWjy\\1PjUTIdbm&",[global])),
+?line <<"labcdJabcdNUhUnH">> = iolist_to_binary(re:replace("abcd","(.*(.)?)*","l&J\\1&NUhUnH",[])),
+?line <<"labcdJabcdNUhUnHlJNUhUnH">> = iolist_to_binary(re:replace("abcd","(.*(.)?)*","l&J\\1&NUhUnH",[global])),
+?line <<"onfTbTLJLVnabcd">> = iolist_to_binary(re:replace("abcd","( (A | (?(1)0|) )* )","onfTbTLJLVn",[extended])),
+?line <<"onfTbTLJLVnaonfTbTLJLVnbonfTbTLJLVnconfTbTLJLVndonfTbTLJLVn">> = iolist_to_binary(re:replace("abcd","( (A | (?(1)0|) )* )","onfTbTLJLVn",[extended,
+ global])),
+?line <<"rilgPabcd">> = iolist_to_binary(re:replace("abcd","( ( (?(1)0|) )* )","ri\\1&lgP",[extended])),
+?line <<"rilgParilgPbrilgPcrilgPdrilgP">> = iolist_to_binary(re:replace("abcd","( ( (?(1)0|) )* )","ri\\1&lgP",[extended,
+ global])),
+?line <<"LjAUxSNabcd">> = iolist_to_binary(re:replace("abcd","( (?(1)0|)* )","L\\1jAUxSN\\1",[extended])),
+?line <<"LjAUxSNaLjAUxSNbLjAUxSNcLjAUxSNdLjAUxSN">> = iolist_to_binary(re:replace("abcd","( (?(1)0|)* )","L\\1jAUxSN\\1",[extended,
+ global])),
+?line <<"xYgnYjja]Bgw">> = iolist_to_binary(re:replace("a]","[[:abcd:xyz]]","\\1xYgnYjj&Bgw",[])),
+?line <<"xYgnYjja]Bgw">> = iolist_to_binary(re:replace("a]","[[:abcd:xyz]]","\\1xYgnYjj&Bgw",[global])),
+?line <<"K:]Y:]tTEIHHPgm">> = iolist_to_binary(re:replace(":]","[[:abcd:xyz]]","K\\1&Y&tT\\1EI\\1H\\1HP\\1gm",[])),
+?line <<"K:]Y:]tTEIHHPgm">> = iolist_to_binary(re:replace(":]","[[:abcd:xyz]]","K\\1&Y&tT\\1EI\\1H\\1HP\\1gm",[global])),
+?line <<"pcaYhpF">> = iolist_to_binary(re:replace("a","[abc[:x\\]pqr]","pc&Y\\1hpF",[])),
+?line <<"pcaYhpF">> = iolist_to_binary(re:replace("a","[abc[:x\\]pqr]","pc&Y\\1hpF",[global])),
+?line <<"I[">> = iolist_to_binary(re:replace("[","[abc[:x\\]pqr]","I\\1&",[])),
+?line <<"I[">> = iolist_to_binary(re:replace("[","[abc[:x\\]pqr]","I\\1&",[global])),
+?line <<"SYn:iPpASU">> = iolist_to_binary(re:replace(":","[abc[:x\\]pqr]","SY\\1n&iP\\1pAS\\1U",[])),
+?line <<"SYn:iPpASU">> = iolist_to_binary(re:replace(":","[abc[:x\\]pqr]","SY\\1n&iP\\1pAS\\1U",[global])),
+?line <<"VwLRsMyuKqCwx">> = iolist_to_binary(re:replace("]","[abc[:x\\]pqr]","VwLRsMyuKqCwx",[])),
+?line <<"VwLRsMyuKqCwx">> = iolist_to_binary(re:replace("]","[abc[:x\\]pqr]","VwLRsMyuKqCwx",[global])),
+?line <<"YqUufuU">> = iolist_to_binary(re:replace("p","[abc[:x\\]pqr]","YqU\\1ufuU",[])),
+?line <<"YqUufuU">> = iolist_to_binary(re:replace("p","[abc[:x\\]pqr]","YqU\\1ufuU",[global])),
+ok.
diff --git a/lib/stdlib/test/re_testoutput1_split_test.erl b/lib/stdlib/test/re_testoutput1_split_test.erl
new file mode 100644
index 0000000000..7e2d3f79ec
--- /dev/null
+++ b/lib/stdlib/test/re_testoutput1_split_test.erl
@@ -0,0 +1,29418 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(re_testoutput1_split_test).
+-compile(export_all).
+-include("test_server.hrl").
+%% This file is generated by running run_pcre_tests:gen_split_test("re_SUITE_data/testoutput1")
+join([]) -> [];
+join([A]) -> [A];
+join([H|T]) -> [H,<<":">>|join(T)].
+run() ->
+?line <<"">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[]))),
+?line <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[trim]))),
+?line <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[{parts,
+ 2}]))),
+?line <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[trim]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[{parts,
+ 2}]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[]))),
+?line <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[trim]))),
+?line <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[{parts,
+ 2}]))),
+?line <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("the quick brown fox","The quick brown fox",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("the quick brown fox","The quick brown fox",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("the quick brown fox","The quick brown fox",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("The quick brown FOX","The quick brown fox",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("The quick brown FOX","The quick brown fox",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("The quick brown FOX","The quick brown fox",[caseless]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","The quick brown fox",[caseless,
+ trim]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","The quick brown fox",[caseless,
+ {parts,
+ 2}]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","The quick brown fox",[caseless]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","The quick brown fox",[caseless,
+ trim]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","The quick brown fox",[caseless,
+ {parts,
+ 2}]))),
+?line <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","The quick brown fox",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd
+ 9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd
+ 9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd
+ 9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<">>>">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<">>>:">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<">>>:">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<">">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<">:">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<">:">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<">>>>">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<">>>>:">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<">>>>:">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))),
+?line <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts,
+ 2}]))),
+?line <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[]))),
+?line <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[trim]))),
+?line <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[{parts,
+ 2}]))),
+?line <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[]))),
+?line <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[trim]))),
+?line <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[{parts,
+ 2}]))),
+?line <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[]))),
+?line <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[trim]))),
+?line <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[{parts,
+ 2}]))),
+?line <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[]))),
+?line <<":bb">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[trim]))),
+?line <<":bb:">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":bb:">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[]))),
+?line <<":bbbbbbbbbbb">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[trim]))),
+?line <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[]))),
+?line <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[trim]))),
+?line <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[]))),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[trim]))),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[]))),
+?line <<":bb">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[trim]))),
+?line <<":bb:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":bb:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[]))),
+?line <<":bbb">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[trim]))),
+?line <<":bbb:">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":bbb:">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[]))),
+?line <<":bbbbbbbbbbb">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[trim]))),
+?line <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[]))),
+?line <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[trim]))),
+?line <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[]))),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[trim]))),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[{parts,
+ 2}]))),
+?line <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[]))),
+?line <<":ba">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[trim]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[{parts,
+ 2}]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[]))),
+?line <<":ba">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[trim]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[{parts,
+ 2}]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[]))),
+?line <<":ba">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[trim]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[{parts,
+ 2}]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[]))),
+?line <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[trim]))),
+?line <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[{parts,
+ 2}]))),
+?line <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[]))),
+?line <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[trim]))),
+?line <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[{parts,
+ 2}]))),
+?line <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[]))),
+?line <<":ba">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[trim]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[{parts,
+ 2}]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[]))),
+?line <<":ba">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[trim]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[{parts,
+ 2}]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[]))),
+?line <<":ba">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[trim]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[{parts,
+ 2}]))),
+?line <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[]))),
+?line <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[trim]))),
+?line <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[{parts,
+ 2}]))),
+?line <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[]))),
+?line <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[trim]))),
+?line <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[{parts,
+ 2}]))),
+?line <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[\\c{\\c:",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[\\c{\\c:",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[\\c{\\c:",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[]))),
+?line <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[trim]))),
+?line <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[]))),
+?line <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[trim]))),
+?line <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[]))),
+?line <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[trim]))),
+?line <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[]))),
+?line <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[trim]))),
+?line <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[{parts,
+ 2}]))),
+?line <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[]))),
+?line <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[trim]))),
+?line <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[{parts,
+ 2}]))),
+?line <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[trim]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[]))),
+?line <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[trim]))),
+?line <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[]))),
+?line <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[trim]))),
+?line <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[]))),
+?line <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[trim]))),
+?line <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[]))),
+?line <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[trim]))),
+?line <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[]))),
+?line <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[trim]))),
+?line <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[]))),
+?line <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[trim]))),
+?line <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[{parts,
+ 2}]))),
+?line <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[trim]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[{parts,
+ 2}]))),
+?line <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[trim]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[{parts,
+ 2}]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[]))),
+?line <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[trim]))),
+?line <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[{parts,
+ 2}]))),
+?line <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[]))),
+?line <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[trim]))),
+?line <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[{parts,
+ 2}]))),
+?line <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[]))),
+?line <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[trim]))),
+?line <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[{parts,
+ 2}]))),
+?line <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[]))),
+?line <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[trim]))),
+?line <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[{parts,
+ 2}]))),
+?line <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("�","^\\�",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("�","^\\�",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("�","^\\�",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("�","^�",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("�","^�",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("�","^�",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("enter","^.*nter",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("enter","^.*nter",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("enter","^.*nter",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("inter","^.*nter",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("inter","^.*nter",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("inter","^.*nter",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("uponter","^.*nter",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("uponter","^.*nter",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("uponter","^.*nter",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[]))),
+?line <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[trim]))),
+?line <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[{parts,
+ 2}]))),
+?line <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[trim]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[trim]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[]))),
+?line <<":abc:pqr">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))),
+?line <<":abc:pqr:">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts,
+ 2}]))),
+?line <<":abc:pqr:">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))),
+?line <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))),
+?line <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts,
+ 2}]))),
+?line <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))),
+?line <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))),
+?line <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts,
+ 2}]))),
+?line <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))),
+?line <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))),
+?line <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts,
+ 2}]))),
+?line <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))),
+?line <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))),
+?line <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts,
+ 2}]))),
+?line <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))),
+?line <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[trim]))),
+?line <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[{parts,
+ 2}]))),
+?line <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[]))),
+?line <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[trim]))),
+?line <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[{parts,
+ 2}]))),
+?line <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[]))),
+?line <<":0abc">> = iolist_to_binary(join(re:split("0abc","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<":0abc:">> = iolist_to_binary(join(re:split("0abc","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":0abc:">> = iolist_to_binary(join(re:split("0abc","([\\da-f:]+)$",[caseless]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abc","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc","([\\da-f:]+)$",[caseless]))),
+?line <<":fed">> = iolist_to_binary(join(re:split("fed","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<":fed:">> = iolist_to_binary(join(re:split("fed","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":fed:">> = iolist_to_binary(join(re:split("fed","([\\da-f:]+)$",[caseless]))),
+?line <<":E">> = iolist_to_binary(join(re:split("E","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<":E:">> = iolist_to_binary(join(re:split("E","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":E:">> = iolist_to_binary(join(re:split("E","([\\da-f:]+)$",[caseless]))),
+?line <<":::">> = iolist_to_binary(join(re:split("::","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"::::">> = iolist_to_binary(join(re:split("::","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"::::">> = iolist_to_binary(join(re:split("::","([\\da-f:]+)$",[caseless]))),
+?line <<":5f03:12C0::932e">> = iolist_to_binary(join(re:split("5f03:12C0::932e","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<":5f03:12C0::932e:">> = iolist_to_binary(join(re:split("5f03:12C0::932e","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":5f03:12C0::932e:">> = iolist_to_binary(join(re:split("5f03:12C0::932e","([\\da-f:]+)$",[caseless]))),
+?line <<"fed :def">> = iolist_to_binary(join(re:split("fed def","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"fed :def:">> = iolist_to_binary(join(re:split("fed def","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"fed :def:">> = iolist_to_binary(join(re:split("fed def","([\\da-f:]+)$",[caseless]))),
+?line <<"Any old stu:ff">> = iolist_to_binary(join(re:split("Any old stuff","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"Any old stu:ff:">> = iolist_to_binary(join(re:split("Any old stuff","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"Any old stu:ff:">> = iolist_to_binary(join(re:split("Any old stuff","([\\da-f:]+)$",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\da-f:]+)$",[caseless]))),
+?line <<"0zzz">> = iolist_to_binary(join(re:split("0zzz","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"0zzz">> = iolist_to_binary(join(re:split("0zzz","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"0zzz">> = iolist_to_binary(join(re:split("0zzz","([\\da-f:]+)$",[caseless]))),
+?line <<"gzzz">> = iolist_to_binary(join(re:split("gzzz","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"gzzz">> = iolist_to_binary(join(re:split("gzzz","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"gzzz">> = iolist_to_binary(join(re:split("gzzz","([\\da-f:]+)$",[caseless]))),
+?line <<"fed ">> = iolist_to_binary(join(re:split("fed ","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"fed ">> = iolist_to_binary(join(re:split("fed ","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"fed ">> = iolist_to_binary(join(re:split("fed ","([\\da-f:]+)$",[caseless]))),
+?line <<"Any old rubbish">> = iolist_to_binary(join(re:split("Any old rubbish","([\\da-f:]+)$",[caseless,
+ trim]))),
+?line <<"Any old rubbish">> = iolist_to_binary(join(re:split("Any old rubbish","([\\da-f:]+)$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"Any old rubbish">> = iolist_to_binary(join(re:split("Any old rubbish","([\\da-f:]+)$",[caseless]))),
+?line <<":1:2:3">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))),
+?line <<":1:2:3:">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts,
+ 2}]))),
+?line <<":1:2:3:">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))),
+?line <<":12:123:0">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))),
+?line <<":12:123:0:">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts,
+ 2}]))),
+?line <<":12:123:0:">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))),
+?line <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))),
+?line <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts,
+ 2}]))),
+?line <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))),
+?line <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))),
+?line <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts,
+ 2}]))),
+?line <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))),
+?line <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))),
+?line <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts,
+ 2}]))),
+?line <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))),
+?line <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))),
+?line <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts,
+ 2}]))),
+?line <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))),
+?line <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))),
+?line <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts,
+ 2}]))),
+?line <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))),
+?line <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))),
+?line <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts,
+ 2}]))),
+?line <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<":.pq-r">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<":.pq-r:">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<":.pq-r:">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<":.uk">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<":.uk:">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<":.uk:">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<":.y-">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<":.y-:">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<":.y-:">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))),
+?line <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts,
+ 2}]))),
+?line <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<"::::">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<"::::">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<":0-a">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<":0-a:::">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<":0-a:::">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<":3-b:.c">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<":3-b:.c::">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<":3-b:.c::">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<":-a:.b-c:-c">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<":-a:.b-c:-c:">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<":-a:.b-c:-c:">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))),
+?line <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts,
+ 2}]))),
+?line <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))),
+?line <<":de:abd:e">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[trim]))),
+?line <<":de:abd:e:">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[{parts,
+ 2}]))),
+?line <<":de:abd:e:">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[]))),
+?line <<"::abd:f">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[trim]))),
+?line <<"::abd:f:">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[{parts,
+ 2}]))),
+?line <<"::abd:f:">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[]))),
+?line <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[trim]))),
+?line <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[{parts,
+ 2}]))),
+?line <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[]))),
+?line <<":.d">> = iolist_to_binary(join(re:split("a.b.c.d","^[\\da-f](\\.[\\da-f])*$",[caseless,
+ trim]))),
+?line <<":.d:">> = iolist_to_binary(join(re:split("a.b.c.d","^[\\da-f](\\.[\\da-f])*$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":.d:">> = iolist_to_binary(join(re:split("a.b.c.d","^[\\da-f](\\.[\\da-f])*$",[caseless]))),
+?line <<":.D">> = iolist_to_binary(join(re:split("A.B.C.D","^[\\da-f](\\.[\\da-f])*$",[caseless,
+ trim]))),
+?line <<":.D:">> = iolist_to_binary(join(re:split("A.B.C.D","^[\\da-f](\\.[\\da-f])*$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":.D:">> = iolist_to_binary(join(re:split("A.B.C.D","^[\\da-f](\\.[\\da-f])*$",[caseless]))),
+?line <<":.C">> = iolist_to_binary(join(re:split("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$",[caseless,
+ trim]))),
+?line <<":.C:">> = iolist_to_binary(join(re:split("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":.C:">> = iolist_to_binary(join(re:split("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[]))),
+?line <<":;">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[trim]))),
+?line <<":;:">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[{parts,
+ 2}]))),
+?line <<":;:">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[]))),
+?line <<":; rhubarb">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[trim]))),
+?line <<":; rhubarb:">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[{parts,
+ 2}]))),
+?line <<":; rhubarb:">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[]))),
+?line <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[trim]))),
+?line <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[{parts,
+ 2}]))),
+?line <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^$",[trim]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^$",[{parts,
+ 2}]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ {parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))),
+?line <<"ab cde">> = iolist_to_binary(join(re:split("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ trim]))),
+?line <<"ab cde">> = iolist_to_binary(join(re:split("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended,
+ {parts,
+ 2}]))),
+?line <<"ab cde">> = iolist_to_binary(join(re:split("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))),
+?line <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))),
+?line <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts,
+ 2}]))),
+?line <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a bcd","^ a\\ b[c ]d $",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a bcd","^ a\\ b[c ]d $",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a bcd","^ a\\ b[c ]d $",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("a b d","^ a\\ b[c ]d $",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a b d","^ a\\ b[c ]d $",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a b d","^ a\\ b[c ]d $",[extended]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^ a\\ b[c ]d $",[extended,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^ a\\ b[c ]d $",[extended,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^ a\\ b[c ]d $",[extended]))),
+?line <<"abcd">> = iolist_to_binary(join(re:split("abcd","^ a\\ b[c ]d $",[extended,
+ trim]))),
+?line <<"abcd">> = iolist_to_binary(join(re:split("abcd","^ a\\ b[c ]d $",[extended,
+ {parts,
+ 2}]))),
+?line <<"abcd">> = iolist_to_binary(join(re:split("abcd","^ a\\ b[c ]d $",[extended]))),
+?line <<"ab d">> = iolist_to_binary(join(re:split("ab d","^ a\\ b[c ]d $",[extended,
+ trim]))),
+?line <<"ab d">> = iolist_to_binary(join(re:split("ab d","^ a\\ b[c ]d $",[extended,
+ {parts,
+ 2}]))),
+?line <<"ab d">> = iolist_to_binary(join(re:split("ab d","^ a\\ b[c ]d $",[extended]))),
+?line <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[trim]))),
+?line <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[{parts,
+ 2}]))),
+?line <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[]))),
+?line <<":bc:c:ef:f:ij:j:lm:m">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[trim]))),
+?line <<":bc:c:ef:f:ij:j:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[{parts,
+ 2}]))),
+?line <<":bc:c:ef:f:ij:j:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a+ Z0+
+","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a+ Z0+
+","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a+ Z0+
+","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("z","^a*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","^a*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","^a*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("az","^a*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("az","^a*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("az","^a*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","^a*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^a*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^a*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aa","^a*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","^a*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","^a*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[]))),
+?line <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[trim]))),
+?line <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[{parts,
+ 2}]))),
+?line <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[trim]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[{parts,
+ 2}]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("z","^a*?\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","^a*?\\w",[]))),
+?line <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[trim]))),
+?line <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[]))),
+?line <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[trim]))),
+?line <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","^a*?\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^a*?\\w",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[trim]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[]))),
+?line <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[trim]))),
+?line <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[]))),
+?line <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[trim]))),
+?line <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[]))),
+?line <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[trim]))),
+?line <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[{parts,
+ 2}]))),
+?line <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("az","^a+\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("az","^a+\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("az","^a+\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aa","^a+\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","^a+\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","^a+\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[trim]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[{parts,
+ 2}]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("az","^a+?\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("az","^a+?\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("az","^a+?\\w",[]))),
+?line <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[trim]))),
+?line <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[{parts,
+ 2}]))),
+?line <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[]))),
+?line <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[trim]))),
+?line <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[{parts,
+ 2}]))),
+?line <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[trim]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[{parts,
+ 2}]))),
+?line <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[]))),
+?line <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[trim]))),
+?line <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[{parts,
+ 2}]))),
+?line <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[]))),
+?line <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[trim]))),
+?line <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[{parts,
+ 2}]))),
+?line <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[]))),
+?line <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[trim]))),
+?line <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[{parts,
+ 2}]))),
+?line <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[trim]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[{parts,
+ 2}]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[]))),
+?line <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[trim]))),
+?line <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[{parts,
+ 2}]))),
+?line <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[]))),
+?line <<":abc:abc">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))),
+?line <<":abc:abc:">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[{parts,
+ 2}]))),
+?line <<":abc:abc:">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[]))),
+?line <<":def:def">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))),
+?line <<":def:def:">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[{parts,
+ 2}]))),
+?line <<":def:def:">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[]))),
+?line <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))),
+?line <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[{parts,
+ 2}]))),
+?line <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:cd">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[trim]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[{parts,
+ 2}]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:cd">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[trim]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[{parts,
+ 2}]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[]))),
+?line <<":cataract:aract:ract::3">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))),
+?line <<":cataract:aract:ract::3:">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[{parts,
+ 2}]))),
+?line <<":cataract:aract:ract::3:">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))),
+?line <<":catatonic:atonic:tonic::3">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))),
+?line <<":catatonic:atonic:tonic::3:">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[{parts,
+ 2}]))),
+?line <<":catatonic:atonic:tonic::3:">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))),
+?line <<":caterpillar:erpillar:::3">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))),
+?line <<":caterpillar:erpillar:::3:">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[{parts,
+ 2}]))),
+?line <<":caterpillar:erpillar:::3:">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))),
+?line <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[trim]))),
+?line <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[{parts,
+ 2}]))),
+?line <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[]))),
+?line <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))),
+?line <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts,
+ 2}]))),
+?line <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))),
+?line <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))),
+?line <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts,
+ 2}]))),
+?line <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))),
+?line <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))),
+?line <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts,
+ 2}]))),
+?line <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12
+34","^12.34",[dotall,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12
+34","^12.34",[dotall,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12
+34","^12.34",[dotall]))),
+?line <<"">> = iolist_to_binary(join(re:split("12 34","^12.34",[dotall,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12 34","^12.34",[dotall,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12 34","^12.34",[dotall]))),
+?line <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[trim]))),
+?line <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[{parts,
+ 2}]))),
+?line <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[]))),
+?line <<"foobar is :lish see?">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[trim]))),
+?line <<"foobar is :lish see?:">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[{parts,
+ 2}]))),
+?line <<"foobar is :lish see?:">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[]))),
+?line <<"foobar c: etc">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))),
+?line <<"foobar c: etc:">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts,
+ 2}]))),
+?line <<"foobar c: etc:">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[]))),
+?line <<":rel">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))),
+?line <<":rel:">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts,
+ 2}]))),
+?line <<":rel:">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))),
+?line <<":rel">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))),
+?line <<":rel:">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts,
+ 2}]))),
+?line <<":rel:">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))),
+?line <<":rel">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))),
+?line <<":rel:">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts,
+ 2}]))),
+?line <<":rel:">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))),
+?line <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[trim]))),
+?line <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[{parts,
+ 2}]))),
+?line <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[]))),
+?line <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[trim]))),
+?line <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[{parts,
+ 2}]))),
+?line <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1234","^1234(?# test newlines
+ inside)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^1234(?# test newlines
+ inside)",[{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^1234(?# test newlines
+ inside)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1234","^1234 #comment in extended re
+ ",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^1234 #comment in extended re
+ ",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1234","^1234 #comment in extended re
+ ",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","#rhubarb
+ abcd",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","#rhubarb
+ abcd",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","#rhubarb
+ abcd",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","^abcd#rhubarb",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","^abcd#rhubarb",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","^abcd#rhubarb",[extended]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[trim]))),
+?line <<":a:b:">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[{parts,
+ 2}]))),
+?line <<":a:b:">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[trim]))),
+?line <<":a:b:">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[{parts,
+ 2}]))),
+?line <<":a:b:">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[]))),
+?line <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[trim]))),
+?line <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[{parts,
+ 2}]))),
+?line <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[]))),
+?line <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[trim]))),
+?line <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[{parts,
+ 2}]))),
+?line <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[]))),
+?line <<"the ">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[trim]))),
+?line <<"the :">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[{parts,
+ 2}]))),
+?line <<"the :">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[]))),
+?line <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[trim]))),
+?line <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[{parts,
+ 2}]))),
+?line <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[]))),
+?line <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[trim]))),
+?line <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[{parts,
+ 2}]))),
+?line <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[]))),
+?line <<":abbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[trim]))),
+?line <<":abbbbb:">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[{parts,
+ 2}]))),
+?line <<":abbbbb:">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[]))),
+?line <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[trim]))),
+?line <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[{parts,
+ 2}]))),
+?line <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[]))),
+?line <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[trim]))),
+?line <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[{parts,
+ 2}]))),
+?line <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[]))),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"user.ain">> = iolist_to_binary(join(re:split("user.ain"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"user.ain">> = iolist_to_binary(join(re:split("user.ain"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"user.ain">> = iolist_to_binary(join(re:split("user.ain"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,trim]))),
+?line <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended,
+ {parts,2}]))),
+?line <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox"," (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional leading comment
+(?: (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # one word, optionally followed by....
+(?:
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] | # atom and space parts, or...
+\\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) | # comments, or...
+
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+# quoted strings
+)*
+< (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # leading <
+(?: @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* , (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+)* # further okay, if led by comma
+: # closing colon
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* )? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) # initial word
+(?: (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\" (?: # opening quote...
+[^\\\\\\x80-\\xff\\n\\015\"] # Anything except backslash and quote
+| # or
+\\\\ [^\\x80-\\xff] # Escaped something (something != CR)
+)* \" # closing quote
+) )* # further okay, if led by a period
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* @ (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # initial subdomain
+(?: #
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* \\. # if led by a period...
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* (?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+| \\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+) # ...further okay
+)*
+# address spec
+(?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* > # trailing >
+# name and address
+) (?: [\\040\\t] | \\(
+(?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )*
+\\) )* # optional trailing comment",[extended]))),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"user.ain">> = iolist_to_binary(join(re:split("user.ain","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"user.ain">> = iolist_to_binary(join(re:split("user.ain","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"user.ain">> = iolist_to_binary(join(re:split("user.ain","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,trim]))),
+?line <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended,{parts,2}]))),
+?line <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional leading comment
+(?:
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address
+| # or
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+# leading word
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # \"normal\" atoms and or spaces
+(?:
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+|
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+) # \"special\" comment or quoted string
+[^()<>@,;:\".\\\\\\[\\]\\x80-\\xff\\000-\\010\\012-\\037] * # more \"normal\"
+)*
+<
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# <
+(?:
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+(?: ,
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+)* # additional domains
+:
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)? # optional route
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+# Atom
+| # or
+\" # \"
+[^\\\\\\x80-\\xff\\n\\015\"] * # normal
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015\"] * )* # ( special normal* )*
+\" # \"
+# Quoted string
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# additional words
+)*
+@
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+(?:
+\\.
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+(?:
+[^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]+ # some number of atom characters...
+(?![^(\\040)<>@,;:\".\\\\\\[\\]\\000-\\037\\x80-\\xff]) # ..not followed by something that could be part of an atom
+|
+\\[ # [
+(?: [^\\\\\\x80-\\xff\\n\\015\\[\\]] | \\\\ [^\\x80-\\xff] )* # stuff
+\\] # ]
+)
+[\\040\\t]* # Nab whitespace.
+(?:
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: # (
+(?: \\\\ [^\\x80-\\xff] |
+\\( # (
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+(?: \\\\ [^\\x80-\\xff] [^\\\\\\x80-\\xff\\n\\015()] * )* # (special normal*)*
+\\) # )
+) # special
+[^\\\\\\x80-\\xff\\n\\015()] * # normal*
+)* # )*
+\\) # )
+[\\040\\t]* )* # If comment found, allow more spaces.
+# optional trailing comments
+)*
+# address spec
+> # >
+# name and address
+)",[extended]))),
+?line <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[trim]))),
+?line <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[{parts,
+ 2}]))),
+?line <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[]))),
+?line <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[trim]))),
+?line <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[{parts,
+ 2}]))),
+?line <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[]))),
+?line <<"abc efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[trim]))),
+?line <<"abc efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[{parts,
+ 2}]))),
+?line <<"abc efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[]))),
+?line <<"abc456 abc efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[trim]))),
+?line <<"abc456 abc efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[{parts,
+ 2}]))),
+?line <<"abc456 abc efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[]))),
+?line <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[trim]))),
+?line <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[{parts,
+ 2}]))),
+?line <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[]))),
+?line <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[trim]))),
+?line <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[{parts,
+ 2}]))),
+?line <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[]))),
+?line <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[trim]))),
+?line <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[{parts,
+ 2}]))),
+?line <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("","\\0*",[trim]))),
+?line <<"">> = iolist_to_binary(join(re:split("","\\0*",[{parts,
+ 2}]))),
+?line <<"">> = iolist_to_binary(join(re:split("","\\0*",[]))),
+?line <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[trim]))),
+?line <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[{parts,
+ 2}]))),
+?line <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[]))),
+?line <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[trim]))),
+?line <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[{parts,
+ 2}]))),
+?line <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[]))),
+?line <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[trim]))),
+?line <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[{parts,
+ 2}]))),
+?line <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[]))),
+?line <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[trim]))),
+?line <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[{parts,
+ 2}]))),
+?line <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[]))),
+?line <<":cow:bell">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[trim]))),
+?line <<":cow:bell:">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[{parts,
+ 2}]))),
+?line <<":cow:bell:">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[]))),
+?line <<"::bell">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[trim]))),
+?line <<"::bell:">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[{parts,
+ 2}]))),
+?line <<"::bell:">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[]))),
+?line <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[trim]))),
+?line <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[{parts,
+ 2}]))),
+?line <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[{parts,
+ 2}]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[{parts,
+ 2}]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("
+abc","^\\s",[trim]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("
+abc","^\\s",[{parts,2}]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("
+abc","^\\s",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[{parts,
+ 2}]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[{parts,
+ 2}]))),
+?line <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^a b
+ c",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^a b
+ c",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^a b
+ c",[extended]))),
+?line <<":a">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[]))),
+?line <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[trim]))),
+?line <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[{parts,
+ 2}]))),
+?line <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[trim]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[{parts,
+ 2}]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[]))),
+?line <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[trim]))),
+?line <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[{parts,
+ 2}]))),
+?line <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[trim]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[{parts,
+ 2}]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[trim]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[{parts,
+ 2}]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[]))),
+?line <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[trim]))),
+?line <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[{parts,
+ 2}]))),
+?line <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[trim]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[{parts,
+ 2}]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[trim]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[{parts,
+ 2}]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[]))),
+?line <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[trim]))),
+?line <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[{parts,
+ 2}]))),
+?line <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[]))),
+?line <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[trim]))),
+?line <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[{parts,
+ 2}]))),
+?line <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[]))),
+?line <<":track1:title:Blah blah blah">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[trim]))),
+?line <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[{parts,
+ 2}]))),
+?line <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[]))),
+?line <<":track1:title:Blah blah blah">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[caseless,
+ trim]))),
+?line <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[caseless]))),
+?line <<":track1:title:Blah blah blah">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)",[caseless,
+ trim]))),
+?line <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[]))),
+?line <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[trim]))),
+?line <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[{parts,
+ 2}]))),
+?line <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("wxy_^ABC","^[W-c]+$",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[W-c]+$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[W-c]+$",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("WXY_^abc","^[\\x3f-\\x5F]+$",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[\\x3f-\\x5F]+$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[\\x3f-\\x5F]+$",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("wxy_^ABC","^[\\x3f-\\x5F]+$",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[\\x3f-\\x5F]+$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[\\x3f-\\x5F]+$",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[multiline,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[multiline,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[multiline]))),
+?line <<"qqq
+">> = iolist_to_binary(join(re:split("qqq
+abc","^abc$",[multiline,trim]))),
+?line <<"qqq
+:">> = iolist_to_binary(join(re:split("qqq
+abc","^abc$",[multiline,{parts,2}]))),
+?line <<"qqq
+:">> = iolist_to_binary(join(re:split("qqq
+abc","^abc$",[multiline]))),
+?line <<":
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","^abc$",[multiline,trim]))),
+?line <<":
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","^abc$",[multiline,{parts,2}]))),
+?line <<":
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","^abc$",[multiline]))),
+?line <<"qqq
+:
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","^abc$",[multiline,trim]))),
+?line <<"qqq
+:
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","^abc$",[multiline,{parts,2}]))),
+?line <<"qqq
+:
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","^abc$",[multiline]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","^abc$",[trim]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","^abc$",[{parts,2}]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","^abc$",[]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","^abc$",[trim]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","^abc$",[{parts,2}]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","^abc$",[]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","^abc$",[trim]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","^abc$",[{parts,2}]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","^abc$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\Z",[multiline,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\Z",[multiline,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\Z",[multiline]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","\\Aabc\\Z",[multiline,trim]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","\\Aabc\\Z",[multiline,{parts,2}]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","\\Aabc\\Z",[multiline]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","\\Aabc\\Z",[multiline,trim]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","\\Aabc\\Z",[multiline,{parts,2}]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","\\Aabc\\Z",[multiline]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","\\Aabc\\Z",[multiline,trim]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","\\Aabc\\Z",[multiline,{parts,2}]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","\\Aabc\\Z",[multiline]))),
+?line <<":f">> = iolist_to_binary(join(re:split("abc
+def","\\A(.)*\\Z",[dotall,trim]))),
+?line <<":f:">> = iolist_to_binary(join(re:split("abc
+def","\\A(.)*\\Z",[dotall,{parts,2}]))),
+?line <<":f:">> = iolist_to_binary(join(re:split("abc
+def","\\A(.)*\\Z",[dotall]))),
+?line <<":s">> = iolist_to_binary(join(re:split("*** Failers","\\A(.)*\\Z",[multiline,
+ trim]))),
+?line <<":s:">> = iolist_to_binary(join(re:split("*** Failers","\\A(.)*\\Z",[multiline,
+ {parts,
+ 2}]))),
+?line <<":s:">> = iolist_to_binary(join(re:split("*** Failers","\\A(.)*\\Z",[multiline]))),
+?line <<"abc
+def">> = iolist_to_binary(join(re:split("abc
+def","\\A(.)*\\Z",[multiline,trim]))),
+?line <<"abc
+def">> = iolist_to_binary(join(re:split("abc
+def","\\A(.)*\\Z",[multiline,{parts,2}]))),
+?line <<"abc
+def">> = iolist_to_binary(join(re:split("abc
+def","\\A(.)*\\Z",[multiline]))),
+?line <<"::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[trim]))),
+?line <<":::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[{parts,
+ 2}]))),
+?line <<"::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[]))),
+?line <<"c">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[trim]))),
+?line <<"c:b">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[{parts,
+ 2}]))),
+?line <<"c::">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("az-","[-az]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("az-","[-az]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("az-","[-az]+",[]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[trim]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[{parts,
+ 2}]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[trim]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[{parts,
+ 2}]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("za-","[az-]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("za-","[az-]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("za-","[az-]+",[]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[trim]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[{parts,
+ 2}]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[trim]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[{parts,
+ 2}]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[trim]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[{parts,
+ 2}]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[trim]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[{parts,
+ 2}]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[trim]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[{parts,
+ 2}]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[trim]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[{parts,
+ 2}]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[]))),
+?line <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[trim]))),
+?line <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[{parts,
+ 2}]))),
+?line <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[]))),
+?line <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[trim]))),
+?line <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[{parts,
+ 2}]))),
+?line <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[]))),
+?line <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[trim]))),
+?line <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[{parts,
+ 2}]))),
+?line <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[caseless,
+ trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[caseless,
+ {parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[caseless]))),
+?line <<":ABC">> = iolist_to_binary(join(re:split("ABCabc","(abc)\\1",[caseless,
+ trim]))),
+?line <<":ABC:">> = iolist_to_binary(join(re:split("ABCabc","(abc)\\1",[caseless,
+ {parts,
+ 2}]))),
+?line <<":ABC:">> = iolist_to_binary(join(re:split("ABCabc","(abc)\\1",[caseless]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcABC","(abc)\\1",[caseless,
+ trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcABC","(abc)\\1",[caseless,
+ {parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcABC","(abc)\\1",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","abc$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","abc$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","abc$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","abc$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","abc$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","abc$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[]))),
+?line <<"abc
+def">> = iolist_to_binary(join(re:split("abc
+def","abc$",[trim]))),
+?line <<"abc
+def">> = iolist_to_binary(join(re:split("abc
+def","abc$",[{parts,2}]))),
+?line <<"abc
+def">> = iolist_to_binary(join(re:split("abc
+def","abc$",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abc�","(abc)\\223",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc�","(abc)\\223",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc�","(abc)\\223",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abc�","(abc)\\323",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc�","(abc)\\323",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc�","(abc)\\323",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))),
+?line <<"abc81">> = iolist_to_binary(join(re:split("abc81","abc\\81",[trim]))),
+?line <<"abc81">> = iolist_to_binary(join(re:split("abc81","abc\\81",[{parts,
+ 2}]))),
+?line <<"abc81">> = iolist_to_binary(join(re:split("abc81","abc\\81",[]))),
+?line <<"abc81">> = iolist_to_binary(join(re:split("abc81","abc\\81",[trim]))),
+?line <<"abc81">> = iolist_to_binary(join(re:split("abc81","abc\\81",[{parts,
+ 2}]))),
+?line <<"abc81">> = iolist_to_binary(join(re:split("abc81","abc\\81",[]))),
+?line <<"abc91">> = iolist_to_binary(join(re:split("abc91","abc\\91",[trim]))),
+?line <<"abc91">> = iolist_to_binary(join(re:split("abc91","abc\\91",[{parts,
+ 2}]))),
+?line <<"abc91">> = iolist_to_binary(join(re:split("abc91","abc\\91",[]))),
+?line <<"abc91">> = iolist_to_binary(join(re:split("abc91","abc\\91",[trim]))),
+?line <<"abc91">> = iolist_to_binary(join(re:split("abc91","abc\\91",[{parts,
+ 2}]))),
+?line <<"abc91">> = iolist_to_binary(join(re:split("abc91","abc\\91",[]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:l">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[trim]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:l:">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[{parts,
+ 2}]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:l:">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k">> = iolist_to_binary(join(re:split("abcdefghijk
+S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[trim]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:">> = iolist_to_binary(join(re:split("abcdefghijk
+S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[{parts,2}]))),
+?line <<":a:b:c:d:e:f:g:h:i:j:k:">> = iolist_to_binary(join(re:split("abcdefghijk
+S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("bc","a{0}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("bc","a{0}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("bc","a{0}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[trim]))),
+?line <<":::">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[{parts,
+ 2}]))),
+?line <<":::">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a
+b","(?s)a.b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","(?s)a.b",[{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","(?s)a.b",[]))),
+?line <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<":*:*:* Fail:ers">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<":*:*:* Fail:ers:">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<":*:*:* Fail:ers:">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))),
+?line <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts,
+ 2}]))),
+?line <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("Abc","[^a]",[trim]))),
+?line <<":bc">> = iolist_to_binary(join(re:split("Abc","[^a]",[{parts,
+ 2}]))),
+?line <<":::">> = iolist_to_binary(join(re:split("Abc","[^a]",[]))),
+?line <<"A">> = iolist_to_binary(join(re:split("Abc","[^a]",[caseless,
+ trim]))),
+?line <<"A:c">> = iolist_to_binary(join(re:split("Abc","[^a]",[caseless,
+ {parts,
+ 2}]))),
+?line <<"A::">> = iolist_to_binary(join(re:split("Abc","[^a]",[caseless]))),
+?line <<":a">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[trim]))),
+?line <<":aAbc">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[]))),
+?line <<"AAAaA">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[caseless,
+ trim]))),
+?line <<"AAAaA:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AAAaA:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("bbb
+ccc","[^a]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("bbb
+ccc","[^a]+",[{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("bbb
+ccc","[^a]+",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("abc","[^k]$",[trim]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abc","[^k]$",[{parts,
+ 2}]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abc","[^k]$",[]))),
+?line <<"*** Failer">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[trim]))),
+?line <<"*** Failer:">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[{parts,
+ 2}]))),
+?line <<"*** Failer:">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[]))),
+?line <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[trim]))),
+?line <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[{parts,
+ 2}]))),
+?line <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[]))),
+?line <<"k">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[trim]))),
+?line <<"k:">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[{parts,
+ 2}]))),
+?line <<"k:">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[]))),
+?line <<"k">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[trim]))),
+?line <<"k:">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[{parts,
+ 2}]))),
+?line <<"k:">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[]))),
+?line <<"*** Fail">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[trim]))),
+?line <<"*** Fail:">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[{parts,
+ 2}]))),
+?line <<"*** Fail:">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[]))),
+?line <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[trim]))),
+?line <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[{parts,
+ 2}]))),
+?line <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[]))),
+?line <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[trim]))),
+?line <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[{parts,
+ 2}]))),
+?line <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[]))),
+?line <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[trim]))),
+?line <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[{parts,
+ 2}]))),
+?line <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[]))),
+?line <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[trim]))),
+?line <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[{parts,
+ 2}]))),
+?line <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[]))),
+?line <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[trim]))),
+?line <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[{parts,
+ 2}]))),
+?line <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[]))),
+?line <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[trim]))),
+?line <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[{parts,
+ 2}]))),
+?line <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[]))),
+?line <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[trim]))),
+?line <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[{parts,
+ 2}]))),
+?line <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[]))),
+?line <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[trim]))),
+?line <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[{parts,
+ 2}]))),
+?line <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[]))),
+?line <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[trim]))),
+?line <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[{parts,
+ 2}]))),
+?line <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[]))),
+?line <<"aa:a">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[trim]))),
+?line <<"aa:abcd">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[{parts,
+ 2}]))),
+?line <<"aa:a:::">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[]))),
+?line <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[caseless,
+ trim]))),
+?line <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[caseless,
+ {parts,
+ 2}]))),
+?line <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[caseless]))),
+?line <<"aaAa">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[caseless,
+ trim]))),
+?line <<"aaAa:cd">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[caseless,
+ {parts,
+ 2}]))),
+?line <<"aaAa:::">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[caseless]))),
+?line <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[trim]))),
+?line <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[{parts,
+ 2}]))),
+?line <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[]))),
+?line <<"aa:a">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[trim]))),
+?line <<"aa:abcd">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[{parts,
+ 2}]))),
+?line <<"aa:a:::">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[]))),
+?line <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[caseless,
+ trim]))),
+?line <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[caseless,
+ {parts,
+ 2}]))),
+?line <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[caseless]))),
+?line <<"aaAa">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[caseless,
+ trim]))),
+?line <<"aaAa:cd">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[caseless,
+ {parts,
+ 2}]))),
+?line <<"aaAa:::">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[caseless]))),
+?line <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[trim]))),
+?line <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[{parts,
+ 2}]))),
+?line <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[]))),
+?line <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[trim]))),
+?line <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[{parts,
+ 2}]))),
+?line <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[]))),
+?line <<"1:.23">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[trim]))),
+?line <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[{parts,
+ 2}]))),
+?line <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[]))),
+?line <<"1:.875">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[trim]))),
+?line <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[{parts,
+ 2}]))),
+?line <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[]))),
+?line <<"1:.23">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[trim]))),
+?line <<"1:.23:">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[{parts,
+ 2}]))),
+?line <<"1:.23:">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[]))),
+?line <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))),
+?line <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts,
+ 2}]))),
+?line <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))),
+?line <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))),
+?line <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts,
+ 2}]))),
+?line <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))),
+?line <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))),
+?line <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts,
+ 2}]))),
+?line <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","a(?)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","a(?)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","a(?)b",[]))),
+?line <<"Food is on the :foo:table">> = iolist_to_binary(join(re:split("Food is on the foo table","\\b(foo)\\s+(\\w+)",[caseless,
+ trim]))),
+?line <<"Food is on the :foo:table:">> = iolist_to_binary(join(re:split("Food is on the foo table","\\b(foo)\\s+(\\w+)",[caseless,
+ {parts,
+ 2}]))),
+?line <<"Food is on the :foo:table:">> = iolist_to_binary(join(re:split("Food is on the foo table","\\b(foo)\\s+(\\w+)",[caseless]))),
+?line <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[trim]))),
+?line <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[{parts,
+ 2}]))),
+?line <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[]))),
+?line <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[trim]))),
+?line <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[{parts,
+ 2}]))),
+?line <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[]))),
+?line <<":I have 2 numbers: 53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[trim]))),
+?line <<":I have 2 numbers: 53147::">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[{parts,
+ 2}]))),
+?line <<":I have 2 numbers: 53147::">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[]))),
+?line <<":I have 2 numbers: 5314:7">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[trim]))),
+?line <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[{parts,
+ 2}]))),
+?line <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[]))),
+?line <<":I::: :::h:::a:::v:::e::: :2:: :::n:::u:::m:::b:::e:::r:::s::::::: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[trim]))),
+?line <<":I:: have 2 numbers: 53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[{parts,
+ 2}]))),
+?line <<":I::: :::h:::a:::v:::e::: :2:: :::n:::u:::m:::b:::e:::r:::s::::::: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[]))),
+?line <<":I have :2:: numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[trim]))),
+?line <<":I have :2: numbers: 53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[{parts,
+ 2}]))),
+?line <<":I have :2:: numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[]))),
+?line <<":I have 2 numbers: 5314:7">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[trim]))),
+?line <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[{parts,
+ 2}]))),
+?line <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[]))),
+?line <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[trim]))),
+?line <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[{parts,
+ 2}]))),
+?line <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[]))),
+?line <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[trim]))),
+?line <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[{parts,
+ 2}]))),
+?line <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[]))),
+?line <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[trim]))),
+?line <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[{parts,
+ 2}]))),
+?line <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[]))),
+?line <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[trim]))),
+?line <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[{parts,
+ 2}]))),
+?line <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[]))),
+?line <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[trim]))),
+?line <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[{parts,
+ 2}]))),
+?line <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[]))),
+?line <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[trim]))),
+?line <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[{parts,
+ 2}]))),
+?line <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[]))),
+?line <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[trim]))),
+?line <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[{parts,
+ 2}]))),
+?line <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[]))),
+?line <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[trim]))),
+?line <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[{parts,
+ 2}]))),
+?line <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[]))),
+?line <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[trim]))),
+?line <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[{parts,
+ 2}]))),
+?line <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[]))),
+?line <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[trim]))),
+?line <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[{parts,
+ 2}]))),
+?line <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[]))),
+?line <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[trim]))),
+?line <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[{parts,
+ 2}]))),
+?line <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[]))),
+?line <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[trim]))),
+?line <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[{parts,
+ 2}]))),
+?line <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[]))),
+?line <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[trim]))),
+?line <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[{parts,
+ 2}]))),
+?line <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[]))),
+?line <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[trim]))),
+?line <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[]))),
+?line <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[trim]))),
+?line <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[]))),
+?line <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[trim]))),
+?line <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[]))),
+?line <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[trim]))),
+?line <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[]))),
+?line <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[trim]))),
+?line <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[]))),
+?line <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[trim]))),
+?line <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[]))),
+?line <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[trim]))),
+?line <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[]))),
+?line <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[trim]))),
+?line <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[]))),
+?line <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[trim]))),
+?line <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[]))),
+?line <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[trim]))),
+?line <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[{parts,
+ 2}]))),
+?line <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[trim]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[{parts,
+ 2}]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[trim]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[{parts,
+ 2}]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[trim]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[{parts,
+ 2}]))),
+?line <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[]))),
+?line <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[trim]))),
+?line <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[{parts,
+ 2}]))),
+?line <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[{parts,
+ 2}]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[{parts,
+ 2}]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[{parts,
+ 2}]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[]))),
+?line <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[trim]))),
+?line <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[{parts,
+ 2}]))),
+?line <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[{parts,
+ 2}]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[{parts,
+ 2}]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[trim]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[{parts,
+ 2}]))),
+?line <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[trim]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[{parts,
+ 2}]))),
+?line <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[{parts,
+ 2}]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[trim]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[{parts,2}]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".{0,}\\.gif",[trim]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".{0,}\\.gif",[{parts,2}]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".{0,}\\.gif",[]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[multiline,trim]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[multiline,{parts,2}]))),
+?line <<"borfle
+:
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[multiline]))),
+?line <<":
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[dotall,trim]))),
+?line <<":
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[dotall,{parts,2}]))),
+?line <<":
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[dotall]))),
+?line <<":
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[multiline,dotall,trim]))),
+?line <<":
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[multiline,dotall,{parts,2}]))),
+?line <<":
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*\\.gif",[multiline,dotall]))),
+?line <<"borfle
+bib.gif
+">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[trim]))),
+?line <<"borfle
+bib.gif
+:">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[{parts,2}]))),
+?line <<"borfle
+bib.gif
+:">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[]))),
+?line <<":
+:
+">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,trim]))),
+?line <<":
+bib.gif
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,{parts,2}]))),
+?line <<":
+:
+:">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline]))),
+?line <<"">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[dotall,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[dotall,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[dotall]))),
+?line <<"">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,dotall,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,dotall,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,dotall]))),
+?line <<"borfle
+bib.gif
+">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[trim]))),
+?line <<"borfle
+bib.gif
+:">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[{parts,2}]))),
+?line <<"borfle
+bib.gif
+:">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[]))),
+?line <<":
+:
+">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,trim]))),
+?line <<":
+bib.gif
+no">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,{parts,2}]))),
+?line <<":
+:
+:">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline]))),
+?line <<"">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[dotall,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[dotall,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[dotall]))),
+?line <<"">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,dotall,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,dotall,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("borfle
+bib.gif
+no",".*$",[multiline,dotall]))),
+?line <<"abcde
+:1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[trim]))),
+?line <<"abcde
+:1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[{parts,2}]))),
+?line <<"abcde
+:1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[trim]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[{parts,
+ 2}]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[trim]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[{parts,2}]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[]))),
+?line <<"abcde
+:1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[multiline,trim]))),
+?line <<"abcde
+:1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[multiline,{parts,2}]))),
+?line <<"abcde
+:1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[multiline]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline,
+ trim]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline,
+ {parts,
+ 2}]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline]))),
+?line <<"abcde
+:B:ar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[multiline,trim]))),
+?line <<"abcde
+:B:ar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[multiline,{parts,2}]))),
+?line <<"abcde
+:B:ar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[multiline]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[dotall,trim]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[dotall,{parts,2}]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[dotall]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[dotall,
+ trim]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[dotall,
+ {parts,
+ 2}]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[dotall]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[dotall,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[dotall,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[dotall]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[dotall,trim]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[dotall,{parts,2}]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[dotall]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[multiline,dotall,trim]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[multiline,dotall,{parts,2}]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(.*X|^B)",[multiline,dotall]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline,
+ dotall,
+ trim]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline,
+ dotall,
+ {parts,
+ 2}]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline,
+ dotall]))),
+?line <<"abcde
+:B:ar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[multiline,dotall,trim]))),
+?line <<"abcde
+:B:ar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[multiline,dotall,{parts,2}]))),
+?line <<"abcde
+:B:ar">> = iolist_to_binary(join(re:split("abcde
+Bar","(.*X|^B)",[multiline,dotall]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(?s)(.*X|^B)",[trim]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(?s)(.*X|^B)",[{parts,2}]))),
+?line <<":abcde
+1234X:yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(?s)(.*X|^B)",[]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[trim]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[{parts,
+ 2}]))),
+?line <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(?s)(.*X|^B)",[trim]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(?s)(.*X|^B)",[{parts,2}]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(?s)(.*X|^B)",[]))),
+?line <<":yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(?s:.*X|^B)",[trim]))),
+?line <<":yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(?s:.*X|^B)",[{parts,2}]))),
+?line <<":yz">> = iolist_to_binary(join(re:split("abcde
+1234Xyz","(?s:.*X|^B)",[]))),
+?line <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[trim]))),
+?line <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[{parts,
+ 2}]))),
+?line <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(?s:.*X|^B)",[trim]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(?s:.*X|^B)",[{parts,2}]))),
+?line <<"abcde
+Bar">> = iolist_to_binary(join(re:split("abcde
+Bar","(?s:.*X|^B)",[]))),
+?line <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[trim]))),
+?line <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[{parts,
+ 2}]))),
+?line <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[]))),
+?line <<"abc
+B">> = iolist_to_binary(join(re:split("abc
+B","^.*B",[trim]))),
+?line <<"abc
+B">> = iolist_to_binary(join(re:split("abc
+B","^.*B",[{parts,2}]))),
+?line <<"abc
+B">> = iolist_to_binary(join(re:split("abc
+B","^.*B",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc
+B","(?s)^.*B",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc
+B","(?s)^.*B",[{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc
+B","(?s)^.*B",[]))),
+?line <<"abc
+">> = iolist_to_binary(join(re:split("abc
+B","(?m)^.*B",[trim]))),
+?line <<"abc
+:">> = iolist_to_binary(join(re:split("abc
+B","(?m)^.*B",[{parts,2}]))),
+?line <<"abc
+:">> = iolist_to_binary(join(re:split("abc
+B","(?m)^.*B",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc
+B","(?ms)^.*B",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc
+B","(?ms)^.*B",[{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc
+B","(?ms)^.*B",[]))),
+?line <<"abc
+">> = iolist_to_binary(join(re:split("abc
+B","(?ms)^B",[trim]))),
+?line <<"abc
+:">> = iolist_to_binary(join(re:split("abc
+B","(?ms)^B",[{parts,2}]))),
+?line <<"abc
+:">> = iolist_to_binary(join(re:split("abc
+B","(?ms)^B",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("B","(?s)B$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("B","(?s)B$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("B","(?s)B$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[trim]))),
+?line <<":c:">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[{parts,
+ 2}]))),
+?line <<":c:">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))),
+?line <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))),
+?line <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[{parts,
+ 2}]))),
+?line <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[]))),
+?line <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[trim]))),
+?line <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[{parts,
+ 2}]))),
+?line <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[]))),
+?line <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[trim]))),
+?line <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[{parts,
+ 2}]))),
+?line <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[]))),
+?line <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[trim]))),
+?line <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[{parts,
+ 2}]))),
+?line <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a(b*)",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","a(b*)",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","a(b*)",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("ab","a(b*)",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","a(b*)",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","a(b*)",[]))),
+?line <<":bbbb">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[trim]))),
+?line <<":bbbb:">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[{parts,
+ 2}]))),
+?line <<":bbbb:">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[]))),
+?line <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[trim]))),
+?line <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[{parts,
+ 2}]))),
+?line <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[]))),
+?line <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[trim]))),
+?line <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[{parts,
+ 2}]))),
+?line <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[]))),
+?line <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[trim]))),
+?line <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[{parts,
+ 2}]))),
+?line <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[]))),
+?line <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[trim]))),
+?line <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[{parts,
+ 2}]))),
+?line <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[]))),
+?line <<": brown fox">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[trim]))),
+?line <<": brown fox:">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[{parts,
+ 2}]))),
+?line <<": brown fox:">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[]))),
+?line <<"a:b:c">> = iolist_to_binary(join(re:split("abc","",[trim]))),
+?line <<"a:bc">> = iolist_to_binary(join(re:split("abc","",[{parts,
+ 2}]))),
+?line <<"a:b:c:">> = iolist_to_binary(join(re:split("abc","",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("acb","a[^a]b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a
+b","a[^a]b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","a[^a]b",[{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","a[^a]b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("acb","a.b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a.b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a.b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[]))),
+?line <<"a
+b">> = iolist_to_binary(join(re:split("a
+b","a.b",[trim]))),
+?line <<"a
+b">> = iolist_to_binary(join(re:split("a
+b","a.b",[{parts,2}]))),
+?line <<"a
+b">> = iolist_to_binary(join(re:split("a
+b","a.b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("acb","a[^a]b",[dotall,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[dotall,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[dotall]))),
+?line <<"">> = iolist_to_binary(join(re:split("a
+b","a[^a]b",[dotall,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","a[^a]b",[dotall,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","a[^a]b",[dotall]))),
+?line <<"">> = iolist_to_binary(join(re:split("acb","a.b",[dotall,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a.b",[dotall,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("acb","a.b",[dotall]))),
+?line <<"">> = iolist_to_binary(join(re:split("a
+b","a.b",[dotall,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","a.b",[dotall,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a
+b","a.b",[dotall]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[]))),
+?line <<"x
+b">> = iolist_to_binary(join(re:split("x
+b","(?!\\A)x",[multiline,trim]))),
+?line <<"x
+b">> = iolist_to_binary(join(re:split("x
+b","(?!\\A)x",[multiline,{parts,2}]))),
+?line <<"x
+b">> = iolist_to_binary(join(re:split("x
+b","(?!\\A)x",[multiline]))),
+?line <<"a">> = iolist_to_binary(join(re:split("ax","(?!\\A)x",[multiline,
+ trim]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("ax","(?!\\A)x",[multiline,
+ {parts,
+ 2}]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("ax","(?!\\A)x",[multiline]))),
+?line <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[trim]))),
+?line <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[{parts,
+ 2}]))),
+?line <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[]))),
+?line <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[trim]))),
+?line <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[{parts,
+ 2}]))),
+?line <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[]))),
+?line <<":AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[trim]))),
+?line <<":AB:">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[{parts,
+ 2}]))),
+?line <<":AB:">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[]))),
+?line <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[trim]))),
+?line <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[]))),
+?line <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[trim]))),
+?line <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[]))),
+?line <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[trim]))),
+?line <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[trim]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[]))),
+?line <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[trim]))),
+?line <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[]))),
+?line <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[trim]))),
+?line <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[]))),
+?line <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[trim]))),
+?line <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[trim]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[]))),
+?line <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[trim]))),
+?line <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[{parts,
+ 2}]))),
+?line <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[]))),
+?line <<"fooa:foo">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[trim]))),
+?line <<"fooa:foo:">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[{parts,
+ 2}]))),
+?line <<"fooa:foo:">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[]))),
+?line <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[trim]))),
+?line <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[{parts,
+ 2}]))),
+?line <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[]))),
+?line <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[trim]))),
+?line <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[{parts,
+ 2}]))),
+?line <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\z",[multiline,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\z",[multiline,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\z",[multiline]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","\\Aabc\\z",[multiline,trim]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","\\Aabc\\z",[multiline,{parts,2}]))),
+?line <<"qqq
+abc">> = iolist_to_binary(join(re:split("qqq
+abc","\\Aabc\\z",[multiline]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","\\Aabc\\z",[multiline,trim]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","\\Aabc\\z",[multiline,{parts,2}]))),
+?line <<"abc
+zzz">> = iolist_to_binary(join(re:split("abc
+zzz","\\Aabc\\z",[multiline]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","\\Aabc\\z",[multiline,trim]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","\\Aabc\\z",[multiline,{parts,2}]))),
+?line <<"qqq
+abc
+zzz">> = iolist_to_binary(join(re:split("qqq
+abc
+zzz","\\Aabc\\z",[multiline]))),
+?line <<"1:.23">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))),
+?line <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts,
+ 2}]))),
+?line <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[]))),
+?line <<"1:.875">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))),
+?line <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts,
+ 2}]))),
+?line <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[]))),
+?line <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))),
+?line <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts,
+ 2}]))),
+?line <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[]))),
+?line <<":party">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[trim]))),
+?line <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[{parts,
+ 2}]))),
+?line <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[]))),
+?line <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[trim]))),
+?line <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[{parts,
+ 2}]))),
+?line <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[]))),
+?line <<":12345:a">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[trim]))),
+?line <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[{parts,
+ 2}]))),
+?line <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[]))),
+?line <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[trim]))),
+?line <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[{parts,
+ 2}]))),
+?line <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[]))),
+?line <<":12345:a">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[trim]))),
+?line <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[{parts,
+ 2}]))),
+?line <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[]))),
+?line <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[trim]))),
+?line <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[{parts,
+ 2}]))),
+?line <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[]))),
+?line <<":aaab">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[trim]))),
+?line <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[{parts,
+ 2}]))),
+?line <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[]))),
+?line <<":aaa">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[trim]))),
+?line <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[{parts,
+ 2}]))),
+?line <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[]))),
+?line <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[trim]))),
+?line <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[{parts,
+ 2}]))),
+?line <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[]))),
+?line <<"::::d">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[trim]))),
+?line <<":cccd">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[{parts,
+ 2}]))),
+?line <<"::::d">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[]))),
+?line <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[trim]))),
+?line <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[{parts,
+ 2}]))),
+?line <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))),
+?line <<":xyz">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))),
+?line <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts,
+ 2}]))),
+?line <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))),
+?line <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))),
+?line <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts,
+ 2}]))),
+?line <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","a(?-i)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","a(?-i)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","a(?-i)b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("Ab","a(?-i)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("Ab","a(?-i)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("Ab","a(?-i)b",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?-i)b",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?-i)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?-i)b",[caseless]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","a(?-i)b",[caseless,
+ trim]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","a(?-i)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","a(?-i)b",[caseless]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","a(?-i)b",[caseless,
+ trim]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","a(?-i)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","a(?-i)b",[caseless]))),
+?line <<":a bc">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[trim]))),
+?line <<":a bc:">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[{parts,
+ 2}]))),
+?line <<":a bc:">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[]))),
+?line <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[trim]))),
+?line <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[{parts,
+ 2}]))),
+?line <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[]))),
+?line <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[trim]))),
+?line <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[{parts,
+ 2}]))),
+?line <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[]))),
+?line <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[trim]))),
+?line <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[{parts,
+ 2}]))),
+?line <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[]))),
+?line <<":a bcde f">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[trim]))),
+?line <<":a bcde f:">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[{parts,
+ 2}]))),
+?line <<":a bcde f:">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[]))),
+?line <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[trim]))),
+?line <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[{parts,
+ 2}]))),
+?line <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[]))),
+?line <<":ab">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[trim]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[]))),
+?line <<":aB">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[trim]))),
+?line <<":aB:">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<":aB:">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[trim]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[trim]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[]))),
+?line <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[trim]))),
+?line <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[]))),
+?line <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[trim]))),
+?line <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[trim]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[]))),
+?line <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[trim]))),
+?line <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[{parts,
+ 2}]))),
+?line <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[trim]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[{parts,
+ 2}]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[trim]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[{parts,
+ 2}]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[trim]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[{parts,
+ 2}]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[trim]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[{parts,
+ 2}]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[]))),
+?line <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[trim]))),
+?line <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[{parts,
+ 2}]))),
+?line <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[]))),
+?line <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[trim]))),
+?line <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[{parts,
+ 2}]))),
+?line <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[]))),
+?line <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[trim]))),
+?line <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[{parts,
+ 2}]))),
+?line <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("more than million","(?s-i:more.*than).*million",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than million","(?s-i:more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than million","(?s-i:more.*than).*million",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("more than MILLION","(?s-i:more.*than).*million",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?s-i:more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?s-i:more.*than).*million",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("more
+ than Million","(?s-i:more.*than).*million",[caseless,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("more
+ than Million","(?s-i:more.*than).*million",[caseless,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("more
+ than Million","(?s-i:more.*than).*million",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s-i:more.*than).*million",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s-i:more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s-i:more.*than).*million",[caseless]))),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?s-i:more.*than).*million",[caseless,
+ trim]))),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?s-i:more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?s-i:more.*than).*million",[caseless]))),
+?line <<"more
+ than
+ million">> = iolist_to_binary(join(re:split("more
+ than
+ million","(?s-i:more.*than).*million",[caseless,trim]))),
+?line <<"more
+ than
+ million">> = iolist_to_binary(join(re:split("more
+ than
+ million","(?s-i:more.*than).*million",[caseless,{parts,2}]))),
+?line <<"more
+ than
+ million">> = iolist_to_binary(join(re:split("more
+ than
+ million","(?s-i:more.*than).*million",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("more than million","(?:(?s-i)more.*than).*million",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than million","(?:(?s-i)more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than million","(?:(?s-i)more.*than).*million",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("more than MILLION","(?:(?s-i)more.*than).*million",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?:(?s-i)more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?:(?s-i)more.*than).*million",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("more
+ than Million","(?:(?s-i)more.*than).*million",[caseless,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("more
+ than Million","(?:(?s-i)more.*than).*million",[caseless,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("more
+ than Million","(?:(?s-i)more.*than).*million",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?s-i)more.*than).*million",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?s-i)more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?s-i)more.*than).*million",[caseless]))),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?:(?s-i)more.*than).*million",[caseless,
+ trim]))),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?:(?s-i)more.*than).*million",[caseless,
+ {parts,
+ 2}]))),
+?line <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?:(?s-i)more.*than).*million",[caseless]))),
+?line <<"more
+ than
+ million">> = iolist_to_binary(join(re:split("more
+ than
+ million","(?:(?s-i)more.*than).*million",[caseless,trim]))),
+?line <<"more
+ than
+ million">> = iolist_to_binary(join(re:split("more
+ than
+ million","(?:(?s-i)more.*than).*million",[caseless,{parts,2}]))),
+?line <<"more
+ than
+ million">> = iolist_to_binary(join(re:split("more
+ than
+ million","(?:(?s-i)more.*than).*million",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[]))),
+?line <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[trim]))),
+?line <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[{parts,
+ 2}]))),
+?line <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[]))),
+?line <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[trim]))),
+?line <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[{parts,
+ 2}]))),
+?line <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[]))),
+?line <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[trim]))),
+?line <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[{parts,
+ 2}]))),
+?line <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[trim]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[{parts,
+ 2}]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[trim]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[{parts,
+ 2}]))),
+?line <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[trim]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[{parts,
+ 2}]))),
+?line <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[]))),
+?line <<"ab:xx">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))),
+?line <<"ab:xx:">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[{parts,
+ 2}]))),
+?line <<"ab:xx:">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[]))),
+?line <<"aB:xx">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))),
+?line <<"aB:xx:">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[{parts,
+ 2}]))),
+?line <<"aB:xx:">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[]))),
+?line <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))),
+?line <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[{parts,
+ 2}]))),
+?line <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[]))),
+?line <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))),
+?line <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[{parts,
+ 2}]))),
+?line <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[]))),
+?line <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[trim]))),
+?line <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[{parts,
+ 2}]))),
+?line <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[trim]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[{parts,
+ 2}]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[]))),
+?line <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[trim]))),
+?line <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[{parts,
+ 2}]))),
+?line <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[trim]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[{parts,
+ 2}]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts,
+ 2}]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts,
+ 2}]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts,
+ 2}]))),
+?line <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts,
+ 2}]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))),
+?line <<"foo">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[trim]))),
+?line <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[{parts,
+ 2}]))),
+?line <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[]))),
+?line <<"f">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[trim]))),
+?line <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[{parts,
+ 2}]))),
+?line <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[]))),
+?line <<"fo">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[trim]))),
+?line <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[{parts,
+ 2}]))),
+?line <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[]))),
+?line <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[trim]))),
+?line <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[{parts,
+ 2}]))),
+?line <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[]))),
+?line <<"foo">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[trim]))),
+?line <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[{parts,
+ 2}]))),
+?line <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[]))),
+?line <<"f">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[trim]))),
+?line <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[{parts,
+ 2}]))),
+?line <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[]))),
+?line <<"fo">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[trim]))),
+?line <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[{parts,
+ 2}]))),
+?line <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[]))),
+?line <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[trim]))),
+?line <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[{parts,
+ 2}]))),
+?line <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ {parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))),
+?line <<":(">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ trim]))),
+?line <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ {parts,
+ 2}]))),
+?line <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))),
+?line <<":::(">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ trim]))),
+?line <<"::(abcd) fox">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ {parts,
+ 2}]))),
+?line <<":::(:::">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))),
+?line <<"(">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ trim]))),
+?line <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended,
+ {parts,
+ 2}]))),
+?line <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))),
+?line <<":(">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ trim]))),
+?line <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))),
+?line <<":::(">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ trim]))),
+?line <<"::(abcd) fox">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<":::(:::">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))),
+?line <<"(">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ trim]))),
+?line <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))),
+?line <<":1:2">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[trim]))),
+?line <<":1:2:">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[{parts,
+ 2}]))),
+?line <<":1:2:">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[]))),
+?line <<":1:2">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[trim]))),
+?line <<":1:2:">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[{parts,
+ 2}]))),
+?line <<":1:2:">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[]))),
+?line <<":1:2">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[trim]))),
+?line <<":1:2:">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[{parts,
+ 2}]))),
+?line <<":1:2:">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[]))),
+?line <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[trim]))),
+?line <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[{parts,
+ 2}]))),
+?line <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[]))),
+?line <<":blah">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[trim]))),
+?line <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[]))),
+?line <<":BLAH">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[trim]))),
+?line <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[]))),
+?line <<":Blah">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[trim]))),
+?line <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[]))),
+?line <<":blaH">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[trim]))),
+?line <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[]))),
+?line <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[trim]))),
+?line <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[]))),
+?line <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[trim]))),
+?line <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[]))),
+?line <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[trim]))),
+?line <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[{parts,
+ 2}]))),
+?line <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[]))),
+?line <<":blah">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[trim]))),
+?line <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[{parts,
+ 2}]))),
+?line <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[]))),
+?line <<":BLAH">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[trim]))),
+?line <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[{parts,
+ 2}]))),
+?line <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[]))),
+?line <<":Blah">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[trim]))),
+?line <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[{parts,
+ 2}]))),
+?line <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[]))),
+?line <<":blaH">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[trim]))),
+?line <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[{parts,
+ 2}]))),
+?line <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[]))),
+?line <<":blah">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[trim]))),
+?line <<":blah:">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[{parts,
+ 2}]))),
+?line <<":blah:">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[]))),
+?line <<":Blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[trim]))),
+?line <<":Blah:">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[{parts,
+ 2}]))),
+?line <<":Blah:">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[]))),
+?line <<":blaH">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[trim]))),
+?line <<":blaH:">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[{parts,
+ 2}]))),
+?line <<":blaH:">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","(?>a*)*",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","(abc|)+",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abc","(abc|)+",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abc","(abc|)+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[]))),
+?line <<"x::y::z">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[trim]))),
+?line <<"x::yz">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[{parts,
+ 2}]))),
+?line <<"x::y::z::">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","([a]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([a]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([a]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","([ab]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([ab]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([ab]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","([ab]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([ab]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([ab]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[]))),
+?line <<"::c::d::e">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[trim]))),
+?line <<"::cde">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[{parts,
+ 2}]))),
+?line <<"::c::d::e::">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","([^a]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([^a]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([^a]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[]))),
+?line <<"a::a::a">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[trim]))),
+?line <<"a::aa">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[{parts,
+ 2}]))),
+?line <<"a::a::a::">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[]))),
+?line <<"a::b::a::b">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[trim]))),
+?line <<"a::bab">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[{parts,
+ 2}]))),
+?line <<"a::b::a::b::">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","([a]*?)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([a]*?)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([a]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[trim]))),
+?line <<"::aaa">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[{parts,
+ 2}]))),
+?line <<"::::::::">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[trim]))),
+?line <<"::bab">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[{parts,
+ 2}]))),
+?line <<"::::::::">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[trim]))),
+?line <<"::aba">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[{parts,
+ 2}]))),
+?line <<"::::::::">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[trim]))),
+?line <<"::bbb">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[{parts,
+ 2}]))),
+?line <<"::::::::">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[]))),
+?line <<"a::a::a">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[trim]))),
+?line <<"a::aa">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[{parts,
+ 2}]))),
+?line <<"a::a::a::">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[trim]))),
+?line <<"::ccc">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[{parts,
+ 2}]))),
+?line <<"::::::::">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[]))),
+?line <<"b::a::b::a">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[trim]))),
+?line <<"b::aba">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[{parts,
+ 2}]))),
+?line <<"b::a::b::a::">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","(?>a*)*",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[]))),
+?line <<":b:c:d:e">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[trim]))),
+?line <<":bcde">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[{parts,
+ 2}]))),
+?line <<":b:c:d:e:">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[]))),
+?line <<"::b::b">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[trim]))),
+?line <<"::bbaa">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[{parts,
+ 2}]))),
+?line <<"::b::b::">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[]))),
+?line <<"a::a::a::a::a">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[trim]))),
+?line <<"a::aaaa">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[{parts,
+ 2}]))),
+?line <<"a::a::a::a::a::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[]))),
+?line <<"a::a::b::b::a::a">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[trim]))),
+?line <<"a::abbaa">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[{parts,
+ 2}]))),
+?line <<"a::a::b::b::a::a::">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))),
+?line <<"sep-12-98">> = iolist_to_binary(join(re:split("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ trim]))),
+?line <<"sep-12-98">> = iolist_to_binary(join(re:split("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended,
+ {parts,
+ 2}]))),
+?line <<"sep-12-98">> = iolist_to_binary(join(re:split("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))),
+?line <<"foo:foo">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[trim]))),
+?line <<"foo:foo:">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[{parts,
+ 2}]))),
+?line <<"foo:foo:">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[]))),
+?line <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[trim]))),
+?line <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[{parts,
+ 2}]))),
+?line <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[]))),
+?line <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[trim]))),
+?line <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[{parts,
+ 2}]))),
+?line <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[trim]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[{parts,
+ 2}]))),
+?line <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[]))),
+?line <<":aBC">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[trim]))),
+?line <<":aBC:">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<":aBC:">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[]))),
+?line <<":bb">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[trim]))),
+?line <<":bb:">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<":bb:">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[]))),
+?line <<":BB">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[trim]))),
+?line <<":BB:">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<":BB:">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[]))),
+?line <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[trim]))),
+?line <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[]))),
+?line <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[trim]))),
+?line <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[]))),
+?line <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[trim]))),
+?line <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[]))),
+?line <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[trim]))),
+?line <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[{parts,
+ 2}]))),
+?line <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[]))),
+?line <<":ac">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<":ac:">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<":ac:">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[]))),
+?line <<":aC">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<":aC:">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<":aC:">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[]))),
+?line <<":bD">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<":bD:">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<":bD:">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[]))),
+?line <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[]))),
+?line <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[]))),
+?line <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[]))),
+?line <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[]))),
+?line <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[trim]))),
+?line <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[{parts,
+ 2}]))),
+?line <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[]))),
+?line <<":ab">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<":aBd">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<":aBd:">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<":aBd:">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<":xy">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<":xy:">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<":xy:">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<":xY">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<":xY:">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<":xY:">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))),
+?line <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts,
+ 2}]))),
+?line <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))),
+?line <<"foo
+">> = iolist_to_binary(join(re:split("foo
+bar","(?<=foo\\n)^bar",[multiline,trim]))),
+?line <<"foo
+:">> = iolist_to_binary(join(re:split("foo
+bar","(?<=foo\\n)^bar",[multiline,{parts,2}]))),
+?line <<"foo
+:">> = iolist_to_binary(join(re:split("foo
+bar","(?<=foo\\n)^bar",[multiline]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=foo\\n)^bar",[multiline,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=foo\\n)^bar",[multiline,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=foo\\n)^bar",[multiline]))),
+?line <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=foo\\n)^bar",[multiline,
+ trim]))),
+?line <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=foo\\n)^bar",[multiline,
+ {parts,
+ 2}]))),
+?line <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=foo\\n)^bar",[multiline]))),
+?line <<"baz
+bar">> = iolist_to_binary(join(re:split("baz
+bar","(?<=foo\\n)^bar",[multiline,trim]))),
+?line <<"baz
+bar">> = iolist_to_binary(join(re:split("baz
+bar","(?<=foo\\n)^bar",[multiline,{parts,2}]))),
+?line <<"baz
+bar">> = iolist_to_binary(join(re:split("baz
+bar","(?<=foo\\n)^bar",[multiline]))),
+?line <<"bar">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[trim]))),
+?line <<"bar:">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[{parts,
+ 2}]))),
+?line <<"bar:">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[]))),
+?line <<"barbar">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[trim]))),
+?line <<"barbar:">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[{parts,
+ 2}]))),
+?line <<"barbar:">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[]))),
+?line <<"koobar">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[trim]))),
+?line <<"koobar:">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[{parts,
+ 2}]))),
+?line <<"koobar:">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[]))),
+?line <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[trim]))),
+?line <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[{parts,
+ 2}]))),
+?line <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[]))),
+?line <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[trim]))),
+?line <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[{parts,
+ 2}]))),
+?line <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[trim]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?){4}$",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?){4}$",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<":a:a:a:a">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<":a:a:a:a:">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<":a:a:a:a:">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<":a:aa:a:a">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<":a:aa:a:a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<":a:aa:a:a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<":a:aa:a:aa">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<":a:aa:a:aa:">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<":a:aa:a:aa:">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<":a:aa:aaa:a">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<":a:aa:aaa:a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<":a:aa:aaa:a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<":a:aa:aaa:aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<":a:aa:aaa:aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<":a:aa:aaa:aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","abc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","abc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","abc",[]))),
+?line <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[trim]))),
+?line <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[{parts,
+ 2}]))),
+?line <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ababc","abc",[trim]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("ababc","abc",[{parts,
+ 2}]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("ababc","abc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[]))),
+?line <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[trim]))),
+?line <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[{parts,
+ 2}]))),
+?line <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[]))),
+?line <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[trim]))),
+?line <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[{parts,
+ 2}]))),
+?line <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[]))),
+?line <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[trim]))),
+?line <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[{parts,
+ 2}]))),
+?line <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","ab*c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab*c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab*c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","ab*bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab*bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab*bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbc","ab*bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab*bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab*bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[trim]))),
+?line <<":bbbbc">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[{parts,
+ 2}]))),
+?line <<"::::::">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[]))),
+?line <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[trim]))),
+?line <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[{parts,
+ 2}]))),
+?line <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbc","ab+bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab+bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab+bc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[]))),
+?line <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[trim]))),
+?line <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[{parts,
+ 2}]))),
+?line <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[]))),
+?line <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[trim]))),
+?line <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[{parts,
+ 2}]))),
+?line <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[]))),
+?line <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[trim]))),
+?line <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[{parts,
+ 2}]))),
+?line <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abbc","ab?bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab?bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abbc","ab?bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","ab?bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab?bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab?bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","ab?c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab?c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab?c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[]))),
+?line <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[trim]))),
+?line <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[{parts,
+ 2}]))),
+?line <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[]))),
+?line <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[trim]))),
+?line <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[{parts,
+ 2}]))),
+?line <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[trim]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[{parts,
+ 2}]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("aabc","abc$",[trim]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[{parts,
+ 2}]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("aabc","abc$",[trim]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[{parts,
+ 2}]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[]))),
+?line <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[trim]))),
+?line <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[{parts,
+ 2}]))),
+?line <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","$",[trim]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","$",[{parts,
+ 2}]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","a.c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","a.c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","a.c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("axc","a.c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("axc","a.c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("axc","a.c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("axyzc","a.*c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("axyzc","a.*c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("axyzc","a.*c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abd","a[bc]d",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abd","a[bc]d",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abd","a[bc]d",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[]))),
+?line <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[trim]))),
+?line <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[{parts,
+ 2}]))),
+?line <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("aac","a[b-d]",[trim]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("aac","a[b-d]",[{parts,
+ 2}]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("aac","a[b-d]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-","a[-b]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-","a[-b]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-","a[-b]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-","a[b-]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-","a[b-]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-","a[b-]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a]","a]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a]","a]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a]","a]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a]b","a[]]b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a]b","a[]]b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a]b","a[]]b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[]))),
+?line <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[trim]))),
+?line <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[{parts,
+ 2}]))),
+?line <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[]))),
+?line <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[trim]))),
+?line <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[{parts,
+ 2}]))),
+?line <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[]))),
+?line <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[trim]))),
+?line <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[{parts,
+ 2}]))),
+?line <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[]))),
+?line <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[trim]))),
+?line <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[{parts,
+ 2}]))),
+?line <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[trim]))),
+?line <<"-:">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[{parts,
+ 2}]))),
+?line <<"-:">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[]))),
+?line <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[trim]))),
+?line <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[{parts,
+ 2}]))),
+?line <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[]))),
+?line <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[trim]))),
+?line <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[{parts,
+ 2}]))),
+?line <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[]))),
+?line <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[trim]))),
+?line <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[{parts,
+ 2}]))),
+?line <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[trim]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[{parts,
+ 2}]))),
+?line <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[trim]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[{parts,
+ 2}]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[]))),
+?line <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[trim]))),
+?line <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[{parts,
+ 2}]))),
+?line <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[]))),
+?line <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[trim]))),
+?line <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[{parts,
+ 2}]))),
+?line <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[]))),
+?line <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[trim]))),
+?line <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[{parts,
+ 2}]))),
+?line <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[]))),
+?line <<"x">> = iolist_to_binary(join(re:split("xy","\\By\\b",[trim]))),
+?line <<"x:">> = iolist_to_binary(join(re:split("xy","\\By\\b",[{parts,
+ 2}]))),
+?line <<"x:">> = iolist_to_binary(join(re:split("xy","\\By\\b",[]))),
+?line <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[trim]))),
+?line <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[{parts,
+ 2}]))),
+?line <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[]))),
+?line <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[trim]))),
+?line <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[{parts,
+ 2}]))),
+?line <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","\\W",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\W",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\W",[]))),
+?line <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[trim]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[{parts,
+ 2}]))),
+?line <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","\\W",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\W",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\W",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","\\W",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","\\W",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","\\W",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a b","a\\sb",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a b","a\\sb",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a b","a\\sb",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[]))),
+?line <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[trim]))),
+?line <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[{parts,
+ 2}]))),
+?line <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1","\\d",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1","\\d",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1","\\d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","\\D",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\D",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\D",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("*** Failers","\\D",[trim]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\D",[{parts,
+ 2}]))),
+?line <<":::::::::::">> = iolist_to_binary(join(re:split("*** Failers","\\D",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","\\D",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\D",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","\\D",[]))),
+?line <<"1">> = iolist_to_binary(join(re:split("1","\\D",[trim]))),
+?line <<"1">> = iolist_to_binary(join(re:split("1","\\D",[{parts,
+ 2}]))),
+?line <<"1">> = iolist_to_binary(join(re:split("1","\\D",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","[\\w]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","[\\w]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","[\\w]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","[\\W]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[]))),
+?line <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[trim]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[{parts,
+ 2}]))),
+?line <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","[\\W]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[]))),
+?line <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[trim]))),
+?line <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[{parts,
+ 2}]))),
+?line <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("1","[\\d]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("1","[\\d]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("1","[\\d]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","[\\D]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[trim]))),
+?line <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[{parts,
+ 2}]))),
+?line <<":::::::::::">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","[\\D]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[]))),
+?line <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[trim]))),
+?line <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[{parts,
+ 2}]))),
+?line <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[trim]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[{parts,
+ 2}]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","ab|cd",[trim]))),
+?line <<":cd">> = iolist_to_binary(join(re:split("abcd","ab|cd",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcd","ab|cd",[]))),
+?line <<"d">> = iolist_to_binary(join(re:split("def","()ef",[trim]))),
+?line <<"d::">> = iolist_to_binary(join(re:split("def","()ef",[{parts,
+ 2}]))),
+?line <<"d::">> = iolist_to_binary(join(re:split("def","()ef",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a(b","a\\(b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a(b","a\\(b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a(b","a\\(b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","a\\(*b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","a\\(*b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","a\\(*b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[]))),
+?line <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[trim]))),
+?line <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[{parts,
+ 2}]))),
+?line <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[]))),
+?line <<":a:c">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[trim]))),
+?line <<":a:c:">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[{parts,
+ 2}]))),
+?line <<":a:c:">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[]))),
+?line <<"aabb">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[trim]))),
+?line <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[{parts,
+ 2}]))),
+?line <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[]))),
+?line <<"aabb">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[trim]))),
+?line <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[{parts,
+ 2}]))),
+?line <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[trim]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[trim]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[]))),
+?line <<":a::b">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[{parts,
+ 2}]))),
+?line <<":a::b:">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[]))),
+?line <<":a::b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[{parts,
+ 2}]))),
+?line <<":a::b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("cde","[^ab]*",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("cde","[^ab]*",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("cde","[^ab]*",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","abc",[trim]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","abc",[{parts,
+ 2}]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","abc",[]))),
+?line <<":c">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[trim]))),
+?line <<":c:">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[{parts,
+ 2}]))),
+?line <<":c:">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[]))),
+?line <<"x:y:z">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[trim]))),
+?line <<"x:yabbbz">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[{parts,
+ 2}]))),
+?line <<"x:y:z">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[]))),
+?line <<"x:y:z">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[trim]))),
+?line <<"x:yabbbz">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[{parts,
+ 2}]))),
+?line <<"x:y:z">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[]))),
+?line <<"ab:cd">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[trim]))),
+?line <<"ab:cd:">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[{parts,
+ 2}]))),
+?line <<"ab:cd:">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[]))),
+?line <<"abcd">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[trim]))),
+?line <<"abcd::">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[{parts,
+ 2}]))),
+?line <<"abcd::">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[]))),
+?line <<"a:b">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[trim]))),
+?line <<"a:b:">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[{parts,
+ 2}]))),
+?line <<"a:b:">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[]))),
+?line <<":bc">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[trim]))),
+?line <<":bc:">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[{parts,
+ 2}]))),
+?line <<":bc:">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[]))),
+?line <<":bc:d">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[trim]))),
+?line <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[{parts,
+ 2}]))),
+?line <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[]))),
+?line <<":bc:d">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[trim]))),
+?line <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[{parts,
+ 2}]))),
+?line <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[]))),
+?line <<":b:cd">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[trim]))),
+?line <<":b:cd:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[{parts,
+ 2}]))),
+?line <<":b:cd:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[]))),
+?line <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[trim]))),
+?line <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[{parts,
+ 2}]))),
+?line <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[]))),
+?line <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[trim]))),
+?line <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[{parts,
+ 2}]))),
+?line <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[]))),
+?line <<":ab">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[trim]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[{parts,
+ 2}]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[]))),
+?line <<":abc:a:b:d">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[trim]))),
+?line <<":abc:a:b:d:">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[{parts,
+ 2}]))),
+?line <<":abc:a:b:d:">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[trim]))),
+?line <<"a::">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[{parts,
+ 2}]))),
+?line <<"a::">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[]))),
+?line <<":effgz">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[trim]))),
+?line <<":effgz::">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[{parts,
+ 2}]))),
+?line <<":effgz::">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[]))),
+?line <<":ij:j">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[trim]))),
+?line <<":ij:j:">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[{parts,
+ 2}]))),
+?line <<":ij:j:">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[]))),
+?line <<"r:effgz">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[trim]))),
+?line <<"r:effgz::">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[{parts,
+ 2}]))),
+?line <<"r:effgz::">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[]))),
+?line <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[trim]))),
+?line <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[{parts,
+ 2}]))),
+?line <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[]))),
+?line <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[trim]))),
+?line <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[{parts,
+ 2}]))),
+?line <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[]))),
+?line <<":a:a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[trim]))),
+?line <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[{parts,
+ 2}]))),
+?line <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[]))),
+?line <<":a:a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[trim]))),
+?line <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[{parts,
+ 2}]))),
+?line <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[]))),
+?line <<":a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[trim]))),
+?line <<":a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[{parts,
+ 2}]))),
+?line <<":a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[trim]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[{parts,
+ 2}]))),
+?line <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[]))),
+?line <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[trim]))),
+?line <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[{parts,
+ 2}]))),
+?line <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[]))),
+?line <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[trim]))),
+?line <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[{parts,
+ 2}]))),
+?line <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[]))),
+?line <<":ab:de">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[trim]))),
+?line <<":ab:de:">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[{parts,
+ 2}]))),
+?line <<":ab:de:">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[trim]))),
+?line <<":a:b:">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[{parts,
+ 2}]))),
+?line <<":a:b:">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","abcd",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","abcd",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcd","abcd",[]))),
+?line <<":bc">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[trim]))),
+?line <<":bc:">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[{parts,
+ 2}]))),
+?line <<":bc:">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ac","a[-]?c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ac","a[-]?c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ac","a[-]?c",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("a","(a)|\\1",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("a","(a)|\\1",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("a","(a)|\\1",[]))),
+?line <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[trim]))),
+?line <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[{parts,
+ 2}]))),
+?line <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[trim]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[{parts,
+ 2}]))),
+?line <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[]))),
+?line <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[trim]))),
+?line <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[{parts,
+ 2}]))),
+?line <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[]))),
+?line <<":bb:b:b:cbc:c">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[trim]))),
+?line <<":bb:b:bcbc">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[{parts,
+ 2}]))),
+?line <<":bb:b:b:cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[]))),
+?line <<":cbc:c">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[trim]))),
+?line <<":cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[{parts,
+ 2}]))),
+?line <<":cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[]))),
+?line <<"aaaxabaxbaax:bbax:b:a">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[trim]))),
+?line <<"aaaxabaxbaax:bbax:b:a:">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[{parts,
+ 2}]))),
+?line <<"aaaxabaxbaax:bbax:b:a:">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[]))),
+?line <<"bbaababbabaaaaa:bba:b:a">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[trim]))),
+?line <<"bbaababbabaaaaa:bba:b:a:">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[{parts,
+ 2}]))),
+?line <<"bbaababbabaaaaa:bba:b:a:">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","abc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","abc",[caseless]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XABCY","abc",[caseless,
+ trim]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XABCY","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XABCY","abc",[caseless]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("ABABC","abc",[caseless,
+ trim]))),
+?line <<"AB:">> = iolist_to_binary(join(re:split("ABABC","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AB:">> = iolist_to_binary(join(re:split("ABABC","abc",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[caseless]))),
+?line <<"aaxabxbaxbbx">> = iolist_to_binary(join(re:split("aaxabxbaxbbx","abc",[caseless,
+ trim]))),
+?line <<"aaxabxbaxbbx">> = iolist_to_binary(join(re:split("aaxabxbaxbbx","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"aaxabxbaxbbx">> = iolist_to_binary(join(re:split("aaxabxbaxbbx","abc",[caseless]))),
+?line <<"XBC">> = iolist_to_binary(join(re:split("XBC","abc",[caseless,
+ trim]))),
+?line <<"XBC">> = iolist_to_binary(join(re:split("XBC","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"XBC">> = iolist_to_binary(join(re:split("XBC","abc",[caseless]))),
+?line <<"AXC">> = iolist_to_binary(join(re:split("AXC","abc",[caseless,
+ trim]))),
+?line <<"AXC">> = iolist_to_binary(join(re:split("AXC","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AXC">> = iolist_to_binary(join(re:split("AXC","abc",[caseless]))),
+?line <<"ABX">> = iolist_to_binary(join(re:split("ABX","abc",[caseless,
+ trim]))),
+?line <<"ABX">> = iolist_to_binary(join(re:split("ABX","abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABX">> = iolist_to_binary(join(re:split("ABX","abc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","ab*c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab*c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab*c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","ab*bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab*bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab*bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBC","ab*bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBC","ab*bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBC","ab*bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab*?bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab*?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab*?bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{0,}?bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{0,}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{0,}?bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBC","ab+?bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBC","ab+?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBC","ab+?bc",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[caseless]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","ab+bc",[caseless,
+ trim]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","ab+bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","ab+bc",[caseless]))),
+?line <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab+bc",[caseless,
+ trim]))),
+?line <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab+bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab+bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab+bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab+bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab+bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,}?bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,}?bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,3}?bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,3}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,3}?bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{3,4}?bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{3,4}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{3,4}?bc",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}?bc",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}?bc",[caseless]))),
+?line <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab{4,5}?bc",[caseless,
+ trim]))),
+?line <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab{4,5}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab{4,5}?bc",[caseless]))),
+?line <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","ab{4,5}?bc",[caseless,
+ trim]))),
+?line <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","ab{4,5}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","ab{4,5}?bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABBC","ab??bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBC","ab??bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABBC","ab??bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","ab??bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab??bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab??bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?bc",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?bc",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","ab??c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab??c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab??c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","^abc$",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","^abc$",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","^abc$",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[caseless]))),
+?line <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","^abc$",[caseless,
+ trim]))),
+?line <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","^abc$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","^abc$",[caseless]))),
+?line <<"ABCC">> = iolist_to_binary(join(re:split("ABCC","^abc$",[caseless,
+ trim]))),
+?line <<"ABCC">> = iolist_to_binary(join(re:split("ABCC","^abc$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABCC">> = iolist_to_binary(join(re:split("ABCC","^abc$",[caseless]))),
+?line <<":C">> = iolist_to_binary(join(re:split("ABCC","^abc",[caseless,
+ trim]))),
+?line <<":C">> = iolist_to_binary(join(re:split("ABCC","^abc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":C">> = iolist_to_binary(join(re:split("ABCC","^abc",[caseless]))),
+?line <<"A">> = iolist_to_binary(join(re:split("AABC","abc$",[caseless,
+ trim]))),
+?line <<"A:">> = iolist_to_binary(join(re:split("AABC","abc$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"A:">> = iolist_to_binary(join(re:split("AABC","abc$",[caseless]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","^",[caseless,
+ trim]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","^",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","^",[caseless]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","$",[caseless,
+ trim]))),
+?line <<"ABC:">> = iolist_to_binary(join(re:split("ABC","$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABC:">> = iolist_to_binary(join(re:split("ABC","$",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABC","a.c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","a.c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABC","a.c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("AXC","a.c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("AXC","a.c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("AXC","a.c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("AXYZC","a.*?c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("AXYZC","a.*?c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("AXYZC","a.*?c",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.*c",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.*c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.*c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("AABC","a.*c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("AABC","a.*c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("AABC","a.*c",[caseless]))),
+?line <<"AXYZD">> = iolist_to_binary(join(re:split("AXYZD","a.*c",[caseless,
+ trim]))),
+?line <<"AXYZD">> = iolist_to_binary(join(re:split("AXYZD","a.*c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AXYZD">> = iolist_to_binary(join(re:split("AXYZD","a.*c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABD","a[bc]d",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABD","a[bc]d",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABD","a[bc]d",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ACE","a[b-d]e",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ACE","a[b-d]e",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ACE","a[b-d]e",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[b-d]e",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[b-d]e",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[b-d]e",[caseless]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","a[b-d]e",[caseless,
+ trim]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","a[b-d]e",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABC">> = iolist_to_binary(join(re:split("ABC","a[b-d]e",[caseless]))),
+?line <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[b-d]e",[caseless,
+ trim]))),
+?line <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[b-d]e",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[b-d]e",[caseless]))),
+?line <<"A">> = iolist_to_binary(join(re:split("AAC","a[b-d]",[caseless,
+ trim]))),
+?line <<"A:">> = iolist_to_binary(join(re:split("AAC","a[b-d]",[caseless,
+ {parts,
+ 2}]))),
+?line <<"A:">> = iolist_to_binary(join(re:split("AAC","a[b-d]",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("A-","a[-b]",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("A-","a[-b]",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("A-","a[-b]",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("A-","a[b-]",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("A-","a[b-]",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("A-","a[b-]",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("A]","a]",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("A]","a]",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("A]","a]",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("A]B","a[]]b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("A]B","a[]]b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("A]B","a[]]b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("AED","a[^bc]d",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("AED","a[^bc]d",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("AED","a[^bc]d",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ADC","a[^-b]c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ADC","a[^-b]c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ADC","a[^-b]c",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^-b]c",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^-b]c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^-b]c",[caseless]))),
+?line <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[^-b]c",[caseless,
+ trim]))),
+?line <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[^-b]c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[^-b]c",[caseless]))),
+?line <<"A-C">> = iolist_to_binary(join(re:split("A-C","a[^-b]c",[caseless,
+ trim]))),
+?line <<"A-C">> = iolist_to_binary(join(re:split("A-C","a[^-b]c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"A-C">> = iolist_to_binary(join(re:split("A-C","a[^-b]c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ADC","a[^]b]c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ADC","a[^]b]c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ADC","a[^]b]c",[caseless]))),
+?line <<":C">> = iolist_to_binary(join(re:split("ABC","ab|cd",[caseless,
+ trim]))),
+?line <<":C">> = iolist_to_binary(join(re:split("ABC","ab|cd",[caseless,
+ {parts,
+ 2}]))),
+?line <<":C">> = iolist_to_binary(join(re:split("ABC","ab|cd",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABCD","ab|cd",[caseless,
+ trim]))),
+?line <<":CD">> = iolist_to_binary(join(re:split("ABCD","ab|cd",[caseless,
+ {parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("ABCD","ab|cd",[caseless]))),
+?line <<"D">> = iolist_to_binary(join(re:split("DEF","()ef",[caseless,
+ trim]))),
+?line <<"D::">> = iolist_to_binary(join(re:split("DEF","()ef",[caseless,
+ {parts,
+ 2}]))),
+?line <<"D::">> = iolist_to_binary(join(re:split("DEF","()ef",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","$b",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","$b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","$b",[caseless]))),
+?line <<"A]C">> = iolist_to_binary(join(re:split("A]C","$b",[caseless,
+ trim]))),
+?line <<"A]C">> = iolist_to_binary(join(re:split("A]C","$b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"A]C">> = iolist_to_binary(join(re:split("A]C","$b",[caseless]))),
+?line <<"B">> = iolist_to_binary(join(re:split("B","$b",[caseless,
+ trim]))),
+?line <<"B">> = iolist_to_binary(join(re:split("B","$b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"B">> = iolist_to_binary(join(re:split("B","$b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("A(B","a\\(b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("A(B","a\\(b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("A(B","a\\(b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("AB","a\\(*b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("AB","a\\(*b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("AB","a\\(*b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("A((B","a\\(*b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("A((B","a\\(*b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("A((B","a\\(*b",[caseless]))),
+?line <<"A">> = iolist_to_binary(join(re:split("A","a\\\\b",[caseless,
+ notbol,
+ trim]))),
+?line <<"A">> = iolist_to_binary(join(re:split("A","a\\\\b",[caseless,
+ notbol,
+ {parts,
+ 2}]))),
+?line <<"A">> = iolist_to_binary(join(re:split("A","a\\\\b",[caseless,
+ notbol]))),
+?line <<":A:A:BC">> = iolist_to_binary(join(re:split("ABC","((a))",[caseless,
+ trim]))),
+?line <<":A:A:BC">> = iolist_to_binary(join(re:split("ABC","((a))",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:A:BC">> = iolist_to_binary(join(re:split("ABC","((a))",[caseless]))),
+?line <<":A:C">> = iolist_to_binary(join(re:split("ABC","(a)b(c)",[caseless,
+ trim]))),
+?line <<":A:C:">> = iolist_to_binary(join(re:split("ABC","(a)b(c)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:C:">> = iolist_to_binary(join(re:split("ABC","(a)b(c)",[caseless]))),
+?line <<"AABB">> = iolist_to_binary(join(re:split("AABBABC","a+b+c",[caseless,
+ trim]))),
+?line <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a+b+c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a+b+c",[caseless]))),
+?line <<"AABB">> = iolist_to_binary(join(re:split("AABBABC","a{1,}b{1,}c",[caseless,
+ trim]))),
+?line <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a{1,}b{1,}c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a{1,}b{1,}c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABCABC","a.+?c",[caseless,
+ trim]))),
+?line <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","a.+?c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.+?c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABCABC","a.*?c",[caseless,
+ trim]))),
+?line <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","a.*?c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.*?c",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABCABC","a.{0,5}?c",[caseless,
+ trim]))),
+?line <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","a.{0,5}?c",[caseless,
+ {parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.{0,5}?c",[caseless]))),
+?line <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b)*",[caseless,
+ trim]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)*",[caseless,
+ {parts,
+ 2}]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)*",[caseless]))),
+?line <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,}",[caseless,
+ trim]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,}",[caseless,
+ {parts,
+ 2}]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,}",[caseless]))),
+?line <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b)+",[caseless,
+ trim]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)+",[caseless,
+ {parts,
+ 2}]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)+",[caseless]))),
+?line <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b){1,}",[caseless,
+ trim]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){1,}",[caseless,
+ {parts,
+ 2}]))),
+?line <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){1,}",[caseless]))),
+?line <<":A::B">> = iolist_to_binary(join(re:split("AB","(a+|b)?",[caseless,
+ trim]))),
+?line <<":A:B">> = iolist_to_binary(join(re:split("AB","(a+|b)?",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b)?",[caseless]))),
+?line <<":A::B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}",[caseless,
+ trim]))),
+?line <<":A:B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}",[caseless]))),
+?line <<":A::B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}?",[caseless,
+ trim]))),
+?line <<":A:B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}?",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}?",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("CDE","[^ab]*",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("CDE","[^ab]*",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("CDE","[^ab]*",[caseless]))),
+?line <<":C">> = iolist_to_binary(join(re:split("ABBBCD","([abc])*d",[caseless,
+ trim]))),
+?line <<":C:">> = iolist_to_binary(join(re:split("ABBBCD","([abc])*d",[caseless,
+ {parts,
+ 2}]))),
+?line <<":C:">> = iolist_to_binary(join(re:split("ABBBCD","([abc])*d",[caseless]))),
+?line <<":A">> = iolist_to_binary(join(re:split("ABCD","([abc])*bcd",[caseless,
+ trim]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("ABCD","([abc])*bcd",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("ABCD","([abc])*bcd",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("E","a|b|c|d|e",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("E","a|b|c|d|e",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("E","a|b|c|d|e",[caseless]))),
+?line <<":E">> = iolist_to_binary(join(re:split("EF","(a|b|c|d|e)f",[caseless,
+ trim]))),
+?line <<":E:">> = iolist_to_binary(join(re:split("EF","(a|b|c|d|e)f",[caseless,
+ {parts,
+ 2}]))),
+?line <<":E:">> = iolist_to_binary(join(re:split("EF","(a|b|c|d|e)f",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABCDEFG","abcd*efg",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABCDEFG","abcd*efg",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABCDEFG","abcd*efg",[caseless]))),
+?line <<"X:Y:Z">> = iolist_to_binary(join(re:split("XABYABBBZ","ab*",[caseless,
+ trim]))),
+?line <<"X:YABBBZ">> = iolist_to_binary(join(re:split("XABYABBBZ","ab*",[caseless,
+ {parts,
+ 2}]))),
+?line <<"X:Y:Z">> = iolist_to_binary(join(re:split("XABYABBBZ","ab*",[caseless]))),
+?line <<"X:Y:Z">> = iolist_to_binary(join(re:split("XAYABBBZ","ab*",[caseless,
+ trim]))),
+?line <<"X:YABBBZ">> = iolist_to_binary(join(re:split("XAYABBBZ","ab*",[caseless,
+ {parts,
+ 2}]))),
+?line <<"X:Y:Z">> = iolist_to_binary(join(re:split("XAYABBBZ","ab*",[caseless]))),
+?line <<"AB:CD">> = iolist_to_binary(join(re:split("ABCDE","(ab|cd)e",[caseless,
+ trim]))),
+?line <<"AB:CD:">> = iolist_to_binary(join(re:split("ABCDE","(ab|cd)e",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AB:CD:">> = iolist_to_binary(join(re:split("ABCDE","(ab|cd)e",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("HIJ","[abhgefdc]ij",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("HIJ","[abhgefdc]ij",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("HIJ","[abhgefdc]ij",[caseless]))),
+?line <<"ABCDE">> = iolist_to_binary(join(re:split("ABCDE","^(ab|cd)e",[caseless,
+ trim]))),
+?line <<"ABCDE">> = iolist_to_binary(join(re:split("ABCDE","^(ab|cd)e",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABCDE">> = iolist_to_binary(join(re:split("ABCDE","^(ab|cd)e",[caseless]))),
+?line <<"ABCD">> = iolist_to_binary(join(re:split("ABCDEF","(abc|)ef",[caseless,
+ trim]))),
+?line <<"ABCD::">> = iolist_to_binary(join(re:split("ABCDEF","(abc|)ef",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ABCD::">> = iolist_to_binary(join(re:split("ABCDEF","(abc|)ef",[caseless]))),
+?line <<"A:B">> = iolist_to_binary(join(re:split("ABCD","(a|b)c*d",[caseless,
+ trim]))),
+?line <<"A:B:">> = iolist_to_binary(join(re:split("ABCD","(a|b)c*d",[caseless,
+ {parts,
+ 2}]))),
+?line <<"A:B:">> = iolist_to_binary(join(re:split("ABCD","(a|b)c*d",[caseless]))),
+?line <<":A">> = iolist_to_binary(join(re:split("ABC","(ab|ab*)bc",[caseless,
+ trim]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("ABC","(ab|ab*)bc",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("ABC","(ab|ab*)bc",[caseless]))),
+?line <<":BC">> = iolist_to_binary(join(re:split("ABC","a([bc]*)c*",[caseless,
+ trim]))),
+?line <<":BC:">> = iolist_to_binary(join(re:split("ABC","a([bc]*)c*",[caseless,
+ {parts,
+ 2}]))),
+?line <<":BC:">> = iolist_to_binary(join(re:split("ABC","a([bc]*)c*",[caseless]))),
+?line <<":BC:D">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c*d)",[caseless,
+ trim]))),
+?line <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c*d)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c*d)",[caseless]))),
+?line <<":BC:D">> = iolist_to_binary(join(re:split("ABCD","a([bc]+)(c*d)",[caseless,
+ trim]))),
+?line <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]+)(c*d)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]+)(c*d)",[caseless]))),
+?line <<":B:CD">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c+d)",[caseless,
+ trim]))),
+?line <<":B:CD:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c+d)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":B:CD:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c+d)",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ADCDCDE","a[bcd]*dcdcde",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ADCDCDE","a[bcd]*dcdcde",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ADCDCDE","a[bcd]*dcdcde",[caseless]))),
+?line <<":AB">> = iolist_to_binary(join(re:split("ABC","(ab|a)b*c",[caseless,
+ trim]))),
+?line <<":AB:">> = iolist_to_binary(join(re:split("ABC","(ab|a)b*c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":AB:">> = iolist_to_binary(join(re:split("ABC","(ab|a)b*c",[caseless]))),
+?line <<":ABC:A:B:D">> = iolist_to_binary(join(re:split("ABCD","((a)(b)c)(d)",[caseless,
+ trim]))),
+?line <<":ABC:A:B:D:">> = iolist_to_binary(join(re:split("ABCD","((a)(b)c)(d)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":ABC:A:B:D:">> = iolist_to_binary(join(re:split("ABCD","((a)(b)c)(d)",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*",[caseless]))),
+?line <<"A">> = iolist_to_binary(join(re:split("ABH","^a(bc+|b[eh])g|.h$",[caseless,
+ trim]))),
+?line <<"A::">> = iolist_to_binary(join(re:split("ABH","^a(bc+|b[eh])g|.h$",[caseless,
+ {parts,
+ 2}]))),
+?line <<"A::">> = iolist_to_binary(join(re:split("ABH","^a(bc+|b[eh])g|.h$",[caseless]))),
+?line <<":EFFGZ">> = iolist_to_binary(join(re:split("EFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ trim]))),
+?line <<":EFFGZ::">> = iolist_to_binary(join(re:split("EFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ {parts,
+ 2}]))),
+?line <<":EFFGZ::">> = iolist_to_binary(join(re:split("EFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))),
+?line <<":IJ:J">> = iolist_to_binary(join(re:split("IJ","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ trim]))),
+?line <<":IJ:J:">> = iolist_to_binary(join(re:split("IJ","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ {parts,
+ 2}]))),
+?line <<":IJ:J:">> = iolist_to_binary(join(re:split("IJ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))),
+?line <<"R:EFFGZ">> = iolist_to_binary(join(re:split("REFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ trim]))),
+?line <<"R:EFFGZ::">> = iolist_to_binary(join(re:split("REFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ {parts,
+ 2}]))),
+?line <<"R:EFFGZ::">> = iolist_to_binary(join(re:split("REFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[caseless]))),
+?line <<"ADCDCDE">> = iolist_to_binary(join(re:split("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ trim]))),
+?line <<"ADCDCDE">> = iolist_to_binary(join(re:split("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ {parts,
+ 2}]))),
+?line <<"ADCDCDE">> = iolist_to_binary(join(re:split("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))",[caseless]))),
+?line <<"EFFG">> = iolist_to_binary(join(re:split("EFFG","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ trim]))),
+?line <<"EFFG">> = iolist_to_binary(join(re:split("EFFG","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ {parts,
+ 2}]))),
+?line <<"EFFG">> = iolist_to_binary(join(re:split("EFFG","(bc+d$|ef*g.|h?i(j|k))",[caseless]))),
+?line <<"BCDD">> = iolist_to_binary(join(re:split("BCDD","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ trim]))),
+?line <<"BCDD">> = iolist_to_binary(join(re:split("BCDD","(bc+d$|ef*g.|h?i(j|k))",[caseless,
+ {parts,
+ 2}]))),
+?line <<"BCDD">> = iolist_to_binary(join(re:split("BCDD","(bc+d$|ef*g.|h?i(j|k))",[caseless]))),
+?line <<":A:A:A:A:A:A:A:A:A:A">> = iolist_to_binary(join(re:split("A","((((((((((a))))))))))",[caseless,
+ trim]))),
+?line <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","((((((((((a))))))))))",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","((((((((((a))))))))))",[caseless]))),
+?line <<":A:A:A:A:A:A:A:A:A:A">> = iolist_to_binary(join(re:split("AA","((((((((((a))))))))))\\10",[caseless,
+ trim]))),
+?line <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("AA","((((((((((a))))))))))\\10",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("AA","((((((((((a))))))))))\\10",[caseless]))),
+?line <<":A:A:A:A:A:A:A:A:A">> = iolist_to_binary(join(re:split("A","(((((((((a)))))))))",[caseless,
+ trim]))),
+?line <<":A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","(((((((((a)))))))))",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","(((((((((a)))))))))",[caseless]))),
+?line <<":A">> = iolist_to_binary(join(re:split("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))",[caseless,
+ trim]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))",[caseless]))),
+?line <<":C">> = iolist_to_binary(join(re:split("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))",[caseless,
+ trim]))),
+?line <<":C:">> = iolist_to_binary(join(re:split("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))",[caseless,
+ {parts,
+ 2}]))),
+?line <<":C:">> = iolist_to_binary(join(re:split("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[caseless]))),
+?line <<"AA">> = iolist_to_binary(join(re:split("AA","multiple words of text",[caseless,
+ trim]))),
+?line <<"AA">> = iolist_to_binary(join(re:split("AA","multiple words of text",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AA">> = iolist_to_binary(join(re:split("AA","multiple words of text",[caseless]))),
+?line <<"UH-UH">> = iolist_to_binary(join(re:split("UH-UH","multiple words of text",[caseless,
+ trim]))),
+?line <<"UH-UH">> = iolist_to_binary(join(re:split("UH-UH","multiple words of text",[caseless,
+ {parts,
+ 2}]))),
+?line <<"UH-UH">> = iolist_to_binary(join(re:split("UH-UH","multiple words of text",[caseless]))),
+?line <<":, YEAH">> = iolist_to_binary(join(re:split("MULTIPLE WORDS, YEAH","multiple words",[caseless,
+ trim]))),
+?line <<":, YEAH">> = iolist_to_binary(join(re:split("MULTIPLE WORDS, YEAH","multiple words",[caseless,
+ {parts,
+ 2}]))),
+?line <<":, YEAH">> = iolist_to_binary(join(re:split("MULTIPLE WORDS, YEAH","multiple words",[caseless]))),
+?line <<":AB:DE">> = iolist_to_binary(join(re:split("ABCDE","(.*)c(.*)",[caseless,
+ trim]))),
+?line <<":AB:DE:">> = iolist_to_binary(join(re:split("ABCDE","(.*)c(.*)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":AB:DE:">> = iolist_to_binary(join(re:split("ABCDE","(.*)c(.*)",[caseless]))),
+?line <<":A:B">> = iolist_to_binary(join(re:split("(A, B)","\\((.*), (.*)\\)",[caseless,
+ trim]))),
+?line <<":A:B:">> = iolist_to_binary(join(re:split("(A, B)","\\((.*), (.*)\\)",[caseless,
+ {parts,
+ 2}]))),
+?line <<":A:B:">> = iolist_to_binary(join(re:split("(A, B)","\\((.*), (.*)\\)",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ABCD","abcd",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABCD","abcd",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ABCD","abcd",[caseless]))),
+?line <<":BC">> = iolist_to_binary(join(re:split("ABCD","a(bc)d",[caseless,
+ trim]))),
+?line <<":BC:">> = iolist_to_binary(join(re:split("ABCD","a(bc)d",[caseless,
+ {parts,
+ 2}]))),
+?line <<":BC:">> = iolist_to_binary(join(re:split("ABCD","a(bc)d",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("AC","a[-]?c",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("AC","a[-]?c",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("AC","a[-]?c",[caseless]))),
+?line <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","(abc)\\1",[caseless,
+ trim]))),
+?line <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","(abc)\\1",[caseless,
+ {parts,
+ 2}]))),
+?line <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","(abc)\\1",[caseless]))),
+?line <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","([a-c]*)\\1",[caseless,
+ trim]))),
+?line <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","([a-c]*)\\1",[caseless,
+ {parts,
+ 2}]))),
+?line <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","([a-c]*)\\1",[caseless]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("abad","a(?!b).",[trim]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?!b).",[{parts,
+ 2}]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?!b).",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("abad","a(?=d).",[trim]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=d).",[{parts,
+ 2}]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=d).",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[trim]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[{parts,
+ 2}]))),
+?line <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[]))),
+?line <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[trim]))),
+?line <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[{parts,
+ 2}]))),
+?line <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[]))),
+?line <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[trim]))),
+?line <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[{parts,
+ 2}]))),
+?line <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[trim]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[{parts,
+ 2}]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[]))),
+?line <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[trim]))),
+?line <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[{parts,
+ 2}]))),
+?line <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[]))),
+?line <<":bar:foo:bar">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[trim]))),
+?line <<":bar:foo:bar:">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[{parts,
+ 2}]))),
+?line <<":bar:foo:bar:">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[trim]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[{parts,
+ 2}]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[]))),
+?line <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[trim]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[{parts,
+ 2}]))),
+?line <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[trim]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[{parts,
+ 2}]))),
+?line <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[]))),
+?line <<":c:e">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[trim]))),
+?line <<":c:e:">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[{parts,
+ 2}]))),
+?line <<":c:e:">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[]))),
+?line <<":A">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[trim]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[{parts,
+ 2}]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[]))),
+?line <<":.">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[trim]))),
+?line <<":.::">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[{parts,
+ 2}]))),
+?line <<":.::">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[]))),
+?line <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[trim]))),
+?line <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[{parts,
+ 2}]))),
+?line <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[]))),
+?line <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[trim]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[trim]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[]))),
+?line <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))),
+?line <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[{parts,
+ 2}]))),
+?line <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[]))),
+?line <<":f:o:o:b:a:r">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[trim]))),
+?line <<":f:o:o:b:a:r:">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[{parts,
+ 2}]))),
+?line <<":f:o:o:b:a:r:">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[trim]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[{parts,
+ 2}]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[]))),
+?line <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[trim]))),
+?line <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[{parts,
+ 2}]))),
+?line <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[trim]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[{parts,
+ 2}]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[trim]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[{parts,
+ 2}]))),
+?line <<"a:">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","(?<!c)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","(?<!c)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[]))),
+?line <<":b">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[trim]))),
+?line <<":ba">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[{parts,
+ 2}]))),
+?line <<":b:">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[]))),
+?line <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[trim]))),
+?line <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[{parts,
+ 2}]))),
+?line <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[]))),
+?line <<":a:a">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[trim]))),
+?line <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[{parts,
+ 2}]))),
+?line <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[]))),
+?line <<":a:a">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[trim]))),
+?line <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[{parts,
+ 2}]))),
+?line <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[]))),
+?line <<"c">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[trim]))),
+?line <<"c::">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[{parts,
+ 2}]))),
+?line <<"c::">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[]))),
+?line <<"c">> = iolist_to_binary(join(re:split("cab","(a)*ab",[trim]))),
+?line <<"c::">> = iolist_to_binary(join(re:split("cab","(a)*ab",[{parts,
+ 2}]))),
+?line <<"c::">> = iolist_to_binary(join(re:split("cab","(a)*ab",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[]))),
+?line <<":A">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[trim]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[{parts,
+ 2}]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[]))),
+?line <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[trim]))),
+?line <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[{parts,
+ 2}]))),
+?line <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[trim]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[{parts,
+ 2}]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[{parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[]))),
+?line <<":A">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[trim]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[{parts,
+ 2}]))),
+?line <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[trim]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[{parts,
+ 2}]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[trim]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[{parts,
+ 2}]))),
+?line <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?:(?-i)a)b",[caseless]))),
+?line <<":a">> = iolist_to_binary(join(re:split("ab","((?-i)a)b",[caseless,
+ trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i)a)b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless,
+ trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless,
+ trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?:(?-i)a)b",[caseless,
+ trim]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?:(?-i)a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?:(?-i)a)b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","(?-i:a)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","(?-i:a)b",[caseless]))),
+?line <<":a">> = iolist_to_binary(join(re:split("ab","((?-i:a))b",[caseless,
+ trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i:a))b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i:a))b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless,
+ trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless,
+ trim]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless,
+ trim]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless,
+ trim]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless,
+ {parts,
+ 2}]))),
+?line <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless,
+ trim]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless,
+ trim]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?-i:a.))b",[caseless,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?-i:a.))b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?-i:a.))b",[caseless]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","((?-i:a.))b",[caseless,
+ trim]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","((?-i:a.))b",[caseless,
+ {parts,
+ 2}]))),
+?line <<"AB">> = iolist_to_binary(join(re:split("AB","((?-i:a.))b",[caseless]))),
+?line <<"a
+B">> = iolist_to_binary(join(re:split("a
+B","((?-i:a.))b",[caseless,trim]))),
+?line <<"a
+B">> = iolist_to_binary(join(re:split("a
+B","((?-i:a.))b",[caseless,{parts,2}]))),
+?line <<"a
+B">> = iolist_to_binary(join(re:split("a
+B","((?-i:a.))b",[caseless]))),
+?line <<":a
+">> = iolist_to_binary(join(re:split("a
+B","((?s-i:a.))b",[caseless,trim]))),
+?line <<":a
+:">> = iolist_to_binary(join(re:split("a
+B","((?s-i:a.))b",[caseless,{parts,2}]))),
+?line <<":a
+:">> = iolist_to_binary(join(re:split("a
+B","((?s-i:a.))b",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[]))),
+?line <<":Ab">> = iolist_to_binary(join(re:split("Ab4ab","(ab)\\d\\1",[caseless,
+ trim]))),
+?line <<":Ab:">> = iolist_to_binary(join(re:split("Ab4ab","(ab)\\d\\1",[caseless,
+ {parts,
+ 2}]))),
+?line <<":Ab:">> = iolist_to_binary(join(re:split("Ab4ab","(ab)\\d\\1",[caseless]))),
+?line <<":ab">> = iolist_to_binary(join(re:split("ab4Ab","(ab)\\d\\1",[caseless,
+ trim]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("ab4Ab","(ab)\\d\\1",[caseless,
+ {parts,
+ 2}]))),
+?line <<":ab:">> = iolist_to_binary(join(re:split("ab4Ab","(ab)\\d\\1",[caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[]))),
+?line <<":~~">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[trim]))),
+?line <<":~~:">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[{parts,
+ 2}]))),
+?line <<":~~:">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaac","^a (?#xxx) (?#yyy) {3}c",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaac","^a (?#xxx) (?#yyy) {3}c",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaac","^a (?#xxx) (?#yyy) {3}c",[extended]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[]))),
+?line <<"B
+B">> = iolist_to_binary(join(re:split("B
+B","(?<![cd])b",[trim]))),
+?line <<"B
+B">> = iolist_to_binary(join(re:split("B
+B","(?<![cd])b",[{parts,2}]))),
+?line <<"B
+B">> = iolist_to_binary(join(re:split("B
+B","(?<![cd])b",[]))),
+?line <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[trim]))),
+?line <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[{parts,
+ 2}]))),
+?line <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[]))),
+?line <<"db::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[trim]))),
+?line <<"db:acb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[{parts,
+ 2}]))),
+?line <<"db::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[]))),
+?line <<"db::::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[trim]))),
+?line <<"db::acb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[{parts,
+ 2}]))),
+?line <<"db::::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[]))),
+?line <<"cdacc">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[trim]))),
+?line <<"cdacc:">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[{parts,
+ 2}]))),
+?line <<"cdacc:">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[trim]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[]))),
+?line <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[trim]))),
+?line <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[]))),
+?line <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[trim]))),
+?line <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[]))),
+?line <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[trim]))),
+?line <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[{parts,
+ 2}]))),
+?line <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[]))),
+?line <<":a
+:
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)^a(.))((?m)^b$)",[trim]))),
+?line <<":a
+:
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)^a(.))((?m)^b$)",[{parts,2}]))),
+?line <<":a
+:
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)^a(.))((?m)^b$)",[]))),
+?line <<"a
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?m)^b$)",[trim]))),
+?line <<"a
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?m)^b$)",[{parts,2}]))),
+?line <<"a
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?m)^b$)",[]))),
+?line <<"a
+">> = iolist_to_binary(join(re:split("a
+b","(?m)^b",[trim]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","(?m)^b",[{parts,2}]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","(?m)^b",[]))),
+?line <<"a
+:b">> = iolist_to_binary(join(re:split("a
+b","(?m)^(b)",[trim]))),
+?line <<"a
+:b:">> = iolist_to_binary(join(re:split("a
+b","(?m)^(b)",[{parts,2}]))),
+?line <<"a
+:b:">> = iolist_to_binary(join(re:split("a
+b","(?m)^(b)",[]))),
+?line <<"a
+:b">> = iolist_to_binary(join(re:split("a
+b","((?m)^b)",[trim]))),
+?line <<"a
+:b:">> = iolist_to_binary(join(re:split("a
+b","((?m)^b)",[{parts,2}]))),
+?line <<"a
+:b:">> = iolist_to_binary(join(re:split("a
+b","((?m)^b)",[]))),
+?line <<"a:b">> = iolist_to_binary(join(re:split("a
+b","\\n((?m)^b)",[trim]))),
+?line <<"a:b:">> = iolist_to_binary(join(re:split("a
+b","\\n((?m)^b)",[{parts,2}]))),
+?line <<"a:b:">> = iolist_to_binary(join(re:split("a
+b","\\n((?m)^b)",[]))),
+?line <<"a
+b:
+">> = iolist_to_binary(join(re:split("a
+b
+c","((?s).)c(?!.)",[trim]))),
+?line <<"a
+b:
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s).)c(?!.)",[{parts,2}]))),
+?line <<"a
+b:
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s).)c(?!.)",[]))),
+?line <<"a
+b:
+">> = iolist_to_binary(join(re:split("a
+b
+c","((?s).)c(?!.)",[trim]))),
+?line <<"a
+b:
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s).)c(?!.)",[{parts,2}]))),
+?line <<"a
+b:
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s).)c(?!.)",[]))),
+?line <<"a
+:b
+">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)b.)c(?!.)",[trim]))),
+?line <<"a
+:b
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)b.)c(?!.)",[{parts,2}]))),
+?line <<"a
+:b
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)b.)c(?!.)",[]))),
+?line <<"a
+:b
+">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)b.)c(?!.)",[trim]))),
+?line <<"a
+:b
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)b.)c(?!.)",[{parts,2}]))),
+?line <<"a
+:b
+:">> = iolist_to_binary(join(re:split("a
+b
+c","((?s)b.)c(?!.)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[]))),
+?line <<"a
+b
+c">> = iolist_to_binary(join(re:split("a
+b
+c","()^b",[trim]))),
+?line <<"a
+b
+c">> = iolist_to_binary(join(re:split("a
+b
+c","()^b",[{parts,2}]))),
+?line <<"a
+b
+c">> = iolist_to_binary(join(re:split("a
+b
+c","()^b",[]))),
+?line <<"a
+b
+c">> = iolist_to_binary(join(re:split("a
+b
+c","()^b",[trim]))),
+?line <<"a
+b
+c">> = iolist_to_binary(join(re:split("a
+b
+c","()^b",[{parts,2}]))),
+?line <<"a
+b
+c">> = iolist_to_binary(join(re:split("a
+b
+c","()^b",[]))),
+?line <<"a
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?m)^b)",[trim]))),
+?line <<"a
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?m)^b)",[{parts,2}]))),
+?line <<"a
+:b:
+c">> = iolist_to_binary(join(re:split("a
+b
+c","((?m)^b)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[]))),
+?line <<":(:)">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[trim]))),
+?line <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[trim]))),
+?line <<":::">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<":::">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[]))),
+?line <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[trim]))),
+?line <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[]))),
+?line <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[trim]))),
+?line <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[]))),
+?line <<":(:)">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[trim]))),
+?line <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[trim]))),
+?line <<":::">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<":::">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[]))),
+?line <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[trim]))),
+?line <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[]))),
+?line <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[trim]))),
+?line <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[{parts,
+ 2}]))),
+?line <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[trim]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[{parts,
+ 2}]))),
+?line <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[]))),
+?line <<"a:a:aab">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[trim]))),
+?line <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[{parts,
+ 2}]))),
+?line <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[]))),
+?line <<":one:">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[trim]))),
+?line <<":one::">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[{parts,
+ 2}]))),
+?line <<":one::">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[]))),
+?line <<"a:a">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[trim]))),
+?line <<"a:a:">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[{parts,
+ 2}]))),
+?line <<"a:a:">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[]))),
+?line <<"a:a:aab">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[trim]))),
+?line <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[{parts,
+ 2}]))),
+?line <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[trim]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[{parts,
+ 2}]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[trim]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[{parts,
+ 2}]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[]))),
+?line <<"::abcd">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[trim]))),
+?line <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[{parts,
+ 2}]))),
+?line <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[]))),
+?line <<":xy:z::::abcd">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[trim]))),
+?line <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[{parts,
+ 2}]))),
+?line <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[]))),
+?line <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[trim]))),
+?line <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[{parts,
+ 2}]))),
+?line <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[]))),
+?line <<"c:aa">> = iolist_to_binary(join(re:split("caab","(a*)b+",[trim]))),
+?line <<"c:aa:">> = iolist_to_binary(join(re:split("caab","(a*)b+",[{parts,
+ 2}]))),
+?line <<"c:aa:">> = iolist_to_binary(join(re:split("caab","(a*)b+",[]))),
+?line <<"::abcd">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[trim]))),
+?line <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[{parts,
+ 2}]))),
+?line <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[]))),
+?line <<":xy:z::::abcd">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[trim]))),
+?line <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[{parts,
+ 2}]))),
+?line <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[]))),
+?line <<"*** ::Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[trim]))),
+?line <<"*** ::Failers:">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[{parts,
+ 2}]))),
+?line <<"*** ::Failers:">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[]))),
+?line <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[trim]))),
+?line <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[{parts,
+ 2}]))),
+?line <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[]))),
+?line <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[trim]))),
+?line <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[{parts,
+ 2}]))),
+?line <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[]))),
+?line <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[trim]))),
+?line <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[{parts,
+ 2}]))),
+?line <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[]))),
+?line <<"a::[:b]::">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[trim]))),
+?line <<"a::[:b]:">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[{parts,
+ 2}]))),
+?line <<"a::[:b]:::">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[]))),
+?line <<"a:=[:b]:=">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[trim]))),
+?line <<"a:=[:b]=">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[{parts,
+ 2}]))),
+?line <<"a:=[:b]:=:">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[]))),
+?line <<"a:.[:b]:.">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[trim]))),
+?line <<"a:.[:b].">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[{parts,
+ 2}]))),
+?line <<"a:.[:b]:.:">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[]))),
+?line <<":aaab">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[trim]))),
+?line <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[{parts,
+ 2}]))),
+?line <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[]))),
+?line <<":aaa">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[trim]))),
+?line <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[{parts,
+ 2}]))),
+?line <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[]))),
+?line <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[trim]))),
+?line <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[{parts,
+ 2}]))),
+?line <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[trim]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[{parts,
+ 2}]))),
+?line <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[]))),
+?line <<"a
+b">> = iolist_to_binary(join(re:split("a
+b","a\\Z",[trim]))),
+?line <<"a
+b">> = iolist_to_binary(join(re:split("a
+b","a\\Z",[{parts,2}]))),
+?line <<"a
+b">> = iolist_to_binary(join(re:split("a
+b","a\\Z",[]))),
+?line <<"a
+">> = iolist_to_binary(join(re:split("a
+b","b\\Z",[trim]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","b\\Z",[{parts,2}]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","b\\Z",[]))),
+?line <<"a
+">> = iolist_to_binary(join(re:split("a
+b","b\\Z",[trim]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","b\\Z",[{parts,2}]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","b\\Z",[]))),
+?line <<"a
+">> = iolist_to_binary(join(re:split("a
+b","b\\z",[trim]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","b\\z",[{parts,2}]))),
+?line <<"a
+:">> = iolist_to_binary(join(re:split("a
+b","b\\z",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))),
+?line <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts,
+ 2}]))),
+?line <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))),
+?line <<":abcd">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[trim]))),
+?line <<":abcd:">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[{parts,
+ 2}]))),
+?line <<":abcd:">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[]))),
+?line <<":wxyz">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[trim]))),
+?line <<":wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[{parts,
+ 2}]))),
+?line <<":wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[]))),
+?line <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[trim]))),
+?line <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[{parts,
+ 2}]))),
+?line <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[trim]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[{parts,
+ 2}]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[trim]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[{parts,
+ 2}]))),
+?line <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[]))),
+?line <<"999">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[trim]))),
+?line <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[{parts,
+ 2}]))),
+?line <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[]))),
+?line <<"123999">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[trim]))),
+?line <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[{parts,
+ 2}]))),
+?line <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[]))),
+?line <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[trim]))),
+?line <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[{parts,
+ 2}]))),
+?line <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[]))),
+?line <<"999">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[trim]))),
+?line <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[{parts,
+ 2}]))),
+?line <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[]))),
+?line <<"123999">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[trim]))),
+?line <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[{parts,
+ 2}]))),
+?line <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[]))),
+?line <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[trim]))),
+?line <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[{parts,
+ 2}]))),
+?line <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[]))),
+?line <<"123abc">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[trim]))),
+?line <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[{parts,
+ 2}]))),
+?line <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[]))),
+?line <<"123456">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[trim]))),
+?line <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[{parts,
+ 2}]))),
+?line <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[]))),
+?line <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[trim]))),
+?line <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[{parts,
+ 2}]))),
+?line <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[]))),
+?line <<"123abc">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[trim]))),
+?line <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[{parts,
+ 2}]))),
+?line <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[]))),
+?line <<"123456">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[trim]))),
+?line <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[{parts,
+ 2}]))),
+?line <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[]))),
+?line <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[trim]))),
+?line <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[{parts,
+ 2}]))),
+?line <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href=
+ ([\\\"\\'])? # find single or double quote
+ (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ trim]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended,
+ {parts,
+ 2}]))),
+?line <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href=
+ ([\"'])? # find single or double quote
+ (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching
+ # quote, otherwise match up to next space",[caseless,
+ dotall,
+ extended]))),
+?line <<":A:Z:B:::C:::D:::E:::F:::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[trim]))),
+?line <<":A:Z:BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[{parts,
+ 2}]))),
+?line <<":A:Z:B:::C:::D:::E:::F:::G:::">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[]))),
+?line <<":A::B:::C:::D:::E:::F:::G">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[trim]))),
+?line <<":A::BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[{parts,
+ 2}]))),
+?line <<":A::B:::C:::D:::E:::F:::G:::">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[]))),
+?line <<":A:::B::::C::::D::::E::::F::::G">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[trim]))),
+?line <<":A:::BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[{parts,
+ 2}]))),
+?line <<":A:::B::::C::::D::::E::::F::::G::::">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[]))),
+?line <<":A:B::C::D::E::F::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[trim]))),
+?line <<":A:BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[{parts,
+ 2}]))),
+?line <<":A:B::C::D::E::F::G::">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[]))),
+?line <<"Z::::B::C::D::E::F::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[trim]))),
+?line <<"Z::ABCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[{parts,
+ 2}]))),
+?line <<"Z::::B::C::D::E::F::G::">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[]))),
+?line <<":b:b:b">> = iolist_to_binary(join(re:split("abbab","a*",[trim]))),
+?line <<":bbab">> = iolist_to_binary(join(re:split("abbab","a*",[{parts,
+ 2}]))),
+?line <<":b:b:b:">> = iolist_to_binary(join(re:split("abbab","a*",[]))),
+?line <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[a-\\d]",[trim]))),
+?line <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[a-\\d]",[{parts,
+ 2}]))),
+?line <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[a-\\d]",[]))),
+?line <<":things">> = iolist_to_binary(join(re:split("-things","^[a-\\d]",[trim]))),
+?line <<":things">> = iolist_to_binary(join(re:split("-things","^[a-\\d]",[{parts,
+ 2}]))),
+?line <<":things">> = iolist_to_binary(join(re:split("-things","^[a-\\d]",[]))),
+?line <<":digit">> = iolist_to_binary(join(re:split("0digit","^[a-\\d]",[trim]))),
+?line <<":digit">> = iolist_to_binary(join(re:split("0digit","^[a-\\d]",[{parts,
+ 2}]))),
+?line <<":digit">> = iolist_to_binary(join(re:split("0digit","^[a-\\d]",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-\\d]",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-\\d]",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-\\d]",[]))),
+?line <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[a-\\d]",[trim]))),
+?line <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[a-\\d]",[{parts,
+ 2}]))),
+?line <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[a-\\d]",[]))),
+?line <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[trim]))),
+?line <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[{parts,
+ 2}]))),
+?line <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[]))),
+?line <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[trim]))),
+?line <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[{parts,
+ 2}]))),
+?line <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[]))),
+?line <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[trim]))),
+?line <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[{parts,
+ 2}]))),
+?line <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[]))),
+?line <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[trim]))),
+?line <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[{parts,
+ 2}]))),
+?line <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[]))),
+?line <<">:<">> = iolist_to_binary(join(re:split(">
+ <","[[:space:]]+",[trim]))),
+?line <<">:<">> = iolist_to_binary(join(re:split(">
+ <","[[:space:]]+",[{parts,2}]))),
+?line <<">:<">> = iolist_to_binary(join(re:split(">
+ <","[[:space:]]+",[]))),
+?line <<">:
+ <">> = iolist_to_binary(join(re:split(">
+ <","[[:blank:]]+",[trim]))),
+?line <<">:
+ <">> = iolist_to_binary(join(re:split(">
+ <","[[:blank:]]+",[{parts,2}]))),
+?line <<">:
+ <">> = iolist_to_binary(join(re:split(">
+ <","[[:blank:]]+",[]))),
+?line <<">: <">> = iolist_to_binary(join(re:split(">
+ <","[\\s]+",[trim]))),
+?line <<">: <">> = iolist_to_binary(join(re:split(">
+ <","[\\s]+",[{parts,2}]))),
+?line <<">: <">> = iolist_to_binary(join(re:split(">
+ <","[\\s]+",[]))),
+?line <<">: <">> = iolist_to_binary(join(re:split(">
+ <","\\s+",[trim]))),
+?line <<">: <">> = iolist_to_binary(join(re:split(">
+ <","\\s+",[{parts,2}]))),
+?line <<">: <">> = iolist_to_binary(join(re:split(">
+ <","\\s+",[]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","a b",[extended,
+ trim]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","a b",[extended,
+ {parts,
+ 2}]))),
+?line <<"ab">> = iolist_to_binary(join(re:split("ab","a b",[extended]))),
+?line <<"a
+:b">> = iolist_to_binary(join(re:split("a
+xb","(?!\\A)x",[multiline,trim]))),
+?line <<"a
+:b">> = iolist_to_binary(join(re:split("a
+xb","(?!\\A)x",[multiline,{parts,2}]))),
+?line <<"a
+:b">> = iolist_to_binary(join(re:split("a
+xb","(?!\\A)x",[multiline]))),
+?line <<"a
+xb">> = iolist_to_binary(join(re:split("a
+xb","(?!^)x",[multiline,trim]))),
+?line <<"a
+xb">> = iolist_to_binary(join(re:split("a
+xb","(?!^)x",[multiline,{parts,2}]))),
+?line <<"a
+xb">> = iolist_to_binary(join(re:split("a
+xb","(?!^)x",[multiline]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc abcabc"," abc\\Q abc\\Eabc",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc abcabc"," abc\\Q abc\\Eabc",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc abcabc"," abc\\Q abc\\Eabc",[extended]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," abc\\Q abc\\Eabc",[extended,
+ trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," abc\\Q abc\\Eabc",[extended,
+ {parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," abc\\Q abc\\Eabc",[extended]))),
+?line <<"abcabcabc">> = iolist_to_binary(join(re:split("abcabcabc"," abc\\Q abc\\Eabc",[extended,
+ trim]))),
+?line <<"abcabcabc">> = iolist_to_binary(join(re:split("abcabcabc"," abc\\Q abc\\Eabc",[extended,
+ {parts,
+ 2}]))),
+?line <<"abcabcabc">> = iolist_to_binary(join(re:split("abcabcabc"," abc\\Q abc\\Eabc",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment
+ ",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment
+ ",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment
+ ",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc#not comment
+ literal","abc#comment
+ \\Q#not comment
+ literal\\E #more comment",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[]))),
+?line <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[trim]))),
+?line <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[{parts,
+ 2}]))),
+?line <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[]))),
+?line <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[trim]))),
+?line <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[{parts,
+ 2}]))),
+?line <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[]))),
+?line <<"::xyz">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[trim]))),
+?line <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[{parts,
+ 2}]))),
+?line <<"::xyz:">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[trim]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[{parts,
+ 2}]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[]))),
+?line <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[trim]))),
+?line <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[{parts,
+ 2}]))),
+?line <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[]))),
+?line <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[trim]))),
+?line <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[{parts,
+ 2}]))),
+?line <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[]))),
+?line <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[trim]))),
+?line <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[{parts,
+ 2}]))),
+?line <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[trim]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[{parts,
+ 2}]))),
+?line <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[]))),
+?line <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[trim]))),
+?line <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[{parts,
+ 2}]))),
+?line <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[]))),
+?line <<":abC">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[trim]))),
+?line <<":abC:">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[{parts,
+ 2}]))),
+?line <<":abC:">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[]))),
+?line <<":D">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[trim]))),
+?line <<":D:">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[{parts,
+ 2}]))),
+?line <<":D:">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[{parts,
+ 2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[]))),
+?line <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[trim]))),
+?line <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[{parts,
+ 2}]))),
+?line <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[]))),
+?line <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[trim]))),
+?line <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[{parts,
+ 2}]))),
+?line <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[]))),
+?line <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[trim]))),
+?line <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[{parts,
+ 2}]))),
+?line <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[]))),
+?line <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[trim]))),
+?line <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[{parts,
+ 2}]))),
+?line <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[{parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[]))),
+?line <<"a:bc">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[trim]))),
+?line <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[{parts,
+ 2}]))),
+?line <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[]))),
+?line <<":abc">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[dotall,
+ trim]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[dotall,
+ {parts,
+ 2}]))),
+?line <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[dotall]))),
+?line <<"a:bc">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[dotall,
+ trim]))),
+?line <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[dotall,
+ {parts,
+ 2}]))),
+?line <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[dotall]))),
+?line <<":abc:abc">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[trim]))),
+?line <<":abc:abc:">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[{parts,
+ 2}]))),
+?line <<":abc:abc:">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[]))),
+?line <<"a:bc:bc">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[trim]))),
+?line <<"a:bc:bc:">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[{parts,
+ 2}]))),
+?line <<"a:bc:bc:">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a123::a123","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123::a123","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123::a123","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("a123:b342::abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:b342::abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:b342::abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("a123:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"::1">> = iolist_to_binary(join(re:split("::1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"::1">> = iolist_to_binary(join(re:split("::1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"::1">> = iolist_to_binary(join(re:split("::1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"abcd:fee0:123::">> = iolist_to_binary(join(re:split("abcd:fee0:123::","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"abcd:fee0:123::">> = iolist_to_binary(join(re:split("abcd:fee0:123::","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"abcd:fee0:123::">> = iolist_to_binary(join(re:split("abcd:fee0:123::","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<":1">> = iolist_to_binary(join(re:split(":1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<":1">> = iolist_to_binary(join(re:split(":1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<":1">> = iolist_to_binary(join(re:split(":1","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"1:">> = iolist_to_binary(join(re:split("1:","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,trim]))),
+?line <<"1:">> = iolist_to_binary(join(re:split("1:","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless,{parts,2}]))),
+?line <<"1:">> = iolist_to_binary(join(re:split("1:","^(?!:) # colon disallowed at start
+ (?: # start of item
+ (?: [0-9a-f]{1,4} | # 1-4 hex digits or
+ (?(1)0 | () ) ) # if null previously matched, fail; else null
+ : # followed by colon
+ ){1,7} # end item; 1-7 of them required
+ [0-9a-f]{1,4} $ # final hex number at end of string
+ (?(1)|.) # check that there was an empty component
+ ",[extended,caseless]))),
+?line <<"">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[trim]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[{parts,
+ 2}]))),
+?line <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[trim]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[{parts,
+ 2}]))),
+?line <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("M","\\M",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("M","\\M",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("M","\\M",[]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[trim]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("REGular","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("REGular","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("REGular","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("regulaer","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("regulaer","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("regulaer","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("Regex","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("Regex","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("Regex","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("regul�r","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("regul�r","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("regul�r","(?i)reg(?:ul(?:[a�]|ae)r|ex)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("�����","����[�-��-�]+",[]))),
+?line <<"�XAZ">> = iolist_to_binary(join(re:split("�XAZXB","(?<=Z)X.",[trim]))),
+?line <<"�XAZ:">> = iolist_to_binary(join(re:split("�XAZXB","(?<=Z)X.",[{parts,
+ 2}]))),
+?line <<"�XAZ:">> = iolist_to_binary(join(re:split("�XAZXB","(?<=Z)X.",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[]))),
+?line <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[trim]))),
+?line <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[{parts,
+ 2}]))),
+?line <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[]))),
+?line <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[trim]))),
+?line <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[{parts,
+ 2}]))),
+?line <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[]))),
+?line <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[trim]))),
+?line <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[{parts,
+ 2}]))),
+?line <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[]))),
+?line <<"off">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[trim]))),
+?line <<"off:">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[{parts,
+ 2}]))),
+?line <<"off:">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[]))),
+?line <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[trim]))),
+?line <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[{parts,
+ 2}]))),
+?line <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[]))),
+?line <<"ony">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[trim]))),
+?line <<"ony:">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[{parts,
+ 2}]))),
+?line <<"ony:">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[]))),
+?line <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[trim]))),
+?line <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[{parts,
+ 2}]))),
+?line <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[]))),
+?line <<"a
+:b
+:c">> = iolist_to_binary(join(re:split("a
+b
+c","^",[multiline,trim]))),
+?line <<"a
+:b
+c">> = iolist_to_binary(join(re:split("a
+b
+c","^",[multiline,{parts,2}]))),
+?line <<"a
+:b
+:c">> = iolist_to_binary(join(re:split("a
+b
+c","^",[multiline]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^",[multiline,
+ trim]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^",[multiline,
+ {parts,
+ 2}]))),
+?line <<"">> = iolist_to_binary(join(re:split("","^",[multiline]))),
+?line <<"A
+C
+:C">> = iolist_to_binary(join(re:split("A
+C
+C","(?<=C\\n)^",[multiline,trim]))),
+?line <<"A
+C
+:C">> = iolist_to_binary(join(re:split("A
+C
+C","(?<=C\\n)^",[multiline,{parts,2}]))),
+?line <<"A
+C
+:C">> = iolist_to_binary(join(re:split("A
+C
+C","(?<=C\\n)^",[multiline]))),
+?line <<":X">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[trim]))),
+?line <<":X:">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[{parts,
+ 2}]))),
+?line <<":X:">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[]))),
+?line <<":Y">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[trim]))),
+?line <<":Y:">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[{parts,
+ 2}]))),
+?line <<":Y:">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[]))),
+?line <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[trim]))),
+?line <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[{parts,
+ 2}]))),
+?line <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[]))),
+?line <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[trim]))),
+?line <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[{parts,
+ 2}]))),
+?line <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[]))),
+?line <<"A:B">> = iolist_to_binary(join(re:split("A B","(?-x: )",[extended,
+ trim]))),
+?line <<"A:B">> = iolist_to_binary(join(re:split("A B","(?-x: )",[extended,
+ {parts,
+ 2}]))),
+?line <<"A:B">> = iolist_to_binary(join(re:split("A B","(?-x: )",[extended]))),
+?line <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[trim]))),
+?line <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[{parts,
+ 2}]))),
+?line <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[]))),
+?line <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[trim]))),
+?line <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[{parts,
+ 2}]))),
+?line <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[]))),
+?line <<"A">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))),
+?line <<"A:">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts,
+ 2}]))),
+?line <<"A:">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))),
+?line <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))),
+?line <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts,
+ 2}]))),
+?line <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))),
+?line <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))),
+?line <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts,
+ 2}]))),
+?line <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a*b*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[trim]))),
+?line <<":bb">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a*b?\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b?\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b?\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a*b *\\w",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b *\\w",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b *\\w",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a*b#comment
+ *\\w",[extended,trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b#comment
+ *\\w",[extended,{parts,2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a*b#comment
+ *\\w",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","a* b *\\w",[extended,
+ trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a* b *\\w",[extended,
+ {parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","a* b *\\w",[extended]))),
+?line <<"::
+pqr">> = iolist_to_binary(join(re:split("abc=xyz\\
+pqr","^\\w+=.*(\\\\\\n.*)*",[trim]))),
+?line <<"::
+pqr">> = iolist_to_binary(join(re:split("abc=xyz\\
+pqr","^\\w+=.*(\\\\\\n.*)*",[{parts,2}]))),
+?line <<"::
+pqr">> = iolist_to_binary(join(re:split("abc=xyz\\
+pqr","^\\w+=.*(\\\\\\n.*)*",[]))),
+?line <<":abcd">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[trim]))),
+?line <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[{parts,
+ 2}]))),
+?line <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[]))),
+?line <<":abcd">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[trim]))),
+?line <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[{parts,
+ 2}]))),
+?line <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[trim]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[{parts,
+ 2}]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[trim]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[{parts,
+ 2}]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[trim]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[{parts,
+ 2}]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[trim]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[{parts,
+ 2}]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[trim]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[{parts,
+ 2}]))),
+?line <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[{parts,
+ 2}]))),
+?line <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[{parts,
+ 2}]))),
+?line <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[trim]))),
+?line <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[{parts,
+ 2}]))),
+?line <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[trim]))),
+?line <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[{parts,
+ 2}]))),
+?line <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[]))),
+?line <<":a">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[trim]))),
+?line <<":a::">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[{parts,
+ 2}]))),
+?line <<":a::">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[trim]))),
+?line <<":::">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[{parts,
+ 2}]))),
+?line <<":::">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[trim]))),
+?line <<":::">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[{parts,
+ 2}]))),
+?line <<":::">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[trim]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[trim]))),
+?line <<"::">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[{parts,
+ 2}]))),
+?line <<"::">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[trim]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[trim]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[{parts,
+ 2}]))),
+?line <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","\\Z",[trim]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","\\Z",[{parts,
+ 2}]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","\\Z",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[trim]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[{parts,
+ 2}]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[trim]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[{parts,
+ 2}]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[]))),
+?line <<"abc">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[trim]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[{parts,
+ 2}]))),
+?line <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[trim]))),
+?line <<":::">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[{parts,
+ 2}]))),
+?line <<":::">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[]))),
+?line <<"a:::b:::c:::d">> = iolist_to_binary(join(re:split("abcd","( (A | (?(1)0|) )* )",[extended,
+ trim]))),
+?line <<"a:::bcd">> = iolist_to_binary(join(re:split("abcd","( (A | (?(1)0|) )* )",[extended,
+ {parts,
+ 2}]))),
+?line <<"a:::b:::c:::d:::">> = iolist_to_binary(join(re:split("abcd","( (A | (?(1)0|) )* )",[extended]))),
+?line <<"a:::b:::c:::d">> = iolist_to_binary(join(re:split("abcd","( ( (?(1)0|) )* )",[extended,
+ trim]))),
+?line <<"a:::bcd">> = iolist_to_binary(join(re:split("abcd","( ( (?(1)0|) )* )",[extended,
+ {parts,
+ 2}]))),
+?line <<"a:::b:::c:::d:::">> = iolist_to_binary(join(re:split("abcd","( ( (?(1)0|) )* )",[extended]))),
+?line <<"a::b::c::d">> = iolist_to_binary(join(re:split("abcd","( (?(1)0|)* )",[extended,
+ trim]))),
+?line <<"a::bcd">> = iolist_to_binary(join(re:split("abcd","( (?(1)0|)* )",[extended,
+ {parts,
+ 2}]))),
+?line <<"a::b::c::d::">> = iolist_to_binary(join(re:split("abcd","( (?(1)0|)* )",[extended]))),
+?line <<"">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[]))),
+?line <<"">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[trim]))),
+?line <<":">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[{parts,
+ 2}]))),
+?line <<":">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[]))),
+ok.
diff --git a/lib/stdlib/test/run_pcre_tests.erl b/lib/stdlib/test/run_pcre_tests.erl
new file mode 100644
index 0000000000..0ef3986918
--- /dev/null
+++ b/lib/stdlib/test/run_pcre_tests.erl
@@ -0,0 +1,1201 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(run_pcre_tests).
+
+-compile(export_all).
+
+test(RootDir) ->
+ put(verbose,false),
+ erts_debug:set_internal_state(available_internal_state,true),
+ io:format("oldlimit: ~p~n",[ erts_debug:set_internal_state(re_loop_limit,10)]),
+ Testfiles0 = ["testoutput1", "testoutput2", "testoutput3", "testoutput4",
+ "testoutput5", "testoutput6", "testoutput10"],
+ Testfiles = [ filename:join([RootDir,FN]) || FN <- Testfiles0 ],
+ Res = [ begin io:format("~s~n",[X]), t(X) end || X <- Testfiles ],
+ io:format("limit was: ~p~n",[ erts_debug:set_internal_state(re_loop_limit,default)]),
+ Res2 = Res ++ [ begin io:format("~s~n",[X]), t(X) end || X <- Testfiles ],
+ erts_debug:set_internal_state(available_internal_state,false),
+ put(verbose,true),
+ Res2.
+t(OneFile) ->
+ t(OneFile,infinite).
+t(OneFile,Num) ->
+ {ok,Bin} = file:read_file(OneFile),
+ Lines = splitfile(0,Bin,1),
+ Structured = stru(Lines),
+ put(error_limit,Num),
+ put(skipped,0),
+ Res =
+ [test(Structured,true,index),
+ test(Structured,false,index),
+ test(Structured,true,binary),
+ test(Structured,false,binary),
+ test(Structured,true,list),
+ test(Structured,false,list)],
+ {lists:sum(Res),length(Structured)*6,get(skipped)}.
+
+
+pick_exec_options([{exec_option,Opt}|T]) ->
+ {O,E} = pick_exec_options(T),
+ {O,[Opt|E]};
+pick_exec_options([unicode|T]) ->
+ {O,E} = pick_exec_options(T),
+ {[unicode|O],[unicode|E]};
+pick_exec_options([Opt|T]) ->
+ {O,E} = pick_exec_options(T),
+ {[Opt|O],E};
+pick_exec_options([]) ->
+ {[],[]}.
+
+test([],_,_) ->
+ 0;
+test([{RE,Line,Options0,Tests}|T],PreCompile,XMode) ->
+ %io:format("."),
+ %case RE of <<>> -> io:format("Empty re:~w~n",[Line]); _ -> ok end,
+ {Options,ExecOptions} = pick_exec_options(Options0),
+ {Cres, Xopt} = case PreCompile of
+ true ->
+ {re:compile(RE,Options),[]};
+ _ ->
+ {{ok,RE},Options}
+ end,
+ case Cres of
+ {ok,P} ->
+ %erlang:display({testrun,RE,P,Tests,ExecOptions,Xopt,XMode}),
+ case (catch testrun(RE,P,Tests,ExecOptions,Xopt,XMode)) of
+ N when is_integer(N) ->
+ N + test(T,PreCompile,XMode);
+ limit ->
+ io:format("Error limit reached.~n"),
+ 1;
+ skip ->
+ case get(skipped) of
+ N when is_integer(N) ->
+ put(skipped,N+1);
+ _ ->
+ put(skipped,1)
+ end,
+ test(T,PreCompile,XMode)
+ end;
+ {error,Err} ->
+ io:format("Compile error(~w): ~w~n",[Line,Err]),
+ case get(error_limit) of
+ infinite -> 1 + test(T,PreCompile,XMode);
+ X ->
+ case X-1 of
+ Y when Y =< 0 ->
+ io:format("Error limit reached.~n"),
+ 1;
+ Y ->
+ put(error_limit,Y),
+ 1 + test(T,PreCompile,XMode)
+ end
+ end
+ end.
+
+loopexec(_,_,X,Y,_,_) when X > Y ->
+ {match,[]};
+loopexec(P,Chal,X,Y,Unicode,Xopt) ->
+ %io:format("~p~n",[X]),
+ case re:run(Chal,P,[{offset,X}]++Xopt) of
+ nomatch ->
+ %io:format(" re:exec(~p,~p,[{offset,~p}]) -> ~p~n",
+ % [P,Chal,X,no]),
+ {match,[]};
+ %loopexec(P,Chal,X+1,Y);
+ {match,[{A,B}|More]} ->
+ %io:format(" re:exec(~p,~p,[{offset,~p}]) -> ~p~n",
+ % [P,Chal,X,{match,[{A,B}|More]}]),
+ {match,Rest} =
+ case B>0 of
+ true ->
+ loopexec(P,Chal,A+B,Y,Unicode,Xopt);
+ false ->
+ {match,M} = case re:run(Chal,P,[{offset,X},notempty,anchored]++Xopt) of
+ nomatch ->
+ {match,[]};
+ {match,Other} ->
+ {match,fixup(Chal,Other,0)}
+ end,
+ NewA = forward(Chal,A,1,Unicode),
+ {match,MM} = loopexec(P,Chal,NewA,Y,Unicode,Xopt),
+ {match,M ++ MM}
+ end,
+ {match,fixup(Chal,[{A,B}|More],0)++Rest}
+ end.
+
+forward(_Chal,A,0,_) ->
+ A;
+forward(_Chal,A,N,false) ->
+ A+N;
+forward(Chal,A,N,true) ->
+ <<_:A/binary,Tl/binary>> = Chal,
+ Forw = case Tl of
+ <<1:1,1:1,0:1,_:5,_/binary>> ->
+ 2;
+ <<1:1,1:1,1:1,0:1,_:4,_/binary>> ->
+ 3;
+ <<1:1,1:1,1:1,1:1,0:1,_:3,_/binary>> ->
+ 4;
+ _ ->
+ 1
+ end,
+ %io:format("Forward ~p~n",[Forw]),
+ forward(Chal,A+Forw,N-1,true).
+
+contains_eightbit(<<>>) ->
+ false;
+contains_eightbit(<<X:8,_/binary>>) when X >= 128 ->
+ true;
+contains_eightbit(<<_,R/binary>>) ->
+ contains_eightbit(R).
+
+clean_duplicates([],_) ->
+ [];
+clean_duplicates([{X,Y}|T],L) ->
+ case lists:keymember(X,1,L) of
+ true ->
+ clean_duplicates(T,L);
+ false ->
+ [{X,Y}|clean_duplicates(T,L)]
+ end;
+clean_duplicates([bsr_anycrlf|T],L) ->
+ case (lists:member(bsr_anycrlf,L) orelse lists:member(bsr_unicode,L)) of
+ true ->
+ clean_duplicates(T,L);
+ false ->
+ [bsr_anycrlf|clean_duplicates(T,L)]
+ end;
+clean_duplicates([bsr_unicode|T],L) ->
+ case (lists:member(bsr_anycrlf,L) orelse lists:member(bsr_unicode,L)) of
+ true ->
+ clean_duplicates(T,L);
+ false ->
+ [bsr_unicode|clean_duplicates(T,L)]
+ end;
+clean_duplicates([X|T],L) ->
+ case lists:member(X,L) of
+ true ->
+ clean_duplicates(T,L);
+ false ->
+ [X|clean_duplicates(T,L)]
+ end.
+
+
+global_fixup(_,nomatch) ->
+ nomatch;
+global_fixup(P,{match,M}) ->
+ {match,lists:flatten(global_fixup2(P,M))}.
+
+global_fixup2(_,[]) ->
+ [];
+global_fixup2(P,[H|T]) ->
+ [gfixup_one(P,0,H)|global_fixup2(P,T)].
+
+gfixup_one(_,_,[]) ->
+ [];
+gfixup_one(P,I,[{Start,Len}|T]) ->
+ <<_:Start/binary,R:Len/binary,_/binary>> = P,
+ [{I,R}|gfixup_one(P,I+1,T)].
+
+
+press([]) ->
+ [];
+press([H|T]) ->
+ H++press(T).
+
+testrun(_,_,[],_,_,_) ->
+ 0;
+testrun(RE,P,[{Chal,Line,ExecOpt,Responses}|T],EO,Xopt0,XMode) ->
+ Xopt = clean_duplicates(Xopt0,ExecOpt),
+
+ case lists:keymember(newline,1,Xopt) of
+ true ->
+ info("skipping inconsistent newlines "
+ "when compiling and running in one go (~p)~n",
+ [Line]),
+ throw(skip);
+ false ->
+ ok
+ end,
+
+ Res =
+ case lists:member(g,EO) of
+ true ->
+ case XMode of
+ binary ->
+ case re:run(Chal,P,ExecOpt++Xopt++
+ [global,{capture,all,binary}]) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ {match,press([bfix(R)|| R <- Reslist])}
+ end;
+ list ->
+ case re:run(Chal,P,ExecOpt++Xopt++
+ [global,{capture,all,list}]) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ UFix = lists:member(unicode,EO),
+ {match,press([bfix([if UFix =:= true -> list_to_utf8(L); true -> list_to_binary(L) end || L <- R]) || R <- Reslist])}
+ end;
+ index ->
+ case re:run(Chal,P,ExecOpt++Xopt++[global]) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ {match,press([fixup(Chal,R,0) || R <- Reslist])}
+ end
+ end;
+ false ->
+ case EO -- [accept_nonascii] of
+ EO ->
+ case contains_eightbit(Chal) of
+ true ->
+ info("skipping 8bit without LANG (~p)~n",
+ [Line]),
+ throw(skip);
+ false ->
+ ok
+ end,
+
+ case XMode of
+ binary ->
+ case re:run(Chal,P,ExecOpt++Xopt++
+ [{capture,all,binary}]) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ {match,bfix(Reslist)}
+ end;
+ list ->
+ case re:run(Chal,P,ExecOpt++Xopt++
+ [{capture,all,list}]) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ UFix = lists:member(unicode,EO),
+ {match,bfix([if
+ UFix =:= true -> list_to_utf8(L);
+ true -> list_to_binary(L)
+ end || L <- Reslist])}
+ end;
+ index ->
+ case re:run(Chal,P,ExecOpt++Xopt) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ {match,fixup(Chal,Reslist,0)}
+ end
+ end;
+ _LesserOpt ->
+ case XMode of
+ binary ->
+ case re:run(Chal,P,ExecOpt++Xopt++
+ [{capture,all,binary}]) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ {match,bfix(Reslist)}
+ end;
+ list ->
+ case re:run(Chal,P,ExecOpt++Xopt++
+ [{capture,all,list}]) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ %io:format("re:run(~w,~w,~w) -> ~w~n",[Chal,P,ExecOpt++Xopt++
+ % [{capture,all,list}],Reslist]),
+ UFix = lists:member(unicode,EO),
+ {match,bfix([if
+ UFix =:= true -> list_to_utf8(L);
+ true -> list_to_binary(L)
+ end || L <- Reslist])}
+ end;
+ index ->
+ case re:run(Chal,P,ExecOpt++Xopt) of
+ nomatch ->
+ nomatch;
+ {match, Reslist} ->
+ {match,fixup(Chal,Reslist,0)}
+ end
+ end
+ end
+ end,
+ case compare_sloppy(Res,Responses) of
+ true ->
+ testrun(RE,P,T,EO,Xopt0,XMode);
+ false ->
+ io:format("FAIL(~w): re = ~p, ~nmatched against = ~p(~w), ~nwith options = ~p. ~nexpected = ~p, ~ngot = ~p~n",
+ [Line,RE,Chal,binary_to_list(Chal),{ExecOpt,EO,Xopt},Responses,Res]),
+ case get(error_limit) of
+ infinite -> ok;
+ X ->
+ case X-1 of
+ Y when Y =< 0 ->
+ throw(limit);
+ Y ->
+ put(error_limit,Y)
+ end
+ end,
+ 1
+ end.
+compare_sloppy({A,L1},{A,L2}) ->
+ compare_sloppy(L1,L2);
+compare_sloppy(A,A) ->
+ true;
+compare_sloppy([{X,Y}|T1],[{X,Y}|T2]) ->
+ compare_sloppy(T1,T2);
+compare_sloppy([{X,[Y,_]}|T1],[{X,Y}|T2]) ->
+ compare_sloppy(T1,T2);
+compare_sloppy([{X,[_,Y]}|T1],[{X,Y}|T2]) ->
+ compare_sloppy(T1,T2);
+compare_sloppy(_,_) ->
+ false.
+
+bfix(RL) ->
+ bfix(RL,0).
+bfix([],_) ->
+ [];
+bfix([<<>>|T],N) ->
+ [{N,[<<>>,<<"<unset>">>]}|bfix(T,N+1)]; % indeterminable
+bfix([H|T],N) ->
+ [{N,H}|bfix(T,N+1)].
+
+fixup(List,Any,Any2) when is_list(List)->
+ fixup(unicode:characters_to_binary(List,unicode,unicode),Any,Any2);
+fixup(_,[],_) ->
+ [];
+fixup(Bin,[{-1,0}|T],N) ->
+ [{N,<<"<unset>">>}|fixup(Bin,T,N+1)];
+fixup(Bin,[{X,Y}|T],N) ->
+ <<_:X/binary,Res:Y/binary,_/binary>> = Bin,
+ [{N,Res}|fixup(Bin,T,N+1)].
+
+splitfile(N,Bin,_Line) when N >= size(Bin) ->
+ [];
+splitfile(N,Bin,Line) ->
+ {Res,NewN} = pickline(N,N,Bin),
+ case emptyline(Res) of
+ true ->
+ [{Line,<<>>}|splitfile(NewN,Bin,Line+1)];
+ false ->
+ [{Line,Res}|splitfile(NewN,Bin,Line+1)]
+ end.
+
+emptyline(<<>>) ->
+ true;
+emptyline(<<$ ,R/binary>>) ->
+ emptyline(R);
+emptyline(_) ->
+ false.
+pickline(Start,Stop,Bin) when Stop >= size(Bin) ->
+ Len = Stop - Start - 1,
+ <<_:Start/binary,Res:Len/binary,_/binary>> = Bin,
+ {Res,Stop};
+
+pickline(Start,Stop,Bin) ->
+ %erlang:display({Start,Stop,size(Bin)}),
+ <<_:Stop/binary,Ch,_/binary>> = Bin,
+ case Ch of
+ $\n ->
+ Len = Stop - Start,
+ <<_:Start/binary,Res:Len/binary,_/binary>> = Bin,
+ {Res,Stop+1};
+ _ ->
+ pickline(Start,Stop+1,Bin)
+ end.
+
+skip_until_empty([]) ->
+ [];
+skip_until_empty([{_,<<>>}|T]) ->
+ T;
+skip_until_empty([{_,_}|T]) ->
+ skip_until_empty(T).
+
+skip_debug([{_,<<$-,_/binary>>}|Con]) ->
+ Con;
+skip_debug([_|T]) ->
+ skip_debug(T);
+skip_debug([]) ->
+ [].
+
+skip_extra_info([{_,<<$ ,$ ,$ ,_/binary>>}=H|Con]) ->
+ [H|Con];
+skip_extra_info([{_,<<>>}|Con]) ->
+ Con;
+skip_extra_info([_|T]) ->
+ skip_extra_info(T);
+skip_extra_info([]) ->
+ [].
+
+stru([]) ->
+ [];
+stru([{_,<<>>}|T]) ->
+ stru(T);
+stru([{Line,<<Ch,Re0/binary>>}|T0]) ->
+ {T,Re} = find_rest_re(Ch,[{Line,Re0}|T0]),
+ %io:format("DBG: ~p~n",[Re]),
+ {NewRe,<< Ch, Options/binary >>} = end_of_re(Ch,Re),
+ case interpret_options_x(backstrip(frontstrip(Options)),NewRe) of
+ {Olist,<<>>} ->
+ case T of
+ [{_,<<$-,_/binary>>}|Con] ->
+ %Debug output, we skip those
+ %io:format("Skipping debug (~w)~n",[Line]),
+ TmpT = skip_debug(Con),
+ {NewT,Matches} = stru2(TmpT),
+ [{NewRe,Line,Olist,Matches}|stru(NewT)];
+ [{_,<<$C,$a,$p,$t,$u,$r,$i,$n,$g,_/binary>>}|_] ->
+ NewT0 = skip_extra_info(T),
+ {NewT,Matches} = stru2(NewT0),
+ [{NewRe,Line,Olist,Matches}|stru(NewT)];
+ [{_,<<Bla,_/binary>>}|_] when Bla =/= $ ->
+ %io:format("Skipping blabla (~w)~n",[Line]),
+ NewT = skip_until_empty(T),
+ stru(NewT);
+ _ ->
+ {NewT,Matches} = stru2(T),
+ %erlang:display({NewRe,Line,Olist,Matches}),
+ Matches1 = case lists:member(unicode,Olist) of
+ true ->
+ Matches ++
+ [ {unicode:characters_to_list(E1,unicode),E2,E3,E4} ||
+ {E1,E2,E3,E4} <- Matches];
+ false ->
+ Matches
+ end,
+ %erlang:display({NewRe,Line,Olist,Matches1}),
+ [{NewRe,Line,Olist,Matches1}|stru(NewT)]
+ end;
+ {_,Rest} ->
+%% case T of
+%% [{_,<<Bla,_/binary>>}|_] when Bla =/= $ ->
+%% io:format("Skipping blabla (~w)~n",[Line]);
+%% _ ->
+%% ok
+%% end,
+ NewT = skip_until_empty(T),
+ %{NewT,_Matches} = stru2(T),
+ info("Skipping options ~s for now (~w)~n",[binary_to_list(Rest),Line]),
+ case NewT of
+ [{Li,_}|_] ->
+ info("Skip to line ~p~n",[Li]);
+ _ ->
+ ok
+ end,
+ stru(NewT)
+ end.
+
+contains_lang_sens(<<>>) ->
+ false;
+contains_lang_sens(<<$\\,$W,_/binary>>) ->
+ true;
+contains_lang_sens(<<$\\,$w,_/binary>>) ->
+ true;
+contains_lang_sens(<<$\\,$b,_/binary>>) ->
+ true;
+contains_lang_sens(<<_,R/binary>>) ->
+ contains_lang_sens(R).
+
+
+interpret_options_x(Options,RE) ->
+ {O,R} = interpret_options(Options),
+ case (contains_lang_sens(RE) or lists:member(caseless,O)) of
+ false ->
+ {[{exec_option,accept_nonascii}|O],R};
+ true ->
+ case lists:member(unicode,O) of
+ true ->
+ {[{exec_option,accept_nonascii}|O],R};
+ false ->
+ {O,R}
+ end
+ end.
+tr_option($i) ->
+ [caseless];
+tr_option($I) ->
+ [];
+tr_option($B) ->
+ [];
+tr_option($Z) ->
+ [];
+tr_option($x) ->
+ [extended];
+tr_option($s) ->
+ [dotall];
+tr_option($m) ->
+ [multiline];
+tr_option($J) ->
+ [dupnames];
+tr_option($N) ->
+ [no_auto_capture];
+tr_option($8) ->
+ [unicode];
+tr_option($g) ->
+ [{exec_option,g}];
+tr_option(_) ->
+ false.
+
+interpret_options(<<$<,Rest0/binary>>) ->
+ {Option,Rest} = pinch_cr(Rest0),
+ {Olist,NRest} = interpret_options(Rest),
+ {[Option | Olist], NRest};
+interpret_options(<<$L,$f,$r,$_,$F,$R,Rest/binary>>) ->
+ info("Accepting (and ignoring) french locale~n",[]),
+ {Olist,NRest} = interpret_options(Rest),
+ {[{exec_option, accept_nonascii}|Olist],NRest};
+interpret_options(<<Ch,Rest/binary>>) ->
+ {Olist,NRest} = interpret_options(Rest),
+ case tr_option(Ch) of
+ false ->
+ {Olist,<<Ch,NRest/binary>>};
+ Option ->
+ {Option ++ Olist, NRest}
+ end;
+interpret_options(<<>>) ->
+ {[],<<>>}.
+
+find_unsupported([{not_supported,X}|T]) ->
+ [X | find_unsupported(T)];
+find_unsupported([_|T]) ->
+ find_unsupported(T);
+find_unsupported([]) ->
+ [].
+
+backslash_end(<<>>) ->
+ false;
+backslash_end(<<$\\>>) ->
+ true;
+backslash_end(<<_>>) ->
+ false;
+backslash_end(<<_,R/binary>>) ->
+ backslash_end(R).
+
+%stru2([<<$ ,$ ,$ ,$ , $*,$*,$*,$ ,_/binary>> | T]) ->
+% stru2(T);
+stru2([{Line,<<$ ,Rest/binary>>} | T]) ->
+ % A challenge
+ case (catch responses(T)) of
+ {NewT,Rlist} ->
+ {NewNewT,StrList} = stru2(NewT),
+ %% Hack...
+ FS = case backstrip(frontstrip(Rest)) of
+ <<"\\">> ->
+ %% Single backslash is to be considered
+ %% an empty line in challenge
+ <<>>;
+ OFS ->
+ case backslash_end(OFS) of
+ true ->
+ <<OFS/binary,$ >>;
+ _ ->
+ OFS
+ end
+ end,
+ {ExecOpts,NFS} = escape(FS),
+ case find_unsupported(ExecOpts) of
+ [] ->
+ {NewNewT,[{NFS,Line,ExecOpts,
+ case
+ Rlist of nomatch -> nomatch;
+ RR -> {match,RR}
+ end} | StrList]};
+ UList ->
+ info("WARNING(~w): the exec-option(s) ~p are unsupported, skipping challenge.~n",[Line,UList]),
+ {NewNewT,StrList}
+ end;
+ fail ->
+ NewT = skip_until_empty(T),
+ {NewT,[]}
+ end;
+
+stru2(X) ->
+ {X,[]}.
+%responses([<< $ ,$ ,$ ,$ ,$*,$*,$*,$ ,_/binary>>|T]) ->
+% responses(T);
+responses([{_Line,<< X:2/binary,$:,$ ,Resp/binary>>}|T]) ->
+ {NT,R2} = responses(T),
+ NX=list_to_integer(binary_to_list(frontstrip(X))),
+ {NT,[{NX,escape2(Resp)} | R2]};
+responses([{_Line,<< X:3/binary,$:,$ ,Resp/binary>>}|T]) ->
+ {NT,R2} = responses(T),
+ NX=list_to_integer(binary_to_list(frontstrip(X))),
+ {NT,[{NX,escape2(Resp)} | R2]};
+responses([{_Line,<<$N,$o,$ ,$m,$a,$t,$c,$h,_/binary>>}|T]) ->
+ {T,nomatch};
+responses([{Line,<<$ ,No,Ch,_/binary>>}|T]) when No >= $0, No =< $9, Ch >= $A, Ch =< $Z ->
+ info("Skipping strange debug response at line ~p~n",[Line]),
+ responses(T);
+responses([{Line,<<$ ,$ ,Ch,_/binary>>}|T]) when Ch =:= $G; Ch =:= $C ->
+ info("Skipping stranger debug response at line ~p~n",[Line]),
+ responses(T);
+responses([{Line,<<C,_/binary>>=X}|_]) when C =/= $ ->
+ info("Offending response line(~w)! ~p~n",[Line,X]),
+ throw(fail);
+responses(X) ->
+ {X,[]}.
+
+
+end_of_re(_,<<>>) ->
+ {<<>>,<<>>};
+end_of_re(C,<<C,_Rest/binary>> = R) ->
+ {<<>>,R};
+end_of_re(C,<<$\\,C,Rest/binary>>) ->
+ {Sub,Rest2} = end_of_re(C,Rest),
+ {<<C,Sub/binary>>,Rest2};
+end_of_re(C,<<Ch,Rest/binary>>) ->
+ {Sub,Rest2} = end_of_re(C,Rest),
+ {<<Ch,Sub/binary>>,Rest2}.
+
+frontstrip(<<>>) ->
+ <<>>;
+frontstrip(<< $ ,Rest/binary>>) ->
+ frontstrip(Rest);
+frontstrip(Bin) ->
+ Bin.
+
+backstrip(<<>>) ->
+ <<>>;
+backstrip(<<$ >>) ->
+ <<>>;
+backstrip(<<X,Rest/binary>>) ->
+ case backstrip(Rest) of
+ Rest ->
+ <<X,Rest/binary>>;
+ Other ->
+ NRest = backstrip(Other),
+ <<X,NRest/binary>>
+ end.
+
+find_rest_re(_,[]) ->
+ {<<>>,<<>>};
+find_rest_re(Ch,[{_,H}|T]) ->
+ case end_of_re(Ch,H) of
+ {_,<<>>} ->
+ {NewT,Rest} = find_rest_re(Ch,T),
+ {NewT,<<H/binary,$\n,Rest/binary>>};
+ {_,_} ->
+ {T,H}
+ end.
+
+eopt($A) ->
+ [anchored];
+eopt($B) ->
+ [notbol];
+eopt(X) ->
+ [{not_supported,X}].
+
+pinch_cr(<<$c,$r,$>,Rest/binary>>) ->
+ {{newline,cr},Rest};
+pinch_cr(<<$l,$f,$>,Rest/binary>>) ->
+ {{newline,lf},Rest};
+pinch_cr(<<$c,$r,$l,$f,$>,Rest/binary>>) ->
+ {{newline,crlf},Rest};
+pinch_cr(<<$C,$R,$>,Rest/binary>>) ->
+ {{newline,cr},Rest};
+pinch_cr(<<$L,$F,$>,Rest/binary>>) ->
+ {{newline,lf},Rest};
+pinch_cr(<<$C,$R,$L,$F,$>,Rest/binary>>) ->
+ {{newline,crlf},Rest};
+pinch_cr(<<$a,$n,$y,$c,$r,$l,$f,$>,Rest/binary>>) ->
+ {{newline,anycrlf},Rest};
+pinch_cr(<<$b,$s,$r,$_,$a,$n,$y,$c,$r,$l,$f,$>,Rest/binary>>) ->
+ {bsr_anycrlf,Rest};
+pinch_cr(<<$b,$s,$r,$_,$u,$n,$i,$c,$o,$d,$e,$>,Rest/binary>>) ->
+ {bsr_unicode,Rest};
+pinch_cr(<<$a,$n,$y,$>,Rest/binary>>) ->
+ {{newline,any},Rest};
+pinch_cr(<<$A,$N,$Y,$>,Rest/binary>>) ->
+ {{newline,any},Rest};
+pinch_cr(Other) ->
+ case splitby($>,Other,<<>>) of
+ {Unk,Rest} ->
+ {{not_supported,{newline,Unk}},Rest};
+ no ->
+ {{not_supported,$<},Other}
+ end.
+
+splitby(_,<<>>,_) ->
+ no;
+splitby(Ch,<<Ch,Rest/binary>>,Acc) ->
+ {Acc,Rest};
+splitby(Ch,<<OCh,Rest/binary>>,Acc) ->
+ splitby(Ch,Rest,<<Acc/binary,OCh>>).
+
+
+escape(<<>>) ->
+ {[],<<>>};
+escape(<<$\\, Ch, Rest/binary>>) when Ch >= $A, Ch =< $Z; Ch =:= $? ->
+ %Options in the string...
+ NewOpts = eopt(Ch),
+ {MoreOpts,Tail} = escape(Rest),
+ {NewOpts ++ MoreOpts,Tail};
+escape(<<$\\, $<, Rest/binary>>) ->
+ %CR Options in the string...
+ {NewOpt,NewRest} = pinch_cr(Rest),
+ {MoreOpts,Tail} = escape(NewRest),
+ {[NewOpt|MoreOpts],Tail};
+escape(<<$\\, Ch, Rest/binary>>) ->
+ {C,NR} = case single_esc(Ch) of
+ no ->
+ case multi_esc(<<Ch,Rest/binary>>) of
+ {CharBin,NewRest} ->
+ {CharBin,NewRest};
+ no ->
+ {<<$\\>>,<<Ch,Rest/binary>>}
+ end;
+ CCC ->
+ %erlang:display({escape,CCC}),
+ {<<CCC>>,Rest}
+ end,
+ {MoreOpts,Tail} = escape(NR),
+ {MoreOpts,<<C/binary,Tail/binary>>};
+%escape(<<$\\,Rest/binary>>) ->
+% escape(<<Rest/binary>>);
+escape(<<Ch,Rest/binary>>) ->
+ {X,RR} = escape(<<Rest/binary>>),
+ {X,<<Ch,RR/binary>>};
+escape(Any) ->
+ {[],Any}.
+escape2(<<>>) ->
+ <<>>;
+escape2(<<$\\, Ch, Rest/binary>>) ->
+ {C,NR} = case multi_esc(<<Ch,Rest/binary>>) of
+ {CharBin,NewRest} ->
+ {CharBin,NewRest};
+ no ->
+ {<<$\\>>,<<Ch,Rest/binary>>}
+ end,
+ Tail = escape2(NR),
+ <<C/binary,Tail/binary>>;
+escape2(<<Ch,Rest/binary>>) ->
+ RR = escape2(<<Rest/binary>>),
+ <<Ch,RR/binary>>;
+escape2(Any) ->
+ Any.
+
+
+trx(N) when ((N >= $0) and (N =< $9)) ->
+ N - $0;
+trx($A) -> 10;
+trx($B) -> 11;
+trx($C) -> 12;
+trx($D) -> 13;
+trx($E) -> 14;
+trx($F) -> 15;
+trx($a) -> 10;
+trx($b) -> 11;
+trx($c) -> 12;
+trx($d) -> 13;
+trx($e) -> 14;
+trx($f) -> 15.
+
+
+int_to_utf8(I) when I =< 16#7F ->
+ <<I>>;
+int_to_utf8(I) when I =< 16#7FF ->
+ B2 = I band 16#3f,
+ B1 = (I bsr 6) band 16#1f,
+ <<1:1,1:1,0:1,B1:5,1:1,0:1,B2:6>>;
+int_to_utf8(I) when I =< 16#FFFF ->
+ B3 = I band 16#3f,
+ B2 = (I bsr 6) band 16#3f,
+ B1 = (I bsr 12) band 16#f,
+ <<1:1,1:1,1:1,0:1,B1:4,1:1,0:1,B2:6,1:1,0:1,B3:6>>;
+int_to_utf8(I) when I =< 16#10FFFF ->
+ B4 = I band 16#3f,
+ B3 = (I bsr 6) band 16#3f,
+ B2 = (I bsr 12) band 16#3f,
+ B1 = (I bsr 18) band 16#7,
+ <<1:1,1:1,1:1,1:1,0:1,B1:3,1:1,0:1,B2:6,1:1,0:1,B3:6,1:1,0:1,B4:6>>;
+int_to_utf8(_) ->
+ exit(unsupported_utf8).
+
+list_to_utf8(L) when is_list(L); is_binary(L) ->
+ iolist_to_binary([int_to_utf8(I) || I <- L]);
+list_to_utf8({Tag,_,_}) when Tag =:= incomplete ; Tag =:= error ->
+ throw(skip).
+
+multi_esc(<<M,N,O,Rest/binary>>)
+ when M >= $0, M =< $7, N >= $0, N =< $7, O >= $0, O =< $7 ->
+ Cha = ((M - $0) bsl 6) bor ((N - $0) bsl 3) bor (O - $0),
+ {<<Cha>>,Rest};
+multi_esc(<<N,O,Rest/binary>>)
+ when N >= $0, N =< $7, O >= $0, O =< $7 ->
+ Cha = ((N - $0) bsl 3) bor (O - $0),
+ {<<Cha>>,Rest};
+multi_esc(<<O,Rest/binary>>)
+ when O >= $0, O =< $7 ->
+ Cha = (O - $0),
+ {<<Cha>>,Rest};
+
+
+multi_esc(<<$x,${,N,O,$},Rest/binary>>)
+ when ((((N >= $0) and (N =< $9)) or ((N >= $A) and (N =< $F)) or
+ ((N >= $a) and (N =< $f))) and
+ (((O >= $0) and (O =< $9)) or ((O >= $A) and (O =< $F)) or
+ ((O >= $a) and (O =< $f)))) ->
+ Cha = (trx(N) bsl 4) bor trx(O),
+ {int_to_utf8(Cha),Rest};
+multi_esc(<<$x,${,N,O,P,$},Rest/binary>>)
+ when ((((N >= $0) and (N =< $9)) or ((N >= $A) and (N =< $F)) or
+ ((N >= $a) and (N =< $f))) and
+ (((O >= $0) and (O =< $9)) or ((O >= $A) and (O =< $F)) or
+ ((O >= $a) and (O =< $f)))and
+ (((P >= $0) and (P =< $9)) or ((P >= $A) and (P =< $F)) or
+ ((P >= $a) and (P =< $f)))) ->
+ Cha = (trx(N) bsl 8) bor (trx(O) bsl 4) bor trx(P),
+ {int_to_utf8(Cha),Rest};
+multi_esc(<<$x,${,N,O,P,Q,$},Rest/binary>>)
+ when ((((N >= $0) and (N =< $9)) or ((N >= $A) and (N =< $F)) or
+ ((N >= $a) and (N =< $f))) and
+ (((O >= $0) and (O =< $9)) or ((O >= $A) and (O =< $F)) or
+ ((O >= $a) and (O =< $f))) and
+ (((P >= $0) and (P =< $9)) or ((P >= $A) and (P =< $F)) or
+ ((P >= $a) and (P =< $f))) and
+ (((Q >= $0) and (Q =< $9)) or ((Q >= $A) and (Q =< $F)) or
+ ((Q >= $a) and (Q =< $f)))) ->
+ Cha = (trx(N) bsl 12) bor (trx(O) bsl 8) bor (trx(P) bsl 4) bor trx(Q),
+ {int_to_utf8(Cha),Rest};
+multi_esc(<<$x,${,N,O,P,Q,R,$},Rest/binary>>)
+ when ((((N >= $0) and (N =< $9)) or ((N >= $A) and (N =< $F)) or
+ ((N >= $a) and (N =< $f))) and
+ (((O >= $0) and (O =< $9)) or ((O >= $A) and (O =< $F)) or
+ ((O >= $a) and (O =< $f))) and
+ (((P >= $0) and (P =< $9)) or ((P >= $A) and (P =< $F)) or
+ ((P >= $a) and (P =< $f))) and
+ (((Q >= $0) and (Q =< $9)) or ((Q >= $A) and (Q =< $F)) or
+ ((Q >= $a) and (Q =< $f))) and
+ (((R >= $0) and (R =< $9)) or ((R >= $A) and (R =< $F)) or
+ ((R >= $a) and (R =< $f)))) ->
+ Cha = (trx(N) bsl 16) bor (trx(O) bsl 12) bor (trx(P) bsl 8) bor (trx(Q) bsl 4) bor trx(R),
+ {int_to_utf8(Cha),Rest};
+multi_esc(<<$x,${,N,O,P,Q,R,S,$},Rest/binary>>)
+ when ((((N >= $0) and (N =< $9)) or ((N >= $A) and (N =< $F)) or
+ ((N >= $a) and (N =< $f))) and
+ (((O >= $0) and (O =< $9)) or ((O >= $A) and (O =< $F)) or
+ ((O >= $a) and (O =< $f))) and
+ (((P >= $0) and (P =< $9)) or ((P >= $A) and (P =< $F)) or
+ ((P >= $a) and (P =< $f))) and
+ (((Q >= $0) and (Q =< $9)) or ((Q >= $A) and (Q =< $F)) or
+ ((Q >= $a) and (Q =< $f))) and
+ (((R >= $0) and (R =< $9)) or ((R >= $A) and (R =< $F)) or
+ ((R >= $a) and (R =< $f))) and
+ (((S >= $0) and (S =< $9)) or ((S >= $A) and (S =< $F)) or
+ ((S >= $a) and (S =< $f)))) ->
+ Cha = (trx(N) bsl 20) bor (trx(O) bsl 16) bor (trx(P) bsl 12) bor (trx(Q) bsl 8) bor (trx(R) bsl 4) bor trx(S),
+ {int_to_utf8(Cha),Rest};
+multi_esc(<<$x,N,O,Rest/binary>>)
+ when ((((N >= $0) and (N =< $9)) or ((N >= $A) and (N =< $F)) or
+ ((N >= $a) and (N =< $f))) and
+ (((O >= $0) and (O =< $9)) or ((O >= $A) and (O =< $F)) or
+ ((O >= $a) and (O =< $f)))) ->
+ Cha = (trx(N) bsl 4) bor trx(O),
+ {<<Cha>>,Rest};
+multi_esc(<<$x,N,Rest/binary>>)
+ when (((N >= $0) and (N =< $9)) or ((N >= $A) and (N =< $F)) or
+ ((N >= $a) and (N =< $f))) ->
+ Cha = trx(N),
+ {<<Cha>>,Rest};
+multi_esc(_) ->
+ no.
+
+single_esc($") ->
+ $";
+single_esc($ ) ->
+ $ ;
+single_esc($') ->
+ $';
+single_esc($@) ->
+ $@;
+single_esc($t) ->
+ $\t;
+single_esc($n) ->
+ $\n;
+single_esc($r) ->
+ $\r;
+single_esc($f) ->
+ $\f;
+single_esc($e) ->
+ $\e;
+single_esc($b) ->
+ $\b;
+single_esc($$) ->
+ $$;
+single_esc($\\) ->
+ $\\;
+single_esc($a) ->
+ 7;
+%single_esc(Ch) when Ch >= $A, Ch =< $Z -> % eh?
+% Ch;
+
+single_esc(_) ->
+ no.
+
+info(Str,Lst) ->
+ case get(verbose) of
+ true ->
+ io:format(Str,Lst);
+ _ ->
+ ok
+ end.
+
+
+%% Generate split tests from indatafile,
+%% you will need perl on the machine
+gen_split_test(OneFile) ->
+ {ok,Bin} = file:read_file(OneFile),
+ Lines = splitfile(0,Bin,1),
+ Structured = stru(Lines),
+ PerlShellScript = OneFile++"_split_test_gen.sh",
+ dumpsplit(Structured,PerlShellScript),
+ PerlShellScript,
+ ErlModule = "re_"++filename:basename(OneFile)++"_split_test",
+ ErlFileName = ErlModule++".erl",
+ {ok,F}= file:open(ErlFileName,[write]),
+ io:format(F,"-module(~s).~n",[ErlModule]),
+ io:format(F,"-compile(export_all).~n",[]),
+ io:format(F,"-include(\"test_server.hrl\").~n",[]),
+ %io:format(F,"-define(line,erlang:display(?LINE),).~n",[]),
+ io:format(F,"%% This file is generated by running ~w:gen_split_test(~p)~n",
+ [?MODULE,OneFile]),
+ io:format(F,"join([]) -> [];~n",[]),
+ io:format(F,"join([A]) -> [A];~n",[]),
+ io:format(F,"join([H|T]) -> [H,<<\":\">>|join(T)].~n",[]),
+ io:format(F,"run() ->~n",[]),
+ file:close(F),
+ os:cmd("sh "++ PerlShellScript++" 2>/dev/null >> "++ErlFileName),
+ {ok,F2}= file:open(ErlFileName,[append]),
+ io:format(F2,"ok.~n",[]),
+ file:close(F2),
+ io:format("~s~n",[os:cmd("wc -l "++ErlFileName)]),
+ ok.
+
+dumpsplit(S,Fname) ->
+ {ok,F}= file:open(Fname,[write]),
+ dodumpsplit(F,S),
+ file:close(F).
+
+dodumpsplit(_,[]) ->
+ ok;
+dodumpsplit(F,[H|T]) ->
+ dumponesplit(F,H),
+ dodumpsplit(F,T).
+
+dumponesplit(F,{RE,_,O,TS}) ->
+ [begin
+ {NO,_} = pick_exec_options(O++Op),
+ SSS = opt_to_string(NO),
+ io:format(F,"perl -e '$x = join(\":\",split(/~s/~s,\"~s\")); "
+ "$x =~~ s/\\\\/\\\\\\\\/g; $x =~~ s/\\\"/\\\\\"/g; "
+ "print \"?line <<\\\"$x\\\">> = "
+ "iolist_to_binary(join(re:split(\\\"~s\\\","
+ "\\\"~s\\\",~p))), \\n\";'~n",
+ [zsafe(safe(RE)),
+ SSS,
+ ysafe(safe(Str)),
+ dsafe(safe(Str)),
+ dsafe2(safe(RE)),
+ NO++[trim]]),
+ io:format(F,"perl -e '$x = join(\":\",split(/~s/~s,\"~s\",2)); "
+ "$x =~~ s/\\\\/\\\\\\\\/g; $x =~~ s/\\\"/\\\\\"/g; "
+ "print \"?line <<\\\"$x\\\">> = "
+ "iolist_to_binary(join(re:split(\\\"~s\\\","
+ "\\\"~s\\\",~p))), \\n\";'~n",
+ [zsafe(safe(RE)),
+ SSS,
+ ysafe(safe(Str)),
+ dsafe(safe(Str)),
+ dsafe2(safe(RE)),
+ NO++[{parts,2}]]),
+ io:format(F,"perl -e '$x = join(\":\",split(/~s/~s,\"~s\",-1)); "
+ "$x =~~ s/\\\\/\\\\\\\\/g; $x =~~ s/\\\"/\\\\\"/g; "
+ "print \"?line <<\\\"$x\\\">> = "
+ "iolist_to_binary(join(re:split(\\\"~s\\\","
+ "\\\"~s\\\",~p))), \\n\";'~n",
+ [zsafe(safe(RE)),
+ SSS,
+ ysafe(safe(Str)),
+ dsafe(safe(Str)),
+ dsafe2(safe(RE)),
+ NO])
+ end ||
+ {Str,_,Op,_} <- TS].
+
+%% Generate replacement tests from indatafile,
+%% you will need perl on the machine
+gen_repl_test(OneFile) ->
+ random:seed(1219,687731,62804),
+ {ok,Bin} = file:read_file(OneFile),
+ Lines = splitfile(0,Bin,1),
+ Structured = stru(Lines),
+ PerlShellScript = OneFile++"_replacement_test_gen.sh",
+ dump(Structured,PerlShellScript),
+ ErlModule = "re_"++filename:basename(OneFile)++"_replacement_test",
+ ErlFileName = ErlModule++".erl",
+ {ok,F}= file:open(ErlFileName,[write]),
+ io:format(F,"-module(~s).~n",[ErlModule]),
+ io:format(F,"-compile(export_all).~n",[]),
+ io:format(F,"-include(\"test_server.hrl\").~n",[]),
+ io:format(F,"%% This file is generated by running ~w:gen_repl_test(~p)~n",
+ [?MODULE,OneFile]),
+ io:format(F,"run() ->~n",[]),
+ file:close(F),
+ os:cmd("sh "++ PerlShellScript++" 2>/dev/null >> "++ErlFileName),
+ {ok,F2}= file:open(ErlFileName,[append]),
+ io:format(F2,"ok.~n",[]),
+ file:close(F2),
+ io:format("~s~n",[os:cmd("wc -l "++ErlFileName)]),
+ ok.
+dump(S,Fname) ->
+ {ok,F}= file:open(Fname,[write]),
+ dodump(F,S),
+ file:close(F).
+
+dodump(_,[]) ->
+ ok;
+dodump(F,[H|T]) ->
+ dumpone(F,H),
+ dodump(F,T).
+
+dumpone(F,{RE,_,O,TS}) ->
+ [begin
+ {NO,_} = pick_exec_options(O++Op),
+ SSS = opt_to_string(NO),
+ RS = ranstring(),
+ io:format(F,"perl -e '$x = \"~s\"; $x =~~ s/~s/~s/~s; $x =~~ s/\\\\/\\\\\\\\/g; $x =~~ s/\\\"/\\\\\"/g; print \"?line <<\\\"$x\\\">> = iolist_to_binary(re:replace(\\\"~s\\\",\\\"~s\\\",\\\"~s\\\",~p)), \\n\";'~n",[ysafe(safe(Str)),zsafe(safe(RE)),perlify(binary_to_list(RS)),SSS,dsafe(safe(Str)),dsafe(safe(RE)),xsafe(RS),NO]),
+ io:format(F,"perl -e '$x = \"~s\"; $x =~~ s/~s/~s/g~s; $x =~~ s/\\\\/\\\\\\\\/g; $x =~~ s/\\\"/\\\\\"/g; print \"?line <<\\\"$x\\\">> = iolist_to_binary(re:replace(\\\"~s\\\",\\\"~s\\\",\\\"~s\\\",~p)), \\n\";'~n",[ysafe(safe(Str)),zsafe(safe(RE)),perlify(binary_to_list(RS)),SSS,dsafe(safe(Str)),dsafe(safe(RE)),xsafe(RS),NO++[global]])
+ end ||
+ {Str,_,Op,_} <- TS].
+
+dsafe2([]) ->
+ [];
+dsafe2([$\',$\",$\',$\",$\'|T]) ->
+ [$\',$\",$\',$\",$\' |dsafe2(T)];
+dsafe2([$\"|T]) ->
+ [$\\,$\\,$\\,$\" |dsafe2(T)];
+dsafe2([$\\, $G|T]) ->
+ [$\\,$\\,$\\,$\\,$A |dsafe2(T)];
+dsafe2([$\\|T]) ->
+ [$\\,$\\,$\\,$\\ |dsafe2(T)];
+dsafe2([$$|T]) ->
+ [$\\,$$|dsafe2(T)];
+dsafe2([H|T]) ->
+ [H|dsafe2(T)].
+
+dsafe([]) ->
+ [];
+dsafe([$\',$\",$\',$\",$\'|T]) ->
+ [$\',$\",$\',$\",$\' |dsafe(T)];
+dsafe([$\"|T]) ->
+ [$\\,$\\,$\\,$\" |dsafe(T)];
+dsafe([$\\|T]) ->
+ [$\\,$\\,$\\,$\\ |dsafe(T)];
+dsafe([$$|T]) ->
+ [$\\,$$|dsafe(T)];
+dsafe([H|T]) ->
+ [H|dsafe(T)].
+
+xsafe(<<>>) ->
+ [];
+xsafe(<<$\\,R/binary>>) ->
+ [$\\,$\\,$\\,$\\ | xsafe(R)];
+xsafe(<<X,R/binary>>) ->
+ [X|xsafe(R)].
+
+zsafe([]) ->
+ [];
+zsafe([$$, $b|T]) ->
+ [$\\,$$, $b | zsafe(T)];
+zsafe([X|R]) ->
+ [X|zsafe(R)].
+
+ysafe([]) ->
+ [];
+ysafe([$\',$\",$\',$\",$\'|T]) ->
+ [$\',$\",$\',$\",$\' |ysafe(T)];
+ysafe([$\"|T]) ->
+ [$\\,$\" |ysafe(T)];
+ysafe([$\\|T]) ->
+ [$\\,$\\ |ysafe(T)];
+ysafe([$$|T]) ->
+ [$\\,$$|ysafe(T)];
+ysafe([H|T]) ->
+ [H|ysafe(T)].
+
+safe(<<>>) ->
+ [];
+safe(<<$\n>>) -> %chomp
+ [];
+safe(<<$\',R/binary>>) ->
+ [$\',$\",$\',$\",$\' | safe(R)];
+safe(<<X,R/binary>>) ->
+ [X|safe(R)].
+perlify([$\\,N|Rest]) when N >= $0, N =< $9 ->
+ [$$,N|perlify(Rest)];
+perlify([$& | Rest]) ->
+ [$$,$& | perlify(Rest)];
+perlify([H|T]) ->
+ [H|perlify(T)];
+perlify([]) ->
+ [].
+
+opt_to_string([]) ->
+ [];
+opt_to_string([A|T]) ->
+ case btr(A) of
+ false ->
+ opt_to_string(T);
+ Ch ->
+ [Ch | opt_to_string(T)]
+ end.
+
+btr(caseless) ->
+ $i;
+btr(extended) ->
+ $x;
+btr(dotall) ->
+ $s;
+btr(multiline) ->
+ $m;
+btr(dupnames) ->
+ $J;
+btr(no_auto_capture) ->
+ $N;
+btr(unicode) ->
+ $8;
+btr(_) ->
+ false.
+
+
+ranchar() ->
+ case random:uniform(10) of
+ 9 -> $&;
+ 10 -> <<"\\1">>;
+ N when N < 5 ->
+ random:uniform($Z-$A)+$A-1;
+ M when M < 9 ->
+ random:uniform($z-$a)+$a-1
+ end.
+
+ranstring() ->
+ iolist_to_binary([ranchar() || _ <- lists:duplicate(random:uniform(20),0) ]).
+
diff --git a/lib/stdlib/test/select_SUITE.erl b/lib/stdlib/test/select_SUITE.erl
new file mode 100644
index 0000000000..54664fbb00
--- /dev/null
+++ b/lib/stdlib/test/select_SUITE.erl
@@ -0,0 +1,804 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(select_SUITE).
+-author('[email protected]').
+
+-export([test/0]).
+
+%%
+%% Define to run outside of test server
+%%
+%%-define(STANDALONE,1).
+
+%%
+%% Define for debug output
+%%
+%%-define(debug,1).
+
+-ifdef(STANDALONE).
+-define(config(A,B),config(A,B)).
+-export([config/2]).
+-define(fmt(A,B),io:format(A,B)).
+-else.
+-include("test_server.hrl").
+-define(fmt(A,B),test_server:format(A,B)).
+-endif.
+
+-ifdef(debug).
+-ifdef(STANDALONE).
+-define(line, erlang:display({?MODULE,?LINE}), ).
+-endif.
+-define(dbgformat(A,B),io:format(A,B)).
+-else.
+-ifdef(STANDALONE).
+-define(line, noop, ).
+-endif.
+-define(dbgformat(A,B),noop).
+-endif.
+
+-ifdef(STANDALONE).
+config(priv_dir,_) ->
+ ".".
+-else.
+%% When run in test server.
+-export([all/1,select_test/1,init_per_testcase/2, fin_per_testcase/2,
+ return_values/1]).
+
+init_per_testcase(_Case, Config) when is_list(Config) ->
+ ?line Dog=test_server:timetrap(test_server:seconds(1200)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(doc) ->
+ ["Test ets:select"];
+all(suite) ->
+ [return_values, select_test].
+
+select_test(suite) ->
+ [];
+select_test(doc) ->
+ ["Tests select in numerous ways"];
+select_test(Config) when list(Config) ->
+ do_test(Config).
+
+return_values(suite) ->
+ [];
+return_values(doc) ->
+ ["Tests return values in specific situations for select/3 and select/1"];
+return_values(Config) when list(Config) ->
+ do_return_values().
+
+-endif.
+
+
+table_factor({dets,_}) ->
+ 1;
+table_factor({ets,_}) ->
+ 100.
+
+gen_dets_filename(Config,N) ->
+ filename:join(?config(priv_dir,Config),
+ "testdets_" ++ integer_to_list(N) ++ ".dets").
+
+create_tables(Config) ->
+ Hash = ets:new(xxx, []),
+ Tree = ets:new(yyy, [ordered_set]),
+ Bag = ets:new(yyy, [bag]),
+ DBag = ets:new(yyy, [duplicate_bag]),
+ F1 = gen_dets_filename(Config,1),
+ (catch file:delete(F1)),
+ {ok,DetsPlain} = dets:open_file(testdets_1,
+ [{file, F1}]),
+ F3 = gen_dets_filename(Config,3),
+ (catch file:delete(F3)),
+ {ok,DetsBag} = dets:open_file(testdets_3,
+ [{file, F3},{type, bag}]),
+ F4 = gen_dets_filename(Config,4),
+ (catch file:delete(F4)),
+ {ok,DetsDBag} = dets:open_file(testdets_4,
+ [{file, F4},{type, duplicate_bag}]),
+ [{ets,Hash}, {ets,Tree}, {ets,Bag}, {ets,DBag},
+ {dets, DetsPlain}, {dets, DetsBag}, {dets, DetsDBag}].
+
+
+gen_key(N,list) ->
+ [N,N+1,N+2];
+gen_key(N,tuple) ->
+ {N,N+1,N+2};
+gen_key(N,complex) ->
+ {[N,N+1],N+2}.
+
+gen_fun(N) ->
+ fun() ->
+ N
+ end.
+
+gen_bin(N) ->
+ list_to_binary(integer_to_list(N)).
+
+gen_object(N,Type) ->
+ L = integer_to_list(N),
+ A = list_to_atom("a" ++ L),
+ {gen_key(N,Type), L, A, gen_fun(N), gen_bin(N)}.
+gen_object1(N,Type) ->
+ L = integer_to_list(N),
+ A = list_to_atom("a" ++ L),
+ {gen_key(N,Type), A, L, gen_fun(N), gen_bin(N)}.
+
+fill_table(_,0,_) ->
+ ok;
+fill_table({Mod,Tab},N,Type) ->
+ Obj1 = gen_object1(N,Type),
+ Obj = gen_object(N,Type),
+ Mod:insert(Tab, Obj1),
+ case Mod:info(Tab,type) of
+ bag ->
+ Mod:insert(Tab, Obj);
+ duplicate_bag ->
+ Mod:insert(Tab, Obj),
+ Mod:insert(Tab, Obj1);
+ _ ->
+ ok
+ end,
+ fill_table({Mod,Tab},N-1,Type).
+
+table_size(Tab) ->
+ 15 *table_factor(Tab).
+
+build_tables(Config,Type) ->
+ L = create_tables(Config),
+ ?dbgformat("Tables: ~p~n",[L]),
+ lists:foreach(fun(TD) ->
+ fill_table(TD,table_size(TD),Type)
+ end,
+ L),
+ L.
+
+destroy_tables([]) ->
+ ok;
+destroy_tables([{ets,Tab}|T]) ->
+ ets:delete(Tab),
+ destroy_tables(T);
+destroy_tables([{dets,Tab}|T]) ->
+ dets:close(Tab),
+ destroy_tables(T).
+
+
+init_random(Config) ->
+ WriteDir = ReadDir = ?config(priv_dir,Config),
+ (catch file:make_dir(WriteDir)),
+ Seed = case file:consult(filename:join([ReadDir,
+ "preset_random_seed2.txt"])) of
+ {ok,[X]} ->
+ X;
+ _ ->
+ {A,B,C} = erlang:now(),
+ random:seed(A,B,C),
+ get(random_seed)
+ end,
+ put(random_seed,Seed),
+ {ok, F} = file:open(filename:join([WriteDir, "last_random_seed2.txt"]),
+ [write]),
+ io:format(F,"~p. ~n",[Seed]),
+ file:close(F),
+ ok.
+
+create_random_key(N,Type) ->
+ gen_key(random:uniform(N),Type).
+
+create_pb_key(N,list) ->
+ X = random:uniform(N),
+ case random:uniform(4) of
+ 3 -> {[X, X+1, '_'], fun([Z,Z1,P1]) ->
+ [Z,Z1,P1] =:= [X,X+1,P1] end};
+ 2 -> {[X, '_', '_'], fun([Z,P1,P2]) -> [Z,P1,P2] =:= [X,P1,P2] end};
+ 1 -> {[X, X+1, '$1'], fun([Z,Z1,P1]) ->
+ [Z,Z1,P1] =:= [X,X+1,P1] end};
+ _ -> {[X, '$1', '$2'], fun([Z,P1,P2]) -> [Z,P1,P2] =:= [X,P1,P2] end}
+ end;
+create_pb_key(N, tuple) ->
+ X = random:uniform(N),
+ case random:uniform(2) of
+ 1 -> {{X, X+1, '$1'},fun({Z,Z1,P1}) -> {Z,Z1,P1} =:= {X,X+1,P1} end};
+ _ -> {{X, '$1', '$2'},fun({Z,P1,P2}) -> {Z,P1,P2} =:= {X,P1,P2} end}
+ end;
+create_pb_key(N, complex) ->
+ X = random:uniform(N),
+ case random:uniform(2) of
+ 1 -> {{[X, X+1], '$1'}, fun({[Z,Z1],P1}) ->
+ {[Z,Z1],P1} =:= {[X,X+1],P1} end};
+ _ -> {{[X, '$1'], '$2'},fun({[Z,P1],P2}) ->
+ {[Z,P1],P2} =:= {[X,P1],P2} end}
+ end.
+table_foldl(_Fun, Acc,{_Mod,_Tab},'$end_of_table') ->
+ Acc;
+table_foldl(Fun, Acc,{Mod,Tab},Key) ->
+ Objs = Mod:lookup(Tab,Key),
+ Acc2 = lists:foldl(Fun,Acc,Objs),
+ ?dbgformat("Objs: ~p, Acc2: ~p~n",[Objs,Acc2]),
+ table_foldl(Fun, Acc2, {Mod,Tab}, Mod:next(Tab,Key)).
+table_foldl(Fun, Acc,{Mod,Tab}) ->
+ table_foldl(Fun, Acc,{Mod,Tab},Mod:first(Tab)).
+
+chunked_select(Mod,Tab,MS,0) ->
+ Mod:select(Tab,MS);
+chunked_select(Mod,Tab,MS,Chunk) when Chunk > 0->
+ do_chunk_select(Mod, Mod:select(Tab,MS,Chunk),[]);
+chunked_select(Mod,Tab,MS,Chunk) when Chunk < 0->
+ case Mod of
+ ets ->
+ do_chunk_select_reverse(Mod,
+ Mod:select_reverse(Tab,MS,-Chunk),[]);
+ _ ->
+ chunked_select(Mod,Tab,MS,-Chunk)
+ end.
+
+
+do_chunk_select_reverse(_Mod, '$end_of_table',Acc) ->
+ %% OK, all this reversing is only needed for ordered_set, but
+ %% this is only testcases, right?
+ erlang:display(did_chunked_select_reverse),
+ Acc;
+do_chunk_select_reverse(Mod, {L,C},Acc) ->
+ NewAcc = lists:reverse(L)++Acc,
+ do_chunk_select(Mod, Mod:select(C), NewAcc).
+
+do_chunk_select(_Mod, '$end_of_table',Acc) ->
+ %% OK, all this reversing is only needed for ordered_set, but
+ %% this is only testcases, right?
+ lists:reverse(Acc);
+do_chunk_select(Mod, {L,C},Acc) ->
+ NewAcc = lists:reverse(L)++Acc,
+ do_chunk_select(Mod, Mod:select(C), NewAcc).
+
+cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2) ->
+ cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2, 0).
+
+cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2, ChunkSize) ->
+ MSRes = lists:sort(chunked_select(Mod,Tab,MS,ChunkSize)),
+ FunRes0 = table_foldl(Fun1,[],{Mod,Tab}),
+ FunRes = case Fun2 of
+ F when function(F) ->
+ FunRes1 = table_foldl(F,[],{Mod,Tab}),
+ lists:merge(FunRes0,FunRes1);
+ [] ->
+ lists:sort(FunRes0)
+ end,
+ case MSRes =:= FunRes of
+ true ->
+ true;
+ false ->
+ ?fmt("Match_spec result differs from fun result:~n",[]),
+ ?fmt("Parameters: ~p,~p,~p,~p~n",
+ [{Mod,Tab}, MS, Fun1, Fun2]),
+ ?fmt("Match_spec Result: ~p~n", [MSRes]),
+ ?fmt("Fun Result: ~p~n", [FunRes]),
+ Info = (catch Mod:info(Tab)),
+ ?fmt("Table info:~p~n", [Info]),
+ {'EXIT', {hej, ST}} = (catch erlang:error(hej)),
+ ?fmt("Stack backtrace: ~p~n", [ST]),
+ erlang:error(badmatch)
+ end.
+
+do_n(0,_) -> ok;
+do_n(N,Fun) ->
+ Fun(),
+ do_n(N-1,Fun).
+
+%%
+%% We want some misses too, so pretend the tables are slightly
+%% larger than they really are.
+%%
+num_els(Tab) ->
+ 16 * table_factor(Tab).
+
+
+test() ->
+ do_return_values(),
+ do_test([]).
+
+do_test(Config) ->
+ init_random(Config),
+ whitebox(),
+ lists:foreach(fun(Type) ->
+ Tabs = build_tables(Config,Type),
+ basic_key(Tabs,Type),
+ ?fmt("basic_key done for type ~w~n",[Type]),
+ basic_pb_key(Tabs,Type),
+ ?fmt("basic_pb_key done for type ~w~n",[Type]),
+ double_pb_key(Tabs,Type),
+ ?fmt("double_pb_key done for type ~w~n",[Type]),
+ multi_key(Tabs,Type),
+ ?fmt("multi_key done for type ~w~n",[Type]),
+ multi_mixed_key(Tabs,Type),
+ ?fmt("multi_mixed_key done for type ~w~n",
+ [Type]),
+ destroy_tables(Tabs)
+ end,
+ [tuple, list, complex]),
+ ok.
+
+basic_key(Tabs,Type) ->
+ Fun = fun() ->
+ lists:map(fun(Tab) ->
+ ?line Key =
+ create_random_key(num_els(Tab),Type),
+ ?line MS =
+ [{{Key,'_','_','_','_'},[],['$_']}],
+ MF = fun({Key0,A,B,F,Bi},Acc) ->
+ case Key =:= Key0 of
+ true ->
+ [{Key0,A,B,F,Bi} |
+ Acc];
+ _ ->
+ Acc
+ end
+ end,
+ ?line cmp_ms_to_fun(Tab,MS,MF,[])
+ end,
+ Tabs)
+ end,
+ ?line do_n(50,Fun),
+ ok.
+
+basic_pb_key(Tabs,Type) ->
+ InnerFun = fun(Tab) ->
+ ?line {Key,KeyFun} =
+ create_pb_key(num_els(Tab),Type),
+ ?line MS = [{{Key,'_','_','_','_'},[],['$_']}],
+ MF = fun({Key0,A,B,F,Bi},Acc) ->
+ case KeyFun(Key0) of
+ true ->
+ [{Key0,A,B,F,Bi} |
+ Acc];
+ _ ->
+ Acc
+ end
+ end,
+ ?line cmp_ms_to_fun(Tab,MS,MF,[])
+ end,
+ ?line {Etses, Detses} = split_by_type(Tabs),
+
+ ?line FunEts = fun() ->
+ ?line lists:foreach(InnerFun,
+ Etses)
+ end,
+ ?line FunDets = fun() ->
+ ?line lists:foreach(InnerFun,
+ Detses)
+ end,
+ ?line do_n(table_factor(hd(Etses)) div 2,FunEts),
+ ?line do_n(10,FunDets),
+ ok.
+
+double_pb_key(Tabs,Type) ->
+ InnerFun = fun(Tab) ->
+ ?line {KeyA,KeyFunA} =
+ create_pb_key(num_els(Tab),Type),
+ ?line {KeyB,KeyFunB} =
+ create_pb_key(num_els(Tab),Type),
+ MS = [{{KeyA,'_','_','_','_'},[],['$_']},
+ {{KeyB,'_','_','_','_'},[],['$_']}],
+ ?dbgformat("Tab: ~p, MS: ~p~n",
+ [Tab,MS]),
+ MF = fun({Key0,A,B,F,Bi},Acc) ->
+ case KeyFunA(Key0) of
+ true ->
+ ?dbgformat
+ ("FunMatched:"
+ " ~p~n",
+ [{Key0,A,
+ B,F,Bi}]),
+ [{Key0,A,B,F,Bi} |
+ Acc];
+ _ ->
+ case KeyFunB(Key0) of
+ true ->
+ ?dbgformat
+ ("Fun"
+ "Matched:"
+ " ~p~n",
+ [{Key0,A,
+ B,F,
+ Bi}]),
+ [{Key0,A,B,
+ F,Bi} |
+ Acc];
+ _ ->
+ Acc
+ end
+ end
+ end,
+ ?line cmp_ms_to_fun(Tab,MS,MF,[])
+ end,
+ ?line {Etses, Detses} = split_by_type(Tabs),
+
+ ?line FunEts = fun() ->
+ ?line lists:foreach(InnerFun,
+ Etses)
+ end,
+ ?line FunDets = fun() ->
+ ?line lists:foreach(InnerFun,
+ Detses)
+ end,
+ ?line do_n(table_factor(hd(Etses)) div 2,FunEts),
+ ?line do_n(10,FunDets),
+ ok.
+
+
+multi_key(Tabs,Type) ->
+ Fun = fun() ->
+ lists:map(fun(Tab) ->
+ ?line KeyA =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyB =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyC =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyD =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyE =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyF =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyG =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyH =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyI =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyJ =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyK =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyL =
+ create_random_key(num_els(Tab),Type),
+
+ MS = [{{KeyA,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyB,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyC,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyD,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyE,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyF,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyG,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyH,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyI,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyJ,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyK,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyL,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]}
+ ],
+ ?dbgformat("Tab: ~p, MS: ~p~n",
+ [Tab,MS]),
+ MF = fun({Key0,A,_B,F,_Bi},Acc) ->
+ case Key0 of
+ KeyA ->
+ [ {A,F} |
+ Acc];
+ KeyB ->
+ [ {A,F} |
+ Acc];
+ KeyC ->
+ [ {A,F} |
+ Acc];
+ KeyD ->
+ [ {A,F} |
+ Acc];
+ KeyE ->
+ [ {A,F} |
+ Acc];
+ KeyF ->
+ [ {A,F} |
+ Acc];
+ KeyG ->
+ [ {A,F} |
+ Acc];
+ KeyH ->
+ [ {A,F} |
+ Acc];
+ KeyI ->
+ [ {A,F} |
+ Acc];
+ KeyJ ->
+ [ {A,F} |
+ Acc];
+ KeyK ->
+ [ {A,F} |
+ Acc];
+ KeyL ->
+ [ {A,F} |
+ Acc];
+ _ ->
+ Acc
+ end
+ end,
+ ?line cmp_ms_to_fun(Tab,MS,MF,[])
+ end,
+ Tabs)
+ end,
+ ?line do_n(33,Fun),
+ ok.
+
+multi_mixed_key(Tabs,Type) ->
+ InnerFun = fun(Tab) ->
+ ?line KeyA =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyB =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyC =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyD =
+ create_random_key(num_els(Tab),Type),
+ ?line {KeyE, FunE} =
+ create_pb_key(num_els(Tab),Type),
+ ?line KeyF =
+ create_random_key(num_els(Tab),Type),
+ ?line {KeyG, FunG} =
+ create_pb_key(num_els(Tab),Type),
+ ?line KeyH =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyI =
+ create_random_key(num_els(Tab),Type),
+ ?line {KeyJ, FunJ} =
+ create_pb_key(num_els(Tab),Type),
+ ?line KeyK =
+ create_random_key(num_els(Tab),Type),
+ ?line KeyL =
+ create_random_key(num_els(Tab),Type),
+
+ MS = [{{KeyA,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyB,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyC,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyD,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyE,'$100','_','$200','_'},[],
+ [{{'$100','$200'}}]},
+ {{KeyF,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyG,'$100','_','$200','_'},[],
+ [{{'$100','$200'}}]},
+ {{KeyH,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyI,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyJ,'$100','_','$200','_'},[],
+ [{{'$100','$200'}}]},
+ {{KeyK,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]},
+ {{KeyL,'$1','_','$2','_'},[],
+ [{{'$1','$2'}}]}
+ ],
+ ?dbgformat("Tab: ~p, MS: ~p~n",
+ [Tab,MS]),
+ MF = fun({Key0,A,_B,F,_Bi},Acc) ->
+ case Key0 of
+ KeyA ->
+ [ {A,F} |
+ Acc];
+ KeyB ->
+ [ {A,F} |
+ Acc];
+ KeyC ->
+ [ {A,F} |
+ Acc];
+ KeyD ->
+ [ {A,F} |
+ Acc];
+ KeyF ->
+ [ {A,F} |
+ Acc];
+ KeyH ->
+ [ {A,F} |
+ Acc];
+ KeyI ->
+ [ {A,F} |
+ Acc];
+ KeyK ->
+ [ {A,F} |
+ Acc];
+ KeyL ->
+ [ {A,F} |
+ Acc];
+ Else ->
+ case FunE(Else) or
+ FunG(Else) or
+ FunJ(Else) of
+ true ->
+ [ {A,F} |
+ Acc];
+ _ ->
+ Acc
+ end
+ end
+ end,
+ ?line cmp_ms_to_fun(Tab,MS,MF,[]),
+ ?line case Tab of
+ {ets,_} ->
+ ?line cmp_ms_to_fun(Tab,MS,MF,[],1),
+ ?line cmp_ms_to_fun(Tab,MS,MF,[],10),
+ ?line cmp_ms_to_fun(Tab,MS,MF,[],1000000),
+ ?line cmp_ms_to_fun(Tab,MS,MF,[],-1),
+ ?line cmp_ms_to_fun(Tab,MS,MF,[],-10),
+ ?line cmp_ms_to_fun(Tab,MS,MF,[],-1000000);
+ _ ->
+ ok
+ end
+ end,
+ ?line {Etses, Detses} = split_by_type(Tabs),
+
+ ?line FunEts = fun() ->
+ ?line lists:foreach(InnerFun,
+ Etses)
+ end,
+ ?line FunDets = fun() ->
+ ?line lists:foreach(InnerFun,
+ Detses)
+ end,
+ ?line do_n(table_factor(hd(Etses)) div 2,FunEts),
+ ?line do_n(table_factor(hd(Detses)) div 2,FunDets),
+ ok.
+
+
+split_by_type(List) ->
+ split_by_type(List,[],[]).
+split_by_type([],AccEts,AccDets) ->
+ {AccEts,AccDets};
+split_by_type([{dets,Tab}|T],AccEts,AccDets) ->
+ split_by_type(T,AccEts,[{dets,Tab}|AccDets]);
+split_by_type([{ets,Tab}|T],AccEts,AccDets) ->
+ split_by_type(T,[{ets,Tab}|AccEts],AccDets).
+
+whitebox() ->
+ ?line ets:new(xxx,[named_table, ordered_set]),
+ ?line ets:new(yyy,[named_table]),
+ ?line E = fun(0,_)->ok;
+ (N,F) ->
+ ?line ets:insert(xxx,{N,N rem 10}),
+ ?line ets:insert(yyy,{N,N rem 10}),
+ F(N-1,F)
+ end,
+ ?line E(10000,E),
+
+ ?line G = fun(F,C,A) ->
+ ?line case ets:select(C) of
+ {L,C2} ->
+ ?line F(F,C2,A+length(L));
+ '$end_of_table' ->
+ ?line A
+ end
+ end,
+ ?line H=fun({L,C}) ->
+ ?line G(G,C,length(L))
+ end,
+
+ ?line 1 = H(ets:select(xxx,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)),
+ ?line 10000 = H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],1)),
+ ?line 1 = H(ets:select(yyy,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)),
+ ?line 10000 = H(ets:select(yyy,[{{'$1','$2'},[],['$_']}],1)),
+
+ ?line {[{5,5}],_} = ets:select(xxx,[{{5,'$2'},[],['$_']}],1),
+ ?line {[{5,5}],_} = ets:select(yyy,[{{5,'$2'},[],['$_']}],1),
+
+ ?line I = fun(_,0) ->
+ ok;
+ (I,N) ->
+ ?line 10000 =
+ H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)),
+ I(I,N-1)
+ end,
+ ?line I(I,2000),
+ ?line J = fun(F,C,A) ->
+ ?line case ets:select(C) of
+ {L,C2} ->
+ ?line F(F,C2,lists:reverse(L)++A);
+ '$end_of_table' ->
+ ?line lists:reverse(A)
+ end
+ end,
+ ?line K = fun({L,C}) ->
+ ?line J(J,C,lists:reverse(L))
+ end,
+ ?line M = fun(_, _, 0) ->
+ ok;
+ (F, What, N) ->
+ ?line What =
+ K(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)),
+ F(F, What, N-1)
+ end,
+ ?line N = fun(HM) ->
+ ?line What = ets:select(xxx,[{{'$1','$2'},[],['$_']}]),
+ ?line What = lists:sort(What),
+ M(M, What, HM)
+ end,
+ ?line N(2000),
+ ?line ets:delete(xxx),
+ ?line ets:delete(yyy).
+
+
+do_return_values() ->
+ ?line T = ets:new(xxx,[ordered_set]),
+ ?line U = ets:new(xxx,[]),
+ ?line '$end_of_table' = ets:select(T,[{'_',[],['$_']}],1),
+ ?line '$end_of_table' = ets:select(U,[{'_',[],['$_']}],1),
+ ?line ets:insert(T,{ett,1}),
+ ?line ets:insert(U,{ett,1}),
+ ?line {[{ett,1}],C1} = ets:select(T,[{'_',[],['$_']}],1),
+ ?line '$end_of_table' = ets:select(C1),
+ ?line {[{ett,1}],C2} = ets:select(U,[{'_',[],['$_']}],1),
+ ?line '$end_of_table' = ets:select(C2),
+ ?line {[{ett,1}],C3} = ets:select(T,[{'_',[],['$_']}],2),
+ ?line '$end_of_table' = ets:select(C3),
+ ?line {[{ett,1}],C4} = ets:select(U,[{'_',[],['$_']}],2),
+ ?line '$end_of_table' = ets:select(C4),
+ ?line E = fun(0,_)->ok;
+ (N,F) ->
+ ?line ets:insert(T,{N,N rem 10}),
+ ?line ets:insert(U,{N,N rem 10}),
+ F(N-1,F)
+ end,
+ ?line E(10000,E),
+ ?line '$end_of_table' = ets:select(T,[{{hej, hopp},[],['$_']}],1),
+ ?line '$end_of_table' = ets:select(U,[{{hej,hopp},[],['$_']}],1),
+ ?line {[{ett,1}],CC1} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}],
+ ['$_']}],1),
+ ?line '$end_of_table' = ets:select(CC1),
+ ?line {[{ett,1}],CC2} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}],
+ ['$_']}],1),
+ ?line '$end_of_table' = ets:select(CC2),
+ ?line {[{ett,1}],CC3} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}],
+ ['$_']}],2),
+ ?line '$end_of_table' = ets:select(CC3),
+ ?line {[{ett,1}],CC4} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}],
+ ['$_']}],2),
+ ?line '$end_of_table' = ets:select(CC4),
+ ?line ets:delete(T),
+ ?line ets:delete(U),
+ ?line V = ets:new(xxx,[{keypos, 4}]),
+ ?line X = ets:new(xxx,[ordered_set, {keypos, 4}]),
+ ?line ets:insert(V,{1,1,1,ett}),
+ ?line ets:insert(X,{1,1,1,ett}),
+ ?line '$end_of_table' = ets:select(V,[{{1,1,1},[],['$_']}],1),
+ ?line '$end_of_table' = ets:select(X,[{{1,1,1},[],['$_']}],1),
+ ?line ets:delete(V),
+ ?line ets:delete(X),
+ ok.
+
+
+
+
+
diff --git a/lib/stdlib/test/sets_SUITE.erl b/lib/stdlib/test/sets_SUITE.erl
new file mode 100644
index 0000000000..c9f1a03598
--- /dev/null
+++ b/lib/stdlib/test/sets_SUITE.erl
@@ -0,0 +1,495 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% This module tests the ordsets, sets, and gb_sets modules.
+%%
+
+-module(sets_SUITE).
+
+-export([all/1,init_per_testcase/2,fin_per_testcase/2,
+ create/1,add_element/1,del_element/1,
+ subtract/1,intersection/1,union/1,is_subset/1,
+ is_set/1,fold/1,filter/1,
+ take_smallest/1,take_largest/1]).
+
+-include("test_server.hrl").
+
+-import(lists, [foldl/3,reverse/1]).
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?t:minutes(5)),
+ [{watchdog,Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+all(suite) ->
+ [create,add_element,del_element,subtract,
+ intersection,union,is_subset,is_set,fold,filter,
+ take_smallest,take_largest].
+
+create(Config) when is_list(Config) ->
+ test_all(fun create_1/1).
+
+create_1(M) ->
+ ?line S0 = M:empty(),
+ ?line [] = M:to_list(S0),
+ ?line 0 = M:size(S0),
+ ?line true = M:is_empty(S0),
+ E = make_ref(),
+ ?line One = M:singleton(E),
+ ?line 1 = M:size(One),
+ ?line false = M:is_empty(One),
+ [E] = M:to_list(One),
+ S0.
+
+add_element(Config) when is_list(Config) ->
+ test_all([{0,132},{253,258},{510,514}], fun add_element_1/2).
+
+add_element_1(List, M) ->
+ ?line S = M:from_list(List),
+ ?line SortedSet = lists:usort(List),
+ ?line SortedSet = lists:sort(M:to_list(S)),
+
+ %% Make sure that we get the same result by inserting
+ %% elements one at the time.
+ ?line S2 = foldl(fun(El, Set) -> M:add_element(El, Set) end,
+ M:empty(), List),
+ ?line true = M:equal(S, S2),
+
+ %% Insert elements, randomly delete inserted elements,
+ %% and re-inserted all deleted elements at the end.
+ ?line S3 = add_element_del(List, M, M:empty(), [], []),
+ ?line true = M:equal(S2, S3),
+ ?line true = M:equal(S, S3),
+ S.
+
+add_element_del([H|T], M, S, Del, []) ->
+ add_element_del(T, M, M:add_element(H, S), Del, [H]);
+add_element_del([H|T], M, S0, Del, Inserted) ->
+ S1 = M:add_element(H, S0),
+ case random:uniform(3) of
+ 1 ->
+ OldEl = lists:nth(random:uniform(length(Inserted)), Inserted),
+ S = M:del_element(OldEl, S1),
+ add_element_del(T, M, S, [OldEl|Del], [H|Inserted]);
+ _ ->
+ add_element_del(T, M, S1, Del, [H|Inserted])
+ end;
+add_element_del([], M, S, Del, _) ->
+ M:union(S, M:from_list(Del)).
+
+del_element(Config) when is_list(Config) ->
+ test_all([{0,132},{253,258},{510,514},{1022,1026}], fun del_element_1/2).
+
+del_element_1(List, M) ->
+ ?line S0 = M:from_list(List),
+ ?line Empty = foldl(fun(El, Set) -> M:del_element(El, Set) end, S0, List),
+ ?line Empty = M:empty(),
+ ?line M:is_empty(Empty),
+ ?line S1 = foldl(fun(El, Set) ->
+ M:add_element(El, Set)
+ end, S0, reverse(List)),
+ ?line true = M:equal(S0, S1),
+ S1.
+
+subtract(Config) when is_list(Config) ->
+ test_all(fun subtract_empty/1),
+
+ %% Note: No empty set.
+ test_all([{2,69},{126,130},{253,258},511,512,{1023,1030}], fun subtract_1/2).
+
+subtract_empty(M) ->
+ ?line Empty = M:empty(),
+ ?line true = M:is_empty(M:subtract(Empty, Empty)),
+ M:subtract(Empty, Empty).
+
+subtract_1(List, M) ->
+ ?line S0 = M:from_list(List),
+ ?line Empty = M:empty(),
+
+ %% Trivial cases.
+ ?line true = M:is_empty(M:subtract(Empty, S0)),
+ ?line true = M:equal(S0, M:subtract(S0, Empty)),
+
+ %% Not so trivial.
+ ?line subtract_check(List, mutate_some(remove_some(List, 0.4)), M),
+ ?line subtract_check(List, rnd_list(length(List) div 2 + 5), M),
+ ?line subtract_check(List, rnd_list(length(List) div 7 + 9), M),
+ ?line subtract_check(List, mutate_some(List), M).
+
+subtract_check(A, B, M) ->
+ one_subtract_check(B, A, M),
+ one_subtract_check(A, B, M).
+
+one_subtract_check(A, B, M) ->
+ ASorted = lists:usort(A),
+ BSorted = lists:usort(B),
+ ASet = M:from_list(A),
+ BSet = M:from_list(B),
+ DiffSet = M:subtract(ASet, BSet),
+ Diff = ASorted -- BSorted,
+ true = M:equal(DiffSet, M:from_list(Diff)),
+ Diff = lists:sort(M:to_list(DiffSet)),
+ DiffSet.
+
+intersection(Config) when is_list(Config) ->
+ %% Note: No empty set.
+ test_all([{1,65},{126,130},{253,259},{499,513},{1023,1025}], fun intersection_1/2).
+
+intersection_1(List, M) ->
+ ?line S0 = M:from_list(List),
+
+ %% Intersection with self.
+ ?line true = M:equal(S0, M:intersection(S0, S0)),
+ ?line true = M:equal(S0, M:intersection([S0,S0])),
+ ?line true = M:equal(S0, M:intersection([S0,S0,S0])),
+ ?line true = M:equal(S0, M:intersection([S0])),
+
+ %% Intersection with empty.
+ ?line Empty = M:empty(),
+ ?line true = M:equal(Empty, M:intersection(S0, Empty)),
+ ?line true = M:equal(Empty, M:intersection([S0,Empty,S0,Empty])),
+
+ %% The intersection of no sets is undefined.
+ ?line {'EXIT',_} = (catch M:intersection([])),
+
+ %% Disjoint sets.
+ ?line Disjoint = [{El} || El <- List],
+ ?line DisjointSet = M:from_list(Disjoint),
+ ?line M:is_empty(M:intersection(S0, DisjointSet)),
+
+ %% Disjoint, different sizes.
+ ?line M:is_empty(M:intersection(S0, M:from_list(remove_some(Disjoint, 0.3)))),
+ ?line M:is_empty(M:intersection(S0, M:from_list(remove_some(Disjoint, 0.7)))),
+ ?line M:is_empty(M:intersection(S0, M:from_list(remove_some(Disjoint, 0.9)))),
+ ?line M:is_empty(M:intersection(M:from_list(remove_some(List, 0.3)), DisjointSet)),
+ ?line M:is_empty(M:intersection(M:from_list(remove_some(List, 0.5)), DisjointSet)),
+ ?line M:is_empty(M:intersection(M:from_list(remove_some(List, 0.9)), DisjointSet)),
+
+ %% Partial overlap (one or more elements in result set).
+ %% The sets have almost the same size. (Almost because a duplicated
+ %% element in the original list could be mutated and not mutated
+ %% at the same time.)
+ ?line PartialOverlap = mutate_some(List, []),
+ ?line IntersectionSet = check_intersection(List, PartialOverlap, M),
+ ?line false = M:is_empty(IntersectionSet),
+
+ %% Partial overlap, different set sizes. (Intersection possibly empty.)
+ ?line check_intersection(List, remove_some(PartialOverlap, 0.1), M),
+ ?line check_intersection(List, remove_some(PartialOverlap, 0.3), M),
+ ?line check_intersection(List, remove_some(PartialOverlap, 0.5), M),
+ ?line check_intersection(List, remove_some(PartialOverlap, 0.7), M),
+ ?line check_intersection(List, remove_some(PartialOverlap, 0.9), M),
+
+ IntersectionSet.
+
+check_intersection(Orig, Mutated, M) ->
+ OrigSet = M:from_list(Orig),
+ MutatedSet = M:from_list(Mutated),
+ Intersection = [El || El <- Mutated, not is_tuple(El)],
+ SortedIntersection = lists:usort(Intersection),
+ IntersectionSet = M:intersection(OrigSet, MutatedSet),
+ true = M:equal(IntersectionSet, M:from_list(SortedIntersection)),
+ SortedIntersection = lists:sort(M:to_list(IntersectionSet)),
+
+ IntersectionSet.
+
+
+union(Config) when is_list(Config) ->
+ %% Note: No empty set.
+ test_all([{1,71},{125,129},{254,259},{510,513},{1023,1025}], fun union_1/2).
+
+union_1(List, M) ->
+ ?line S = M:from_list(List),
+
+ %% Union with self and empty.
+ ?line Empty = M:empty(),
+ ?line true = M:equal(S, M:union(S, S)),
+ ?line true = M:equal(S, M:union([S,S])),
+ ?line true = M:equal(S, M:union([S,S,Empty])),
+ ?line true = M:equal(S, M:union([S,Empty,S])),
+ ?line true = M:equal(S, M:union(S, Empty)),
+ ?line true = M:equal(S, M:union([S])),
+ ?line true = M:is_empty(M:union([])),
+
+ %% Partial overlap.
+ ?line check_union(List, remove_some(mutate_some(List), 0.9), M),
+ ?line check_union(List, remove_some(mutate_some(List), 0.7), M),
+ ?line check_union(List, remove_some(mutate_some(List), 0.5), M),
+ ?line check_union(List, remove_some(mutate_some(List), 0.3), M),
+ ?line check_union(List, remove_some(mutate_some(List), 0.1), M),
+
+ ?line check_union(List, mutate_some(remove_some(List, 0.9)), M),
+ ?line check_union(List, mutate_some(remove_some(List, 0.7)), M),
+ ?line check_union(List, mutate_some(remove_some(List, 0.5)), M),
+ ?line check_union(List, mutate_some(remove_some(List, 0.3)), M),
+ ?line check_union(List, mutate_some(remove_some(List, 0.1)), M).
+
+check_union(Orig, Other, M) ->
+ OrigSet = M:from_list(Orig),
+ OtherSet = M:from_list(Other),
+ Union = Orig++Other,
+ SortedUnion = lists:usort(Union),
+ UnionSet = M:union(OrigSet, OtherSet),
+ SortedUnion = lists:sort(M:to_list(UnionSet)),
+ M:equal(UnionSet, M:from_list(Union)),
+ UnionSet.
+
+is_subset(Config) when is_list(Config) ->
+ test_all([{1,132},{253,270},{299,311}], fun is_subset_1/2).
+
+is_subset_1(List, M) ->
+ ?line S = M:from_list(List),
+ ?line Empty = M:empty(),
+
+ %% Subset of empty and self.
+ ?line true = M:is_subset(Empty, Empty),
+ ?line true = M:is_subset(Empty, S),
+ ?line false = M:is_subset(S, Empty),
+ ?line true = M:is_subset(S, S),
+
+ %% Other cases.
+ Res = [?line false = M:is_subset(M:singleton(make_ref()), S),
+ ?line true = M:is_subset(M:singleton(hd(List)), S),
+ ?line true = check_subset(remove_some(List, 0.1), List, M),
+ ?line true = check_subset(remove_some(List, 0.5), List, M),
+ ?line true = check_subset(remove_some(List, 0.9), List, M),
+ ?line check_subset(mutate_some(List), List, M),
+ ?line check_subset(rnd_list(length(List) div 2 + 5), List, M),
+ ?line subtract_check(List, rnd_list(length(List) div 7 + 9), M)
+ ],
+ res_to_set(Res, M, 0, []).
+
+check_subset(X, Y, M) ->
+ check_one_subset(Y, X, M),
+ check_one_subset(X, Y, M).
+
+check_one_subset(X, Y, M) ->
+ XSet = M:from_list(X),
+ YSet = M:from_list(Y),
+ SortedX = lists:usort(X),
+ SortedY = lists:usort(Y),
+ IsSubSet = length(SortedY--SortedX) =:= length(SortedY) - length(SortedX),
+ IsSubSet = M:is_subset(XSet, YSet),
+ IsSubSet.
+
+%% Encode all test results as a set to return.
+res_to_set([true|T], M, I, Acc) ->
+ res_to_set(T, M, I+1, [I|Acc]);
+res_to_set([_|T], M, I, Acc) ->
+ res_to_set(T, M, I+1, Acc);
+res_to_set([], M, _, Acc) -> M:from_list(Acc).
+
+is_set(Config) when is_list(Config) ->
+ %% is_set/1 is tested in the other test cases when its argument
+ %% is a set. Here test some arguments that makes it return false.
+
+ ?line false = gb_sets:is_set([a,b]),
+ ?line false = gb_sets:is_set({a,very,bad,tuple}),
+
+ ?line false = sets:is_set([a,b]),
+ ?line false = sets:is_set({a,very,bad,tuple}),
+
+ ?line false = ordsets:is_set([b,a]),
+ ?line false = ordsets:is_set({bad,tuple}),
+
+ %% Now test values that are known to be bad for all set representations.
+ test_all(fun is_set_1/1).
+
+is_set_1(M) ->
+ ?line false = M:is_set(self()),
+ ?line false = M:is_set(blurf),
+ ?line false = M:is_set(make_ref()),
+ ?line false = M:is_set(<<1,2,3>>),
+ ?line false = M:is_set(42),
+ ?line false = M:is_set(math:pi()),
+ ?line false = M:is_set({}),
+ M:empty().
+
+fold(Config) when is_list(Config) ->
+ test_all([{0,71},{125,129},{254,259},{510,513},{1023,1025},{9999,10001}],
+ fun fold_1/2).
+
+fold_1(List, M) ->
+ ?line S = M:from_list(List),
+ ?line L = M:fold(fun(E, A) -> [E|A] end, [], S),
+ ?line true = lists:sort(L) =:= lists:usort(List),
+ M:empty().
+
+filter(Config) when is_list(Config) ->
+ test_all([{0,69},{126,130},{254,259},{510,513},{1023,1025},{7999,8000}],
+ fun filter_1/2).
+
+filter_1(List, M) ->
+ ?line S = M:from_list(List),
+ IsNumber = fun(X) -> is_number(X) end,
+ ?line M:equal(M:from_list(lists:filter(IsNumber, List)),
+ M:filter(IsNumber, S)),
+ ?line M:filter(fun(X) -> is_atom(X) end, S).
+
+%%%
+%%% Test specifics for gb_sets.
+%%%
+
+take_smallest(Config) when is_list(Config) ->
+ test_all([{1,71},{125,129},{254,259},{510,513},{1023,1025}],
+ fun take_smallest_1/2).
+
+take_smallest_1(List, M) ->
+ case M:module() of
+ gb_sets -> take_smallest_2(List, M);
+ _ -> ok
+ end,
+ M:empty().
+
+take_smallest_2(List0, M) ->
+ ?line List = lists:usort(List0),
+ ?line S = M:from_list(List0),
+ take_smallest_3(S, List, M).
+
+take_smallest_3(S0, List0, M) ->
+ case M:is_empty(S0) of
+ true -> ok;
+ false ->
+ ?line Smallest = hd(List0),
+ ?line Smallest = gb_sets:smallest(S0),
+ ?line {Smallest,S} = gb_sets:take_smallest(S0),
+ ?line List = tl(List0),
+ ?line true = gb_sets:to_list(S) =:= List,
+ take_smallest_3(S, List, M)
+ end.
+
+take_largest(Config) when is_list(Config) ->
+ test_all([{1,71},{125,129},{254,259},{510,513},{1023,1025}],
+ fun take_largest_1/2).
+
+take_largest_1(List, M) ->
+ case M:module() of
+ gb_sets -> take_largest_2(List, M);
+ _ -> ok
+ end,
+ M:empty().
+
+take_largest_2(List0, M) ->
+ ?line List = reverse(lists:usort(List0)),
+ ?line S = M:from_list(List0),
+ take_largest_3(S, List, M).
+
+take_largest_3(S0, List0, M) ->
+ case M:is_empty(S0) of
+ true -> ok;
+ false ->
+ ?line Largest = hd(List0),
+ ?line Largest = gb_sets:largest(S0),
+ ?line {Largest,S} = gb_sets:take_largest(S0),
+ ?line List = tl(List0),
+ ?line true = gb_sets:to_list(S) =:= reverse(List),
+ take_largest_3(S, List, M)
+ end.
+
+%%%
+%%% Helper functions.
+%%%
+
+sets_mods() ->
+ Ordsets = sets_test_lib:new(ordsets, fun(X, Y) -> X == Y end),
+ Sets = sets_test_lib:new(sets, fun(X, Y) ->
+ lists:sort(sets:to_list(X)) ==
+ lists:sort(sets:to_list(Y)) end),
+ Gb = sets_test_lib:new(gb_sets, fun(X, Y) ->
+ gb_sets:to_list(X) ==
+ gb_sets:to_list(Y) end),
+ [Ordsets,Sets,Gb].
+
+test_all(Tester) ->
+ ?line Res = [begin
+ random:seed(1, 2, 42),
+ S = Tester(M),
+ {M:size(S),lists:sort(M:to_list(S))}
+ end || M <- sets_mods()],
+ ?line all_same(Res).
+
+test_all([{Low,High}|T], Tester) ->
+ test_all(lists:seq(Low, High)++T, Tester);
+test_all([Sz|T], Tester) when is_integer(Sz) ->
+ List = rnd_list(Sz),
+ ?line Res = [begin
+ random:seed(19, 2, Sz),
+ S = Tester(List, M),
+ {M:size(S),lists:sort(M:to_list(S))}
+ end || M <- sets_mods()],
+ ?line all_same(Res),
+ test_all(T, Tester);
+test_all([], _) -> ok.
+
+
+all_same([H|T]) ->
+ all_same_1(T, H).
+
+all_same_1([H|T], H) ->
+ all_same_1(T, H);
+all_same_1([], _) -> ok.
+
+rnd_list(Sz) ->
+ rnd_list_1(Sz, []).
+
+atomic_rnd_term() ->
+ case random:uniform(3) of
+ 1 -> list_to_atom(integer_to_list($\s+random:uniform(94))++"rnd");
+ 2 -> random:uniform();
+ 3 -> random:uniform(50)-37
+ end.
+
+rnd_list_1(0, Acc) -> Acc;
+rnd_list_1(N, Acc) -> rnd_list_1(N-1, [atomic_rnd_term()|Acc]).
+
+mutate_some(List) ->
+ mutate_some(List, []).
+
+mutate_some([X,Y,Z|T], Acc) ->
+ %% Intentionally change order. (Order should not matter.)
+ mutate_some(T, [{X},Z,Y|Acc]);
+mutate_some([H|T], Acc) ->
+ mutate_some(T, [H|Acc]);
+mutate_some([], Acc) ->
+ %% Intentionally not reversing.
+ Acc.
+
+%% Removes at least one element.
+remove_some(List0, P) ->
+ case remove_some(List0, P, []) of
+ List when length(List0) =:= length(List) ->
+ tl(List);
+ List ->
+ List
+ end.
+
+remove_some([H|T], P, Acc) ->
+ case random:uniform() of
+ F when F < P -> %Remove.
+ remove_some(T, P, Acc);
+ _ ->
+ remove_some(T, P, [H|Acc])
+ end;
+remove_some([], _, Acc) ->
+ %% Intentionally no reverse. Order should not matter.
+ Acc.
diff --git a/lib/stdlib/test/sets_test_lib.erl b/lib/stdlib/test/sets_test_lib.erl
new file mode 100644
index 0000000000..6b6fb00550
--- /dev/null
+++ b/lib/stdlib/test/sets_test_lib.erl
@@ -0,0 +1,124 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(sets_test_lib, [Mod,Equal]).
+
+-export([module/0,equal/2,empty/0,from_list/1,to_list/1,singleton/1,
+ add_element/2,del_element/2,size/1,is_empty/1,is_set/1,
+ intersection/1,intersection/2,subtract/2,
+ union/1,union/2,is_subset/2,fold/3,filter/2]).
+
+module() ->
+ Mod.
+
+equal(X, Y) ->
+ Equal(X, Y).
+
+empty() ->
+ Mod:new().
+
+from_list(L) ->
+ Mod:from_list(L).
+
+to_list(S) ->
+ Mod:to_list(S).
+
+singleton(E) ->
+ case erlang:function_exported(Mod, singleton, 1) of
+ true -> Mod:singleton(E);
+ false -> from_list([E])
+ end.
+
+add_element(El, S0) ->
+ S = Mod:add_element(El, S0),
+ true = Mod:is_element(El, S),
+ false = is_empty(S),
+ true = Mod:is_set(S),
+ S.
+
+del_element(El, S0) ->
+ S = Mod:del_element(El, S0),
+ false = Mod:is_element(El, S),
+ true = Mod:is_set(S),
+ S.
+
+size(S) ->
+ Mod:size(S).
+
+is_empty(S) ->
+ true = Mod:is_set(S),
+ case erlang:function_exported(Mod, is_empty, 1) of
+ true -> Mod:is_empty(S);
+ false -> Mod:size(S) == 0
+ end.
+
+is_set(S) ->
+ Mod:is_set(S).
+
+intersection(S1, S2) ->
+ S = Mod:intersection(S1, S2),
+ true = Equal(S, Mod:intersection(S2, S1)),
+ Disjoint = is_empty(S),
+ Disjoint = Mod:is_disjoint(S1, S2),
+ Disjoint = Mod:is_disjoint(S2, S1),
+ S.
+
+intersection(Ss) ->
+ S = Mod:intersection(Ss),
+ true = Equal(S, Mod:intersection(lists:reverse(Ss))),
+ S.
+
+subtract(S1, S2) ->
+ S = Mod:subtract(S1, S2),
+ true = Mod:is_set(S),
+ true = Mod:size(S) =< Mod:size(S1),
+ S.
+
+union(S1, S2) ->
+ S = Mod:union(S1, S2),
+ true = Equal(S, Mod:union(S2, S1)),
+ true = Mod:is_set(S),
+ S.
+
+union(Ss) ->
+ S = Mod:union(Ss),
+ true = Equal(S, Mod:union(lists:reverse(Ss))),
+ S.
+
+is_subset(S, Set) ->
+ case Mod:is_subset(S, Set) of
+ false -> false;
+ true ->
+ case Mod:is_subset(Set, S) of
+ false -> ok;
+ true ->
+ %% The sets are subsets of each other.
+ %% They must be equal.
+ true = Equal(S, Set)
+ end,
+ true
+ end.
+
+fold(F, A, S) ->
+ true = Mod:is_set(S),
+ Mod:fold(F, A, S).
+
+filter(F, S) ->
+ true = Mod:is_set(S),
+ Mod:filter(F, S).
diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl
new file mode 100644
index 0000000000..5827d5f332
--- /dev/null
+++ b/lib/stdlib/test/shell_SUITE.erl
@@ -0,0 +1,2822 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(shell_SUITE).
+-export([all/1]).
+
+-export([forget/1, records/1, known_bugs/1, otp_5226/1, otp_5327/1,
+ otp_5435/1, otp_5195/1, otp_5915/1, otp_5916/1,
+ bits/1, bs_match_misc_SUITE/1, bs_match_int_SUITE/1,
+ bs_match_tail_SUITE/1, bs_match_bin_SUITE/1,
+ bs_construct_SUITE/1,
+ refman/1, refman_bit_syntax/1,
+ progex/1, progex_bit_syntax/1, progex_records/1,
+ progex_lc/1, progex_funs/1,
+ tickets/1, otp_5990/1, otp_6166/1, otp_6554/1, otp_6785/1,
+ otp_7184/1, otp_7232/1]).
+
+-export([restricted/1, start_restricted_from_shell/1,
+ start_restricted_on_command_line/1,restricted_local/1]).
+
+%% Internal export.
+-export([otp_5435_2/0]).
+
+%%
+%% Define to run outside of test server
+%%
+%% -define(STANDALONE,1).
+
+-ifdef(STANDALONE).
+-define(config(A,B),config(A,B)).
+-define(t,test_server).
+-export([config/2]).
+-define(line, noop, ).
+config(priv_dir,_) ->
+ ".".
+-else.
+-include("test_server.hrl").
+-export([init_per_testcase/2, fin_per_testcase/2]).
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(2)).
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ ?line OrigPath = code:get_path(),
+ ?line code:add_patha(?config(priv_dir,Config)),
+ [{orig_path,OrigPath}, {watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, Config) ->
+ ?line Dog = ?config(watchdog, Config),
+ ?line test_server:timetrap_cancel(Dog),
+ ?line OrigPath = ?config(orig_path,Config),
+ ?line code:set_path(OrigPath),
+ ?line application:unset_env(stdlib, restricted_shell),
+ ?line (catch code:purge(user_default)),
+ ?line (catch code:delete(user_default)),
+ ok.
+-endif.
+
+all(doc) ->
+ ["Test cases for the 'shell' module."];
+all(suite) ->
+ [forget, records, known_bugs, otp_5226, otp_5327, otp_5435, otp_5195,
+ otp_5915, otp_5916, bits, refman, progex, tickets, restricted].
+
+-record(state, {bin, reply, leader}).
+
+restricted(doc) ->
+ ["Test restricted_shell"];
+restricted(suite) ->
+ [start_restricted_from_shell,start_restricted_on_command_line,restricted_local].
+
+start_restricted_from_shell(doc) ->
+ ["Test that a restricted shell can be started from the normal shell"];
+start_restricted_from_shell(suite) ->
+ [];
+start_restricted_from_shell(Config) when is_list(Config) ->
+ ?line [{error,nofile}] = scan(<<"begin shell:start_restricted("
+ "nonexisting_module) end.">>),
+ ?line Test = filename:join(?config(priv_dir, Config),
+ "test_restricted.erl"),
+ Contents = <<"-module(test_restricted).
+ -export([local_allowed/3, non_local_allowed/3]).
+ local_allowed(i,[],State) ->
+ {true,State};
+ local_allowed(ugly,[],_State) ->
+ non_conforming_reply;
+ local_allowed(_,_,State) ->
+ {false,State}.
+
+ non_local_allowed({shell,stop_restricted},[],State) ->
+ {true,State};
+ non_local_allowed({erlang,'+'},[_],State) ->
+ {true,State};
+ non_local_allowed({erlang,'-'},[_,_],_State) ->
+ non_conforming_reply;
+ non_local_allowed({h, d}, [Arg], S) ->
+ {{redirect, {erlang,hd}, [Arg]}, S};
+ non_local_allowed(_,_,State) ->
+ {false,State}.
+ ">>,
+ ?line ok = compile_file(Config, Test, Contents, []),
+ ?line "exception exit: restricted shell starts now" =
+ comm_err(<<"begin shell:start_restricted("
+ "test_restricted) end.">>),
+ ?line {ok, test_restricted} =
+ application:get_env(stdlib, restricted_shell),
+ ?line "Pid" ++ _ = t(<<"begin i() end.">>),
+ ?line "exception exit: restricted shell does not allow c(foo)" =
+ comm_err(<<"begin c(foo) end.">>),
+ ?line "exception exit: restricted shell does not allow init:stop()" =
+ comm_err(<<"begin init:stop() end.">>),
+ ?line "exception exit: restricted shell does not allow init:stop()" =
+ comm_err(<<"begin F = fun() -> init:stop() end, F() end.">>),
+ ?line "exception error: bad argument in an arithmetic expression" =
+ comm_err(<<"begin +a end.">>),
+ ?line "exception exit: restricted shell does not allow a + b" =
+ comm_err(<<"begin a+b end.">>),
+ ?line "exception exit: restricted shell does not allow - b" =
+ comm_err(<<"begin -b end.">>),
+ ?line "exception exit: restricted shell does not allow 1 + 2" =
+ comm_err(<<"begin if atom(1 + 2> 0) -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow 1 + 2" =
+ comm_err(<<"begin if is_atom(1 + 2> 0) -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow - 2" =
+ comm_err(<<"begin if - 2 -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow - 2" =
+ comm_err(<<"begin if (- 2 > 0) andalso true -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow - 2" =
+ comm_err(<<"begin if (- 2 > 0) orelse true -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow 1 + 2" =
+ comm_err(<<"begin if 1 + 2 > 0 -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow 1 + 2" =
+ comm_err(<<"begin if erlang:is_atom(1 + 2> 0) -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow is_integer(1)" =
+ comm_err(<<"begin if is_integer(1) -> 1; true -> 2 end end.">>),
+ ?line "exception exit: restricted shell does not allow is_integer(1)" =
+ comm_err(<<"begin if integer(1) -> 1; true -> 2 end end.">>),
+ ?line "exception exit: "
+ "restricted shell module returned bad value non_conforming_reply" =
+ comm_err(<<"ugly().">>),
+ ?line [one] = scan(<<"h:d([one,two]).">>),
+ ?line "exception exit: "
+ "restricted shell module returned bad value non_conforming_reply" =
+ comm_err(<<"1 - 2.">>),
+ ?line "exception exit: restricted shell stopped"=
+ comm_err(<<"begin shell:stop_restricted() end.">>),
+ ?line undefined =
+ application:get_env(stdlib, restricted_shell),
+ ok.
+
+start_restricted_on_command_line(doc) ->
+ ["Check restricted shell when started from the command line"];
+start_restricted_on_command_line(suite) ->
+ [];
+start_restricted_on_command_line(Config) when is_list(Config) ->
+ ?line {ok,Node} = start_node(shell_suite_helper_1,
+ "-pa "++?config(priv_dir,Config)++
+ " -stdlib restricted_shell foo"),
+ ?line "Warning! Restricted shell module foo not found: nofile"++_ =
+ t({Node, <<"begin i() end.">>}),
+ ?line "exception exit: restricted shell does not allow i()" =
+ comm_err({Node, <<"begin i() end.">>}),
+ ?line [ok] =
+ (catch scan({Node, <<"begin q() end.">>})),
+ ?line test_server:stop_node(Node),
+ ?line Test = filename:join(?config(priv_dir, Config),
+ "test_restricted2.erl"),
+ Contents = <<"-module(test_restricted2).
+ -export([local_allowed/3, non_local_allowed/3]).
+ local_allowed(i,[],State) ->
+ {true,State};
+ local_allowed(_,_,State) ->
+ {false,State}.
+
+ non_local_allowed({shell,stop_restricted},[],State) ->
+ {true,State};
+ non_local_allowed({erlang,node},[],State) ->
+ {true,State};
+ non_local_allowed(_,_,State) ->
+ {false,State}.
+ ">>,
+ ?line ok = compile_file(Config, Test, Contents, []),
+ ?line {ok,Node2} = start_node(shell_suite_helper_2,
+ "-pa "++?config(priv_dir,Config)++
+ " -stdlib restricted_shell test_restricted2"),
+ ?line "Pid" ++ _ = t({Node2,<<"begin i() end.">>}),
+ ?line "exception exit: restricted shell does not allow c(foo)" =
+ comm_err({Node2,<<"begin c(foo) end.">>}),
+ ?line "exception exit: restricted shell does not allow init:stop()" =
+ comm_err({Node2,<<"begin init:stop() end.">>}),
+ ?line "exception exit: restricted shell does not allow init:stop()" =
+ comm_err({Node2,<<"begin F = fun() -> init:stop() end, F() end.">>}),
+ ?line [Node2] =
+ scan({Node2, <<"begin erlang:node() end.">>}),
+ ?line [Node2] =
+ scan({Node2, <<"begin node() end.">>}),
+ ?line "exception exit: restricted shell stopped"=
+ comm_err({Node2,<<"begin shell:stop_restricted() end.">>}),
+ ?line [ok] =
+ scan({Node2, <<"begin q() end.">>}),
+ ?line test_server:stop_node(Node2),
+ ok.
+
+restricted_local(suite) ->
+ [];
+restricted_local(doc) ->
+ ["Tests calling local shell functions with spectacular arguments in restricted shell"];
+restricted_local(Config) when is_list(Config) ->
+ ?line [{error,nofile}] = scan(<<"begin shell:start_restricted("
+ "nonexisting_module) end.">>),
+ ?line Test = filename:join(?config(priv_dir, Config),
+ "test_restricted_local.erl"),
+ Contents = <<"-module(test_restricted_local).
+ -export([local_allowed/3, non_local_allowed/3]).
+ local_allowed(i,[],State) ->
+ {true,State};
+ local_allowed(banan,_,State) ->
+ {true,State};
+ local_allowed(funkis,_,State) ->
+ {true,State};
+ local_allowed(c,_,State) ->
+ {true,State};
+ local_allowed(_,_,State) ->
+ {false,State}.
+
+ non_local_allowed({shell,stop_restricted},[],State) ->
+ {true,State};
+ non_local_allowed(_,_,State) ->
+ {false,State}.
+ ">>,
+ ?line ok = compile_file(Config, Test, Contents, []),
+ ?line Test2 = filename:join(?config(priv_dir, Config),
+ "user_default.erl"),
+ Contents2 = <<"-module(user_default).
+ -export([funkis/1,apple/1]).
+ funkis(F) when is_function(F) ->
+ funkis;
+ funkis(_) ->
+ nofunkis.
+ apple(_) ->
+ apple.
+ ">>,
+ ?line ok = compile_file(Config, Test2, Contents2, []),
+ ?line "exception exit: restricted shell starts now" =
+ comm_err(<<"begin shell:start_restricted("
+ "test_restricted_local) end.">>),
+ ?line {ok, test_restricted_local} =
+ application:get_env(stdlib, restricted_shell),
+ ?line "exception exit: restricted shell does not allow foo(" ++ _ =
+ comm_err(<<"begin F=fun() -> hello end, foo(F) end.">>),
+ ?line "exception error: undefined shell command banan/1" =
+ comm_err(<<"begin F=fun() -> hello end, banan(F) end.">>),
+ ?line "{error,"++_ = t(<<"begin F=fun() -> hello end, c(F) end.">>),
+ ?line "exception exit: restricted shell does not allow l(" ++ _ =
+ comm_err(<<"begin F=fun() -> hello end, l(F) end.">>),
+ ?line "exception error: variable 'F' is unbound" =
+ comm_err(<<"begin F=fun() -> hello end, f(F), F end.">>),
+ ?line [funkis] =
+ scan(<<"begin F=fun() -> hello end, funkis(F) end.">>),
+ ?line "exception exit: restricted shell does not allow apple(" ++ _ =
+ comm_err(<<"begin F=fun() -> hello end, apple(F) end.">>),
+ ?line "exception exit: restricted shell stopped"=
+ comm_err(<<"begin shell:stop_restricted() end.">>),
+ ?line undefined =
+ application:get_env(stdlib, restricted_shell),
+ ?line (catch code:purge(user_default)),
+ ?line true = (catch code:delete(user_default)),
+ ok.
+
+
+forget(doc) ->
+ ["f/0 and f/1"];
+forget(suite) ->
+ [];
+forget(Config) when is_list(Config) ->
+ %% f/0
+ ?line [ok] = scan(<<"begin f() end.">>),
+ ?line "1: variable 'A' is unbound" =
+ comm_err(<<"A = 3, f(), A.">>),
+ ?line [ok] = scan(<<"A = 3, A = f(), A.">>),
+
+ %% f/1
+ ?line [ok] = scan(<<"begin f(A) end.">>),
+ ?line "1: variable 'A' is unbound" =
+ comm_err(<<"A = 3, f(A), A.">>),
+ ?line [ok] = scan(<<"A = 3, A = f(A), A.">>),
+ ?line "exception error: no function clause matching call to f/1" =
+ comm_err(<<"f(a).">>),
+ ok.
+
+records(doc) ->
+ ["Test of the record support. OTP-5063."];
+records(suite) ->
+ [];
+records(Config) when is_list(Config) ->
+ %% rd/2
+ ?line [{attribute,_,record,{bar,_}},ok] =
+ scan(<<"rd(foo,{bar}),
+ rd(bar,{foo = (#foo{})#foo.bar}),
+ rl(bar).">>),
+ ?line "variable 'R' is unbound" = % used to work (before OTP-5878, R11B)
+ exit_string(<<"rd(foo,{bar}),
+ R = #foo{},
+ rd(bar,{foo = R#foo.bar}).">>),
+ ?line "exception error: no function clause matching call to rd/2" =
+ comm_err(<<"rd({foo},{bar}).">>),
+ ?line "bad record declaration" = exit_string(<<"A = bar, rd(foo,A).">>),
+ ?line [foo] = scan(<<"begin rd(foo,{bar}) end.">>),
+ ?line "1: record foo undefined" =
+ comm_err(<<"begin rd(foo,{bar}), #foo{} end.">>),
+ ?line ['f o o'] = scan(<<"rd('f o o', {bar}).">>),
+ ?line [foo] = scan(<<"rd(foo,{bar}), rd(foo,{foo = #foo{}}).">>),
+
+ %% rf/0,1
+ ?line [_, {attribute,_,record,{foo,_}},ok] =
+ scan(<<"rf('_'). rd(foo,{bar}),rl().">>),
+ ?line "1: record foo undefined" =
+ comm_err(<<"rd(foo,{bar}), #foo{}, rf(foo), #foo{}.">>),
+ ?line [ok,{foo,undefined}] =
+ scan(<<"rd(foo,{bar}), A = #foo{}, rf(foo). A.">>),
+ ?line [_] = scan(<<"begin rf() end.">>),
+ ?line [ok] = scan(<<"begin rf(foo) end.">>),
+
+ %% rp/1
+ ?line "#foo{bar = undefined}.\nok.\n" =
+ t(<<"rd(foo,{bar}), rp(#foo{}).">>),
+ ?line [{foo,3,4,3},ok] = scan(<<"rd(foo,{a = 3, b}), rp({foo,3,4,3}).">>),
+ ?line "#foo{a = 12}.\nok.\n" = t(<<"rd(foo,{a = 3}), rp({foo,12}).">>),
+ ?line [{[{foo}],12},ok] = scan(<<"rd(foo,{a = 3}), rp({[{foo}],12}).">>),
+
+ %% rr/1,2,3
+ MS = ?MODULE_STRING,
+ RR1 = "rr(" ++ MS ++ "). #state{}.",
+ ?line "[state]\n"
+ "#state{bin = undefined,reply = undefined,leader = undefined}.\n" =
+ t(RR1),
+ RR2 = "rr(" ++ MS ++ ",[state]). #state{}.",
+ ?line "[state]\n"
+ "#state{bin = undefined,reply = undefined,leader = undefined}.\n" =
+ t(RR2),
+ RR3 = "rr(" ++ MS ++ ",'_'). #state{}.",
+ ?line "[state]\n"
+ "#state{bin = undefined,reply = undefined,leader = undefined}.\n" =
+ t(RR3),
+ RR4 = "rr(" ++ MS ++ ", '_', {d,test1}).",
+ ?line [[state]] = scan(RR4),
+
+ Test = filename:join(?config(priv_dir, Config), "test.erl"),
+ Contents = <<"-module(test).
+ -record(state, {bin, reply, leader}).
+
+ -ifdef(test1).
+ -record(test1, {f}).
+ -endif.
+
+ -ifdef(test2).
+ -record(test2, {g}).
+ -endif.">>,
+ ?line ok = file:write_file(Test, Contents),
+
+ RR5 = "rr(\"" ++ Test ++ "\", '_', {d,test1}), rl([test1,test2]).",
+ ?line [{attribute,1,record,{test1,_}},ok] = scan(RR5),
+ RR6 = "rr(\"" ++ Test ++ "\", '_', {d,test2}), rl([test1,test2]).",
+ ?line [{attribute,1,record,{test2,_}},ok] = scan(RR6),
+ RR7 = "rr(\"" ++ Test ++
+ "\", '_', [{d,test1},{d,test2,17}]), rl([test1,test2]).",
+ ?line [{attribute,1,record,{test1,_}},{attribute,1,record,{test2,_}},
+ ok] = scan(RR7),
+ ?line PreReply = scan(<<"rr(prim_file).">>), % preloaded...
+ ?line true = is_list(PreReply),
+ ?line Dir = filename:join(?config(priv_dir, Config), "*.erl"),
+ ?line RR8 = "rp(rr(\"" ++ Dir ++ "\")).",
+ ?line [_,ok] = scan(RR8),
+ file:delete(Test),
+
+ RR1000 = "begin rr(" ++ MS ++ ") end.",
+ ?line [_] = scan(RR1000),
+ RR1001 = "begin rr(" ++ MS ++ ", state) end.",
+ ?line [_] = scan(RR1001),
+ RR1002 = "begin rr(" ++ MS ++ ", state,{i,'.'}) end.",
+ ?line [_] = scan(RR1002),
+
+ ?line [{error,nofile}] = scan(<<"rr(not_a_module).">>),
+ ?line [{error,invalid_filename}] = scan(<<"rr({foo}).">>),
+ ?line [[]] = scan(<<"rr(\"not_a_file\").">>),
+
+ %% using records
+ ?line [2] = scan(<<"rd(foo,{bar}), record_info(size, foo).">>),
+ ?line [true] = scan(<<"rd(foo,{bar}), is_record(#foo{}, foo).">>),
+ ?line [true] = scan(<<"rd(foo,{bar}), erlang:is_record(#foo{}, foo).">>),
+ ?line [true] = scan(<<"rd(foo,{bar}),
+ fun() when record(#foo{},foo) -> true end().">>),
+ ?line [2] = scan(<<"rd(foo,{bar}), #foo.bar.">>),
+ ?line "#foo{bar = 17}.\n" =
+ t(<<"rd(foo,{bar}), A = #foo{}, A#foo{bar = 17}.">>),
+
+ %% test of is_record/2 in lc
+ ?line "[#foo{bar = 3}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "is_record(X, foo)].">>),
+ ?line "[x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "not is_record(X, foo)].">>),
+ ?line "[#foo{bar = 3}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "begin is_record(X, foo) end].">>),
+ ?line "[x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "begin not is_record(X, foo) end].">>),
+
+ ?line "[#foo{bar = 3},x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "is_record(X, foo) or not is_binary(X)].">>),
+ ?line "[#foo{bar = 3},x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "not is_record(X, foo) or not is_binary(X)].">>),
+ ?line "[#foo{bar = 3}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "is_record(X, foo) or is_reference(X)].">>),
+ ?line "[x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "not is_record(X, foo) or is_reference(X)].">>),
+
+ ?line "[#foo{bar = 3},x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "begin is_record(X, foo) or not is_binary(X) end].">>),
+ ?line "[#foo{bar = 3},x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "begin not is_record(X, foo) or not is_binary(X) end].">>),
+ ?line "[#foo{bar = 3}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "begin is_record(X, foo) or is_reference(X) end].">>),
+ ?line "[x,[],{a,b}].\n" =
+ t(<<"rd(foo,{bar}), [X || X <- [#foo{bar=3},x,[],{a,b}],"
+ "begin not is_record(X, foo) or is_reference(X) end].">>),
+
+ ?line [ok] =
+ scan(<<"rd(a,{}), is_record({a},a) andalso true, b().">>),
+
+ %% nested record defs
+ ?line "#b{a = #a{}}.\n" = t(<<"rd(a,{}), rd(b, {a = #a{}}), #b{}.">>),
+
+ ?line [ok,ok,ok] = scan(<<"rf('_'), rp(rp(rl(rf(rf(rf(rl())))))).">>),
+
+ ok.
+
+known_bugs(doc) ->
+ ["Known bugs."];
+known_bugs(suite) ->
+ [];
+known_bugs(Config) when is_list(Config) ->
+ %% erl_eval:merge_bindings/2 cannot handle _removal_ of bindings.
+ ?line [3] = scan(<<"A = 3, length(begin f(A), [3] end), A.">>),
+ ok.
+
+otp_5226(doc) ->
+ ["OTP-5226. Wildcards accepted when reading BEAM files using rr/1,2,3."];
+otp_5226(suite) ->
+ [];
+otp_5226(Config) when is_list(Config) ->
+ Test1 = <<"-module(test1).
+ -record('_test1', {a,b}).">>,
+ Test2 = <<"-module(test2).
+ -record('_test2', {c,d}).">>,
+ ?line File1 = filename("test1.erl", Config),
+ ?line File2 = filename("test2.erl", Config),
+ ?line Beam = filename("*.beam", Config),
+ ?line ok = compile_file(Config, File1, Test1, [no_debug_info]),
+ ?line ok = compile_file(Config, File2, Test2, [no_debug_info]),
+ RR = "rr(\"" ++ Beam ++ "\").",
+ ?line [Recs] = scan(RR),
+ ?line true = lists:member('_test1', Recs),
+ ?line true = lists:member('_test2', Recs),
+ file:delete(filename("test1.beam", Config)),
+ file:delete(filename("test2.beam", Config)),
+ file:delete(File1),
+ file:delete(File2),
+ ok.
+
+otp_5327(doc) ->
+ ["OTP-5226. Test of eval_bits, mostly."];
+otp_5327(suite) ->
+ [];
+otp_5327(Config) when is_list(Config) ->
+ ?line "exception error: bad argument" =
+ comm_err(<<"<<\"hej\":default>>.">>),
+ ?line <<"abc">> =
+ erl_parse:normalise({bin,1,[{bin_element,1,{string,1,"abc"},
+ default,default}]}),
+ ?line [<<"abc">>] = scan(<<"<<(<<\"abc\">>):3/binary>>.">>),
+ ?line [<<"abc">>] = scan(<<"<<(<<\"abc\">>)/binary>>.">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"<<(<<\"abc\">>):4/binary>>.">>),
+ ?line true = byte_size(hd(scan("<<3.14:64/float>>."))) =:= 8,
+ ?line true = byte_size(hd(scan("<<3.14:32/float>>."))) =:= 4,
+ ?line "exception error: bad argument" =
+ comm_err(<<"<<3.14:128/float>>.">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"<<10:default>>.">>),
+ ?line [<<98,1:1>>] = scan(<<"<<3:3,5:6>>.">>),
+ ?line {'EXIT',{badarg,_}} =
+ (catch erl_parse:normalise({bin,1,[{bin_element,1,{integer,1,17},
+ {atom,1,all},
+ default}]})),
+ ?line [<<-20/signed>>] = scan(<<"<<-20/signed>> = <<-20>>.">>),
+ ?line [<<-300:16/signed>>] =
+ scan(<<"<<-300:16/signed>> = <<-300:16>>.">>),
+ ?line [<<-1000:24/signed>>] =
+ scan(<<"<<-1000:24/signed>> = <<-1000:24>>.">>),
+ ?line [<<-(1 bsl 29):32/signed>>] =
+ scan(<<"<<-(1 bsl 29):32/signed>> = <<-(1 bsl 29):32>>.">>),
+
+ ?line "exception error: no match of right hand side value <<0,0,0>>" =
+ comm_err(<<"<<B:3/unit:7-binary,_/binary>> = <<0:24>>.">>),
+ ?line true = [<<103133:64/float>>] =:=
+ scan(<<"<<103133:64/float>> = <<103133:64/float>>.">>),
+ ?line true = [<<103133.0:64/float>>] =:=
+ scan(<<"<<103133.0:64/float>> = <<103133:64/float>>.">>),
+ ?line true = [<<103133:64/float>>] =:= scan(<<"<<103133:64/float>>.">>),
+ Int = 17,
+ ?line true = [<<Int:64/float>>] =:= scan(<<"Int = 17, <<Int:64/float>>.">>),
+ ?line "exception error: no match of right hand side value" ++ _ =
+ comm_err(<<"<<103133:64/binary>> = <<103133:64/float>>.">>),
+ ?line "exception error: interpreted function with arity 1 called with two arguments" =
+ comm_err(<<"(fun(X) -> X end)(a,b).">>),
+ ?line {'EXIT', {{illegal_pattern,_}, _}} =
+ (catch evaluate("<<A:a>> = <<17:32>>.", [])),
+ C = <<"
+ <<A:4,B:4,C:4,D:4,E:4,F:4>> = <<\"hej\">>,
+ case <<7:4,A:4,B:4,C:4,D:4,E:4,F:4,3:4>> of
+ <<_:4,\"hej\",3:4>> -> 1;
+ _ -> 2
+ end.
+ ">>,
+ ?line 1 = evaluate(C, []),
+ %% unbound_var would be nicer...
+ ?line {'EXIT',{{illegal_pattern,_},_}} =
+ (catch evaluate(<<"<<A:B>> = <<17:32>>.">>, [])),
+ %% undefined_bittype is turned into badmatch:
+ ?line {'EXIT',{{badmatch,<<17:32>>},_}} =
+ (catch evaluate(<<"<<A/apa>> = <<17:32>>.">>, [])),
+ ?line {'EXIT',_} =
+ (catch evaluate(<<"<<17/binary-unit:8-unit:16>>.">>, [])),
+ ?line {'EXIT',_} =
+ (catch evaluate(<<"<<17:32/unsigned-signed>> = <<17:32>>.">>, [])),
+ ?line {'EXIT',_} =
+ (catch evaluate(<<"<<17:32/unsigned-signed>>.">>, [])),
+ ?line <<17:32>> = evaluate(<<"<<17:32/signed-signed>>.">>, []),
+ ?line {'EXIT',_} =
+ (catch evaluate(<<"<<32/unit:8>>.">>, [])),
+ ok.
+
+otp_5435(doc) ->
+ ["OTP-5435. sys_pre_expand not in the path."];
+otp_5435(suite) ->
+ [];
+otp_5435(Config) when is_list(Config) ->
+ ?line true = <<103133:64/float>> =:=
+ evaluate(<<"<<103133:64/float>> = <<103133:64/float>>.">>, []),
+ ?line true = <<103133.0:64/float>> =:=
+ evaluate(<<"<<103133.0:64/float>> = <<103133:64/float>>.">>, []),
+ ?line true = is_alive(),
+ ?line {ok, Node} = start_node(shell_SUITE_otp_5435),
+ ?line ok = rpc:call(Node, ?MODULE, otp_5435_2, []),
+ ?line ?t:stop_node(Node),
+ ok.
+
+start_node(Name) ->
+ ?line PA = filename:dirname(code:which(?MODULE)),
+ ?t:start_node(Name, slave, [{args, "-pa " ++ PA}]).
+
+otp_5435_2() ->
+ ?line true = code:del_path(compiler),
+ %% sys_pre_expand can no longer be found
+ %% OTP-5876. But erl_expand_records can!
+ ?line [{attribute,_,record,{bar,_}},ok] =
+ scan(<<"rd(foo,{bar}),
+ rd(bar,{foo = (#foo{})#foo.bar}),
+ rl(bar).">>),
+ ok.
+
+otp_5195(doc) ->
+ ["OTP-5195. QLC, mostly."];
+otp_5195(suite) ->
+ [];
+otp_5195(Config) when is_list(Config) ->
+ %% QLC. It was easier to put these cases here than in qlc_SUITE.
+ ?line "[#a{b = undefined}].\n" =
+ t(<<"rd(a,{b}), qlc:e(qlc:q([X || X <- [#a{}],is_record(X, a)])).">>),
+
+ %% An experimental shell used to translate error tuples:
+ %% "(qlc) \"1: generated variable 'X' must not be used in "
+ %% "list expression\".\n" =
+ %% t(<<"qlc:q([X || X <- [{a}], Y <- [X]]).">>),
+ %% Same as last one (if the shell does not translate error tuples):
+ ?line [{error,qlc,{1,qlc,{used_generator_variable,'X'}}}] =
+ scan(<<"qlc:q([X || X <- [{a}], Y <- [X]]).">>),
+ ?line {error,qlc,{1,qlc,{used_generator_variable,'X'}}} =
+ evaluate(<<"qlc:q([X || X <- [{a}], Y <- [X]]).">>, []),
+ Ugly = <<"qlc:e(qlc:q([X || X <- qlc:append([[1,2,3],ugly()])])).">>,
+ ?line "undefined shell command ugly/0" = error_string(Ugly),
+ ?line {'EXIT',{undef,_}} = (catch evaluate(Ugly, [])),
+
+ V_1 = <<"qlc:e(qlc:q([X || X <- qlc:append([[1,2,3],v(-1)])])).">>,
+ ?line "- 1: command not found" = comm_err(V_1),
+ ?line {'EXIT', {undef,_}} = (catch evaluate(V_1, [])),
+
+ ?line "1\n2\n3\n3.\n" =
+ t(<<"1. 2. 3. 3 = fun(A) when A =:= 2 -> v(3) end(v(2)).">>),
+
+ ?line List4 = t(<<"[a,list]. A = [1,2]. "
+ "qlc:q([X || X <- qlc:append(A, v(1))]). "
+ "[1,2,a,list] = qlc:e(v(-1)).">>),
+ ?line "[1,2,a,list].\n" = string:substr(List4, string:len(List4)-13),
+
+ ok.
+
+otp_5915(doc) ->
+ ["OTP-5915. Strict record tests in guards."];
+otp_5915(suite) ->
+ [];
+otp_5915(Config) when is_list(Config) ->
+ C = <<"
+ rd(r, {a = 4,b}),
+ rd(r1, {a,b}),
+ rd(r2, {a = #r1{},b,c=length([1,2,3])}),
+ rd(r3, {a = fun(_) -> #r1{} end(1), b}),
+
+ foo = fun(A) when A#r1.a > A#r1.b -> foo end(#r1{b = 2}),
+ 0 = fun(A) when A#r2.a -> 0 end(#r2{a = true}),
+ 1 = fun(A) when (#r1{a = A})#r1.a > 2 -> 1 end(3),
+ 2 = fun(N) when ((#r2{a = #r{a = 4}, b = length([a,b,c])})#r2.a)#r.a > N ->
+ 2 end(2),
+ 3 = fun(A) when (A#r2.a)#r1.a =:= 3 -> 3 end(#r2{a = #r1{a = 3}}),
+ ok = fun() ->
+ F = fun(A) when record(A#r.a, r1) -> 4;
+ (A) when record(A#r1.a, r1) -> 5
+ end,
+ 5 = F(#r1{a = #r1{}}),
+ 4 = F(#r{a = #r1{}}),
+ ok
+ end(),
+ 3 = fun(A) when record(A#r1.a, r),
+ (A#r1.a)#r.a > 3 -> 3
+ end(#r1{a = #r{a = 4}}),
+ 7 = fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}),
+ [#r1{a = 2,b = 1}] =
+ fun() ->
+ [A || A <- [#r1{a = 1, b = 3},
+ #r2{a = 2,b = 1},
+ #r1{a = 2, b = 1}],
+ A#r1.a >
+ A#r1.b]
+ end(),
+ {[_],b} =
+ fun(L) ->
+ %% A is checked only once:
+ R1 = [{A,B} || A <- L, A#r1.a, B <- L, A#r1.b],
+ A = #r2{a = true},
+ %% A is checked again:
+ B = if A#r1.a -> a; true -> b end,
+ {R1,B}
+ end([#r1{a = true, b = true}]),
+
+ p = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ o = fun(A) when (A#r1.a =:= 2) orelse (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end(#r1{a = 2}),
+
+ 3 = fun(A) when A#r1.a > 3,
+ record(A, r1) -> 3
+ end(#r1{a = 5}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r2.a =:= 1) orelse (A#r2.a) -> 2;
+ (A) when (A#r1.a =:= 1) orelse (A#r1.a) -> 1;
+ (A) when (A#r2.a =:= 2) andalso (A#r2.b) -> 3
+ end,
+ 1 = F(#r1{a = 1}),
+ 2 = F(#r2{a = true}),
+ 3 = F(#r2{a = 2, b = true}),
+ ok
+ end(),
+
+ b = fun(A) when false or not (A#r.a =:= 1) -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+ b = fun(A) when not (A#r.a =:= 1) or false -> a;
+ (_) -> b
+ end(#r1{a = 1}),
+
+ ok = fun() ->
+ F = fun(A) when not (A#r.a =:= 1) -> yes;
+ (_) -> no
+ end,
+ no = F(#r1{a = 2}),
+ yes = F(#r{a = 2}),
+ no = F(#r{a = 1}),
+ ok
+ end(),
+
+ a = fun(A) when record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 ->a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when erlang:is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+ a = fun(A) when is_record(A, r),
+ A#r.a =:= 1,
+ A#r.b =:= 2 -> a
+ end(#r{a = 1, b = 2}),
+
+ nop = fun(A) when (is_record(A, r1) and (A#r1.a > 3)) or (A#r2.a < 1) ->
+ japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+ nop = fun(A) when (A#r1.a > 3) or (A#r2.a < 1) -> japp;
+ (_) ->
+ nop
+ end(#r2{a = 0}),
+
+ ok = fun() ->
+ F = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o;
+ (_) -> p
+ end,
+ p = F(#r2{a = 1}),
+ p = F(#r1{a = 2}),
+ ok
+ end(),
+
+ ok = fun() ->
+ F = fun(A) when fail, A#r1.a; A#r1.a -> ab;
+ (_) -> bu
+ end,
+ ab = F(#r1{a = true}),
+ bu = F(#r2{a = true}),
+ ok
+ end(),
+
+ both = fun(A) when A#r.a, A#r.b -> both
+ end(#r{a = true, b = true}),
+
+ ok = fun() ->
+ F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
+ or (B#r2.b) or (A#r1.b) -> true;
+ (_, _) -> false
+ end,
+ true = F(#r1{a = false, b = false}, #r2{a = false, b = true}),
+ false = F(#r1{a = true, b = true}, #r1{a = false, b = true}),
+ ok
+ end(),
+
+ ok.">>,
+ [ok] = scan(C),
+ ok.
+
+otp_5916(doc) ->
+ ["OTP-5916. erlang:is_record/3 allowed in guards."];
+otp_5916(suite) ->
+ [];
+otp_5916(Config) when is_list(Config) ->
+ C = <<"
+ rd(r1, {a,b}),
+ rd(r2, {a,b}),
+
+ true = if erlang:is_record(#r1{},r1,3) -> true; true -> false end,
+ false = if erlang:is_record(#r2{},r1,3) -> true; true -> false end,
+
+ true = if is_record(#r1{},r1,3) -> true; true -> false end,
+ false = if is_record(#r2{},r1,3) -> true; true -> false end,
+
+ true = if {erlang,is_record}(#r1{},r1,3) -> true; true -> false end,
+ false = if {erlang,is_record}(#r2{},r1,3) -> true; true -> false end,
+
+ ok.">>,
+ [ok] = scan(C),
+ ok.
+
+bits(suite) ->
+ [bs_match_misc_SUITE, % bs_match_int_SUITE/,
+ bs_match_tail_SUITE, bs_match_bin_SUITE, bs_construct_SUITE].
+
+bs_match_misc_SUITE(doc) ->
+ ["OTP-5327. Adopted from parts of emulator/test/bs_match_misc_SUITE.erl."];
+bs_match_misc_SUITE(suite) ->
+ [];
+bs_match_misc_SUITE(Config) when is_list(Config) ->
+ C = <<"
+ F1 = fun() -> 3.1415 end,
+
+ FOne = fun() -> 1.0 end,
+
+ Fcmp = fun(F1, F2) when (F1 - F2) / F2 < 0.0000001 -> ok end,
+
+ MakeSubBin = fun(Bin0) ->
+ Sz = size(Bin0),
+ Bin1 = <<37,Bin0/binary,38,39>>,
+ <<_:8,Bin:Sz/binary,_:8,_:8>> = Bin1,
+ Bin
+ end,
+
+ MatchFloat =
+ fun(Bin0, Fsz, I) ->
+ Bin = MakeSubBin(Bin0),
+ Bsz = size(Bin) * 8,
+ Tsz = Bsz - Fsz - I,
+ <<_:I,F:Fsz/float,_:Tsz>> = Bin,
+ F
+ end,
+
+ TFloat = fun() ->
+ F = F1(),
+ G = FOne(),
+
+ G = MatchFloat(<<63,128,0,0>>, 32, 0),
+ G = MatchFloat(<<63,240,0,0,0,0,0,0>>, 64, 0),
+
+ Fcmp(F, MatchFloat(<<F:32/float>>, 32, 0)),
+ Fcmp(F, MatchFloat(<<F:64/float>>, 64, 0)),
+ Fcmp(F, MatchFloat(<<1:1,F:32/float,127:7>>, 32, 1)),
+ Fcmp(F, MatchFloat(<<1:1,F:64/float,127:7>>, 64, 1)),
+ Fcmp(F, MatchFloat(<<1:13,F:32/float,127:3>>, 32, 13)),
+ Fcmp(F, MatchFloat(<<1:13,F:64/float,127:3>>, 64, 13))
+ end,
+ TFloat(),
+
+ F2 = fun() -> 2.7133 end,
+
+ MatchFloatLittle = fun(Bin0, Fsz, I) ->
+ Bin = MakeSubBin(Bin0),
+ Bsz = size(Bin) * 8,
+ Tsz = Bsz - Fsz - I,
+ <<_:I,F:Fsz/float-little,_:Tsz>> = Bin,
+ F
+ end,
+
+ LittleFloat = fun() ->
+ F = F2(),
+ G = FOne(),
+
+ G = MatchFloatLittle(<<0,0,0,0,0,0,240,63>>, 64, 0),
+ G = MatchFloatLittle(<<0,0,128,63>>, 32, 0),
+
+ Fcmp(F, MatchFloatLittle(<<F:32/float-little>>, 32, 0)),
+ Fcmp(F, MatchFloatLittle(<<F:64/float-little>>, 64, 0)),
+ Fcmp(F, MatchFloatLittle(<<1:1,F:32/float-little,127:7>>, 32, 1)),
+ Fcmp(F, MatchFloatLittle(<<1:1,F:64/float-little,127:7>>, 64, 1)),
+ Fcmp(F, MatchFloatLittle(<<1:13,F:32/float-little,127:3>>, 32, 13)),
+ Fcmp(F, MatchFloatLittle(<<1:13,F:64/float-little,127:3>>, 64, 13))
+ end,
+ LittleFloat(),
+
+ Sean1 = fun(<<B/binary>>) when size(B) < 4 -> small;
+ (<<1, _B/binary>>) -> large
+ end,
+
+ Sean = fun() ->
+ small = Sean1(<<>>),
+ small = Sean1(<<1>>),
+ small = Sean1(<<1,2>>),
+ small = Sean1(<<1,2,3>>),
+ large = Sean1(<<1,2,3,4>>),
+
+ small = Sean1(<<4>>),
+ small = Sean1(<<4,5>>),
+ small = Sean1(<<4,5,6>>),
+ {'EXIT',{function_clause,_}} = (catch Sean1(<<4,5,6,7>>))
+ end,
+ Sean(),
+
+ NativeBig = fun() ->
+ <<37.33:64/native-float>> = <<37.33:64/big-float>>,
+ <<3974:16/native-integer>> = <<3974:16/big-integer>>
+ end,
+
+ NativeLittle = fun() ->
+ <<37869.32343:64/native-float>> = <<37869.32343:64/little-float>>,
+ <<7974:16/native-integer>> = <<7974:16/little-integer>>
+ end,
+
+ Native = fun() ->
+ <<3.14:64/native-float>> = <<3.14:64/native-float>>,
+ <<333:16/native>> = <<333:16/native>>,
+ <<38658345:32/native>> = <<38658345:32/native>>,
+ case <<1:16/native>> of
+ <<0,1>> -> NativeBig();
+ <<1,0>> -> NativeLittle()
+ end
+ end,
+ Native(),
+
+ Split = fun(<<N:16,B:N/binary,T/binary>>) -> {B,T} end,
+
+ Split2 = fun(N, <<N:16,B:N/binary,T/binary>>) -> {B,T} end,
+
+ Split_2 = fun(<<N0:8,N:N0,B:N/binary,T/binary>>) -> {B,T} end,
+
+ Skip = fun(<<N:8,_:N/binary,T/binary>>) -> T end,
+
+ SizeVar = fun() ->
+ {<<45>>,<<>>} = Split(<<1:16,45>>),
+ {<<45>>,<<46,47>>} = Split(<<1:16,45,46,47>>),
+ {<<45,46>>,<<47>>} = Split(<<2:16,45,46,47>>),
+
+ {<<45,46,47>>,<<48>>} = Split_2(<<16:8,3:16,45,46,47,48>>),
+
+ {<<45,46>>,<<47>>} = Split2(2, <<2:16,45,46,47>>),
+ {'EXIT',{function_clause,_}} =
+ (catch Split2(42, <<2:16,45,46,47>>)),
+
+ <<\"cdef\">> = Skip(<<2:8,\"abcdef\">>)
+ end,
+ SizeVar(),
+
+ Wcheck = fun(<<A>>) when A==3-> ok1;
+ (<<_,_:2/binary>>) -> ok2;
+ (<<_>>) -> ok3;
+ (Other) -> {error,Other}
+ end,
+
+ Wiger = fun() ->
+ ok1 = Wcheck(<<3>>),
+ ok2 = Wcheck(<<1,2,3>>),
+ ok3 = Wcheck(<<4>>),
+ {error,<<1,2,3,4>>} = Wcheck(<<1,2,3,4>>),
+ {error,<<>>} = Wcheck(<<>>)
+ end,
+ Wiger(),
+
+ ok.
+ ">>,
+ [ok] = scan(C),
+ ok = evaluate(C, []).
+
+%% This one is not run during night builds since it takes several minutes.
+bs_match_int_SUITE(doc) ->
+ ["OTP-5327. Adopted from emulator/test/bs_match_int_SUITE.erl."];
+bs_match_int_SUITE(suite) ->
+ [];
+bs_match_int_SUITE(Config) when is_list(Config) ->
+ C = <<"
+ FunClause = fun({'EXIT',{function_clause,_}}) -> ok end,
+
+ Mkbin = fun(L) when list(L) -> list_to_binary(L) end,
+
+ GetInt1 = fun(<<I:0>>) -> I;
+ (<<I:8>>) -> I;
+ (<<I:16>>) -> I;
+ (<<I:24>>) -> I;
+ (<<I:32>>) -> I
+ end,
+
+ GetInt2 = fun(Bin0, I, F) when size(Bin0) < 4 ->
+ Bin = <<0,Bin0/binary>>,
+ I = GetInt1(Bin),
+ F(Bin, I, F);
+ (_, I, _F) -> I
+ end,
+
+ GetInt = fun(Bin) ->
+ I = GetInt1(Bin),
+ GetInt2(Bin, I, GetInt2)
+ end,
+
+
+ Cmp128 = fun(<<I:128>>, I) -> equal;
+ (_, _) -> not_equal
+ end,
+
+ Uint2 = fun([H|T], Acc, F) -> F(T, Acc bsl 8 bor H, F);
+ ([], Acc, _F) -> Acc
+ end,
+
+ Uint = fun(L) -> Uint2(L, 0, Uint2) end,
+
+ Integer = fun() ->
+ 0 = GetInt(Mkbin([])),
+ 0 = GetInt(Mkbin([0])),
+ 42 = GetInt(Mkbin([42])),
+ 255 = GetInt(Mkbin([255])),
+ 256 = GetInt(Mkbin([1,0])),
+ 257 = GetInt(Mkbin([1,1])),
+ 258 = GetInt(Mkbin([1,2])),
+ 258 = GetInt(Mkbin([1,2])),
+ 65534 = GetInt(Mkbin([255,254])),
+ 16776455 = GetInt(Mkbin([255,253,7])),
+ 4245492555 = GetInt(Mkbin([253,13,19,75])),
+ 4294967294 = GetInt(Mkbin([255,255,255,254])),
+ 4294967295 = GetInt(Mkbin([255,255,255,255])),
+ Eight = [200,1,19,128,222,42,97,111],
+ Cmp128(Eight, Uint(Eight)),
+ FunClause(catch GetInt(Mkbin(lists:seq(1,5))))
+ end,
+ Integer(),
+
+ Sint = fun(Bin) ->
+ case Bin of
+ <<I:8/signed>> -> I;
+ <<I:8/signed,_:3,_:5>> -> I;
+ Other -> {no_match,Other}
+ end
+ end,
+
+ SignedInteger = fun() ->
+ {no_match,_} = Sint(Mkbin([])),
+ {no_match,_} = Sint(Mkbin([1,2,3])),
+ 127 = Sint(Mkbin([127])),
+ -1 = Sint(Mkbin([255])),
+ -128 = Sint(Mkbin([128])),
+ 42 = Sint(Mkbin([42,255])),
+ 127 = Sint(Mkbin([127,255]))
+ end,
+ SignedInteger(),
+
+ Dynamic5 = fun(Bin, S1, S2, A, B) ->
+ case Bin of
+ <<A:S1,B:S2>> ->
+ % io:format(\"~p ~p ~p ~p~n\", [S1,S2,A,B]),
+ ok;
+ _Other -> erlang:error(badmatch, [Bin,S1,S2,A,B])
+ end
+ end,
+
+ Dynamic2 = fun(Bin, S1, F) when S1 >= 0 ->
+ S2 = size(Bin) * 8 - S1,
+ Dynamic5(Bin, S1, S2, (1 bsl S1) - 1, (1 bsl S2) - 1),
+ F(Bin, S1-1, F);
+ (_, _, _) -> ok
+ end,
+
+ Dynamic = fun(Bin, S1) ->
+ Dynamic2(Bin, S1, Dynamic2)
+ end,
+
+ Dynamic(Mkbin([255]), 8),
+ Dynamic(Mkbin([255,255]), 16),
+ Dynamic(Mkbin([255,255,255]), 24),
+ Dynamic(Mkbin([255,255,255,255]), 32),
+
+ BigToLittle4 =
+ fun([B0,B1,B2,B3,B4,B5,B6,B7|T], N, Acc, F) when N >= 8 ->
+ F(T, N-8, [B0,B1,B2,B3,B4,B5,B6,B7|Acc], F);
+ (List, N, Acc, _F) -> lists:sublist(List, 1, N) ++ Acc
+ end,
+
+ BigToLittle =
+ fun(List, N) -> BigToLittle4(List, N, [], BigToLittle4) end,
+
+ ReversedSublist =
+ fun(_List, 0, Acc, _F) -> Acc;
+ ([H|T], N, Acc, F) -> F(T, N-1, [H|Acc], F)
+ end,
+
+ TwoComplementAndReverse =
+ fun([H|T], Carry, Acc, F) ->
+ Sum = 1-H+Carry,
+ F(T, Sum div 2, [Sum rem 2|Acc], F);
+ ([], Carry, Acc, _F) -> [Carry|Acc]
+ end,
+
+ MakeInt = fun(_List, 0, Acc, _F) -> Acc;
+ ([H|T], N, Acc, F) -> F(T, N-1, Acc bsl 1 bor H, F)
+ end,
+
+ MakeSignedInt =
+ fun(_List, 0) -> 0;
+ ([0|_]=List, N) -> MakeInt(List, N, 0, MakeInt);
+ ([1|_]=List0, N) ->
+ List1 = ReversedSublist(List0, N, [], ReversedSublist),
+ List2 = TwoComplementAndReverse(List1, 1, [],
+ TwoComplementAndReverse),
+ -MakeInt(List2, length(List2), 0, MakeInt)
+ end,
+
+ BitsToList =
+ fun([H|T], 0, F) -> F(T, 16#80, F);
+ ([H|_]=List, Mask, F) ->
+ [case H band Mask of
+ 0 -> 0;
+ _ -> 1
+ end | F(List, Mask bsr 1, F)];
+ ([], _, _F) -> []
+ end,
+
+ MoreDynamic3 =
+ fun(Action, Bin, List, Bef, Aft, F) when Bef =< Aft ->
+ Action(Bin, List, Bef, Aft-Bef),
+ F(Action, Bin, List, Bef, Aft-1, F);
+ (_, _, _, _, _, _) -> ok
+ end,
+
+ MoreDynamic2 =
+ fun(Action, Bin, [_|T]=List, Bef, F) ->
+ MoreDynamic3(Action, Bin, List, Bef, size(Bin)*8,
+ MoreDynamic3),
+ F(Action, Bin, T, Bef+1, F);
+ (_, _, [], _, _F) -> ok
+ end,
+
+ MoreDynamic1 =
+ fun(Action, Bin) ->
+ BitList = BitsToList(binary_to_list(Bin),16#80,BitsToList),
+ MoreDynamic2(Action, Bin, BitList, 0, MoreDynamic2)
+ end,
+
+ MoreDynamic = fun() ->
+ % Unsigned big-endian numbers.
+ Unsigned = fun(Bin, List, SkipBef, N) ->
+ SkipAft = 8*size(Bin) - N - SkipBef,
+ <<_:SkipBef,Int:N,_:SkipAft>> = Bin,
+ Int = MakeInt(List, N, 0, MakeInt)
+ end,
+ MoreDynamic1(Unsigned, erlang:md5(Mkbin([42]))),
+
+ %% Signed big-endian numbers.
+ Signed = fun(Bin, List, SkipBef, N) ->
+ SkipAft = 8*size(Bin) - N - SkipBef,
+ <<_:SkipBef,Int:N/signed,_:SkipAft>> = Bin,
+ case MakeSignedInt(List, N) of
+ Int -> ok;
+ Other ->
+ io:format(\"Bin = ~p,\", [Bin]),
+ io:format(\"SkipBef = ~p, N = ~p\",
+ [SkipBef,N]),
+ io:format(\"Expected ~p, got ~p\",
+ [Int,Other])
+ end
+ end,
+ MoreDynamic1(Signed, erlang:md5(Mkbin([43]))),
+
+ %% Unsigned little-endian numbers.
+ UnsLittle = fun(Bin, List, SkipBef, N) ->
+ SkipAft = 8*size(Bin) - N - SkipBef,
+ <<_:SkipBef,Int:N/little,_:SkipAft>> = Bin,
+ Int = MakeInt(BigToLittle(List, N), N, 0,
+ MakeInt)
+ end,
+ MoreDynamic1(UnsLittle, erlang:md5(Mkbin([44]))),
+
+ %% Signed little-endian numbers.
+ SignLittle = fun(Bin, List, SkipBef, N) ->
+ SkipAft = 8*size(Bin) - N - SkipBef,
+ <<_:SkipBef,Int:N/signed-little,_:SkipAft>> = Bin,
+ Little = BigToLittle(List, N),
+ Int = MakeSignedInt(Little, N)
+ end,
+ MoreDynamic1(SignLittle, erlang:md5(Mkbin([45])))
+ end,
+ MoreDynamic(),
+
+ ok.
+ ">>,
+ [ok] = scan(C),
+ ok = evaluate(C, []).
+
+bs_match_tail_SUITE(doc) ->
+ ["OTP-5327. Adopted from emulator/test/bs_match_tail_SUITE.erl."];
+bs_match_tail_SUITE(suite) ->
+ [];
+bs_match_tail_SUITE(Config) when is_list(Config) ->
+ C = <<"
+ GetTailUsed = fun(<<A:1,T/binary>>) -> {A,T} end,
+
+ GetTailUnused = fun(<<A:15,_/binary>>) -> A end,
+
+ GetDynTailUsed = fun(Bin, Sz) ->
+ <<A:Sz,T/binary>> = Bin,
+ {A,T}
+ end,
+
+ GetDynTailUnused = fun(Bin, Sz) ->
+ <<A:Sz,_/binary>> = Bin,
+ A
+ end,
+
+ Mkbin = fun(L) when list(L) -> list_to_binary(L) end,
+
+ TestZeroTail = fun(<<A:8>>) -> A end,
+
+ TestZeroTail2 = fun(<<_A:4,_B:4>>) -> ok end,
+
+ ZeroTail = fun() ->
+ 7 = (catch TestZeroTail(Mkbin([7]))),
+ {'EXIT',{function_clause,_}} =
+ (catch TestZeroTail(Mkbin([1,2]))),
+ {'EXIT',{function_clause,_}} =
+ (catch TestZeroTail2(Mkbin([1,2,3])))
+ end,
+ ZeroTail(),
+
+ AlGetTailUsed = fun(<<A:16,T/binary>>) -> {A,T} end,
+
+ AlGetTailUnused = fun(<<A:16,_/binary>>) -> A end,
+
+ Aligned = fun() ->
+ Tail1 = Mkbin([]),
+ {258,Tail1} = AlGetTailUsed(Mkbin([1,2])),
+ Tail2 = Mkbin(lists:seq(1, 127)),
+ {35091,Tail2} = AlGetTailUsed(Mkbin([137,19|Tail2])),
+
+ 64896 = AlGetTailUnused(Mkbin([253,128])),
+ 64895 = AlGetTailUnused(Mkbin([253,127|lists:seq(42, 255)])),
+
+ Tail3 = Mkbin(lists:seq(0, 19)),
+ {0,Tail1} = GetDynTailUsed(Tail1, 0),
+ {0,Tail3} = GetDynTailUsed(Mkbin([Tail3]), 0),
+ {73,Tail3} = GetDynTailUsed(Mkbin([73|Tail3]), 8),
+
+ 0 = GetDynTailUnused(Mkbin([]), 0),
+ 233 = GetDynTailUnused(Mkbin([233]), 8),
+ 23 = GetDynTailUnused(Mkbin([23,22,2]), 8)
+ end,
+ Aligned(),
+
+ UnAligned = fun() ->
+ {'EXIT',{function_clause,_}} =
+ (catch GetTailUsed(Mkbin([42]))),
+ {'EXIT',{{badmatch,_},_}} =
+ (catch GetDynTailUsed(Mkbin([137]), 3)),
+ {'EXIT',{function_clause,_}} =
+ (catch GetTailUnused(Mkbin([42,33]))),
+ {'EXIT',{{badmatch,_},_}} =
+ (catch GetDynTailUnused(Mkbin([44]), 7))
+ end,
+ UnAligned(),
+ ok.
+ ">>,
+ [ok] = scan(C),
+ ok = evaluate(C, []).
+
+bs_match_bin_SUITE(doc) ->
+ ["OTP-5327. Adopted from emulator/test/bs_match_bin_SUITE.erl."];
+bs_match_bin_SUITE(suite) ->
+ [];
+bs_match_bin_SUITE(Config) when is_list(Config) ->
+ ByteSplitBinary =
+ <<"ByteSplit =
+ fun(L, B, Pos, Fun) when Pos >= 0 ->
+ Sz1 = Pos,
+ Sz2 = size(B) - Pos,
+ <<B1:Sz1/binary,B2:Sz2/binary>> = B,
+ B1 = list_to_binary(lists:sublist(L, 1, Pos)),
+ B2 = list_to_binary(lists:nthtail(Pos, L)),
+ Fun(L, B, Pos-1, Fun);
+ (L, B, _, _Fun) -> ok
+ end,
+ Mkbin = fun(L) when list(L) -> list_to_binary(L) end,
+ L = lists:seq(0, 57),
+ B = Mkbin(L),
+ ByteSplit(L, B, size(B), ByteSplit),
+ Id = fun(I) -> I end,
+ MakeUnalignedSubBinary =
+ fun(Bin0) ->
+ Bin1 = <<0:3,Bin0/binary,31:5>>,
+ Sz = size(Bin0),
+ <<0:3,Bin:Sz/binary,31:5>> = Id(Bin1),
+ Bin
+ end,
+ Unaligned = MakeUnalignedSubBinary(B),
+ ByteSplit(L, Unaligned, size(Unaligned), ByteSplit),
+ ok.
+ ">>,
+ [ok] = scan(ByteSplitBinary),
+ ok = evaluate(ByteSplitBinary, []),
+ BitSplitBinary =
+ <<"Mkbin = fun(L) when list(L) -> list_to_binary(L) end,
+
+ MakeInt =
+ fun(List, 0, Acc, _F) -> Acc;
+ ([H|T], N, Acc, F) -> F(T, N-1, Acc bsl 1 bor H, F)
+ end,
+
+ MakeBinFromList =
+ fun(List, 0, _F) -> Mkbin([]);
+ (List, N, F) ->
+ list_to_binary([MakeInt(List, 8, 0, MakeInt),
+ F(lists:nthtail(8, List), N-8, F)])
+ end,
+
+ BitSplitBinary3 =
+ fun(Action, Bin, List, Bef, Aft, F) when Bef =< Aft ->
+ Action(Bin, List, Bef, (Aft-Bef) div 8 * 8),
+ F(Action, Bin, List, Bef, Aft-8, F);
+ (_, _, _, _, _, _) -> ok
+ end,
+
+ BitSplitBinary2 =
+ fun(Action, Bin, [_|T]=List, Bef, F) ->
+ BitSplitBinary3(Action, Bin, List, Bef, size(Bin)*8,
+ BitSplitBinary3),
+ F(Action, Bin, T, Bef+1, F);
+ (Action, Bin, [], Bef, F) -> ok
+ end,
+
+ BitsToList =
+ fun([H|T], 0, F) -> F(T, 16#80, F);
+ ([H|_]=List, Mask, F) ->
+ [case H band Mask of
+ 0 -> 0;
+ _ -> 1
+ end | F(List, Mask bsr 1, F)];
+ ([], _, _F) -> []
+ end,
+
+ BitSplitBinary1 =
+ fun(Action, Bin) ->
+ BitList = BitsToList(binary_to_list(Bin), 16#80,
+ BitsToList),
+ BitSplitBinary2(Action, Bin, BitList, 0, BitSplitBinary2)
+ end,
+
+ Fun = fun(Bin, List, SkipBef, N) ->
+ SkipAft = 8*size(Bin) - N - SkipBef,
+ <<I1:SkipBef,OutBin:N/binary-unit:1,I2:SkipAft>> = Bin,
+ OutBin = MakeBinFromList(List, N, MakeBinFromList)
+ end,
+
+ BitSplitBinary1(Fun, erlang:md5(<<1,2,3>>)),
+ Id = fun(I) -> I end,
+ MakeUnalignedSubBinary =
+ fun(Bin0) ->
+ Bin1 = <<0:3,Bin0/binary,31:5>>,
+ Sz = size(Bin0),
+ <<0:3,Bin:Sz/binary,31:5>> = Id(Bin1),
+ Bin
+ end,
+ BitSplitBinary1(Fun, MakeUnalignedSubBinary(erlang:md5(<<1,2,3>>))),
+ ok.
+ ">>,
+ [ok] = scan(BitSplitBinary),
+ ok = evaluate(BitSplitBinary, []).
+
+-define(FAIL(Expr), "{'EXIT',{badarg,_}} = (catch " ??Expr ")").
+
+-define(COF(Int0),
+ "(fun(Int) ->
+ true = <<Int:32/float>> =:= <<(float(Int)):32/float>>,
+ true = <<Int:64/float>> =:= <<(float(Int)):64/float>>
+ end)(Nonliteral(" ??Int0 ")),
+ true = <<" ??Int0 ":32/float>> =:= <<(float("??Int0")):32/float>>,
+ true = <<" ??Int0 ":64/float>> =:= <<(float("??Int0")):64/float>>").
+
+-define(COF64(Int0),
+ "(fun(Int) ->
+ true = <<Int:64/float>> =:= <<(float(Int)):64/float>>
+ end)(Nonliteral(" ??Int0 ")),
+ true = <<" ??Int0 ":64/float>> =:= <<(float("??Int0")):64/float>>").
+
+bs_construct_SUITE(doc) ->
+ ["OTP-5327. Adopted from parts of emulator/test/bs_construct_SUITE.erl."];
+bs_construct_SUITE(suite) ->
+ [];
+bs_construct_SUITE(Config) when is_list(Config) ->
+ C1 = <<"
+
+ Testf_1 = fun(W, B) -> "
+ ?FAIL(<<42:W>>) ","
+ ?FAIL(<<3.14:W/float>>) ","
+ ?FAIL(<<B:W/binary>>) "
+ end,
+
+ TestF = fun() -> "
+ ?FAIL(<<3.14>>) ","
+ ?FAIL(<<<<1,2>>>>) ","
+
+ ?FAIL(<<2.71/binary>>) ","
+ ?FAIL(<<24334/binary>>) ","
+ ?FAIL(<<24334344294788947129487129487219847/binary>>) ","
+
+ ?FAIL(<<<<1,2,3>>/float>>) ",
+
+ %% Negative field widths.
+ Testf_1(-8, <<1,2,3,4,5>>),"
+
+ ?FAIL(<<42:(-16)>>) ","
+ ?FAIL(<<3.14:(-8)/float>>) ","
+ ?FAIL(<<<<23,56,0,2>>:(-16)/binary>>) ","
+ ?FAIL(<<<<23,56,0,2>>:(2.5)/binary>>) ","
+ ?FAIL(<<<<23,56,0,2>>:(anka)>>) "
+ end,
+ TestF(),
+
+ NotUsed1 = fun(I, BinString) -> <<I:32,BinString/binary>>, ok end,
+
+ NotUsed2 = fun(I, Sz) -> <<I:Sz>>, ok end,
+
+ NotUsed3 = fun(I) -><<I:(-8)>>, ok end,
+
+ NotUsed = fun() ->
+ ok = NotUsed1(3, <<\"dum\">>),
+ {'EXIT',{badarg,_}} = (catch NotUsed1(3, \"dum\")), "
+ ?FAIL(NotUsed2(444, -2)) ","
+ ?FAIL(NotUsed2(444, anka)) ","
+ ?FAIL(NotUsed3(444)) "
+ end,
+ NotUsed(),
+
+ InGuard3 = fun(Bin, A, B) when <<A:13,B:3>> == Bin -> 1;
+ (Bin, A, B) when <<A:16,B/binary>> == Bin -> 2;
+ (Bin, A, B) when <<A:14,B/float,3:2>> == Bin -> 3;
+ (Bin, A, B) when {a,b,<<A:14,B/float,3:2>>} == Bin ->
+ cant_happen;
+ (_, _, _) -> nope
+ end,
+
+ InGuard = fun() ->
+ 1 = InGuard3(<<16#74ad:16>>, 16#e95, 5),
+ 2 = InGuard3(<<16#3A,16#F7,\"hello\">>, 16#3AF7, <<\"hello\">>),
+ 3 = InGuard3(<<16#FBCD:14,3.1415/float,3:2>>, 16#FBCD, 3.1415),
+ nope = InGuard3(<<1>>, 42, b),
+ nope = InGuard3(<<1>>, a, b),
+ nope = InGuard3(<<1,2>>, 1, 1),
+ nope = InGuard3(<<4,5>>, 1, 2.71),
+ nope = InGuard3(<<4,5>>, 1, <<12,13>>)
+ end,
+ InGuard(),
+
+ Nonliteral = fun(X) -> X end,
+
+ CoerceToFloat = fun() -> "
+ ?COF(0) ","
+ ?COF(-1) ","
+ ?COF(1) ","
+ ?COF(42) ","
+ ?COF(255) ","
+ ?COF(-255) ","
+ ?COF64(298748888888888888888888888883478264866528467367364766666666666666663) ","
+ ?COF64(-367546729879999999999947826486652846736736476555566666663) "
+ end,
+ CoerceToFloat(),
+ ok.
+ ">>,
+ [ok] = scan(C1),
+ ok = evaluate(C1, []),
+
+ %% There is another one, lib/compiler/test/bs_construct_SUITE.erl...
+ C2 = <<"
+ I = fun(X) -> X end,
+
+ Fail = fun() ->
+
+ I_minus_777 = I(-777),
+ I_minus_2047 = I(-2047),
+
+ %% One negative field size, but the sum of field sizes will be 1 byte.
+ %% Make sure that we reject that properly.
+
+ {'EXIT',{badarg,_}} = (catch <<I_minus_777:2048/unit:8,
+ 57:I_minus_2047/unit:8>>),
+
+ %% Same thing, but use literals.
+ {'EXIT',{badarg,_}} = (catch <<I_minus_777:2048/unit:8,
+ 57:(-2047)/unit:8>>),
+
+ %% Bad alignment.
+ I_one = I(1),
+ <<1:1>> = <<2375:I_one>>,
+ <<3:2>> = <<45:1,2375:I_one>>,
+ <<14:4>> = <<45:1,2375:I_one,918:2>>,
+ <<118:7>> = <<45:1,2375:I_one,918:5>>,
+
+ %% Not numbers.
+ {'EXIT',{badarg,_}} = (catch <<45:(I(not_a_number))>>),
+ {'EXIT',{badarg,_}} = (catch <<13:8,45:(I(not_a_number))>>),
+
+ %% Unaligned sizes.
+ BadSz = I(7),
+ <<2:4>> = <<34:4>>,
+ <<34:7>> = <<34:BadSz>>,
+
+ [] = [X || {X} <- [], X == <<3:BadSz>>],
+ [] = [X || {X} <- [], X == <<3:4>>]
+ end,
+ Fail(),
+
+ FloatBin1 = fun(F) ->
+ {<<1,2,3>>,F+3.0}
+ end,
+
+ FloatBin = fun() ->
+ %% Some more coverage.
+ {<<1,2,3>>,7.0} = FloatBin1(4)
+ end,
+ FloatBin(),
+
+ ok.
+ ">>,
+ [ok] = scan(C2),
+ ok = evaluate(C2, []).
+
+evaluate(B, Vars) when is_binary(B) ->
+ evaluate(binary_to_list(B), Vars);
+evaluate(Str, Vars) ->
+ {ok,Tokens,_} =
+ erl_scan:string(Str),
+ {ok, Exprs} = erl_parse:parse_exprs(Tokens),
+ case erl_eval:exprs(Exprs, Vars, none) of
+ {value, Result, _} ->
+ Result
+ end.
+
+refman(suite) ->
+ [refman_bit_syntax].
+
+refman_bit_syntax(doc) ->
+ ["Bit syntax examples from the Reference Manual. OTP-5237."];
+refman_bit_syntax(suite) ->
+ [];
+refman_bit_syntax(Config) when is_list(Config) ->
+ %% Reference Manual "Bit Syntax Expressions"
+ ?line Bin1 = <<1,17,42>>,
+ ?line true = [1,17,42] =:= binary_to_list(Bin1),
+ ?line Bin2 = <<"abc">>,
+ ?line true = "abc" =:= binary_to_list(Bin2),
+ ?line Bin3 = <<1,17,42:16>>,
+ ?line true = [1,17,0,42] =:= binary_to_list(Bin3),
+ ?line <<_A,_B,C:16>> = <<1,17,42:16>>,
+ ?line true = C =:= 42,
+ ?line <<D:16,_E,F>> = <<1,17,42:16>>,
+ ?line true = D =:= 273,
+ ?line true = F =:= 42,
+ <<_G,H/binary>> = <<1,17,42:16>>,
+ ?line true = H =:= <<17,0,42>>,
+
+ ?line [ok] =
+ scan(<<"Bin1 = <<1,17,42>>,
+ true = [1,17,42] =:= binary_to_list(Bin1),
+ Bin2 = <<\"abc\">>,
+ true = \"abc\" =:= binary_to_list(Bin2),
+ Bin3 = <<1,17,42:16>>,
+ true =
+ [1,17,0,42] =:= binary_to_list(Bin3),
+ <<A,B,C:16>> = <<1,17,42:16>>,
+ true = C =:= 42,
+ <<D:16,E,F>> = <<1,17,42:16>>,
+ true = D =:= 273,
+ true = F =:= 42,
+ <<G,H/binary>> = <<1,17,42:16>>,
+ true = H =:= <<17,0,42>>,
+ ok.">>),
+
+ %% Binary comprehensions.
+ ?line <<2,4,6>> = << << (X*2) >> || <<X>> <= << 1,2,3 >> >>,
+ ok.
+
+progex(suite) ->
+ [progex_bit_syntax, progex_records, progex_lc, progex_funs].
+
+-define(IP_VERSION, 4).
+-define(IP_MIN_HDR_LEN, 5).
+progex_bit_syntax(doc) ->
+ ["Bit syntax examples from Programming Examples. OTP-5237."];
+progex_bit_syntax(suite) ->
+ [];
+progex_bit_syntax(Config) when is_list(Config) ->
+ Bin11 = <<1, 17, 42>>,
+ true = [1, 17, 42] =:= binary_to_list(Bin11),
+ Bin12 = <<"abc">>,
+ true = [97, 98, 99] =:= binary_to_list(Bin12),
+
+ A = 1, B = 17, C = 42,
+ Bin2 = <<A, B, C:16>>,
+ true = [1, 17, 00, 42] =:= binary_to_list(Bin2),
+ <<D:16, E, F/binary>> = Bin2,
+ true = D =:= 273,
+ true = E =:= 00,
+ true = [42] =:= binary_to_list(F),
+
+ Fun4 = fun(Dgram) ->
+ DgramSize = byte_size(Dgram),
+ case Dgram of
+ <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
+ ID:16, Flgs:3, FragOff:13,
+ TTL:8, Proto:8, HdrChkSum:16,
+ SrcIP:32, DestIP:32,
+ RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
+ OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
+ <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
+ {SrvcType, TotLen, Flgs, FragOff, ID, HdrChkSum,
+ Proto, TTL, SrcIP, DestIP, Data, Opts};
+ _ ->
+ not_ok
+ end
+ end,
+ true = Fun4(<<>>) =:= not_ok,
+ true = is_tuple(Fun4(list_to_binary([<<?IP_VERSION:4,5:4>>,
+ list_to_binary(lists:seq(1,255))]))),
+
+ X = 23432324, Y = 24324234,
+ <<10:7>> = <<X:1, Y:6>>,
+ Z = 234324324,
+ XYZ = <<X:1, Y:6, Z:1>>,
+ true = [20] =:= binary_to_list(XYZ),
+ Hello1 = <<"hello">>,
+ Hello2 = <<$h,$e,$l,$l,$o>>,
+ true = "hello" =:= binary_to_list(Hello1),
+ true = "hello" =:= binary_to_list(Hello2),
+
+ FunM1 = fun(<<X1:7/binary, Y1:1/binary>>) -> {X1,Y1} end,
+ true = {<<"1234567">>,<<"8">>} =:= FunM1(<<"12345678">>),
+
+ FunM2 = fun(<<_X1:7/binary-unit:7, _Y1:1/binary-unit:1>>) -> ok;
+ (_) -> not_ok end,
+ true = not_ok =:= FunM2(<<"1">>),
+
+ BL = [{3,4,5},{6,7,8}],
+ Lst = [0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,7,0,0,0,8],
+ B1 = triples_to_bin1(BL),
+ true = Lst =:= binary_to_list(B1),
+ B2 = triples_to_bin2(BL),
+ true = Lst =:= binary_to_list(B2),
+
+ ?line [ok] = scan(
+ <<"Bin11 = <<1, 17, 42>>,
+ true = [1, 17, 42] =:= binary_to_list(Bin11),
+ Bin12 = <<\"abc\">>,
+ true = [97, 98, 99] =:= binary_to_list(Bin12),
+
+ A = 1, B = 17, C = 42,
+ Bin2 = <<A, B, C:16>>,
+ true = [1, 17, 00, 42] =:= binary_to_list(Bin2),
+ <<D:16, E, F/binary>> = Bin2,
+ true = D =:= 273,
+ true = E =:= 00,
+ true = [42] =:= binary_to_list(F),
+
+ Fun4 = fun(Dgram) ->
+ DgramSize = byte_size(Dgram),
+ case Dgram of
+ <<4:4, HLen:4, SrvcType:8, TotLen:16,
+ ID:16, Flgs:3, FragOff:13,
+ TTL:8, Proto:8, HdrChkSum:16,
+ SrcIP:32, DestIP:32,
+ RestDgram/binary>> when HLen>=5,
+ 4*HLen=<DgramSize ->
+ OptsLen = 4*(HLen - 5),
+ <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
+ {SrvcType, TotLen, Flgs, FragOff, ID, HdrChkSum,
+ Proto, TTL, SrcIP, DestIP, Data, Opts};
+ _ ->
+ not_ok
+ end
+ end,
+ true = Fun4(<<>>) =:= not_ok,
+ true = is_tuple(Fun4(list_to_binary
+ ([<<4:4,5:4>>,list_to_binary(lists:seq(1,255))]))),
+
+ X = 23432324, Y = 24324234,
+ <<10:7>> = <<X:1, Y:6>>,
+ Z = 234324324,
+ XYZ = <<X:1, Y:6, Z:1>>,
+ true = [20] =:= binary_to_list(XYZ),
+ Hello1 = <<\"hello\">>,
+ Hello2 = <<$h,$e,$l,$l,$o>>,
+ true = \"hello\" =:= binary_to_list(Hello1),
+ true = \"hello\" =:= binary_to_list(Hello2),
+
+ FunM1 = fun(<<X1:7/binary, Y1:1/binary>>) -> {X1,Y1} end,
+ true = {<<\"1234567\">>,<<\"8\">>} =:= FunM1(<<\"12345678\">>),
+
+ FunM2 = fun(<<_X1:7/binary-unit:7, _Y1:1/binary-unit:1>>) -> ok;
+ (_) -> not_ok end,
+ true = not_ok =:= FunM2(<<\"1\">>),
+ ok.">>),
+
+ ok.
+
+triples_to_bin1(T) ->
+ triples_to_bin1(T, <<>>).
+
+triples_to_bin1([{X,Y,Z} | T], Acc) ->
+ triples_to_bin1(T, <<Acc/binary, X:32, Y:32, Z:32>>); % inefficient
+triples_to_bin1([], Acc) ->
+ Acc.
+
+triples_to_bin2(T) ->
+ triples_to_bin2(T, []).
+
+triples_to_bin2([{X,Y,Z} | T], Acc) ->
+ triples_to_bin2(T, [<<X:32, Y:32, Z:32>> | Acc]);
+triples_to_bin2([], Acc) ->
+ list_to_binary(lists:reverse(Acc)).
+
+progex_records(doc) ->
+ ["Record examples from Programming Examples. OTP-5237."];
+progex_records(suite) ->
+ [];
+progex_records(Config) when is_list(Config) ->
+ Test1 =
+ <<"-module(recs).
+ -record(person, {name = \"\", phone = [], address}).
+ -record(name, {first = \"Robert\", last = \"Ericsson\"}).
+ -record(person2, {name = #name{}, phone}).
+ -export([t/0]).
+
+ t() ->
+ _P1 = #person{phone=[0,8,2,3,4,3,1,2], name=\"Robert\"},
+ \"Robert\" = _P1#person.name,
+ [0,8,2,3,4,3,1,2] = _P1#person.phone,
+ undefined = _P1#person.address,
+
+ _P2 = #person{name = \"Jakob\", _ = '_'},
+ \"Jakob\" = _P2#person.name,
+ '_' = _P2#person.phone,
+ '_' = _P2#person.address,
+
+ P = #person{name = \"Joe\", phone = [0,8,2,3,4,3,1,2]},
+ \"Joe\" = P#person.name,
+ [0,8,2,3,4,3,1,2] = P#person.phone,
+ undefined = P#person.address,
+
+ P1 = #person{name=\"Joe\", phone=[1,2,3], address=\"A street\"},
+ P2 = P1#person{name=\"Robert\"},
+ \"Robert\" = P2#person.name,
+ [1,2,3] = P2#person.phone,
+ \"A street\" = P2#person.address,
+ a_person = foo(P1),
+
+ {found, [1,2,3]} =
+ find_phone([#person{name = a},
+ #person{name = b, phone = [3,2,1]},
+ #person{name = c, phone = [1,2,3]}],
+ c),
+
+ P3 = #person{name=\"Joe\", phone=[0,0,7], address=\"A street\"},
+ #person{name = Name} = P3,
+ \"Joe\" = Name,
+
+ \"Robert\" = demo(),
+ ok.
+
+ foo(P) when is_record(P, person) -> a_person;
+ foo(_) -> not_a_person.
+
+ find_phone([#person{name=Name, phone=Phone} | _], Name) ->
+ {found, Phone};
+ find_phone([_| T], Name) ->
+ find_phone(T, Name);
+ find_phone([], _Name) ->
+ not_found.
+
+ demo() ->
+ P = #person2{name= #name{first=\"Robert\",last=\"Virding\"},
+ phone=123},
+ _First = (P#person2.name)#name.first.
+ ">>,
+ ?line ok = run_file(Config, recs, Test1),
+
+ Test1_shell =
+ <<"rd(person, {name = \"\", phone = [], address}),
+ rd(name, {first = \"Robert\", last = \"Ericsson\"}),
+ rd(person2, {name = #name{}, phone}),
+
+ _P1 = #person{phone=[0,8,2,3,4,3,1,2], name=\"Robert\"},
+ \"Robert\" = _P1#person.name,
+ [0,8,2,3,4,3,1,2] = _P1#person.phone,
+ undefined = _P1#person.address,
+
+ _P2 = #person{name = \"Jakob\", _ = '_'},
+ \"Jakob\" = _P2#person.name,
+ '_' = _P2#person.phone,
+ '_' = _P2#person.address,
+
+ P = #person{name = \"Joe\", phone = [0,8,2,3,4,3,1,2]},
+ \"Joe\" = P#person.name,
+ [0,8,2,3,4,3,1,2] = P#person.phone,
+ undefined = P#person.address,
+
+ P1 = #person{name=\"Joe\", phone=[1,2,3], address=\"A street\"},
+ P2 = P1#person{name=\"Robert\"},
+ \"Robert\" = P2#person.name,
+ [1,2,3] = P2#person.phone,
+ \"A street\" = P2#person.address,
+ Foo = fun(P) when is_record(P, person) -> a_person;
+ (_) -> not_a_person
+ end,
+ a_person = Foo(P1),
+
+ Find = fun([#person{name=Name, phone=Phone} | _], Name, Fn) ->
+ {found, Phone};
+ ([_| T], Name, Fn) ->
+ Fn(T, Name, Fn);
+ ([], _Name, _Fn) ->
+ not_found
+ end,
+
+ {found, [1,2,3]} = Find([#person{name = a},
+ #person{name = b, phone = [3,2,1]},
+ #person{name = c, phone = [1,2,3]}],
+ c,
+ Find),
+
+ P3 = #person{name=\"Joe\", phone=[0,0,7], address=\"A street\"},
+ #person{name = Name} = P3,
+ \"Joe\" = Name,
+
+ Demo = fun() ->
+ P17 = #person2{name= #name{first=\"Robert\",last=\"Virding\"},
+ phone=123},
+ _First = (P17#person2.name)#name.first
+ end,
+
+ \"Robert\" = Demo(),
+ ok.
+ ">>,
+ ?line [ok] = scan(Test1_shell),
+
+ Test2 =
+ <<"-module(recs).
+ -record(person, {name, age, phone = [], dict = []}).
+ -compile(export_all).
+
+ t() -> ok.
+
+ make_hacker_without_phone(Name, Age) ->
+ #person{name = Name, age = Age,
+ dict = [{computer_knowledge, excellent},
+ {drinks, coke}]}.
+ print(#person{name = Name, age = Age,
+ phone = Phone, dict = Dict}) ->
+ io:format(\"Name: ~s, Age: ~w, Phone: ~w ~n\"
+ \"Dictionary: ~w.~n\", [Name, Age, Phone, Dict]).
+
+ birthday(P) when record(P, person) ->
+ P#person{age = P#person.age + 1}.
+
+ register_two_hackers() ->
+ Hacker1 = make_hacker_without_phone(\"Joe\", 29),
+ OldHacker = birthday(Hacker1),
+ % The central_register_server should have
+ % an interface function for this.
+ central_register_server ! {register_person, Hacker1},
+ central_register_server ! {register_person,
+ OldHacker#person{name = \"Robert\",
+ phone = [0,8,3,2,4,5,3,1]}}.
+ ">>,
+ ?line ok = run_file(Config, recs, Test2),
+ ok.
+
+progex_lc(doc) ->
+ ["List comprehension examples from Programming Examples. OTP-5237."];
+progex_lc(suite) ->
+ [];
+progex_lc(Config) when is_list(Config) ->
+ Test1 =
+ <<"-module(lc).
+ -export([t/0]).
+
+ t() ->
+ [a,4,b,5,6] = [X || X <- [1,2,a,3,4,b,5,6], X > 3],
+ [4,5,6] = [X || X <- [1,2,a,3,4,b,5,6], integer(X), X > 3],
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] =
+ [{X, Y} || X <- [1,2,3], Y <- [a,b]],
+
+ [1,2,3,4,5,6,7,8] = sort([4,5,1,8,3,6,7,2]),
+ [[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]] =
+ perms([b,u,g]),
+ [] = pyth(11),
+ [{3,4,5},{4,3,5}] = pyth(12),
+ [{3,4,5},{4,3,5},{5,12,13},{6,8,10},{8,6,10},{8,15,17},
+ {9,12,15},{12,5,13},{12,9,15},{12,16,20},{15,8,17},
+ {16,12,20}] = pyth(50),
+ [] = pyth1(11),
+ [{3,4,5},{4,3,5}] = pyth1(12),
+ [{3,4,5},{4,3,5},{5,12,13},{6,8,10},{8,6,10},{8,15,17},
+ {9,12,15},{12,5,13},{12,9,15},{12,16,20},{15,8,17},
+ {16,12,20}] = pyth1(50),
+ [1,2,3,4,5] = append([[1,2,3],[4,5]]),
+ [2,3,4] = map(fun(X) -> X + 1 end, [1,2,3]),
+ [2,4] = filter(fun(X) -> X > 1 end, [0,2,4]),
+ [1,2,3,7] = select(b,[{a,1},{b,2},{c,3},{b,7}]),
+ [2,7] = select2(b,[{a,1},{b,2},{c,3},{b,7}]),
+ ok.
+
+ sort([Pivot|T]) ->
+ sort([ X || X <- T, X < Pivot]) ++
+ [Pivot] ++
+ sort([ X || X <- T, X >= Pivot]);
+ sort([]) -> [].
+
+ perms([]) -> [[]];
+ perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])].
+
+ pyth(N) ->
+ [ {A,B,C} ||
+ A <- lists:seq(1,N),
+ B <- lists:seq(1,N),
+ C <- lists:seq(1,N),
+ A+B+C =< N,
+ A*A+B*B == C*C
+ ].
+
+ pyth1(N) ->
+ [{A,B,C} ||
+ A <- lists:seq(1,N),
+ B <- lists:seq(1,N-A+1),
+ C <- lists:seq(1,N-A-B+2),
+ A+B+C =< N,
+ A*A+B*B == C*C ].
+
+ append(L) -> [X || L1 <- L, X <- L1].
+ map(Fun, L) -> [Fun(X) || X <- L].
+ filter(Pred, L) -> [X || X <- L, Pred(X)].
+
+ select(X, L) -> [Y || {X, Y} <- L].
+ select2(X, L) -> [Y || {X1, Y} <- L, X == X1].
+ ">>,
+ ?line ok = run_file(Config, lc, Test1),
+
+ Test1_shell =
+ <<"[a,4,b,5,6] = [X || X <- [1,2,a,3,4,b,5,6], X > 3],
+ [4,5,6] = [X || X <- [1,2,a,3,4,b,5,6], integer(X), X > 3],
+ [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}] =
+ [{X, Y} || X <- [1,2,3], Y <- [a,b]],
+
+ Sort = fun([Pivot|T], Fn) ->
+ Fn([ X || X <- T, X < Pivot], Fn) ++
+ [Pivot] ++
+ Fn([ X || X <- T, X >= Pivot], Fn);
+ ([], _Fn) -> []
+ end,
+
+ [1,2,3,4,5,6,7,8] = Sort([4,5,1,8,3,6,7,2], Sort),
+ Perms = fun([], _Fn) -> [[]];
+ (L, Fn) -> [[H|T] || H <- L, T <- Fn(L--[H], Fn)]
+ end,
+ [[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]] =
+ Perms([b,u,g], Perms),
+
+ Pyth = fun(N) ->
+ [ {A,B,C} ||
+ A <- lists:seq(1,N),
+ B <- lists:seq(1,N),
+ C <- lists:seq(1,N),
+ A+B+C =< N,
+ A*A+B*B == C*C
+ ]
+ end,
+
+ [] = Pyth(11),
+ [{3,4,5},{4,3,5}] = Pyth(12),
+ %[{3,4,5},{4,3,5},{5,12,13},{6,8,10},{8,6,10},{8,15,17},
+ % {9,12,15},{12,5,13},{12,9,15},{12,16,20},{15,8,17},
+ % {16,12,20}] = Pyth(50),
+
+ Pyth1 = fun(N) ->
+ [{A,B,C} ||
+ A <- lists:seq(1,N),
+ B <- lists:seq(1,N-A+1),
+ C <- lists:seq(1,N-A-B+2),
+ A+B+C =< N,
+ A*A+B*B == C*C ]
+ end,
+
+ [] = Pyth1(11),
+ [{3,4,5},{4,3,5}] = Pyth1(12),
+ [{3,4,5},{4,3,5},{5,12,13},{6,8,10},{8,6,10},{8,15,17},
+ {9,12,15},{12,5,13},{12,9,15},{12,16,20},{15,8,17},
+ {16,12,20}] = Pyth1(50),
+
+ Append = fun(L) -> [X || L1 <- L, X <- L1] end,
+ [1,2,3,4,5] = Append([[1,2,3],[4,5]]),
+ Map = fun(Fun, L) -> [Fun(X) || X <- L] end,
+ [2,3,4] = Map(fun(X) -> X + 1 end, [1,2,3]),
+ Filter = fun(Pred, L) -> [X || X <- L, Pred(X)] end,
+ [2,4] = Filter(fun(X) -> X > 1 end, [0,2,4]),
+
+ Select = fun(X, L) -> [Y || {X, Y} <- L] end,
+ [1,2,3,7] = Select(b,[{a,1},{b,2},{c,3},{b,7}]),
+ Select2 = fun(X, L) -> [Y || {X1, Y} <- L, X == X1] end,
+ [2,7] = Select2(b,[{a,1},{b,2},{c,3},{b,7}]),
+ ok.
+ ">>,
+ ?line [ok] = scan(Test1_shell),
+ ok.
+
+progex_funs(doc) ->
+ ["Funs examples from Programming Examples. OTP-5237."];
+progex_funs(suite) ->
+ [];
+progex_funs(Config) when is_list(Config) ->
+ Test1 =
+ <<"-module(funs).
+ -compile(export_all).
+
+ double([H|T]) -> [2*H|double(T)];
+ double([]) -> [].
+
+ add_one([H|T]) -> [H+1|add_one(T)];
+ add_one([]) -> [].
+
+ map(F, [H|T]) -> [F(H)|map(F, T)];
+ map(F, []) -> [].
+
+ double2(L) -> map(fun(X) -> 2*X end, L).
+ add_one2(L) -> map(fun(X) -> 1 + X end, L).
+
+ print_list(Stream, [H|T]) ->
+ io:format(Stream, \"~p~n\", [H]),
+ print_list(Stream, T);
+ print_list(Stream, []) ->
+ true.
+
+ broadcast(Msg, [Pid|Pids]) ->
+ Pid ! Msg,
+ broadcast(Msg, Pids);
+ broadcast(_, []) ->
+ true.
+
+ foreach(F, [H|T]) ->
+ F(H),
+ foreach(F, T);
+ foreach(F, []) ->
+ ok.
+
+ print_list2(S, L) ->
+ foreach(fun(H) -> io:format(S, \"~p~n\",[H]) end, L).
+
+ broadcast2(M, L) -> foreach(fun(Pid) -> Pid ! M end, L).
+
+ t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
+
+ t2() -> map(fun double/1, [1,2,3,4,5]).
+
+ t3() -> map({?MODULE, double3}, [1,2,3,4,5]).
+
+ double3(X) -> X * 2.
+
+ f(F, Args) when function(F) ->
+ apply(F, Args);
+ f(N, _) when integer(N) ->
+ N.
+
+ print_list3(File, List) ->
+ {ok, Stream} = file:open(File, write),
+ foreach(fun(X) -> io:format(Stream,\"~p~n\",[X]) end, List),
+ file:close(Stream).
+
+ print_list4(File, List) ->
+ {ok, Stream} = file:open(File, write),
+ foreach(fun(File) ->
+ io:format(Stream,\"~p~n\",[File])
+ end, List),
+ file:close(Stream).
+
+ any(Pred, [H|T]) ->
+ case Pred(H) of
+ true -> true;
+ false -> any(Pred, T)
+ end;
+ any(Pred, []) ->
+ false.
+
+ all(Pred, [H|T]) ->
+ case Pred(H) of
+ true -> all(Pred, T);
+ false -> false
+ end;
+ all(Pred, []) ->
+ true.
+
+ foldl(F, Accu, [Hd|Tail]) ->
+ foldl(F, F(Hd, Accu), Tail);
+ foldl(F, Accu, []) -> Accu.
+
+ mapfoldl(F, Accu0, [Hd|Tail]) ->
+ {R,Accu1} = F(Hd, Accu0),
+ {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
+ {[R|Rs], Accu2};
+ mapfoldl(F, Accu, []) -> {[], Accu}.
+
+ filter(F, [H|T]) ->
+ case F(H) of
+ true -> [H|filter(F, T)];
+ false -> filter(F, T)
+ end;
+ filter(F, []) -> [].
+
+ diff(L1, L2) ->
+ filter(fun(X) -> not lists:member(X, L2) end, L1).
+
+ intersection(L1,L2) -> filter(fun(X) -> lists:member(X,L1) end, L2).
+
+ takewhile(Pred, [H|T]) ->
+ case Pred(H) of
+ true -> [H|takewhile(Pred, T)];
+ false -> []
+ end;
+ takewhile(Pred, []) ->
+ [].
+
+ dropwhile(Pred, [H|T]) ->
+ case Pred(H) of
+ true -> dropwhile(Pred, T);
+ false -> [H|T]
+ end;
+ dropwhile(Pred, []) ->
+ [].
+
+ splitlist(Pred, L) ->
+ splitlist(Pred, L, []).
+
+ splitlist(Pred, [H|T], L) ->
+ case Pred(H) of
+ true -> splitlist(Pred, T, [H|L]);
+ false -> {lists:reverse(L), [H|T]}
+ end;
+ splitlist(Pred, [], L) ->
+ {lists:reverse(L), []}.
+
+ first(Pred, [H|T]) ->
+ case Pred(H) of
+ true ->
+ {true, H};
+ false ->
+ first(Pred, T)
+ end;
+ first(Pred, []) ->
+ false.
+
+ ints_from(N) ->
+ fun() ->
+ [N|ints_from(N+1)]
+ end.
+
+ pconst(X) ->
+ fun (T) ->
+ case T of
+ [X|T1] -> {ok, {const, X}, T1};
+ _ -> fail
+ end
+ end.
+
+ pand(P1, P2) ->
+ fun (T) ->
+ case P1(T) of
+ {ok, R1, T1} ->
+ case P2(T1) of
+ {ok, R2, T2} ->
+ {ok, {'and', R1, R2}};
+ fail ->
+ fail
+ end;
+ fail ->
+ fail
+ end
+ end.
+
+ por(P1, P2) ->
+ fun (T) ->
+ case P1(T) of
+ {ok, R, T1} ->
+ {ok, {'or',1,R}, T1};
+ fail ->
+ case P2(T) of
+ {ok, R1, T1} ->
+ {ok, {'or',2,R1}, T1};
+ fail ->
+ fail
+ end
+ end
+ end.
+
+ grammar() ->
+ pand(
+ por(pconst(a), pconst(b)),
+ por(pconst(c), pconst(d))).
+
+ parse(List) ->
+ (grammar())(List).
+
+
+ t() ->
+ [2,4,6,8] = double([1,2,3,4]),
+ [2,3,4,5] = add_one([1,2,3,4]),
+ [2,4,6,8] = double2([1,2,3,4]),
+ [2,3,4,5] = add_one2([1,2,3,4]),
+ XX = ints_from(1),
+ [1 | _] = XX(),
+ 1 = hd(XX()),
+ Y = tl(XX()),
+ 2 = hd(Y()),
+
+ P1 = pconst(a),
+ {ok,{const,a},[b,c]} = P1([a,b,c]),
+ fail = P1([x,y,z]),
+
+ {ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}} =
+ parse([a,c]),
+ {ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}} =
+ parse([a,d]),
+ {ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}} =
+ parse([b,c]),
+ {ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}} =
+ parse([b,d]),
+ fail = parse([a,b]),
+ ok.
+ ">>,
+ ?line ok = run_file(Config, funs, Test1),
+
+ Test2_shell =
+ <<"Double = fun(X) -> 2 * X end,
+ [2,4,6,8,10] = lists:map(Double, [1,2,3,4,5]),
+
+ Big = fun(X) -> if X > 10 -> true; true -> false end end,
+ false = lists:any(Big, [1,2,3,4]),
+ true = lists:any(Big, [1,2,3,12,5]),
+ false = lists:all(Big, [1,2,3,4,12,6]),
+ true = lists:all(Big, [12,13,14,15]),
+ L = [\"I\",\"like\",\"Erlang\"],
+ 11 = lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L),
+ Upcase = fun(X) when $a =< X, X =< $z -> X + $A - $a;
+ (X) -> X
+ end,
+ Upcase_word = fun(X) -> lists:map(Upcase, X) end,
+ \"ERLANG\" = Upcase_word(\"Erlang\"),
+ [\"I\",\"LIKE\",\"ERLANG\"] = lists:map(Upcase_word, L),
+ {[\"I\",\"LIKE\",\"ERLANG\"],11} =
+ lists:mapfoldl(fun(Word, Sum) ->
+ {Upcase_word(Word), Sum + length(Word)}
+ end, 0, L),
+ [500,12,45] = lists:filter(Big, [500,12,2,45,6,7]),
+ [200,500,45] = lists:takewhile(Big, [200,500,45,5,3,45,6]),
+ [5,3,45,6] = lists:dropwhile(Big, [200,500,45,5,3,45,6]),
+ {[200,500,45],[5,3,45,6]} =
+ lists:splitwith(Big, [200,500,45,5,3,45,6]),
+ %% {true,45} = lists:first(Big, [1,2,45,6,123]),
+ %% false = lists:first(Big, [1,2,4,5]),
+
+ Adder = fun(X) -> fun(Y) -> X + Y end end,
+ Add6 = Adder(6),
+ 16 = Add6(10),
+ ok.
+ ">>,
+ ?line [ok] = scan(Test2_shell),
+ ok.
+
+tickets(suite) ->
+ [otp_5990, otp_6166, otp_6554, otp_6785, otp_7184, otp_7232].
+
+otp_5990(doc) ->
+ "OTP-5990. {erlang,is_record}.";
+otp_5990(suite) -> [];
+otp_5990(Config) when is_list(Config) ->
+ ?line [true] =
+ scan(<<"rd(foo,{bar}), {erlang,is_record}(#foo{}, foo).">>),
+ ?line [3] =
+ scan(<<"rd(foo,{bar}), A = #foo{}, "
+ "{if {erlang,is_record}(A, foo) -> erlang; "
+ "true -> not_a_module end, length}([1,2,3]).">>),
+ ?line [true] =
+ scan(<<"rd('OrdSet', {orddata = {},ordtype = type}), "
+ "S = #'OrdSet'{ordtype = {}}, "
+ "if tuple(S#'OrdSet'.ordtype) -> true; true -> false end.">>),
+ ok.
+
+otp_6166(doc) ->
+ "OTP-6166. Order of record definitions.";
+otp_6166(suite) -> [];
+otp_6166(Config) when is_list(Config) ->
+ Test1 = filename:join(?config(priv_dir, Config), "test1.hrl"),
+ Contents1 = <<"-module(test1).
+ -record(r5, {f}). -record(r3, {f = #r5{}}). "
+ "-record(r1, {f = #r3{}}). -record(r4, {f = #r1{}}). "
+ "-record(r2, {f = #r4{}}).">>,
+ ?line ok = file:write_file(Test1, Contents1),
+
+ Test2 = filename:join(?config(priv_dir, Config), "test2.hrl"),
+ Contents2 = <<"-module(test2).
+ -record(r5, {f}). -record(r3, {f = #r5{}}). "
+ "-record(r1, {f = #r3{}}). -record(r4, {f = #r1{}}). "
+ "-record(r2, {f = #r4{}}).
+ -record(r6, {f = #r5{}}). % r6 > r0
+ -record(r0, {f = #r5{}, g = #r5{}}). % r0 < r5">>,
+ ?line ok = file:write_file(Test2, Contents2),
+
+ RR12 = "[r1,r2,r3,r4,r5] = rr(\"" ++ Test1 ++ "\"),
+ [r0,r1,r2,r3,r4,r5,r6] = rr(\"" ++ Test2 ++ "\"),
+ R0 = #r0{}, R6 = #r6{},
+ true = is_record(R0, r0),
+ true = is_record(R6, r6),
+ ok. ",
+ ?line [ok] = scan(RR12),
+
+ file:delete(Test1),
+ file:delete(Test2),
+ ok.
+
+otp_6554(doc) ->
+ "OTP-6554. Formatted exits and error messages.";
+otp_6554(suite) -> [];
+otp_6554(Config) when is_list(Config) ->
+ %% Should check the stacktrace as well...
+ ?line "exception error: bad argument" =
+ comm_err(<<"math:sqrt(a).">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"fun(X, Y) -> X ++ Y end(a, b).">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"math:sqrt(lists:seq(1,40)).">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"math:sqrt(lists:seq(1,10)).">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"a ++ b.">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"I = {file_info,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined},
+ aa ++ I.">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"I = {file_info,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined},
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ I.">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"I = {file_info,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined},
+ I ++ I.">>),
+ ?line "exception error: bad argument" =
+ comm_err(<<"fun(X) -> not X end(a).">>),
+ ?line "exception error: bad argument: a" =
+ comm_err(<<"fun(A, B) -> A orelse B end(a, b).">>),
+ ?line "exception error: bad argument in an arithmetic expression" =
+ comm_err(<<"math:sqrt(2)/round(math:sqrt(0)).">>),
+ ?line "exception error: interpreted function with arity 1 called with no arguments" =
+ comm_err(<<"fun(V) -> V end().">>),
+ ?line "exception error: interpreted function with arity 1 called with two arguments" =
+ comm_err(<<"fun(V) -> V end(1,2).">>),
+ ?line "exception error: interpreted function with arity 0 called with one argument" =
+ comm_err(<<"fun() -> v end(1).">>),
+ ?line "exception error: interpreted function with arity 0 called with 4 arguments" =
+ comm_err(<<"fun() -> v end(1,2,3,4).">>),
+ ?line "exception error: math:sqrt/1 called with two arguments" =
+ comm_err(<<"fun math:sqrt/1(1,2).">>),
+ ?line "exception error: bad function 1." ++ _ =
+ comm_err(<<"(math:sqrt(2))().">>),
+ ?line "exception error: bad function [1," ++ _ =
+ comm_err(<<"(lists:seq(1, 100))().">>),
+ ?line "exception error: no match of right hand side value 1" ++ _ =
+ comm_err(<<"a = math:sqrt(2).">>),
+ ?line "exception error: no match of right hand side value" ++ _ =
+ comm_err(<<"I = {file_info,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined,undefined,
+ undefined,undefined,undefined,undefined},
+ a = I.">>),
+ ?line "exception error: no case clause matching 1" ++ _ =
+ comm_err(<<"case math:sqrt(2) of a -> ok end.">>),
+ ?line "exception error: no case clause matching [1," ++ _ =
+ comm_err(<<"V = lists:seq(1, 20), case V of a -> ok end.">>),
+ ?line "exception error: no function clause matching" =
+ comm_err(<<"fun(P) when is_pid(P) -> true end(a).">>),
+ ?line "exception error: {function_clause,[{erl_eval,do_apply,[unproper|list]}"++_ =
+ comm_err(<<"erlang:error(function_clause, [unproper | list]).">>),
+ ?line "exception error: function_clause" =
+ comm_err(<<"erlang:error(function_clause, 4).">>),
+ %% Cheating:
+ ?line "exception error: no function clause matching erl_eval:do_apply(4)" =
+ comm_err(<<"erlang:error(function_clause, [4]).">>),
+ ?line "exception error: no function clause matching" ++ _ =
+ comm_err(<<"fun(a, b, c, d) -> foo end"
+ " (lists:seq(1,17),"
+ " lists:seq(1, 18),"
+ " lists:seq(1, 40),"
+ " lists:seq(1, 5)).">>),
+
+ ?line "exception error: no function clause matching" =
+ comm_err(<<"fun(P, q) when is_pid(P) -> true end(a, b).">>),
+ ?line "exception error: no function clause matching lists:reverse(" ++ _ =
+ comm_err(<<"F=fun() -> hello end, lists:reverse(F).">>),
+ ?line "exception error: no function clause matching lists:reverse(34)" =
+ comm_err(<<"lists:reverse(34).">>),
+ ?line "exception error: no true branch found when evaluating an if expression" =
+ comm_err(<<"if length([a,b]) > 17 -> a end.">>),
+ ?line "exception error: no such process or port" =
+ comm_err(<<"Pid = spawn(fun() -> a end),"
+ "timer:sleep(1),"
+ "link(Pid).">>),
+ ?line "exception error: a system limit has been reached" =
+ comm_err(<<"list_to_atom(lists:duplicate(300,$a)).">>),
+ ?line "exception error: bad receive timeout value" =
+ comm_err(<<"receive after a -> foo end.">>),
+ ?line "exception error: no try clause matching 1" ++ _ =
+ comm_err(<<"try math:sqrt(2) of bar -> yes after 3 end.">>),
+ ?line "exception error: no try clause matching [1" ++ _ =
+ comm_err(<<"V = lists:seq(1, 20),"
+ "try V of bar -> yes after 3 end.">>),
+ ?line "exception error: undefined function math:sqrt/2" =
+ comm_err(<<"math:sqrt(2, 2).">>),
+ ?line "exception error: limit of number of arguments to interpreted function "
+ "exceeded" =
+ comm_err(<<"fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U) ->"
+ " a end().">>),
+ ?line "exception error: bad filter a" =
+ comm_err(<<"[b || begin a end].">>),
+ ?line "exception error: bad generator a" =
+ comm_err(<<"[X || X <- a].">>),
+ ?line "exception throw: undef" = comm_err(<<"throw(undef).">>),
+ ?line "exception exit: undef" = comm_err(<<"exit(undef).">>),
+
+ ?line "exception exit: foo" =
+ comm_err(<<"catch spawn_link(fun() ->"
+ " timer:sleep(300), exit(foo) "
+ " end),"
+ "timer:sleep(500).">>),
+ ?line [ok] = scan(
+ <<"begin process_flag(trap_exit, true),"
+ " Pid = spawn_link(fun() ->"
+ " timer:sleep(300), exit(foo) "
+ " end),"
+ " timer:sleep(500),"
+ " receive {'EXIT', Pid, foo} -> ok end end.">>),
+ ?line "exception exit: badarith" =
+ comm_err(<<"catch spawn_link(fun() ->"
+ " timer:sleep(300), 1/0 "
+ " end),"
+ "timer:sleep(500).">>),
+ ?line "exception exit: {nocatch,foo}" =
+ comm_err(<<"catch spawn_link(fun() ->"
+ " timer:sleep(300), throw(foo) "
+ " end),"
+ "timer:sleep(500).">>),
+ ?line [ok] = scan(
+ <<"begin process_flag(trap_exit, true),"
+ " Pid = spawn_link(fun() ->"
+ " timer:sleep(300), throw(foo) "
+ " end),"
+ " timer:sleep(500),"
+ " receive {'EXIT', Pid, {{nocatch,foo},_}} -> ok end "
+ "end.">>),
+
+ ?line "exception error: bad argument in an arithmetic expression" =
+ comm_err(<<"begin catch_exception(true), 1/0 end.">>),
+ ?line "exception error: bad argument in an arithmetic expression" =
+ comm_err(<<"begin catch_exception(false), 1/0 end.">>),
+ ?line "exception error: no function clause matching call to catch_exception/1" =
+ comm_err(<<"catch_exception(1).">>),
+
+ %% A bug was corrected (expansion of 'try'):
+ ?line "2: command not found" =
+ comm_err(<<"try 1 of 1 -> v(2) after 3 end.">>),
+ %% Cover a few lines:
+ ?line "3: command not found" =
+ comm_err(<<"receive foo -> foo after 0 -> v(3) end.">>),
+ ?line "3: command not found" =
+ comm_err(<<"receive foo -> foo after 0 -> e(3) end.">>),
+ ?line "1 / 0: command not found" = comm_err(<<"v(1/0).">>),
+ ?line "1\n1.\n" = t(<<"1. e(1).">>),
+ ?line [ok] = scan(<<"h().">>),
+ ?line "exception exit: normal" = comm_err(<<"exit(normal).">>),
+ ?line [foo] = scan(<<"begin history(0), foo end.">>),
+ ?line application:unset_env(stdlib, shell_history_length),
+ ?line [true] = scan(<<"begin <<10:(1024*1024*10)>>,"
+ "<<10:(1024*1024*10)>>, garbage_collect() end.">>),
+ ?line "1: syntax error before: '.'" = comm_err("1-."),
+ %% ?line comm_err(<<"exit().">>), % would hang
+ ?line "exception error: no function clause matching call to history/1" =
+ comm_err(<<"history(foo).">>),
+ ?line "exception error: no function clause matching call to results/1" =
+ comm_err(<<"results(foo).">>),
+
+ ?line Test = filename:join(?config(priv_dir, Config),
+ "otp_6554.erl"),
+ Contents = <<"-module(otp_6554).
+ -export([local_allowed/3, non_local_allowed/3]).
+ local_allowed(_,_,State) ->
+ {true,State}.
+
+ non_local_allowed(_,_,State) ->
+ {true,State}.
+ ">>,
+ ?line ok = compile_file(Config, Test, Contents, []),
+ ?line "exception exit: restricted shell starts now" =
+ comm_err(<<"begin shell:start_restricted(otp_6554) end.">>),
+ ?line "-record(r,{}).\n1.\nok.\n" =
+ t(<<"f(), f(B), h(), b(), history(20), results(20),"
+ "rd(r, {}), rl(r), rf('_'), rl(), rf(),"
+ "rp(1), _ = rr({foo}), _ = rr({foo}, []),"
+ "rr({foo}, [], []), ok.">>),
+ ?line "false.\n" = t(<<"catch_exception(true).">>),
+ ?line "exception exit: restricted shell stopped"=
+ comm_err(<<"begin shell:stop_restricted() end.">>),
+ ?line "true.\n" = t(<<"catch_exception(false).">>),
+
+ ?line "20\n1\n1\n1: results(2)\n2: 1\n-> 1\n3: v(2)\n-> 1.\nok.\n" =
+ t(<<"results(2). 1. v(2). h().">>),
+ ?line application:unset_env(stdlib, shell_saved_results),
+ ?line "1\nfoo\n17\nB = foo\nC = 17\nF = fun() ->\n foo"
+ "\n end.\nok.\n" =
+ t(<<"begin F = fun() -> foo end, 1 end. B = F(). C = 17. b().">>),
+
+ %% Tests I'd like to do: (you should try them manually)
+ %% "catch spawn_link(fun() -> timer:sleep(1000), exit(foo) end)."
+ %% "** exception error: foo" should be output after 1 second
+ %% "catch spawn_link(fun() -> timer:sleep(1000), 1/0 end)."
+ %% "** exception error: bad argument..." should be output after 1 second
+ %% "1/0", "exit(foo)", "throw(foo)".
+ %% "h()." should show {'EXIT', {badarith,..}}, {'EXIT',{foo,...}},
+ %% and {'EXIT',{{nocatch,foo},...}}.
+
+ ok.
+
+otp_6785(doc) ->
+ "OTP-6785. Parameterized modules.";
+otp_6785(suite) -> [];
+otp_6785(Config) when is_list(Config) ->
+ MFile = filename:join(?config(priv_dir, Config), "parameterized.erl"),
+ Contents = <<"-module(parameterized, [A]). "
+ "-export([test/0]). "
+ "test() -> A. ">>,
+ ?line ok = compile_file(Config, MFile, Contents, []),
+ ?line (parameterized:new(adsf)):test(),
+ file:delete(MFile),
+ ok.
+
+otp_7184(doc) ->
+ "OTP-7184. Propagate exit signals from dying evaluator process.";
+otp_7184(suite) -> [];
+otp_7184(Config) when is_list(Config) ->
+ register(otp_7184, self()),
+ ?line catch
+ t(<<"P = self(),
+ spawn_link(fun() -> process_flag(trap_exit,true),
+ P ! up,
+ receive X ->
+ otp_7184 ! {otp_7184, X}
+ end
+ end),
+ receive up -> ok end,
+ erlang:raise(throw, thrown, []).">>),
+ receive {otp_7184,{'EXIT',_,{{nocatch,thrown},[]}}} -> ok end,
+
+ ?line catch
+ t(<<"P = self(),
+ spawn_link(fun() -> process_flag(trap_exit,true),
+ P ! up,
+ receive X ->
+ otp_7184 ! {otp_7184, X}
+ end
+ end),
+ receive up -> ok end,
+ erlang:raise(exit, fini, []).">>),
+ receive {otp_7184,{'EXIT',_,{fini,[]}}} -> ok end,
+
+ ?line catch
+ t(<<"P = self(),
+ spawn_link(fun() -> process_flag(trap_exit,true),
+ P ! up,
+ receive X ->
+ otp_7184 ! {otp_7184,X}
+ end
+ end),
+ receive up -> ok end,
+ erlang:raise(error, bad, []).">>),
+ receive {otp_7184,{'EXIT',_,{bad,[]}}} -> ok end,
+
+ unregister(otp_7184),
+
+ %% v/1, a few missed cases
+ ?line "17\n<<0,0,0,64>>.\nok.\n" =
+ t(<<"17. "
+ "<<64:32>>. "
+ "<<64>> = << << X >> || << X >> <= v(2), X > v(1) >>, ok.">>),
+
+ ?line "17\n<<0,17>>.\n" =t(<<"17. <<(v(1)):16>>.">>),
+
+ ok.
+
+otp_7232(doc) ->
+ "OTP-7232. qlc:info() bug.";
+otp_7232(suite) -> [];
+otp_7232(Config) when is_list(Config) ->
+ Info = <<"qlc:info(qlc:sort(qlc:q([X || X <- [1000,2000]]), "
+ "{order, fun(A,B)-> A>B end})).">>,
+ "qlc:sort([1000,2000],\n"
+ " [{order,\n"
+ " fun(A, B) ->\n"
+ " A > B\n"
+ " end}])" = evaluate(Info, []),
+ ok.
+
+-ifdef(not_used).
+exit_term(B) ->
+ "** exception exit:" ++ Reply = t(B),
+ S0 = string:left(Reply, string:chr(Reply, $\n)-1),
+ S = string:strip(S0, right, $*),
+ {ok,Ts,_} = erl_scan:string(S),
+ {ok,Term} = erl_parse:parse_term(Ts),
+ Term.
+-endif.
+
+error_string(B) ->
+ "** exception error:" ++ Reply = t(B),
+ caught_string(Reply).
+
+exit_string(B) ->
+ "** exception exit:" ++ Reply = t(B),
+ caught_string(Reply).
+
+caught_string(Reply) ->
+ S0 = string:left(Reply, string:chr(Reply, $\n)-1),
+ S1 = string:strip(S0, right, $.),
+ S2 = string:strip(S1, left, $*),
+ S = string:strip(S2, both, $ ),
+ string:strip(S, both, $").
+
+comm_err(B) ->
+ Reply = t(B),
+ S0 = string:left(Reply, string:chr(Reply, $\n)-1),
+ S1 = string:strip(S0, left, $*),
+ S2 = string:strip(S1, both, $ ),
+ S = string:strip(S2, both, $"),
+ string:strip(S, right, $.).
+
+scan(B) ->
+ F = fun(Ts) ->
+ case erl_parse:parse_term(Ts) of
+ {ok,Term} ->
+ Term;
+ _Error ->
+ {ok,Form} = erl_parse:parse_form(Ts),
+ Form
+ end
+ end,
+ scan(t(B), F).
+
+scan(S0, F) ->
+ case erl_scan:tokens([], S0, 1) of
+ {done,{ok,Ts,_},S} ->
+ [F(Ts) | scan(S, F)];
+ _Else ->
+ []
+ end.
+
+t({Node,Bin}) when is_atom(Node),is_binary(Bin) ->
+ t0(Bin, fun() -> start_new_shell(Node) end);
+t(Bin) when is_binary(Bin) ->
+ t0(Bin, fun() -> start_new_shell() end);
+t(L) ->
+ t(list_to_binary(L)).
+
+t0(Bin, F) ->
+ %% Spawn a process so that io_request messages do not interfer.
+ P = self(),
+ C = spawn(fun() -> t1(P, Bin, F) end),
+ receive {C, R} -> R end.
+
+t1(Parent, Bin, F) ->
+ %% io:format("*** Testing ~s~n", [binary_to_list(Bin)]),
+ S = #state{bin = Bin, reply = [], leader = group_leader()},
+ group_leader(self(), self()),
+ _Shell = F(),
+ try
+ server_loop(S)
+ catch exit:R -> Parent ! {self(), R};
+ throw:{?MODULE,LoopReply} ->
+ L0 = binary_to_list(list_to_binary(LoopReply)),
+ [$\n | L1] = lists:dropwhile(fun(X) -> X =/= $\n end, L0),
+ Parent ! {self(), dotify(L1)}
+ after group_leader(S#state.leader, self())
+ end.
+
+dotify([$., $\n | L]) ->
+ [$., $\n | dotify(L)];
+dotify([$,, $\n | L]) ->
+ [$,, $\n | dotify(L)];
+dotify("ok\n" ++ L) ->
+ "ok.\n" ++ dotify(L);
+dotify("\nok\n" ++ L) ->
+ ".\nok.\n" ++ dotify(L);
+dotify([$\n]) ->
+ [$., $\n];
+dotify([C | L]) ->
+ [C | dotify(L)];
+dotify([]) ->
+ [].
+
+start_new_shell() ->
+ Shell = shell:start(),
+ link(Shell),
+ Shell.
+
+start_new_shell(Node) ->
+ Shell = rpc:call(Node,shell,start,[]),
+ link(Shell),
+ Shell.
+
+%% This is a very minimal implementation of the IO protocol...
+
+server_loop(S) ->
+ receive
+ {io_request, From, ReplyAs, Request} when is_pid(From) ->
+ server_loop(do_io_request(Request, From, S, ReplyAs));
+ NotExpected ->
+ exit(NotExpected)
+ end.
+
+do_io_request(Req, From, S, ReplyAs) ->
+ case io_requests([Req], [], S) of
+ {_Status,{eof,_},S1} ->
+ io_reply(From, ReplyAs, {error,terminated}),
+ throw({?MODULE,S1#state.reply});
+ {_Status,Reply,S1} ->
+ io_reply(From, ReplyAs, Reply),
+ S1
+ end.
+
+io_reply(From, ReplyAs, Reply) ->
+ From ! {io_reply, ReplyAs, Reply}.
+
+io_requests([{requests, Rs1} | Rs], Cont, S) ->
+ io_requests(Rs1, [Rs | Cont], S);
+io_requests([R | Rs], Cont, S) ->
+ case io_request(R, S) of
+ {ok, ok, S1} ->
+ io_requests(Rs, Cont, S1);
+ Reply ->
+ Reply
+ end;
+io_requests([], [Rs|Cont], S) ->
+ io_requests(Rs, Cont, S);
+io_requests([], [], S) ->
+ {ok,ok,S}.
+
+io_request({get_geometry,columns}, S) ->
+ {ok,80,S};
+io_request({get_geometry,rows}, S) ->
+ {ok,24,S};
+io_request({put_chars,Chars}, S) ->
+ {ok,ok,S#state{reply = [S#state.reply | Chars]}};
+io_request({put_chars,_,Chars}, S) ->
+ {ok,ok,S#state{reply = [S#state.reply | Chars]}};
+io_request({put_chars,Mod,Func,Args}, S) ->
+ case catch apply(Mod, Func, Args) of
+ Chars when is_list(Chars) ->
+ io_request({put_chars,Chars}, S)
+ end;
+io_request({put_chars,Enc,Mod,Func,Args}, S) ->
+ case catch apply(Mod, Func, Args) of
+ Chars when is_list(Chars) ->
+ io_request({put_chars,Enc,Chars}, S)
+ end;
+io_request({get_until,_Prompt,Mod,Func,ExtraArgs}, S) ->
+ get_until(Mod, Func, ExtraArgs, S, latin1);
+io_request({get_until,Enc,_Prompt,Mod,Func,ExtraArgs}, S) ->
+ get_until(Mod, Func, ExtraArgs, S, Enc).
+
+get_until(Mod, Func, ExtraArgs, S, Enc) ->
+ get_until_loop(Mod, Func, ExtraArgs, S, {more,[]}, Enc).
+
+get_until_loop(M, F, As, S, {more,Cont}, Enc) ->
+ Bin = S#state.bin,
+ case byte_size(Bin) of
+ 0 ->
+ get_until_loop(M, F, As, S,
+ catch apply(M, F, [Cont,eof|As]), Enc);
+ _ ->
+ get_until_loop(M, F, As, S#state{bin = <<>>},
+ catch apply(M, F, [Cont,binary_to_list(Bin)|As]), Enc)
+ end;
+get_until_loop(_M, _F, _As, S, {done,Res,Buf}, Enc) ->
+ {ok,Res,S#state{bin = buf2bin(Buf, Enc)}};
+get_until_loop(_M, F, _As, S, _Other, _Enc) ->
+ {error,{error,F},S}.
+
+buf2bin(eof,_) ->
+ <<>>;
+buf2bin(Buf,latin1) ->
+ list_to_binary(Buf);
+buf2bin(Buf,unicode) ->
+ unicode:characters_to_binary(Buf,unicode,unicode).
+
+run_file(Config, Module, Test) ->
+ FileName = filename(lists:concat([Module, ".erl"]), Config),
+ BeamFile = filename(lists:concat([Module, ".beam"]), Config),
+ LoadBeamFile = filename(Module, Config),
+ ok = file:write_file(FileName, Test),
+ ok = compile_file(Config, FileName, Test, []),
+ code:purge(Module),
+ {module, Module} = code:load_abs(LoadBeamFile),
+ ok = Module:t(),
+ file:delete(FileName),
+ file:delete(BeamFile),
+ ok.
+
+compile_file(Config, File, Test, Opts0) ->
+ ?line Opts = [export_all,return,{outdir,?config(priv_dir, Config)}|Opts0],
+ ?line ok = file:write_file(File, Test),
+ ?line case compile:file(File, Opts) of
+ {ok, _M, _Ws} -> ok;
+ _ -> error
+ end.
+
+filename(Name, Config) when is_atom(Name) ->
+ filename(atom_to_list(Name), Config);
+filename(Name, Config) ->
+ filename:join(?config(priv_dir, Config), Name).
+
+start_node(Name, Xargs) ->
+ ?line N = test_server:start_node(Name, slave, [{args, " " ++ Xargs}]),
+ global:sync(),
+ N.
+
diff --git a/lib/stdlib/test/slave_SUITE.erl b/lib/stdlib/test/slave_SUITE.erl
new file mode 100644
index 0000000000..3b737af64d
--- /dev/null
+++ b/lib/stdlib/test/slave_SUITE.erl
@@ -0,0 +1,261 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(slave_SUITE).
+
+-include("test_server.hrl").
+
+-export([all/1, t_start/1, t_start_link/1,
+ start_link_nodedown/1, errors/1]).
+
+%% Internal exports.
+-export([fun_init/1, test_errors/1]).
+-export([timeout_test/1, auth_test/1, rsh_test/1, start_a_slave/3]).
+
+all(suite) ->
+ [t_start_link, start_link_nodedown, t_start, errors].
+
+t_start_link(suite) -> [];
+t_start_link(Config) when list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(20)),
+
+ %% Define useful variables.
+
+ ?line Host = host(),
+ ?line Slave1 = node_name(Host, slave1),
+ ?line Slave2 = node_name(Host, slave2),
+
+ %% Test slave:start_link() with one, two, and three arguments.
+
+ ?line ThisNode = node(),
+ ?line {error, {already_running, ThisNode}} = slave:start_link(Host),
+ ?line {ok, Slave1} = slave:start_link(Host, slave1),
+ ?line {ok, Slave2} = slave:start_link(Host, slave2, "-my_option 42"),
+ ?line {ok, [["42"]]} = rpc:call(Slave2, init, get_argument, [my_option]),
+
+ %% Kill the two slave nodes and verify that they are dead.
+
+ ?line rpc:cast(Slave1, erlang, halt, []),
+ ?line rpc:cast(Slave2, erlang, halt, []),
+ ?line is_dead(Slave1),
+ ?line is_dead(Slave2),
+
+ %% Start two slave nodes from another process and verify that
+ %% the slaves die when that process terminates.
+
+ Parent = self(),
+ Pid = fun_spawn(fun () ->
+ {ok, Slave1} = slave:start_link(Host, slave1),
+ {ok, Slave2} = slave:start_link(Host, slave2),
+ Parent ! slaves_started,
+ receive never -> ok end
+ end),
+ ?line receive slaves_started -> ok end,
+ ?line process_flag(trap_exit, true),
+ ?line wait_alive(Slave1),
+ ?line wait_alive(Slave2),
+ ?line exit(Pid, kill),
+ ?line receive {'EXIT', Pid, killed} -> ok end,
+ ?line test_server:sleep(250),
+ ?line is_dead(Slave1),
+ ?line is_dead(Slave2),
+
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+%% Test that slave:start_link() works when the master exits.
+
+start_link_nodedown(suite) -> [];
+start_link_nodedown(Config) when list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(20)),
+
+ %% Define useful variables.
+
+ ?line Host = host(),
+ ?line Master = node_name(Host, my_master),
+ ?line Slave = node_name(Host, my_slave),
+
+ ?line Pa = "-pa " ++ filename:dirname(code:which(?MODULE)),
+ ?line {ok, Master} = slave:start_link(Host, my_master, Pa),
+ ?line spawn(Master, ?MODULE, start_a_slave, [self(), Host, my_slave]),
+ ?line {reply, {ok, _Node}} = receive Any -> Any end,
+
+ ?line rpc:call(Master, erlang, halt, []),
+ ?line receive after 200 -> ok end,
+ ?line pang = net_adm:ping(Slave),
+
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+start_a_slave(ReplyTo, Host, Name) ->
+ ReplyTo ! {reply, slave:start_link(Host, Name)},
+ receive never -> ok end.
+
+%% Test slave:start().
+
+t_start(suite) -> [];
+t_start(Config) when list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(20)),
+
+ %% Define useful variables.
+
+ ?line Host = host(),
+ ?line Slave1 = node_name(Host, slave1),
+ ?line Slave2 = node_name(Host, slave2),
+
+ %% By running all tests from this master node which is linked
+ %% to this test case, we ensure that all slaves are killed
+ %% if this test case fails. (If they are not, and therefore further
+ %% test cases fail, there is a bug in slave.)
+
+ ?line {ok, Master} = slave:start_link(Host, master),
+
+ %% Test slave:start() with one, two, and three arguments.
+
+ ?line ThisNode = node(),
+ ?line {error, {already_running, ThisNode}} = slave:start(Host),
+ ?line {ok, Slave1} = rpc:call(Master, slave, start, [Host, slave1]),
+ ?line {ok, Slave2} = rpc:call(Master, slave, start,
+ [Host, slave2, "-my_option 42"]),
+ ?line {ok, [["42"]]} = rpc:call(Slave2, init, get_argument, [my_option]),
+
+ %% Test that a slave terminates when its master node terminates.
+
+ ?line ok = slave:stop(Slave2),
+ ?line is_dead(Slave2),
+ ?line {ok, Slave2} = rpc:call(Slave1, slave, start, [Host, slave2]),
+ ?line is_alive(Slave2),
+ ?line rpc:call(Slave1, erlang, halt, []), % Kill master.
+ receive after 1000 -> ok end, % Make sure slaves have noticed
+ % their dead master.
+ ?line is_dead(Slave1),
+ ?line is_dead(Slave2), % Slave should be dead, too.
+
+ %% Kill all slaves and verify that they are dead.
+
+ ?line ok = slave:stop(Slave1),
+ ?line ok = slave:stop(Slave2),
+ ?line is_dead(Slave1),
+ ?line is_dead(Slave2),
+
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+%% Test the various error conditions in parallell (since the timeout
+%% in slave is 32 seconds).
+
+errors(suite) -> [];
+errors(Config) when list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(50)),
+
+ ?line process_flag(trap_exit, true),
+ ?line Pa = filename:dirname(code:which(?MODULE)),
+ ?line {ok, Master} = slave_start_link(host(), master,
+ "-rsh no_rsh_program -pa "++Pa++
+ " -env ERL_CRASH_DUMP erl_crash_dump.master"),
+ ?line Pids = rpc:call(Master, ?MODULE, test_errors, [self()]),
+ ?line wait_for_result(Pids),
+
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+wait_for_result([]) ->
+ ok;
+wait_for_result(Pids) ->
+ ?line receive
+ {'EXIT', Pid, normal} ->
+ io:format("Process ~p terminated", [Pid]),
+ wait_for_result(lists:delete(Pid, Pids));
+ {'EXIT', _, Reason} ->
+ exit(Reason)
+ end.
+
+show_process_info(Pid) ->
+ io:format("~p: ~p", [Pid, catch process_info(Pid, initial_call)]).
+
+test_errors(ResultTo) ->
+ %% Sigh! We use ordinary spawn instead of fun_spawn/1 to be able
+ %% identify the processes by their initial call.
+ ?line P1 = spawn(?MODULE, timeout_test, [ResultTo]),
+ ?line P2 = spawn(?MODULE, auth_test, [ResultTo]),
+ ?line P3 = spawn(?MODULE, rsh_test, [ResultTo]),
+ Pids =[P1, P2, P3],
+ ?line lists:foreach(fun show_process_info/1, Pids),
+ Pids.
+
+timeout_test(ResultTo) ->
+ link(ResultTo),
+ ?line {error, timeout} = slave:start(host(), slave1, "-boot no_boot_script").
+
+auth_test(ResultTo) ->
+ link(ResultTo),
+ ?line {error, timeout} = slave:start(host(), slave2,
+ "-setcookie definitely_not_a_cookie").
+
+rsh_test(ResultTo) ->
+ link(ResultTo),
+ ?line {error, no_rsh} = slave:start(super, slave3).
+
+
+%%% Utilities.
+
+
+wait_alive(Node) ->
+ wait_alive_1(10, Node).
+
+wait_alive_1(0, Node) ->
+ ?t:fail({still_not_alive,Node});
+wait_alive_1(N, Node) ->
+ case rpc:call(Node, init, get_status, []) of
+ {started,_} ->
+ ok;
+ {starting,_} ->
+ receive after 1 -> ok end,
+ wait_alive_1(N-1, Node)
+ end.
+
+is_alive(Node) ->
+ {started, _} = rpc:call(Node, init, get_status, []).
+
+is_dead(Node) ->
+ {badrpc, nodedown} = rpc:call(Node, init, get_status, []).
+
+node_name(Host, Name) ->
+ list_to_atom(lists:concat([Name, "@", Host])).
+
+host() ->
+ from($@, atom_to_list(node())).
+
+from(H, [H | T]) -> T;
+from(H, [_ | T]) -> from(H, T);
+from(_H, []) -> [].
+
+slave_start_link(Host, Name, Args) ->
+ case slave:start_link(Host, Name, Args) of
+ {ok, Node} ->
+ {ok, Node};
+ Other ->
+ io:format("slave:start_link(~p, ~p, ~p) -> ~p",
+ [Host, Name, Args, Other])
+ end.
+
+fun_spawn(Fun) ->
+ spawn_link(?MODULE, fun_init, [Fun]).
+
+fun_init(Fun) ->
+ Fun().
diff --git a/lib/stdlib/test/sofs_SUITE.erl b/lib/stdlib/test/sofs_SUITE.erl
new file mode 100644
index 0000000000..0849e0f59c
--- /dev/null
+++ b/lib/stdlib/test/sofs_SUITE.erl
@@ -0,0 +1,2374 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(sofs_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), foo).
+-define(t, test_server).
+-else.
+-include("test_server.hrl").
+-define(format(S, A), ok).
+-endif.
+
+-export([all/1]).
+
+-export([sofs/1, from_term_1/1, set_1/1, from_sets_1/1, relation_1/1,
+ a_function_1/1, family_1/1, projection/1,
+ relation_to_family_1/1, domain_1/1, range_1/1, image/1,
+ inverse_image/1, inverse_1/1, converse_1/1, no_elements_1/1,
+ substitution/1, restriction/1, drestriction/1,
+ strict_relation_1/1, extension/1, weak_relation_1/1,
+ to_sets_1/1, specification/1, union_1/1, intersection_1/1,
+ difference/1, symdiff/1, symmetric_partition/1,
+ is_sofs_set_1/1, is_set_1/1, is_equal/1, is_subset/1,
+ is_a_function_1/1, is_disjoint/1, join/1, canonical/1,
+ composite_1/1, relative_product_1/1, relative_product_2/1,
+ product_1/1, partition_1/1, partition_3/1,
+ multiple_relative_product/1, digraph/1, constant_function/1,
+ misc/1]).
+
+-export([sofs_family/1, family_specification/1,
+ family_domain_1/1, family_range_1/1,
+ family_to_relation_1/1,
+ union_of_family_1/1, intersection_of_family_1/1,
+ family_projection/1, family_difference/1,
+ family_intersection_1/1, family_union_1/1,
+ family_intersection_2/1, family_union_2/1,
+ partition_family/1]).
+
+-import(sofs,
+ [a_function/1, a_function/2, constant_function/2,
+ canonical_relation/1, composite/2,
+ converse/1, extension/3, from_term/1, from_term/2,
+ difference/2, domain/1, empty_set/0, family_difference/2,
+ family_intersection/1, family_intersection/2, family_union/1,
+ family_union/2, family/1, family/2, family_specification/2,
+ family_domain/1, family_range/1, family_field/1,
+ family_projection/2, family_to_relation/1, union_of_family/1,
+ field/1, from_external/2, image/2, intersection/1,
+ intersection/2, intersection_of_family/1, inverse/1,
+ inverse_image/2, is_disjoint/2, is_empty_set/1, is_equal/2,
+ is_a_function/1, is_set/1, is_sofs_set/1, is_subset/2,
+ join/4, from_sets/1, multiple_relative_product/2,
+ no_elements/1, partition/1, partition/2, partition/3,
+ partition_family/2, product/1, product/2, projection/2,
+ range/1, relation/1, relation/2, relation_to_family/1,
+ relative_product/1, relative_product/2, relative_product1/2,
+ strict_relation/1, weak_relation/1, restriction/2,
+ restriction/3, drestriction/2, drestriction/3, to_sets/1,
+ is_type/1, set/1, set/2, specification/2, substitution/2,
+ symdiff/2, symmetric_partition/2, to_external/1, type/1,
+ union/1, union/2, family_to_digraph/1, family_to_digraph/2,
+ digraph_to_family/1, digraph_to_family/2]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+-compile({inline,[{eval,2}]}).
+
+all(suite) ->
+ [sofs, sofs_family].
+
+init_per_testcase(_Case, Config) ->
+ Dog=?t:timetrap(?t:minutes(2)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%% [{2,b},{1,a,b}] == lists:sort([{2,b},{1,a,b}])
+%% [{1,a,b},{2,b}] == lists:keysort(1,[{2,b},{1,a,b}])
+
+sofs(suite) ->
+ [from_term_1, set_1, from_sets_1, relation_1, a_function_1,
+ family_1, relation_to_family_1, domain_1, range_1, image,
+ inverse_image, inverse_1, converse_1, no_elements_1,
+ substitution, restriction, drestriction, projection,
+ strict_relation_1, extension, weak_relation_1, to_sets_1,
+ specification, union_1, intersection_1, difference, symdiff,
+ symmetric_partition, is_sofs_set_1, is_set_1, is_equal,
+ is_subset, is_a_function_1, is_disjoint, join, canonical,
+ composite_1, relative_product_1, relative_product_2, product_1,
+ partition_1, partition_3, multiple_relative_product, digraph,
+ constant_function, misc].
+
+from_term_1(suite) -> [];
+from_term_1(doc) -> [""];
+from_term_1(Conf) when list(Conf) ->
+ %% would go wrong: projection(1,from_term([{2,b},{1,a,b}])),
+
+ ?line {'EXIT', {badarg, _}} = (catch from_term([], {atom,'_',atom})),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([], [])),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([], [atom,atom])),
+
+ ?line [] = to_external(from_term([])),
+ ?line eval(from_term([]), empty_set()),
+ ?line [] = to_external(from_term([], ['_'])),
+ ?line eval(from_term([], ['_']), empty_set()),
+ ?line [[]] = to_external(from_term([[]])),
+ ?line [[['_']]] = type(from_term([[],[[]]])),
+ ?line [[],[[]]] = to_external(from_term([[],[[]]])),
+ ?line [[['_']]] = type(from_term([[],[[]]])),
+ ?line eval(from_term([a],['_']), set([a])),
+ ?line [[],[a]] = to_external(from_term([[],[a]])),
+ ?line [[],[{a}]] = to_external(from_term([[{a}],[]])),
+ ?line [{[],[{a,b,[d]}]},{[{a,b}],[]}] =
+ to_external(from_term([{[],[{a,b,[d]}]},{[{a,b}],[]}])),
+
+ ?line [{[a,b],[c,d]}] = to_external(from_term([{[a,b],[c,d]}])),
+ ?line [{{a,b},[a,b],{{a},{b}}}] =
+ to_external(from_term([{{a,b},[a,b],{{a},{b}}}])),
+ ?line [{{a,{[a,b]},a}},{{z,{[y,z]},z}}] =
+ to_external(from_term([{{a,{[a,b,a]},a}},{{z,{[y,y,z]},z}}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch from_term([{m1,[{m1,f1,1},{m1,f2,2}]},{m2,[]},{m3,[a]}])),
+ ?line MS1 = [{m1,[{m1,f1,1},{m1,f2,2}]},{m2,[]},{m3,[{m3,f3,3}]}],
+ ?line eval(to_external(from_term(MS1)), MS1),
+
+ ?line eval(to_external(from_term(a)), a),
+ ?line eval(to_external(from_term({a})), {a}),
+
+ ?line eval(to_external(from_term([[a],[{b,c}]],[[atomic]])),
+ [[a],[{b,c}]]),
+ ?line eval(type(from_term([[a],[{b,c}]],[[atomic]])),
+ [[atomic]]),
+
+ ?line {'EXIT', {badarg, _}} = (catch from_term([[],[],a])),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([{[a,b],[c,{d}]}])),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([[],[a],[{a}]])),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([a,{a,b}])),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([[a],[{b,c}]],[['_']])),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([a | {a,b}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch from_term([{{a},b,c},{d,e,f}],[{{atom},atom,atom}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch from_term([{a,{b,c}} | tail], [{atom,{atom,atom}}])),
+ ?line {'EXIT', {badarg, _}} = (catch from_term({})),
+ ?line {'EXIT', {badarg, _}} = (catch from_term([{}])),
+
+ ?line [{foo,bar},[b,a]] =
+ to_external(from_term([[b,a],{foo,bar},[b,a]], [atom])),
+ ?line [{[atom],{atom,atom}}] =
+ type(from_term([{[], {a,b}},{[a,b],{e,f}}])),
+ ?line [{[atom],{atom,atom}}] =
+ type(from_term([{[], {a,b}},{[a,b],{e,f}}], [{[atom],{atom,atom}}])),
+ ?line [[atom]] = type(from_term([[a],[{b,c}]],[[atom]])),
+
+ ?line {atom, atom} = type(from_term({a,b}, {atom, atom})),
+ ?line atom = type(from_term(a, atom)),
+ ?line {'EXIT', {badarg, _}} = (catch from_term({a,b},{atom})),
+ ?line [{{a},b,c},{{d},e,f}] =
+ to_external(from_term([{{a},b,c},{{a},b,c},{{d},e,f}],
+ [{{atom},atom,atom}])),
+
+ %% from_external too...
+ ?line e = to_external(from_external(e, atom)),
+ ?line {e} = to_external(from_external({e}, {atom})),
+ ?line [e] = to_external(from_external([e], [atom])),
+
+ %% and is_type...
+ ?line true = is_type(['_']),
+ ?line false = is_type('_'),
+ ?line true = is_type([['_']]),
+ ?line false = is_type({atom,[],atom}),
+ ?line false = is_type({atom,'_',atom}),
+ ?line true = is_type({atom,atomic,atom}),
+ ?line true = is_type({atom,atom}),
+ ?line true = is_type(atom),
+ ?line true = is_type([atom]),
+ ?line true = is_type(type),
+
+ ok.
+
+set_1(suite) -> [];
+set_1(doc) -> [""];
+set_1(Conf) when list(Conf) ->
+ %% set/1
+ ?line {'EXIT', {badarg, _}} = (catch set(a)),
+ ?line {'EXIT', {badarg, _}} = (catch set({a})),
+ ?line eval(set([]), from_term([],[atom])),
+ ?line eval(set([a,b,c]), from_term([a,b,c])),
+ ?line eval(set([a,b,a,a,b]), from_term([a,b])),
+ ?line eval(set([a,b,c,a,d,d,c,1]), from_term([1,a,b,c,d])),
+ ?line eval(set([a,b,d,a,c]), from_term([a,b,c,d])),
+ ?line eval(set([f,e,d,c,d]), from_term([c,d,e,f])),
+ ?line eval(set([h,f,d,g,g,d,c]), from_term([c,d,f,g,h])),
+ ?line eval(set([h,e,d,k,l]), from_term([d,e,h,k,l])),
+ ?line eval(set([h,e,c,k,d]), from_term([c,d,e,h,k])),
+
+ %% set/2
+ ?line {'EXIT', {badarg, _}} = (catch set(a, [a])),
+ ?line {'EXIT', {badarg, _}} = (catch set({a}, [a])),
+ ?line {'EXIT', {badarg, _}} = (catch set([a], {a})),
+ ?line {'EXIT', {badarg, _}} = (catch set([a], a)),
+ ?line {'EXIT', {badarg, _}} = (catch set([a], [a,b])),
+ ?line {'EXIT', {badarg, _}} = (catch set([a | b],[foo])),
+ ?line {'EXIT', {badarg, _}} = (catch set([a | b],['_'])),
+ ?line {'EXIT', {badarg, _}} = (catch set([a | b],[[atom]])),
+ ?line {'EXIT', {badarg, _}} = (catch set([{}],[{}])),
+ ?line eval(set([a],['_']), from_term([a],['_'])),
+ ?line eval(set([], ['_']), empty_set()),
+ ?line eval(set([a,b,a,b],[foo]), from_term([a,b],[foo])),
+
+ ok.
+
+from_sets_1(suite) -> [];
+from_sets_1(doc) -> [""];
+from_sets_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+
+ %% unordered
+ ?line eval(from_sets([]), E),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch from_sets([from_term([{a,b}]),
+ E,
+ from_term([{a,b,c}])])),
+ ?line eval(from_sets([from_term([{a,b}]), E]),
+ from_term([[],[{a,b}]])),
+
+ ?line eval(from_sets([from_term({a,b},{atom,atom}),
+ from_term({b,c},{atom,atom})]),
+ relation([{a,b}, {b,c}])),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch from_sets([from_term({a,b},{atom,atom}),
+ from_term({a,b,c},{atom,atom,atom})])),
+ ?line {'EXIT', {badarg, _}} = (catch from_sets(foo)),
+ ?line eval(from_sets([E]), from_term([[]])),
+ ?line eval(from_sets([E,E]), from_term([[]])),
+ ?line eval(from_sets([E,set([a])]), from_term([[],[a]])),
+ ?line {'EXIT', {badarg, _}} = (catch from_sets([E,{a}])),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch from_sets([E,from_term({a}),E])),
+ ?line {'EXIT', {type_mismatch, _}} = (catch from_sets([from_term({a}),E])),
+
+ %% ordered
+ ?line O = {from_term(a,atom), from_term({b}, {atom}), set([c,d])},
+ ?line eval(from_sets(O), from_term({a,{b},[c,d]}, {atom,{atom},[atom]})),
+ ?line {'EXIT', {badarg, _}} = (catch from_sets([a,b])),
+ ?line {'EXIT', {badarg, _}} = (catch from_sets({a,b})),
+ ?line eval(from_sets({from_term({a}),E}), from_term({{a},[]})),
+ ok.
+
+relation_1(suite) -> [];
+relation_1(doc) -> [""];
+relation_1(Conf) when list(Conf) ->
+ %% relation/1
+ ?line eval(relation([]), from_term([], [{atom,atom}])),
+ ?line eval(from_term([{a}]), relation([{a}])),
+ ?line {'EXIT', {badarg, _}} = (catch relation(a)),
+ ?line {'EXIT', {badarg, _}} = (catch relation([{a} | a])),
+ ?line {'EXIT', {badarg, _}} = (catch relation([{}])),
+ ?line {'EXIT', {badarg, _}} = (catch relation([],0)),
+ ?line {'EXIT', {badarg, _}} = (catch relation([{a}],a)),
+
+ %% relation/2
+ ?line eval(relation([{a},{b}], 1), from_term([{a},{b}])),
+ ?line eval(relation([{1,a},{2,b},{1,a}], [{x,y}]),
+ from_term([{1,a},{2,b}], [{x,y}])),
+ ?line eval(relation([{[1,2],a},{[2,1],b},{[2,1],a}], [{[x],y}]),
+ from_term([{[1,2],a},{[1,2],b}], [{[x],y}])),
+ ?line {'EXIT', {badarg, _}} = (catch relation([{1,a},{2,b}], [{[x],y}])),
+ ?line {'EXIT', {badarg, _}} = (catch relation([{1,a},{1,a,b}], [{x,y}])),
+ ?line {'EXIT', {badarg, _}} = (catch relation([{a}], 2)),
+ ?line {'EXIT', {badarg, _}} = (catch relation([{a},{b},{c,d}], 1)),
+ ?line eval(relation([{{a},[{foo,bar}]}], ['_']),
+ from_term([{{a},[{foo,bar}]}], ['_'])),
+ ?line eval(relation([], ['_']), from_term([], ['_'])),
+ ?line {'EXIT', {badarg, _}} = (catch relation([[a]],['_'])),
+ ?line eval(relation([{[a,b,a]}], [{[atom]}]), from_term([{[a,b,a]}])),
+ ?line eval(relation([{[a,b,a],[[d,e,d]]}], [{[atom],[[atom]]}]),
+ from_term([{[a,b,a],[[d,e,d]]}])),
+ ?line eval(relation([{[a,b,a],[[d,e,d]]}], [{atom,[[atom]]}]),
+ from_term([{[a,b,a],[[d,e,d]]}], [{atom,[[atom]]}])),
+ ok.
+
+a_function_1(suite) -> [];
+a_function_1(doc) -> [""];
+a_function_1(Conf) when list(Conf) ->
+ %% a_function/1
+ ?line eval(a_function([]), from_term([], [{atom,atom}])),
+ ?line eval(a_function([{a,b},{a,b},{b,c}]), from_term([{a,b},{b,c}])),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a}])),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a},{b},{c,d}])),
+ ?line {'EXIT', {badarg, _}} = (catch a_function(a)),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a,b} | a])),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch a_function([{a,b},{b,c},{a,c}])),
+ F = 0.0, I = round(F),
+ if
+ F == I -> % term ordering
+ ?line {'EXIT', {bad_function, _}} =
+ (catch a_function([{I,a},{F,b}])),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch a_function([{[I],a},{[F],b}],[{[a],b}]));
+ true ->
+ ?line 2 = no_elements(a_function([{I,a},{F,b}])),
+ ?line 2 = no_elements(a_function([{[I],a},{[F],b}],[{[a],b}]))
+ end,
+
+ %% a_function/2
+ FT = [{atom,atom}],
+ ?line eval(a_function([], FT), from_term([], FT)),
+ ?line eval(a_function([{a,b},{b,c},{b,c}], FT),
+ from_term([{a,b},{b,c}], FT)),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a,b}], [{a}])),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a,b}], [{a,[b,c]}])),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a}], FT)),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a},{b},{c,d}], FT)),
+ ?line {'EXIT', {badarg, _}} = (catch a_function(a, FT)),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([{a,b} | a], FT)),
+ ?line eval(a_function([{{a},[{foo,bar}]}], ['_']),
+ from_term([{{a},[{foo,bar}]}], ['_'])),
+ ?line eval(a_function([], ['_']), from_term([], ['_'])),
+ ?line {'EXIT', {badarg, _}} = (catch a_function([[a]],['_'])),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch a_function([{a,b},{b,c},{a,c}], FT)),
+ ?line eval(a_function([{a,[a]},{a,[a,a]}], [{atom,[atom]}]),
+ from_term([{a,[a]}])),
+ ?line eval(a_function([{[b,a],c},{[a,b],c}], [{[atom],atom}]),
+ from_term([{[a,b],c}])),
+ ok.
+
+family_1(suite) -> [];
+family_1(doc) -> [""];
+family_1(Conf) when list(Conf) ->
+ %% family/1
+ ?line eval(family([]), from_term([],[{atom,[atom]}])),
+ ?line {'EXIT', {badarg, _}} = (catch family(a)),
+ ?line {'EXIT', {badarg, _}} = (catch family([a])),
+ ?line {'EXIT', {badarg, _}} = (catch family([{a,b}])),
+ ?line {'EXIT', {badarg, _}} = (catch family([{a,[]} | a])),
+ ?line {'EXIT', {badarg, _}} = (catch family([{a,[a|b]}])),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch family([{a,[a]},{a,[]}])),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch family([{a,[]},{b,[]},{a,[a]}])),
+ F = 0.0, I = round(F),
+ if
+ F == I -> % term ordering
+ ?line {'EXIT', {bad_function, _}} =
+ (catch family([{I,[a]},{F,[b]}])),
+ ?line true = (1 =:= no_elements(family([{a,[I]},{a,[F]}])));
+ true ->
+ ?line {'EXIT', {bad_function, _}} =
+ (catch family([{a,[I]},{a,[F]}]))
+ end,
+ ?line eval(family([{a,[]},{b,[b]},{a,[]}]), from_term([{a,[]},{b,[b]}])),
+ ?line eval(to_external(family([{b,[{hej,san},tjo]},{a,[]}])),
+ [{a,[]},{b,[tjo,{hej,san}]}]),
+ ?line eval(family([{a,[a]},{a,[a,a]}]), family([{a,[a]}])),
+
+ %% family/2
+ FT = [{a,[a]}],
+ ?line eval(family([], FT), from_term([],FT)),
+ ?line {'EXIT', {badarg, _}} = (catch family(a,FT)),
+ ?line {'EXIT', {badarg, _}} = (catch family([a],FT)),
+ ?line {'EXIT', {badarg, _}} = (catch family([{a,b}],FT)),
+ ?line {'EXIT', {badarg, _}} = (catch family([{a,[]} | a],FT)),
+ ?line {'EXIT', {badarg, _}} = (catch family([{a,[a|b]}], FT)),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch family([{a,[a]},{a,[]}], FT)),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch family([{a,[]},{b,[]},{a,[a]}], FT)),
+ ?line eval(family([{a,[]},{b,[b,b]},{a,[]}], FT),
+ from_term([{a,[]},{b,[b]}], FT)),
+ ?line eval(to_external(family([{b,[{hej,san},tjo]},{a,[]}], FT)),
+ [{a,[]},{b,[tjo,{hej,san}]}]),
+
+ ?line eval(family([{{a},[{foo,bar}]}], ['_']),
+ from_term([{{a},[{foo,bar}]}], ['_'])),
+ ?line eval(family([], ['_']), from_term([], ['_'])),
+ ?line {'EXIT', {badarg, _}} = (catch family([[a]],['_'])),
+ ?line {'EXIT', {badarg, _}} = (catch family([{a,b}],['_'])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family([{a,[foo]}], [{atom,atom}])),
+ ?line eval(family([{{a},[{foo,bar}]}], [{{dt},[{r1,t2}]}]),
+ from_term([{{a},[{foo,bar}]}], [{{dt},[{r1,t2}]}])),
+ ?line eval(family([{a,[a]},{a,[a,a]}],[{atom,[atom]}]),
+ family([{a,[a]}])),
+ ?line eval(family([{[a,b],[a]},{[b,a],[a,a]}],[{[atom],[atom]}]),
+ from_term([{[a,b],[a]},{[b,a],[a,a]}])),
+ ok.
+
+projection(suite) -> [];
+projection(doc) -> [""];
+projection(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+
+ %% set of ordered sets
+ ?line S1 = relation([{a,1},{b,2},{b,22},{c,0}]),
+ ?line S2 = relation([{a,1},{a,2},{a,3},{b,4},{b,5},{b,6}]),
+
+ ?line eval(projection(1, E), E),
+ ?line eval(projection(1, ER), set([])),
+ ?line eval(projection(1, relation([{a,1}])), set([a])),
+ ?line eval(projection(1, S1), set([a,b,c])),
+ ?line eval(projection(1, S2), set([a,b])),
+ ?line eval(projection(2, S1), set([0,1,2,22])),
+ ?line eval(projection(2, relation([{1,a},{2,a},{3,b}])), set([a,b])),
+ ?line eval(projection(1, relation([{a},{b},{c}])), set([a,b,c])),
+
+ Fun1 = {external, fun({A,B,C}) -> {A,{B,C}} end},
+ ?line eval(projection(Fun1, E), E),
+ %% No check here:
+ ?line eval(projection(3, projection(Fun1, empty_set())), E),
+ ?line E2 = relation([], 3),
+ ?line eval(projection(Fun1, E2), from_term([], [{atom,{atom,atom}}])),
+
+ Fun2 = {external, fun({A,_B}) -> {A} end},
+ ?line eval(projection(Fun2, ER), from_term([], [{atom}])),
+ ?line eval(projection(Fun2, relation([{a,1}])), relation([{a}])),
+ ?line eval(projection(Fun2, relation([{a,1},{b,3},{a,2}])),
+ relation([{a},{b}])),
+ Fun3 = {external, fun({A,_B,C}) -> {C,{A},C} end},
+ ?line eval(projection(Fun3, relation([{a,1,x},{b,3,y},{a,2,z}])),
+ from_term([{x,{a},x},{y,{b},y},{z,{a},z}])),
+ Fun4 = {external, fun(A={B,_C,_D}) -> {B, A} end},
+ ?line eval(projection(Fun4, relation([{a,1,x},{b,3,y},{a,2,z}])),
+ from_term([{a,{a,1,x}},{b,{b,3,y}},{a,{a,2,z}}])),
+
+ ?line eval(projection({external, fun({A,B,_C,D}) -> {A,B,A,D} end},
+ relation([{1,1,1,2}, {1,1,3,1}])),
+ relation([{1,1,1,1}, {1,1,1,2}])),
+
+ ?line {'EXIT', {badarg, _}} = (catch projection(1, set([]))),
+ ?line {'EXIT', {function_clause, _}} =
+ (catch projection({external, fun({A}) -> A end}, S1)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch projection({external, fun({A,_}) -> {A,0} end},
+ from_term([{1,a}]))),
+
+ %% {} is not an ordered set
+ ?line {'EXIT', {badarg, _}} =
+ (catch projection({external, fun(_) -> {} end}, ER)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch projection({external, fun(_) -> {{}} end}, ER)),
+ ?line eval(projection({external, fun({T,_}) -> T end},
+ relation([{{},a},{{},b}])),
+ set([{}])),
+ ?line eval(projection({external, fun({T}) -> T end}, relation([{{}}])),
+ set([{}])),
+
+ ?line eval(projection({external, fun(A) -> {A} end},
+ relation([{1,a},{2,b}])),
+ from_term([{{1,a}},{{2,b}}])),
+ ?line eval(projection({external, fun({A,B}) -> {B,A} end},
+ relation([{1,a},{2,b}])),
+ relation([{a,1},{b,2}])),
+ ?line eval(projection({external, fun(X=Y=A) -> {X,Y,A} end}, set([a,b,c])),
+ relation([{a,a,a},{b,b,b},{c,c,c}])),
+
+ ?line eval(projection({external, fun({A,{_},B}) -> {A,B} end},
+ from_term([{a,{a},b},{a,{b},c}])),
+ relation([{a,b},{a,c}])),
+ ?line eval(projection({external, fun({A,_,B}) -> {A,B} end},
+ relation([{a,{},b},{a,{},c}])),
+ relation([{a,b},{a,c}])),
+ Fun5 = fun(S) -> from_term({to_external(S),0}, {type(S),atom}) end,
+ ?line eval(projection(Fun5, E), E),
+ ?line eval(projection(Fun5, set([a,b])), from_term([{a,0},{b,0}])),
+ ?line eval(projection(Fun5, relation([{a,1},{b,2}])),
+ from_term([{{a,1},0},{{b,2},0}])),
+ ?line eval(projection(Fun5, from_term([[a],[b]])),
+ from_term([{[a],0},{[b],0}])),
+
+ F = 0.0, I = round(F),
+ ?line FR = relation([{I},{F}]),
+ if
+ F == I -> % term ordering
+ true = (no_elements(projection(1, FR)) =:= 1);
+ true ->
+ eval(projection(1, FR), set([I,F]))
+ end,
+
+ %% set of sets
+ ?line {'EXIT', {badarg, _}} =
+ (catch projection({external, fun(X) -> X end},
+ from_term([], [[atom]]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch projection({external, fun(X) -> X end}, from_term([[a]]))),
+ ?line eval(projection({sofs,union},
+ from_term([[[1,2],[2,3]], [[a,b],[b,c]]])),
+ from_term([[1,2,3], [a,b,c]])),
+ ?line eval(projection(fun(_) -> from_term([a]) end,
+ from_term([[b]], [[a]])),
+ from_term([[a]])),
+ ?line eval(projection(fun(_) -> from_term([a]) end,
+ from_term([[1,2],[3,4]])),
+ from_term([[a]])),
+ Fun10 = fun(S) ->
+ %% Cheating a lot...
+ case to_external(S) of
+ [1] -> from_term({1,1});
+ _ -> S
+ end
+ end,
+ ?line eval(projection(Fun10, from_term([[1]])), from_term([{1,1}])),
+ ?line eval(projection(fun(_) -> from_term({a}) end, from_term([[a]])),
+ from_term([{a}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch projection(fun(_) -> {a} end, from_term([[a]]))),
+
+ ok.
+
+substitution(suite) -> [];
+substitution(doc) -> [""];
+substitution(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+
+ %% set of ordered sets
+ ?line S1 = relation([{a,1},{b,2},{b,22},{c,0}]),
+ ?line S2 = relation([{a,1},{a,2},{a,3},{b,4},{b,5},{b,6}]),
+
+ ?line eval(substitution(1, E), E),
+ %% No check here:
+ Fun0 = {external, fun({A,B,C}) -> {A,{B,C}} end},
+ ?line eval(substitution(3, substitution(Fun0, empty_set())), E),
+ ?line eval(substitution(1, ER), from_term([],[{{atom,atom},atom}])),
+ ?line eval(substitution(1, relation([{a,1}])), from_term([{{a,1},a}])),
+ ?line eval(substitution(1, S1),
+ from_term([{{a,1},a},{{b,2},b},{{b,22},b},{{c,0},c}])),
+ ?line eval(substitution(1, S2),
+ from_term([{{a,1},a},{{a,2},a},{{a,3},a},{{b,4},b},
+ {{b,5},b},{{b,6},b}])),
+ ?line eval(substitution(2, S1),
+ from_term([{{a,1},1},{{b,2},2},{{b,22},22},{{c,0},0}])),
+
+ Fun1 = fun({A,_B}) -> {A} end,
+ XFun1 = {external, Fun1},
+ ?line eval(substitution(XFun1, E), E),
+ ?line eval(substitution(Fun1, E), E),
+ ?line eval(substitution(XFun1, ER), from_term([], [{{atom,atom},{atom}}])),
+ ?line eval(substitution(XFun1, relation([{a,1}])),
+ from_term([{{a,1},{a}}])),
+ ?line eval(substitution(XFun1, relation([{a,1},{b,3},{a,2}])),
+ from_term([{{a,1},{a}},{{a,2},{a}},{{b,3},{b}}])),
+ ?line eval(substitution({external, fun({A,_B,C}) -> {C,A,C} end},
+ relation([{a,1,x},{b,3,y},{a,2,z}])),
+ from_term([{{a,1,x},{x,a,x}},{{a,2,z},{z,a,z}},
+ {{b,3,y},{y,b,y}}])),
+ Fun2 = fun(S) -> {A,_B} = to_external(S), from_term({A}) end,
+ ?line eval(substitution(Fun2, ER), E),
+ ?line eval(substitution(Fun2, relation([{a,1}])),
+ from_term([{{a,1},{a}}])),
+ Fun3 = fun(S) -> from_term({to_external(S),0}, {type(S),atom}) end,
+ ?line eval(substitution(Fun3, E), E),
+ ?line eval(substitution(Fun3, set([a,b])),
+ from_term([{a,{a,0}},{b,{b,0}}])),
+ ?line eval(substitution(Fun3, relation([{a,1},{b,2}])),
+ from_term([{{a,1},{{a,1},0}},{{b,2},{{b,2},0}}])),
+ ?line eval(substitution(Fun3, from_term([[a],[b]])),
+ from_term([{[a],{[a],0}},{[b],{[b],0}}])),
+
+ ?line eval(substitution(fun(_) -> E end, from_term([[a],[b]])),
+ from_term([{[a],[]},{[b],[]}])),
+
+ ?line {'EXIT', {badarg, _}} = (catch substitution(1, set([]))),
+ ?line eval(substitution(1, ER), from_term([], [{{atom,atom},atom}])),
+ ?line {'EXIT', {function_clause, _}} =
+ (catch substitution({external, fun({A,_}) -> A end}, set([]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch substitution({external, fun({A,_}) -> {A,0} end},
+ from_term([{1,a}]))),
+
+ %% set of sets
+ ?line {'EXIT', {badarg, _}} =
+ (catch substitution({external, fun(X) -> X end},
+ from_term([], [[atom]]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch substitution({external, fun(X) -> X end}, from_term([[a]]))),
+ ?line eval(substitution(fun(X) -> X end, from_term([], [[atom]])), E),
+ ?line eval(substitution({sofs,union},
+ from_term([[[1,2],[2,3]], [[a,b],[b,c]]])),
+ from_term([{[[1,2],[2,3]],[1,2,3]}, {[[a,b],[b,c]],[a,b,c]}])),
+ ?line eval(substitution(fun(_) -> from_term([a]) end,
+ from_term([[b]], [[a]])),
+ from_term([{[b],[a]}], [{[a],[atom]}])),
+ ?line eval(substitution(fun(_) -> from_term([a]) end,
+ from_term([[1,2],[3,4]])),
+ from_term([{[1,2],[a]},{[3,4],[a]}])),
+ Fun10 = fun(S) ->
+ %% Cheating a lot...
+ case to_external(S) of
+ [1] -> from_term({1,1});
+ _ -> S
+ end
+ end,
+ ?line eval(substitution(Fun10, from_term([[1]])),
+ from_term([{[1],{1,1}}])),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch substitution(Fun10, from_term([[1],[2]]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch substitution(Fun10, from_term([[1],[0]]))),
+
+ ?line eval(substitution(fun(_) -> from_term({a}) end, from_term([[a]])),
+ from_term([{[a],{a}}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch substitution(fun(_) -> {a} end, from_term([[a]]))),
+
+ ok.
+
+restriction(suite) -> [];
+restriction(doc) -> [""];
+restriction(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([], 2),
+
+ %% set of ordered sets
+ ?line S1 = relation([{a,1},{b,2},{b,22},{c,0}]),
+ ?line eval(restriction(S1, set([a,b])),
+ relation([{a,1},{b,2},{b,22}])),
+ ?line eval(restriction(2, S1, set([1,2])),
+ relation([{a,1},{b,2}])),
+ ?line eval(restriction(S1, set([a,b,c])), S1),
+ ?line eval(restriction(1, S1, set([0,1,d,e])), ER),
+ ?line eval(restriction(1, S1, E), ER),
+ ?line eval(restriction({external, fun({_A,B,C}) -> {B,C} end},
+ relation([{a,aa,1},{b,bb,2},{c,cc,3}]),
+ relation([{bb,2},{cc,3}])),
+ relation([{b,bb,2},{c,cc,3}])),
+ R1 = relation([],[{a,b}]),
+ ?line eval(restriction(2, R1,sofs:set([],[b])), R1),
+ Id = fun(X) -> X end,
+ XId = {external, Id},
+ ?line eval(restriction(XId, relation([{a,b}]), E), ER),
+ ?line eval(restriction(XId, E, relation([{b,d}])), E),
+ Fun1 = fun(S) -> {_A,B,C} = to_external(S), from_term({B,C}) end,
+ ?line eval(restriction(Fun1,
+ relation([{a,aa,1},{b,bb,2},{c,cc,3}]),
+ relation([{bb,2},{cc,3}])),
+ relation([{b,bb,2},{c,cc,3}])),
+ ?line eval(restriction({external, fun({_,{A},B}) -> {A,B} end},
+ from_term([{a,{aa},1},{b,{bb},2},{c,{cc},3}]),
+ from_term([{bb,2},{cc,3}])),
+ from_term([{b,{bb},2},{c,{cc},3}])),
+ S5 = relation([{1,a},{2,b},{3,c}]),
+ ?line eval(restriction(2, S5, set([b,c])), relation([{2,b},{3,c}])),
+ S4 = relation([{a,1},{b,2},{b,27},{c,0}]),
+ ?line eval(restriction(2, S4, E), ER),
+ S6 = relation([{1,a},{2,c},{3,b}]),
+ ?line eval(restriction(2, S6, set([d,e])), ER),
+ ?line eval(restriction(2,
+ relation([{1,d},{2,c},{3,b},{4,a},{5,e}]),
+ set([c])),
+ relation([{2,c}])),
+ ?line eval(restriction(XId,
+ relation([{1,a},{3,b},{4,c},{4,d}]),
+ relation([{2,a},{2,c},{4,c}])),
+ relation([{4,c}])),
+ ?line eval(restriction(2, relation([{a,b}]), E), ER),
+ ?line eval(restriction(2, E, relation([{b,d}])), E),
+ ?line eval(restriction(2, relation([{b,d}]), E), ER),
+ ?line eval(restriction(XId, E, set([a])), E),
+ ?line eval(restriction(1, S1, E), ER),
+ ?line {'EXIT', {badarg, _}} =
+ (catch restriction(3, relation([{a,b}]), E)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch restriction(3, relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch restriction(3, relation([{a,b}]), set([{b,d}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch restriction(2, relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch restriction({external, fun({A,_B}) -> A end},
+ relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch restriction({external, fun({A,_}) -> {A,0} end},
+ from_term([{1,a}]),
+ from_term([{1,0}]))),
+ ?line eval(restriction(2, relation([{a,d},{b,e},{c,b},{d,c}]), set([b,d])),
+ relation([{a,d},{c,b}])),
+ ?line {'EXIT', {function_clause, _}} =
+ (catch restriction({external, fun({A,_B}) -> A end}, set([]), E)),
+
+ Fun3 = fun(S) -> from_term({to_external(S),0}, {type(S),atom}) end,
+ ?line eval(restriction(Fun3, set([1,2]), from_term([{1,0}])),
+ from_term([1])),
+
+ %% set of sets
+ ?line {'EXIT', {badarg, _}} =
+ (catch restriction({external, fun(X) -> X end},
+ from_term([], [[atom]]), set([a]))),
+ S2 = from_term([], [[atom]]),
+ ?line eval(restriction(Id, S2, E), E),
+ S3 = from_term([[a],[b]], [[atom]]),
+ ?line eval(restriction(Id, S3, E), E),
+ ?line eval(restriction(Id, from_term([], [[atom]]), set([a])),
+ from_term([], [[atom]])),
+ ?line eval(restriction({sofs,union},
+ from_term([[[a],[b]], [[b],[c]],
+ [[], [a,b]], [[1],[2]]]),
+ from_term([[a,b],[1,2,3],[b,c]])),
+ from_term([[[],[a,b]], [[a],[b]],[[b],[c]]])),
+ ?line eval(restriction(fun(_) -> from_term([a]) end,
+ from_term([], [[atom]]),
+ from_term([], [[a]])),
+ from_term([], [[atom]])),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch restriction(fun(_) -> from_term([a]) end,
+ from_term([[1,2],[3,4]]),
+ from_term([], [atom]))),
+ Fun10 = fun(S) ->
+ %% Cheating a lot...
+ case to_external(S) of
+ [1] -> from_term({1,1});
+ _ -> S
+ end
+ end,
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch restriction(Fun10, from_term([[1]]), from_term([], [[atom]]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch restriction(fun(_) -> from_term({a}) end,
+ from_term([[a]]),
+ from_term([], [atom]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch restriction(fun(_) -> {a} end,
+ from_term([[a]]),
+ from_term([], [atom]))),
+ ok.
+
+drestriction(suite) -> [];
+drestriction(doc) -> [""];
+drestriction(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([], 2),
+
+ %% set of ordered sets
+ ?line S1 = relation([{a,1},{b,2},{b,22},{c,0}]),
+ ?line eval(drestriction(S1, set([a,b])), relation([{c,0}])),
+ ?line eval(drestriction(2, S1, set([1,2])),
+ relation([{b,22},{c,0}])),
+ ?line eval(drestriction(S1, set([a,b,c])), ER),
+ ?line eval(drestriction(2, ER, set([a,b])), ER),
+ ?line eval(drestriction(1, S1, set([0,1,d,e])), S1),
+ ?line eval(drestriction(1, S1, E), S1),
+ ?line eval(drestriction({external, fun({_A,B,C}) -> {B,C} end},
+ relation([{a,aa,1},{b,bb,2},{c,cc,3}]),
+ relation([{bb,2},{cc,3}])),
+ relation([{a,aa,1}])),
+ Id = fun(X) -> X end,
+ XId = {external, Id},
+ ?line eval(drestriction(XId, relation([{a,b}]), E), relation([{a,b}])),
+ ?line eval(drestriction(XId, E, relation([{b,d}])), E),
+ Fun1 = fun(S) -> {_A,B,C} = to_external(S), from_term({B,C}) end,
+ ?line eval(drestriction(Fun1,
+ relation([{a,aa,1},{b,bb,2},{c,cc,3}]),
+ relation([{bb,2},{cc,3}])),
+ relation([{a,aa,1}])),
+ ?line eval(drestriction({external, fun({_,{A},B}) -> {A,B} end},
+ from_term([{a,{aa},1},{b,{bb},2},{c,{cc},3}]),
+ from_term([{bb,2},{cc,3}])),
+ from_term([{a,{aa},1}])),
+ S5 = relation([{1,a},{2,b},{3,c}]),
+ ?line eval(drestriction(2, S5, set([b,c])), relation([{1,a}])),
+ S4 = relation([{a,1},{b,2},{b,27},{c,0}]),
+ ?line eval(drestriction(2, S4, set([])), S4),
+ S6 = relation([{1,a},{2,c},{3,b}]),
+ ?line eval(drestriction(2, S6, set([d,e])), S6),
+ ?line eval(drestriction(2,
+ relation([{1,d},{2,c},{3,b},{4,a},{5,e}]),
+ set([c])),
+ relation([{1,d},{3,b},{4,a},{5,e}])),
+ ?line eval(drestriction(XId,
+ relation([{1,a},{3,b},{4,c},{4,d}]),
+ relation([{2,a},{2,c},{4,c}])),
+ relation([{1,a},{3,b},{4,d}])),
+ ?line eval(drestriction(2, relation([{a,b}]), E), relation([{a,b}])),
+ ?line eval(drestriction(2, E, relation([{b,d}])), E),
+ ?line eval(drestriction(2, relation([{b,d}]), E), relation([{b,d}])),
+ ?line eval(drestriction(XId, E, set([a])), E),
+ ?line eval(drestriction(1, S1, E), S1),
+ ?line {'EXIT', {badarg, _}} =
+ (catch drestriction(3, relation([{a,b}]), E)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch drestriction(3, relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch drestriction(3, relation([{a,b}]), set([{b,d}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch drestriction(2, relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch drestriction({external, fun({A,_B}) -> A end},
+ relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch drestriction({external, fun({A,_}) -> {A,0} end},
+ from_term([{1,a}]),
+ from_term([{1,0}]))),
+ ?line eval(drestriction(2, relation([{a,d},{b,e},{c,b},{d,c}]), set([b,d])),
+ relation([{b,e},{d,c}])),
+ ?line {'EXIT', {function_clause, _}} =
+ (catch drestriction({external, fun({A,_B}) -> A end}, set([]), E)),
+
+ Fun3 = fun(S) -> from_term({to_external(S),0}, {type(S),atom}) end,
+ ?line eval(drestriction(Fun3, set([1,2]), from_term([{1,0}])),
+ from_term([2])),
+
+ %% set of sets
+ ?line {'EXIT', {badarg, _}} =
+ (catch drestriction({external, fun(X) -> X end},
+ from_term([], [[atom]]), set([a]))),
+ S2 = from_term([], [[atom]]),
+ ?line eval(drestriction(Id, S2, E), S2),
+ S3 = from_term([[a],[b]], [[atom]]),
+ ?line eval(drestriction(Id, S3, E), S3),
+ ?line eval(drestriction(Id, from_term([], [[atom]]), set([a])),
+ from_term([], [[atom]])),
+ ?line eval(drestriction({sofs,union},
+ from_term([[[a],[b]], [[b],[c]],
+ [[], [a,b]], [[1],[2]]]),
+ from_term([[a,b],[1,2,3],[b,c]])),
+ from_term([[[1],[2]]])),
+ ?line eval(drestriction(fun(_) -> from_term([a]) end,
+ from_term([], [[atom]]),
+ from_term([], [[a]])),
+ from_term([], [[atom]])),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch drestriction(fun(_) -> from_term([a]) end,
+ from_term([[1,2],[3,4]]),
+ from_term([], [atom]))),
+ Fun10 = fun(S) ->
+ %% Cheating a lot...
+ case to_external(S) of
+ [1] -> from_term({1,1});
+ _ -> S
+ end
+ end,
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch drestriction(Fun10, from_term([[1]]), from_term([], [[atom]]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch drestriction(fun(_) -> from_term({a}) end,
+ from_term([[a]]),
+ from_term([], [atom]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch drestriction(fun(_) -> {a} end,
+ from_term([[a]]),
+ from_term([], [atom]))),
+ ok.
+
+strict_relation_1(suite) -> [];
+strict_relation_1(doc) -> [""];
+strict_relation_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([], 2),
+ ?line eval(strict_relation(E), E),
+ ?line eval(strict_relation(ER), ER),
+ ?line eval(strict_relation(relation([{1,a},{a,a},{2,b}])),
+ relation([{1,a},{2,b}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch strict_relation(relation([{1,2,3}]))),
+ F = 0.0, I = round(F),
+ ?line FR = relation([{F,I}]),
+ if
+ F == I -> % term ordering
+ eval(strict_relation(FR), ER);
+ true ->
+ eval(strict_relation(FR), FR)
+ end,
+ ok.
+
+extension(suite) -> [];
+extension(doc) -> [""];
+extension(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([], 2),
+ ?line EF = family([]),
+ ?line C1 = from_term(3),
+ ?line C2 = from_term([3]),
+ ?line {'EXIT', {function_clause, _}} = (catch extension(foo, E, C1)),
+ ?line {'EXIT', {function_clause, _}} = (catch extension(ER, foo, C1)),
+ ?line {'EXIT', {{case_clause, _},_}} = (catch extension(ER, E, foo)),
+ ?line {'EXIT', {type_mismatch, _}} = (catch extension(ER, E, E)),
+ ?line {'EXIT', {badarg, _}} = (catch extension(C2, E, E)),
+ ?line eval(E, extension(E, E, E)),
+ ?line eval(EF, extension(EF, E, E)),
+ ?line eval(family([{3,[]}]), extension(EF, set([3]), E)),
+ ?line eval(ER, extension(ER, E, C1)),
+ ?line eval(E, extension(E, ER, E)),
+ ?line eval(from_term([],[{{atom,atom},type(ER)}]), extension(E, ER, ER)),
+
+ ?line R1 = relation([{c,7},{c,9},{c,11},{d,17},{f,20}]),
+ ?line S1 = set([a,c,d,e]),
+ ?line eval(extension(R1, S1, C1), lextension(R1, S1, C1)),
+
+ ?line S2 = set([1,2,3]),
+ ?line eval(extension(ER, S2, C1), lextension(ER, S2, C1)),
+
+ ?line R3 = relation([{4,a},{8,b}]),
+ ?line S3 = set([1,2,3,4,5,6,7,8,9,10,11]),
+ ?line eval(extension(R3, S3, C1), lextension(R3, S3, C1)),
+
+ ?line R4 = relation([{2,b},{4,d},{6,f}]),
+ ?line S4 = set([1,3,5,7]),
+ ?line eval(extension(R4, S4, C1), lextension(R4, S4, C1)),
+
+ ?line F1 = family([{a,[1]},{c,[2]}]),
+ ?line S5 = set([a,b,c,d]),
+ ?line eval(extension(F1, S5, C2), lextension(F1, S5, C2)),
+ ok.
+
+lextension(R, S, C) ->
+ union(R, drestriction(1, constant_function(S, C), domain(R))).
+
+weak_relation_1(suite) -> [];
+weak_relation_1(doc) -> [""];
+weak_relation_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([], 2),
+ ?line eval(weak_relation(E), E),
+ ?line eval(weak_relation(ER), ER),
+ ?line eval(weak_relation(relation([{a,1},{a,2},{b,2},{c,c}])),
+ relation([{1,1},{2,2},{a,1},{a,2},{a,a},{b,2},{b,b},{c,c}])),
+ ?line eval(weak_relation(relation([{a,1},{a,a},{a,b}])),
+ relation([{1,1},{a,1},{a,a},{a,b},{b,b}])),
+ ?line eval(weak_relation(relation([{a,1},{a,b},{7,w}])),
+ relation([{1,1},{7,7},{7,w},{a,1},{a,a},{a,b},{b,b},{w,w}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch weak_relation(from_term([{{a},a}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch weak_relation(from_term([{a,a}],[{d,r}]))),
+ ?line {'EXIT', {badarg, _}} = (catch weak_relation(relation([{1,2,3}]))),
+
+ F = 0.0, I = round(F),
+ if
+ F == I -> % term ordering
+ ?line FR1 = relation([{F,I}]),
+ eval(weak_relation(FR1), FR1),
+ ?line FR2 = relation([{F,2},{I,1}]),
+ true = no_elements(weak_relation(FR2)) =:= 5,
+ ?line FR3 = relation([{1,0},{1.0,1}]),
+ true = no_elements(weak_relation(FR3)) =:= 3;
+ true ->
+ ok
+ end,
+ ok.
+
+to_sets_1(suite) -> [];
+to_sets_1(doc) -> [""];
+to_sets_1(Conf) when list(Conf) ->
+ ?line {'EXIT', {badarg, _}} = (catch to_sets(from_term(a))),
+ ?line {'EXIT', {function_clause, _}} = (catch to_sets(a)),
+ %% unordered
+ ?line [] = to_sets(empty_set()),
+ ?line eval(to_sets(from_term([a])), [from_term(a)]),
+ ?line eval(to_sets(from_term([[]],[[atom]])), [set([])]),
+
+ ?line L = [from_term([a,b]),from_term([c,d])],
+ ?line eval(to_sets(from_sets(L)), L),
+
+ ?line eval(to_sets(relation([{a,1},{b,2}])),
+ [from_term({a,1},{atom,atom}), from_term({b,2},{atom,atom})]),
+
+ %% ordered
+ ?line O = {from_term(a,atom), from_term({b}, {atom}), set([c,d])},
+ ?line eval(to_sets(from_sets(O)), O),
+ ok.
+
+
+specification(suite) -> [];
+specification(doc) -> [""];
+specification(Conf) when list(Conf) ->
+ Fun = {external, fun(I) when integer(I) -> true; (_) -> false end},
+ ?line [1,2,3] = to_external(specification(Fun, set([a,1,b,2,c,3]))),
+
+ Fun2 = fun(S) -> is_subset(S, set([1,3,5,7,9])) end,
+ S2 = from_term([[1],[2],[3],[4],[5],[6],[7]]),
+ ?line eval(specification(Fun2, S2), from_term([[1],[3],[5],[7]])),
+ Fun2x = fun([1]) -> true;
+ ([3]) -> true;
+ (_) -> false
+ end,
+ ?line eval(specification({external,Fun2x}, S2), from_term([[1],[3]])),
+
+ Fun3 = fun(_) -> neither_true_or_false end,
+ ?line {'EXIT', {badarg, _}} =
+ (catch specification(Fun3, set([a]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch specification({external, Fun3}, set([a]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch specification(Fun3, from_term([[a]]))),
+ ?line {'EXIT', {function_clause, _}} =
+ (catch specification(Fun, a)),
+ ok.
+
+union_1(suite) -> [];
+union_1(doc) -> [""];
+union_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([], 2),
+ ?line {'EXIT', {badarg, _}} = (catch union(ER)),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch union(relation([{a,b}]), relation([{a,b,c}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch union(from_term([{a,b}]), from_term([{c,[x]}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch union(from_term([{a,b}]), from_term([{c,d}], [{d,r}]))),
+ ?line {'EXIT', {badarg, _}} = (catch union(set([a,b,c]))),
+ ?line eval(union(E), E),
+ ?line eval(union(from_term([[]],[[atom]])), set([])),
+ ?line eval(union(from_term([[{a,b},{b,c}],[{b,c}]])),
+ relation([{a,b},{b,c}])),
+ ?line eval(union(from_term([[1,2,3],[2,3,4],[3,4,5]])),
+ set([1,2,3,4,5])),
+
+ ?line eval(union(from_term([{[a],[],c}]), from_term([{[],[],q}])),
+ from_term([{[a],[],c},{[],[],q}])),
+
+ ?line eval(union(E, E), E),
+ ?line eval(union(set([a,b]), E), set([a,b])),
+ ?line eval(union(E, set([a,b])), set([a,b])),
+
+ ?line eval(union(from_term([[a,b]])), from_term([a,b])),
+ ok.
+
+intersection_1(suite) -> [];
+intersection_1(doc) -> [""];
+intersection_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line {'EXIT', {badarg, _}} = (catch intersection(from_term([a,b]))),
+ ?line {'EXIT', {badarg, _}} = (catch intersection(E)),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch intersection(relation([{a,b}]), relation([{a,b,c}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch intersection(relation([{a,b}]), from_term([{a,b}],[{d,r}]))),
+
+ ?line eval(intersection(from_term([[a,b,c],[d,e,f],[g,h,i]])), set([])),
+
+ ?line eval(intersection(E, E), E),
+ ?line eval(intersection(set([a,b,c]),set([0,b,q])),
+ set([b])),
+ ?line eval(intersection(set([0,b,q]),set([a,b,c])),
+ set([b])),
+ ?line eval(intersection(set([a,b,c]),set([a,b,c])),
+ set([a,b,c])),
+ ?line eval(intersection(set([a,b,d]),set([c,d])),
+ set([d])),
+ ok.
+
+difference(suite) -> [];
+difference(doc) -> [""];
+difference(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch difference(relation([{a,b}]), relation([{a,b,c}]))),
+ ?line eval(difference(E, E), E),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch difference(relation([{a,b}]), from_term([{a,c}],[{d,r}]))),
+ ?line eval(difference(set([a,b,c,d,f]), set([a,d,e,g])),
+ set([b,c,f])),
+ ?line eval(difference(set([a,b,c]), set([d,e,f])),
+ set([a,b,c])),
+ ?line eval(difference(set([a,b,c]), set([a,b,c,d,e,f])),
+ set([])),
+ ?line eval(difference(set([e,f,g]), set([a,b,c,e])),
+ set([f,g])),
+ ?line eval(difference(set([a,b,d,e,f]), set([c])),
+ set([a,b,d,e,f])),
+ ok.
+
+symdiff(suite) -> [];
+symdiff(doc) -> [""];
+symdiff(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch symdiff(relation([{a,b}]), relation([{a,b,c}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch symdiff(relation([{a,b}]), from_term([{a,b}], [{d,r}]))),
+ ?line eval(symdiff(E, E), E),
+ ?line eval(symdiff(set([a,b,c,d,e,f]), set([0,1,a,c])),
+ union(set([b,d,e,f]), set([0,1]))),
+ ?line eval(symdiff(set([a,b,c]), set([q,v,w,x,y])),
+ union(set([a,b,c]), set([q,v,w,x,y]))),
+ ?line eval(symdiff(set([a,b,c,d,e,f]), set([a,b,c])),
+ set([d,e,f])),
+ ?line eval(symdiff(set([c,e,g,h,i]), set([b,d,f])),
+ union(set([c,e,g,h,i]), set([b,d,f]))),
+ ?line eval(symdiff(set([c,d,g,h,k,l]),
+ set([a,b,e,f,i,j,m,n])),
+ union(set([c,d,g,h,k,l]), set([a,b,e,f,i,j,m,n]))),
+ ?line eval(symdiff(set([c,d,g,h,k,l]),
+ set([d,e,h,i,l,m,n,o,p])),
+ union(set([c,g,k]), set([e,i,m,n,o,p]))),
+ ok.
+
+symmetric_partition(suite) -> [];
+symmetric_partition(doc) -> [""];
+symmetric_partition(Conf) when list(Conf) ->
+ ?line E = set([]),
+ ?line S1 = set([1,2,3,4]),
+ ?line S2 = set([3,4,5,6]),
+ ?line S3 = set([3,4]),
+ ?line S4 = set([1,2,3,4,5,6]),
+ ?line T1 = set([1,2]),
+ ?line T2 = set([3,4]),
+ ?line T3 = set([5,6]),
+ ?line T4 = set([1,2,5,6]),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch symmetric_partition(relation([{a,b}]), relation([{a,b,c}]))),
+ ?line {E, E, E} = symmetric_partition(E, E),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch symmetric_partition(relation([{a,b}]),
+ from_term([{a,c}],[{d,r}]))),
+ ?line {E, E, S1} = symmetric_partition(E, S1),
+ ?line {S1, E, E} = symmetric_partition(S1, E),
+ ?line {T1, T2, T3} = symmetric_partition(S1, S2),
+ ?line {T3, T2, T1} = symmetric_partition(S2, S1),
+ ?line {E, T2, T4} = symmetric_partition(S3, S4),
+ ?line {T4, T2, E} = symmetric_partition(S4, S3),
+
+ ?line S5 = set([1,3,5]),
+ ?line S6 = set([2,4,6,7,8]),
+ ?line {S5, E, S6} = symmetric_partition(S5, S6),
+ ?line {S6, E, S5} = symmetric_partition(S6, S5),
+ ?line EE = empty_set(),
+ ?line {EE, EE, EE} = symmetric_partition(EE, EE),
+
+ ok.
+
+is_sofs_set_1(suite) -> [];
+is_sofs_set_1(doc) -> [""];
+is_sofs_set_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line true = is_sofs_set(E),
+ ?line true = is_sofs_set(from_term([a])),
+ ?line true = is_sofs_set(from_term({a})),
+ ?line true = is_sofs_set(from_term(a)),
+ ?line false = is_sofs_set(a),
+ ok.
+
+is_set_1(suite) -> [];
+is_set_1(doc) -> [""];
+is_set_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line true = is_set(E),
+ ?line true = is_set(from_term([a])),
+ ?line false = is_set(from_term({a})),
+ ?line false = is_set(from_term(a)),
+ ?line {'EXIT', _} = (catch is_set(a)),
+
+ ?line true = is_empty_set(E),
+ ?line false = is_empty_set(from_term([a])),
+ ?line false = is_empty_set(from_term({a})),
+ ?line false = is_empty_set(from_term(a)),
+ ?line {'EXIT', _} = (catch is_empty_set(a)),
+
+ ok.
+
+is_equal(suite) -> [];
+is_equal(doc) -> [""];
+is_equal(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line true = is_equal(E, E),
+ ?line false = is_equal(from_term([a]), E),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_equal(intersection(set([a]), set([b])),
+ intersection(from_term([{a}]), from_term([{b}])))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_equal(from_term([],[{[atom],atom,[atom]}]),
+ from_term([],[{[atom],{atom},[atom]}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_equal(set([a]), from_term([a],[type]))),
+
+ ?line E2 = from_sets({from_term(a,atom)}),
+ ?line true = is_equal(E2, E2),
+ ?line true = is_equal(from_term({a}, {atom}), E2),
+ ?line false = is_equal(from_term([{[a],[],c}]),
+ from_term([{[],[],q}])),
+
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_equal(E, E2)),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_equal(E2, E)),
+ ?line true = is_equal(from_term({[],a,[]},{[atom],atom,[atom]}),
+ from_term({[],a,[]},{[atom],atom,[atom]})),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_equal(from_term({[],a,[]},{[atom],atom,[atom]}),
+ from_term({[],{a},[]},{[atom],{atom},[atom]}))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_equal(from_term({a}), from_term({a},{type}))),
+
+ ok.
+
+is_subset(suite) -> [];
+is_subset(doc) -> [""];
+is_subset(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line true = is_subset(E, E),
+ ?line true = is_subset(set([a,c,e]), set([a,b,c,d,e])),
+ ?line false = is_subset(set([a,b]), E),
+ ?line false = is_subset(set([d,e,f]), set([b,c,d,e])),
+ ?line false = is_subset(set([a,b,c]), set([b,c])),
+ ?line false = is_subset(set([b,c]), set([a,c])),
+ ?line false = is_subset(set([d,e]), set([a,b])),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_subset(intersection(set([a]), set([b])),
+ intersection(from_term([{a}]), from_term([{b}])))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_subset(set([a]), from_term([a,b], [at]))),
+ ok.
+
+is_a_function_1(suite) -> [];
+is_a_function_1(doc) -> [""];
+is_a_function_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([], 2),
+ ?line {'EXIT', {badarg, _}} = (catch is_a_function(set([a,b]))),
+ ?line true = is_a_function(E),
+ ?line true = is_a_function(ER),
+ ?line true = is_a_function(relation([])),
+ ?line true = is_a_function(relation([],2)),
+ ?line true = is_a_function(relation([{a,b},{b,c}])),
+ ?line false = is_a_function(relation([{a,b},{b,c},{b,d},{e,f}])),
+ ?line IS = relation([{{a,b},c},{{a,b},d}]),
+ ?line false = is_a_function(IS),
+ F = 0.0, I = round(F),
+ ?line FR = relation([{I,F},{F,1}]),
+ if
+ F == I -> % term ordering
+ false = is_a_function(FR);
+ true ->
+ true = is_a_function(FR)
+ end,
+ ok.
+
+is_disjoint(suite) -> [];
+is_disjoint(doc) -> [""];
+is_disjoint(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_disjoint(relation([{a,1}]), set([a,b]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch is_disjoint(set([a]), from_term([a],[mota]))),
+ ?line true = is_disjoint(E, E),
+ ?line false = is_disjoint(set([a,b,c]),set([b,c,d])),
+ ?line false = is_disjoint(set([b,c,d]),set([a,b,c])),
+ ?line true = is_disjoint(set([a,c,e]),set([b,d,f])),
+ ok.
+
+join(suite) -> [];
+join(doc) -> [""];
+join(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+
+ ?line {'EXIT', {badarg, _}} = (catch join(relation([{a,1}]), 3, E, 5)),
+ ?line {'EXIT', {badarg, _}} = (catch join(E, 1, relation([{a,1}]), 3)),
+ ?line {'EXIT', {badarg, _}} = (catch join(E, 1, from_term([a]), 1)),
+
+ ?line eval(join(E, 1, E, 2), E),
+ ?line eval(join(E, 1, from_term([{{a},b}]), 2), E),
+ ?line eval(join(from_term([{{a},b}]), 2, E, 1), E),
+ ?line eval(join(from_term([{{a},b,e}]), 2, from_term([{c,{d}}]), 1),
+ from_term([], [{{atom},atom,atom,{atom}}])),
+ ?line eval(join(relation([{a}]), 1, relation([{1,a},{2,a}]), 2),
+ relation([{a,1},{a,2}])),
+ ?line eval(join(relation([{a,b,c},{b,c,d}]), 2,
+ relation([{1,b},{2,a},{3,c}]), 2),
+ relation([{a,b,c,1},{b,c,d,3}])),
+ ?line eval(join(relation([{1,a,aa},{1,b,bb},{1,c,cc},{2,a,aa},{2,b,bb}]),
+ 1,
+ relation([{1,c,cc},{1,d,dd},{1,e,ee},{2,c,cc},{2,d,dd}]),
+ 1),
+ relation([{1,a,aa,c,cc},{1,a,aa,d,dd},{1,a,aa,e,ee},{1,b,bb,c,cc},
+ {1,b,bb,d,dd},{1,b,bb,e,ee},{1,c,cc,c,cc},{1,c,cc,d,dd},
+ {1,c,cc,e,ee},{2,a,aa,c,cc},{2,a,aa,d,dd},{2,b,bb,c,cc},
+ {2,b,bb,d,dd}])),
+
+ R1 = relation([{a,b},{b,c}]),
+ R2 = relation([{b,1},{a,2},{c,3},{c,4}]),
+ ?line eval(join(R1, 1, R2, 1), from_term([{a,b,2},{b,c,1}])),
+ ?line eval(join(R1, 2, R2, 1), from_term([{a,b,1},{b,c,3},{b,c,4}])),
+ ?line eval(join(R1, 1, converse(R2), 2),
+ from_term([{a,b,2},{b,c,1}])),
+ ?line eval(join(R1, 2, converse(R2), 2),
+ from_term([{a,b,1},{b,c,3},{b,c,4}])),
+ ok.
+
+canonical(suite) -> [];
+canonical(doc) -> [""];
+canonical(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line {'EXIT', {badarg, _}} =
+ (catch canonical_relation(set([a,b]))),
+ ?line eval(canonical_relation(E), E),
+ ?line eval(canonical_relation(from_term([[]])), E),
+ ?line eval(canonical_relation(from_term([[a,b,c]])),
+ from_term([{a,[a,b,c]},{b,[a,b,c]},{c,[a,b,c]}])),
+ ok.
+
+relation_to_family_1(suite) -> [];
+relation_to_family_1(doc) -> [""];
+relation_to_family_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = family([]),
+ ?line eval(relation_to_family(E), E),
+ ?line eval(relation_to_family(relation([])), EF),
+ ?line eval(relation_to_family(relation([], 2)), EF),
+ ?line R = relation([{b,1},{c,7},{c,9},{c,11}]),
+ ?line F = family([{b,[1]},{c,[7,9,11]}]),
+ ?line eval(relation_to_family(R), F),
+ ?line eval(sofs:rel2fam(R), F),
+ ?line {'EXIT', {badarg, _}} = (catch relation_to_family(set([a]))),
+ ok.
+
+domain_1(suite) -> [];
+domain_1(doc) -> [""];
+domain_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line {'EXIT', {badarg, _}} = (catch domain(relation([],3))),
+ ?line eval(domain(E), E),
+ ?line eval(domain(ER), set([])),
+ ?line eval(domain(relation([{1,a},{1,b},{2,a},{2,b}])), set([1,2])),
+ ?line eval(domain(relation([{a,1},{b,2},{c,3}])), set([a,b,c])),
+ ?line eval(field(relation([{a,1},{b,2},{c,3}])),
+ set([a,b,c,1,2,3])),
+ F = 0.0, I = round(F),
+ ?line FR = relation([{I,a},{F,b}]),
+ if
+ F == I -> % term ordering
+ ?line true = (1 =:= no_elements(domain(FR)));
+ true ->
+ ?line true = (2 =:= no_elements(domain(FR)))
+ end,
+ ok.
+
+range_1(suite) -> [];
+range_1(doc) -> [""];
+range_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line {'EXIT', {badarg, _}} = (catch range(relation([],3))),
+ ?line eval(range(E), E),
+ ?line eval(range(ER), set([])),
+ ?line eval(range(relation([{1,a},{1,b},{2,a},{2,b}])), set([a,b])),
+ ?line eval(range(relation([{a,1},{b,2},{c,3}])), set([1,2,3])),
+ ok.
+
+inverse_1(suite) -> [];
+inverse_1(doc) -> [""];
+inverse_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line {'EXIT', {badarg, _}} = (catch inverse(relation([],3))),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch inverse(relation([{1,a},{1,b}]))),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch inverse(relation([{1,a},{2,a}]))),
+ ?line eval(inverse(E), E),
+ ?line eval(inverse(ER), ER),
+ ?line eval(inverse(relation([{a,1},{b,2},{c,3}])),
+ relation([{1,a},{2,b},{3,c}])),
+ F = 0.0, I = round(F),
+ ?line FR = relation([{I,a},{F,b}]),
+ if
+ F == I -> % term ordering
+ ?line {'EXIT', {bad_function, _}} = (catch inverse(FR));
+ true ->
+ ?line eval(inverse(FR), relation([{a,I},{b,F}]))
+ end,
+ ok.
+
+converse_1(suite) -> [];
+converse_1(doc) -> [""];
+converse_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line {'EXIT', {badarg, _}} = (catch converse(relation([],3))),
+ ?line eval(converse(ER), ER),
+ ?line eval(converse(E), E),
+ ?line eval(converse(relation([{a,1},{b,2},{c,3}])),
+ relation([{1,a},{2,b},{3,c}])),
+ ?line eval(converse(relation([{1,a},{1,b}])),
+ relation([{a,1},{b,1}])),
+ ?line eval(converse(relation([{1,a},{2,a}])),
+ relation([{a,1},{a,2}])),
+ ok.
+
+no_elements_1(suite) -> [];
+no_elements_1(doc) -> [""];
+no_elements_1(Conf) when list(Conf) ->
+ ?line 0 = no_elements(empty_set()),
+ ?line 0 = no_elements(set([])),
+ ?line 1 = no_elements(from_term([a])),
+ ?line 10 = no_elements(from_term(lists:seq(1,10))),
+ ?line 3 = no_elements(from_term({a,b,c},{atom,atom,atom})),
+ ?line {'EXIT', {badarg, _}} = (catch no_elements(from_term(a))),
+ ?line {'EXIT', {function_clause, _}} = (catch no_elements(a)),
+ ok.
+
+image(suite) -> [];
+image(doc) -> [""];
+image(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line eval(image(E, E), E),
+ ?line eval(image(ER, E), set([])),
+ ?line eval(image(relation([{a,1},{b,2},{c,3},{f,6}]), set([a,b,c,d,f])),
+ set([1,2,3,6])),
+ ?line eval(image(relation([{a,1},{b,2},{c,3},{d,4},{r,17}]),
+ set([b,c,q,r])),
+ set([2,3,17])),
+ ?line eval(image(from_term([{[a],{1}},{[b],{2}}]), from_term([[a]])),
+ from_term([{1}])),
+ ?line eval(image(relation([{1,a},{2,a},{3,a},{4,b},{2,b}]), set([1,2,4])),
+ set([a,b])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch image(from_term([a,b]), E)),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch image(from_term([{[a],1}]), set([[a]]))),
+ ok.
+
+inverse_image(suite) -> [];
+inverse_image(doc) -> [""];
+inverse_image(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line eval(inverse_image(E, E), E),
+ ?line eval(inverse_image(ER, E), set([])),
+ ?line eval(inverse_image(converse(relation([{a,1},{b,2},{c,3},{f,6}])),
+ set([a,b,c,d,f])),
+ set([1,2,3,6])),
+ ?line eval(inverse_image(converse(relation([{a,1},{b,2},{c,3},
+ {d,4},{r,17}])),
+ set([b,c,q,r])),
+ set([2,3,17])),
+ ?line eval(inverse_image(converse(from_term([{[a],{1}},{[b],{2}}])),
+ from_term([[a]])),
+ from_term([{1}])),
+ ?line eval(inverse_image(converse(relation([{1,a},{2,a},
+ {3,a},{4,b},{2,b}])),
+ set([1,2,4])),
+ set([a,b])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch inverse_image(from_term([a,b]), E)),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch inverse_image(converse(from_term([{[a],1}])), set([[a]]))),
+ ok.
+
+composite_1(suite) -> [];
+composite_1(doc) -> [""];
+composite_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = a_function([]),
+ ?line eval(composite(E, E), E),
+ ?line eval(composite(E, a_function([{a,b}])), E),
+ ?line eval(composite(relation([{a,b}]), E), E),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch composite(EF, relation([{a,b},{a,c}]))),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch composite(a_function([{b,a}]), EF)),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch composite(relation([{1,a},{2,b},{2,a}]),
+ a_function([{a,1},{b,3}]))),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch composite(a_function([{1,a},{2,b}]), a_function([{b,3}]))),
+ ?line eval(composite(EF, EF), EF),
+ ?line eval(composite(a_function([{b,a}]), from_term([{a,{b,c}}])),
+ from_term([{b,{b,c}}])),
+ ?line eval(composite(a_function([{q,1},{z,2}]),
+ a_function([{1,a},{2,a}])),
+ a_function([{q,a},{z,a}])),
+ ?line eval(composite(a_function([{a,0},{b,0},{c,1},{d,1},{e,2},{f,3}]),
+ a_function([{0,p},{1,q},{2,r},{3,w},{4,aa}])),
+ a_function([{c,q},{d,q},{f,w},{e,r},{a,p},{b,p}])),
+ ?line eval(composite(a_function([{1,c}]),
+ a_function([{a,1},{b,3},{c,4}])),
+ a_function([{1,4}])),
+ ?line {'EXIT', {bad_function, _}} =
+ (catch composite(a_function([{1,a},{2,b}]),
+ a_function([{a,1},{c,3}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch composite(from_term([a,b]), E)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch composite(E, from_term([a,b]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch composite(from_term([{a,b}]), from_term([{{a},b}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch composite(from_term([{a,b}]),
+ from_term([{b,c}], [{d,r}]))),
+ F = 0.0, I = round(F),
+ ?line FR1 = relation([{1,c}]),
+ ?line FR2 = relation([{I,1},{F,3},{c,4}]),
+ if
+ F == I -> % term ordering
+ ?line {'EXIT', {bad_function, _}} = (catch composite(FR1, FR2));
+ true ->
+ ?line eval(composite(FR1, FR2), a_function([{1,4}]))
+ end,
+ ok.
+
+relative_product_1(suite) -> [];
+relative_product_1(doc) -> [""];
+relative_product_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line eval(relative_product1(E, E), E),
+ ?line eval(relative_product1(E, relation([{a,b}])), E),
+ ?line eval(relative_product1(relation([{a,b}]), E), E),
+ ?line eval(relative_product1(relation([{a,b}]), from_term([{a,{b,c}}])),
+ from_term([{b,{b,c}}])),
+ ?line eval(relative_product1(relation([{1,z},{1,q},{2,z}]),
+ relation([{1,a},{1,b},{2,a}])),
+ relation([{q,a},{q,b},{z,a},{z,b}])),
+ ?line eval(relative_product1(relation([{0,a},{0,b},{1,c},
+ {1,d},{2,e},{3,f}]),
+ relation([{1,q},{3,w}])),
+ relation([{c,q},{d,q},{f,w}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch relative_product1(from_term([a,b]), ER)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch relative_product1(ER, from_term([a,b]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch relative_product1(from_term([{a,b}]), from_term([{{a},b}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch relative_product1(from_term([{a,b}]),
+ from_term([{b,c}], [{d,r}]))),
+ ok.
+
+relative_product_2(suite) -> [];
+relative_product_2(doc) -> [""];
+relative_product_2(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+
+ ?line {'EXIT', {badarg, _}} = (catch relative_product({from_term([a,b])})),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch relative_product({from_term([{a,b}]), from_term([{{a},b}])})),
+ ?line {'EXIT', {badarg, _}} = (catch relative_product({})),
+ ?line true = is_equal(relative_product({ER}),
+ from_term([], [{atom,{atom}}])),
+ ?line eval(relative_product({relation([{a,b},{c,a}]),
+ relation([{a,1},{a,2}]),
+ relation([{a,aa},{c,1}])}),
+ from_term([{a,{b,1,aa}},{a,{b,2,aa}}])),
+ ?line eval(relative_product({relation([{a,b}])}, E), E),
+ ?line eval(relative_product({E}, relation([{a,b}])), E),
+ ?line eval(relative_product({E,from_term([], [{{atom,atom,atom},atom}])}),
+ E),
+ ?line {'EXIT', {badarg, _}} =
+ (catch relative_product({from_term([a,b])}, E)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch relative_product({relation([])}, set([]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch relative_product({from_term([{a,b}]),
+ from_term([{{a},b}])}, ER)),
+
+ ?line {'EXIT', {badarg, _}} = (catch relative_product({}, ER)),
+ ?line eval(relative_product({relation([{a,b}])},
+ from_term([],[{{atom},atom}])),
+ ER),
+ ?line eval(relative_product({relation([{a,b}]),relation([{a,1}])},
+ from_term([{{b,1},{tjo,hej,sa}}])),
+ from_term([{a,{tjo,hej,sa}}])),
+ ?line eval(relative_product({relation([{a,b}]), ER},
+ from_term([{{a,b},b}])),
+ ER),
+ ?line eval(relative_product({relation([{a,b},{c,a}]),
+ relation([{a,1},{a,2}])},
+ from_term([{{b,1},b1},{{b,2},b2}])),
+ relation([{a,b1},{a,b2}])),
+ ?line eval(relative_product({relation([{a,b}]), ER}),
+ from_term([],[{atom,{atom,atom}}])),
+ ?line eval(relative_product({from_term([{{a,[a,b]},[a]}]),
+ from_term([{{a,[a,b]},[[a,b]]}])}),
+ from_term([{{a,[a,b]},{[a],[[a,b]]}}])),
+ ok.
+
+product_1(suite) -> [];
+product_1(doc) -> [""];
+product_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line eval(product(E, E), E),
+ ?line eval(product(relation([]), E), E),
+ ?line eval(product(E, relation([])), E),
+ ?line eval(product(relation([{a,b}]),relation([{c,d}])),
+ from_term([{{a,b},{c,d}}],[{{atom,atom},{atom,atom}}])),
+
+ ?line eval(product({E, set([a,b,c])}), E),
+ ?line eval(product({set([a,b,c]), E}), E),
+ ?line eval(product({set([a,b,c]), E, E}), E),
+ ?line eval(product({E,E}), E),
+ ?line eval(product({set([a,b]),set([1,2])}),
+ relation([{a,1},{a,2},{b,1},{b,2}])),
+ ?line eval(product({from_term([a,b]), from_term([{a,b},{c,d}]),
+ from_term([1])}),
+ from_term([{a,{a,b},1},{a,{c,d},1},{b,{a,b},1},{b,{c,d},1}])),
+ ?line {'EXIT', {badarg, _}} = (catch product({})),
+ ?line {'EXIT', {badarg, _}} = (catch product({foo})),
+ ?line eval(product({E}), E),
+ ?line eval(product({E, E}), E),
+ ?line eval(product(set([a,b]), set([1,2])),
+ relation([{a,1},{a,2},{b,1},{b,2}])),
+ ?line eval(product({relation([]), E}), E),
+ ok.
+
+partition_1(suite) -> [];
+partition_1(doc) -> [""];
+partition_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line Id = fun(A) -> A end,
+ ?line S1 = relation([{a,1},{b,2},{b,22},{c,0}]),
+ ?line eval(partition(1, E), E),
+ ?line eval(partition(2, E), E),
+ ?line eval(partition(1, ER), from_term([], [type(ER)])),
+ ?line eval(partition(2, ER), from_term([], [type(ER)])),
+ ?line eval(partition(1, relation([{1,a},{1,b},{2,c},{2,d}])),
+ from_term([[{1,a},{1,b}],[{2,c},{2,d}]])),
+ ?line eval(partition(2, relation([{1,a},{1,b},{2,a},{2,b},{3,c}])),
+ from_term([[{1,a},{2,a}],[{1,b},{2,b}],[{3,c}]])),
+ ?line eval(partition(2, relation([{1,a}])), from_term([[{1,a}]])),
+ ?line eval(partition(2, relation([{1,a},{2,b}])),
+ from_term([[{1,a}],[{2,b}]])),
+ ?line eval(partition(2, relation([{1,a},{2,a},{3,a}])),
+ from_term([[{1,a},{2,a},{3,a}]])),
+ ?line eval(partition(2, relation([{1,b},{2,a}])), % OTP-4516
+ from_term([[{1,b}],[{2,a}]])),
+ ?line eval(union(partition(Id, S1)), S1),
+ ?line eval(partition({external, fun({A,{B,_}}) -> {A,B} end},
+ from_term([{a,{b,c}},{b,{c,d}},{a,{b,f}}])),
+ from_term([[{a,{b,c}},{a,{b,f}}],[{b,{c,d}}]])),
+ F = 0.0, I = round(F),
+ ?line FR = relation([{I,a},{F,b}]),
+ if
+ F == I -> % term ordering
+ ?line eval(partition(1, FR), from_term([[{I,a},{F,b}]]));
+ true ->
+ ?line eval(partition(1, FR), from_term([[{I,a}],[{F,b}]]))
+ end,
+ ?line {'EXIT', {badarg, _}} = (catch partition(2, set([a]))),
+ ?line {'EXIT', {badarg, _}} = (catch partition(1, set([a]))),
+ ?line eval(partition(Id, set([a])), from_term([[a]])),
+
+ ?line eval(partition(E), E),
+ ?line P1 = from_term([[a,b,c],[d,e,f],[g,h]]),
+ ?line P2 = from_term([[a,d],[b,c,e,f,q,v]]),
+ ?line eval(partition(union(P1, P2)),
+ from_term([[a],[b,c],[d],[e,f],[g,h],[q,v]])),
+ ?line {'EXIT', {badarg, _}} = (catch partition(from_term([a]))),
+ ok.
+
+partition_3(suite) -> [];
+partition_3(doc) -> [""];
+partition_3(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+
+ %% set of ordered sets
+ ?line S1 = relation([{a,1},{b,2},{b,22},{c,0}]),
+ ?line eval(partition(1, S1, set([0,1,d,e])),
+ lpartition(1, S1, set([0,1,d,e]))),
+ ?line eval(partition(1, S1, E), lpartition(1, S1, E)),
+ ?line eval(partition(2, ER, set([a,b])), lpartition(2, ER, set([a,b]))),
+
+ XFun1 = {external, fun({_A,B,C}) -> {B,C} end},
+ R1a = relation([{a,aa,1},{b,bb,2},{c,cc,3}]),
+ R1b = relation([{bb,2},{cc,3}]),
+ ?line eval(partition(XFun1, R1a, R1b), lpartition(XFun1, R1a, R1b)),
+
+ Id = fun(X) -> X end,
+ XId = {external, Id},
+ R2 = relation([{a,b}]),
+ ?line eval(partition(XId, R2, E), lpartition(XId, R2, E)),
+
+ R3 = relation([{b,d}]),
+ ?line eval(partition(XId, E, R3), lpartition(XId, E, R3)),
+
+ Fun1 = fun(S) -> {_A,B,C} = to_external(S), from_term({B,C}) end,
+ R4a = relation([{a,aa,1},{b,bb,2},{c,cc,3}]),
+ R4b = relation([{bb,2},{cc,3}]),
+ ?line eval(partition(Fun1,R4a,R4b), lpartition(Fun1,R4a,R4b)),
+
+ XFun2 = {external, fun({_,{A},B}) -> {A,B} end},
+ R5a = from_term([{a,{aa},1},{b,{bb},2},{c,{cc},3}]),
+ R5b = from_term([{bb,2},{cc,3}]),
+ ?line eval(partition(XFun2,R5a, R5b), lpartition(XFun2,R5a, R5b)),
+
+ R6 = relation([{a,b}]),
+ ?line eval(partition(2, R6, E), lpartition(2, R6, E)),
+
+ R7 = relation([{b,d}]),
+ ?line eval(partition(2, E, R7), lpartition(2, E, R7)),
+
+ S2 = set([a]),
+ ?line eval(partition(XId, E, S2), lpartition(XId, E, S2)),
+ ?line eval(partition(XId, S1, E), lpartition(XId, S1, E)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition(3, relation([{a,b}]), E)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition(3, relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition(3, relation([{a,b}]), set([{b,d}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch partition(2, relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch partition({external, fun({A,_B}) -> A end},
+ relation([{a,b}]), relation([{b,d}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition({external, fun({A,_}) -> {A,0} end},
+ from_term([{1,a}]),
+ from_term([{1,0}]))),
+
+ S18a = relation([{1,e},{2,b},{3,c},{4,b},{5,a},{6,0}]),
+ S18b = set([b,d,f]),
+ ?line eval(partition({external,fun({_,X}) -> X end}, S18a, S18b),
+ lpartition({external,fun({_,X}) -> X end}, S18a, S18b)),
+ S19a = sofs:relation([{3,a},{8,b}]),
+ S19b = set([2,6,7]),
+ ?line eval(partition({external,fun({X,_}) -> X end}, S19a, S19b),
+ lpartition({external,fun({X,_}) -> X end}, S19a, S19b)),
+
+ R8a = relation([{a,d},{b,e},{c,b},{d,c}]),
+ S8 = set([b,d]),
+ ?line eval(partition(2, R8a, S8), lpartition(2, R8a, S8)),
+
+ S16a = relation([{1,e},{2,b},{3,c},{4,b},{5,a},{6,0}]),
+ S16b = set([b,c,d]),
+ ?line eval(partition(2, S16a, S16b), lpartition(2, S16a, S16b)),
+ S17a = relation([{e,1},{b,2},{c,3},{b,4},{a,5},{0,6}]),
+ S17b = set([b,c,d]),
+ ?line eval(partition(1, S17a, S17b), lpartition(1, S17a, S17b)),
+
+ ?line {'EXIT', {function_clause, _}} =
+ (catch partition({external, fun({A,_B}) -> A end}, set([]), E)),
+
+ Fun3 = fun(S) -> from_term({to_external(S),0}, {type(S),atom}) end,
+ S9a = set([1,2]),
+ S9b = from_term([{1,0}]),
+ ?line eval(partition(Fun3, S9a, S9b), lpartition(Fun3, S9a, S9b)),
+
+ S14a = relation([{1,a},{2,b},{3,c},{0,0}]),
+ S14b = set([b,c]),
+ ?line eval(partition(2, S14a, S14b), lpartition(2, S14a, S14b)),
+ S15a = relation([{a,1},{b,2},{c,3},{0,0}]),
+ S15b = set([b,c]),
+ ?line eval(partition(1, S15a, S15b), lpartition(1, S15a, S15b)),
+
+ %% set of sets
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition({external, fun(X) -> X end},
+ from_term([], [[atom]]), set([a]))),
+
+ S10 = from_term([], [[atom]]),
+ ?line eval(partition(Id, S10, E), lpartition(Id, S10, E)),
+
+ S10e = from_term([[a],[b]], [[atom]]),
+ ?line eval(partition(Id, S10e, E), lpartition(Id, S10e, E)),
+
+ S11a = from_term([], [[atom]]),
+ S11b = set([a]),
+ ?line eval(partition(Id, S11a, S11b), lpartition(Id, S11a, S11b)),
+
+ S12a = from_term([[[a],[b]], [[b],[c]], [[], [a,b]], [[1],[2]]]),
+ S12b = from_term([[a,b],[1,2,3],[b,c]]),
+ ?line eval(partition({sofs,union}, S12a, S12b),
+ lpartition({sofs,union}, S12a, S12b)),
+
+ Fun13 = fun(_) -> from_term([a]) end,
+ S13a = from_term([], [[atom]]),
+ S13b = from_term([], [[a]]),
+ ?line eval(partition(Fun13, S13a, S13b), lpartition(Fun13, S13a, S13b)),
+
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch partition(fun(_) -> from_term([a]) end,
+ from_term([[1,2],[3,4]]),
+ from_term([], [atom]))),
+ Fun10 = fun(S) ->
+ %% Cheating a lot...
+ case to_external(S) of
+ [1] -> from_term({1,1});
+ _ -> S
+ end
+ end,
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch partition(Fun10, from_term([[1]]), from_term([], [[atom]]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch partition(fun(_) -> from_term({a}) end,
+ from_term([[a]]),
+ from_term([], [atom]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition(fun(_) -> {a} end,
+ from_term([[a]]),
+ from_term([], [atom]))),
+ ok.
+
+lpartition(F, S1, S2) ->
+ {restriction(F, S1, S2), drestriction(F, S1, S2)}.
+
+multiple_relative_product(suite) -> [];
+multiple_relative_product(doc) -> [""];
+multiple_relative_product(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line T = relation([{a,1},{a,11},{b,2},{c,3},{c,33},{d,4}]),
+ ?line {'EXIT', {badarg, _}} =
+ (catch multiple_relative_product({}, ER)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch multiple_relative_product({}, relation([{a,b}]))),
+ ?line eval(multiple_relative_product({E,T,T}, relation([], 3)), E),
+ ?line eval(multiple_relative_product({T,T,T}, E), E),
+ ?line eval(multiple_relative_product({T,T,T}, relation([],3)),
+ from_term([],[{{atom,atom,atom},{atom,atom,atom}}])),
+ ?line eval(multiple_relative_product({T,T,T},
+ relation([{a,b,c},{c,d,a}])),
+ from_term([{{a,b,c},{1,2,3}}, {{a,b,c},{1,2,33}},
+ {{a,b,c},{11,2,3}}, {{a,b,c},{11,2,33}},
+ {{c,d,a},{3,4,1}}, {{c,d,a},{3,4,11}},
+ {{c,d,a},{33,4,1}}, {{c,d,a},{33,4,11}}])),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch multiple_relative_product({T}, from_term([{{a}}]))),
+ ok.
+
+digraph(suite) -> [];
+digraph(doc) -> [""];
+digraph(Conf) when list(Conf) ->
+ ?line T0 = ets:all(),
+ ?line E = empty_set(),
+ ?line R = relation([{a,b},{b,c},{c,d},{d,a}]),
+ ?line F = relation_to_family(R),
+ Type = type(F),
+
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_to_digraph(set([a]))),
+ ?line {'EXIT', {badarg, [{sofs,family_to_digraph,[_,_]}|_]}} =
+ (catch family_to_digraph(set([a]), [foo])),
+ ?line {'EXIT', {badarg, [{sofs,family_to_digraph,[_,_]}|_]}} =
+ (catch family_to_digraph(F, [foo])),
+ ?line {'EXIT', {cyclic, [{sofs,family_to_digraph,[_,_]}|_]}} =
+ (catch family_to_digraph(family([{a,[a]}]),[acyclic])),
+
+ ?line G1 = family_to_digraph(E),
+ ?line {'EXIT', {badarg, _}} = (catch digraph_to_family(G1, foo)),
+ ?line {'EXIT', {badarg, _}} = (catch digraph_to_family(G1, atom)),
+ ?line true = [] == to_external(digraph_to_family(G1)),
+ ?line true = [] == to_external(digraph_to_family(G1, Type)),
+ ?line true = digraph:delete(G1),
+
+ ?line G1a = family_to_digraph(E, [protected]),
+ ?line true = [] == to_external(digraph_to_family(G1a)),
+ ?line true = [] == to_external(digraph_to_family(G1a, Type)),
+ ?line true = digraph:delete(G1a),
+
+ ?line G2 = family_to_digraph(F),
+ ?line true = F == digraph_to_family(G2),
+ ?line true = F == digraph_to_family(G2, type(F)),
+ ?line true = digraph:delete(G2),
+
+ ?line R2 = from_term([{{a},b},{{c},d}]),
+ ?line F2 = relation_to_family(R2),
+ ?line Type2 = type(F2),
+ ?line G3 = family_to_digraph(F2, [protected]),
+ ?line true = is_subset(F2, digraph_to_family(G3, Type2)),
+ ?line true = digraph:delete(G3),
+
+ Fl = 0.0, I = round(Fl),
+ if
+ Fl == I -> % term ordering
+ ?line G4 = digraph:new(),
+ digraph:add_vertex(G4, Fl),
+ digraph:add_vertex(G4, I),
+ ?line {'EXIT', {badarg, _}} =
+ (catch digraph_to_family(G4, Type)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch digraph_to_family(G4)),
+ ?line true = digraph:delete(G4);
+ true -> ok
+ end,
+
+ ?line true = T0 == ets:all(),
+ ok.
+
+constant_function(suite) -> [];
+constant_function(doc) -> [""];
+constant_function(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line C = from_term(3),
+ ?line eval(constant_function(E, C), E),
+ ?line eval(constant_function(set([a,b]), E), from_term([{a,[]},{b,[]}])),
+ ?line eval(constant_function(set([a,b]), C), from_term([{a,3},{b,3}])),
+ ?line {'EXIT', {badarg, _}} = (catch constant_function(C, C)),
+ ?line {'EXIT', {badarg, _}} = (catch constant_function(set([]), foo)),
+ ok.
+
+misc(suite) -> [];
+misc(doc) -> [""];
+misc(Conf) when list(Conf) ->
+ % find "relational" part of relation:
+ ?line S = relation([{a,b},{b,c},{b,d},{c,d}]),
+ Id = fun(A) -> A end,
+ ?line RR = relational_restriction(S),
+ ?line eval(union(difference(partition(Id,S), partition(1,S))), RR),
+ ?line eval(union(difference(partition(1,S), partition(Id,S))), RR),
+
+ % the "functional" part:
+ ?line eval(union(intersection(partition(1,S), partition(Id,S))),
+ difference(S, RR)),
+
+ %% The function external:foo/1 is undefined.
+ ?line {'EXIT', {undef, _}} =
+ (catch projection({external,foo}, set([a,b,c]))),
+ ok.
+
+relational_restriction(R) ->
+ Fun = fun(S) -> no_elements(S) > 1 end,
+ family_to_relation(family_specification(Fun, relation_to_family(R))).
+
+sofs_family(suite) ->
+ [family_specification, family_domain_1, family_range_1,
+ family_to_relation_1, union_of_family_1, intersection_of_family_1,
+ family_projection, family_difference,
+ family_intersection_1, family_intersection_2,
+ family_union_1, family_union_2, partition_family].
+
+family_specification(suite) -> [];
+family_specification(doc) -> [""];
+family_specification(Conf) when list(Conf) ->
+ E = empty_set(),
+ %% internal
+ ?line eval(family_specification({sofs, is_set}, E), E),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_specification({sofs,is_set}, set([]))),
+ ?line F1 = from_term([{1,[1]}]),
+ ?line eval(family_specification({sofs,is_set}, F1), F1),
+ Fun = fun(S) -> is_subset(S, set([0,1,2,3,4])) end,
+ ?line F2 = family([{a,[1,2]},{b,[3,4,5]}]),
+ ?line eval(family_specification(Fun, F2), family([{a,[1,2]}])),
+ ?line F3 = from_term([{a,[]},{b,[]}]),
+ ?line eval(family_specification({sofs,is_set}, F3), F3),
+ Fun2 = fun(_) -> throw(fippla) end,
+ ?line fippla = (catch family_specification(Fun2, family([{a,[1]}]))),
+ Fun3 = fun(_) -> neither_true_or_false end,
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_specification(Fun3, F3)),
+
+ %% external
+ IsList = {external, fun(L) when list(L) -> true; (_) -> false end},
+ ?line eval(family_specification(IsList, E), E),
+ ?line eval(family_specification(IsList, F1), F1),
+ MF = {external, fun(L) -> lists:member(3, L) end},
+ ?line eval(family_specification(MF, F2), family([{b,[3,4,5]}])),
+ ?line fippla = (catch family_specification(Fun2, family([{a,[1]}]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_specification({external, Fun3}, F3)),
+ ok.
+
+family_domain_1(suite) -> [];
+family_domain_1(doc) -> [""];
+family_domain_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = from_term([{a,[]},{b,[]}],[{atom,[{atom,atom}]}]),
+ ?line EF = from_term([{a,[]},{b,[]}],[{atom,[atom]}]),
+ ?line eval(family_domain(E), E),
+ ?line eval(family_domain(ER), EF),
+ ?line FR = from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),
+ ?line eval(family_domain(FR), from_term([{a,[1,2,3]},{b,[]},{c,[4,5]}])),
+ ?line eval(family_field(E), E),
+ ?line eval(family_field(FR),
+ from_term([{a,[a,b,c,1,2,3]},{b,[]},{c,[d,e,4,5]}])),
+ ?line eval(family_domain(from_term([{{a},[{{1,[]},c}]}])),
+ from_term([{{a},[{1,[]}]}])),
+ ?line eval(family_domain(from_term([{{a},[{{1,[a]},c}]}])),
+ from_term([{{a},[{1,[a]}]}])),
+ ?line eval(family_domain(from_term([{{a},[]}])),
+ from_term([{{a},[]}])),
+ ?line eval(family_domain(from_term([], type(FR))),
+ from_term([], [{atom,[atom]}])),
+ ?line {'EXIT', {badarg, _}} = (catch family_domain(set([a]))),
+ ?line {'EXIT', {badarg, _}} = (catch family_field(set([a]))),
+ ?line {'EXIT', {badarg, _}} = (catch family_domain(set([{a,[b]}]))),
+ ok.
+
+family_range_1(suite) -> [];
+family_range_1(doc) -> [""];
+family_range_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = from_term([{a,[]},{b,[]}],[{atom,[{atom,atom}]}]),
+ ?line EF = from_term([{a,[]},{b,[]}],[{atom,[atom]}]),
+ ?line eval(family_range(E), E),
+ ?line eval(family_range(ER), EF),
+ ?line FR = from_term([{a,[{1,a},{2,b},{3,c}]},{b,[]},{c,[{4,d},{5,e}]}]),
+ ?line eval(family_range(FR), from_term([{a,[a,b,c]},{b,[]},{c,[d,e]}])),
+ ?line eval(family_range(from_term([{{a},[{c,{1,[a]}}]}])),
+ from_term([{{a},[{1,[a]}]}])),
+ ?line eval(family_range(from_term([{{a},[{c,{1,[]}}]}])),
+ from_term([{{a},[{1,[]}]}])),
+ ?line eval(family_range(from_term([{{a},[]}])),
+ from_term([{{a},[]}])),
+ ?line eval(family_range(from_term([], type(FR))),
+ from_term([], [{atom,[atom]}])),
+ ?line {'EXIT', {badarg, _}} = (catch family_range(set([a]))),
+ ?line {'EXIT', {badarg, _}} = (catch family_range(set([{a,[b]}]))),
+ ok.
+
+family_to_relation_1(suite) -> [];
+family_to_relation_1(doc) -> [""];
+family_to_relation_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line ER = relation([]),
+ ?line EF = family([]),
+ ?line eval(family_to_relation(E), E),
+ ?line eval(family_to_relation(EF), ER),
+ ?line eval(sofs:fam2rel(EF), ER),
+ ?line F = family([{a,[]},{b,[1]},{c,[7,9,11]}]),
+ ?line eval(family_to_relation(F), relation([{b,1},{c,7},{c,9},{c,11}])),
+ ?line {'EXIT', {badarg, _}} = (catch family_to_relation(set([a]))),
+ ok.
+
+union_of_family_1(suite) -> [];
+union_of_family_1(doc) -> [""];
+union_of_family_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = from_term([{a,[]},{b,[]}],[{atom,[atom]}]),
+ ?line eval(union_of_family(E), E),
+ ?line eval(union_of_family(EF), set([])),
+ ?line eval(union_of_family(family([])), set([])),
+ ?line FR = from_term([{a,[1,2,3]},{b,[]},{c,[4,5]}]),
+ ?line eval(union_of_family(FR), set([1,2,3,4,5])),
+ ?line eval(union_of_family(sofs:family([{a,[1,2]},{b,[1,2]}])),
+ set([1,2])),
+ ?line {'EXIT', {badarg, _}} = (catch union_of_family(set([a]))),
+ ok.
+
+intersection_of_family_1(suite) -> [];
+intersection_of_family_1(doc) -> [""];
+intersection_of_family_1(Conf) when list(Conf) ->
+ ?line EF = from_term([{a,[]},{b,[]}],[{atom,[atom]}]),
+ ?line eval(intersection_of_family(EF), set([])),
+ ?line FR = from_term([{a,[1,2,3]},{b,[2,3]},{c,[3,4,5]}]),
+ ?line eval(intersection_of_family(FR), set([3])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch intersection_of_family(family([]))),
+ ?line EE = from_term([], [[atom]]),
+ ?line {'EXIT', {badarg, _}} = (catch intersection_of_family(EE)),
+ ?line {'EXIT', {badarg, _}} = (catch intersection_of_family(set([a]))),
+ ok.
+
+family_projection(suite) -> [];
+family_projection(doc) -> [""];
+family_projection(Conf) when list(Conf) ->
+ SSType = [{atom,[[atom]]}],
+ SRType = [{atom,[{atom,atom}]}],
+ ?line E = empty_set(),
+
+ ?line eval(family_projection(fun(X) -> X end, family([])), E),
+ ?line L1 = [{a,[]}],
+ ?line eval(family_projection({sofs,union}, E), E),
+ ?line eval(family_projection({sofs,union}, from_term(L1, SSType)),
+ family(L1)),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_projection({sofs,union}, set([]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_projection({sofs,union}, from_term([{1,[1]}]))),
+
+ ?line F2 = from_term([{a,[[1],[2]]},{b,[[3,4],[5]]}], SSType),
+ ?line eval(family_projection({sofs,union}, F2),
+ family_union(F2)),
+
+ ?line F3 = from_term([{1,[{a,b},{b,c},{c,d}]},{3,[]},{5,[{3,5}]}],
+ SRType),
+ ?line eval(family_projection({sofs,domain}, F3), family_domain(F3)),
+ ?line eval(family_projection({sofs,range}, F3), family_range(F3)),
+
+ ?line eval(family_projection(fun(_) -> E end, family([{a,[b,c]}])),
+ from_term([{a,[]}])),
+
+ Fun1 = fun(S) ->
+ case to_external(S) of
+ [1] -> from_term({1,1});
+ _ -> S
+ end
+ end,
+ ?line eval(family_projection(Fun1, family([{a,[1]}])),
+ from_term([{a,{1,1}}])),
+ Fun2 = fun(_) -> throw(fippla) end,
+ ?line fippla =
+ (catch family_projection(Fun2, family([{a,[1]}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch family_projection(Fun1, from_term([{1,[1]},{2,[2]}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch family_projection(Fun1, from_term([{1,[1]},{0,[0]}]))),
+
+ ?line eval(family_projection(fun(_) -> E end, from_term([{a,[]}])),
+ from_term([{a,[]}])),
+ F4 = from_term([{a,[{1,2,3}]},{b,[{4,5,6}]},{c,[]},{m3,[]}]),
+ Z = from_term(0),
+ ?line eval(family_projection(fun(S) -> local_adjoin(S, Z) end, F4),
+ from_term([{a,[{{1,2,3},0}]},{b,[{{4,5,6},0}]},{c,[]},{m3,[]}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_projection({external, fun(X) -> X end},
+ from_term([{1,[1]}]))),
+
+ %% ordered set element
+ ?line eval(family_projection(fun(_) -> from_term(a, atom) end,
+ from_term([{1,[a]}])),
+ from_term([{1,a}])),
+ ok.
+
+family_difference(suite) -> [];
+family_difference(doc) -> [""];
+family_difference(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = family([]),
+ ?line F9 = from_term([{b,[b,c]}]),
+ ?line F10 = from_term([{a,[b,c]}]),
+ ?line eval(family_difference(E, E), E),
+ ?line eval(family_difference(E, F10), from_term([], type(F10))),
+ ?line eval(family_difference(F10, E), F10),
+ ?line eval(family_difference(F9, F10), F9),
+ ?line eval(family_difference(F10, F10), family([{a,[]}])),
+ ?line F20 = from_term([{a,[1,2,3]},{b,[1,2,3]},{c,[1,2,3]}]),
+ ?line F21 = from_term([{b,[1,2,3]},{c,[1,2,3]}]),
+ ?line eval(family_difference(F20, from_term([{a,[2]}])),
+ from_term([{a,[1,3]},{b,[1,2,3]},{c,[1,2,3]}])),
+ ?line eval(family_difference(F20, from_term([{0,[2]},{q,[1,2]}])), F20),
+ ?line eval(family_difference(F20, F21),
+ from_term([{a,[1,2,3]},{b,[]},{c,[]}])),
+
+ ?line eval(family_difference(from_term([{e,[f,g]}]), family([])),
+ from_term([{e,[f,g]}])),
+ ?line eval(family_difference(from_term([{e,[f,g]}]), EF),
+ from_term([{e,[f,g]}])),
+ ?line eval(family_difference(from_term([{a,[a,b,c,d]},{c,[b,c]}]),
+ from_term([{a,[b,c]},{b,[d]},{d,[e,f]}])),
+ from_term([{a,[a,d]},{c,[b,c]}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_difference(set([]), set([]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch family_difference(from_term([{a,[b,c]}]),
+ from_term([{e,[{f}]}]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch family_difference(from_term([{a,[b]}]),
+ from_term([{c,[d]}], [{i,[s]}]))),
+ ok.
+
+family_intersection_1(suite) -> [];
+family_intersection_1(doc) -> [""];
+family_intersection_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = family([]),
+ ?line ES = from_term([], [{atom,[[atom]]}]),
+ ?line eval(family_intersection(E), E),
+ ?line {'EXIT', {badarg, _}} = (catch family_intersection(EF)),
+ ?line eval(family_intersection(ES), EF),
+ ?line {'EXIT', {badarg, _}} = (catch family_intersection(set([]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_intersection(from_term([{a,[1,2]}]))),
+ ?line F1 = from_term([{a,[[1],[2],[2,3]]},{b,[]},{c,[[4]]}]),
+ ?line {'EXIT', {badarg, _}} = (catch family_intersection(F1)),
+ ?line F2 = from_term([{b,[[1],[2],[2,3]]},{a,[]},{c,[[4]]}]),
+ ?line {'EXIT', {badarg, _}} = (catch family_intersection(F2)),
+ ?line F3 = from_term([{a,[[1,2,3],[2],[2,3]]},{c,[[4,5,6],[5,6,7]]}]),
+ ?line eval(family_intersection(F3), family([{a,[2]},{c,[5,6]}])),
+ ok.
+
+family_intersection_2(suite) -> [];
+family_intersection_2(doc) -> [""];
+family_intersection_2(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = family([]),
+ ?line F1 = from_term([{a,[1,2]},{b,[4,5]},{c,[7,8]},{d,[10,11]}]),
+ ?line F2 = from_term([{c,[6,7]},{d,[9,10,11]},{q,[1]}]),
+ ?line F3 = from_term([{a,[1,2]},{b,[4,5]},{c,[6,7,8]},{d,[9,10,11]},
+ {q,[1]}]),
+
+ ?line eval(family_intersection(E, E), E),
+ ?line eval(family_intersection(EF, EF), EF),
+ ?line eval(family_intersection(F1, F2),
+ from_term([{c,[7]},{d,[10,11]}])),
+ ?line eval(family_intersection(F1, F3), F1),
+ ?line eval(family_intersection(F2, F3), F2),
+
+ ?line eval(family_intersection(EF, from_term([{e,[f,g]}])), EF),
+ ?line eval(family_intersection(E, from_term([{e,[f,g]}])), EF),
+ ?line eval(family_intersection(from_term([{e,[f,g]}]), EF), EF),
+ ?line eval(family_intersection(from_term([{e,[f,g]}]), E), EF),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch family_intersection(from_term([{a,[b,c]}]),
+ from_term([{e,[{f}]}]))),
+
+ ?line F11 = family([{a,[1,2,3]},{b,[0,2,4]},{c,[0,3,6,9]}]),
+ ?line eval(union_of_family(F11), set([0,1,2,3,4,6,9])),
+ ?line F12 = from_term([{a,[1,2,3,4]},{b,[0,2,4]},{c,[2,3,4,5]}]),
+ ?line eval(intersection_of_family(F12), set([2,4])),
+ ok.
+
+family_union_1(suite) -> [];
+family_union_1(doc) -> [""];
+family_union_1(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = family([]),
+ ?line ES = from_term([], [{atom,[[atom]]}]),
+ ?line eval(family_union(E), E),
+ ?line eval(family_union(ES), EF),
+ ?line {'EXIT', {badarg, _}} = (catch family_union(set([]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_union(from_term([{a,[1,2]}]))),
+ ?line eval(family_union(from_term([{a,[[1],[2],[2,3]]},{b,[]},{c,[[4]]}])),
+ family([{a,[1,2,3]},{b,[]},{c,[4]}])),
+ ok.
+
+family_union_2(suite) -> [];
+family_union_2(doc) -> [""];
+family_union_2(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+ ?line EF = family([]),
+ ?line F1 = from_term([{a,[1,2]},{b,[4,5]},{c,[7,8]},{d,[10,11]}]),
+ ?line F2 = from_term([{c,[6,7]},{d,[9,10,11]},{q,[1]}]),
+ ?line F3 = from_term([{a,[1,2]},{b,[4,5]},{c,[6,7,8]},{d,[9,10,11]},
+ {q,[1]}]),
+
+ ?line eval(family_union(E, E), E),
+ ?line eval(family_union(F1, E), F1),
+ ?line eval(family_union(E, F2), F2),
+ ?line eval(family_union(F1, F2), F3),
+ ?line eval(family_union(F2, F1), F3),
+
+ ?line eval(family_union(E, from_term([{e,[f,g]}])),
+ from_term([{e,[f,g]}])),
+ ?line eval(family_union(EF, from_term([{e,[f,g]}])),
+ from_term([{e,[f,g]}])),
+ ?line eval(family_union(from_term([{e,[f,g]}]), E),
+ from_term([{e,[f,g]}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch family_union(set([]),set([]))),
+ ?line {'EXIT', {type_mismatch, _}} =
+ (catch family_union(from_term([{a,[b,c]}]),
+ from_term([{e,[{f}]}]))),
+ ok.
+
+partition_family(suite) -> [];
+partition_family(doc) -> [""];
+partition_family(Conf) when list(Conf) ->
+ ?line E = empty_set(),
+
+ %% set of ordered sets
+ ?line ER = relation([]),
+ ?line EF = from_term([], [{atom,[{atom,atom}]}]),
+
+ ?line eval(partition_family(1, E), E),
+ ?line eval(partition_family(2, E), E),
+ ?line eval(partition_family({sofs,union}, E), E),
+ ?line eval(partition_family(1, ER), EF),
+ ?line eval(partition_family(2, ER), EF),
+ ?line {'EXIT', {badarg, _}} = (catch partition_family(1, set([]))),
+ ?line {'EXIT', {badarg, _}} = (catch partition_family(2, set([]))),
+ ?line {'EXIT', {function_clause, _}} =
+ (catch partition_family(fun({_A,B}) -> {B} end, from_term([{1}]))),
+ ?line eval(partition_family(1, relation([{1,a},{1,b},{2,c},{2,d}])),
+ from_term([{1,[{1,a},{1,b}]},{2,[{2,c},{2,d}]}])),
+ ?line eval(partition_family(1, relation([{1,a},{2,b}])),
+ from_term([{1,[{1,a}]},{2,[{2,b}]}])),
+ ?line eval(partition_family(2, relation([{1,a},{1,b},{2,a},{2,b},{3,c}])),
+ from_term([{a,[{1,a},{2,a}]},{b,[{1,b},{2,b}]},{c,[{3,c}]}])),
+ ?line eval(partition_family(2, relation([{1,a}])),
+ from_term([{a,[{1,a}]}])),
+ ?line eval(partition_family(2, relation([{1,a},{2,a},{3,a}])),
+ from_term([{a,[{1,a},{2,a},{3,a}]}])),
+ ?line eval(partition_family(2, relation([{1,a},{2,b}])),
+ from_term([{a,[{1,a}]},{b,[{2,b}]}])),
+ ?line F13 = from_term([{a,b,c},{a,b,d},{b,b,c},{a,c,c},{a,c,d},{b,c,c}]),
+ ?line eval(partition_family(2, F13),
+ from_term([{b,[{a,b,c},{a,b,d},{b,b,c}]},
+ {c,[{a,c,c},{a,c,d},{b,c,c}]}])),
+
+ Fun1 = {external, fun({A,_B}) -> {A} end},
+ ?line eval(partition_family(Fun1, relation([{a,1},{a,2},{b,3}])),
+ from_term([{{a},[{a,1},{a,2}]},{{b},[{b,3}]}])),
+ Fun2 = fun(S) -> {A,_B} = to_external(S), from_term({A}) end,
+ ?line eval(partition_family(Fun2, relation([{a,1},{a,2},{b,3}])),
+ from_term([{{a},[{a,1},{a,2}]},{{b},[{b,3}]}])),
+
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition_family({external, fun({A,_}) -> {A,0} end},
+ from_term([{1,a}]))),
+ ?line [{{atom,atom},[{atom,atom,atom,atom}]}] =
+ type(partition_family({external, fun({A,_B,C,_D}) -> {C,A} end},
+ relation([],4))),
+
+ Fun3 = fun(S) -> from_term({to_external(S),0}, {type(S),atom}) end,
+ ?line eval(partition_family(Fun3, E), E),
+ ?line eval(partition_family(Fun3, set([a,b])),
+ from_term([{{a,0},[a]}, {{b,0},[b]}])),
+ ?line eval(partition_family(Fun3, relation([{a,1},{b,2}])),
+ from_term([{{{a,1},0},[{a,1}]},{{{b,2},0},[{b,2}]}])),
+ ?line eval(partition_family(Fun3, from_term([[a],[b]])),
+ from_term([{{[a],0},[[a]]}, {{[b],0},[[b]]}])),
+ ?line partition_family({external, fun(X) -> X end}, E),
+
+ F = 0.0, I = round(F),
+ ?line FR = relation([{I,a},{F,b}]),
+ if
+ F == I -> % term ordering
+ ?line true = (1 =:= no_elements(partition_family(1, FR)));
+ true ->
+ ?line eval(partition_family(1, FR),
+ from_term([{I,[{I,a}]},{F,[{F,b}]}]))
+ end,
+ %% set of sets
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition_family({external, fun(X) -> X end},
+ from_term([], [[atom]]))),
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition_family({external, fun(X) -> X end},
+ from_term([[a]]))),
+ ?line eval(partition_family({sofs,union},
+ from_term([[[1],[1,2]], [[1,2]]])),
+ from_term([{[1,2], [[[1],[1,2]],[[1,2]]]}])),
+ ?line eval(partition_family(fun(X) -> X end,
+ from_term([[1],[1,2],[1,2,3]])),
+ from_term([{[1],[[1]]},{[1,2],[[1,2]]},{[1,2,3],[[1,2,3]]}])),
+
+ ?line eval(partition_family(fun(_) -> from_term([a]) end,
+ from_term([], [[atom]])),
+ E),
+ Fun10 = fun(S) ->
+ %% Cheating a lot...
+ case to_external(S) of
+ [1] -> from_term({1,1});
+ _ -> S
+ end
+ end,
+
+ ?line eval(partition_family(Fun10, from_term([[1]])),
+ from_term([{{1,1},[[1]]}])),
+ ?line eval(partition_family(fun(_) -> from_term({a}) end,
+ from_term([[a]])),
+ from_term([{{a},[[a]]}])),
+ ?line {'EXIT', {badarg, _}} =
+ (catch partition_family(fun(_) -> {a} end, from_term([[a]]))),
+ ok.
+
+%% Not meant to be efficient...
+local_adjoin(S, C) ->
+ X = to_external(C),
+ T = type(C),
+ F = fun(Y) -> from_term({to_external(Y),X}, {type(Y),T}) end,
+ projection(F, S).
+
+eval(R, E) when R == E ->
+ R;
+eval(R, E) ->
+ io:format("expected ~p~n got ~p~n", [E, R]),
+ exit({R,E}).
+
diff --git a/lib/stdlib/test/stdlib.cover b/lib/stdlib/test/stdlib.cover
new file mode 100644
index 0000000000..b98d949889
--- /dev/null
+++ b/lib/stdlib/test/stdlib.cover
@@ -0,0 +1,10 @@
+%% -*- erlang -*-
+{exclude,
+ [erl_parse,
+ ets,
+ filename,
+ gen_event,
+ gen_server,
+ gen,
+ lists,
+ proc_lib]}.
diff --git a/lib/stdlib/test/stdlib.spec b/lib/stdlib/test/stdlib.spec
new file mode 100644
index 0000000000..bbfb43bd15
--- /dev/null
+++ b/lib/stdlib/test/stdlib.spec
@@ -0,0 +1,4 @@
+{topcase, {dir, "../stdlib_test"}}.
+%{skip,{dets_SUITE,open_file_1,"Crashes Windows tests"}}.
+%{skip,{dets_SUITE,fold,"Crashes Windows tests"}}.
+%{skip,{dets_SUITE,match,"Crashes Windows tests"}}.
diff --git a/lib/stdlib/test/stdlib.spec.vxworks b/lib/stdlib/test/stdlib.spec.vxworks
new file mode 100644
index 0000000000..ddc804b831
--- /dev/null
+++ b/lib/stdlib/test/stdlib.spec.vxworks
@@ -0,0 +1,8 @@
+{topcase, {dir, "../stdlib_test"}}.
+{skip,{dets_SUITE,"Not runnable VxWorks/NFS"}}.
+{skip,{slave_SUITE,"VxWorks: slave nodes are not supported"}}.
+{skip,{tar_SUITE,errors,"VxWorks filesystem too primitive"}}.
+{skip,{tar_SUITE,create_long_names,"VxWorks names too short"}}.
+{skip,{epp_SUITE,"Test not adopted to VxWorks"}}.
+{skip,{select_SUITE,"Test too memory consuming for VxWorks"}}.
+{skip,{beam_lib_SUITE,error,"All sections not present in stripped beam files"}}.
diff --git a/lib/stdlib/test/stdlib_SUITE.erl b/lib/stdlib/test/stdlib_SUITE.erl
new file mode 100644
index 0000000000..d46a2caf90
--- /dev/null
+++ b/lib/stdlib/test/stdlib_SUITE.erl
@@ -0,0 +1,64 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------
+%%% Purpose:Stdlib application test suite.
+%%%-----------------------------------------------------------------
+-module(stdlib_SUITE).
+-include("test_server.hrl").
+
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+-define(application, stdlib).
+
+% Test server specific exports
+-export([all/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+% Test cases must be exported.
+-export([app_test/1]).
+-define(cases, [app_test]).
+
+%%
+%% all/1
+%%
+all(doc) ->
+ [];
+all(suite) ->
+ [?cases].
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%
+% Test cases starts here.
+%
+app_test(suite) ->
+ [];
+app_test(doc) ->
+ ["Application consistency test."];
+app_test(Config) when list(Config) ->
+ ?t:app_test(stdlib),
+ ok.
+
diff --git a/lib/stdlib/test/string_SUITE.erl b/lib/stdlib/test/string_SUITE.erl
new file mode 100644
index 0000000000..3171b87c44
--- /dev/null
+++ b/lib/stdlib/test/string_SUITE.erl
@@ -0,0 +1,511 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------
+%%% Purpose: string test suite.
+%%%-----------------------------------------------------------------
+-module(string_SUITE).
+-include("test_server.hrl").
+
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+
+% Test server specific exports
+-export([all/1]).
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+% Test cases must be exported.
+-export([len/1,equal/1,concat/1,chr_rchr/1,str_rstr/1]).
+-export([span_cspan/1,substr/1,tokens/1,chars/1]).
+-export([copies/1,words/1,strip/1,sub_word/1,left_right/1]).
+-export([sub_string/1,centre/1, join/1]).
+-export([to_integer/1,to_float/1]).
+-export([to_upper_to_lower/1]).
+%%
+%% all/1
+%%
+all(doc) ->
+ [];
+all(suite) ->
+ [len,equal,concat,chr_rchr,str_rstr,
+ span_cspan,substr,tokens,chars,
+ copies,words,strip,sub_word,left_right,
+ sub_string,centre, join,
+ to_integer,to_float,to_upper_to_lower].
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%
+% Test cases starts here.
+%
+
+len(suite) ->
+ [];
+len(doc) ->
+ [];
+len(Config) when is_list(Config) ->
+ ?line 0 = string:len(""),
+ ?line L = tuple_size(list_to_tuple(atom_to_list(?MODULE))),
+ ?line L = string:len(atom_to_list(?MODULE)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:len({})),
+ ok.
+
+equal(suite) ->
+ [];
+equal(doc) ->
+ [];
+equal(Config) when is_list(Config) ->
+ ?line true = string:equal("", ""),
+ ?line false = string:equal("", " "),
+ ?line true = string:equal("laban", "laban"),
+ ?line false = string:equal("skvimp", "skvump"),
+ %% invalid arg type
+ ?line true = string:equal(2, 2), % not good, should crash
+ ok.
+
+concat(suite) ->
+ [];
+concat(doc) ->
+ [];
+concat(Config) when is_list(Config) ->
+ ?line "erlang rules" = string:concat("erlang ", "rules"),
+ ?line "" = string:concat("", ""),
+ ?line "x" = string:concat("x", ""),
+ ?line "y" = string:concat("", "y"),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:concat(hello, please)),
+ ok.
+
+chr_rchr(suite) ->
+ [];
+chr_rchr(doc) ->
+ [];
+chr_rchr(Config) when is_list(Config) ->
+ ?line {_,_,X} = now(),
+ ?line 0 = string:chr("", (X rem (255-32)) + 32),
+ ?line 0 = string:rchr("", (X rem (255-32)) + 32),
+ ?line 1 = string:chr("x", $x),
+ ?line 1 = string:rchr("x", $x),
+ ?line 1 = string:chr("xx", $x),
+ ?line 2 = string:rchr("xx", $x),
+ ?line 3 = string:chr("xyzyx", $z),
+ ?line 3 = string:rchr("xyzyx", $z),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:chr(hello, $h)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:chr("hello", h)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:rchr(hello, $h)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:rchr("hello", h)),
+ ok.
+
+str_rstr(suite) ->
+ [];
+str_rstr(doc) ->
+ [];
+str_rstr(Config) when is_list(Config) ->
+ ?line {_,_,X} = now(),
+ ?line 0 = string:str("", [(X rem (255-32)) + 32]),
+ ?line 0 = string:rstr("", [(X rem (255-32)) + 32]),
+ ?line 1 = string:str("x", "x"),
+ ?line 1 = string:rstr("x", "x"),
+ ?line 0 = string:str("hello", ""),
+ ?line 0 = string:rstr("hello", ""),
+ ?line 1 = string:str("xxxx", "xx"),
+ ?line 3 = string:rstr("xxxx", "xx"),
+ ?line 3 = string:str("xy z yx", " z"),
+ ?line 3 = string:rstr("xy z yx", " z"),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:str(hello, "he")),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:str("hello", he)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:rstr(hello, "he")),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:rstr("hello", he)),
+ ok.
+
+span_cspan(suite) ->
+ [];
+span_cspan(doc) ->
+ [];
+span_cspan(Config) when is_list(Config) ->
+ ?line 0 = string:span("", "1"),
+ ?line 0 = string:span("1", ""),
+ ?line 0 = string:cspan("", "1"),
+ ?line 1 = string:cspan("1", ""),
+ ?line 1 = string:span("1 ", "1"),
+ ?line 5 = string:span(" 1 ", "12 "),
+ ?line 6 = string:span("1231234", "123"),
+ ?line 0 = string:cspan("1 ", "1"),
+ ?line 1 = string:cspan("3 ", "12 "),
+ ?line 6 = string:cspan("1231234", "4"),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:span(1234, "1")),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:span(1234, "1")),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:cspan("1234", 1)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:cspan("1234", 4)),
+ ok.
+
+
+substr(suite) ->
+ [];
+substr(doc) ->
+ [];
+substr(Config) when is_list(Config) ->
+ ?line {'EXIT',_} = (catch string:substr("", 0)),
+ ?line [] = string:substr("", 1),
+ ?line {'EXIT',_} = (catch string:substr("", 2)),
+ ?line [] = string:substr("1", 2),
+ ?line {'EXIT',_} = (catch string:substr("", 0, 1)),
+ ?line [] = string:substr("", 1, 1),
+ ?line [] = string:substr("", 1, 2),
+ ?line {'EXIT',_} = (catch string:substr("", 2, 2)),
+ ?line "1234" = string:substr("1234", 1),
+ ?line "1234" = string:substr("1234", 1, 4),
+ ?line "1234" = string:substr("1234", 1, 5),
+ ?line "23" = string:substr("1234", 2, 2),
+ ?line "4" = string:substr("1234", 4),
+ ?line "" = string:substr("1234", 4, 0),
+ ?line "4" = string:substr("1234", 4, 1),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:substr(1234, 1)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:substr("1234", "1")),
+ ok.
+
+tokens(suite) ->
+ [];
+tokens(doc) ->
+ [];
+tokens(Config) when is_list(Config) ->
+ ?line [] = string:tokens("",""),
+ ?line [] = string:tokens("abc","abc"),
+ ?line ["abc"] = string:tokens("abc", ""),
+ ?line ["1","2 34","4","5"] = string:tokens("1,2 34,4;5", ";,"),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:tokens('x,y', ",")),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:tokens("x,y", ',')),
+ ok.
+
+
+chars(suite) ->
+ [];
+chars(doc) ->
+ [];
+chars(Config) when is_list(Config) ->
+ ?line [] = string:chars($., 0),
+ ?line [] = string:chars($., 0, []),
+ ?line 10 = length(string:chars(32, 10, [])),
+ ?line "aaargh" = string:chars($a, 3, "rgh"),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:chars($x, [])),
+ ok.
+
+copies(suite) ->
+ [];
+copies(doc) ->
+ [];
+copies(Config) when is_list(Config) ->
+ ?line "" = string:copies("", 10),
+ ?line "" = string:copies(".", 0),
+ ?line "." = string:copies(".", 1),
+ ?line 30 = length(string:copies("123", 10)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:chars("hej", -1)),
+ ok.
+
+words(suite) ->
+ [];
+words(doc) ->
+ [];
+words(Config) when is_list(Config) ->
+ ?line 1 = string:words(""),
+ ?line 1 = string:words("", $,),
+ ?line 1 = string:words("hello"),
+ ?line 1 = string:words("hello", $,),
+ ?line 1 = string:words("...", $.),
+ ?line 2 = string:words("2.35", $.),
+ ?line 100 = string:words(string:copies(". ", 100)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:chars(hej)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:chars("hej", " ")),
+ ok.
+
+
+strip(suite) ->
+ [];
+strip(doc) ->
+ [];
+strip(Config) when is_list(Config) ->
+ ?line "" = string:strip(""),
+ ?line "" = string:strip("", both),
+ ?line "" = string:strip("", both, $.),
+ ?line "hej" = string:strip(" hej "),
+ ?line "hej " = string:strip(" hej ", left),
+ ?line " hej" = string:strip(" hej ", right),
+ ?line " hej " = string:strip(" hej ", right, $.),
+ ?line "hej hopp" = string:strip(" hej hopp ", both),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:strip(hej)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:strip(" hej", up)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:strip(" hej", left, " ")), % not good
+ ok.
+
+sub_word(suite) ->
+ [];
+sub_word(doc) ->
+ [];
+sub_word(Config) when is_list(Config) ->
+ ?line "" = string:sub_word("", 1),
+ ?line "" = string:sub_word("", 1, $,),
+ ?line {'EXIT',_} = (catch string:sub_word("1 2 3", 0)),
+ ?line "" = string:sub_word("1 2 3", 4),
+ ?line "llo th" = string:sub_word("but hello there", 2, $e),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:sub_word('hello there', 1)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:sub_word("hello there", 1, "e")),
+ ok.
+
+left_right(suite) ->
+ [];
+left_right(doc) ->
+ [];
+left_right(Config) when is_list(Config) ->
+ ?line "" = string:left("", 0),
+ ?line "" = string:left("hej", 0),
+ ?line "" = string:left("hej", 0, $.),
+ ?line "" = string:right("", 0),
+ ?line "" = string:right("hej", 0),
+ ?line "" = string:right("hej", 0, $.),
+ ?line "123 " = string:left("123 ", 5),
+ ?line " 123" = string:right(" 123", 5),
+ ?line "123!!" = string:left("123!", 5, $!),
+ ?line "==123" = string:right("=123", 5, $=),
+ ?line "1" = string:left("123", 1, $.),
+ ?line "3" = string:right("123", 1, $.),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:left(hello, 5)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:right(hello, 5)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:left("hello", 5, ".")),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:right("hello", 5, ".")),
+ ok.
+
+sub_string(suite) ->
+ [];
+sub_string(doc) ->
+ [];
+sub_string(Config) when is_list(Config) ->
+ ?line {'EXIT',_} = (catch string:sub_string("", 0)),
+ ?line [] = string:sub_string("", 1),
+ ?line {'EXIT',_} = (catch string:sub_string("", 2)),
+ ?line [] = string:sub_string("1", 2),
+ ?line {'EXIT',_} = (catch string:sub_string("", 0, 1)),
+ ?line [] = string:sub_string("", 1, 1),
+ ?line [] = string:sub_string("", 1, 2),
+ ?line {'EXIT',_} = (catch string:sub_string("", 2, 2)),
+ ?line "1234" = string:sub_string("1234", 1),
+ ?line "1234" = string:sub_string("1234", 1, 4),
+ ?line "1234" = string:sub_string("1234", 1, 5),
+ ?line "23" = string:sub_string("1234", 2, 3),
+ ?line "4" = string:sub_string("1234", 4),
+ ?line "4" = string:sub_string("1234", 4, 4),
+ ?line "4" = string:sub_string("1234", 4, 5),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:sub_string(1234, 1)),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:sub_string("1234", "1")),
+ ok.
+
+centre(suite) ->
+ [];
+centre(doc) ->
+ [];
+centre(Config) when is_list(Config) ->
+ ?line "" = string:centre("", 0),
+ ?line "" = string:centre("1", 0),
+ ?line "" = string:centre("", 0, $-),
+ ?line "" = string:centre("1", 0, $-),
+ ?line "gd" = string:centre("agda", 2),
+ ?line "agda " = string:centre("agda", 5),
+ ?line " agda " = string:centre("agda", 6),
+ ?line "agda." = string:centre("agda", 5, $.),
+ ?line "--agda--" = string:centre("agda", 8, $-),
+ ?line "agda" = string:centre("agda", 4),
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:centre(hello, 10)),
+ ok.
+
+to_integer(suite) ->
+ [];
+to_integer(doc) ->
+ [];
+to_integer(Config) when is_list(Config) ->
+ ?line {1,""} = test_to_integer("1"),
+ ?line {1,""} = test_to_integer("+1"),
+ ?line {-1,""} = test_to_integer("-1"),
+ ?line {1,"="} = test_to_integer("1="),
+ ?line {7,"F"} = test_to_integer("7F"),
+ ?line {709,""} = test_to_integer("709"),
+ ?line {709,"*2"} = test_to_integer("709*2"),
+ ?line {0,"xAB"} = test_to_integer("0xAB"),
+ ?line {16,"#FF"} = test_to_integer("16#FF"),
+ ?line {error,no_integer} = test_to_integer(""),
+ ?line {error,no_integer} = test_to_integer("!1"),
+ ?line {error,no_integer} = test_to_integer("F1"),
+ ?line {error,not_a_list} = test_to_integer('23'),
+ ?line {3,[[]]} = test_to_integer([$3,[]]),
+ ?line {3,[hello]} = test_to_integer([$3,hello]),
+ ok.
+
+test_to_integer(Str) ->
+ io:format("Checking ~p~n", [Str]),
+ case string:to_integer(Str) of
+ {error,_Reason} = Bad ->
+ ?line {'EXIT',_} = (catch list_to_integer(Str)),
+ Bad;
+ {F,_Rest} = Res ->
+ ?line _ = integer_to_list(F),
+ Res
+ end.
+
+to_float(suite) ->
+ [];
+to_float(doc) ->
+ [];
+to_float(Config) when is_list(Config) ->
+ ?line {1.2,""} = test_to_float("1.2"),
+ ?line {1.2,""} = test_to_float("1,2"),
+ ?line {120.0,""} = test_to_float("1.2e2"),
+ ?line {120.0,""} = test_to_float("+1,2e2"),
+ ?line {-120.0,""} = test_to_float("-1.2e2"),
+ ?line {-120.0,""} = test_to_float("-1,2e+2"),
+ ?line {-1.2e-2,""} = test_to_float("-1.2e-2"),
+ ?line {1.2,"="} = test_to_float("1.2="),
+ ?line {7.9,"e"} = test_to_float("7.9e"),
+ ?line {7.9,"ee"} = test_to_float("7.9ee"),
+ ?line {7.9,"e+"} = test_to_float("7.9e+"),
+ ?line {7.9,"e-"} = test_to_float("7.9e-"),
+ ?line {7.9,"e++"} = test_to_float("7.9e++"),
+ ?line {7.9,"e--"} = test_to_float("7.9e--"),
+ ?line {7.9,"e+e"} = test_to_float("7.9e+e"),
+ ?line {7.9,"e-e"} = test_to_float("7.9e-e"),
+ ?line {7.9,"e+."} = test_to_float("7.9e+."),
+ ?line {7.9,"e-."} = test_to_float("7.9e-."),
+ ?line {7.9,"e+,"} = test_to_float("7.9e+,"),
+ ?line {7.9,"e-,"} = test_to_float("7.9e-,"),
+ ?line {error,no_float} = test_to_float(""),
+ ?line {error,no_float} = test_to_float("e1,0"),
+ ?line {error,no_float} = test_to_float("1;0"),
+ ?line {error,no_float} = test_to_float("1"),
+ ?line {error,no_float} = test_to_float("1e"),
+ ?line {error,no_float} = test_to_float("2."),
+ ?line {error,not_a_list} = test_to_float('2.3'),
+ ?line {2.3,[[]]} = test_to_float([$2,$.,$3,[]]),
+ ?line {2.3,[hello]} = test_to_float([$2,$.,$3,hello]),
+ ok.
+
+test_to_float(Str) ->
+ io:format("Checking ~p~n", [Str]),
+ case string:to_float(Str) of
+ {error,_Reason} = Bad ->
+ ?line {'EXIT',_} = (catch list_to_float(Str)),
+ Bad;
+ {F,_Rest} = Res ->
+ ?line _ = float_to_list(F),
+ Res
+ end.
+
+to_upper_to_lower(suite) ->
+ [];
+to_upper_to_lower(doc) ->
+ [];
+to_upper_to_lower(Config) when is_list(Config) ->
+ ?line "1234ABCDEF���=" = string:to_upper("1234abcdef���="),
+ ?line "����������abc()" = string:to_lower("����������abc()"),
+ ?line All = lists:seq(0, 255),
+
+ ?line UC = string:to_upper(All),
+ ?line 256 = length(UC),
+ ?line all_upper_latin1(UC, 0),
+
+ ?line LC = string:to_lower(All),
+ ?line all_lower_latin1(LC, 0),
+
+ ?line LC = string:to_lower(string:to_upper(LC)),
+ ?line LC = string:to_lower(string:to_upper(UC)),
+ ?line UC = string:to_upper(string:to_lower(LC)),
+ ?line UC = string:to_upper(string:to_lower(UC)),
+ ok.
+
+all_upper_latin1([C|T], C) when 0 =< C, C < $a;
+ $z < C, C < 16#E0;
+ C =:= 16#F7; C =:= 16#FF ->
+ all_upper_latin1(T, C+1);
+all_upper_latin1([H|T], C) when $a =< C, C =< $z;
+ 16#E0 =< C, C =< 16#F6;
+ 16#F8 =< C, C =< 16#FE ->
+ H = C - 32,
+ all_upper_latin1(T, C+1);
+all_upper_latin1([], 256) -> ok.
+
+all_lower_latin1([C|T], C) when 0 =< C, C < $A;
+ $Z < C, C < 16#C0;
+ C =:= 16#D7;
+ 16#DF =< C, C =< 255 ->
+ all_lower_latin1(T, C+1);
+all_lower_latin1([H|T], C) when $A =< C, C =< $Z;
+ 16#C0 =< C, C =< 16#F6;
+ 16#C8 =< C, C =< 16#DE ->
+ io:format("~p\n", [{H,C}]),
+ H = C + 32,
+ all_lower_latin1(T, C+1);
+all_lower_latin1([], 256) -> ok.
+
+join(suite) ->
+ [];
+join(doc) ->
+ [];
+join(Config) when is_list(Config) ->
+ ?line "erlang rules" = string:join(["erlang", "rules"], " "),
+ ?line "a,-,b,-,c" = string:join(["a", "b", "c"], ",-,"),
+ ?line "1234" = string:join(["1", "2", "3", "4"], ""),
+ ?line [] = string:join([], ""), % OTP-7231
+ %% invalid arg type
+ ?line {'EXIT',_} = (catch string:join([apa], "")),
+ ok.
diff --git a/lib/stdlib/test/suite_release.exclude b/lib/stdlib/test/suite_release.exclude
new file mode 100644
index 0000000000..ecad6e9c08
--- /dev/null
+++ b/lib/stdlib/test/suite_release.exclude
@@ -0,0 +1,4 @@
+tar_SUITE.erl
+tar_SUITE_data
+{skip,{ets_SUITE,partly_bound,"System dependent"}}.
+{skip,{gen_server_SUITE,multicall_down,"Network dependent"}}.
diff --git a/lib/stdlib/test/supervisor_1.erl b/lib/stdlib/test/supervisor_1.erl
new file mode 100644
index 0000000000..297550b230
--- /dev/null
+++ b/lib/stdlib/test/supervisor_1.erl
@@ -0,0 +1,79 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Description: Simulates the behaviour that a child process may have.
+%% Is used by the supervisor_SUITE test suite.
+-module(supervisor_1).
+
+-export([start_child/0, start_child/1, init/1]).
+
+-export([handle_call/3, handle_info/2, terminate/2]).
+
+start_child(ignore) ->
+ case get(child_ignored) of
+ true ->
+ start_child();
+ _ ->
+ put(child_ignored, true),
+ ignore
+ end;
+
+start_child(error) ->
+ case get(start_child_error) of
+ undefined ->
+ put(start_child_error, set),
+ start_child();
+ set -> gen_server:start_link(?MODULE, error, [])
+ end;
+
+
+start_child(Extra) ->
+ {ok, Pid} = gen_server:start_link(?MODULE, normal, []),
+ {ok, Pid, Extra}.
+
+start_child() ->
+ gen_server:start_link(?MODULE, normal, []).
+
+init(normal) ->
+ process_flag(trap_exit, true),
+ {ok, {}}.
+
+handle_call(Req, _From, State) ->
+ {reply, Req, State}.
+
+handle_info(die, State) ->
+ {stop, died, State};
+
+handle_info(stop, State) ->
+ {stop, normal, State};
+
+handle_info({sleep, Time}, State) ->
+ io:format("FOO: ~p~n", [Time]),
+ timer:sleep(Time),
+ io:format("FOO: sleept~n", []),
+ handle_info({sleep, Time}, State);
+
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+
+
+
diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl
new file mode 100644
index 0000000000..b5d9ca44bf
--- /dev/null
+++ b/lib/stdlib/test/supervisor_SUITE.erl
@@ -0,0 +1,1203 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Description: Tests supervisor.erl
+
+-module(supervisor_SUITE).
+
+-include("test_server.hrl").
+
+%% Testserver specific export
+-export([all/1]).
+
+%% Indirect spawn export
+-export([init/1]).
+
+%% API tests
+-export([sup_start/1, sup_start_normal/1, sup_start_ignore_init/1,
+ sup_start_ignore_child/1, sup_start_error_return/1,
+ sup_start_fail/1, sup_stop/1, sup_stop_infinity/1,
+ sup_stop_timeout/1, sup_stop_brutal_kill/1, child_adm/1,
+ child_adm_simple/1, child_specs/1, extra_return/1]).
+
+%% Tests concept permanent, transient and temporary
+-export([normal_termination/1, permanent_normal/1, transient_normal/1,
+ temporary_normal/1, abnormal_termination/1,
+ permanent_abnormal/1, transient_abnormal/1,
+ temporary_abnormal/1]).
+
+%% Restart strategy tests
+-export([restart_one_for_one/1, one_for_one/1,
+ one_for_one_escalation/1, restart_one_for_all/1, one_for_all/1,
+ one_for_all_escalation/1, restart_simple_one_for_one/1,
+ simple_one_for_one/1, simple_one_for_one_escalation/1,
+ restart_rest_for_one/1, rest_for_one/1, rest_for_one_escalation/1,
+ simple_one_for_one_extra/1]).
+
+%% Misc tests
+-export([child_unlink/1, tree/1]).
+
+%-------------------------------------------------------------------------
+
+all(suite) ->
+ {req,[stdlib],
+ [sup_start, sup_stop, child_adm,
+ child_adm_simple, extra_return, child_specs,
+ restart_one_for_one, restart_one_for_all,
+ restart_simple_one_for_one, restart_rest_for_one,
+ normal_termination, abnormal_termination, child_unlink, tree]}.
+
+
+start(InitResult) ->
+ supervisor:start_link({local, sup_test}, ?MODULE, InitResult).
+
+%% Simulate different supervisors callback.
+init(fail) ->
+ erlang:error({badmatch,2});
+init(InitResult) ->
+ InitResult.
+
+%-------------------------------------------------------------------------
+%
+% Test cases starts here.
+%
+%-------------------------------------------------------------------------
+
+sup_start(doc) ->
+ ["Test start of a supervisor."];
+sup_start(suite) ->
+ [sup_start_normal, sup_start_ignore_init, sup_start_ignore_child,
+ sup_start_error_return, sup_start_fail].
+
+%-------------------------------------------------------------------------
+sup_start_normal(doc) ->
+ ["Tests that the supervisor process starts correctly and that it "
+ "can be terminated gracefully."];
+sup_start_normal(suite) -> [];
+sup_start_normal(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {ok, Pid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ ?line exit(Pid, shutdown),
+ receive
+ {'EXIT', Pid, shutdown} ->
+ ok;
+ {'EXIT', Pid, Else} ->
+ ?line test_server:fail({bad_exit_reason, Else})
+ after
+ 2000 ->
+ ?line test_server:fail(no_exit_reason)
+ end,
+ ok.
+%-------------------------------------------------------------------------
+sup_start_ignore_init(doc) ->
+ ["Tests what happens if init-callback returns ignore"];
+sup_start_ignore_init(suite) -> [];
+sup_start_ignore_init(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line ignore = start(ignore),
+
+ receive
+ {'EXIT', _Pid, normal} ->
+ ok;
+ {'EXIT', _Pid, Else} ->
+ ?line test_server:fail({bad_exit_reason, Else})
+ after
+ 2000 ->
+ ?line test_server:fail(no_exit_reason)
+ end,
+ ok.
+
+
+%-------------------------------------------------------------------------
+sup_start_ignore_child(doc) ->
+ ["Tests what happens if init-callback returns ignore"];
+sup_start_ignore_child(suite) -> [];
+sup_start_ignore_child(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {ok, _Pid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, [ignore]},
+ permanent, 1000, worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent,
+ 1000, worker, []},
+
+ ?line {ok, undefined} = supervisor:start_child(sup_test, Child1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+
+ ?line [{child2, CPid2, worker, []},{child1, undefined, worker, []}]
+ = supervisor:which_children(sup_test),
+ ok.
+
+%-------------------------------------------------------------------------
+sup_start_error_return(doc) ->
+ ["Tests what happens if init-callback returns a invalid value"];
+sup_start_error_return(suite) -> [];
+sup_start_error_return(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {error, Term} = start(invalid),
+
+ receive
+ {'EXIT', _Pid, Term} ->
+ ok;
+ {'EXIT', _Pid, Else} ->
+ ?line test_server:fail({bad_exit_reason, Else})
+ after
+ 2000 ->
+ ?line test_server:fail(no_exit_reason)
+ end,
+ ok.
+
+%-------------------------------------------------------------------------
+sup_start_fail(doc) ->
+ ["Tests what happens if init-callback fails"];
+sup_start_fail(suite) -> [];
+sup_start_fail(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {error, Term} = start(fail),
+
+ receive
+ {'EXIT', _Pid, Term} ->
+ ok;
+ {'EXIT', _Pid, Else} ->
+ ?line test_server:fail({bad_exit_reason, Else})
+ after
+ 2000 ->
+ ?line test_server:fail(no_exit_reason)
+ end,
+ ok.
+%-------------------------------------------------------------------------
+sup_stop(doc) ->
+ ["Tests that the supervisor shoutdowns its children if it is "
+ "shutdown itself."];
+sup_stop(suite) -> [sup_stop_infinity, sup_stop_timeout, sup_stop_brutal_kill].
+
+%-------------------------------------------------------------------------
+
+sup_stop_infinity(doc) ->
+ ["See sup_stop/1 when Shutdown = infinity, this walue is only allowed "
+ "for children of type supervisor"];
+sup_stop_infinity(suite) -> [];
+
+sup_stop_infinity(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {ok, Pid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []},
+ permanent, infinity, supervisor, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent,
+ infinity, worker, []},
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {error, {invalid_shutdown,infinity}} =
+ supervisor:start_child(sup_test, Child2),
+
+ ?line exit(Pid, shutdown),
+
+ receive
+ {'EXIT', Pid, shutdown} ->
+ ok;
+ {'EXIT', Pid, Else} ->
+ ?line test_server:fail({bad_exit_reason, Else})
+ after
+ 5000 ->
+ ?line test_server:fail(no_exit_reason)
+ end,
+ receive
+ {'EXIT', CPid1, shutdown} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ after
+ 2000 -> ?line test_server:fail(no_exit_reason)
+ end,
+ ok.
+
+%-------------------------------------------------------------------------
+
+sup_stop_timeout(doc) ->
+ ["See sup_stop/1 when Shutdown = 1000"];
+sup_stop_timeout(suite) -> [];
+
+sup_stop_timeout(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {ok, Pid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []},
+ permanent, 1000, worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent,
+ 1000, worker, []},
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+
+ CPid2 ! {sleep, 200000},
+
+ ?line exit(Pid, shutdown),
+
+ receive
+ {'EXIT', Pid, shutdown} ->
+ ok;
+ {'EXIT', Pid, Else} ->
+ ?line test_server:fail({bad_exit_reason, Else})
+ after
+ 5000 ->
+ ?line test_server:fail(no_exit_reason)
+ end,
+
+ receive
+ {'EXIT', CPid1, shutdown} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason,Reason})
+ after
+ 2000 -> ?line test_server:fail(no_exit_reason)
+ end,
+
+ receive
+ {'EXIT', CPid2, killed} -> ok;
+ {'EXIT', CPid2, Reason2} ->
+ ?line test_server:fail({bad_exit_reason, Reason2})
+ after
+ 2000 -> ?line test_server:fail(no_exit_reason)
+ end,
+ ok.
+
+%-------------------------------------------------------------------------
+sup_stop_brutal_kill(doc) ->
+ ["See sup_stop/1 when Shutdown = brutal_kill"];
+sup_stop_brutal_kill(suite) -> [];
+
+sup_stop_brutal_kill(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {ok, Pid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []},
+ permanent, 1000, worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent,
+ brutal_kill, worker, []},
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+
+ ?line exit(Pid, shutdown),
+
+ receive
+ {'EXIT', Pid, shutdown} ->
+ ok;
+ {'EXIT', Pid, Else} ->
+ ?line test_server:fail({bad_exit_reason, Else})
+ after
+ 5000 ->
+ ?line test_server:fail(no_exit_reason)
+ end,
+
+ receive
+ {'EXIT', CPid1, shutdown} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ after
+ 2000 -> ?line test_server:fail(no_exit_reason)
+ end,
+ receive
+ {'EXIT', CPid2, killed} -> ok;
+ {'EXIT', CPid2, Reason2} ->
+ ?line test_server:fail({bad_exit_reason, Reason2})
+ after
+ 2000 -> ?line test_server:fail(no_exit_reason)
+ end,
+ ok.
+
+%-------------------------------------------------------------------------
+extra_return(doc) ->
+ ["The start function provided to start a child may "
+ "return {ok, Pid} or {ok, Pid, Info}, if it returns "
+ "the later check that the supervisor ignores the Info, "
+ "and includes it unchanged in return from start_child/2 "
+ "and restart_child/2"];
+extra_return(suite) -> [];
+
+extra_return(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child = {child1, {supervisor_1, start_child, [extra_return]},
+ permanent, 1000,
+ worker, []},
+ ?line {ok, _Pid} = start({ok, {{one_for_one, 2, 3600}, [Child]}}),
+ ?line [{child1, CPid, worker, []}] = supervisor:which_children(sup_test),
+ link(CPid),
+ ?line {error, not_found} = supervisor:terminate_child(sup_test, hej),
+ ?line {error, not_found} = supervisor:delete_child(sup_test, hej),
+ ?line {error, not_found} = supervisor:restart_child(sup_test, hej),
+ ?line {error, running} = supervisor:delete_child(sup_test, child1),
+ ?line {error, running} = supervisor:restart_child(sup_test, child1),
+ ?line [{child1, CPid, worker, []}] = supervisor:which_children(sup_test),
+ ?line ok = supervisor:terminate_child(sup_test, child1),
+ receive
+ {'EXIT', CPid, shutdown} -> ok;
+ {'EXIT', CPid, Reason} ->
+ ?line test_server:fail({bad_reason, Reason})
+ after 1000 ->
+ ?line test_server:fail(no_child_termination)
+ end,
+ ?line [{child1,undefined,worker,[]}] = supervisor:which_children(sup_test),
+ ?line {ok, CPid2,extra_return} =
+ supervisor:restart_child(sup_test, child1),
+ ?line [{child1, CPid2, worker, []}] = supervisor:which_children(sup_test),
+ ?line ok = supervisor:terminate_child(sup_test, child1),
+ ?line ok = supervisor:terminate_child(sup_test, child1),
+ ?line ok = supervisor:delete_child(sup_test, child1),
+ ?line {error, not_found} = supervisor:restart_child(sup_test, child1),
+ ?line [] = supervisor:which_children(sup_test),
+ ?line {ok, CPid3, extra_return} = supervisor:start_child(sup_test, Child),
+ ?line [{child1, CPid3, worker, []}] = supervisor:which_children(sup_test),
+ ok.
+%-------------------------------------------------------------------------
+child_adm(doc)->
+ ["Test API functions start_child/2, terminate_child/2, delete_child/2 "
+ "restart_child/2, which_children/1. Only correct childspecs are used, "
+ "handling of incorrect childspecs is tested in child_specs/1"];
+child_adm(suite) -> [];
+child_adm(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, _Pid} = start({ok, {{one_for_one, 2, 3600}, [Child]}}),
+ ?line [{child1, CPid, worker, []}] = supervisor:which_children(sup_test),
+ link(CPid),
+
+ %% Start of an already runnig process
+ ?line {error,{already_started, CPid}} =
+ supervisor:start_child(sup_test, Child),
+
+ %% Termination
+ ?line {error, not_found} = supervisor:terminate_child(sup_test, hej),
+ ?line {'EXIT',{noproc,{gen_server,call, _}}} =
+ (catch supervisor:terminate_child(foo, child1)),
+ ?line ok = supervisor:terminate_child(sup_test, child1),
+ receive
+ {'EXIT', CPid, shutdown} -> ok;
+ {'EXIT', CPid, Reason} ->
+ ?line test_server:fail({bad_reason, Reason})
+ after 1000 ->
+ ?line test_server:fail(no_child_termination)
+ end,
+ ?line [{child1,undefined,worker,[]}] = supervisor:which_children(sup_test),
+ %% Like deleting something that does not exist, it will succeed!
+ ?line ok = supervisor:terminate_child(sup_test, child1),
+
+ %% Start of already existing but not running process
+ ?line {error,already_present} =
+ supervisor:start_child(sup_test, Child),
+
+ %% Restart
+ ?line {ok, CPid2} = supervisor:restart_child(sup_test, child1),
+ ?line [{child1, CPid2, worker, []}] = supervisor:which_children(sup_test),
+ ?line {error, running} = supervisor:restart_child(sup_test, child1),
+ ?line {error, not_found} = supervisor:restart_child(sup_test, child2),
+
+ %% Deletion
+ ?line {error, running} = supervisor:delete_child(sup_test, child1),
+ ?line {error, not_found} = supervisor:delete_child(sup_test, hej),
+ ?line {'EXIT',{noproc,{gen_server,call, _}}} =
+ (catch supervisor:delete_child(foo, child1)),
+ ?line ok = supervisor:terminate_child(sup_test, child1),
+ ?line ok = supervisor:delete_child(sup_test, child1),
+ ?line {error, not_found} = supervisor:restart_child(sup_test, child1),
+ ?line [] = supervisor:which_children(sup_test),
+
+ %% Start
+ ?line {'EXIT',{noproc,{gen_server,call, _}}} =
+ (catch supervisor:start_child(foo, Child)),
+ ?line {ok, CPid3} = supervisor:start_child(sup_test, Child),
+ ?line [{child1, CPid3, worker, []}] = supervisor:which_children(sup_test),
+
+ ?line {'EXIT',{noproc,{gen_server,call,[foo,which_children,infinity]}}}
+ = (catch supervisor:which_children(foo)),
+ ok.
+%-------------------------------------------------------------------------
+child_adm_simple(doc) ->
+ ["The API functions terminate_child/2, delete_child/2 "
+ "restart_child/2 are not valid for a simple_one_for_one supervisor "
+ "check that the correct error message is returned."];
+child_adm_simple(suite) -> [];
+child_adm_simple(Config) when list(Config) ->
+ Child = {child, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, _Pid} = start({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
+ %% In simple_one_for_one all children are added dynamically
+ ?line [] = supervisor:which_children(sup_test),
+
+ %% Start
+ ?line {'EXIT',{noproc,{gen_server,call, _}}} =
+ (catch supervisor:start_child(foo, [])),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, []),
+ ?line [{undefined, CPid1, worker, []}] =
+ supervisor:which_children(sup_test),
+
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, []),
+ ?line Children = supervisor:which_children(sup_test),
+ ?line 2 = length(Children),
+ ?line true = lists:member({undefined, CPid2, worker, []}, Children),
+ ?line true = lists:member({undefined, CPid1, worker, []}, Children),
+
+ %% Termination
+ ?line {error, simple_one_for_one} =
+ supervisor:terminate_child(sup_test, child1),
+
+ %% Restart
+ ?line {error, simple_one_for_one} =
+ supervisor:restart_child(sup_test, child1),
+
+ %% Deletion
+ ?line {error, simple_one_for_one} =
+ supervisor:delete_child(sup_test, child1),
+ ok.
+
+%-------------------------------------------------------------------------
+child_specs(doc) ->
+ ["Tests child specs, invalid formats should be rejected."];
+child_specs(suite) -> [];
+child_specs(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ ?line {ok, _Pid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ ?line {error, _} = supervisor:start_child(sup_test, hej),
+
+ %% Bad child specs
+ B1 = {child, mfa, permanent, 1000, worker, []},
+ B2 = {child, {m,f,[a]}, prmanent, 1000, worker, []},
+ B3 = {child, {m,f,[a]}, permanent, -10, worker, []},
+ B4 = {child, {m,f,[a]}, permanent, 10, wrker, []},
+ B5 = {child, {m,f,[a]}, permanent, infinity, worker, []},
+ B6 = {child, {m,f,[a]}, permanent, 1000, worker, dy},
+ B7 = {child, {m,f,[a]}, permanent, 1000, worker, [1,2,3]},
+
+ %% Correct child specs!
+ %% <Modules> (last parameter in a child spec) can be [] as we do
+ %% not test code upgrade here.
+ C1 = {child, {m,f,[a]}, permanent, infinity, supervisor, []},
+ C2 = {child, {m,f,[a]}, permanent, 1000, supervisor, []},
+ C3 = {child, {m,f,[a]}, temporary, 1000, worker, dynamic},
+ C4 = {child, {m,f,[a]}, transient, 1000, worker, [m]},
+
+ ?line {error, {invalid_mfa,mfa}} = supervisor:start_child(sup_test, B1),
+ ?line {error, {invalid_restart_type, prmanent}} =
+ supervisor:start_child(sup_test, B2),
+ ?line {error, {invalid_shutdown,-10}}
+ = supervisor:start_child(sup_test, B3),
+ ?line {error, {invalid_child_type,wrker}}
+ = supervisor:start_child(sup_test, B4),
+ ?line {error, _} = supervisor:start_child(sup_test, B5),
+ ?line {error, {invalid_modules,dy}}
+ = supervisor:start_child(sup_test, B6),
+
+ ?line {error, {invalid_mfa,mfa}} = supervisor:check_childspecs([B1]),
+ ?line {error, {invalid_restart_type,prmanent}} =
+ supervisor:check_childspecs([B2]),
+ ?line {error, {invalid_shutdown,-10}} = supervisor:check_childspecs([B3]),
+ ?line {error, {invalid_child_type,wrker}}
+ = supervisor:check_childspecs([B4]),
+ ?line {error, _} = supervisor:check_childspecs([B5]),
+ ?line {error, {invalid_modules,dy}} = supervisor:check_childspecs([B6]),
+ ?line {error, {invalid_module, 1}} =
+ supervisor:check_childspecs([B7]),
+
+ ?line ok = supervisor:check_childspecs([C1]),
+ ?line ok = supervisor:check_childspecs([C2]),
+ ?line ok = supervisor:check_childspecs([C3]),
+ ?line ok = supervisor:check_childspecs([C4]),
+ ok.
+%-------------------------------------------------------------------------
+normal_termination(doc) ->
+ ["Testes the supervisors behaviour if a child dies with reason normal"];
+normal_termination(suite) ->
+ [permanent_normal, transient_normal, temporary_normal].
+
+%-------------------------------------------------------------------------
+permanent_normal(doc) ->
+ ["A permanent child should always be restarted"];
+permanent_normal(suite) -> [];
+permanent_normal(Config) when list(Config) ->
+ ?line {ok, _SupPid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+
+ CPid1 ! stop,
+ test_server:sleep(100),
+ ?line [{child1, Pid ,worker,[]}] = supervisor:which_children(sup_test),
+ case is_pid(Pid) of
+ true ->
+ ok;
+ false ->
+ ?line test_server:fail({permanent_child_not_restarted, Child1})
+ end.
+
+%-------------------------------------------------------------------------
+transient_normal(doc) ->
+ ["A transient child should not be restarted if it exits with "
+ "reason normal"];
+transient_normal(suite) -> [];
+transient_normal(Config) when list(Config) ->
+ ?line {ok, _SupPid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []}, transient, 1000,
+ worker, []},
+
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+
+ CPid1 ! stop,
+ test_server:sleep(100),
+
+ ?line [{child1,undefined,worker,[]}] = supervisor:which_children(sup_test).
+
+%-------------------------------------------------------------------------
+temporary_normal(doc) ->
+ ["A temporary process should never be restarted"];
+temporary_normal(suite) -> [];
+temporary_normal(Config) when list(Config) ->
+ ?line {ok, _SupPid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []}, temporary, 1000,
+ worker, []},
+
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+
+ CPid1 ! stop,
+ test_server:sleep(100),
+
+ ?line [{child1,undefined,worker,[]}] = supervisor:which_children(sup_test).
+
+%-------------------------------------------------------------------------
+abnormal_termination(doc) ->
+ ["Testes the supervisors behaviour if a child dies with reason abnormal"];
+abnormal_termination(suite) ->
+ [permanent_abnormal, transient_abnormal, temporary_abnormal].
+
+%-------------------------------------------------------------------------
+permanent_abnormal(doc) ->
+ ["A permanent child should always be restarted"];
+permanent_abnormal(suite) -> [];
+permanent_abnormal(Config) when list(Config) ->
+ ?line {ok, _SupPid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+
+ CPid1 ! die,
+ test_server:sleep(100),
+ ?line [{child1, Pid ,worker,[]}] = supervisor:which_children(sup_test),
+ case is_pid(Pid) of
+ true ->
+ ok;
+ false ->
+ ?line test_server:fail({permanent_child_not_restarted, Child1})
+ end.
+
+%-------------------------------------------------------------------------
+transient_abnormal(doc) ->
+ ["A transient child should be restarted if it exits with "
+ "reason abnormal"];
+transient_abnormal(suite) -> [];
+transient_abnormal(Config) when list(Config) ->
+ ?line {ok, _SupPid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []}, transient, 1000,
+ worker, []},
+
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+
+ CPid1 ! die,
+ test_server:sleep(100),
+
+ ?line [{child1, Pid ,worker,[]}] = supervisor:which_children(sup_test),
+ case is_pid(Pid) of
+ true ->
+ ok;
+ false ->
+ ?line test_server:fail({transient_child_not_restarted, Child1})
+ end.
+
+
+%-------------------------------------------------------------------------
+temporary_abnormal(doc) ->
+ ["A temporary process should never be restarted"];
+temporary_abnormal(suite) -> [];
+temporary_abnormal(Config) when list(Config) ->
+ ?line {ok, _SupPid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ Child1 = {child1, {supervisor_1, start_child, []}, temporary, 1000,
+ worker, []},
+
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+
+ CPid1 ! die,
+ test_server:sleep(100),
+
+ ?line [{child1,undefined,worker,[]}] = supervisor:which_children(sup_test).
+
+%-------------------------------------------------------------------------
+restart_one_for_one(doc) ->
+ ["Test that the one_for_one strategy works."];
+
+restart_one_for_one(suite) -> [one_for_one, one_for_one_escalation].
+
+%-------------------------------------------------------------------------
+one_for_one(doc) ->
+ ["Test the one_for_one base case."];
+one_for_one(suite) -> [];
+one_for_one(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ test_server:sleep(100),
+ Children = supervisor:which_children(sup_test),
+ if length(Children) == 2 ->
+ case lists:keysearch(CPid2, 2, Children) of
+ {value, _} -> ok;
+ _ -> ?line test_server:fail(bad_child)
+ end;
+ true -> ?line test_server:fail({bad_child_list, Children})
+ end,
+
+ %% Test restart frequency property
+ CPid2 ! die,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ end,
+ test_server:sleep(100),
+ [{_, Pid4, _, _}|_] = supervisor:which_children(sup_test),
+ Pid4 ! die,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after 3000 -> ?line test_server:fail(restart_failed)
+ end,
+ ok.
+%-------------------------------------------------------------------------
+one_for_one_escalation(doc) ->
+ ["Test restart escalation on a one_for_one supervisor."];
+one_for_one_escalation(suite) -> [];
+one_for_one_escalation(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child1 = {child1, {supervisor_1, start_child, [error]},
+ permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{one_for_one, 4, 3600}, []}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after
+ 2000 -> ?line test_server:fail(supervisor_alive)
+ end,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ after
+ 4000 -> ?line test_server:fail(all_not_terminated)
+ end,
+ ok.
+%-------------------------------------------------------------------------
+restart_one_for_all(doc) ->
+ ["Test that the one_for_all strategy works."];
+
+restart_one_for_all(suite) ->
+ [one_for_all, one_for_all_escalation].
+
+%-------------------------------------------------------------------------
+one_for_all(doc) ->
+ ["Test the one_for_all base case."];
+one_for_all(suite) -> [];
+one_for_all(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{one_for_all, 2, 3600}, []}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ end,
+ test_server:sleep(100),
+ Children = supervisor:which_children(sup_test),
+ if length(Children) == 2 -> ok;
+ true -> ?line test_server:fail({bad_child_list, Children})
+ end,
+ %% Test that no old children is still alive
+ SCh = lists:map(fun({_,P,_,_}) -> P end, Children),
+ case lists:member(CPid1, SCh) of
+ true -> ?line test_server:fail(bad_child);
+ false -> ok
+ end,
+ case lists:member(CPid2, SCh) of
+ true -> ?line test_server:fail(bad_child);
+ false -> ok
+ end,
+ %%% Test restart frequency property
+ [{_, Pid3, _, _}|_] = supervisor:which_children(sup_test),
+ Pid3 ! die,
+ test_server:sleep(100),
+ [{_, Pid4, _, _}|_] = supervisor:which_children(sup_test),
+ Pid4 ! die,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after 3000 -> ?line test_server:fail(restart_failed)
+ end,
+ exit(Pid, shutdown).
+
+%-------------------------------------------------------------------------
+one_for_all_escalation(doc) ->
+ ["Test restart escalation on a one_for_all supervisor."];
+one_for_all_escalation(suite) -> [];
+one_for_all_escalation(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, [error]},
+ permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{one_for_all, 4, 3600}, []}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ after
+ 2000 -> ?line test_server:fail(all_not_terminated)
+ end,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after
+ 4000 -> ?line test_server:fail(supervisor_alive)
+ end,
+ ok.
+
+%-------------------------------------------------------------------------
+restart_simple_one_for_one(doc) ->
+ ["Test that the simple_one_for_one strategy works."];
+
+restart_simple_one_for_one(suite) ->
+ [simple_one_for_one, simple_one_for_one_extra,
+ simple_one_for_one_escalation].
+
+%-------------------------------------------------------------------------
+simple_one_for_one(doc) ->
+ ["Test the simple_one_for_one base case."];
+simple_one_for_one(suite) -> [];
+simple_one_for_one(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child = {child, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, []),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, []),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ test_server:sleep(100),
+ Children = supervisor:which_children(sup_test),
+ if length(Children) == 2 ->
+ case lists:keysearch(CPid2, 2, Children) of
+ {value, _} -> ok;
+ _ -> ?line test_server:fail(bad_child)
+ end;
+ true -> ?line test_server:fail({bad_child_list, Children})
+ end,
+ %% Test restart frequency property
+ CPid2 ! die,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ end,
+ test_server:sleep(100),
+ [{_, Pid4, _, _}|_] = supervisor:which_children(sup_test),
+ Pid4 ! die,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after 3000 -> ?line test_server:fail(restart_failed)
+ end,
+ ok.
+%-------------------------------------------------------------------------
+simple_one_for_one_extra(doc) ->
+ ["Tests automatic restart of children "
+ "who's start function return extra info."];
+simple_one_for_one_extra(suite) -> [];
+simple_one_for_one_extra(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child = {child, {supervisor_1, start_child, [extra_info]},
+ permanent, 1000, worker, []},
+ ?line {ok, Pid} = start({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
+ ?line {ok, CPid1, extra_info} = supervisor:start_child(sup_test, []),
+ link(CPid1),
+ ?line {ok, CPid2, extra_info} = supervisor:start_child(sup_test, []),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ test_server:sleep(100),
+ Children = supervisor:which_children(sup_test),
+ if length(Children) == 2 ->
+ case lists:keysearch(CPid2, 2, Children) of
+ {value, _} -> ok;
+ _ -> ?line test_server:fail(bad_child)
+ end;
+ true -> ?line test_server:fail({bad_child_list, Children})
+ end,
+ CPid2 ! die,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ end,
+ test_server:sleep(100),
+ [{_, Pid4, _, _}|_] = supervisor:which_children(sup_test),
+ Pid4 ! die,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after 3000 -> ?line test_server:fail(restart_failed)
+ end,
+ ok.
+%-------------------------------------------------------------------------
+simple_one_for_one_escalation(doc) ->
+ ["Test restart escalation on a simple_one_for_one supervisor."];
+simple_one_for_one_escalation(suite) -> [];
+simple_one_for_one_escalation(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child = {child, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{simple_one_for_one, 4, 3600}, [Child]}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, [error]),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, []),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after
+ 2000 -> ?line test_server:fail(supervisor_alive)
+ end,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ after
+ 2000 -> ?line test_server:fail(all_not_terminated)
+ end,
+ ok.
+%-------------------------------------------------------------------------
+restart_rest_for_one(doc) ->
+ ["Test that the rest_for_one strategy works."];
+restart_rest_for_one(suite) -> [rest_for_one, rest_for_one_escalation].
+
+%-------------------------------------------------------------------------
+rest_for_one(doc) ->
+ ["Test the rest_for_one base case."];
+rest_for_one(suite) -> [];
+rest_for_one(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ Child3 = {child3, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{rest_for_one, 2, 3600}, []}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+ ?line {ok, CPid3} = supervisor:start_child(sup_test, Child3),
+ link(CPid3),
+ CPid2 ! die,
+ receive
+ {'EXIT', CPid2, died} -> ok;
+ {'EXIT', CPid2, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ after 2000 ->
+ ?line test_server:fail(no_exit)
+ end,
+ %% Check that Cpid3 did die
+ receive
+ {'EXIT', CPid3, _} -> ok
+ after 2000 ->
+ ?line test_server:fail(no_exit)
+ end,
+ %% Check that Cpid1 didn't die
+ receive
+ {'EXIT', CPid1, _} ->
+ ?line test_server:fail(bad_exit)
+ after
+ 100 -> ok
+ end,
+ Children = supervisor:which_children(sup_test),
+ if length(Children) == 3 -> ok;
+ true -> ?line test_server:fail({bad_child_list, Children})
+ end,
+ %% Test that no old children is still alive
+ SCh = lists:map(fun({_,P,_,_}) -> P end, Children),
+ case lists:member(CPid1, SCh) of
+ true -> ok;
+ false -> ?line test_server:fail(bad_child)
+ end,
+ case lists:member(CPid2, SCh) of
+ true -> ?line test_server:fail(bad_child);
+ false -> ok
+ end,
+ case lists:member(CPid3, SCh) of
+ true -> ?line test_server:fail(bad_child);
+ false -> ok
+ end,
+
+ %% Test restart frequency property
+ [{child3, Pid3, _, _}|_] = supervisor:which_children(sup_test),
+ Pid3 ! die,
+ test_server:sleep(100),
+ [_,{child2, Pid4, _, _}|_] = supervisor:which_children(sup_test),
+ Pid4 ! die,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after 3000 -> ?line test_server:fail(restart_failed)
+ end,
+ exit(Pid, shutdown).
+
+%-------------------------------------------------------------------------
+rest_for_one_escalation(doc) ->
+ ["Test restart escalation on a rest_for_one supervisor."];
+rest_for_one_escalation(suite) -> [];
+rest_for_one_escalation(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, [error]},
+ permanent, 1000,
+ worker, []},
+ ?line {ok, Pid} = start({ok, {{rest_for_one, 4, 3600}, []}}),
+ ?line {ok, CPid1} = supervisor:start_child(sup_test, Child1),
+ link(CPid1),
+ ?line {ok, CPid2} = supervisor:start_child(sup_test, Child2),
+ link(CPid2),
+ CPid1 ! die,
+ receive
+ {'EXIT', CPid1, died} -> ok;
+ {'EXIT', CPid1, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ end,
+ receive
+ {'EXIT', CPid2, _} -> ok
+ after
+ 2000 -> ?line test_server:fail(not_terminated)
+ end,
+ receive
+ {'EXIT', Pid, _} -> ok
+ after
+ 4000 -> ?line test_server:fail(supervisor_alive)
+ end,
+ ok.
+
+%-------------------------------------------------------------------------
+child_unlink(doc)-> ["Test that the supervisor does not hang forever if "
+ "the child unliks and then is terminated by the supervisor."];
+child_unlink(suite) -> [];
+child_unlink(Config) when list(Config) ->
+
+ ?line {ok, SupPid} = start({ok, {{one_for_one, 2, 3600}, []}}),
+
+ Child = {naughty_child, {naughty_child,
+ start_link, [SupPid]}, permanent,
+ 1000, worker, [supervisor_SUITE]},
+
+ ?line {ok, _ChildPid} = supervisor:start_child(sup_test, Child),
+
+ Pid = spawn(supervisor, terminate_child, [SupPid, naughty_child]),
+
+ SupPid ! foo,
+ timer:sleep(5000),
+ %% If the supervisor did not hang it will have got rid of the
+ %% foo message that we sent.
+ case erlang:process_info(SupPid, message_queue_len) of
+ {message_queue_len, 0}->
+ ok;
+ _ ->
+ exit(Pid, kill),
+ ?line test_server:fail(supervisor_hangs)
+ end.
+%-------------------------------------------------------------------------
+
+tree(doc) ->
+ ["Test a basic supervison tree."];
+tree(suite) ->
+ [];
+tree(Config) when list(Config) ->
+ process_flag(trap_exit, true),
+
+ Child1 = {child1, {supervisor_1, start_child, []},
+ permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []},
+ permanent, 1000,
+ worker, []},
+ Child3 = {child3, {supervisor_1, start_child, [error]},
+ permanent, 1000,
+ worker, []},
+ Child4 = {child4, {supervisor_1, start_child, []},
+ permanent, 1000,
+ worker, []},
+
+ ChildSup1 = {supchild1,
+ {supervisor, start_link,
+ [?MODULE, {ok, {{one_for_one, 4, 3600}, [Child1, Child2]}}]},
+ permanent, infinity,
+ supervisor, []},
+ ChildSup2 = {supchild2,
+ {supervisor, start_link,
+ [?MODULE, {ok, {{one_for_one, 4, 3600}, []}}]},
+ permanent, infinity,
+ supervisor, []},
+
+ %% Top supervisor
+ ?line {ok, Pid} = start({ok, {{one_for_all, 4, 3600}, []}}),
+
+ %% Child supervisors
+ ?line {ok, Sup1} = supervisor:start_child(Pid, ChildSup1),
+ ?line {ok, Sup2} = supervisor:start_child(Pid, ChildSup2),
+
+ %% Workers
+
+ ?line [{_, CPid2, _, _},{_, CPid1, _, _}] =
+ supervisor:which_children(Sup1),
+
+ %% Dynamic children
+ ?line {ok, CPid3} = supervisor:start_child(Sup2, Child3),
+ ?line {ok, CPid4} = supervisor:start_child(Sup2, Child4),
+
+ link(Sup1),
+ link(Sup2),
+ link(CPid1),
+ link(CPid2),
+ link(CPid3),
+ link(CPid4),
+
+ %% Test that the only the process that dies is restarted
+ CPid4 ! die,
+
+ receive
+ {'EXIT', CPid4, _} -> ?line ok
+ after 10000 ->
+ ?line test_server:fail(child_was_not_killed)
+ end,
+
+ test_server:sleep(100),
+
+ ?line [{_, CPid2, _, _},{_, CPid1, _, _}] =
+ supervisor:which_children(Sup1),
+
+ ?line [{_, NewCPid4, _, _},{_, CPid3, _, _}] =
+ supervisor:which_children(Sup2),
+
+ link(NewCPid4),
+
+ %% Test that supervisor tree is restarted, but not dynamic children.
+ CPid3 ! die,
+
+ receive
+ {'EXIT', CPid3, died} -> ?line ok;
+ {'EXIT', CPid3, Reason} ->
+ ?line test_server:fail({bad_exit_reason, Reason})
+ after 1000 ->
+ ?line test_server:fail(child_was_not_killed)
+ end,
+
+ test_server:sleep(1000),
+
+ receive
+ {'EXIT', NewCPid4, _} -> ?line ok
+ after 1000 ->
+ ?line test_server:fail(child_was_not_killed)
+ end,
+
+ receive
+ {'EXIT', Sup2, _} -> ?line ok
+ after 1000 ->
+ ?line test_server:fail(child_was_not_killed)
+ end,
+
+ receive
+ {'EXIT', CPid1, _} -> ?line ok
+ after 1000 ->
+ ?line test_server:fail(child_was_not_killed)
+ end,
+
+ receive
+ {'EXIT', CPid2, _} -> ?line ok
+ after 1000 ->
+ ?line test_server:fail(child_was_not_killed)
+ end,
+
+ receive
+ {'EXIT', Sup1, _} -> ?line ok
+ after 1000 ->
+ ?line test_server:fail(child_was_not_killed)
+ end,
+
+ ?line [{supchild2, NewSup2, _, _},{supchild1, NewSup1, _, _}] =
+ supervisor:which_children(Pid),
+
+ ?line [{child2, _, _, _},{child1, _, _, _}] =
+ supervisor:which_children(NewSup1),
+ ?line [] = supervisor:which_children(NewSup2),
+
+ ok.
diff --git a/lib/stdlib/test/supervisor_bridge_SUITE.erl b/lib/stdlib/test/supervisor_bridge_SUITE.erl
new file mode 100644
index 0000000000..b23bac2d44
--- /dev/null
+++ b/lib/stdlib/test/supervisor_bridge_SUITE.erl
@@ -0,0 +1,178 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(supervisor_bridge_SUITE).
+-export([all/1,starting/1,mini_terminate/1,mini_die/1,badstart/1]).
+-export([client/1,init/1,internal_loop_init/1,terminate/2]).
+
+-include("test_server.hrl").
+-define(bridge_name,supervisor_bridge_SUITE_server).
+-define(work_bridge_name,work_supervisor_bridge_SUITE_server).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(suite) -> [starting,mini_terminate,mini_die,badstart].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+starting(suite) -> [];
+starting(Config) when is_list(Config) ->
+ process_flag(trap_exit,true),
+
+ ?line ignore = start(1),
+ ?line {error,testing} = start(2),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+mini_terminate(suite) -> [];
+mini_terminate(Config) when is_list(Config) ->
+ miniappl(1),
+ ok.
+
+mini_die(suite) -> [];
+mini_die(Config) when is_list(Config) ->
+ miniappl(2),
+ ok.
+
+miniappl(N) ->
+ process_flag(trap_exit,true),
+ ?line {ok,Server} = start(3),
+ ?line Client = spawn_link(?MODULE,client,[N]),
+ ?line Handle = test_server:timetrap(2000),
+ ?line miniappl_loop(Client,Server),
+ ?line test_server:timetrap_cancel(Handle).
+
+miniappl_loop([],[]) ->
+ ok;
+miniappl_loop(Client,Server) ->
+ io:format("Client ~p, Server ~p\n",[Client,Server]),
+ receive
+ {'EXIT',Client,_} ->
+ ?line miniappl_loop([],Server);
+ {'EXIT',Server,killed} -> %% terminate
+ ?line miniappl_loop(Client,[]);
+ {'EXIT',Server,died} -> %% die
+ ?line miniappl_loop(Client,[]);
+ {dying,_Reason} ->
+ ?line miniappl_loop(Client, Server);
+ Other ->
+ ?line exit({failed,Other})
+ end.
+
+%%%%%%%%%%%%%%%%%%%%
+% Client
+
+client(N) ->
+ io:format("Client starting...\n"),
+ ok = public_request(),
+ case N of
+ 1 -> public_kill();
+ 2 -> ?work_bridge_name ! die
+ end,
+ io:format("Killed server, terminating client...\n"),
+ exit(fine).
+
+%%%%%%%%%%%%%%%%%%%%
+% Non compliant server
+
+start(N) ->
+ supervisor_bridge:start_link({local,?bridge_name},?MODULE,N).
+
+public_request() ->
+ ?work_bridge_name ! {non_compliant_message,self()},
+ io:format("Client waiting for answer...\n"),
+ receive
+ non_compliant_answer ->
+ ok
+ end,
+ io:format("Client got answer...\n").
+
+public_kill() ->
+ %% This func knows about supervisor_bridge
+ exit(whereis(?work_bridge_name),kill).
+
+init(1) ->
+ ignore;
+init(2) ->
+ {error,testing};
+init(3) ->
+ %% This func knows about supervisor_bridge
+ InternalPid = spawn_link(?MODULE,internal_loop_init,[self()]),
+ receive
+ {InternalPid,init_done} ->
+ {ok,InternalPid,self()}
+ end.
+
+internal_loop_init(Parent) ->
+ register(?work_bridge_name, self()),
+ Parent ! {self(),init_done},
+ internal_loop({Parent,self()}).
+
+internal_loop(State) ->
+ receive
+ {non_compliant_message,From} ->
+ io:format("Got request from ~p\n",[From]),
+ From ! non_compliant_answer,
+ internal_loop(State);
+ die ->
+ exit(died)
+ end.
+
+terminate(Reason,{Parent,Worker}) ->
+ %% This func knows about supervisor_bridge
+ io:format("Terminating bridge...\n"),
+ exit(kill,Worker),
+ Parent ! {dying,Reason},
+ anything.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+badstart(suite) -> [];
+badstart(doc) -> "Test various bad ways of starting a supervisor bridge.";
+badstart(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:minutes(1)),
+
+ %% Various bad arguments.
+
+ ?line {'EXIT',_} =
+ (catch supervisor_bridge:start_link({xxx,?bridge_name},?MODULE,1)),
+ ?line {'EXIT',_} =
+ (catch supervisor_bridge:start_link({local,"foo"},?MODULE,1)),
+ ?line {'EXIT',_} =
+ (catch supervisor_bridge:start_link(?bridge_name,?MODULE,1)),
+ ?line [] = test_server:messages_get(), % No messages waiting
+
+ %% Already started.
+
+ ?line process_flag(trap_exit, true),
+ ?line {ok,Pid} =
+ supervisor_bridge:start_link({local,?bridge_name},?MODULE,3),
+ ?line {error,{already_started,Pid}} =
+ supervisor_bridge:start_link({local,?bridge_name},?MODULE,3),
+ ?line public_kill(),
+
+ %% We used to wait 1 ms before retrieving the message queue,
+ %% but that might not always be enough if the machine is overloaded.
+ ?line receive
+ {'EXIT', Pid, killed} -> ok
+ end,
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/stdlib/test/sys_SUITE.erl b/lib/stdlib/test/sys_SUITE.erl
new file mode 100644
index 0000000000..e44fd56403
--- /dev/null
+++ b/lib/stdlib/test/sys_SUITE.erl
@@ -0,0 +1,173 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(sys_SUITE).
+-export([all/1,log/1,log_to_file/1,stats/1,trace/1,suspend/1,install/1]).
+-export([handle_call/3,terminate/2,init/1]).
+-include("test_server.hrl").
+
+-define(server,sys_SUITE_server).
+
+
+%% Doesn't look into change_code at all
+%% Doesn't address writing your own process that understands
+%% system messages at all.
+
+
+all(suite) -> [log,log_to_file,stats,trace,suspend,install].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+log(suite) -> [];
+log(Config) when is_list(Config) ->
+ ?line {ok,_Server} = start(),
+ ?line ok = sys:log(?server,true),
+ ?line {ok,-44} = public_call(44),
+ ?line ok = sys:log(?server,false),
+ ?line ok = sys:log(?server,print),
+ ?line stop(),
+ ok.
+
+log_to_file(suite) -> [];
+log_to_file(Config) when is_list(Config) ->
+ TempName = test_server:temp_name(?config(priv_dir,Config) ++ "sys."),
+ ?line {ok,_Server} = start(),
+ ?line ok = sys:log_to_file(?server,TempName),
+ ?line {ok,-44} = public_call(44),
+ ?line ok = sys:log_to_file(?server,false),
+ ?line {ok,Fd} = file:open(TempName,read),
+ ?line Msg1 = io:get_line(Fd,''),
+ ?line Msg2 = io:get_line(Fd,''),
+ ?line file:close(Fd),
+ ?line lists:prefix("*DBG* sys_SUITE_server got call {req,44} from ",Msg1),
+ ?line lists:prefix("*DBG* sys_SUITE_server sent {ok,-44} to ",Msg2),
+ ?line stop(),
+ ok.
+
+stats(suite) -> [];
+stats(Config) when is_list(Config) ->
+ ?line Self = self(),
+ ?line {ok,_Server} = start(),
+ ?line ok = sys:statistics(?server,true),
+ ?line {ok,-44} = public_call(44),
+ ?line {ok,Stats} = sys:statistics(?server,get),
+ ?line lists:member({messages_in,1},Stats),
+ ?line lists:member({messages_out,1},Stats),
+ ?line ok = sys:statistics(?server,false),
+ ?line {status,_Pid,{module,_Mod},[_PDict,running,Self,_,_]} =
+ sys:get_status(?server),
+ ?line {ok,no_statistics} = sys:statistics(?server,get),
+ ?line stop(),
+ ok.
+
+trace(suite) -> [];
+trace(Config) when is_list(Config) ->
+ ?line {ok,_Server} = start(),
+ case os:type() of
+ vxworks ->
+ ?line test_server:sleep(20000);
+ _ ->
+ ?line test_server:sleep(2000)
+ end,
+ ?line test_server:capture_start(),
+ ?line sys:trace(?server,true),
+ ?line {ok,-44} = public_call(44),
+ %% ho, hum, allow for the io to reach us..
+ case os:type() of
+ vxworks ->
+ ?line test_server:sleep(10000);
+ _ ->
+ ?line test_server:sleep(1000)
+ end,
+ ?line test_server:capture_stop(),
+ ?line [Msg1,Msg2] = test_server:capture_get(),
+ ?line lists:prefix("*DBG* sys_SUITE_server got call {req,44} from ",Msg1),
+ ?line lists:prefix("*DBG* sys_SUITE_server sent {ok,-44} to ",Msg2),
+ ?line stop(),
+ ok.
+
+suspend(suite) -> [];
+suspend(Config) when is_list(Config) ->
+ ?line {ok,_Server} = start(),
+ ?line sys:suspend(?server,1000),
+ ?line {'EXIT',_} = (catch public_call(48)),
+ ?line {status,_,_,[_,suspended,_,_,_]} = sys:get_status(?server),
+ ?line sys:suspend(?server,1000), %% doing it twice is no error
+ ?line {'EXIT',_} = (catch public_call(48)),
+ ?line sys:resume(?server),
+ ?line {status,_,_,[_,running,_,_,_]} = sys:get_status(?server),
+ ?line {ok,-48} = (catch public_call(48)),
+ ?line sys:resume(?server), %% doing it twice is no error
+ ?line {ok,-48} = (catch public_call(48)),
+ ?line stop(),
+ ok.
+
+install(suite) -> [];
+install(Config) when is_list(Config) ->
+ ?line {ok,_Server} = start(),
+ ?line Master = self(),
+ ?line SpyFun =
+ fun(func_state,Event,ProcState) ->
+ case Event of
+ {in,{'$gen_call',_From,{req,Arg}}} ->
+ io:format("Trigged\n"),
+ Master ! {spy_got,{request,Arg},ProcState};
+ Other ->
+ io:format("Trigged other=~p\n",[Other])
+ end
+ end,
+ ?line sys:install(?server,{SpyFun,func_state}),
+ ?line {ok,-1} = (catch public_call(1)),
+ ?line sys:no_debug(?server),
+ ?line {ok,-2} = (catch public_call(2)),
+ ?line sys:install(?server,{SpyFun,func_state}),
+ ?line sys:install(?server,{SpyFun,func_state}),
+ ?line {ok,-3} = (catch public_call(3)),
+ ?line sys:remove(?server,SpyFun),
+ ?line {ok,-4} = (catch public_call(4)),
+ ?line Msgs = test_server:messages_get(),
+ ?line [{spy_got,{request,1},sys_SUITE_server},
+ {spy_got,{request,3},sys_SUITE_server}] = Msgs,
+ ?line stop(),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%
+%% Dummy server
+
+public_call(Arg) ->
+ gen_server:call(?server,{req,Arg},1000).
+
+start() ->
+ gen_server:start_link({local,?server},?MODULE,[],[]).
+
+stop() ->
+ gen_server:call(?server,stop,1000).
+
+init([]) ->
+ {ok,0}.
+
+handle_call({req,Arg},_From,State) ->
+ NewState = State+1,
+ {reply,{ok,-Arg},NewState};
+handle_call(stop,_From,State) ->
+ {stop,normal,ok,State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/stdlib/test/tar_SUITE.erl b/lib/stdlib/test/tar_SUITE.erl
new file mode 100644
index 0000000000..af687ed2e1
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE.erl
@@ -0,0 +1,718 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(tar_SUITE).
+
+-export([all/1, borderline/1, atomic/1, long_names/1,
+ create_long_names/1, bad_tar/1, errors/1, extract_from_binary/1,
+ extract_from_binary_compressed/1,
+ extract_from_open_file/1, symlinks/1, open_add_close/1, cooked_compressed/1,
+ memory/1]).
+
+-include("test_server.hrl").
+-include_lib("kernel/include/file.hrl").
+
+all(suite) -> [borderline, atomic, long_names, create_long_names,
+ bad_tar, errors,
+ extract_from_binary, extract_from_binary_compressed,
+ extract_from_open_file,
+ symlinks, open_add_close, cooked_compressed,
+ memory].
+
+borderline(doc) ->
+ ["Test creating, listing and extracting one file from an archive",
+ "multiple times with different file sizes. ",
+ "Also check that the file attributes of the extracted file has survived."];
+borderline(Config) when is_list(Config) ->
+
+ %% Note: We cannot use absolute paths, because the pathnames will be
+ %% too long for the limit allowed in tar files (100 characters).
+ %% Therefore, strip off the current working directory from the front
+ %% of the private directory path.
+
+ ?line {ok, Cwd} = file:get_cwd(),
+ ?line RootDir = ?config(priv_dir, Config),
+ ?line TempDir = remove_prefix(Cwd++"/", filename:join(RootDir, borderline)),
+ ?line ok = file:make_dir(TempDir),
+
+ ?line Record = 512,
+ ?line Block = 20 * Record,
+
+ ?line lists:foreach(fun(Size) -> borderline_test(Size, TempDir) end,
+ [0, 1, 10, 13, 127, 333, Record-1, Record, Record+1,
+ Block-Record-1, Block-Record, Block-Record+1,
+ Block-1, Block, Block+1,
+ Block+Record-1, Block+Record, Block+Record+1]),
+
+ %% Clean up.
+ ?line delete_files([TempDir]),
+
+ ok.
+
+borderline_test(Size, TempDir) ->
+ ?line Archive = filename:join(TempDir, "ar_"++integer_to_list(Size)++".tar"),
+ ?line Name = filename:join(TempDir, "file_"++integer_to_list(Size)),
+ ?line io:format("Testing size ~p", [Size]),
+
+ %% Create a file and archive it.
+ ?line {_, _, X0} = erlang:now(),
+ ?line file:write_file(Name, random_byte_list(X0, Size)),
+ ?line ok = erl_tar:create(Archive, [Name]),
+ ?line ok = file:delete(Name),
+
+ %% Verify listing and extracting.
+ ?line {ok, [Name]} = erl_tar:table(Archive),
+ ?line ok = erl_tar:extract(Archive, [verbose]),
+
+ %% Verify contents of extracted file.
+ ?line {ok, Bin} = file:read_file(Name),
+ ?line true = match_byte_list(X0, binary_to_list(Bin)),
+
+ %% Verify that Unix tar can read it.
+ ?line tar_tf(Archive, Name),
+
+ ok.
+
+tar_tf(Archive, Name) ->
+ case os:type() of
+ {unix, _} ->
+ tar_tf1(Archive, Name);
+ _ ->
+ ok
+ end.
+
+tar_tf1(Archive, Name) ->
+ ?line Expect = Name ++ "\n",
+ ?line cmd_expect("tar tf " ++ Archive, Expect).
+
+%% We can't use os:cmd/1, because Unix 'tar tf Name' on Solaris never
+%% terminates when given an archive of a size it doesn't like.
+
+cmd_expect(Cmd, Expect) ->
+ ?line Port = open_port({spawn, make_cmd(Cmd)}, [stream, in, eof]),
+ ?line get_data(Port, Expect).
+
+get_data(Port, Expect) ->
+ receive
+ {Port, {data, Bytes}} ->
+ ?line get_data(Port, match_output(Bytes, Expect, Port));
+ {Port, eof} ->
+ Port ! {self(), close},
+ receive
+ {Port, closed} ->
+ true
+ end,
+ receive
+ {'EXIT', Port, _} ->
+ ok
+ after 1 -> % force context switch
+ ok
+ end,
+ ?line match_output(eof, Expect, Port)
+ end.
+
+match_output([C|Output], [C|Expect], Port) ->
+ ?line match_output(Output, Expect, Port);
+match_output([_|_], [_|_], Port) ->
+ ?line kill_port_and_fail(Port, badmatch);
+match_output([X|Output], [], Port) ->
+ ?line kill_port_and_fail(Port, {too_much_data, [X|Output]});
+match_output([], Expect, _Port) ->
+ Expect;
+match_output(eof, [], _Port) ->
+ [];
+match_output(eof, _Expect, Port) ->
+ ?line kill_port_and_fail(Port, unexpected_end_of_input).
+
+kill_port_and_fail(Port, Reason) ->
+ unlink(Port),
+ exit(Port, die),
+ test_server:fail(Reason).
+
+make_cmd(Cmd) ->
+ case os:type() of
+ {win32, _} -> lists:concat(["cmd /c", Cmd]);
+ {unix, _} -> lists:concat(["sh -c '", Cmd, "'"])
+ end.
+
+%% Verifies a random byte list.
+
+match_byte_list(X0, [Byte|Rest]) ->
+ X = next_random(X0),
+ case (X bsr 26) band 16#ff of
+ Byte -> match_byte_list(X, Rest);
+ _ -> false
+ end;
+match_byte_list(_, []) ->
+ true.
+
+%% Generates a random byte list.
+
+random_byte_list(X0, Count) ->
+ random_byte_list(X0, Count, []).
+
+random_byte_list(X0, Count, Result) when Count > 0->
+ X = next_random(X0),
+ random_byte_list(X, Count-1, [(X bsr 26) band 16#ff|Result]);
+random_byte_list(_X, 0, Result) ->
+ lists:reverse(Result).
+
+%% This RNG is from line 21 on page 102 in Knuth: The Art of Computer Programming,
+%% Volume II, Seminumerical Algorithms.
+
+next_random(X) ->
+ (X*17059465+1) band 16#fffffffff.
+
+atomic(doc) ->
+ ["Test the 'atomic' operations: create/extract/table, on compressed "
+ "and uncompressed archives."
+ "Also test the 'cooked' option."];
+atomic(suite) -> [];
+atomic(Config) when list(Config) ->
+ ?line ok = file:set_cwd(?config(priv_dir, Config)),
+ ?line DataFiles = data_files(),
+ ?line Names = [Name || {Name,_,_} <- DataFiles],
+ io:format("Names: ~p", [Names]),
+
+ %% Create an uncompressed archive. The compressed flag should still be
+ %% allowed when listing contents or extracting.
+
+ ?line Tar1 = "uncompressed.tar",
+ ?line erl_tar:create(Tar1, Names, []),
+ ?line {ok, Names} = erl_tar:table(Tar1, []),
+ ?line {ok, Names} = erl_tar:table(Tar1, [compressed]),
+ ?line {ok, Names} = erl_tar:table(Tar1, [cooked]),
+ ?line {ok, Names} = erl_tar:table(Tar1, [compressed,cooked]),
+
+ %% Create a compressed archive.
+
+ ?line Tar2 = "compressed.tar",
+ ?line erl_tar:create(Tar2, Names, [compressed]),
+ ?line {ok, Names} = erl_tar:table(Tar2, [compressed]),
+ ?line {error, Reason} = erl_tar:table(Tar2, []),
+ ?line {ok, Names} = erl_tar:table(Tar2, [compressed,cooked]),
+ ?line {error, Reason} = erl_tar:table(Tar2, [cooked]),
+ ?line ok = io:format("No compressed option: ~p, ~s",
+ [Reason, erl_tar:format_error(Reason)]),
+
+ %% Same test again, but this time created with 'cooked'
+
+ ?line Tar3 = "uncompressed_cooked.tar",
+ ?line erl_tar:create(Tar3, Names, [cooked]),
+ ?line {ok, Names} = erl_tar:table(Tar3, []),
+ ?line {ok, Names} = erl_tar:table(Tar3, [compressed]),
+ ?line {ok, Names} = erl_tar:table(Tar3, [cooked]),
+ ?line {ok, Names} = erl_tar:table(Tar3, [compressed,cooked]),
+
+ ?line Tar4 = "compressed_cooked.tar",
+ ?line erl_tar:create(Tar4, Names, [compressed,cooked]),
+ ?line {ok, Names} = erl_tar:table(Tar4, [compressed]),
+ ?line {error, Reason} = erl_tar:table(Tar4, []),
+ ?line {ok, Names} = erl_tar:table(Tar4, [compressed,cooked]),
+ ?line {error, Reason} = erl_tar:table(Tar4, [cooked]),
+ ?line ok = io:format("No compressed option: ~p, ~s",
+ [Reason, erl_tar:format_error(Reason)]),
+
+ %% Clean up.
+ ?line delete_files([Tar1,Tar2,Tar3,Tar4|Names]),
+
+ ok.
+
+%% Returns a sequence of characters.
+
+char_seq(N, First) ->
+ char_seq(N, First, []).
+
+char_seq(0, _, Result) ->
+ Result;
+char_seq(N, C, Result) when C < 127 ->
+ char_seq(N-1, C+1, [C|Result]);
+char_seq(N, _, Result) ->
+ char_seq(N, $!, Result).
+
+data_files() ->
+ Files = [{"first_file", 1555, $a},
+ {"small_file", 7, $d},
+ {"big_file", 23875, $e},
+ {"last_file", 7500, $g}],
+ create_files(Files),
+ Files.
+
+create_files([{Name, Size, First}|Rest]) ->
+ ok = file:write_file(Name, char_seq(Size, First)),
+ create_files(Rest);
+create_files([]) ->
+ ok.
+
+long_names(doc) ->
+ ["Test to extract an Unix tar file containing filenames longer than 100 ",
+ "characters and empty directories."];
+long_names(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line Long = filename:join(DataDir, "long_names.tar"),
+
+ %% Try table/2 and extract/2.
+ ?line case erl_tar:table(Long, [verbose]) of
+ {ok,List} when is_list(List) ->
+ ?line io:format("~p\n", [List])
+ end,
+
+
+ %% To avoid getting too long paths for Windows to handle, extract into
+ %% the current directory (which is the test_server directory). Its path
+ %% is quite a bit shorter than the path to priv_dir.
+ ?line {ok,Cwd} = file:get_cwd(),
+ ?line ok = erl_tar:extract(Long),
+ ?line Base = filename:join([Cwd, "original_software", "written_by",
+ "a_bunch_of_hackers",
+ "spending_all_their_nights",
+ "still", "not_long_enough",
+ "but_soon_it_will_be"]),
+
+ %% Verify that the empty directory was created.
+ ?line EmptyDir = filename:join(Base, "empty_directory"),
+ ?line {ok, #file_info{type=directory}} = file:read_file_info(EmptyDir),
+
+ %% Verify that the files were created.
+ ?line {ok,First} = file:read_file(filename:join(Base, "first_file")),
+ ?line {ok,Second} = file:read_file(filename:join(Base, "second_file")),
+ ?line "Here"++_ = binary_to_list(First),
+ ?line "And"++_ = binary_to_list(Second),
+
+ %% Clean up.
+ ?line delete_files([filename:join(Cwd, "original_software"),EmptyDir]),
+
+ ok.
+
+create_long_names(doc) ->
+ ["Creates a tar file from a deep directory structure (filenames are ",
+ "longer than 100 characters)."];
+create_long_names(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line ok = file:set_cwd(PrivDir),
+ Dirs = [aslfjkshjkhliuf,
+ asdhjfehnbfsky,
+ sahajfskdfhsz,
+ asldfkdlfy4y8rchg,
+ f7nafhjgffagkhsfkhsjk,
+ dfjasldkfjsdkfjashbv],
+
+ ?line DeepDir = make_dirs(Dirs, []),
+ ?line AFile = filename:join(DeepDir, "a_file"),
+ ?line Hello = "hello, world\n",
+ ?line ok = file:write_file(AFile, Hello),
+ ?line TarName = filename:join(PrivDir, "my_tar_with_long_names.tar"),
+ ?line ok = erl_tar:create(TarName, [AFile]),
+
+ %% Print contents.
+ ?line ok = erl_tar:tt(TarName),
+
+ %% Extract and verify.
+ ?line ExtractDir = "extract_dir",
+ ?line ok = file:make_dir(ExtractDir),
+ ?line ok = erl_tar:extract(TarName, [{cwd,ExtractDir}]),
+ ?line {ok, Bin} = file:read_file(filename:join(ExtractDir, AFile)),
+ ?line Hello = binary_to_list(Bin),
+
+ %% Clean up.
+ ?line delete_files([ExtractDir,TarName,hd(Dirs)]),
+
+ ok.
+
+make_dirs([Dir|Rest], []) ->
+ ?line ok = file:make_dir(Dir),
+ ?line make_dirs(Rest, Dir);
+make_dirs([Dir|Rest], Parent) ->
+ ?line Name = filename:join(Parent, Dir),
+ ?line ok = file:make_dir(Name),
+ ?line make_dirs(Rest, Name);
+make_dirs([], Dir) ->
+ Dir.
+
+bad_tar(doc) ->
+ ["Try erl_tar:table/2 and erl_tar:extract/2 on some corrupted tar files."];
+bad_tar(Config) when is_list(Config) ->
+ ?line try_bad("bad_checksum", bad_header, Config),
+ ?line try_bad("bad_octal", bad_header, Config),
+ ?line try_bad("bad_too_short", eof, Config),
+ ?line try_bad("bad_even_shorter", eof, Config),
+ ok.
+
+try_bad(Name0, Reason, Config) ->
+ %% Intentionally no ?line macros here.
+
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Name = Name0 ++ ".tar",
+ io:format("~nTrying ~s", [Name]),
+ Full = filename:join(DataDir, Name),
+ Opts = [verbose, {cwd, PrivDir}],
+ Expected = {error, Reason},
+ case {erl_tar:table(Full, Opts), erl_tar:extract(Full, Opts)} of
+ {Expected, Expected} ->
+ io:format("Result: ~p", [Expected]),
+ case catch erl_tar:format_error(Reason) of
+ {'EXIT', CrashReason} ->
+ test_server:fail({format_error, crashed, CrashReason});
+ String when list(String) ->
+ io:format("format_error(~p) -> ~s", [Reason, String]);
+ Other ->
+ test_server:fail({format_error, returned, Other})
+ end;
+ {Other1, Other2} ->
+ io:format("table/2 returned ~p", [Other1]),
+ io:format("extract/2 returned ~p", [Other2]),
+ test_server:fail({bad_return_value, Other1, Other2})
+ end.
+
+errors(doc) ->
+ ["Tests that some common errors return correct error codes ",
+ "and that format_error/1 handles them correctly."];
+errors(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+
+ %% Give the tar file the same name as a directory.
+ ?line BadTar = filename:join(PrivDir, "bad_tarfile.tar"),
+ ?line ok = file:make_dir(BadTar),
+ ?line try_error(erl_tar, create, [BadTar, []], {BadTar, eisdir}),
+
+ %% Try including non-existent files in the tar file.
+ ?line NonExistent = "non_existent_file",
+ ?line GoodTar = filename:join(PrivDir, "a_good_tarfile.tar"),
+ ?line try_error(erl_tar, create, [GoodTar, [NonExistent]],
+ {NonExistent, enoent}),
+
+ %% Clean up.
+ ?line delete_files([GoodTar,BadTar]),
+
+ ok.
+
+try_error(M, F, A, Error) ->
+ io:format("Trying ~p:~p(~p)", [M, F, A]),
+ case catch apply(M, F, A) of
+ {'EXIT', Reason} ->
+ exit(Reason);
+ ok ->
+ test_server:fail(unexpected_success);
+ {error, Error} ->
+ case catch erl_tar:format_error(Error) of
+ {'EXIT', FReason} ->
+ test_server:fail({format_error, crashed, FReason});
+ String when list(String) ->
+ io:format("format_error(~p) -> ~s", [Error, String]);
+ Other ->
+ test_server:fail({format_error, returned, Other})
+ end;
+ Other ->
+ test_server:fail({expected, {error, Error}, actual, Other})
+ end.
+
+%% remove_prefix(Prefix, List) -> ListWithoutPrefix.
+
+remove_prefix([C|Rest1], [C|Rest2]) ->
+ remove_prefix(Rest1, Rest2);
+remove_prefix(_, Result) ->
+ Result.
+
+extract_from_binary(doc) ->
+ "Test extracting a tar archive from a binary.";
+extract_from_binary(Config) when list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Long = filename:join(DataDir, "no_fancy_stuff.tar"),
+ ?line ExtractDir = filename:join(PrivDir, "extract_from_binary"),
+ ?line ok = file:make_dir(ExtractDir),
+
+ %% Read a tar file into a binary and extract from the binary.
+ ?line {ok, Bin} = file:read_file(Long),
+ ?line ok = erl_tar:extract({binary, Bin}, [{cwd,ExtractDir}]),
+
+ %% Verify.
+ Dir = filename:join(ExtractDir, "no_fancy_stuff"),
+ ?line true = filelib:is_dir(Dir),
+ ?line true = filelib:is_file(filename:join(Dir, "a_dir_list")),
+ ?line true = filelib:is_file(filename:join(Dir, "EPLICENCE")),
+
+ %% Clean up.
+ ?line delete_files([ExtractDir]),
+
+ ok.
+
+extract_from_binary_compressed(Config) when is_list(Config) ->
+ %% Test extracting a compressed tar archive from a binary.
+ ?line DataDir = ?config(data_dir, Config),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Name = filename:join(DataDir, "cooked_tar_problem.tar.gz"),
+ ?line ExtractDir = filename:join(PrivDir, "extract_from_binary_compressed"),
+ ?line ok = file:make_dir(ExtractDir),
+ ?line {ok,Bin} = file:read_file(Name),
+
+ %% Try taking contents.
+ ?line {ok,Files} = erl_tar:table({binary,Bin}, [compressed]),
+ ?line io:format("~p\n", [Files]),
+ ?line 19 = length(Files),
+
+ %% Trying extracting from a binary.
+ ?line ok = erl_tar:extract({binary,Bin}, [compressed,{cwd,ExtractDir}]),
+ ?line {ok,List} = file:list_dir(filename:join(ExtractDir, ddll_SUITE_data)),
+ ?line io:format("~p\n", [List]),
+ ?line 19 = length(List),
+
+ %% Clean up while at the same time testing that all file
+ %% were extracted as expected.
+ lists:foreach(fun(N) ->
+ File = filename:join(ExtractDir, N),
+ io:format("Deleting: ~p\n", [File]),
+ ?line ok = file:delete(File)
+ end, Files),
+
+ %% Clean up the rest.
+ ?line delete_files([ExtractDir]),
+
+ ok.
+
+extract_from_open_file(doc) ->
+ "Test extracting a tar archive from an open file.";
+extract_from_open_file(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Long = filename:join(DataDir, "no_fancy_stuff.tar"),
+ ?line ExtractDir = filename:join(PrivDir, "extract_from_open_file"),
+ ?line ok = file:make_dir(ExtractDir),
+
+ ?line {ok, File} = file:open(Long, [read]),
+ ?line ok = erl_tar:extract({file, File}, [{cwd,ExtractDir}]),
+
+ %% Verify.
+ Dir = filename:join(ExtractDir, "no_fancy_stuff"),
+ ?line true = filelib:is_dir(Dir),
+ ?line true = filelib:is_file(filename:join(Dir, "a_dir_list")),
+ ?line true = filelib:is_file(filename:join(Dir, "EPLICENCE")),
+
+ %% Close open file.
+ ?line ok = file:close(File),
+
+ %% Clean up.
+ ?line delete_files([ExtractDir]),
+
+ ok.
+
+symlinks(doc) ->
+ "Test that archives containing symlinks can be created and extracted.";
+symlinks(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Dir = filename:join(PrivDir, "symlinks"),
+ ?line ok = file:make_dir(Dir),
+ ?line ABadSymlink = filename:join(Dir, "bad_symlink"),
+ ?line PointsTo = "/a/definitely/non_existing/path",
+ ?line Res = case file:make_symlink("/a/definitely/non_existing/path", ABadSymlink) of
+ {error, enotsup} ->
+ {skip, "Symbolic links not supported on this platform"};
+ ok ->
+ symlinks(Dir, "bad_symlink", PointsTo),
+ long_symlink(Dir)
+ end,
+
+ %% Clean up.
+ ?line delete_files([Dir]),
+ Res.
+
+symlinks(Dir, BadSymlink, PointsTo) ->
+ ?line Tar = filename:join(Dir, "symlink.tar"),
+ ?line DerefTar = filename:join(Dir, "dereference.tar"),
+
+ %% Create the archive.
+
+ ?line ok = file:set_cwd(Dir),
+ ?line GoodSymlink = "good_symlink",
+ ?line AFile = "a_good_file",
+ ?line ALine = "A line of text for a file.",
+ ?line ok = file:write_file(AFile, ALine),
+ ?line ok = file:make_symlink(AFile, GoodSymlink),
+ ?line ok = erl_tar:create(Tar, [BadSymlink, GoodSymlink, AFile], [verbose]),
+
+ %% List contents of tar file.
+
+ ?line ok = erl_tar:tt(Tar),
+
+ %% Also create another archive with the dereference flag.
+
+ ?line ok = erl_tar:create(DerefTar, [AFile, GoodSymlink], [dereference, verbose]),
+
+ %% Extract files to a new directory.
+
+ ?line NewDir = filename:join(Dir, "extracted"),
+ ?line ok = file:make_dir(NewDir),
+ ?line ok = erl_tar:extract(Tar, [{cwd, NewDir}, verbose]),
+
+ %% Verify that the files are there.
+
+ ?line ok = file:set_cwd(NewDir),
+ ?line {ok, #file_info{type=symlink}} = file:read_link_info(BadSymlink),
+ ?line {ok, PointsTo} = file:read_link(BadSymlink),
+ ?line {ok, #file_info{type=symlink}} = file:read_link_info(GoodSymlink),
+ ?line {ok, AFile} = file:read_link(GoodSymlink),
+ ?line Expected = list_to_binary(ALine),
+ ?line {ok, Expected} = file:read_file(GoodSymlink),
+
+ %% Extract the "dereferenced archive" to a new directory.
+
+ ?line NewDirDeref = filename:join(Dir, "extracted_deref"),
+ ?line ok = file:make_dir(NewDirDeref),
+ ?line ok = erl_tar:extract(DerefTar, [{cwd, NewDirDeref}, verbose]),
+
+ %% Verify that the files are there.
+
+ ?line ok = file:set_cwd(NewDirDeref),
+ ?line {ok, #file_info{type=regular}} = file:read_link_info(GoodSymlink),
+ ?line {ok, #file_info{type=regular}} = file:read_link_info(AFile),
+ ?line {ok, Expected} = file:read_file(GoodSymlink),
+ ?line {ok, Expected} = file:read_file(AFile),
+
+ ok.
+
+long_symlink(Dir) ->
+ ?line Tar = filename:join(Dir, "long_symlink.tar"),
+ ?line ok = file:set_cwd(Dir),
+
+ ?line AFile = "long_symlink",
+ ?line FarTooLong = "/tmp/aarrghh/this/path/is/far/longer/than/one/hundred/characters/which/is/the/maximum/number/of/characters/allowed",
+ ?line ok = file:make_symlink(FarTooLong, AFile),
+ ?line {error,Error} = erl_tar:create(Tar, [AFile], [verbose]),
+ ?line io:format("Error: ~s\n", [erl_tar:format_error(Error)]),
+ ?line {FarTooLong,symbolic_link_too_long} = Error,
+ ok.
+
+open_add_close(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line ok = file:set_cwd(PrivDir),
+ ?line Dir = filename:join(PrivDir, "open_add_close"),
+ ?line ok = file:make_dir(Dir),
+
+ ?line [{FileOne,_,_},{FileTwo,_,_},{FileThree,_,_}] = oac_files(),
+ ?line ADir = "empty_dir",
+ ?line AnotherDir = "another_dir",
+ ?line SomeContent = filename:join(AnotherDir, "some_content"),
+ ?line ok = file:make_dir(ADir),
+ ?line ok = file:make_dir(AnotherDir),
+ ?line ok = file:make_dir(SomeContent),
+
+ ?line TarOne = filename:join(Dir, "archive1.tar"),
+ ?line {ok,AD} = erl_tar:open(TarOne, [write]),
+ ?line ok = erl_tar:add(AD, FileOne, []),
+ ?line ok = erl_tar:add(AD, FileTwo, "second file", []),
+ ?line ok = erl_tar:add(AD, FileThree, [verbose]),
+ ?line ok = erl_tar:add(AD, ADir, [verbose]),
+ ?line ok = erl_tar:add(AD, AnotherDir, [verbose]),
+ ?line ok = erl_tar:close(AD),
+
+ ?line ok = erl_tar:t(TarOne),
+ ?line ok = erl_tar:tt(TarOne),
+
+ ?line {ok,[FileOne,"second file",FileThree,ADir,SomeContent]} = erl_tar:table(TarOne),
+
+ ?line delete_files(["oac_file","oac_small","oac_big",Dir,AnotherDir,ADir]),
+
+ ok.
+
+oac_files() ->
+ Files = [{"oac_file", 1459, $x},
+ {"oac_small", 99, $w},
+ {"oac_big", 33896, $A}],
+ create_files(Files),
+ Files.
+
+cooked_compressed(Config) when is_list(Config) ->
+ %% Test that a compressed archive can be read in cooked mode.
+ ?line DataDir = ?config(data_dir, Config),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Name = filename:join(DataDir, "cooked_tar_problem.tar.gz"),
+
+ %% Try table/2 and extract/2.
+ ?line {ok,List} = erl_tar:table(Name, [cooked,compressed]),
+ ?line io:format("~p\n", [List]),
+ ?line 19 = length(List),
+ ?line ok = erl_tar:extract(Name, [cooked,compressed,{cwd,PrivDir}]),
+
+ %% Clean up while at the same time testing that all file
+ %% were extracted as expected.
+ lists:foreach(fun(N) ->
+ File = filename:join(PrivDir, N),
+ io:format("Deleting: ~p\n", [File]),
+ ?line ok = file:delete(File)
+ end, List),
+
+ %% Clean up.
+ ?line delete_files([filename:join(PrivDir, ddll_SUITE_data)]),
+ ok.
+
+memory(doc) ->
+ ["Test that an archive can be created directly from binaries and "
+ "that an archive can be extracted into binaries."];
+memory(Config) when is_list(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+
+ ?line FileBins = [{"bar/fum", <<"BARFUM">>},{"foo", <<"FOO">>}],
+ ?line Name1 = filename:join(DataDir, "memory.tar"),
+ ?line ok = erl_tar:create(Name1, FileBins, [write,verbose]),
+ ?line {ok,Extracted1} = erl_tar:extract(Name1, [memory,verbose]),
+ ?line FileBins1 = lists:sort(Extracted1),
+
+ ?line io:format("FileBins: ~p\n", [FileBins]),
+ ?line io:format("FileBins1: ~p\n", [FileBins1]),
+ ?line FileBins = FileBins1,
+
+ ?line Name2 = filename:join(DataDir, "memory2.tar"),
+ ?line {ok,Fd} = erl_tar:open(Name2, [write]),
+ ?line [ok,ok] = [erl_tar:add(Fd, B, N, [write,verbose]) || {N,B} <- FileBins],
+ ?line ok = erl_tar:close(Fd),
+ ?line {ok,Extracted2} = erl_tar:extract(Name2, [memory,verbose]),
+ ?line FileBins2 = lists:sort(Extracted2),
+ ?line io:format("FileBins2: ~p\n", [FileBins2]),
+ ?line FileBins = FileBins2,
+
+ %% Clean up.
+ ?line ok = delete_files([Name1,Name2]),
+ ok.
+
+%% Delete the given list of files.
+delete_files([]) -> ok;
+delete_files([Item|Rest]) ->
+ case file:delete(Item) of
+ ok ->
+ delete_files(Rest);
+ {error,eperm} ->
+ file:change_mode(Item, 8#777),
+ delete_files(filelib:wildcard(filename:join(Item, "*"))),
+ file:del_dir(Item),
+ ok;
+ {error,eacces} ->
+ %% We'll see about that!
+ file:change_mode(Item, 8#777),
+ case file:delete(Item) of
+ ok -> ok;
+ {error,_} ->
+ erlang:yield(),
+ file:change_mode(Item, 8#777),
+ file:delete(Item),
+ ok
+ end;
+ {error,_} -> ok
+ end,
+ delete_files(Rest).
+
diff --git a/lib/stdlib/test/tar_SUITE_data/bad_checksum.tar b/lib/stdlib/test/tar_SUITE_data/bad_checksum.tar
new file mode 100644
index 0000000000..05a60c5ca9
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/bad_checksum.tar
Binary files differ
diff --git a/lib/stdlib/test/tar_SUITE_data/bad_even_shorter.tar b/lib/stdlib/test/tar_SUITE_data/bad_even_shorter.tar
new file mode 100644
index 0000000000..142935c49d
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/bad_even_shorter.tar
Binary files differ
diff --git a/lib/stdlib/test/tar_SUITE_data/bad_octal.tar b/lib/stdlib/test/tar_SUITE_data/bad_octal.tar
new file mode 100644
index 0000000000..85e63e3ea2
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/bad_octal.tar
Binary files differ
diff --git a/lib/stdlib/test/tar_SUITE_data/bad_too_short.tar b/lib/stdlib/test/tar_SUITE_data/bad_too_short.tar
new file mode 100644
index 0000000000..052b8b0e85
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/bad_too_short.tar
Binary files differ
diff --git a/lib/stdlib/test/tar_SUITE_data/cooked_tar_problem.tar.gz b/lib/stdlib/test/tar_SUITE_data/cooked_tar_problem.tar.gz
new file mode 100644
index 0000000000..92b66bc6d3
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/cooked_tar_problem.tar.gz
Binary files differ
diff --git a/lib/stdlib/test/tar_SUITE_data/long_names.tar b/lib/stdlib/test/tar_SUITE_data/long_names.tar
new file mode 100644
index 0000000000..431ea982c8
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/long_names.tar
Binary files differ
diff --git a/lib/stdlib/test/tar_SUITE_data/make_tar b/lib/stdlib/test/tar_SUITE_data/make_tar
new file mode 100755
index 0000000000..733a7ec2df
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/make_tar
@@ -0,0 +1,21 @@
+#! /bin/sh
+
+#
+# This script creates the test tar file in this directory.
+# Not needed for running any test case, but useful if you need
+# to update the test cases and create a new tar file.
+#
+
+first=original_software/written_by/a_bunch_of_hackers/spending_all_their_nights
+second=still/not_long_enough/but_soon_it_will_be
+base=$first/$second
+
+mkdir -p $base
+
+mkdir $base/empty_directory
+
+echo "Here is the first file." > $base/first_file
+echo "And here is the second file." > $base/second_file
+
+tar cf long_names.tar original_software
+rm -rf original_software
diff --git a/lib/stdlib/test/tar_SUITE_data/no_fancy_stuff.tar b/lib/stdlib/test/tar_SUITE_data/no_fancy_stuff.tar
new file mode 100644
index 0000000000..a45b7e0068
--- /dev/null
+++ b/lib/stdlib/test/tar_SUITE_data/no_fancy_stuff.tar
Binary files differ
diff --git a/lib/stdlib/test/timer_SUITE.erl b/lib/stdlib/test/timer_SUITE.erl
new file mode 100644
index 0000000000..86d8612b56
--- /dev/null
+++ b/lib/stdlib/test/timer_SUITE.erl
@@ -0,0 +1,391 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(timer_SUITE).
+
+-export([all/1]).
+-export([do_big_test/1]).
+-export([big_test/1, collect/3, i_t/3, a_t/2]).
+-export([do_nrev/1, internal_watchdog/2]).
+
+-include("test_server.hrl").
+
+%% Test suite for timer module. This is a really nasty test it runs a
+%% lot of timeouts and then checks in the end if any of them was
+%% trigggered too early or if any late timeouts was much too
+%% late. What should be added is more testing of the interface
+%% functions I guess. But I don't have time for that now.
+%%
+%% Expect it to run for at least 5-10 minutes!
+%% Except for VxWorks of course, where a couple of hours is more apropriate...
+
+
+%% The main test case in this module is "do_big_test", which
+%% orders a large number of timeouts and measures how
+%% exact the timeouts arrives. To simulate a system under load there is
+%% also a number of other concurrent processes running "nrev" at the same
+%% time. The result is analyzed afterwards by trying to check if the
+%% measured values are reasonable. It is hard to determine what is
+%% reasonable on different machines therefore the test can sometimes
+%% fail, even though the timer module is ok. I have checked against
+%% previous versions of the timer module (which contained bugs) and it
+%% seems it fails every time when running the buggy timer modules.
+%%
+%% The solution is to rewrite the test suite. Possible strategies for a
+%% rewrite: smarter math on the measuring data, test cases with varying
+%% amount of load. The test suite should also include tests that test the
+%% interface of the timer module.
+
+all(suite) -> [do_big_test].
+
+%% ------------------------------------------------------- %%
+
+do_big_test(TConfig) when is_list(TConfig) ->
+ Dog = ?t:timetrap(?t:minutes(20)),
+ Save = process_flag(trap_exit, true),
+ Result = case os:type() of
+ vxworks ->
+ big_test(10);
+ _ ->
+ big_test(200)
+ end,
+ process_flag(trap_exit, Save),
+ ?t:timetrap_cancel(Dog),
+ report_result(Result).
+
+report_result(ok) -> ok;
+report_result(Error) -> ?line test_server:fail(Error).
+
+%% ------------------------------------------------------- %%
+
+big_test(N) ->
+ C = start_collect(),
+ system_time(), system_time(), system_time(),
+ A1 = element(2, erlang:now()),
+ A2 = A1 * 3,
+ A3 = element(3, erlang:now()),
+ random:seed(A1, A2, A3),
+ random:uniform(100),random:uniform(100),random:uniform(100),
+
+ big_loop(C, N, []),
+
+ %%C ! print_report,
+ C ! {self(), get_report},
+ Report = receive
+ {report, R} ->
+ R
+ end,
+ C ! stop,
+ receive
+ {'EXIT', C, normal} ->
+ ok
+ end,
+ print_report(Report),
+ Result = analyze_report(Report),
+ %%io:format("big_test is done: ~w~n", [Result]),
+ Result.
+
+big_loop(_C, 0, []) ->
+ %%io:format("All processes are done!~n", []),
+ ok;
+big_loop(C, 0, Pids) ->
+ %%ok = io:format("Loop done, ~w processes remaining~n", [length(Pids)]),
+ %% wait for remaining processes
+ receive
+ {'EXIT', Pid, done} ->
+ big_loop(C, 0, lists:delete(Pid, Pids));
+ {'EXIT', Pid, Error} ->
+ ?line ok = io:format("XXX Pid ~w died with reason ~p~n",
+ [Pid, Error]),
+ big_loop(C, 0, lists:delete(Pid, Pids))
+ end;
+big_loop(C, N, Pids) ->
+ %% First reap any processes that are done.
+ receive
+ {'EXIT', Pid, done} ->
+ big_loop(C, N, lists:delete(Pid, Pids));
+ {'EXIT', Pid, Error} ->
+ ?line ok =io:format("XXX Internal error: Pid ~w died, reason ~p~n",
+ [Pid, Error]),
+ big_loop(C, N, lists:delete(Pid, Pids))
+ after 0 ->
+
+ %% maybe start an interval timer test
+ Pids1 = maybe_start_i_test(Pids, C, random:uniform(4)),
+
+ %% start 1-4 "after" tests
+ Pids2 = start_after_test(Pids1, C, random:uniform(4)),
+ %%Pids2=Pids1,
+
+ %% wait a little while
+ timer:sleep(random:uniform(200)*10),
+
+ %% spawn zero, one or two nrev to get some load ;-/
+ Pids3 = start_nrev(Pids2, random:uniform(100)),
+
+ big_loop(C, N-1, Pids3)
+ end.
+
+
+start_nrev(Pids, N) when N < 25 ->
+ Pids;
+start_nrev(Pids, N) when N < 75 ->
+ [spawn_link(timer_SUITE, do_nrev, [1])|Pids];
+start_nrev(Pids, _N) ->
+ NrevPid1 = spawn_link(timer_SUITE, do_nrev, [random:uniform(1000)*10]),
+ NrevPid2 = spawn_link(timer_SUITE, do_nrev, [1]),
+ [NrevPid1,NrevPid2|Pids].
+
+
+start_after_test(Pids, C, 1) ->
+ TO1 = random:uniform(100)*100,
+ [s_a_t(C, TO1)|Pids];
+start_after_test(Pids, C, 2) ->
+ TO1 = random:uniform(100)*100,
+ TO2 = TO1 div random:uniform(3) + 200,
+ [s_a_t(C, TO1),s_a_t(C, TO2)|Pids];
+start_after_test(Pids, C, N) ->
+ TO1 = random:uniform(100)*100,
+ start_after_test([s_a_t(C, TO1)|Pids], C, N-1).
+
+s_a_t(C, TimeOut) ->
+ spawn_link(timer_SUITE, a_t, [C, TimeOut]).
+
+a_t(C, TimeOut) ->
+ start_watchdog(self(), TimeOut),
+ Start = system_time(),
+ timer:send_after(TimeOut, self(), now),
+ receive
+ now ->
+ Stop = system_time(),
+ report(C, Start,Stop,TimeOut),
+ exit(done);
+ watchdog ->
+ Stop = system_time(),
+ report(C, Start,Stop,TimeOut),
+ ?line ok = io:format("Internal watchdog timeout (a), not good!!~n",
+ []),
+ exit(done)
+ end.
+
+
+maybe_start_i_test(Pids, C, 1) ->
+ %% ok do it
+ TOI = random:uniform(100)*100,
+ CountI = random:uniform(10) + 3, % at least 4 times
+ [spawn_link(timer_SUITE, i_t, [C, TOI, CountI])|Pids];
+maybe_start_i_test(Pids, _C, _) ->
+ Pids.
+
+i_t(C, TimeOut, Times) ->
+ start_watchdog(self(), TimeOut*Times),
+ Start = system_time(),
+ {ok, Ref} = timer:send_interval(TimeOut, interval),
+ i_wait(Start, Start, 1, TimeOut, Times, Ref, C).
+
+i_wait(Start, Prev, Times, TimeOut, Times, Ref, C) ->
+ receive
+ interval ->
+ Now = system_time(),
+ report_interval(C, {final,Times}, Start, Prev, Now, TimeOut),
+ timer:cancel(Ref),
+ exit(done);
+ watchdog ->
+ Now = system_time(),
+ report_interval(C, {final,Times}, Start, Prev, Now, TimeOut),
+ timer:cancel(Ref),
+ ?line ok = io:format("Internal watchdog timeout (i), not good!!~n",
+ []),
+ exit(done)
+ end;
+i_wait(Start, Prev, Count, TimeOut, Times, Ref, C) ->
+ receive
+ interval ->
+ Now = system_time(),
+ report_interval(C, Count, Start, Prev, Now, TimeOut),
+ i_wait(Start, Now, Count+1, TimeOut, Times, Ref, C);
+ watchdog ->
+ Now = system_time(),
+ report_interval(C, {final,Count}, Start, Prev, Now, TimeOut),
+ ?line ok = io:format("Internal watchdog timeout (j), not good!!~n",
+ []),
+ exit(done)
+ end.
+
+report(C, Start, Stop, Time) ->
+ C ! {a_sample, Start, Stop, Time}.
+report_interval(C, Count, Start, Prev, Now, TimeOut) ->
+ C ! {i_sample, Count, Start, Prev, Now, TimeOut}.
+
+%% ------------------------------------------------------- %%
+
+%% internal watchdog
+start_watchdog(Pid, TimeOut) ->
+ spawn_link(timer_SUITE, internal_watchdog, [Pid, 3*TimeOut+1000]).
+
+internal_watchdog(Pid, TimeOut) ->
+ receive
+ after TimeOut ->
+ Pid ! watchdog,
+ exit(normal)
+ end.
+
+%% ------------------------------------------------------- %%
+
+-record(stat, {n=0,max=0,min=min,avg=0}).
+
+start_collect() ->
+ spawn_link(timer_SUITE, collect, [0,{0,new_update(),new_update()},[]]).
+
+collect(N, {E,A,B}, I) ->
+ receive
+ {a_sample, Start, Stop, Time} when Stop - Start > Time ->
+ collect(N+1, {E,update(Stop-Start-Time,A),B}, I);
+ {a_sample, Start, Stop, Time} when Stop - Start < Time ->
+ collect(N+1, {E,A,update(Time-Stop+Start,B)}, I);
+ {a_sample, _Start, _Stop, _Time} ->
+ collect(N+1, {E+1,A,B}, I);
+ {i_sample, {final,Count}, Start, Prev, Now, TimeOut} ->
+ IntervDiff = Now - Prev - TimeOut,
+ Drift = Now - (Count*TimeOut) - Start,
+ collect(N, {E,A,B}, [{{final,Count},IntervDiff,Drift}|I]);
+ {i_sample, Count, Start, Prev, Now, TimeOut} ->
+ IntervDiff = Now - Prev - TimeOut,
+ Drift = Now - (Count*TimeOut) - Start,
+ collect(N, {E,A,B}, [{Count,IntervDiff,Drift}|I]);
+ print_report ->
+ print_report({E,A,B,I}),
+ collect(N,{E,A,B}, I);
+ {Pid, get_report} when pid(Pid) ->
+ Pid ! {report, {E, A, B, I}},
+ collect(N,{E,A,B}, I);
+ reset ->
+ collect(0, {0,new_update(),new_update()}, []);
+ stop ->
+ exit(normal);
+ _Other ->
+ collect(N, {E,A,B}, I)
+ end.
+
+new_update() -> #stat{}.
+update(New, Stat) when New > Stat#stat.max ->
+ Stat#stat{n=Stat#stat.n + 1, max=New, avg=(New+Stat#stat.avg) div 2};
+update(New, Stat) when New < Stat#stat.min ->
+ Stat#stat{n=Stat#stat.n + 1, min=New, avg=(New+Stat#stat.avg) div 2};
+update(New, Stat) ->
+ Stat#stat{n=Stat#stat.n + 1, avg=(New+Stat#stat.avg) div 2}.
+
+%update(New, {N,Max,Min,Avg}) when New>Max ->
+% {N+1,New,Min,(New+Avg) div 2};
+%update(New, {N,Max,Min,Avg}) when New<Min ->
+% {N+1,Max,New,(New+Avg) div 2};
+%update(New, {N,Max,Min,Avg}) ->
+% {N+1,Max,Min,(New+Avg) div 2}.
+
+print_report({E,LateS,EarlyS,I}) ->
+ Early = EarlyS#stat.n, Late = LateS#stat.n,
+ Total = E + Early + Late,
+ io:format("~nOn total of ~w timeouts, there were ~w exact, ~w "
+ "late and ~w early.~n", [Total, E, Late, Early]),
+ io:format("Late stats (N,Max,Min,Avg): ~w~nEarly stats: ~w~n",
+ [LateS, EarlyS]),
+ IntervS = collect_interval_final_stats(I),
+ io:format("Interval stats (Max,Min,Avg): ~w~n", [IntervS]),
+ ok.
+
+collect_interval_final_stats(I) ->
+ collect_interval_final_stats(I, 0, min, 0).
+collect_interval_final_stats([], Max, Min, Avg) ->
+ {Max, Min, Avg};
+collect_interval_final_stats([{{final,_Count},_,Dev}|T], Max, Min, Avg) ->
+ NMax = if Dev>Max -> Dev; true -> Max end,
+ NMin = if Dev<Min -> Dev; true -> Min end,
+ collect_interval_final_stats(T, NMax, NMin, (Dev+Avg) div 2);
+collect_interval_final_stats([_|T], Max, Min, Avg) ->
+ collect_interval_final_stats(T, Max, Min, Avg).
+
+analyze_report({E,LateS,EarlyS,I}) ->
+ Early = EarlyS#stat.n, Late = LateS#stat.n,
+ IntervS = collect_interval_final_stats(I),
+ Res1 = min_and_early_check(E, Early, Late, element(2,IntervS)),
+ Res2 = abnormal_max_check(LateS#stat.max, element(1,IntervS)),
+ res_combine(ok, [Res1, Res2]).
+
+-define(ok_i_min, -100).
+-define(ok_max, 8000).
+-define(ok_i_max, 4000).
+
+%% ok as long as Early == 0 and IntervMin >= ok_interv_min
+min_and_early_check(_Exact, 0, _Late, IntervMin) when IntervMin >= ?ok_i_min ->
+ ok;
+min_and_early_check(_Exact, Early, _Late, IntervMin) when IntervMin >= ?ok_i_min ->
+ {error, {early_timeouts, Early}};
+min_and_early_check(_Exact, 0, _Late, _IntervMin) ->
+ {error, early_interval_timeout};
+min_and_early_check(_Exact, Early, _Late, _IntervMin) ->
+ {error, [{early_timeouts, Early},{error, early_interval_timeout}]}.
+
+abnormal_max_check(LateMax, IntMax) when LateMax < ?ok_max,
+ IntMax < ?ok_i_max ->
+ ok;
+abnormal_max_check(LateMax, IntMax) when IntMax < ?ok_i_max ->
+ {error, {big_late_max, LateMax}};
+abnormal_max_check(LateMax, IntMax) when LateMax < ?ok_max ->
+ {error, {big_interval_max, IntMax}};
+abnormal_max_check(LateMax, IntMax) ->
+ {error, [{big_late_max, LateMax},{big_interval_max, IntMax}]}.
+
+res_combine(Res, []) ->
+ Res;
+res_combine(Res, [ok|T]) ->
+ res_combine(Res, T);
+res_combine(ok, [{error,What}|T]) ->
+ res_combine({error,What}, T);
+res_combine({error,Es}, [{error,E}|T]) ->
+ res_combine({error,lists:flatten([E,Es])}, T).
+
+
+system_time() ->
+ %%element(1, statistics(wall_clock)).
+ {M,S,U} = erlang:now(),
+ 1000000000 * M + 1000 * S + (U div 1000).
+
+%% ------------------------------------------------------- %%
+
+do_nrev(Sleep) ->
+ timer:sleep(Sleep),
+ test(1000,"abcdefghijklmnopqrstuvxyz1234"),
+ exit(done).
+
+test(0,_) ->
+ true;
+test(N,L) ->
+ nrev(L),
+ test(N - 1, L).
+
+nrev([]) ->
+ [];
+nrev([H|T]) ->
+ append(nrev(T), [H]).
+
+append([H|T],Z) ->
+ [H|append(T,Z)];
+append([],X) ->
+ X.
+
+%% ------------------------------------------------------- %%
diff --git a/lib/stdlib/test/timer_simple_SUITE.erl b/lib/stdlib/test/timer_simple_SUITE.erl
new file mode 100644
index 0000000000..021a22c61b
--- /dev/null
+++ b/lib/stdlib/test/timer_simple_SUITE.erl
@@ -0,0 +1,551 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%% Purpose : Test the timer module a simpler/faster test than timer_SUITE
+
+-module(timer_simple_SUITE).
+
+%% external
+-export([all/1,
+ init_per_testcase/2,
+ apply_after/1,
+ send_after1/1,
+ send_after2/1,
+ send_after3/1,
+ exit_after1/1,
+ exit_after2/1,
+ kill_after1/1,
+ kill_after2/1,
+ apply_interval/1,
+ send_interval1/1,
+ send_interval2/1,
+ send_interval3/1,
+ send_interval4/1,
+ cancel1/1,
+ cancel2/1,
+ tc/1,
+ unique_refs/1,
+ timer_perf/1]).
+
+%% internal
+-export([forever/0,
+ do_nrev/2,
+ send/2,
+ timer/4,
+ timer/5]).
+
+-include("test_server.hrl").
+
+-define(MAXREF, (1 bsl 18)).
+-define(REFMARG, 30).
+
+all(doc) -> "Test of the timer module.";
+all(suite) ->
+ [apply_after,
+ send_after1,
+ send_after2,
+ send_after3,
+ exit_after1,
+ exit_after2,
+ kill_after1,
+ kill_after2,
+ apply_interval,
+ send_interval1,
+ send_interval2,
+ send_interval3,
+ send_interval4,
+ cancel1,
+ cancel2,
+ tc,
+ unique_refs,
+ timer_perf].
+
+init_per_testcase(_, Config) when is_list(Config) ->
+ timer:start(),
+ Config.
+
+%% Testing timer interface!!
+
+apply_after(doc) -> "Test of apply_after, with sending of message.";
+apply_after(suite) -> [];
+apply_after(Config) when is_list(Config) ->
+ ?line timer:apply_after(500, ?MODULE, send, [self(), ok_apply]),
+ ?line ok = get_mess(1000, ok_apply).
+
+send_after1(doc) -> "Test of send_after with time = 0.";
+send_after1(suite) -> [];
+send_after1(Config) when is_list(Config) ->
+ ?line timer:send_after(0, ok_send1),
+ ?line ok = get_mess(1000, ok_send1).
+
+send_after2(doc) -> "Test of send_after with time = 500.";
+send_after2(suite) -> [];
+send_after2(Config) when is_list(Config) ->
+ ?line timer:send_after(500, self(), ok_send2),
+ ?line ok = get_mess(2000, ok_send2).
+
+send_after3(doc) -> "Test of send_after with time = 500, with receiver "
+ "a registered process. [OTP-2735]";
+send_after3(suite) -> [];
+send_after3(Config) when is_list(Config) ->
+ ?line Name = list_to_atom(pid_to_list(self())),
+ ?line register(Name, self()),
+ ?line timer:send_after(500, Name, ok_send3),
+ ?line ok = get_mess(2000, ok_send3),
+ ?line unregister(Name).
+
+exit_after1(doc) -> "Test of exit_after with time = 1000.";
+exit_after1(suite) -> [];
+exit_after1(Config) when is_list(Config) ->
+ ?line process_flag(trap_exit, true),
+ ?line Pid = spawn_link(?MODULE, forever, []),
+ ?line timer:exit_after(1000, Pid, exit_test1),
+ ?line ok = get_mess(5000, {'EXIT', Pid, exit_test1}).
+
+exit_after2(doc) -> "Test of exit_after with time = 1000. The process to "
+ "exit is the name of a registered process. "
+ "[OTP-2735]";
+exit_after2(suite) -> [];
+exit_after2(Config) when is_list(Config) ->
+ ?line process_flag(trap_exit, true),
+ ?line Pid = spawn_link(?MODULE, forever, []),
+ ?line Name = list_to_atom(pid_to_list(Pid)),
+ ?line register(Name, Pid),
+ ?line timer:exit_after(1000, Name, exit_test2),
+ ?line ok = get_mess(2000, {'EXIT', Pid, exit_test2}).
+
+kill_after1(doc) -> "Test of kill_after with time = 1000.";
+kill_after1(suite) -> [];
+kill_after1(Config) when is_list(Config) ->
+ ?line process_flag(trap_exit, true),
+ ?line Pid = spawn_link(?MODULE, forever, []),
+ ?line timer:kill_after(1000, Pid),
+ ?line ok = get_mess(2000, {'EXIT', Pid, killed}).
+
+kill_after2(doc) -> "Test of kill_after with time = 1000. The process to "
+ "exit is the name of a registered process. "
+ "[OTP-2735]";
+kill_after2(suite) -> [];
+kill_after2(Config) when is_list(Config) ->
+ ?line process_flag(trap_exit, true),
+ ?line Pid = spawn_link(?MODULE, forever, []),
+ ?line Name = list_to_atom(pid_to_list(Pid)),
+ ?line register(Name, Pid),
+ ?line timer:kill_after(1000, Name),
+ ?line ok = get_mess(2000, {'EXIT', Pid, killed}).
+
+apply_interval(doc) -> "Test of apply_interval by sending messages. Receive "
+ "3 messages, cancel the timer, and check that we do "
+ "not get any more messages.";
+apply_interval(suite) -> [];
+apply_interval(Config) when is_list(Config) ->
+ ?line {ok, Ref} = timer:apply_interval(1000, ?MODULE, send,
+ [self(), apply_int]),
+ ?line ok = get_mess(1500, apply_int, 3),
+ ?line timer:cancel(Ref),
+ ?line nor = get_mess(1000, apply_int).
+
+send_interval1(doc) -> "Test of send_interval/2. Receive 5 messages, cancel "
+ "the timer, and check that we do not get any more "
+ "messages.";
+send_interval1(suite) -> [];
+send_interval1(Config) when is_list(Config) ->
+ {ok, Ref} = timer:send_interval(1000, send_int),
+ ?line ok = get_mess(1500, send_int, 5),
+ timer:cancel(Ref),
+ ?line nor = get_mess(1000, send_int). % We should receive only five
+
+send_interval2(doc) -> "Test of send_interval/3. Receive 2 messages, cancel "
+ "the timer, and check that we do not get any more "
+ "messages.";
+send_interval2(suite) -> [];
+send_interval2(Config) when is_list(Config) ->
+ {ok, Ref} = timer:send_interval(1000, self(), send_int2),
+ ?line ok = get_mess(1500, send_int2, 2),
+ timer:cancel(Ref),
+ ?line nor = get_mess(1000, send_int2). % We should receive only two
+
+send_interval3(doc) -> "Test of send_interval/3. Receive 2 messages, cancel "
+ "the timer, and check that we do not get any more "
+ "messages. The receiver is the name of a registered "
+ "process. [OTP-2735]";
+send_interval3(suite) -> [];
+send_interval3(Config) when is_list(Config) ->
+ ?line process_flag(trap_exit, true),
+ ?line Name = list_to_atom(pid_to_list(self())),
+ ?line register(Name, self()),
+ ?line {ok, Ref} = timer:send_interval(1000, Name, send_int3),
+ ?line ok = get_mess(1500, send_int3, 2),
+ timer:cancel(Ref),
+ ?line nor = get_mess(1000, send_int3), % We should receive only two
+ ?line unregister(Name).
+
+send_interval4(doc) -> "Test that send interval stops sending msg when the "
+ "receiving process terminates.";
+send_interval4(suite) -> [];
+send_interval4(Config) when is_list(Config) ->
+ ?line timer:send_interval(500, one_time_only),
+ receive
+ one_time_only -> ok
+ end,
+ ?line timer_server ! {'EXIT', self(), normal}, % Should remove the timer
+ ?line timer:send_after(600, send_intv_ok),
+ ?line send_intv_ok = receive
+ Msg -> Msg
+ end.
+
+cancel1(doc) -> "Test that we can cancel a timer.";
+cancel1(suite) -> [];
+cancel1(Config) when is_list(Config) ->
+ ?line {ok, Ref} = timer:send_after(1000, this_should_be_canceled),
+ ?line timer:cancel(Ref),
+ ?line nor = get_mess(2000, this_should_be_canceled). % We should rec 0 msgs
+
+cancel2(doc) -> "Test cancel/1 with bad argument.";
+cancel2(suite) -> [];
+cancel2(Config) when is_list(Config) ->
+ ?line {error, badarg} = timer:cancel(no_reference).
+
+tc(doc) -> "Test sleep/1 and tc/3.";
+tc(suite) -> [];
+tc(Config) when is_list(Config) ->
+ % This should both sleep and tc
+ ?line {Res, ok} = timer:tc(timer, sleep, [500]),
+ ?line ok = if
+ Res < 500*1000 -> {too_early, Res}; % Too early
+ Res > 800*1000 -> {too_late, Res}; % Too much time
+ true -> ok
+ end,
+
+ ?line Sec = timer:seconds(4),
+ ?line Min = timer:minutes(4),
+ ?line Hour = timer:hours(4),
+ ?line MyRes = 4*1000 + 4*60*1000 + 4*60*60*1000,
+ ?line if MyRes == Sec + Min + Hour -> ok end,
+ ?line TimerRes = timer:hms(4,4,4),
+ ?line if MyRes == TimerRes -> ok end,
+ ok.
+
+unique_refs(doc) ->
+ "Tests that cancellations of one-shot timers do not accidentally "
+ "cancel interval timers [OTP-2771].";
+unique_refs(suite) ->
+ [];
+unique_refs(Config) when is_list(Config) ->
+ ?line ITimers = repeat_send_interval(10), % 10 interval timers
+ ?line eat_refs(?MAXREF - ?REFMARG),
+ ?line set_and_cancel_one_shots(?REFMARG),
+ ?line NumLeft = num_timers(),
+ ?line io:format("~w timers left, should be 10\n", [NumLeft]),
+ ?line cancel(ITimers),
+ ?line receive_nisse(),
+ ?line 10 = NumLeft.
+
+
+repeat_send_interval(0) ->
+ [];
+repeat_send_interval(M) ->
+ ?line {ok, Ref} = timer:send_interval(6000,self(), nisse),
+ ?line [Ref| repeat_send_interval(M - 1)].
+
+eat_refs(0) ->
+ 0;
+eat_refs(N) ->
+ _ = make_ref(),
+ eat_refs(N-1).
+
+set_and_cancel_one_shots(0) ->
+ 0;
+set_and_cancel_one_shots(N) ->
+ {ok, Ref} = timer:send_after(7000, self(), kalle),
+ %% Cancel twice
+ timer:cancel(Ref),
+ timer:cancel(Ref),
+ set_and_cancel_one_shots(N-1).
+
+cancel([T| Ts]) ->
+ ?line timer:cancel(T),
+ ?line cancel(Ts);
+cancel([]) ->
+ ok.
+
+num_timers() ->
+ {{_, TotalTimers},{_, _IntervalTimers}} = timer:get_status(),
+ TotalTimers.
+
+receive_nisse() ->
+ receive
+ nisse ->
+ receive_nisse()
+ after 0 ->
+ ok
+ end.
+
+
+get_mess(Time, Mess) -> get_mess(Time, Mess, 1).
+get_mess(_, _, 0) -> ok; % Received
+get_mess(Time, Mess, N) ->
+ receive
+ Mess -> get_mess(Time, Mess, N-1)
+ after Time
+ -> nor % Not Received
+ end.
+
+forever() ->
+ timer:sleep(1000),
+ forever().
+
+
+%
+% Testing for performance (on different implementations) of timers
+%
+
+timer_perf(suite) -> [];
+timer_perf(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(10)),
+ Res = performance(timer),
+ ?t:timetrap_cancel(Dog),
+ Res.
+
+performance(Mod) ->
+ process_flag(trap_exit, true),
+ {Y,Mo,D} = date(),
+ {H,M,S} = time(),
+ io:format("Testing module '~p' Date: ~w/~w/~w ~w:~w:~w~n",
+ [Mod,Y,Mo,D,H,M,S]),
+ Result = big_test(Mod),
+ report_result(Result).
+
+big_test(M) ->
+ Load_Pids = start_nrev(20, M), % Increase if more load wanted :)
+
+ apply(M, sleep, [9000]),
+ LPids = spawn_timers(5, M, 10000, 5),
+
+ apply(M, sleep, [4000]),
+ MPids = spawn_timers(10, M, 1000, 6),
+
+ apply(M, sleep, [3500]),
+ SPids = spawn_timers(15, M, 100, 3),
+
+ Res = wait(SPids ++ MPids ++ LPids, [], 0, M),
+
+ lists:foreach(fun(Pid) -> exit(Pid, kill) end, Load_Pids),
+ Res.
+
+wait([], Res, N, _) ->
+ {Res, N};
+wait(Pids, ResList, N, M) ->
+ receive
+ {Pid, ok, Res, T} ->
+ wait(lists:delete(Pid, Pids), [{T, Res} | ResList], N, M);
+ {Pid, Error}->
+ ?line test_server:fail(Error),
+ wait(lists:delete(Pid, Pids), ResList, N+1, M);
+ {'EXIT', Pid, normal} ->
+ wait(lists:delete(Pid, Pids), ResList, N, M);
+ {'EXIT', Pid, Reason} ->
+ ?line test_server:fail({Pid,Reason})
+ end.
+
+spawn_timers(0, _, _, _) ->
+ [];
+spawn_timers(N, M, T, NumIter) ->
+ apply(M, sleep, [120*N]),
+ Pid1 = spawn_link(?MODULE, timer, [apply, M, T, self()]),
+ Pid2 = spawn_link(?MODULE, timer, [interval, M, T, self(), NumIter]),
+ [Pid1, Pid2 | spawn_timers(N-1, M, T, NumIter)].
+
+timer(apply, Mod, T, Pid) ->
+ Before = system_time(),
+ {ok, Ref} = apply(Mod, apply_after, [T, ?MODULE, send, [self(), done]]),
+ receive
+ done ->
+ After = system_time(),
+ Pid ! {self(), ok, (After-Before) div 1000, T}
+ after T*3 + 300 -> % Watch dog
+ io:format("WARNING TIMER WATCHDOG timed out: ~w ~n", [T]),
+ timer:cancel(Ref),
+ Pid ! {self(), watch_dog_timed_out}
+ end.
+
+timer(interval, Mod, T, Pid, NumIter) ->
+ Before = system_time(),
+ {ok, Ref} = apply(Mod, apply_interval, [T, ?MODULE, send, [self(), done]]),
+ timer_irec(Before, T, {0, NumIter}, [], {Pid, Mod, Ref}).
+
+timer_irec(_Start, T, {N, N}, Res, {Pid, Mod, Ref}) ->
+ apply(Mod, cancel, [Ref]),
+ Min = lists:min(Res),
+ Max = lists:max(Res),
+ Tot = lists:sum(Res),
+ Pid ! {self(), ok, {N, Tot, Tot div N, Min, Max}, T};
+timer_irec(Start, T, {N, Max}, Res, {Pid, Mod, Ref}) ->
+ receive
+ done ->
+ Now = system_time(),
+ Elapsed = (Now - (Start + (N*T*1000))) div 1000,
+% io:format("~w Now ~w Started ~w Elap ~w~n", [T,Now,Start,Elapsed]),
+ timer_irec(Start, T,
+ {N+1, Max},
+ [Elapsed | Res],
+ {Pid, Mod, Ref})
+ after T*3 + 300 ->
+ apply(Mod, cancel, [Ref]),
+ io:format("WARNING: TIMER WATCHDOG timed out <Interval>~w~n",[T]),
+ Pid ! {self(), timer_watchdog_timed_out_in_interlval_test}
+ end.
+
+%% ------------------------------------------------------- %%
+%% Small last generator
+
+start_nrev(0, _) ->
+ [];
+
+start_nrev(N, M) ->
+ Pid = spawn_link(?MODULE, do_nrev, [N, M]),
+ [Pid | start_nrev(N-1, M)].
+
+do_nrev(Sleep, Mod) ->
+ apply(Mod, sleep, [50 * Sleep]),
+ test(1000,"abcdefghijklmnopqrstuvxyz1234"),
+ ok.
+
+test(0,_) ->
+ true;
+test(N,L) ->
+ nrev(L),
+ test(N - 1, L).
+
+nrev([]) ->
+ [];
+nrev([H|T]) ->
+ append(nrev(T), [H]).
+
+append([H|T],Z) ->
+ [H|append(T,Z)];
+append([],X) ->
+ X.
+
+system_time() ->
+ {M,S,U} = erlang:now(),
+ 1000000*(M*1000000 + S) + U.
+
+%% ------------------------------------------------------- %%
+
+report_result({Res, 0}) ->
+% io:format("DEBUG0 all ~p ~n", [Res]),
+ {A_List, I_List} = split_list(Res, [], []),
+ A_val = calc_a_val(A_List),
+ I_val = calc_i_val(I_List),
+ print_report(A_val, I_val),
+ ok;
+
+report_result({Head, N}) ->
+ io:format("Test Failed: Number of internal tmo ~w~n", [N]),
+ ?line test_server:fail({Head, N}).
+
+split_list([], AL, IL) ->
+ {AL, IL};
+split_list([{T, {N, Tot, A, Min, Max}} | Rest], AL, IL) ->
+ split_list(Rest, AL, [{T, {N, Tot, A, Min, Max}} | IL]);
+split_list([Head | Rest], AL, IL) ->
+ split_list(Rest, [Head | AL], IL).
+
+split([{T, Res} | R]) ->
+ split(R, {{T,[Res]}, {T*10,[]}, {T*100,[]}}).
+
+split([{T, Res} | R], {{T,S}, M, L}) ->
+ split(R, {{T,[Res|S]}, M, L});
+
+split([{T, Res} | R], {S, {T,M}, L}) ->
+ split(R, {S, {T, [Res|M]}, L});
+
+split([{T, Res} | R], {S, M, {T,L}}) ->
+ split(R, {S, M, {T, [Res|L]}});
+
+split(_Done, Vals) ->
+ Vals.
+
+calc_a_val(List) ->
+ New = lists:sort(List),
+ {{T1, S}, {T2, M}, {T3, L}} = split(New),
+ S2 = {length(S), lists:max(S), lists:min(S),
+ lists:sum(S) div length(S)},
+ M2 = {length(M), lists:max(M), lists:min(M),
+ lists:sum(M) div length(M)},
+ L2 = {length(L), lists:max(L), lists:min(L),
+ lists:sum(L) div length(L)},
+ [{T1, S2}, {T2, M2}, {T3, L2}].
+
+calc_i_val(List) ->
+ New = lists:sort(List),
+ {{T1, S}, {T2, M}, {T3, L}} = split(New),
+ S2 = get_ivals(S),
+ M2 = get_ivals(M),
+ L2 = get_ivals(L),
+ [{T1, S2}, {T2, M2}, {T3, L2}].
+
+get_ivals(List) ->
+ Len = length(List),
+ Num = element(1, hd(List)), % Number of iterations
+
+ LTot = lists:map(fun(X) -> element(2, X) end, List),
+ LMin = lists:map(fun(X) -> element(4, X) end, List),
+ LMax = lists:map(fun(X) -> element(5, X) end, List),
+
+ MaxTot = lists:max(LTot),
+ MinTot = lists:min(LTot),
+ AverTot = lists:sum(LTot) div Len,
+
+ IterMax = lists:max(LMax),
+ IterMin = lists:min(LMin),
+ IterAver= AverTot div Num,
+
+ {Len, Num,
+ {MaxTot, MinTot, AverTot},
+ {IterMax, IterMin, IterAver}}.
+
+
+print_report(A_L, I_L) ->
+ io:format("~nRESULTS from timer test~n~n",[]),
+ io:format("Time out times for send_after~n~n", []),
+ io:format("Time No of tests Max Min Average~n",[]),
+ print_aval(A_L),
+ io:format("Time out times for send_interval~n~n", []),
+ io:format("Time No.tests No.intvals TotMax TotMin TotAver MaxI MinI AverI~n", []),
+ print_ival(I_L).
+
+print_aval([]) ->
+ io:format("~n~n", []);
+print_aval([{T, {L, Max, Min, Aver}}|R]) ->
+ io:format("~5w ~8w ~6w ~6w ~8w ~n",
+ [T,L,Max,Min,Aver]),
+ print_aval(R).
+
+print_ival([]) ->
+ io:format("~n", []);
+print_ival([{T, {Len, Num,
+ {MaxT, MinT, AverT},
+ {MaxI, MinI, AverI}}}|R]) ->
+ io:format("~5w ~6w ~10w ~8w ~6w ~6w ~6w ~6w ~6w~n",
+ [T,Len,Num,MaxT,MinT,AverT, MaxI, MinI, AverI]),
+ print_ival(R).
+
+send(Pid, Msg) ->
+ Pid ! Msg.
diff --git a/lib/stdlib/test/unicode_SUITE.erl b/lib/stdlib/test/unicode_SUITE.erl
new file mode 100644
index 0000000000..706445005c
--- /dev/null
+++ b/lib/stdlib/test/unicode_SUITE.erl
@@ -0,0 +1,1241 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(unicode_SUITE).
+
+-include("test_server.hrl").
+
+-export([all/1,
+ init_per_testcase/2,
+ fin_per_testcase/2,
+ utf8_illegal_sequences_bif/1,
+ utf16_illegal_sequences_bif/1,
+ random_lists/1,
+ roundtrips/1,
+ latin1/1,
+ exceptions/1]).
+
+init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ Dog=?t:timetrap(?t:minutes(20)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ ?t:timetrap_cancel(Dog).
+
+all(suite) ->
+ [utf8_illegal_sequences_bif,utf16_illegal_sequences_bif,random_lists,roundtrips,latin1,exceptions].
+
+
+exceptions(Config) when is_list(Config) ->
+ setlimit(10),
+ ex_exceptions(Config),
+ setlimit(default),
+ ex_exceptions(Config).
+
+ex_exceptions(Config) when is_list(Config) ->
+ ?line L = lists:seq(0,255),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L++255,unicode)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary({1,2,3},unicode)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1,unicode)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1.0,unicode)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary('1',unicode)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,apa],unicode)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,4.0],unicode)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L++255,latin1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary({1,2,3},latin1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1,latin1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1.0,latin1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary('1',latin1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,apa],latin1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,4.0],latin1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,gnarfl)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,L)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,{latin1})),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,[latin1])),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,1.0)),
+ Encodings = [unicode, utf8,utf16,utf32,{utf16,big},
+ {utf16,little},{utf32,big},{utf32,little}],
+ [ begin
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L++255,unicode,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary({1,2,3},unicode,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1,unicode,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1.0,unicode,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary('1',unicode,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,apa],unicode,
+ Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,4.0],unicode,
+ Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L++255,latin1,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary({1,2,3},latin1,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1,latin1,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(1.0,latin1,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary('1',latin1,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,apa],latin1,
+ Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary([1,2,3,4.0],latin1,
+ Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,gnarfl,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,L,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,{latin1},Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,[latin1],Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,1,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_binary(L,1.0,Enc))
+ end || Enc <- Encodings ],
+
+
+ Encodings2 = [latin1, unicode, utf8,utf16,utf32,{utf16,big},
+ {utf16,little},{utf32,big},{utf32,little}],
+ [ begin
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(L++255,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list({1,2,3},Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(1,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(1.0,Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list('1',Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list([1,2,3,apa],Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list([1,2,3,4.0],Enc)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(L,{Enc})),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(L,[Enc]))
+ end || Enc <- Encodings2 ],
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(L,gnarfl)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(L,L)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(L,1)),
+ ?line {'EXIT',{badarg,_}} = (catch unicode:characters_to_list(L,1.0)),
+ [ begin
+ ?line Bx = unicode:characters_to_binary(L,latin1, Enc),
+ ?line L = unicode:characters_to_list(Bx,Enc)
+ end || Enc <- Encodings ],
+ ?line B = unicode:characters_to_binary(L,latin1),
+ ?line L = unicode:characters_to_list(B,unicode),
+ ?line L = unicode:characters_to_list(list_to_binary(L),latin1),
+ ?line More = <<B/binary,0,1,2>>,
+ ?line B2 = list_to_binary([254,255]),
+ ?line B3 = list_to_binary([0,1,2,254,255]),
+ ?line {error,B,Rest1} = unicode:characters_to_binary([L,B2],unicode),
+ ?line B2 = iolist_to_binary(Rest1),
+ ?line {error,More,Rest2} = unicode:characters_to_binary([L,B3],unicode),
+ [ begin ?line {error,_,_} = unicode:characters_to_binary([L,B2],unicode,Enc) end
+ || Enc <- Encodings ],
+ ?line Valid0 = unicode:characters_to_binary([L,254,255],unicode),
+ ?line Valid1 = unicode:characters_to_binary([L,254,255],latin1),
+ ?line Valid2 = unicode:characters_to_binary([L,254,255,256,257],unicode),
+ ?line Valid3 = unicode:characters_to_binary([L,B2],latin1),
+ ?line true = is_binary(Valid0),
+ ?line true = is_binary(Valid1),
+ ?line true = is_binary(Valid2),
+ ?line true = is_binary(Valid3),
+ ?line Valid4 = unicode:characters_to_binary([L,B3],latin1),
+ ?line true = is_binary(Valid4),
+ ?line B2 = iolist_to_binary(Rest2),
+ ?line true = (L ++ [254,255] =:= unicode:characters_to_list(Valid0,unicode)),
+ ?line true = (L ++ [254,255,256,257] =:= unicode:characters_to_list(Valid2,unicode)),
+ lists:foreach(fun(Enco) ->
+ ?line Valid0x = unicode:characters_to_binary([L,254,255],unicode,Enco),
+ ?line Valid1x = unicode:characters_to_binary([L,254,255],latin1,Enco),
+ ?line Valid2x = unicode:characters_to_binary([L,254,255,256,257],unicode,Enco),
+ ?line Valid3x = unicode:characters_to_binary([L,B2],latin1,Enco),
+ ?line true = is_binary(Valid0x),
+ ?line true = is_binary(Valid1x),
+ ?line true = is_binary(Valid2x),
+ ?line true = is_binary(Valid3x)
+
+ end, Encodings),
+ ok.
+
+
+latin1(Config) when is_list(Config) ->
+ setlimit(10),
+ ex_latin1(Config),
+ setlimit(default),
+ ex_latin1(Config).
+
+ex_latin1(Config) when is_list(Config) ->
+ ?line All = lists:seq(0,255),
+ ?line AllBin = list_to_binary(All),
+ ?line AllUtf8 = unicode:characters_to_binary(All,latin1),
+ ?line AllUtf8 = unicode:characters_to_binary(AllBin,latin1),
+ ?line AllUtf8 = unicode:characters_to_binary([AllBin],latin1),
+ ?line AllUtf8 = unicode:characters_to_binary(make_unaligned(AllBin),latin1),
+ ?line AllUtf8 = unicode:characters_to_binary([make_unaligned(AllBin)],latin1),
+ ?line AllUtf8 = list_to_utf8_bsyntax([AllBin],latin1),
+ ?line AllUtf8 = list_to_utf8_bsyntax([make_unaligned(AllBin)],latin1),
+ ?line AllUtf8 = unicode_mixed_to_utf8_1(All),
+
+ ?line AllUtf16_Big = unicode:characters_to_binary(All,latin1,utf16),
+ ?line AllUtf16_Big = unicode:characters_to_binary(AllBin,latin1,utf16),
+ ?line AllUtf16_Big = unicode:characters_to_binary([AllBin],latin1,utf16),
+ ?line AllUtf16_Big = unicode:characters_to_binary(make_unaligned(AllBin),latin1,utf16),
+ ?line AllUtf16_Big = unicode:characters_to_binary([make_unaligned(AllBin)],latin1,utf16),
+ ?line AllUtf16_Big = list_to_utf16_big_bsyntax([AllBin],latin1),
+ ?line AllUtf16_Big = list_to_utf16_big_bsyntax([make_unaligned(AllBin)],latin1),
+
+ ?line AllUtf16_Little = unicode:characters_to_binary(All,latin1,{utf16,little}),
+ ?line AllUtf16_Little = unicode:characters_to_binary(AllBin,latin1,{utf16,little}),
+ ?line AllUtf16_Little = unicode:characters_to_binary([AllBin],latin1,{utf16,little}),
+ ?line AllUtf16_Little = unicode:characters_to_binary(make_unaligned(AllBin),latin1,
+ {utf16,little}),
+ ?line AllUtf16_Little = unicode:characters_to_binary([make_unaligned(AllBin)],latin1,
+ {utf16,little}),
+ ?line AllUtf16_Little = list_to_utf16_little_bsyntax([AllBin],latin1),
+ ?line AllUtf16_Little = list_to_utf16_little_bsyntax([make_unaligned(AllBin)],latin1),
+
+ ?line AllUtf32_Big = unicode:characters_to_binary(All,latin1,utf32),
+ ?line AllUtf32_Big = unicode:characters_to_binary(AllBin,latin1,utf32),
+ ?line AllUtf32_Big = unicode:characters_to_binary([AllBin],latin1,utf32),
+ ?line AllUtf32_Big = unicode:characters_to_binary(make_unaligned(AllBin),latin1,utf32),
+ ?line AllUtf32_Big = unicode:characters_to_binary([make_unaligned(AllBin)],latin1,utf32),
+ ?line AllUtf32_Big = list_to_utf32_big_bsyntax([AllBin],latin1),
+ ?line AllUtf32_Big = list_to_utf32_big_bsyntax([make_unaligned(AllBin)],latin1),
+
+ ?line AllUtf32_Little = unicode:characters_to_binary(All,latin1,{utf32,little}),
+ ?line AllUtf32_Little = unicode:characters_to_binary(AllBin,latin1,{utf32,little}),
+ ?line AllUtf32_Little = unicode:characters_to_binary([AllBin],latin1,{utf32,little}),
+ ?line AllUtf32_Little = unicode:characters_to_binary(make_unaligned(AllBin),latin1,
+ {utf32,little}),
+ ?line AllUtf32_Little = unicode:characters_to_binary([make_unaligned(AllBin)],latin1,
+ {utf32,little}),
+ ?line AllUtf32_Little = list_to_utf32_little_bsyntax([AllBin],latin1),
+ ?line AllUtf32_Little = list_to_utf32_little_bsyntax([make_unaligned(AllBin)],latin1),
+
+ ?line DoubleUtf8 = <<AllUtf8/binary,AllUtf8/binary>>,
+ ?line DoubleUtf8 = unicode:characters_to_binary([All,AllBin],latin1),
+ ?line DoubleUtf8 =
+ unicode:characters_to_binary([All,make_unaligned(AllBin)],latin1),
+ ?line DoubleUtf8 = unicode:characters_to_binary([All|AllBin],latin1),
+ ?line DoubleUtf8 =
+ unicode:characters_to_binary([All|make_unaligned(AllBin)],latin1),
+ ?line DoubleUtf8 = unicode:characters_to_binary([AllBin,All],latin1),
+ ?line DoubleUtf8 = unicode:characters_to_binary([AllBin|All],latin1),
+ ?line DoubleUtf8 = list_to_utf8_bsyntax([AllBin|All],latin1),
+
+ ?line DoubleUtf16 = <<AllUtf16_Big/binary,AllUtf16_Big/binary>>,
+ ?line DoubleUtf16 = unicode:characters_to_binary([All,AllBin],latin1,{utf16,big}),
+ ?line DoubleUtf16 =
+ unicode:characters_to_binary([All,make_unaligned(AllBin)],latin1,{utf16,big}),
+ ?line DoubleUtf16 = unicode:characters_to_binary([All|AllBin],latin1,{utf16,big}),
+ ?line DoubleUtf16 =
+ unicode:characters_to_binary([All|make_unaligned(AllBin)],latin1,{utf16,big}),
+ ?line DoubleUtf16 = unicode:characters_to_binary([AllBin,All],latin1,{utf16,big}),
+ ?line DoubleUtf16 = unicode:characters_to_binary([AllBin|All],latin1,{utf16,big}),
+ ?line DoubleUtf16 = list_to_utf16_big_bsyntax([AllBin|All],latin1),
+
+ ?line All = unicode:characters_to_list(AllUtf8,unicode),
+ ?line All = unicode:characters_to_list(make_unaligned(AllUtf8),unicode),
+ ?line All = utf8_to_list_bsyntax(AllUtf8),
+ ?line AllAll = All ++ All,
+ ?line AllAll = unicode:characters_to_list(DoubleUtf8,unicode),
+ ?line AllAll = unicode:characters_to_list(make_unaligned(DoubleUtf8),unicode),
+ ?line AllAll = utf8_to_list_bsyntax(DoubleUtf8),
+ ?line {error,AllUtf8,Rest1} = unicode:characters_to_binary(All++[16#FFF],latin1),
+ ?line [16#FFF] = lists:flatten(Rest1),
+ ?line {error,DoubleUtf8,Rest2} =
+ unicode:characters_to_binary([All,AllBin,16#FFF],latin1),
+ ?line {error,DoubleUtf16,Rest2x} =
+ unicode:characters_to_binary([All,AllBin,16#FFF],latin1,utf16),
+ ?line [16#FFF] = lists:flatten(Rest2),
+ ?line [16#FFF] = lists:flatten(Rest2x),
+ ?line {error,AllUtf8,Rest3} =
+ unicode:characters_to_binary([All,16#FFF,AllBin,16#FFF],
+ latin1),
+ ?line {error,AllUtf8,Rest3} =
+ unicode:characters_to_binary([All,16#FFF,make_unaligned(AllBin),16#FFF],
+ latin1),
+ ?line {error,AllUtf16_Big,Rest3x} =
+ unicode:characters_to_binary([All,16#FFF,AllBin,16#FFF],
+ latin1,{utf16,big}),
+ ?line {error,AllUtf16_Big,Rest3x} =
+ unicode:characters_to_binary([All,16#FFF,make_unaligned(AllBin),16#FFF],
+ latin1,{utf16,big}),
+ ?line [16#FFF,AllBin,16#FFF] = lists:flatten(Rest3),
+ ?line [16#FFF,AllBin,16#FFF] = lists:flatten(Rest3x),
+ ?line DoubleSize = byte_size(DoubleUtf8),
+ ?line AllBut1 = DoubleSize - 1,
+ ?line AllBut2 = DoubleSize - 2,
+ ?line <<MissingLastByte:AllBut1/binary,_>> = DoubleUtf8,
+ ?line <<_:AllBut2/binary,MissingStart:1/binary,_>> = DoubleUtf8,
+ ?line {ChompedList,_} = lists:split(length(AllAll) - 1,AllAll),
+ ?line {incomplete,ChompedList,MissingStart} =
+ unicode:characters_to_list(MissingLastByte,unicode),
+ ?line {incomplete,ChompedList,MissingStart} =
+ unicode:characters_to_list(make_unaligned(MissingLastByte),unicode),
+
+ ?line DoubleSize16 = byte_size(DoubleUtf16),
+ ?line DoubleUtf16_2 = erlang:concat_binary([DoubleUtf16,<<16#FFFFF/utf16-big>>]),
+ ?line DoubleSize16_2 = byte_size(DoubleUtf16_2),
+ ?line AllBut1_16 = DoubleSize16 - 1,
+ ?line AllBut2_16_2 = DoubleSize16_2 - 2,
+ ?line <<MissingLastBytes16:AllBut2_16_2/binary,_,_>> = DoubleUtf16_2,
+ ?line <<MissingLastByte16:AllBut1_16/binary,_>> = DoubleUtf16,
+ ?line {incomplete,AllAll,_} =
+ unicode:characters_to_list(MissingLastBytes16,utf16),
+ ?line {incomplete,AllAll,_} =
+ unicode:characters_to_list(make_unaligned(MissingLastBytes16),utf16),
+ ?line {incomplete,ChompedList,_} =
+ unicode:characters_to_list(MissingLastByte16,utf16),
+ ?line {incomplete,ChompedList,_} =
+ unicode:characters_to_list(make_unaligned(MissingLastByte16),utf16),
+ ok.
+
+roundtrips(Config) when is_list(Config) ->
+ setlimit(10),
+ ex_roundtrips(Config),
+ setlimit(default),
+ ex_roundtrips(Config).
+
+ex_roundtrips(Config) when is_list(Config) ->
+ ?line L1 = ranges(0, 16#D800 - 1,
+ erlang:system_info(context_reductions) * 11),
+ ?line L2 = ranges(16#DFFF + 1, 16#FFFE - 1,
+ erlang:system_info(context_reductions) * 11),
+ %?line L3 = ranges(16#FFFF + 1, 16#10FFFF,
+ % erlang:system_info(context_reductions) * 11),
+ ?line L3 = ranges(16#FFFFF, 16#10FFFF,
+ erlang:system_info(context_reductions) * 11),
+ ?line L = L1 ++ L2 ++ L3,
+ ?line LLen = length(L),
+ ?line Parts = erlang:system_info(schedulers),
+ ?line Lists = splitup(L,LLen,Parts),
+ ?line PidRefs = [spawn_monitor(fun() ->
+ do_roundtrips(MyPart)
+ end) || MyPart <- Lists],
+ ?line [receive {'DOWN',Ref,process,Pid,Reason} -> normal=Reason end ||
+ {Pid,Ref} <- PidRefs],
+ ok.
+
+do_roundtrips([]) ->
+ ok;
+do_roundtrips([{Start,Stop}|T]) ->
+ erlang:display({Start,Stop}),
+ List = lists:seq(Start,Stop),
+ Utf = unicode:characters_to_binary(List,unicode),
+ Utf16_Big = unicode:characters_to_binary(List,unicode,{utf16,big}),
+ Utf16_Little = unicode:characters_to_binary(List,unicode,{utf16,little}),
+ Utf32_Big = unicode:characters_to_binary(List,unicode,{utf32,big}),
+ Utf32_Little = unicode:characters_to_binary(List,unicode,{utf32,little}),
+
+ Utf = unicode:characters_to_binary([Utf],unicode),
+ Utf16_Big = unicode:characters_to_binary([Utf16_Big],{utf16,big},{utf16,big}),
+ Utf16_Little = unicode:characters_to_binary([Utf16_Little],{utf16,little},{utf16,little}),
+ Utf32_Big = unicode:characters_to_binary([Utf32_Big],{utf32,big},{utf32,big}),
+ Utf32_Little = unicode:characters_to_binary([Utf32_Little],{utf32,little},{utf32,little}),
+
+ Utf = list_to_utf8_bsyntax(List,unicode),
+ Utf16_Big = list_to_utf16_big_bsyntax(List,{utf16,big}),
+ Utf16_Little = list_to_utf16_little_bsyntax(List,{utf16,little}),
+ Utf32_Big = list_to_utf32_big_bsyntax(List,{utf32,big}),
+ Utf32_Little = list_to_utf32_little_bsyntax(List,{utf32,little}),
+
+ Utf = unicode_mixed_to_utf8_1(List),
+
+ List = unicode:characters_to_list(Utf,unicode),
+ List = unicode:characters_to_list(Utf16_Big,{utf16,big}),
+ List = unicode:characters_to_list(Utf16_Little,{utf16,little}),
+ List = unicode:characters_to_list(Utf32_Big,{utf32,big}),
+ List = unicode:characters_to_list(Utf32_Little,{utf32,little}),
+ List = utf8_to_list_bsyntax(Utf),
+ List = utf16_big_to_list_bsyntax(Utf16_Big),
+ List = utf16_little_to_list_bsyntax(Utf16_Little),
+ List = utf32_big_to_list_bsyntax(Utf32_Big),
+ List = utf32_little_to_list_bsyntax(Utf32_Little),
+ List = utf8_to_list(Utf),
+ List = utf16_big_to_list(Utf16_Big),
+ List = utf16_little_to_list(Utf16_Little),
+ List = utf32_big_to_list(Utf32_Big),
+ List = utf32_little_to_list(Utf32_Little),
+ do_roundtrips(T).
+
+
+random_lists(Config) when is_list(Config) ->
+ setlimit(10),
+ ex_random_lists(Config),
+ setlimit(default),
+ ex_random_lists(Config).
+ex_random_lists(Config) when is_list(Config) ->
+ PlainFlatten1 = fun(L) ->
+ unicode:characters_to_binary(flat(L),latin1)
+ end,
+ PlainFlatten2 = fun(L) ->
+ unicode:characters_to_binary(L,latin1)
+ end,
+ PlainFlatten3 = fun(L) ->
+ unicode:characters_to_binary(flatb(L),latin1)
+ end,
+ PlainFlatten4 = fun(L) ->
+ iolist_to_binary([int_to_utf8(X) || X <- unicode:characters_to_list(flatb(L),latin1)])
+ end,
+ ?line random_iolist:run(150, PlainFlatten1, PlainFlatten3),
+ ?line random_iolist:run(150, PlainFlatten2, PlainFlatten3),
+ ?line random_iolist:run(150, PlainFlatten1, PlainFlatten2),
+ ?line random_iolist:run(150, PlainFlatten1, PlainFlatten4),
+ SelfMade = fun(L) ->
+ iolist_to_binary(lists:map(fun(X) ->
+ int_to_utf8(X)
+ end,
+ flatb(L)))
+ end,
+ SelfMadeA = fun(L) ->
+ case (catch list_to_utf8_bsyntax(L,latin1)) of
+ {'EXIT', Reason} ->
+ io:format("Exit: ~p (~w)~n",[Reason,L]),
+ exit(Reason);
+ Other ->
+ Other
+ end
+ end,
+ ?line random_iolist:run(150, PlainFlatten1, SelfMade),
+ ?line random_iolist:run(150, PlainFlatten2, SelfMadeA),
+
+ RoundTrip11 = fun(L) ->
+ unicode:characters_to_list(unicode:characters_to_binary(L,latin1),unicode)
+ end,
+ RoundTrip21 = fun(L) ->
+ utf8_to_list_bsyntax(unicode:characters_to_binary(L,latin1))
+ end,
+ RoundTrip31 = fun(L) ->
+ unicode:characters_to_list(list_to_utf8_bsyntax(L,latin1),unicode)
+ end,
+ RoundTrip41 = fun(L) ->
+ utf8_to_list_bsyntax(list_to_utf8_bsyntax(L,latin1))
+ end,
+ RoundTrip51 = fun(L) ->
+ unicode:characters_to_list(L,latin1)
+ end,
+ ?line random_iolist:run(150, RoundTrip11,RoundTrip21),
+ ?line random_iolist:run(150, RoundTrip21,RoundTrip31),
+ ?line random_iolist:run(150, RoundTrip31,RoundTrip41),
+ ?line random_iolist:run(150, RoundTrip11,RoundTrip41),
+ ?line random_iolist:run(150, RoundTrip21,RoundTrip41),
+ ?line random_iolist:run(150, RoundTrip11,RoundTrip31),
+ ?line random_iolist:run(150, RoundTrip11,RoundTrip51),
+
+
+ UniFlatten1 = fun(L) ->
+ unicode:characters_to_binary(flat(L),unicode)
+ end,
+ UniFlatten2 = fun(L) ->
+ unicode:characters_to_binary(L,unicode)
+ end,
+ UniFlatten3 = fun(L) ->
+ unicode:characters_to_binary(flatx(L),unicode)
+ end,
+ UniFlatten4 = fun(L) ->
+ unicode:characters_to_binary(unicode:characters_to_list(L,unicode),unicode)
+ end,
+ ?line random_unicode_list:run(150, UniFlatten1,UniFlatten2),
+ ?line random_unicode_list:run(150, UniFlatten1,UniFlatten3),
+ ?line random_unicode_list:run(150, UniFlatten2,UniFlatten4),
+ ?line random_unicode_list:run(150, UniFlatten2,UniFlatten3),
+
+ ?line Encodings = [utf8,{utf16,big},
+ {utf16,little},{utf32,big},{utf32,little}],
+ lists:foreach(fun(OutEnc1) ->
+ lists:foreach(fun(InEnc1) ->
+ Uni16BigFlatten1 = fun(L) ->
+ unicode:characters_to_binary(flat(L),InEnc1,OutEnc1)
+ end,
+ Uni16BigFlatten2 = fun(L) ->
+ unicode:characters_to_binary(L,InEnc1,OutEnc1)
+ end,
+ Uni16BigFlatten3 = fun(L) ->
+ unicode:characters_to_binary(flatx(L),InEnc1,OutEnc1)
+ end,
+ Uni16BigFlatten4 = fun(L) ->
+ unicode:characters_to_binary(unicode:characters_to_list(L,InEnc1),InEnc1,OutEnc1)
+ end,
+ %erlang:display({InEnc1,OutEnc1}),
+ ?line random_unicode_list:run(150, Uni16BigFlatten1,Uni16BigFlatten2,InEnc1),
+ ?line random_unicode_list:run(150, Uni16BigFlatten1,Uni16BigFlatten3,InEnc1),
+ ?line random_unicode_list:run(150, Uni16BigFlatten2,Uni16BigFlatten4,InEnc1),
+ ?line random_unicode_list:run(150, Uni16BigFlatten2,Uni16BigFlatten3,InEnc1)
+ end, Encodings)
+ end, Encodings),
+ SelfMade1 = fun(L) ->
+ unicode_mixed_to_utf8_1(L)
+ end,
+ SelfMade2 = fun(L) ->
+ unicode_mixed_to_utf8_2(L)
+ end,
+ SelfMade3 = fun(L) ->
+ list_to_utf8_bsyntax(L,unicode)
+ end,
+ ?line random_unicode_list:run(150, SelfMade1,SelfMade2),
+ ?line random_unicode_list:run(150, UniFlatten2, SelfMade1),
+ ?line random_unicode_list:run(150, UniFlatten2, SelfMade2),
+ ?line random_unicode_list:run(150, UniFlatten2, SelfMade3),
+ RoundTrip1 = fun(L) ->
+ unicode:characters_to_list(unicode:characters_to_binary(L,unicode),unicode)
+ end,
+ RoundTrip2 = fun(L) ->
+ utf8_to_list_bsyntax(unicode:characters_to_binary(L,unicode))
+ end,
+ RoundTrip3 = fun(L) ->
+ unicode:characters_to_list(list_to_utf8_bsyntax(L,unicode),unicode)
+ end,
+ RoundTrip4 = fun(L) ->
+ utf8_to_list_bsyntax(list_to_utf8_bsyntax(L,unicode))
+ end,
+ ?line random_unicode_list:run(150, RoundTrip1,RoundTrip2),
+ ?line random_unicode_list:run(150, RoundTrip2,RoundTrip3),
+ ?line random_unicode_list:run(150, RoundTrip3,RoundTrip4),
+ ?line random_unicode_list:run(150, RoundTrip1,RoundTrip4),
+ ?line random_unicode_list:run(150, RoundTrip2,RoundTrip4),
+ ?line random_unicode_list:run(150, RoundTrip1,RoundTrip3),
+ lists:foreach(fun(OutEnc2) ->
+ lists:foreach(fun(InEnc2) ->
+ RoundTripUtf16_Big_1 = fun(L) ->
+ unicode:characters_to_list(unicode:characters_to_binary(L,InEnc2,OutEnc2),OutEnc2)
+ end,
+ RoundTripUtf16_Big_2 = fun(L) ->
+ x_to_list_bsyntax(OutEnc2,unicode:characters_to_binary(L,InEnc2,OutEnc2))
+ end,
+ RoundTripUtf16_Big_3 = fun(L) ->
+ unicode:characters_to_list(list_to_x_bsyntax(InEnc2,L,InEnc2),InEnc2)
+ end,
+ RoundTripUtf16_Big_4 = fun(L) ->
+ x_to_list_bsyntax(InEnc2,list_to_x_bsyntax(InEnc2,L,InEnc2))
+ end,
+ ?line random_unicode_list:run(150, RoundTripUtf16_Big_1,RoundTripUtf16_Big_2,InEnc2),
+ ?line random_unicode_list:run(150, RoundTripUtf16_Big_2,RoundTripUtf16_Big_3,InEnc2),
+ ?line random_unicode_list:run(150, RoundTripUtf16_Big_3,RoundTripUtf16_Big_4,InEnc2),
+ ?line random_unicode_list:run(150, RoundTripUtf16_Big_1,RoundTripUtf16_Big_4,InEnc2),
+ ?line random_unicode_list:run(150, RoundTripUtf16_Big_2,RoundTripUtf16_Big_4,InEnc2),
+ ?line random_unicode_list:run(150, RoundTripUtf16_Big_1,RoundTripUtf16_Big_3,InEnc2)
+ end, Encodings)
+ end, Encodings),
+ ToList1 = fun(L) ->
+ unicode:characters_to_list(L,unicode)
+ end,
+ ToList2 = fun(L) ->
+ unicode:characters_to_list(unicode:characters_to_binary(L,unicode),unicode)
+ end,
+ ToList3 = fun(L) ->
+ unicode:characters_to_list(unicode_mixed_to_utf8_2(L),unicode)
+ end,
+ ToList4 = fun(L) ->
+ utf8_to_list(unicode_mixed_to_utf8_2(L))
+ end,
+ ?line random_unicode_list:run(150, ToList1,ToList2),
+ ?line random_unicode_list:run(150, ToList2,ToList3),
+ ?line random_unicode_list:run(150, ToList3,ToList4),
+ ?line random_unicode_list:run(150, ToList1,ToList4),
+ ?line random_unicode_list:run(150, ToList2,ToList4),
+ ?line random_unicode_list:run(150, ToList1,ToList3),
+
+ ok.
+
+utf16_illegal_sequences_bif(Config) when is_list(Config) ->
+ setlimit(10),
+ ex_utf16_illegal_sequences_bif(Config),
+ setlimit(default),
+ ex_utf16_illegal_sequences_bif(Config).
+
+ex_utf16_illegal_sequences_bif(Config) when is_list(Config) ->
+ ?line utf16_fail_range_bif_simple(16#10FFFF+1, 16#10FFFF+512), %Too large.
+ ?line utf16_fail_range_bif(16#D800, 16#DFFF), %Reserved for UTF-16.
+ ?line utf16_fail_range_bif(16#FFFE, 16#FFFF), %Non-characters.
+
+ ?line lonely_hi_surrogate_bif(16#D800, 16#DBFF,incomplete),
+ ?line lonely_hi_surrogate_bif(16#DC00, 16#DFFF,error),
+ ?line leading_lo_surrogate_bif(16#DC00, 16#DFFF),
+
+ ok.
+
+utf16_fail_range_bif(Char, End) when Char =< End ->
+ {error,_,_} = unicode:characters_to_binary([Char],{utf16,big}),
+ BigBin = int_to_utf16_big(Char),
+ fail_bif(BigBin,{utf16,big}),
+ {error,_,_} = unicode:characters_to_binary([Char],{utf16,little}),
+ LittleBin = int_to_utf16_little(Char),
+ fail_bif(LittleBin,{utf16,little}),
+ utf16_fail_range_bif(Char+1, End);
+utf16_fail_range_bif(_, _) -> ok.
+
+utf16_fail_range_bif_simple(Char, End) when Char =< End ->
+ {error,_,_} = unicode:characters_to_binary([Char],{utf16,big}),
+ {error,_,_} = unicode:characters_to_binary([Char],{utf16,little}),
+ utf16_fail_range_bif_simple(Char+1, End);
+utf16_fail_range_bif_simple(_, _) -> ok.
+
+
+lonely_hi_surrogate_bif(Char, End, EType) when Char =< End ->
+ BinBig = <<Char:16/big>>,
+ BinLittle = <<Char:16/little>>,
+ case unicode:characters_to_binary(BinBig,{utf16,big}) of
+ {EType,_,_} ->
+ ok;
+ Other ->
+ exit({lonely_hi_surrogate_accepted,BinBig,{utf16,big},Other})
+ end,
+ case unicode:characters_to_binary(BinLittle,{utf16,little}) of
+ {EType,_,_} ->
+ ok;
+ Other2 ->
+ exit({lonely_hi_surrogate_accepted,BinLittle,{utf16,little},Other2})
+ end,
+ lonely_hi_surrogate_bif(Char+1, End, EType);
+lonely_hi_surrogate_bif(_, _, _) -> ok.
+
+leading_lo_surrogate_bif(Char, End) when Char =< End ->
+ leading_lo_surrogate_bif(Char, 16#D800, 16#DFFF),
+ leading_lo_surrogate_bif(Char+1, End);
+leading_lo_surrogate_bif(_, _) -> ok.
+
+leading_lo_surrogate_bif(HiSurr, LoSurr, End) when LoSurr =< End ->
+ BinBig = <<HiSurr:16/big,LoSurr:16/big>>,
+ BinLittle = <<HiSurr:16/little,LoSurr:16/little>>,
+ case unicode:characters_to_binary(BinBig,{utf16,big}) of
+ {error,_,_} ->
+ ok;
+ Other ->
+ exit({leading_lo_surrogate_accepted,BinBig,{utf16,big},Other})
+ end,
+ case unicode:characters_to_binary(BinLittle,{utf16,little}) of
+ {error,_,_} ->
+ ok;
+ Other2 ->
+ exit({leading_lo_surrogate_accepted,BinLittle,{utf16,little},Other2})
+ end,
+ leading_lo_surrogate_bif(HiSurr, LoSurr+1, End);
+leading_lo_surrogate_bif(_, _, _) -> ok.
+
+utf8_illegal_sequences_bif(Config) when is_list(Config) ->
+ setlimit(10),
+ ex_utf8_illegal_sequences_bif(Config),
+ setlimit(default),
+ ex_utf8_illegal_sequences_bif(Config).
+
+ex_utf8_illegal_sequences_bif(Config) when is_list(Config) ->
+ ?line fail_range_bif(16#10FFFF+1, 16#10FFFF+512), %Too large.
+ ?line fail_range_bif(16#D800, 16#DFFF), %Reserved for UTF-16.
+ ?line fail_range_bif(16#FFFE, 16#FFFF), %Reserved (BOM).
+
+ %% Illegal first character.
+ ?line [fail_bif(<<I,16#8F,16#8F,16#8F>>,unicode) || I <- lists:seq(16#80, 16#BF)],
+
+ %% Short sequences.
+ ?line short_sequences_bif(16#80, 16#10FFFF),
+
+ %% Overlong sequences. (Using more bytes than necessary
+ %% is not allowed.)
+ ?line overlong_bif(0, 127, 2),
+ ?line overlong_bif(128, 16#7FF, 3),
+ ?line overlong_bif(16#800, 16#FFFF, 4),
+ ok.
+
+fail_range_bif(Char, End) when Char =< End ->
+ {error,_,_} = unicode:characters_to_binary([Char],unicode),
+ {error,_,_} = unicode:characters_to_binary([Char],unicode,utf16),
+ {error,_,_} = unicode:characters_to_binary([Char],unicode,utf32),
+ Bin = int_to_utf8(Char),
+ fail_bif(Bin,unicode),
+ fail_range_bif(Char+1, End);
+fail_range_bif(_, _) -> ok.
+
+short_sequences_bif(Char, End) ->
+ Step = (End - Char) div erlang:system_info(schedulers) + 1,
+% Step = (End - Char) + 1,
+ PidRefs = short_sequences_bif_1(Char, Step, End),
+ [receive {'DOWN',Ref,process,Pid,Reason} -> normal=Reason end ||
+ {Pid,Ref} <- PidRefs],
+ ok.
+
+short_sequences_bif_1(Char, Step, End) when Char =< End ->
+ CharEnd = lists:min([Char+Step-1,End]),
+ [spawn_monitor(fun() ->
+ io:format("~p - ~p\n", [Char,CharEnd]),
+ do_short_sequences_bif(Char, CharEnd)
+ end)|short_sequences_bif_1(Char+Step, Step, End)];
+short_sequences_bif_1(_, _, _) -> [].
+
+do_short_sequences_bif(Char, End) when Char =< End ->
+ short_sequence_bif(Char),
+ do_short_sequences_bif(Char+1, End);
+do_short_sequences_bif(_, _) -> ok.
+
+short_sequence_bif(I) ->
+ case int_to_utf8(I) of
+ <<S0:3/binary,_:8>> ->
+ <<S1:2/binary,R1:8>> = S0,
+ <<S2:1/binary,_:8>> = S1,
+ incomplete_bif(S0,S0),
+ incomplete_bif(S1,S1),
+ incomplete_bif(S2,S2),
+ only_fail_bif(<<S2/binary,16#7F,R1,R1>>,unicode),
+ only_fail_bif(<<S1/binary,16#7F,R1>>,unicode),
+ only_fail_bif(<<S0/binary,16#7F>>,unicode);
+ <<S0:2/binary,_:8>> ->
+ <<S1:1/binary,R1:8>> = S0,
+ incomplete_bif(S0,S0),
+ incomplete_bif(S1,S1),
+ only_fail_bif(<<S0/binary,16#7F>>,unicode),
+ only_fail_bif(<<S1/binary,16#7F>>,unicode),
+ only_fail_bif(<<S1/binary,16#7F,R1>>,unicode);
+ <<S:1/binary,_:8>> ->
+ incomplete_bif(S,S),
+ only_fail_bif(<<S/binary,16#7F>>,unicode)
+ end.
+
+
+overlong_bif(Char, Last, NumBytes) when Char =< Last ->
+ overlong_bif(Char, NumBytes),
+ overlong_bif(Char+1, Last, NumBytes);
+overlong_bif(_, _, _) -> ok.
+
+overlong_bif(Char, NumBytes) when NumBytes < 5 ->
+ case unicode:characters_to_binary([int_to_utf8(Char, NumBytes)],unicode) of
+ {error,_,_} ->
+ ok;
+ Other->
+ exit({illegal_encoding_accepted,Char,NumBytes,Other})
+ end,
+ overlong_bif(Char, NumBytes+1);
+overlong_bif(_, _) -> ok.
+
+incomplete_bif(Bin,Tail) ->
+ incomplete_bif_1(Bin,Tail),
+ incomplete_bif_1(make_unaligned(Bin),Tail),
+ incomplete_bif_r_1(Bin,Tail),
+ incomplete_bif_r_1(make_unaligned(Bin),Tail),
+ ok.
+
+incomplete_bif_1(Bin,Tail) ->
+ case unicode:characters_to_binary([Bin],unicode) of
+ {incomplete,_,Tail} ->
+ case unicode:characters_to_binary(Bin,unicode) of
+ {incomplete,_,Tail} ->
+ ok;
+ Other0 ->
+ exit({incomplete_encoding_accepted,Bin,Other0})
+ end;
+ Other ->
+ exit({incomplete_encoding_accepted,[Bin],Other})
+ end.
+incomplete_bif_r_1(Bin,Tail) ->
+ case unicode:characters_to_list([Bin],unicode) of
+ {incomplete,_,Tail} ->
+ case unicode:characters_to_list(Bin,unicode) of
+ {incomplete,_,Tail} ->
+ ok;
+ Other ->
+ exit({incomplete_encoding_accepted_r,[Bin],Other})
+ end;
+ Other ->
+ exit({incomplete_encoding_accepted_r,[Bin],Other})
+ end.
+
+only_fail_bif(Bin,Coding) ->
+ only_fail_bif_1(Bin,Coding),
+ only_fail_bif_1(make_unaligned(Bin),Coding),
+ only_fail_bif_r_1(Bin,Coding),
+ only_fail_bif_r_1(make_unaligned(Bin),Coding),
+ ok.
+
+only_fail_bif_r_1(Bin,Coding) ->
+ case unicode:characters_to_list([Bin],Coding) of
+ {error,_,_} ->
+ case unicode:characters_to_list(Bin,Coding) of
+ {error,_,_} ->
+ ok;
+ Other ->
+ exit({faulty_encoding_accepted_r,Bin,Coding,Other})
+ end;
+ Other ->
+ exit({faulty_encoding_accepted_r,Bin,Coding,Other})
+ end.
+only_fail_bif_1(Bin,Coding) ->
+ case unicode:characters_to_binary([Bin],Coding) of
+ {error,_,_} ->
+ case unicode:characters_to_binary(Bin,Coding) of
+ {error,_,_} ->
+ ok;
+ Other0 ->
+ exit({faulty_encoding_accepted,Bin,Coding,Other0})
+ end;
+ Other ->
+ exit({faulty_encoding_accepted,[Bin],Coding,Other})
+ end.
+
+
+
+
+fail_bif(Bin,Coding) ->
+ fail_bif_1(Bin,Coding),
+ fail_bif_1(make_unaligned(Bin),Coding),
+ fail_bif_r_1(Bin,Coding),
+ fail_bif_r_1(make_unaligned(Bin),Coding),
+ ok.
+fail_bif_r_1(Bin,Coding) ->
+ case unicode:characters_to_list(Bin,Coding) of
+ L when is_list(L) ->
+ exit({illegal_encoding_accepted,Bin,Coding});
+ _ ->
+ ok
+ end.
+
+fail_bif_1(Bin,Coding) ->
+ case unicode:characters_to_binary([Bin],Coding) of
+ Bin2 when is_binary(Bin2) ->
+ exit({illegal_encoding_accepted,Bin,Coding});
+ _ ->
+ ok
+ end.
+
+%%
+%% Diverse utilities
+%%
+
+ranges(X,Y,_N) when X >= Y ->
+ [];
+ranges(X,Y,N) when X + N > Y ->
+ [{X,Y}];
+ranges(X,Y,N) ->
+ Upper = X+N,
+ [{X,Upper}|ranges(Upper+1,Y,N)].
+
+splitup(L,_Len,1) ->
+ [L];
+splitup(L,Len,Parts) ->
+ Num = Len div Parts,
+ {A,B} = lists:split(Num,L),
+ [A| splitup(B,Len - Num,Parts - 1)].
+
+flat(List) ->
+ lists:reverse(flat(List,[])).
+
+flat([H|T],Acc) ->
+ NewAcc = flat(H,Acc),
+ flat(T,NewAcc);
+flat([],Acc) ->
+ Acc;
+flat(X,Acc) ->
+ [X|Acc].
+
+flatb(List) ->
+ lists:reverse(flatb(List,[])).
+
+flatb(<<X:8,Rest/binary>>,Acc) ->
+ flatb(Rest,[X|Acc]);
+flatb(<<>>,Acc) ->
+ Acc;
+flatb([H|T],Acc) ->
+ NewAcc = flatb(H,Acc),
+ flatb(T,NewAcc);
+flatb([],Acc) ->
+ Acc;
+flatb(X,Acc) ->
+ [X|Acc].
+flatx(List) ->
+ lists:reverse(flatx(List,[])).
+
+flatx([B1,B2|T],Acc) when is_binary(B1), is_binary(B2) ->
+ flatx([<<B1/binary,B2/binary>>|T],Acc);
+flatx([H|T],Acc) ->
+ NewAcc = flatx(H,Acc),
+ flatx(T,NewAcc);
+flatx([],Acc) ->
+ Acc;
+flatx(X,Acc) ->
+ [X|Acc].
+
+
+unicode_mixed_to_utf8_1(L) ->
+ Flist = flatx([L]),
+ ExpList = [ case is_binary(E) of
+ true ->
+ utf8_to_list(E);
+ false ->
+ E
+ end || E <- Flist ],
+ iolist_to_binary([int_to_utf8(I) || I <- flat(ExpList)]).
+
+unicode_mixed_to_utf8_2(L) ->
+ Flist = flatx([L]),
+ ExpList = [ case is_binary(E) of
+ true ->
+ E;
+ false ->
+ int_to_utf8(E)
+ end || E <- Flist ],
+ iolist_to_binary([ExpList]).
+
+
+
+
+utf8_to_list_bsyntax(<<>>) ->
+ [];
+utf8_to_list_bsyntax(<<C/utf8,R/binary>>) ->
+ [C|utf8_to_list_bsyntax(R)].
+
+list_to_utf8_bsyntax(List,unicode) ->
+ FList = flatx(List),
+ erlang:concat_binary([ if
+ is_binary(E) ->
+ E;
+ true ->
+ <<E/utf8>>
+ end || E <- FList ]);
+list_to_utf8_bsyntax(List,latin1) ->
+ FList = flatb(List),
+ erlang:concat_binary([ <<E/utf8>> || E <- FList ]).
+
+
+
+
+
+%%
+%% Conversion utilities
+%%
+
+int_to_utf16_big(U) when U < 16#10000 ->
+ <<U:16/big>>;
+int_to_utf16_big(U) when U >= 16#10000, U =< 16#10FFFF ->
+ UPrim = U - 16#10000,
+ HI = (16#D800 bor (UPrim bsr 10)),
+ LO = (16#DC00 bor (UPrim band 16#3FF)),
+ <<HI:16/big,LO:16/big>>.
+
+int_to_utf16_little(U) when U < 16#10000 ->
+ <<U:16/little>>;
+int_to_utf16_little(U) when U >= 16#10000, U =< 16#10FFFF ->
+ UPrim = U - 16#10000,
+ HI = (16#D800 bor (UPrim bsr 10)),
+ LO = (16#DC00 bor (UPrim band 16#3FF)),
+ <<HI:16/little,LO:16/little>>.
+
+
+%% This function intentionally allows construction of
+%% UTF-8 sequence in illegal ranges.
+int_to_utf8(I) when I =< 16#7F ->
+ <<I>>;
+int_to_utf8(I) when I =< 16#7FF ->
+ B2 = I,
+ B1 = (I bsr 6),
+ <<1:1,1:1,0:1,B1:5,1:1,0:1,B2:6>>;
+int_to_utf8(I) when I =< 16#FFFF ->
+ B3 = I,
+ B2 = (I bsr 6),
+ B1 = (I bsr 12),
+ <<1:1,1:1,1:1,0:1,B1:4,1:1,0:1,B2:6,1:1,0:1,B3:6>>;
+int_to_utf8(I) when I =< 16#3FFFFF ->
+ B4 = I,
+ B3 = (I bsr 6),
+ B2 = (I bsr 12),
+ B1 = (I bsr 18),
+ <<1:1,1:1,1:1,1:1,0:1,B1:3,1:1,0:1,B2:6,1:1,0:1,B3:6,1:1,0:1,B4:6>>;
+int_to_utf8(I) when I =< 16#3FFFFFF ->
+ B5 = I,
+ B4 = (I bsr 6),
+ B3 = (I bsr 12),
+ B2 = (I bsr 18),
+ B1 = (I bsr 24),
+ <<1:1,1:1,1:1,1:1,1:1,0:1,B1:2,1:1,0:1,B2:6,1:1,0:1,B3:6,1:1,0:1,B4:6,
+ 1:1,0:1,B5:6>>.
+
+utf16_big_to_list_bsyntax(<<>>) ->
+ [];
+utf16_big_to_list_bsyntax(<<C/utf16-big,R/binary>>) ->
+ [C|utf16_big_to_list_bsyntax(R)].
+
+list_to_utf16_big_bsyntax(List,{utf16,big}) ->
+ FList = flatx(List),
+ erlang:concat_binary([ if
+ is_binary(E) ->
+ E;
+ true ->
+ <<E/utf16-big>>
+ end || E <- FList ]);
+list_to_utf16_big_bsyntax(List,latin1) ->
+ FList = flatb(List),
+ erlang:concat_binary([ <<E/utf16-big>> || E <- FList ]).
+
+
+utf16_little_to_list_bsyntax(<<>>) ->
+ [];
+utf16_little_to_list_bsyntax(<<C/utf16-little,R/binary>>) ->
+ [C|utf16_little_to_list_bsyntax(R)].
+
+list_to_utf16_little_bsyntax(List,{utf16,little}) ->
+ FList = flatx(List),
+ erlang:concat_binary([ if
+ is_binary(E) ->
+ E;
+ true ->
+ <<E/utf16-little>>
+ end || E <- FList ]);
+list_to_utf16_little_bsyntax(List,latin1) ->
+ FList = flatb(List),
+ erlang:concat_binary([ <<E/utf16-little>> || E <- FList ]).
+
+
+
+utf32_big_to_list_bsyntax(<<>>) ->
+ [];
+utf32_big_to_list_bsyntax(<<C/utf32-big,R/binary>>) ->
+ [C|utf32_big_to_list_bsyntax(R)].
+
+list_to_utf32_big_bsyntax(List,{utf32,big}) ->
+ FList = flatx(List),
+ erlang:concat_binary([ if
+ is_binary(E) ->
+ E;
+ true ->
+ <<E/utf32-big>>
+ end || E <- FList ]);
+list_to_utf32_big_bsyntax(List,latin1) ->
+ FList = flatb(List),
+ erlang:concat_binary([ <<E/utf32-big>> || E <- FList ]).
+
+
+utf32_little_to_list_bsyntax(<<>>) ->
+ [];
+utf32_little_to_list_bsyntax(<<C/utf32-little,R/binary>>) ->
+ [C|utf32_little_to_list_bsyntax(R)].
+
+list_to_utf32_little_bsyntax(List,{utf32,little}) ->
+ FList = flatx(List),
+ erlang:concat_binary([ if
+ is_binary(E) ->
+ E;
+ true ->
+ <<E/utf32-little>>
+ end || E <- FList ]);
+list_to_utf32_little_bsyntax(List,latin1) ->
+ FList = flatb(List),
+ erlang:concat_binary([ <<E/utf32-little>> || E <- FList ]).
+
+
+
+%% int_to_utf8(I, NumberOfBytes) -> Binary.
+%% This function can be used to construct overlong sequences.
+int_to_utf8(I, 1) ->
+ <<I>>;
+int_to_utf8(I, 2) ->
+ B2 = I,
+ B1 = (I bsr 6),
+ <<1:1,1:1,0:1,B1:5,1:1,0:1,B2:6>>;
+int_to_utf8(I, 3) ->
+ B3 = I,
+ B2 = (I bsr 6),
+ B1 = (I bsr 12),
+ <<1:1,1:1,1:1,0:1,B1:4,1:1,0:1,B2:6,1:1,0:1,B3:6>>;
+int_to_utf8(I, 4) ->
+ B4 = I,
+ B3 = (I bsr 6),
+ B2 = (I bsr 12),
+ B1 = (I bsr 18),
+ <<1:1,1:1,1:1,1:1,0:1,B1:3,1:1,0:1,B2:6,1:1,0:1,B3:6,1:1,0:1,B4:6>>.
+
+utf8_to_list(<<>>) ->
+ [];
+utf8_to_list(Bin) ->
+ N = utf8_siz(Bin),
+ <<X:N/binary,Rest/binary>> = Bin,
+ [utf8_to_int(X) | utf8_to_list(Rest)].
+utf8_siz(<<0:1,_:7,_/binary>>) ->
+ 1;
+utf8_siz(<<1:1,1:1,0:1,_:5,_/binary>>) ->
+ 2;
+utf8_siz(<<1:1,1:1,1:1,0:1,_:4,_/binary>>) ->
+ 3;
+utf8_siz(<<1:1,1:1,1:1,1:1,0:1,_:3,_/binary>>) ->
+ 4.
+
+utf8_to_int(<<0:1,B:7>>) ->
+ B;
+utf8_to_int(<<1:1,1:1,0:1,B1:5,1:1,0:1,B2:6>>) ->
+ (B1 bsl 6) bor B2;
+utf8_to_int(<<1:1,1:1,1:1,0:1,B1:4,1:1,0:1,B2:6,1:1,0:1,B3:6>>) ->
+ (B1 bsl 12) bor (B2 bsl 6) bor B3;
+utf8_to_int(<<1:1,1:1,1:1,1:1,0:1,B1:3,1:1,0:1,
+ B2:6,1:1,0:1,B3:6,1:1,0:1,B4:6>>) ->
+ Res = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B4,
+ case Res of
+ X when X > 16#10FFFF ->
+ exit(unsupported_utf8);
+ Other ->
+ Other
+ end;
+utf8_to_int(_) ->
+ exit(unsupported_utf8).
+
+
+utf16_big_to_list(<<>>) ->
+ [];
+utf16_big_to_list(Bin) ->
+ N = utf16_big_siz(Bin),
+ <<X:N/binary,Rest/binary>> = Bin,
+ [utf16_big_to_int(X) | utf16_big_to_list(Rest)].
+
+utf16_big_siz(<<1:1,1:1,0:1,1:1,1:1,0:1,_:1,_:1,_/binary>>) ->
+ 4;
+utf16_big_siz(_) ->
+ 2.
+
+utf16_big_to_int(<<1:1,1:1,0:1,1:1,1:1,0:1,W1:10,1:1,1:1,0:1,1:1,1:1,1:1,W2:10>>) ->
+ ((W1 bsl 10) bor W2) + 16#10000;
+utf16_big_to_int(<<W:16>>) ->
+ W;
+utf16_big_to_int(_) ->
+ exit(unsupported_utf16_big).
+
+utf16_little_to_list(<<>>) ->
+ [];
+utf16_little_to_list(Bin) ->
+ N = utf16_little_siz(Bin),
+ <<X:N/binary,Rest/binary>> = Bin,
+ [utf16_little_to_int(X) | utf16_little_to_list(Rest)].
+utf16_little_siz(<<_:8,1:1,1:1,0:1,1:1,1:1,0:1,_:1,_:1,_/binary>>) ->
+ 4;
+utf16_little_siz(_) ->
+ 2.
+
+utf16_little_to_int(<<W1B:8,1:1,1:1,0:1,1:1,1:1,0:1,W1A:2,W2B:8,1:1,1:1,0:1,1:1,1:1,1:1,W2A:2>>) ->
+ W1 = (W1A bsl 8) bor W1B,
+ W2 = (W2A bsl 8) bor W2B,
+ ((W1 bsl 10) bor W2) + 16#10000;
+utf16_little_to_int(<<W:16/little>>) ->
+ W;
+utf16_little_to_int(_) ->
+ exit(unsupported_utf16_little).
+
+utf32_big_to_list(<<>>) ->
+ [];
+utf32_big_to_list(<<I:32,Rest/binary>>) ->
+ [ I | utf32_big_to_list(Rest)].
+utf32_little_to_list(<<>>) ->
+ [];
+utf32_little_to_list(<<I:32/little,Rest/binary>>) ->
+ [ I | utf32_little_to_list(Rest)].
+
+
+x_to_list_bsyntax(utf8,Bin) ->
+ utf8_to_list_bsyntax(Bin);
+x_to_list_bsyntax({utf16,big},Bin) ->
+ utf16_big_to_list_bsyntax(Bin);
+x_to_list_bsyntax({utf16,little},Bin) ->
+ utf16_little_to_list_bsyntax(Bin);
+x_to_list_bsyntax({utf32,big},Bin) ->
+ utf32_big_to_list_bsyntax(Bin);
+x_to_list_bsyntax({utf32,little},Bin) ->
+ utf32_little_to_list_bsyntax(Bin).
+
+list_to_x_bsyntax(utf8,L,utf8) ->
+ list_to_utf8_bsyntax(L,unicode);
+list_to_x_bsyntax(utf8,L,Enc) ->
+ list_to_utf8_bsyntax(L,Enc);
+list_to_x_bsyntax({utf16,big},L,Enc) ->
+ list_to_utf16_big_bsyntax(L,Enc);
+list_to_x_bsyntax({utf16,little},L,Enc) ->
+ list_to_utf16_little_bsyntax(L,Enc);
+list_to_x_bsyntax({utf32,big},L,Enc) ->
+ list_to_utf32_big_bsyntax(L,Enc);
+list_to_x_bsyntax({utf32,little},L,Enc) ->
+ list_to_utf32_little_bsyntax(L,Enc).
+
+
+make_unaligned(Bin0) when is_binary(Bin0) ->
+% put(c_count,get(c_count)+1),
+ Bin1 = <<0:3,Bin0/binary,31:5>>,
+ Sz = byte_size(Bin0),
+ <<0:3,Bin:Sz/binary,31:5>> = id(Bin1),
+ Bin.
+
+id(I) -> I.
+
+setlimit(X) ->
+ erts_debug:set_internal_state(available_internal_state,true),
+ io:format("Setting loop limit, old: ~p, now set to ~p~n",
+ [erts_debug:set_internal_state(unicode_loop_limit,X),X]).
+
+
+%%
+%% Tracing utility
+%%
+
+%% tr_dump() ->
+%% erlang:display(lists:sort(ets:tab2list(values))).
+
+%% tr_off(Pid) ->
+%% receive after 10000 -> ok end,
+%% tr_dump(),
+%% Ref = erlang:monitor(process,Pid),
+%% exit(Pid,kill),
+%% receive
+%% {'DOWN',Ref,_,_,_} -> ok
+%% end,
+%% ok.
+
+%% tr_on() ->
+%% catch ets:delete(values),
+%% ets:new(values,[named_table,public]),
+%% ets:insert(values,{traps,0}),
+%% catch ets:delete(state),
+%% ets:new(state,[named_table,public]),
+%% Pid = spawn(?MODULE,trace_recv,[values,state]),
+%% erlang:trace(new,true,[garbage_collection,{tracer,Pid},timestamp,call]),
+%% erlang:trace_pattern({erlang,list_to_utf8,2},[{'_',[],[{return_trace}]}],[global]),
+%% Pid.
+
+%% ts_to_int({Mega,Sec,Micro}) ->
+%% ((Mega * 1000000) + Sec) * 1000000 + Micro.
+
+%% trace_recv(Values,State) ->
+%% receive
+%% {trace_ts,Pid,call,_,TS} ->
+%% case ets:lookup(State,{call,Pid}) of
+%% [{{call,Pid},_}] ->
+%% ets:update_counter(values,traps,1);
+%% _ ->
+%% ok
+%% end,
+%% ets:insert(State,{{call,Pid},ts_to_int(TS)});
+%% {trace_ts,Pid,return_from,_,_,TS} ->
+%% case ets:lookup(State,{call,Pid}) of
+%% [{{call,Pid},TS2}] ->
+%% ets:delete(State,{call,Pid}),
+%% Elapsed = ts_to_int(TS) - TS2,
+%% case ets:lookup(Values,Pid) of
+%% [{Pid,GCNum,CallNum,GCTime,CallTime}] ->
+%% ets:insert(Values,{Pid,GCNum,CallNum+1,GCTime,CallTime+Elapsed});
+%% [] ->
+%% ets:insert(Values,{Pid,0,1,0,Elapsed})
+%% end;
+%% _Other ->
+%% erlang:display({what2,Pid})
+%% end;
+%% {trace_ts,Pid,gc_start,_,TS} ->
+%% ets:insert(State,{{gc,Pid},ts_to_int(TS)});
+%% {trace_ts,Pid,gc_end,_,TS} ->
+%% case ets:lookup(State,{gc,Pid}) of
+%% [{{gc,Pid},TS2}] ->
+%% ets:delete(State,{gc,Pid}),
+%% Elapsed = ts_to_int(TS) - TS2,
+%% case ets:lookup(Values,Pid) of
+%% [{Pid,Num,CNum,Time,CTime}] ->
+%% ets:insert(Values,{Pid,Num+1,CNum,Time+Elapsed,CTime});
+%% [] ->
+%% ets:insert(Values,{Pid,1,0,Elapsed,0})
+%% end;
+%% _Other ->
+%% erlang:display({what,Pid})
+%% end;
+%% X ->
+%% erlang:display({trace_recv,X})
+%% end,
+%% trace_recv(Values,State).
diff --git a/lib/stdlib/test/win32reg_SUITE.erl b/lib/stdlib/test/win32reg_SUITE.erl
new file mode 100644
index 0000000000..3ad58eba03
--- /dev/null
+++ b/lib/stdlib/test/win32reg_SUITE.erl
@@ -0,0 +1,89 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(win32reg_SUITE).
+
+-export([all/1,long/1,evil_write/1]).
+-export([ostype/1,fini/1]).
+
+-include("test_server.hrl").
+
+all(suite) ->
+ [{conf,ostype,[long,evil_write],fini}].
+
+ostype(Config) when is_list(Config) ->
+ case os:type() of
+ {win32, _} ->
+ Config;
+ _ ->
+ {skip,"Doesn't run on UNIX."}
+ end.
+fini(Config) when is_list(Config) ->
+ Config.
+
+long(doc) -> "Test long keys and entries (OTP-3446).";
+long(Config) when list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(10)),
+
+ ?line LongKey = "software\\" ++
+ lists:flatten(lists:duplicate(10, "..\\software\\")) ++
+ "Ericsson\\Erlang",
+ ?line {ok,Reg} = win32reg:open([read,write]),
+ ?line ok = win32reg:change_key(Reg, "\\hklm"),
+ ?line ok = win32reg:change_key(Reg, LongKey),
+ ?line {ok,ErlangKey} = win32reg:current_key(Reg),
+ io:format("Erlang key: ~s", [ErlangKey]),
+
+ %% Write a long value and read it back.
+ ?line TestKey = "test_key",
+ ?line LongValue = lists:concat(["This is a long value generated by the test case ",?MODULE,":long/1. "|lists:duplicate(128, "a")]),
+ ?line ok = win32reg:set_value(Reg, TestKey, LongValue),
+ ?line {ok,LongValue} = win32reg:value(Reg, TestKey),
+
+ %% Done.
+
+ ?line ok = win32reg:close(Reg),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+evil_write(Config) when list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(10)),
+
+ ?line Key = "Software\\Ericsson\\Erlang",
+ ?line {ok,Reg} = win32reg:open([read,write]),
+ ?line ok = win32reg:change_key(Reg, "\\hklm"),
+ ?line ok = win32reg:change_key(Reg, Key),
+ ?line {ok,ErlangKey} = win32reg:current_key(Reg),
+ io:format("Erlang key: ~s", [ErlangKey]),
+
+ %% Write keys with different length and read it back.
+ ?line TestKey = "test_key " ++ lists:duplicate(128, $a),
+ evil_write_1(Reg, TestKey),
+
+ %% Done.
+ ?line ok = win32reg:close(Reg),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+evil_write_1(Reg, [_|[_|_]=Key]=Key0) ->
+ ?line io:format("Key = ~p\n", [Key0]),
+ ?line ok = win32reg:set_value(Reg, Key0, "A good value for me"),
+ ?line {ok,_Val} = win32reg:value(Reg, Key0),
+ ?line ok = win32reg:delete_value(Reg, Key0),
+ evil_write_1(Reg, Key);
+evil_write_1(_, [_]) -> ok.
diff --git a/lib/stdlib/test/y2k_SUITE.erl b/lib/stdlib/test/y2k_SUITE.erl
new file mode 100644
index 0000000000..a574d5e36e
--- /dev/null
+++ b/lib/stdlib/test/y2k_SUITE.erl
@@ -0,0 +1,185 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% File: y2k_SUITE.erl
+%% Purpose: Year 2000 tests.
+
+-module(y2k_SUITE).
+
+-include("test_server.hrl").
+
+-export([all/1,
+ date_1999_01_01/1, date_1999_02_28/1,
+ date_1999_09_09/1, date_2000_01_01/1,
+ date_2000_02_29/1, date_2001_01_01/1,
+ date_2001_02_29/1, date_2004_02_29/1
+ ]).
+
+all(doc) ->
+ "This is the test suite for year 2000. Eight dates according "
+ "to Ericsson Corporate Millennium Test Specification "
+ "(LME/DT-98:1097 are tested.";
+
+all(suite) ->
+ [date_1999_01_01,
+ date_1999_02_28,
+ date_1999_09_09,
+ date_2000_01_01,
+ date_2000_02_29,
+ date_2001_01_01,
+ date_2001_02_29,
+ date_2004_02_29
+ ].
+
+date_1999_01_01(doc) ->
+ "#1 : 1999-01-01: test roll-over from 1998-12-31 to 1999-01-01.";
+date_1999_01_01(suite) ->
+ [];
+date_1999_01_01(Config) when is_list(Config) ->
+ ?line Date = {1998, 12, 31}, NextDate = {1999, 1, 1},
+ ?line match(next_date(Date), NextDate),
+ TZD = tzd(Date),
+ if
+ TZD > 0 ->
+ ?line Time = {24 - TZD, 0, 0},
+ ?line {NDate, _NTime} =
+ erlang:localtime_to_universaltime({Date, Time}),
+ ?line match(NDate, NextDate);
+ TZD < 0 ->
+ ?line Time = {24 + TZD, 0, 0},
+ ?line {NDate, _NTime} =
+ erlang:universaltime_to_localtime({Date, Time}),
+ ?line match(NDate, NextDate);
+ true ->
+ ok
+ end.
+
+date_1999_02_28(doc) ->
+ "#2 : 1999-02-28: test roll-over from 1999-02-28 to 1999-03-01.";
+date_1999_02_28(suite) ->
+ [];
+date_1999_02_28(Config) when is_list(Config) ->
+ ?line Date = {1999, 2, 28}, NextDate = {1999, 3, 1},
+ ?line match(next_date(Date), NextDate),
+ ?line match(tz_next_date(Date), NextDate).
+
+date_1999_09_09(doc) ->
+ "#3 : 1999-09-09: test roll-over from 1999-09-08 to 1999-09-09.";
+date_1999_09_09(suite) ->
+ [];
+date_1999_09_09(Config) when is_list(Config) ->
+ ?line Date = {1999, 9, 8}, NextDate = {1999, 9, 9},
+ ?line match(next_date(Date), NextDate),
+ ?line match(tz_next_date(Date), NextDate).
+
+date_2000_01_01(doc) ->
+ "#4 : 2000-01-01: test roll-over from 1999-12-31 to 2000-01-01 to "
+ "2000-01-02.";
+date_2000_01_01(suite) ->
+ [];
+date_2000_01_01(Config) when is_list(Config) ->
+ ?line Date = {1999, 12, 31}, NextDate = {2000, 1, 1},
+ ?line match(next_date(Date), NextDate),
+ ?line match(tz_next_date(Date), NextDate),
+ ?line NextDate1 = {2000, 1, 2},
+ ?line match(next_date(NextDate), NextDate1),
+ ?line match(tz_next_date(NextDate), NextDate1).
+
+date_2000_02_29(doc) ->
+ "#5 : 2000-02-29: test roll-over from 2000-02-28 to 2000-02-29 to "
+ "2000-03-01.";
+date_2000_02_29(suite) ->
+ [];
+date_2000_02_29(Config) when is_list(Config) ->
+ ?line Date = {2000, 2, 28}, NextDate = {2000, 2, 29},
+ ?line match(next_date(Date), NextDate),
+ ?line match(tz_next_date(Date), NextDate),
+ ?line NextDate1 = {2000, 3, 1},
+ ?line match(next_date(NextDate), NextDate1),
+ ?line match(tz_next_date(NextDate), NextDate1).
+
+date_2001_01_01(doc) ->
+ "#6 : 2001-01-01: test roll-over from 2000-12-31 to 2001-01-01.";
+date_2001_01_01(suite) ->
+ [];
+date_2001_01_01(Config) when is_list(Config) ->
+ ?line Date = {2000, 12, 31}, NextDate = {2001, 1, 1},
+ ?line match(next_date(Date), NextDate),
+ ?line match(tz_next_date(Date), NextDate).
+
+date_2001_02_29(doc) ->
+ "#7 : 2001-02-29: test roll-over from 2001-02-28 to 2001-03-01.";
+date_2001_02_29(suite) ->
+ [];
+date_2001_02_29(Config) when is_list(Config) ->
+ ?line Date = {2001, 2, 28}, NextDate = {2001, 3, 1},
+ ?line match(next_date(Date), NextDate),
+ ?line match(tz_next_date(Date), NextDate).
+
+date_2004_02_29(doc) ->
+ "#8 : 2004-02-29: test roll-over from 2004-02-28 to 2004-02-29 to "
+ "2004-03-01.";
+date_2004_02_29(suite) ->
+ [];
+date_2004_02_29(Config) when is_list(Config) ->
+ ?line Date = {2004, 2, 28}, NextDate = {2004, 2, 29},
+ ?line match(next_date(Date), NextDate),
+ ?line match(tz_next_date(Date), NextDate),
+ ?line NextDate1 = {2004, 3, 1},
+ ?line match(next_date(NextDate), NextDate1),
+ ?line match(tz_next_date(NextDate), NextDate1).
+
+%%
+%% Local functions
+%%
+next_date(Date) ->
+ calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(Date) + 1).
+%%
+%% timezonediff
+%%
+tzd(Date) ->
+ ?line {_LDate, {LH, _LM, _LS}} =
+ erlang:universaltime_to_localtime({Date, {12, 0, 0}}),
+ 12 - LH.
+
+tz_next_date(Date) ->
+ TZD = tzd(Date),
+ if
+ TZD > 0 ->
+ ?line Time = {24 - TZD, 0, 0},
+ ?line {NDate, _NTime} =
+ erlang:localtime_to_universaltime({Date, Time}),
+ ?line NDate;
+ TZD < 0 ->
+ ?line Time = {24 + TZD, 0, 0},
+ ?line {NDate, _NTime} =
+ erlang:universaltime_to_localtime({Date, Time}),
+ ?line NDate;
+ true ->
+ Date
+ end.
+
+%%
+%% match({X, X}) ->
+%% ok.
+
+match(X, X) ->
+ ok.
+
+
+
diff --git a/lib/stdlib/test/zip_SUITE.erl b/lib/stdlib/test/zip_SUITE.erl
new file mode 100644
index 0000000000..55cbd277ef
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE.erl
@@ -0,0 +1,759 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(zip_SUITE).
+
+-export([all/1, borderline/1, atomic/1,
+ bad_zip/1, unzip_from_binary/1, unzip_to_binary/1,
+ zip_to_binary/1,
+ unzip_options/1, zip_options/1, list_dir_options/1, aliases/1,
+ openzip_api/1, zip_api/1, unzip_jar/1,
+ compress_control/1]).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include_lib("kernel/include/file.hrl").
+-include_lib("stdlib/include/zip.hrl").
+
+all(suite) -> [borderline, atomic, bad_zip,
+ unzip_from_binary, unzip_to_binary,
+ zip_to_binary,
+ unzip_options, zip_options, list_dir_options, aliases,
+ openzip_api, zip_api, unzip_jar,
+ compress_control].
+
+borderline(doc) ->
+ ["Test creating, listing and extracting one file from an archive "
+ "multiple times with different file sizes. Also check that the "
+ "modification date of the extracted file has survived."];
+borderline(Config) when is_list(Config) ->
+ RootDir = ?config(priv_dir, Config),
+ TempDir = filename:join(RootDir, "borderline"),
+ ok = file:make_dir(TempDir),
+
+ Record = 512,
+ Block = 20 * Record,
+
+ lists:foreach(fun(Size) -> borderline_test(Size, TempDir) end,
+ [0, 1, 10, 13, 127, 333, Record-1, Record, Record+1,
+ Block-Record-1, Block-Record, Block-Record+1,
+ Block-1, Block, Block+1,
+ Block+Record-1, Block+Record, Block+Record+1]),
+
+ %% Clean up.
+ delete_files([TempDir]),
+ ok.
+
+borderline_test(Size, TempDir) ->
+ Archive = filename:join(TempDir, "ar_"++integer_to_list(Size)++".zip"),
+ Name = filename:join(TempDir, "file_"++integer_to_list(Size)),
+ io:format("Testing size ~p", [Size]),
+
+ %% Create a file and archive it.
+ {_, _, X0} = erlang:now(),
+ file:write_file(Name, random_byte_list(X0, Size)),
+ {ok, Archive} = zip:zip(Archive, [Name]),
+ ok = file:delete(Name),
+
+ %% Verify listing and extracting.
+ {ok, [#zip_comment{comment = []},
+ #zip_file{name = Name,
+ info = Info,
+ offset = 0,
+ comp_size = _}]} = zip:list_dir(Archive),
+ Size = Info#file_info.size,
+ {ok, [Name]} = zip:extract(Archive, [verbose]),
+
+ %% Verify contents of extracted file.
+ {ok, Bin} = file:read_file(Name),
+ true = match_byte_list(X0, binary_to_list(Bin)),
+
+
+ %% Verify that Unix zip can read it. (if we have a unix zip that is!)
+ unzip_list(Archive, Name),
+
+ ok.
+
+unzip_list(Archive, Name) ->
+ case os:find_executable("unzip") of
+ Unzip when is_list(Unzip) ->
+ unzip_list1(Archive, Name);
+ _ ->
+ ok
+ end.
+
+unzip_list1(Archive, Name) ->
+ Expect = Name ++ "\n",
+ cmd_expect("unzip -Z -1 " ++ Archive, Expect).
+
+cmd_expect(Cmd, Expect) ->
+ Port = open_port({spawn, make_cmd(Cmd)}, [stream, in, eof]),
+ get_data(Port, Expect).
+
+get_data(Port, Expect) ->
+ receive
+ {Port, {data, Bytes}} ->
+ get_data(Port, match_output(Bytes, Expect, Port));
+ {Port, eof} ->
+ Port ! {self(), close},
+ receive
+ {Port, closed} ->
+ true
+ end,
+ receive
+ {'EXIT', Port, _} ->
+ ok
+ after 1 -> % force context switch
+ ok
+ end,
+ match_output(eof, Expect, Port)
+ end.
+
+match_output([C|Output], [C|Expect], Port) ->
+ match_output(Output, Expect, Port);
+match_output([_|_], [_|_], Port) ->
+ kill_port_and_fail(Port, badmatch);
+match_output([X|Output], [], Port) ->
+ kill_port_and_fail(Port, {too_much_data, [X|Output]});
+match_output([], Expect, _Port) ->
+ Expect;
+match_output(eof, [], _Port) ->
+ [];
+match_output(eof, Expect, Port) ->
+ kill_port_and_fail(Port, {unexpected_end_of_input, Expect}).
+
+kill_port_and_fail(Port, Reason) ->
+ unlink(Port),
+ exit(Port, die),
+ test_server:fail(Reason).
+
+make_cmd(Cmd) ->
+ Cmd.
+%% case os:type() of
+%% {win32, _} -> lists:concat(["cmd /c", Cmd]);
+%% {unix, _} -> lists:concat(["sh -c '", Cmd, "'"])
+%% end.
+
+%% Verifies a random byte list.
+
+match_byte_list(X0, [Byte|Rest]) ->
+ X = next_random(X0),
+ case (X bsr 26) band 16#ff of
+ Byte -> match_byte_list(X, Rest);
+ _ -> false
+ end;
+match_byte_list(_, []) ->
+ true.
+
+%% Generates a random byte list.
+
+random_byte_list(X0, Count) ->
+ random_byte_list(X0, Count, []).
+
+random_byte_list(X0, Count, Result) when Count > 0->
+ X = next_random(X0),
+ random_byte_list(X, Count-1, [(X bsr 26) band 16#ff|Result]);
+random_byte_list(_X, 0, Result) ->
+ lists:reverse(Result).
+
+%% This RNG is from line 21 on page 102 in Knuth: The Art of Computer Programming,
+%% Volume II, Seminumerical Algorithms.
+
+next_random(X) ->
+ (X*17059465+1) band 16#fffffffff.
+
+atomic(doc) ->
+ ["Test the 'atomic' operations: zip/unzip/list_dir, on archives."
+ "Also test the 'cooked' option."];
+atomic(suite) -> [];
+atomic(Config) when list(Config) ->
+ ok = file:set_cwd(?config(priv_dir, Config)),
+ DataFiles = data_files(),
+ Names = [Name || {Name,_,_} <- DataFiles],
+ io:format("Names: ~p", [Names]),
+
+ %% Create a zip archive.
+
+ Zip2 = "zip.zip",
+ {ok, Zip2} = zip:zip(Zip2, Names, []),
+ Names = names_from_list_dir(zip:list_dir(Zip2)),
+
+ %% Same test again, but this time created with 'cooked'
+
+ Zip3 = "cooked.zip",
+ {ok, Zip3} = zip:zip(Zip3, Names, [cooked]),
+ Names = names_from_list_dir(zip:list_dir(Zip3)),
+ Names = names_from_list_dir(zip:list_dir(Zip3, [cooked])),
+
+ %% Clean up.
+ delete_files([Zip2,Zip3|Names]),
+
+ ok.
+
+openzip_api(doc) ->
+ ["Test the openzip_open/2, openzip_get/1, openzip_get/2, openzip_close/1 "
+ "and openzip_list_dir/1 functions."];
+openzip_api(suite) -> [];
+openzip_api(Config) when list(Config) ->
+ ok = file:set_cwd(?config(priv_dir, Config)),
+ DataFiles = data_files(),
+ Names = [Name || {Name, _, _} <- DataFiles],
+ io:format("Names: ~p", [Names]),
+
+ %% Create a zip archive
+
+ Zip = "zip.zip",
+ {ok, Zip} = zip:zip(Zip, Names, []),
+
+ %% Open archive
+ {ok, OpenZip} = zip:openzip_open(Zip, [memory]),
+
+ %% List dir
+ Names = names_from_list_dir(zip:openzip_list_dir(OpenZip)),
+
+ %% Get a file
+ Name1 = hd(Names),
+ {ok, Data1} = file:read_file(Name1),
+ {ok, {Name1, Data1}} = zip:openzip_get(Name1, OpenZip),
+
+ %% Get all files
+ FilesDatas = lists:map(fun(Name) -> {ok, B} = file:read_file(Name),
+ {Name, B} end, Names),
+ {ok, FilesDatas} = zip:openzip_get(OpenZip),
+
+ %% Close
+ ok = zip:openzip_close(OpenZip),
+
+ %% Clean up.
+ delete_files([Names]),
+
+ ok.
+
+zip_api(doc) ->
+ ["Test the zip_open/2, zip_get/1, zip_get/2, zip_close/1 "
+ "and zip_list_dir/1 functions."];
+zip_api(suite) -> [];
+zip_api(Config) when list(Config) ->
+ ok = file:set_cwd(?config(priv_dir, Config)),
+ DataFiles = data_files(),
+ Names = [Name || {Name, _, _} <- DataFiles],
+ io:format("Names: ~p", [Names]),
+
+ %% Create a zip archive
+ Zip = "zip.zip",
+ {ok, Zip} = zip:zip(Zip, Names, []),
+
+ %% Open archive
+ {ok, ZipSrv} = zip:zip_open(Zip, [memory]),
+
+ %% List dir
+ Names = names_from_list_dir(zip:zip_list_dir(ZipSrv)),
+
+ %% Get a file
+ Name1 = hd(Names),
+ {ok, Data1} = file:read_file(Name1),
+ {ok, {Name1, Data1}} = zip:zip_get(Name1, ZipSrv),
+
+ %% Get all files
+ FilesDatas = lists:map(fun(Name) -> {ok, B} = file:read_file(Name),
+ {Name, B} end, Names),
+ {ok, FilesDatas} = zip:zip_get(ZipSrv),
+
+ %% Close
+ ok = zip:zip_close(ZipSrv),
+
+ %% Clean up.
+ delete_files([Names]),
+
+ ok.
+
+unzip_options(doc) ->
+ ["Test options for unzip, only cwd and file_list currently"];
+unzip_options(suite) ->
+ [];
+unzip_options(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Long = filename:join(DataDir, "abc.zip"),
+
+ %% create a temp directory
+ Subdir = filename:join(PrivDir, "t"),
+ ok = file:make_dir(Subdir),
+
+ FList = ["quotes/rain.txt","wikipedia.txt"],
+
+ %% Unzip a zip file in Subdir
+ ?line {ok, RetList} = zip:unzip(Long, [{cwd, Subdir},
+ {file_list, FList}]),
+
+ %% Verify.
+ ?line true = (length(FList) =:= length(RetList)),
+ ?line lists:foreach(fun(F)-> {ok,B} = file:read_file(filename:join(DataDir, F)),
+ {ok,B} = file:read_file(filename:join(Subdir, F)) end,
+ FList),
+ ?line lists:foreach(fun(F)-> ok = file:delete(F) end,
+ RetList),
+
+ %% Clean up and verify no more files.
+ ?line 0 = delete_files([Subdir]),
+ ok.
+
+unzip_jar(doc) ->
+ ["Test unzip a jar file (OTP-7382)"];
+unzip_jar(suite) ->
+ [];
+unzip_jar(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ JarFile = filename:join(DataDir, "test.jar"),
+
+ %% create a temp directory
+ Subdir = filename:join(PrivDir, "jartest"),
+ ok = file:make_dir(Subdir),
+ ok = file:set_cwd(Subdir),
+
+ FList = ["META-INF/MANIFEST.MF","test.txt"],
+
+ {ok, RetList} = zip:unzip(JarFile),
+
+ %% Verify.
+ ?line lists:foreach(fun(F)-> {ok,B} = file:read_file(filename:join(DataDir, F)),
+ {ok,B} = file:read_file(filename:join(Subdir, F)) end,
+ FList),
+ ?line lists:foreach(fun(F)-> ok = file:delete(F) end,
+ RetList),
+
+ %% Clean up and verify no more files.
+ ?line 0 = delete_files([Subdir]),
+ ok.
+
+zip_options(doc) ->
+ ["Test the options for unzip, only cwd currently"];
+zip_options(suite) ->
+ [];
+zip_options(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ ok = file:set_cwd(PrivDir),
+ DataFiles = data_files(),
+ Names = [Name || {Name, _, _} <- DataFiles],
+
+ %% Make sure cwd is not where we get the files
+ ok = file:set_cwd(?config(data_dir, Config)),
+
+ %% Create a zip archive
+ {ok, Zip} = zip:zip("filename_not_used.zip", Names, [memory, {cwd, PrivDir}]),
+
+ %% Open archive
+ {ok, ZipSrv} = zip:zip_open(Zip, [memory]),
+
+ %% List dir
+ Names = names_from_list_dir(zip:zip_list_dir(ZipSrv)),
+
+ %% Get a file
+ Name1 = hd(Names),
+ {ok, Data1} = file:read_file(filename:join(PrivDir, Name1)),
+ {ok, {Name1, Data1}} = zip:zip_get(Name1, ZipSrv),
+
+ %% Get all files
+ FilesDatas = lists:map(fun(Name) -> {ok, B} = file:read_file(filename:join(PrivDir, Name)),
+ {Name, B} end, Names),
+ {ok, FilesDatas} = zip:zip_get(ZipSrv),
+
+ %% Close
+ ok = zip:zip_close(ZipSrv),
+
+ %% Clean up.
+ delete_files([Names]),
+
+ ok.
+
+list_dir_options(doc) ->
+ ["Test the options for list_dir... one day"];
+list_dir_options(suite) ->
+ [];
+list_dir_options(Config) when is_list(Config) ->
+ ok.
+
+
+
+
+%% convert zip_info as returned from list_dir to a list of names
+names_from_list_dir({ok, Info}) ->
+ names_from_list_dir(Info);
+names_from_list_dir(Info) ->
+ tl(lists:map(fun(#zip_file{name = Name}) -> Name;
+ (_) -> ok end, Info)).
+
+%% Returns a sequence of characters.
+char_seq(N, First) ->
+ char_seq(N, First, []).
+
+char_seq(0, _, Result) ->
+ Result;
+char_seq(N, C, Result) when C < 127 ->
+ char_seq(N-1, C+1, [C|Result]);
+char_seq(N, _, Result) ->
+ char_seq(N, $!, Result).
+
+data_files() ->
+ Files = [{"first_file", 1555, $a},
+ {"small_file", 7, $d},
+ {"big_file", 23875, $e},
+ {"last_file", 7500, $g}],
+ create_files(Files),
+ Files.
+
+create_files([{Name, dir, _First}|Rest]) ->
+ ok = file:make_dir(Name),
+ create_files(Rest);
+create_files([{Name, Size, First}|Rest]) when is_integer(Size) ->
+ ok = file:write_file(Name, char_seq(Size, First)),
+ create_files(Rest);
+create_files([]) ->
+ ok.
+
+%% make_dirs([Dir|Rest], []) ->
+%% ok = file:make_dir(Dir),
+%% make_dirs(Rest, Dir);
+%% make_dirs([Dir|Rest], Parent) ->
+%% Name = filename:join(Parent, Dir),
+%% ok = file:make_dir(Name),
+%% make_dirs(Rest, Name);
+%% make_dirs([], Dir) ->
+%% Dir.
+
+bad_zip(doc) ->
+ ["Try zip:unzip/1 on some corrupted zip files."];
+bad_zip(Config) when is_list(Config) ->
+ ok = file:set_cwd(?config(priv_dir, Config)),
+ try_bad("bad_crc", {bad_crc, "abc.txt"}, Config),
+ try_bad("bad_central_directory", bad_central_directory, Config),
+ try_bad("bad_file_header", bad_file_header, Config),
+ try_bad("bad_eocd", bad_eocd, Config),
+ try_bad("enoent", enoent, Config),
+ GetNotFound = fun(A) ->
+ {ok, O} = zip:openzip_open(A, []),
+ zip:openzip_get("not_here", O)
+ end,
+ try_bad("abc", file_not_found, GetNotFound, Config),
+ ok.
+
+try_bad(N, R, Config) ->
+ try_bad(N, R, fun(A) -> io:format("name : ~p\n", [A]),
+ zip:unzip(A, [verbose]) end, Config).
+
+try_bad(Name0, Reason, What, Config) ->
+ %% Intentionally no macros here.
+
+ DataDir = ?config(data_dir, Config),
+ Name = Name0 ++ ".zip",
+ io:format("~nTrying ~s", [Name]),
+ Full = filename:join(DataDir, Name),
+ Expected = {error, Reason},
+ case What(Full) of
+ Expected ->
+ io:format("Result: ~p\n", [Expected]);
+ Other ->
+ io:format("unzip/2 returned ~p (expected ~p)\n", [Other, Expected]),
+ test_server:fail({bad_return_value, Other})
+ end.
+
+unzip_to_binary(doc) ->
+ ["Test extracting to binary with memory option."];
+unzip_to_binary(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ delete_all_in(PrivDir),
+ file:set_cwd(PrivDir),
+ Long = filename:join(DataDir, "abc.zip"),
+
+ %% Unzip a zip file into a binary
+ {ok, FBList} = zip:unzip(Long, [memory]),
+
+ %% Verify.
+ lists:foreach(fun({F,B}) -> {ok,B}=file:read_file(filename:join(DataDir, F))
+ end, FBList),
+
+ %% Make sure no files created in cwd
+ {ok,[]} = file:list_dir(PrivDir),
+
+ ok.
+
+zip_to_binary(doc) ->
+ ["Test compressing to binary with memory option."];
+zip_to_binary(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ delete_all_in(PrivDir),
+ file:set_cwd(PrivDir),
+ FileName = "abc.txt",
+ ZipName = "t.zip",
+ FilePath = filename:join(DataDir, FileName),
+ {ok, _Size} = file:copy(FilePath, FileName),
+
+ %% Zip to a binary archive
+ {ok, {ZipName, ZipB}} = zip:zip(ZipName, [FileName], [memory]),
+
+ %% Make sure no files created in cwd
+ {ok,[FileName]} = file:list_dir(PrivDir),
+
+ %% Zip to a file
+ {ok, ZipName} = zip:zip(ZipName, [FileName]),
+
+ %% Verify.
+ {ok, ZipB} = file:read_file(ZipName),
+ {ok, FData} = file:read_file(FileName),
+ {ok, [{FileName, FData}]} = zip:unzip(ZipB, [memory]),
+
+ %% Clean up.
+ delete_files([FileName, ZipName]),
+
+ ok.
+
+aliases(doc) ->
+ ["Test using the aliases, extract/2, table/2 and create/3"];
+aliases(Config) when is_list(Config) ->
+ {_, _, X0} = erlang:now(),
+ Size = 100,
+ B = list_to_binary(random_byte_list(X0, Size)),
+ %% create
+ {ok, {"z.zip", ZArchive}} = zip:create("z.zip", [{"b", B}], [memory]),
+ %% extract
+ {ok, [{"b", B}]} = zip:extract(ZArchive, [memory]),
+ %% table
+ {ok, [#zip_comment{comment = _}, #zip_file{name = "b",
+ info = FI,
+ comp_size = _,
+ offset = 0}]} =
+ zip:table(ZArchive),
+ Size = FI#file_info.size,
+
+ ok.
+
+
+
+unzip_from_binary(doc) ->
+ ["Test extracting a zip archive from a binary."];
+unzip_from_binary(Config) when list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ ExtractDir = filename:join(PrivDir, "extract_from_binary"),
+ ok = file:make_dir(ExtractDir),
+ Archive = filename:join(ExtractDir, "abc.zip"),
+ {ok, _Size} = file:copy(filename:join(DataDir, "abc.zip"), Archive),
+ FileName = "abc.txt",
+ Quote = "quotes/rain.txt",
+ Wikipedia = "wikipedia.txt",
+ EmptyFile = "emptyFile",
+ file:set_cwd(ExtractDir),
+
+ %% Read a zip file into a binary and extract from the binary.
+ {ok, Bin} = file:read_file(Archive),
+ {ok, [FileName,Quote,Wikipedia,EmptyFile]} = zip:unzip(Bin),
+
+ %% Verify.
+ DestFilename = filename:join(ExtractDir, "abc.txt"),
+ {ok, Data} = file:read_file(filename:join(DataDir, FileName)),
+ {ok, Data} = file:read_file(DestFilename),
+
+ DestQuote = filename:join([ExtractDir, "quotes", "rain.txt"]),
+ {ok, QuoteData} = file:read_file(filename:join(DataDir, Quote)),
+ {ok, QuoteData} = file:read_file(DestQuote),
+
+ %% Clean up.
+ delete_files([DestFilename, DestQuote, Archive, ExtractDir]),
+ ok.
+
+%% oac_files() ->
+%% Files = [{"oac_file", 1459, $x},
+%% {"oac_small", 99, $w},
+%% {"oac_big", 33896, $A}],
+%% create_files(Files),
+%% Files.
+
+%% Delete the given list of files and directories.
+%% Return total number of deleted files (not directories)
+delete_files(List) ->
+ do_delete_files(List, 0).
+do_delete_files([],Cnt) ->
+ Cnt;
+do_delete_files([Item|Rest], Cnt) ->
+ case file:delete(Item) of
+ ok ->
+ DelCnt = 1;
+ {error,eperm} ->
+ file:change_mode(Item, 8#777),
+ DelCnt = delete_files(filelib:wildcard(filename:join(Item, "*"))),
+ file:del_dir(Item);
+ {error,eacces} ->
+ %% We'll see about that!
+ file:change_mode(Item, 8#777),
+ case file:delete(Item) of
+ ok ->
+ DelCnt = 1;
+ {error,_} ->
+ erlang:yield(),
+ file:change_mode(Item, 8#777),
+ file:delete(Item),
+ DelCnt = 1
+ end;
+ {error,_} ->
+ DelCnt = 0
+ end,
+ do_delete_files(Rest, Cnt + DelCnt).
+
+delete_all_in(Dir) ->
+ {ok, Files} = file:list_dir(Dir),
+ delete_files(lists:map(fun(F) -> filename:join(Dir,F) end,
+ Files)).
+
+compress_control(doc) ->
+ ["Test control of which files that should be compressed"];
+compress_control(suite) -> [];
+compress_control(Config) when list(Config) ->
+ ok = file:set_cwd(?config(priv_dir, Config)),
+ Dir = "compress_control",
+ Files = [
+ {Dir, dir, $d},
+ {filename:join([Dir, "first_file.txt"]), 10000, $f},
+ {filename:join([Dir, "a_dir"]), dir, $d},
+ {filename:join([Dir, "a_dir", "zzz.zip"]), 10000, $z},
+ {filename:join([Dir, "a_dir", "lll.lzh"]), 10000, $l},
+ {filename:join([Dir, "a_dir", "eee.exe"]), 10000, $e},
+ {filename:join([Dir, "a_dir", "ggg.arj"]), 10000, $g},
+ {filename:join([Dir, "a_dir", "b_dir"]), dir, $d},
+ {filename:join([Dir, "a_dir", "b_dir", "ggg.arj"]), 10000, $a},
+ {filename:join([Dir, "last_file.txt"]), 10000, $l}
+ ],
+
+ test_compress_control(Dir,
+ Files,
+ [{compress, []}],
+ []),
+
+ test_compress_control(Dir,
+ Files,
+ [{uncompress, all}],
+ []),
+
+ test_compress_control(Dir,
+ Files,
+ [{uncompress, []}],
+ [".txt", ".exe", ".zip", ".lzh", ".arj"]),
+
+ test_compress_control(Dir,
+ Files,
+ [],
+ [".txt", ".exe"]),
+
+ test_compress_control(Dir,
+ Files,
+ [{uncompress, {add, [".exe"]}},
+ {uncompress, {del, [".zip", "arj"]}}],
+ [".txt", ".zip", "arj"]),
+
+ test_compress_control(Dir,
+ Files,
+ [{uncompress, []},
+ {uncompress, {add, [".exe"]}},
+ {uncompress, {del, [".zip", "arj"]}}],
+ [".txt", ".zip", ".lzh", ".arj"]),
+
+ ok.
+
+test_compress_control(Dir, Files, ZipOptions, Expected) ->
+ %% Cleanup
+ Zip = "zip.zip",
+ Names = [N || {N, _, _} <- Files],
+ delete_files([Zip]),
+ delete_files(lists:reverse(Names)),
+
+ create_files(Files),
+ {ok, Zip} = zip:create(Zip, [Dir], ZipOptions),
+
+ {ok, OpenZip} = zip:openzip_open(Zip, [memory]),
+ {ok,[#zip_comment{comment = ""} | ZipList]} = zip:openzip_list_dir(OpenZip),
+ io:format("compress_control: -> ~p -> ~p\n -> ~pn", [Expected, ZipOptions, ZipList]),
+ verify_compression(Files, ZipList, OpenZip, ZipOptions, Expected),
+ ok = zip:openzip_close(OpenZip),
+
+ %% Cleanup
+ delete_files([Zip]),
+ delete_files(lists:reverse(Names)), % Remove plain files before directories
+
+ ok.
+
+verify_compression([{Name, Kind, _Filler} | Files], ZipList, OpenZip, ZipOptions, Expected) ->
+ {Name2, BinSz} =
+ case Kind of
+ dir ->
+ {Name ++ "/", 0};
+ _ ->
+ {ok, {Name, Bin}} = zip:openzip_get(Name, OpenZip),
+ {Name, size(Bin)}
+ end,
+ {Name2, {value, ZipFile}} = {Name2, lists:keysearch(Name2, #zip_file.name, ZipList)},
+ #zip_file{info = #file_info{size = InfoSz, type = InfoType}, comp_size = InfoCompSz} = ZipFile,
+
+ Ext = filename:extension(Name),
+ IsComp = is_compressed(Ext, Kind, ZipOptions),
+ ExpComp = lists:member(Ext, Expected),
+ case {Name, Kind, InfoType, IsComp, ExpComp, BinSz, InfoSz, InfoCompSz} of
+ {_, dir, directory, false, _, Sz, Sz, Sz} when Sz =:= BinSz -> ok;
+ {_, Sz, regular, false, false, Sz, Sz, Sz} when Sz =:= BinSz -> ok;
+ {_, Sz, regular, true, true, Sz, Sz, OtherSz} when Sz =:= BinSz, OtherSz =/= BinSz -> ok
+ end,
+ verify_compression(Files, ZipList -- [ZipFile], OpenZip, ZipOptions, Expected);
+verify_compression([], [], _OpenZip, _ZipOptions, _Expected) ->
+ ok.
+
+is_compressed(_Ext, dir, _Options) ->
+ false;
+is_compressed(Ext, _Sz, Options) ->
+ CompressOpt =
+ case [What || {compress, What} <- Options] of
+ [] -> all;
+ CompressOpts-> extensions(CompressOpts, all)
+ end,
+ DoCompress = (CompressOpt =:= all) orelse lists:member(Ext, CompressOpt),
+ Default = [".Z", ".zip", ".zoo", ".arc", ".lzh", ".arj"],
+ UncompressOpt =
+ case [What || {uncompress, What} <- Options] of
+ [] -> Default;
+ UncompressOpts-> extensions(UncompressOpts, Default)
+ end,
+ DoUncompress = (UncompressOpt =:= all) orelse lists:member(Ext, UncompressOpt),
+ DoCompress andalso not DoUncompress.
+
+extensions([H | T], Old) ->
+ case H of
+ all ->
+ extensions(T, H);
+ H when is_list(H) ->
+ extensions(T, H);
+ {add, New} when is_list(New), is_list(Old) ->
+ extensions(T, Old ++ New);
+ {del, New} when is_list(New), is_list(Old) ->
+ extensions(T, Old -- New);
+ _ ->
+ extensions(T, Old)
+ end;
+extensions([], Old) ->
+ Old.
+
diff --git a/lib/stdlib/test/zip_SUITE_data/META-INF/MANIFEST.MF b/lib/stdlib/test/zip_SUITE_data/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..f334c5d368
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Created-By: 1.4.2_08 (Sun Microsystems Inc.)
+
diff --git a/lib/stdlib/test/zip_SUITE_data/abc.txt b/lib/stdlib/test/zip_SUITE_data/abc.txt
new file mode 100644
index 0000000000..da0202e539
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/abc.txt
@@ -0,0 +1,23 @@
+Introduction
+
+abc is a language designed to notate tunes in an ascii
+format. It was designed primarily for folk and traditional tunes of
+Western European origin (such as English, Irish and Scottish) which
+can be written on one stave in standard classical notation. However,
+it is extendible to many other types of music and recently Steve Allen
+has coded Beethoven's Symphony No. 7, Movement 2 in abc! Since its
+introduction at the end of 1991 it has become very popular and there
+now exist several Windows, Mac, Palmtop and UNIX based tools which can
+read abc notation and either process it into staff notation or play it
+through the speakers of a computer.
+
+One of the most important aims of abc notation, and perhaps one that
+distinguishes it from most, if not all, computer-readable musical
+languages is that it can be easily read by humans. In other words,
+with a little practice, it is possible to play a tune directly from
+the abc notation without having to process and print it out. Even if
+this isn't of interest, the resulting clarity of the notation makes it
+fairly easy to notate tunes. In addition, the ability to write music
+in abc notation means that it can be easily and portably stored or
+transported electronically hence enabling the discussion and
+dissemination of music via email. \ No newline at end of file
diff --git a/lib/stdlib/test/zip_SUITE_data/abc.zip b/lib/stdlib/test/zip_SUITE_data/abc.zip
new file mode 100644
index 0000000000..e78558f9a5
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/abc.zip
Binary files differ
diff --git a/lib/stdlib/test/zip_SUITE_data/bad_central_directory.zip b/lib/stdlib/test/zip_SUITE_data/bad_central_directory.zip
new file mode 100644
index 0000000000..ae92173f71
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/bad_central_directory.zip
Binary files differ
diff --git a/lib/stdlib/test/zip_SUITE_data/bad_crc.zip b/lib/stdlib/test/zip_SUITE_data/bad_crc.zip
new file mode 100644
index 0000000000..1cfae6c0c5
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/bad_crc.zip
Binary files differ
diff --git a/lib/stdlib/test/zip_SUITE_data/bad_eocd.zip b/lib/stdlib/test/zip_SUITE_data/bad_eocd.zip
new file mode 100644
index 0000000000..c40ca4f85c
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/bad_eocd.zip
Binary files differ
diff --git a/lib/stdlib/test/zip_SUITE_data/bad_file_header.zip b/lib/stdlib/test/zip_SUITE_data/bad_file_header.zip
new file mode 100644
index 0000000000..4b14a0eb34
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/bad_file_header.zip
Binary files differ
diff --git a/lib/stdlib/test/zip_SUITE_data/emptyFile b/lib/stdlib/test/zip_SUITE_data/emptyFile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/emptyFile
diff --git a/lib/stdlib/test/zip_SUITE_data/quotes/rain.txt b/lib/stdlib/test/zip_SUITE_data/quotes/rain.txt
new file mode 100644
index 0000000000..19946d2f0e
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/quotes/rain.txt
@@ -0,0 +1 @@
+The rain in Spain stays mainly in the plain
diff --git a/lib/stdlib/test/zip_SUITE_data/test.jar b/lib/stdlib/test/zip_SUITE_data/test.jar
new file mode 100644
index 0000000000..53b9939c0b
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/test.jar
Binary files differ
diff --git a/lib/stdlib/test/zip_SUITE_data/test.txt b/lib/stdlib/test/zip_SUITE_data/test.txt
new file mode 100644
index 0000000000..0c452d43fb
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/test.txt
@@ -0,0 +1 @@
+Lorem Ipsum osv osv \ No newline at end of file
diff --git a/lib/stdlib/test/zip_SUITE_data/wikipedia.txt b/lib/stdlib/test/zip_SUITE_data/wikipedia.txt
new file mode 100644
index 0000000000..83ebb6fbf9
--- /dev/null
+++ b/lib/stdlib/test/zip_SUITE_data/wikipedia.txt
@@ -0,0 +1,30 @@
+The ZIP file format is a popular lossless data compression and
+archival format. A ZIP file contains one or more files that have been
+compressed, to reduce their file size, or stored as-is. A number of
+compression algorithms are permitted in zip files but as of 2008 only
+DEFLATE is widely used and supported.
+
+The format was originally evolved by Phil Katz for PKZIP from the
+previous ARC compression format by Thom Henderson. However, many
+software utilities other than PKZIP itself are now available to
+create, modify, or open (unzip, decompress) ZIP files, notably WinZip,
+BOMArchiveHelper, KGB Archiver, PicoZip, Info-ZIP, WinRAR, IZArc,
+7-Zip, ALZip, TUGZip, PeaZip, Universal Extractor and Zip
+Genius. Microsoft has included built-in ZIP support (under the name
+"compressed folders") in later versions of its Windows operating
+system. Apple has included built-in ZIP support in Mac OS X 10.3 and
+later via the BOMArchiveHelper utility.
+
+ZIP files generally use the file extensions ".zip" or ".ZIP" and the
+MIME media type application/zip. Some software uses the ZIP file
+format as a wrapper for a large number of small items in a specific
+structure. Generally when this is done a different file extension is
+used. Examples of this usage are Java JAR files, id Software .pk3/.pk4
+files, package files for StepMania and Winamp/Windows Media Player
+skins, XPInstall, as well as OpenDocument and Office Open XML office
+formats. Both OpenDocument and Office Open XML formats use the JAR
+file format internally, so files can be easily uncompressed and
+compressed using tools for ZIP files. Google Earth makes use of KMZ
+files, which are just KML files in ZIP format. Mozilla Firefox Add-ons
+are zip files with extension "xpi". Nokia's mobile phone themes are
+zipped with extension "nth".
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
new file mode 100644
index 0000000000..c6e93a4b4b
--- /dev/null
+++ b/lib/stdlib/vsn.mk
@@ -0,0 +1,2 @@
+STDLIB_VSN = 1.16.4
+