aboutsummaryrefslogtreecommitdiff
path: root/plugins/zyklonb/eval
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/zyklonb/eval')
-rwxr-xr-xplugins/zyklonb/eval312
1 files changed, 312 insertions, 0 deletions
diff --git a/plugins/zyklonb/eval b/plugins/zyklonb/eval
new file mode 100755
index 0000000..982d78b
--- /dev/null
+++ b/plugins/zyklonb/eval
@@ -0,0 +1,312 @@
+#!/usr/bin/awk -f
+#
+# ZyklonB eval plugin, LISP-like expression evaluator
+#
+# Copyright 2013, 2014 Přemysl Janouch
+# 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] ~ /^[#&!+]/)
+ {
+ ctx_quote = ctx ": "
+ ctx = msg_param[0]
+ }
+ else
+ ctx_quote = ""
+
+
+ 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 " :" ctx_quote 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;
+ }
+ }
+}
+