-
-
Notifications
You must be signed in to change notification settings - Fork 444
/
Copy pathInterpreter.dm
251 lines (229 loc) · 8.58 KB
/
Interpreter.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
* Macros: Status Macros
*/
///Indicates that the current function is returning a value.
#define RETURNING (1<<0)
///Indicates that the current loop is being terminated.
#define BREAKING (1<<2)
///Indicates that the rest of the current iteration of a loop is being skipped.
#define CONTINUING (1<<3)
///Indicates that we are entering a new function and the allowed_status var should be cleared
#define RESET_STATUS (1<<4)
/*
* Macros: Maximums
* MAX_STATEMENTS fuckin'... holds the maximum statements. I'unno, dude, I'm not the guy who made NTSL,
* I don't do fuckin verbose-ass comments line this.
* Figure it out yourself, fuckface.
*
* very helpful comment ^ ty
*/
///Maximum amount of statements that can be called in one execution. this is to prevent massive crashes and exploitation
#define MAX_STATEMENTS 900
///Max number of uninterrupted loops possible
#define MAX_ITERATIONS 100
///<ax recursions without returning anything (or completing the code block)
#define MAX_RECURSION 10
#define MAX_STRINGLEN 1024
#define MAX_LISTLEN 256
/**
* n_Interpreter
* Procedures allowing for interaction with the script that is being run by the interpreter object.
*/
/datum/n_Interpreter
var/datum/scope/globalScope
var/datum/node/BlockDefinition/program
var/datum/node/statement/FunctionDefinition/curFunction
var/datum/stack/functions = new()
var/datum/TCS_Compiler/container // associated container for interpeter
///Boolean indicating that the rest of the current block should be skipped. This may be set to any combination of <Status Macros>.
var/status = FALSE
var/returnVal
/// current amount of statements called
var/cur_statements = FALSE
//Boolean on wheteher admins should be notified of anymore issues.
var/alertadmins = FALSE
///Current amount of recursion
var/cur_recursion = 0
///Boolean that will reset global variables after Run() finishes.
var/persist = TRUE
var/paused = FALSE
/datum/n_Interpreter/New(datum/node/BlockDefinition/GlobalBlock/program)
. = ..()
if(program)
Load(program)
/**
* Load
* Loads a 'compiled' script into Memory.
* program - A <GlobalBlock> object which represents the script's global scope.
*
* Parameters:
* program - A <GlobalBlock> object which represents the script's global scope.
*/
/datum/n_Interpreter/proc/Load(datum/node/BlockDefinition/GlobalBlock/program)
ASSERT(program)
src.program = program
CreateGlobalScope()
alertadmins = FALSE
///Trims strings and vectors down to an acceptable size, to prevent runaway memory usage
/datum/n_Interpreter/proc/Trim(value)
if(istext(value) && (length(value) > MAX_STRINGLEN))
value = copytext(value, 1, MAX_STRINGLEN+1)
else if(islist(value) && (length(value) > MAX_LISTLEN))
var/list/L = value
value = L.Copy(1, MAX_LISTLEN+1)
return value
///Sets ourselves to Garbage Collect.
/datum/n_Interpreter/proc/garbage_collect()
container = null
///Raises a Runtime error.
/datum/n_Interpreter/proc/RaiseError(datum/runtimeError/e, datum/scope/scope, datum/token/token)
e.scope = scope
if(istype(token))
e.token = token
else if(istype(token, /datum/node))
var/datum/node/N = token
e.token = N.token
HandleError(e)
/datum/n_Interpreter/proc/CreateGlobalScope()
var/datum/scope/S = new(program, null)
globalScope = S
for(var/functype in subtypesof(/datum/n_function/default))
var/datum/n_function/default/god_damn_it_byond = functype
if(!istype(src, initial(god_damn_it_byond.interp_type)))
continue
var/datum/n_function/default/func = new functype()
globalScope.init_var(func.name, func)
for(var/alias in func.aliases)
globalScope.init_var(alias, func)
return S
///Alerts the admins of a script that is bad.
/datum/n_Interpreter/proc/AlertAdmins()
if(!container || alertadmins)
return
if(!istype(container, /datum/TCS_Compiler))
return
var/datum/TCS_Compiler/Compiler = container
var/obj/machinery/telecomms/server/Holder = Compiler.Holder
var/message = "Potential crash-inducing NTSL script detected at telecommunications server [Compiler.Holder] ([Holder.x], [Holder.y], [Holder.z])."
alertadmins = TRUE
message_admins(message)
///Runs each statement in a block of code.
/datum/n_Interpreter/proc/RunBlock(datum/node/BlockDefinition/Block, datum/scope/scope = globalScope)
if(cur_statements >= MAX_STATEMENTS)
return
for(var/datum/node/S in Block.statements)
while(paused)
sleep(1 SECONDS)
cur_statements++
if(cur_statements >= MAX_STATEMENTS)
RaiseError(new /datum/runtimeError/MaxCPU(MAX_STATEMENTS), scope, S)
AlertAdmins()
break
if(istype(S, /datum/node/expression))
. = Eval(S, scope)
else if(istype(S, /datum/node/statement/VariableDeclaration))
//VariableDeclaration nodes are used to forcibly declare a local variable so that one in a higher scope isn't used by default.
var/datum/node/statement/VariableDeclaration/dec=S
scope.init_var(dec.var_name.id_name, src, S)
else if(istype(S, /datum/node/statement/FunctionDefinition))
var/datum/node/statement/FunctionDefinition/dec=S
scope.init_var(dec.func_name, new /datum/n_function/defined(dec, scope, src), src, S)
else if(istype(S, /datum/node/statement/WhileLoop))
. = RunWhile(S, scope)
else if(istype(S, /datum/node/statement/ForLoop))
. = RunFor(S, scope)
else if(istype(S, /datum/node/statement/IfStatement))
. = RunIf(S, scope)
else if(istype(S, /datum/node/statement/ReturnStatement))
if(!(scope.allowed_status & RETURNING))
RaiseError(new /datum/runtimeError/UnexpectedReturn(), scope, S)
continue
scope.status |= RETURNING
. = (scope.return_val=Eval(S:value, scope))
break
else if(istype(S, /datum/node/statement/BreakStatement))
if(!(scope.allowed_status & BREAKING))
//RaiseError(new /datum/runtimeError/UnexpectedReturn())
continue
scope.status |= BREAKING
break
else if(istype(S, /datum/node/statement/ContinueStatement))
if(!(scope.allowed_status & CONTINUING))
//RaiseError(new /datum/runtimeError/UnexpectedReturn())
continue
scope.status |= CONTINUING
break
else
RaiseError(new /datum/runtimeError/UnknownInstruction(S), scope, S)
if(scope.status)
break
///Runs a function block or a proc with the arguments specified in the script.
/datum/n_Interpreter/proc/RunFunction(datum/node/expression/FunctionCall/stmt, datum/scope/scope)
var/datum/n_function/func
var/this_obj
if(istype(stmt.function, /datum/node/expression/member))
var/datum/node/expression/member/M = stmt.function
this_obj = M.temp_object = Eval(M.object, scope)
func = Eval(M, scope)
else
func = Eval(stmt.function, scope)
if(!istype(func))
RaiseError(new /datum/runtimeError/UndefinedFunction("[stmt.function.ToString()]"), scope, stmt)
return
var/list/params = list()
for(var/datum/node/expression/P in stmt.parameters)
params+=list(Eval(P, scope))
try
return func.execute(this_obj, params, scope, src, stmt)
catch(var/exception/E)
RaiseError(new /datum/runtimeError/Internal(E), scope, stmt)
///Checks a condition and runs either the if block or else block.
/datum/n_Interpreter/proc/RunIf(datum/node/statement/IfStatement/stmt, datum/scope/scope)
if(!stmt.skip)
scope = scope.push(stmt.block)
if(Eval(stmt.cond, scope))
. = RunBlock(stmt.block, scope)
// Loop through the if else chain and tell them to be skipped.
var/datum/node/statement/IfStatement/i = stmt.else_if
var/fail_safe = 800
while(i && fail_safe)
fail_safe -= 1
i.skip = 1
i = i.else_if
else if(stmt.else_block)
. = RunBlock(stmt.else_block, scope)
scope = scope.pop()
// We don't need to skip you anymore.
stmt.skip = FALSE
///Runs a while loop.
/datum/n_Interpreter/proc/RunWhile(datum/node/statement/WhileLoop/stmt, datum/scope/scope)
var/i=1
scope = scope.push(stmt.block, allowed_status = CONTINUING | BREAKING)
while(Eval(stmt.cond, scope) && Iterate(stmt.block, scope, i++))
continue
scope = scope.pop(RETURNING)
/datum/n_Interpreter/proc/RunFor(datum/node/statement/ForLoop/stmt, datum/scope/scope)
var/i=1
scope = scope.push(stmt.block)
Eval(stmt.init, scope)
while(Eval(stmt.test, scope))
if(Iterate(stmt.block, scope, i++))
Eval(stmt.increment, scope)
else
break
scope = scope.pop(RETURNING)
///Runs a single iteration of a loop. Returns a value indicating whether or not to continue looping.
/datum/n_Interpreter/proc/Iterate(datum/node/BlockDefinition/block, datum/scope/scope, count)
RunBlock(block, scope)
if(MAX_ITERATIONS > 0 && count >= MAX_ITERATIONS)
RaiseError(new /datum/runtimeError/IterationLimitReached(), scope, block)
return FALSE
if(status & (BREAKING|RETURNING))
return FALSE
status &= ~CONTINUING
return TRUE
#undef MAX_STATEMENTS
#undef MAX_ITERATIONS
#undef MAX_RECURSION
#undef MAX_STRINGLEN
#undef MAX_LISTLEN