/* # Tiger-bot Attempt at writing an IRC bot in Tiger. Actually mostly worked, by wrapping the call in a specifically designed script to handle SSL and the connection. The Tiger implementation only needed to respond to messages on input, by writing to output. Design started on 6. Marts 2019. ## Usage Run using `run.sh`. Requires `openssl` utility. */ let /* Parsing util */ var seen_char := 0 var next_char := "IS NOT A CHAR; SHOULD NOT EVER BE VISIBLE!" function peak_char (): string = ( if not(seen_char) then ( next_char := getchar() ; seen_char := 1 ) ; next_char ) function pop_char (): string = let var char := peak_char() in seen_char := 0 ; char end /* String and number util */ function max (a:int, b:int): int = if a > b then a else b function min (a:int, b:int): int = if a < b then a else b function safe_substring (str: string, i_start: int, i_end: int): string = ( i_start := max(0, i_start) ; i_end := min(size(str) - 1, i_end) ; if i_start > i_end then "" else substring(str, i_start, i_end - i_start + 1) ) /* Messages */ type str_arr = array of string type message = { /* TODO: @tags, :source, */ command: string , parameters: str_arr , num_parameters: int } function parse_message (plain_msg: string): message = let var msg := message { command = "NO CMD" , parameters = str_arr[16] of "NO PARAM" /* TODO: Support any number of paramters */ , num_parameters = -1 } var word_start_i := 0 function skip_ws () = while substring(plain_msg, word_start_i, 1) = " " do word_start_i := word_start_i + 1 function skip_to_ws () = while substring(plain_msg, word_start_i, 1) <> " " do word_start_i := word_start_i + 1 in skip_ws() /* Skip @tags */ ; if substring(plain_msg, word_start_i, 1) = "@" then skip_to_ws() ; skip_ws() /* Skip :source */ ; if substring(plain_msg, word_start_i, 1) = ":" then skip_to_ws() ; skip_ws() ; for i := word_start_i to size(plain_msg) do let var char := if i < size(plain_msg) then ord(substring(plain_msg, i, 1)) else ord(" ") /* Ensures that msg parsing always ends on a space character */ in if char = ord(" ") then (if word_start_i < i then ( if msg.num_parameters < 0 then msg.command := safe_substring(plain_msg, word_start_i, i-1) else msg.parameters[msg.num_parameters] := safe_substring(plain_msg, word_start_i, i-1) ; msg.num_parameters := msg.num_parameters + 1) ; word_start_i := i + 1) else if char = ord(":") then ( msg.parameters[msg.num_parameters] := safe_substring(plain_msg, i+1, size(plain_msg)) ; msg.num_parameters := msg.num_parameters + 1 ; break ) end /* Rather return nil than an incorrect message */ ; if msg.command <> "NO CMD" & msg.num_parameters >= 0 then msg else nil end function format_message (msg: message): string = let var str := msg.command in for i := 0 to msg.num_parameters-2 do str := concat(concat(str, " "), msg.parameters[i]) /* Last parameter */ ; if msg.num_parameters then str := concat(concat(str, " :"), msg.parameters[msg.num_parameters-1]) ; str end /* Main loop */ var have_registered := 0 function read_next_message(): message = let var msg := "" function is_eoc(c: string): int = c = "\n" | c = "\013" /* Concat until newline */ in while peak_char() <> "\013" do ( msg := concat(msg, pop_char()) ; /*log_info(msg)*/ () ) /* Ignore newline */ ; pop_char(); pop_char() /* Parse message */ ; parse_message(msg) end function send_msg (msg: message) = ( print(format_message(msg)) ; print("\n") ; flush() ) function cmd0 (cmd: string): message = message { command = cmd , parameters = str_arr[0] of "" , num_parameters = 0 } function cmd1 (cmd: string, param1: string): message = message { command = cmd , parameters = str_arr[1] of param1 , num_parameters = 1 } function cmd2 (cmd: string, param1: string, param2:string): message = let var params := str_arr[2] of param1 in params[1] := param2 ; message { command = cmd , parameters = params , num_parameters = 2 } end function wait_for_msg (cmd: string): message = let var msg := read_next_message() in if msg.command <> cmd then wait_for_msg(cmd) else msg end function ping_respond () = let var msg := wait_for_msg("PING") in send_msg(cmd1("PONG", msg.parameters[0])) end function quit(quit_msg: string) = ( log_info(concat("Quitting: ", quit_msg)) ; send_msg(cmd1("QUIT", quit_msg)) ; exit(0) ) function log_info(info: string) = if have_registered then send_msg(cmd2("PRIVMSG", "Jmaa", info)) function is_char_digit(c: int): int = ord("0") <= c & c <= ord("9") function is_number_string(c: string): int = let var derp := 1 in for i := 0 to size(c) - 1 do if not(is_char_digit(ord(substring(c, i, 1)))) then ( derp := 0 ; break ) ; derp end function main_loop () = let var msg := read_next_message() in if msg = nil then log_info("Could not parse message!") /*; quit("Could not parse message") )*/ /* Handle number messages */ else if is_number_string(msg.command) then () /* Ignore */ /* Handle pings */ else if msg.command = "PING" then send_msg(cmd1("PONG", msg.parameters[0])) /* Handle yo */ else if msg.command = "PRIVMSG" & msg.parameters[1] = "yo" then send_msg(cmd2("PRIVMSG", msg.parameters[0], "Yo yourself")) /* Handle ERROR messages */ else if msg.command = "ERROR" then quit("Got ERROR message") /* Do nothing */ else () /*log_info(concat("Got message, but did nothing: ", format_message(msg)))*/ /* THE RIDE NEVER ENDS */ ; main_loop() end in () /* Establish connection */ /* At this point, we can register */ ; print("USER tiger-bot * * : yo\n") ; print("NICK TigerBot\n") ; flush() /* Wait for first PING, and respond to it */ ; ping_respond() /* Wait for 001; so we know that we have been connected and * registered */ ; have_registered := 1 ; wait_for_msg("001") /* Connect to channel and greet */ ; send_msg(cmd1("JOIN", "#bot-test")) ; send_msg(cmd2("PRIVMSG", "#bot-test", "Hello, how's it going?")) /* Start normal connection */ ; main_loop() /* Exit! */ ; quit("Nothing more to say") end