Telecomms Refactor & CodeMirror

This commit does the following:
 - A lot of shit I am really too tired to fucking write about
 - Absolute pathed telecomms scripting
 - Browser Datum traffic control
  - Absolutely lovely replacement for the fucking skin TCS window, using
    codemirror
 - CodeMirror integration for nanoUI
  - Sorta, I didn't work on this as much as I wanted to, because IT TOOK
    11 FUCKING HOURS TO GET THE BROWSER DATUM TO WORK
This commit is contained in:
Tigercat2000
2015-10-28 17:19:18 -07:00
parent f76109ca5c
commit c244a0fe15
38 changed files with 13973 additions and 2155 deletions
+61 -71
View File
@@ -12,128 +12,118 @@
Articles:
- <http://en.wikipedia.org/wiki/Abstract_syntax_tree>
*/
var
const
/*
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_COMPARE - Greater than, less then, etc
OOP_ADD - Addition and subtraction
OOP_MULTIPLY - Multiplication and division
OOP_MULTIPLY - Multiplication and division
OOP_POW - Exponents
OOP_UNARY - Unary Operators
OOP_GROUP - Parentheses
*/
OOP_OR = 1 //||
OOP_AND = OOP_OR + 1 //&&
OOP_BIT = OOP_AND + 1 //&, |
OOP_EQUAL = OOP_BIT + 1 //==, !=
OOP_COMPARE = OOP_EQUAL + 1 //>, <, >=, <=
OOP_ADD = OOP_COMPARE + 1 //+, -
OOP_MULTIPLY= OOP_ADD + 1 //*, /, %
OOP_POW = OOP_MULTIPLY+ 1 //^
OOP_UNARY = OOP_POW + 1 //!
OOP_GROUP = OOP_UNARY + 1 //()
/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
*/
/node
proc
ToString()
return "[src.type]"
/datum/node/proc/ToString()
return "[src.type]"
/*
Class: identifier
*/
/node/identifier
var
id_name
/datum/node/identifier
var/id_name
New(id)
.=..()
src.id_name=id
/datum/node/identifier/New(id)
. = ..()
src.id_name = id
ToString()
return id_name
/datum/node/identifier/ToString()
return id_name
/*
Class: expression
*/
/node/expression
/datum/node/expression
/*
Class: operator
See <Binary Operators> and <Unary Operators> for subtypes.
*/
/node/expression/operator
var
node/expression/exp
tmp
name
precedence
/datum/node/expression/operator
var/datum/node/expression/exp
var/token = "" // Used when giving type mismatches.
var/tmp/name
var/tmp/precedence
New()
.=..()
if(!src.name) src.name="[src.type]"
/datum/node/expression/operator/New()
.=..()
if(!src.name)
src.name = "[src.type]"
ToString()
return "operator: [name]"
/datum/node/expression/operator/ToString()
return "operator: [name]"
/*
Class: FunctionCall
*/
/node/expression/FunctionCall
/datum/node/expression/FunctionCall
//Function calls can also be expressions or statements.
var
func_name
node/identifier/object
list/parameters=new
var/func_name
var/datum/node/identifier/object
var/list/parameters = list()
/*
Class: literal
*/
/node/expression/value/literal
var
value
/datum/node/expression/value/literal
var/value
New(value)
.=..()
src.value=value
/datum/node/expression/value/literal/New(value)
. = ..()
src.value = value
ToString()
return src.value
/datum/node/expression/value/literal/ToString()
return src.value
/*
Class: variable
*/
/node/expression/value/variable
var
node
object //Either a node/identifier or another node/expression/value/variable which points to the object
node/identifier
id
/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
New(ident)
.=..()
id=ident
if(istext(id))id=new(id)
/datum/node/expression/value/variable/New(ident)
. = ..()
id = ident
if(istext(id))
id = new(id)
ToString()
return src.id.ToString()
/datum/node/expression/value/variable/ToString()
return src.id.ToString()
/*
Class: reference
*/
/node/expression/value/reference
var
datum/value
/datum/node/expression/value/reference
var/datum/value
New(value)
.=..()
src.value=value
/datum/node/expression/value/reference/New(value)
. = ..()
src.value = value
ToString()
return "ref: [src.value] ([src.value.type])"
/datum/node/expression/value/reference/ToString()
return "ref: [src.value] ([src.value.type])"
+10 -13
View File
@@ -8,13 +8,11 @@
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.
*/
/node/BlockDefinition
var/list
statements = new
functions = new
initial_variables = new
/datum/node/BlockDefinition
var/list/statements = new
var/list/functions = new
var/list/initial_variables = new
proc
/*
Proc: SetVar
Defines a permanent variable. The variable will not be deleted when it goes out of scope.
@@ -26,8 +24,8 @@
See Also:
- <n_Interpreter.SetVar()>
*/
SetVar(name, value)
initial_variables[name]=value
/datum/node/BlockDefinition/proc/SetVar(name, value)
initial_variables[name] = value
/*
@@ -35,14 +33,13 @@
A block object representing the global scope.
*/
//
GlobalBlock
New()
initial_variables["null"]=null
return ..()
/datum/node/BlockDefinition/GlobalBlock/New()
initial_variables["null"] = null
return ..()
/*
Class: FunctionBlock
A block representing a function body.
*/
//
FunctionBlock
/datum/node/BlockDefinition/FunctionBlock
@@ -7,8 +7,8 @@
Class: binary
Represents a binary operator in the AST. A binary operator takes two operands (ie x and y) and returns a value.
*/
/node/expression/operator/binary
var/node/expression/exp2
/datum/node/expression/operator/binary
var/datum/node/expression/exp2
////////// Comparison Operators //////////
/*
@@ -16,159 +16,176 @@
Returns true if x = y.
*/
//
Equal
precedence=OOP_EQUAL
/datum/node/expression/operator/binary/Equal
token = "=="
precedence = OOP_EQUAL
/*
Class: NotEqual
Returns true if x and y aren't equal.
Class: NotEqual
Returns true if x and y aren't equal.
*/
//
NotEqual
precedence=OOP_EQUAL
/datum/node/expression/operator/binary/NotEqual
token = "!="
precedence = OOP_EQUAL
/*
Class: Greater
Returns true if x > y.
Class: Greater
Returns true if x > y.
*/
//
Greater
precedence=OOP_COMPARE
/datum/node/expression/operator/binary/Greater
token = ">"
precedence = OOP_COMPARE
/*
Class: Less
Returns true if x < y.
Class: Less
Returns true if x < y.
*/
//
Less
precedence=OOP_COMPARE
/datum/node/expression/operator/binary/Less
token = "<"
precedence = OOP_COMPARE
/*
Class: GreaterOrEqual
Returns true if x >= y.
Class: GreaterOrEqual
Returns true if x >= y.
*/
//
GreaterOrEqual
precedence=OOP_COMPARE
/datum/node/expression/operator/binary/GreaterOrEqual
token = ">="
precedence = OOP_COMPARE
/*
Class: LessOrEqual
Returns true if x <= y.
Class: LessOrEqual
Returns true if x <= y.
*/
//
LessOrEqual
precedence=OOP_COMPARE
/datum/node/expression/operator/binary/LessOrEqual
token = "<="
precedence = OOP_COMPARE
////////// Logical Operators //////////
/*
Class: LogicalAnd
Returns true if x and y are true.
Class: LogicalAnd
Returns true if x and y are true.
*/
//
LogicalAnd
precedence=OOP_AND
/datum/node/expression/operator/binary/LogicalAnd
token = "&&"
precedence = OOP_AND
/*
Class: LogicalOr
Returns true if x, y, or both are true.
Class: LogicalOr
Returns true if x, y, or both are true.
*/
//
LogicalOr
precedence=OOP_OR
/datum/node/expression/operator/binary/LogicalOr
token = "||"
precedence = OOP_OR
/*
Class: LogicalXor
Returns true if either x or y but not both are true.
Class: LogicalXor
Returns true if either x or y but not both are true.
*/
//
LogicalXor //Not implemented in nS
precedence=OOP_OR
/datum/node/expression/operator/binary/LogicalXor //Not implemented in nS
precedence = OOP_OR
////////// Bitwise Operators //////////
/*
Class: BitwiseAnd
Performs a bitwise and operation.
Class: BitwiseAnd
Performs a bitwise and operation.
Example:
011 & 110 = 010
Example:
011 & 110 = 010
*/
//
BitwiseAnd
precedence=OOP_BIT
/datum/node/expression/operator/binary/BitwiseAnd
token = "&"
precedence = OOP_BIT
/*
Class: BitwiseOr
Performs a bitwise or operation.
Class: BitwiseOr
Performs a bitwise or operation.
Example:
011 | 110 = 111
Example:
011 | 110 = 111
*/
//
BitwiseOr
precedence=OOP_BIT
/datum/node/expression/operator/binary/BitwiseOr
token = "|"
precedence = OOP_BIT
/*
Class: BitwiseXor
Performs a bitwise exclusive or operation.
Class: BitwiseXor
Performs a bitwise exclusive or operation.
Example:
011 xor 110 = 101
Example:
011 xor 110 = 101
*/
//
BitwiseXor
precedence=OOP_BIT
/datum/node/expression/operator/binary/BitwiseXor
token = "`"
precedence = OOP_BIT
////////// Arithmetic Operators //////////
/*
Class: Add
Returns the sum of x and y.
Class: Add
Returns the sum of x and y.
*/
//
Add
precedence=OOP_ADD
/datum/node/expression/operator/binary/Add
token = "+"
precedence = OOP_ADD
/*
Class: Subtract
Returns the difference of x and y.
Class: Subtract
Returns the difference of x and y.
*/
//
Subtract
precedence=OOP_ADD
/datum/node/expression/operator/binary/Subtract
token = "-"
precedence = OOP_ADD
/*
Class: Multiply
Returns the product of x and y.
Class: Multiply
Returns the product of x and y.
*/
//
Multiply
precedence=OOP_MULTIPLY
/datum/node/expression/operator/binary/Multiply
token = "*"
precedence = OOP_MULTIPLY
/*
Class: Divide
Returns the quotient of x and y.
Class: Divide
Returns the quotient of x and y.
*/
//
Divide
precedence=OOP_MULTIPLY
/datum/node/expression/operator/binary/Divide
token = "/"
precedence = OOP_MULTIPLY
/*
Class: Power
Returns x raised to the power of y.
Class: Power
Returns x raised to the power of y.
*/
//
Power
precedence=OOP_POW
/datum/node/expression/operator/binary/Power
token = "^"
precedence = OOP_POW
/*
Class: Modulo
Returns the remainder of x / y.
Class: Modulo
Returns the remainder of x / y.
*/
//
Modulo
precedence=OOP_MULTIPLY
/datum/node/expression/operator/binary/Modulo
token = "%"
precedence = OOP_MULTIPLY
@@ -5,8 +5,8 @@
Class: unary
Represents a unary operator in the AST. Unary operators take a single operand (referred to as x below) and return a value.
*/
/node/expression/operator/unary
precedence=OOP_UNARY
/datum/node/expression/operator/unary
precedence = OOP_UNARY
/*
Class: LogicalNot
@@ -16,8 +16,8 @@
!true = false and !false = true
*/
//
LogicalNot
name="logical not"
/datum/node/expression/operator/unary/LogicalNot
name = "logical not"
/*
Class: BitwiseNot
@@ -27,25 +27,25 @@
~10 (decimal 2) = 01 (decimal 1).
*/
//
BitwiseNot
name="bitwise not"
/datum/node/expression/operator/unary/BitwiseNot
name = "bitwise not"
/*
Class: Minus
Returns -x.
*/
//
Minus
name="minus"
/datum/node/expression/operator/unary/Minus
name = "minus"
/*
Class: group
A special unary operator representing a value in parentheses.
*/
//
group
precedence=OOP_GROUP
/datum/node/expression/operator/unary/group
precedence = OOP_GROUP
New(node/expression/exp)
src.exp=exp
return ..()
/datum/node/expression/operator/unary/New(var/datum/node/expression/exp)
src.exp = exp
return ..()
+60 -72
View File
@@ -5,118 +5,106 @@
Class: statement
An object representing a single instruction run by an interpreter.
*/
/node/statement
/datum/node/statement
/*
Class: FunctionCall
Represents a call to a function.
*/
//
FunctionCall
var
func_name
node/identifier/object
list/parameters=new
/datum/node/statement/FunctionCall
var/func_name
var/datum/node/identifier/object
var/list/parameters = new
/*
Class: FunctionDefinition
Defines a function.
Class: FunctionDefinition
Defines a function.
*/
//
FunctionDefinition
var
func_name
list/parameters=new
node/BlockDefinition/FunctionBlock/block
/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.
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>.
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>
See Also:
- <VariableDeclaration>
*/
//
VariableAssignment
var
node
identifier
object
var_name
expression/value
/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.
Class: VariableDeclaration
Intializes a local variable to a null value.
See Also:
- <VariableAssignment>
See Also:
- <VariableAssignment>
*/
//
VariableDeclaration
var
node
identifier
object
var_name
/datum/node/statement/VariableDeclaration
var/datum/node/identifier/object
var/datum/node/identifier/var_name
/*
Class: IfStatement
Class: IfStatement
*/
//
IfStatement
var
node
BlockDefinition
block
else_block //may be null
expression/cond
/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.
Class: WhileLoop
Loops while a given condition is true.
*/
//
WhileLoop
var
node
BlockDefinition/block
expression/cond
/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
Class: ForLoop
Loops while test is true, initializing a variable, increasing the variable
*/
ForLoop
var
node
BlockDefinition/block
expression/test
expression/init
expression/increment
/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.
Class: BreakStatement
Ends a loop.
*/
//
BreakStatement
/datum/node/statement/BreakStatement
/*
Class: ContinueStatement
Skips to the next iteration of a loop.
Class: ContinueStatement
Skips to the next iteration of a loop.
*/
//
ContinueStatement
/datum/node/statement/ContinueStatement
/*
Class: ReturnStatement
Ends the function and returns a value.
Class: ReturnStatement
Ends the function and returns a value.
*/
//
ReturnStatement
var
node/expression/value
/datum/node/statement/ReturnStatement
var/datum/node/expression/value
+125 -88
View File
@@ -5,128 +5,165 @@
Class: scriptError
An error scanning or parsing the source code.
*/
/scriptError
var
/datum/scriptError
/*
Var: message
A message describing the problem.
*/
message
New(msg=null)
if(msg)message=msg
var/message
BadToken
message="Unexpected token: "
var/token/token
New(token/t)
token=t
if(t&&t.line) message="[t.line]: [message]"
if(istype(t))message+="[t.value]"
else message+="[t]"
/datum/scriptError/New(msg = null)
if(msg)message = msg
InvalidID
parent_type=/scriptError/BadToken
message="Invalid identifier name: "
/datum/scriptError/BadToken
message = "Unexpected token: "
var/datum/token/token
ReservedWord
parent_type=/scriptError/BadToken
message="Identifer using reserved word: "
/datum/scriptError/BadToken/New(datum/token/t)
token = t
if(t && t.line)
message = "[t.line]: [message]"
BadNumber
parent_type=/scriptError/BadToken
message = "Bad number: "
if(istype(t))
message += "[t.value]"
BadReturn
var/token/token
message = "Unexpected return statement outside of a function."
New(token/t)
src.token=t
else
message += "[t]"
EndOfFile
message = "Unexpected end of file."
/datum/scriptError/InvalidID
parent_type = /datum/scriptError/BadToken
message = "Invalid identifier name: "
ExpectedToken
message="Expected: '"
New(id, token/T)
if(T && T.line) message="[T.line]: [message]"
message+="[id]'. "
if(T)message+="Found '[T.value]'."
/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]'."
UnterminatedComment
message="Unterminated multi-line comment statement: expected */"
/datum/scriptError/UnterminatedComment
message = "Unterminated multi-line comment statement: expected */"
DuplicateFunction
New(name, token/t)
message="Function '[name]' defined twice."
/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.
*/
/runtimeError
var
name
/datum/runtimeError
var/name
/*
Var: message
A basic description as to what went wrong.
*/
message
stack/stack
proc
var/message
var/datum/stack/stack
/*
Proc: ToString
Returns a description of the error suitable for showing to the user.
*/
ToString()
. = "[name]: [message]"
if(!stack.Top()) return
.+="\nStack:"
while(stack.Top())
var/node/statement/FunctionCall/stmt=stack.Pop()
. += "\n\t [stmt.func_name]()"
/datum/runtimeError/proc/ToString()
. = "[name]: [message]"
if(!stack.Top())
return
TypeMismatch
name="TypeMismatchError"
New(op, a, b)
message="Type mismatch: '[a]' [op] '[b]'"
. += "\nStack:"
while(stack.Top())
var/datum/node/statement/FunctionCall/stmt = stack.Pop()
. += "\n\t [stmt.func_name]()"
UnexpectedReturn
name="UnexpectedReturnError"
message="Unexpected return statement."
/datum/runtimeError/TypeMismatch
name = "TypeMismatchError"
UnknownInstruction
name="UnknownInstructionError"
message="Unknown instruction type. This may be due to incompatible compiler and interpreter versions or a lack of implementation."
/datum/runtimeError/TypeMismatch/New(op, a, b)
message = "Type mismatch: '[a]' [op] '[b]'"
UndefinedVariable
name="UndefinedVariableError"
New(variable)
message="Variable '[variable]' has not been declared."
/datum/runtimeError/TypeMismatch/unary/New(op, a)
message = "Type mismatch: [op]'[a]'"
UndefinedFunction
name="UndefinedFunctionError"
New(function)
message="Function '[function]()' has not been defined."
/datum/runtimeError/TypeMismatch/New(op, a, b)
message = "Type mismatch: '[a]' [op] '[b]'"
DuplicateVariableDeclaration
name="DuplicateVariableError"
New(variable)
message="Variable '[variable]' was already declared."
/datum/runtimeError/UnexpectedReturn
name = "UnexpectedReturnError"
message = "Unexpected return statement."
IterationLimitReached
name="MaxIterationError"
message="A loop has reached its maximum number of iterations."
/datum/runtimeError/UnknownInstruction
name = "UnknownInstructionError"
message = "Unknown instruction type. This may be due to incompatible compiler and interpreter versions or a lack of implementation."
RecursionLimitReached
name="MaxRecursionError"
message="The maximum amount of recursion has been reached."
/datum/runtimeError/UndefinedVariable
name = "UndefinedVariableError"
DivisionByZero
name="DivideByZeroError"
message="Division by zero attempted."
/datum/runtimeError/UndefinedVariable/New(variable)
message = "Variable '[variable]' has not been declared."
MaxCPU
name="MaxComputationalUse"
message="Maximum amount of computational cycles reached (>= 1000)."
/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"
+64 -79
View File
@@ -1,16 +1,15 @@
client/verb/tcssave()
/client/verb/tcssave()
set hidden = 1
//writepanic("[__FILE__].[__LINE__] ([src.type])([usr ? usr.ckey : ""]) \\client/verb/tcssave() called tick#: [world.time]")
if(mob.machine || issilicon(mob))
if((istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob)) || issilicon(mob))
if(telecomms_check(mob))
var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
if(Machine.editingcode != mob)
return
if(Machine.SelectedServer)
var/obj/machinery/telecomms/server/Server = Machine.SelectedServer
var/tcscode=winget(src, "tcscode", "text")
var/msg="[mob.name] is adding script to server [Server]: [tcscode]"
diary << msg
var/tcscode = winget(src, "tcscode", "text")
Server.setcode( tcscode ) // this actually saves the code from input to the server
src << output(null, "tcserror") // clear the errors
else
@@ -24,10 +23,11 @@ client/verb/tcssave()
src << output("<font color = red>Failed to save: Unable to locate machine. (Back up your code before exiting the window!)</font color>", "tcserror")
client/verb/tcscompile()
/client/verb/tcscompile()
set hidden = 1
//writepanic("[__FILE__].[__LINE__] ([src.type])([usr ? usr.ckey : ""]) \\client/verb/tcscompile() called tick#: [world.time]")
if(mob.machine || issilicon(mob))
if((istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob)) || (issilicon(mob) && istype(mob.machine, /obj/machinery/computer/telecomms/traffic) ))
if(telecomms_check(mob))
var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
if(Machine.editingcode != mob)
return
@@ -35,35 +35,40 @@ client/verb/tcscompile()
if(Machine.SelectedServer)
var/obj/machinery/telecomms/server/Server = Machine.SelectedServer
Server.setcode( winget(src, "tcscode", "text") ) // save code first
var/list/compileerrors = Server.compile(usr) // then compile the code!
// Output all the compile-time errors
src << output(null, "tcserror")
spawn(0)
// Output all the compile-time errors
src << output(null, "tcserror")
src << output("<font color = black>Please wait, compiling...</font>", "tcserror")
if(compileerrors.len)
src << output("<b>Compile Errors</b>", "tcserror")
for(var/scriptError/e in compileerrors)
src << output("<font color = red>\t>[e.message]</font color>", "tcserror")
src << output("([compileerrors.len] errors)", "tcserror")
var/list/compileerrors = Server.compile(mob) // then compile the code!
if(!telecomms_check(mob))
return
// Output compile errors to all other people viewing the code too
for(var/mob/M in Machine.viewingcode)
if(M.client)
M << output(null, "tcserror")
M << output("<b>Compile Errors</b>", "tcserror")
for(var/scriptError/e in compileerrors)
M << output("<font color = red>\t>[e.message]</font color>", "tcserror")
M << output("([compileerrors.len] errors)", "tcserror")
if(compileerrors.len)
src << output("<b>Compile Errors</b>", "tcserror")
for(var/datum/scriptError/e in compileerrors)
src << output("<font color = red>\t>[e.message]</font color>", "tcserror")
src << output("([compileerrors.len] errors)", "tcserror")
// Output compile errors to all other people viewing the code too
for(var/mob/M in Machine.viewingcode)
if(M.client)
M << output(null, "tcserror")
M << output("<b>Compile Errors</b>", "tcserror")
for(var/datum/scriptError/e in compileerrors)
M << output("<font color = red>\t>[e.message]</font color>", "tcserror")
M << output("([compileerrors.len] errors)", "tcserror")
else
src << output("<font color = blue>TCS compilation successful!</font color>", "tcserror")
src << output("(0 errors)", "tcserror")
else
src << output("<font color = blue>TCS compilation successful!</font color>", "tcserror")
src << output("(0 errors)", "tcserror")
for(var/mob/M in Machine.viewingcode)
if(M.client)
M << output("<font color = blue>TCS compilation successful!</font color>", "tcserror")
M << output("(0 errors)", "tcserror")
for(var/mob/M in Machine.viewingcode)
if(M.client)
M << output("<font color = blue>TCS compilation successful!</font color>", "tcserror")
M << output("(0 errors)", "tcserror")
else
src << output(null, "tcserror")
@@ -75,59 +80,30 @@ client/verb/tcscompile()
src << output(null, "tcserror")
src << output("<font color = red>Failed to compile: Unable to locate machine. (Back up your code before exiting the window!)</font color>", "tcserror")
client/verb/tcsrun()
/client/verb/tcsrun()
set hidden = 1
//writepanic("[__FILE__].[__LINE__] ([src.type])([usr ? usr.ckey : ""]) \\client/verb/tcsrun() called tick#: [world.time]")
if(mob.machine || issilicon(mob))
if((istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob)) || (issilicon(mob) && istype(mob.machine, /obj/machinery/computer/telecomms/traffic) ))
if(telecomms_check(mob))
var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
if(Machine.editingcode != mob)
return
if(Machine.SelectedServer)
var/obj/machinery/telecomms/server/Server = Machine.SelectedServer
Server.setcode( winget(src, "tcscode", "text") ) // save code first
var/list/compileerrors = Server.compile() // then compile the code!
// Output all the compile-time errors
src << output(null, "tcserror")
if(compileerrors.len)
src << output("<b>Compile Errors</b>", "tcserror")
for(var/scriptError/e in compileerrors)
src << output("<font color = red>\t>[e.message]</font color>", "tcserror")
src << output("([compileerrors.len] errors)", "tcserror")
// Output compile errors to all other people viewing the code too
for(var/mob/M in Machine.viewingcode)
if(M.client)
M << output(null, "tcserror")
M << output("<b>Compile Errors</b>", "tcserror")
for(var/scriptError/e in compileerrors)
M << output("<font color = red>\t>[e.message]</font color>", "tcserror")
M << output("([compileerrors.len] errors)", "tcserror")
var/datum/signal/signal = new
signal.data["message"] = ""
if(Server.freq_listening.len > 0)
signal.frequency = Server.freq_listening[1]
else
// Finally, we run the code!
src << output("<font color = blue>TCS compilation successful! Code executed.</font color>", "tcserror")
src << output("(0 errors)", "tcserror")
signal.frequency = 1459
signal.data["name"] = ""
signal.data["job"] = ""
signal.data["reject"] = 0
signal.data["server"] = Server
for(var/mob/M in Machine.viewingcode)
if(M.client)
M << output("<font color = blue>TCS compilation successful!</font color>", "tcserror")
M << output("(0 errors)", "tcserror")
var/datum/signal/signal = new()
signal.data["message"] = ""
if(Server.freq_listening.len > 0)
signal.frequency = Server.freq_listening[1]
else
signal.frequency = 1459
signal.data["name"] = ""
signal.data["job"] = ""
signal.data["reject"] = 0
signal.data["server"] = Server
Server.Compiler.Run(signal)
Server.Compiler.Run(signal)
else
@@ -141,10 +117,11 @@ client/verb/tcsrun()
src << output("<font color = red>Failed to run: Unable to locate machine. (Back up your code before exiting the window!)</font color>", "tcserror")
client/verb/exittcs()
/client/verb/exittcs()
set hidden = 1
//writepanic("[__FILE__].[__LINE__] ([src.type])([usr ? usr.ckey : ""]) \\client/verb/exittcs() called tick#: [world.time]")
if(mob.machine || issilicon(mob))
if((istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob)) || (issilicon(mob) && istype(mob.machine, /obj/machinery/computer/telecomms/traffic) ))
if(telecomms_check(mob))
var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
if(Machine.editingcode == mob)
Machine.storedcode = "[winget(mob, "tcscode", "text")]"
@@ -153,10 +130,11 @@ client/verb/exittcs()
if(mob in Machine.viewingcode)
Machine.viewingcode.Remove(mob)
client/verb/tcsrevert()
/client/verb/tcsrevert()
set hidden = 1
//writepanic("[__FILE__].[__LINE__] ([src.type])([usr ? usr.ckey : ""]) \\client/verb/tcsrevert() called tick#: [world.time]")
if(mob.machine || issilicon(mob))
if((istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob)) || (issilicon(mob) && istype(mob.machine, /obj/machinery/computer/telecomms/traffic) ))
if(telecomms_check(mob))
var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
if(Machine.editingcode != mob)
return
@@ -182,10 +160,11 @@ client/verb/tcsrevert()
src << output("<font color = red>Failed to revert: Unable to locate machine.</font color>", "tcserror")
client/verb/tcsclearmem()
/client/verb/tcsclearmem()
set hidden = 1
//writepanic("[__FILE__].[__LINE__] ([src.type])([usr ? usr.ckey : ""]) \\client/verb/tcsclearmem() called tick#: [world.time]")
if(mob.machine || issilicon(mob))
if((istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, mob)) || (issilicon(mob) && istype(mob.machine, /obj/machinery/computer/telecomms/traffic) ))
if(telecomms_check(mob))
var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
if(Machine.editingcode != mob)
return
@@ -207,4 +186,10 @@ client/verb/tcsclearmem()
src << output("<font color = red>Failed to clear memory: Unable to locate machine.</font color>", "tcserror")
else
src << output(null, "tcserror")
src << output("<font color = red>Failed to clear memory: Unable to locate machine.</font color>", "tcserror")
src << output("<font color = red>Failed to clear memory: Unable to locate machine.</font color>", "tcserror")
/proc/telecomms_check(var/mob/mob)
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/telecomms_check() called tick#: [world.time]")
if(mob && istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && in_range(mob.machine, mob) || issilicon(mob) && istype(mob.machine, /obj/machinery/computer/telecomms/traffic))
return 1
return 0
@@ -4,253 +4,312 @@
/* --- Traffic Control Scripting Language --- */
// Nanotrasen TCS Language - Made by Doohl
/n_Interpreter/TCS_Interpreter
/datum/n_Interpreter/TCS_Interpreter
var/datum/TCS_Compiler/Compiler
HandleError(runtimeError/e)
Compiler.Holder.add_entry(e.ToString(), "Execution Error")
/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/n_Interpreter/TCS_Interpreter/interpreter
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()
/* -- Compile a raw block of text -- */
proc/Compile(code as message)
var/n_scriptOptions/nS_Options/options = new()
var/n_Scanner/nS_Scanner/scanner = new(code, options)
var/list/tokens = scanner.Scan()
var/n_Parser/nS_Parser/parser = new(tokens, options)
var/node/BlockDefinition/GlobalBlock/program = parser.Parse()
/datum/TCS_Compiler/proc/Compile(code as message)
var/datum/n_scriptOptions/nS_Options/options = new()
var/datum/n_Scanner/nS_Scanner/scanner = new(code, options)
var/list/tokens = scanner.Scan()
var/datum/n_Parser/nS_Parser/parser = new(tokens, options)
var/datum/node/BlockDefinition/GlobalBlock/program = parser.Parse()
var/list/returnerrors = list()
var/list/returnerrors = list()
returnerrors += scanner.errors
returnerrors += parser.errors
if(returnerrors.len)
return returnerrors
interpreter = new(program)
interpreter.persist = 1
interpreter.Compiler= src
returnerrors += scanner.errors
returnerrors += parser.errors
if(returnerrors.len)
return returnerrors
/* -- Execute the compiled code -- */
interpreter = new(program)
interpreter.persist = 1
interpreter.Compiler= src
proc/Run(var/datum/signal/signal)
return returnerrors
if(!ready)
return
/* -- Execute the compiled code -- */
if(!interpreter)
return
/datum/TCS_Compiler/proc/Run(var/datum/signal/signal)
if(!ready)
return
interpreter.container = src
if(!interpreter)
return
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("TRUE" , 1) // boolean shortcut to 1
interpreter.container = src
interpreter.SetVar("NORTH" , NORTH) // NORTH (1)
interpreter.SetVar("SOUTH" , SOUTH) // SOUTH (2)
interpreter.SetVar("EAST" , EAST) // EAST (4)
interpreter.SetVar("WEST" , WEST) // WEST (8)
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
// 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("NORTH", NORTH) // NORTH (1)
interpreter.SetVar("SOUTH", SOUTH) // SOUTH (2)
interpreter.SetVar("EAST", EAST) // EAST (4)
interpreter.SetVar("WEST", WEST) // WEST (8)
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")
// 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)
// Set up the script procs
// Signal data
/*
-> Send another signal to a server
@format: broadcast(content, frequency, source, job)
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
@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"))
var/datum/language/speaking = signal.data["language"]
if(speaking)
interpreter.SetVar("$language", speaking.name)
else
interpreter.SetVar("$language", "Unknown")
/*
-> Store a value permanently to the server machine (not the actual game hosting machine, the ingame machine)
@format: mem(address, value)
// Set up the script procs
@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"))
/*
-> Send another signal to a server
@format: broadcast(content, frequency, source, job)
/* -- Clone functions, carried from default BYOND procs --- */
// Vector
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/n_prob)
interpreter.SetProc("substr", /proc/n_substr)
interpreter.SetProc("find", /proc/n_smartfind)
interpreter.SetProc("length", /proc/n_smartlength)
@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"))
// Strings
interpreter.SetProc("lower", /proc/n_lower)
interpreter.SetProc("upper", /proc/n_upper)
interpreter.SetProc("explode", /proc/n_explode)
interpreter.SetProc("implode", /proc/n_implode)
interpreter.SetProc("repeat", /proc/n_repeat)
interpreter.SetProc("reverse", /proc/n_reverse)
interpreter.SetProc("tonum", /proc/n_str2num)
interpreter.SetProc("replace", /proc/n_replace)
interpreter.SetProc("proper", /proc/n_proper)
/*
-> Store a value permanently to the server machine (not the actual game hosting machine, the ingame machine)
@format: mem(address, value)
// 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_ceil)
interpreter.SetProc("round", /proc/n_round)
interpreter.SetProc("clamp", /proc/n_clamp)
interpreter.SetProc("inrange", /proc/n_inrange)
interpreter.SetProc("rand", /proc/n_rand)
interpreter.SetProc("randseed", /proc/n_randseed)
interpreter.SetProc("min", /proc/n_min)
interpreter.SetProc("max", /proc/n_max)
interpreter.SetProc("sin", /proc/n_sin)
interpreter.SetProc("cos", /proc/n_cos)
interpreter.SetProc("asin", /proc/n_asin)
interpreter.SetProc("acos", /proc/n_acos)
interpreter.SetProc("log", /proc/n_log)
@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"))
// Time
interpreter.SetProc("time", /proc/n_time)
interpreter.SetProc("sleep", /proc/n_delay)
interpreter.SetProc("timestamp", /proc/gameTimestamp)
/*
-> Delay code for a given amount of deciseconds
@format: sleep(time)
// Run the compiled code
interpreter.Run()
@param time: time to sleep in deciseconds (1/10th second)
*/
interpreter.SetProc("sleep", /proc/delay)
// Backwards-apply variables onto signal data
/* sanitize EVERYTHING. fucking players can't be trusted with SHIT */
/*
-> Replaces a string with another string
@format: replace(string, substring, replacestring)
signal.data["message"] = interpreter.GetVar("$content")
signal.frequency = interpreter.GetVar("$freq")
@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
var/setname = ""
var/obj/machinery/telecomms/server/S = signal.data["server"]
if(interpreter.GetVar("$source") in S.stored_names)
setname = interpreter.GetVar("$source")
else
setname = "<i>[interpreter.GetVar("$source")]</i>"
*/
interpreter.SetProc("replace", /proc/replacetext)
if(signal.data["name"] != setname)
signal.data["realname"] = setname
signal.data["name"] = setname
signal.data["job"] = interpreter.GetVar("$job")
signal.data["reject"] = !(interpreter.GetVar("$pass")) // set reject to the opposite of $pass
/*
-> Locates an element/substring inside of a list or string
@format: find(haystack, needle, start = 1, end = 0)
// If the message is invalid, just don't broadcast it!
if(signal.data["message"] == "" || !signal.data["message"])
signal.data["reject"] = 1
@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/text2list)
interpreter.SetProc("text2vectorEx",/proc/text2listEx)
interpreter.SetProc("vector2text", /proc/list2text)
// Donkie~
// Strings
interpreter.SetProc("lower", /proc/n_lower)
interpreter.SetProc("upper", /proc/n_upper)
interpreter.SetProc("explode", /proc/string_explode)
interpreter.SetProc("implode", /proc/list2text)
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/replacetextEx)
// Numbers
interpreter.SetProc("tostring", /proc/n_num2str)
interpreter.SetProc("sqrt", /proc/n_sqrt)
interpreter.SetProc("abs", /proc/n_abs)
interpreter.SetProc("floor", /proc/Floor)
interpreter.SetProc("ceil", /proc/Ceiling)
interpreter.SetProc("round", /proc/n_round)
interpreter.SetProc("clamp", /proc/n_clamp)
interpreter.SetProc("inrange", /proc/IsInRange)
interpreter.SetProc("rand", /proc/rand_chance)
interpreter.SetProc("arctan", /proc/Atan2)
interpreter.SetProc("lcm", /proc/Lcm)
interpreter.SetProc("gcd", /proc/Gcd)
interpreter.SetProc("mean", /proc/Mean)
interpreter.SetProc("root", /proc/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/Tan)
interpreter.SetProc("csc", /proc/Csc)
interpreter.SetProc("cot", /proc/Cot)
interpreter.SetProc("sec", /proc/Sec)
interpreter.SetProc("todegrees", /proc/ToDegrees)
interpreter.SetProc("toradians", /proc/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/timestamp)
// 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.*/
signal.data["message"] = interpreter.GetVar("$content", signal.data["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/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
proc/tcombroadcast(var/message, var/freq, var/source, var/job)
var/datum/signal/newsign = new
/datum/signal/proc/mem(var/address, var/value)
if(istext(address))
var/obj/machinery/telecomms/server/S = data["server"]
var/obj/item/device/radio/hradio = S.server_radio
if(!hradio)
error("[src] has no radio.")
return
if(!value && value != 0)
return S.memory[address]
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)
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
if(source in S.stored_names)
newsign.data["name"] = source
else
newsign.data["name"] = "<i>[html_encode(uppertext(source))]<i>"
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
S.memory[address] = value
var/datum/radio_frequency/connection = radio_controller.return_frequency(freq)
newsign.data["connection"] = connection
/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/device/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["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"] = list()
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
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
+101 -154
View File
@@ -1,17 +1,18 @@
// Script -> BYOND code procs
#define SCRIPT_MAX_REPLACEMENTS_ALLOWED 200
// --- List operations (lists known as vectors in n_script) ---
// Creates a list out of all the arguments
// 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
// Picks one random item from the list
// 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))
@@ -24,15 +25,16 @@
return pick(finalpick)
// Gets/Sets a value at a key in the list
// 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)
if(L.len >= pos && !(pos > L.len))
return L[pos]
else
if(L.len >= pos)
if(L.len >= pos && !(pos > L.len))
L[pos] = value
else if(istext(pos))
if(!value)
@@ -40,13 +42,15 @@
else
L[pos] = value
// Copies the list into a new one
// 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)
// Adds arg 2,3,4,5... to the end of list at arg 1
// 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)
@@ -59,8 +63,9 @@
if(chosenlist)
chosenlist.Add(e)
// Removes arg 2,3,4,5... from list at arg 1
// 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)
@@ -73,26 +78,45 @@
if(chosenlist)
chosenlist.Remove(e)
// Cuts out a copy of a list
// 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)
// Swaps two values in the list
// 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)
// Inserts a value into the list
// 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)
// --- String methods ---
// --- Miscellaneous functions ---
//If list, finds a value in it, if text, finds a substring in it
/proc/n_smartfind(var/haystack, var/needle, var/start = 1, var/end = 0)
// Clone of sleep()
/proc/delay(var/time)
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/delay() called tick#: [world.time]")
sleep(time)
// 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))
@@ -104,41 +128,54 @@
if(istext(haystack))
if(length(haystack) >= end && start > 0)
return findtext(haystack, needle, start, end)
//Returns a substring of the string
/proc/n_substr(var/string, var/start = 1, var/end = 0)
// 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)
//Returns the length of the string or list
/proc/n_smartlength(var/container)
// 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
//Lowercase all characters
// 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)
//Uppercase all characters
/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)
//Converts a string to a list
/proc/n_explode(var/string, var/separator = "")
/proc/time()
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/time() called tick#: [world.time]")
return world.time + (12 HOURS)
/proc/timestamp(var/format = "hh:mm:ss") // Get the game time in text
//writepanic("[__FILE__].[__LINE__] (no type)([usr ? usr.ckey : ""]) \\/proc/timestamp() called tick#: [world.time]")
return time2text(world.time + (10 HOURS), format) // Yes, 10, not 12 hours, for some reason time2text() is being moronic (T-thanks BYOND), and it's adding 2 hours to this, I don't even know either.
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 text2list(string, separator)
//Converts a list to a string
/proc/n_implode(var/list/li, var/separator)
/proc/list_implode(var/list/li, var/separator)
if(istype(li) && (istext(separator) || isnull(separator)))
return list2text(li, separator)
//Repeats the string x times
/proc/n_repeat(var/string, var/amount)
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 = ""
@@ -151,166 +188,76 @@
return newstring
//Reverses the order of the string. "Clown" becomes "nwolC"
/proc/n_reverse(var/string)
if(istext(string))
var/newstring = ""
var/i
for(i=lentext(string), i>0, i--)
if(i>=1000)
break
newstring = newstring + copytext(string, i, i+1)
return newstring
// String -> Number
/proc/n_str2num(var/string)
// 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)
/proc/n_proper(var/string)
if(!istext(string))
return ""
// 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)
return text("[][]", uppertext(copytext(string, 1, 2)), lowertext(copytext(string, 2)))
// --- Number methods ---
//Returns the highest value of the arguments
//Need custom functions here cause byond's min and max runtimes if you give them a string or list.
/proc/n_max()
if(args.len == 0)
return 0
var/max = args[1]
for(var/e in args)
if(isnum(e) && e > max)
max = e
return max
//Returns the lowest value of the arguments
/proc/n_min()
if(args.len == 0)
return 0
var/min = args[1]
for(var/e in args)
if(isnum(e) && e < min)
min = e
return min
/proc/n_prob(var/chance)
return prob(chance)
/proc/n_randseed(var/seed)
rand_seed(seed)
return 0
/proc/n_rand(var/low, var/high)
if(isnull(low) && isnull(high))
return rand()
return rand(low, high)
// Number shit
/proc/n_num2str(var/num)
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)
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)
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)
proc/n_floor(var/num)
//writepanic("[__FILE__].[__LINE__] \\/proc/n_floor() called tick#: [world.time]")
if(isnum(num))
return round(num)
// Round up
/proc/n_ceil(var/num)
proc/n_ceil(var/num)
//writepanic("[__FILE__].[__LINE__] \\/proc/n_ceil() called tick#: [world.time]")
if(isnum(num))
return round(num)+1
// Round to nearest integer
/proc/n_round(var/num)
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 n_ceil(num)
return n_ceil(num)\
// Clamps N between min and max
/proc/n_clamp(var/num, var/min=-1, var/max=1)
if(isnum(num)&&isnum(min)&&isnum(max))
if(num<=min)
return min
if(num>=max)
return max
return num
// END OF BY DONKIE :(
// Returns 1 if N is inbetween Min and Max
/proc/n_inrange(var/num, var/min=-1, var/max=1)
if(isnum(num)&&isnum(min)&&isnum(max))
return ((min <= num) && (num <= max))
/proc/n_sin(var/const/x)
return sin(x)
// Returns the sine of num
/proc/n_sin(var/num)
if(isnum(num))
return sin(num)
/proc/n_cos(var/const/x)
return cos(x)
// Returns the cosine of num
/proc/n_cos(var/num)
if(isnum(num))
return cos(num)
/proc/n_asin(var/const/x)
return arcsin(x)
// Returns the arcsine of num
/proc/n_asin(var/num)
if(isnum(num)&&-1<=num&&num<=1)
return arcsin(num)
/proc/n_acos(var/const/x)
return arccos(x)
// Returns the arccosine of num
/proc/n_acos(var/num)
if(isnum(num)&&-1<=num&&num<=1)
return arccos(num)
// Returns the natural log of num
/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)
// Replace text
/proc/n_replace(text, find, replacement)
if(istext(text) && istext(find) && istext(replacement))
var/find_len = length(find)
if(find_len < 1) return text
. = ""
var/last_found = 1
var/count = 0
while(1)
count += 1
if(count > SCRIPT_MAX_REPLACEMENTS_ALLOWED)
break
var/found = findtext(text, find, last_found, 0)
. += copytext(text, last_found, found)
if(found)
. += replacement
last_found = found + find_len
continue
return
// --- Miscellaneous functions ---
/proc/n_time()
return world.timeofday
// Clone of sleep()
/proc/n_delay(var/time)
sleep(time)
if(isnum(num) && 0 < num)
return log(num)
+196 -162
View File
@@ -1,169 +1,203 @@
/proc/isobject(x)
return (istype(x, /datum) || istype(x, /list) || istype(x, /savefile) || istype(x, /client) || (x==world))
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))
/n_Interpreter
proc
Eval(node/expression/exp)
if(istype(exp, /node/expression/FunctionCall))
return RunFunction(exp)
else if(istype(exp, /node/expression/operator))
return EvalOperator(exp)
else if(istype(exp, /node/expression/value/literal))
var/node/expression/value/literal/lit=exp
return lit.value
else if(istype(exp, /node/expression/value/reference))
var/node/expression/value/reference/ref=exp
return ref.value
else if(istype(exp, /node/expression/value/variable))
var/node/expression/value/variable/v=exp
if(!v.object)
return Eval(GetVariable(v.id.id_name))
else
var/datum/D
if(istype(v.object, /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/runtimeError/UndefinedVariable("[v.object.ToString()].[v.id.id_name]"))
return null
return Eval(D.vars[v.id.id_name])
else if(istype(exp, /node/expression))
RaiseError(new/runtimeError/UnknownInstruction())
else
return exp
RaiseError(new/datum/runtimeError/UnknownInstruction())
EvalOperator(node/expression/operator/exp)
if(istype(exp, /node/expression/operator/binary))
var/node/expression/operator/binary/bin=exp
switch(bin.type)
if(/node/expression/operator/binary/Equal)
return Equal(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/NotEqual)
return NotEqual(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Greater)
return Greater(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Less)
return Less(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/GreaterOrEqual)
return GreaterOrEqual(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/LessOrEqual)
return LessOrEqual(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/LogicalAnd)
return LogicalAnd(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/LogicalOr)
return LogicalOr(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/LogicalXor)
return LogicalXor(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/BitwiseAnd)
return BitwiseAnd(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/BitwiseOr)
return BitwiseOr(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/BitwiseXor)
return BitwiseXor(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Add)
return Add(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Subtract)
return Subtract(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Multiply)
return Multiply(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Divide)
return Divide(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Power)
return Power(Eval(bin.exp), Eval(bin.exp2))
if(/node/expression/operator/binary/Modulo)
return Modulo(Eval(bin.exp), Eval(bin.exp2))
else
RaiseError(new/runtimeError/UnknownInstruction())
else
switch(exp.type)
if(/node/expression/operator/unary/Minus)
return Minus(Eval(exp.exp))
if(/node/expression/operator/unary/LogicalNot)
return LogicalNot(Eval(exp.exp))
if(/node/expression/operator/unary/BitwiseNot)
return BitwiseNot(Eval(exp.exp))
if(/node/expression/operator/unary/group)
return Eval(exp.exp)
else
RaiseError(new/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))
//Binary//
//Comparison operators
Equal(a, b) return a==b
NotEqual(a, b) return a!=b //LogicalNot(Equal(a, b))
Greater(a, b) return a>b
Less(a, b) return a<b
GreaterOrEqual(a, b)return a>=b
LessOrEqual(a, b) return a<=b
//Logical Operators
LogicalAnd(a, b) return a&&b
LogicalOr(a, b) return a||b
LogicalXor(a, b) return (a||b) && !(a&&b)
//Bitwise Operators
BitwiseAnd(a, b) return a&b
BitwiseOr(a, b) return a|b
BitwiseXor(a, b) return a^b
//Arithmetic Operators
Add(a, b)
if(istext(a)&&!istext(b)) b="[b]"
else if(istext(b)&&!istext(a)) a="[a]"
if(isobject(a) && !isobject(b))
RaiseError(new/runtimeError/TypeMismatch("+", a, b))
return null
else if(isobject(b) && !isobject(a))
RaiseError(new/runtimeError/TypeMismatch("+", a, b))
return null
return a+b
Subtract(a, b)
if(isobject(a) && !isobject(b))
RaiseError(new/runtimeError/TypeMismatch("-", a, b))
return null
else if(isobject(b) && !isobject(a))
RaiseError(new/runtimeError/TypeMismatch("-", a, b))
return null
return a-b
Divide(a, b)
if(isobject(a) && !isobject(b))
RaiseError(new/runtimeError/TypeMismatch("/", a, b))
return null
else if(isobject(b) && !isobject(a))
RaiseError(new/runtimeError/TypeMismatch("/", a, b))
return null
if(b==0 || b==null)
RaiseError(new/runtimeError/DivisionByZero())
return null
return a/b
Multiply(a, b)
if(isobject(a) && !isobject(b))
RaiseError(new/runtimeError/TypeMismatch("*", a, b))
return null
else if(isobject(b) && !isobject(a))
RaiseError(new/runtimeError/TypeMismatch("*", a, b))
return null
return a*b
Modulo(a, b)
if(isobject(a) && !isobject(b))
RaiseError(new/runtimeError/TypeMismatch("%", a, b))
return null
else if(isobject(b) && !isobject(a))
RaiseError(new/runtimeError/TypeMismatch("%", a, b))
return null
return a%b
Power(a, b)
if(isobject(a) && !isobject(b))
RaiseError(new/runtimeError/TypeMismatch("**", a, b))
return null
else if(isobject(b) && !isobject(a))
RaiseError(new/runtimeError/TypeMismatch("**", a, b))
return null
return a**b
if(/datum/node/expression/operator/unary/LogicalNot)
return LogicalNot(Eval(exp.exp))
//Unary//
Minus(a) return -a
LogicalNot(a) return !a
BitwiseNot(a) return ~a
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
+112 -97
View File
@@ -7,9 +7,6 @@
Procedures allowing for interaction with the script that is being run by the interpreter object.
*/
/n_Interpreter
proc
/*
Proc: Load
Loads a 'compiled' script into memory.
@@ -17,126 +14,144 @@
Parameters:
program - A <GlobalBlock> object which represents the script's global scope.
*/
Load(node/BlockDefinition/GlobalBlock/program)
ASSERT(program)
src.program = program
CreateGlobalScope()
/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.
Proc: Run
Runs the script.
*/
Run()
cur_recursion = 0 // reset recursion
cur_statements = 0 // reset CPU tracking
alertadmins = 0
/datum/n_Interpreter/proc/Run()
cur_recursion = 0 // reset recursion
cur_statements = 0 // reset CPU tracking
ASSERT(src.program)
RunBlock(src.program)
ASSERT(src.program)
RunBlock(src.program)
/*
Proc: SetVar
Defines a global variable for the duration of the next execution of a script.
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.
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()>
See Also:
- <Block.SetVar()>
*/
SetVar(name, value)
if(!istext(name))
//CRASH("Invalid variable name")
return
AssignVariable(name, value)
/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.
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.
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.
*/
SetProc(name, path, object=null, list/params=null)
if(!istext(name))
//CRASH("Invalid function name")
return
if(!object)
globalScope.functions[name] = path
else
var/node/statement/FunctionDefinition/S = new()
S.func_name = name
S.parameters = params
S.block = new()
S.block.SetVar("src", object)
var/node/expression/FunctionCall/C = new()
C.func_name = path
C.object = new("src")
for(var/p in params)
C.parameters += new/node/expression/value/variable(p)
var/node/statement/ReturnStatement/R=new()
R.value=C
S.block.statements += R
globalScope.functions[name] = S
/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.
Proc: VarExists
Checks whether a global variable with the specified name exists.
*/
VarExists(name)
return globalScope.variables.Find(name) //convert to 1/0 first?
/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.
Proc: ProcExists
Checks whether a global function with the specified name exists.
*/
ProcExists(name)
return globalScope.functions.Find(name)
/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.
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()>
See Also:
- <VarExists()>
*/
GetVar(name)
if(!VarExists(name))
//CRASH("No variable named '[name]'.")
return
var/x = globalScope.variables[name]
return Eval(x)
/datum/n_Interpreter/proc/GetVar(name)
if(!VarExists(name))
//CRASH("No variable named '[name]'.")
return
var/x = globalScope.variables[name]
return Eval(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()>
Proc: GetCleanVar
Returns the value of a global variable in the script and cleans it (sanitizes).
*/
CallProc(name, params[]=null)
if(!ProcExists(name))
//CRASH("No function named '[name]'.")
return
var/node/statement/FunctionDefinition/func = globalScope.functions[name]
if(istype(func))
var/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]'.")
/datum/n_Interpreter/proc/GetCleanVar(name, compare)
var/x = GetVar(name)
if(istext(x) && compare && x != compare) // Was changed
x = sanitize(x)
return x
/*
Event: HandleError
Called when the interpreter throws a runtime error.
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:
- <runtimeError>
See Also:
- <ProcExists()>
*/
HandleError(runtimeError/e)
/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)
+311 -247
View File
@@ -13,302 +13,366 @@
#define RETURNING 1
#define BREAKING 2
#define CONTINUING 4
/n_Interpreter
var
scope
curScope
globalScope
node
BlockDefinition/program
statement/FunctionDefinition/curFunction
stack
scopes = new()
functions = new()
datum/container // associated container for interpeter
/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>.
*/
status=0
returnVal
var/status = 0
var/returnVal
max_statements=1000 // maximum amount of statements that can be called in one execution. this is to prevent massive crashes and exploitation
cur_statements=0 // current amount of statements called
alertadmins=0 // set to 1 if the admins shouldn't be notified of anymore issues
max_iterations=100 // max number of uninterrupted loops possible
max_recursion=50 // max recursions without returning anything (or completing the code block)
cur_recursion=0 // current amount of recursion
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.
*/
persist=1
paused=0
var/persist = 1
var/paused = 0
/*
Constructor: New
Calls <Load()> with the given parameters.
*/
New(node/BlockDefinition/GlobalBlock/program=null)
.=..()
if(program)Load(program)
/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
/*
Proc: RaiseError
Raises a runtime error.
*/
RaiseError(runtimeError/e)
e.stack=functions.Copy()
e.stack.Push(curFunction)
src.HandleError(e)
/datum/n_Interpreter/proc/RaiseError(datum/runtimeError/e)
e.stack = functions.Copy()
e.stack.Push(curFunction)
src.HandleError(e)
CreateScope(node/BlockDefinition/B)
var/scope/S = new(B, curScope)
scopes.Push(curScope)
curScope = S
return S
/datum/n_Interpreter/proc/CreateScope(datum/node/BlockDefinition/B)
var/datum/scope/S = new(B, curScope)
scopes.Push(curScope)
curScope = S
return S
CreateGlobalScope()
scopes.Clear()
var/scope/S = new(program, null)
globalScope = S
return S
/datum/n_Interpreter/proc/CreateGlobalScope()
scopes.Clear()
var/datum/scope/S = new(program, null)
globalScope = S
return S
/*
Proc: RunBlock
Runs each statement in a block of code.
Proc: AlertAdmins
Alerts the admins of a script that is bad.
*/
RunBlock(node/BlockDefinition/Block, scope/scope = null)
var/is_global = istype(Block, /node/BlockDefinition/GlobalBlock)
if(!is_global)
if(scope)
curScope = scope
/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
CreateScope(Block)
else
if(!persist)
CreateGlobalScope()
curScope = globalScope
if(cur_statements < max_statements)
for(var/node/statement/S in Block.statements)
while(paused) sleep(10)
cur_statements++
if(cur_statements >= max_statements)
RaiseError(new/runtimeError/MaxCPU())
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)
break
if(istype(S, /node/statement/VariableAssignment))
var/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, /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/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, /node/statement/FunctionCall))
RunFunction(S)
else if(istype(S, /node/statement/FunctionDefinition))
//do nothing
else if(istype(S, /node/statement/WhileLoop))
RunWhile(S)
else if(istype(S, /node/statement/IfStatement))
RunIf(S)
else if(istype(S, /node/statement/ReturnStatement))
if(!curFunction)
RaiseError(new/runtimeError/UnexpectedReturn())
continue
status |= RETURNING
returnVal=Eval(S:value)
break
else if(istype(S, /node/statement/BreakStatement))
status |= BREAKING
break
else if(istype(S, /node/statement/ContinueStatement))
status |= CONTINUING
break
else
RaiseError(new/runtimeError/UnknownInstruction())
if(status)
break
curScope = scopes.Pop()
/*
Proc: RunFunction
Runs a function block or a proc with the arguments specified in the script.
*/
RunFunction(node/statement/FunctionCall/stmt)
//Note that anywhere /node/statement/FunctionCall/stmt is used so may /node/expression/FunctionCall
// If recursion gets too high (max 50 nested functions) throw an error
if(cur_recursion >= max_recursion)
RaiseError(new/runtimeError/RecursionLimitReached())
return 0
var/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/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/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/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/runtimeError/UndefinedFunction("[stmt.object.id_name].[stmt.func_name]"))
var/datum/D = Eval(GetVariable(stmt.object.id_name))
if(!D)
return
return call(def, stmt.func_name)(arglist(params))
else //def is a path to a global proc
return call(def)(arglist(params))
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())
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
// RaiseError(new/runtimeError/UnknownInstruction())
// 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.
Proc: RunIf
Checks a condition and runs either the if block or else block.
*/
RunIf(node/statement/IfStatement/stmt)
if(Eval(stmt.cond))
RunBlock(stmt.block)
else if(stmt.else_block)
RunBlock(stmt.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.
Proc: RunWhile
Runs a while loop.
*/
RunWhile(node/statement/WhileLoop/stmt)
var/i=1
while(Eval(stmt.cond) && Iterate(stmt.block, i++))
continue
status &= ~BREAKING
/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.
Proc:Iterate
Runs a single iteration of a loop. Returns a value indicating whether or not to continue looping.
*/
Iterate(node/BlockDefinition/block, count)
RunBlock(block)
if(max_iterations > 0 && count >= max_iterations)
RaiseError(new/runtimeError/IterationLimitReached())
return 0
if(status & (BREAKING|RETURNING))
return 0
status &= ~CONTINUING
return 1
/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>.
Proc: GetFunction
Finds a function in an accessible scope with the given name. Returns a <FunctionDefinition>.
*/
GetFunction(name)
var/scope/S = curScope
while(S)
if(S.functions.Find(name))
return S.functions[name]
S = S.parent
RaiseError(new/runtimeError/UndefinedFunction(name))
/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.
Proc: GetVariable
Finds a variable in an accessible scope and returns its value.
*/
GetVariable(name)
var/scope/S = curScope
while(S)
if(S.variables.Find(name))
return S.variables[name]
S = S.parent
RaiseError(new/runtimeError/UndefinedVariable(name))
GetVariableScope(name) //needed for when you reassign a variable in a higher scope
var/scope/S = curScope
while(S)
if(S.variables.Find(name))
return S
S = S.parent
/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
IsVariableAccessible(name)
var/scope/S = curScope
while(S)
if(S.variables.Find(name))
return TRUE
S = S.parent
return FALSE
/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.
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.
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.
*/
AssignVariable(name, node/expression/value, 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/node/expression/value/literal(value)
else if(!istype(value) && isobject(value)) value = new/node/expression/value/reference(value)
//TODO: check for invalid name
S.variables["[name]"] = value
/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
+11 -13
View File
@@ -2,17 +2,15 @@
Class: scope
A runtime instance of a block. Used internally by the interpreter.
*/
scope
var
scope/parent = null
node/BlockDefinition/block
list
functions
variables
/datum/scope
var/datum/scope/parent = null
var/datum/node/BlockDefinition/block
var/list/functions
var/list/variables
New(node/BlockDefinition/B, scope/parent)
src.block = B
src.parent = parent
src.variables = B.initial_variables.Copy()
src.functions = B.functions.Copy()
.=..()
/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()
. = ..()
+100 -61
View File
@@ -1,82 +1,121 @@
/*
File: Options
*/
var/const //Ascii values of characters
ascii_A =65
ascii_Z =90
ascii_a =97
ascii_z =122
ascii_DOLLAR = 36 // $
ascii_ZERO=48
ascii_NINE=57
ascii_UNDERSCORE=95 // _
// 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
*/
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/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)
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)
return (char in ascii_A to ascii_Z) || (char in ascii_a to ascii_z) || char == ascii_UNDERSCORE || char == ascii_DOLLAR
IsDigit(char)
if(!isnum(char))char=text2ascii(char)
return char in ascii_ZERO to ascii_NINE
/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)
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
if(lentext(id)==1) return 1
for(var/i=2 to lentext(id))
if(!IsValidIDChar(copytext(id, i, i+1)))
return 0
return 1
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
if(length(id) == 1)
return 1
for(var/i=2 to length(id))
if(!IsValidIDChar(copytext(id, i, i + 1)))
return 0
return 1
/*
Class: nS_Options
An implementation of <n_scriptOptions> for the n_Script language.
*/
nS_Options
var
list
symbols = list("(", ")", "\[", "]", ";", ",", "{", "}") //scanner - Characters that can be in symbols
/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: 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.
*/
keywords = list("if" = /n_Keyword/nS_Keyword/kwIf, "else" = /n_Keyword/nS_Keyword/kwElse, \
"while" = /n_Keyword/nS_Keyword/kwWhile, "break" = /n_Keyword/nS_Keyword/kwBreak, \
"continue" = /n_Keyword/nS_Keyword/kwContinue, \
"return" = /n_Keyword/nS_Keyword/kwReturn, "def" = /n_Keyword/nS_Keyword/kwDef)
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
)
list
assign_operators=list("=" = null, "&=" = "&",
"|=" = "|", "`=" = "`",
"+=" = "+", "-=" = "-",
"*=" = "*", "/=" = "/",
"^=" = "^",
"%=" = "%")
var/list/assign_operators = list(
"=" = null,
"&=" = "&",
"|=" = "|",
"`=" = "`",
"+=" = "+",
"-=" = "-",
"*=" = "*",
"/=" = "/",
"^=" = "^",
"%=" = "%"
)
unary_operators =list("!" = /node/expression/operator/unary/LogicalNot, "~" = /node/expression/operator/unary/BitwiseNot,
"-" = /node/expression/operator/unary/Minus)
var/list/unary_operators = list(
"!" = /datum/node/expression/operator/unary/LogicalNot,
"~" = /datum/node/expression/operator/unary/BitwiseNot,
"-" = /datum/node/expression/operator/unary/Minus
)
binary_operators=list("==" = /node/expression/operator/binary/Equal, "!=" = /node/expression/operator/binary/NotEqual,
">" = /node/expression/operator/binary/Greater, "<" = /node/expression/operator/binary/Less,
">=" = /node/expression/operator/binary/GreaterOrEqual,"<=" = /node/expression/operator/binary/LessOrEqual,
"&&" = /node/expression/operator/binary/LogicalAnd, "||" = /node/expression/operator/binary/LogicalOr,
"&" = /node/expression/operator/binary/BitwiseAnd, "|" = /node/expression/operator/binary/BitwiseOr,
"`" = /node/expression/operator/binary/BitwiseXor, "+" = /node/expression/operator/binary/Add,
"-" = /node/expression/operator/binary/Subtract, "*" = /node/expression/operator/binary/Multiply,
"/" = /node/expression/operator/binary/Divide, "^" = /node/expression/operator/binary/Power,
"%" = /node/expression/operator/binary/Modulo)
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
)
New()
.=..()
for(var/O in assign_operators+binary_operators+unary_operators)
if(!symbols.Find(O)) symbols+=O
/datum/n_scriptOptions/nS_Options/New()
. = ..()
for(var/O in assign_operators + binary_operators + unary_operators)
if(!symbols.Find(O))
symbols += O
+237 -180
View File
@@ -5,10 +5,10 @@
/*
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.
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
@@ -18,63 +18,71 @@
/*
Class: nS_Parser
*/
/n_Parser/nS_Parser
var
/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.
*/
expecting=VALUE
var/expecting = VALUE
proc
/*
Proc: Precedence
Compares two operators, decides which is higher in the order of operations, and returns <SHIFT> or <REDUCE>.
*/
Precedence(node/expression/operator/top, node/expression/operator/input)
if(istype(top))
top=top.precedence
if(istype(input))
input=input:precedence
if(top>=input)
return REDUCE
return SHIFT
/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.
*/
GetExpression(token/T)
if(!T) return
if(istype(T, /node/expression))
return T
switch(T.type)
if(/token/word)
return new/node/expression/value/variable(T.value)
if(/token/accessor)
var
token/accessor/A=T
node/expression/value/variable/E//=new(A.member)
stack/S=new()
while(istype(A.object, /token/accessor))
S.Push(A)
A=A.object
ASSERT(istext(A.object))
/datum/n_Parser/nS_Parser/proc/GetExpression(var/datum/token/T)
if(!T)
return
while(A)
var/node/expression/value/variable/V=new()
V.id=new(A.member)
if(E)
V.object=E
else
V.object=new/node/identifier(A.object)
E=V
A=S.Pop()
return E
if(istype(T, /datum/node/expression))
return T
if(/token/number, /token/string)
return new/node/expression/value/literal(T.value)
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
@@ -91,15 +99,26 @@
- <GetBinaryOperator()>
- <GetUnaryOperator()>
*/
GetOperator(O, type=/node/expression/operator, L[])
if(istype(O, type)) return O //O is already the desired type
if(istype(O, /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
/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
@@ -110,8 +129,8 @@
- <GetOperator()>
- <GetUnaryOperator()>
*/
GetBinaryOperator(O)
return GetOperator(O, /node/expression/operator/binary, options.binary_operators)
/datum/n_Parser/nS_Parser/proc/GetBinaryOperator(O)
return GetOperator(O, /datum/node/expression/operator/binary, options.binary_operators)
/*
Proc: GetUnaryOperator
@@ -122,30 +141,34 @@
- <GetOperator()>
- <GetBinaryOperator()>
*/
GetUnaryOperator(O)
return GetOperator(O, /node/expression/operator/unary, options.unary_operators)
/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.
*/
Reduce(stack/opr, stack/val)
var/node/expression/operator/O=opr.Pop()
if(!O) return
if(!istype(O))
errors+=new/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, /node/expression/operator/binary))
var/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)
/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
@@ -154,14 +177,14 @@
Parameters:
end - A list of values to compare the current token to.
*/
EndOfExpression(end[])
if(!curToken)
return 1
if(istype(curToken, /token/symbol) && end.Find(curToken.value))
return 1
if(istype(curToken, /token/end) && end.Find(/token/end))
return 1
return 0
/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
@@ -180,89 +203,114 @@
- <ParseParenExpression()>
- <ParseParamExpression()>
*/
ParseExpression(list/end=list(/token/end), list/ErrChars=list("{", "}"))
var/stack
opr=new
val=new
src.expecting=VALUE
for()
if(EndOfExpression(end))
break
if(istype(curToken, /token/symbol) && ErrChars.Find(curToken.value))
errors+=new/scriptError/BadToken(curToken)
break
/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(index>tokens.len) //End of File
errors+=new/scriptError/EndOfFile()
break
var/token/ntok
if(index+1<=tokens.len)
ntok=tokens[index+1]
if(EndOfExpression(end))
break
if(istype(curToken, /token/symbol) && curToken.value=="(") //Parse parentheses expression
if(expecting!=VALUE)
errors+=new/scriptError/ExpectedToken("operator", curToken)
NextToken()
continue
val.Push(ParseParenExpression())
else if(istype(curToken, /token/symbol)) //Operator found.
var/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/scriptError/ExpectedToken("operator", curToken)
NextToken()
continue
else
curOperator=GetUnaryOperator(curToken)
if(!curOperator) //given symbol isn't a unary operator
errors+=new/scriptError/ExpectedToken("expression", curToken)
NextToken()
continue
if(istype(curToken, /datum/token/symbol) && ErrChars.Find(curToken.value))
errors += new/datum/scriptError/BadToken(curToken)
break
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, /token/symbol)\
&& istype(curToken, /token/word)) //Parse function call
var/token/preToken=curToken
var/old_expect=src.expecting
var/fex=ParseFunctionExpression()
if(old_expect!=VALUE)
errors+=new/scriptError/ExpectedToken("operator", preToken)
NextToken()
continue
val.Push(fex)
else if(istype(curToken, /token/keyword)) //inline keywords
var/n_Keyword/kw=options.keywords[curToken.value]
kw=new kw(inline=1)
if(kw)
if(!kw.Parse(src))
return
else
errors+=new/scriptError/BadToken(curToken)
else if(istype(curToken, /token/end)) //semicolon found where it wasn't expected
errors+=new/scriptError/BadToken(curToken)
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
else
if(expecting!=VALUE)
errors+=new/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/node/N=val.Pop()
errors+=new/scriptError("Error parsing expression. Unexpected value left on stack: [N.ToString()].")
return null
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
@@ -271,25 +319,33 @@
See Also:
- <ParseExpression()>
*/
ParseFunctionExpression()
var/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>=1000)
CRASH("Something TERRIBLE has gone wrong in ParseFunctionExpression ;__;")
/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
if(istype(curToken, /token/symbol) && curToken.value==")")
return exp
exp.parameters+=ParseParamExpression()
if(curToken.value==","&&istype(curToken, /token/symbol))NextToken() //skip comma
if(istype(curToken, /token/end)) //Prevents infinite loop...
errors+=new/scriptError/ExpectedToken(")")
return exp
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
@@ -298,10 +354,10 @@
See Also:
- <ParseExpression()>
*/
ParseParenExpression()
if(!CheckToken("(", /token/symbol))
return
return new/node/expression/operator/unary/group(ParseExpression(list(")")))
/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
@@ -310,5 +366,6 @@
See Also:
- <ParseExpression()>
*/
ParseParamExpression()
return ParseExpression(list(",", ")"))
/datum/n_Parser/nS_Parser/proc/ParseParamExpression(var/check_functions = 0)
var/cf = check_functions
return ParseExpression(list(",", ")"), check_functions = cf)
+157 -119
View File
@@ -12,17 +12,17 @@ var/const/KW_WARN = 3 //Warning
var/const/Class: n_Keyword
var/const/Represents a special statement in the code triggered by a keyword.
*/
/n_Keyword
New(inline=0)
src.inline=inline
return ..()
/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.
@@ -31,8 +31,8 @@ var/const/Represents a special statement in the code triggered by a keyword.
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.
*/
proc/Parse(n_Parser/parser)
/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
@@ -42,125 +42,163 @@ var/const/Represents a special statement in the code triggered by a keyword.
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.
*/
//
nS_Keyword
New(inline=0)
if(inline)
del(src)
/datum/n_Keyword/nS_Keyword/New(var/inline = 0)
if(inline)
del src
kwReturn
Parse(n_Parser/nS_Parser/parser)
.=KW_PASS
if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
parser.errors+=new/scriptError/BadReturn(parser.curToken)
. = KW_WARN
var/node/statement/ReturnStatement/stmt=new
parser.NextToken() //skip 'return' token
stmt.value=parser.ParseExpression()
parser.curBlock.statements+=stmt
/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
kwIf
Parse(n_Parser/nS_Parser/parser)
.=KW_PASS
var/node/statement/IfStatement/stmt=new
parser.NextToken() //skip 'if' token
stmt.cond=parser.ParseParenExpression()
if(!parser.CheckToken(")", /token/symbol))
return KW_FAIL
if(!parser.CheckToken("{", /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)
var/datum/node/statement/ReturnStatement/stmt = new
parser.NextToken() //skip 'return' token
stmt.value = parser.ParseExpression()
parser.curBlock.statements += stmt
kwElse
Parse(n_Parser/nS_Parser/parser)
.=KW_PASS
var/list/L=parser.curBlock.statements
var/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
parser.errors+=new/scriptError/ExpectedToken("if statement",parser.curToken)
return KW_FAIL
parser.NextToken() //skip 'else' token
if(!parser.CheckToken("{", /token/symbol, skip=0))
return KW_ERR
stmt.else_block=new()
parser.AddBlock(stmt.else_block)
/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
kwWhile
Parse(n_Parser/nS_Parser/parser)
.=KW_PASS
var/node/statement/WhileLoop/stmt=new
parser.NextToken() //skip 'while' token
stmt.cond=parser.ParseParenExpression()
if(!parser.CheckToken(")", /token/symbol))
return KW_FAIL
if(!parser.CheckToken("{", /token/symbol, skip=0))
return KW_ERR
parser.curBlock.statements+=stmt
stmt.block=new
parser.AddBlock(stmt.block)
if(!parser.CheckToken("{", /datum/token/symbol, skip=0)) //Token needs to be preserved for parse loop, so skip=0
return KW_ERR
kwBreak
Parse(n_Parser/nS_Parser/parser)
.=KW_PASS
if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
parser.errors+=new/scriptError/BadToken(parser.curToken)
. = KW_WARN
var/node/statement/BreakStatement/stmt=new
parser.NextToken() //skip 'break' token
parser.curBlock.statements+=stmt
parser.curBlock.statements += stmt
stmt.block = new
parser.AddBlock(stmt.block)
kwContinue
Parse(n_Parser/nS_Parser/parser)
.=KW_PASS
if(istype(parser.curBlock, /node/BlockDefinition/GlobalBlock))
parser.errors+=new/scriptError/BadToken(parser.curToken)
. = KW_WARN
var/node/statement/ContinueStatement/stmt=new
parser.NextToken() //skip 'break' token
parser.curBlock.statements+=stmt
/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
kwDef
Parse(n_Parser/nS_Parser/parser)
.=KW_PASS
var/node/statement/FunctionDefinition/def=new
parser.NextToken() //skip 'def' token
if(!parser.options.IsValidID(parser.curToken.value))
parser.errors+=new/scriptError/InvalidID(parser.curToken)
return KW_FAIL
def.func_name=parser.curToken.value
parser.NextToken()
if(!parser.CheckToken("(", /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, /token/symbol))
switch(parser.curToken.value)
if(",")
parser.NextToken()
if(")")
break
else
parser.errors+=new/scriptError/BadToken(parser.curToken)
return KW_ERR
if(L && L.len)
ifstmt = L[L.len] //Get the last statement in the current block
else if(istype(parser.curToken, /token/word))
def.parameters+=parser.curToken.value
parser.NextToken()
else
parser.errors+=new/scriptError/InvalidID(parser.curToken)
return KW_ERR
if(!parser.CheckToken(")", /token/symbol))
return KW_FAIL
if(!ifstmt || !istype(ifstmt) || ifstmt.else_if)
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
if(istype(parser.curToken, /token/end)) //Function prototype
parser.curBlock.statements+=def
else if(parser.curToken.value=="{" && istype(parser.curToken, /token/symbol))
def.block = new
parser.curBlock.statements+=def
parser.curBlock.functions[def.func_name]=def
parser.AddBlock(def.block)
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
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/scriptError/BadToken(parser.curToken)
return KW_FAIL
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
+144 -128
View File
@@ -5,19 +5,17 @@
Class: n_Parser
An object that reads tokens and produces an AST (abstract syntax tree).
*/
/n_Parser
var
/datum/n_Parser
/*
Var: index
The parser's current position in the token's list.
*/
index = 1
list
var/index = 1
/*
Var: tokens
A list of tokens in the source code generated by a scanner.
*/
tokens = new
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.
@@ -25,48 +23,44 @@
See Also:
- <scriptError>
*/
errors = new
var/list/errors = new
/*
Var: warnings
A list of non-fatal problems in the script.
*/
warnings = new
token
var/list/warnings = new
/*
Var: curToken
The token at <index> in <tokens>.
*/
curToken
stack
blocks=new
node/BlockDefinition
GlobalBlock/global_block=new
curBlock
var/datum/token/curToken
var/datum/stack/blocks = new
var/datum/node/BlockDefinition/GlobalBlock/global_block = new
var/datum/node/BlockDefinition/GlobalBlock/curBlock
proc
/*
Proc: Parse
Reads the tokens and returns the AST's <GlobalBlock> node. Be sure to populate the tokens list before calling this procedure.
*/
Parse()
/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.
*/
NextToken()
if(index>=tokens.len)
curToken=null
else
curToken=tokens[++index]
return curToken
/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.
*/
/n_Parser/nS_Parser
var/n_scriptOptions/nS_Options/options
/datum/n_Parser/nS_Parser
var/datum/n_scriptOptions/nS_Options/options
/*
Constructor: New
@@ -74,116 +68,138 @@
tokens - A list of tokens to parse.
options - An object used for configuration.
*/
New(tokens[], n_scriptOptions/options)
src.tokens=tokens
src.options=options
curBlock=global_block
return ..()
/datum/n_Parser/nS_Parser/New(tokens[], var/datum/n_scriptOptions/options)
src.tokens = tokens
src.options = options
curBlock = global_block
return ..()
Parse()
ASSERT(tokens)
for(,src.index<=src.tokens.len, src.index++)
curToken=tokens[index]
switch(curToken.type)
if(/token/keyword)
var/n_Keyword/kw=options.keywords[curToken.value]
kw=new kw()
if(kw)
if(!kw.Parse(src))
return
if(/token/word)
var/token/ntok
if(index+1>tokens.len)
errors+=new/scriptError/BadToken(curToken)
continue
ntok=tokens[index+1]
if(!istype(ntok, /token/symbol))
errors+=new/scriptError/BadToken(ntok)
continue
if(ntok.value=="(")
ParseFunctionStatement()
else if(options.assign_operators.Find(ntok.value))
ParseAssignment()
else
errors+=new/scriptError/BadToken(ntok)
continue
if(!istype(curToken, /token/end))
errors+=new/scriptError/ExpectedToken(";", curToken)
continue
if(/token/symbol)
if(curToken.value=="}")
if(!EndBlock())
errors+=new/scriptError/BadToken(curToken)
continue
else
errors+=new/scriptError/BadToken(curToken)
continue
if(/token/end)
warnings+=new/scriptError/BadToken(curToken)
/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/scriptError/BadToken(curToken)
return
return global_block
errors += new/datum/scriptError/BadToken(ntok)
continue
proc
CheckToken(val, type, err=1, skip=1)
if(curToken.value!=val || !istype(curToken,type))
if(err)
errors+=new/scriptError/ExpectedToken(val, curToken)
return 0
if(skip)NextToken()
return 1
if(!istype(curToken, /datum/token/end))
errors += new/datum/scriptError/ExpectedToken(";", curToken)
continue
AddBlock(node/BlockDefinition/B)
blocks.Push(curBlock)
curBlock=B
if(/datum/token/symbol)
if(curToken.value == "}")
if(!EndBlock())
errors += new/datum/scriptError/BadToken(curToken)
continue
EndBlock()
if(curBlock==global_block) return 0
curBlock=blocks.Pop()
return 1
else
errors += new/datum/scriptError/BadToken(curToken)
continue
if(/datum/token/end)
warnings += new/datum/scriptError/BadToken(curToken)
continue
ParseAssignment()
var/name=curToken.value
if(!options.IsValidID(name))
errors+=new/scriptError/InvalidID(curToken)
return
NextToken()
var/t=options.binary_operators[options.assign_operators[curToken.value]]
var/node/statement/VariableAssignment/stmt=new()
stmt.var_name=new(name)
NextToken()
if(t)
stmt.value=new t()
stmt.value:exp=new/node/expression/value/variable(stmt.var_name)
stmt.value:exp2=ParseExpression()
else
stmt.value=ParseExpression()
curBlock.statements+=stmt
ParseFunctionStatement()
if(!istype(curToken, /token/word))
errors+=new/scriptError("Bad identifier in function call.")
errors += new/datum/scriptError/BadToken(curToken)
return
var/node/statement/FunctionCall/stmt=new
stmt.func_name=curToken.value
NextToken() //skip function name
if(!CheckToken("(", /token/symbol)) //Check for and skip open parenthesis
return
var/loops = 0
for()
loops++
if(loops>=6000)
CRASH("Something TERRIBLE has gone wrong in ParseFunctionStatement ;__;")
if(!curToken)
errors+=new/scriptError/EndOfFile()
return
if(istype(curToken, /token/symbol) && curToken.value==")")
curBlock.statements+=stmt
NextToken() //Skip close parenthesis
return
var/node/expression/P=ParseParamExpression()
stmt.parameters+=P
if(istype(curToken, /token/symbol) && curToken.value==",") NextToken()
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()
+166 -163
View File
@@ -5,10 +5,8 @@
Class: n_Scanner
An object responsible for breaking up source code into tokens for use by the parser.
*/
/n_Scanner
var
code
list
/datum/n_Scanner
var/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.
@@ -16,54 +14,50 @@
See Also:
- <scriptError>
*/
errors = new
var/list/errors = new
/*
Var: warnings
A list of non-fatal problems in the source code found by the scanner.
*/
warnings = new
var/list/warnings = new
proc
/*
Proc: LoadCode
Loads source code.
*/
LoadCode(c)
code=c
/datum/n_Scanner/proc/LoadCode(var/c)
code=c
/*
Proc: LoadCodeFromFile
Gets the code from a file and calls <LoadCode()>.
*/
LoadCodeFromFile(f)
LoadCode(file2text(f))
/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.
*/
Scan()
/datum/n_Scanner/proc/Scan()
/*
Class: nS_Scanner
A scanner implementation for n_Script.
*/
/n_Scanner/nS_Scanner
var
/datum/n_Scanner/nS_Scanner
/*
Variable: codepos
The scanner's position in the source code.
*/
codepos = 1
line = 1
linepos = 0 //column=codepos-linepos
n_scriptOptions/nS_Options/options
var/codepos = 1
var/line = 1
var/linepos = 0 //column=codepos-linepos
var/datum/n_scriptOptions/nS_Options/options
commenting = 0
var/commenting = 0
// 1: single-line
// 2: multi-line
list
/*
Variable: ignore
A list of characters that are ignored by the scanner.
@@ -71,7 +65,7 @@
Default Value:
Whitespace
*/
ignore = list(" ", "\t", "\n") //Don't add tokens for 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.
@@ -79,7 +73,7 @@
Default Value:
Semicolon
*/
end_stmt = list(";")
var/list/end_stmt = list(";")
/*
Variable: string_delim
A list of characters that can start and end strings.
@@ -87,12 +81,12 @@
Default Value:
Double and single quotes.
*/
string_delim = list("\"", "'")
var/list/string_delim = list("\"", "'")
/*
Variable: delim
A list of characters that denote the start of a new token. This list is automatically populated.
*/
delim = new
var/list/delim = new
/*
Macro: COL
@@ -106,46 +100,52 @@
code - The source code to tokenize.
options - An <nS_Options> object used to configure the scanner.
*/
New(code, n_scriptOptions/nS_Options/options)
.=..()
ignore+= ascii2text(13) //Carriage return
delim += ignore + options.symbols + end_stmt + string_delim
src.options=options
LoadCode(code)
/datum/n_Scanner/nS_Scanner/New(var/code, var/datum/n_scriptOptions/nS_Options/options)
. = ..()
ignore += ascii2text(13) //Carriage return
delim += ignore + options.symbols + end_stmt + string_delim
src.options = options
LoadCode(code)
Scan() //Creates a list of tokens from source code
var/list/tokens=new
for(, src.codepos<=lentext(code), src.codepos++)
/datum/n_Scanner/nS_Scanner/Scan() //Creates a list of tokens from source code
var/list/tokens = new
for(, src.codepos <= length(code), src.codepos++)
var/char=copytext(code, codepos, codepos+1)
var/nextchar=copytext(code, codepos+1, codepos+2)
if(char=="\n")
line++
linepos=codepos
var/char = copytext(code, codepos, codepos + 1)
var/nextchar = copytext(code, codepos + 1, codepos + 2)
if(char == "\n")
line++
linepos = codepos
if(ignore.Find(char))
continue
else if(char == "/" && (nextchar == "/" || nextchar == "*"))
ReadComment()
else if(end_stmt.Find(char))
tokens+=new /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()
if(ignore.Find(char))
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()
codepos=initial(codepos)
line=initial(line)
linepos=initial(linepos)
return tokens
codepos = initial(codepos)
line = initial(line)
linepos = initial(linepos)
return tokens
proc
/*
Proc: ReadString
Reads a string in the source code into a token.
@@ -153,136 +153,139 @@
Parameters:
start - The character used to start the string.
*/
ReadString(start)
var
buf
for(, codepos <= lentext(code), codepos++)//codepos to lentext(code))
var/char=copytext(code, codepos, codepos+1)
/datum/n_Scanner/nS_Scanner/proc/ReadString(start)
var/buf
for(, codepos <= length(code), codepos++)//codepos to length(code))
var/char = copytext(code, codepos, codepos + 1)
switch(char)
if("\\") //Backslash (\) encountered in string
codepos++ //Skip next character in string, since it was escaped by a backslash
char = copytext(code, codepos, codepos+1)
switch(char)
if("\\") //Backslash (\) encountered in string
codepos++ //Skip next character in string, since it was escaped by a backslash
char=copytext(code, codepos, codepos+1)
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/token/string(buf, line, COL)
errors+=new/scriptError("Unterminated string. Newline reached.", .)
line++
linepos=codepos
break
if("\\") //Double backslash
buf += "\\"
if("n") //\n Newline
buf += "\n"
else
if(char==start) //string delimiter found, end string
break
else
buf+=char //Just a normal character in a string
if(!.) return new/token/string(buf, line, COL)
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.
*/
ReadWord()
var
char=copytext(code, codepos, codepos+1)
buf
while(!delim.Find(char) && codepos<=lentext(code))
buf+=char
char=copytext(code, ++codepos, codepos+1)
codepos-- //allow main Scan() proc to read the delimiter
if(options.keywords.Find(buf))
return new /token/keyword(buf, line, COL)
else
return new /token/word(buf, line, COL)
/datum/n_Scanner/nS_Scanner/proc/ReadWord()
var/char = copytext(code, codepos, codepos + 1)
var/buf
while(!delim.Find(char) && codepos <= length(code))
buf += char
char = copytext(code, ++codepos, codepos + 1)
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.
*/
ReadSymbol()
var
char=copytext(code, codepos, codepos+1)
buf
/datum/n_Scanner/nS_Scanner/proc/ReadSymbol()
var/char=copytext(code, codepos, codepos + 1)
var/buf
while(options.symbols.Find(buf+char))
buf+=char
if(++codepos>lentext(code)) break
char=copytext(code, codepos, codepos+1)
while(options.symbols.Find(buf + char))
buf += char
if(++codepos > length(code)) break
char = copytext(code, codepos, codepos + 1)
codepos-- //allow main Scan() proc to read the next character
return new /token/symbol(buf, line, COL)
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.
*/
ReadNumber()
var
char=copytext(code, codepos, codepos+1)
buf
dec=0
/datum/n_Scanner/nS_Scanner/proc/ReadNumber()
var/char = copytext(code, codepos, codepos + 1)
var/buf
var/dec = 0
while(options.IsDigit(char) || (char=="." && !dec))
if(char==".") dec=1
buf+=char
codepos++
char=copytext(code, codepos, codepos+1)
var/token/number/T=new(buf, line, COL)
if(isnull(text2num(buf)))
errors+=new/scriptError("Bad number: ", T)
T.value=0
codepos-- //allow main Scan() proc to read the next character
return T
while(options.IsDigit(char) || (char == "." && !dec))
if(char == ".")
dec = 1
buf += char
codepos++
char = copytext(code, codepos, codepos + 1)
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
*/
ReadComment()
var
char=copytext(code, codepos, codepos+1)
nextchar=copytext(code, codepos+1, codepos+2)
charstring = char+nextchar
comm = 1
// 1: single-line comment
// 2: multi-line comment
/datum/n_Scanner/nS_Scanner/proc/ReadComment()
var/char = copytext(code, codepos, codepos + 1)
var/nextchar = copytext(code, codepos + 1, codepos + 2)
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 > length(code))
break
if(expectedend) // ending statement expected...
char = copytext(code, codepos, codepos + 1)
if(char == "/") // ending statement found - beak the comment
comm = 0
break
if(comm == 2)
// multi-line comments are broken by ending statements
char = copytext(code, codepos, codepos + 1)
if(char == "*")
expectedend = 1
continue
else
char = copytext(code, codepos, codepos + 1)
if(char == "\n")
comm = 0
break
if(expectedend)
expectedend = 0
if(charstring == "//" || charstring == "/*")
if(charstring == "/*")
comm = 2 // starts a multi-line comment
while(comm)
if(++codepos>lentext(code)) break
if(expectedend) // ending statement expected...
char = copytext(code, codepos, codepos+1)
if(char == "/") // ending statement found - beak the comment
comm = 0
break
if(comm == 2)
// multi-line comments are broken by ending statements
char = copytext(code, codepos, codepos+1)
if(char == "*")
expectedend = 1
continue
else
char = copytext(code, codepos, codepos+1)
if(char == "\n")
comm = 0
break
if(expectedend) expectedend = 0
if(comm == 2)
errors+=new/scriptError/UnterminatedComment()
if(comm == 2)
errors += new/datum/scriptError/UnterminatedComment()
+29 -27
View File
@@ -1,38 +1,40 @@
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33
/*
Class: Token
Represents an entity and position in the source code.
*/
/token
/datum/token
var/value
var/line
var/column
New(v, l=0, c=0)
value=v
line=l
column=c
/datum/token/New(v, l = 0, c = 0)
value = v
line = l
column = c
string
symbol
word
keyword
number
New()
.=..()
if(!isnum(value))
value=text2num(value)
ASSERT(!isnull(value))
accessor
var/object
var/member
/datum/token/string
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/symbol
end
/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
+22 -19
View File
@@ -1,23 +1,26 @@
/stack
var/list
contents=new
proc
Push(value)
contents+=value
/datum/stack
var/list/contents = list()
Pop()
if(!contents.len) return null
. = contents[contents.len]
contents.len--
/datum/stack/proc/Push(value)
contents += value
Top() //returns the item on the top of the stack without removing it
if(!contents.len) return null
return contents[contents.len]
/datum/stack/proc/Pop()
if(!contents.len)
return null
Copy()
var/stack/S=new()
S.contents=src.contents.Copy()
return S
. = contents[contents.len]
contents.len--
Clear()
contents.Cut()
/datum/stack/proc/Top() //returns the item on the top of the stack without removing it
if(!contents.len)
return null
return contents[contents.len]
/datum/stack/proc/Copy()
var/datum/stack/S = new()
S.contents = src.contents.Copy()
return S
/datum/stack/proc/Clear()
contents.Cut()