From 3db9f7fe3f2fcff5c7d1ccb89c2a1c03b5a49701 Mon Sep 17 00:00:00 2001 From: totoro Date: Tue, 25 Jul 2017 09:01:28 +0200 Subject: [PATCH] add generic send_mail to calexium lib. This is based on MailFountain source code --- mail/compose_email.anubis | 553 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mail/decode_mail.anubis | 27 +++++++++++++++++++++++++++ mail/encode_mail.anubis | 38 ++++++++++++++++++++++++++++++++++++++ mail/lexers/fqa.anubis | 438 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1056 insertions(+), 0 deletions(-) create mode 100644 mail/compose_email.anubis create mode 100644 mail/decode_mail.anubis create mode 100644 mail/encode_mail.anubis create mode 100644 mail/lexers/fqa.anubis diff --git a/mail/compose_email.anubis b/mail/compose_email.anubis new file mode 100644 index 0000000..0368091 --- /dev/null +++ b/mail/compose_email.anubis @@ -0,0 +1,553 @@ +/* + * Created by PyramIDE. + * User: フランスのトトロ aka (David RENÉ) + * Date: 12/07/2017 + * Time: 01:28 + * © Calexium + */ + +read cipher/base64.anubis +read tools/basis.anubis +read tools/findstring.anubis +read tools/streams.anubis +read system/string.anubis +read system/logger.anubis +read system/data_io.anubis +read web/mime.anubis +read calexium_lib/net_services_protocols/logger_service.anubis +read calexium_lib/mail/decode_mail.anubis +read calexium_lib/mail/encode_mail.anubis +read calexium_lib/mail/lexers/fqa.anubis + + +public type Mail_Structure: + mail_header_content(String header, String content). + +public define Mail_Structure + no_data_mail = + mail_header_content("",""). + +public type Email_Address: + email_address(Maybe(String) name, String address). + +public type File_Attached : + attached_file(String filename, RStream filestream). + +public type Email_to_send : + data_to_send( + Email_Address from, + List(Email_Address) to, + Maybe(List(Email_Address)) cc, + Maybe(List(Email_Address)) bcc, + Maybe(String) subject, + String content, + List(File_Attached) files, + Bool send_copy, //send copy to sender by bcc (useful when the email is sent by an automat) +// String from, + Maybe(String) reply_to + ). + +define Bool + is_element + ( + String element, + List(String) list + )= + if list is + { + [] then false, + [h . t] then + if h = element then + true + else + is_element(element, t) + }. + +define Maybe(List($T2)) + _do_to_list + ( + List($T1) list, + $T1 -> Maybe($T2) to_do, + List($T2) new_list + )= + if list is + { + [] then success(new_list), + [h . t] then + with is_done = to_do(h), + if is_done is + { + failure then failure, + success(done) then + _do_to_list(t, to_do, [done . new_list]) + } + }. + +public define Maybe(List($T2)) + do_to_list + ( + List($T1) list, + $T1 -> Maybe($T2) to_do, + )= + with result = _do_to_list(list, to_do, []), + if result is + { + failure then failure, + success(new_list) then success(reverse(new_list)) + }. + + +define Maybe($T) + last_element + ( + List($T) list + )= + if list is + { + [] then failure, + [h . t] then + if t is + { + [] then success(h), + [h1 . t1] then last_element(t) + } + }. + +define Maybe(String) + day_name + ( + Int day_num + )= + if day_num = 1 then success("Mon") + else if day_num = 2 then success("Tue") + else if day_num = 3 then success("Wed") + else if day_num = 4 then success("Thu") + else if day_num = 5 then success("Fri") + else if day_num = 6 then success("Sat") + else if day_num = 7 then success("Sun") + else failure + . + +define Maybe(String) + month_name + ( + Int month_num + )= + if month_num = 1 then success("Jan") + else if month_num = 2 then success("Feb") + else if month_num = 3 then success("Mar") + else if month_num = 4 then success("Apr") + else if month_num = 5 then success("May") + else if month_num = 6 then success("Jun") + else if month_num = 7 then success("Jul") + else if month_num = 8 then success("Aug") + else if month_num = 9 then success("Sep") + else if month_num = 10 then success("Oct") + else if month_num = 11 then success("Nov") + else if month_num = 12 then success("Dec") + else failure + . + +public define String + address_to_string + ( + Email_Address mail + )= + with string_name = + if mail.name is + { + failure then "", + success(add_name) then to_MIME_text("UTF-8", add_name) + " " + }, + string_name + "<" + mail.address + ">". + +define String + get_mime_of_extensions + ( + String ext, + List(MIME) mime_list + )= + if mime_list is + { + [] then "application/octet-stream", + [h . t] then + if h is mime(_,_, exts) then + if is_element(ext, exts) then + to_String(h) + else + get_mime_of_extensions(ext, t) + }. + +define String + get_mime + ( + String filename + )= + if last_element(split_by_token(filename, '.')) is + { + failure then "application/octet-stream", + success(ext)then get_mime_of_extensions("." + ext, known_mime_types) + }. + + +define String + limit_line_length + ( + String source, + Int limit, + String so_far + ) = + with l = length(source), + if l > limit then + if sub_string(source, 0, limit) is + { + failure then so_far, // impossible + success(s) then + if sub_string(source, limit, l - limit) is + { + failure then so_far + s, // impossible + success(new_source) then + limit_line_length(new_source, limit, so_far + s + crlf) + } + } + else + so_far + source. + +public define String + limit_line_length + ( + String source, + Int limit + ) = + limit_line_length(source, limit, ""). + +public define String + data_file_multipart + ( + String filename, + ByteArray data, + ) = + with data_file = limit_line_length(fast_base64_encode(data, false), 76), + "Content-Type: " + get_mime(filename) + ";" + crlf + + " name=\"" + filename + "\"" + crlf + + //"Content-ID :" + crlf + + "Content-Transfer-Encoding: base64" + crlf + + "Content-Disposition: attachment;" + crlf + + " filename=\"" + filename + "\"" + crlf + + crlf + + data_file + crlf + . + +define String + data_file_multipart + ( + File_Attached file, + )= + with data_file = if reliable_read(file.filestream, file_size(file.filestream), 100) is + { + failure then constant_byte_array(0,0), + success(data) then data + }, + data_file_multipart(file.filename, data_file). + +define String + data_files_multipart + ( + List(File_Attached) files, + String boundary + )= + if files is + { + [] then "", + [file_j . t] then + "--" + boundary + crlf + + data_file_multipart(file_j) + data_files_multipart(t, boundary) + }. + + +public define String + str_utime_send_mail + ( + UTime t + ) = + with date_send = convert_time(t.seconds), + if day_name(date_send.week_day) is + { + failure then "", + success(the_day_name) then the_day_name + ", " + }+ + date_send.day + " " + + if month_name(date_send.month) is + { + failure then to_decimal(date_send.month), + success(the_month_name) then the_month_name + }+ " " + + date_send.year + " " + + zero_pad_n(2, date_send.hour) + ":" + zero_pad_n(2, date_send.minute) + ":" + zero_pad_n(2, date_send.second) + //+ " +0000" + . + +public define String + get_rfc822_date_format + = + str_utime_send_mail(unow). + + if capture_shell_command("date", ["-R"]) is + { + failure then str_utime_send_mail(unow), + success(s) then + if split_lines(s) is [h . t] then h else str_utime_send_mail(unow) + }. + +define Int + _next_ws_pos + ( + List(Word8) line, + Int pos + ) = + if line is + { + [] then pos, + [h . t] then + if h = 32 | h = 9 then pos + else _next_ws_pos(t, pos + 1) + }. + +define String + encode_plain_text_content + ( + List(String) lines, + List(String) output, + ) = + if lines is + { + [] then join(crlf, reverse(output)), + [line . t] then + with l = length(line), + pos = 75 - _next_ws_pos(reverse(explode(force(sub_string(line, 0, 75), line))), 1), + if l > 76 then + if sub_string(line, 0, pos) is + { + failure then + (if pos < 0 then unique else println("sub_string 1 failure into encode_plain_text_content()")); + encode_plain_text_content(t, [line . output]), + success(l1) then + if sub_string(line, pos + 1, l - pos - 1) is + { + failure then + println("sub_string 2 failure into encode_plain_text_content()"); + encode_plain_text_content(t, [line . output]), + success(l2) then + encode_plain_text_content([l2 . t], [l1 + " " . output]) + } + } + else + encode_plain_text_content(t, [line . output]) + }. + + +public define String + encode_content + ( + String content, + Bool is_html, + ) = + if is_html then + join(crlf, split_lines(content)) + crlf + else + encode_plain_text_content(split_lines(content), []) + crlf. + + +public define String + compose_mail + ( + Email_to_send send + ) = + //with header_to_name = "To: ", + if send is data_to_send(from, to, cc, bcc, subject, content, files, send_copy, reply_to) then + with send_date = get_rfc822_date_format, + text_type = if find("" + crlf + + "User-Agent: Calexium lib email sender" + crlf + + "From: " + "<" + from.address + ">" + crlf + + if reply_to is + { + failure then "", + success(reply_t) then "Reply-To: <" + reply_t +">" + crlf + }+ + "MIME-Version: 1.0" + crlf + + //header_to_name + folding(to, 68 - length(header_to_name)) + crlf + + "To: " + folding(to, address_to_string) + crlf + + if cc is + { + failure then "", + success(cc_list) then + // with header_cc_name = "Cc: ", + // header_cc_name + folding(cc_list, 68 - length(header_cc_name)) + crlf + if cc_list is [] then "" + else "Cc: " + folding(cc_list, address_to_string) + crlf + } + + if subject is + { + failure then "", + success(subj) then + "Subject: " + to_folded_base64_text("UTF-8", subj) + crlf + } + + if is_multipart then + with boundary = to_ascii(sha1((from, send_date))), + "Content-Type: multipart/mixed;" + crlf + + " boundary=\"" + boundary + "\"" + crlf + + crlf + + "This is a multi-part message in MIME format." + crlf + + "--" + boundary + crlf + + data_text + crlf + + + data_files_multipart(files, boundary) + + + "--" + boundary + "--" + else + data_text + . + + +define String + message_header + ( + Data_IO d_io + ) = + if (Maybe(String))read_line(d_io) is + { + failure then "", + success(line) then + if length(line) = 0 then + "" + else + line + message_header(d_io) + }. + +public define String + get_message_header + ( + String file_name + )= + //open the message file from the drive + if (Maybe(RStream))file(file_name, read) is + { + failure then println("get_message_header: can't open mail ["+file_name+"] from disk."); "", //logError(debug_log, "get_message_header: can't open mail ["+file_name+"] from disk."); "", + success(source) then message_header(make_data_io(source)) + }. + + +public define String + get_message_header + ( + List(Data_IO) mail_parts + )= + //open the message file from the drive + if rewind(mail_parts) then + "get_message_header NOT YET Implemented. Please report that error to Calexium" + else + "". + + public define Mail_Structure + separate_data_mail + ( + String mail + )= + if find(crlfcrlf, mail, 0) is + { + failure then mail_header_content("", mail), + success(position) then mail_header_content( + if sub_string(mmake_directoriesail, 0, position) is + { + failure then "", + success(substring) then substring + }, + if sub_string(mail, position + 4 , length(mail) - (position + 4)) is + { + failure then mail, + success(substring) then substring + } + ) + + }. + + +public define List(String) + list_of_mails + ( + String string_list, + List(String) mails_list + )= + if find(",", string_list, 0) is + { + failure then [string_list . mails_list], + success(separator) then + if sub_string(string_list, 0, separator) is + { + failure then [], + success(mail) then + if sub_string(string_list, separator + 1, length(string_list) - (separator + 1)) is + { + failure then [], + success(rest) then list_of_mails(rest, [mail . mails_list]) + } + } + } + . + +public define Maybe(Email_Address) + parse_address + ( + String mail, + )= + if find("<", mail, 0) is + { + failure then + with result_mail = trim(mail), + if test_fqa(result_mail) then + success(email_address(failure, result_mail)) + else + failure, + success(separator_b) then + if find(">", mail, separator_b + 1) is + { + failure then + failure, + success(separator_e) then + with name_string = sub_string(mail, 0, separator_b), + address_string = sub_string(mail, separator_b + 1, separator_e - separator_b - 1), + if address_string is + { + failure then failure, + success(add_str) then + with name = if name_string is + { + failure then failure, + success(name_str) then success(trim(name_str)) + }, + if test_fqa(add_str) then + success(email_address(name, add_str)) + else + failure + } + } + }. + + diff --git a/mail/decode_mail.anubis b/mail/decode_mail.anubis new file mode 100644 index 0000000..4615cef --- /dev/null +++ b/mail/decode_mail.anubis @@ -0,0 +1,27 @@ +/* + * Created by PyramIDE. + * User: フランスのトトロ aka (David RENÉ) + * Date: 12/07/2017 + * Time: 21:53 + * © Calexium + */ + +read system/string.anubis + +public define String + folding + ( + List($T) mails_list, + $T -> String to_str, + )= + if mails_list is + { + [] then "", + [h . t] then + if t is + { + [] then " " + to_str(h), + [h1 . t1] then " " + to_str(h) + "," + crlf + folding(t, to_str) + } + } + . diff --git a/mail/encode_mail.anubis b/mail/encode_mail.anubis new file mode 100644 index 0000000..d10e48c --- /dev/null +++ b/mail/encode_mail.anubis @@ -0,0 +1,38 @@ +/* + * Created by PyramIDE. + * User: フランスのトトロ aka (David RENÉ) + * Date: 12/07/2017 + * Time: 22:02 + * © Calexium + */ + +read tools/base64.anubis +read tools/basis.anubis +read system/string.anubis + +public define String + to_folded_base64_text + ( + String charset, + String text, + ) = + with l = length(text), + max_length = (Int)48, + if l > max_length then + if sub_string(text, 0, max_length) is + { + failure then "", // can't occures + success(left_text) then + with text2 = "=?"+charset+"?B?"+to_string(base64_encode(to_byte_array(left_text)))+"?=", + if sub_string(text, max_length, l - max_length) is + { + failure then "", // can't occures + success(rigth_text) then + text2 + crlf + " " + to_folded_base64_text(charset, rigth_text) + } + } + else if l > 0 then + "=?"+charset+"?B?"+to_string(base64_encode(to_byte_array(text)))+"?=" + //find_and_replace(text2, implode([13,10]), implode([13,10,32])) + else + "". diff --git a/mail/lexers/fqa.anubis b/mail/lexers/fqa.anubis new file mode 100644 index 0000000..5342193 --- /dev/null +++ b/mail/lexers/fqa.anubis @@ -0,0 +1,438 @@ +/* + * Created by PyramIDE. + * User: フランスのトトロ aka (David RENÉ) + * Date: 12/07/2017 + * Time: 22:28 + * © Calexium + */ + + + + + This is an example of use of 'lexer_maker'. + +read tools/basis.anubis + + + We want to test email addresses. Below is a regular expression for that + purpose. Actually, this expression is too naïve. A real one would be more complicated. + + +read tools/streams.anubis + +type LM_TokenOrError_FQA: + end_of_file, + token(List(Word8)), + error. + +type LM_LexerState_FQA: ... + +type LM_Match_FQA: + match(List(Word8) characters, + (LM_LexerState_FQA,List(Word8)) -> (LM_LexerState_FQA,LM_TokenOrError_FQA) action, + Bool aeol, + Bool abol). + +type LM_LexerState_FQA: + lexer_state(Stream input, + List(Word8) unput, // in natural order + List(Word8) more, // in reverse order + LM_LexerState_FQA -> (LM_LexerState_FQA,LM_TokenOrError_FQA) lexer, + Bool at_end_of_line, + Bool at_beginning_of_line, + Maybe(LM_Match_FQA) match). + +define LM_LexerState_FQA + lm_initial_state + ( + Stream input, + LM_LexerState_FQA -> (LM_LexerState_FQA,LM_TokenOrError_FQA) lexer + ) = + lexer_state(input,[],[],lexer,false,true,failure). + +define (LM_LexerState_FQA,Word8) + lm_next_char + ( + LM_LexerState_FQA ls + ) = + if ls is lexer_state(input,unput,more,lex,aeol,abol,match) then + if unput is + { + [ ] then + if read_byte(input) is + { + failure then + (lexer_state(input, + [], + more, + lex, + true, + aeol, + match), + -1), + + success(c) then + (lexer_state(input, + [], + [c . more], + lex, + c = '\n', + aeol, + match), + c), + }, + + [h . t] then + (lexer_state(input, + t, + [h . more], + lex, + h = '\n', + aeol, + match), + h) + }. + +define LM_LexerState_FQA + lm_remember_match + ( + (LM_LexerState_FQA,List(Word8)) -> (LM_LexerState_FQA,LM_TokenOrError_FQA) action, + LM_LexerState_FQA ls + ) = + if ls is lexer_state(input,unput,more,lex,aeol,abol,m) then + with chars = if m is + { + failure then [], + success(match) then + if match is match(chars,_,_,_) then chars + }, + lexer_state(input, + unput, + [], + lex, + aeol, + abol, + success(match(append(more,chars),action,aeol,abol))). + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + lm_find_match + ( + LM_LexerState_FQA ls + ) = + if ls is lexer_state(input,unput,more,lex,_,_,mb_m) then + if mb_m is + { + failure then (ls,error), + success(m) then + if m is match(chars,action,aeol,abol) then + action(lexer_state(input, + append(more,unput), + [], + lex, + aeol, + abol, + failure), + reverse(chars)) + }. + +define LM_LexerState_FQA + change_lexer + ( + LM_LexerState_FQA ls, + LM_LexerState_FQA -> (LM_LexerState_FQA,LM_TokenOrError_FQA) lex + ) = + if ls is lexer_state(input,unput,more,_,aeol,abol,mb_m) then + lexer_state(input,unput,more,lex,aeol,abol,mb_m). + +define LM_LexerState_FQA + clear_abol + ( + LM_LexerState_FQA ls + ) = + if ls is lexer_state(input,unput,more,lex,aeol,_,mb_m) then + lexer_state(input,unput,more,lex,aeol,false,mb_m). + + Declarations of all lexer states. + + +public define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_1(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_2(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_3(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_4(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_5(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_6(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_7(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_8(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_9(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_10(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_11(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_12(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_state_13(LM_LexerState_FQA ls). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_action_0 + (LM_LexerState_FQA ls, List(Word8) text). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_action_1 + (LM_LexerState_FQA ls, List(Word8) text). +define (LM_LexerState_FQA,LM_TokenOrError_FQA) email_tester_action_2 + (LM_LexerState_FQA ls, List(Word8) text). + + + Lexer states. + + +public define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester + ( + LM_LexerState_FQA ls, + ) = + if at_beginning_of_line(ls) + then email_tester_state_3(clear_abol(ls)) else + if lm_next_char(ls) is (ls,c) then + if c = -1 then email_tester_state_1(ls) else + if c = 45 then email_tester_state_4(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_4(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_4(ls) else + if c = 95 then email_tester_state_4(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_4(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_1 + ( + LM_LexerState_FQA ls, + ) = + with ls = lm_remember_match(email_tester_action_2,ls), + if lm_next_char(ls) is (ls,c) then + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_2 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_3 + ( + LM_LexerState_FQA ls, + ) = + with ls = lm_remember_match(email_tester_action_1,ls), + if lm_next_char(ls) is (ls,c) then + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_4 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_4(ls) else + if c = 46 then email_tester_state_5(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_4(ls) else + if c = 64 then email_tester_state_7(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_4(ls) else + if c = 95 then email_tester_state_4(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_4(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_5 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_6(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_6(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_6(ls) else + if c = 95 then email_tester_state_6(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_6(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_6 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_6(ls) else + if c = 46 then email_tester_state_5(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_6(ls) else + if c = 64 then email_tester_state_7(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_6(ls) else + if c = 95 then email_tester_state_6(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_6(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_7 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_8(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_8(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_8(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_8(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_8 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_8(ls) else + if c = 46 then email_tester_state_9(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_8(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_8(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_8(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_9 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_10(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_11(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_11(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_11(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_10 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_10(ls) else + if c = 46 then email_tester_state_9(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_10(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_10(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_10(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_11 + ( + LM_LexerState_FQA ls, + ) = + with ls = lm_remember_match(email_tester_action_0,ls), + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_10(ls) else + if c = 46 then email_tester_state_12(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_11(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_11(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_11(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_12 + ( + LM_LexerState_FQA ls, + ) = + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_10(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_13(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_13(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_13(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_state_13 + ( + LM_LexerState_FQA ls, + ) = + with ls = lm_remember_match(email_tester_action_0,ls), + if lm_next_char(ls) is (ls,c) then + if c = 45 then email_tester_state_10(ls) else + if c = 46 then email_tester_state_12(ls) else + if (48 +=< c & c +=< 57) then email_tester_state_13(ls) else + if (65 +=< c & c +=< 90) then email_tester_state_13(ls) else + if (97 +=< c & c +=< 122) then email_tester_state_13(ls) else + lm_find_match(ls). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_action_0 + ( + LM_LexerState_FQA ls, + List(Word8) text + ) = + (ls,token(text)). + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_action_1 + ( + LM_LexerState_FQA ls, + List(Word8) text + ) = + /* default action */ email_tester(ls) . + + +define (LM_LexerState_FQA,LM_TokenOrError_FQA) + email_tester_action_2 + ( + LM_LexerState_FQA ls, + List(Word8) text + ) = + /* default action */ (ls,end_of_file) . + + + Since '@' is a normal character, a string needs to contain exactly one '@' for being + accepted. What is accepted before and after this '@' is described by: + + [a-zA-Z]+(\.[a-zA-Z]+)* + + The first part: [a-zA-Z]+ means ``at least one letter''. The last part: (\.[a-zA-Z]+)* + means: ``a dot followed by at least one letter, and this may be repeated any number of + times (including zero)''. + + + This part of the source file is the 'postambule' (just Anubis text, which is copied 'as + is' to the lexer_maker output file). + + The above stuff produces a function named 'email_tester' into the lexer_maker output + file. This function is used below: + +public define Bool + test_fqa + ( + String account + ) = + with ls = lexer_state(make_stream(account),[],[],email_tester,true,false,failure), + if email_tester(ls) is (_,result) then if result is + { + end_of_file then false, + token(t) then + ( + with result = implode(t), + if length(result) = length(account) then + //logDebug(debug_log,"["+account +"] is FQA valid"); + true + else + println("["+account+"] is valid FQA but different than ["+result+"]"); + false + ), + error then + //logWarning(debug_log,"["+account+"] isn't valid FQA "); + false + }. + -- libgit2 0.21.4