// Examples
/*
-- Will call the proc for all computers in the world, thats dir is 2.
CALL ex_act(1) ON /obj/machinery/computer IN world WHERE dir == 2
-- Will open a window with a list of all the closets in the world, with a link to VV them.
SELECT /obj/structure/closet/secure_closet/security/cargo IN world WHERE icon_off == "secoff"
-- Will change all the tube lights to green
UPDATE /obj/machinery/light IN world SET color = "#0F0" WHERE icon_state == "tube1"
-- Will delete all pickaxes. "IN world" is not required.
DELETE /obj/item/pickaxe
-- Will flicker the lights once, then turn all mobs green. The semicolon is important to separate the consecutive querys, but is not required for standard one-query use
CALL flicker(1) ON /obj/machinery/light; UPDATE /mob SET color = "#00cc00"
--You can use operators other than ==, such as >, <=, != and etc..
*/
/client/proc/SDQL2_query()
set category = "Debug"
if(!check_rights(R_PROCCALL)) //Shouldn't happen... but just to be safe.
message_admins("ERROR: Non-admin [key_name_admin(usr)] attempted to execute a SDQL query!")
log_admin("Non-admin [key_name(usr)] attempted to execute a SDQL query!")
var/query_text = input("SDQL2 query") as message
if(!query_text || length(query_text) < 1)
return
// to_chat(world, query_text)
var/list/query_list = SDQL2_tokenize(query_text)
if(!query_list || query_list.len < 1)
return
var/list/querys = SDQL_parse(query_list)
if(!querys || querys.len < 1)
return
var/query_log = "executed SDQL query: \"[query_text]\"."
message_admins("[key_name_admin(usr)] [query_log]")
query_log = "[key_name(usr)] [query_log]"
log_admin(query_log)
try
for(var/list/query_tree in querys)
var/list/from_objs = list()
var/list/select_types = list()
switch(query_tree[1])
if("explain")
SDQL_testout(query_tree["explain"])
return
if("call")
if("on" in query_tree)
select_types = query_tree["on"]
else
return
if("select", "delete", "update")
select_types = query_tree[query_tree[1]]
from_objs = SDQL_from_objs(query_tree["from"])
var/list/objs = list()
for(var/type in select_types)
var/char = copytext(type, 1, 2)
if(char == "/" || char == "*")
for(var/from in from_objs)
objs += SDQL_get_all(type, from)
else if(char == "'" || char == "\"")
objs += locate(copytext(type, 2, length(type)))
if("where" in query_tree)
var/objs_temp = objs
objs = list()
for(var/d in objs_temp)
if(SDQL_expression(d, query_tree["where"]))
objs += d
switch(query_tree[1])
if("call")
for(var/d in objs)
SDQL_var(d, query_tree["call"][1], source = d)
if("delete")
for(var/d in objs)
if(istype(d, /datum))
var/datum/D = d
if(!D.can_vv_delete())
to_chat(usr, "[D] rejected your deletion")
continue
qdel(d)
if("select")
var/text = ""
for(var/o in objs)
var/datum/t = o
text += "\ref[t]"
if(istype(t, /atom))
var/atom/a = t
if(a.x)
text += ": [t] at ([a.x], [a.y], [a.z])
"
else if(a.loc && a.loc.x)
text += ": [t] in [a.loc] at ([a.loc.x], [a.loc.y], [a.loc.z])
"
else
text += ": [t]
"
else
text += ": [t]
"
usr << browse(text, "window=SDQL-result")
if("update")
if("set" in query_tree)
var/list/set_list = query_tree["set"]
for(var/d in objs)
for(var/list/sets in set_list)
var/datum/temp = d
var/i = 0
for(var/v in sets)
if(++i == sets.len)
if(istype(temp, /turf) && (v == "x" || v == "y" || v == "z"))
continue
if(!temp.vv_edit_var(v, SDQL_expression(d, set_list[sets])))
to_chat(usr, "[temp] rejected your varedit.")
break
if(temp.vars.Find(v) && (istype(temp.vars[v], /datum) || istype(temp.vars[v], /client)))
temp = temp.vars[v]
else
break
catch(var/exception/e)
to_chat(usr, "An exception has occured during the execution of your query and your query has been aborted.")
to_chat(usr, " [e.name]")
to_chat(usr, " at: [e.file]:[e.line]")
/proc/SDQL_parse(list/query_list)
var/datum/SDQL_parser/parser = new()
var/list/querys = list()
var/list/query_tree = list()
var/pos = 1
var/querys_pos = 1
var/do_parse = 0
for(var/val in query_list)
if(val == ";")
do_parse = 1
else if(pos >= query_list.len)
query_tree += val
do_parse = 1
if(do_parse)
parser.query = query_tree
var/list/parsed_tree
parsed_tree = parser.parse()
if(parsed_tree.len > 0)
querys.len = querys_pos
querys[querys_pos] = parsed_tree
querys_pos++
else //There was an error so don't run anything, and tell the user which query has errored.
to_chat(usr, "Parsing error on [querys_pos]\th query. Nothing was executed.")
return list()
query_tree = list()
do_parse = 0
else
query_tree += val
pos++
qdel(parser)
return querys
/proc/SDQL_testout(list/query_tree, indent = 0)
var/spaces = ""
for(var/s = 0, s < indent, s++)
spaces += " "
for(var/item in query_tree)
if(istype(item, /list))
to_chat(usr, "[spaces](")
SDQL_testout(item, indent + 1)
to_chat(usr, "[spaces])")
else
to_chat(usr, "[spaces][item]")
if(!isnum(item) && query_tree[item])
if(istype(query_tree[item], /list))
to_chat(usr, "[spaces] (")
SDQL_testout(query_tree[item], indent + 2)
to_chat(usr, "[spaces] )")
else
to_chat(usr, "[spaces] [query_tree[item]]")
/proc/SDQL_from_objs(list/tree)
if("world" in tree)
return list(world)
var/list/out = list()
for(var/type in tree)
var/char = copytext(type, 1, 2)
if(char == "/")
out += SDQL_get_all(type, world)
else if(char == "'" || char == "\"")
out += locate(copytext(type, 2, length(type)))
return out
/proc/SDQL_get_all(type, location)
var/list/out = list()
if(type == "*")
for(var/datum/d in location)
out += d
return out
type = text2path(type)
if(ispath(type, /mob))
for(var/mob/d in location)
if(istype(d, type))
out += d
else if(ispath(type, /turf))
for(var/turf/d in location)
if(istype(d, type))
out += d
else if(ispath(type, /obj))
for(var/obj/d in location)
if(istype(d, type))
out += d
else if(ispath(type, /area))
for(var/area/d in location)
if(istype(d, type))
out += d
else if(ispath(type, /atom))
for(var/atom/d in location)
if(istype(d, type))
out += d
else if(ispath(type, /client))
for(var/client/C)
if((location != world) && !(C.mob in location))
continue
out += C
else if(location == world)
for(var/datum/d)
if(istype(d, type))
out += d
else
for(var/datum/d in location)
if(istype(d, type))
out += d
return out
/proc/SDQL_expression(datum/object, list/expression, start = 1)
var/result = 0
var/val
for(var/i = start, i <= expression.len, i++)
var/op = ""
if(i > start)
op = expression[i]
i++
var/list/ret = SDQL_value(object, expression, i)
val = ret["val"]
i = ret["i"]
if(op != "")
switch(op)
if("+")
result += val
if("-")
result -= val
if("*")
result *= val
if("/")
result /= val
if("&")
result &= val
if("|")
result |= val
if("^")
result ^= val
if("=", "==")
result = (result == val)
if("!=", "<>")
result = (result != val)
if("<")
result = (result < val)
if("<=")
result = (result <= val)
if(">")
result = (result > val)
if(">=")
result = (result >= val)
if("and", "&&")
result = (result && val)
if("or", "||")
result = (result || val)
else
to_chat(usr, "SDQL2: Unknown op [op]")
result = null
else
result = val
return result
/proc/SDQL_value(datum/object, list/expression, start = 1)
var/i = start
var/val = null
if(i > expression.len)
return list("val" = null, "i" = i)
if(istype(expression[i], /list))
val = SDQL_expression(object, expression[i])
else if(expression[i] == "!")
var/list/ret = SDQL_value(object, expression, i + 1)
val = !ret["val"]
i = ret["i"]
else if(expression[i] == "~")
var/list/ret = SDQL_value(object, expression, i + 1)
val = ~ret["val"]
i = ret["i"]
else if(expression[i] == "-")
var/list/ret = SDQL_value(object, expression, i + 1)
val = -ret["val"]
i = ret["i"]
else if(expression[i] == "null")
val = null
else if(isnum(expression[i]))
val = expression[i]
else if(copytext(expression[i], 1, 2) in list("'", "\""))
val = copytext(expression[i], 2, length(expression[i]))
else if(expression[i] == "{")
var/list/expressions_list = expression[++i]
val = list()
for(var/list/expression_list in expressions_list)
val += SDQL_expression(object, expression_list)
else
val = SDQL_var(object, expression, i, object)
i = expression.len
return list("val" = val, "i" = i)
/proc/SDQL_var(datum/object, list/expression, start = 1, source)
var/v
var/long = start < expression.len
if(object == world && long && expression[start + 1] == ".")
to_chat(usr, "Sorry, but global variables are not supported at the moment.")
return null
if(expression[start] == "\[" && long)
if(lowertext(copytext(expression[start + 1], 1, 3)) != "0x")
to_chat(usr, "Invalid ref syntax: [expression[start + 1]]")
return null
v = locate("\[[expression[start + 1]]\]")
if(!v)
to_chat(usr, "Invalid ref: [expression[start + 1]]")
return null
start++
else if((!long || expression[start + 1] == "." || expression[start + 1] == "\[") && (expression[start] in object.vars))
v = object.vars[expression[start]]
else if(long && expression[start + 1] == ":" && hascall(object, expression[start]))
v = expression[start]
else if(!long || expression[start + 1] == ".")
switch(expression[start])
if("usr")
v = usr
if("src")
v = source
if("marked")
if(usr.client && usr.client.holder && usr.client.holder.marked_datum)
v = usr.client.holder.marked_datum
else
return null
if("global")
v = world // World is mostly a token, really.
else
return null
else if(object == world)
v = expression[start]
if(long)
if(expression[start + 1] == ".")
return SDQL_var(v, expression[start + 2], source = source)
else if(expression[start + 1] == ":")
return SDQL_function(object, v, expression[start + 2], source)
else if(expression[start + 1] == "\[" && islist(v))
var/list/L = v
var/index = SDQL_expression(source, expression[start + 2])
if(isnum(index) && (!ISINTEGER(index) || L.len < index))
to_chat(world, "Invalid list index: [index]")
return null
return L[index]
return v
/proc/SDQL_function(var/datum/object, var/procname, var/list/arguments, source)
var/list/new_args = list()
for(var/arg in arguments)
new_args[++new_args.len] = SDQL_expression(source, arg)
if(object == world) // Global proc.
procname = "/proc/[procname]"
return (WrapAdminProcCall(GLOBAL_PROC, procname, new_args))
return (WrapAdminProcCall(object, procname, new_args))
/proc/SDQL2_tokenize(query_text)
var/list/whitespace = list(" ", "\n", "\t")
var/list/single = list("(", ")", ",", "+", "-", ".", ";", "\[", "\]", "{", "}")
var/list/multi = list(
"=" = list("", "="),
"<" = list("", "=", ">"),
">" = list("", "="),
"!" = list("", "="))
var/word = ""
var/list/query_list = list()
var/len = length(query_text)
for(var/i = 1, i <= len, i++)
var/char = copytext(query_text, i, i + 1)
if(char in whitespace)
if(word != "")
query_list += word
word = ""
else if(char in single)
if(word != "")
query_list += word
word = ""
query_list += char
else if(char in multi)
if(word != "")
query_list += word
word = ""
var/char2 = copytext(query_text, i + 1, i + 2)
if(char2 in multi[char])
query_list += "[char][char2]"
i++
else
query_list += char
else if(char == "'")
if(word != "")
to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unexpected ' in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.")
return null
word = "'"
for(i++, i <= len, i++)
char = copytext(query_text, i, i + 1)
if(char == "'")
if(copytext(query_text, i + 1, i + 2) == "'")
word += "'"
i++
else
break
else
word += char
if(i > len)
to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unmatched ' in query: \"[query_text]\". Please check your syntax, and try again.")
return null
query_list += "[word]'"
word = ""
else if(char == "\"")
if(word != "")
to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unexpected \" in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.")
return null
word = "\""
for(i++, i <= len, i++)
char = copytext(query_text, i, i + 1)
if(char == "\"")
if(copytext(query_text, i + 1, i + 2) == "'")
word += "\""
i++
else
break
else
word += char
if(i > len)
to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unmatched \" in query: \"[query_text]\". Please check your syntax, and try again.")
return null
query_list += "[word]\""
word = ""
else
word += char
if(word != "")
query_list += word
return query_list