mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-10 10:21:11 +00:00
More work done on Telecomms:
▫ Signals can now be rejected by Subspace broadcasters through a specific data[] parameter. ▫ Improved the log browser. ▫ Log browsers and telecommunication monitors no longer require access to use. You do need access to delete logs, however. ▫ Intercoms need power to work. They don't drain power, they just need a constant flow of equipment power. As such, that offline intercom sprite's now finally being put to use. Scripting language: ▫ Sorry about all the files; they're all necessary! It's important to notice that the basic structure of the scripting language code is not mine; I cannibalized the base structure from some obscure BYOND project. It's pretty well documented, and I'd say easier to browse through than atmos. Here's the basic deal: A compiler datum manages the relationships between the three main subsystems of a scripting language: the Scanner, the Parser, and the Interpreter. The Scanner splits raw text into token datums that the Parser can read. The Parser transforms the otherwise random bits and strings into ordered AST Trees and nodes for the Interpreter to read. The interpreter actually executes the code and handles scope/functions/code blocks. git-svn-id: http://tgstation13.googlecode.com/svn/trunk@3193 316c924e-a436-60f5-8080-3fe189b3f50e
This commit is contained in:
committed by
SkyMarshal
parent
02002054de
commit
bbe42a34d8
139
code/modules/scripting/AST/AST Nodes.dm
Normal file
139
code/modules/scripting/AST/AST Nodes.dm
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
File: AST Nodes
|
||||
An abstract syntax tree (AST) is a representation of source code in a computer-friendly format. It is composed of nodes,
|
||||
each of which represents a certain part of the source code. For example, an <IfStatement> node represents an if statement in the
|
||||
script's source code. Because it is a representation of the source code in memory, it is independent of any specific scripting language.
|
||||
This allows a script in any language for which a parser exists to be run by the interpreter.
|
||||
|
||||
The AST is produced by an <n_Parser> object. It consists of a <GlobalBlock> with an arbitrary amount of statements. These statements are
|
||||
run in order by an <n_Interpreter> object. A statement may in turn run another block (such as an if statement might if its condition is
|
||||
met).
|
||||
|
||||
Articles:
|
||||
- <http://en.wikipedia.org/wiki/Abstract_syntax_tree>
|
||||
*/
|
||||
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_ADD - Addition and subtraction
|
||||
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 //()
|
||||
|
||||
/*
|
||||
Class: node
|
||||
*/
|
||||
/node
|
||||
proc
|
||||
ToString()
|
||||
return "[src.type]"
|
||||
/*
|
||||
Class: identifier
|
||||
*/
|
||||
/node/identifier
|
||||
var
|
||||
id_name
|
||||
|
||||
New(id)
|
||||
.=..()
|
||||
src.id_name=id
|
||||
|
||||
ToString()
|
||||
return id_name
|
||||
|
||||
/*
|
||||
Class: expression
|
||||
*/
|
||||
/node/expression
|
||||
/*
|
||||
Class: operator
|
||||
See <Binary Operators> and <Unary Operators> for subtypes.
|
||||
*/
|
||||
/node/expression/operator
|
||||
var
|
||||
node/expression/exp
|
||||
tmp
|
||||
name
|
||||
precedence
|
||||
|
||||
New()
|
||||
.=..()
|
||||
if(!src.name) src.name="[src.type]"
|
||||
|
||||
ToString()
|
||||
return "operator: [name]"
|
||||
|
||||
/*
|
||||
Class: FunctionCall
|
||||
*/
|
||||
/node/expression/FunctionCall
|
||||
//Function calls can also be expressions or statements.
|
||||
var
|
||||
func_name
|
||||
node/identifier/object
|
||||
list/parameters=new
|
||||
|
||||
/*
|
||||
Class: literal
|
||||
*/
|
||||
/node/expression/value/literal
|
||||
var
|
||||
value
|
||||
|
||||
New(value)
|
||||
.=..()
|
||||
src.value=value
|
||||
|
||||
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
|
||||
|
||||
|
||||
New(ident)
|
||||
.=..()
|
||||
id=ident
|
||||
if(istext(id))id=new(id)
|
||||
|
||||
ToString()
|
||||
return src.id.ToString()
|
||||
|
||||
/*
|
||||
Class: reference
|
||||
*/
|
||||
/node/expression/value/reference
|
||||
var
|
||||
datum/value
|
||||
|
||||
New(value)
|
||||
.=..()
|
||||
src.value=value
|
||||
|
||||
ToString()
|
||||
return "ref: [src.value] ([src.value.type])"
|
||||
48
code/modules/scripting/AST/Blocks.dm
Normal file
48
code/modules/scripting/AST/Blocks.dm
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
File: Block Types
|
||||
*/
|
||||
/*
|
||||
Class: BlockDefinition
|
||||
An object representing a set of actions to perform independently from the rest of the script. Blocks are basically just
|
||||
lists of statements to execute which also contain some local variables and methods. Note that since functions are local to a block,
|
||||
it is possible to have a function definition inside of any type of block (such as in an if statement or another function),
|
||||
and not just in the global scope as in many languages.
|
||||
*/
|
||||
/node/BlockDefinition
|
||||
var/list
|
||||
statements = new
|
||||
functions = new
|
||||
initial_variables = new
|
||||
|
||||
proc
|
||||
/*
|
||||
Proc: SetVar
|
||||
Defines a permanent variable. The variable will not be deleted when it goes out of scope.
|
||||
|
||||
Notes:
|
||||
Since all pre-existing temporary variables are deleted, it is not generally desirable to use this proc after the interpreter has been instantiated.
|
||||
Instead, use <n_Interpreter.SetVar()>.
|
||||
|
||||
See Also:
|
||||
- <n_Interpreter.SetVar()>
|
||||
*/
|
||||
SetVar(name, value)
|
||||
initial_variables[name]=value
|
||||
|
||||
|
||||
/*
|
||||
Class: GlobalBlock
|
||||
A block object representing the global scope.
|
||||
*/
|
||||
//
|
||||
GlobalBlock
|
||||
New()
|
||||
initial_variables["null"]=null
|
||||
return ..()
|
||||
|
||||
/*
|
||||
Class: FunctionBlock
|
||||
A block representing a function body.
|
||||
*/
|
||||
//
|
||||
FunctionBlock
|
||||
173
code/modules/scripting/AST/Operators/Binary Operators.dm
Normal file
173
code/modules/scripting/AST/Operators/Binary Operators.dm
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
File: Binary Operators
|
||||
*/
|
||||
/*
|
||||
Class: binary
|
||||
Represents a binary operator in the AST. A binary operator takes two operands (ie x and y) and returns a value.
|
||||
*/
|
||||
/node/expression/operator/binary
|
||||
var
|
||||
node/expression/exp2
|
||||
|
||||
////////// Comparison Operators //////////
|
||||
/*
|
||||
Class: Equal
|
||||
Returns true if x = y.
|
||||
*/
|
||||
//
|
||||
Equal
|
||||
precedence=OOP_EQUAL
|
||||
|
||||
/*
|
||||
Class: NotEqual
|
||||
Returns true if x and y aren't equal.
|
||||
*/
|
||||
//
|
||||
NotEqual
|
||||
precedence=OOP_EQUAL
|
||||
|
||||
/*
|
||||
Class: Greater
|
||||
Returns true if x > y.
|
||||
*/
|
||||
//
|
||||
Greater
|
||||
precedence=OOP_COMPARE
|
||||
|
||||
/*
|
||||
Class: Less
|
||||
Returns true if x < y.
|
||||
*/
|
||||
//
|
||||
Less
|
||||
precedence=OOP_COMPARE
|
||||
|
||||
/*
|
||||
Class: GreaterOrEqual
|
||||
Returns true if x >= y.
|
||||
*/
|
||||
//
|
||||
GreaterOrEqual
|
||||
precedence=OOP_COMPARE
|
||||
|
||||
/*
|
||||
Class: LessOrEqual
|
||||
Returns true if x <= y.
|
||||
*/
|
||||
//
|
||||
LessOrEqual
|
||||
precedence=OOP_COMPARE
|
||||
|
||||
|
||||
////////// Logical Operators //////////
|
||||
|
||||
/*
|
||||
Class: LogicalAnd
|
||||
Returns true if x and y are true.
|
||||
*/
|
||||
//
|
||||
LogicalAnd
|
||||
precedence=OOP_AND
|
||||
|
||||
/*
|
||||
Class: LogicalOr
|
||||
Returns true if x, y, or both are true.
|
||||
*/
|
||||
//
|
||||
LogicalOr
|
||||
precedence=OOP_OR
|
||||
|
||||
/*
|
||||
Class: LogicalXor
|
||||
Returns true if either x or y but not both are true.
|
||||
*/
|
||||
//
|
||||
LogicalXor //Not implemented in nS
|
||||
precedence=OOP_OR
|
||||
|
||||
|
||||
////////// Bitwise Operators //////////
|
||||
|
||||
/*
|
||||
Class: BitwiseAnd
|
||||
Performs a bitwise and operation.
|
||||
|
||||
Example:
|
||||
011 & 110 = 010
|
||||
*/
|
||||
//
|
||||
BitwiseAnd
|
||||
precedence=OOP_BIT
|
||||
|
||||
/*
|
||||
Class: BitwiseOr
|
||||
Performs a bitwise or operation.
|
||||
|
||||
Example:
|
||||
011 | 110 = 111
|
||||
*/
|
||||
//
|
||||
BitwiseOr
|
||||
precedence=OOP_BIT
|
||||
|
||||
/*
|
||||
Class: BitwiseXor
|
||||
Performs a bitwise exclusive or operation.
|
||||
|
||||
Example:
|
||||
011 xor 110 = 101
|
||||
*/
|
||||
//
|
||||
BitwiseXor
|
||||
precedence=OOP_BIT
|
||||
|
||||
|
||||
////////// Arithmetic Operators //////////
|
||||
|
||||
/*
|
||||
Class: Add
|
||||
Returns the sum of x and y.
|
||||
*/
|
||||
//
|
||||
Add
|
||||
precedence=OOP_ADD
|
||||
|
||||
/*
|
||||
Class: Subtract
|
||||
Returns the difference of x and y.
|
||||
*/
|
||||
//
|
||||
Subtract
|
||||
precedence=OOP_ADD
|
||||
|
||||
/*
|
||||
Class: Multiply
|
||||
Returns the product of x and y.
|
||||
*/
|
||||
//
|
||||
Multiply
|
||||
precedence=OOP_MULTIPLY
|
||||
|
||||
/*
|
||||
Class: Divide
|
||||
Returns the quotient of x and y.
|
||||
*/
|
||||
//
|
||||
Divide
|
||||
precedence=OOP_MULTIPLY
|
||||
|
||||
/*
|
||||
Class: Power
|
||||
Returns x raised to the power of y.
|
||||
*/
|
||||
//
|
||||
Power
|
||||
precedence=OOP_POW
|
||||
|
||||
/*
|
||||
Class: Modulo
|
||||
Returns the remainder of x / y.
|
||||
*/
|
||||
//
|
||||
Modulo
|
||||
precedence=OOP_MULTIPLY
|
||||
51
code/modules/scripting/AST/Operators/Unary Operators.dm
Normal file
51
code/modules/scripting/AST/Operators/Unary Operators.dm
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
File: Unary Operators
|
||||
*/
|
||||
/*
|
||||
Class: unary
|
||||
Represents a unary operator in the AST. Unary operators take a single operand (referred to as x below) and return a value.
|
||||
*/
|
||||
/node/expression/operator/unary
|
||||
precedence=OOP_UNARY
|
||||
|
||||
/*
|
||||
Class: LogicalNot
|
||||
Returns !x.
|
||||
|
||||
Example:
|
||||
!true = false and !false = true
|
||||
*/
|
||||
//
|
||||
LogicalNot
|
||||
name="logical not"
|
||||
|
||||
/*
|
||||
Class: BitwiseNot
|
||||
Returns the value of a bitwise not operation performed on x.
|
||||
|
||||
Example:
|
||||
~10 (decimal 2) = 01 (decimal 1).
|
||||
*/
|
||||
//
|
||||
BitwiseNot
|
||||
name="bitwise not"
|
||||
|
||||
/*
|
||||
Class: Minus
|
||||
Returns -x.
|
||||
*/
|
||||
//
|
||||
Minus
|
||||
name="minus"
|
||||
|
||||
/*
|
||||
Class: group
|
||||
A special unary operator representing a value in parentheses.
|
||||
*/
|
||||
//
|
||||
group
|
||||
precedence=OOP_GROUP
|
||||
|
||||
New(node/expression/exp)
|
||||
src.exp=exp
|
||||
return ..()
|
||||
122
code/modules/scripting/AST/Statements.dm
Normal file
122
code/modules/scripting/AST/Statements.dm
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
File: Statement Types
|
||||
*/
|
||||
/*
|
||||
Class: statement
|
||||
An object representing a single instruction run by an interpreter.
|
||||
*/
|
||||
/node/statement
|
||||
/*
|
||||
Class: FunctionCall
|
||||
Represents a call to a function.
|
||||
*/
|
||||
//
|
||||
FunctionCall
|
||||
var
|
||||
func_name
|
||||
node/identifier/object
|
||||
list/parameters=new
|
||||
|
||||
/*
|
||||
Class: FunctionDefinition
|
||||
Defines a function.
|
||||
*/
|
||||
//
|
||||
FunctionDefinition
|
||||
var
|
||||
func_name
|
||||
list/parameters=new
|
||||
node/BlockDefinition/FunctionBlock/block
|
||||
|
||||
/*
|
||||
Class: VariableAssignment
|
||||
Sets a variable in an accessible scope to the given value if one exists, otherwise initializes a new local variable to the given value.
|
||||
|
||||
Notes:
|
||||
If a variable with the same name exists in a higher block, the value will be assigned to it. If not,
|
||||
a new variable is created in the current block. To force creation of a new variable, use <VariableDeclaration>.
|
||||
|
||||
See Also:
|
||||
- <VariableDeclaration>
|
||||
*/
|
||||
//
|
||||
VariableAssignment
|
||||
var
|
||||
node
|
||||
identifier
|
||||
object
|
||||
var_name
|
||||
expression/value
|
||||
|
||||
/*
|
||||
Class: VariableDeclaration
|
||||
Intializes a local variable to a null value.
|
||||
|
||||
See Also:
|
||||
- <VariableAssignment>
|
||||
*/
|
||||
//
|
||||
VariableDeclaration
|
||||
var
|
||||
node
|
||||
identifier
|
||||
object
|
||||
var_name
|
||||
|
||||
/*
|
||||
Class: IfStatement
|
||||
*/
|
||||
//
|
||||
IfStatement
|
||||
var
|
||||
node
|
||||
BlockDefinition
|
||||
block
|
||||
else_block //may be null
|
||||
expression/cond
|
||||
|
||||
/*
|
||||
Class: WhileLoop
|
||||
Loops while a given condition is true.
|
||||
*/
|
||||
//
|
||||
WhileLoop
|
||||
var
|
||||
node
|
||||
BlockDefinition/block
|
||||
expression/cond
|
||||
|
||||
/*
|
||||
Class: ForLoop
|
||||
Loops while test is true, initializing a variable, increasing the variable
|
||||
*/
|
||||
ForLoop
|
||||
var
|
||||
node
|
||||
BlockDefinition/block
|
||||
expression/test
|
||||
expression/init
|
||||
expression/increment
|
||||
|
||||
/*
|
||||
Class: BreakStatement
|
||||
Ends a loop.
|
||||
*/
|
||||
//
|
||||
BreakStatement
|
||||
|
||||
/*
|
||||
Class: ContinueStatement
|
||||
Skips to the next iteration of a loop.
|
||||
*/
|
||||
//
|
||||
ContinueStatement
|
||||
|
||||
/*
|
||||
Class: ReturnStatement
|
||||
Ends the function and returns a value.
|
||||
*/
|
||||
//
|
||||
ReturnStatement
|
||||
var
|
||||
node/expression/value
|
||||
124
code/modules/scripting/Errors.dm
Normal file
124
code/modules/scripting/Errors.dm
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
File: Errors
|
||||
*/
|
||||
/*
|
||||
Class: scriptError
|
||||
An error scanning or parsing the source code.
|
||||
*/
|
||||
/scriptError
|
||||
var
|
||||
/*
|
||||
Var: message
|
||||
A message describing the problem.
|
||||
*/
|
||||
message
|
||||
New(msg=null)
|
||||
if(msg)message=msg
|
||||
|
||||
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]"
|
||||
|
||||
InvalidID
|
||||
parent_type=/scriptError/BadToken
|
||||
message="Invalid identifier name: "
|
||||
|
||||
ReservedWord
|
||||
parent_type=/scriptError/BadToken
|
||||
message="Identifer using reserved word: "
|
||||
|
||||
BadNumber
|
||||
parent_type=/scriptError/BadToken
|
||||
message = "Bad number: "
|
||||
|
||||
BadReturn
|
||||
var/token/token
|
||||
message = "Unexpected return statement outside of a function."
|
||||
New(token/t)
|
||||
src.token=t
|
||||
|
||||
EndOfFile
|
||||
message = "Unexpected end of file."
|
||||
|
||||
ExpectedToken
|
||||
message="Expected: '"
|
||||
New(id, token/T)
|
||||
if(T && T.line) message="[T.line]: [message]"
|
||||
message+="[id]'. "
|
||||
if(T)message+="Found '[T.value]'."
|
||||
|
||||
DuplicateFunction
|
||||
New(name, token/t)
|
||||
message="Function '[name]' defined twice."
|
||||
|
||||
/*
|
||||
Class: runtimeError
|
||||
An error thrown by the interpreter in running the script.
|
||||
*/
|
||||
/runtimeError
|
||||
var
|
||||
name
|
||||
/*
|
||||
Var: message
|
||||
A basic description as to what went wrong.
|
||||
*/
|
||||
message
|
||||
stack/stack
|
||||
|
||||
proc
|
||||
/*
|
||||
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]()"
|
||||
|
||||
TypeMismatch
|
||||
name="TypeMismatchError"
|
||||
New(op, a, b)
|
||||
message="Type mismatch: '[a]' [op] '[b]'"
|
||||
|
||||
UnexpectedReturn
|
||||
name="UnexpectedReturnError"
|
||||
message="Unexpected return statement."
|
||||
|
||||
UnknownInstruction
|
||||
name="UnknownInstructionError"
|
||||
message="Unknown instruction type. This may be due to incompatible compiler and interpreter versions or a lack of implementation."
|
||||
|
||||
UndefinedVariable
|
||||
name="UndefinedVariableError"
|
||||
New(variable)
|
||||
message="Variable '[variable]' has not been declared."
|
||||
|
||||
UndefinedFunction
|
||||
name="UndefinedFunctionError"
|
||||
New(function)
|
||||
message="Function '[function]()' has not been defined."
|
||||
|
||||
DuplicateVariableDeclaration
|
||||
name="DuplicateVariableError"
|
||||
New(variable)
|
||||
message="Variable '[variable]' was already declared."
|
||||
|
||||
IterationLimitReached
|
||||
name="MaxIterationError"
|
||||
message="A loop has reached its maximum number of iterations."
|
||||
|
||||
RecursionLimitReached
|
||||
name="MaxRecursionError"
|
||||
message="The maximum amount of recursion has been reached."
|
||||
|
||||
DivisionByZero
|
||||
name="DivideByZeroError"
|
||||
message="Division by zero attempted."
|
||||
182
code/modules/scripting/IDE.dm
Normal file
182
code/modules/scripting/IDE.dm
Normal file
@@ -0,0 +1,182 @@
|
||||
client/verb/tcssave()
|
||||
set hidden = 1
|
||||
if(mob.machine)
|
||||
if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, 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") ) // this actually saves the code from input to the server
|
||||
src << output(null, "tcserror") // clear the errors
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to save: Unable to locate server machine. (Back up your code before exiting the window!)</font color>", "tcserror")
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to save: Unable to locate machine. (Back up your code before exiting the window!)</font color>", "tcserror")
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
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()
|
||||
set hidden = 1
|
||||
if(mob.machine)
|
||||
if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, 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")
|
||||
|
||||
|
||||
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")
|
||||
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to compile: Unable to locate server machine. (Back up your code before exiting the window!)</font color>", "tcserror")
|
||||
else
|
||||
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")
|
||||
else
|
||||
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()
|
||||
set hidden = 1
|
||||
if(mob.machine)
|
||||
if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, 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")
|
||||
|
||||
else
|
||||
// Finally, we run the code!
|
||||
src << output("<font color = blue>TCS compilation successful! Code executed.</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")
|
||||
|
||||
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)
|
||||
|
||||
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to run: Unable to locate server machine. (Back up your code before exiting the window!)</font color>", "tcserror")
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to run: Unable to locate machine. (Back up your code before exiting the window!)</font color>", "tcserror")
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
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()
|
||||
set hidden = 1
|
||||
if(mob.machine)
|
||||
if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic))
|
||||
var/obj/machinery/computer/telecomms/traffic/Machine = mob.machine
|
||||
if(Machine.editingcode == mob)
|
||||
Machine.storedcode = "[winget(mob, "tcscode", "text")]"
|
||||
Machine.editingcode = null
|
||||
else
|
||||
if(mob in Machine.viewingcode)
|
||||
Machine.viewingcode.Remove(mob)
|
||||
|
||||
client/verb/tcsrevert()
|
||||
set hidden = 1
|
||||
if(mob.machine)
|
||||
if(istype(mob.machine, /obj/machinery/computer/telecomms/traffic) && mob.machine in view(1, 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
|
||||
|
||||
// Replace quotation marks with quotation macros for proper winset() compatibility
|
||||
var/showcode = dd_replacetext(Server.rawcode, "\\\"", "\\\\\"")
|
||||
showcode = dd_replacetext(showcode, "\"", "\\\"")
|
||||
|
||||
winset(mob, "tcscode", "text=\"[showcode]\"")
|
||||
|
||||
src << output(null, "tcserror") // clear the errors
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to revert: Unable to locate server machine.</font color>", "tcserror")
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to revert: Unable to locate machine.</font color>", "tcserror")
|
||||
else
|
||||
src << output(null, "tcserror")
|
||||
src << output("<font color = red>Failed to revert: Unable to locate machine.</font color>", "tcserror")
|
||||
|
||||
|
||||
|
||||
256
code/modules/scripting/Implementations/Telecomms.dm
Normal file
256
code/modules/scripting/Implementations/Telecomms.dm
Normal file
@@ -0,0 +1,256 @@
|
||||
|
||||
/* --- Traffic Control Scripting Language --- */
|
||||
// Nanotrasen TCS Language - Made by Doohl
|
||||
|
||||
/n_Interpreter/TCS_Interpreter
|
||||
var/datum/TCS_Compiler/Compiler
|
||||
|
||||
HandleError(runtimeError/e)
|
||||
Compiler.Holder.add_entry(e.ToString(), "Execution Error")
|
||||
|
||||
/datum/TCS_Compiler
|
||||
var/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
|
||||
|
||||
/* -- Compile a raw block of text -- */
|
||||
|
||||
proc/Compile(code as message)
|
||||
var
|
||||
n_scriptOptions/nS_Options/options = new()
|
||||
n_Scanner/nS_Scanner/scanner = new(code, options)
|
||||
list/tokens = scanner.Scan()
|
||||
n_Parser/nS_Parser/parser = new(tokens, options)
|
||||
node/BlockDefinition/GlobalBlock/program = parser.Parse()
|
||||
|
||||
list/returnerrors = list()
|
||||
|
||||
returnerrors += scanner.errors
|
||||
returnerrors += parser.errors
|
||||
|
||||
if(returnerrors.len)
|
||||
return returnerrors
|
||||
|
||||
interpreter = new(program)
|
||||
interpreter.persist = 1
|
||||
interpreter.Compiler= src
|
||||
|
||||
// Set up all the preprocessor bullshit
|
||||
//TCS_Setup(program)
|
||||
// Apply preprocessor global variables
|
||||
program.SetVar("PI" , 3.141592653) // value of pi
|
||||
program.SetVar("E" , 2.718281828) // value of e
|
||||
program.SetVar("SQURT2" , 1.414213562) // value of the square root of 2
|
||||
program.SetVar("false" , 0) // boolean shortcut to 0
|
||||
program.SetVar("true" , 1) // boolean shortcut to 1
|
||||
|
||||
program.SetVar("NORTH" , NORTH) // NORTH (1)
|
||||
program.SetVar("SOUTH" , SOUTH) // SOUTH (2)
|
||||
program.SetVar("EAST" , EAST) // EAST (4)
|
||||
program.SetVar("WEST" , WEST) // WEST (8)
|
||||
|
||||
program.SetVar("HONK" , "clown griff u")
|
||||
program.SetVar("CODERS" , "hide the fun")
|
||||
program.SetVar("GRIFF" , pick("HALP IM BEING GRIFFED", "HALP AI IS MALF", "HALP GRIFFE", "HALP TRAITORS", "HALP WIZ GRIEFE ME"))
|
||||
|
||||
|
||||
return returnerrors
|
||||
|
||||
/* -- Execute the compiled code -- */
|
||||
|
||||
proc/Run(var/datum/signal/signal)
|
||||
|
||||
if(!ready)
|
||||
return
|
||||
|
||||
world << "sucessful run!"
|
||||
|
||||
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
|
||||
|
||||
// Set up the script procs
|
||||
|
||||
/*
|
||||
-> Send another signal to a server
|
||||
@format: broadcast(content, frequency, source, job)
|
||||
|
||||
@param content: Message to broadcast
|
||||
@param frequency: Frequency to broadcast to
|
||||
@param source: The name of the source you wish to imitate. Must be stored in stored_names list.
|
||||
@param job: The name of the job.
|
||||
*/
|
||||
interpreter.SetProc("broadcast", "tcombroadcast", signal, list("message", "freq", "source", "job"))
|
||||
|
||||
/*
|
||||
-> Store a value permanently to the server machine (not the actual game hosting machine, the ingame machine)
|
||||
@format: mem(address, value)
|
||||
|
||||
@param address: The memory address (string index) to store a value to
|
||||
@param value: The value to store to the memory address
|
||||
*/
|
||||
interpreter.SetProc("mem", "mem", signal, list("address", "value"))
|
||||
|
||||
/*
|
||||
-> Delay code for a given amount of deciseconds
|
||||
@format: sleep(time)
|
||||
|
||||
@param time: time to sleep in deciseconds (1/10th second)
|
||||
*/
|
||||
interpreter.SetProc("sleep", /proc/delay)
|
||||
|
||||
/*
|
||||
-> Replaces a string with another string
|
||||
@format: replace(string, substring, replacestring)
|
||||
|
||||
@param string: the string to search for substrings (best used with $content$ constant)
|
||||
@param substring: the substring to search for
|
||||
@param replacestring: the string to replace the substring with
|
||||
|
||||
*/
|
||||
interpreter.SetProc("replace", /proc/dd_replacetext)
|
||||
|
||||
/*
|
||||
-> Locates an element/substring inside of a list or string
|
||||
@format: find(haystack, needle, start = 1, end = 0)
|
||||
|
||||
@param haystack: the container to search
|
||||
@param needle: the element to search for
|
||||
@param start: the position to start in
|
||||
@param end: the position to end in
|
||||
|
||||
*/
|
||||
interpreter.SetProc("find", /proc/smartfind)
|
||||
|
||||
/*
|
||||
-> Finds the length of a string or list
|
||||
@format: length(container)
|
||||
|
||||
@param container: the list or container to measure
|
||||
|
||||
*/
|
||||
interpreter.SetProc("length", /proc/smartfind)
|
||||
|
||||
/* -- 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)
|
||||
|
||||
|
||||
|
||||
// Run the compiled code
|
||||
interpreter.Run()
|
||||
|
||||
world << "interpreter.Run() complete."
|
||||
|
||||
// Backwards-apply variables onto signal data
|
||||
/* html_encode() EVERYTHING. fucking players can't be trusted with SHIT */
|
||||
|
||||
signal.data["message"] = html_encode(interpreter.GetVar("$content"))
|
||||
signal.frequency = interpreter.GetVar("$freq")
|
||||
|
||||
var/setname = ""
|
||||
var/obj/machinery/telecomms/server/S = signal.data["server"]
|
||||
if(interpreter.GetVar("$source") in S.stored_names)
|
||||
setname = html_encode(interpreter.GetVar("$source"))
|
||||
else
|
||||
setname = "<i>[html_encode(interpreter.GetVar("$source"))]</i>"
|
||||
|
||||
if(signal.data["name"] != setname)
|
||||
signal.data["realname"] = setname
|
||||
signal.data["name"] = setname
|
||||
signal.data["job"] = html_encode(interpreter.GetVar("$job"))
|
||||
signal.data["reject"] = !(interpreter.GetVar("$pass")) // set reject to the opposite of $pass
|
||||
|
||||
world << "message: [interpreter.GetVar("$content")]"
|
||||
world << "freq: [interpreter.GetVar("$freq")]"
|
||||
world << "name: [setname]"
|
||||
world << "job: [interpreter.GetVar("$job")]"
|
||||
world << "pass: [interpreter.GetVar("$pass")]"
|
||||
|
||||
|
||||
|
||||
/* -- 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)
|
||||
return S.memory[address]
|
||||
|
||||
else
|
||||
S.memory[address] = value
|
||||
|
||||
|
||||
proc/tcombroadcast(var/message, var/freq, var/source, var/job)
|
||||
|
||||
world << message
|
||||
world << freq
|
||||
world << source
|
||||
world << job
|
||||
var/mob/living/carbon/human/H = new
|
||||
var/datum/signal/newsign = new
|
||||
var/obj/machinery/telecomms/server/S = data["server"]
|
||||
var/obj/item/device/radio/hradio
|
||||
|
||||
if(!message)
|
||||
message = "*beep*"
|
||||
if(!source)
|
||||
source = "[html_encode(uppertext(S.id))]"
|
||||
hradio = new // sets the hradio as a radio intercom
|
||||
if(!freq)
|
||||
freq = 1459
|
||||
if(!job)
|
||||
job = "None"
|
||||
|
||||
newsign.data["mob"] = H
|
||||
newsign.data["mobtype"] = H.type
|
||||
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"] = html_encode(job)
|
||||
newsign.data["compression"] = 0
|
||||
newsign.data["message"] = html_encode(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
|
||||
|
||||
// This is a really hacky way of finding a source radio - but it works, i suppose.
|
||||
|
||||
if(!hradio)
|
||||
for(var/obj/item/device/radio/headset/r in world)
|
||||
hradio = r
|
||||
break
|
||||
|
||||
// There are no radios in the world! Oh no! Just make a new one and pray to baby jesus
|
||||
if(!hradio)
|
||||
hradio = new
|
||||
|
||||
newsign.data["radio"] = hradio
|
||||
newsign.data["vmessage"] = H.voice_message
|
||||
newsign.data["vname"] = H.voice_name
|
||||
newsign.data["vmask"] = 0
|
||||
S.relay_information(newsign, "/obj/machinery/telecomms/broadcaster") // send this simple message to broadcasters
|
||||
114
code/modules/scripting/Implementations/_Logic.dm
Normal file
114
code/modules/scripting/Implementations/_Logic.dm
Normal file
@@ -0,0 +1,114 @@
|
||||
// Script -> BYOND code procs
|
||||
|
||||
// --- List operations (lists known as vectors in n_script) ---
|
||||
|
||||
// Clone of list()
|
||||
/proc/n_list()
|
||||
var/list/returnlist = list()
|
||||
for(var/e in args)
|
||||
returnlist.Add(e)
|
||||
return returnlist
|
||||
|
||||
// Clone of pick()
|
||||
/proc/n_pick()
|
||||
var/list/finalpick = list()
|
||||
for(var/e in args)
|
||||
if(isobject(e))
|
||||
if(istype(e, /list))
|
||||
var/list/sublist = e
|
||||
for(var/sube in sublist)
|
||||
finalpick.Add(sube)
|
||||
continue
|
||||
finalpick.Add(e)
|
||||
|
||||
return pick(finalpick)
|
||||
|
||||
// Clone of list[]
|
||||
/proc/n_listpos(var/list/L, var/pos, var/value)
|
||||
if(!istype(L, /list)) return
|
||||
if(isnum(pos))
|
||||
if(!value)
|
||||
if(L.len >= pos)
|
||||
return L[pos]
|
||||
else
|
||||
if(L.len >= pos)
|
||||
L[pos] = value
|
||||
else if(istext(pos))
|
||||
if(!value)
|
||||
return L[pos]
|
||||
else
|
||||
L[pos] = value
|
||||
|
||||
// Clone of list.Copy()
|
||||
/proc/n_listcopy(var/list/L, var/start, var/end)
|
||||
if(!istype(L, /list)) return
|
||||
return L.Copy(start, end)
|
||||
|
||||
// Clone of list.Add()
|
||||
/proc/n_listadd()
|
||||
var/list/chosenlist
|
||||
var/i = 1
|
||||
for(var/e in args)
|
||||
if(i == 1)
|
||||
if(isobject(e))
|
||||
if(istype(e, /list))
|
||||
chosenlist = e
|
||||
else
|
||||
if(chosenlist)
|
||||
chosenlist.Add(e)
|
||||
|
||||
// Clone of list.Remove()
|
||||
/proc/n_listremove()
|
||||
var/list/chosenlist
|
||||
var/i = 1
|
||||
for(var/e in args)
|
||||
if(i == 1)
|
||||
if(isobject(e))
|
||||
if(istype(e, /list))
|
||||
chosenlist = e
|
||||
else
|
||||
if(chosenlist)
|
||||
chosenlist.Remove(e)
|
||||
|
||||
// Clone of list.Cut()
|
||||
/proc/n_listcut(var/list/L, var/start, var/end)
|
||||
if(!istype(L, /list)) return
|
||||
return L.Cut(start, end)
|
||||
|
||||
// Clone of list.Swap()
|
||||
/proc/n_listswap(var/list/L, var/firstindex, var/secondindex)
|
||||
if(!istype(L, /list)) return
|
||||
return L.Swap(firstindex, secondindex)
|
||||
|
||||
// Clone of list.Insert()
|
||||
/proc/n_listinsert(var/list/L, var/index, var/element)
|
||||
if(!istype(L, /list)) return
|
||||
return L.Insert(index, element)
|
||||
|
||||
// --- Miscellaneous functions ---
|
||||
|
||||
// Clone of sleep()
|
||||
/proc/delay(var/time)
|
||||
sleep(time)
|
||||
|
||||
// Clone of prob()
|
||||
/proc/prob_chance(var/chance)
|
||||
return prob(chance)
|
||||
|
||||
// Merge of list.Find() and findtext()
|
||||
/proc/smartfind(var/haystack, var/needle, var/start = 1, var/end = 0)
|
||||
if(haystack && needle)
|
||||
if(isobject(haystack))
|
||||
if(istype(haystack, /list))
|
||||
var/list/listhaystack = haystack
|
||||
return listhaystack.Find(needle, start, end)
|
||||
|
||||
else
|
||||
if(istext(haystack))
|
||||
return findtext(haystack, needle, start, end)
|
||||
|
||||
// Clone of length()
|
||||
/proc/smartlength(var/container)
|
||||
if(container)
|
||||
if(istype(container, /list) || istext(container))
|
||||
return length(container)
|
||||
169
code/modules/scripting/Interpreter/Evaluation.dm
Normal file
169
code/modules/scripting/Interpreter/Evaluation.dm
Normal file
@@ -0,0 +1,169 @@
|
||||
/proc/isobject(x)
|
||||
return (istype(x, /datum) || istype(x, /list) || istype(x, /savefile) || istype(x, /client) || (x==world))
|
||||
|
||||
/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
|
||||
|
||||
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())
|
||||
|
||||
|
||||
//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)
|
||||
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
|
||||
|
||||
//Unary//
|
||||
Minus(a) return -a
|
||||
LogicalNot(a) return !a
|
||||
BitwiseNot(a) return ~a
|
||||
140
code/modules/scripting/Interpreter/Interaction.dm
Normal file
140
code/modules/scripting/Interpreter/Interaction.dm
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
File: Interpreter (Public)
|
||||
Contains methods for interacting with the interpreter.
|
||||
*/
|
||||
/*
|
||||
Class: n_Interpreter
|
||||
Procedures allowing for interaction with the script that is being run by the interpreter object.
|
||||
*/
|
||||
|
||||
/n_Interpreter
|
||||
proc
|
||||
|
||||
/*
|
||||
Proc: Load
|
||||
Loads a 'compiled' script into memory.
|
||||
|
||||
Parameters:
|
||||
program - A <GlobalBlock> object which represents the script's global scope.
|
||||
*/
|
||||
Load(node/BlockDefinition/GlobalBlock/program)
|
||||
ASSERT(program)
|
||||
src.program = program
|
||||
CreateGlobalScope()
|
||||
|
||||
/*
|
||||
Proc: Run
|
||||
Runs the script.
|
||||
*/
|
||||
Run()
|
||||
cur_recursion = 0 // reset recursion
|
||||
|
||||
ASSERT(src.program)
|
||||
RunBlock(src.program)
|
||||
|
||||
/*
|
||||
Proc: SetVar
|
||||
Defines a global variable for the duration of the next execution of a script.
|
||||
|
||||
Notes:
|
||||
This differs from <Block.SetVar()> in that variables set using this procedure only last for the session,
|
||||
while those defined from the block object persist if it is ran multiple times.
|
||||
|
||||
See Also:
|
||||
- <Block.SetVar()>
|
||||
*/
|
||||
SetVar(name, value)
|
||||
if(!istext(name))
|
||||
//CRASH("Invalid variable name")
|
||||
return
|
||||
AssignVariable(name, value)
|
||||
|
||||
/*
|
||||
Proc: SetProc
|
||||
Defines a procedure to be available to the script.
|
||||
|
||||
Parameters:
|
||||
name - The name of the procedure as exposed to the script.
|
||||
path - The typepath of a proc to be called when the function call is read by the interpreter, or, if object is specified, a string representing the procedure's name.
|
||||
object - (Optional) An object which will the be target of a function call.
|
||||
params - Only required if object is not null, a list of the names of parameters the proc takes.
|
||||
*/
|
||||
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
|
||||
/*
|
||||
Proc: VarExists
|
||||
Checks whether a global variable with the specified name exists.
|
||||
*/
|
||||
VarExists(name)
|
||||
return globalScope.variables.Find(name) //convert to 1/0 first?
|
||||
|
||||
/*
|
||||
Proc: ProcExists
|
||||
Checks whether a global function with the specified name exists.
|
||||
*/
|
||||
ProcExists(name)
|
||||
return globalScope.functions.Find(name)
|
||||
|
||||
/*
|
||||
Proc: GetVar
|
||||
Returns the value of a global variable in the script. Remember to ensure that the variable exists before calling this procedure.
|
||||
|
||||
See Also:
|
||||
- <VarExists()>
|
||||
*/
|
||||
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()>
|
||||
*/
|
||||
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]'.")
|
||||
|
||||
/*
|
||||
Event: HandleError
|
||||
Called when the interpreter throws a runtime error.
|
||||
|
||||
See Also:
|
||||
- <runtimeError>
|
||||
*/
|
||||
HandleError(runtimeError/e)
|
||||
291
code/modules/scripting/Interpreter/Interpreter.dm
Normal file
291
code/modules/scripting/Interpreter/Interpreter.dm
Normal file
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
File: Interpreter (Internal)
|
||||
*/
|
||||
/*
|
||||
Class: n_Interpreter
|
||||
*/
|
||||
/*
|
||||
Macros: Status Macros
|
||||
RETURNING - Indicates that the current function is returning a value.
|
||||
BREAKING - Indicates that the current loop is being terminated.
|
||||
CONTINUING - Indicates that the rest of the current iteration of a loop is being skipped.
|
||||
*/
|
||||
#define RETURNING 1
|
||||
#define BREAKING 2
|
||||
#define CONTINUING 4
|
||||
/n_Interpreter
|
||||
var
|
||||
scope
|
||||
curScope
|
||||
globalScope
|
||||
node
|
||||
BlockDefinition/program
|
||||
statement/FunctionDefinition/curFunction
|
||||
stack
|
||||
scopes = new()
|
||||
functions = new()
|
||||
/*
|
||||
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
|
||||
|
||||
max_iterations=100 // max iterations without any kind of delay
|
||||
max_recursion=50 // max recursions without returning anything (or completing the code block)
|
||||
cur_recursion=0 // current amount of recursion
|
||||
/*
|
||||
Var: persist
|
||||
If 0, global variables will be reset after Run() finishes.
|
||||
*/
|
||||
persist=1
|
||||
paused=0
|
||||
|
||||
/*
|
||||
Constructor: New
|
||||
Calls <Load()> with the given parameters.
|
||||
*/
|
||||
New(node/BlockDefinition/GlobalBlock/program=null)
|
||||
.=..()
|
||||
if(program)Load(program)
|
||||
|
||||
proc
|
||||
/*
|
||||
Proc: RaiseError
|
||||
Raises a runtime error.
|
||||
*/
|
||||
RaiseError(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
|
||||
|
||||
CreateGlobalScope()
|
||||
scopes.Clear()
|
||||
var/scope/S = new(program, null)
|
||||
globalScope = S
|
||||
return S
|
||||
|
||||
/*
|
||||
Proc: RunBlock
|
||||
Runs each statement in a block of code.
|
||||
*/
|
||||
RunBlock(node/BlockDefinition/Block, scope/scope = null)
|
||||
var/is_global = istype(Block, /node/BlockDefinition/GlobalBlock)
|
||||
if(!is_global)
|
||||
if(scope)
|
||||
curScope = scope
|
||||
else
|
||||
CreateScope(Block)
|
||||
else
|
||||
if(!persist)
|
||||
CreateGlobalScope()
|
||||
curScope = globalScope
|
||||
|
||||
for(var/node/statement/S in Block.statements)
|
||||
while(paused) sleep(10)
|
||||
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]"))
|
||||
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.
|
||||
*/
|
||||
RunIf(node/statement/IfStatement/stmt)
|
||||
if(Eval(stmt.cond))
|
||||
RunBlock(stmt.block)
|
||||
else if(stmt.else_block)
|
||||
RunBlock(stmt.else_block)
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
/*
|
||||
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))
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
|
||||
IsVariableAccessible(name)
|
||||
var/scope/S = curScope
|
||||
while(S)
|
||||
if(S.variables.Find(name))
|
||||
return TRUE
|
||||
S = S.parent
|
||||
return FALSE
|
||||
|
||||
|
||||
/*
|
||||
Proc: AssignVariable
|
||||
Assigns a value to a variable in a specific block.
|
||||
|
||||
Parameters:
|
||||
name - The name of the variable to assign.
|
||||
value - The value to assign to it.
|
||||
S - The scope the variable resides in. If it is null, a scope with the variable already existing is found. If no scopes have a variable of the given name, the current scope is used.
|
||||
*/
|
||||
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
|
||||
|
||||
18
code/modules/scripting/Interpreter/Scope.dm
Normal file
18
code/modules/scripting/Interpreter/Scope.dm
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Class: scope
|
||||
A runtime instance of a block. Used internally by the interpreter.
|
||||
*/
|
||||
scope
|
||||
var
|
||||
scope/parent = null
|
||||
node/BlockDefinition/block
|
||||
list
|
||||
functions
|
||||
variables
|
||||
|
||||
New(node/BlockDefinition/B, scope/parent)
|
||||
src.block = B
|
||||
src.parent = parent
|
||||
src.variables = B.initial_variables.Copy()
|
||||
src.functions = B.functions.Copy()
|
||||
.=..()
|
||||
82
code/modules/scripting/Options.dm
Normal file
82
code/modules/scripting/Options.dm
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
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 // _
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
IsDigit(char)
|
||||
if(!isnum(char))char=text2ascii(char)
|
||||
return char in ascii_ZERO to ascii_NINE
|
||||
|
||||
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
|
||||
|
||||
/*
|
||||
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
|
||||
/*
|
||||
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)
|
||||
|
||||
list
|
||||
assign_operators=list("=" = null, "&=" = "&",
|
||||
"|=" = "|", "`=" = "`",
|
||||
"+=" = "+", "-=" = "-",
|
||||
"*=" = "*", "/=" = "/",
|
||||
"^=" = "^",
|
||||
"%=" = "%")
|
||||
|
||||
unary_operators =list("!" = /node/expression/operator/unary/LogicalNot, "~" = /node/expression/operator/unary/BitwiseNot,
|
||||
"-" = /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)
|
||||
|
||||
New()
|
||||
.=..()
|
||||
for(var/O in assign_operators+binary_operators+unary_operators)
|
||||
if(!symbols.Find(O)) symbols+=O
|
||||
308
code/modules/scripting/Parser/Expressions.dm
Normal file
308
code/modules/scripting/Parser/Expressions.dm
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
File: Expressions
|
||||
Procedures for parsing expressions.
|
||||
*/
|
||||
|
||||
/*
|
||||
Macros: Expression Macros
|
||||
OPERATOR - A value indicating the parser currently expects a binary operator.
|
||||
VALUE - A value indicating the parser currently expects a value.
|
||||
SHIFT - Tells the parser to push the current operator onto the stack.
|
||||
REDUCE - Tells the parser to reduce the stack.
|
||||
*/
|
||||
#define OPERATOR 1
|
||||
#define VALUE 2
|
||||
#define SHIFT 0
|
||||
#define REDUCE 1
|
||||
|
||||
/*
|
||||
Class: nS_Parser
|
||||
*/
|
||||
/n_Parser/nS_Parser
|
||||
var
|
||||
/*
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
/*
|
||||
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))
|
||||
|
||||
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(/token/number, /token/string)
|
||||
return new/node/expression/value/literal(T.value)
|
||||
|
||||
/*
|
||||
Proc: GetOperator
|
||||
Gets a path related to a token or string and returns an instance of the given type. This is used to get an instance of either a binary or unary
|
||||
operator from a token.
|
||||
|
||||
Parameters:
|
||||
O - The input value. If this is a token, O is reset to the token's value.
|
||||
When O is a string and is in L, its associated value is used as the path to instantiate.
|
||||
type - The desired type of the returned object.
|
||||
L - The list in which to search for O.
|
||||
|
||||
See Also:
|
||||
- <GetBinaryOperator()>
|
||||
- <GetUnaryOperator()>
|
||||
*/
|
||||
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
|
||||
|
||||
/*
|
||||
Proc: GetBinaryOperator
|
||||
Uses <GetOperator()> to search for an instance of a binary operator type with which the given string is associated. For example, if
|
||||
O is set to "+", an <Add> node is returned.
|
||||
|
||||
See Also:
|
||||
- <GetOperator()>
|
||||
- <GetUnaryOperator()>
|
||||
*/
|
||||
GetBinaryOperator(O)
|
||||
return GetOperator(O, /node/expression/operator/binary, options.binary_operators)
|
||||
|
||||
/*
|
||||
Proc: GetUnaryOperator
|
||||
Uses <GetOperator()> to search for an instance of a unary operator type with which the given string is associated. For example, if
|
||||
O is set to "!", a <LogicalNot> node is returned.
|
||||
|
||||
See Also:
|
||||
- <GetOperator()>
|
||||
- <GetBinaryOperator()>
|
||||
*/
|
||||
GetUnaryOperator(O)
|
||||
return GetOperator(O, /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)
|
||||
|
||||
/*
|
||||
Proc: EndOfExpression
|
||||
Returns true if the current token represents the end of an expression.
|
||||
|
||||
Parameters:
|
||||
end - A list of values to compare the current token to.
|
||||
*/
|
||||
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
|
||||
|
||||
/*
|
||||
Proc: ParseExpression
|
||||
Uses the Shunting-yard algorithm to parse expressions.
|
||||
|
||||
Notes:
|
||||
- When an opening parenthesis is found, then <ParseParenExpression()> is called to handle it.
|
||||
- The <expecting> variable helps distinguish unary operators from binary operators (for cases like the - operator, which can be either).
|
||||
|
||||
Articles:
|
||||
- <http://epaperpress.com/oper/>
|
||||
- <http://en.wikipedia.org/wiki/Shunting-yard_algorithm>
|
||||
|
||||
See Also:
|
||||
- <ParseFunctionExpression()>
|
||||
- <ParseParenExpression()>
|
||||
- <ParseParamExpression()>
|
||||
*/
|
||||
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
|
||||
|
||||
|
||||
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(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(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)
|
||||
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
|
||||
|
||||
/*
|
||||
Proc: ParseFunctionExpression
|
||||
Parses a function call inside of an expression.
|
||||
|
||||
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
|
||||
for()
|
||||
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
|
||||
|
||||
/*
|
||||
Proc: ParseParenExpression
|
||||
Parses an expression that ends with a close parenthesis. This is used for parsing expressions inside of parentheses.
|
||||
|
||||
See Also:
|
||||
- <ParseExpression()>
|
||||
*/
|
||||
ParseParenExpression()
|
||||
if(!CheckToken("(", /token/symbol))
|
||||
return
|
||||
return new/node/expression/operator/unary/group(ParseExpression(list(")")))
|
||||
|
||||
/*
|
||||
Proc: ParseParamExpression
|
||||
Parses an expression that ends with either a comma or close parenthesis. This is used for parsing the parameters passed to a function call.
|
||||
|
||||
See Also:
|
||||
- <ParseExpression()>
|
||||
*/
|
||||
ParseParamExpression()
|
||||
return ParseExpression(list(",", ")"))
|
||||
166
code/modules/scripting/Parser/Keywords.dm
Normal file
166
code/modules/scripting/Parser/Keywords.dm
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
File: Keywords
|
||||
*/
|
||||
var/const
|
||||
KW_FAIL = 0 //Fatal error; stop parsing entire script.
|
||||
KW_PASS = 1 //OK
|
||||
KW_ERR = 2 //Non-fatal error, keyword couldn't be handled properly. Ignore keyword but continue on.
|
||||
KW_WARN = 3 //Warning
|
||||
|
||||
/*
|
||||
Class: n_Keyword
|
||||
Represents a special statement in the code triggered by a keyword.
|
||||
*/
|
||||
/n_Keyword
|
||||
New(inline=0)
|
||||
src.inline=inline
|
||||
return ..()
|
||||
|
||||
var
|
||||
/*
|
||||
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).
|
||||
*/
|
||||
inline
|
||||
|
||||
/*
|
||||
Proc: Parse
|
||||
Called when the parser finds a keyword in the code.
|
||||
|
||||
Parameters:
|
||||
parser - The parser that created this object. You can use the parameter to manipulate the parser in order to add statements and blocks
|
||||
to its AST.
|
||||
*/
|
||||
proc/Parse(n_Parser/parser)
|
||||
|
||||
/*
|
||||
Class: nS_Keyword
|
||||
A keyword in n_Script. By default these include return, if, else, while, and def. To enable or disable a keyword, change the
|
||||
<nS_Options.keywords> list.
|
||||
|
||||
Behavior:
|
||||
When a parser is expecting a new statement, and a keyword listed in <nS_Options.keywords> is found, it will call the keyword's
|
||||
<n_Keyword.Parse()> proc.
|
||||
*/
|
||||
//
|
||||
nS_Keyword
|
||||
New(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
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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(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)
|
||||
else
|
||||
parser.errors+=new/scriptError/BadToken(parser.curToken)
|
||||
return KW_FAIL
|
||||
184
code/modules/scripting/Parser/Parser.dm
Normal file
184
code/modules/scripting/Parser/Parser.dm
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
File: Parser
|
||||
*/
|
||||
/*
|
||||
Class: n_Parser
|
||||
An object that reads tokens and produces an AST (abstract syntax tree).
|
||||
*/
|
||||
/n_Parser
|
||||
var
|
||||
/*
|
||||
Var: index
|
||||
The parser's current position in the token's list.
|
||||
*/
|
||||
index = 1
|
||||
list
|
||||
/*
|
||||
Var: tokens
|
||||
A list of tokens in the source code generated by a scanner.
|
||||
*/
|
||||
tokens = new
|
||||
/*
|
||||
Var: errors
|
||||
A list of fatal errors found by the parser. If there are any items in this list, then it is not safe to run the returned AST.
|
||||
|
||||
See Also:
|
||||
- <scriptError>
|
||||
*/
|
||||
errors = new
|
||||
/*
|
||||
Var: warnings
|
||||
A list of non-fatal problems in the script.
|
||||
*/
|
||||
warnings = new
|
||||
token
|
||||
/*
|
||||
Var: curToken
|
||||
The token at <index> in <tokens>.
|
||||
*/
|
||||
curToken
|
||||
stack
|
||||
blocks=new
|
||||
node/BlockDefinition
|
||||
GlobalBlock/global_block=new
|
||||
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()
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
/*
|
||||
Class: nS_Parser
|
||||
An implmentation of a parser for n_Script.
|
||||
*/
|
||||
/n_Parser/nS_Parser
|
||||
var/n_scriptOptions/nS_Options/options
|
||||
/*
|
||||
Constructor: New
|
||||
|
||||
Parameters:
|
||||
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 ..()
|
||||
|
||||
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)
|
||||
continue
|
||||
else
|
||||
errors+=new/scriptError/BadToken(curToken)
|
||||
return
|
||||
return global_block
|
||||
|
||||
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
|
||||
|
||||
AddBlock(node/BlockDefinition/B)
|
||||
blocks.Push(curBlock)
|
||||
curBlock=B
|
||||
|
||||
EndBlock()
|
||||
if(curBlock==global_block) return 0
|
||||
curBlock=blocks.Pop()
|
||||
return 1
|
||||
|
||||
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.")
|
||||
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
|
||||
for()
|
||||
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()
|
||||
231
code/modules/scripting/Scanner/Scanner.dm
Normal file
231
code/modules/scripting/Scanner/Scanner.dm
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
File: Scanner
|
||||
*/
|
||||
/*
|
||||
Class: n_Scanner
|
||||
An object responsible for breaking up source code into tokens for use by the parser.
|
||||
*/
|
||||
/n_Scanner
|
||||
var
|
||||
code
|
||||
list
|
||||
/*
|
||||
Var: errors
|
||||
A list of fatal errors found by the scanner. If there are any items in this list, then it is not safe to parse the returned tokens.
|
||||
|
||||
See Also:
|
||||
- <scriptError>
|
||||
*/
|
||||
errors = new
|
||||
/*
|
||||
Var: warnings
|
||||
A list of non-fatal problems in the source code found by the scanner.
|
||||
*/
|
||||
warnings = new
|
||||
|
||||
proc
|
||||
/*
|
||||
Proc: LoadCode
|
||||
Loads source code.
|
||||
*/
|
||||
LoadCode(c)
|
||||
code=c
|
||||
|
||||
/*
|
||||
Proc: LoadCodeFromFile
|
||||
Gets the code from a file and calls <LoadCode()>.
|
||||
*/
|
||||
LoadCodeFromFile(f)
|
||||
LoadCode(file2text(f))
|
||||
|
||||
/*
|
||||
Proc: Scan
|
||||
Runs the scanner and returns the resulting list of tokens. Ensure that <LoadCode()> has been called first.
|
||||
*/
|
||||
Scan()
|
||||
|
||||
/*
|
||||
Class: nS_Scanner
|
||||
A scanner implementation for n_Script.
|
||||
*/
|
||||
/n_Scanner/nS_Scanner
|
||||
|
||||
var
|
||||
/*
|
||||
Variable: codepos
|
||||
The scanner's position in the source code.
|
||||
*/
|
||||
codepos = 1
|
||||
line = 1
|
||||
linepos = 0 //column=codepos-linepos
|
||||
n_scriptOptions/nS_Options/options
|
||||
list
|
||||
/*
|
||||
Variable: ignore
|
||||
A list of characters that are ignored by the scanner.
|
||||
|
||||
Default Value:
|
||||
Whitespace
|
||||
*/
|
||||
ignore = list(" ", "\t", "\n") //Don't add tokens for whitespace
|
||||
/*
|
||||
Variable: end_stmt
|
||||
A list of characters that end a statement. Each item may only be one character long.
|
||||
|
||||
Default Value:
|
||||
Semicolon
|
||||
*/
|
||||
end_stmt = list(";")
|
||||
/*
|
||||
Variable: string_delim
|
||||
A list of characters that can start and end strings.
|
||||
|
||||
Default Value:
|
||||
Double and single quotes.
|
||||
*/
|
||||
string_delim = list("\"", "'")
|
||||
/*
|
||||
Variable: delim
|
||||
A list of characters that denote the start of a new token. This list is automatically populated.
|
||||
*/
|
||||
delim = new
|
||||
|
||||
/*
|
||||
Macro: COL
|
||||
The current column number.
|
||||
*/
|
||||
#define COL codepos-linepos
|
||||
|
||||
/*
|
||||
Constructor: New
|
||||
Parameters:
|
||||
code - The source code to tokenize.
|
||||
options - An <nS_Options> object used to configure the scanner.
|
||||
*/
|
||||
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)
|
||||
|
||||
Scan() //Creates a list of tokens from source code
|
||||
var/list/tokens=new
|
||||
for(, src.codepos<=lentext(code), src.codepos++)
|
||||
var/char=copytext(code, codepos, codepos+1)
|
||||
if(char=="\n")
|
||||
line++
|
||||
linepos=codepos
|
||||
if(ignore.Find(char))
|
||||
continue
|
||||
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()
|
||||
codepos=initial(codepos)
|
||||
line=initial(line)
|
||||
linepos=initial(linepos)
|
||||
return tokens
|
||||
|
||||
proc
|
||||
/*
|
||||
Proc: ReadString
|
||||
Reads a string in the source code into a token.
|
||||
|
||||
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)
|
||||
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
|
||||
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)
|
||||
|
||||
/*
|
||||
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)
|
||||
|
||||
/*
|
||||
Proc: ReadSymbol
|
||||
Reads a symbol into a token.
|
||||
*/
|
||||
ReadSymbol()
|
||||
var
|
||||
char=copytext(code, codepos, codepos+1)
|
||||
buf
|
||||
|
||||
while(options.symbols.Find(buf+char))
|
||||
buf+=char
|
||||
if(++codepos>lentext(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)
|
||||
|
||||
/*
|
||||
Proc: ReadNumber
|
||||
Reads a number into a token.
|
||||
*/
|
||||
ReadNumber()
|
||||
var
|
||||
char=copytext(code, codepos, codepos+1)
|
||||
buf
|
||||
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
|
||||
38
code/modules/scripting/Scanner/Tokens.dm
Normal file
38
code/modules/scripting/Scanner/Tokens.dm
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Class: Token
|
||||
Represents an entity and position in the source code.
|
||||
*/
|
||||
/token
|
||||
var
|
||||
value
|
||||
line
|
||||
column
|
||||
|
||||
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
|
||||
member
|
||||
|
||||
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
|
||||
|
||||
end
|
||||
23
code/modules/scripting/stack.dm
Normal file
23
code/modules/scripting/stack.dm
Normal file
@@ -0,0 +1,23 @@
|
||||
/stack
|
||||
var/list
|
||||
contents=new
|
||||
proc
|
||||
Push(value)
|
||||
contents+=value
|
||||
|
||||
Pop()
|
||||
if(!contents.len) return null
|
||||
. = contents[contents.len]
|
||||
contents.len--
|
||||
|
||||
Top() //returns the item on the top of the stack without removing it
|
||||
if(!contents.len) return null
|
||||
return contents[contents.len]
|
||||
|
||||
Copy()
|
||||
var/stack/S=new()
|
||||
S.contents=src.contents.Copy()
|
||||
return S
|
||||
|
||||
Clear()
|
||||
contents.Cut()
|
||||
Reference in New Issue
Block a user