mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-21 12:04:48 +01:00
@@ -26,6 +26,13 @@
|
||||
investigate_log_subjects |= subject
|
||||
F << "<small>[time_stamp()] \ref[src] ([x],[y],[z])</small> || [src] [message]<br>"
|
||||
|
||||
/proc/log_investigate(message, subject)
|
||||
if(!message) return
|
||||
var/F = investigate_subject2file(subject)
|
||||
if(!F) return
|
||||
investigate_log_subjects |= subject
|
||||
F << "<small>[time_stamp()] || [message]<br>"
|
||||
|
||||
//ADMINVERBS
|
||||
/client/proc/investigate_show( subject in investigate_log_subjects )
|
||||
set name = "Investigate"
|
||||
|
||||
@@ -720,7 +720,7 @@ var/list/admin_verbs_ticket = list(
|
||||
var/list/Lines = file2list("config/admins.txt")
|
||||
for(var/line in Lines)
|
||||
var/list/splitline = splittext(line, " - ")
|
||||
if(n_lower(splitline[1]) == ckey)
|
||||
if(lowertext(splitline[1]) == ckey)
|
||||
if(splitline.len >= 2)
|
||||
rank = ckeyEx(splitline[2])
|
||||
break
|
||||
|
||||
@@ -937,25 +937,20 @@ Traitors and the like can also be revived with the previous role mostly intact.
|
||||
|
||||
/client/proc/reset_all_tcs()
|
||||
set category = "Admin"
|
||||
set name = "Reset Telecomms Scripts"
|
||||
set desc = "Blanks all telecomms scripts from all telecomms servers"
|
||||
set name = "Reset NTSL2 Configuration"
|
||||
set desc = "Resets NTSL2 to the default configuration."
|
||||
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
var/confirm = alert(src, "You sure you want to blank all NTSL scripts?", "Confirm", "Yes", "No")
|
||||
var/confirm = alert(src, "You sure you want to reset NTSL2?", "Confirm", "Yes", "No")
|
||||
if(confirm != "Yes")
|
||||
return
|
||||
|
||||
for(var/obj/machinery/telecomms/server/S in telecomms_list)
|
||||
var/datum/TCS_Compiler/C = S.Compiler
|
||||
S.rawcode = ""
|
||||
C.Compile("")
|
||||
for(var/obj/machinery/computer/telecomms/traffic/T in GLOB.machines)
|
||||
T.storedcode = ""
|
||||
log_admin("[key_name(usr)] blanked all telecomms scripts.")
|
||||
message_admins("[key_name_admin(usr)] blanked all telecomms scripts.")
|
||||
feedback_add_details("admin_verb","RAT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
GLOB.ntsl2_config.reset()
|
||||
log_admin("[key_name(usr)] reset NTSL2 scripts.")
|
||||
message_admins("[key_name_admin(usr)] reset NTSL2 scripts.")
|
||||
feedback_add_details("admin_verb","RAT2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/client/proc/list_ssds()
|
||||
set category = "Admin"
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
slots_list += "paranormal: [paranormal_slots]"
|
||||
if(cyborg_slots > 0)
|
||||
slots_list += "cyborg: [cyborg_slots]"
|
||||
var slot_text = list_implode(slots_list, ", ")
|
||||
var/slot_text = jointext(slots_list, ", ")
|
||||
notify_ghosts("An ERT is being dispatched. Open positions: [slot_text]")
|
||||
message_admins("[key_name_admin(usr)] dispatched a [ert_type] ERT. Slots: [slot_text]", 1)
|
||||
log_admin("[key_name(usr)] dispatched a [ert_type] ERT. Slots: [slot_text]")
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
File: AST Nodes
|
||||
An abstract syntax tree (AST) is a representation of source code in a computer-friendly format. It is composed of nodes,
|
||||
each of which represents a certain part of the source code. For example, an <IfStatement> node represents an if statement in the
|
||||
script's source code. Because it is a representation of the source code in memory, it is independent of any specific scripting language.
|
||||
This allows a script in any language for which a parser exists to be run by the interpreter.
|
||||
|
||||
The AST is produced by an <n_Parser> object. It consists of a <GlobalBlock> with an arbitrary amount of statements. These statements are
|
||||
run in order by an <n_Interpreter> object. A statement may in turn run another block (such as an if statement might if its condition is
|
||||
met).
|
||||
|
||||
Articles:
|
||||
- <http://en.wikipedia.org/wiki/Abstract_syntax_tree>
|
||||
*/
|
||||
/*
|
||||
Constants: Operator Precedence
|
||||
OOP_OR - Logical or
|
||||
OOP_AND - Logical and
|
||||
OOP_BIT - Bitwise operations
|
||||
OOP_EQUAL - Equality checks
|
||||
OOP_COMPARE - Greater than, less then, etc
|
||||
OOP_ADD - Addition and subtraction
|
||||
OOP_MULTIPLY - Multiplication and division
|
||||
OOP_POW - Exponents
|
||||
OOP_UNARY - Unary Operators
|
||||
OOP_GROUP - Parentheses
|
||||
*/
|
||||
/var/const/OOP_OR = 1 //||
|
||||
/var/const/OOP_AND = OOP_OR + 1 //&&
|
||||
/var/const/OOP_BIT = OOP_AND + 1 //&, |
|
||||
/var/const/OOP_EQUAL = OOP_BIT + 1 //==, !=
|
||||
/var/const/OOP_COMPARE = OOP_EQUAL + 1 //>, <, >=, <=
|
||||
/var/const/OOP_ADD = OOP_COMPARE + 1 //+, -
|
||||
/var/const/OOP_MULTIPLY= OOP_ADD + 1 //*, /, %
|
||||
/var/const/OOP_POW = OOP_MULTIPLY + 1 //^
|
||||
/var/const/OOP_UNARY = OOP_POW + 1 //!
|
||||
/var/const/OOP_GROUP = OOP_UNARY + 1 //()
|
||||
|
||||
/*
|
||||
Class: node
|
||||
*/
|
||||
/datum/node/proc/ToString()
|
||||
return "[src.type]"
|
||||
/*
|
||||
Class: identifier
|
||||
*/
|
||||
/datum/node/identifier
|
||||
var/id_name
|
||||
|
||||
/datum/node/identifier/New(id)
|
||||
. = ..()
|
||||
src.id_name = id
|
||||
|
||||
/datum/node/identifier/ToString()
|
||||
return id_name
|
||||
|
||||
/*
|
||||
Class: expression
|
||||
*/
|
||||
/datum/node/expression
|
||||
/*
|
||||
Class: operator
|
||||
See <Binary Operators> and <Unary Operators> for subtypes.
|
||||
*/
|
||||
/datum/node/expression/operator
|
||||
var/datum/node/expression/exp
|
||||
var/token = "" // Used when giving type mismatches.
|
||||
var/tmp/name
|
||||
var/tmp/precedence
|
||||
|
||||
/datum/node/expression/operator/New()
|
||||
.=..()
|
||||
if(!src.name)
|
||||
src.name = "[src.type]"
|
||||
|
||||
/datum/node/expression/operator/ToString()
|
||||
return "operator: [name]"
|
||||
|
||||
/*
|
||||
Class: FunctionCall
|
||||
*/
|
||||
/datum/node/expression/FunctionCall
|
||||
//Function calls can also be expressions or statements.
|
||||
var/func_name
|
||||
var/datum/node/identifier/object
|
||||
var/list/parameters = list()
|
||||
|
||||
/*
|
||||
Class: literal
|
||||
*/
|
||||
/datum/node/expression/value/literal
|
||||
var/value
|
||||
|
||||
/datum/node/expression/value/literal/New(value)
|
||||
. = ..()
|
||||
src.value = value
|
||||
|
||||
/datum/node/expression/value/literal/ToString()
|
||||
return src.value
|
||||
|
||||
/*
|
||||
Class: variable
|
||||
*/
|
||||
/datum/node/expression/value/variable
|
||||
var/datum/node/object //Either a node/identifier or another node/expression/value/variable which points to the object
|
||||
var/datum/node/identifier/id
|
||||
|
||||
|
||||
/datum/node/expression/value/variable/New(ident)
|
||||
. = ..()
|
||||
id = ident
|
||||
if(istext(id))
|
||||
id = new(id)
|
||||
|
||||
/datum/node/expression/value/variable/ToString()
|
||||
return src.id.ToString()
|
||||
|
||||
/*
|
||||
Class: reference
|
||||
*/
|
||||
/datum/node/expression/value/reference
|
||||
var/datum/value
|
||||
|
||||
/datum/node/expression/value/reference/New(value)
|
||||
. = ..()
|
||||
src.value = value
|
||||
|
||||
/datum/node/expression/value/reference/ToString()
|
||||
return "ref: [src.value] ([src.value.type])"
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
File: Block Types
|
||||
*/
|
||||
/*
|
||||
Class: BlockDefinition
|
||||
An object representing a set of actions to perform independently from the rest of the script. Blocks are basically just
|
||||
lists of statements to execute which also contain some local variables and methods. Note that since functions are local to a block,
|
||||
it is possible to have a function definition inside of any type of block (such as in an if statement or another function),
|
||||
and not just in the global scope as in many languages.
|
||||
*/
|
||||
/datum/node/BlockDefinition
|
||||
var/list/statements = new
|
||||
var/list/functions = new
|
||||
var/list/initial_variables = new
|
||||
|
||||
/*
|
||||
Proc: SetVar
|
||||
Defines a permanent variable. The variable will not be deleted when it goes out of scope.
|
||||
|
||||
Notes:
|
||||
Since all pre-existing temporary variables are deleted, it is not generally desirable to use this proc after the interpreter has been instantiated.
|
||||
Instead, use <n_Interpreter.SetVar()>.
|
||||
|
||||
See Also:
|
||||
- <n_Interpreter.SetVar()>
|
||||
*/
|
||||
/datum/node/BlockDefinition/proc/SetVar(name, value)
|
||||
initial_variables[name] = value
|
||||
|
||||
|
||||
/*
|
||||
Class: GlobalBlock
|
||||
A block object representing the global scope.
|
||||
*/
|
||||
//
|
||||
/datum/node/BlockDefinition/GlobalBlock/New()
|
||||
initial_variables["null"] = null
|
||||
return ..()
|
||||
|
||||
/*
|
||||
Class: FunctionBlock
|
||||
A block representing a function body.
|
||||
*/
|
||||
//
|
||||
/datum/node/BlockDefinition/FunctionBlock
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
File: Binary Operators
|
||||
*/
|
||||
/*
|
||||
Class: binary
|
||||
Represents a binary operator in the AST. A binary operator takes two operands (ie x and y) and returns a value.
|
||||
*/
|
||||
/datum/node/expression/operator/binary
|
||||
var/datum/node/expression/exp2
|
||||
|
||||
////////// Comparison Operators //////////
|
||||
/*
|
||||
Class: Equal
|
||||
Returns true if x = y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Equal
|
||||
token = "=="
|
||||
precedence = OOP_EQUAL
|
||||
|
||||
/*
|
||||
Class: NotEqual
|
||||
Returns true if x and y aren't equal.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/NotEqual
|
||||
token = "!="
|
||||
precedence = OOP_EQUAL
|
||||
|
||||
/*
|
||||
Class: Greater
|
||||
Returns true if x > y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Greater
|
||||
token = ">"
|
||||
precedence = OOP_COMPARE
|
||||
|
||||
/*
|
||||
Class: Less
|
||||
Returns true if x < y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Less
|
||||
token = "<"
|
||||
precedence = OOP_COMPARE
|
||||
|
||||
/*
|
||||
Class: GreaterOrEqual
|
||||
Returns true if x >= y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/GreaterOrEqual
|
||||
token = ">="
|
||||
precedence = OOP_COMPARE
|
||||
|
||||
/*
|
||||
Class: LessOrEqual
|
||||
Returns true if x <= y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/LessOrEqual
|
||||
token = "<="
|
||||
precedence = OOP_COMPARE
|
||||
|
||||
|
||||
////////// Logical Operators //////////
|
||||
|
||||
/*
|
||||
Class: LogicalAnd
|
||||
Returns true if x and y are true.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/LogicalAnd
|
||||
token = "&&"
|
||||
precedence = OOP_AND
|
||||
|
||||
/*
|
||||
Class: LogicalOr
|
||||
Returns true if x, y, or both are true.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/LogicalOr
|
||||
token = "||"
|
||||
precedence = OOP_OR
|
||||
|
||||
/*
|
||||
Class: LogicalXor
|
||||
Returns true if either x or y but not both are true.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/LogicalXor //Not implemented in nS
|
||||
precedence = OOP_OR
|
||||
|
||||
|
||||
////////// Bitwise Operators //////////
|
||||
|
||||
/*
|
||||
Class: BitwiseAnd
|
||||
Performs a bitwise and operation.
|
||||
|
||||
Example:
|
||||
011 & 110 = 010
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/BitwiseAnd
|
||||
token = "&"
|
||||
precedence = OOP_BIT
|
||||
|
||||
/*
|
||||
Class: BitwiseOr
|
||||
Performs a bitwise or operation.
|
||||
|
||||
Example:
|
||||
011 | 110 = 111
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/BitwiseOr
|
||||
token = "|"
|
||||
precedence = OOP_BIT
|
||||
|
||||
/*
|
||||
Class: BitwiseXor
|
||||
Performs a bitwise exclusive or operation.
|
||||
|
||||
Example:
|
||||
011 xor 110 = 101
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/BitwiseXor
|
||||
token = "`"
|
||||
precedence = OOP_BIT
|
||||
|
||||
|
||||
////////// Arithmetic Operators //////////
|
||||
|
||||
/*
|
||||
Class: Add
|
||||
Returns the sum of x and y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Add
|
||||
token = "+"
|
||||
precedence = OOP_ADD
|
||||
|
||||
/*
|
||||
Class: Subtract
|
||||
Returns the difference of x and y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Subtract
|
||||
token = "-"
|
||||
precedence = OOP_ADD
|
||||
|
||||
/*
|
||||
Class: Multiply
|
||||
Returns the product of x and y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Multiply
|
||||
token = "*"
|
||||
precedence = OOP_MULTIPLY
|
||||
|
||||
/*
|
||||
Class: Divide
|
||||
Returns the quotient of x and y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Divide
|
||||
token = "/"
|
||||
precedence = OOP_MULTIPLY
|
||||
|
||||
/*
|
||||
Class: Power
|
||||
Returns x raised to the power of y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Power
|
||||
token = "^"
|
||||
precedence = OOP_POW
|
||||
|
||||
/*
|
||||
Class: Modulo
|
||||
Returns the remainder of x / y.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/binary/Modulo
|
||||
token = "%"
|
||||
precedence = OOP_MULTIPLY
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
File: Unary Operators
|
||||
*/
|
||||
/*
|
||||
Class: unary
|
||||
Represents a unary operator in the AST. Unary operators take a single operand (referred to as x below) and return a value.
|
||||
*/
|
||||
/datum/node/expression/operator/unary
|
||||
precedence = OOP_UNARY
|
||||
|
||||
/*
|
||||
Class: LogicalNot
|
||||
Returns !x.
|
||||
|
||||
Example:
|
||||
!true = false and !false = true
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/unary/LogicalNot
|
||||
name = "logical not"
|
||||
|
||||
/*
|
||||
Class: BitwiseNot
|
||||
Returns the value of a bitwise not operation performed on x.
|
||||
|
||||
Example:
|
||||
~10 (decimal 2) = 01 (decimal 1).
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/unary/BitwiseNot
|
||||
name = "bitwise not"
|
||||
|
||||
/*
|
||||
Class: Minus
|
||||
Returns -x.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/unary/Minus
|
||||
name = "minus"
|
||||
|
||||
/*
|
||||
Class: group
|
||||
A special unary operator representing a value in parentheses.
|
||||
*/
|
||||
//
|
||||
/datum/node/expression/operator/unary/group
|
||||
precedence = OOP_GROUP
|
||||
|
||||
/datum/node/expression/operator/unary/New(var/datum/node/expression/exp)
|
||||
src.exp = exp
|
||||
return ..()
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
File: Statement Types
|
||||
*/
|
||||
/*
|
||||
Class: statement
|
||||
An object representing a single instruction run by an interpreter.
|
||||
*/
|
||||
/datum/node/statement
|
||||
/*
|
||||
Class: FunctionCall
|
||||
Represents a call to a function.
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/FunctionCall
|
||||
var/func_name
|
||||
var/datum/node/identifier/object
|
||||
var/list/parameters = new
|
||||
|
||||
/*
|
||||
Class: FunctionDefinition
|
||||
Defines a function.
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/FunctionDefinition
|
||||
var/func_name
|
||||
var/list/parameters = new
|
||||
var/datum/node/BlockDefinition/FunctionBlock/block
|
||||
|
||||
/*
|
||||
Class: VariableAssignment
|
||||
Sets a variable in an accessible scope to the given value if one exists, otherwise initializes a new local variable to the given value.
|
||||
|
||||
Notes:
|
||||
If a variable with the same name exists in a higher block, the value will be assigned to it. If not,
|
||||
a new variable is created in the current block. To force creation of a new variable, use <VariableDeclaration>.
|
||||
|
||||
See Also:
|
||||
- <VariableDeclaration>
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/VariableAssignment
|
||||
var/datum/node/identifier/object
|
||||
var/datum/node/identifier/var_name
|
||||
var/datum/node/expression/value
|
||||
|
||||
/*
|
||||
Class: VariableDeclaration
|
||||
Intializes a local variable to a null value.
|
||||
|
||||
See Also:
|
||||
- <VariableAssignment>
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/VariableDeclaration
|
||||
var/datum/node/identifier/object
|
||||
var/datum/node/identifier/var_name
|
||||
|
||||
/*
|
||||
Class: IfStatement
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/IfStatement
|
||||
var/skip = 0
|
||||
var/datum/node/BlockDefinition/block
|
||||
var/datum/node/BlockDefinition/else_block //may be null
|
||||
var/datum/node/expression/cond
|
||||
var/datum/node/statement/else_if
|
||||
|
||||
/datum/node/statement/IfStatement/ElseIf
|
||||
|
||||
/*
|
||||
Class: WhileLoop
|
||||
Loops while a given condition is true.
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/WhileLoop
|
||||
var/datum/node/BlockDefinition/block
|
||||
var/datum/node/expression/cond
|
||||
|
||||
/*
|
||||
Class: ForLoop
|
||||
Loops while test is true, initializing a variable, increasing the variable
|
||||
*/
|
||||
/datum/node/statement/ForLoop
|
||||
var/datum/node/BlockDefinition/block
|
||||
var/datum/node/expression/test
|
||||
var/datum/node/expression/init
|
||||
var/datum/node/expression/increment
|
||||
|
||||
/*
|
||||
Class: BreakStatement
|
||||
Ends a loop.
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/BreakStatement
|
||||
|
||||
/*
|
||||
Class: ContinueStatement
|
||||
Skips to the next iteration of a loop.
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/ContinueStatement
|
||||
|
||||
/*
|
||||
Class: ReturnStatement
|
||||
Ends the function and returns a value.
|
||||
*/
|
||||
//
|
||||
/datum/node/statement/ReturnStatement
|
||||
var/datum/node/expression/value
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
File: Errors
|
||||
*/
|
||||
/*
|
||||
Class: scriptError
|
||||
An error scanning or parsing the source code.
|
||||
*/
|
||||
/datum/scriptError
|
||||
/*
|
||||
Var: message
|
||||
A message describing the problem.
|
||||
*/
|
||||
var/message
|
||||
|
||||
/datum/scriptError/New(msg = null)
|
||||
if(msg)message = msg
|
||||
|
||||
/datum/scriptError/BadToken
|
||||
message = "Unexpected token: "
|
||||
var/datum/token/token
|
||||
|
||||
/datum/scriptError/BadToken/New(datum/token/t)
|
||||
token = t
|
||||
if(t && t.line)
|
||||
message = "[t.line]: [message]"
|
||||
|
||||
if(istype(t))
|
||||
message += "[t.value]"
|
||||
|
||||
else
|
||||
message += "[t]"
|
||||
|
||||
/datum/scriptError/InvalidID
|
||||
parent_type = /datum/scriptError/BadToken
|
||||
message = "Invalid identifier name: "
|
||||
|
||||
/datum/scriptError/ReservedWord
|
||||
parent_type = /datum/scriptError/BadToken
|
||||
message = "Identifer using reserved word: "
|
||||
|
||||
/datum/scriptError/BadNumber
|
||||
parent_type = /datum/scriptError/BadToken
|
||||
message = "Bad number: "
|
||||
|
||||
/datum/scriptError/BadReturn
|
||||
var/datum/token/token
|
||||
message = "Unexpected return statement outside of a function."
|
||||
|
||||
/datum/scriptError/BadReturn/New(datum/token/t)
|
||||
src.token = t
|
||||
|
||||
/datum/scriptError/EndOfFile
|
||||
message = "Unexpected end of file."
|
||||
|
||||
/datum/scriptError/ExpectedToken
|
||||
message = "Expected: '"
|
||||
|
||||
/datum/scriptError/ExpectedToken/New(id, datum/token/T)
|
||||
if(T && T.line)
|
||||
message = "[T.line]: [message]"
|
||||
|
||||
message += "[id]'. "
|
||||
|
||||
if(T)
|
||||
message += "Found '[T.value]'."
|
||||
|
||||
|
||||
/datum/scriptError/UnterminatedComment
|
||||
message = "Unterminated multi-line comment statement: expected */"
|
||||
|
||||
/datum/scriptError/DuplicateFunction/New(name, datum/token/t)
|
||||
message = "Function '[name]' defined twice."
|
||||
|
||||
/datum/scriptError/ParameterFunction
|
||||
message = "You cannot use a function inside a parameter."
|
||||
|
||||
/datum/scriptError/ParameterFunction/New(datum/token/t)
|
||||
var/line = "?"
|
||||
if(t)
|
||||
line = t.line
|
||||
message = "[line]: [message]"
|
||||
|
||||
/*
|
||||
Class: runtimeError
|
||||
An error thrown by the interpreter in running the script.
|
||||
*/
|
||||
/datum/runtimeError
|
||||
var/name
|
||||
/*
|
||||
Var: message
|
||||
A basic description as to what went wrong.
|
||||
*/
|
||||
var/message
|
||||
var/datum/stack/stack
|
||||
/*
|
||||
Proc: ToString
|
||||
Returns a description of the error suitable for showing to the user.
|
||||
*/
|
||||
/datum/runtimeError/proc/ToString()
|
||||
. = "[name]: [message]"
|
||||
if(!stack.Top())
|
||||
return
|
||||
|
||||
. += "\nStack:"
|
||||
while(stack.Top())
|
||||
var/datum/node/statement/FunctionCall/stmt = stack.Pop()
|
||||
. += "\n\t [stmt.func_name]()"
|
||||
|
||||
/datum/runtimeError/TypeMismatch
|
||||
name = "TypeMismatchError"
|
||||
|
||||
/datum/runtimeError/TypeMismatch/New(op, a, b)
|
||||
message = "Type mismatch: '[a]' [op] '[b]'"
|
||||
|
||||
/datum/runtimeError/TypeMismatch/unary/New(op, a)
|
||||
message = "Type mismatch: [op]'[a]'"
|
||||
|
||||
/datum/runtimeError/TypeMismatch/New(op, a, b)
|
||||
message = "Type mismatch: '[a]' [op] '[b]'"
|
||||
|
||||
/datum/runtimeError/UnexpectedReturn
|
||||
name = "UnexpectedReturnError"
|
||||
message = "Unexpected return statement."
|
||||
|
||||
/datum/runtimeError/UnknownInstruction
|
||||
name = "UnknownInstructionError"
|
||||
message = "Unknown instruction type. This may be due to incompatible compiler and interpreter versions or a lack of implementation."
|
||||
|
||||
/datum/runtimeError/UndefinedVariable
|
||||
name = "UndefinedVariableError"
|
||||
|
||||
/datum/runtimeError/UndefinedVariable/New(variable)
|
||||
message = "Variable '[variable]' has not been declared."
|
||||
|
||||
/datum/runtimeError/UndefinedFunction
|
||||
name = "UndefinedFunctionError"
|
||||
|
||||
/datum/runtimeError/UndefinedFunction/New(function)
|
||||
message = "Function '[function]()' has not been defined."
|
||||
|
||||
/datum/runtimeError/DuplicateVariableDeclaration
|
||||
name = "DuplicateVariableError"
|
||||
|
||||
/datum/runtimeError/DuplicateVariableDeclaration/New(variable)
|
||||
message="Variable '[variable]' was already declared."
|
||||
|
||||
/datum/runtimeError/IterationLimitReached
|
||||
name = "MaxIterationError"
|
||||
message = "A loop has reached its maximum number of iterations."
|
||||
|
||||
/datum/runtimeError/RecursionLimitReached
|
||||
name = "MaxRecursionError"
|
||||
message = "The maximum amount of recursion has been reached."
|
||||
|
||||
/datum/runtimeError/DivisionByZero
|
||||
name = "DivideByZeroError"
|
||||
message = "Division by zero attempted."
|
||||
|
||||
/datum/runtimeError/MaxCPU
|
||||
name = "MaxComputationalUse"
|
||||
message = "Maximum amount of computational cycles reached (>= 1000)."
|
||||
|
||||
/datum/runtimeError/VectorLimit
|
||||
name = "VectorSizeOverflow"
|
||||
message = "Maximum vector size reached"
|
||||
|
||||
/datum/runtimeError/StringLimit
|
||||
name = "StringSizeOverflow"
|
||||
message = "Maximum string size reached"
|
||||
@@ -1,352 +0,0 @@
|
||||
/* --- Traffic Control Scripting Language --- */
|
||||
// Nanotrasen TCS Language - Made by Doohl
|
||||
|
||||
/datum/n_Interpreter/TCS_Interpreter
|
||||
var/datum/TCS_Compiler/Compiler
|
||||
|
||||
/datum/n_Interpreter/TCS_Interpreter/HandleError(datum/runtimeError/e)
|
||||
Compiler.Holder.add_entry(e.ToString(), "Execution Error")
|
||||
|
||||
/datum/n_Interpreter/TCS_Interpreter/GC()
|
||||
..()
|
||||
Compiler = null
|
||||
|
||||
/datum/TCS_Compiler
|
||||
var/datum/n_Interpreter/TCS_Interpreter/interpreter
|
||||
var/obj/machinery/telecomms/server/Holder // the server that is running the code
|
||||
var/ready = 1 // 1 if ready to run code
|
||||
|
||||
/* -- Set ourselves to Garbage Collect -- */
|
||||
|
||||
/datum/TCS_Compiler/proc/GC()
|
||||
Holder = null
|
||||
if(interpreter)
|
||||
interpreter.GC()
|
||||
|
||||
|
||||
//temp
|
||||
/datum/TCS_Compiler
|
||||
var/datum/n_scriptOptions/nS_Options/options
|
||||
var/datum/n_Scanner/nS_Scanner/scanner
|
||||
var/list/tokens
|
||||
var/datum/n_Parser/nS_Parser/parser
|
||||
var/datum/node/BlockDefinition/GlobalBlock/program
|
||||
|
||||
/* -- Compile a raw block of text -- */
|
||||
|
||||
/datum/TCS_Compiler/proc/Compile(list/code)
|
||||
options = new()
|
||||
scanner = new(code, options)
|
||||
tokens = scanner.Scan()
|
||||
parser = new(tokens, options)
|
||||
program = parser.Parse()
|
||||
|
||||
var/list/returnerrors = list()
|
||||
|
||||
returnerrors += scanner.errors
|
||||
returnerrors += parser.errors
|
||||
|
||||
if(returnerrors.len)
|
||||
return returnerrors
|
||||
|
||||
interpreter = new(program)
|
||||
interpreter.persist = 1
|
||||
interpreter.Compiler= src
|
||||
|
||||
return returnerrors
|
||||
|
||||
/* -- Execute the compiled code -- */
|
||||
|
||||
/datum/TCS_Compiler/proc/Run(var/datum/signal/signal)
|
||||
if(!ready)
|
||||
return
|
||||
|
||||
if(!interpreter)
|
||||
return
|
||||
|
||||
interpreter.container = src
|
||||
|
||||
interpreter.CreateGlobalScope() // Reset the variables.
|
||||
interpreter.curScope = interpreter.globalScope
|
||||
|
||||
interpreter.SetVar("PI", 3.141592653) // value of pi
|
||||
interpreter.SetVar("E", 2.718281828) // value of e
|
||||
interpreter.SetVar("SQURT2", 1.414213562) // value of the square root of 2
|
||||
interpreter.SetVar("FALSE", 0) // boolean shortcut to 0
|
||||
interpreter.SetVar("false", 0) // boolean shortcut to 0
|
||||
interpreter.SetVar("TRUE", 1) // boolean shortcut to 1
|
||||
interpreter.SetVar("true", 1) // boolean shortcut to 1
|
||||
|
||||
interpreter.SetVar("NORTH", NORTH) // NORTH (1)
|
||||
interpreter.SetVar("SOUTH", SOUTH) // SOUTH (2)
|
||||
interpreter.SetVar("EAST", EAST) // EAST (4)
|
||||
interpreter.SetVar("WEST", WEST) // WEST (8)
|
||||
|
||||
// Channel macros
|
||||
interpreter.SetVar("$common", PUB_FREQ)
|
||||
interpreter.SetVar("$science", SCI_FREQ)
|
||||
interpreter.SetVar("$command", COMM_FREQ)
|
||||
interpreter.SetVar("$medical", MED_FREQ)
|
||||
interpreter.SetVar("$engineering", ENG_FREQ)
|
||||
interpreter.SetVar("$security", SEC_FREQ)
|
||||
interpreter.SetVar("$supply", SUP_FREQ)
|
||||
interpreter.SetVar("$service", SRV_FREQ)
|
||||
|
||||
// Signal data
|
||||
|
||||
interpreter.SetVar("$content", signal.data["message"])
|
||||
interpreter.SetVar("$freq", signal.frequency)
|
||||
interpreter.SetVar("$source", signal.data["name"])
|
||||
interpreter.SetVar("$job", signal.data["job"])
|
||||
interpreter.SetVar("$sign", signal)
|
||||
interpreter.SetVar("$pass", !(signal.data["reject"])) // if the signal isn't rejected, pass = 1; if the signal IS rejected, pass = 0
|
||||
|
||||
var/datum/language/speaking = signal.data["language"]
|
||||
if(speaking)
|
||||
interpreter.SetVar("$language", speaking.name)
|
||||
else
|
||||
interpreter.SetVar("$language", "Unknown")
|
||||
|
||||
// Set up the script procs
|
||||
|
||||
/*
|
||||
-> Send another signal to a server
|
||||
@format: broadcast(content, frequency, source, job)
|
||||
|
||||
@param content: Message to broadcast
|
||||
@param frequency: Frequency to broadcast to
|
||||
@param source: The name of the source you wish to imitate. Must be stored in stored_names list.
|
||||
@param job: The name of the job.
|
||||
*/
|
||||
interpreter.SetProc("broadcast", "tcombroadcast", signal, list("message", "freq", "source", "job"))
|
||||
|
||||
/*
|
||||
-> Store a value permanently to the server machine (not the actual game hosting machine, the ingame machine)
|
||||
@format: mem(address, value)
|
||||
|
||||
@param address: The memory address (string index) to store a value to
|
||||
@param value: The value to store to the memory address
|
||||
*/
|
||||
interpreter.SetProc("mem", "mem", signal, list("address", "value"))
|
||||
|
||||
/*
|
||||
-> Delay code for a given amount of deciseconds
|
||||
@format: sleep(time)
|
||||
|
||||
@param time: time to sleep in deciseconds (1/10th second)
|
||||
*/
|
||||
interpreter.SetProc("sleep", "delay", signal, list("time"))
|
||||
|
||||
/*
|
||||
-> Replaces a string with another string
|
||||
@format: replace(string, substring, replacestring)
|
||||
|
||||
@param string: the string to search for substrings (best used with $content$ constant)
|
||||
@param substring: the substring to search for
|
||||
@param replacestring: the string to replace the substring with
|
||||
|
||||
*/
|
||||
interpreter.SetProc("replace", /proc/n_replacetext)
|
||||
|
||||
/*
|
||||
-> Locates an element/substring inside of a list or string
|
||||
@format: find(haystack, needle, start = 1, end = 0)
|
||||
|
||||
@param haystack: the container to search
|
||||
@param needle: the element to search for
|
||||
@param start: the position to start in
|
||||
@param end: the position to end in
|
||||
|
||||
*/
|
||||
interpreter.SetProc("find", /proc/smartfind)
|
||||
|
||||
/*
|
||||
-> Finds the length of a string or list
|
||||
@format: length(container)
|
||||
|
||||
@param container: the list or container to measure
|
||||
|
||||
*/
|
||||
interpreter.SetProc("length", /proc/smartlength)
|
||||
|
||||
/* -- Clone functions, carried from default BYOND procs --- */
|
||||
|
||||
// vector namespace
|
||||
interpreter.SetProc("vector", /proc/n_list)
|
||||
interpreter.SetProc("at", /proc/n_listpos)
|
||||
interpreter.SetProc("copy", /proc/n_listcopy)
|
||||
interpreter.SetProc("push_back", /proc/n_listadd)
|
||||
interpreter.SetProc("remove", /proc/n_listremove)
|
||||
interpreter.SetProc("cut", /proc/n_listcut)
|
||||
interpreter.SetProc("swap", /proc/n_listswap)
|
||||
interpreter.SetProc("insert", /proc/n_listinsert)
|
||||
|
||||
interpreter.SetProc("pick", /proc/n_pick)
|
||||
interpreter.SetProc("prob", /proc/prob_chance)
|
||||
interpreter.SetProc("substr", /proc/docopytext)
|
||||
|
||||
interpreter.SetProc("shuffle", /proc/shuffle)
|
||||
interpreter.SetProc("uniquevector", /proc/uniquelist)
|
||||
|
||||
interpreter.SetProc("text2vector", /proc/n_splittext)
|
||||
interpreter.SetProc("vector2text", /proc/n_jointext)
|
||||
|
||||
// Donkie~
|
||||
// Strings
|
||||
interpreter.SetProc("lower", /proc/n_lower)
|
||||
interpreter.SetProc("upper", /proc/n_upper)
|
||||
interpreter.SetProc("explode", /proc/string_explode)
|
||||
interpreter.SetProc("implode", /proc/n_jointext)
|
||||
interpreter.SetProc("repeat", /proc/n_repeat)
|
||||
interpreter.SetProc("reverse", /proc/reverse_text)
|
||||
interpreter.SetProc("tonum", /proc/n_str2num)
|
||||
interpreter.SetProc("capitalize", /proc/capitalize)
|
||||
interpreter.SetProc("replacetextEx",/proc/n_replacetextEx)
|
||||
|
||||
// Numbers
|
||||
interpreter.SetProc("tostring", /proc/n_num2str)
|
||||
interpreter.SetProc("sqrt", /proc/n_sqrt)
|
||||
interpreter.SetProc("abs", /proc/n_abs)
|
||||
interpreter.SetProc("floor", /proc/n_floor)
|
||||
interpreter.SetProc("ceil", /proc/n_ceiling)
|
||||
interpreter.SetProc("round", /proc/n_round)
|
||||
interpreter.SetProc("clamp", /proc/n_clamp)
|
||||
interpreter.SetProc("inrange", /proc/n_isInRange)
|
||||
interpreter.SetProc("rand", /proc/rand_chance)
|
||||
interpreter.SetProc("arctan", /proc/Atan2)
|
||||
interpreter.SetProc("lcm", /proc/n_lcm)
|
||||
interpreter.SetProc("gcd", /proc/Gcd)
|
||||
interpreter.SetProc("mean", /proc/Mean)
|
||||
interpreter.SetProc("root", /proc/n_root)
|
||||
interpreter.SetProc("sin", /proc/n_sin)
|
||||
interpreter.SetProc("cos", /proc/n_cos)
|
||||
interpreter.SetProc("arcsin", /proc/n_asin)
|
||||
interpreter.SetProc("arccos", /proc/n_acos)
|
||||
interpreter.SetProc("tan", /proc/n_tan)
|
||||
interpreter.SetProc("csc", /proc/n_csc)
|
||||
interpreter.SetProc("cot", /proc/n_cot)
|
||||
interpreter.SetProc("sec", /proc/n_sec)
|
||||
interpreter.SetProc("todegrees", /proc/n_toDegrees)
|
||||
interpreter.SetProc("toradians", /proc/n_toRadians)
|
||||
interpreter.SetProc("lerp", /proc/Lerp)
|
||||
interpreter.SetProc("max", /proc/n_max)
|
||||
interpreter.SetProc("min", /proc/n_min)
|
||||
|
||||
// End of Donkie~
|
||||
|
||||
// Time
|
||||
interpreter.SetProc("time", /proc/time)
|
||||
interpreter.SetProc("timestamp", /proc/gameTimestamp)
|
||||
|
||||
// Run the compiled code
|
||||
interpreter.Run()
|
||||
|
||||
// Backwards-apply variables onto signal data
|
||||
/* sanitize EVERYTHING. fucking players can't be trusted with SHIT */
|
||||
|
||||
/* Okay, so, the original 'sanitizing' code... did fucking nothing. Then PJB fixed it, which means no HTML.
|
||||
But I like HTML, so back to no sanitizing.*/
|
||||
|
||||
var/message = interpreter.GetVar("$content")
|
||||
var/regex/bannedTags = new ("(<script|<iframe|<video|<audio|<embed|<svg)", "i")
|
||||
if(bannedTags.Find(message)) //uh oh
|
||||
message_admins("Warning: Current Telecomms script contains banned html. Stripping message.")
|
||||
log_admin("Warning: Current Telecomms script contains banned html. Stripping message.")
|
||||
message = interpreter.GetCleanVar("$content", signal.data["message"])
|
||||
|
||||
signal.data["message"] = message
|
||||
signal.frequency = interpreter.GetCleanVar("$freq", signal.frequency)
|
||||
|
||||
var/setname = interpreter.GetVar("$source", signal.data["name"])
|
||||
|
||||
if(signal.data["name"] != setname)
|
||||
signal.data["realname"] = setname
|
||||
signal.data["name"] = setname
|
||||
signal.data["job"] = interpreter.GetVar("$job", signal.data["job"])
|
||||
signal.data["reject"] = !(interpreter.GetCleanVar("$pass")) // set reject to the opposite of $pass
|
||||
|
||||
// If the message is invalid, just don't broadcast it!
|
||||
if(signal.data["message"] == "" || !signal.data["message"])
|
||||
signal.data["reject"] = 1
|
||||
|
||||
/* -- Actual language proc code -- */
|
||||
/datum/signal/proc/delay(var/time)
|
||||
var/obj/machinery/telecomms/server/S = data["server"]
|
||||
var/datum/n_Interpreter/TCS_Interpreter/interpreter = S.Compiler.interpreter
|
||||
// Backup the scope
|
||||
var/datum/scope/globalScope = interpreter.globalScope
|
||||
var/datum/scope/curScope = interpreter.curScope
|
||||
var/list/scopes = interpreter.scopes.stack.Copy()
|
||||
var/list/functions = interpreter.functions.stack.Copy()
|
||||
var/datum/node/statement/FunctionDefinition/curFunction = interpreter.curFunction
|
||||
var/status = interpreter.status
|
||||
var/returnVal = interpreter.returnVal
|
||||
// Sleep
|
||||
sleep(time)
|
||||
// Restore the scope
|
||||
interpreter.returnVal = returnVal
|
||||
interpreter.status = status
|
||||
interpreter.curFunction = curFunction
|
||||
interpreter.functions.stack = functions
|
||||
interpreter.scopes.stack = scopes
|
||||
interpreter.curScope = curScope
|
||||
interpreter.globalScope = globalScope
|
||||
|
||||
/datum/signal/proc/mem(var/address, var/value)
|
||||
if(istext(address))
|
||||
var/obj/machinery/telecomms/server/S = data["server"]
|
||||
|
||||
if(!value && value != 0)
|
||||
return S.memory[address]
|
||||
|
||||
else
|
||||
S.memory[address] = value
|
||||
|
||||
/datum/signal/proc/tcombroadcast(var/message, var/freq, var/source, var/job)
|
||||
var/datum/signal/newsign = new
|
||||
var/obj/machinery/telecomms/server/S = data["server"]
|
||||
var/obj/item/radio/hradio = S.server_radio
|
||||
|
||||
if(!hradio)
|
||||
error("[src] has no radio.")
|
||||
return
|
||||
|
||||
if((!message || message == "") && message != 0)
|
||||
message = "*beep*"
|
||||
if(!source)
|
||||
source = "[html_encode(uppertext(S.id))]"
|
||||
hradio = new // sets the hradio as a radio intercom
|
||||
if(!freq || (!isnum(freq) && text2num(freq) == null))
|
||||
freq = 1459
|
||||
if(findtext(num2text(freq), ".")) // if the frequency has been set as a decimal
|
||||
freq *= 10 // shift the decimal one place
|
||||
|
||||
if(!job)
|
||||
job = "?"
|
||||
|
||||
newsign.data["mob"] = null
|
||||
newsign.data["mobtype"] = /mob/living/carbon/human
|
||||
newsign.data["race"] = "Automated Signal"
|
||||
newsign.data["name"] = source
|
||||
newsign.data["realname"] = newsign.data["name"]
|
||||
newsign.data["job"] = "[job]"
|
||||
newsign.data["compression"] = 0
|
||||
newsign.data["message"] = message
|
||||
newsign.data["type"] = 2 // artificial broadcast
|
||||
if(!isnum(freq))
|
||||
freq = text2num(freq)
|
||||
newsign.frequency = freq
|
||||
|
||||
var/datum/radio_frequency/connection = radio_controller.return_frequency(freq)
|
||||
newsign.data["connection"] = connection
|
||||
|
||||
|
||||
newsign.data["radio"] = hradio
|
||||
newsign.data["vmessage"] = message
|
||||
newsign.data["vname"] = source
|
||||
newsign.data["vmask"] = 0
|
||||
newsign.data["level"] = data["level"]
|
||||
|
||||
var/pass = S.relay_information(newsign, "/obj/machinery/telecomms/hub")
|
||||
if(!pass)
|
||||
S.relay_information(newsign, "/obj/machinery/telecomms/broadcaster") // send this simple message to broadcasters
|
||||
@@ -1,305 +0,0 @@
|
||||
// Script -> BYOND code procs
|
||||
#define SCRIPT_MAX_REPLACEMENTS_ALLOWED 200
|
||||
// --- List operations (lists known as vectors in n_script) ---
|
||||
|
||||
// Clone of list()
|
||||
/proc/n_list()
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_list() called tick#: [world.time]")
|
||||
var/list/returnlist = list()
|
||||
for(var/e in args)
|
||||
returnlist.Add(e)
|
||||
return returnlist
|
||||
|
||||
// Clone of pick()
|
||||
/proc/n_pick()
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_pick() called tick#: [world.time]")
|
||||
var/list/finalpick = list()
|
||||
for(var/e in args)
|
||||
if(isobject(e))
|
||||
if(istype(e, /list))
|
||||
var/list/sublist = e
|
||||
for(var/sube in sublist)
|
||||
finalpick.Add(sube)
|
||||
continue
|
||||
finalpick.Add(e)
|
||||
|
||||
return pick(finalpick)
|
||||
|
||||
// Clone of list[]
|
||||
/proc/n_listpos(var/list/L, var/pos, var/value)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_listpos() called tick#: [world.time]")
|
||||
if(!istype(L, /list)) return
|
||||
if(isnum(pos))
|
||||
if(!value)
|
||||
if(L.len >= pos && !(pos > L.len))
|
||||
return L[pos]
|
||||
else
|
||||
if(L.len >= pos && !(pos > L.len))
|
||||
L[pos] = value
|
||||
else if(istext(pos))
|
||||
if(!value)
|
||||
return L[pos]
|
||||
else
|
||||
L[pos] = value
|
||||
|
||||
// Clone of list.Copy()
|
||||
/proc/n_listcopy(var/list/L, var/start, var/end)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_listcopy() called tick#: [world.time]")
|
||||
if(!istype(L, /list)) return
|
||||
return L.Copy(start, end)
|
||||
|
||||
// Clone of list.Add()
|
||||
/proc/n_listadd()
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_listadd() called tick#: [world.time]")
|
||||
var/list/chosenlist
|
||||
var/i = 1
|
||||
for(var/e in args)
|
||||
if(i == 1)
|
||||
if(isobject(e))
|
||||
if(istype(e, /list))
|
||||
chosenlist = e
|
||||
i = 2
|
||||
else
|
||||
if(chosenlist)
|
||||
chosenlist.Add(e)
|
||||
|
||||
// Clone of list.Remove()
|
||||
/proc/n_listremove()
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_listremove() called tick#: [world.time]")
|
||||
var/list/chosenlist
|
||||
var/i = 1
|
||||
for(var/e in args)
|
||||
if(i == 1)
|
||||
if(isobject(e))
|
||||
if(istype(e, /list))
|
||||
chosenlist = e
|
||||
i = 2
|
||||
else
|
||||
if(chosenlist)
|
||||
chosenlist.Remove(e)
|
||||
|
||||
// Clone of list.len = 0
|
||||
/proc/n_listcut(var/list/L, var/start, var/end)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_listcut() called tick#: [world.time]")
|
||||
if(!istype(L, /list)) return
|
||||
return L.Cut(start, end)
|
||||
|
||||
// Clone of list.Swap()
|
||||
/proc/n_listswap(var/list/L, var/firstindex, var/secondindex)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_listswap() called tick#: [world.time]")
|
||||
if(!istype(L, /list)) return
|
||||
if(L.len >= secondindex && L.len >= firstindex)
|
||||
return L.Swap(firstindex, secondindex)
|
||||
|
||||
// Clone of list.Insert()
|
||||
/proc/n_listinsert(var/list/L, var/index, var/element)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_listinsert() called tick#: [world.time]")
|
||||
if(!istype(L, /list)) return
|
||||
return L.Insert(index, element)
|
||||
|
||||
// --- Miscellaneous functions ---
|
||||
|
||||
// Clone of rand()
|
||||
/proc/rand_chance(var/low = 0, var/high)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/rand_chance() called tick#: [world.time]")
|
||||
return rand(low, high)
|
||||
|
||||
// Clone of prob()
|
||||
/proc/prob_chance(var/chance)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/prob_chance() called tick#: [world.time]")
|
||||
return prob(chance)
|
||||
|
||||
// Merge of list.Find() and findtext()
|
||||
/proc/smartfind(var/haystack, var/needle, var/start = 1, var/end = 0)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/smartfind() called tick#: [world.time]")
|
||||
if(haystack && needle)
|
||||
if(isobject(haystack))
|
||||
if(istype(haystack, /list))
|
||||
if(length(haystack) >= end && start > 0)
|
||||
var/list/listhaystack = haystack
|
||||
return listhaystack.Find(needle, start, end)
|
||||
|
||||
else
|
||||
if(istext(haystack))
|
||||
if(length(haystack) >= end && start > 0)
|
||||
return findtext(haystack, needle, start, end)
|
||||
|
||||
// Clone of copytext()
|
||||
/proc/docopytext(var/string, var/start = 1, var/end = 0)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/docopytext() called tick#: [world.time]")
|
||||
if(istext(string) && isnum(start) && isnum(end))
|
||||
if(start > 0)
|
||||
return copytext(string, start, end)
|
||||
|
||||
// Clone of length()
|
||||
/proc/smartlength(var/container)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/smartlength() called tick#: [world.time]")
|
||||
if(container)
|
||||
if(istype(container, /list) || istext(container))
|
||||
return length(container)
|
||||
return 0
|
||||
|
||||
// BY DONKIE~
|
||||
// String stuff
|
||||
/proc/n_lower(var/string)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_lower() called tick#: [world.time]")
|
||||
if(istext(string))
|
||||
return lowertext(string)
|
||||
|
||||
/proc/n_upper(var/string)
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/n_upper() called tick#: [world.time]")
|
||||
if(istext(string))
|
||||
return uppertext(string)
|
||||
|
||||
/proc/time()
|
||||
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/time() called tick#: [world.time]")
|
||||
return world.time + (12 HOURS)
|
||||
|
||||
proc/string_explode(var/string, var/separator = "")
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/string_explode() called tick#: [world.time]")
|
||||
if(istext(string) && (istext(separator) || isnull(separator)))
|
||||
return splittext(string, separator)
|
||||
|
||||
//Converts a list to a string
|
||||
/proc/list_implode(var/list/li, var/separator)
|
||||
if(istype(li) && (istext(separator) || isnull(separator)))
|
||||
return jointext(li, separator)
|
||||
|
||||
proc/n_repeat(var/string, var/amount)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_repeat() called tick#: [world.time]")
|
||||
if(istext(string) && isnum(amount))
|
||||
var/i
|
||||
var/newstring = ""
|
||||
if(length(newstring)*amount >=1000)
|
||||
return
|
||||
for(i=0, i<=amount, i++)
|
||||
if(i>=1000)
|
||||
break
|
||||
newstring = newstring + string
|
||||
|
||||
return newstring
|
||||
|
||||
// I don't know if it's neccesary to make my own proc, but I think I have to to be able to check for istext.
|
||||
proc/n_str2num(var/string)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_str2num() called tick#: [world.time]")
|
||||
if(istext(string))
|
||||
return text2num(string)
|
||||
|
||||
// Clamps N between min and max
|
||||
/proc/n_clamp(var/num, var/min = 0, var/max = 1)
|
||||
if(isnum(num) && isnum(min) && isnum(max))
|
||||
return Clamp(num, min, max)
|
||||
|
||||
// Number shit
|
||||
proc/n_num2str(var/num)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_num2str() called tick#: [world.time]")
|
||||
if(isnum(num))
|
||||
return num2text(num)
|
||||
|
||||
// Squareroot
|
||||
proc/n_sqrt(var/num)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_sqrt() called tick#: [world.time]")
|
||||
if(isnum(num))
|
||||
return sqrt(num)
|
||||
|
||||
// Magnitude of num
|
||||
proc/n_abs(var/num)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_abs() called tick#: [world.time]")
|
||||
if(isnum(num))
|
||||
return abs(num)
|
||||
|
||||
// Round down
|
||||
proc/n_floor(var/num)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_floor() called tick#: [world.time]")
|
||||
if(isnum(num))
|
||||
return Floor(num)
|
||||
|
||||
// Round up
|
||||
proc/n_ceiling(var/num)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_ceil() called tick#: [world.time]")
|
||||
if(isnum(num))
|
||||
return Ceiling(num)
|
||||
|
||||
// Round to nearest integer
|
||||
proc/n_round(var/num)
|
||||
//writepanic("[__FILE__].[__LINE__] \\/proc/n_round() called tick#: [world.time]")
|
||||
if(isnum(num))
|
||||
if(num-round(num)<0.5)
|
||||
return round(num)
|
||||
return Ceiling(num)
|
||||
|
||||
// END OF BY DONKIE :(
|
||||
|
||||
/proc/n_sin(var/const/x)
|
||||
if(isnum(x))
|
||||
return sin(x)
|
||||
|
||||
/proc/n_cos(var/const/x)
|
||||
if(isnum(x))
|
||||
return cos(x)
|
||||
|
||||
/proc/n_tan(var/const/x)
|
||||
if(isnum(x))
|
||||
return Tan(x)
|
||||
|
||||
/proc/n_csc(var/const/x)
|
||||
if(isnum(x))
|
||||
return Csc(x)
|
||||
|
||||
/proc/n_cot(var/const/x)
|
||||
if(isnum(x))
|
||||
return Cot(x)
|
||||
|
||||
/proc/n_sec(var/const/x)
|
||||
if(isnum(x))
|
||||
return Sec(x)
|
||||
|
||||
/proc/n_asin(var/const/x)
|
||||
if(isnum(x))
|
||||
return arcsin(x)
|
||||
|
||||
/proc/n_acos(var/const/x)
|
||||
if(isnum(x))
|
||||
return arccos(x)
|
||||
|
||||
/proc/n_isInRange(var/const/x, var/const/min, var/const/max)
|
||||
if(isnum(x) && isnum(min) && isnum(max))
|
||||
return IsInRange(x, min, max)
|
||||
|
||||
/proc/n_lcm(var/const/a, var/const/b)
|
||||
if(isnum(a) && isnum(b))
|
||||
return Lcm(a, b)
|
||||
|
||||
/proc/n_root(var/const/n, var/const/x)
|
||||
if(isnum(n) && isnum(x))
|
||||
return Root(n, x)
|
||||
|
||||
/proc/n_toDegrees(var/const/x)
|
||||
if(isnum(x))
|
||||
return ToDegrees(x)
|
||||
|
||||
/proc/n_toRadians(var/const/x)
|
||||
if(isnum(x))
|
||||
return ToRadians(x)
|
||||
|
||||
/proc/n_max(...)
|
||||
return max(arglist(args))
|
||||
|
||||
/proc/n_min(...)
|
||||
return min(arglist(args))
|
||||
|
||||
/proc/n_log(var/num)
|
||||
if(isnum(num) && 0 < num)
|
||||
return log(num)
|
||||
|
||||
/proc/n_replacetext(text, r, with)
|
||||
return replacetext(text, r, with)
|
||||
|
||||
/proc/n_replacetextEx(text, r, with)
|
||||
return replacetextEx(text, r, with)
|
||||
|
||||
/proc/n_jointext(list, glue)
|
||||
return jointext(list, glue)
|
||||
|
||||
/proc/n_splittext(text, delim)
|
||||
return splittext(text, delim)
|
||||
@@ -1,203 +0,0 @@
|
||||
/proc/isobject(x)
|
||||
return (istype(x, /datum) || istype(x, /list) || istype(x, /savefile) || istype(x, /client) || (x == world))
|
||||
|
||||
/datum/n_Interpreter/proc/Eval(datum/node/expression/exp)
|
||||
if(istype(exp, /datum/node/expression/FunctionCall))
|
||||
return RunFunction(exp)
|
||||
|
||||
else if(istype(exp, /datum/node/expression/operator))
|
||||
return EvalOperator(exp)
|
||||
|
||||
else if(istype(exp, /datum/node/expression/value/literal))
|
||||
var/datum/node/expression/value/literal/lit = exp
|
||||
return lit.value
|
||||
|
||||
else if(istype(exp, /datum/node/expression/value/reference))
|
||||
var/datum/node/expression/value/reference/ref = exp
|
||||
return ref.value
|
||||
|
||||
else if(istype(exp, /datum/node/expression/value/variable))
|
||||
var/datum/node/expression/value/variable/v = exp
|
||||
if(!v.object)
|
||||
return Eval(GetVariable(v.id.id_name))
|
||||
else
|
||||
var/datum/D
|
||||
if(istype(v.object, /datum/node/identifier))
|
||||
D = GetVariable(v.object:id_name)
|
||||
else
|
||||
D = v.object
|
||||
|
||||
D = Eval(D)
|
||||
if(!isobject(D))
|
||||
return null
|
||||
|
||||
if(!D.vars.Find(v.id.id_name))
|
||||
RaiseError(new/datum/runtimeError/UndefinedVariable("[v.object.ToString()].[v.id.id_name]"))
|
||||
return null
|
||||
|
||||
return Eval(D.vars[v.id.id_name])
|
||||
|
||||
else if(istype(exp, /datum/node/expression))
|
||||
RaiseError(new/datum/runtimeError/UnknownInstruction())
|
||||
|
||||
else
|
||||
return exp
|
||||
|
||||
/datum/n_Interpreter/proc/EvalOperator(datum/node/expression/operator/exp)
|
||||
if(istype(exp, /datum/node/expression/operator/binary))
|
||||
var/datum/node/expression/operator/binary/bin = exp
|
||||
try // This way we can forgo sanity in the actual evaluation (other than divide by 0).
|
||||
switch(bin.type)
|
||||
if(/datum/node/expression/operator/binary/Equal)
|
||||
return Equal(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/NotEqual)
|
||||
return NotEqual(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Greater)
|
||||
return Greater(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Less)
|
||||
return Less(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/GreaterOrEqual)
|
||||
return GreaterOrEqual(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/LessOrEqual)
|
||||
return LessOrEqual(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/LogicalAnd)
|
||||
return LogicalAnd(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/LogicalOr)
|
||||
return LogicalOr(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/LogicalXor)
|
||||
return LogicalXor(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/BitwiseAnd)
|
||||
return BitwiseAnd(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/BitwiseOr)
|
||||
return BitwiseOr(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/BitwiseXor)
|
||||
return BitwiseXor(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Add)
|
||||
return Add(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Subtract)
|
||||
return Subtract(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Multiply)
|
||||
return Multiply(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Divide)
|
||||
return Divide(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Power)
|
||||
return Power(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
if(/datum/node/expression/operator/binary/Modulo)
|
||||
return Modulo(Eval(bin.exp), Eval(bin.exp2))
|
||||
|
||||
else
|
||||
RaiseError(new/datum/runtimeError/UnknownInstruction())
|
||||
|
||||
catch
|
||||
RaiseError(new/datum/runtimeError/TypeMismatch(bin.token, Eval(bin.exp), Eval(bin.exp2)))
|
||||
|
||||
else
|
||||
try
|
||||
switch(exp.type)
|
||||
if(/datum/node/expression/operator/unary/Minus)
|
||||
return Minus(Eval(exp.exp))
|
||||
|
||||
if(/datum/node/expression/operator/unary/LogicalNot)
|
||||
return LogicalNot(Eval(exp.exp))
|
||||
|
||||
if(/datum/node/expression/operator/unary/BitwiseNot)
|
||||
return BitwiseNot(Eval(exp.exp))
|
||||
|
||||
if(/datum/node/expression/operator/unary/group)
|
||||
return Eval(exp.exp)
|
||||
|
||||
else
|
||||
RaiseError(new/datum/runtimeError/UnknownInstruction())
|
||||
catch
|
||||
RaiseError(new/datum/runtimeError/TypeMismatch/unary(exp.token, Eval(exp.exp)))
|
||||
|
||||
//Binary//
|
||||
//Comparison operators
|
||||
/datum/n_Interpreter/proc/Equal(a, b)
|
||||
return a == b
|
||||
|
||||
/datum/n_Interpreter/proc/NotEqual(a, b)
|
||||
return a != b //LogicalNot(Equal(a, b))
|
||||
|
||||
/datum/n_Interpreter/proc/Greater(a, b)
|
||||
return a > b
|
||||
|
||||
/datum/n_Interpreter/proc/Less(a, b)
|
||||
return a < b
|
||||
|
||||
/datum/n_Interpreter/proc/GreaterOrEqual(a, b)
|
||||
return a >= b
|
||||
|
||||
/datum/n_Interpreter/proc/LessOrEqual(a, b)
|
||||
return a <= b
|
||||
|
||||
//Logical Operators
|
||||
/datum/n_Interpreter/proc/LogicalAnd(a, b)
|
||||
return a && b
|
||||
|
||||
/datum/n_Interpreter/proc/LogicalOr(a, b)
|
||||
return a || b
|
||||
|
||||
/datum/n_Interpreter/proc/LogicalXor(a, b)
|
||||
return (a || b) && !(a && b)
|
||||
|
||||
//Bitwise Operators
|
||||
/datum/n_Interpreter/proc/BitwiseAnd(a, b)
|
||||
return a & b
|
||||
|
||||
/datum/n_Interpreter/proc/BitwiseOr(a, b)
|
||||
return a | b
|
||||
|
||||
/datum/n_Interpreter/proc/BitwiseXor(a, b)
|
||||
return a ^ b
|
||||
|
||||
//Arithmetic Operators
|
||||
/datum/n_Interpreter/proc/Add(a, b)
|
||||
return a + b
|
||||
|
||||
/datum/n_Interpreter/proc/Subtract(a, b)
|
||||
return a - b
|
||||
|
||||
/datum/n_Interpreter/proc/Divide(a, b)
|
||||
if(b == 0)
|
||||
RaiseError(new/datum/runtimeError/DivisionByZero())
|
||||
return null
|
||||
|
||||
return a / b
|
||||
|
||||
/datum/n_Interpreter/proc/Multiply(a, b)
|
||||
return a * b
|
||||
|
||||
/datum/n_Interpreter/proc/Modulo(a, b)
|
||||
|
||||
return a % b
|
||||
|
||||
/datum/n_Interpreter/proc/Power(a, b)
|
||||
return a ** b
|
||||
|
||||
//Unary//
|
||||
/datum/n_Interpreter/proc/Minus(a)
|
||||
return -a
|
||||
|
||||
/datum/n_Interpreter/proc/LogicalNot(a)
|
||||
return !a
|
||||
|
||||
/datum/n_Interpreter/proc/BitwiseNot(a)
|
||||
return ~a
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
File: Interpreter (Public)
|
||||
Contains methods for interacting with the interpreter.
|
||||
*/
|
||||
/*
|
||||
Class: n_Interpreter
|
||||
Procedures allowing for interaction with the script that is being run by the interpreter object.
|
||||
*/
|
||||
|
||||
/*
|
||||
Proc: Load
|
||||
Loads a 'compiled' script into memory.
|
||||
|
||||
Parameters:
|
||||
program - A <GlobalBlock> object which represents the script's global scope.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/Load(var/datum/node/BlockDefinition/GlobalBlock/program)
|
||||
ASSERT(program)
|
||||
src.program = program
|
||||
CreateGlobalScope()
|
||||
alertadmins = 0 // reset admin alerts
|
||||
|
||||
/*
|
||||
Proc: Run
|
||||
Runs the script.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/Run()
|
||||
cur_recursion = 0 // reset recursion
|
||||
cur_statements = 0 // reset CPU tracking
|
||||
|
||||
ASSERT(src.program)
|
||||
RunBlock(src.program)
|
||||
|
||||
/*
|
||||
Proc: SetVar
|
||||
Defines a global variable for the duration of the next execution of a script.
|
||||
|
||||
Notes:
|
||||
This differs from <Block.SetVar()> in that variables set using this procedure only last for the session,
|
||||
while those defined from the block object persist if it is ran multiple times.
|
||||
|
||||
See Also:
|
||||
- <Block.SetVar()>
|
||||
*/
|
||||
/datum/n_Interpreter/proc/SetVar(name, value)
|
||||
if(!istext(name))
|
||||
//CRASH("Invalid variable name")
|
||||
return
|
||||
|
||||
AssignVariable(name, value)
|
||||
|
||||
/*
|
||||
Proc: SetProc
|
||||
Defines a procedure to be available to the script.
|
||||
|
||||
Parameters:
|
||||
name - The name of the procedure as exposed to the script.
|
||||
path - The typepath of a proc to be called when the function call is read by the interpreter, or, if object is specified, a string representing the procedure's name.
|
||||
object - (Optional) An object which will the be target of a function call.
|
||||
params - Only required if object is not null, a list of the names of parameters the proc takes.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/SetProc(name, path, object = null, list/params = null)
|
||||
if(!istext(name))
|
||||
//CRASH("Invalid function name")
|
||||
return
|
||||
|
||||
if(!object)
|
||||
globalScope.functions[name] = path
|
||||
|
||||
else
|
||||
var/datum/node/statement/FunctionDefinition/S = new()
|
||||
S.func_name = name
|
||||
S.parameters = params
|
||||
S.block = new()
|
||||
S.block.SetVar("src", object)
|
||||
var/datum/node/expression/FunctionCall/C = new()
|
||||
C.func_name = path
|
||||
C.object = new("src")
|
||||
for(var/p in params)
|
||||
C.parameters += new/datum/node/expression/value/variable(p)
|
||||
|
||||
var/datum/node/statement/ReturnStatement/R = new()
|
||||
R.value = C
|
||||
S.block.statements += R
|
||||
globalScope.functions[name] = S
|
||||
/*
|
||||
Proc: VarExists
|
||||
Checks whether a global variable with the specified name exists.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/VarExists(name)
|
||||
return globalScope.variables.Find(name) //convert to 1/0 first?
|
||||
|
||||
/*
|
||||
Proc: ProcExists
|
||||
Checks whether a global function with the specified name exists.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/ProcExists(name)
|
||||
return globalScope.functions.Find(name)
|
||||
|
||||
/*
|
||||
Proc: GetVar
|
||||
Returns the value of a global variable in the script. Remember to ensure that the variable exists before calling this procedure.
|
||||
|
||||
See Also:
|
||||
- <VarExists()>
|
||||
*/
|
||||
/datum/n_Interpreter/proc/GetVar(name)
|
||||
if(!VarExists(name))
|
||||
//CRASH("No variable named '[name]'.")
|
||||
return
|
||||
var/x = globalScope.variables[name]
|
||||
return Eval(x)
|
||||
|
||||
/*
|
||||
Proc: GetCleanVar
|
||||
Returns the value of a global variable in the script and cleans it (sanitizes).
|
||||
*/
|
||||
|
||||
/datum/n_Interpreter/proc/GetCleanVar(name, compare)
|
||||
var/x = GetVar(name)
|
||||
if(istext(x) && compare && x != compare) // Was changed
|
||||
x = sanitize(x)
|
||||
return x
|
||||
|
||||
/*
|
||||
Proc: CallProc
|
||||
Calls a global function defined in the script and, amazingly enough, returns its return value. Remember to ensure that the function
|
||||
exists before calling this procedure.
|
||||
|
||||
See Also:
|
||||
- <ProcExists()>
|
||||
*/
|
||||
/datum/n_Interpreter/proc/CallProc(name, params[]=null)
|
||||
if(!ProcExists(name))
|
||||
//CRASH("No function named '[name]'.")
|
||||
return
|
||||
|
||||
var/datum/node/statement/FunctionDefinition/func = globalScope.functions[name]
|
||||
|
||||
if(istype(func))
|
||||
var/datum/node/statement/FunctionCall/stmt = new
|
||||
stmt.func_name = func.func_name
|
||||
stmt.parameters = params
|
||||
return RunFunction(stmt)
|
||||
|
||||
else
|
||||
return call(func)(arglist(params))
|
||||
//CRASH("Unknown function type '[name]'.")
|
||||
|
||||
/*
|
||||
Event: HandleError
|
||||
Called when the interpreter throws a runtime error.
|
||||
|
||||
See Also:
|
||||
- <runtimeError>
|
||||
*/
|
||||
/datum/n_Interpreter/proc/HandleError(var/datum/runtimeError/e)
|
||||
@@ -1,380 +0,0 @@
|
||||
/*
|
||||
File: Interpreter (Internal)
|
||||
*/
|
||||
/*
|
||||
Class: n_Interpreter
|
||||
*/
|
||||
/*
|
||||
Macros: Status Macros
|
||||
RETURNING - Indicates that the current function is returning a value.
|
||||
BREAKING - Indicates that the current loop is being terminated.
|
||||
CONTINUING - Indicates that the rest of the current iteration of a loop is being skipped.
|
||||
*/
|
||||
#define RETURNING 1
|
||||
#define BREAKING 2
|
||||
#define CONTINUING 4
|
||||
|
||||
/datum/n_Interpreter
|
||||
var/datum/scope/curScope
|
||||
var/datum/scope/globalScope
|
||||
|
||||
var/datum/node/BlockDefinition/program
|
||||
var/datum/node/statement/FunctionDefinition/curFunction
|
||||
var/datum/stack/scopes = new()
|
||||
var/datum/stack/functions = new()
|
||||
|
||||
var/datum/container // associated container for interpeter
|
||||
/*
|
||||
Var: status
|
||||
A variable indicating that the rest of the current block should be skipped. This may be set to any combination of <Status Macros>.
|
||||
*/
|
||||
var/status = 0
|
||||
var/returnVal
|
||||
|
||||
var/max_statements = 900 // maximum amount of statements that can be called in one execution. this is to prevent massive crashes and exploitation
|
||||
var/cur_statements = 0 // current amount of statements called
|
||||
var/alertadmins = 0 // set to 1 if the admins shouldn't be notified of anymore issues
|
||||
var/max_iterations = 100 // max number of uninterrupted loops possible
|
||||
var/max_recursion = 10 // max recursions without returning anything (or completing the code block)
|
||||
var/cur_recursion = 0 // current amount of recursion
|
||||
/*
|
||||
Var: persist
|
||||
If 0, global variables will be reset after Run() finishes.
|
||||
*/
|
||||
var/persist = 1
|
||||
var/paused = 0
|
||||
|
||||
/*
|
||||
Constructor: New
|
||||
Calls <Load()> with the given parameters.
|
||||
*/
|
||||
/datum/n_Interpreter/New(datum/node/BlockDefinition/GlobalBlock/program = null)
|
||||
. = ..()
|
||||
if(program)
|
||||
Load(program)
|
||||
|
||||
/*
|
||||
Set ourselves to Garbage Collect
|
||||
*/
|
||||
/datum/n_Interpreter/proc/GC()
|
||||
..()
|
||||
container = null
|
||||
|
||||
/*
|
||||
Proc: RaiseError
|
||||
Raises a runtime error.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/RaiseError(datum/runtimeError/e)
|
||||
e.stack = functions.Copy()
|
||||
e.stack.Push(curFunction)
|
||||
src.HandleError(e)
|
||||
|
||||
/datum/n_Interpreter/proc/CreateScope(datum/node/BlockDefinition/B)
|
||||
var/datum/scope/S = new(B, curScope)
|
||||
scopes.Push(curScope)
|
||||
curScope = S
|
||||
return S
|
||||
|
||||
/datum/n_Interpreter/proc/CreateGlobalScope()
|
||||
scopes.Clear()
|
||||
var/datum/scope/S = new(program, null)
|
||||
globalScope = S
|
||||
return S
|
||||
|
||||
/*
|
||||
Proc: AlertAdmins
|
||||
Alerts the admins of a script that is bad.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/AlertAdmins()
|
||||
if(container && !alertadmins)
|
||||
if(istype(container, /datum/TCS_Compiler))
|
||||
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 = 1
|
||||
message_admins(message, 1)
|
||||
/*
|
||||
Proc: RunBlock
|
||||
Runs each statement in a block of code.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/RunBlock(var/datum/node/BlockDefinition/Block, var/datum/scope/scope = null)
|
||||
var/is_global = istype(Block, /datum/node/BlockDefinition/GlobalBlock)
|
||||
if(!is_global)
|
||||
if(scope)
|
||||
curScope = scope
|
||||
else
|
||||
CreateScope(Block)
|
||||
else
|
||||
if(!persist)
|
||||
CreateGlobalScope()
|
||||
|
||||
curScope = globalScope
|
||||
|
||||
if(cur_statements < max_statements)
|
||||
for(var/datum/node/statement/S in Block.statements)
|
||||
while(paused) sleep(10)
|
||||
|
||||
cur_statements++
|
||||
if(cur_statements >= max_statements)
|
||||
RaiseError(new/datum/runtimeError/MaxCPU())
|
||||
AlertAdmins()
|
||||
break
|
||||
|
||||
if(istype(S, /datum/node/statement/VariableAssignment))
|
||||
var/datum/node/statement/VariableAssignment/stmt = S
|
||||
var/name = stmt.var_name.id_name
|
||||
|
||||
if(!stmt.object)
|
||||
// Below we assign the variable first to null if it doesn't already exist.
|
||||
// This is necessary for assignments like +=, and when the variable is used in a function
|
||||
// If the variable already exists in a different block, then AssignVariable will automatically use that one.
|
||||
if(!IsVariableAccessible(name))
|
||||
AssignVariable(name, null)
|
||||
|
||||
AssignVariable(name, Eval(stmt.value))
|
||||
else
|
||||
var/datum/D = Eval(GetVariable(stmt.object.id_name))
|
||||
if(!D)
|
||||
return
|
||||
|
||||
D.vars[stmt.var_name.id_name] = Eval(stmt.value)
|
||||
|
||||
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
|
||||
if(!dec.object)
|
||||
AssignVariable(dec.var_name.id_name, null, curScope)
|
||||
else
|
||||
var/datum/D = Eval(GetVariable(dec.object.id_name))
|
||||
if(!D)
|
||||
return
|
||||
|
||||
D.vars[dec.var_name.id_name] = null
|
||||
|
||||
else if(istype(S, /datum/node/statement/FunctionCall))
|
||||
RunFunction(S)
|
||||
|
||||
else if(istype(S, /datum/node/statement/FunctionDefinition))
|
||||
//do nothing
|
||||
|
||||
else if(istype(S, /datum/node/statement/WhileLoop))
|
||||
RunWhile(S)
|
||||
|
||||
else if(istype(S, /datum/node/statement/IfStatement))
|
||||
RunIf(S)
|
||||
|
||||
else if(istype(S, /datum/node/statement/ReturnStatement))
|
||||
if(!curFunction)
|
||||
RaiseError(new/datum/runtimeError/UnexpectedReturn())
|
||||
continue
|
||||
|
||||
status |= RETURNING
|
||||
returnVal = Eval(S:value)
|
||||
break
|
||||
|
||||
else if(istype(S, /datum/node/statement/BreakStatement))
|
||||
status |= BREAKING
|
||||
break
|
||||
|
||||
else if(istype(S, /datum/node/statement/ContinueStatement))
|
||||
status |= CONTINUING
|
||||
break
|
||||
|
||||
else
|
||||
RaiseError(new/datum/runtimeError/UnknownInstruction())
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
if(status)
|
||||
break
|
||||
|
||||
curScope = scopes.Pop()
|
||||
|
||||
/*
|
||||
Proc: RunFunction
|
||||
Runs a function block or a proc with the arguments specified in the script.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/RunFunction(var/datum/node/statement/FunctionCall/stmt)
|
||||
//Note that anywhere /datum/node/statement/FunctionCall/stmt is used so may /datum/node/expression/FunctionCall
|
||||
|
||||
// If recursion gets too high (max 50 nested functions) throw an error
|
||||
if(cur_recursion >= max_recursion)
|
||||
AlertAdmins()
|
||||
RaiseError(new/datum/runtimeError/RecursionLimitReached())
|
||||
return 0
|
||||
|
||||
var/datum/node/statement/FunctionDefinition/def
|
||||
if(!stmt.object) //A scope's function is being called, stmt.object is null
|
||||
def = GetFunction(stmt.func_name)
|
||||
|
||||
else if(istype(stmt.object)) //A method of an object exposed as a variable is being called, stmt.object is a /node/identifier
|
||||
var/O = GetVariable(stmt.object.id_name) //Gets a reference to the object which is the target of the function call.
|
||||
if(!O) return //Error already thrown in GetVariable()
|
||||
def = Eval(O)
|
||||
|
||||
if(!def)
|
||||
return
|
||||
|
||||
cur_recursion++ // add recursion
|
||||
if(istype(def))
|
||||
if(curFunction) functions.Push(curFunction)
|
||||
var/datum/scope/S = CreateScope(def.block)
|
||||
|
||||
for(var/i = 1 to def.parameters.len)
|
||||
var/val
|
||||
if(stmt.parameters.len >= i)
|
||||
val = stmt.parameters[i]
|
||||
//else
|
||||
// unspecified param
|
||||
AssignVariable(def.parameters[i], new/datum/node/expression/value/literal(Eval(val)), S)
|
||||
|
||||
curFunction = stmt
|
||||
RunBlock(def.block, S)
|
||||
|
||||
//Handle return value
|
||||
. = returnVal
|
||||
status &= ~RETURNING
|
||||
returnVal = null
|
||||
curFunction = functions.Pop()
|
||||
cur_recursion--
|
||||
|
||||
else
|
||||
cur_recursion--
|
||||
var/list/params = new
|
||||
for(var/datum/node/expression/P in stmt.parameters)
|
||||
params += list(Eval(P))
|
||||
|
||||
if(isobject(def)) //def is an object which is the target of a function call
|
||||
if(!hascall(def, stmt.func_name))
|
||||
RaiseError(new/datum/runtimeError/UndefinedFunction("[stmt.object.id_name].[stmt.func_name]"))
|
||||
return
|
||||
|
||||
return call(def, stmt.func_name)(arglist(params))
|
||||
|
||||
else //def is a path to a global proc
|
||||
return call(def)(arglist(params))
|
||||
//else
|
||||
// RaiseError(new/runtimeError/UnknownInstruction())
|
||||
|
||||
/*
|
||||
Proc: RunIf
|
||||
Checks a condition and runs either the if block or else block.
|
||||
*/
|
||||
/datum/n_Interpreter/proc/RunIf(var/datum/node/statement/IfStatement/stmt)
|
||||
if(!stmt.skip)
|
||||
if(Eval(stmt.cond))
|
||||
RunBlock(stmt.block)
|
||||
// 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)
|
||||
|
||||
// We don't need to skip you anymore.
|
||||
stmt.skip = 0
|
||||
|
||||
/*
|
||||
Proc: RunWhile
|
||||
Runs a while loop.
|
||||
*/
|
||||
|
||||
/datum/n_Interpreter/proc/RunWhile(var/datum/node/statement/WhileLoop/stmt)
|
||||
var/i = 1
|
||||
while(Eval(stmt.cond) && Iterate(stmt.block, i++))
|
||||
continue
|
||||
|
||||
status &= ~BREAKING
|
||||
|
||||
/*
|
||||
Proc:Iterate
|
||||
Runs a single iteration of a loop. Returns a value indicating whether or not to continue looping.
|
||||
*/
|
||||
|
||||
/datum/n_Interpreter/proc/Iterate(var/datum/node/BlockDefinition/block, count)
|
||||
RunBlock(block)
|
||||
if(max_iterations > 0 && count >= max_iterations)
|
||||
RaiseError(new/datum/runtimeError/IterationLimitReached())
|
||||
return 0
|
||||
|
||||
if(status & (BREAKING|RETURNING))
|
||||
return 0
|
||||
|
||||
status &= ~CONTINUING
|
||||
return 1
|
||||
|
||||
/*
|
||||
Proc: GetFunction
|
||||
Finds a function in an accessible scope with the given name. Returns a <FunctionDefinition>.
|
||||
*/
|
||||
|
||||
/datum/n_Interpreter/proc/GetFunction(name)
|
||||
var/datum/scope/S = curScope
|
||||
while(S)
|
||||
if(S.functions.Find(name))
|
||||
return S.functions[name]
|
||||
S = S.parent
|
||||
|
||||
RaiseError(new/datum/runtimeError/UndefinedFunction(name))
|
||||
|
||||
/*
|
||||
Proc: GetVariable
|
||||
Finds a variable in an accessible scope and returns its value.
|
||||
*/
|
||||
|
||||
/datum/n_Interpreter/proc/GetVariable(name)
|
||||
var/datum/scope/S = curScope
|
||||
while(S)
|
||||
if(S.variables.Find(name))
|
||||
return S.variables[name]
|
||||
S = S.parent
|
||||
|
||||
RaiseError(new/datum/runtimeError/UndefinedVariable(name))
|
||||
|
||||
/datum/n_Interpreter/proc/GetVariableScope(name) //needed for when you reassign a variable in a higher scope
|
||||
var/datum/scope/S = curScope
|
||||
while(S)
|
||||
if(S.variables.Find(name))
|
||||
return S
|
||||
|
||||
S = S.parent
|
||||
|
||||
|
||||
/datum/n_Interpreter/proc/IsVariableAccessible(name)
|
||||
var/datum/scope/S = curScope
|
||||
while(S)
|
||||
if(S.variables.Find(name))
|
||||
return TRUE
|
||||
S = S.parent
|
||||
|
||||
return FALSE
|
||||
|
||||
|
||||
/*
|
||||
Proc: AssignVariable
|
||||
Assigns a value to a variable in a specific block.
|
||||
|
||||
Parameters:
|
||||
name - The name of the variable to assign.
|
||||
value - The value to assign to it.
|
||||
S - The scope the variable resides in. If it is null, a scope with the variable already existing is found. If no scopes have a variable of the given name, the current scope is used.
|
||||
*/
|
||||
|
||||
/datum/n_Interpreter/proc/AssignVariable(name, datum/node/expression/value, var/datum/scope/S = null)
|
||||
if(!S) S = GetVariableScope(name)
|
||||
if(!S) S = curScope
|
||||
if(!S) S = globalScope
|
||||
|
||||
ASSERT(istype(S))
|
||||
if(istext(value) || isnum(value) || isnull(value)) value = new/datum/node/expression/value/literal(value)
|
||||
else if(!istype(value) && isobject(value)) value = new/datum/node/expression/value/reference(value)
|
||||
//TODO: check for invalid name
|
||||
S.variables["[name]"] = value
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
Class: scope
|
||||
A runtime instance of a block. Used internally by the interpreter.
|
||||
*/
|
||||
/datum/scope
|
||||
var/datum/scope/parent = null
|
||||
var/datum/node/BlockDefinition/block
|
||||
var/list/functions
|
||||
var/list/variables
|
||||
|
||||
/datum/scope/New(var/datum/node/BlockDefinition/B, var/datum/scope/parent)
|
||||
src.block = B
|
||||
src.parent = parent
|
||||
src.variables = B.initial_variables.Copy()
|
||||
src.functions = B.functions.Copy()
|
||||
. = ..()
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
File: Options
|
||||
*/
|
||||
// Ascii values of characters
|
||||
/var/const/ascii_A = 65
|
||||
/var/const/ascii_Z = 90
|
||||
/var/const/ascii_a = 97
|
||||
/var/const/ascii_z = 122
|
||||
/var/const/ascii_DOLLAR = 36 // $
|
||||
/var/const/ascii_ZERO = 48
|
||||
/var/const/ascii_NINE = 57
|
||||
/var/const/ascii_UNDERSCORE = 95 // _
|
||||
|
||||
/*
|
||||
Class: n_scriptOptions
|
||||
*/
|
||||
/datum/n_scriptOptions/proc/CanStartID(char) //returns true if the character can start a variable, function, or keyword name (by default letters or an underscore)
|
||||
if(!isnum(char))
|
||||
char = text2ascii(char)
|
||||
|
||||
return (char in ascii_A to ascii_Z) || (char in ascii_a to ascii_z) || char == ascii_UNDERSCORE || char == ascii_DOLLAR
|
||||
|
||||
/datum/n_scriptOptions/proc/IsValidIDChar(char) //returns true if the character can be in the body of a variable, function, or keyword name (by default letters, numbers, and underscore)
|
||||
if(!isnum(char))
|
||||
char = text2ascii(char)
|
||||
|
||||
return CanStartID(char) || IsDigit(char)
|
||||
|
||||
/datum/n_scriptOptions/proc/IsDigit(char)
|
||||
if(!isnum(char))
|
||||
char = text2ascii(char)
|
||||
|
||||
return char in ascii_ZERO to ascii_NINE
|
||||
|
||||
/datum/n_scriptOptions/proc/IsValidID(id) //returns true if all the characters in the string are okay to be in an identifier name
|
||||
if(!CanStartID(id)) //don't need to grab first char in id, since text2ascii does it automatically
|
||||
return 0
|
||||
|
||||
var/list/charmap = string2charlist(id)
|
||||
if(charmap.len == 1)
|
||||
return 1
|
||||
|
||||
for(var/i = 2 to charmap.len)
|
||||
if(!IsValidIDChar(charmap[i]))
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/*
|
||||
Class: nS_Options
|
||||
An implementation of <n_scriptOptions> for the n_Script language.
|
||||
*/
|
||||
/datum/n_scriptOptions/nS_Options
|
||||
var/list/symbols = list(
|
||||
"(",
|
||||
")",
|
||||
"\[",
|
||||
"]",
|
||||
";",
|
||||
",",
|
||||
"{",
|
||||
"}"
|
||||
) //scanner - Characters that can be in symbols
|
||||
/*
|
||||
Var: keywords
|
||||
An associative list used by the parser to parse keywords. Indices are strings which will trigger the keyword when parsed and the
|
||||
associated values are <nS_Keyword> types of which the <n_Keyword.Parse()> proc will be called.
|
||||
*/
|
||||
var/list/keywords = list(
|
||||
"if" = /datum/n_Keyword/nS_Keyword/kwIf,
|
||||
"else" = /datum/n_Keyword/nS_Keyword/kwElse,
|
||||
"elseif" = /datum/n_Keyword/nS_Keyword/kwElseIf,
|
||||
"while" = /datum/n_Keyword/nS_Keyword/kwWhile,
|
||||
"break" = /datum/n_Keyword/nS_Keyword/kwBreak,
|
||||
"continue" = /datum/n_Keyword/nS_Keyword/kwContinue,
|
||||
"return" = /datum/n_Keyword/nS_Keyword/kwReturn,
|
||||
"def" = /datum/n_Keyword/nS_Keyword/kwDef
|
||||
)
|
||||
|
||||
var/list/assign_operators = list(
|
||||
"=" = null,
|
||||
"&=" = "&",
|
||||
"|=" = "|",
|
||||
"`=" = "`",
|
||||
"+=" = "+",
|
||||
"-=" = "-",
|
||||
"*=" = "*",
|
||||
"/=" = "/",
|
||||
"^=" = "^",
|
||||
"%=" = "%"
|
||||
)
|
||||
|
||||
var/list/unary_operators = list(
|
||||
"!" = /datum/node/expression/operator/unary/LogicalNot,
|
||||
"~" = /datum/node/expression/operator/unary/BitwiseNot,
|
||||
"-" = /datum/node/expression/operator/unary/Minus
|
||||
)
|
||||
|
||||
var/list/binary_operators = list(
|
||||
"==" = /datum/node/expression/operator/binary/Equal,
|
||||
"!=" = /datum/node/expression/operator/binary/NotEqual,
|
||||
">" = /datum/node/expression/operator/binary/Greater,
|
||||
"<" = /datum/node/expression/operator/binary/Less,
|
||||
">=" = /datum/node/expression/operator/binary/GreaterOrEqual,
|
||||
"<=" = /datum/node/expression/operator/binary/LessOrEqual,
|
||||
"&&" = /datum/node/expression/operator/binary/LogicalAnd,
|
||||
"||" = /datum/node/expression/operator/binary/LogicalOr,
|
||||
"&" = /datum/node/expression/operator/binary/BitwiseAnd,
|
||||
"|" = /datum/node/expression/operator/binary/BitwiseOr,
|
||||
"`" = /datum/node/expression/operator/binary/BitwiseXor,
|
||||
"+" = /datum/node/expression/operator/binary/Add,
|
||||
"-" = /datum/node/expression/operator/binary/Subtract,
|
||||
"*" = /datum/node/expression/operator/binary/Multiply,
|
||||
"/" = /datum/node/expression/operator/binary/Divide,
|
||||
"^" = /datum/node/expression/operator/binary/Power,
|
||||
"%" = /datum/node/expression/operator/binary/Modulo
|
||||
)
|
||||
|
||||
/datum/n_scriptOptions/nS_Options/New()
|
||||
. = ..()
|
||||
for(var/O in assign_operators + binary_operators + unary_operators)
|
||||
if(!symbols.Find(O))
|
||||
symbols += O
|
||||
@@ -1,371 +0,0 @@
|
||||
/*
|
||||
File: Expressions
|
||||
Procedures for parsing expressions.
|
||||
*/
|
||||
|
||||
/*
|
||||
Macros: Expression Macros
|
||||
OPERATOR - A value indicating the parser currently expects a binary operator.
|
||||
VALUE - A value indicating the parser currently expects a value.
|
||||
SHIFT - Tells the parser to push the current operator onto the stack.
|
||||
REDUCE - Tells the parser to reduce the stack.
|
||||
*/
|
||||
#define OPERATOR 1
|
||||
#define VALUE 2
|
||||
#define SHIFT 0
|
||||
#define REDUCE 1
|
||||
|
||||
/*
|
||||
Class: nS_Parser
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser
|
||||
/*
|
||||
Var: expecting
|
||||
A variable which keeps track of whether an operator or value is expected. It should be either <OPERATOR> or <VALUE>. See <ParseExpression()>
|
||||
for more information.
|
||||
*/
|
||||
var/expecting = VALUE
|
||||
|
||||
/*
|
||||
Proc: Precedence
|
||||
Compares two operators, decides which is higher in the order of operations, and returns <SHIFT> or <REDUCE>.
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/Precedence(var/datum/node/expression/operator/top, var/datum/node/expression/operator/input)
|
||||
if(istype(top))
|
||||
top = top.precedence
|
||||
|
||||
if(istype(input))
|
||||
|
||||
input = input:precedence
|
||||
|
||||
if(top >= input)
|
||||
return REDUCE
|
||||
|
||||
return SHIFT
|
||||
|
||||
/*
|
||||
Proc: GetExpression
|
||||
Takes a token expected to represent a value and returns an <expression> node.
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/GetExpression(var/datum/token/T)
|
||||
if(!T)
|
||||
return
|
||||
|
||||
if(istype(T, /datum/node/expression))
|
||||
return T
|
||||
|
||||
switch(T.type)
|
||||
if(/datum/token/word)
|
||||
return new/datum/node/expression/value/variable(T.value)
|
||||
|
||||
if(/datum/token/accessor)
|
||||
var/datum/token/accessor/A = T
|
||||
var/datum/node/expression/value/variable/E// = new(A.member)
|
||||
var/datum/stack/S = new()
|
||||
|
||||
while(istype(A.object, /datum/token/accessor))
|
||||
S.Push(A)
|
||||
A = A.object
|
||||
|
||||
ASSERT(istext(A.object))
|
||||
|
||||
while(A)
|
||||
var/datum/node/expression/value/variable/V = new()
|
||||
V.id = new(A.member)
|
||||
if(E)
|
||||
V.object = E
|
||||
else
|
||||
V.object = new/datum/node/identifier(A.object)
|
||||
|
||||
E = V
|
||||
A = S.Pop()
|
||||
return E
|
||||
|
||||
if(/datum/token/number, /datum/token/string)
|
||||
return new/datum/node/expression/value/literal(T.value)
|
||||
|
||||
/*
|
||||
Proc: GetOperator
|
||||
Gets a path related to a token or string and returns an instance of the given type. This is used to get an instance of either a binary or unary
|
||||
operator from a token.
|
||||
|
||||
Parameters:
|
||||
O - The input value. If this is a token, O is reset to the token's value.
|
||||
When O is a string and is in L, its associated value is used as the path to instantiate.
|
||||
type - The desired type of the returned object.
|
||||
L - The list in which to search for O.
|
||||
|
||||
See Also:
|
||||
- <GetBinaryOperator()>
|
||||
- <GetUnaryOperator()>
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/GetOperator(O, type = /datum/node/expression/operator, L[])
|
||||
if(istype(O, type))
|
||||
return O //O is already the desired type
|
||||
|
||||
if(istype(O, /datum/token))
|
||||
O = O:value //sets O to text
|
||||
|
||||
if(istext(O)) //sets O to path
|
||||
if(L.Find(O))
|
||||
O = L[O]
|
||||
else
|
||||
return null
|
||||
|
||||
if(ispath(O))
|
||||
O = new O //catches path from last check
|
||||
|
||||
else
|
||||
return null //Unknown type
|
||||
|
||||
return O
|
||||
|
||||
/*
|
||||
Proc: GetBinaryOperator
|
||||
Uses <GetOperator()> to search for an instance of a binary operator type with which the given string is associated. For example, if
|
||||
O is set to "+", an <Add> node is returned.
|
||||
|
||||
See Also:
|
||||
- <GetOperator()>
|
||||
- <GetUnaryOperator()>
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/GetBinaryOperator(O)
|
||||
return GetOperator(O, /datum/node/expression/operator/binary, options.binary_operators)
|
||||
|
||||
/*
|
||||
Proc: GetUnaryOperator
|
||||
Uses <GetOperator()> to search for an instance of a unary operator type with which the given string is associated. For example, if
|
||||
O is set to "!", a <LogicalNot> node is returned.
|
||||
|
||||
See Also:
|
||||
- <GetOperator()>
|
||||
- <GetBinaryOperator()>
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/GetUnaryOperator(O)
|
||||
return GetOperator(O, /datum/node/expression/operator/unary, options.unary_operators)
|
||||
|
||||
/*
|
||||
Proc: Reduce
|
||||
Takes the operator on top of the opr stack and assigns its operand(s). Then this proc pushes the value of that operation to the top
|
||||
of the val stack.
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/Reduce(var/datum/stack/opr, var/datum/stack/val)
|
||||
var/datum/node/expression/operator/O = opr.Pop()
|
||||
if(!O)
|
||||
return
|
||||
|
||||
if(!istype(O))
|
||||
errors += new/datum/scriptError("Error reducing expression - invalid operator.")
|
||||
return
|
||||
|
||||
//Take O and assign its operands, popping one or two values from the val stack
|
||||
//depending on whether O is a binary or unary operator.
|
||||
if(istype(O, /datum/node/expression/operator/binary))
|
||||
var/datum/node/expression/operator/binary/B=O
|
||||
B.exp2 = val.Pop()
|
||||
B.exp = val.Pop()
|
||||
val.Push(B)
|
||||
|
||||
else
|
||||
O.exp=val.Pop()
|
||||
val.Push(O)
|
||||
|
||||
/*
|
||||
Proc: EndOfExpression
|
||||
Returns true if the current token represents the end of an expression.
|
||||
|
||||
Parameters:
|
||||
end - A list of values to compare the current token to.
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/EndOfExpression(end[])
|
||||
if(!curToken)
|
||||
return 1
|
||||
if(istype(curToken, /datum/token/symbol) && end.Find(curToken.value))
|
||||
return 1
|
||||
if(istype(curToken, /datum/token/end) && end.Find(/datum/token/end))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/*
|
||||
Proc: ParseExpression
|
||||
Uses the Shunting-yard algorithm to parse expressions.
|
||||
|
||||
Notes:
|
||||
- When an opening parenthesis is found, then <ParseParenExpression()> is called to handle it.
|
||||
- The <expecting> variable helps distinguish unary operators from binary operators (for cases like the - operator, which can be either).
|
||||
|
||||
Articles:
|
||||
- <http://epaperpress.com/oper/>
|
||||
- <http://en.wikipedia.org/wiki/Shunting-yard_algorithm>
|
||||
|
||||
See Also:
|
||||
- <ParseFunctionExpression()>
|
||||
- <ParseParenExpression()>
|
||||
- <ParseParamExpression()>
|
||||
*/
|
||||
|
||||
/datum/n_Parser/nS_Parser/proc/ParseExpression(var/list/end = list(/datum/token/end), list/ErrChars = list("{", "}"), check_functions = 0)
|
||||
var/datum/stack/opr = new
|
||||
var/datum/stack/val = new
|
||||
src.expecting = VALUE
|
||||
var/loop = 0
|
||||
for()
|
||||
loop++
|
||||
if(loop > 800)
|
||||
errors += new/datum/scriptError("Too many nested tokens.")
|
||||
return
|
||||
|
||||
if(EndOfExpression(end))
|
||||
break
|
||||
|
||||
if(istype(curToken, /datum/token/symbol) && ErrChars.Find(curToken.value))
|
||||
errors += new/datum/scriptError/BadToken(curToken)
|
||||
break
|
||||
|
||||
if(index > tokens.len) //End of File
|
||||
errors += new/datum/scriptError/EndOfFile()
|
||||
break
|
||||
|
||||
var/datum/token/ntok
|
||||
if(index + 1 <= tokens.len)
|
||||
ntok = tokens[index + 1]
|
||||
|
||||
if(istype(curToken, /datum/token/symbol) && curToken.value == "(") //Parse parentheses expression
|
||||
if(expecting != VALUE)
|
||||
errors += new/datum/scriptError/ExpectedToken("operator", curToken)
|
||||
NextToken()
|
||||
continue
|
||||
|
||||
val.Push(ParseParenExpression())
|
||||
|
||||
else if(istype(curToken, /datum/token/symbol)) //Operator found.
|
||||
var/datum/node/expression/operator/curOperator //Figure out whether it is unary or binary and get a new instance.
|
||||
if(src.expecting == OPERATOR)
|
||||
curOperator = GetBinaryOperator(curToken)
|
||||
if(!curOperator)
|
||||
errors += new/datum/scriptError/ExpectedToken("operator", curToken)
|
||||
NextToken()
|
||||
continue
|
||||
else
|
||||
curOperator = GetUnaryOperator(curToken)
|
||||
if(!curOperator) //given symbol isn't a unary operator
|
||||
errors += new/datum/scriptError/ExpectedToken("expression", curToken)
|
||||
NextToken()
|
||||
continue
|
||||
|
||||
if(opr.Top() && Precedence(opr.Top(), curOperator) == REDUCE) //Check order of operations and reduce if necessary
|
||||
Reduce(opr, val)
|
||||
continue
|
||||
|
||||
opr.Push(curOperator)
|
||||
src.expecting = VALUE
|
||||
|
||||
else if(ntok && ntok.value == "(" && istype(ntok, /datum/token/symbol)\
|
||||
&& istype(curToken, /datum/token/word)) //Parse function call
|
||||
|
||||
if(!check_functions)
|
||||
|
||||
var/datum/token/preToken = curToken
|
||||
var/old_expect = src.expecting
|
||||
var/fex = ParseFunctionExpression()
|
||||
if(old_expect != VALUE)
|
||||
errors += new/datum/scriptError/ExpectedToken("operator", preToken)
|
||||
NextToken()
|
||||
continue
|
||||
|
||||
val.Push(fex)
|
||||
else
|
||||
errors += new/datum/scriptError/ParameterFunction(curToken)
|
||||
break
|
||||
|
||||
else if(istype(curToken, /datum/token/keyword)) //inline keywords
|
||||
var/datum/n_Keyword/kw = options.keywords[curToken.value]
|
||||
kw = new kw(inline=1)
|
||||
if(kw)
|
||||
if(!kw.Parse(src))
|
||||
return
|
||||
else
|
||||
errors += new/datum/scriptError/BadToken(curToken)
|
||||
|
||||
else if(istype(curToken, /datum/token/end)) //semicolon found where it wasn't expected
|
||||
errors += new/datum/scriptError/BadToken(curToken)
|
||||
NextToken()
|
||||
continue
|
||||
else
|
||||
if(expecting != VALUE)
|
||||
errors += new/datum/scriptError/ExpectedToken("operator", curToken)
|
||||
NextToken()
|
||||
continue
|
||||
|
||||
val.Push(GetExpression(curToken))
|
||||
src.expecting = OPERATOR
|
||||
|
||||
NextToken()
|
||||
|
||||
while(opr.Top())
|
||||
Reduce(opr, val)
|
||||
//Reduce the value stack completely
|
||||
. = val.Pop() //Return what should be the last value on the stack
|
||||
|
||||
if(val.Top()) //
|
||||
var/datum/node/N = val.Pop()
|
||||
errors += new/datum/scriptError("Error parsing expression. Unexpected value left on stack: [N.ToString()].")
|
||||
return null
|
||||
|
||||
/*
|
||||
Proc: ParseFunctionExpression
|
||||
Parses a function call inside of an expression.
|
||||
|
||||
See Also:
|
||||
- <ParseExpression()>
|
||||
*/
|
||||
|
||||
/datum/n_Parser/nS_Parser/proc/ParseFunctionExpression()
|
||||
var/datum/node/expression/FunctionCall/exp = new
|
||||
exp.func_name = curToken.value
|
||||
NextToken() //skip function name
|
||||
NextToken() //skip open parenthesis, already found
|
||||
var/loops = 0
|
||||
|
||||
for()
|
||||
loops++
|
||||
if(loops >= 800)
|
||||
errors += new/datum/scriptError("Too many nested expressions.")
|
||||
break
|
||||
//CRASH("Something TERRIBLE has gone wrong in ParseFunctionExpression ;__;")
|
||||
|
||||
if(istype(curToken, /datum/token/symbol) && curToken.value == ")")
|
||||
return exp
|
||||
exp.parameters += ParseParamExpression()
|
||||
if(errors.len)
|
||||
return exp
|
||||
|
||||
if(curToken.value == "," && istype(curToken, /datum/token/symbol))
|
||||
NextToken() //skip comma
|
||||
|
||||
if(istype(curToken, /datum/token/end)) //Prevents infinite loop...
|
||||
errors += new/datum/scriptError/ExpectedToken(")")
|
||||
return exp
|
||||
|
||||
/*
|
||||
Proc: ParseParenExpression
|
||||
Parses an expression that ends with a close parenthesis. This is used for parsing expressions inside of parentheses.
|
||||
|
||||
See Also:
|
||||
- <ParseExpression()>
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/ParseParenExpression()
|
||||
if(!CheckToken("(", /datum/token/symbol))
|
||||
return
|
||||
return new/datum/node/expression/operator/unary/group(ParseExpression(list(")")))
|
||||
|
||||
/*
|
||||
Proc: ParseParamExpression
|
||||
Parses an expression that ends with either a comma or close parenthesis. This is used for parsing the parameters passed to a function call.
|
||||
|
||||
See Also:
|
||||
- <ParseExpression()>
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/proc/ParseParamExpression(var/check_functions = 0)
|
||||
var/cf = check_functions
|
||||
return ParseExpression(list(",", ")"), check_functions = cf)
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
File: Keywords
|
||||
*/
|
||||
var/const/KW_FAIL = 0 //Fatal error; stop parsing entire script.
|
||||
var/const/KW_PASS = 1 //OK
|
||||
var/const/KW_ERR = 2 //Non-fatal error, keyword couldn't be handled properly. Ignore keyword but continue on.
|
||||
var/const/KW_WARN = 3 //Warning
|
||||
|
||||
/*
|
||||
var/const/Class: n_Keyword
|
||||
var/const/Represents a special statement in the code triggered by a keyword.
|
||||
*/
|
||||
/datum/n_Keyword
|
||||
/*
|
||||
Var: inline
|
||||
1 if the keyword is in an expression (e.g. the new keyword in many languages), 0 otherwise (such as the if and else keywords).
|
||||
*/
|
||||
var/inline
|
||||
|
||||
/datum/n_Keyword/New(inline = 0)
|
||||
src.inline = inline
|
||||
return ..()
|
||||
|
||||
/*
|
||||
Proc: Parse
|
||||
Called when the parser finds a keyword in the code.
|
||||
|
||||
Parameters:
|
||||
parser - The parser that created this object. You can use the parameter to manipulate the parser in order to add statements and blocks
|
||||
to its AST.
|
||||
*/
|
||||
/datum/n_Keyword/proc/Parse(var/datum/n_Parser/parser)
|
||||
return
|
||||
/*
|
||||
Class: nS_Keyword
|
||||
A keyword in n_Script. By default these include return, if, else, while, and def. To enable or disable a keyword, change the
|
||||
<nS_Options.keywords> list.
|
||||
|
||||
Behavior:
|
||||
When a parser is expecting a new statement, and a keyword listed in <nS_Options.keywords> is found, it will call the keyword's
|
||||
<n_Keyword.Parse()> proc.
|
||||
*/
|
||||
/datum/n_Keyword/nS_Keyword/New(var/inline = 0)
|
||||
if(inline)
|
||||
del src
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwReturn/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
if(istype(parser.curBlock, /datum/node/BlockDefinition/GlobalBlock)) // Exit out of the program by setting the tokens list size to the same as index.
|
||||
parser.tokens.len = parser.index
|
||||
return
|
||||
|
||||
var/datum/node/statement/ReturnStatement/stmt = new
|
||||
parser.NextToken() //skip 'return' token
|
||||
stmt.value = parser.ParseExpression()
|
||||
parser.curBlock.statements += stmt
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwIf/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
var/datum/node/statement/IfStatement/stmt = new
|
||||
parser.NextToken() //skip 'if' token
|
||||
stmt.cond = parser.ParseParenExpression()
|
||||
if(!parser.CheckToken(")", /datum/token/symbol))
|
||||
return KW_FAIL
|
||||
|
||||
if(!parser.CheckToken("{", /datum/token/symbol, skip=0)) //Token needs to be preserved for parse loop, so skip=0
|
||||
return KW_ERR
|
||||
|
||||
parser.curBlock.statements += stmt
|
||||
stmt.block = new
|
||||
parser.AddBlock(stmt.block)
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwElseIf/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
var/list/L = parser.curBlock.statements
|
||||
var/datum/node/statement/IfStatement/ifstmt
|
||||
|
||||
if(L && L.len)
|
||||
ifstmt = L[L.len] //Get the last statement in the current block
|
||||
|
||||
if(!ifstmt || !istype(ifstmt) || ifstmt.else_if)
|
||||
to_chat(usr, "NTSL: ELSE IF FAILED: [!ifstmt], [!istype(ifstmt)], [!istype(ifstmt) || ifstmt.else_if]")// Usr is unsafe as SHIT but JUST incase I forget this debug line like the fucking asset cache...
|
||||
|
||||
parser.errors += new/datum/scriptError/ExpectedToken("if statement", parser.curToken)
|
||||
return KW_FAIL
|
||||
|
||||
var/datum/node/statement/IfStatement/ElseIf/stmt = new
|
||||
parser.NextToken() //skip 'if' token
|
||||
stmt.cond = parser.ParseParenExpression()
|
||||
if(!parser.CheckToken(")", /datum/token/symbol))
|
||||
return KW_FAIL
|
||||
|
||||
if(!parser.CheckToken("{", /datum/token/symbol, skip = 0)) //Token needs to be preserved for parse loop, so skip=0
|
||||
return KW_ERR
|
||||
|
||||
parser.curBlock.statements += stmt
|
||||
stmt.block = new
|
||||
ifstmt.else_if = stmt
|
||||
parser.AddBlock(stmt.block)
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwElse/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
var/list/L = parser.curBlock.statements
|
||||
var/datum/node/statement/IfStatement/stmt
|
||||
|
||||
if(L && L.len)
|
||||
stmt = L[L.len] //Get the last statement in the current block
|
||||
|
||||
if(!stmt || !istype(stmt) || stmt.else_block) //Ensure that it is an if statement
|
||||
to_chat(usr, "NTSL: ELSE IF FAILED: [!stmt], [!istype(stmt)], [!istype(stmt) || stmt.else_block]")// Usr is unsafe as SHIT but JUST incase I forget this debug line like the fucking asset cache...
|
||||
|
||||
parser.errors += new/datum/scriptError/ExpectedToken("if statement", parser.curToken)
|
||||
return KW_FAIL
|
||||
|
||||
parser.NextToken() //skip 'else' token
|
||||
if(!parser.CheckToken("{", /datum/token/symbol, skip = 0))
|
||||
return KW_ERR
|
||||
|
||||
stmt.else_block = new()
|
||||
parser.AddBlock(stmt.else_block)
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwWhile/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
var/datum/node/statement/WhileLoop/stmt = new
|
||||
parser.NextToken() //skip 'while' token
|
||||
stmt.cond = parser.ParseParenExpression()
|
||||
if(!parser.CheckToken(")", /datum/token/symbol))
|
||||
return KW_FAIL
|
||||
|
||||
if(!parser.CheckToken("{", /datum/token/symbol, skip=0))
|
||||
return KW_ERR
|
||||
|
||||
parser.curBlock.statements += stmt
|
||||
stmt.block = new
|
||||
parser.AddBlock(stmt.block)
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwBreak/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
if(istype(parser.curBlock, /datum/node/BlockDefinition/GlobalBlock))
|
||||
parser.errors += new/datum/scriptError/BadToken(parser.curToken)
|
||||
. = KW_WARN
|
||||
|
||||
var/datum/node/statement/BreakStatement/stmt = new
|
||||
parser.NextToken() //skip 'break' token
|
||||
parser.curBlock.statements += stmt
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwContinue/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
if(istype(parser.curBlock, /datum/node/BlockDefinition/GlobalBlock))
|
||||
parser.errors += new/datum/scriptError/BadToken(parser.curToken)
|
||||
. = KW_WARN
|
||||
|
||||
var/datum/node/statement/ContinueStatement/stmt = new
|
||||
parser.NextToken() //skip 'break' token
|
||||
parser.curBlock.statements += stmt
|
||||
|
||||
/datum/n_Keyword/nS_Keyword/kwDef/Parse(var/datum/n_Parser/nS_Parser/parser)
|
||||
. = KW_PASS
|
||||
var/datum/node/statement/FunctionDefinition/def = new
|
||||
parser.NextToken() //skip 'def' token
|
||||
|
||||
if(!parser.options.IsValidID(parser.curToken.value))
|
||||
parser.errors += new/datum/scriptError/InvalidID(parser.curToken)
|
||||
return KW_FAIL
|
||||
|
||||
def.func_name = parser.curToken.value
|
||||
parser.NextToken()
|
||||
|
||||
if(!parser.CheckToken("(", /datum/token/symbol))
|
||||
return KW_FAIL
|
||||
|
||||
for() //for now parameters can be separated by whitespace - they don't need a comma in between
|
||||
if(istype(parser.curToken, /datum/token/symbol))
|
||||
switch(parser.curToken.value)
|
||||
if(",")
|
||||
parser.NextToken()
|
||||
if(")")
|
||||
break
|
||||
else
|
||||
parser.errors += new/datum/scriptError/BadToken(parser.curToken)
|
||||
return KW_ERR
|
||||
|
||||
else if(istype(parser.curToken, /datum/token/word))
|
||||
def.parameters += parser.curToken.value
|
||||
parser.NextToken()
|
||||
|
||||
else
|
||||
parser.errors += new/datum/scriptError/InvalidID(parser.curToken)
|
||||
return KW_ERR
|
||||
|
||||
if(!parser.CheckToken(")", /datum/token/symbol))
|
||||
return KW_FAIL
|
||||
|
||||
if(istype(parser.curToken, /datum/token/end)) //Function prototype
|
||||
parser.curBlock.statements += def
|
||||
|
||||
else if(parser.curToken.value == "{" && istype(parser.curToken, /datum/token/symbol))
|
||||
def.block = new
|
||||
parser.curBlock.statements += def
|
||||
parser.curBlock.functions[def.func_name] = def
|
||||
parser.AddBlock(def.block)
|
||||
else
|
||||
parser.errors += new/datum/scriptError/BadToken(parser.curToken)
|
||||
return KW_FAIL
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
File: Parser
|
||||
*/
|
||||
/*
|
||||
Class: n_Parser
|
||||
An object that reads tokens and produces an AST (abstract syntax tree).
|
||||
*/
|
||||
/datum/n_Parser
|
||||
/*
|
||||
Var: index
|
||||
The parser's current position in the token's list.
|
||||
*/
|
||||
var/index = 1
|
||||
/*
|
||||
Var: tokens
|
||||
A list of tokens in the source code generated by a scanner.
|
||||
*/
|
||||
var/list/tokens = new
|
||||
/*
|
||||
Var: errors
|
||||
A list of fatal errors found by the parser. If there are any items in this list, then it is not safe to run the returned AST.
|
||||
|
||||
See Also:
|
||||
- <scriptError>
|
||||
*/
|
||||
var/list/errors = new
|
||||
/*
|
||||
Var: warnings
|
||||
A list of non-fatal problems in the script.
|
||||
*/
|
||||
var/list/warnings = new
|
||||
/*
|
||||
Var: curToken
|
||||
The token at <index> in <tokens>.
|
||||
*/
|
||||
var/datum/token/curToken
|
||||
var/datum/stack/blocks = new
|
||||
var/datum/node/BlockDefinition/GlobalBlock/global_block = new
|
||||
var/datum/node/BlockDefinition/GlobalBlock/curBlock
|
||||
|
||||
/*
|
||||
Proc: Parse
|
||||
Reads the tokens and returns the AST's <GlobalBlock> node. Be sure to populate the tokens list before calling this procedure.
|
||||
*/
|
||||
/datum/n_Parser/proc/Parse()
|
||||
return
|
||||
/*
|
||||
Proc: NextToken
|
||||
Sets <curToken> to the next token in the <tokens> list, or null if there are no more tokens.
|
||||
*/
|
||||
/datum/n_Parser/proc/NextToken()
|
||||
if(index >= tokens.len)
|
||||
curToken = null
|
||||
else
|
||||
curToken = tokens[++index]
|
||||
return curToken
|
||||
|
||||
/*
|
||||
Class: nS_Parser
|
||||
An implmentation of a parser for n_Script.
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser
|
||||
var/datum/n_scriptOptions/nS_Options/options
|
||||
/*
|
||||
Constructor: New
|
||||
|
||||
Parameters:
|
||||
tokens - A list of tokens to parse.
|
||||
options - An object used for configuration.
|
||||
*/
|
||||
/datum/n_Parser/nS_Parser/New(tokens[], var/datum/n_scriptOptions/options)
|
||||
src.tokens = tokens
|
||||
src.options = options
|
||||
curBlock = global_block
|
||||
return ..()
|
||||
|
||||
/datum/n_Parser/nS_Parser/Parse()
|
||||
ASSERT(tokens)
|
||||
for(,src.index <= src.tokens.len, src.index++)
|
||||
curToken = tokens[index]
|
||||
switch(curToken.type)
|
||||
if(/datum/token/keyword)
|
||||
var/datum/n_Keyword/kw = options.keywords[curToken.value]
|
||||
kw = new kw()
|
||||
if(kw)
|
||||
if(!kw.Parse(src))
|
||||
return
|
||||
|
||||
if(/datum/token/word)
|
||||
var/datum/token/ntok
|
||||
if(index + 1 > tokens.len)
|
||||
errors += new/datum/scriptError/BadToken(curToken)
|
||||
continue
|
||||
|
||||
ntok = tokens[index + 1]
|
||||
|
||||
if(!istype(ntok, /datum/token/symbol))
|
||||
errors += new/datum/scriptError/BadToken(ntok)
|
||||
continue
|
||||
|
||||
if(ntok.value == "(")
|
||||
ParseFunctionStatement()
|
||||
|
||||
else if(options.assign_operators.Find(ntok.value))
|
||||
ParseAssignment()
|
||||
|
||||
else
|
||||
errors += new/datum/scriptError/BadToken(ntok)
|
||||
continue
|
||||
|
||||
if(!istype(curToken, /datum/token/end))
|
||||
errors += new/datum/scriptError/ExpectedToken(";", curToken)
|
||||
continue
|
||||
|
||||
if(/datum/token/symbol)
|
||||
if(curToken.value == "}")
|
||||
if(!EndBlock())
|
||||
errors += new/datum/scriptError/BadToken(curToken)
|
||||
continue
|
||||
|
||||
else
|
||||
errors += new/datum/scriptError/BadToken(curToken)
|
||||
continue
|
||||
|
||||
if(/datum/token/end)
|
||||
warnings += new/datum/scriptError/BadToken(curToken)
|
||||
continue
|
||||
|
||||
else
|
||||
errors += new/datum/scriptError/BadToken(curToken)
|
||||
return
|
||||
|
||||
return global_block
|
||||
|
||||
/datum/n_Parser/nS_Parser/proc/CheckToken(val, type, err = 1, skip = 1)
|
||||
if(curToken.value != val || !istype(curToken, type))
|
||||
if(err)
|
||||
errors += new/datum/scriptError/ExpectedToken(val, curToken)
|
||||
return 0
|
||||
if(skip)
|
||||
NextToken()
|
||||
|
||||
return 1
|
||||
|
||||
/datum/n_Parser/nS_Parser/proc/AddBlock(var/datum/node/BlockDefinition/B)
|
||||
blocks.Push(curBlock)
|
||||
curBlock = B
|
||||
|
||||
/datum/n_Parser/nS_Parser/proc/EndBlock()
|
||||
if(curBlock == global_block)
|
||||
return 0
|
||||
|
||||
curBlock = blocks.Pop()
|
||||
return 1
|
||||
|
||||
/datum/n_Parser/nS_Parser/proc/ParseAssignment()
|
||||
var/name = curToken.value
|
||||
if(!options.IsValidID(name))
|
||||
errors += new/datum/scriptError/InvalidID(curToken)
|
||||
return
|
||||
|
||||
NextToken()
|
||||
var/t = options.binary_operators[options.assign_operators[curToken.value]]
|
||||
var/datum/node/statement/VariableAssignment/stmt = new()
|
||||
stmt.var_name = new(name)
|
||||
NextToken()
|
||||
|
||||
if(t)
|
||||
stmt.value = new t()
|
||||
stmt.value:exp = new/datum/node/expression/value/variable(stmt.var_name)
|
||||
stmt.value:exp2 = ParseExpression()
|
||||
else
|
||||
stmt.value = ParseExpression()
|
||||
|
||||
curBlock.statements += stmt
|
||||
|
||||
/datum/n_Parser/nS_Parser/proc/ParseFunctionStatement()
|
||||
if(!istype(curToken, /datum/token/word))
|
||||
errors += new/datum/scriptError("Bad identifier in function call.")
|
||||
return
|
||||
|
||||
var/datum/node/statement/FunctionCall/stmt=new
|
||||
stmt.func_name = curToken.value
|
||||
NextToken() //skip function name
|
||||
if(!CheckToken("(", /datum/token/symbol)) //Check for and skip open parenthesis
|
||||
return
|
||||
var/loops = 0
|
||||
for()
|
||||
loops++
|
||||
if(loops >= 800)
|
||||
errors +=new/datum/scriptError("Cannot find ending params.")
|
||||
return
|
||||
|
||||
if(!curToken)
|
||||
errors+=new/datum/scriptError/EndOfFile()
|
||||
return
|
||||
if(istype(curToken, /datum/token/symbol) && curToken.value == ")")
|
||||
curBlock.statements += stmt
|
||||
NextToken() //Skip close parenthesis
|
||||
return
|
||||
|
||||
var/datum/node/expression/P = ParseParamExpression(check_functions = 1)
|
||||
stmt.parameters += P
|
||||
if(istype(curToken, /datum/token/symbol) && curToken.value == ",")
|
||||
NextToken()
|
||||
@@ -1,293 +0,0 @@
|
||||
/*
|
||||
File: Scanner
|
||||
*/
|
||||
/*
|
||||
Class: n_Scanner
|
||||
An object responsible for breaking up source code into tokens for use by the parser.
|
||||
*/
|
||||
/datum/n_Scanner
|
||||
var/list/code
|
||||
/*
|
||||
Var: errors
|
||||
A list of fatal errors found by the scanner. If there are any items in this list, then it is not safe to parse the returned tokens.
|
||||
|
||||
See Also:
|
||||
- <scriptError>
|
||||
*/
|
||||
var/list/errors = new
|
||||
/*
|
||||
Var: warnings
|
||||
A list of non-fatal problems in the source code found by the scanner.
|
||||
*/
|
||||
var/list/warnings = new
|
||||
|
||||
/*
|
||||
Proc: LoadCode
|
||||
Loads source code.
|
||||
*/
|
||||
/datum/n_Scanner/proc/LoadCode(var/list/c)
|
||||
code=c
|
||||
|
||||
/*
|
||||
Proc: LoadCodeFromFile
|
||||
Gets the code from a file and calls <LoadCode()>.
|
||||
*/
|
||||
/datum/n_Scanner/proc/LoadCodeFromFile(var/f)
|
||||
LoadCode(file2text(f))
|
||||
|
||||
/*
|
||||
Proc: Scan
|
||||
Runs the scanner and returns the resulting list of tokens. Ensure that <LoadCode()> has been called first.
|
||||
*/
|
||||
/datum/n_Scanner/proc/Scan()
|
||||
|
||||
/*
|
||||
Class: nS_Scanner
|
||||
A scanner implementation for n_Script.
|
||||
*/
|
||||
/datum/n_Scanner/nS_Scanner
|
||||
/*
|
||||
Variable: codepos
|
||||
The scanner's position in the source code.
|
||||
*/
|
||||
var/codepos = 1
|
||||
var/line = 1
|
||||
var/linepos = 0 //column=codepos-linepos
|
||||
var/datum/n_scriptOptions/nS_Options/options
|
||||
|
||||
var/commenting = 0
|
||||
// 1: single-line
|
||||
// 2: multi-line
|
||||
/*
|
||||
Variable: ignore
|
||||
A list of characters that are ignored by the scanner.
|
||||
|
||||
Default Value:
|
||||
Whitespace
|
||||
*/
|
||||
var/list/ignore = list(" ", "\t", "\n") //Don't add tokens for whitespace
|
||||
/*
|
||||
Variable: end_stmt
|
||||
A list of characters that end a statement. Each item may only be one character long.
|
||||
|
||||
Default Value:
|
||||
Semicolon
|
||||
*/
|
||||
var/list/end_stmt = list(";")
|
||||
/*
|
||||
Variable: string_delim
|
||||
A list of characters that can start and end strings.
|
||||
|
||||
Default Value:
|
||||
Double and single quotes.
|
||||
*/
|
||||
var/list/string_delim = list("\"", "'")
|
||||
/*
|
||||
Variable: delim
|
||||
A list of characters that denote the start of a new token. This list is automatically populated.
|
||||
*/
|
||||
var/list/delim = new
|
||||
|
||||
/*
|
||||
Macro: COL
|
||||
The current column number.
|
||||
*/
|
||||
#define COL codepos-linepos
|
||||
|
||||
/*
|
||||
Constructor: New
|
||||
Parameters:
|
||||
code - The source code to tokenize.
|
||||
options - An <nS_Options> object used to configure the scanner.
|
||||
*/
|
||||
/datum/n_Scanner/nS_Scanner/New(var/list/c, var/datum/n_scriptOptions/nS_Options/options)
|
||||
. = ..()
|
||||
ignore += ascii2text(13) //Carriage return
|
||||
delim += ignore + options.symbols + end_stmt + string_delim
|
||||
src.options = options
|
||||
LoadCode(c)
|
||||
|
||||
/datum/n_Scanner/nS_Scanner/Scan() //Creates a list of tokens from source code
|
||||
var/list/tokens = new
|
||||
for(, src.codepos <= code.len, src.codepos++)
|
||||
var/char = code[codepos]
|
||||
var/nextchar = TCOMMS_SAFE_INDEX(code, codepos + 1)
|
||||
if(char == "\n")
|
||||
line++
|
||||
linepos = codepos
|
||||
|
||||
if(char in ignore)
|
||||
continue
|
||||
|
||||
else if(char == "/" && (nextchar == "*" || nextchar == "/"))
|
||||
ReadComment()
|
||||
|
||||
else if(end_stmt.Find(char))
|
||||
tokens += new/datum/token/end(char, line, COL)
|
||||
|
||||
else if(string_delim.Find(char))
|
||||
codepos++ //skip string delimiter
|
||||
tokens += ReadString(char)
|
||||
|
||||
else if(options.CanStartID(char))
|
||||
tokens += ReadWord()
|
||||
|
||||
else if(options.IsDigit(char))
|
||||
tokens += ReadNumber()
|
||||
|
||||
else if(options.symbols.Find(char))
|
||||
tokens += ReadSymbol()
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
codepos = initial(codepos)
|
||||
line = initial(line)
|
||||
linepos = initial(linepos)
|
||||
return tokens
|
||||
|
||||
|
||||
/*
|
||||
Proc: ReadString
|
||||
Reads a string in the source code into a token.
|
||||
|
||||
Parameters:
|
||||
start - The character used to start the string.
|
||||
*/
|
||||
/datum/n_Scanner/nS_Scanner/proc/ReadString(start)
|
||||
var/buf
|
||||
for(, codepos <= code.len, codepos++)//codepos to length(code))
|
||||
var/char = code[codepos]
|
||||
switch(char)
|
||||
if("\\") //Backslash (\) encountered in string
|
||||
codepos++ //Skip next character in string, since it was escaped by a backslash
|
||||
char = TCOMMS_SAFE_INDEX(code, codepos)
|
||||
switch(char)
|
||||
if("\\") //Double backslash
|
||||
buf += "\\"
|
||||
if("n") //\n Newline
|
||||
buf += "\n"
|
||||
else
|
||||
if(char == start) //\" Doublequote
|
||||
buf += start
|
||||
else //Unknown escaped text
|
||||
buf += char
|
||||
if("\n")
|
||||
. = new/datum/token/string(buf, line, COL)
|
||||
errors += new/datum/scriptError("Unterminated string. Newline reached.", .)
|
||||
line++
|
||||
linepos = codepos
|
||||
break
|
||||
else
|
||||
if(char == start) //string delimiter found, end string
|
||||
break
|
||||
else
|
||||
buf += char //Just a normal character in a string
|
||||
if(!.)
|
||||
return new/datum/token/string(buf, line, COL)
|
||||
|
||||
/*
|
||||
Proc: ReadWord
|
||||
Reads characters separated by an item in <delim> into a token.
|
||||
*/
|
||||
/datum/n_Scanner/nS_Scanner/proc/ReadWord()
|
||||
var/char = code[codepos]
|
||||
var/buf
|
||||
|
||||
while(!delim.Find(char))
|
||||
buf += char
|
||||
if(++codepos > code.len) break
|
||||
char = code[codepos]
|
||||
|
||||
codepos-- //allow main Scan() proc to read the delimiter
|
||||
if(options.keywords.Find(buf))
|
||||
return new/datum/token/keyword(buf, line, COL)
|
||||
else
|
||||
return new/datum/token/word(buf, line, COL)
|
||||
|
||||
/*
|
||||
Proc: ReadSymbol
|
||||
Reads a symbol into a token.
|
||||
*/
|
||||
/datum/n_Scanner/nS_Scanner/proc/ReadSymbol()
|
||||
var/char = code[codepos]
|
||||
var/buf
|
||||
|
||||
while(options.symbols.Find(buf + char))
|
||||
buf += char
|
||||
if(++codepos > code.len) break
|
||||
char = code[codepos]
|
||||
|
||||
codepos-- //allow main Scan() proc to read the next character
|
||||
return new /datum/token/symbol(buf, line, COL)
|
||||
|
||||
/*
|
||||
Proc: ReadNumber
|
||||
Reads a number into a token.
|
||||
*/
|
||||
/datum/n_Scanner/nS_Scanner/proc/ReadNumber()
|
||||
var/char = code[codepos]
|
||||
var/buf
|
||||
var/dec = 0
|
||||
|
||||
while(options.IsDigit(char) || (char == "." && !dec))
|
||||
if(char == ".")
|
||||
dec = 1
|
||||
|
||||
buf += char
|
||||
codepos++
|
||||
char = TCOMMS_SAFE_INDEX(code, codepos)
|
||||
|
||||
var/datum/token/number/T = new(buf, line, COL)
|
||||
if(isnull(text2num(buf)))
|
||||
errors += new/datum/scriptError("Bad number: ", T)
|
||||
T.value = 0
|
||||
|
||||
codepos-- //allow main Scan() proc to read the next character
|
||||
return T
|
||||
|
||||
/*
|
||||
Proc: ReadComment
|
||||
Reads a comment and outputs the type of comment
|
||||
*/
|
||||
|
||||
/datum/n_Scanner/nS_Scanner/proc/ReadComment()
|
||||
var/char = code[codepos]
|
||||
var/nextchar = TCOMMS_SAFE_INDEX(code, codepos + 1)
|
||||
var/charstring = char + nextchar
|
||||
var/comm = 1
|
||||
// 1: single-line comment
|
||||
// 2: multi-line comment
|
||||
var/expectedend = 0
|
||||
|
||||
if(charstring == "//" || charstring == "/*")
|
||||
if(charstring == "/*")
|
||||
comm = 2 // starts a multi-line comment
|
||||
|
||||
while(comm)
|
||||
if(++codepos > code.len)
|
||||
break
|
||||
|
||||
if(expectedend) // ending statement expected...
|
||||
char = code[codepos]
|
||||
if(char == "/") // ending statement found - beak the comment
|
||||
comm = 0
|
||||
break
|
||||
|
||||
if(comm == 2)
|
||||
// multi-line comments are broken by ending statements
|
||||
char = code[codepos]
|
||||
if(char == "*")
|
||||
expectedend = 1
|
||||
continue
|
||||
else
|
||||
char = code[codepos]
|
||||
if(char == "\n")
|
||||
comm = 0
|
||||
break
|
||||
|
||||
if(expectedend)
|
||||
expectedend = 0
|
||||
|
||||
if(comm == 2)
|
||||
errors += new/datum/scriptError/UnterminatedComment()
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
Class: Token
|
||||
Represents an entity and position in the source code.
|
||||
*/
|
||||
/datum/token
|
||||
var/value
|
||||
var/line
|
||||
var/column
|
||||
|
||||
/datum/token/New(v, l = 0, c = 0)
|
||||
value = v
|
||||
line = l
|
||||
column = c
|
||||
|
||||
/datum/token/string
|
||||
|
||||
/datum/token/symbol
|
||||
|
||||
/datum/token/word
|
||||
|
||||
/datum/token/keyword
|
||||
|
||||
/datum/token/number/New()
|
||||
. = ..()
|
||||
if(!isnum(value))
|
||||
value = text2num(value)
|
||||
ASSERT(!isnull(value))
|
||||
|
||||
/datum/token/accessor
|
||||
var/object
|
||||
var/member
|
||||
|
||||
/datum/token/accessor/New(object, member, l = 0, c = 0)
|
||||
src.object = object
|
||||
src.member = member
|
||||
src.value = "[object].[member]" //for debugging only
|
||||
src.line = l
|
||||
src.column = c
|
||||
|
||||
/datum/token/end
|
||||
@@ -1 +0,0 @@
|
||||
#define TCOMMS_SAFE_INDEX(list, index) list.len > index ? list[index] : null
|
||||
Reference in New Issue
Block a user