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