mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +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:
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()
|
||||
Reference in New Issue
Block a user