xK/plugins/xB/eval
2021-08-06 17:18:06 +02:00

313 lines
6.3 KiB
Awk
Executable File

#!/usr/bin/awk -f
#
# xB eval plugin, LISP-like expression evaluator
#
# Copyright 2013, 2014 Přemysl Eric Janouch
# See the file LICENSE for licensing information.
#
BEGIN \
{
RS = "\r"
ORS = "\r\n"
IGNORECASE = 1
srand()
prefix = get_config("prefix")
print "XB 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 "XB 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;
}
}
}