313 lines
6.3 KiB
Awk
Executable File
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 "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;
|
|
}
|
|
}
|
|
}
|
|
|