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