From 9d9802c05f2dd3e2b5d9807acf4e1875f986443d Mon Sep 17 00:00:00 2001 From: MataVsn <113342160+MataVsn@users.noreply.github.com> Date: Sat, 2 Dec 2023 20:43:09 +0600 Subject: [PATCH] refactor: Porting TG SDQL2. (#3946) --- code/modules/admin/verbs/SDQL2/SDQL_2.dm | 1262 +++++++++++++---- .../admin/verbs/SDQL2/SDQL_2_parser.dm | 551 +++---- .../modules/admin/verbs/SDQL2/useful_procs.dm | 216 +++ code/modules/mob/mob.dm | 113 +- 4 files changed, 1559 insertions(+), 583 deletions(-) diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm index c4eabef3eae..4da5196e175 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm @@ -1,283 +1,818 @@ -// Examples +//SDQL2 datumized, /tg/station special! + /* - -- 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.. + Welcome admins, badmins and coders alike, to Structured Datum Query Language. + SDQL allows you to powerfully run code on batches of objects (or single objects, it's still unmatched + even there.) + When I say "powerfully" I mean it you're in for a ride. -*/ + Ok so say you want to get a list of every mob. How does one do this? + "SELECT /mob" + This will open a list of every object in world that is a /mob. + And you can VV them if you need. -/client/proc/SDQL2_query() - set category = "Debug" + What if you want to get every mob on a *specific z-level*? + "SELECT /mob WHERE z == 4" - 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!") + What if you want to select every mob on even numbered z-levels? + "SELECT /mob WHERE z % 2 == 0" - var/query_text = input("SDQL2 query") as message + Can you see where this is going? You can select objects with an arbitrary expression. + These expressions can also do variable access and proc calls (yes, both on-object and globals!) + Keep reading! - if(!query_text || length(query_text) < 1) - return + Ok. What if you want to get every machine in the SSmachine process list? Looping through world is kinda + slow. -// to_chat(world, query_text) + "SELECT * IN SSmachines.machinery" - var/list/query_list = SDQL2_tokenize(query_text) + Here "*" as type functions as a wildcard. + We know everything in the global SSmachines.machinery list is a machine. - if(!query_list || query_list.len < 1) - return + You can specify "IN " to return a list to operate on. + This can be any list that you can wizard together from global variables and global proc calls. + Every variable/proc name in the "IN" block is global. + It can also be a single object, in which case the object is wrapped in a list for you. + So yeah SDQL is unironically better than VV for complex single-object operations. - var/list/querys = SDQL_parse(query_list) + You can of course combine these. + "SELECT * IN SSmachines.machinery WHERE z == 4" + "SELECT * IN SSmachines.machinery WHERE stat & 2" // (2 is NOPOWER, can't use defines from SDQL. Sorry!) + "SELECT * IN SSmachines.machinery WHERE stat & 2 && z == 4" - if(!querys || querys.len < 1) - return + The possibilities are endless (just don't crash the server, ok?). - 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) + Oh it gets better. - try - for(var/list/query_tree in querys) - var/list/from_objs = list() - var/list/select_types = list() + You can use "MAP " to run some code per object and use the result. For example: - switch(query_tree[1]) - if("explain") - SDQL_testout(query_tree["explain"]) - return + "SELECT /obj/machinery/power/smes MAP [charge / capacity * 100, RCon_tag, src]" - 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]") + This will give you a list of all the APCs, their charge AND RCon tag. Useful eh? -/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 + [] being a list here. Yeah you can write out lists directly without > lol lists in VV. Color matrix + shenanigans inbound. - for(var/val in query_list) - if(val == ";") - do_parse = 1 - else if(pos >= query_list.len) - query_tree += val - do_parse = 1 + After the "MAP" segment is executed, the rest of the query executes as if it's THAT object you just made + (here the list). + Yeah, by the way, you can chain these MAP / WHERE things FOREVER! - 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++ + "SELECT /mob WHERE client MAP client WHERE holder MAP holder" - qdel(parser) + You can also generate a new list on the fly using a selector array. @[] will generate a list of objects based off the selector provided. - return querys + "SELECT /mob/living IN (@[/area/service/bar MAP contents])[1]" + What if some dumbass admin spawned a bajillion spiders and you need to kill them all? + Oh yeah you'd rather not delete all the spiders in maintenace. Only that one room the spiders were + spawned in. + "DELETE /mob/living/carbon/superior_animal/giant_spider WHERE loc.loc == marked" -/proc/SDQL_testout(list/query_tree, indent = 0) - var/spaces = "" - for(var/s = 0, s < indent, s++) - spaces += "    " + Here I used VV to mark the area they were in, and since loc.loc = area, voila. + Only the spiders in a specific area are gone. - for(var/item in query_tree) - if(istype(item, /list)) - to_chat(usr, "[spaces](") - SDQL_testout(item, indent + 1) - to_chat(usr, "[spaces])") + Or you know if you want to catch spiders that crawled into lockers too (how even?) - else - to_chat(usr, "[spaces][item]") + "DELETE /mob/living/carbon/superior_animal/giant_spider WHERE global.get_area(src) == marked" - if(!isnum(item) && query_tree[item]) + What else can you do? - if(istype(query_tree[item], /list)) - to_chat(usr, "[spaces]    (") - SDQL_testout(query_tree[item], indent + 2) - to_chat(usr, "[spaces]    )") + Well suppose you'd rather gib those spiders instead of simply flat deleting them... - else - to_chat(usr, "[spaces]    [query_tree[item]]") + "CALL gib() ON /mob/living/carbon/superior_animal/giant_spider WHERE global.get_area(src) == marked" -/proc/SDQL_from_objs(list/tree) - if("world" in tree) - return list(world) + Or you can have some fun.. - var/list/out = list() + "CALL forceMove(marked) ON /mob/living/carbon/superior_animal" - for(var/type in tree) - var/char = copytext(type, 1, 2) + You can also run multiple queries sequentially: - if(char == "/") - out += SDQL_get_all(type, world) + "CALL forceMove(marked) ON /mob/living/carbon/superior_animal; CALL gib() ON + /mob/living/carbon/superior_animal" - else if(char == "'" || char == "\"") - out += locate(copytext(type, 2, length(type))) + And finally, you can directly modify variables on objects. - return out + "UPDATE /mob WHERE client SET client.color = [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]" + + Don't crash the server, OK? + + "UPDATE /mob/living/carbon/human/species/monkey SET #null = forceMove(usr.loc)" + + Writing "#null" in front of the "=" will call the proc and discard the return value. + + A quick recommendation: before you run something like a DELETE or another query.. Run it through SELECT + first. + You'd rather not gib every player on accident. + Or crash the server. + + By the way, queries are slow and take a while. Be patient. + They don't hang the entire server though. + + With great power comes great responsability. + + Here's a slightly more formal quick reference. + + The 4 queries you can do are: + + "SELECT " + "CALL ON " + "UPDATE SET var=,var2=" + "DELETE " + + "" in this context is " [IN ] [chain of MAP/WHERE modifiers]" + + "IN" (or "FROM", that works too but it's kinda weird to read), + is the list of objects to work on. This defaults to world if not provided. + But doing something like "IN living_mob_list" is quite handy and can optimize your query. + All names inside the IN block are global scope, so you can do living_mob_list (a global var) easily. + You can also run it on a single object. Because SDQL is that convenient even for single operations. + + filters out objects of, well, that type easily. "*" is a wildcard and just takes everything in + the source list. + + And then there's the MAP/WHERE chain. + These operate on each individual object being ran through the query. + They're both expressions like IN, but unlike it the expression is scoped *on the object*. + So if you do "WHERE z == 4", this does "src.z", effectively. + If you want to access global variables, you can do `global.living_mob_list`. + Same goes for procs. + + MAP "changes" the object into the result of the expression. + WHERE "drops" the object if the expression is falsey (0, null or "") + + What can you do inside expressions? + + * Proc calls + * Variable reads + * Literals (numbers, strings, type paths, etc...) + * \ref referencing: {0x30000cc} grabs the object with \ref [0x30000cc] + * Lists: [a, b, c] or [a: b, c: d] + * Math and stuff. + * A few special variables: src (the object currently scoped on), usr (your mob), + marked (your marked datum), global(global scope) + + TG ADDITIONS START: + Add USING keyword to the front of the query to use options system + The defaults aren't necessarily implemented, as there is no need to. + Available options: (D) means default + PROCCALL = (D)ASYNC, BLOCKING + SELECT = FORCE_NULLS, (D)SKIP_NULLS + PRIORITY = HIGH, (D) NORMAL + AUTOGC = (D) AUTOGC, KEEP_ALIVE + SEQUENTIAL = TRUE - The queries in this batch will be executed sequentially one by one not in parallel + + Example: USING PROCCALL = BLOCKING, SELECT = FORCE_NULLS, PRIORITY = HIGH SELECT /mob FROM world WHERE z == 1 + +*/ + +#define SDQL2_STATE_ERROR 0 +#define SDQL2_STATE_IDLE 1 +#define SDQL2_STATE_PRESEARCH 2 +#define SDQL2_STATE_SEARCHING 3 +#define SDQL2_STATE_EXECUTING 4 +#define SDQL2_STATE_SWITCHING 5 +#define SDQL2_STATE_HALTING 6 + +#define SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS (1<<0) +#define SDQL2_OPTION_BLOCKING_CALLS (1<<1) +#define SDQL2_OPTION_HIGH_PRIORITY (1<<2) //High priority SDQL query, allow using almost all of the tick. +#define SDQL2_OPTION_DO_NOT_AUTOGC (1<<3) +#define SDQL2_OPTION_SEQUENTIAL (1<<4) + +#define SDQL2_OPTIONS_DEFAULT (SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + +#define SDQL2_IS_RUNNING (state == SDQL2_STATE_EXECUTING || state == SDQL2_STATE_SEARCHING || state == SDQL2_STATE_SWITCHING || state == SDQL2_STATE_PRESEARCH) +#define SDQL2_HALT_CHECK if(!SDQL2_IS_RUNNING) {state = SDQL2_STATE_HALTING; return FALSE;}; + +#define SDQL2_TICK_CHECK ((options & SDQL2_OPTION_HIGH_PRIORITY)? CHECK_TICK_HIGH_PRIORITY : CHECK_TICK) + +#define SDQL2_STAGE_SWITCH_CHECK if(state != SDQL2_STATE_SWITCHING){\ + if(state == SDQL2_STATE_HALTING){\ + state = SDQL2_STATE_IDLE;\ + return FALSE}\ + state = SDQL2_STATE_ERROR;\ + CRASH("SDQL2 fatal error");}; + +/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!") + return FALSE + + var/query_text = input("SDQL2 query") as message + + if(!query_text || length(query_text) < 1) + return + + var/list/results = world.SDQL2_query(query_text, key_name_admin(usr), "[key_name(usr)]") + if(length(results) == 3) + for(var/I in 1 to 3) + to_chat(usr, results[I]) + +/world/proc/SDQL2_query(query_text, log_entry1, log_entry2) + log_and_message_admins("executed SDQL query: \"[query_text]\".") + + var/start_time_total = REALTIMEOFDAY + var/sequential = FALSE + + if(!length(query_text)) + return + var/list/query_list = SDQL2_tokenize(query_text) + if(!length(query_list)) + return + var/list/querys = SDQL_parse(query_list) + if(!length(querys)) + return + var/list/datum/sdql2_query/running = list() + var/list/datum/sdql2_query/waiting_queue = list() //Sequential queries queue. + + for(var/list/query_tree in querys) + var/datum/sdql2_query/query = new /datum/sdql2_query(query_tree) + if(QDELETED(query)) + continue + if(usr) + query.show_next_to_key = usr.ckey + waiting_queue += query + if(query.options & SDQL2_OPTION_SEQUENTIAL) + sequential = TRUE + + if(sequential) //Start first one + var/datum/sdql2_query/query = popleft(waiting_queue) + running += query + var/msg = "Starting query #[query.id] - [query.get_query_text()]." + if(usr) + to_chat(usr, span_admin("[msg]")) + log_admin(msg) + query.ARun() + else //Start all + for(var/datum/sdql2_query/query in waiting_queue) + running += query + var/msg = "Starting query #[query.id] - [query.get_query_text()]." + if(usr) + to_chat(usr, span_admin("[msg]")) + log_admin(msg) + query.ARun() + + var/finished = FALSE + var/objs_all = 0 + var/objs_eligible = 0 + var/selectors_used = FALSE + var/list/combined_refs = list() + do + CHECK_TICK + finished = TRUE + for(var/i in running) + var/datum/sdql2_query/query = i + if(QDELETED(query)) + running -= query + continue + else if(query.state != SDQL2_STATE_IDLE) + finished = FALSE + if(query.state == SDQL2_STATE_ERROR) + if(usr) + to_chat(usr, span_admin("SDQL query [query.get_query_text()] errored. It will NOT be automatically garbage collected. Please remove manually.")) + running -= query + else + if(query.finished) + objs_all += islist(query.obj_count_all)? length(query.obj_count_all) : query.obj_count_all + objs_eligible += islist(query.obj_count_eligible)? length(query.obj_count_eligible) : query.obj_count_eligible + selectors_used |= query.where_switched + combined_refs |= query.select_refs + running -= query + if(!(query.options & SDQL2_OPTION_DO_NOT_AUTOGC)) + QDEL_IN(query, 50) + if(sequential && waiting_queue.len) + finished = FALSE + var/datum/sdql2_query/next_query = popleft(waiting_queue) + running += next_query + var/msg = "Starting query #[next_query.id] - [next_query.get_query_text()]." + if(usr) + to_chat(usr, span_admin("[msg]")) + log_admin(msg) + next_query.ARun() + else + if(usr) + to_chat(usr, span_admin("SDQL query [query.get_query_text()] was halted. It will NOT be automatically garbage collected. Please remove manually.")) + running -= query + while(!finished) + + var/end_time_total = REALTIMEOFDAY - start_time_total + return list(span_admin("SDQL query combined results: [query_text]"),\ + span_admin("SDQL query completed: [objs_all] objects selected by path, and [selectors_used ? objs_eligible : objs_all] objects executed on after WHERE filtering/MAPping if applicable."),\ + span_admin("SDQL combined querys took [DisplayTimeText(end_time_total)] to complete.")) + combined_refs + +GLOBAL_LIST_INIT(sdql2_queries, GLOB.sdql2_queries || list()) +GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/sdql2_vv_all, new(null, "VIEW VARIABLES (all)", null)) + +/datum/sdql2_query + var/list/query_tree + var/state = SDQL2_STATE_IDLE + var/options = SDQL2_OPTIONS_DEFAULT + var/superuser = FALSE //Run things like proccalls without using admin protections + var/allow_admin_interact = TRUE //Allow admins to do things to this excluding varedit these two vars + var/static/id_assign = 1 + var/id = 0 + + var/qdel_on_finish = FALSE + + //Last run + //General + var/finished = FALSE + var/start_time + var/end_time + var/where_switched = FALSE + var/show_next_to_key + //Select query only + var/list/select_refs + var/list/select_text + //Runtime tracked + //These three are weird. For best performance, they are only a number when they're not being changed by the SDQL searching/execution code. They only become numbers when they finish changing. + var/list/obj_count_all + var/list/obj_count_eligible + var/obj_count_finished + + //Statclick + var/obj/effect/statclick/SDQL2_delete/delete_click + var/obj/effect/statclick/SDQL2_action/action_click + +/datum/sdql2_query/New(list/tree, SU = FALSE, admin_interact = TRUE, _options = SDQL2_OPTIONS_DEFAULT, finished_qdel = FALSE) + if(IsAdminAdvancedProcCall() || !LAZYLEN(tree)) + qdel(src) + return + LAZYADD(GLOB.sdql2_queries, src) + superuser = SU + allow_admin_interact = admin_interact + query_tree = tree + options = _options + id = id_assign++ + qdel_on_finish = finished_qdel + +/datum/sdql2_query/Destroy() + state = SDQL2_STATE_HALTING + query_tree = null + obj_count_all = null + obj_count_eligible = null + obj_count_finished = null + select_text = null + select_refs = null + GLOB.sdql2_queries -= src + return ..() + +/datum/sdql2_query/proc/get_query_text() + var/list/out = list() + recursive_list_print(out, query_tree) + return out.Join() + +/proc/recursive_list_print(list/output = list(), list/input, datum/callback/datum_handler, datum/callback/atom_handler) + output += "\[ " + for(var/i in 1 to input.len) + var/final = i == input.len + var/key = input[i] + + //print the key + if(islist(key)) + recursive_list_print(output, key, datum_handler, atom_handler) + else if(isdatum(key) && (datum_handler || (isatom(key) && atom_handler))) + if(isatom(key) && atom_handler) + output += atom_handler.Invoke(key) + else + output += datum_handler.Invoke(key) + else + output += "[key]" + + //print the value + var/is_value = (!isnum(key) && !isnull(input[key])) + if(is_value) + var/value = input[key] + if(islist(value)) + recursive_list_print(output, value, datum_handler, atom_handler) + else if(isdatum(value) && (datum_handler || (isatom(value) && atom_handler))) + if(isatom(value) && atom_handler) + output += atom_handler.Invoke(value) + else + output += datum_handler.Invoke(value) + else + output += " = [value]" + + if(!final) + output += " , " + + output += " \]" + +/datum/sdql2_query/proc/text_state() + switch(state) + if(SDQL2_STATE_ERROR) + return "###ERROR" + if(SDQL2_STATE_IDLE) + return "####IDLE" + if(SDQL2_STATE_PRESEARCH) + return "PRESEARCH" + if(SDQL2_STATE_SEARCHING) + return "SEARCHING" + if(SDQL2_STATE_EXECUTING) + return "EXECUTING" + if(SDQL2_STATE_SWITCHING) + return "SWITCHING" + if(SDQL2_STATE_HALTING) + return "##HALTING" + +/datum/sdql2_query/proc/generate_stat() + if(!allow_admin_interact) + return + if(!delete_click) + delete_click = new(null, "INITIALIZING", src) + if(!action_click) + action_click = new(null, "INITIALIZNG", src) + stat("[id] ", delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \ + [islist(obj_count_all)? length(obj_count_all) : (isnull(obj_count_all)? "0" : obj_count_all)]/\ + [islist(obj_count_eligible)? length(obj_count_eligible) : (isnull(obj_count_eligible)? "0" : obj_count_eligible)]/\ + [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]")) + stat(" ", action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]")) + +/datum/sdql2_query/proc/delete_click() + admin_del(usr) + +/datum/sdql2_query/proc/action_click() + if(SDQL2_IS_RUNNING) + admin_halt(usr) + else + admin_run(usr) -/proc/SDQL_get_all(type, location) +/datum/sdql2_query/proc/admin_halt(user = usr) + if(!SDQL2_IS_RUNNING) + return + var/msg = "[key_name(user)] has halted query #[id]" + message_admins(msg) + log_admin(msg) + state = SDQL2_STATE_HALTING + +/datum/sdql2_query/proc/admin_run(mob/user = usr) + if(SDQL2_IS_RUNNING) + return + var/msg = "[key_name(user)] has (re)started query #[id]" + message_admins(msg) + log_admin(msg) + show_next_to_key = user.ckey + ARun() + +/datum/sdql2_query/proc/admin_del(user = usr) + var/msg = "[key_name(user)] has stopped + deleted query #[id]" + message_admins(msg) + log_admin(msg) + qdel(src) + +/datum/sdql2_query/proc/set_option(name, value) + switch(name) + if("select") + switch(value) + if("force_nulls") + options &= ~(SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + if("proccall") + switch(value) + if("blocking") + options |= SDQL2_OPTION_BLOCKING_CALLS + if("priority") + switch(value) + if("high") + options |= SDQL2_OPTION_HIGH_PRIORITY + if("autogc") + switch(value) + if("keep_alive") + options |= SDQL2_OPTION_DO_NOT_AUTOGC + if("sequential") + switch(value) + if("true") + options |= SDQL2_OPTION_SEQUENTIAL + +/datum/sdql2_query/proc/ARun() + INVOKE_ASYNC(src, PROC_REF(Run)) + +/datum/sdql2_query/proc/Run() + if(SDQL2_IS_RUNNING) + return FALSE + if(query_tree["options"]) + for(var/name in query_tree["options"]) + var/value = query_tree["options"][name] + set_option(name, value) + select_refs = list() + select_text = null + obj_count_all = 0 + obj_count_eligible = 0 + obj_count_finished = 0 + start_time = REALTIMEOFDAY + + state = SDQL2_STATE_PRESEARCH + var/list/search_tree = PreSearch() + SDQL2_STAGE_SWITCH_CHECK + + state = SDQL2_STATE_SEARCHING + var/list/found = Search(search_tree) + SDQL2_STAGE_SWITCH_CHECK + + state = SDQL2_STATE_EXECUTING + Execute(found) + SDQL2_STAGE_SWITCH_CHECK + + end_time = REALTIMEOFDAY + state = SDQL2_STATE_IDLE + finished = TRUE + . = TRUE + if(show_next_to_key) + var/client/C = GLOB.directory[show_next_to_key] + if(C) + var/mob/showmob = C.mob + to_chat(showmob, "SDQL query results: [get_query_text()]
\ + SDQL query completed: [islist(obj_count_all)? length(obj_count_all) : obj_count_all] objects selected by path, and \ + [where_switched? "[islist(obj_count_eligible)? length(obj_count_eligible) : obj_count_eligible] objects executed on after WHERE keyword selection." : ""]
\ + SDQL query took [DisplayTimeText(end_time - start_time)] to complete.
") + if(length(select_text)) + var/text = islist(select_text)? select_text.Join() : select_text + var/static/result_offset = 0 + showmob << browse(text, "window=SDQL-result-[result_offset++]") + show_next_to_key = null + if(qdel_on_finish) + qdel(src) + +/datum/sdql2_query/proc/PreSearch() + SDQL2_HALT_CHECK + switch(query_tree[1]) + if("explain") + SDQL_testout(query_tree["explain"]) + state = SDQL2_STATE_HALTING + return + if("call") + . = query_tree["on"] + if("select", "delete", "update") + . = query_tree[query_tree[1]] + state = SDQL2_STATE_SWITCHING + +/datum/sdql2_query/proc/Search(list/tree) + SDQL2_HALT_CHECK + var/type = tree[1] + var/list/from = tree[2] + var/list/objs = SDQL_from_objs(from) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + objs = SDQL_get_all(type, objs) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + // 1 and 2 are type and FROM. + var/i = 3 + while (i <= tree.len) + var/key = tree[i++] + var/list/expression = tree[i++] + switch (key) + if ("map") + for(var/j = 1 to objs.len) + var/x = objs[j] + objs[j] = SDQL_expression(x, expression) + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + if ("where") + where_switched = TRUE + var/list/out = list() + obj_count_eligible = out + for(var/x in objs) + if(SDQL_expression(x, expression)) + out += x + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + objs = out + if(islist(obj_count_eligible)) + obj_count_eligible = objs.len + else + obj_count_eligible = obj_count_all + . = objs + state = SDQL2_STATE_SWITCHING + +/datum/sdql2_query/proc/SDQL_from_objs(list/tree) + if(IsAdminAdvancedProcCall()) + if("world" in tree) + var/text = "[key_name(usr)] attempted to grab world with a procedure call to a SDQL datum." + message_admins(text) + log_admin(text) + return + if("world" in tree) + return world + return SDQL_expression(world, tree) + +/datum/sdql2_query/proc/SDQL_get_all(type, location) var/list/out = list() + obj_count_all = out - if(type == "*") - for(var/datum/d in location) - out += d +// If only a single object got returned, wrap it into a list so the for loops run on it. + if(!islist(location) && location != world) + location = list(location) + if(type == "*") + for(var/i in location) + var/datum/d = i + if(d.can_vv_get() || superuser) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK return out - - type = text2path(type) + if(istext(type)) + type = text2path(type) + var/typecache = typecacheof(type) if(ispath(type, /mob)) for(var/mob/d in location) - if(istype(d, type)) + if(typecache[d.type] && (d.can_vv_get() || superuser)) out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK else if(ispath(type, /turf)) for(var/turf/d in location) - if(istype(d, type)) + if(typecache[d.type] && (d.can_vv_get() || superuser)) out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK else if(ispath(type, /obj)) for(var/obj/d in location) - if(istype(d, type)) + if(typecache[d.type] && (d.can_vv_get() || superuser)) out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK else if(ispath(type, /area)) for(var/area/d in location) - if(istype(d, type)) + if(typecache[d.type] && (d.can_vv_get() || superuser)) out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK 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)) + if(typecache[d.type] && (d.can_vv_get() || superuser)) out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + else if(ispath(type, /datum)) + if(location == world) //snowflake for byond shortcut + for(var/datum/d) //stupid byond trick to have it not return atoms to make this less laggy + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + else + for(var/datum/d in location) + if(typecache[d.type] && (d.can_vv_get() || superuser)) + out += d + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + obj_count_all = out.len + return out +/datum/sdql2_query/proc/Execute(list/found) + SDQL2_HALT_CHECK + select_refs = list() + select_text = list() + switch(query_tree[1]) + if("call") + for(var/i in found) + if(!isdatum(i)) + continue + world.SDQL_var(i, query_tree["call"][1], null, i, superuser, src) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + if("delete") + for(var/datum/d in found) + qdel(d) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + + if("select") + var/list/text_list = list() + var/print_nulls = !(options & SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS) + obj_count_finished = select_refs + for(var/i in found) + var/datum/I = i + SDQL_print(i, text_list, print_nulls) + if(isdatum(i)) + select_refs[I.UID()] = TRUE + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + select_text = text_list + + if("update") + if("set" in query_tree) + var/list/set_list = query_tree["set"] + for(var/d in found) + if(!isdatum(d)) + continue + SDQL_internal_vv(d, set_list) + obj_count_finished++ + SDQL2_TICK_CHECK + SDQL2_HALT_CHECK + if(islist(obj_count_finished)) + obj_count_finished = length(obj_count_finished) + state = SDQL2_STATE_SWITCHING + +/datum/sdql2_query/proc/SDQL_print(object, list/text_list, print_nulls = TRUE) + if(isdatum(object)) + var/datum/O = object + text_list += "\ref[O] : [object]" + if(istype(object, /atom)) + var/atom/A = object + var/turf/T = A.loc + var/area/a + if(isturf(A)) + a = A.loc + T = A //this should prevent the "inside" part + text_list += " at [ADMIN_COORDJMP(A)]" + else if(istype(T)) + text_list += " at [T] [ADMIN_COORDJMP(T)]" + a = T.loc + else + var/turf/final = get_turf(T) //Recursive, hopefully? + if(istype(final)) + text_list += " at [final] [ADMIN_COORDJMP(final)]" + a = final.loc + else + text_list += " at nonexistent location" + if(a) + text_list += " in area [a]" + if(T.loc != a) + text_list += " inside [T]" + text_list += "
" + else if(islist(object)) + var/list/L = object + var/first = TRUE + text_list += "\[" + for (var/x in L) + if (!first) + text_list += ", " + first = FALSE + SDQL_print(x, text_list) + if (!isnull(x) && !isnum(x) && L[x] != null) + text_list += " -> " + SDQL_print(L[L[x]]) + text_list += "]
" else - for(var/datum/d in location) - if(istype(d, type)) - out += d + if(isnull(object)) + if(print_nulls) + text_list += "NULL
" + else + text_list += "[object]
" + +/datum/sdql2_query/CanProcCall() + if(!allow_admin_interact) + return FALSE + return ..() + +/datum/sdql2_query/vv_edit_var(var_name, var_value) + if(!allow_admin_interact) + return FALSE + if(var_name == NAMEOF(src, superuser) || var_name == NAMEOF(src, allow_admin_interact) || var_name == NAMEOF(src, query_tree)) + return FALSE + return ..() + +/datum/sdql2_query/proc/SDQL_internal_vv(d, list/set_list) + for(var/list/sets in set_list) + var/datum/temp = d + var/i = 0 + for(var/v in sets) + if(v == "#null") + SDQL_expression(d, set_list[sets]) + break + i++ + if(i == sets.len) + if(superuser) + if(temp.vars.Find(v)) + temp.vars[v] = SDQL_expression(d, set_list[sets]) + else + temp.vv_edit_var(v, SDQL_expression(d, set_list[sets])) + break + if(temp.vars.Find(v) && (istype(temp.vars[v], /datum) || istype(temp.vars[v], /client))) + temp = temp.vars[v] + else + break - return out +/datum/sdql2_query/proc/SDQL_function_blocking(datum/object, procname, list/arguments, source) + var/list/new_args = list() + for(var/arg in arguments) + new_args[++new_args.len] = SDQL_expression(source, arg) + if(object == GLOB) // Global proc. + return superuser ? (call("/proc/[procname]")(arglist(new_args))) : (WrapAdminProcCall(GLOBAL_PROC, procname, new_args)) + return superuser ? (call(object, procname)(arglist(new_args))) : (WrapAdminProcCall(object, procname, new_args)) -/proc/SDQL_expression(datum/object, list/expression, start = 1) +/datum/sdql2_query/proc/SDQL_function_async(datum/object, procname, list/arguments, source) + set waitfor = FALSE + return SDQL_function_blocking(object, procname, arguments, source) + +/datum/sdql2_query/proc/SDQL_expression(datum/object, list/expression, start = 1) var/result = 0 var/val @@ -295,19 +830,21 @@ if(op != "") switch(op) if("+") - result += val + result = (result + val) if("-") - result -= val + result = (result - val) if("*") - result *= val + result = (result * val) if("/") - result /= val + result = (result / val) if("&") - result &= val + result = (result & val) if("|") - result |= val + result = (result | val) if("^") - result ^= val + result = (result ^ val) + if("%") + result = (result % val) if("=", "==") result = (result == val) if("!=", "<>") @@ -325,14 +862,14 @@ if("or", "||") result = (result || val) else - to_chat(usr, "SDQL2: Unknown op [op]") + to_chat(usr, span_danger("SDQL2: Unknown op [op]")) result = null else result = val return result -/proc/SDQL_value(datum/object, list/expression, start = 1) +/datum/sdql2_query/proc/SDQL_value(datum/object, list/expression, start = 1) var/i = start var/val = null @@ -342,6 +879,12 @@ if(istype(expression[i], /list)) val = SDQL_expression(object, expression[i]) + else if(expression[i] == "TRUE") + val = TRUE + + else if(expression[i] == "FALSE") + val = FALSE + else if(expression[i] == "!") var/list/ret = SDQL_value(object, expression, i + 1) val = !ret["val"] @@ -363,46 +906,144 @@ 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(ispath(expression[i])) + val = expression[i] + + else if(expression[i][1] in list("'", "\"")) + val = copytext_char(expression[i], 2, -1) - else if(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) + var/result = SDQL_expression(object, expression_list) + var/assoc + if(expressions_list[expression_list] != null) + assoc = SDQL_expression(object, expressions_list[expression_list]) + if(assoc != null) + // Need to insert the key like this to prevent duplicate keys fucking up. + var/list/dummy = list() + dummy[result] = assoc + result = dummy + val += result + + else if(expression[i] == "@\[") + var/list/search_tree = expression[++i] + var/already_searching = (state == SDQL2_STATE_SEARCHING) //In case we nest, don't want to break out of the searching state until we're all done. + + if(!already_searching) + state = SDQL2_STATE_SEARCHING + + val = Search(search_tree) + SDQL2_STAGE_SWITCH_CHECK + + if(!already_searching) + state = SDQL2_STATE_EXECUTING + else + state = SDQL2_STATE_SEARCHING else - val = SDQL_var(object, expression, i, object) + val = world.SDQL_var(object, expression, i, object, superuser, src) i = expression.len return list("val" = val, "i" = i) -/proc/SDQL_var(datum/object, list/expression, start = 1, source) - var/v +/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, span_danger("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/static/whitespace = "    " + var/spaces = "" + if(indent > 0) + for(var/i in 1 to indent) + spaces += whitespace + + 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][whitespace](") + SDQL_testout(query_tree[item], indent + 2) + to_chat(usr, "[spaces][whitespace])") + + else + to_chat(usr, "[spaces][whitespace][query_tree[item]]") + +//Staying as a world proc as this is called too often for changes to offset the potential IsAdminAdvancedProcCall checking overhead. +/world/proc/SDQL_var(object, list/expression, start = 1, source, superuser, datum/sdql2_query/query) + var/v + var/static/list/exclude = list("usr", "src", "marked", "global", "MC", "FS", "CFG") var/long = start < expression.len + var/datum/D + if(isdatum(object)) + D = object - if(object == world && long && expression[start + 1] == ".") - to_chat(usr, "Sorry, but global variables are not supported at the moment.") + if (object == world && (!long || expression[start + 1] == ".") && !(expression[start] in exclude) && copytext(expression[start], 1, 3) != "SS") //3 == length("SS") + 1 + to_chat(usr, span_danger("World variables are not allowed to be accessed. Use global.")) return null - if(expression[start] == "\[" && long) - if(lowertext(copytext(expression[start + 1], 1, 3)) != "0x") - to_chat(usr, "Invalid ref syntax: [expression[start + 1]]") + else if(expression [start] == "{" && long) + if(lowertext(copytext(expression[start + 1], 1, 3)) != "0x") //3 == length("0x") + 1 + to_chat(usr, span_danger("Invalid pointer syntax: [expression[start + 1]]")) return null - v = locate("\[[expression[start + 1]]\]") - if(!v) - to_chat(usr, "Invalid ref: [expression[start + 1]]") + var/datum/located = locate("\[[expression[start + 1]]]") + if(!istype(located)) + to_chat(usr, span_danger("Invalid pointer: [expression[start + 1]] - null or not datum")) return null + v = located 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])) + long = start < expression.len + else if(expression[start] == "(" && long) + v = query.SDQL_expression(source, expression[start + 1]) + start++ + long = start < expression.len + else if(D != null && (!long || expression[start + 1] == ".") && (expression[start] in D.vars)) + if(D.can_vv_get(expression[start]) || superuser) + v = D.vars[expression[start]] + else + v = "SECRET" + else if(D != null && long && expression[start + 1] == ":" && hascall(D, expression[start])) v = expression[start] - else if(!long || expression[start + 1] == ".") switch(expression[start]) if("usr") @@ -414,56 +1055,66 @@ v = usr.client.holder.marked_datum else return null + if("world") + v = world if("global") - v = world // World is mostly a token, really. + v = GLOB + if("MC") + v = Master + if("FS") + v = Failsafe + if("CFG") + v = config else - return null - - else if(object == world) + if(copytext(expression[start], 1, 3) == "SS") //Subsystem //3 == length("SS") + 1 + var/SSname = copytext_char(expression[start], 3) + var/SSlength = length(SSname) + var/datum/controller/subsystem/SS + var/SSmatch + for(var/_SS in Master.subsystems) + SS = _SS + if(copytext("[SS.type]", -SSlength) == SSname) + SSmatch = SS + break + if(!SSmatch) + return null + v = SSmatch + else + return null + else if(object == GLOB) // Shitty ass hack kill me. v = expression[start] - if(long) if(expression[start + 1] == ".") - return SDQL_var(v, expression[start + 2], source = source) + return SDQL_var(v, expression[start + 2], null, source, superuser, query) else if(expression[start + 1] == ":") - return SDQL_function(object, v, expression[start + 2], source) + return (query.options & SDQL2_OPTION_BLOCKING_CALLS)? query.SDQL_function_async(object, v, expression[start + 2], source) : query.SDQL_function_blocking(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]) + var/index = query.SDQL_expression(source, expression[start + 2]) if(isnum(index) && (!ISINTEGER(index) || L.len < index)) - to_chat(world, "Invalid list index: [index]") + to_chat(usr, span_danger("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/single = list("(", ")", ",", "+", "-", ".", "\[", "]", "{", "}", ";", ":") var/list/multi = list( "=" = list("", "="), "<" = list("", "=", ">"), ">" = list("", "="), - "!" = list("", "=")) + "!" = list("", "="), + "@" = list("\[")) var/word = "" var/list/query_list = list() var/len = length(query_text) + var/char = "" - for(var/i = 1, i <= len, i++) - var/char = copytext(query_text, i, i + 1) + for(var/i = 1, i <= len, i += length(char)) + char = query_text[i] if(char in whitespace) if(word != "") @@ -482,7 +1133,7 @@ query_list += word word = "" - var/char2 = copytext(query_text, i + 1, i + 2) + var/char2 = query_text[i + length(char)] if(char2 in multi[char]) query_list += "[char][char2]" @@ -493,18 +1144,18 @@ 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.") + to_chat(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) + for(i += length(char), i <= len, i += length(char)) + char = query_text[i] if(char == "'") - if(copytext(query_text, i + 1, i + 2) == "'") + if(query_text[i + length(char)] == "'") word += "'" - i++ + i += length(query_text[i + length(char)]) else break @@ -513,7 +1164,7 @@ 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.") + to_chat(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]'" @@ -521,18 +1172,18 @@ 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.") + to_chat(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) + for(i += length(char), i <= len, i += length(char)) + char = query_text[i] if(char == "\"") - if(copytext(query_text, i + 1, i + 2) == "'") + if((i + length(char) <= len) && query_text[i + length(char)] == "'") word += "\"" - i++ + i += length(query_text[i + length(char)]) else break @@ -541,7 +1192,7 @@ 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.") + to_chat(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]\"" @@ -553,3 +1204,44 @@ if(word != "") query_list += word return query_list + +/obj/effect/statclick/SDQL2_delete/Click() + if(!usr.client?.holder) + log_and_message_admins("non-holder clicked on a statclick! ([src])") + return + var/datum/sdql2_query/Q = target + Q.delete_click() + +/obj/effect/statclick/SDQL2_action/Click() + if(!usr.client?.holder) + log_and_message_admins("non-holder clicked on a statclick! ([src])") + return + var/datum/sdql2_query/Q = target + Q.action_click() + +/obj/effect/statclick/sdql2_vv_all + name = "VIEW VARIABLES" + +/obj/effect/statclick/sdql2_vv_all/Click() + if(!usr.client?.holder) + log_and_message_admins("non-holder clicked on a statclick! ([src])") + return + usr.client.debug_variables(GLOB.sdql2_queries) + +#undef SDQL2_HALT_CHECK +#undef SDQL2_IS_RUNNING +#undef SDQL2_OPTION_BLOCKING_CALLS +#undef SDQL2_OPTION_DO_NOT_AUTOGC +#undef SDQL2_OPTION_HIGH_PRIORITY +#undef SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS +#undef SDQL2_OPTION_SEQUENTIAL +#undef SDQL2_OPTIONS_DEFAULT +#undef SDQL2_STAGE_SWITCH_CHECK +#undef SDQL2_STATE_ERROR +#undef SDQL2_STATE_EXECUTING +#undef SDQL2_STATE_HALTING +#undef SDQL2_STATE_IDLE +#undef SDQL2_STATE_PRESEARCH +#undef SDQL2_STATE_SEARCHING +#undef SDQL2_STATE_SWITCHING +#undef SDQL2_TICK_CHECK diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm index 50c852af03b..a3caac2d1be 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm @@ -1,96 +1,148 @@ //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 +// query : select_query | delete_query | update_query | call_query | explain +// explain : 'EXPLAIN' query +// select_query : 'SELECT' object_selectors +// delete_query : 'DELETE' object_selectors +// update_query : 'UPDATE' object_selectors 'SET' assignments +// call_query : 'CALL' variable 'ON' object_selectors // Note here: 'variable' does function calls. This simplifies parsing. +// +// select_item : '*' | object_type // -// 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]] +// object_selectors : select_item [('FROM' | 'IN') from_item] [modifier_list] +// modifier_list : ('WHERE' bool_expression | 'MAP' expression) [modifier_list] // -// select_list : select_item [',' select_list] -// select_item : '*' | select_function | object_type -// select_function : count_function -// count_function : 'COUNT' '(' '*' ')' | 'COUNT' '(' object_types ')' +// from_item : 'world' | expression // -// from_list : from_item [',' from_list] -// from_item : 'world' | object_type +// call_function : '(' [expression_list] ')' // -// call_function : ['(' [arguments] ')'] -// arguments : expression [',' arguments] +// object_type : // -// object_type : | string +// assignments : assignment [',' assignments] +// assignment : '=' expression +// variable : | variable '.' variable | variable '[' ']' | '{' '}' | '(' expression ')' | call_function // -// assignments : assignment, [',' assignments] -// assignment : '=' expression -// variable : | '.' variable | '[' ']' | '[' ']' '.' variable +// bool_expression : expression comparator expression [bool_operator bool_expression] +// expression : ( unary_expression | '(' expression ')' | value ) [binary_operator expression] +// expression_list : expression [',' expression_list] +// unary_expression : unary_operator ( unary_expression | value ) // -// 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 | array | number | 'null' -// unary_operator : '!' | '-' | '~' -// binary_operator : comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' -// bool_operator : 'AND' | '&&' | 'OR' | '||' +// comparator : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' +// value : variable | string | number | 'null' | object_type | array | selectors_array +// unary_operator : '!' | '-' | '~' +// binary_operator : comparator | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%' +// bool_operator : 'AND' | '&&' | 'OR' | '||' // -// string : ''' ''' | '"' '"' -// array : '{' [arguments] '}' -// number : +// array : '[' expression_list ']' +// selectors_array : '@[' object_selectors ']' +// +// string : ''' ''' | '"' '"' +// number : // ////////// -/datum/SDQL_parser +#define SDQL2_VALID_OPTION_TYPES list(\ + "autogc",\ + "priority",\ + "proccall",\ + "select",\ + "sequential",\ +) + +#define SDQL2_VALID_OPTION_VALUES list(\ + "async",\ + "blocking",\ + "force_nulls",\ + "high",\ + "keep_alive" ,\ + "normal",\ + "skip_nulls",\ + "true",\ +) + +/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("=", "==", "!=", "<>", "<", "<=", ">", ">=") + var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^", "%") + var/list/comparators = list("=", "==", "!=", "<>", "<", "<=", ">", ">=") -/datum/SDQL_parser/New(query_list) +/datum/sdql_parser/New(query_list) query = query_list -/datum/SDQL_parser/proc/parse_error(error_message) +/datum/sdql_parser/proc/parse_error(error_message) error = 1 - to_chat(usr, "SDQL2 Parsing Error: [error_message]") + to_chat(usr, span_warning("SDQL2 Parsing Error: [error_message]")) return query.len + 1 -/datum/SDQL_parser/proc/parse() +/datum/sdql_parser/proc/parse() tree = list() - query(1, tree) + query_options(1, tree) if(error) return list() else return tree -/datum/SDQL_parser/proc/token(i) +/datum/sdql_parser/proc/token(i) if(i <= query.len) return query[i] else return null -/datum/SDQL_parser/proc/tokens(i, num) +/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) +/datum/sdql_parser/proc/tokenl(i) return lowertext(token(i)) -//query: select_query | delete_query | update_query -/datum/SDQL_parser/proc/query(i, list/node) +/datum/sdql_parser/proc/query_options(i, list/node) + var/list/options = list() + if(tokenl(i) == "using") + i = option_assignments(i + 1, node, options) + query(i, node) + if(length(options)) + node["options"] = options + +//option_assignment: query_option '=' define +/datum/sdql_parser/proc/option_assignment(i, list/node, list/assignment_list = list()) + var/type = tokenl(i) + if(!(type in SDQL2_VALID_OPTION_TYPES)) + parse_error("Invalid option type: [type]") + if(!(token(i + 1) == "=")) + parse_error("Invalid option assignment symbol: [token(i + 1)]") + var/val = tokenl(i + 2) + if(!(val in SDQL2_VALID_OPTION_VALUES)) + parse_error("Invalid option value: [val]") + assignment_list[type] = val + return (i + 3) + +//option_assignments: option_assignment, [',' option_assignments] +/datum/sdql_parser/proc/option_assignments(i, list/node, list/store) + i = option_assignment(i, node, store) + + if(token(i) == ",") + i = option_assignments(i + 1, node, store) + + return i + +//query: select_query | delete_query | update_query +/datum/sdql_parser/proc/query(i, list/node) query_type = tokenl(i) switch(query_type) @@ -112,154 +164,107 @@ query(i + 1, node["explain"]) -// select_query: 'SELECT' select_list [('FROM' | 'IN') from_list] ['WHERE' bool_expression] -/datum/SDQL_parser/proc/select_query(i, list/node) +// select_query: 'SELECT' object_selectors +/datum/sdql_parser/proc/select_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(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] -/datum/SDQL_parser/proc/delete_query(i, list/node) +//delete_query: 'DELETE' object_selectors +/datum/sdql_parser/proc/delete_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(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] -/datum/SDQL_parser/proc/update_query(i, list/node) +//update_query: 'UPDATE' object_selectors 'SET' assignments +/datum/sdql_parser/proc/update_query(i, list/node) var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(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]] -/datum/SDQL_parser/proc/call_query(i, list/node) +//call_query: 'CALL' call_function ['ON' object_selectors] +/datum/sdql_parser/proc/call_query(i, list/node) var/list/func = list() - i = variable(i + 1, func) + i = variable(i + 1, func) // Yes technically does anything variable() matches but I don't care, if admins fuck up this badly then they shouldn't be allowed near SDQL. - node += "call" node["call"] = func if(tokenl(i) != "on") - return i + return parse_error("You need to specify what to call ON.") var/list/select = list() - i = select_list(i + 1, select) + i = object_selectors(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" + return i - node += "from" - node["from"] = from +// object_selectors: select_item [('FROM' | 'IN') from_item] [modifier_list] +/datum/sdql_parser/proc/object_selectors(i, list/node) + i = select_item(i, node) - if(tokenl(i) == "where") - var/list/where = list() - i = bool_expression(i + 1, where) + if (tokenl(i) == "from" || tokenl(i) == "in") + i++ + var/list/from = list() + i = from_item(i, from) + node[++node.len] = from - node += "where" - node["where"] = where + else + node[++node.len] = list("world") + i = modifier_list(i, node) return i +// modifier_list: ('WHERE' bool_expression | 'MAP' expression) [modifier_list] +/datum/sdql_parser/proc/modifier_list(i, list/node) + while (TRUE) + if (tokenl(i) == "where") + i++ + node += "where" + var/list/expr = list() + i = bool_expression(i, expr) + node[++node.len] = expr -//select_list: select_item [',' select_list] -/datum/SDQL_parser/proc/select_list(i, list/node) - i = select_item(i, node) - - if(token(i) == ",") - i = select_list(i + 1, node) - - return i + else if (tokenl(i) == "map") + i++ + node += "map" + var/list/expr = list() + i = expression(i, expr) + node[++node.len] = expr + else + return i -//from_list: from_item [',' from_list] -/datum/SDQL_parser/proc/from_list(i, list/node) - i = from_item(i, node) +//select_list:select_item [',' select_list] +/datum/sdql_parser/proc/select_list(i, list/node) + i = select_item(i, node) if(token(i) == ",") - i = from_list(i + 1, node) + i = select_list(i + 1, node) return i - -//assignments: assignment, [',' assignments] -/datum/SDQL_parser/proc/assignments(i, list/node) +//assignments: assignment, [',' assignments] +/datum/sdql_parser/proc/assignments(i, list/node) i = assignment(i, node) if(token(i) == ",") @@ -268,36 +273,61 @@ return i -//select_item: '*' | select_function | object_type -/datum/SDQL_parser/proc/select_item(i, list/node) - if(token(i) == "*") +//select_item: '*' | select_function | object_type +/datum/sdql_parser/proc/select_item(i, list/node) + if (token(i) == "*") node += "*" i++ - else if(tokenl(i) in select_functions) - i = select_function(i, node) + else if(token(i)[1] == "/") + i = object_type(i, node) else - i = object_type(i, node) + i = parse_error("Expected '*' or type path for select item") return i +// Standardized method for handling the IN/FROM and WHERE options. +/datum/sdql_parser/proc/selectors(i, list/node) + while (token(i)) + var/tok = tokenl(i) + if (tok in list("from", "in")) + var/list/from = list() + i = from_item(i + 1, from) + + node["from"] = from + continue + + if (tok == "where") + var/list/where = list() + i = bool_expression(i + 1, where) + + node["where"] = where + continue -//from_item: 'world' | object_type -/datum/SDQL_parser/proc/from_item(i, list/node) + parse_error("Expected either FROM, IN or WHERE token, found [token(i)] instead.") + return i + 1 + + if (!node.Find("from")) + node["from"] = list("world") + + return i +//from_item: 'world' | expression +/datum/sdql_parser/proc/from_item(i, list/node) if(token(i) == "world") node += "world" i++ else - i = object_type(i, node) + i = expression(i, node) return i -//bool_expression: expression [bool_operator bool_expression] -/datum/SDQL_parser/proc/bool_expression(i, list/node) +//bool_expression: expression [bool_operator bool_expression] +/datum/sdql_parser/proc/bool_expression(i, list/node) + var/list/bool = list() i = expression(i, bool) @@ -310,8 +340,8 @@ return i -//assignment: '=' expression -/datum/SDQL_parser/proc/assignment(var/i, var/list/node, var/list/assignment_list = list()) +//assignment: '=' expression +/datum/sdql_parser/proc/assignment(i, list/node, list/assignment_list = list()) assignment_list += token(i) if(token(i + 1) == ".") @@ -329,32 +359,42 @@ return i -//variable: | '.' variable -/datum/SDQL_parser/proc/variable(i, list/node) +//variable: | variable '.' variable | variable '[' ']' | '{' '}' | '(' expression ')' | call_function +/datum/sdql_parser/proc/variable(i, list/node) var/list/L = list(token(i)) node[++node.len] = L - if(token(i) == "\[") + if(token(i) == "{") L += token(i + 1) i += 2 - if(token(i) != "\]") - parse_error("Missing \] at end of reference.") + if(token(i) != "}") + parse_error("Missing } at end of pointer.") + + else if(token(i) == "(") // not a proc but an expression + var/list/sub_expression = list() + + i = expression(i + 1, sub_expression) + + if(token(i) != ")") + parse_error("Missing ) at end of expression.") + + L[++L.len] = sub_expression if(token(i + 1) == ".") L += "." i = variable(i + 2, L) - else if(token(i + 1) == "(") // OH BOY PROC + else if (token(i + 1) == "(") // OH BOY PROC var/list/arguments = list() i = call_function(i, null, arguments) L += ":" L[++L.len] = arguments - else if(token(i + 1) == "\[") // list index + else if (token(i + 1) == "\[") var/list/expression = list() i = expression(i + 2, expression) - if(token(i) != "]") + if (token(i) != "]") parse_error("Missing ] at the end of list access.") L += "\[" @@ -367,46 +407,49 @@ return i -//object_type: | string -/datum/SDQL_parser/proc/object_type(i, list/node) +//object_type: +/datum/sdql_parser/proc/object_type(i, list/node) - if(copytext(token(i), 1, 2) == "/") - node += token(i) + if(token(i)[1] != "/") + return parse_error("Expected type, but it didn't begin with /") - else - i = string(i, node) + var/path = text2path(token(i)) + if (path == null) + return parse_error("Nonexistent type path: [token(i)]") + + node += path return i + 1 -//comparitor: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' -/datum/SDQL_parser/proc/comparitor(i, list/node) +//comparator: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>=' +/datum/sdql_parser/proc/comparator(i, list/node) if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">=")) node += token(i) else - parse_error("Unknown comparitor [token(i)]") + parse_error("Unknown comparator [token(i)]") return i + 1 -//bool_operator: 'AND' | '&&' | 'OR' | '||' -/datum/SDQL_parser/proc/bool_operator(i, list/node) +//bool_operator: 'AND' | '&&' | 'OR' | '||' +/datum/sdql_parser/proc/bool_operator(i, list/node) if(tokenl(i) in list("and", "or", "&&", "||")) node += token(i) else - parse_error("Unknown comparitor [token(i)]") + parse_error("Unknown comparator [token(i)]") return i + 1 -//string: ''' ''' | '"' '"' -/datum/SDQL_parser/proc/string(i, list/node) +//string: ''' ''' | '"' '"' +/datum/sdql_parser/proc/string(i, list/node) - if(copytext(token(i), 1, 2) in list("'", "\"")) + if(token(i)[1] in list("'", "\"")) node += token(i) else @@ -414,37 +457,78 @@ return i + 1 - //array: '{' expression, expression, ... '}' -/datum/SDQL_parser/proc/array(var/i, var/list/node) - // Arrays get turned into this: list("{", list(exp_1a = exp_1b, ...), ...), "{" is to mark the next node as an array. - if(copytext(token(i), 1, 2) != "{") +//array: '[' expression_list ']' +/datum/sdql_parser/proc/array(i, list/node) + // Arrays get turned into this: list("[", list(exp_1a = exp_1b, ...), ...), "[" is to mark the next node as an array. + if(token(i)[1] != "\[") parse_error("Expected an array but found '[token(i)]'") return i + 1 - node += token(i) // Add the "{" + node += token(i) // Add the "[" + var/list/expression_list = list() - if(token(i + 1) != "}") + i++ + if(token(i) != "]") var/list/temp_expression_list = list() - + var/tok do - i = expression(i + 1, temp_expression_list) + tok = token(i) + if (tok == "," || tok == ":") + if (temp_expression_list == null) + parse_error("Found ',' or ':' without expression in an array.") + return i + 1 - if(token(i) == ",") expression_list[++expression_list.len] = temp_expression_list - temp_expression_list = list() - while(token(i) && token(i) != "}") + temp_expression_list = null + if (tok == ":") + temp_expression_list = list() + i = expression(i + 1, temp_expression_list) + expression_list[expression_list[expression_list.len]] = temp_expression_list + temp_expression_list = null + tok = token(i) + if (tok != ",") + if (tok == "]") + break - expression_list[++expression_list.len] = temp_expression_list - else - i++ + parse_error("Expected ',' or ']' after array assoc value, but found '[token(i)]'") + return i + + + i++ + continue + + temp_expression_list = list() + i = expression(i, temp_expression_list) + + while(token(i) && token(i) != "]") + + if (temp_expression_list) + expression_list[++expression_list.len] = temp_expression_list node[++node.len] = expression_list + return i + 1 -//call_function: ['(' [arguments] ')'] -/datum/SDQL_parser/proc/call_function(i, list/node, list/arguments) - var/list/cur_argument = list() +//selectors_array: '@[' object_selectors ']' +/datum/sdql_parser/proc/selectors_array(i, list/node) + if(token(i) == "@\[") + node += token(i++) + if(token(i) != "]") + var/list/select = list() + i = object_selectors(i, select) + node[++node.len] = select + if(token(i) != "]") + parse_error("Expected ']' to close selector array, but found '[token(i)]'") + else + parse_error("Selector array expected a selector, but found nothing") + else + parse_error("Expected '@\[' but found '[token(i)]'") + + return i + 1 + +//call_function: ['(' [arguments] ')'] +/datum/sdql_parser/proc/call_function(i, list/node, list/arguments) if(length(tokenl(i))) var/procname = "" if(tokenl(i) == "global" && token(i + 1) == ".") // Global proc. @@ -453,15 +537,19 @@ node += procname + token(i++) if(token(i) != "(") parse_error("Expected ( but found '[token(i)]'") + else if(token(i + 1) != ")") + var/list/temp_expression_list = list() do - i = expression(i + 1, cur_argument) + i = expression(i + 1, temp_expression_list) if(token(i) == ",") - arguments += list(cur_argument) - cur_argument = list() + arguments[++arguments.len] = temp_expression_list + temp_expression_list = list() continue + while(token(i) && token(i) != ")") - arguments += list(cur_argument) + + arguments[++arguments.len] = temp_expression_list // The code this is copy pasted from won't be executed when it's the last param, this fixes that. else i++ else @@ -469,33 +557,12 @@ return i + 1 -//select_function: count_function -/datum/SDQL_parser/proc/select_function(i, list/node) - - parse_error("Sorry, function calls aren't available yet") - - return i - - -//expression: ( unary_expression | '(' expression ')' | value ) [binary_operator expression] -/datum/SDQL_parser/proc/expression(i, list/node) +//expression: ( unary_expression | value ) [binary_operator expression] +/datum/sdql_parser/proc/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) @@ -503,7 +570,7 @@ i = binary_operator(i, node) i = expression(i, node) - else if(token(i) in comparitors) + else if(token(i) in comparators) i = binary_operator(i, node) var/list/rhs = list() @@ -515,8 +582,8 @@ return i -//unary_expression: unary_operator ( unary_expression | value | '(' expression ')' ) -/datum/SDQL_parser/proc/unary_expression(i, list/node) +//unary_expression: unary_operator ( unary_expression | value ) +/datum/sdql_parser/proc/unary_expression(i, list/node) if(token(i) in unary_operators) var/list/unary_exp = list() @@ -527,19 +594,6 @@ 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) @@ -552,10 +606,10 @@ return i -//binary_operator: comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' -/datum/SDQL_parser/proc/binary_operator(i, list/node) +//binary_operator: comparator | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%' +/datum/sdql_parser/proc/binary_operator(i, list/node) - if(token(i) in (binary_operators + comparitors)) + if(token(i) in (binary_operators + comparators)) node += token(i) else @@ -564,14 +618,13 @@ return i + 1 -//value: variable | string | number | 'null' -/datum/SDQL_parser/proc/value(i, list/node) - +//value: variable | string | number | 'null' | object_type | array | selectors_array +/datum/sdql_parser/proc/value(i, list/node) if(token(i) == "null") node += "null" i++ - else if(lowertext(copytext(token(i), 1, 3)) == "0x" && isnum(hex2num(copytext(token(i), 3)))) + else if(lowertext(copytext(token(i), 1, 3)) == "0x" && isnum(hex2num(copytext(token(i), 3))))//3 == length("0x") + 1 node += hex2num(copytext(token(i), 3)) i++ @@ -579,14 +632,22 @@ node += text2num(token(i)) i++ - else if(copytext(token(i), 1, 2) in list("'", "\"")) + else if(token(i)[1] in list("'", "\"")) i = string(i, node) - else if(copytext(token(i), 1, 2) == "{") // Start a list. + else if(token(i)[1] == "\[") // Start a list. i = array(i, node) + else if(copytext(token(i), 1, 3) == "@\[")//3 == length("@\[") + 1 + i = selectors_array(i, node) + + else if(token(i)[1] == "/") + i = object_type(i, node) + else i = variable(i, node) return i -/*EXPLAIN SELECT * WHERE 42 = 6 * 9 OR val = - 5 == 7*/ + +#undef SDQL2_VALID_OPTION_TYPES +#undef SDQL2_VALID_OPTION_VALUES diff --git a/code/modules/admin/verbs/SDQL2/useful_procs.dm b/code/modules/admin/verbs/SDQL2/useful_procs.dm index 3d2a0e0848d..267ee288804 100644 --- a/code/modules/admin/verbs/SDQL2/useful_procs.dm +++ b/code/modules/admin/verbs/SDQL2/useful_procs.dm @@ -27,3 +27,219 @@ CALL global.json_to_object_arbitrary_vars("{'type':'/obj/item/crowbar', 'color': thing.vars[attribute] = data[attribute] return thing + +// Wrappers for BYOND default procs which can't directly be called by call(). + +/proc/_abs(A) + return abs(A) + +/proc/_animate(atom/A, set_vars, time = 10, loop = 1, easing = LINEAR_EASING, flags = null) + var/mutable_appearance/MA = new() + for(var/v in set_vars) + MA.vars[v] = set_vars[v] + animate(A, appearance = MA, time, loop, easing, flags) + +/proc/_arccos(A) + return arccos(A) + +/proc/_arcsin(A) + return arcsin(A) + +/proc/_ascii2text(A) + return ascii2text(A) + +/proc/_block(Start, End) + return block(Start, End) + +/proc/_ckey(Key) + return ckey(Key) + +/proc/_ckeyEx(Key) + return ckeyEx(Key) + +/proc/_copytext(T, Start = 1, End = 0) + return copytext(T, Start, End) + +/proc/_cos(X) + return cos(X) + +/proc/_get_dir(Loc1, Loc2) + return get_dir(Loc1, Loc2) + +/proc/_get_dist(Loc1, Loc2) + return get_dist(Loc1, Loc2) + +/proc/_get_step(Ref, Dir) + return get_step(Ref, Dir) + +/proc/_hearers(Depth = world.view, Center = usr) + return hearers(Depth, Center) + +/proc/_image(icon, loc, icon_state, layer, dir) + return image(icon, loc, icon_state, layer, dir) + +/proc/_istype(object, type) + return istype(object, type) + +/proc/_ispath(path, type) + return ispath(path, type) + +/proc/_length(E) + return length(E) + +/proc/_link(thing, url) + thing << link(url) + +/proc/_locate(X, Y, Z) + if (isnull(Y)) // Assuming that it's only a single-argument call. + return locate(X) + + return locate(X, Y, Z) + +/proc/_log(X, Y) + return log(X, Y) + +/proc/_lowertext(T) + return lowertext(T) + +/proc/_matrix(a, b, c, d, e, f) + return matrix(a, b, c, d, e, f) + +/proc/_max(...) + return max(arglist(args)) + +/proc/_md5(T) + return md5(T) + +/proc/_min(...) + return min(arglist(args)) + +/proc/_new(type, arguments) + return new type (arglist(arguments)) + +/proc/_num2text(N, SigFig = 6) + return num2text(N, SigFig) + +/proc/_ohearers(Dist, Center = usr) + return ohearers(Dist, Center) + +/proc/_orange(Dist, Center = usr) + return orange(Dist, Center) + +/proc/_output(thing, msg, control) + thing << output(msg, control) + +/proc/_oview(Dist, Center = usr) + return oview(Dist, Center) + +/proc/_oviewers(Dist, Center = usr) + return oviewers(Dist, Center) + +/proc/_params2list(Params) + return params2list(Params) + +/proc/_pick(...) + return pick(arglist(args)) + +/proc/_prob(P) + return prob(P) + +/proc/_rand(L = 0, H = 1) + return rand(L, H) + +/proc/_range(Dist, Center = usr) + return range(Dist, Center) + +/proc/_regex(pattern, flags) + return regex(pattern, flags) + +/proc/_REGEX_QUOTE(text) + return REGEX_QUOTE(text) + +/proc/_REGEX_QUOTE_REPLACEMENT(text) + return REGEX_QUOTE_REPLACEMENT(text) + +/proc/_replacetext(Haystack, Needle, Replacement, Start = 1,End = 0) + return replacetext(Haystack, Needle, Replacement, Start, End) + +/proc/_replacetextEx(Haystack, Needle, Replacement, Start = 1,End = 0) + return replacetextEx(Haystack, Needle, Replacement, Start, End) + +/proc/_rgb(R, G, B) + return rgb(R, G, B) + +/proc/_rgba(R, G, B, A) + return rgb(R, G, B, A) + +/proc/_roll(dice) + return roll(dice) + +/proc/_round(A, B = 1) + return round(A, B) + +/proc/_sin(X) + return sin(X) + +/proc/_list_add(list/L, ...) + if (args.len < 2) + return + L += args.Copy(2) + +/proc/_list_copy(list/L, Start = 1, End = 0) + return L.Copy(Start, End) + +/proc/_list_cut(list/L, Start = 1, End = 0) + L.Cut(Start, End) + +/proc/_list_find(list/L, Elem, Start = 1, End = 0) + return L.Find(Elem, Start, End) + +/proc/_list_insert(list/L, Index, Item) + return L.Insert(Index, Item) + +/proc/_list_join(list/L, Glue, Start = 0, End = 1) + return L.Join(Glue, Start, End) + +/proc/_list_remove(list/L, ...) + if (args.len < 2) + return + L -= args.Copy(2) + +/proc/_list_set(list/L, key, value) + L[key] = value + +/proc/_list_numerical_add(L, key, num) + L[key] += num + +/proc/_list_swap(list/L, Index1, Index2) + L.Swap(Index1, Index2) + +/proc/_walk(ref, dir, lag) + walk(ref, dir, lag) + +/proc/_walk_towards(ref, trg, lag) + walk_towards(ref, trg, lag) + +/proc/_walk_to(ref, trg, min, lag) + walk_to(ref, trg, min, lag) + +/proc/_walk_away(ref, trg, max, lag) + walk_away(ref, trg, max, lag) + +/proc/_walk_rand(ref, lag) + walk_rand(ref, lag) + +/proc/_step(ref, dir) + step(ref, dir) + +/proc/_step_rand(ref) + step_rand(ref) + +/proc/_step_to(ref, trg, min) + step_to(ref, trg, min) + +/proc/_step_towards(ref, trg) + step_towards(ref, trg) + +/proc/_step_away(ref, trg, max) + step_away(ref, trg, max) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 9155d6bfbc8..e6020be3b5d 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -742,60 +742,67 @@ add_spell_to_statpanel(S) // Allow admins + PR reviewers to VIEW the panel. Doesnt mean they can click things. - if((is_admin(src) || check_rights(R_VIEWRUNTIMES, FALSE)) && (client?.prefs.toggles2 & PREFTOGGLE_2_MC_TABS)) + if((is_admin(src) || check_rights(R_VIEWRUNTIMES, FALSE))) + // Shows SDQL2 list + if(length(GLOB.sdql2_queries)) + if(statpanel("SDQL2")) + stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj) + for(var/i in GLOB.sdql2_queries) + var/datum/sdql2_query/Q = i + Q.generate_stat() // Below are checks to see which MC panel you are looking at - - // Shows MC Metadata - if(statpanel("MC|M")) - stat("Info", "Showing MC metadata") - var/turf/T = get_turf(client.eye) - stat("Location:", COORD(T)) - stat("CPU:", "[Master.formatcpu(world.cpu)]") - stat("Map CPU:", "[Master.formatcpu(world.map_cpu)]") - //stat("Map CPU:", "[Master.formatcpu(world.map_cpu)]") - stat("Instances:", "[num2text(world.contents.len, 10)]") - GLOB.stat_entry() - stat("Server Time:", time_stamp()) - if(Master) - Master.stat_entry() - else - stat("Master Controller:", "ERROR") - if(Failsafe) - Failsafe.stat_entry() - else - stat("Failsafe Controller:", "ERROR") - - // Shows subsystems with SS_NO_FIRE - if(statpanel("MC|N")) - stat("Info", "Showing subsystems that do not fire") - if(Master) - for(var/datum/controller/subsystem/SS as anything in Master.subsystems) - if(SS.flags & SS_NO_FIRE) - SS.stat_entry() - - // Shows subsystems with the SS_CPUDISPLAY_LOW flag - if(statpanel("MC|L")) - stat("Info", "Showing subsystems marked as low intensity") - if(Master) - for(var/datum/controller/subsystem/SS as anything in Master.subsystems) - if((SS.cpu_display == SS_CPUDISPLAY_LOW) && !(SS.flags & SS_NO_FIRE)) - SS.stat_entry() - - // Shows subsystems with the SS_CPUDISPLAY_DEFAULT flag - if(statpanel("MC|D")) - stat("Info", "Showing subsystems marked as default intensity") - if(Master) - for(var/datum/controller/subsystem/SS as anything in Master.subsystems) - if((SS.cpu_display == SS_CPUDISPLAY_DEFAULT) && !(SS.flags & SS_NO_FIRE)) - SS.stat_entry() - - // Shows subsystems with the SS_CPUDISPLAY_HIGH flag - if(statpanel("MC|H")) - stat("Info", "Showing subsystems marked as high intensity") - if(Master) - for(var/datum/controller/subsystem/SS as anything in Master.subsystems) - if((SS.cpu_display == SS_CPUDISPLAY_HIGH) && !(SS.flags & SS_NO_FIRE)) - SS.stat_entry() + if(client?.prefs.toggles2 & PREFTOGGLE_2_MC_TABS) + // Shows MC Metadata + if(statpanel("MC|M")) + stat("Info", "Showing MC metadata") + var/turf/T = get_turf(client.eye) + stat("Location:", COORD(T)) + stat("CPU:", "[Master.formatcpu(world.cpu)]") + stat("Map CPU:", "[Master.formatcpu(world.map_cpu)]") + //stat("Map CPU:", "[Master.formatcpu(world.map_cpu)]") + stat("Instances:", "[num2text(world.contents.len, 10)]") + GLOB.stat_entry() + stat("Server Time:", time_stamp()) + if(Master) + Master.stat_entry() + else + stat("Master Controller:", "ERROR") + if(Failsafe) + Failsafe.stat_entry() + else + stat("Failsafe Controller:", "ERROR") + + // Shows subsystems with SS_NO_FIRE + if(statpanel("MC|N")) + stat("Info", "Showing subsystems that do not fire") + if(Master) + for(var/datum/controller/subsystem/SS as anything in Master.subsystems) + if(SS.flags & SS_NO_FIRE) + SS.stat_entry() + + // Shows subsystems with the SS_CPUDISPLAY_LOW flag + if(statpanel("MC|L")) + stat("Info", "Showing subsystems marked as low intensity") + if(Master) + for(var/datum/controller/subsystem/SS as anything in Master.subsystems) + if((SS.cpu_display == SS_CPUDISPLAY_LOW) && !(SS.flags & SS_NO_FIRE)) + SS.stat_entry() + + // Shows subsystems with the SS_CPUDISPLAY_DEFAULT flag + if(statpanel("MC|D")) + stat("Info", "Showing subsystems marked as default intensity") + if(Master) + for(var/datum/controller/subsystem/SS as anything in Master.subsystems) + if((SS.cpu_display == SS_CPUDISPLAY_DEFAULT) && !(SS.flags & SS_NO_FIRE)) + SS.stat_entry() + + // Shows subsystems with the SS_CPUDISPLAY_HIGH flag + if(statpanel("MC|H")) + stat("Info", "Showing subsystems marked as high intensity") + if(Master) + for(var/datum/controller/subsystem/SS as anything in Master.subsystems) + if((SS.cpu_display == SS_CPUDISPLAY_HIGH) && !(SS.flags & SS_NO_FIRE)) + SS.stat_entry() statpanel("Status") // Switch to the Status panel again, for the sake of the lazy Stat procs