diff --git a/baystation12.dme b/baystation12.dme
index 39a562a6a3b..87314513017 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -679,6 +679,9 @@
#include "code\modules\admin\verbs\possess.dm"
#include "code\modules\admin\verbs\pray.dm"
#include "code\modules\admin\verbs\randomverbs.dm"
+#include "code\modules\admin\verbs\SDQL.dm"
+#include "code\modules\admin\verbs\SDQL_2.dm"
+#include "code\modules\admin\verbs\SDQL_2_parser.dm"
#include "code\modules\admin\verbs\striketeam.dm"
#include "code\modules\admin\verbs\striketeam_syndicate.dm"
#include "code\modules\admin\verbs\ticklag.dm"
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index dd330ec00c0..5fbcf52dd6a 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -69,7 +69,9 @@ var/list/admin_verbs_admin = list(
/client/proc/check_customitem_activity,
/client/proc/man_up,
/client/proc/global_man_up,
- /client/proc/response_team // Response Teams admin verb
+ /client/proc/response_team, // Response Teams admin verb
+ /client/proc/SDQL_query,
+ /client/proc/SDQL2_query
)
var/list/admin_verbs_ban = list(
/client/proc/unban_panel,
diff --git a/code/modules/admin/verbs/SDQL.dm b/code/modules/admin/verbs/SDQL.dm
new file mode 100644
index 00000000000..d467cf71904
--- /dev/null
+++ b/code/modules/admin/verbs/SDQL.dm
@@ -0,0 +1,497 @@
+
+//Structured Datum Query Language. Basically SQL meets BYOND objects.
+
+//Note: For use in BS12, need text_starts_with proc, and to modify the action on select to use BS12's object edit command(s).
+
+/client/proc/SDQL_query(query_text as message)
+ set category = "Admin"
+ if(!check_rights(R_ADMIN)) //Shouldn't happen... but just to be safe.
+ message_admins("\red ERROR: Non-admin [usr.key] attempted to execute a SDQL query!")
+ log_admin("Non-admin [usr.key] attempted to execute a SDQL query!")
+
+ var/list/query_list = SDQL_tokenize(query_text)
+
+ if(query_list.len < 2)
+ if(query_list.len > 0)
+ usr << "\red SDQL: Too few discrete tokens in query \"[query_text]\". Please check your syntax and try again."
+ return
+
+ if(!(lowertext(query_list[1]) in list("select", "delete", "update")))
+ usr << "\red SDQL: Unknown query type: \"[query_list[1]]\" in query \"[query_text]\". Please check your syntax and try again."
+ return
+
+ var/list/types = list()
+
+ var/i
+ for(i = 2; i <= query_list.len; i += 2)
+ types += query_list[i]
+
+ if(i + 1 >= query_list.len || query_list[i + 1] != ",")
+ break
+
+ i++
+
+ var/list/from = list()
+
+ if(i <= query_list.len)
+ if(lowertext(query_list[i]) in list("from", "in"))
+ for(i++; i <= query_list.len; i += 2)
+ from += query_list[i]
+
+ if(i + 1 >= query_list.len || query_list[i + 1] != ",")
+ break
+
+ i++
+
+ if(from.len < 1)
+ from += "world"
+
+ var/list/set_vars = list()
+
+ if(lowertext(query_list[1]) == "update")
+ if(i <= query_list.len && lowertext(query_list[i]) == "set")
+ for(i++; i <= query_list.len; i++)
+ if(i + 2 <= query_list.len && query_list[i + 1] == "=")
+ set_vars += query_list[i]
+ set_vars[query_list[i]] = query_list[i + 2]
+
+ else
+ usr << "\red SDQL: Invalid set parameter in query \"[query_text]\". Please check your syntax and try again."
+ return
+
+ i += 3
+
+ if(i >= query_list.len || query_list[i] != ",")
+ break
+
+ if(set_vars.len < 1)
+ usr << "\red SDQL: Invalid or missing set in query \"[query_text]\". Please check your syntax and try again."
+ return
+
+ var/list/where = list()
+
+ if(i <= query_list.len && lowertext(query_list[i]) == "where")
+ where = query_list.Copy(i + 1)
+
+ var/list/from_objs = list()
+ if("world" in from)
+ from_objs += world
+ else
+ for(var/f in from)
+ if(copytext(f, 1, 2) == "'" || copytext(f, 1, 2) == "\"")
+ from_objs += locate(copytext(f, 2, length(f)))
+ else if(copytext(f, 1, 2) != "/")
+ from_objs += locate(f)
+ else
+ var/f2 = text2path(f)
+ if(text_starts_with(f, "/mob"))
+ for(var/mob/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/turf/space"))
+ for(var/turf/space/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/turf/simulated"))
+ for(var/turf/simulated/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/turf/unsimulated"))
+ for(var/turf/unsimulated/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/turf"))
+ for(var/turf/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/area"))
+ for(var/area/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/obj/item"))
+ for(var/obj/item/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/obj/machinery"))
+ for(var/obj/machinery/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/obj"))
+ for(var/obj/m in world)
+ if(istype(m, f2))
+ from_objs += m
+
+ else if(text_starts_with(f, "/atom"))
+ for(var/atom/m in world)
+ if(istype(m, f2))
+ from_objs += m
+/*
+ else
+ for(var/datum/m in world)
+ if(istype(m, f2))
+ from_objs += m
+*/
+
+ var/list/objs = list()
+
+ for(var/from_obj in from_objs)
+ if("*" in types)
+ objs += from_obj:contents
+ else
+ for(var/f in types)
+ if(copytext(f, 1, 2) == "'" || copytext(f, 1, 2) == "\"")
+ objs += locate(copytext(f, 2, length(f))) in from_obj
+ else if(copytext(f, 1, 2) != "/")
+ objs += locate(f) in from_obj
+ else
+ var/f2 = text2path(f)
+ if(text_starts_with(f, "/mob"))
+ for(var/mob/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/turf/space"))
+ for(var/turf/space/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/turf/simulated"))
+ for(var/turf/simulated/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/turf/unsimulated"))
+ for(var/turf/unsimulated/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/turf"))
+ for(var/turf/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/area"))
+ for(var/area/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/obj/item"))
+ for(var/obj/item/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/obj/machinery"))
+ for(var/obj/machinery/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/obj"))
+ for(var/obj/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else if(text_starts_with(f, "/atom"))
+ for(var/atom/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+ else
+ for(var/datum/m in from_obj)
+ if(istype(m, f2))
+ objs += m
+
+
+ for(var/datum/t in objs)
+ var/currently_false = 0
+ for(i = 1, i - 1 < where.len, i++)
+ var/v = where[i++]
+ var/compare_op = where[i++]
+ if(!(compare_op in list("==", "=", "<>", "<", ">", "<=", ">=", "!=")))
+ usr << "\red SDQL: Unknown comparison operator [compare_op] in where clause following [v] in query \"[query_text]\". Please check your syntax and try again."
+ return
+
+ var/j
+ for(j = i, j <= where.len, j++)
+ if(lowertext(where[j]) in list("and", "or", ";"))
+ break
+
+ if(!currently_false)
+ var/value = SDQL_text2value(t, v)
+ var/result = SDQL_evaluate(t, where.Copy(i, j))
+
+ switch(compare_op)
+ if("=", "==")
+ currently_false = !(value == result)
+
+ if("!=", "<>")
+ currently_false = !(value != result)
+
+ if("<")
+ currently_false = !(value < result)
+
+ if(">")
+ currently_false = !(value > result)
+
+ if("<=")
+ currently_false = !(value <= result)
+
+ if(">=")
+ currently_false = !(value >= result)
+
+
+ if(j > where.len || lowertext(where[j]) == ";")
+ break
+ else if(lowertext(where[j]) == "or")
+ if(currently_false)
+ currently_false = 0
+ else
+ break
+
+ i = j
+
+ if(currently_false)
+ objs -= t
+
+
+
+ usr << "\blue SQDL Query: [query_text]"
+ message_admins("[usr] executed SDQL query: \"[query_text]\".")
+/*
+ for(var/t in types)
+ usr << "Type: [t]"
+
+ for(var/t in from)
+ usr << "From: [t]"
+
+ for(var/t in set_vars)
+ usr << "Set: [t] = [set_vars[t]]"
+
+ if(where.len)
+ var/where_str = ""
+ for(var/t in where)
+ where_str += "[t] "
+
+ usr << "Where: [where_str]"
+
+ usr << "From objects:"
+ for(var/datum/t in from_objs)
+ usr << t
+
+ usr << "Objects:"
+ for(var/datum/t in objs)
+ usr << t
+*/
+ switch(lowertext(query_list[1]))
+ if("delete")
+ for(var/datum/t in objs)
+ del t
+
+ if("update")
+ for(var/datum/t in objs)
+ objs[t] = list()
+ for(var/v in set_vars)
+ if(v in t.vars)
+ objs[t][v] = SDQL_text2value(t, set_vars[v])
+
+ for(var/datum/t in objs)
+ for(var/v in objs[t])
+ t.vars[v] = objs[t][v]
+
+ if("select")
+ var/text = ""
+ for(var/datum/t in objs)
+ if(istype(t, /atom))
+ var/atom/a = t
+
+ if(a.x)
+ text += "\ref[t]: [t] at ([a.x], [a.y], [a.z])
"
+
+ else if(a.loc && a.loc.x)
+ text += "\ref[t]: [t] in [a.loc] at ([a.loc.x], [a.loc.y], [a.loc.z])
"
+
+ else
+ text += "\ref[t]: [t]
"
+
+ else
+ text += "\ref[t]: [t]
"
+
+ //text += "[t]
"
+ usr << browse(text, "window=sdql_result")
+
+
+/client/Topic(href,href_list[],hsrc)
+ if(href_list["SDQL_select"])
+ debug_variables(locate(href_list["SDQL_select"]))
+
+ ..()
+
+
+/proc/SDQL_evaluate(datum/object, list/equation)
+ if(equation.len == 0)
+ return null
+
+ else if(equation.len == 1)
+ return SDQL_text2value(object, equation[1])
+
+ else if(equation[1] == "!")
+ return !SDQL_evaluate(object, equation.Copy(2))
+
+ else if(equation[1] == "-")
+ return -SDQL_evaluate(object, equation.Copy(2))
+
+
+ else
+ usr << "\red SDQL: Sorry, equations not yet supported :("
+ return null
+
+
+/proc/SDQL_text2value(datum/object, text)
+ if(text2num(text) != null)
+ return text2num(text)
+ else if(text == "null")
+ return null
+ else if(copytext(text, 1, 2) == "'" || copytext(text, 1, 2) == "\"" )
+ return copytext(text, 2, length(text))
+ else if(copytext(text, 1, 2) == "/")
+ return text2path(text)
+ else
+ if(findtext(text, "."))
+ var/split = findtext(text, ".")
+ var/v = copytext(text, 1, split)
+
+ if((v in object.vars) && istype(object.vars[v], /datum))
+ return SDQL_text2value(object.vars[v], copytext(text, split + 1))
+ else
+ return null
+
+ else
+ if(text in object.vars)
+ return object.vars[text]
+ else
+ return null
+
+
+/proc/text_starts_with(text, start)
+ if(copytext(text, 1, length(start) + 1) == start)
+ return 1
+ else
+ return 0
+
+
+
+
+
+/proc/SDQL_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 != "")
+ usr << "\red SDQL: 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)
+ usr << "\red SDQL: 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 != "")
+ usr << "\red SDQL: 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)
+ usr << "\red SDQL: 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
diff --git a/code/modules/admin/verbs/SDQL_2.dm b/code/modules/admin/verbs/SDQL_2.dm
new file mode 100644
index 00000000000..6627d926f39
--- /dev/null
+++ b/code/modules/admin/verbs/SDQL_2.dm
@@ -0,0 +1,426 @@
+
+
+/client/proc/SDQL2_query(query_text as message)
+ set category = "Admin"
+ if(!check_rights(R_ADMIN)) //Shouldn't happen... but just to be safe.
+ message_admins("\red ERROR: Non-admin [usr.key] attempted to execute a SDQL query!")
+ log_admin("Non-admin [usr.key] attempted to execute a SDQL query!")
+
+ if(!query_text || length(query_text) < 1)
+ return
+
+ //world << query_text
+
+ var/list/query_list = SDQL2_tokenize(query_text)
+
+ if(!query_list || query_list.len < 1)
+ return
+
+ var/list/query_tree = SDQL_parse(query_list)
+
+ if(query_tree.len < 1)
+ return
+
+ 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/datum/d in objs_temp)
+ if(SDQL_expression(d, query_tree["where"]))
+ objs += d
+
+ //usr << "Query: [query_text]"
+ message_admins("[usr] executed SDQL query: \"[query_text]\".")
+
+ switch(query_tree[1])
+ if("delete")
+ for(var/datum/d in objs)
+ del d
+
+ if("select")
+ var/text = ""
+ for(var/datum/t in objs)
+ if(istype(t, /atom))
+ var/atom/a = t
+
+ if(a.x)
+ text += "\ref[t]: [t] at ([a.x], [a.y], [a.z])
"
+
+ else if(a.loc && a.loc.x)
+ text += "\ref[t]: [t] in [a.loc] at ([a.loc.x], [a.loc.y], [a.loc.z])
"
+
+ else
+ text += "\ref[t]: [t]
"
+
+ else
+ text += "\ref[t]: [t]
"
+
+ usr << browse(text, "window=SDQL-result")
+
+ if("update")
+ if("set" in query_tree)
+ var/list/set_list = query_tree["set"]
+ for(var/datum/d in objs)
+ var/list/vals = list()
+ for(var/v in set_list)
+ if(v in d.vars)
+ vals += v
+ vals[v] = SDQL_expression(d, set_list[v])
+
+ if(istype(d, /turf))
+ for(var/v in vals)
+ if(v == "x" || v == "y" || v == "z")
+ continue
+
+ d.vars[v] = vals[v]
+
+ else
+ for(var/v in vals)
+ d.vars[v] = vals[v]
+
+
+
+
+
+/proc/SDQL_parse(list/query_list)
+ var/datum/SDQL_parser/parser = new(query_list)
+ var/list/query_tree = parser.parse()
+
+ del(parser)
+
+ return query_tree
+
+
+
+/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))
+ world << "[spaces]("
+ SDQL_testout(item, indent + 1)
+ world << "[spaces])"
+
+ else
+ world << "[spaces][item]"
+
+ if(!isnum(item) && query_tree[item])
+
+ if(istype(query_tree[item], /list))
+ world << "[spaces] ("
+ SDQL_testout(query_tree[item], indent + 2)
+ world << "[spaces] )"
+
+ else
+ world << "[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
+ 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
+ usr << "\red 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
+ val = SDQL_var(object, expression, i)
+ i = expression.len
+
+ return list("val" = val, "i" = i)
+
+/proc/SDQL_var(datum/object, list/expression, start = 1)
+
+ if(expression[start] in object.vars)
+
+ if(start < expression.len && expression[start + 1] == ".")
+ return SDQL_var(object.vars[expression[start]], expression[start + 2])
+
+ else
+ return object.vars[expression[start]]
+
+ else
+ return null
+
+/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 != "")
+ usr << "\red 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)
+ usr << "\red 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 != "")
+ usr << "\red 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)
+ usr << "\red 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
diff --git a/code/modules/admin/verbs/SDQL_2_parser.dm b/code/modules/admin/verbs/SDQL_2_parser.dm
new file mode 100644
index 00000000000..d1d30b33b5e
--- /dev/null
+++ b/code/modules/admin/verbs/SDQL_2_parser.dm
@@ -0,0 +1,531 @@
+//I'm pretty sure that this is a recursive [s]descent[/s] ascent parser.
+
+
+
+//Spec
+
+//////////
+//
+// query : select_query | delete_query | update_query | call_query | explain
+// explain : 'EXPLAIN' query
+//
+// select_query : 'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
+// delete_query : 'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
+// update_query : 'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression]
+// call_query : 'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]]
+//
+// select_list : select_item [',' select_list]
+// select_item : '*' | select_function | object_type
+// select_function : count_function
+// count_function : 'COUNT' '(' '*' ')' | 'COUNT' '(' object_types ')'
+//
+// from_list : from_item [',' from_list]
+// from_item : 'world' | object_type
+//
+// call_function : ['(' [arguments] ')']
+// arguments : expression [',' arguments]
+//
+// object_type : | string
+//
+// assignments : assignment, [',' assignments]
+// assignment : '=' expression
+// variable : | '.' variable
+//
+// bool_expression : expression comparitor expression [bool_operator bool_expression]
+// expression : ( unary_expression | '(' expression ')' | value ) [binary_operator expression]
+// unary_expression : unary_operator ( unary_expression | value | '(' expression ')' )
+// comparitor : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
+// value : variable | string | number | 'null'
+// unary_operator : '!' | '-' | '~'
+// binary_operator : comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^'
+// bool_operator : 'AND' | '&&' | 'OR' | '||'
+//
+// string : ''' ''' | '"' '"'
+// number :
+//
+//////////
+
+/datum/SDQL_parser
+ var/query_type
+ var/error = 0
+
+ var/list/query
+ var/list/tree
+
+ var/list/select_functions = list("count")
+ var/list/boolean_operators = list("and", "or", "&&", "||")
+ var/list/unary_operators = list("!", "-", "~")
+ var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^")
+ var/list/comparitors = list("=", "==", "!=", "<>", "<", "<=", ">", ">=")
+
+
+
+/datum/SDQL_parser/New(query_list)
+ query = query_list
+
+
+
+/datum/SDQL_parser/proc/parse_error(error_message)
+ error = 1
+ usr << "\red SQDL2 Parsing Error: [error_message]"
+ return query.len + 1
+
+/datum/SDQL_parser/proc/parse()
+ tree = list()
+ query(1, tree)
+
+ if(error)
+ return list()
+ else
+ return tree
+
+/datum/SDQL_parser/proc/token(i)
+ if(i <= query.len)
+ return query[i]
+
+ else
+ return null
+
+/datum/SDQL_parser/proc/tokens(i, num)
+ if(i + num <= query.len)
+ return query.Copy(i, i + num)
+
+ else
+ return null
+
+/datum/SDQL_parser/proc/tokenl(i)
+ return lowertext(token(i))
+
+
+
+/datum/SDQL_parser/proc
+
+//query: select_query | delete_query | update_query
+ query(i, list/node)
+ query_type = tokenl(i)
+
+ switch(query_type)
+ if("select")
+ select_query(i, node)
+
+ if("delete")
+ delete_query(i, node)
+
+ if("update")
+ update_query(i, node)
+
+ if("call")
+ call_query(i, node)
+
+ if("explain")
+ node += "explain"
+ node["explain"] = list()
+ query(i + 1, node["explain"])
+
+
+// select_query: 'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
+ select_query(i, list/node)
+ var/list/select = list()
+ i = select_list(i + 1, select)
+
+ node += "select"
+ node["select"] = select
+
+ var/list/from = list()
+ if(tokenl(i) in list("from", "in"))
+ i = from_list(i + 1, from)
+ else
+ from += "world"
+
+ node += "from"
+ node["from"] = from
+
+ if(tokenl(i) == "where")
+ var/list/where = list()
+ i = bool_expression(i + 1, where)
+
+ node += "where"
+ node["where"] = where
+
+ return i
+
+
+//delete_query: 'DELETE' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]
+ delete_query(i, list/node)
+ var/list/select = list()
+ i = select_list(i + 1, select)
+
+ node += "delete"
+ node["delete"] = select
+
+ var/list/from = list()
+ if(tokenl(i) in list("from", "in"))
+ i = from_list(i + 1, from)
+ else
+ from += "world"
+
+ node += "from"
+ node["from"] = from
+
+ if(tokenl(i) == "where")
+ var/list/where = list()
+ i = bool_expression(i + 1, where)
+
+ node += "where"
+ node["where"] = where
+
+ return i
+
+
+//update_query: 'UPDATE' select_list [('FROM' | 'IN') from_list] 'SET' assignments ['WHERE' bool_expression]
+ update_query(i, list/node)
+ var/list/select = list()
+ i = select_list(i + 1, select)
+
+ node += "update"
+ node["update"] = select
+
+ var/list/from = list()
+ if(tokenl(i) in list("from", "in"))
+ i = from_list(i + 1, from)
+ else
+ from += "world"
+
+ node += "from"
+ node["from"] = from
+
+ if(tokenl(i) != "set")
+ i = parse_error("UPDATE has misplaced SET")
+
+ var/list/set_assignments = list()
+ i = assignments(i + 1, set_assignments)
+
+ node += "set"
+ node["set"] = set_assignments
+
+ if(tokenl(i) == "where")
+ var/list/where = list()
+ i = bool_expression(i + 1, where)
+
+ node += "where"
+ node["where"] = where
+
+ return i
+
+
+//call_query: 'CALL' call_function ['ON' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression]]
+ call_query(i, list/node)
+ var/list/func = list()
+ i = call_function(i + 1, func)
+
+ node += "call"
+ node["call"] = func
+
+ if(tokenl(i) != "on")
+ return i
+
+ var/list/select = list()
+ i = select_list(i + 1, select)
+
+ node += "on"
+ node["on"] = select
+
+ var/list/from = list()
+ if(tokenl(i) in list("from", "in"))
+ i = from_list(i + 1, from)
+ else
+ from += "world"
+
+ node += "from"
+ node["from"] = from
+
+ if(tokenl(i) == "where")
+ var/list/where = list()
+ i = bool_expression(i + 1, where)
+
+ node += "where"
+ node["where"] = where
+
+ return i
+
+
+//select_list: select_item [',' select_list]
+ select_list(i, list/node)
+ i = select_item(i, node)
+
+ if(token(i) == ",")
+ i = select_list(i + 1, node)
+
+ return i
+
+
+//from_list: from_item [',' from_list]
+ from_list(i, list/node)
+ i = from_item(i, node)
+
+ if(token(i) == ",")
+ i = from_list(i + 1, node)
+
+ return i
+
+
+//assignments: assignment, [',' assignments]
+ assignments(i, list/node)
+ i = assignment(i, node)
+
+ if(token(i) == ",")
+ i = assignments(i + 1, node)
+
+ return i
+
+
+//select_item: '*' | select_function | object_type
+ select_item(i, list/node)
+
+ if(token(i) == "*")
+ node += "*"
+ i++
+
+ else if(tokenl(i) in select_functions)
+ i = select_function(i, node)
+
+ else
+ i = object_type(i, node)
+
+ return i
+
+
+//from_item: 'world' | object_type
+ from_item(i, list/node)
+
+ if(token(i) == "world")
+ node += "world"
+ i++
+
+ else
+ i = object_type(i, node)
+
+ return i
+
+
+//bool_expression: expression [bool_operator bool_expression]
+ bool_expression(i, list/node)
+
+ var/list/bool = list()
+ i = expression(i, bool)
+
+ node[++node.len] = bool
+
+ if(tokenl(i) in boolean_operators)
+ i = bool_operator(i, node)
+ i = bool_expression(i, node)
+
+ return i
+
+
+//assignment: '=' expression
+ assignment(i, list/node)
+
+ node += token(i)
+
+ if(token(i + 1) == "=")
+ var/varname = token(i)
+ node[varname] = list()
+
+ i = expression(i + 2, node[varname])
+
+ else
+ parse_error("Assignment expected, but no = found")
+
+ return i
+
+
+//variable: | '.' variable
+ variable(i, list/node)
+ var/list/L = list(token(i))
+ node[++node.len] = L
+
+ if(token(i + 1) == ".")
+ L += "."
+ i = variable(i + 2, L)
+
+ else
+ i++
+
+ return i
+
+
+//object_type: | string
+ object_type(i, list/node)
+
+ if(copytext(token(i), 1, 2) == "/")
+ node += token(i)
+
+ else
+ i = string(i, node)
+
+ return i + 1
+
+
+//comparitor: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
+ comparitor(i, list/node)
+
+ if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">="))
+ node += token(i)
+
+ else
+ parse_error("Unknown comparitor [token(i)]")
+
+ return i + 1
+
+
+//bool_operator: 'AND' | '&&' | 'OR' | '||'
+ bool_operator(i, list/node)
+
+ if(tokenl(i) in list("and", "or", "&&", "||"))
+ node += token(i)
+
+ else
+ parse_error("Unknown comparitor [token(i)]")
+
+ return i + 1
+
+
+//string: ''' ''' | '"' '"'
+ string(i, list/node)
+
+ if(copytext(token(i), 1, 2) in list("'", "\""))
+ node += token(i)
+
+ else
+ parse_error("Expected string but found '[token(i)]'")
+
+ return i + 1
+
+
+//call_function: ['(' [arguments] ')']
+ call_function(i, list/node)
+
+ parse_error("Sorry, function calls aren't available yet")
+
+ return i
+
+
+//select_function: count_function
+ select_function(i, list/node)
+
+ parse_error("Sorry, function calls aren't available yet")
+
+ return i
+
+
+//expression: ( unary_expression | '(' expression ')' | value ) [binary_operator expression]
+ expression(i, list/node)
+
+ if(token(i) in unary_operators)
+ i = unary_expression(i, node)
+
+ else if(token(i) == "(")
+ var/list/expr = list()
+
+ i = expression(i + 1, expr)
+
+ if(token(i) != ")")
+ parse_error("Missing ) at end of expression.")
+
+ else
+ i++
+
+ node[++node.len] = expr
+
+ else
+ i = value(i, node)
+
+ if(token(i) in binary_operators)
+ i = binary_operator(i, node)
+ i = expression(i, node)
+
+ else if(token(i) in comparitors)
+ i = binary_operator(i, node)
+
+ var/list/rhs = list()
+ i = expression(i, rhs)
+
+ node[++node.len] = rhs
+
+
+ return i
+
+
+//unary_expression: unary_operator ( unary_expression | value | '(' expression ')' )
+ unary_expression(i, list/node)
+
+ if(token(i) in unary_operators)
+ var/list/unary_exp = list()
+
+ unary_exp += token(i)
+ i++
+
+ if(token(i) in unary_operators)
+ i = unary_expression(i, unary_exp)
+
+ else if(token(i) == "(")
+ var/list/expr = list()
+
+ i = expression(i + 1, expr)
+
+ if(token(i) != ")")
+ parse_error("Missing ) at end of expression.")
+
+ else
+ i++
+
+ unary_exp[++unary_exp.len] = expr
+
+ else
+ i = value(i, unary_exp)
+
+ node[++node.len] = unary_exp
+
+
+ else
+ parse_error("Expected unary operator but found '[token(i)]'")
+
+ return i
+
+
+//binary_operator: comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^'
+ binary_operator(i, list/node)
+
+ if(token(i) in (binary_operators + comparitors))
+ node += token(i)
+
+ else
+ parse_error("Unknown binary operator [token(i)]")
+
+ return i + 1
+
+
+//value: variable | string | number | 'null'
+ value(i, list/node)
+
+ if(token(i) == "null")
+ node += "null"
+ i++
+
+ else if(isnum(text2num(token(i))))
+ node += text2num(token(i))
+ i++
+
+ else if(copytext(token(i), 1, 2) in list("'", "\""))
+ i = string(i, node)
+
+ else
+ i = variable(i, node)
+
+ return i
+
+
+
+
+/*EXPLAIN SELECT * WHERE 42 = 6 * 9 OR val = - 5 == 7*/
\ No newline at end of file