#!/usr/bin/awk -f # # ZyklonB eval plugin, LISP-like expression evaluator # # Copyright 2013, 2014 Přemysl Janouch. All rights reserved. # See the file LICENSE for licensing information. # BEGIN \ { RS = "\r" ORS = "\r\n" IGNORECASE = 1 srand() prefix = get_config("prefix") print "ZYKLONB register" fflush("") # All functions have to be in this particular array min_args["int"] = 1 min_args["+"] = 1 min_args["-"] = 1 min_args["*"] = 1 min_args["/"] = 1 min_args["%"] = 1 min_args["^"] = 1 min_args["**"] = 1 min_args["exp"] = 1 min_args["sin"] = 1 min_args["cos"] = 1 min_args["atan2"] = 2 min_args["log"] = 1 min_args["rand"] = 0 min_args["sqrt"] = 1 min_args["pi"] = 0 min_args["e"] = 0 min_args["min"] = 1 min_args["max"] = 1 # Whereas here their presence is only optional max_args["int"] = 1 max_args["sin"] = 1 max_args["cos"] = 1 max_args["atan2"] = 2 max_args["log"] = 1 max_args["rand"] = 0 max_args["sqrt"] = 1 max_args["pi"] = 0 max_args["e"] = 0 } { parse($0) } msg_command == "PRIVMSG" \ { # Context = either channel or user nickname match(msg_prefix, /^[^!]+/) ctx = substr(msg_prefix, RSTART, RLENGTH) if (msg_param[0] ~ /^[#&!+]/) { ctxquote = ctx ": " ctx = msg_param[0] } else ctxquote = "" if (substr(msg_param[1], 1, length(prefix)) == prefix) { keyword = "eval" text = substr(msg_param[1], 1 + length(prefix)) if (match(text, "^" keyword "([^A-Za-z0-9].*|$)")) process_request(substr(text, 1 + length(keyword))) } } { fflush("") } function pmrespond (text) { print "PRIVMSG " ctx " :" ctxquote text } function process_request (input, res, x) { delete funs delete accumulator delete n_args res = "" fun_top = 0 funs[0] = "" accumulator[0] = 0 n_args[0] = 0 if (match(input, "^[ \t]*")) input = substr(input, RLENGTH + 1) if (input == "") res = "expression missing" while (res == "" && input != "") { if (match(input, "^-?[0-9]+\\.?[0-9]*")) { x = substr(input, RSTART, RLENGTH) input = substr(input, RLENGTH + 1) match(input, "^ *") input = substr(input, RLENGTH + 1) res = process_argument(x) } else if (match(input, "^[(]([^ ()]+)")) { x = substr(input, RSTART + 1, RLENGTH - 1) input = substr(input, RLENGTH + 1) match(input, "^ *") input = substr(input, RLENGTH + 1) if (!(x in min_args)) { res = "undefined function '" x "'" } else { fun_top++ funs[fun_top] = x accumulator[fun_top] = 636363 n_args[fun_top] = 0 } } else if (match(input, "^[)] *")) { input = substr(input, RLENGTH + 1) res = process_end() } else res = "invalid input at '" substr(input, 1, 10) "...'" } if (res == "") { if (fun_top != 0) res = "unclosed '" funs[fun_top] "'" else if (n_args[0] != 1) res = "internal error, expected one result" \ ", got " n_args[0] " instead" } if (res == "") pmrespond(accumulator[0]) else pmrespond(res) } function process_argument (arg) { if (fun_top == 0) { if (n_args[0]++ != 0) return "too many results, I only expect one" accumulator[0] = arg return "" } fun = funs[fun_top] if (fun in max_args && max_args[fun] <= n_args[fun_top]) return "too many operands for " fun if (fun == "int") { accumulator[fun_top] = int(arg) } else if (fun == "+") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else accumulator[fun_top] += arg } else if (fun == "-") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else accumulator[fun_top] -= arg } else if (fun == "*") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else accumulator[fun_top] *= arg } else if (fun == "/") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else if (arg == 0) return "division by zero" else accumulator[fun_top] /= arg } else if (fun == "%") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else if (arg == 0) return "division by zero" else accumulator[fun_top] %= arg } else if (fun == "^" || fun == "**" || fun == "exp") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else accumulator[fun_top] ^= arg } else if (fun == "sin") { accumulator[fun_top] = sin(arg) } else if (fun == "cos") { accumulator[fun_top] = cos(arg) } else if (fun == "atan2") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else accumulator[fun_top] = atan2(accumulator[fun_top], arg) } else if (fun == "log") { accumulator[fun_top] = log(arg) } else if (fun == "rand") { # Just for completeness, execution never gets here } else if (fun == "sqrt") { accumulator[fun_top] = sqrt(arg) } else if (fun == "min") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else if (accumulator[fun_top] > arg) accumulator[fun_top] = arg } else if (fun == "max") { if (n_args[fun_top] == 0) accumulator[fun_top] = arg else if (accumulator[fun_top] < arg) accumulator[fun_top] = arg } else return "internal error, unhandled operands for " fun n_args[fun_top]++ return "" } function process_end () { if (fun_top <= 0) return "extraneous ')'" fun = funs[fun_top] if (!(fun in min_args)) return "internal error, unhandled ')' for '" fun "'" if (min_args[fun] > n_args[fun_top]) return "not enough operands for '" fun "'" # There's no 'init' function to do it in if (fun == "rand") accumulator[fun_top] = rand() else if (fun == "pi") accumulator[fun_top] = 3.141592653589793 else if (fun == "e") accumulator[fun_top] = 2.718281828459045 return process_argument(accumulator[fun_top--]) } function get_config (key) { print "ZYKLONB get_config :" key fflush("") getline parse($0) return msg_param[0] } function parse (line, s, n, id, token) { s = 1 id = 0 # NAWK only uses the first character of RS if (line ~ /^\n/) line = substr(line, 2) msg_prefix = "" msg_command = "" delete msg_param n = match(substr(line, s), / |$/) while (n) { token = substr(line, s, n - 1) if (token ~ /^:/) { if (s == 1) msg_prefix = substr(token, 2) else { msg_param[id] = substr(line, s + 1) break } } else if (!msg_command) msg_command = toupper(token) else msg_param[id++] = token s = s + n n = index(substr(line, s), " ") if (!n) { n = length(substr(line, s)) + 1 if (n == 1) break; } } }