added everything
This commit is contained in:
1951
engine/console/BASgram.cc
Executable file
1951
engine/console/BASgram.cc
Executable file
File diff suppressed because it is too large
Load Diff
533
engine/console/BASgram.y
Executable file
533
engine/console/BASgram.y
Executable file
@ -0,0 +1,533 @@
|
||||
%{
|
||||
// Make sure we don't get gram.h twice.
|
||||
#define _BASGRAM_H_
|
||||
#define _CMDGRAM_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "console/console.h"
|
||||
#include "console/compiler.h"
|
||||
#include "console/consoleInternal.h"
|
||||
|
||||
#ifndef YYDEBUG
|
||||
#define YYDEBUG 0
|
||||
#endif
|
||||
|
||||
#define YYSSIZE 350
|
||||
|
||||
int outtext(char *fmt, ...);
|
||||
extern int serrors;
|
||||
|
||||
#define nil 0
|
||||
#undef YY_ARGS
|
||||
#define YY_ARGS(x) x
|
||||
|
||||
int BASlex();
|
||||
void BASerror(char *a, ...);
|
||||
|
||||
#define alloca dMalloc
|
||||
|
||||
%}
|
||||
%{
|
||||
/* Reserved Word Definitions */
|
||||
|
||||
/* IMPORTANT: SEE COMMENTS BELOW IF YOU ARE THINKING ABOUT MODIFYING TOKENS */
|
||||
|
||||
/*
|
||||
ALL TOKENS BETWEEN HERE AND ADDITIONAL TOKENS BELOW MUST BE IDENTICAL TO THOSE
|
||||
IN CS_CMD.y OTHERWISE BAD STUFF HAPPENS
|
||||
*/
|
||||
%}
|
||||
%token <i> rwDEFINE rwENDDEF rwDECLARE
|
||||
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
|
||||
%token <i> rwIF rwNIL rwRETURN rwWHILE
|
||||
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
|
||||
%token <i> rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
|
||||
%token <i> rwCASEOR rwPACKAGE
|
||||
%token ILLEGAL_TOKEN
|
||||
%{
|
||||
/* Constants and Identifier Definitions */
|
||||
%}
|
||||
%token <c> CHRCONST
|
||||
%token <i> INTCONST
|
||||
%token <s> TTAG
|
||||
%token <s> VAR
|
||||
%token <s> IDENT
|
||||
%token <str> STRATOM
|
||||
%token <str> TAGATOM
|
||||
%token <f> FLTCONST
|
||||
|
||||
%{
|
||||
/* Operator Definitions */
|
||||
%}
|
||||
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
|
||||
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
|
||||
%token <i> opMINUSMINUS opPLUSPLUS
|
||||
%token <i> STMT_SEP
|
||||
%token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN
|
||||
%token <i> opXORASN opORASN opSLASN opSRASN opCAT
|
||||
%token <i> opEQ opNE opGE opLE opAND opOR opSTREQ
|
||||
%token <i> opCOLONCOLON
|
||||
|
||||
%union {
|
||||
char c;
|
||||
int i;
|
||||
const char *s;
|
||||
char *str;
|
||||
double f;
|
||||
StmtNode *stmt;
|
||||
ExprNode *expr;
|
||||
SlotAssignNode *slist;
|
||||
VarNode *var;
|
||||
SlotDecl slot;
|
||||
ObjectBlockDecl odcl;
|
||||
ObjectDeclNode *od;
|
||||
AssignDecl asn;
|
||||
IfStmtNode *ifnode;
|
||||
}
|
||||
|
||||
%type <s> parent_block
|
||||
%type <ifnode> case_block
|
||||
%type <stmt> switch_stmt
|
||||
%type <stmt> decl
|
||||
%type <stmt> decl_list
|
||||
%type <stmt> package_decl
|
||||
%type <stmt> fn_decl_stmt
|
||||
%type <stmt> fn_decl_list
|
||||
%type <stmt> statement_list
|
||||
%type <stmt> stmt
|
||||
%type <expr> expr_list
|
||||
%type <expr> expr_list_decl
|
||||
%type <expr> aidx_expr
|
||||
%type <expr> funcall_expr
|
||||
%type <expr> object_name
|
||||
%type <expr> object_args
|
||||
%type <expr> stmt_expr
|
||||
%type <expr> case_expr
|
||||
%type <expr> class_name_expr
|
||||
%type <stmt> if_stmt
|
||||
%type <stmt> while_stmt
|
||||
%type <stmt> for_stmt
|
||||
%type <stmt> stmt_block
|
||||
%type <stmt> datablock_decl
|
||||
%type <od> object_decl
|
||||
%type <od> object_decl_list
|
||||
%type <odcl> object_declare_block
|
||||
%type <expr> expr
|
||||
%type <slist> slot_assign_list
|
||||
%type <slist> slot_assign
|
||||
%type <slot> slot_acc
|
||||
%type <stmt> expression_stmt
|
||||
%type <var> var_list
|
||||
%type <var> var_list_decl
|
||||
%type <asn> assign_op_struct
|
||||
|
||||
|
||||
%left '['
|
||||
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '='
|
||||
%left '?' ':'
|
||||
%left opOR
|
||||
%left opAND
|
||||
%left '|'
|
||||
%left '^'
|
||||
%left '&'
|
||||
%left opEQ opNE
|
||||
%left '<' opLE '>' opGE
|
||||
%left '@' opCAT opSTREQ opSTRNE
|
||||
%left opSHL opSHR
|
||||
%left '+' '-'
|
||||
%left '*' '/' '%'
|
||||
%right '!' '~' opPLUSPLUS opMINUSMINUS UNARY
|
||||
%left '.'
|
||||
|
||||
%{
|
||||
/*
|
||||
Additional tokens
|
||||
|
||||
NOTE: These MUST be here, otherwise the definitions of the usual TorqueScript tokens changes
|
||||
which will break the compiler. Double check the standard TS tokens arent being redefined before
|
||||
testing a build.
|
||||
*/
|
||||
%}
|
||||
%token <i> rwTHEN rwEND rwBEGIN rwCFOR rwTO rwSTEP
|
||||
|
||||
%%
|
||||
|
||||
start
|
||||
: decl_list
|
||||
{ }
|
||||
;
|
||||
|
||||
decl_list
|
||||
:
|
||||
{ $$ = nil; }
|
||||
| decl_list decl
|
||||
{ if(!statementList) { statementList = $2; } else { statementList->append($2); } }
|
||||
;
|
||||
|
||||
decl
|
||||
: stmt
|
||||
{ $$ = $1; }
|
||||
| fn_decl_stmt
|
||||
{ $$ = $1; }
|
||||
| package_decl
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
package_decl
|
||||
: rwPACKAGE IDENT fn_decl_list rwEND
|
||||
{ $$ = $3; for(StmtNode *walk = ($3);walk;walk = walk->getNext() ) walk->setPackage($2); }
|
||||
;
|
||||
|
||||
fn_decl_list
|
||||
: fn_decl_stmt
|
||||
{ $$ = $1; }
|
||||
| fn_decl_list fn_decl_stmt
|
||||
{ $$ = $1; ($1)->append($2); }
|
||||
;
|
||||
|
||||
statement_list
|
||||
:
|
||||
{ $$ = nil; }
|
||||
| statement_list stmt
|
||||
{ if(!$1) { $$ = $2; } else { ($1)->append($2); $$ = $1; } }
|
||||
;
|
||||
|
||||
stmt
|
||||
: if_stmt
|
||||
| while_stmt
|
||||
| for_stmt
|
||||
| datablock_decl
|
||||
| switch_stmt
|
||||
| rwBREAK semicolon
|
||||
{ $$ = BreakStmtNode::alloc(); }
|
||||
| rwCONTINUE semicolon
|
||||
{ $$ = ContinueStmtNode::alloc(); }
|
||||
| rwRETURN semicolon
|
||||
{ $$ = ReturnStmtNode::alloc(NULL); }
|
||||
| rwRETURN expr semicolon
|
||||
{ $$ = ReturnStmtNode::alloc($2); }
|
||||
| expression_stmt semicolon
|
||||
{ $$ = $1; }
|
||||
| TTAG '=' expr semicolon
|
||||
{ $$ = TTagSetStmtNode::alloc($1, $3, NULL); }
|
||||
| TTAG '=' expr ',' expr semicolon
|
||||
{ $$ = TTagSetStmtNode::alloc($1, $3, $5); }
|
||||
;
|
||||
|
||||
fn_decl_stmt
|
||||
: rwDEFINE IDENT '(' var_list_decl ')' statement_list rwEND
|
||||
{ $$ = FunctionDeclStmtNode::alloc($2, NULL, $4, $6); }
|
||||
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' statement_list rwEND
|
||||
{ $$ = FunctionDeclStmtNode::alloc($4, $2, $6, $8); }
|
||||
;
|
||||
|
||||
var_list_decl
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| var_list
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
var_list
|
||||
: VAR
|
||||
{ $$ = VarNode::alloc($1, NULL); }
|
||||
| var_list ',' VAR
|
||||
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)VarNode::alloc($3, NULL)); }
|
||||
;
|
||||
|
||||
datablock_decl
|
||||
: rwDATABLOCK IDENT '(' IDENT parent_block ')' slot_assign_list rwEND
|
||||
{ $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $5, $7, NULL, true); }
|
||||
;
|
||||
|
||||
object_decl
|
||||
: rwDECLARE class_name_expr '(' object_name parent_block object_args ')' object_declare_block rwEND
|
||||
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $8.slots, $8.decls, false); }
|
||||
| rwDECLARE class_name_expr '(' object_name parent_block object_args ')' rwEND
|
||||
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false); }
|
||||
;
|
||||
|
||||
parent_block
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| ':' IDENT
|
||||
{ $$ = $2; }
|
||||
;
|
||||
|
||||
object_name
|
||||
:
|
||||
{ $$ = StrConstNode::alloc("", false); }
|
||||
| expr
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
object_args
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| ',' expr_list
|
||||
{ $$ = $2; }
|
||||
;
|
||||
|
||||
object_declare_block
|
||||
:
|
||||
{ $$.slots = NULL; $$.decls = NULL; }
|
||||
| slot_assign_list
|
||||
{ $$.slots = $1; $$.decls = NULL; }
|
||||
| object_decl_list
|
||||
{ $$.slots = NULL; $$.decls = $1; }
|
||||
| slot_assign_list object_decl_list
|
||||
{ $$.slots = $1; $$.decls = $2; }
|
||||
;
|
||||
|
||||
object_decl_list
|
||||
: object_decl semicolon
|
||||
{ $$ = $1; }
|
||||
| object_decl_list object_decl semicolon
|
||||
{ $1->append($2); $$ = $1; }
|
||||
;
|
||||
|
||||
stmt_block
|
||||
: rwBEGIN statement_list rwEND
|
||||
{ $$ = $2; }
|
||||
| stmt
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
switch_stmt
|
||||
: rwSWITCH expr case_block rwEND
|
||||
{ $$ = $3; $3->propagateSwitchExpr($2, false); }
|
||||
| rwSWITCHSTR expr case_block rwEND
|
||||
{ $$ = $3; $3->propagateSwitchExpr($2, true); }
|
||||
;
|
||||
|
||||
case_block
|
||||
: rwCASE case_expr ':' statement_list
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
|
||||
| rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, $7, false); }
|
||||
| rwCASE case_expr ':' statement_list case_block
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, $5, true); }
|
||||
;
|
||||
|
||||
case_expr
|
||||
: expr
|
||||
{ $$ = $1;}
|
||||
| case_expr rwCASEOR expr
|
||||
{ ($1)->append($3); $$=$1; }
|
||||
;
|
||||
|
||||
if_stmt
|
||||
: rwIF expr rwTHEN statement_list rwEND
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
|
||||
| rwIF expr rwTHEN statement_list rwELSE statement_list rwEND
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, $6, false); }
|
||||
;
|
||||
|
||||
while_stmt
|
||||
: rwWHILE expr rwBEGIN statement_list rwEND
|
||||
{ $$ = LoopStmtNode::alloc($1, nil, $2, nil, $4, false); }
|
||||
;
|
||||
|
||||
for_stmt
|
||||
: rwFOR expr ',' expr ',' expr rwBEGIN statement_list rwEND
|
||||
{ $$ = LoopStmtNode::alloc($1, $2, $4, $6, $8, false); }
|
||||
;
|
||||
|
||||
expression_stmt
|
||||
: stmt_expr
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
expr
|
||||
: stmt_expr
|
||||
{ $$ = $1; }
|
||||
| '(' expr ')'
|
||||
{ $$ = $2; }
|
||||
| expr '^' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '%' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '&' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '|' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '+' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '-' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '*' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '/' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| '-' expr %prec UNARY
|
||||
{ $$ = FloatUnaryExprNode::alloc($1, $2); }
|
||||
| '*' expr %prec UNARY
|
||||
{ $$ = TTagDerefNode::alloc($2); }
|
||||
| TTAG
|
||||
{ $$ = TTagExprNode::alloc($1); }
|
||||
| expr '?' expr ':' expr
|
||||
{ $$ = ConditionalExprNode::alloc($1, $3, $5); }
|
||||
| expr '<' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '>' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opGE expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opLE expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opEQ expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opNE expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opOR expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opSHL expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opSHR expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opAND expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opSTREQ expr
|
||||
{ $$ = StreqExprNode::alloc($1, $3, true); }
|
||||
| expr opSTRNE expr
|
||||
{ $$ = StreqExprNode::alloc($1, $3, false); }
|
||||
| expr '@' expr
|
||||
{ $$ = StrcatExprNode::alloc($1, $3, $2); }
|
||||
| '!' expr
|
||||
{ $$ = IntUnaryExprNode::alloc($1, $2); }
|
||||
| '~' expr
|
||||
{ $$ = IntUnaryExprNode::alloc($1, $2); }
|
||||
| TAGATOM
|
||||
{ $$ = StrConstNode::alloc($1, true); }
|
||||
| FLTCONST
|
||||
{ $$ = FloatNode::alloc($1); }
|
||||
| INTCONST
|
||||
{ $$ = IntNode::alloc($1); }
|
||||
| rwBREAK
|
||||
{ $$ = ConstantNode::alloc(StringTable->insert("break")); }
|
||||
| slot_acc
|
||||
{ $$ = SlotAccessNode::alloc($1.object, $1.array, $1.slotName); }
|
||||
| IDENT
|
||||
{ $$ = ConstantNode::alloc($1); }
|
||||
| STRATOM
|
||||
{ $$ = StrConstNode::alloc($1, false); }
|
||||
| VAR
|
||||
{ $$ = (ExprNode*)VarNode::alloc($1, NULL); }
|
||||
| VAR '[' aidx_expr ']'
|
||||
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
|
||||
;
|
||||
|
||||
slot_acc
|
||||
: expr '.' IDENT
|
||||
{ $$.object = $1; $$.slotName = $3; $$.array = NULL; }
|
||||
| expr '.' IDENT '[' aidx_expr ']'
|
||||
{ $$.object = $1; $$.slotName = $3; $$.array = $5; }
|
||||
;
|
||||
|
||||
class_name_expr
|
||||
: IDENT
|
||||
{ $$ = ConstantNode::alloc($1); }
|
||||
| '(' expr ')'
|
||||
{ $$ = $2; }
|
||||
;
|
||||
|
||||
assign_op_struct
|
||||
: opPLUSPLUS
|
||||
{ $$.token = '+'; $$.expr = FloatNode::alloc(1); }
|
||||
| opMINUSMINUS
|
||||
{ $$.token = '-'; $$.expr = FloatNode::alloc(1); }
|
||||
| opPLASN expr
|
||||
{ $$.token = '+'; $$.expr = $2; }
|
||||
| opMIASN expr
|
||||
{ $$.token = '-'; $$.expr = $2; }
|
||||
| opMLASN expr
|
||||
{ $$.token = '*'; $$.expr = $2; }
|
||||
| opDVASN expr
|
||||
{ $$.token = '/'; $$.expr = $2; }
|
||||
| opMODASN expr
|
||||
{ $$.token = '%'; $$.expr = $2; }
|
||||
| opANDASN expr
|
||||
{ $$.token = '&'; $$.expr = $2; }
|
||||
| opXORASN expr
|
||||
{ $$.token = '^'; $$.expr = $2; }
|
||||
| opORASN expr
|
||||
{ $$.token = '|'; $$.expr = $2; }
|
||||
| opSLASN expr
|
||||
{ $$.token = opSHL; $$.expr = $2; }
|
||||
| opSRASN expr
|
||||
{ $$.token = opSHR; $$.expr = $2; }
|
||||
;
|
||||
|
||||
stmt_expr
|
||||
: funcall_expr
|
||||
{ $$ = $1; }
|
||||
| object_decl
|
||||
{ $$ = $1; }
|
||||
| VAR '=' expr
|
||||
{ $$ = AssignExprNode::alloc($1, NULL, $3); }
|
||||
| VAR '[' aidx_expr ']' '=' expr
|
||||
{ $$ = AssignExprNode::alloc($1, $3, $6); }
|
||||
| VAR assign_op_struct
|
||||
{ $$ = AssignOpExprNode::alloc($1, NULL, $2.expr, $2.token); }
|
||||
| VAR '[' aidx_expr ']' assign_op_struct
|
||||
{ $$ = AssignOpExprNode::alloc($1, $3, $5.expr, $5.token); }
|
||||
| slot_acc assign_op_struct
|
||||
{ $$ = SlotAssignOpNode::alloc($1.object, $1.slotName, $1.array, $2.token, $2.expr); }
|
||||
| slot_acc '=' expr
|
||||
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $3); }
|
||||
| slot_acc '=' '{' expr_list '}'
|
||||
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $4); }
|
||||
;
|
||||
|
||||
funcall_expr
|
||||
: IDENT '(' expr_list_decl ')'
|
||||
{ $$ = FuncCallExprNode::alloc($1, NULL, $3, false); }
|
||||
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
|
||||
{ $$ = FuncCallExprNode::alloc($3, $1, $5, false); }
|
||||
| expr '.' IDENT '(' expr_list_decl ')'
|
||||
{ $1->append($5); $$ = FuncCallExprNode::alloc($3, NULL, $1, true); }
|
||||
;
|
||||
|
||||
expr_list_decl
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| expr_list
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
expr_list
|
||||
: expr
|
||||
{ $$ = $1; }
|
||||
| expr_list ',' expr
|
||||
{ ($1)->append($3); $$ = $1; }
|
||||
;
|
||||
|
||||
slot_assign_list
|
||||
: slot_assign
|
||||
{ $$ = $1; }
|
||||
| slot_assign_list slot_assign
|
||||
{ $1->append($2); $$ = $1; }
|
||||
;
|
||||
|
||||
slot_assign
|
||||
: IDENT '=' expr semicolon
|
||||
{ $$ = SlotAssignNode::alloc(NULL, NULL, $1, $3); }
|
||||
| rwDATABLOCK '=' expr semicolon
|
||||
{ $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); }
|
||||
| IDENT '[' aidx_expr ']' '=' expr semicolon
|
||||
{ $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); }
|
||||
;
|
||||
|
||||
aidx_expr
|
||||
: expr
|
||||
{ $$ = $1; }
|
||||
| aidx_expr ',' expr
|
||||
{ $$ = CommaCatExprNode::alloc($1, $3); }
|
||||
;
|
||||
|
||||
semicolon:
|
||||
| semicolon ';'
|
||||
;
|
||||
%%
|
||||
|
2190
engine/console/BASscan.cc
Executable file
2190
engine/console/BASscan.cc
Executable file
File diff suppressed because it is too large
Load Diff
289
engine/console/BASscan.l
Executable file
289
engine/console/BASscan.l
Executable file
@ -0,0 +1,289 @@
|
||||
%{
|
||||
#define YYLMAX 4096
|
||||
|
||||
#include <stdio.h>
|
||||
#include "platform/platform.h"
|
||||
#include "core/stringTable.h"
|
||||
#include "console/console.h"
|
||||
#define _CMDGRAM_H_
|
||||
#include "console/compiler.h"
|
||||
#include "console/basgram.h"
|
||||
|
||||
using namespace Compiler;
|
||||
|
||||
#define YY_NEVER_INTERACTIVE 1
|
||||
|
||||
// Some basic parsing primitives...
|
||||
static int Sc_ScanString(int ret);
|
||||
static int Sc_ScanNum();
|
||||
static int Sc_ScanVar();
|
||||
static int Sc_ScanHex();
|
||||
|
||||
// Deal with debuggability of FLEX.
|
||||
#ifdef TORQUE_DEBUG
|
||||
#define FLEX_DEBUG 1
|
||||
#else
|
||||
#define FLEX_DEBUG 0
|
||||
#endif
|
||||
|
||||
//#undef input
|
||||
//#undef unput
|
||||
#undef BASgetc
|
||||
int BASgetc();
|
||||
|
||||
#define YY_INPUT(buf,result,max_size) \
|
||||
{ \
|
||||
int c = '*', n; \
|
||||
for ( n = 0; n < max_size && \
|
||||
(c = BASgetc()) != EOF && c != '\n'; ++n ) \
|
||||
buf[n] = (char) c; \
|
||||
if ( c == '\n' ) \
|
||||
buf[n++] = (char) c; \
|
||||
result = n; \
|
||||
}
|
||||
|
||||
static int lineIndex;
|
||||
|
||||
// Prototypes
|
||||
void BASSetScanBuffer(const char *sb, const char *fn);
|
||||
void BASerror(char * s, ...);
|
||||
|
||||
// Error reporting
|
||||
void CMDerror(char * s, ...);
|
||||
|
||||
// Reset the parser.
|
||||
void CMDrestart(FILE *in);
|
||||
|
||||
%}
|
||||
|
||||
DIGIT [0-9]
|
||||
INTEGER {DIGIT}+
|
||||
FLOAT ({INTEGER}\.{INTEGER})|({INTEGER}(\.{INTEGER})?[eE][+-]?{INTEGER})
|
||||
LETTER [A-Za-z_]
|
||||
FILECHAR [A-Za-z_\.]
|
||||
VARMID [:A-Za-z0-9_]
|
||||
IDTAIL [A-Za-z0-9_]
|
||||
VARTAIL {VARMID}*{IDTAIL}
|
||||
VAR [$%]{LETTER}{VARTAIL}*
|
||||
ID {LETTER}{IDTAIL}*
|
||||
ILID [$%]{DIGIT}+{LETTER}{VARTAIL}*
|
||||
FILENAME {FILECHAR}+
|
||||
SPACE [ \t\v\f]
|
||||
HEXDIGIT [a-fA-F0-9]
|
||||
|
||||
%%
|
||||
;
|
||||
{SPACE}+ { }
|
||||
"//"[^\n\r]* ;
|
||||
"rem"{SPACE}+[^\n\r]* ;
|
||||
[\r] ;
|
||||
[\n] { lineIndex++; }
|
||||
\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); }
|
||||
\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); }
|
||||
"==" return(BASlval.i = opEQ);
|
||||
"!=" return(BASlval.i = opNE);
|
||||
">=" return(BASlval.i = opGE);
|
||||
"<=" return(BASlval.i = opLE);
|
||||
"&&" return(BASlval.i = opAND);
|
||||
"||" return(BASlval.i = opOR);
|
||||
"::" return(BASlval.i = opCOLONCOLON);
|
||||
"--" return(BASlval.i = opMINUSMINUS);
|
||||
"++" return(BASlval.i = opPLUSPLUS);
|
||||
"$=" return(BASlval.i = opSTREQ);
|
||||
"!$=" return(BASlval.i = opSTRNE);
|
||||
"<<" return(BASlval.i = opSHL);
|
||||
">>" return(BASlval.i = opSHR);
|
||||
"+=" return(BASlval.i = opPLASN);
|
||||
"-=" return(BASlval.i = opMIASN);
|
||||
"*=" return(BASlval.i = opMLASN);
|
||||
"/=" return(BASlval.i = opDVASN);
|
||||
"%=" return(BASlval.i = opMODASN);
|
||||
"&=" return(BASlval.i = opANDASN);
|
||||
"^=" return(BASlval.i = opXORASN);
|
||||
"|=" return(BASlval.i = opORASN);
|
||||
"<<=" return(BASlval.i = opSLASN);
|
||||
">>=" return(BASlval.i = opSRASN);
|
||||
"NL" {BASlval.i = '\n'; return '@'; }
|
||||
"TAB" {BASlval.i = '\t'; return '@'; }
|
||||
"SPC" {BASlval.i = ' '; return '@'; }
|
||||
"@" {BASlval.i = 0; return '@'; }
|
||||
"?" |
|
||||
"[" |
|
||||
"]" |
|
||||
"(" |
|
||||
")" |
|
||||
"+" |
|
||||
"-" |
|
||||
"*" |
|
||||
"/" |
|
||||
"<" |
|
||||
">" |
|
||||
"|" |
|
||||
"." |
|
||||
"!" |
|
||||
":" |
|
||||
";" |
|
||||
"{" |
|
||||
"}" |
|
||||
"," |
|
||||
"&" |
|
||||
"%" |
|
||||
"^" |
|
||||
"~" |
|
||||
"=" { return(BASlval.i = BAStext[0]); }
|
||||
"or" { BASlval.i = lineIndex; return(rwCASEOR); }
|
||||
"break" { BASlval.i = lineIndex; return(rwBREAK); }
|
||||
"return" { BASlval.i = lineIndex; return(rwRETURN); }
|
||||
"else" { BASlval.i = lineIndex; return(rwELSE); }
|
||||
"while" { BASlval.i = lineIndex; return(rwWHILE); }
|
||||
"if" { BASlval.i = lineIndex; return(rwIF); }
|
||||
"then" { BASlval.i = lineIndex; return(rwTHEN); }
|
||||
"do" { BASlval.i = lineIndex; return(rwBEGIN); }
|
||||
"begin" { BASlval.i = lineIndex; return(rwBEGIN); }
|
||||
"end" { BASlval.i = lineIndex; return(rwEND); }
|
||||
"for" { BASlval.i = lineIndex; return(rwFOR); }
|
||||
"cfor" { BASlval.i = lineIndex; return(rwCFOR); }
|
||||
"to" { BASlval.i = lineIndex; return(rwTO); }
|
||||
"step" { BASlval.i = lineIndex; return(rwSTEP); }
|
||||
"continue" { BASlval.i = lineIndex; return(rwCONTINUE); }
|
||||
"function" { BASlval.i = lineIndex; return(rwDEFINE); }
|
||||
"sub" { BASlval.i = lineIndex; return(rwDEFINE); }
|
||||
"new" { BASlval.i = lineIndex; return(rwDECLARE); }
|
||||
"datablock" { BASlval.i = lineIndex; return(rwDATABLOCK); }
|
||||
"case" { BASlval.i = lineIndex; return(rwCASE); }
|
||||
"switch$" { BASlval.i = lineIndex; return(rwSWITCHSTR); }
|
||||
"switch" { BASlval.i = lineIndex; return(rwSWITCH); }
|
||||
"default" { BASlval.i = lineIndex; return(rwDEFAULT); }
|
||||
"package" { BASlval.i = lineIndex; return(rwPACKAGE); }
|
||||
"true" { BASlval.i = 1; return INTCONST; }
|
||||
"false" { BASlval.i = 0; return INTCONST; }
|
||||
{VAR} return(Sc_ScanVar());
|
||||
{ID} { BAStext[BASleng] = 0; BASlval.s = StringTable->insert(BAStext); return(IDENT); }
|
||||
0[xX]{HEXDIGIT}+ return(Sc_ScanHex());
|
||||
{INTEGER} { BAStext[BASleng] = 0; BASlval.i = atoi(BAStext); return INTCONST; }
|
||||
{FLOAT} return Sc_ScanNum();
|
||||
{ILID} return(ILLEGAL_TOKEN);
|
||||
. return(ILLEGAL_TOKEN);
|
||||
%%
|
||||
|
||||
/*
|
||||
* Scan character constant.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Scan identifier.
|
||||
*/
|
||||
|
||||
static const char *scanBuffer;
|
||||
static const char *fileName;
|
||||
static int scanIndex;
|
||||
|
||||
const char * BASGetCurrentFile()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
int BASGetCurrentLine()
|
||||
{
|
||||
return lineIndex;
|
||||
}
|
||||
|
||||
void BASerror(char *, ...)
|
||||
{
|
||||
gSyntaxError = true;
|
||||
if(fileName)
|
||||
Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - Syntax error.",
|
||||
fileName, lineIndex);
|
||||
else
|
||||
Con::errorf(ConsoleLogEntry::Script, "Syntax error in input.");
|
||||
}
|
||||
|
||||
void BASSetScanBuffer(const char *sb, const char *fn)
|
||||
{
|
||||
scanBuffer = sb;
|
||||
fileName = fn;
|
||||
scanIndex = 0;
|
||||
lineIndex = 1;
|
||||
}
|
||||
|
||||
int BASgetc()
|
||||
{
|
||||
int ret = scanBuffer[scanIndex];
|
||||
if(ret)
|
||||
scanIndex++;
|
||||
else
|
||||
ret = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int BASwrap()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int Sc_ScanVar()
|
||||
{
|
||||
BAStext[BASleng] = 0;
|
||||
BASlval.s = StringTable->insert(BAStext);
|
||||
return(VAR);
|
||||
}
|
||||
/*
|
||||
* Scan string constant.
|
||||
*/
|
||||
|
||||
// Defined in CS_CMD.l
|
||||
extern void expandEscape(char *dest, const char *src);
|
||||
extern bool collapseEscape(char *buf);
|
||||
|
||||
static int charConv(int in)
|
||||
{
|
||||
switch(in)
|
||||
{
|
||||
case 'r':
|
||||
return '\r';
|
||||
case 'n':
|
||||
return '\n';
|
||||
case 't':
|
||||
return '\t';
|
||||
default:
|
||||
return in;
|
||||
}
|
||||
}
|
||||
|
||||
static int getHexDigit(char c)
|
||||
{
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if(c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if(c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int Sc_ScanString(int ret)
|
||||
{
|
||||
BAStext[BASleng - 1] = 0;
|
||||
if(!collapseEscape(BAStext+1))
|
||||
return -1;
|
||||
BASlval.str = (char *) consoleAlloc(dStrlen(BAStext));
|
||||
dStrcpy(BASlval.str, BAStext + 1);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
|
||||
static int Sc_ScanNum()
|
||||
{
|
||||
BAStext[BASleng] = 0;
|
||||
BASlval.f = atof(BAStext);
|
||||
return(FLTCONST);
|
||||
}
|
||||
|
||||
static int Sc_ScanHex()
|
||||
{
|
||||
int val = 0;
|
||||
dSscanf(BAStext, "%x", &val);
|
||||
BASlval.i = val;
|
||||
return INTCONST;
|
||||
}
|
||||
|
2140
engine/console/CMDgram.cc
Executable file
2140
engine/console/CMDgram.cc
Executable file
File diff suppressed because it is too large
Load Diff
526
engine/console/CMDgram.y
Executable file
526
engine/console/CMDgram.y
Executable file
@ -0,0 +1,526 @@
|
||||
%{
|
||||
|
||||
// Make sure we don't get gram.h twice.
|
||||
#define _CMDGRAM_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "console/console.h"
|
||||
#include "console/compiler.h"
|
||||
#include "console/consoleInternal.h"
|
||||
|
||||
#ifndef YYDEBUG
|
||||
#define YYDEBUG 0
|
||||
#endif
|
||||
|
||||
#define YYSSIZE 350
|
||||
|
||||
int outtext(char *fmt, ...);
|
||||
extern int serrors;
|
||||
|
||||
#define nil 0
|
||||
#undef YY_ARGS
|
||||
#define YY_ARGS(x) x
|
||||
|
||||
int CMDlex();
|
||||
void CMDerror(char *, ...);
|
||||
|
||||
#define alloca dMalloc
|
||||
|
||||
%}
|
||||
%{
|
||||
/* Reserved Word Definitions */
|
||||
%}
|
||||
%token <i> rwDEFINE rwENDDEF rwDECLARE
|
||||
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
|
||||
%token <i> rwIF rwNIL rwRETURN rwWHILE rwDO
|
||||
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
|
||||
%token <i> rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
|
||||
%token <i> rwCASEOR rwPACKAGE rwNAMESPACE rwCLASS
|
||||
%token ILLEGAL_TOKEN
|
||||
%{
|
||||
/* Constants and Identifier Definitions */
|
||||
%}
|
||||
%token <c> CHRCONST
|
||||
%token <i> INTCONST
|
||||
%token <s> TTAG
|
||||
%token <s> VAR
|
||||
%token <s> IDENT
|
||||
%token <str> STRATOM
|
||||
%token <str> TAGATOM
|
||||
%token <f> FLTCONST
|
||||
|
||||
%{
|
||||
/* Operator Definitions */
|
||||
%}
|
||||
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
|
||||
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
|
||||
%token <i> opMINUSMINUS opPLUSPLUS
|
||||
%token <i> STMT_SEP
|
||||
%token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN
|
||||
%token <i> opXORASN opORASN opSLASN opSRASN opCAT
|
||||
%token <i> opEQ opNE opGE opLE opAND opOR opSTREQ
|
||||
%token <i> opCOLONCOLON
|
||||
|
||||
%union {
|
||||
char c;
|
||||
int i;
|
||||
const char * s;
|
||||
char * str;
|
||||
double f;
|
||||
StmtNode * stmt;
|
||||
ExprNode * expr;
|
||||
SlotAssignNode * slist;
|
||||
VarNode * var;
|
||||
SlotDecl slot;
|
||||
ObjectBlockDecl odcl;
|
||||
ObjectDeclNode * od;
|
||||
AssignDecl asn;
|
||||
IfStmtNode * ifnode;
|
||||
}
|
||||
|
||||
%type <s> parent_block
|
||||
%type <ifnode> case_block
|
||||
%type <stmt> switch_stmt
|
||||
%type <stmt> decl
|
||||
%type <stmt> decl_list
|
||||
%type <stmt> package_decl
|
||||
%type <stmt> fn_decl_stmt
|
||||
%type <stmt> fn_decl_list
|
||||
%type <stmt> statement_list
|
||||
%type <stmt> stmt
|
||||
%type <expr> expr_list
|
||||
%type <expr> expr_list_decl
|
||||
%type <expr> aidx_expr
|
||||
%type <expr> funcall_expr
|
||||
%type <expr> object_name
|
||||
%type <expr> object_args
|
||||
%type <expr> stmt_expr
|
||||
%type <expr> case_expr
|
||||
%type <expr> class_name_expr
|
||||
%type <stmt> if_stmt
|
||||
%type <stmt> while_stmt
|
||||
%type <stmt> for_stmt
|
||||
%type <stmt> stmt_block
|
||||
%type <stmt> datablock_decl
|
||||
%type <od> object_decl
|
||||
%type <od> object_decl_list
|
||||
%type <odcl> object_declare_block
|
||||
%type <expr> expr
|
||||
%type <slist> slot_assign_list
|
||||
%type <slist> slot_assign
|
||||
%type <slot> slot_acc
|
||||
%type <stmt> expression_stmt
|
||||
%type <var> var_list
|
||||
%type <var> var_list_decl
|
||||
%type <asn> assign_op_struct
|
||||
|
||||
%left '['
|
||||
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '='
|
||||
%left '?' ':'
|
||||
%left opOR
|
||||
%left opAND
|
||||
%left '|'
|
||||
%left '^'
|
||||
%left '&'
|
||||
%left opEQ opNE
|
||||
%left '<' opLE '>' opGE
|
||||
%left '@' opCAT opSTREQ opSTRNE
|
||||
%left opSHL opSHR
|
||||
%left '+' '-'
|
||||
%left '*' '/' '%'
|
||||
%right '!' '~' opPLUSPLUS opMINUSMINUS UNARY
|
||||
%left '.'
|
||||
|
||||
%%
|
||||
|
||||
start
|
||||
: decl_list
|
||||
{ }
|
||||
;
|
||||
|
||||
decl_list
|
||||
:
|
||||
{ $$ = nil; }
|
||||
| decl_list decl
|
||||
{ if(!statementList) { statementList = $2; } else { statementList->append($2); } }
|
||||
;
|
||||
|
||||
decl
|
||||
: stmt
|
||||
{ $$ = $1; }
|
||||
| fn_decl_stmt
|
||||
{ $$ = $1; }
|
||||
| package_decl
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
package_decl
|
||||
: rwPACKAGE IDENT '{' fn_decl_list '}' ';'
|
||||
{ $$ = $4; for(StmtNode *walk = ($4);walk;walk = walk->getNext() ) walk->setPackage($2); }
|
||||
;
|
||||
|
||||
fn_decl_list
|
||||
: fn_decl_stmt
|
||||
{ $$ = $1; }
|
||||
| fn_decl_list fn_decl_stmt
|
||||
{ $$ = $1; ($1)->append($2); }
|
||||
;
|
||||
|
||||
statement_list
|
||||
:
|
||||
{ $$ = nil; }
|
||||
| statement_list stmt
|
||||
{ if(!$1) { $$ = $2; } else { ($1)->append($2); $$ = $1; } }
|
||||
;
|
||||
|
||||
stmt
|
||||
: if_stmt
|
||||
| while_stmt
|
||||
| for_stmt
|
||||
| datablock_decl
|
||||
| switch_stmt
|
||||
| rwBREAK ';'
|
||||
{ $$ = BreakStmtNode::alloc(); }
|
||||
| rwCONTINUE ';'
|
||||
{ $$ = ContinueStmtNode::alloc(); }
|
||||
| rwRETURN ';'
|
||||
{ $$ = ReturnStmtNode::alloc(NULL); }
|
||||
| rwRETURN expr ';'
|
||||
{ $$ = ReturnStmtNode::alloc($2); }
|
||||
| expression_stmt ';'
|
||||
{ $$ = $1; }
|
||||
| TTAG '=' expr ';'
|
||||
{ $$ = TTagSetStmtNode::alloc($1, $3, NULL); }
|
||||
| TTAG '=' expr ',' expr ';'
|
||||
{ $$ = TTagSetStmtNode::alloc($1, $3, $5); }
|
||||
;
|
||||
|
||||
fn_decl_stmt
|
||||
: rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}'
|
||||
{ $$ = FunctionDeclStmtNode::alloc($2, NULL, $4, $7); }
|
||||
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}'
|
||||
{ $$ = FunctionDeclStmtNode::alloc($4, $2, $6, $9); }
|
||||
;
|
||||
|
||||
var_list_decl
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| var_list
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
var_list
|
||||
: VAR
|
||||
{ $$ = VarNode::alloc($1, NULL); }
|
||||
| var_list ',' VAR
|
||||
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)VarNode::alloc($3, NULL)); }
|
||||
;
|
||||
|
||||
datablock_decl
|
||||
: rwDATABLOCK IDENT '(' IDENT parent_block ')' '{' slot_assign_list '}' ';'
|
||||
{ $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $5, $8, NULL, true); }
|
||||
;
|
||||
|
||||
object_decl
|
||||
: rwDECLARE class_name_expr '(' object_name parent_block object_args ')' '{' object_declare_block '}'
|
||||
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $9.slots, $9.decls, false); }
|
||||
| rwDECLARE class_name_expr '(' object_name parent_block object_args ')'
|
||||
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false); }
|
||||
;
|
||||
|
||||
parent_block
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| ':' IDENT
|
||||
{ $$ = $2; }
|
||||
;
|
||||
|
||||
object_name
|
||||
:
|
||||
{ $$ = StrConstNode::alloc("", false); }
|
||||
| expr
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
object_args
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| ',' expr_list
|
||||
{ $$ = $2; }
|
||||
;
|
||||
|
||||
object_declare_block
|
||||
:
|
||||
{ $$.slots = NULL; $$.decls = NULL; }
|
||||
| slot_assign_list
|
||||
{ $$.slots = $1; $$.decls = NULL; }
|
||||
| object_decl_list
|
||||
{ $$.slots = NULL; $$.decls = $1; }
|
||||
| slot_assign_list object_decl_list
|
||||
{ $$.slots = $1; $$.decls = $2; }
|
||||
;
|
||||
|
||||
object_decl_list
|
||||
: object_decl ';'
|
||||
{ $$ = $1; }
|
||||
| object_decl_list object_decl ';'
|
||||
{ $1->append($2); $$ = $1; }
|
||||
;
|
||||
|
||||
stmt_block
|
||||
: '{' statement_list '}'
|
||||
{ $$ = $2; }
|
||||
| stmt
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
switch_stmt
|
||||
: rwSWITCH '(' expr ')' '{' case_block '}'
|
||||
{ $$ = $6; $6->propagateSwitchExpr($3, false); }
|
||||
| rwSWITCHSTR '(' expr ')' '{' case_block '}'
|
||||
{ $$ = $6; $6->propagateSwitchExpr($3, true); }
|
||||
;
|
||||
|
||||
case_block
|
||||
: rwCASE case_expr ':' statement_list
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
|
||||
| rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, $7, false); }
|
||||
| rwCASE case_expr ':' statement_list case_block
|
||||
{ $$ = IfStmtNode::alloc($1, $2, $4, $5, true); }
|
||||
;
|
||||
|
||||
case_expr
|
||||
: expr
|
||||
{ $$ = $1;}
|
||||
| case_expr rwCASEOR expr
|
||||
{ ($1)->append($3); $$=$1; }
|
||||
;
|
||||
|
||||
if_stmt
|
||||
: rwIF '(' expr ')' stmt_block
|
||||
{ $$ = IfStmtNode::alloc($1, $3, $5, NULL, false); }
|
||||
| rwIF '(' expr ')' stmt_block rwELSE stmt_block
|
||||
{ $$ = IfStmtNode::alloc($1, $3, $5, $7, false); }
|
||||
;
|
||||
|
||||
while_stmt
|
||||
: rwWHILE '(' expr ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, nil, $3, nil, $5, false); }
|
||||
| rwDO stmt_block rwWHILE '(' expr ')'
|
||||
{ $$ = LoopStmtNode::alloc($3, nil, $5, nil, $2, true); }
|
||||
;
|
||||
|
||||
for_stmt
|
||||
: rwFOR '(' expr ';' expr ';' expr ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, $3, $5, $7, $9, false); }
|
||||
| rwFOR '(' expr ';' expr ';' ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, $3, $5, NULL, $8, false); }
|
||||
| rwFOR '(' expr ';' ';' expr ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, $3, NULL, $6, $8, false); }
|
||||
| rwFOR '(' expr ';' ';' ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, $3, NULL, NULL, $7, false); }
|
||||
| rwFOR '(' ';' expr ';' expr ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, NULL, $4, $6, $8, false); }
|
||||
| rwFOR '(' ';' expr ';' ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, NULL, $4, NULL, $7, false); }
|
||||
| rwFOR '(' ';' ';' expr ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, NULL, NULL, $5, $7, false); }
|
||||
| rwFOR '(' ';' ';' ')' stmt_block
|
||||
{ $$ = LoopStmtNode::alloc($1, NULL, NULL, NULL, $6, false); }
|
||||
;
|
||||
|
||||
expression_stmt
|
||||
: stmt_expr
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
expr
|
||||
: stmt_expr
|
||||
{ $$ = $1; }
|
||||
| '(' expr ')'
|
||||
{ $$ = $2; }
|
||||
| expr '^' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '%' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '&' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '|' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '+' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '-' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '*' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '/' expr
|
||||
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
|
||||
| '-' expr %prec UNARY
|
||||
{ $$ = FloatUnaryExprNode::alloc($1, $2); }
|
||||
| '*' expr %prec UNARY
|
||||
{ $$ = TTagDerefNode::alloc($2); }
|
||||
| TTAG
|
||||
{ $$ = TTagExprNode::alloc($1); }
|
||||
| expr '?' expr ':' expr
|
||||
{ $$ = ConditionalExprNode::alloc($1, $3, $5); }
|
||||
| expr '<' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr '>' expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opGE expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opLE expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opEQ expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opNE expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opOR expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opSHL expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opSHR expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opAND expr
|
||||
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
|
||||
| expr opSTREQ expr
|
||||
{ $$ = StreqExprNode::alloc($1, $3, true); }
|
||||
| expr opSTRNE expr
|
||||
{ $$ = StreqExprNode::alloc($1, $3, false); }
|
||||
| expr '@' expr
|
||||
{ $$ = StrcatExprNode::alloc($1, $3, $2); }
|
||||
| '!' expr
|
||||
{ $$ = IntUnaryExprNode::alloc($1, $2); }
|
||||
| '~' expr
|
||||
{ $$ = IntUnaryExprNode::alloc($1, $2); }
|
||||
| TAGATOM
|
||||
{ $$ = StrConstNode::alloc($1, true); }
|
||||
| FLTCONST
|
||||
{ $$ = FloatNode::alloc($1); }
|
||||
| INTCONST
|
||||
{ $$ = IntNode::alloc($1); }
|
||||
| rwBREAK
|
||||
{ $$ = ConstantNode::alloc(StringTable->insert("break")); }
|
||||
| slot_acc
|
||||
{ $$ = SlotAccessNode::alloc($1.object, $1.array, $1.slotName); }
|
||||
| IDENT
|
||||
{ $$ = ConstantNode::alloc($1); }
|
||||
| STRATOM
|
||||
{ $$ = StrConstNode::alloc($1, false); }
|
||||
| VAR
|
||||
{ $$ = (ExprNode*)VarNode::alloc($1, NULL); }
|
||||
| VAR '[' aidx_expr ']'
|
||||
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
|
||||
;
|
||||
|
||||
slot_acc
|
||||
: expr '.' IDENT
|
||||
{ $$.object = $1; $$.slotName = $3; $$.array = NULL; }
|
||||
| expr '.' IDENT '[' aidx_expr ']'
|
||||
{ $$.object = $1; $$.slotName = $3; $$.array = $5; }
|
||||
;
|
||||
|
||||
class_name_expr
|
||||
: IDENT
|
||||
{ $$ = ConstantNode::alloc($1); }
|
||||
| '(' expr ')'
|
||||
{ $$ = $2; }
|
||||
;
|
||||
|
||||
assign_op_struct
|
||||
: opPLUSPLUS
|
||||
{ $$.token = '+'; $$.expr = FloatNode::alloc(1); }
|
||||
| opMINUSMINUS
|
||||
{ $$.token = '-'; $$.expr = FloatNode::alloc(1); }
|
||||
| opPLASN expr
|
||||
{ $$.token = '+'; $$.expr = $2; }
|
||||
| opMIASN expr
|
||||
{ $$.token = '-'; $$.expr = $2; }
|
||||
| opMLASN expr
|
||||
{ $$.token = '*'; $$.expr = $2; }
|
||||
| opDVASN expr
|
||||
{ $$.token = '/'; $$.expr = $2; }
|
||||
| opMODASN expr
|
||||
{ $$.token = '%'; $$.expr = $2; }
|
||||
| opANDASN expr
|
||||
{ $$.token = '&'; $$.expr = $2; }
|
||||
| opXORASN expr
|
||||
{ $$.token = '^'; $$.expr = $2; }
|
||||
| opORASN expr
|
||||
{ $$.token = '|'; $$.expr = $2; }
|
||||
| opSLASN expr
|
||||
{ $$.token = opSHL; $$.expr = $2; }
|
||||
| opSRASN expr
|
||||
{ $$.token = opSHR; $$.expr = $2; }
|
||||
;
|
||||
|
||||
stmt_expr
|
||||
: funcall_expr
|
||||
{ $$ = $1; }
|
||||
| object_decl
|
||||
{ $$ = $1; }
|
||||
| VAR '=' expr
|
||||
{ $$ = AssignExprNode::alloc($1, NULL, $3); }
|
||||
| VAR '[' aidx_expr ']' '=' expr
|
||||
{ $$ = AssignExprNode::alloc($1, $3, $6); }
|
||||
| VAR assign_op_struct
|
||||
{ $$ = AssignOpExprNode::alloc($1, NULL, $2.expr, $2.token); }
|
||||
| VAR '[' aidx_expr ']' assign_op_struct
|
||||
{ $$ = AssignOpExprNode::alloc($1, $3, $5.expr, $5.token); }
|
||||
| slot_acc assign_op_struct
|
||||
{ $$ = SlotAssignOpNode::alloc($1.object, $1.slotName, $1.array, $2.token, $2.expr); }
|
||||
| slot_acc '=' expr
|
||||
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $3); }
|
||||
| slot_acc '=' '{' expr_list '}'
|
||||
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $4); }
|
||||
;
|
||||
|
||||
funcall_expr
|
||||
: IDENT '(' expr_list_decl ')'
|
||||
{ $$ = FuncCallExprNode::alloc($1, NULL, $3, false); }
|
||||
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
|
||||
{ $$ = FuncCallExprNode::alloc($3, $1, $5, false); }
|
||||
| expr '.' IDENT '(' expr_list_decl ')'
|
||||
{ $1->append($5); $$ = FuncCallExprNode::alloc($3, NULL, $1, true); }
|
||||
;
|
||||
|
||||
expr_list_decl
|
||||
:
|
||||
{ $$ = NULL; }
|
||||
| expr_list
|
||||
{ $$ = $1; }
|
||||
;
|
||||
|
||||
expr_list
|
||||
: expr
|
||||
{ $$ = $1; }
|
||||
| expr_list ',' expr
|
||||
{ ($1)->append($3); $$ = $1; }
|
||||
;
|
||||
|
||||
slot_assign_list
|
||||
: slot_assign
|
||||
{ $$ = $1; }
|
||||
| slot_assign_list slot_assign
|
||||
{ $1->append($2); $$ = $1; }
|
||||
;
|
||||
|
||||
slot_assign
|
||||
: IDENT '=' expr ';'
|
||||
{ $$ = SlotAssignNode::alloc(NULL, NULL, $1, $3); }
|
||||
| rwDATABLOCK '=' expr ';'
|
||||
{ $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); }
|
||||
| IDENT '[' aidx_expr ']' '=' expr ';'
|
||||
{ $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); }
|
||||
;
|
||||
|
||||
aidx_expr
|
||||
: expr
|
||||
{ $$ = $1; }
|
||||
| aidx_expr ',' expr
|
||||
{ $$ = CommaCatExprNode::alloc($1, $3); }
|
||||
;
|
||||
%%
|
||||
|
2426
engine/console/CMDscan.cc
Executable file
2426
engine/console/CMDscan.cc
Executable file
File diff suppressed because it is too large
Load Diff
541
engine/console/CMDscan.l
Executable file
541
engine/console/CMDscan.l
Executable file
@ -0,0 +1,541 @@
|
||||
%{
|
||||
#define YYLMAX 4096
|
||||
|
||||
#include <stdio.h>
|
||||
#include "platform/platform.h"
|
||||
#include "core/stringTable.h"
|
||||
#include "console/console.h"
|
||||
#include "console/compiler.h"
|
||||
|
||||
using namespace Compiler;
|
||||
|
||||
#define YY_NEVER_INTERACTIVE 1
|
||||
|
||||
// Some basic parsing primitives...
|
||||
static int Sc_ScanString(int ret);
|
||||
static int Sc_ScanNum();
|
||||
static int Sc_ScanVar();
|
||||
static int Sc_ScanHex();
|
||||
|
||||
// Deal with debuggability of FLEX.
|
||||
#ifdef TORQUE_DEBUG
|
||||
#define FLEX_DEBUG 1
|
||||
#else
|
||||
#define FLEX_DEBUG 0
|
||||
#endif
|
||||
|
||||
// Install our own input code...
|
||||
#undef CMDgetc
|
||||
int CMDgetc();
|
||||
|
||||
// Hack to make windows lex happy.
|
||||
#ifndef isatty
|
||||
inline int isatty(int) { return 0; }
|
||||
#endif
|
||||
|
||||
// Wrap our getc, so that lex doesn't try to do its own buffering/file IO.
|
||||
#define YY_INPUT(buf,result,max_size) \
|
||||
{ \
|
||||
int c = '*', n; \
|
||||
for ( n = 0; n < max_size && \
|
||||
(c = CMDgetc()) != EOF && c != '\n'; ++n ) \
|
||||
buf[n] = (char) c; \
|
||||
if ( c == '\n' ) \
|
||||
buf[n++] = (char) c; \
|
||||
result = n; \
|
||||
}
|
||||
|
||||
// General helper stuff.
|
||||
static int lineIndex;
|
||||
|
||||
// File state
|
||||
void CMDSetScanBuffer(const char *sb, const char *fn);
|
||||
const char * CMDgetFileLine(int &lineNumber);
|
||||
|
||||
// Error reporting
|
||||
void CMDerror(char * s, ...);
|
||||
|
||||
// Reset the parser.
|
||||
void CMDrestart(FILE *in);
|
||||
|
||||
%}
|
||||
|
||||
DIGIT [0-9]
|
||||
INTEGER {DIGIT}+
|
||||
FLOAT ({INTEGER}\.{INTEGER})|({INTEGER}(\.{INTEGER})?[eE][+-]?{INTEGER})|(\.{INTEGER})|((\.{INTEGER})?[eE][+-]?{INTEGER})
|
||||
LETTER [A-Za-z_]
|
||||
FILECHAR [A-Za-z_\.]
|
||||
VARMID [:A-Za-z0-9_]
|
||||
IDTAIL [A-Za-z0-9_]
|
||||
VARTAIL {VARMID}*{IDTAIL}
|
||||
VAR [$%]{LETTER}{VARTAIL}*
|
||||
ID {LETTER}{IDTAIL}*
|
||||
ILID [$%]{DIGIT}+{LETTER}{VARTAIL}*
|
||||
FILENAME {FILECHAR}+
|
||||
SPACE [ \t\v\f]
|
||||
HEXDIGIT [a-fA-F0-9]
|
||||
|
||||
%%
|
||||
;
|
||||
{SPACE}+ { }
|
||||
"//"[^\n\r]* ;
|
||||
[\r] ;
|
||||
[\n] {lineIndex++;}
|
||||
\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); }
|
||||
\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); }
|
||||
"==" return(CMDlval.i = opEQ);
|
||||
"!=" return(CMDlval.i = opNE);
|
||||
">=" return(CMDlval.i = opGE);
|
||||
"<=" return(CMDlval.i = opLE);
|
||||
"&&" return(CMDlval.i = opAND);
|
||||
"||" return(CMDlval.i = opOR);
|
||||
"::" return(CMDlval.i = opCOLONCOLON);
|
||||
"--" return(CMDlval.i = opMINUSMINUS);
|
||||
"++" return(CMDlval.i = opPLUSPLUS);
|
||||
"$=" return(CMDlval.i = opSTREQ);
|
||||
"!$=" return(CMDlval.i = opSTRNE);
|
||||
"<<" return(CMDlval.i = opSHL);
|
||||
">>" return(CMDlval.i = opSHR);
|
||||
"+=" return(CMDlval.i = opPLASN);
|
||||
"-=" return(CMDlval.i = opMIASN);
|
||||
"*=" return(CMDlval.i = opMLASN);
|
||||
"/=" return(CMDlval.i = opDVASN);
|
||||
"%=" return(CMDlval.i = opMODASN);
|
||||
"&=" return(CMDlval.i = opANDASN);
|
||||
"^=" return(CMDlval.i = opXORASN);
|
||||
"|=" return(CMDlval.i = opORASN);
|
||||
"<<=" return(CMDlval.i = opSLASN);
|
||||
">>=" return(CMDlval.i = opSRASN);
|
||||
"NL" {CMDlval.i = '\n'; return '@'; }
|
||||
"TAB" {CMDlval.i = '\t'; return '@'; }
|
||||
"SPC" {CMDlval.i = ' '; return '@'; }
|
||||
"@" {CMDlval.i = 0; return '@'; }
|
||||
"/*" {
|
||||
register int c = 0, l;
|
||||
for ( ; ; )
|
||||
{
|
||||
l = c;
|
||||
c = yyinput();
|
||||
|
||||
// Is this an open comment?
|
||||
if ( c == EOF )
|
||||
{
|
||||
CMDerror( "unexpected end of file found in comment" );
|
||||
break;
|
||||
}
|
||||
|
||||
// Increment line numbers.
|
||||
else if ( c == '\n' )
|
||||
lineIndex++;
|
||||
|
||||
// Did we find the end of the comment?
|
||||
else if ( l == '*' && c == '/' )
|
||||
break;
|
||||
}
|
||||
}
|
||||
"?" |
|
||||
"[" |
|
||||
"]" |
|
||||
"(" |
|
||||
")" |
|
||||
"+" |
|
||||
"-" |
|
||||
"*" |
|
||||
"/" |
|
||||
"<" |
|
||||
">" |
|
||||
"|" |
|
||||
"." |
|
||||
"!" |
|
||||
":" |
|
||||
";" |
|
||||
"{" |
|
||||
"}" |
|
||||
"," |
|
||||
"&" |
|
||||
"%" |
|
||||
"^" |
|
||||
"~" |
|
||||
"=" { return(CMDlval.i = CMDtext[0]); }
|
||||
"or" { CMDlval.i = lineIndex; return(rwCASEOR); }
|
||||
"break" { CMDlval.i = lineIndex; return(rwBREAK); }
|
||||
"return" { CMDlval.i = lineIndex; return(rwRETURN); }
|
||||
"else" { CMDlval.i = lineIndex; return(rwELSE); }
|
||||
"while" { CMDlval.i = lineIndex; return(rwWHILE); }
|
||||
"do" { CMDlval.i = lineIndex; return(rwDO); }
|
||||
"if" { CMDlval.i = lineIndex; return(rwIF); }
|
||||
"for" { CMDlval.i = lineIndex; return(rwFOR); }
|
||||
"continue" { CMDlval.i = lineIndex; return(rwCONTINUE); }
|
||||
"function" { CMDlval.i = lineIndex; return(rwDEFINE); }
|
||||
"new" { CMDlval.i = lineIndex; return(rwDECLARE); }
|
||||
"datablock" { CMDlval.i = lineIndex; return(rwDATABLOCK); }
|
||||
"case" { CMDlval.i = lineIndex; return(rwCASE); }
|
||||
"switch$" { CMDlval.i = lineIndex; return(rwSWITCHSTR); }
|
||||
"switch" { CMDlval.i = lineIndex; return(rwSWITCH); }
|
||||
"default" { CMDlval.i = lineIndex; return(rwDEFAULT); }
|
||||
"package" { CMDlval.i = lineIndex; return(rwPACKAGE); }
|
||||
"namespace" { CMDlval.i = lineIndex; return(rwNAMESPACE); }
|
||||
"true" { CMDlval.i = 1; return INTCONST; }
|
||||
"false" { CMDlval.i = 0; return INTCONST; }
|
||||
{VAR} return(Sc_ScanVar());
|
||||
{ID} { CMDtext[CMDleng] = 0; CMDlval.s = StringTable->insert(CMDtext); return(IDENT); }
|
||||
0[xX]{HEXDIGIT}+ return(Sc_ScanHex());
|
||||
{INTEGER} { CMDtext[CMDleng] = 0; CMDlval.i = dAtoi(CMDtext); return INTCONST; }
|
||||
{FLOAT} return Sc_ScanNum();
|
||||
{ILID} return(ILLEGAL_TOKEN);
|
||||
. return(ILLEGAL_TOKEN);
|
||||
%%
|
||||
|
||||
static const char *scanBuffer;
|
||||
static const char *fileName;
|
||||
static int scanIndex;
|
||||
|
||||
const char * CMDGetCurrentFile()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
int CMDGetCurrentLine()
|
||||
{
|
||||
return lineIndex;
|
||||
}
|
||||
|
||||
extern bool gConsoleSyntaxError;
|
||||
|
||||
void CMDerror(char *format, ...)
|
||||
{
|
||||
Compiler::gSyntaxError = true;
|
||||
|
||||
const int BUFMAX = 1024;
|
||||
char tempBuf[BUFMAX];
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
#ifdef TORQUE_OS_WIN32
|
||||
_vsnprintf( tempBuf, BUFMAX, format, args );
|
||||
#else
|
||||
vsnprintf( tempBuf, BUFMAX, format, args );
|
||||
#endif
|
||||
|
||||
if(fileName)
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - %s", fileName, lineIndex, tempBuf);
|
||||
|
||||
#ifndef NO_ADVANCED_ERROR_REPORT
|
||||
// dhc - lineIndex is bogus. let's try to add some sanity back in.
|
||||
int i,j,n;
|
||||
char c;
|
||||
int linediv = 1;
|
||||
// first, walk the buffer, trying to detect line ending type.
|
||||
// this is imperfect, if inconsistant line endings exist...
|
||||
for (i=0; i<scanIndex; i++)
|
||||
{
|
||||
c = scanBuffer[i];
|
||||
if (c=='\r' && scanBuffer[i+1]=='\n') linediv = 2; // crlf detected
|
||||
if (c=='\r' || c=='\n' || c==0) break; // enough for us to stop.
|
||||
}
|
||||
// grab some of the chars starting at the error location - lineending.
|
||||
i = 1; j = 0; n = 1;
|
||||
// find prev lineending
|
||||
while (n<BUFMAX-8 && i<scanIndex) // cap at file start
|
||||
{
|
||||
c = scanBuffer[scanIndex-i];
|
||||
if ((c=='\r' || c=='\n') && i>BUFMAX>>2) break; // at least get a little data
|
||||
n++; i++;
|
||||
}
|
||||
// find next lineending
|
||||
while (n<BUFMAX-8 && j<BUFMAX>>1) // cap at half-buf-size forward
|
||||
{
|
||||
c = scanBuffer[scanIndex+j];
|
||||
if (c==0) break;
|
||||
if ((c=='\r' || c=='\n') && j>BUFMAX>>2) break; // at least get a little data
|
||||
n++; j++;
|
||||
}
|
||||
if (i) i--; // chop off extra linefeed.
|
||||
if (j) j--; // chop off extra linefeed.
|
||||
// build our little text block
|
||||
if (i) dStrncpy(tempBuf,scanBuffer+scanIndex-i,i);
|
||||
dStrncpy(tempBuf+i,"##", 2); // bracketing.
|
||||
tempBuf[i+2] = scanBuffer[scanIndex]; // copy the halt character.
|
||||
dStrncpy(tempBuf+i+3,"##", 2); // bracketing.
|
||||
if (j) dStrncpy(tempBuf+i+5,scanBuffer+scanIndex+1,j); // +1 to go past current char.
|
||||
tempBuf[i+j+5] = 0; // null terminate
|
||||
for(n=0; n<i+j+5; n++) // convert CR to LF if alone...
|
||||
if (tempBuf[n]=='\r' && tempBuf[n+1]!='\n') tempBuf[n] = '\n';
|
||||
// write out to console the advanced error report
|
||||
Con::warnf(ConsoleLogEntry::Script, ">>> Advanced script error report. Line %d.", lineIndex);
|
||||
Con::warnf(ConsoleLogEntry::Script, ">>> Some error context, with ## on sides of error halt:");
|
||||
Con::errorf(ConsoleLogEntry::Script, "%s", tempBuf);
|
||||
Con::warnf(ConsoleLogEntry::Script, ">>> Error report complete.\n");
|
||||
#endif
|
||||
|
||||
// Update the script-visible error buffer.
|
||||
const char *prevStr = Con::getVariable("$ScriptError");
|
||||
if (prevStr[0])
|
||||
dSprintf(tempBuf, sizeof(tempBuf), "%s\n%s Line: %d - Syntax error.", prevStr, fileName, lineIndex);
|
||||
else
|
||||
dSprintf(tempBuf, sizeof(tempBuf), "%s Line: %d - Syntax error.", fileName, lineIndex);
|
||||
Con::setVariable("$ScriptError", tempBuf);
|
||||
|
||||
// We also need to mark that we came up with a new error.
|
||||
static S32 sScriptErrorHash=1000;
|
||||
Con::setIntVariable("$ScriptErrorHash", sScriptErrorHash++);
|
||||
}
|
||||
else
|
||||
Con::errorf(ConsoleLogEntry::Script, tempBuf);
|
||||
}
|
||||
|
||||
void CMDSetScanBuffer(const char *sb, const char *fn)
|
||||
{
|
||||
scanBuffer = sb;
|
||||
fileName = fn;
|
||||
scanIndex = 0;
|
||||
lineIndex = 1;
|
||||
}
|
||||
|
||||
int CMDgetc()
|
||||
{
|
||||
int ret = scanBuffer[scanIndex];
|
||||
if(ret)
|
||||
scanIndex++;
|
||||
else
|
||||
ret = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int CMDwrap()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int Sc_ScanVar()
|
||||
{
|
||||
// Truncate the temp buffer...
|
||||
CMDtext[CMDleng] = 0;
|
||||
|
||||
// Make it a stringtable string!
|
||||
CMDlval.s = StringTable->insert(CMDtext);
|
||||
return(VAR);
|
||||
}
|
||||
|
||||
static int charConv(int in)
|
||||
{
|
||||
switch(in)
|
||||
{
|
||||
case 'r':
|
||||
return '\r';
|
||||
case 'n':
|
||||
return '\n';
|
||||
case 't':
|
||||
return '\t';
|
||||
default:
|
||||
return in;
|
||||
}
|
||||
}
|
||||
|
||||
static int getHexDigit(char c)
|
||||
{
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if(c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if(c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int Sc_ScanString(int ret)
|
||||
{
|
||||
CMDtext[CMDleng - 1] = 0;
|
||||
if(!collapseEscape(CMDtext+1))
|
||||
return -1;
|
||||
CMDlval.str = (char *) consoleAlloc(dStrlen(CMDtext));
|
||||
dStrcpy(CMDlval.str, CMDtext + 1);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
void expandEscape(char *dest, const char *src)
|
||||
{
|
||||
U8 c;
|
||||
while((c = (U8) *src++) != 0)
|
||||
{
|
||||
if(c == '\"')
|
||||
{
|
||||
*dest++ = '\\';
|
||||
*dest++ = '\"';
|
||||
}
|
||||
else if(c == '\\')
|
||||
{
|
||||
*dest++ = '\\';
|
||||
*dest++ = '\\';
|
||||
}
|
||||
else if(c == '\r')
|
||||
{
|
||||
*dest++ = '\\';
|
||||
*dest++ = 'r';
|
||||
}
|
||||
else if(c == '\n')
|
||||
{
|
||||
*dest++ = '\\';
|
||||
*dest++ = 'n';
|
||||
}
|
||||
else if(c == '\t')
|
||||
{
|
||||
*dest++ = '\\';
|
||||
*dest++ = 't';
|
||||
}
|
||||
else if(c == '\'')
|
||||
{
|
||||
*dest++ = '\\';
|
||||
*dest++ = '\'';
|
||||
}
|
||||
else if((c >= 1 && c <= 7) ||
|
||||
(c >= 11 && c <= 12) ||
|
||||
(c >= 14 && c <= 15))
|
||||
{
|
||||
/* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */
|
||||
static U8 expandRemap[15] = { 0x0,
|
||||
0x0,
|
||||
0x1,
|
||||
0x2,
|
||||
0x3,
|
||||
0x4,
|
||||
0x5,
|
||||
0x6,
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
0x7,
|
||||
0x8,
|
||||
0x0,
|
||||
0x9 };
|
||||
|
||||
*dest++ = '\\';
|
||||
*dest++ = 'c';
|
||||
if(c == 15)
|
||||
*dest++ = 'r';
|
||||
else if(c == 16)
|
||||
*dest++ = 'p';
|
||||
else if(c == 17)
|
||||
*dest++ = 'o';
|
||||
else
|
||||
*dest++ = expandRemap[c] + '0';
|
||||
}
|
||||
else if(c < 32)
|
||||
{
|
||||
*dest++ = '\\';
|
||||
*dest++ = 'x';
|
||||
S32 dig1 = c >> 4;
|
||||
S32 dig2 = c & 0xf;
|
||||
if(dig1 < 10)
|
||||
dig1 += '0';
|
||||
else
|
||||
dig1 += 'A' - 10;
|
||||
if(dig2 < 10)
|
||||
dig2 += '0';
|
||||
else
|
||||
dig2 += 'A' - 10;
|
||||
*dest++ = dig1;
|
||||
*dest++ = dig2;
|
||||
}
|
||||
else
|
||||
*dest++ = c;
|
||||
}
|
||||
*dest = '\0';
|
||||
}
|
||||
|
||||
bool collapseEscape(char *buf)
|
||||
{
|
||||
S32 len = dStrlen(buf) + 1;
|
||||
for(S32 i = 0; i < len;)
|
||||
{
|
||||
if(buf[i] == '\\')
|
||||
{
|
||||
if(buf[i+1] == 'x')
|
||||
{
|
||||
S32 dig1 = getHexDigit(buf[i+2]);
|
||||
if(dig1 == -1)
|
||||
return false;
|
||||
|
||||
S32 dig2 = getHexDigit(buf[i+3]);
|
||||
if(dig2 == -1)
|
||||
return false;
|
||||
buf[i] = dig1 * 16 + dig2;
|
||||
dMemmove(buf + i + 1, buf + i + 4, len - i - 3);
|
||||
len -= 3;
|
||||
i++;
|
||||
}
|
||||
else if(buf[i+1] == 'c')
|
||||
{
|
||||
/* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */
|
||||
static U8 collapseRemap[10] = { 0x1,
|
||||
0x2,
|
||||
0x3,
|
||||
0x4,
|
||||
0x5,
|
||||
0x6,
|
||||
0x7,
|
||||
0xb,
|
||||
0xc,
|
||||
0xe };
|
||||
|
||||
if(buf[i+2] == 'r')
|
||||
buf[i] = 15;
|
||||
else if(buf[i+2] == 'p')
|
||||
buf[i] = 16;
|
||||
else if(buf[i+2] == 'o')
|
||||
buf[i] = 17;
|
||||
else
|
||||
{
|
||||
int dig1 = buf[i+2] - '0';
|
||||
if(dig1 < 0 || dig1 > 9)
|
||||
return false;
|
||||
buf[i] = collapseRemap[dig1];
|
||||
}
|
||||
// Make sure we don't put 0x1 at the beginning of the string.
|
||||
if ((buf[i] == 0x1) && (i == 0))
|
||||
{
|
||||
buf[i] = 0x2;
|
||||
buf[i+1] = 0x1;
|
||||
dMemmove(buf + i + 2, buf + i + 3, len - i - 1);
|
||||
len -= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
dMemmove(buf + i + 1, buf + i + 3, len - i - 2);
|
||||
len -= 2;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[i] = charConv(buf[i+1]);
|
||||
dMemmove(buf + i + 1, buf + i + 2, len - i - 1);
|
||||
len--;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int Sc_ScanNum()
|
||||
{
|
||||
CMDtext[CMDleng] = 0;
|
||||
CMDlval.f = dAtof(CMDtext);
|
||||
return(FLTCONST);
|
||||
}
|
||||
|
||||
static int Sc_ScanHex()
|
||||
{
|
||||
S32 val = 0;
|
||||
dSscanf(CMDtext, "%x", &val);
|
||||
CMDlval.i = val;
|
||||
return INTCONST;
|
||||
}
|
||||
|
||||
void CMD_reset()
|
||||
{
|
||||
CMDrestart(NULL);
|
||||
}
|
461
engine/console/ast.h
Executable file
461
engine/console/ast.h
Executable file
@ -0,0 +1,461 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _AST_H_
|
||||
#define _AST_H_
|
||||
|
||||
class ExprEvalState;
|
||||
class Namespace;
|
||||
class SimObject;
|
||||
class SimGroup;
|
||||
|
||||
enum TypeReq {
|
||||
TypeReqNone,
|
||||
TypeReqUInt,
|
||||
TypeReqFloat,
|
||||
TypeReqString
|
||||
};
|
||||
|
||||
/// Representation of a node for the scripting language parser.
|
||||
///
|
||||
/// When the scripting language is evaluated, it is turned from a string representation,
|
||||
/// into a parse tree, thence into byte code, which is ultimately interpreted by the VM.
|
||||
///
|
||||
/// This is the base class for the nodes in the parse tree. There are a great many subclasses,
|
||||
/// each representing a different language construct.
|
||||
struct StmtNode
|
||||
{
|
||||
StmtNode *next; ///< Next entry in parse tree.
|
||||
|
||||
StmtNode();
|
||||
|
||||
/// @name next Accessors
|
||||
/// @{
|
||||
|
||||
///
|
||||
void append(StmtNode *next);
|
||||
StmtNode *getNext() { return next; }
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Debug Info
|
||||
/// @{
|
||||
|
||||
StringTableEntry dbgFileName; ///< Name of file this node is associated with.
|
||||
S32 dbgLineNumber; ///< Line number this node is associated with.
|
||||
/// @}
|
||||
|
||||
/// @name Breaking
|
||||
/// @{
|
||||
|
||||
void addBreakCount();
|
||||
void addBreakLine(U32 ip);
|
||||
/// @}
|
||||
|
||||
/// @name Compilation
|
||||
/// @{
|
||||
|
||||
virtual U32 precompileStmt(U32 loopCount) = 0;
|
||||
virtual U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint) = 0;
|
||||
virtual void setPackage(StringTableEntry packageName);
|
||||
/// @}
|
||||
};
|
||||
|
||||
struct BreakStmtNode : StmtNode
|
||||
{
|
||||
static BreakStmtNode *alloc();
|
||||
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
};
|
||||
|
||||
struct ContinueStmtNode : StmtNode
|
||||
{
|
||||
static ContinueStmtNode *alloc();
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
};
|
||||
|
||||
/// A mathematical expression.
|
||||
struct ExprNode : StmtNode
|
||||
{
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
|
||||
virtual U32 precompile(TypeReq type) = 0;
|
||||
virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type) = 0;
|
||||
virtual TypeReq getPreferredType() = 0;
|
||||
};
|
||||
|
||||
struct ReturnStmtNode : StmtNode
|
||||
{
|
||||
ExprNode *expr;
|
||||
|
||||
static ReturnStmtNode *alloc(ExprNode *expr);
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
};
|
||||
|
||||
struct IfStmtNode : StmtNode
|
||||
{
|
||||
ExprNode *testExpr;
|
||||
StmtNode *ifBlock, *elseBlock;
|
||||
U32 endifOffset;
|
||||
U32 elseOffset;
|
||||
bool integer;
|
||||
bool propagate;
|
||||
|
||||
static IfStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagateThrough);
|
||||
void propagateSwitchExpr(ExprNode *left, bool string);
|
||||
ExprNode *getSwitchOR(ExprNode *left, ExprNode *list, bool string);
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
};
|
||||
|
||||
struct LoopStmtNode : StmtNode
|
||||
{
|
||||
ExprNode *testExpr;
|
||||
ExprNode *initExpr;
|
||||
ExprNode *endLoopExpr;
|
||||
StmtNode *loopBlock;
|
||||
bool isDoLoop;
|
||||
U32 breakOffset;
|
||||
U32 continueOffset;
|
||||
U32 loopBlockStartOffset;
|
||||
bool integer;
|
||||
|
||||
static LoopStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, ExprNode *initExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop);
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
};
|
||||
|
||||
/// A binary mathematical expression (ie, left op right).
|
||||
struct BinaryExprNode : ExprNode
|
||||
{
|
||||
S32 op;
|
||||
ExprNode *left;
|
||||
ExprNode *right;
|
||||
};
|
||||
|
||||
struct FloatBinaryExprNode : BinaryExprNode
|
||||
{
|
||||
static FloatBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct ConditionalExprNode : ExprNode
|
||||
{
|
||||
ExprNode *testExpr;
|
||||
ExprNode *trueExpr;
|
||||
ExprNode *falseExpr;
|
||||
bool integer;
|
||||
static ConditionalExprNode *alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr);
|
||||
virtual U32 precompile(TypeReq type);
|
||||
virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
virtual TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct IntBinaryExprNode : BinaryExprNode
|
||||
{
|
||||
TypeReq subType;
|
||||
U32 operand;
|
||||
|
||||
static IntBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right);
|
||||
|
||||
void getSubTypeOperand();
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct StreqExprNode : BinaryExprNode
|
||||
{
|
||||
bool eq;
|
||||
static StreqExprNode *alloc(ExprNode *left, ExprNode *right, bool eq);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct StrcatExprNode : BinaryExprNode
|
||||
{
|
||||
int appendChar;
|
||||
static StrcatExprNode *alloc(ExprNode *left, ExprNode *right, int appendChar);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct CommaCatExprNode : BinaryExprNode
|
||||
{
|
||||
static CommaCatExprNode *alloc(ExprNode *left, ExprNode *right);
|
||||
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct IntUnaryExprNode : ExprNode
|
||||
{
|
||||
S32 op;
|
||||
ExprNode *expr;
|
||||
bool integer;
|
||||
|
||||
static IntUnaryExprNode *alloc(S32 op, ExprNode *expr);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct FloatUnaryExprNode : ExprNode
|
||||
{
|
||||
S32 op;
|
||||
ExprNode *expr;
|
||||
|
||||
static FloatUnaryExprNode *alloc(S32 op, ExprNode *expr);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct VarNode : ExprNode
|
||||
{
|
||||
StringTableEntry varName;
|
||||
ExprNode *arrayIndex;
|
||||
|
||||
static VarNode *alloc(StringTableEntry varName, ExprNode *arrayIndex);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct IntNode : ExprNode
|
||||
{
|
||||
S32 value;
|
||||
U32 index; // if it's converted to float/string
|
||||
|
||||
static IntNode *alloc(S32 value);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct FloatNode : ExprNode
|
||||
{
|
||||
F64 value;
|
||||
U32 index;
|
||||
|
||||
static FloatNode *alloc(F64 value);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct StrConstNode : ExprNode
|
||||
{
|
||||
char *str;
|
||||
F64 fVal;
|
||||
U32 index;
|
||||
bool tag;
|
||||
|
||||
static StrConstNode *alloc(char *str, bool tag);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct ConstantNode : ExprNode
|
||||
{
|
||||
StringTableEntry value;
|
||||
F64 fVal;
|
||||
U32 index;
|
||||
|
||||
static ConstantNode *alloc(StringTableEntry value);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct AssignExprNode : ExprNode
|
||||
{
|
||||
StringTableEntry varName;
|
||||
ExprNode *expr;
|
||||
ExprNode *arrayIndex;
|
||||
TypeReq subType;
|
||||
|
||||
static AssignExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct AssignDecl
|
||||
{
|
||||
S32 token;
|
||||
ExprNode *expr;
|
||||
bool integer;
|
||||
};
|
||||
|
||||
struct AssignOpExprNode : ExprNode
|
||||
{
|
||||
StringTableEntry varName;
|
||||
ExprNode *expr;
|
||||
ExprNode *arrayIndex;
|
||||
S32 op;
|
||||
U32 operand;
|
||||
TypeReq subType;
|
||||
|
||||
static AssignOpExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct TTagSetStmtNode : StmtNode
|
||||
{
|
||||
StringTableEntry tag;
|
||||
ExprNode *valueExpr;
|
||||
ExprNode *stringExpr;
|
||||
|
||||
static TTagSetStmtNode *alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr);
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
};
|
||||
|
||||
struct TTagDerefNode : ExprNode
|
||||
{
|
||||
ExprNode *expr;
|
||||
|
||||
static TTagDerefNode *alloc(ExprNode *expr);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct TTagExprNode : ExprNode
|
||||
{
|
||||
StringTableEntry tag;
|
||||
|
||||
static TTagExprNode *alloc(StringTableEntry tag);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct FuncCallExprNode : ExprNode
|
||||
{
|
||||
StringTableEntry funcName;
|
||||
StringTableEntry nameSpace;
|
||||
ExprNode *args;
|
||||
U32 callType;
|
||||
enum {
|
||||
FunctionCall,
|
||||
MethodCall,
|
||||
ParentCall
|
||||
};
|
||||
|
||||
static FuncCallExprNode *alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct SlotDecl
|
||||
{
|
||||
ExprNode *object;
|
||||
StringTableEntry slotName;
|
||||
ExprNode *array;
|
||||
};
|
||||
|
||||
struct SlotAccessNode : ExprNode
|
||||
{
|
||||
ExprNode *objectExpr, *arrayExpr;
|
||||
StringTableEntry slotName;
|
||||
|
||||
static SlotAccessNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct SlotAssignNode : ExprNode
|
||||
{
|
||||
ExprNode *objectExpr, *arrayExpr;
|
||||
StringTableEntry slotName;
|
||||
ExprNode *valueExpr;
|
||||
|
||||
static SlotAssignNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct SlotAssignOpNode : ExprNode
|
||||
{
|
||||
ExprNode *objectExpr, *arrayExpr;
|
||||
StringTableEntry slotName;
|
||||
S32 op;
|
||||
ExprNode *valueExpr;
|
||||
U32 operand;
|
||||
TypeReq subType;
|
||||
|
||||
static SlotAssignOpNode *alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct ObjectDeclNode : ExprNode
|
||||
{
|
||||
ExprNode *classNameExpr;
|
||||
StringTableEntry parentObject;
|
||||
ExprNode *objectNameExpr;
|
||||
ExprNode *argList;
|
||||
SlotAssignNode *slotDecls;
|
||||
ObjectDeclNode *subObjects;
|
||||
bool structDecl;
|
||||
U32 failOffset;
|
||||
|
||||
static ObjectDeclNode *alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool structDecl);
|
||||
U32 precompile(TypeReq type);
|
||||
U32 precompileSubObject(bool);
|
||||
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
|
||||
U32 compileSubObject(U32 *codeStream, U32 ip, bool);
|
||||
TypeReq getPreferredType();
|
||||
};
|
||||
|
||||
struct ObjectBlockDecl
|
||||
{
|
||||
SlotAssignNode *slots;
|
||||
ObjectDeclNode *decls;
|
||||
};
|
||||
|
||||
struct FunctionDeclStmtNode : StmtNode
|
||||
{
|
||||
StringTableEntry fnName;
|
||||
VarNode *args;
|
||||
StmtNode *stmts;
|
||||
StringTableEntry nameSpace;
|
||||
StringTableEntry package;
|
||||
U32 endOffset;
|
||||
U32 argc;
|
||||
|
||||
static FunctionDeclStmtNode *alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts);
|
||||
U32 precompileStmt(U32 loopCount);
|
||||
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
void setPackage(StringTableEntry packageName);
|
||||
};
|
||||
|
||||
extern StmtNode *statementList;
|
||||
extern void createFunction(const char *fnName, VarNode *args, StmtNode *statements);
|
||||
extern ExprEvalState gEvalState;
|
||||
extern bool lookupFunction(const char *fnName, VarNode **args, StmtNode **statements);
|
||||
typedef const char *(*cfunc)(S32 argc, char **argv);
|
||||
extern bool lookupCFunction(const char *fnName, cfunc *f);
|
||||
|
||||
|
||||
#endif
|
335
engine/console/astAlloc.cc
Executable file
335
engine/console/astAlloc.cc
Executable file
@ -0,0 +1,335 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "console/console.h"
|
||||
#include "console/compiler.h"
|
||||
#include "console/consoleInternal.h"
|
||||
|
||||
using namespace Compiler;
|
||||
|
||||
/// @file
|
||||
///
|
||||
/// TorqueScript AST node allocators.
|
||||
///
|
||||
/// These static methods exist to allocate new AST node for the compiler. They
|
||||
/// all allocate memory from the consoleAllocator for efficiency, and often take
|
||||
/// arguments relating to the state of the nodes. They are called from gram.y
|
||||
/// (really gram.c) as the lexer analyzes the script code.
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
BreakStmtNode *BreakStmtNode::alloc()
|
||||
{
|
||||
BreakStmtNode *ret = (BreakStmtNode *) consoleAlloc(sizeof(BreakStmtNode));
|
||||
constructInPlace(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ContinueStmtNode *ContinueStmtNode::alloc()
|
||||
{
|
||||
ContinueStmtNode *ret = (ContinueStmtNode *) consoleAlloc(sizeof(ContinueStmtNode));
|
||||
constructInPlace(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ReturnStmtNode *ReturnStmtNode::alloc(ExprNode *expr)
|
||||
{
|
||||
ReturnStmtNode *ret = (ReturnStmtNode *) consoleAlloc(sizeof(ReturnStmtNode));
|
||||
constructInPlace(ret);
|
||||
ret->expr = expr;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
IfStmtNode *IfStmtNode::alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagate)
|
||||
{
|
||||
IfStmtNode *ret = (IfStmtNode *) consoleAlloc(sizeof(IfStmtNode));
|
||||
constructInPlace(ret);
|
||||
ret->dbgLineNumber = lineNumber;
|
||||
|
||||
ret->testExpr = testExpr;
|
||||
ret->ifBlock = ifBlock;
|
||||
ret->elseBlock = elseBlock;
|
||||
ret->propagate = propagate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
LoopStmtNode *LoopStmtNode::alloc(S32 lineNumber, ExprNode *initExpr, ExprNode *testExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop)
|
||||
{
|
||||
LoopStmtNode *ret = (LoopStmtNode *) consoleAlloc(sizeof(LoopStmtNode));
|
||||
constructInPlace(ret);
|
||||
ret->dbgLineNumber = lineNumber;
|
||||
ret->testExpr = testExpr;
|
||||
ret->initExpr = initExpr;
|
||||
ret->endLoopExpr = endLoopExpr;
|
||||
ret->loopBlock = loopBlock;
|
||||
ret->isDoLoop = isDoLoop;
|
||||
|
||||
// Deal with setting some dummy constant nodes if we weren't provided with
|
||||
// info... This allows us to play nice with missing parts of for(;;) for
|
||||
// instance.
|
||||
if(!ret->testExpr) ret->testExpr = IntNode::alloc(1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FloatBinaryExprNode *FloatBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right)
|
||||
{
|
||||
FloatBinaryExprNode *ret = (FloatBinaryExprNode *) consoleAlloc(sizeof(FloatBinaryExprNode));
|
||||
constructInPlace(ret);
|
||||
|
||||
ret->op = op;
|
||||
ret->left = left;
|
||||
ret->right = right;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
IntBinaryExprNode *IntBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right)
|
||||
{
|
||||
IntBinaryExprNode *ret = (IntBinaryExprNode *) consoleAlloc(sizeof(IntBinaryExprNode));
|
||||
constructInPlace(ret);
|
||||
|
||||
ret->op = op;
|
||||
ret->left = left;
|
||||
ret->right = right;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
StreqExprNode *StreqExprNode::alloc(ExprNode *left, ExprNode *right, bool eq)
|
||||
{
|
||||
StreqExprNode *ret = (StreqExprNode *) consoleAlloc(sizeof(StreqExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->left = left;
|
||||
ret->right = right;
|
||||
ret->eq = eq;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
StrcatExprNode *StrcatExprNode::alloc(ExprNode *left, ExprNode *right, int appendChar)
|
||||
{
|
||||
StrcatExprNode *ret = (StrcatExprNode *) consoleAlloc(sizeof(StrcatExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->left = left;
|
||||
ret->right = right;
|
||||
ret->appendChar = appendChar;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
CommaCatExprNode *CommaCatExprNode::alloc(ExprNode *left, ExprNode *right)
|
||||
{
|
||||
CommaCatExprNode *ret = (CommaCatExprNode *) consoleAlloc(sizeof(CommaCatExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->left = left;
|
||||
ret->right = right;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
IntUnaryExprNode *IntUnaryExprNode::alloc(S32 op, ExprNode *expr)
|
||||
{
|
||||
IntUnaryExprNode *ret = (IntUnaryExprNode *) consoleAlloc(sizeof(IntUnaryExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->op = op;
|
||||
ret->expr = expr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
FloatUnaryExprNode *FloatUnaryExprNode::alloc(S32 op, ExprNode *expr)
|
||||
{
|
||||
FloatUnaryExprNode *ret = (FloatUnaryExprNode *) consoleAlloc(sizeof(FloatUnaryExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->op = op;
|
||||
ret->expr = expr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
VarNode *VarNode::alloc(StringTableEntry varName, ExprNode *arrayIndex)
|
||||
{
|
||||
VarNode *ret = (VarNode *) consoleAlloc(sizeof(VarNode));
|
||||
constructInPlace(ret);
|
||||
ret->varName = varName;
|
||||
ret->arrayIndex = arrayIndex;
|
||||
return ret;
|
||||
}
|
||||
|
||||
IntNode *IntNode::alloc(S32 value)
|
||||
{
|
||||
IntNode *ret = (IntNode *) consoleAlloc(sizeof(IntNode));
|
||||
constructInPlace(ret);
|
||||
ret->value = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConditionalExprNode *ConditionalExprNode::alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr)
|
||||
{
|
||||
ConditionalExprNode *ret = (ConditionalExprNode *) consoleAlloc(sizeof(ConditionalExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->testExpr = testExpr;
|
||||
ret->trueExpr = trueExpr;
|
||||
ret->falseExpr = falseExpr;
|
||||
ret->integer = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
FloatNode *FloatNode::alloc(F64 value)
|
||||
{
|
||||
FloatNode *ret = (FloatNode *) consoleAlloc(sizeof(FloatNode));
|
||||
constructInPlace(ret);
|
||||
ret->value = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
StrConstNode *StrConstNode::alloc(char *str, bool tag)
|
||||
{
|
||||
StrConstNode *ret = (StrConstNode *) consoleAlloc(sizeof(StrConstNode));
|
||||
constructInPlace(ret);
|
||||
ret->str = (char *) consoleAlloc(dStrlen(str) + 1);
|
||||
ret->tag = tag;
|
||||
dStrcpy(ret->str, str);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConstantNode *ConstantNode::alloc(StringTableEntry value)
|
||||
{
|
||||
ConstantNode *ret = (ConstantNode *) consoleAlloc(sizeof(ConstantNode));
|
||||
constructInPlace(ret);
|
||||
ret->value = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
AssignExprNode *AssignExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr)
|
||||
{
|
||||
AssignExprNode *ret = (AssignExprNode *) consoleAlloc(sizeof(AssignExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->varName = varName;
|
||||
ret->expr = expr;
|
||||
ret->arrayIndex = arrayIndex;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AssignOpExprNode *AssignOpExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op)
|
||||
{
|
||||
AssignOpExprNode *ret = (AssignOpExprNode *) consoleAlloc(sizeof(AssignOpExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->varName = varName;
|
||||
ret->expr = expr;
|
||||
ret->arrayIndex = arrayIndex;
|
||||
ret->op = op;
|
||||
return ret;
|
||||
}
|
||||
|
||||
TTagSetStmtNode *TTagSetStmtNode::alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr)
|
||||
{
|
||||
TTagSetStmtNode *ret = (TTagSetStmtNode *) consoleAlloc(sizeof(TTagSetStmtNode));
|
||||
constructInPlace(ret);
|
||||
ret->tag = tag;
|
||||
ret->valueExpr = valueExpr;
|
||||
ret->stringExpr = stringExpr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
TTagDerefNode *TTagDerefNode::alloc(ExprNode *expr)
|
||||
{
|
||||
TTagDerefNode *ret = (TTagDerefNode *) consoleAlloc(sizeof(TTagDerefNode));
|
||||
constructInPlace(ret);
|
||||
ret->expr = expr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
TTagExprNode *TTagExprNode::alloc(StringTableEntry tag)
|
||||
{
|
||||
TTagExprNode *ret = (TTagExprNode *) consoleAlloc(sizeof(TTagExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->tag = tag;
|
||||
return ret;
|
||||
}
|
||||
|
||||
FuncCallExprNode *FuncCallExprNode::alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot)
|
||||
{
|
||||
FuncCallExprNode *ret = (FuncCallExprNode *) consoleAlloc(sizeof(FuncCallExprNode));
|
||||
constructInPlace(ret);
|
||||
ret->funcName = funcName;
|
||||
ret->nameSpace = nameSpace;
|
||||
ret->args = args;
|
||||
if(dot)
|
||||
ret->callType = MethodCall;
|
||||
else
|
||||
{
|
||||
if(nameSpace && !dStricmp(nameSpace, "Parent"))
|
||||
ret->callType = ParentCall;
|
||||
else
|
||||
ret->callType = FunctionCall;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SlotAccessNode *SlotAccessNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName)
|
||||
{
|
||||
SlotAccessNode *ret = (SlotAccessNode *) consoleAlloc(sizeof(SlotAccessNode));
|
||||
constructInPlace(ret);
|
||||
ret->objectExpr = objectExpr;
|
||||
ret->arrayExpr = arrayExpr;
|
||||
ret->slotName = slotName;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SlotAssignNode *SlotAssignNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr)
|
||||
{
|
||||
SlotAssignNode *ret = (SlotAssignNode *) consoleAlloc(sizeof(SlotAssignNode));
|
||||
constructInPlace(ret);
|
||||
ret->objectExpr = objectExpr;
|
||||
ret->arrayExpr = arrayExpr;
|
||||
ret->slotName = slotName;
|
||||
ret->valueExpr = valueExpr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SlotAssignOpNode *SlotAssignOpNode::alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr)
|
||||
{
|
||||
SlotAssignOpNode *ret = (SlotAssignOpNode *) consoleAlloc(sizeof(SlotAssignOpNode));
|
||||
constructInPlace(ret);
|
||||
ret->objectExpr = objectExpr;
|
||||
ret->arrayExpr = arrayExpr;
|
||||
ret->slotName = slotName;
|
||||
ret->op = op;
|
||||
ret->valueExpr = valueExpr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ObjectDeclNode *ObjectDeclNode::alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool structDecl)
|
||||
{
|
||||
ObjectDeclNode *ret = (ObjectDeclNode *) consoleAlloc(sizeof(ObjectDeclNode));
|
||||
constructInPlace(ret);
|
||||
ret->classNameExpr = classNameExpr;
|
||||
ret->objectNameExpr = objectNameExpr;
|
||||
ret->argList = argList;
|
||||
ret->slotDecls = slotDecls;
|
||||
ret->subObjects = subObjects;
|
||||
ret->structDecl = structDecl;
|
||||
if(parentObject)
|
||||
ret->parentObject = parentObject;
|
||||
else
|
||||
ret->parentObject = StringTable->insert("");
|
||||
return ret;
|
||||
}
|
||||
|
||||
FunctionDeclStmtNode *FunctionDeclStmtNode::alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts)
|
||||
{
|
||||
FunctionDeclStmtNode *ret = (FunctionDeclStmtNode *) consoleAlloc(sizeof(FunctionDeclStmtNode));
|
||||
constructInPlace(ret);
|
||||
ret->fnName = fnName;
|
||||
ret->args = args;
|
||||
ret->stmts = stmts;
|
||||
ret->nameSpace = nameSpace;
|
||||
ret->package = NULL;
|
||||
return ret;
|
||||
}
|
1680
engine/console/astNodes.cc
Executable file
1680
engine/console/astNodes.cc
Executable file
File diff suppressed because it is too large
Load Diff
85
engine/console/basgram.h
Executable file
85
engine/console/basgram.h
Executable file
@ -0,0 +1,85 @@
|
||||
typedef union {
|
||||
char c;
|
||||
int i;
|
||||
const char *s;
|
||||
char *str;
|
||||
double f;
|
||||
StmtNode *stmt;
|
||||
ExprNode *expr;
|
||||
SlotAssignNode *slist;
|
||||
VarNode *var;
|
||||
SlotDecl slot;
|
||||
ObjectBlockDecl odcl;
|
||||
ObjectDeclNode *od;
|
||||
AssignDecl asn;
|
||||
IfStmtNode *ifnode;
|
||||
} YYSTYPE;
|
||||
#define rwDEFINE 258
|
||||
#define rwENDDEF 259
|
||||
#define rwDECLARE 260
|
||||
#define rwBREAK 261
|
||||
#define rwELSE 262
|
||||
#define rwCONTINUE 263
|
||||
#define rwGLOBAL 264
|
||||
#define rwIF 265
|
||||
#define rwNIL 266
|
||||
#define rwRETURN 267
|
||||
#define rwWHILE 268
|
||||
#define rwENDIF 269
|
||||
#define rwENDWHILE 270
|
||||
#define rwENDFOR 271
|
||||
#define rwDEFAULT 272
|
||||
#define rwFOR 273
|
||||
#define rwDATABLOCK 274
|
||||
#define rwSWITCH 275
|
||||
#define rwCASE 276
|
||||
#define rwSWITCHSTR 277
|
||||
#define rwCASEOR 278
|
||||
#define rwPACKAGE 279
|
||||
#define ILLEGAL_TOKEN 280
|
||||
#define CHRCONST 281
|
||||
#define INTCONST 282
|
||||
#define TTAG 283
|
||||
#define VAR 284
|
||||
#define IDENT 285
|
||||
#define STRATOM 286
|
||||
#define TAGATOM 287
|
||||
#define FLTCONST 288
|
||||
#define opMINUSMINUS 289
|
||||
#define opPLUSPLUS 290
|
||||
#define STMT_SEP 291
|
||||
#define opSHL 292
|
||||
#define opSHR 293
|
||||
#define opPLASN 294
|
||||
#define opMIASN 295
|
||||
#define opMLASN 296
|
||||
#define opDVASN 297
|
||||
#define opMODASN 298
|
||||
#define opANDASN 299
|
||||
#define opXORASN 300
|
||||
#define opORASN 301
|
||||
#define opSLASN 302
|
||||
#define opSRASN 303
|
||||
#define opCAT 304
|
||||
#define opEQ 305
|
||||
#define opNE 306
|
||||
#define opGE 307
|
||||
#define opLE 308
|
||||
#define opAND 309
|
||||
#define opOR 310
|
||||
#define opSTREQ 311
|
||||
#define opCOLONCOLON 312
|
||||
#define opMDASN 313
|
||||
#define opNDASN 314
|
||||
#define opNTASN 315
|
||||
#define opSTRNE 316
|
||||
#define UNARY 317
|
||||
#define rwTHEN 318
|
||||
#define rwEND 319
|
||||
#define rwBEGIN 320
|
||||
#define rwCFOR 321
|
||||
#define rwTO 322
|
||||
#define rwSTEP 323
|
||||
|
||||
|
||||
extern YYSTYPE BASlval;
|
6
engine/console/bison.bat
Executable file
6
engine/console/bison.bat
Executable file
@ -0,0 +1,6 @@
|
||||
echo Changing to %4 ...
|
||||
cd %4
|
||||
echo Generating %2 and %3 with prefix %1.
|
||||
..\..\bin\bison\bison.exe -o %2 %3 --defines -p %1
|
||||
echo Renaming %2 to %5 .
|
||||
move /Y %2 %5
|
686
engine/console/bison.simple
Executable file
686
engine/console/bison.simple
Executable file
@ -0,0 +1,686 @@
|
||||
/* -*-C-*- Note some compilers choke on comments on `#line' lines. */
|
||||
#line 3 "bison.simple"
|
||||
|
||||
/* Skeleton output parser for bison,
|
||||
Copyright (C) 1984, 1989, 1990 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
||||
|
||||
/* As a special exception, when this file is copied by Bison into a
|
||||
Bison output file, you may use that output file without restriction.
|
||||
This special exception was added by the Free Software Foundation
|
||||
in version 1.24 of Bison. */
|
||||
|
||||
#ifndef alloca
|
||||
#ifdef __GNUC__
|
||||
#define alloca __builtin_alloca
|
||||
#else /* not GNU C. */
|
||||
#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi)
|
||||
#include <alloca.h>
|
||||
#else /* not sparc */
|
||||
#if defined (MSDOS) && !defined (__TURBOC__)
|
||||
#include <malloc.h>
|
||||
#else /* not MSDOS, or __TURBOC__ */
|
||||
#if defined(_AIX)
|
||||
#include <malloc.h>
|
||||
#pragma alloca
|
||||
#else /* not MSDOS, __TURBOC__, or _AIX */
|
||||
#ifdef __hpux
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
void *alloca (unsigned int);
|
||||
};
|
||||
#else /* not __cplusplus */
|
||||
void *alloca ();
|
||||
#endif /* not __cplusplus */
|
||||
#endif /* __hpux */
|
||||
#endif /* not _AIX */
|
||||
#endif /* not MSDOS, or __TURBOC__ */
|
||||
#endif /* not sparc. */
|
||||
#endif /* not GNU C. */
|
||||
#endif /* alloca not defined. */
|
||||
|
||||
/* This is the parser code that is written into each bison parser
|
||||
when the %semantic_parser declaration is not specified in the grammar.
|
||||
It was written by Richard Stallman by simplifying the hairy parser
|
||||
used when %semantic_parser is specified. */
|
||||
|
||||
/* Note: there must be only one dollar sign in this file.
|
||||
It is replaced by the list of actions, each action
|
||||
as one case of the switch. */
|
||||
|
||||
#define yyerrok (yyerrstatus = 0)
|
||||
#define yyclearin (yychar = YYEMPTY)
|
||||
#define YYEMPTY -2
|
||||
#define YYEOF 0
|
||||
#define YYACCEPT return(0)
|
||||
#define YYABORT return(1)
|
||||
#define YYERROR goto yyerrlab1
|
||||
/* Like YYERROR except do call yyerror.
|
||||
This remains here temporarily to ease the
|
||||
transition to the new meaning of YYERROR, for GCC.
|
||||
Once GCC version 2 has supplanted version 1, this can go. */
|
||||
#define YYFAIL goto yyerrlab
|
||||
#define YYRECOVERING() (!!yyerrstatus)
|
||||
#define YYBACKUP(token, value) \
|
||||
do \
|
||||
if (yychar == YYEMPTY && yylen == 1) \
|
||||
{ yychar = (token), yylval = (value); \
|
||||
yychar1 = YYTRANSLATE (yychar); \
|
||||
YYPOPSTACK; \
|
||||
goto yybackup; \
|
||||
} \
|
||||
else \
|
||||
{ yyerror ("syntax error: cannot back up"); YYERROR; } \
|
||||
while (0)
|
||||
|
||||
#define YYTERROR 1
|
||||
#define YYERRCODE 256
|
||||
|
||||
#ifndef YYPURE
|
||||
#define YYLEX yylex()
|
||||
#endif
|
||||
|
||||
#ifdef YYPURE
|
||||
#ifdef YYLSP_NEEDED
|
||||
#ifdef YYLEX_PARAM
|
||||
#define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM)
|
||||
#else
|
||||
#define YYLEX yylex(&yylval, &yylloc)
|
||||
#endif
|
||||
#else /* not YYLSP_NEEDED */
|
||||
#ifdef YYLEX_PARAM
|
||||
#define YYLEX yylex(&yylval, YYLEX_PARAM)
|
||||
#else
|
||||
#define YYLEX yylex(&yylval)
|
||||
#endif
|
||||
#endif /* not YYLSP_NEEDED */
|
||||
#endif
|
||||
|
||||
/* If nonreentrant, generate the variables here */
|
||||
|
||||
#ifndef YYPURE
|
||||
|
||||
int yychar; /* the lookahead symbol */
|
||||
YYSTYPE yylval; /* the semantic value of the */
|
||||
/* lookahead symbol */
|
||||
|
||||
#ifdef YYLSP_NEEDED
|
||||
YYLTYPE yylloc; /* location data for the lookahead */
|
||||
/* symbol */
|
||||
#endif
|
||||
|
||||
int yynerrs; /* number of parse errors so far */
|
||||
#endif /* not YYPURE */
|
||||
|
||||
#if YYDEBUG != 0
|
||||
int yydebug; /* nonzero means print parse trace */
|
||||
/* Since this is uninitialized, it does not stop multiple parsers
|
||||
from coexisting. */
|
||||
#endif
|
||||
|
||||
/* YYINITDEPTH indicates the initial size of the parser's stacks */
|
||||
|
||||
#ifndef YYINITDEPTH
|
||||
#define YYINITDEPTH 200
|
||||
#endif
|
||||
|
||||
/* YYMAXDEPTH is the maximum size the stacks can grow to
|
||||
(effective only if the built-in stack extension method is used). */
|
||||
|
||||
#if YYMAXDEPTH == 0
|
||||
#undef YYMAXDEPTH
|
||||
#endif
|
||||
|
||||
#ifndef YYMAXDEPTH
|
||||
#define YYMAXDEPTH 10000
|
||||
#endif
|
||||
|
||||
/* Prevent warning if -Wstrict-prototypes. */
|
||||
#ifdef __GNUC__
|
||||
int yyparse (void);
|
||||
#endif
|
||||
|
||||
#if __GNUC__ > 1 /* GNU C and GNU C++ define this. */
|
||||
#define __yy_memcpy(FROM,TO,COUNT) __builtin_memcpy(TO,FROM,COUNT)
|
||||
#else /* not GNU C or C++ */
|
||||
#ifndef __cplusplus
|
||||
|
||||
/* This is the most reliable way to avoid incompatibilities
|
||||
in available built-in functions on various systems. */
|
||||
static void
|
||||
__yy_memcpy (from, to, count)
|
||||
char *from;
|
||||
char *to;
|
||||
int count;
|
||||
{
|
||||
register char *f = from;
|
||||
register char *t = to;
|
||||
register int i = count;
|
||||
|
||||
while (i-- > 0)
|
||||
*t++ = *f++;
|
||||
}
|
||||
|
||||
#else /* __cplusplus */
|
||||
|
||||
/* This is the most reliable way to avoid incompatibilities
|
||||
in available built-in functions on various systems. */
|
||||
static void
|
||||
__yy_memcpy (char *from, char *to, int count)
|
||||
{
|
||||
register char *f = from;
|
||||
register char *t = to;
|
||||
register int i = count;
|
||||
|
||||
while (i-- > 0)
|
||||
*t++ = *f++;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#line 192 "bison.simple"
|
||||
|
||||
/* The user can define YYPARSE_PARAM as the name of an argument to be passed
|
||||
into yyparse. The argument should have type void *.
|
||||
It should actually point to an object.
|
||||
Grammar actions can access the variable by casting it
|
||||
to the proper pointer type. */
|
||||
|
||||
#ifdef YYPARSE_PARAM
|
||||
#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM;
|
||||
#else
|
||||
#define YYPARSE_PARAM
|
||||
#define YYPARSE_PARAM_DECL
|
||||
#endif
|
||||
|
||||
int
|
||||
yyparse(YYPARSE_PARAM)
|
||||
YYPARSE_PARAM_DECL
|
||||
{
|
||||
register int yystate;
|
||||
register int yyn;
|
||||
register short *yyssp;
|
||||
register YYSTYPE *yyvsp;
|
||||
int yyerrstatus; /* number of tokens to shift before error messages enabled */
|
||||
int yychar1 = 0; /* lookahead token as an internal (translated) token number */
|
||||
|
||||
short yyssa[YYINITDEPTH]; /* the state stack */
|
||||
YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */
|
||||
|
||||
short *yyss = yyssa; /* refer to the stacks thru separate pointers */
|
||||
YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */
|
||||
|
||||
#ifdef YYLSP_NEEDED
|
||||
YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */
|
||||
YYLTYPE *yyls = yylsa;
|
||||
YYLTYPE *yylsp;
|
||||
|
||||
#define YYPOPSTACK (yyvsp--, yyssp--, yylsp--)
|
||||
#else
|
||||
#define YYPOPSTACK (yyvsp--, yyssp--)
|
||||
#endif
|
||||
|
||||
int yystacksize = YYINITDEPTH;
|
||||
|
||||
#ifdef YYPURE
|
||||
int yychar;
|
||||
YYSTYPE yylval;
|
||||
int yynerrs;
|
||||
#ifdef YYLSP_NEEDED
|
||||
YYLTYPE yylloc;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
YYSTYPE yyval; /* the variable used to return */
|
||||
/* semantic values from the action */
|
||||
/* routines */
|
||||
|
||||
int yylen;
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Starting parse\n");
|
||||
#endif
|
||||
|
||||
yystate = 0;
|
||||
yyerrstatus = 0;
|
||||
yynerrs = 0;
|
||||
yychar = YYEMPTY; /* Cause a token to be read. */
|
||||
|
||||
/* Initialize stack pointers.
|
||||
Waste one element of value and location stack
|
||||
so that they stay on the same level as the state stack.
|
||||
The wasted elements are never initialized. */
|
||||
|
||||
yyssp = yyss - 1;
|
||||
yyvsp = yyvs;
|
||||
#ifdef YYLSP_NEEDED
|
||||
yylsp = yyls;
|
||||
#endif
|
||||
|
||||
/* Push a new state, which is found in yystate . */
|
||||
/* In all cases, when you get here, the value and location stacks
|
||||
have just been pushed. so pushing a state here evens the stacks. */
|
||||
yynewstate:
|
||||
|
||||
*++yyssp = yystate;
|
||||
|
||||
if (yyssp >= yyss + yystacksize - 1)
|
||||
{
|
||||
/* Give user a chance to reallocate the stack */
|
||||
/* Use copies of these so that the &'s don't force the real ones into memory. */
|
||||
YYSTYPE *yyvs1 = yyvs;
|
||||
short *yyss1 = yyss;
|
||||
#ifdef YYLSP_NEEDED
|
||||
YYLTYPE *yyls1 = yyls;
|
||||
#endif
|
||||
|
||||
/* Get the current used size of the three stacks, in elements. */
|
||||
int size = yyssp - yyss + 1;
|
||||
|
||||
#ifdef yyoverflow
|
||||
/* Each stack pointer address is followed by the size of
|
||||
the data in use in that stack, in bytes. */
|
||||
#ifdef YYLSP_NEEDED
|
||||
/* This used to be a conditional around just the two extra args,
|
||||
but that might be undefined if yyoverflow is a macro. */
|
||||
yyoverflow("parser stack overflow",
|
||||
&yyss1, size * sizeof (*yyssp),
|
||||
&yyvs1, size * sizeof (*yyvsp),
|
||||
&yyls1, size * sizeof (*yylsp),
|
||||
&yystacksize);
|
||||
#else
|
||||
yyoverflow("parser stack overflow",
|
||||
&yyss1, size * sizeof (*yyssp),
|
||||
&yyvs1, size * sizeof (*yyvsp),
|
||||
&yystacksize);
|
||||
#endif
|
||||
|
||||
yyss = yyss1; yyvs = yyvs1;
|
||||
#ifdef YYLSP_NEEDED
|
||||
yyls = yyls1;
|
||||
#endif
|
||||
#else /* no yyoverflow */
|
||||
/* Extend the stack our own way. */
|
||||
if (yystacksize >= YYMAXDEPTH)
|
||||
{
|
||||
yyerror("parser stack overflow");
|
||||
return 2;
|
||||
}
|
||||
yystacksize *= 2;
|
||||
if (yystacksize > YYMAXDEPTH)
|
||||
yystacksize = YYMAXDEPTH;
|
||||
yyss = (short *) alloca (yystacksize * sizeof (*yyssp));
|
||||
__yy_memcpy ((char *)yyss1, (char *)yyss, size * sizeof (*yyssp));
|
||||
yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp));
|
||||
__yy_memcpy ((char *)yyvs1, (char *)yyvs, size * sizeof (*yyvsp));
|
||||
#ifdef YYLSP_NEEDED
|
||||
yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp));
|
||||
__yy_memcpy ((char *)yyls1, (char *)yyls, size * sizeof (*yylsp));
|
||||
#endif
|
||||
#endif /* no yyoverflow */
|
||||
|
||||
yyssp = yyss + size - 1;
|
||||
yyvsp = yyvs + size - 1;
|
||||
#ifdef YYLSP_NEEDED
|
||||
yylsp = yyls + size - 1;
|
||||
#endif
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Stack size increased to %d\n", yystacksize);
|
||||
#endif
|
||||
|
||||
if (yyssp >= yyss + yystacksize - 1)
|
||||
YYABORT;
|
||||
}
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Entering state %d\n", yystate);
|
||||
#endif
|
||||
|
||||
goto yybackup;
|
||||
yybackup:
|
||||
|
||||
/* Do appropriate processing given the current state. */
|
||||
/* Read a lookahead token if we need one and don't already have one. */
|
||||
/* yyresume: */
|
||||
|
||||
/* First try to decide what to do without reference to lookahead token. */
|
||||
|
||||
yyn = yypact[yystate];
|
||||
if (yyn == YYFLAG)
|
||||
goto yydefault;
|
||||
|
||||
/* Not known => get a lookahead token if don't already have one. */
|
||||
|
||||
/* yychar is either YYEMPTY or YYEOF
|
||||
or a valid token in external form. */
|
||||
|
||||
if (yychar == YYEMPTY)
|
||||
{
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Reading a token: ");
|
||||
#endif
|
||||
yychar = YYLEX;
|
||||
}
|
||||
|
||||
/* Convert token to internal form (in yychar1) for indexing tables with */
|
||||
|
||||
if (yychar <= 0) /* This means end of input. */
|
||||
{
|
||||
yychar1 = 0;
|
||||
yychar = YYEOF; /* Don't call YYLEX any more */
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Now at end of input.\n");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
yychar1 = YYTRANSLATE(yychar);
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
{
|
||||
fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]);
|
||||
/* Give the individual parser a way to print the precise meaning
|
||||
of a token, for further debugging info. */
|
||||
#ifdef YYPRINT
|
||||
YYPRINT (stderr, yychar, yylval);
|
||||
#endif
|
||||
fprintf (stderr, ")\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
yyn += yychar1;
|
||||
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1)
|
||||
goto yydefault;
|
||||
|
||||
yyn = yytable[yyn];
|
||||
|
||||
/* yyn is what to do for this token type in this state.
|
||||
Negative => reduce, -yyn is rule number.
|
||||
Positive => shift, yyn is new state.
|
||||
New state is final state => don't bother to shift,
|
||||
just return success.
|
||||
0, or most negative number => error. */
|
||||
|
||||
if (yyn < 0)
|
||||
{
|
||||
if (yyn == YYFLAG)
|
||||
goto yyerrlab;
|
||||
yyn = -yyn;
|
||||
goto yyreduce;
|
||||
}
|
||||
else if (yyn == 0)
|
||||
goto yyerrlab;
|
||||
|
||||
if (yyn == YYFINAL)
|
||||
YYACCEPT;
|
||||
|
||||
/* Shift the lookahead token. */
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]);
|
||||
#endif
|
||||
|
||||
/* Discard the token being shifted unless it is eof. */
|
||||
if (yychar != YYEOF)
|
||||
yychar = YYEMPTY;
|
||||
|
||||
*++yyvsp = yylval;
|
||||
#ifdef YYLSP_NEEDED
|
||||
*++yylsp = yylloc;
|
||||
#endif
|
||||
|
||||
/* count tokens shifted since error; after three, turn off error status. */
|
||||
if (yyerrstatus) yyerrstatus--;
|
||||
|
||||
yystate = yyn;
|
||||
goto yynewstate;
|
||||
|
||||
/* Do the default action for the current state. */
|
||||
yydefault:
|
||||
|
||||
yyn = yydefact[yystate];
|
||||
if (yyn == 0)
|
||||
goto yyerrlab;
|
||||
|
||||
/* Do a reduction. yyn is the number of a rule to reduce with. */
|
||||
yyreduce:
|
||||
yylen = yyr2[yyn];
|
||||
if (yylen > 0)
|
||||
yyval = yyvsp[1-yylen]; /* implement default value of the action */
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
{
|
||||
int i;
|
||||
|
||||
fprintf (stderr, "Reducing via rule %d (line %d), ",
|
||||
yyn, yyrline[yyn]);
|
||||
|
||||
/* Print the symbols being reduced, and their result. */
|
||||
for (i = yyprhs[yyn]; yyrhs[i] > 0; i++)
|
||||
fprintf (stderr, "%s ", yytname[yyrhs[i]]);
|
||||
fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]);
|
||||
}
|
||||
#endif
|
||||
|
||||
$ /* the action file gets copied in in place of this dollarsign */
|
||||
#line 487 "bison.simple"
|
||||
|
||||
yyvsp -= yylen;
|
||||
yyssp -= yylen;
|
||||
#ifdef YYLSP_NEEDED
|
||||
yylsp -= yylen;
|
||||
#endif
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
{
|
||||
short *ssp1 = yyss - 1;
|
||||
fprintf (stderr, "state stack now");
|
||||
while (ssp1 != yyssp)
|
||||
fprintf (stderr, " %d", *++ssp1);
|
||||
fprintf (stderr, "\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
*++yyvsp = yyval;
|
||||
|
||||
#ifdef YYLSP_NEEDED
|
||||
yylsp++;
|
||||
if (yylen == 0)
|
||||
{
|
||||
yylsp->first_line = yylloc.first_line;
|
||||
yylsp->first_column = yylloc.first_column;
|
||||
yylsp->last_line = (yylsp-1)->last_line;
|
||||
yylsp->last_column = (yylsp-1)->last_column;
|
||||
yylsp->text = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
yylsp->last_line = (yylsp+yylen-1)->last_line;
|
||||
yylsp->last_column = (yylsp+yylen-1)->last_column;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Now "shift" the result of the reduction.
|
||||
Determine what state that goes to,
|
||||
based on the state we popped back to
|
||||
and the rule number reduced by. */
|
||||
|
||||
yyn = yyr1[yyn];
|
||||
|
||||
yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
|
||||
if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp)
|
||||
yystate = yytable[yystate];
|
||||
else
|
||||
yystate = yydefgoto[yyn - YYNTBASE];
|
||||
|
||||
goto yynewstate;
|
||||
|
||||
yyerrlab: /* here on detecting error */
|
||||
|
||||
if (! yyerrstatus)
|
||||
/* If not already recovering from an error, report this error. */
|
||||
{
|
||||
++yynerrs;
|
||||
|
||||
#ifdef YYERROR_VERBOSE
|
||||
yyn = yypact[yystate];
|
||||
|
||||
if (yyn > YYFLAG && yyn < YYLAST)
|
||||
{
|
||||
int size = 0;
|
||||
char *msg;
|
||||
int x, count;
|
||||
|
||||
count = 0;
|
||||
/* Start X at -yyn if nec to avoid negative indexes in yycheck. */
|
||||
for (x = (yyn < 0 ? -yyn : 0);
|
||||
x < (sizeof(yytname) / sizeof(char *)); x++)
|
||||
if (yycheck[x + yyn] == x)
|
||||
size += strlen(yytname[x]) + 15, count++;
|
||||
msg = (char *) malloc(size + 15);
|
||||
if (msg != 0)
|
||||
{
|
||||
strcpy(msg, "parse error");
|
||||
|
||||
if (count < 5)
|
||||
{
|
||||
count = 0;
|
||||
for (x = (yyn < 0 ? -yyn : 0);
|
||||
x < (sizeof(yytname) / sizeof(char *)); x++)
|
||||
if (yycheck[x + yyn] == x)
|
||||
{
|
||||
strcat(msg, count == 0 ? ", expecting `" : " or `");
|
||||
strcat(msg, yytname[x]);
|
||||
strcat(msg, "'");
|
||||
count++;
|
||||
}
|
||||
}
|
||||
yyerror(msg);
|
||||
free(msg);
|
||||
}
|
||||
else
|
||||
yyerror ("parse error; also virtual memory exceeded");
|
||||
}
|
||||
else
|
||||
#endif /* YYERROR_VERBOSE */
|
||||
yyerror("parse error");
|
||||
}
|
||||
|
||||
goto yyerrlab1;
|
||||
yyerrlab1: /* here on error raised explicitly by an action */
|
||||
|
||||
if (yyerrstatus == 3)
|
||||
{
|
||||
/* if just tried and failed to reuse lookahead token after an error, discard it. */
|
||||
|
||||
/* return failure if at end of input */
|
||||
if (yychar == YYEOF)
|
||||
YYABORT;
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]);
|
||||
#endif
|
||||
|
||||
yychar = YYEMPTY;
|
||||
}
|
||||
|
||||
/* Else will try to reuse lookahead token
|
||||
after shifting the error token. */
|
||||
|
||||
yyerrstatus = 3; /* Each real token shifted decrements this */
|
||||
|
||||
goto yyerrhandle;
|
||||
|
||||
yyerrdefault: /* current state does not do anything special for the error token. */
|
||||
|
||||
#if 0
|
||||
/* This is wrong; only states that explicitly want error tokens
|
||||
should shift them. */
|
||||
yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/
|
||||
if (yyn) goto yydefault;
|
||||
#endif
|
||||
|
||||
yyerrpop: /* pop the current state because it cannot handle the error token */
|
||||
|
||||
if (yyssp == yyss) YYABORT;
|
||||
yyvsp--;
|
||||
yystate = *--yyssp;
|
||||
#ifdef YYLSP_NEEDED
|
||||
yylsp--;
|
||||
#endif
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
{
|
||||
short *ssp1 = yyss - 1;
|
||||
fprintf (stderr, "Error: state stack now");
|
||||
while (ssp1 != yyssp)
|
||||
fprintf (stderr, " %d", *++ssp1);
|
||||
fprintf (stderr, "\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
yyerrhandle:
|
||||
|
||||
yyn = yypact[yystate];
|
||||
if (yyn == YYFLAG)
|
||||
goto yyerrdefault;
|
||||
|
||||
yyn += YYTERROR;
|
||||
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR)
|
||||
goto yyerrdefault;
|
||||
|
||||
yyn = yytable[yyn];
|
||||
if (yyn < 0)
|
||||
{
|
||||
if (yyn == YYFLAG)
|
||||
goto yyerrpop;
|
||||
yyn = -yyn;
|
||||
goto yyreduce;
|
||||
}
|
||||
else if (yyn == 0)
|
||||
goto yyerrpop;
|
||||
|
||||
if (yyn == YYFINAL)
|
||||
YYACCEPT;
|
||||
|
||||
#if YYDEBUG != 0
|
||||
if (yydebug)
|
||||
fprintf(stderr, "Shifting error token, ");
|
||||
#endif
|
||||
|
||||
*++yyvsp = yylval;
|
||||
#ifdef YYLSP_NEEDED
|
||||
*++yylsp = yylloc;
|
||||
#endif
|
||||
|
||||
yystate = yyn;
|
||||
goto yynewstate;
|
||||
}
|
82
engine/console/cmdgram.h
Executable file
82
engine/console/cmdgram.h
Executable file
@ -0,0 +1,82 @@
|
||||
typedef union {
|
||||
char c;
|
||||
int i;
|
||||
const char * s;
|
||||
char * str;
|
||||
double f;
|
||||
StmtNode * stmt;
|
||||
ExprNode * expr;
|
||||
SlotAssignNode * slist;
|
||||
VarNode * var;
|
||||
SlotDecl slot;
|
||||
ObjectBlockDecl odcl;
|
||||
ObjectDeclNode * od;
|
||||
AssignDecl asn;
|
||||
IfStmtNode * ifnode;
|
||||
} YYSTYPE;
|
||||
#define rwDEFINE 258
|
||||
#define rwENDDEF 259
|
||||
#define rwDECLARE 260
|
||||
#define rwBREAK 261
|
||||
#define rwELSE 262
|
||||
#define rwCONTINUE 263
|
||||
#define rwGLOBAL 264
|
||||
#define rwIF 265
|
||||
#define rwNIL 266
|
||||
#define rwRETURN 267
|
||||
#define rwWHILE 268
|
||||
#define rwDO 269
|
||||
#define rwENDIF 270
|
||||
#define rwENDWHILE 271
|
||||
#define rwENDFOR 272
|
||||
#define rwDEFAULT 273
|
||||
#define rwFOR 274
|
||||
#define rwDATABLOCK 275
|
||||
#define rwSWITCH 276
|
||||
#define rwCASE 277
|
||||
#define rwSWITCHSTR 278
|
||||
#define rwCASEOR 279
|
||||
#define rwPACKAGE 280
|
||||
#define rwNAMESPACE 281
|
||||
#define rwCLASS 282
|
||||
#define ILLEGAL_TOKEN 283
|
||||
#define CHRCONST 284
|
||||
#define INTCONST 285
|
||||
#define TTAG 286
|
||||
#define VAR 287
|
||||
#define IDENT 288
|
||||
#define STRATOM 289
|
||||
#define TAGATOM 290
|
||||
#define FLTCONST 291
|
||||
#define opMINUSMINUS 292
|
||||
#define opPLUSPLUS 293
|
||||
#define STMT_SEP 294
|
||||
#define opSHL 295
|
||||
#define opSHR 296
|
||||
#define opPLASN 297
|
||||
#define opMIASN 298
|
||||
#define opMLASN 299
|
||||
#define opDVASN 300
|
||||
#define opMODASN 301
|
||||
#define opANDASN 302
|
||||
#define opXORASN 303
|
||||
#define opORASN 304
|
||||
#define opSLASN 305
|
||||
#define opSRASN 306
|
||||
#define opCAT 307
|
||||
#define opEQ 308
|
||||
#define opNE 309
|
||||
#define opGE 310
|
||||
#define opLE 311
|
||||
#define opAND 312
|
||||
#define opOR 313
|
||||
#define opSTREQ 314
|
||||
#define opCOLONCOLON 315
|
||||
#define opMDASN 316
|
||||
#define opNDASN 317
|
||||
#define opNTASN 318
|
||||
#define opSTRNE 319
|
||||
#define UNARY 320
|
||||
|
||||
|
||||
extern YYSTYPE CMDlval;
|
565
engine/console/codeBlock.cc
Executable file
565
engine/console/codeBlock.cc
Executable file
@ -0,0 +1,565 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "console/console.h"
|
||||
#include "console/compiler.h"
|
||||
#include "console/codeBlock.h"
|
||||
#include "console/telnetDebugger.h"
|
||||
#include "core/resManager.h"
|
||||
|
||||
using namespace Compiler;
|
||||
|
||||
bool CodeBlock::smInFunction = false;
|
||||
U32 CodeBlock::smBreakLineCount = 0;
|
||||
CodeBlock * CodeBlock::smCodeBlockList = NULL;
|
||||
CodeBlock * CodeBlock::smCurrentCodeBlock = NULL;
|
||||
ConsoleParser *CodeBlock::smCurrentParser = NULL;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
CodeBlock::CodeBlock()
|
||||
{
|
||||
globalStrings = NULL;
|
||||
functionStrings = NULL;
|
||||
globalFloats = NULL;
|
||||
functionFloats = NULL;
|
||||
lineBreakPairs = NULL;
|
||||
breakList = NULL;
|
||||
breakListSize = 0;
|
||||
|
||||
refCount = 0;
|
||||
code = NULL;
|
||||
name = NULL;
|
||||
mRoot = StringTable->insert("");
|
||||
}
|
||||
|
||||
CodeBlock::~CodeBlock()
|
||||
{
|
||||
// Make sure we aren't lingering in the current code block...
|
||||
AssertFatal(smCurrentCodeBlock != this, "CodeBlock::~CodeBlock - Caught lingering in smCurrentCodeBlock!")
|
||||
|
||||
if(name)
|
||||
removeFromCodeList();
|
||||
delete[] const_cast<char*>(globalStrings);
|
||||
delete[] const_cast<char*>(functionStrings);
|
||||
delete[] globalFloats;
|
||||
delete[] functionFloats;
|
||||
delete[] code;
|
||||
delete[] breakList;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
StringTableEntry CodeBlock::getCurrentCodeBlockName()
|
||||
{
|
||||
if (CodeBlock::getCurrentBlock())
|
||||
return CodeBlock::getCurrentBlock()->name;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
CodeBlock *CodeBlock::find(StringTableEntry name)
|
||||
{
|
||||
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
|
||||
if(walk->name == name)
|
||||
return walk;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
void CodeBlock::addToCodeList()
|
||||
{
|
||||
// remove any code blocks with my name
|
||||
for(CodeBlock **walk = &smCodeBlockList; *walk;walk = &((*walk)->nextFile))
|
||||
{
|
||||
if((*walk)->name == name)
|
||||
{
|
||||
*walk = (*walk)->nextFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
nextFile = smCodeBlockList;
|
||||
smCodeBlockList = this;
|
||||
}
|
||||
|
||||
void CodeBlock::clearAllBreaks()
|
||||
{
|
||||
if(!lineBreakPairs)
|
||||
return;
|
||||
for(U32 i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 *p = lineBreakPairs + i * 2;
|
||||
code[p[1]] = p[0] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeBlock::clearBreakpoint(U32 lineNumber)
|
||||
{
|
||||
if(!lineBreakPairs)
|
||||
return;
|
||||
for(U32 i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 *p = lineBreakPairs + i * 2;
|
||||
if((p[0] >> 8) == lineNumber)
|
||||
{
|
||||
code[p[1]] = p[0] & 0xFF;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeBlock::setAllBreaks()
|
||||
{
|
||||
if(!lineBreakPairs)
|
||||
return;
|
||||
for(U32 i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 *p = lineBreakPairs + i * 2;
|
||||
code[p[1]] = OP_BREAK;
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeBlock::setBreakpoint(U32 lineNumber)
|
||||
{
|
||||
if(!lineBreakPairs)
|
||||
return false;
|
||||
|
||||
for(U32 i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 *p = lineBreakPairs + i * 2;
|
||||
if((p[0] >> 8) == lineNumber)
|
||||
{
|
||||
code[p[1]] = OP_BREAK;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
U32 CodeBlock::findFirstBreakLine(U32 lineNumber)
|
||||
{
|
||||
if(!lineBreakPairs)
|
||||
return 0;
|
||||
|
||||
for(U32 i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 *p = lineBreakPairs + i * 2;
|
||||
U32 line = (p[0] >> 8);
|
||||
|
||||
if( lineNumber <= line )
|
||||
return line;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct LinePair
|
||||
{
|
||||
U32 instLine;
|
||||
U32 ip;
|
||||
};
|
||||
|
||||
void CodeBlock::findBreakLine(U32 ip, U32 &line, U32 &instruction)
|
||||
{
|
||||
U32 min = 0;
|
||||
U32 max = lineBreakPairCount - 1;
|
||||
LinePair *p = (LinePair *) lineBreakPairs;
|
||||
|
||||
U32 found;
|
||||
if(!lineBreakPairCount || p[min].ip > ip || p[max].ip < ip)
|
||||
{
|
||||
line = 0;
|
||||
instruction = OP_INVALID;
|
||||
return;
|
||||
}
|
||||
else if(p[min].ip == ip)
|
||||
found = min;
|
||||
else if(p[max].ip == ip)
|
||||
found = max;
|
||||
else
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
if(min == max - 1)
|
||||
{
|
||||
found = min;
|
||||
break;
|
||||
}
|
||||
U32 mid = (min + max) >> 1;
|
||||
if(p[mid].ip == ip)
|
||||
{
|
||||
found = mid;
|
||||
break;
|
||||
}
|
||||
else if(p[mid].ip > ip)
|
||||
max = mid;
|
||||
else
|
||||
min = mid;
|
||||
}
|
||||
}
|
||||
instruction = p[found].instLine & 0xFF;
|
||||
line = p[found].instLine >> 8;
|
||||
}
|
||||
|
||||
const char *CodeBlock::getFileLine(U32 ip)
|
||||
{
|
||||
static char nameBuffer[256];
|
||||
U32 line, inst;
|
||||
findBreakLine(ip, line, inst);
|
||||
|
||||
dSprintf(nameBuffer, sizeof(nameBuffer), "%s (%d)", name ? name : "<input>", line);
|
||||
return nameBuffer;
|
||||
}
|
||||
|
||||
void CodeBlock::removeFromCodeList()
|
||||
{
|
||||
for(CodeBlock **walk = &smCodeBlockList; *walk; walk = &((*walk)->nextFile))
|
||||
{
|
||||
if(*walk == this)
|
||||
{
|
||||
*walk = nextFile;
|
||||
|
||||
// clear out all breakpoints
|
||||
clearAllBreaks();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Let the telnet debugger know that this code
|
||||
// block has been unloaded and that it needs to
|
||||
// remove references to it.
|
||||
if ( TelDebugger )
|
||||
TelDebugger->clearCodeBlockPointers( this );
|
||||
}
|
||||
|
||||
void CodeBlock::calcBreakList()
|
||||
{
|
||||
U32 size = 0;
|
||||
S32 line = -1;
|
||||
U32 seqCount = 0;
|
||||
U32 i;
|
||||
for(i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 lineNumber = lineBreakPairs[i * 2];
|
||||
if(lineNumber == U32(line + 1))
|
||||
seqCount++;
|
||||
else
|
||||
{
|
||||
if(seqCount)
|
||||
size++;
|
||||
size++;
|
||||
seqCount = 1;
|
||||
}
|
||||
line = lineNumber;
|
||||
}
|
||||
if(seqCount)
|
||||
size++;
|
||||
|
||||
breakList = new U32[size];
|
||||
breakListSize = size;
|
||||
line = -1;
|
||||
seqCount = 0;
|
||||
size = 0;
|
||||
|
||||
for(i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 lineNumber = lineBreakPairs[i * 2];
|
||||
|
||||
if(lineNumber == U32(line + 1))
|
||||
seqCount++;
|
||||
else
|
||||
{
|
||||
if(seqCount)
|
||||
breakList[size++] = seqCount;
|
||||
breakList[size++] = lineNumber - getMax(0, line) - 1;
|
||||
seqCount = 1;
|
||||
}
|
||||
|
||||
line = lineNumber;
|
||||
}
|
||||
|
||||
if(seqCount)
|
||||
breakList[size++] = seqCount;
|
||||
|
||||
for(i = 0; i < lineBreakPairCount; i++)
|
||||
{
|
||||
U32 *p = lineBreakPairs + i * 2;
|
||||
p[0] = (p[0] << 8) | code[p[1]];
|
||||
}
|
||||
|
||||
// Let the telnet debugger know that this code
|
||||
// block has been loaded and that it can add break
|
||||
// points it has for it.
|
||||
if ( TelDebugger )
|
||||
TelDebugger->addAllBreakpoints( this );
|
||||
}
|
||||
|
||||
bool CodeBlock::read(StringTableEntry fileName, Stream &st)
|
||||
{
|
||||
name = fileName;
|
||||
|
||||
//
|
||||
if (name)
|
||||
{
|
||||
if (const char *slash = dStrchr(this->name, '/'))
|
||||
{
|
||||
char root[512];
|
||||
dStrncpy(root, this->name, slash-this->name);
|
||||
root[slash-this->name] = 0;
|
||||
mRoot = StringTable->insert(root);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
addToCodeList();
|
||||
|
||||
U32 globalSize,size,i;
|
||||
st.read(&size);
|
||||
if(size)
|
||||
{
|
||||
globalSize = size;
|
||||
globalStrings = new char[size];
|
||||
st.read(size, globalStrings);
|
||||
}
|
||||
st.read(&size);
|
||||
if(size)
|
||||
{
|
||||
functionStrings = new char[size];
|
||||
st.read(size, functionStrings);
|
||||
}
|
||||
st.read(&size);
|
||||
if(size)
|
||||
{
|
||||
globalFloats = new F64[size];
|
||||
for(U32 i = 0; i < size; i++)
|
||||
st.read(&globalFloats[i]);
|
||||
}
|
||||
st.read(&size);
|
||||
if(size)
|
||||
{
|
||||
functionFloats = new F64[size];
|
||||
for(U32 i = 0; i < size; i++)
|
||||
st.read(&functionFloats[i]);
|
||||
}
|
||||
U32 codeSize;
|
||||
st.read(&codeSize);
|
||||
st.read(&lineBreakPairCount);
|
||||
|
||||
U32 totSize = codeSize + lineBreakPairCount * 2;
|
||||
code = new U32[totSize];
|
||||
|
||||
for(i = 0; i < codeSize; i++)
|
||||
{
|
||||
U8 b;
|
||||
st.read(&b);
|
||||
if(b == 0xFF)
|
||||
st.read(&code[i]);
|
||||
else
|
||||
code[i] = b;
|
||||
}
|
||||
|
||||
for(i = codeSize; i < totSize; i++)
|
||||
st.read(&code[i]);
|
||||
|
||||
lineBreakPairs = code + codeSize;
|
||||
|
||||
// StringTable-ize our identifiers.
|
||||
U32 identCount;
|
||||
st.read(&identCount);
|
||||
while(identCount--)
|
||||
{
|
||||
U32 offset;
|
||||
st.read(&offset);
|
||||
StringTableEntry ste;
|
||||
if(offset < globalSize)
|
||||
ste = StringTable->insert(globalStrings + offset);
|
||||
else
|
||||
ste = StringTable->insert("");
|
||||
U32 count;
|
||||
st.read(&count);
|
||||
while(count--)
|
||||
{
|
||||
U32 ip;
|
||||
st.read(&ip);
|
||||
code[ip] = *((U32 *) &ste);
|
||||
}
|
||||
}
|
||||
|
||||
if(lineBreakPairCount)
|
||||
calcBreakList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CodeBlock::compile(const char *codeFileName, StringTableEntry fileName, const char *script)
|
||||
{
|
||||
gSyntaxError = false;
|
||||
|
||||
consoleAllocReset();
|
||||
|
||||
STEtoU32 = compileSTEtoU32;
|
||||
|
||||
statementList = NULL;
|
||||
|
||||
// Set up the parser.
|
||||
smCurrentParser = getParserForFile(fileName);
|
||||
AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName));
|
||||
|
||||
// Now do some parsing.
|
||||
smCurrentParser->setScanBuffer(script, fileName);
|
||||
smCurrentParser->restart(NULL);
|
||||
smCurrentParser->parse();
|
||||
|
||||
if(gSyntaxError)
|
||||
{
|
||||
consoleAllocReset();
|
||||
return false;
|
||||
}
|
||||
|
||||
FileStream st;
|
||||
if(!ResourceManager->openFileForWrite(st, codeFileName))
|
||||
return false;
|
||||
st.write(U32(Con::DSOVersion));
|
||||
|
||||
// Reset all our value tables...
|
||||
resetTables();
|
||||
|
||||
smInFunction = false;
|
||||
smBreakLineCount = 0;
|
||||
setBreakCodeBlock(this);
|
||||
|
||||
if(statementList)
|
||||
codeSize = precompileBlock(statementList, 0) + 1;
|
||||
else
|
||||
codeSize = 1;
|
||||
|
||||
lineBreakPairCount = smBreakLineCount;
|
||||
code = new U32[codeSize + smBreakLineCount * 2];
|
||||
lineBreakPairs = code + codeSize;
|
||||
|
||||
// Write string table data...
|
||||
getGlobalStringTable().write(st);
|
||||
getFunctionStringTable().write(st);
|
||||
|
||||
// Write float table data...
|
||||
getGlobalFloatTable().write(st);
|
||||
getFunctionFloatTable().write(st);
|
||||
|
||||
smBreakLineCount = 0;
|
||||
U32 lastIp;
|
||||
if(statementList)
|
||||
lastIp = compileBlock(statementList, code, 0, 0, 0);
|
||||
else
|
||||
lastIp = 0;
|
||||
|
||||
if(lastIp != codeSize - 1)
|
||||
Con::errorf(ConsoleLogEntry::General, "CodeBlock::compile - precompile size mismatch, a precompile/compile function pair is probably mismatched.");
|
||||
|
||||
code[lastIp++] = OP_RETURN;
|
||||
U32 totSize = codeSize + smBreakLineCount * 2;
|
||||
st.write(codeSize);
|
||||
st.write(lineBreakPairCount);
|
||||
|
||||
// Write out our bytecode, doing a bit of compression for low numbers.
|
||||
U32 i;
|
||||
for(i = 0; i < codeSize; i++)
|
||||
{
|
||||
if(code[i] < 0xFF)
|
||||
st.write(U8(code[i]));
|
||||
else
|
||||
{
|
||||
st.write(U8(0xFF));
|
||||
st.write(code[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the break info...
|
||||
for(i = codeSize; i < totSize; i++)
|
||||
st.write(code[i]);
|
||||
|
||||
getIdentTable().write(st);
|
||||
|
||||
consoleAllocReset();
|
||||
st.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *CodeBlock::compileExec(StringTableEntry fileName, const char *string, bool noCalls, int setFrame)
|
||||
{
|
||||
STEtoU32 = evalSTEtoU32;
|
||||
consoleAllocReset();
|
||||
|
||||
name = fileName;
|
||||
|
||||
if(name)
|
||||
addToCodeList();
|
||||
|
||||
statementList = NULL;
|
||||
|
||||
// Set up the parser.
|
||||
smCurrentParser = getParserForFile(fileName);
|
||||
AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName));
|
||||
|
||||
// Now do some parsing.
|
||||
smCurrentParser->setScanBuffer(string, fileName);
|
||||
smCurrentParser->restart(NULL);
|
||||
smCurrentParser->parse();
|
||||
|
||||
if(!statementList)
|
||||
{
|
||||
delete this;
|
||||
return "";
|
||||
}
|
||||
|
||||
resetTables();
|
||||
|
||||
smInFunction = false;
|
||||
smBreakLineCount = 0;
|
||||
setBreakCodeBlock(this);
|
||||
|
||||
codeSize = precompileBlock(statementList, 0) + 1;
|
||||
|
||||
lineBreakPairCount = smBreakLineCount;
|
||||
|
||||
globalStrings = getGlobalStringTable().build();
|
||||
functionStrings = getFunctionStringTable().build();
|
||||
globalFloats = getGlobalFloatTable().build();
|
||||
functionFloats = getFunctionFloatTable().build();
|
||||
|
||||
code = new U32[codeSize + lineBreakPairCount * 2];
|
||||
lineBreakPairs = code + codeSize;
|
||||
|
||||
smBreakLineCount = 0;
|
||||
U32 lastIp = compileBlock(statementList, code, 0, 0, 0);
|
||||
code[lastIp++] = OP_RETURN;
|
||||
|
||||
consoleAllocReset();
|
||||
|
||||
if(lineBreakPairCount && fileName)
|
||||
calcBreakList();
|
||||
|
||||
if(lastIp != codeSize)
|
||||
Con::warnf(ConsoleLogEntry::General, "precompile size mismatch");
|
||||
|
||||
return exec(0, fileName, NULL, 0, 0, noCalls, NULL, setFrame);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
void CodeBlock::incRefCount()
|
||||
{
|
||||
refCount++;
|
||||
}
|
||||
|
||||
void CodeBlock::decRefCount()
|
||||
{
|
||||
refCount--;
|
||||
if(!refCount)
|
||||
delete this;
|
||||
}
|
129
engine/console/codeBlock.h
Executable file
129
engine/console/codeBlock.h
Executable file
@ -0,0 +1,129 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _CODEBLOCK_H_
|
||||
#define _CODEBLOCK_H_
|
||||
|
||||
#include "console/compiler.h"
|
||||
#include "console/consoleParser.h"
|
||||
|
||||
class Stream;
|
||||
|
||||
/// Core TorqueScript code management class.
|
||||
///
|
||||
/// This class represents a block of code, usually mapped directly to a file.
|
||||
class CodeBlock
|
||||
{
|
||||
private:
|
||||
static CodeBlock* smCodeBlockList;
|
||||
static CodeBlock* smCurrentCodeBlock;
|
||||
|
||||
public:
|
||||
static U32 smBreakLineCount;
|
||||
static bool smInFunction;
|
||||
static Compiler::ConsoleParser * smCurrentParser;
|
||||
|
||||
static CodeBlock* getCurrentBlock()
|
||||
{
|
||||
return smCurrentCodeBlock;
|
||||
}
|
||||
|
||||
static CodeBlock *getCodeBlockList()
|
||||
{
|
||||
return smCodeBlockList;
|
||||
}
|
||||
|
||||
static StringTableEntry getCurrentCodeBlockName();
|
||||
static CodeBlock *find(StringTableEntry);
|
||||
|
||||
CodeBlock();
|
||||
~CodeBlock();
|
||||
|
||||
StringTableEntry name;
|
||||
|
||||
char *globalStrings;
|
||||
char *functionStrings;
|
||||
|
||||
F64 *globalFloats;
|
||||
F64 *functionFloats;
|
||||
|
||||
U32 codeSize;
|
||||
U32 *code;
|
||||
|
||||
U32 refCount;
|
||||
U32 lineBreakPairCount;
|
||||
U32 *lineBreakPairs;
|
||||
U32 breakListSize;
|
||||
U32 *breakList;
|
||||
CodeBlock *nextFile;
|
||||
StringTableEntry mRoot;
|
||||
|
||||
|
||||
void addToCodeList();
|
||||
void removeFromCodeList();
|
||||
void calcBreakList();
|
||||
void clearAllBreaks();
|
||||
void setAllBreaks();
|
||||
|
||||
/// Returns the first breakable line or 0 if none was found.
|
||||
/// @param lineNumber The one based line number.
|
||||
U32 findFirstBreakLine(U32 lineNumber);
|
||||
|
||||
void clearBreakpoint(U32 lineNumber);
|
||||
|
||||
/// Set a OP_BREAK instruction on a line. If a break
|
||||
/// is not possible on that line it returns false.
|
||||
/// @param lineNumber The one based line number.
|
||||
bool setBreakpoint(U32 lineNumber);
|
||||
|
||||
void findBreakLine(U32 ip, U32 &line, U32 &instruction);
|
||||
void getFunctionArgs(char buffer[1024], U32 offset);
|
||||
const char *getFileLine(U32 ip);
|
||||
|
||||
bool read(StringTableEntry fileName, Stream &st);
|
||||
bool compile(const char *dsoName, StringTableEntry fileName, const char *script);
|
||||
|
||||
void incRefCount();
|
||||
void decRefCount();
|
||||
|
||||
/// Compiles and executes a block of script storing the compiled code in this
|
||||
/// CodeBlock. If there is no filename breakpoints will not be generated and
|
||||
/// the CodeBlock will not be added to the linked list of loaded CodeBlocks.
|
||||
/// Note that if the script contains no executable statements the CodeBlock
|
||||
/// will delete itself on return an empty string. The return string is any
|
||||
/// result of the code executed, if any, or an empty string.
|
||||
///
|
||||
/// @param fileName The file name, including path and extension, for the
|
||||
/// block of code or an empty string.
|
||||
/// @param script The script code to compile and execute.
|
||||
/// @param noCalls Skips calling functions from the script.
|
||||
/// @param setFrame A zero based index of the stack frame to execute the code
|
||||
/// with, zero being the top of the stack. If the the index is
|
||||
/// -1 a new frame is created. If the index is out of range the
|
||||
/// top stack frame is used.
|
||||
const char *compileExec(StringTableEntry fileName, const char *script,
|
||||
bool noCalls, int setFrame = -1 );
|
||||
|
||||
/// Executes the existing code in the CodeBlock. The return string is any
|
||||
/// result of the code executed, if any, or an empty string.
|
||||
///
|
||||
/// @param offset The instruction offset to start executing from.
|
||||
/// @param fnName The name of the function to execute or null.
|
||||
/// @param ns The namespace of the function to execute or null.
|
||||
/// @param argc The number of parameters passed to the function or
|
||||
/// zero to execute code outside of a function.
|
||||
/// @param argv The function parameter list.
|
||||
/// @param noCalls Skips calling functions from the script.
|
||||
/// @param setFrame A zero based index of the stack frame to execute the code
|
||||
/// with, zero being the top of the stack. If the the index is
|
||||
/// -1 a new frame is created. If the index is out of range the
|
||||
/// top stack frame is used.
|
||||
/// @param packageName The code package name or null.
|
||||
const char *exec(U32 offset, const char *fnName, Namespace *ns, U32 argc,
|
||||
const char **argv, bool noCalls, StringTableEntry packageName,
|
||||
S32 setFrame = -1);
|
||||
};
|
||||
|
||||
#endif
|
1205
engine/console/compiledEval.cc
Executable file
1205
engine/console/compiledEval.cc
Executable file
File diff suppressed because it is too large
Load Diff
271
engine/console/compiler.cc
Executable file
271
engine/console/compiler.cc
Executable file
@ -0,0 +1,271 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/console.h"
|
||||
#include "console/telnetDebugger.h"
|
||||
#include "platform/event.h"
|
||||
|
||||
#include "console/ast.h"
|
||||
#include "core/tAlgorithm.h"
|
||||
#include "core/resManager.h"
|
||||
|
||||
#include "core/findMatch.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "core/fileStream.h"
|
||||
#include "console/compiler.h"
|
||||
|
||||
#include "console/simBase.h"
|
||||
|
||||
namespace Compiler
|
||||
{
|
||||
|
||||
F64 consoleStringToNumber(const char *str, StringTableEntry file, U32 line)
|
||||
{
|
||||
F64 val = dAtof(str);
|
||||
if(val != 0)
|
||||
return val;
|
||||
else if(!dStricmp(str, "true"))
|
||||
return 1;
|
||||
else if(!dStricmp(str, "false"))
|
||||
return 0;
|
||||
else if(file)
|
||||
{
|
||||
Con::warnf(ConsoleLogEntry::General, "%s (%d): string always evaluates to 0.", file, line);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
CompilerStringTable *gCurrentStringTable, gGlobalStringTable, gFunctionStringTable;
|
||||
CompilerFloatTable *gCurrentFloatTable, gGlobalFloatTable, gFunctionFloatTable;
|
||||
DataChunker gConsoleAllocator;
|
||||
CompilerIdentTable gIdentTable;
|
||||
CodeBlock *gCurBreakBlock;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
|
||||
CodeBlock *getBreakCodeBlock() { return gCurBreakBlock; }
|
||||
void setBreakCodeBlock(CodeBlock *cb) { gCurBreakBlock = cb; }
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
U32 evalSTEtoU32(StringTableEntry ste, U32)
|
||||
{
|
||||
return *((U32 *) &ste);
|
||||
}
|
||||
|
||||
U32 compileSTEtoU32(StringTableEntry ste, U32 ip)
|
||||
{
|
||||
if(ste)
|
||||
getIdentTable().add(ste, ip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
U32 (*STEtoU32)(StringTableEntry ste, U32 ip) = evalSTEtoU32;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
bool gSyntaxError = false;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
CompilerStringTable *getCurrentStringTable() { return gCurrentStringTable; }
|
||||
CompilerStringTable &getGlobalStringTable() { return gGlobalStringTable; }
|
||||
CompilerStringTable &getFunctionStringTable() { return gFunctionStringTable; }
|
||||
|
||||
void setCurrentStringTable (CompilerStringTable* cst) { gCurrentStringTable = cst; }
|
||||
|
||||
CompilerFloatTable *getCurrentFloatTable() { return gCurrentFloatTable; }
|
||||
CompilerFloatTable &getGlobalFloatTable() { return gGlobalFloatTable; }
|
||||
CompilerFloatTable &getFunctionFloatTable() { return gFunctionFloatTable; }
|
||||
|
||||
void setCurrentFloatTable (CompilerFloatTable* cst) { gCurrentFloatTable = cst; }
|
||||
|
||||
CompilerIdentTable &getIdentTable() { return gIdentTable; }
|
||||
|
||||
void precompileIdent(StringTableEntry ident)
|
||||
{
|
||||
if(ident)
|
||||
gGlobalStringTable.add(ident);
|
||||
}
|
||||
|
||||
void resetTables()
|
||||
{
|
||||
setCurrentStringTable(&gGlobalStringTable);
|
||||
setCurrentFloatTable(&gGlobalFloatTable);
|
||||
getGlobalFloatTable().reset();
|
||||
getGlobalStringTable().reset();
|
||||
getFunctionFloatTable().reset();
|
||||
getFunctionStringTable().reset();
|
||||
getIdentTable().reset();
|
||||
}
|
||||
|
||||
void *consoleAlloc(U32 size) { return gConsoleAllocator.alloc(size); }
|
||||
void consoleAllocReset() { gConsoleAllocator.freeBlocks(); }
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
using namespace Compiler;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
|
||||
U32 CompilerStringTable::add(const char *str, bool caseSens, bool tag)
|
||||
{
|
||||
// Is it already in?
|
||||
Entry **walk;
|
||||
for(walk = &list; *walk; walk = &((*walk)->next))
|
||||
{
|
||||
if((*walk)->tag != tag)
|
||||
continue;
|
||||
|
||||
if(caseSens)
|
||||
{
|
||||
if(!dStrcmp((*walk)->string, str))
|
||||
return (*walk)->start;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!dStricmp((*walk)->string, str))
|
||||
return (*walk)->start;
|
||||
}
|
||||
}
|
||||
|
||||
// Write it out.
|
||||
Entry *newStr = (Entry *) consoleAlloc(sizeof(Entry));
|
||||
*walk = newStr;
|
||||
newStr->next = NULL;
|
||||
newStr->start = totalLen;
|
||||
U32 len = dStrlen(str) + 1;
|
||||
if(tag && len < 7) // alloc space for the numeric tag 1 for tag, 5 for # and 1 for nul
|
||||
len = 7;
|
||||
totalLen += len;
|
||||
newStr->string = (char *) consoleAlloc(len);
|
||||
newStr->len = len;
|
||||
newStr->tag = tag;
|
||||
dStrcpy(newStr->string, str);
|
||||
return newStr->start;
|
||||
}
|
||||
|
||||
U32 CompilerStringTable::addIntString(U32 value)
|
||||
{
|
||||
dSprintf(buf, sizeof(buf), "%d", value);
|
||||
return add(buf);
|
||||
}
|
||||
|
||||
U32 CompilerStringTable::addFloatString(F64 value)
|
||||
{
|
||||
dSprintf(buf, sizeof(buf), "%g", value);
|
||||
return add(buf);
|
||||
}
|
||||
void CompilerStringTable::reset()
|
||||
{
|
||||
list = NULL;
|
||||
totalLen = 0;
|
||||
}
|
||||
char *CompilerStringTable::build()
|
||||
{
|
||||
char *ret = new char[totalLen];
|
||||
for(Entry *walk = list; walk; walk = walk->next)
|
||||
dStrcpy(ret + walk->start, walk->string);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CompilerStringTable::write(Stream &st)
|
||||
{
|
||||
st.write(totalLen);
|
||||
for(Entry *walk = list; walk; walk = walk->next)
|
||||
st.write(walk->len, walk->string);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
U32 CompilerFloatTable::add(F64 value)
|
||||
{
|
||||
Entry **walk;
|
||||
U32 i = 0;
|
||||
for(walk = &list; *walk; walk = &((*walk)->next), i++)
|
||||
if(value == (*walk)->val)
|
||||
return i;
|
||||
Entry *newFloat = (Entry *) consoleAlloc(sizeof(Entry));
|
||||
newFloat->val = value;
|
||||
newFloat->next = NULL;
|
||||
count++;
|
||||
*walk = newFloat;
|
||||
return count-1;
|
||||
}
|
||||
void CompilerFloatTable::reset()
|
||||
{
|
||||
list = NULL;
|
||||
count = 0;
|
||||
}
|
||||
F64 *CompilerFloatTable::build()
|
||||
{
|
||||
F64 *ret = new F64[count];
|
||||
U32 i = 0;
|
||||
for(Entry *walk = list; walk; walk = walk->next, i++)
|
||||
ret[i] = walk->val;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CompilerFloatTable::write(Stream &st)
|
||||
{
|
||||
st.write(count);
|
||||
for(Entry *walk = list; walk; walk = walk->next)
|
||||
st.write(walk->val);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
void CompilerIdentTable::reset()
|
||||
{
|
||||
list = NULL;
|
||||
}
|
||||
|
||||
void CompilerIdentTable::add(StringTableEntry ste, U32 ip)
|
||||
{
|
||||
U32 index = gGlobalStringTable.add(ste, false);
|
||||
Entry *newEntry = (Entry *) consoleAlloc(sizeof(Entry));
|
||||
newEntry->offset = index;
|
||||
newEntry->ip = ip;
|
||||
for(Entry *walk = list; walk; walk = walk->next)
|
||||
{
|
||||
if(walk->offset == index)
|
||||
{
|
||||
newEntry->nextIdent = walk->nextIdent;
|
||||
walk->nextIdent = newEntry;
|
||||
return;
|
||||
}
|
||||
}
|
||||
newEntry->next = list;
|
||||
list = newEntry;
|
||||
newEntry->nextIdent = NULL;
|
||||
}
|
||||
|
||||
void CompilerIdentTable::write(Stream &st)
|
||||
{
|
||||
U32 count = 0;
|
||||
Entry * walk;
|
||||
for(walk = list; walk; walk = walk->next)
|
||||
count++;
|
||||
st.write(count);
|
||||
for(walk = list; walk; walk = walk->next)
|
||||
{
|
||||
U32 ec = 0;
|
||||
Entry * el;
|
||||
for(el = walk; el; el = el->nextIdent)
|
||||
ec++;
|
||||
st.write(walk->offset);
|
||||
st.write(ec);
|
||||
for(el = walk; el; el = el->nextIdent)
|
||||
st.write(el->ip);
|
||||
}
|
||||
}
|
235
engine/console/compiler.h
Executable file
235
engine/console/compiler.h
Executable file
@ -0,0 +1,235 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#ifndef _COMPILER_H_
|
||||
#define _COMPILER_H_
|
||||
|
||||
class Stream;
|
||||
class DataChunker;
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/ast.h"
|
||||
#include "console/codeBlock.h"
|
||||
|
||||
// Autogenerated, so we should only ever include from once place - here.
|
||||
// (We can't stick include guards in it without patching bison.)
|
||||
#ifndef _CMDGRAM_H_
|
||||
#define _CMDGRAM_H_
|
||||
#include "console/cmdgram.h"
|
||||
#endif
|
||||
|
||||
namespace Compiler
|
||||
{
|
||||
/// The opcodes for the TorqueScript VM.
|
||||
enum CompiledInstructions
|
||||
{
|
||||
OP_FUNC_DECL,
|
||||
OP_CREATE_OBJECT,
|
||||
OP_ADD_OBJECT,
|
||||
OP_END_OBJECT,
|
||||
OP_JMPIFFNOT,
|
||||
OP_JMPIFNOT,
|
||||
OP_JMPIFF,
|
||||
OP_JMPIF,
|
||||
OP_JMPIFNOT_NP,
|
||||
OP_JMPIF_NP,
|
||||
OP_JMP,
|
||||
OP_RETURN,
|
||||
OP_CMPEQ,
|
||||
OP_CMPGR,
|
||||
OP_CMPGE,
|
||||
OP_CMPLT,
|
||||
OP_CMPLE,
|
||||
OP_CMPNE,
|
||||
OP_XOR,
|
||||
OP_MOD,
|
||||
OP_BITAND,
|
||||
OP_BITOR,
|
||||
OP_NOT,
|
||||
OP_NOTF,
|
||||
OP_ONESCOMPLEMENT,
|
||||
|
||||
OP_SHR,
|
||||
OP_SHL,
|
||||
OP_AND,
|
||||
OP_OR,
|
||||
|
||||
OP_ADD,
|
||||
OP_SUB,
|
||||
OP_MUL,
|
||||
OP_DIV,
|
||||
OP_NEG,
|
||||
|
||||
OP_SETCURVAR,
|
||||
OP_SETCURVAR_CREATE,
|
||||
OP_SETCURVAR_ARRAY,
|
||||
OP_SETCURVAR_ARRAY_CREATE,
|
||||
|
||||
OP_LOADVAR_UINT,
|
||||
OP_LOADVAR_FLT,
|
||||
OP_LOADVAR_STR,
|
||||
|
||||
OP_SAVEVAR_UINT,
|
||||
OP_SAVEVAR_FLT,
|
||||
OP_SAVEVAR_STR,
|
||||
|
||||
OP_SETCUROBJECT,
|
||||
OP_SETCUROBJECT_NEW,
|
||||
|
||||
OP_SETCURFIELD,
|
||||
OP_SETCURFIELD_ARRAY,
|
||||
|
||||
OP_LOADFIELD_UINT,
|
||||
OP_LOADFIELD_FLT,
|
||||
OP_LOADFIELD_STR,
|
||||
|
||||
OP_SAVEFIELD_UINT,
|
||||
OP_SAVEFIELD_FLT,
|
||||
OP_SAVEFIELD_STR,
|
||||
|
||||
OP_STR_TO_UINT,
|
||||
OP_STR_TO_FLT,
|
||||
OP_STR_TO_NONE,
|
||||
OP_FLT_TO_UINT,
|
||||
OP_FLT_TO_STR,
|
||||
OP_FLT_TO_NONE,
|
||||
OP_UINT_TO_FLT,
|
||||
OP_UINT_TO_STR,
|
||||
OP_UINT_TO_NONE,
|
||||
|
||||
OP_LOADIMMED_UINT,
|
||||
OP_LOADIMMED_FLT,
|
||||
OP_TAG_TO_STR,
|
||||
OP_LOADIMMED_STR,
|
||||
OP_LOADIMMED_IDENT,
|
||||
|
||||
OP_CALLFUNC_RESOLVE,
|
||||
OP_CALLFUNC,
|
||||
|
||||
OP_ADVANCE_STR,
|
||||
OP_ADVANCE_STR_APPENDCHAR,
|
||||
OP_ADVANCE_STR_COMMA,
|
||||
OP_ADVANCE_STR_NUL,
|
||||
OP_REWIND_STR,
|
||||
OP_TERMINATE_REWIND_STR,
|
||||
OP_COMPARE_STR,
|
||||
|
||||
OP_PUSH,
|
||||
OP_PUSH_FRAME,
|
||||
|
||||
OP_BREAK,
|
||||
|
||||
OP_INVALID
|
||||
};
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
F64 consoleStringToNumber(const char *str, StringTableEntry file = 0, U32 line = 0);
|
||||
U32 precompileBlock(StmtNode *block, U32 loopCount);
|
||||
U32 compileBlock(StmtNode *block, U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
struct CompilerIdentTable
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
U32 offset;
|
||||
U32 ip;
|
||||
Entry *next;
|
||||
Entry *nextIdent;
|
||||
};
|
||||
Entry *list;
|
||||
void add(StringTableEntry ste, U32 ip);
|
||||
void reset();
|
||||
void write(Stream &st);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
struct CompilerStringTable
|
||||
{
|
||||
U32 totalLen;
|
||||
struct Entry
|
||||
{
|
||||
char *string;
|
||||
U32 start;
|
||||
U32 len;
|
||||
bool tag;
|
||||
Entry *next;
|
||||
};
|
||||
Entry *list;
|
||||
|
||||
char buf[256];
|
||||
|
||||
U32 add(const char *str, bool caseSens = true, bool tag = false);
|
||||
U32 addIntString(U32 value);
|
||||
U32 addFloatString(F64 value);
|
||||
void reset();
|
||||
char *build();
|
||||
void write(Stream &st);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
struct CompilerFloatTable
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
F64 val;
|
||||
Entry *next;
|
||||
};
|
||||
U32 count;
|
||||
Entry *list;
|
||||
|
||||
U32 add(F64 value);
|
||||
void reset();
|
||||
F64 *build();
|
||||
void write(Stream &st);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
inline StringTableEntry U32toSTE(U32 u)
|
||||
{
|
||||
return *((StringTableEntry *) &u);
|
||||
}
|
||||
|
||||
extern U32 (*STEtoU32)(StringTableEntry ste, U32 ip);
|
||||
|
||||
U32 evalSTEtoU32(StringTableEntry ste, U32);
|
||||
U32 compileSTEtoU32(StringTableEntry ste, U32 ip);
|
||||
|
||||
CompilerStringTable *getCurrentStringTable();
|
||||
CompilerStringTable &getGlobalStringTable();
|
||||
CompilerStringTable &getFunctionStringTable();
|
||||
|
||||
void setCurrentStringTable (CompilerStringTable* cst);
|
||||
|
||||
CompilerFloatTable *getCurrentFloatTable();
|
||||
CompilerFloatTable &getGlobalFloatTable();
|
||||
CompilerFloatTable &getFunctionFloatTable();
|
||||
|
||||
void setCurrentFloatTable (CompilerFloatTable* cst);
|
||||
|
||||
CompilerIdentTable &getIdentTable();
|
||||
|
||||
void precompileIdent(StringTableEntry ident);
|
||||
|
||||
CodeBlock *getBreakCodeBlock();
|
||||
void setBreakCodeBlock(CodeBlock *cb);
|
||||
|
||||
/// Helper function to reset the float, string, and ident tables to a base
|
||||
/// starting state.
|
||||
void resetTables();
|
||||
|
||||
void *consoleAlloc(U32 size);
|
||||
void consoleAllocReset();
|
||||
|
||||
extern bool gSyntaxError;
|
||||
};
|
||||
|
||||
#endif
|
1043
engine/console/console.cc
Executable file
1043
engine/console/console.cc
Executable file
File diff suppressed because it is too large
Load Diff
808
engine/console/console.h
Executable file
808
engine/console/console.h
Executable file
@ -0,0 +1,808 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _CONSOLE_H_
|
||||
#define _CONSOLE_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _BITSET_H_
|
||||
#include "core/bitSet.h"
|
||||
#endif
|
||||
#include <stdarg.h>
|
||||
|
||||
class SimObject;
|
||||
struct EnumTable;
|
||||
class Namespace;
|
||||
|
||||
/// Indicates that warnings about undefined script variables should be displayed.
|
||||
///
|
||||
/// @note This is set and controlled by script.
|
||||
extern bool gWarnUndefinedScriptVariables;
|
||||
|
||||
enum StringTableConstants
|
||||
{
|
||||
StringTagPrefixByte = 0x01 ///< Magic value prefixed to tagged strings.
|
||||
};
|
||||
|
||||
/// Represents an entry in the log.
|
||||
struct ConsoleLogEntry
|
||||
{
|
||||
/// This field indicates the severity of the log entry.
|
||||
///
|
||||
/// Log entries are filtered and displayed differently based on
|
||||
/// their severity. Errors are highlighted red, while normal entries
|
||||
/// are displayed as normal text. Often times, the engine will be
|
||||
/// configured to hide all log entries except warnings or errors,
|
||||
/// or to perform a special notification when it encounters an error.
|
||||
enum Level
|
||||
{
|
||||
Normal = 0,
|
||||
Warning,
|
||||
Error,
|
||||
NUM_CLASS
|
||||
} mLevel;
|
||||
|
||||
/// Used to associate a log entry with a module.
|
||||
///
|
||||
/// Log entries can come from different sources; for instance,
|
||||
/// the scripting engine, or the network code. This allows the
|
||||
/// logging system to be aware of where different log entries
|
||||
/// originated from.
|
||||
enum Type
|
||||
{
|
||||
General = 0,
|
||||
Assert,
|
||||
Script,
|
||||
GUI,
|
||||
Network,
|
||||
NUM_TYPE
|
||||
} mType;
|
||||
|
||||
/// Indicates the actual log entry.
|
||||
///
|
||||
/// This contains a description of the event being logged.
|
||||
/// For instance, "unable to access file", or "player connected
|
||||
/// successfully", or nearly anything else you might imagine.
|
||||
///
|
||||
/// Typically, the description should contain a concise, descriptive
|
||||
/// string describing whatever is being logged. Whenever possible,
|
||||
/// include useful details like the name of the file being accessed,
|
||||
/// or the id of the player or GuiControl, so that if a log needs
|
||||
/// to be used to locate a bug, it can be done as painlessly as
|
||||
/// possible.
|
||||
const char *mString;
|
||||
};
|
||||
|
||||
/// Scripting engine representation of an enum.
|
||||
///
|
||||
/// This data structure is used by the scripting engine
|
||||
/// to expose enumerations to the scripting language. It
|
||||
/// acts to relate named constants to integer values, just
|
||||
/// like an enum in C++.
|
||||
struct EnumTable
|
||||
{
|
||||
/// Number of enumerated items in the table.
|
||||
S32 size;
|
||||
|
||||
/// This represents a specific item in the enumeration.
|
||||
struct Enums
|
||||
{
|
||||
S32 index; ///< Index label maps to.
|
||||
const char *label;///< Label for this index.
|
||||
};
|
||||
|
||||
Enums *table;
|
||||
|
||||
/// Constructor.
|
||||
///
|
||||
/// This sets up the EnumTable with predefined data.
|
||||
///
|
||||
/// @param sSize Size of the table.
|
||||
/// @param sTable Pointer to table of Enums.
|
||||
///
|
||||
/// @see gLiquidTypeTable
|
||||
/// @see gAlignTable
|
||||
EnumTable(S32 sSize, Enums *sTable)
|
||||
{ size = sSize; table = sTable; }
|
||||
};
|
||||
|
||||
typedef const char *StringTableEntry;
|
||||
|
||||
/// @defgroup console_callbacks Scripting Engine Callbacks
|
||||
///
|
||||
/// The scripting engine makes heavy use of callbacks to represent
|
||||
/// function exposed to the scripting language. StringCallback,
|
||||
/// IntCallback, FloatCallback, VoidCallback, and BoolCallback all
|
||||
/// represent exposed script functions returning different types.
|
||||
///
|
||||
/// ConsumerCallback is used with the function Con::addConsumer; functions
|
||||
/// registered with Con::addConsumer are called whenever something is outputted
|
||||
/// to the console. For instance, the TelnetConsole registers itself with the
|
||||
/// console so it can echo the console over the network.
|
||||
///
|
||||
/// @note Callbacks to the scripting language - for instance, onExit(), which is
|
||||
/// a script function called when the engine is shutting down - are handled
|
||||
/// using Con::executef() and kin.
|
||||
/// @{
|
||||
|
||||
///
|
||||
typedef const char * (*StringCallback)(SimObject *obj, S32 argc, const char *argv[]);
|
||||
typedef S32 (*IntCallback)(SimObject *obj, S32 argc, const char *argv[]);
|
||||
typedef F32 (*FloatCallback)(SimObject *obj, S32 argc, const char *argv[]);
|
||||
typedef void (*VoidCallback)(SimObject *obj, S32 argc, const char *argv[]); // We have it return a value so things don't break..
|
||||
typedef bool (*BoolCallback)(SimObject *obj, S32 argc, const char *argv[]);
|
||||
|
||||
typedef void (*ConsumerCallback)(ConsoleLogEntry::Level level, const char *consoleLine);
|
||||
/// @}
|
||||
|
||||
/// @defgroup console_types Scripting Engine Type Functions
|
||||
///
|
||||
/// @see Con::registerType
|
||||
/// @{
|
||||
typedef const char* (*GetDataFunction)(void *dptr, EnumTable *tbl, BitSet32 flag);
|
||||
typedef void (*SetDataFunction)(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag);
|
||||
/// @}
|
||||
|
||||
/// This namespace contains the core of the console functionality.
|
||||
///
|
||||
/// @section con_intro Introduction
|
||||
///
|
||||
/// The console is a key part of Torque's architecture. It allows direct run-time control
|
||||
/// of many aspects of the engine.
|
||||
///
|
||||
/// @nosubgrouping
|
||||
namespace Con
|
||||
{
|
||||
/// Various configuration constants.
|
||||
enum Constants
|
||||
{
|
||||
/// This is the version number associated with DSO files.
|
||||
///
|
||||
/// If you make any changes to the way the scripting language works
|
||||
/// (such as DSO format changes, adding/removing op-codes) that would
|
||||
/// break compatibility, then you should increment this.
|
||||
///
|
||||
/// If you make a really major change, increment it to the next multiple
|
||||
/// of ten.
|
||||
///
|
||||
/// 12/29/04 - BJG - 33->34 Removed some opcodes, part of namespace upgrade.
|
||||
/// 12/30/04 - BJG - 34->35 Reordered some things, further general shuffling.
|
||||
/// 11/03/05 - BJG - 35->36 Integrated new debugger code.
|
||||
DSOVersion = 36,
|
||||
|
||||
MaxLineLength = 512, ///< Maximum length of a line of console input.
|
||||
MaxDataTypes = 256 ///< Maximum number of registered data types.
|
||||
};
|
||||
|
||||
/// @name Control Functions
|
||||
///
|
||||
/// The console must be initialized and shutdown appropriately during the
|
||||
/// lifetime of the app. These functions are used to manage this behavior.
|
||||
///
|
||||
/// @note Torque deals with this aspect of console management, so you don't need
|
||||
/// to call these functions in normal usage of the engine.
|
||||
/// @{
|
||||
|
||||
/// Initializes the console.
|
||||
///
|
||||
/// This performs the following steps:
|
||||
/// - Calls Namespace::init() to initialize the scripting namespace hierarchy.
|
||||
/// - Calls ConsoleConstructor::setup() to initialize globally defined console
|
||||
/// methods and functions.
|
||||
/// - Registers some basic global script variables.
|
||||
/// - Calls AbstractClassRep::init() to initialize Torque's class database.
|
||||
/// - Registers some basic global script functions that couldn't usefully
|
||||
/// be defined anywhere else.
|
||||
void init();
|
||||
|
||||
/// Shuts down the console.
|
||||
///
|
||||
/// This performs the following steps:
|
||||
/// - Closes the console log file.
|
||||
/// - Calls Namespace::shutdown() to shut down the scripting namespace hierarchy.
|
||||
void shutdown();
|
||||
|
||||
/// Is the console active at this time?
|
||||
bool isActive();
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Console Consumers
|
||||
///
|
||||
/// The console distributes its output through Torque by using
|
||||
/// consumers. Every time a new line is printed to the console,
|
||||
/// all the ConsumerCallbacks registered using addConsumer are
|
||||
/// called, in order.
|
||||
///
|
||||
/// @note The GuiConsole control, which provides the on-screen
|
||||
/// in-game console, uses a different technique to render
|
||||
/// the console. It calls getLockLog() to lock the Vector
|
||||
/// of on-screen console entries, then it renders them as
|
||||
/// needed. While the Vector is locked, the console will
|
||||
/// not change the Vector. When the GuiConsole control is
|
||||
/// done with the console entries, it calls unlockLog()
|
||||
/// to tell the console that it is again safe to modify
|
||||
/// the Vector.
|
||||
///
|
||||
/// @see TelnetConsole
|
||||
/// @see TelnetDebugger
|
||||
/// @see WinConsole
|
||||
/// @see MacCarbConsole
|
||||
/// @see StdConsole
|
||||
/// @see ConsoleLogger
|
||||
///
|
||||
/// @{
|
||||
void addConsumer(ConsumerCallback cb);
|
||||
void removeConsumer(ConsumerCallback cb);
|
||||
/// @}
|
||||
|
||||
/// @name Miscellaneous
|
||||
/// @{
|
||||
|
||||
/// Remove color marking information from a string.
|
||||
///
|
||||
/// @note It does this in-place, so be careful! It may
|
||||
/// potentially blast data if you're not careful.
|
||||
/// When in doubt, make a copy of the string first.
|
||||
void stripColorChars(char* line);
|
||||
|
||||
/// Convert from a relative script path to an absolute script path.
|
||||
///
|
||||
/// This is used in (among other places) the exec() script function, which
|
||||
/// takes a parameter indicating a script file and executes it. Script paths
|
||||
/// can be one of:
|
||||
/// - <b>Absolute:</b> <i>fps/foo/bar.cs</i> Paths of this sort are passed
|
||||
/// through.
|
||||
/// - <b>Mod-relative:</b> <i>~/foo/bar.cs</i> Paths of this sort have their
|
||||
/// replaced with the name of the current mod.
|
||||
/// - <b>File-relative:</b> <i>./baz/blip.cs</i> Paths of this sort are
|
||||
/// calculated relative to the path of the current scripting file.
|
||||
///
|
||||
/// @note This function determines paths relative to the currently executing
|
||||
/// CodeBlock. Calling it outside of script execution will result in
|
||||
/// it directly copying src to filename, since it won't know to what the
|
||||
/// path is relative!
|
||||
///
|
||||
/// @param filename Pointer to string buffer to fill with absolute path.
|
||||
/// @param size Size of buffer pointed to by filename.
|
||||
/// @param src Original, possibly relative script path.
|
||||
bool expandScriptFilename(char *filename, U32 size, const char *src);
|
||||
|
||||
/// Returns true if fn is a global scripting function.
|
||||
///
|
||||
/// This looks in the global namespace. It also checks to see if fn
|
||||
/// is in the StringTable; if not, it returns false.
|
||||
bool isFunction(const char *fn);
|
||||
|
||||
/// This is the basis for tab completion in the console.
|
||||
///
|
||||
/// @note This is an internally used function. You probably don't
|
||||
/// care much about how this works.
|
||||
///
|
||||
/// This function does some basic parsing to try to ascertain the namespace in which
|
||||
/// we are attempting to do tab completion, then bumps control off to the appropriate
|
||||
/// tabComplete function, either in SimObject or Namespace.
|
||||
///
|
||||
/// @param inputBuffer Pointer to buffer containing starting data, or last result.
|
||||
/// @param cursorPos Location of cursor in this buffer. This is used to indicate
|
||||
/// what part of the string should be kept and what part should
|
||||
/// be advanced to the next match if any.
|
||||
/// @param maxResultLength Maximum amount of result data to put into inputBuffer. This
|
||||
/// is capped by MaxCompletionBufferSize.
|
||||
/// @param forwardTab Should we go forward to next match or backwards to previous
|
||||
/// match? True indicates forward.
|
||||
U32 tabComplete(char* inputBuffer, U32 cursorPos, U32 maxResultLength, bool forwardTab);
|
||||
|
||||
/// @}
|
||||
|
||||
|
||||
/// @name Variable Management
|
||||
/// @{
|
||||
|
||||
/// Add a console variable that references the value of a variable in C++ code.
|
||||
///
|
||||
/// If a value is assigned to the console variable the C++ variable is updated,
|
||||
/// and vice-versa.
|
||||
///
|
||||
/// @param name Global console variable name to create
|
||||
/// @param type The type of the C++ variable; see the ConsoleDynamicTypes enum for a complete list.
|
||||
/// @param pointer Pointer to the variable.
|
||||
/// @see ConsoleDynamicTypes
|
||||
bool addVariable(const char *name, S32 type, void *pointer);
|
||||
|
||||
/// Remove a console variable.
|
||||
///
|
||||
/// @param name Global console variable name to remove
|
||||
/// @return true if variable existed before removal.
|
||||
bool removeVariable(const char *name);
|
||||
|
||||
/// Assign a string value to a locally scoped console variable
|
||||
///
|
||||
/// @note The context of the variable is determined by gEvalState; that is,
|
||||
/// by the currently executing code.
|
||||
///
|
||||
/// @param name Local console variable name to set
|
||||
/// @param value String value to assign to name
|
||||
void setLocalVariable(const char *name, const char *value);
|
||||
|
||||
/// Retrieve the string value to a locally scoped console variable
|
||||
///
|
||||
/// @note The context of the variable is determined by gEvalState; that is,
|
||||
/// by the currently executing code.
|
||||
///
|
||||
/// @param name Local console variable name to get
|
||||
const char* getLocalVariable(const char* name);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Global Variable Accessors
|
||||
/// @{
|
||||
/// Assign a string value to a global console variable
|
||||
/// @param name Global console variable name to set
|
||||
/// @param value String value to assign to this variable.
|
||||
void setVariable(const char *name, const char *value);
|
||||
|
||||
/// Retrieve the string value of a global console variable
|
||||
/// @param name Global Console variable name to query
|
||||
/// @return The string value of the variable or "" if the variable does not exist.
|
||||
const char* getVariable(const char* name);
|
||||
|
||||
/// Same as setVariable(), but for bools.
|
||||
void setBoolVariable (const char* name,bool var);
|
||||
|
||||
/// Same as getVariable(), but for bools.
|
||||
///
|
||||
/// @param name Name of the variable.
|
||||
/// @param def Default value to supply if no matching variable is found.
|
||||
bool getBoolVariable (const char* name,bool def = false);
|
||||
|
||||
/// Same as setVariable(), but for ints.
|
||||
void setIntVariable (const char* name,S32 var);
|
||||
|
||||
/// Same as getVariable(), but for ints.
|
||||
///
|
||||
/// @param name Name of the variable.
|
||||
/// @param def Default value to supply if no matching variable is found.
|
||||
S32 getIntVariable (const char* name,S32 def = 0);
|
||||
|
||||
/// Same as setVariable(), but for floats.
|
||||
void setFloatVariable(const char* name,F32 var);
|
||||
|
||||
/// Same as getVariable(), but for floats.
|
||||
///
|
||||
/// @param name Name of the variable.
|
||||
/// @param def Default value to supply if no matching variable is found.
|
||||
F32 getFloatVariable(const char* name,F32 def = .0f);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Global Function Registration
|
||||
/// @{
|
||||
|
||||
/// Register a C++ function with the console making it a global function callable from the scripting engine.
|
||||
///
|
||||
/// @param name Name of the new function.
|
||||
/// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value.
|
||||
/// @param usage Documentation for this function. @ref console_autodoc
|
||||
/// @param minArgs Minimum number of arguments this function accepts
|
||||
/// @param maxArgs Maximum number of arguments this function accepts
|
||||
void addCommand(const char *name, StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs);
|
||||
|
||||
void addCommand(const char *name, IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
|
||||
void addCommand(const char *name, FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
|
||||
void addCommand(const char *name, VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
|
||||
void addCommand(const char *name, BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
|
||||
/// @}
|
||||
|
||||
/// @name Namespace Function Registration
|
||||
/// @{
|
||||
|
||||
/// Register a C++ function with the console making it callable
|
||||
/// as a method of the given namespace from the scripting engine.
|
||||
///
|
||||
/// @param nameSpace Name of the namespace to associate the new function with; this is usually the name of a class.
|
||||
/// @param name Name of the new function.
|
||||
/// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value.
|
||||
/// @param usage Documentation for this function. @ref console_autodoc
|
||||
/// @param minArgs Minimum number of arguments this function accepts
|
||||
/// @param maxArgs Maximum number of arguments this function accepts
|
||||
void addCommand(const char *nameSpace, const char *name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs);
|
||||
void addCommand(const char *nameSpace, const char *name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
|
||||
void addCommand(const char *nameSpace, const char *name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
|
||||
void addCommand(const char *nameSpace, const char *name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
|
||||
void addCommand(const char *nameSpace, const char *name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
|
||||
/// @}
|
||||
|
||||
/// @name Special Purpose Registration
|
||||
///
|
||||
/// These are special-purpose functions that exist to allow commands to be grouped, so
|
||||
/// that when we generate console docs, they can be more meaningfully presented.
|
||||
///
|
||||
/// @ref console_autodoc "Click here for more information about console docs and grouping."
|
||||
///
|
||||
/// @{
|
||||
|
||||
void markCommandGroup (const char * nsName, const char *name, const char* usage=NULL);
|
||||
void beginCommandGroup(const char * nsName, const char *name, const char* usage);
|
||||
void endCommandGroup (const char * nsName, const char *name);
|
||||
|
||||
/// @deprecated
|
||||
void addOverload (const char * nsName, const char *name, const char *altUsage);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Console Output
|
||||
///
|
||||
/// These functions process the formatted string and pass it to all the ConsumerCallbacks that are
|
||||
/// currently registered. The console log file and the console window callbacks are installed by default.
|
||||
///
|
||||
/// @see addConsumer()
|
||||
/// @see removeConsumer()
|
||||
/// @{
|
||||
|
||||
/// @param _format A stdlib printf style formatted out put string
|
||||
/// @param ... Variables to be written
|
||||
void printf(const char *_format, ...);
|
||||
|
||||
/// @note The console window colors warning text as LIGHT GRAY.
|
||||
/// @param _format A stdlib printf style formatted out put string
|
||||
/// @param ... Variables to be written
|
||||
void warnf(const char *_format, ...);
|
||||
|
||||
/// @note The console window colors warning text as RED.
|
||||
/// @param _format A stdlib printf style formatted out put string
|
||||
/// @param ... Variables to be written
|
||||
void errorf(const char *_format, ...);
|
||||
|
||||
/// @note The console window colors warning text as LIGHT GRAY.
|
||||
/// @param type Allows you to associate the warning message with an internal module.
|
||||
/// @param _format A stdlib printf style formatted out put string
|
||||
/// @param ... Variables to be written
|
||||
/// @see Con::warnf()
|
||||
void warnf(ConsoleLogEntry::Type type, const char *_format, ...);
|
||||
|
||||
/// @note The console window colors warning text as RED.
|
||||
/// @param type Allows you to associate the warning message with an internal module.
|
||||
/// @param _format A stdlib printf style formatted out put string
|
||||
/// @param ... Variables to be written
|
||||
/// @see Con::errorf()
|
||||
void errorf(ConsoleLogEntry::Type type, const char *_format, ...);
|
||||
|
||||
/// @}
|
||||
|
||||
/// Returns true when called from the main thread, false otherwise
|
||||
bool isMainThread();
|
||||
|
||||
|
||||
/// @name Console Execution
|
||||
///
|
||||
/// These are functions relating to the execution of script code.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Call a script function from C/C++ code.
|
||||
///
|
||||
/// @param argc Number of elements in the argv parameter
|
||||
/// @param argv A character string array containing the name of the function
|
||||
/// to call followed by the arguments to that function.
|
||||
/// @code
|
||||
/// // Call a Torque script function called mAbs, having one parameter.
|
||||
/// char* argv[] = {"abs", "-9"};
|
||||
/// char* result = execute(2, argv);
|
||||
/// @endcode
|
||||
const char *execute(S32 argc, const char* argv[]);
|
||||
|
||||
/// @see execute(S32 argc, const char* argv[])
|
||||
const char *executef(S32 argc, ...);
|
||||
|
||||
/// Call a Torque Script member function of a SimObject from C/C++ code.
|
||||
/// @param object Object on which to execute the method call.
|
||||
/// @param argc Number of elements in the argv parameter (must be >2, see argv)
|
||||
/// @param argv A character string array containing the name of the member function
|
||||
/// to call followed by an empty parameter (gets filled with object ID)
|
||||
/// followed by arguments to that function.
|
||||
/// @code
|
||||
/// // Call the method setMode() on an object, passing it one parameter.
|
||||
///
|
||||
/// char* argv[] = {"setMode", "", "2"};
|
||||
/// char* result = execute(mysimobject, 3, argv);
|
||||
/// @endcode
|
||||
const char *execute(SimObject *object, S32 argc, const char *argv[]);
|
||||
|
||||
/// @see execute(SimObject *, S32 argc, const char *argv[])
|
||||
const char *executef(SimObject *, S32 argc, ...);
|
||||
|
||||
/// Evaluate an arbitrary chunk of code.
|
||||
///
|
||||
/// @param string Buffer containing code to execute.
|
||||
/// @param echo Should we echo the string to the console?
|
||||
/// @param fileName Indicate what file this code is coming from; used in error reporting and such.
|
||||
const char *evaluate(const char* string, bool echo = false, const char *fileName = NULL);
|
||||
|
||||
/// Evaluate an arbitrary line of script.
|
||||
///
|
||||
/// This wraps dVsprintf(), so you can substitute parameters into the code being executed.
|
||||
const char *evaluatef(const char* string, ...);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Console Function Implementation Helpers
|
||||
///
|
||||
/// The functions Con::getIntArg, Con::getFloatArg and Con::getArgBuffer(size) are used to
|
||||
/// allocate on the console stack string variables that will be passed into the next console
|
||||
// function called. This allows the console to avoid copying some data.
|
||||
///
|
||||
/// getReturnBuffer lets you allocate stack space to return data in.
|
||||
/// @{
|
||||
|
||||
///
|
||||
char *getReturnBuffer(U32 bufferSize);
|
||||
|
||||
char *getArgBuffer(U32 bufferSize);
|
||||
char *getFloatArg(F64 arg);
|
||||
char *getIntArg (S32 arg);
|
||||
/// @}
|
||||
|
||||
/// @name Namespaces
|
||||
/// @{
|
||||
|
||||
Namespace *lookupNamespace(const char *nsName);
|
||||
bool linkNamespaces(const char *parentName, const char *childName);
|
||||
bool unlinkNamespaces(const char *parentName, const char *childName);
|
||||
|
||||
/// @note This should only be called from consoleObject.h
|
||||
bool classLinkNamespaces(Namespace *parent, Namespace *child);
|
||||
/// @}
|
||||
|
||||
/// @name Logging
|
||||
/// @{
|
||||
|
||||
void getLockLog(ConsoleLogEntry * &log, U32 &size);
|
||||
void unlockLog(void);
|
||||
void setLogMode(S32 mode);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Dynamic Type System
|
||||
/// @{
|
||||
|
||||
///
|
||||
/* void registerType( const char *typeName, S32 type, S32 size, GetDataFunction gdf, SetDataFunction sdf, bool isDatablockType = false );
|
||||
void registerType( const char* typeName, S32 type, S32 size, bool isDatablockType = false );
|
||||
void registerTypeGet( S32 type, GetDataFunction gdf );
|
||||
void registerTypeSet( S32 type, SetDataFunction sdf );
|
||||
|
||||
const char *getTypeName(S32 type);
|
||||
bool isDatablockType( S32 type ); */
|
||||
|
||||
void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, EnumTable *tbl = NULL, BitSet32 flag = 0);
|
||||
const char *getData(S32 type, void *dptr, S32 index, EnumTable *tbl = NULL, BitSet32 flag = 0);
|
||||
/// @}
|
||||
};
|
||||
|
||||
extern void expandEscape(char *dest, const char *src);
|
||||
extern bool collapseEscape(char *buf);
|
||||
extern S32 HashPointer(StringTableEntry ptr);
|
||||
|
||||
/// This is the backend for the ConsoleMethod()/ConsoleFunction() macros.
|
||||
///
|
||||
/// See the group ConsoleConstructor Innards for specifics on how this works.
|
||||
///
|
||||
/// @see @ref console_autodoc
|
||||
/// @nosubgrouping
|
||||
class ConsoleConstructor
|
||||
{
|
||||
public:
|
||||
/// @name Entry Type Fields
|
||||
///
|
||||
/// One of these is set based on the type of entry we want
|
||||
/// inserted in the console.
|
||||
///
|
||||
/// @ref console_autodoc
|
||||
/// @{
|
||||
StringCallback sc; ///< A function/method that returns a string.
|
||||
IntCallback ic; ///< A function/method that returns an int.
|
||||
FloatCallback fc; ///< A function/method that returns a float.
|
||||
VoidCallback vc; ///< A function/method that returns nothing.
|
||||
BoolCallback bc; ///< A function/method that returns a bool.
|
||||
bool group; ///< Indicates that this is a group marker.
|
||||
bool overload; ///< Indicates that this is an overload marker.
|
||||
/// @deprecated Unused.
|
||||
/// @}
|
||||
|
||||
/// Minimum/maximum number of arguments for the function.
|
||||
S32 mina, maxa;
|
||||
const char *usage; ///< Usage string.
|
||||
const char *funcName; ///< Function name.
|
||||
const char *className; ///< Class name.
|
||||
|
||||
/// @name ConsoleConstructer Innards
|
||||
///
|
||||
/// The ConsoleConstructor class is used as the backend for the ConsoleFunction() and
|
||||
/// ConsoleMethod() macros. The way it works takes advantage of several properties of
|
||||
/// C++.
|
||||
///
|
||||
/// The ConsoleFunction()/ConsoleMethod() macros wrap the declaration of a ConsoleConstructor.
|
||||
///
|
||||
/// @code
|
||||
/// // The definition of a ConsoleFunction using the macro
|
||||
/// ConsoleFunction(ExpandFilename, const char*, 2, 2, "(string filename)")
|
||||
/// {
|
||||
/// argc;
|
||||
/// char* ret = Con::getReturnBuffer( 1024 );
|
||||
/// Con::expandScriptFilename(ret, 1024, argv[1]);
|
||||
/// return ret;
|
||||
/// }
|
||||
///
|
||||
/// // Resulting code
|
||||
/// static const char* cExpandFilename(SimObject *, S32, const char **argv);
|
||||
/// static ConsoleConstructor
|
||||
/// gExpandFilenameobj(NULL,"ExpandFilename", cExpandFilename,
|
||||
/// "(string filename)", 2, 2);
|
||||
/// static const char* cExpandFilename(SimObject *, S32 argc, const char **argv)
|
||||
/// {
|
||||
/// argc;
|
||||
/// char* ret = Con::getReturnBuffer( 1024 );
|
||||
/// Con::expandScriptFilename(ret, 1024, argv[1]);
|
||||
/// return ret;
|
||||
/// }
|
||||
///
|
||||
/// // A similar thing happens when you do a ConsoleMethod.
|
||||
/// @endcode
|
||||
///
|
||||
/// As you can see, several global items are defined when you use the ConsoleFunction method.
|
||||
/// The macro constructs the name of these items from the parameters you passed it. Your
|
||||
/// implementation of the console function is is placed in a function with a name based on
|
||||
/// the actual name of the console funnction. In addition, a ConsoleConstructor is declared.
|
||||
///
|
||||
/// Because it is defined as a global, the constructor for the ConsoleConstructor is called
|
||||
/// before execution of main() is started. The constructor is called once for each global
|
||||
/// ConsoleConstructor variable, in the order in which they were defined (this property only holds true
|
||||
/// within file scope).
|
||||
///
|
||||
/// We have ConsoleConstructor create a linked list at constructor time, by storing a static
|
||||
/// pointer to the head of the list, and keeping a pointer to the next item in each instance
|
||||
/// of ConsoleConstructor. init() is a helper function in this process, automatically filling
|
||||
/// in commonly used fields and updating first and next as needed. In this way, a list of
|
||||
/// items to add to the console is assemble in memory, ready for use, before we start
|
||||
/// execution of the program proper.
|
||||
///
|
||||
/// In Con::init(), ConsoleConstructor::setup() is called to process this prepared list. Each
|
||||
/// item in the list is iterated over, and the appropriate Con namespace functions (usually
|
||||
/// Con::addCommand) are invoked to register the ConsoleFunctions and ConsoleMethods in
|
||||
/// the appropriate namespaces.
|
||||
///
|
||||
/// @see Namespace
|
||||
/// @see Con
|
||||
/// @{
|
||||
|
||||
ConsoleConstructor *next;
|
||||
static ConsoleConstructor *first;
|
||||
|
||||
void init(const char *cName, const char *fName, const char *usg, S32 minArgs, S32 maxArgs);
|
||||
static void setup();
|
||||
/// @}
|
||||
|
||||
/// @name Basic Console Constructors
|
||||
/// @{
|
||||
|
||||
ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char* usage, S32 minArgs, S32 maxArgs);
|
||||
ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char* usage, S32 minArgs, S32 maxArgs);
|
||||
ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char* usage, S32 minArgs, S32 maxArgs);
|
||||
ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char* usage, S32 minArgs, S32 maxArgs);
|
||||
ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char* usage, S32 minArgs, S32 maxArgs);
|
||||
/// @}
|
||||
|
||||
/// @name Magic Console Constructors
|
||||
///
|
||||
/// These perform various pieces of "magic" related to consoleDoc functionality.
|
||||
/// @ref console_autodoc
|
||||
/// @{
|
||||
|
||||
/// Indicates a group marker. (A doxygen illusion)
|
||||
///
|
||||
/// @see Con::markCommandGroup
|
||||
/// @ref console_autodoc
|
||||
ConsoleConstructor(const char *className, const char *groupName, const char* usage);
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
/// @name Global Console Definition Macros
|
||||
///
|
||||
/// @note If TORQUE_DEBUG is defined, then we gather documentation information, and
|
||||
/// do some extra sanity checks.
|
||||
///
|
||||
/// @see ConsoleConstructor
|
||||
/// @ref console_autodoc
|
||||
/// @{
|
||||
|
||||
// O hackery of hackeries
|
||||
#define conmethod_return_const return (const
|
||||
#define conmethod_return_S32 return (S32
|
||||
#define conmethod_return_F32 return (F32
|
||||
#define conmethod_nullify(val)
|
||||
#define conmethod_return_void conmethod_nullify(void
|
||||
#define conmethod_return_bool return (bool
|
||||
|
||||
#if !defined(TORQUE_SHIPPING)
|
||||
|
||||
// Console function macros
|
||||
# define ConsoleFunctionGroupBegin(groupName, usage) \
|
||||
static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupBegin(NULL,#groupName,usage);
|
||||
|
||||
# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \
|
||||
static returnType c##name(SimObject *, S32, const char **argv); \
|
||||
static ConsoleConstructor g##name##obj(NULL,#name,c##name,usage1,minArgs,maxArgs); \
|
||||
static returnType c##name(SimObject *, S32 argc, const char **argv)
|
||||
|
||||
# define ConsoleFunctionGroupEnd(groupName) \
|
||||
static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupEnd(NULL,#groupName,NULL);
|
||||
|
||||
// Console method macros
|
||||
# define ConsoleMethodGroupBegin(className, groupName, usage) \
|
||||
static ConsoleConstructor className##groupName##__GroupBegin(#className,#groupName,usage);
|
||||
|
||||
# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \
|
||||
static inline returnType c##className##name(className *, S32, const char **argv); \
|
||||
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
|
||||
AssertFatal( dynamic_cast<className*>( object ), "Object passed to " #name " is not a " #className "!" ); \
|
||||
conmethod_return_##returnType ) c##className##name(static_cast<className*>(object),argc,argv); \
|
||||
}; \
|
||||
static ConsoleConstructor className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \
|
||||
static inline returnType c##className##name(className *object, S32 argc, const char **argv)
|
||||
|
||||
# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \
|
||||
static inline returnType c##className##name(S32, const char **); \
|
||||
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
|
||||
conmethod_return_##returnType ) c##className##name(argc,argv); \
|
||||
}; \
|
||||
static ConsoleConstructor \
|
||||
className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \
|
||||
static inline returnType c##className##name(S32 argc, const char **argv)
|
||||
|
||||
# define ConsoleMethodGroupEnd(className, groupName) \
|
||||
static ConsoleConstructor className##groupName##__GroupEnd(#className,#groupName,NULL);
|
||||
|
||||
#else
|
||||
|
||||
// These do nothing if we don't want doc information.
|
||||
# define ConsoleFunctionGroupBegin(groupName, usage)
|
||||
# define ConsoleFunctionGroupEnd(groupName)
|
||||
# define ConsoleMethodGroupBegin(className, groupName, usage)
|
||||
# define ConsoleMethodGroupEnd(className, groupName)
|
||||
|
||||
// These are identical to what's above, we just want to null out the usage strings.
|
||||
# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \
|
||||
static returnType c##name(SimObject *, S32, const char **); \
|
||||
static ConsoleConstructor g##name##obj(NULL,#name,c##name,"",minArgs,maxArgs);\
|
||||
static returnType c##name(SimObject *, S32 argc, const char **argv)
|
||||
|
||||
# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \
|
||||
static inline returnType c##className##name(className *, S32, const char **argv); \
|
||||
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
|
||||
conmethod_return_##returnType ) c##className##name(static_cast<className*>(object),argc,argv); \
|
||||
}; \
|
||||
static ConsoleConstructor \
|
||||
className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \
|
||||
static inline returnType c##className##name(className *object, S32 argc, const char **argv)
|
||||
|
||||
# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \
|
||||
static inline returnType c##className##name(S32, const char **); \
|
||||
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
|
||||
conmethod_return_##returnType ) c##className##name(argc,argv); \
|
||||
}; \
|
||||
static ConsoleConstructor \
|
||||
className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \
|
||||
static inline returnType c##className##name(S32 argc, const char **argv)
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
#endif
|
378
engine/console/consoleDoc.cc
Executable file
378
engine/console/consoleDoc.cc
Executable file
@ -0,0 +1,378 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/console.h"
|
||||
|
||||
#include "console/ast.h"
|
||||
#include "core/tAlgorithm.h"
|
||||
#include "core/resManager.h"
|
||||
|
||||
#include "core/findMatch.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "core/fileStream.h"
|
||||
#include "console/compiler.h"
|
||||
|
||||
//--- Information pertaining to this page... ------------------
|
||||
/// @file
|
||||
///
|
||||
/// For specifics on using the consoleDoc functionality, see @ref console_autodoc
|
||||
|
||||
ConsoleFunctionGroupBegin(ConsoleDoc, "Console self-documentation functions. These output psuedo C++ suitable for feeeding through Doxygen or another auto documentation tool.");
|
||||
|
||||
ConsoleFunction(dumpConsoleClasses, void, 1, 1, "() dumps all declared console classes to the console.")
|
||||
{
|
||||
Namespace::dumpClasses();
|
||||
}
|
||||
|
||||
ConsoleFunction(dumpConsoleFunctions, void, 1, 1, "() dumps all declared console functions to the console.")
|
||||
{
|
||||
Namespace::dumpFunctions();
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd(ConsoleDoc);
|
||||
|
||||
/// Helper table to convert type ids to human readable names.
|
||||
const char *typeNames[] =
|
||||
{
|
||||
"Script",
|
||||
"string",
|
||||
"int",
|
||||
"float",
|
||||
"void",
|
||||
"bool",
|
||||
"",
|
||||
"",
|
||||
"unknown_overload"
|
||||
};
|
||||
|
||||
void printClassHeader(const char * className, const char * superClassName, const bool stub)
|
||||
{
|
||||
if(stub)
|
||||
{
|
||||
Con::printf("/// Stub class");
|
||||
Con::printf("/// ");
|
||||
Con::printf("/// @note This is a stub class to ensure a proper class hierarchy. No ");
|
||||
Con::printf("/// information was available for this class.");
|
||||
}
|
||||
|
||||
// Print out appropriate class header
|
||||
if(superClassName)
|
||||
Con::printf("class %s : public %s {", className, superClassName ? superClassName : "");
|
||||
else if(!className)
|
||||
Con::printf("namespace Global {");
|
||||
else
|
||||
Con::printf("class %s {", className);
|
||||
|
||||
if(className)
|
||||
Con::printf(" public:");
|
||||
|
||||
}
|
||||
|
||||
void printClassMethod(const bool isVirtual, const char *retType, const char *methodName, const char* args, const char*usage)
|
||||
{
|
||||
if(usage && usage[0] != ';' && usage[0] != 0)
|
||||
Con::printf(" /*! %s */", usage);
|
||||
Con::printf(" %s%s %s(%s) {}", isVirtual ? "virtual " : "", retType, methodName, args);
|
||||
}
|
||||
|
||||
void printGroupStart(const char * aName, const char * aDocs)
|
||||
{
|
||||
Con::printf("");
|
||||
Con::printf(" /*! @name %s", aName);
|
||||
|
||||
if(aDocs)
|
||||
{
|
||||
Con::printf(" ");
|
||||
Con::printf(" %s", aDocs);
|
||||
}
|
||||
|
||||
Con::printf(" @{ */");
|
||||
}
|
||||
|
||||
void printClassMember(const bool isDeprec, const char * aType, const char * aName, const char * aDocs)
|
||||
{
|
||||
Con::printf(" /*!");
|
||||
|
||||
if(aDocs)
|
||||
{
|
||||
Con::printf(" %s", aDocs);
|
||||
Con::printf(" ");
|
||||
}
|
||||
|
||||
if(isDeprec)
|
||||
Con::printf(" @deprecated This member is deprecated, which means that its value is always undefined.");
|
||||
|
||||
Con::printf(" */");
|
||||
|
||||
Con::printf(" %s %s;", isDeprec ? "deprecated" : aType, aName);
|
||||
}
|
||||
|
||||
void printGroupEnd()
|
||||
{
|
||||
Con::printf(" /// @}");
|
||||
Con::printf("");
|
||||
}
|
||||
|
||||
void printClassFooter()
|
||||
{
|
||||
Con::printf("};");
|
||||
Con::printf("");
|
||||
}
|
||||
|
||||
void Namespace::printNamespaceEntries(Namespace * g)
|
||||
{
|
||||
static bool inGroup = false;
|
||||
|
||||
// Go through all the entries.
|
||||
// Iterate through the methods of the namespace...
|
||||
for(Entry *ewalk = g->mEntryList; ewalk; ewalk = ewalk->mNext)
|
||||
{
|
||||
char buffer[1024]; //< This will bite you in the butt someday.
|
||||
int eType = ewalk->mType;
|
||||
const char * funcName = ewalk->mFunctionName;
|
||||
|
||||
// If it's a function
|
||||
if(eType >= Entry::ScriptFunctionType || eType == Entry::OverloadMarker)
|
||||
{
|
||||
if(eType==Entry::OverloadMarker)
|
||||
{
|
||||
// Deal with crap from the OverloadMarker case.
|
||||
// It has no type information so we have to "correct" its type.
|
||||
|
||||
// Find the original
|
||||
eType = 8;
|
||||
for(Entry *eseek = g->mEntryList; eseek; eseek = eseek->mNext)
|
||||
{
|
||||
if(!dStrcmp(eseek->mFunctionName, ewalk->cb.mGroupName))
|
||||
{
|
||||
eType = eseek->mType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// And correct the name
|
||||
funcName = ewalk->cb.mGroupName;
|
||||
}
|
||||
|
||||
// A quick note - if your usage field starts with a (, then it's auto-integrated into
|
||||
// the script docs! Use this HEAVILY!
|
||||
|
||||
// We add some heuristics here as well. If you're of the form:
|
||||
// *.methodName(*)
|
||||
// then we will also extract parameters.
|
||||
|
||||
const char *use = ewalk->mUsage ? ewalk->mUsage : "";
|
||||
const char *bgn = dStrchr(use, '(');
|
||||
const char *end = dStrchr(use, ')');
|
||||
const char *dot = dStrchr(use, '.');
|
||||
|
||||
if(use[0] == '(')
|
||||
{
|
||||
if(!end)
|
||||
end = use + 1;
|
||||
|
||||
use++;
|
||||
|
||||
U32 len = end - use;
|
||||
dStrncpy(buffer, use, len);
|
||||
buffer[len] = 0;
|
||||
|
||||
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
|
||||
|
||||
continue; // Skip to next one.
|
||||
}
|
||||
|
||||
// We check to see if they're giving a prototype.
|
||||
if(dot && bgn && end) // If there's two parentheses, and a dot...
|
||||
if(dot < bgn && bgn < end) // And they're in the order dot, bgn, end...
|
||||
{
|
||||
use++;
|
||||
U32 len = end - bgn - 1;
|
||||
dStrncpy(buffer, bgn+1, len);
|
||||
buffer[len] = 0;
|
||||
|
||||
// Then let's do the heuristic-trick
|
||||
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
|
||||
continue; // Get to next item.
|
||||
}
|
||||
|
||||
// Finally, see if they did it foo(*) style.
|
||||
char* func_pos = dStrstr(use, funcName);
|
||||
if((func_pos) && (func_pos < bgn) && (end > bgn))
|
||||
{
|
||||
U32 len = end - bgn - 1;
|
||||
dStrncpy(buffer, bgn+1, len);
|
||||
buffer[len] = 0;
|
||||
|
||||
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default...
|
||||
printClassMethod(true, typeNames[eType], funcName, "", ewalk->mUsage);
|
||||
}
|
||||
else if(ewalk->mType == Entry::GroupMarker)
|
||||
{
|
||||
if(!inGroup)
|
||||
printGroupStart(ewalk->cb.mGroupName, ewalk->mUsage);
|
||||
else
|
||||
printGroupEnd();
|
||||
|
||||
inGroup = !inGroup;
|
||||
}
|
||||
else if(ewalk->mFunctionOffset) // If it's a builtin function...
|
||||
{
|
||||
ewalk->mCode->getFunctionArgs(buffer, ewalk->mFunctionOffset);
|
||||
printClassMethod(false, typeNames[ewalk->mType], ewalk->mFunctionName, buffer, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
Con::printf(" // got an unknown thing?? %d", ewalk->mType );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Namespace::dumpClasses()
|
||||
{
|
||||
Vector<Namespace *> vec;
|
||||
trashCache();
|
||||
|
||||
// We use mHashSequence to mark if we have traversed...
|
||||
// so mark all as zero to start.
|
||||
for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext)
|
||||
walk->mHashSequence = 0;
|
||||
|
||||
for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext)
|
||||
{
|
||||
Vector<Namespace *> stack;
|
||||
|
||||
// Get all the parents of this namespace... (and mark them as we go)
|
||||
Namespace *parentWalk = walk;
|
||||
while(parentWalk)
|
||||
{
|
||||
if(parentWalk->mHashSequence != 0)
|
||||
break;
|
||||
if(parentWalk->mPackage == 0)
|
||||
{
|
||||
parentWalk->mHashSequence = 1; // Mark as traversed.
|
||||
stack.push_back(parentWalk);
|
||||
}
|
||||
parentWalk = parentWalk->mParent;
|
||||
}
|
||||
|
||||
// Load stack into our results vector.
|
||||
while(stack.size())
|
||||
{
|
||||
vec.push_back(stack[stack.size() - 1]);
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Go through previously discovered classes
|
||||
U32 i;
|
||||
for(i = 0; i < vec.size(); i++)
|
||||
{
|
||||
const char *className = vec[i]->mName;
|
||||
const char *superClassName = vec[i]->mParent ? vec[i]->mParent->mName : NULL;
|
||||
|
||||
// Skip the global namespace, that gets dealt with in dumpFunctions
|
||||
if(!className) continue;
|
||||
|
||||
// If we hit a class with no members and no classRep, do clever filtering.
|
||||
if(vec[i]->mEntryList == NULL && vec[i]->mClassRep == NULL)
|
||||
{
|
||||
// Print out a short stub so we get a proper class hierarchy.
|
||||
if(superClassName) { // Filter hack; we don't want non-inheriting classes...
|
||||
printClassHeader(className,superClassName, true);
|
||||
printClassFooter();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Print the header for the class..
|
||||
printClassHeader(className, superClassName, false);
|
||||
|
||||
// Deal with entries.
|
||||
printNamespaceEntries(vec[i]);
|
||||
|
||||
// Deal with the classRep (to get members)...
|
||||
AbstractClassRep *rep = vec[i]->mClassRep;
|
||||
AbstractClassRep::FieldList emptyList;
|
||||
AbstractClassRep::FieldList *parentList = &emptyList;
|
||||
AbstractClassRep::FieldList *fieldList = &emptyList;
|
||||
|
||||
if(rep)
|
||||
{
|
||||
// Get information about the parent's fields...
|
||||
AbstractClassRep *parentRep = vec[i]->mParent ? vec[i]->mParent->mClassRep : NULL;
|
||||
if(parentRep)
|
||||
parentList = &(parentRep->mFieldList);
|
||||
|
||||
// Get information about our fields
|
||||
fieldList = &(rep->mFieldList);
|
||||
|
||||
// Go through all our fields...
|
||||
for(U32 j = 0; j < fieldList->size(); j++)
|
||||
{
|
||||
switch((*fieldList)[j].type)
|
||||
{
|
||||
case AbstractClassRep::StartGroupFieldType:
|
||||
printGroupStart((*fieldList)[j].pGroupname, (*fieldList)[j].pFieldDocs);
|
||||
break;
|
||||
case AbstractClassRep::EndGroupFieldType:
|
||||
printGroupEnd();
|
||||
break;
|
||||
default:
|
||||
case AbstractClassRep::DepricatedFieldType:
|
||||
{
|
||||
bool isDeprecated = ((*fieldList)[j].type == AbstractClassRep::DepricatedFieldType);
|
||||
|
||||
if(isDeprecated)
|
||||
{
|
||||
printClassMember(
|
||||
true,
|
||||
"<deprecated>",
|
||||
(*fieldList)[j].pFieldname,
|
||||
(*fieldList)[j].pFieldDocs
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsoleBaseType *cbt = ConsoleBaseType::getType((*fieldList)[j].type);
|
||||
|
||||
printClassMember(
|
||||
false,
|
||||
cbt ? cbt->getTypeClassName() : "<unknown>",
|
||||
(*fieldList)[j].pFieldname,
|
||||
(*fieldList)[j].pFieldDocs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close the class/namespace.
|
||||
printClassFooter();
|
||||
}
|
||||
}
|
||||
|
||||
void Namespace::dumpFunctions()
|
||||
{
|
||||
// Get the global namespace.
|
||||
Namespace* g = find(NULL); //->mParent;
|
||||
|
||||
printClassHeader(NULL,NULL, false);
|
||||
|
||||
while(g)
|
||||
{
|
||||
printNamespaceEntries(g);
|
||||
g = g->mParent;
|
||||
}
|
||||
|
||||
printClassFooter();
|
||||
}
|
176
engine/console/consoleDoc.h
Executable file
176
engine/console/consoleDoc.h
Executable file
@ -0,0 +1,176 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// This file exists solely to document consoleDoc.cc
|
||||
|
||||
/// @page console_autodoc Console Auto-Documentation
|
||||
///
|
||||
/// @see consoleDoc.cc
|
||||
///
|
||||
/// @section console_autodoc_using Using Console Auto-Documentation
|
||||
///
|
||||
/// There are on the order of three hundred functions exposed to the script language
|
||||
/// through the console. It is therefore extremely important that they be documented,
|
||||
/// but due to their number, it is difficult to maintain a seperate reference document.
|
||||
///
|
||||
/// Therefore, a simple documentation system has been built into the scripting engine. It
|
||||
/// was initially added by Mark Frohnmayer, and later enhanced by Ben Garney. The
|
||||
/// scripting engine supports grouping functions and methods, to help organize the
|
||||
/// several hundred functions, as well as associating a "usage string" with functions and
|
||||
/// groups.
|
||||
///
|
||||
/// @note The results of a console doc dump will vary depending on when you run it. If
|
||||
/// you run it, for example, while in the game menu, it won't output any data for
|
||||
/// the script-defined classes which are defined for gameplay. To get comprehensive
|
||||
/// documentation, you may need to write a special script that will get all your
|
||||
/// classes loaded into the scripting engine.
|
||||
///
|
||||
/// The console documentation system is designed to output a dump of the current state
|
||||
/// of the scripting engine in a format understandable by Doxygen. It does this by
|
||||
/// traversing the namespace/class hierarchy in memory at the time of the dump, and
|
||||
/// outputting psuedo-C++ code equivalent to this class hierarchy.
|
||||
///
|
||||
/// @subsection console_autodoc_using_script For the Scripter...
|
||||
///
|
||||
/// Currently, there is no way to associate usage strings or other documentation with script code
|
||||
/// like you can with C++ code.
|
||||
///
|
||||
/// You can get a list of all the methods and fields of an object from any object which inherits
|
||||
/// from SimObject (ie, every object), as well as the documentation on those objects by using the
|
||||
/// dump() method from the console:
|
||||
///
|
||||
/// @code
|
||||
/// ==>$foo = new SimObject();
|
||||
/// ==>$foo.dump();
|
||||
/// Member Fields:
|
||||
/// Tagged Fields:
|
||||
/// Methods:
|
||||
/// delete() - obj.delete()
|
||||
/// dump() - obj.dump()
|
||||
/// getClassName() - obj.getClassName()
|
||||
/// getGroup() - obj.getGroup()
|
||||
/// getId() - obj.getId()
|
||||
/// getName() - obj.getName()
|
||||
/// getType() - obj.getType()
|
||||
/// save() - obj.save(fileName, <selectedOnly>)
|
||||
/// schedule() - object.schedule(time, command, <arg1...argN>);
|
||||
/// setName() - obj.setName(newName)
|
||||
/// @endcode
|
||||
///
|
||||
/// In the Torque example app, there are two functions defined in common\\client\\scriptDoc.cs
|
||||
/// which automate the process of dumping the documentation. They make use of the ConsoleLogger
|
||||
/// object to output the documentation to a file, and look like this:
|
||||
///
|
||||
/// @note You may want to add this code, or code like it, to your project if you have
|
||||
/// rewritten the script code in common.
|
||||
///
|
||||
/// @code
|
||||
/// // Writes out all script functions to a file
|
||||
/// function writeOutFunctions() {
|
||||
/// new ConsoleLogger( logger, "scriptFunctions.txt", false );
|
||||
/// dumpConsoleFunctions();
|
||||
/// logger.delete();
|
||||
/// }
|
||||
///
|
||||
/// // Writes out all script classes to a file
|
||||
/// function writeOutClasses() {
|
||||
/// new ConsoleLogger( logger, "scriptClasses.txt", false );
|
||||
/// dumpConsoleClasses();
|
||||
/// logger.delete();
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// @subsection console_autodoc_using_coder For the C++ Coder...
|
||||
///
|
||||
/// @note <b>It is of the utmost important that you keep your usage strings up to date!</b>
|
||||
/// Usage strings are the only way that a scripter has to know how to use the methods,
|
||||
/// functions, and variables you expose. Misleading, missing, or out of date documentation
|
||||
/// will make their lives much harder - and yours, too, because you'll have to keep
|
||||
/// explaining things to them! So make everyone's lives easier - keep your usage strings
|
||||
/// clear, concise, and up to date.
|
||||
///
|
||||
/// There are four types of items which can be documented using the autodocumentation system:
|
||||
/// - <b>Fields</b>, which are defined using the addField() calls. They are documented
|
||||
/// by passing a string to the usage parameter.
|
||||
/// - <b>Field groups</b>, which are defined using the beginGroup() and endGroup() calls.
|
||||
/// They are documented by passing a descriptive string to the usage parameter.
|
||||
/// - <b>Method groups</b>, which are defined using beginCommandGroup(), endCommandGroup(),
|
||||
/// ConsoleMethodGroupEnd(), ConsoleMethodGroupBegin(), ConsoleFunctionGroupEnd(), and
|
||||
/// ConsoleFunctionGroupBegin().
|
||||
/// - <b>Methods and functions</b>, which are defined using either SimObject::addCommand(),
|
||||
/// the ConsoleMethod() macro, or the ConsoleFunction() macro. Methods and functions are
|
||||
/// special in that the usage strings should be in a specific format, so
|
||||
/// that parameter information can be extracted from them and placed into the Doxygen
|
||||
/// output.
|
||||
///
|
||||
/// You can use standard Doxygen commands in your comments, to make the documentation clearer.
|
||||
/// Of particular use are \@returns, \@param, \@note, and \@deprecated.
|
||||
///
|
||||
/// <b>Examples using global definitions.</b>
|
||||
///
|
||||
/// @code
|
||||
/// // Example of using Doxygen commands.
|
||||
/// ConsoleFunction(alxGetWaveLen, S32, 2, 2, "(string filename)"
|
||||
/// "Get length of a wave file\n\n"
|
||||
/// "@param filename File to determine length of.\n"
|
||||
/// "@returns Length in milliseconds.")
|
||||
///
|
||||
/// // A function group...
|
||||
/// ConsoleFunctionGroupBegin(Example, "This is an example group! Notice that the name for the group"
|
||||
/// "must be a valid identifier, due to limitations in the C preprocessor.");
|
||||
///
|
||||
/// // ConsoleFunction definitions go here.
|
||||
///
|
||||
/// ConsoleFunctionGroupEnd(Example);
|
||||
///
|
||||
/// // You can do similar things with methods...
|
||||
/// ConsoleMethodGroupBegin(SimSet, UsefulFuncs, "Here are some useful functions involving a SimSet.");
|
||||
/// ConsoleMethod(SimSet, listObjects, void, 2, 2, "set.listObjects();")
|
||||
/// ConsoleMethodGroupEnd(SimSet, UsefulFuncs, "Here are some more useful functions involving a SimSet.");
|
||||
/// @endcode
|
||||
///
|
||||
/// <b>Examples using addField</b>
|
||||
///
|
||||
/// @note Using addCommand is strongly deprecated.
|
||||
///
|
||||
/// @code
|
||||
/// // Example of a field group.
|
||||
/// addGroup( "Logging", "Things relating to logging." );
|
||||
/// addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable );
|
||||
/// endGroup( "Logging" );
|
||||
/// @endcode
|
||||
///
|
||||
/// @section console_autodoc_makingdocs How to Generate Console Docs
|
||||
///
|
||||
/// Console docs can be generated by running the dumpConsoleFunctions() and
|
||||
/// dumpConsoleClasses(), then running the output through Doxygen. There is an
|
||||
/// example Doxygen configuration file to do this in HEAD,
|
||||
/// at doc\\doxygen\\html\\script_doxygen.html.cfg. Doxygen will parse the psuedo-C++
|
||||
/// generated by the console doc code and produce a class hierarchy and documentation
|
||||
/// of the global namespace. You may need to tweak the paths in the configuration file
|
||||
/// slightly to reflect your individual setup.
|
||||
///
|
||||
/// @section console_autodoc_internals Console Auto-Documentation Internals
|
||||
///
|
||||
/// The consoleDoc system works by inserting "hidden" entries in Namespace and
|
||||
/// AbstractClassRep; these hidden entries are assigned special type IDs so that
|
||||
/// they aren't touched by the standard name resolution code. At documentation
|
||||
/// creation time, the dumpConsole functions iterate through the Namespace hierarchy
|
||||
/// and the AbstractClassRep data and extract this "hidden" information, outputting
|
||||
/// it in a Doxygen-compatible format.
|
||||
///
|
||||
/// @note You can customize the output of the console documentation system by modifying
|
||||
/// these functions:
|
||||
/// - printClassHeader()
|
||||
/// - printClassMethod()
|
||||
/// - printGroupStart()
|
||||
/// - printClassMember()
|
||||
/// - printGroupEnd()
|
||||
/// - printClassFooter()
|
||||
///
|
||||
/// @note There was once support for 'overloaded' script functions; ie, script functions
|
||||
/// with multiple usage strings. Certain functions in the audio library used this.
|
||||
/// However, it was deemed too complex, and removed from the scripting engine. There
|
||||
/// are still some latent traces of it, however.
|
1512
engine/console/consoleFunctions.cc
Executable file
1512
engine/console/consoleFunctions.cc
Executable file
File diff suppressed because it is too large
Load Diff
1087
engine/console/consoleInternal.cc
Executable file
1087
engine/console/consoleInternal.cc
Executable file
File diff suppressed because it is too large
Load Diff
311
engine/console/consoleInternal.h
Executable file
311
engine/console/consoleInternal.h
Executable file
@ -0,0 +1,311 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _CONSOLEINTERNAL_H_
|
||||
#define _CONSOLEINTERNAL_H_
|
||||
|
||||
#ifndef _STRINGTABLE_H_
|
||||
#include "core/stringTable.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/tVector.h"
|
||||
#endif
|
||||
#ifndef _CONSOLETYPES_H_
|
||||
#include "console/consoleTypes.h"
|
||||
#endif
|
||||
|
||||
class ExprEvalState;
|
||||
struct FunctionDecl;
|
||||
class CodeBlock;
|
||||
class AbstractClassRep;
|
||||
|
||||
class Namespace
|
||||
{
|
||||
enum {
|
||||
MaxActivePackages = 512,
|
||||
};
|
||||
|
||||
static U32 mNumActivePackages;
|
||||
static U32 mOldNumActivePackages;
|
||||
static StringTableEntry mActivePackages[MaxActivePackages];
|
||||
public:
|
||||
StringTableEntry mName;
|
||||
StringTableEntry mPackage;
|
||||
|
||||
Namespace *mParent;
|
||||
Namespace *mNext;
|
||||
AbstractClassRep *mClassRep;
|
||||
U32 mRefCountToParent;
|
||||
|
||||
struct Entry
|
||||
{
|
||||
enum {
|
||||
GroupMarker = -3,
|
||||
OverloadMarker = -2,
|
||||
InvalidFunctionType = -1,
|
||||
ScriptFunctionType,
|
||||
StringCallbackType,
|
||||
IntCallbackType,
|
||||
FloatCallbackType,
|
||||
VoidCallbackType,
|
||||
BoolCallbackType
|
||||
};
|
||||
|
||||
Namespace *mNamespace;
|
||||
Entry *mNext;
|
||||
StringTableEntry mFunctionName;
|
||||
S32 mType;
|
||||
S32 mMinArgs;
|
||||
S32 mMaxArgs;
|
||||
const char *mUsage;
|
||||
StringTableEntry mPackage;
|
||||
|
||||
CodeBlock *mCode;
|
||||
U32 mFunctionOffset;
|
||||
union {
|
||||
StringCallback mStringCallbackFunc;
|
||||
IntCallback mIntCallbackFunc;
|
||||
VoidCallback mVoidCallbackFunc;
|
||||
FloatCallback mFloatCallbackFunc;
|
||||
BoolCallback mBoolCallbackFunc;
|
||||
const char* mGroupName;
|
||||
} cb;
|
||||
Entry();
|
||||
void clear();
|
||||
|
||||
const char *execute(S32 argc, const char **argv, ExprEvalState *state);
|
||||
|
||||
};
|
||||
Entry *mEntryList;
|
||||
|
||||
Entry **mHashTable;
|
||||
U32 mHashSize;
|
||||
U32 mHashSequence; ///< @note The hash sequence is used by the autodoc console facility
|
||||
/// as a means of testing reference state.
|
||||
|
||||
Namespace();
|
||||
void addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset);
|
||||
void addCommand(StringTableEntry name,StringCallback, const char *usage, S32 minArgs, S32 maxArgs);
|
||||
void addCommand(StringTableEntry name,IntCallback, const char *usage, S32 minArgs, S32 maxArgs);
|
||||
void addCommand(StringTableEntry name,FloatCallback, const char *usage, S32 minArgs, S32 maxArgs);
|
||||
void addCommand(StringTableEntry name,VoidCallback, const char *usage, S32 minArgs, S32 maxArgs);
|
||||
void addCommand(StringTableEntry name,BoolCallback, const char *usage, S32 minArgs, S32 maxArgs);
|
||||
|
||||
void addOverload(const char *name, const char* altUsage);
|
||||
|
||||
void markGroup(const char* name, const char* usage);
|
||||
char * lastUsage;
|
||||
|
||||
void getEntryList(Vector<Entry *> *);
|
||||
|
||||
Entry *lookup(StringTableEntry name);
|
||||
Entry *lookupRecursive(StringTableEntry name);
|
||||
Entry *createLocalEntry(StringTableEntry name);
|
||||
void buildHashTable();
|
||||
void clearEntries();
|
||||
bool classLinkTo(Namespace *parent);
|
||||
bool unlinkClass(Namespace *parent);
|
||||
|
||||
const char *tabComplete(const char *prevText, S32 baseLen, bool fForward);
|
||||
|
||||
static U32 mCacheSequence;
|
||||
static DataChunker mCacheAllocator;
|
||||
static DataChunker mAllocator;
|
||||
static void trashCache();
|
||||
static Namespace *mNamespaceList;
|
||||
static Namespace *mGlobalNamespace;
|
||||
|
||||
static void init();
|
||||
static void shutdown();
|
||||
static Namespace *global();
|
||||
|
||||
static Namespace *find(StringTableEntry name, StringTableEntry package=NULL);
|
||||
|
||||
static void activatePackage(StringTableEntry name);
|
||||
static void deactivatePackage(StringTableEntry name);
|
||||
static void dumpClasses();
|
||||
static void dumpFunctions();
|
||||
static void printNamespaceEntries(Namespace * g);
|
||||
static void unlinkPackages();
|
||||
static void relinkPackages();
|
||||
static bool isPackage(StringTableEntry name);
|
||||
};
|
||||
|
||||
extern char *typeValueEmpty;
|
||||
|
||||
class Dictionary
|
||||
{
|
||||
public:
|
||||
struct Entry
|
||||
{
|
||||
enum
|
||||
{
|
||||
TypeInternalInt = -3,
|
||||
TypeInternalFloat = -2,
|
||||
TypeInternalString = -1,
|
||||
};
|
||||
|
||||
StringTableEntry name;
|
||||
Entry *nextEntry;
|
||||
S32 type;
|
||||
char *sval;
|
||||
U32 ival; // doubles as strlen when type = -1
|
||||
F32 fval;
|
||||
U32 bufferLen;
|
||||
void *dataPtr;
|
||||
|
||||
Entry(StringTableEntry name);
|
||||
~Entry();
|
||||
|
||||
U32 getIntValue()
|
||||
{
|
||||
if(type <= TypeInternalString)
|
||||
return ival;
|
||||
else
|
||||
return dAtoi(Con::getData(type, dataPtr, 0));
|
||||
}
|
||||
F32 getFloatValue()
|
||||
{
|
||||
if(type <= TypeInternalString)
|
||||
return fval;
|
||||
else
|
||||
return dAtof(Con::getData(type, dataPtr, 0));
|
||||
}
|
||||
const char *getStringValue()
|
||||
{
|
||||
if(type == TypeInternalString)
|
||||
return sval;
|
||||
if(type == TypeInternalFloat)
|
||||
return Con::getData(TypeF32, &fval, 0);
|
||||
else if(type == TypeInternalInt)
|
||||
return Con::getData(TypeS32, &ival, 0);
|
||||
else
|
||||
return Con::getData(type, dataPtr, 0);
|
||||
}
|
||||
void setIntValue(U32 val)
|
||||
{
|
||||
if(type <= TypeInternalString)
|
||||
{
|
||||
fval = (F32)val;
|
||||
ival = val;
|
||||
if(sval != typeValueEmpty)
|
||||
{
|
||||
dFree(sval);
|
||||
sval = typeValueEmpty;
|
||||
}
|
||||
type = TypeInternalInt;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *dptr = Con::getData(TypeS32, &val, 0);
|
||||
Con::setData(type, dataPtr, 0, 1, &dptr);
|
||||
}
|
||||
}
|
||||
void setFloatValue(F32 val)
|
||||
{
|
||||
if(type <= TypeInternalString)
|
||||
{
|
||||
fval = val;
|
||||
ival = static_cast<U32>(val);
|
||||
if(sval != typeValueEmpty)
|
||||
{
|
||||
dFree(sval);
|
||||
sval = typeValueEmpty;
|
||||
}
|
||||
type = TypeInternalFloat;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *dptr = Con::getData(TypeF32, &val, 0);
|
||||
Con::setData(type, dataPtr, 0, 1, &dptr);
|
||||
}
|
||||
}
|
||||
void setStringValue(const char *value);
|
||||
};
|
||||
|
||||
private:
|
||||
struct HashTableData
|
||||
{
|
||||
Dictionary* owner;
|
||||
S32 size;
|
||||
S32 count;
|
||||
Entry **data;
|
||||
};
|
||||
|
||||
HashTableData *hashTable;
|
||||
ExprEvalState *exprState;
|
||||
public:
|
||||
StringTableEntry scopeName;
|
||||
Namespace *scopeNamespace;
|
||||
CodeBlock *code;
|
||||
U32 ip;
|
||||
|
||||
Dictionary();
|
||||
Dictionary(ExprEvalState *state, Dictionary* ref=NULL);
|
||||
~Dictionary();
|
||||
Entry *lookup(StringTableEntry name);
|
||||
Entry *add(StringTableEntry name);
|
||||
void setState(ExprEvalState *state, Dictionary* ref=NULL);
|
||||
void remove(Entry *);
|
||||
void reset();
|
||||
|
||||
void exportVariables(const char *varString, const char *fileName, bool append);
|
||||
void deleteVariables(const char *varString);
|
||||
|
||||
void setVariable(StringTableEntry name, const char *value);
|
||||
const char *getVariable(StringTableEntry name, bool *valid = NULL);
|
||||
|
||||
void addVariable(const char *name, S32 type, void *dataPtr);
|
||||
bool removeVariable(StringTableEntry name);
|
||||
|
||||
/// Return the best tab completion for prevText, with the length
|
||||
/// of the pre-tab string in baseLen.
|
||||
const char *tabComplete(const char *prevText, S32 baseLen, bool);
|
||||
};
|
||||
|
||||
class ExprEvalState
|
||||
{
|
||||
public:
|
||||
/// @name Expression Evaluation
|
||||
/// @{
|
||||
|
||||
///
|
||||
SimObject *thisObject;
|
||||
Dictionary::Entry *currentVariable;
|
||||
bool traceOn;
|
||||
|
||||
ExprEvalState();
|
||||
~ExprEvalState();
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Stack Management
|
||||
/// @{
|
||||
|
||||
///
|
||||
Dictionary globalVars;
|
||||
Vector<Dictionary *> stack;
|
||||
void setCurVarName(StringTableEntry name);
|
||||
void setCurVarNameCreate(StringTableEntry name);
|
||||
S32 getIntVariable();
|
||||
F64 getFloatVariable();
|
||||
const char *getStringVariable();
|
||||
void setIntVariable(S32 val);
|
||||
void setFloatVariable(F64 val);
|
||||
void setStringVariable(const char *str);
|
||||
|
||||
void pushFrame(StringTableEntry frameName, Namespace *ns);
|
||||
void popFrame();
|
||||
|
||||
/// Puts a reference to an existing stack frame
|
||||
/// on the top of the stack.
|
||||
void pushFrameRef(S32 stackIndex);
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
#endif
|
215
engine/console/consoleLogger.cc
Executable file
215
engine/console/consoleLogger.cc
Executable file
@ -0,0 +1,215 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "console/consoleLogger.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
Vector<ConsoleLogger *> ConsoleLogger::mActiveLoggers;
|
||||
bool ConsoleLogger::smInitialized = false;
|
||||
|
||||
IMPLEMENT_CONOBJECT( ConsoleLogger );
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleLogger::ConsoleLogger()
|
||||
{
|
||||
mFilename = NULL;
|
||||
mLogging = false;
|
||||
mAppend = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleLogger::ConsoleLogger( const char *fileName, bool append )
|
||||
{
|
||||
mLogging = false;
|
||||
|
||||
mLevel = ConsoleLogEntry::Normal;
|
||||
mFilename = StringTable->insert( fileName );
|
||||
mAppend = append;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static EnumTable::Enums logLevelEnums[] =
|
||||
{
|
||||
{ ConsoleLogEntry::Normal, "normal" },
|
||||
{ ConsoleLogEntry::Warning, "warning" },
|
||||
{ ConsoleLogEntry::Error, "error" },
|
||||
};
|
||||
|
||||
static EnumTable gLogLevelTable( 3, &logLevelEnums[0] );
|
||||
|
||||
void ConsoleLogger::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addGroup( "Logging" );
|
||||
addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable );
|
||||
endGroup( "Logging" );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool ConsoleLogger::processArguments( S32 argc, const char **argv )
|
||||
{
|
||||
if( argc == 0 )
|
||||
return false;
|
||||
|
||||
bool append = false;
|
||||
|
||||
if( argc == 2 )
|
||||
append = dAtob( argv[1] );
|
||||
|
||||
mAppend = append;
|
||||
mFilename = StringTable->insert( argv[0] );
|
||||
|
||||
if( init() )
|
||||
{
|
||||
attach();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleLogger::~ConsoleLogger()
|
||||
{
|
||||
detach();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool ConsoleLogger::init()
|
||||
{
|
||||
if( smInitialized )
|
||||
return true;
|
||||
|
||||
Con::addConsumer( ConsoleLogger::logCallback );
|
||||
smInitialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool ConsoleLogger::attach()
|
||||
{
|
||||
if( mFilename == NULL )
|
||||
{
|
||||
Con::errorf( "ConsoleLogger failed to attach: no filename supplied." );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to see if this is initialized before using it
|
||||
if( !smInitialized )
|
||||
{
|
||||
if( !init() )
|
||||
{
|
||||
Con::errorf( "ConsoleLogger failed to initalize." );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if( mLogging )
|
||||
return false;
|
||||
|
||||
// Open the filestream
|
||||
mStream.open( mFilename, ( mAppend ? FileStream::WriteAppend : FileStream::Write ) );
|
||||
|
||||
// Add this to list of active loggers
|
||||
mActiveLoggers.push_back( this );
|
||||
mLogging = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool ConsoleLogger::detach()
|
||||
{
|
||||
|
||||
// Make sure this is valid before messing with it
|
||||
if( !smInitialized )
|
||||
{
|
||||
if( !init() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if( !mLogging )
|
||||
return false;
|
||||
|
||||
// Close filestream
|
||||
mStream.close();
|
||||
|
||||
// Remove this object from the list of active loggers
|
||||
for( int i = 0; i < mActiveLoggers.size(); i++ )
|
||||
{
|
||||
if( mActiveLoggers[i] == this )
|
||||
{
|
||||
mActiveLoggers.erase( i );
|
||||
mLogging = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // If this happens, it's bad...
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ConsoleLogger::logCallback( ConsoleLogEntry::Level level, const char *consoleLine )
|
||||
{
|
||||
|
||||
ConsoleLogger *curr;
|
||||
|
||||
// Loop through active consumers and send them the message
|
||||
for( int i = 0; i < mActiveLoggers.size(); i++ )
|
||||
{
|
||||
curr = mActiveLoggers[i];
|
||||
|
||||
// If the log level is within the log threshhold, log it
|
||||
if( curr->mLevel <= level )
|
||||
curr->log( consoleLine );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ConsoleLogger::log( const char *consoleLine )
|
||||
{
|
||||
// Check to see if this is intalized before using it
|
||||
if( !smInitialized )
|
||||
{
|
||||
if( !init() )
|
||||
{
|
||||
Con::errorf( "I don't know how this happened, but log called on this without it being initialized" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mStream.writeLine( (U8 *)consoleLine );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod( ConsoleLogger, attach, bool, 2, 2, "() Attaches this object to the console and begins logging" )
|
||||
{
|
||||
ConsoleLogger *logger = static_cast<ConsoleLogger *>( object );
|
||||
return logger->attach();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod( ConsoleLogger, detach, bool, 2, 2, "() Detaches this object from the console and stops logging" )
|
||||
{
|
||||
ConsoleLogger *logger = static_cast<ConsoleLogger *>( object );
|
||||
return logger->detach();
|
||||
}
|
93
engine/console/consoleLogger.h
Executable file
93
engine/console/consoleLogger.h
Executable file
@ -0,0 +1,93 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "console/simBase.h"
|
||||
#include "console/console.h"
|
||||
#include "core/fileStream.h"
|
||||
|
||||
#ifndef _CONSOLE_LOGGER_H_
|
||||
#define _CONSOLE_LOGGER_H_
|
||||
|
||||
/// A class designed to be used as a console consumer and log
|
||||
/// the data it receives to a file.
|
||||
class ConsoleLogger : public SimObject
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
|
||||
private:
|
||||
bool mLogging; ///< True if it is currently consuming and logging
|
||||
FileStream mStream; ///< File stream this object writes to
|
||||
static bool smInitialized; ///< This is for use with the default constructor
|
||||
bool mAppend; ///< If false, it will clear the file before logging to it.
|
||||
StringTableEntry mFilename; ///< The file name to log to.
|
||||
|
||||
/// List of active ConsoleLoggers to send log messages to
|
||||
static Vector<ConsoleLogger *> mActiveLoggers;
|
||||
|
||||
/// The log function called by the consumer callback
|
||||
/// @param consoleLine Line of text to log
|
||||
void log( const char *consoleLine );
|
||||
|
||||
/// Utility function, sets up the object (for script interface) returns true if successful
|
||||
bool init();
|
||||
|
||||
public:
|
||||
|
||||
// @name Public console variables
|
||||
/// @{
|
||||
ConsoleLogEntry::Level mLevel; ///< The level of log messages to log
|
||||
/// @}
|
||||
|
||||
DECLARE_CONOBJECT( ConsoleLogger );
|
||||
|
||||
static void initPersistFields();
|
||||
|
||||
/// Console constructor
|
||||
///
|
||||
/// @code
|
||||
/// // Example script constructor usage.
|
||||
/// %obj = new ConsoleLogger( objName, logFileName, [append = false] );
|
||||
/// @endcode
|
||||
bool processArguments( S32 argc, const char **argv );
|
||||
|
||||
/// Default constructor, make sure to initalize
|
||||
ConsoleLogger();
|
||||
|
||||
/// Constructor
|
||||
/// @param fileName File name to log to
|
||||
/// @param append If false, it will clear the file, then start logging, else it will append
|
||||
ConsoleLogger( const char *fileName, bool append = false );
|
||||
|
||||
/// Destructor
|
||||
~ConsoleLogger();
|
||||
|
||||
/// Attach to the console and begin logging
|
||||
///
|
||||
/// Returns true if the action is successful
|
||||
bool attach();
|
||||
|
||||
/// Detach from the console and stop logging
|
||||
///
|
||||
/// Returns true if the action is successful
|
||||
bool detach();
|
||||
|
||||
/// Sets the level of console messages to log.
|
||||
///
|
||||
/// @param level Log level. Only items of the specified level or
|
||||
/// lower are logged.
|
||||
/// @see ConsoleLogEntry::Level
|
||||
void setLogLevel( ConsoleLogEntry::Level level );
|
||||
|
||||
/// Returns the level of console messages to log
|
||||
ConsoleLogEntry::Level getLogLevel() const;
|
||||
|
||||
/// The callback for the console consumer
|
||||
///
|
||||
/// @note This is a global callback, not executed per-instance.
|
||||
/// @see Con::addConsumer
|
||||
static void logCallback( ConsoleLogEntry::Level level, const char *consoleLine );
|
||||
};
|
||||
|
||||
#endif
|
408
engine/console/consoleObject.cc
Executable file
408
engine/console/consoleObject.cc
Executable file
@ -0,0 +1,408 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "core/stringTable.h"
|
||||
#include "core/crc.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "console/typeValidators.h"
|
||||
|
||||
AbstractClassRep * AbstractClassRep::classLinkList = NULL;
|
||||
static AbstractClassRep::FieldList sg_tempFieldList;
|
||||
U32 AbstractClassRep::NetClassCount [NetClassGroupsCount][NetClassTypesCount] = {{0, },};
|
||||
U32 AbstractClassRep::NetClassBitSize[NetClassGroupsCount][NetClassTypesCount] = {{0, },};
|
||||
|
||||
AbstractClassRep ** AbstractClassRep::classTable[NetClassGroupsCount][NetClassTypesCount];
|
||||
|
||||
U32 AbstractClassRep::classCRC[NetClassGroupsCount] = {INITIAL_CRC_VALUE, };
|
||||
bool AbstractClassRep::initialized = false;
|
||||
|
||||
//--------------------------------------
|
||||
const AbstractClassRep::Field *AbstractClassRep::findField(StringTableEntry name) const
|
||||
{
|
||||
for(U32 i = 0; i < mFieldList.size(); i++)
|
||||
if(mFieldList[i].pFieldname == name)
|
||||
return &mFieldList[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
void AbstractClassRep::registerClassRep(AbstractClassRep* in_pRep)
|
||||
{
|
||||
AssertFatal(in_pRep != NULL, "AbstractClassRep::registerClassRep was passed a NULL pointer!");
|
||||
|
||||
#ifdef TORQUE_DEBUG // assert if this class is already registered.
|
||||
for(AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass)
|
||||
{
|
||||
AssertFatal(dStrcmp(in_pRep->mClassName, walk->mClassName),
|
||||
"Duplicate class name registered in AbstractClassRep::registerClassRep()");
|
||||
}
|
||||
#endif
|
||||
|
||||
in_pRep->nextClass = classLinkList;
|
||||
classLinkList = in_pRep;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
ConsoleObject* AbstractClassRep::create(const char* in_pClassName)
|
||||
{
|
||||
AssertFatal(initialized,
|
||||
"AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize().");
|
||||
|
||||
for (AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass)
|
||||
if (!dStrcmp(walk->getClassName(), in_pClassName))
|
||||
return walk->create();
|
||||
|
||||
AssertWarn(0, avar("Couldn't find class rep for dynamic class: %s", in_pClassName));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
ConsoleObject* AbstractClassRep::create(const U32 groupId, const U32 typeId, const U32 in_classId)
|
||||
{
|
||||
AssertFatal(initialized,
|
||||
"AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize().");
|
||||
AssertFatal(in_classId < NetClassCount[groupId][typeId],
|
||||
"AbstractClassRep::create() - Class id out of range.");
|
||||
AssertFatal(classTable[groupId][typeId][in_classId] != NULL,
|
||||
"AbstractClassRep::create() - No class with requested ID type.");
|
||||
|
||||
// Look up the specified class and create it.
|
||||
if(classTable[groupId][typeId][in_classId])
|
||||
return classTable[groupId][typeId][in_classId]->create();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
static S32 QSORT_CALLBACK ACRCompare(const void *aptr, const void *bptr)
|
||||
{
|
||||
const AbstractClassRep *a = *((const AbstractClassRep **) aptr);
|
||||
const AbstractClassRep *b = *((const AbstractClassRep **) bptr);
|
||||
|
||||
if(a->mClassType != b->mClassType)
|
||||
return a->mClassType - b->mClassType;
|
||||
return dStrcmp(a->getClassName(), b->getClassName());
|
||||
}
|
||||
|
||||
void AbstractClassRep::initialize()
|
||||
{
|
||||
AssertFatal(!initialized, "Duplicate call to AbstractClassRep::initialize()!");
|
||||
Vector<AbstractClassRep *> dynamicTable(__FILE__, __LINE__);
|
||||
|
||||
AbstractClassRep *walk;
|
||||
|
||||
// Initialize namespace references...
|
||||
for (walk = classLinkList; walk; walk = walk->nextClass)
|
||||
{
|
||||
walk->mNamespace = Con::lookupNamespace(StringTable->insert(walk->getClassName()));
|
||||
walk->mNamespace->mClassRep = walk;
|
||||
}
|
||||
|
||||
// Initialize field lists... (and perform other console registration).
|
||||
for (walk = classLinkList; walk; walk = walk->nextClass)
|
||||
{
|
||||
// sg_tempFieldList is used as a staging area for field lists
|
||||
// (see addField, addGroup, etc.)
|
||||
sg_tempFieldList.setSize(0);
|
||||
|
||||
walk->init();
|
||||
|
||||
// So if we have things in it, copy it over...
|
||||
if (sg_tempFieldList.size() != 0)
|
||||
walk->mFieldList = sg_tempFieldList;
|
||||
|
||||
// And of course delete it every round.
|
||||
sg_tempFieldList.clear();
|
||||
}
|
||||
|
||||
// Calculate counts and bit sizes for the various NetClasses.
|
||||
for (U32 group = 0; group < NetClassGroupsCount; group++)
|
||||
{
|
||||
U32 groupMask = 1 << group;
|
||||
|
||||
// Specifically, for each NetClass of each NetGroup...
|
||||
for(U32 type = 0; type < NetClassTypesCount; type++)
|
||||
{
|
||||
// Go through all the classes and find matches...
|
||||
for (walk = classLinkList; walk; walk = walk->nextClass)
|
||||
{
|
||||
if(walk->mClassType == type && walk->mClassGroupMask & groupMask)
|
||||
dynamicTable.push_back(walk);
|
||||
}
|
||||
|
||||
// Set the count for this NetGroup and NetClass
|
||||
NetClassCount[group][type] = dynamicTable.size();
|
||||
if(!NetClassCount[group][type])
|
||||
continue; // If no classes matched, skip to next.
|
||||
|
||||
// Sort by type and then by name.
|
||||
dQsort((void *) &dynamicTable[0], dynamicTable.size(), sizeof(AbstractClassRep *), ACRCompare);
|
||||
|
||||
// Allocate storage in the classTable
|
||||
classTable[group][type] = new AbstractClassRep*[NetClassCount[group][type]];
|
||||
|
||||
// Fill this in and assign class ids for this group.
|
||||
for(U32 i = 0; i < NetClassCount[group][type];i++)
|
||||
{
|
||||
classTable[group][type][i] = dynamicTable[i];
|
||||
dynamicTable[i]->mClassId[group] = i;
|
||||
}
|
||||
|
||||
// And calculate the size of bitfields for this group and type.
|
||||
NetClassBitSize[group][type] =
|
||||
getBinLog2(getNextPow2(NetClassCount[group][type] + 1));
|
||||
|
||||
dynamicTable.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, we're golden!
|
||||
initialized = true;
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//-------------------------------------- ConsoleObject
|
||||
|
||||
char replacebuf[1024];
|
||||
char* suppressSpaces(const char* in_pname)
|
||||
{
|
||||
U32 i = 0;
|
||||
char chr;
|
||||
do
|
||||
{
|
||||
chr = in_pname[i];
|
||||
replacebuf[i++] = (chr != 32) ? chr : '_';
|
||||
} while(chr);
|
||||
|
||||
return replacebuf;
|
||||
}
|
||||
|
||||
void ConsoleObject::addGroup(const char* in_pGroupname, const char* in_pGroupDocs)
|
||||
{
|
||||
// Remove spaces.
|
||||
char* pFieldNameBuf = suppressSpaces(in_pGroupname);
|
||||
|
||||
// Append group type to fieldname.
|
||||
dStrcat(pFieldNameBuf, "_begingroup");
|
||||
|
||||
// Create Field.
|
||||
AbstractClassRep::Field f;
|
||||
f.pFieldname = StringTable->insert(pFieldNameBuf);
|
||||
f.pGroupname = StringTable->insert(in_pGroupname);
|
||||
|
||||
if(in_pGroupDocs)
|
||||
f.pFieldDocs = StringTable->insert(in_pGroupDocs);
|
||||
else
|
||||
f.pFieldDocs = NULL;
|
||||
|
||||
f.type = AbstractClassRep::StartGroupFieldType;
|
||||
f.elementCount = 0;
|
||||
f.groupExpand = false;
|
||||
f.validator = NULL;
|
||||
f.setDataFn = &defaultProtectedSetFn;
|
||||
f.getDataFn = &defaultProtectedGetFn;
|
||||
|
||||
// Add to field list.
|
||||
sg_tempFieldList.push_back(f);
|
||||
}
|
||||
|
||||
void ConsoleObject::endGroup(const char* in_pGroupname)
|
||||
{
|
||||
// Remove spaces.
|
||||
char* pFieldNameBuf = suppressSpaces(in_pGroupname);
|
||||
|
||||
// Append group type to fieldname.
|
||||
dStrcat(pFieldNameBuf, "_endgroup");
|
||||
|
||||
// Create Field.
|
||||
AbstractClassRep::Field f;
|
||||
f.pFieldname = StringTable->insert(pFieldNameBuf);
|
||||
f.pGroupname = StringTable->insert(in_pGroupname);
|
||||
f.pFieldDocs = NULL;
|
||||
f.type = AbstractClassRep::EndGroupFieldType;
|
||||
f.groupExpand = false;
|
||||
f.validator = NULL;
|
||||
f.setDataFn = &defaultProtectedSetFn;
|
||||
f.getDataFn = &defaultProtectedGetFn;
|
||||
f.elementCount = 0;
|
||||
|
||||
// Add to field list.
|
||||
sg_tempFieldList.push_back(f);
|
||||
}
|
||||
|
||||
void ConsoleObject::addField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
const char* in_pFieldDocs)
|
||||
{
|
||||
addField(
|
||||
in_pFieldname,
|
||||
in_fieldType,
|
||||
in_fieldOffset,
|
||||
1,
|
||||
NULL,
|
||||
in_pFieldDocs);
|
||||
}
|
||||
|
||||
void ConsoleObject::addProtectedField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
AbstractClassRep::SetDataNotify in_setDataFn,
|
||||
AbstractClassRep::GetDataNotify in_getDataFn,
|
||||
const char* in_pFieldDocs)
|
||||
{
|
||||
addProtectedField(
|
||||
in_pFieldname,
|
||||
in_fieldType,
|
||||
in_fieldOffset,
|
||||
in_setDataFn,
|
||||
in_getDataFn,
|
||||
1,
|
||||
NULL,
|
||||
in_pFieldDocs);
|
||||
}
|
||||
|
||||
|
||||
void ConsoleObject::addField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
const U32 in_elementCount,
|
||||
EnumTable *in_table,
|
||||
const char* in_pFieldDocs)
|
||||
{
|
||||
AbstractClassRep::Field f;
|
||||
f.pFieldname = StringTable->insert(in_pFieldname);
|
||||
f.pGroupname = NULL;
|
||||
|
||||
if(in_pFieldDocs)
|
||||
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
|
||||
else
|
||||
f.pFieldDocs = NULL;
|
||||
|
||||
f.type = in_fieldType;
|
||||
f.offset = in_fieldOffset;
|
||||
f.elementCount = in_elementCount;
|
||||
f.table = in_table;
|
||||
f.validator = NULL;
|
||||
|
||||
f.setDataFn = &defaultProtectedSetFn;
|
||||
f.getDataFn = &defaultProtectedGetFn;
|
||||
|
||||
sg_tempFieldList.push_back(f);
|
||||
}
|
||||
|
||||
void ConsoleObject::addProtectedField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
AbstractClassRep::SetDataNotify in_setDataFn,
|
||||
AbstractClassRep::GetDataNotify in_getDataFn,
|
||||
const U32 in_elementCount,
|
||||
EnumTable *in_table,
|
||||
const char* in_pFieldDocs)
|
||||
{
|
||||
AbstractClassRep::Field f;
|
||||
f.pFieldname = StringTable->insert(in_pFieldname);
|
||||
f.pGroupname = NULL;
|
||||
|
||||
if(in_pFieldDocs)
|
||||
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
|
||||
else
|
||||
f.pFieldDocs = NULL;
|
||||
|
||||
f.type = in_fieldType;
|
||||
f.offset = in_fieldOffset;
|
||||
f.elementCount = in_elementCount;
|
||||
f.table = in_table;
|
||||
f.validator = NULL;
|
||||
|
||||
f.setDataFn = in_setDataFn;
|
||||
f.getDataFn = in_getDataFn;
|
||||
|
||||
sg_tempFieldList.push_back(f);
|
||||
}
|
||||
|
||||
void ConsoleObject::addFieldV(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
TypeValidator *v,
|
||||
const char* in_pFieldDocs)
|
||||
{
|
||||
AbstractClassRep::Field f;
|
||||
f.pFieldname = StringTable->insert(in_pFieldname);
|
||||
f.pGroupname = NULL;
|
||||
if(in_pFieldDocs)
|
||||
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
|
||||
else
|
||||
f.pFieldDocs = NULL;
|
||||
f.type = in_fieldType;
|
||||
f.offset = in_fieldOffset;
|
||||
f.elementCount = 1;
|
||||
f.table = NULL;
|
||||
f.setDataFn = &defaultProtectedSetFn;
|
||||
f.getDataFn = &defaultProtectedGetFn;
|
||||
f.validator = v;
|
||||
v->fieldIndex = sg_tempFieldList.size();
|
||||
|
||||
sg_tempFieldList.push_back(f);
|
||||
}
|
||||
|
||||
void ConsoleObject::addDepricatedField(const char *fieldName)
|
||||
{
|
||||
AbstractClassRep::Field f;
|
||||
f.pFieldname = StringTable->insert(fieldName);
|
||||
f.pGroupname = NULL;
|
||||
f.pFieldDocs = NULL;
|
||||
f.type = AbstractClassRep::DepricatedFieldType;
|
||||
f.offset = 0;
|
||||
f.elementCount = 0;
|
||||
f.table = NULL;
|
||||
f.validator = NULL;
|
||||
f.setDataFn = &defaultProtectedSetFn;
|
||||
f.getDataFn = &defaultProtectedGetFn;
|
||||
|
||||
sg_tempFieldList.push_back(f);
|
||||
}
|
||||
|
||||
|
||||
bool ConsoleObject::removeField(const char* in_pFieldname)
|
||||
{
|
||||
for (U32 i = 0; i < sg_tempFieldList.size(); i++) {
|
||||
if (dStricmp(in_pFieldname, sg_tempFieldList[i].pFieldname) == 0) {
|
||||
sg_tempFieldList.erase(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
void ConsoleObject::initPersistFields()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
void ConsoleObject::consoleInit()
|
||||
{
|
||||
}
|
||||
|
||||
ConsoleObject::~ConsoleObject()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
AbstractClassRep* ConsoleObject::getClassRep() const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
744
engine/console/consoleObject.h
Executable file
744
engine/console/consoleObject.h
Executable file
@ -0,0 +1,744 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _CONSOLEOBJECT_H_
|
||||
#define _CONSOLEOBJECT_H_
|
||||
|
||||
//Includes
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/tVector.h"
|
||||
#endif
|
||||
#ifndef _STRINGTABLE_H_
|
||||
#include "core/stringTable.h"
|
||||
#endif
|
||||
#ifndef _BITSET_H_
|
||||
#include "core/bitSet.h"
|
||||
#endif
|
||||
#ifndef _CONSOLE_H_
|
||||
#include "console/console.h"
|
||||
#endif
|
||||
|
||||
class Namespace;
|
||||
class ConsoleObject;
|
||||
|
||||
enum NetClassTypes {
|
||||
NetClassTypeObject = 0,
|
||||
NetClassTypeDataBlock,
|
||||
NetClassTypeEvent,
|
||||
NetClassTypesCount,
|
||||
};
|
||||
|
||||
enum NetClassGroups {
|
||||
NetClassGroupGame = 0,
|
||||
NetClassGroupCommunity,
|
||||
NetClassGroup3,
|
||||
NetClassGroup4,
|
||||
NetClassGroupsCount,
|
||||
};
|
||||
|
||||
enum NetClassMasks {
|
||||
NetClassGroupGameMask = BIT(NetClassGroupGame),
|
||||
NetClassGroupCommunityMask = BIT(NetClassGroupCommunity),
|
||||
};
|
||||
|
||||
enum NetDirection
|
||||
{
|
||||
NetEventDirAny,
|
||||
NetEventDirServerToClient,
|
||||
NetEventDirClientToServer,
|
||||
};
|
||||
|
||||
class SimObject;
|
||||
class TypeValidator;
|
||||
|
||||
/// Core functionality for class manipulation.
|
||||
///
|
||||
/// @section AbstractClassRep_intro Introduction (or, Why AbstractClassRep?)
|
||||
///
|
||||
/// Many of Torque's subsystems, especially network, console, and sim,
|
||||
/// require the ability to programatically instantiate classes. For instance,
|
||||
/// when objects are ghosted, the networking layer needs to be able to create
|
||||
/// an instance of the object on the client. When the console scripting
|
||||
/// language runtime encounters the "new" keyword, it has to be able to fill
|
||||
/// that request.
|
||||
///
|
||||
/// Since standard C++ doesn't provide a function to create a new instance of
|
||||
/// an arbitrary class at runtime, one must be created. This is what
|
||||
/// AbstractClassRep and ConcreteClassRep are all about. They allow the registration
|
||||
/// and instantiation of arbitrary classes at runtime.
|
||||
///
|
||||
/// In addition, ACR keeps track of the fields (registered via addField() and co.) of
|
||||
/// a class, allowing programmatic access of class fields.
|
||||
///
|
||||
/// @see ConsoleObject
|
||||
///
|
||||
/// @note In general, you will only access the functionality implemented in this class via
|
||||
/// ConsoleObject::create(). Most of the time, you will only ever need to use this part
|
||||
/// part of the engine indirectly - ie, you will use the networking system or the console,
|
||||
/// or ConsoleObject, and they will indirectly use this code. <b>The following discussion
|
||||
/// is really only relevant for advanced engine users.</b>
|
||||
///
|
||||
/// @section AbstractClassRep_netstuff NetClasses and Class IDs
|
||||
///
|
||||
/// Torque supports a notion of group, type, and direction for objects passed over
|
||||
/// the network. Class IDs are assigned sequentially per-group, per-type, so that, for instance,
|
||||
/// the IDs assigned to Datablocks are seperate from the IDs assigned to NetObjects or NetEvents.
|
||||
/// This can translate into significant bandwidth savings (especially since the size of the fields
|
||||
/// for transmitting these bits are determined at run-time based on the number of IDs given out.
|
||||
///
|
||||
/// @section AbstractClassRep_details AbstractClassRep Internals
|
||||
///
|
||||
/// Much like ConsoleConstructor, ACR does some preparatory work at runtime before execution
|
||||
/// is passed to main(). In actual fact, this preparatory work is done by the ConcreteClassRep
|
||||
/// template. Let's examine this more closely.
|
||||
///
|
||||
/// If we examine ConsoleObject, we see that two macros must be used in the definition of a
|
||||
/// properly integrated objects. From the ConsoleObject example:
|
||||
///
|
||||
/// @code
|
||||
/// // This is from inside the class definition...
|
||||
/// DECLARE_CONOBJECT(TorqueObject);
|
||||
///
|
||||
/// // And this is from outside the class definition...
|
||||
/// IMPLEMENT_CONOBJECT(TorqueObject);
|
||||
/// @endcode
|
||||
///
|
||||
/// What do these things actually do?
|
||||
///
|
||||
/// Not all that much, in fact. They expand to code something like this:
|
||||
///
|
||||
/// @code
|
||||
/// // This is from inside the class definition...
|
||||
/// static ConcreteClassRep<TorqueObject> dynClassRep;
|
||||
/// static AbstractClassRep* getParentStaticClassRep();
|
||||
/// static AbstractClassRep* getStaticClassRep();
|
||||
/// virtual AbstractClassRep* getClassRep() const;
|
||||
/// @endcode
|
||||
///
|
||||
/// @code
|
||||
/// // And this is from outside the class definition...
|
||||
/// AbstractClassRep* TorqueObject::getClassRep() const { return &TorqueObject::dynClassRep; }
|
||||
/// AbstractClassRep* TorqueObject::getStaticClassRep() { return &dynClassRep; }
|
||||
/// AbstractClassRep* TorqueObject::getParentStaticClassRep() { return Parent::getStaticClassRep(); }
|
||||
/// ConcreteClassRep<TorqueObject> TorqueObject::dynClassRep("TorqueObject", 0, -1, 0);
|
||||
/// @endcode
|
||||
///
|
||||
/// As you can see, getClassRep(), getStaticClassRep(), and getParentStaticClassRep() are just
|
||||
/// accessors to allow access to various ConcreteClassRep instances. This is where the Parent
|
||||
/// typedef comes into play as well - it lets getParentStaticClassRep() get the right
|
||||
/// class rep.
|
||||
///
|
||||
/// In addition, dynClassRep is declared as a member of TorqueObject, and defined later
|
||||
/// on. Much like ConsoleConstructor, ConcreteClassReps add themselves to a global linked
|
||||
/// list in their constructor.
|
||||
///
|
||||
/// Then, when AbstractClassRep::initialize() is called, from Con::init(), we iterate through
|
||||
/// the list and perform the following tasks:
|
||||
/// - Sets up a Namespace for each class.
|
||||
/// - Call the init() method on each ConcreteClassRep. This method:
|
||||
/// - Links namespaces between parent and child classes, using Con::classLinkNamespaces.
|
||||
/// - Calls initPersistFields() and consoleInit().
|
||||
/// - As a result of calling initPersistFields, the field list for the class is populated.
|
||||
/// - Assigns network IDs for classes based on their NetGroup membership. Determines
|
||||
/// bit allocations for network ID fields.
|
||||
///
|
||||
/// @nosubgrouping
|
||||
class AbstractClassRep
|
||||
{
|
||||
friend class ConsoleObject;
|
||||
|
||||
public:
|
||||
|
||||
/// @name 'Tructors
|
||||
/// @{
|
||||
|
||||
AbstractClassRep()
|
||||
{
|
||||
VECTOR_SET_ASSOCIATION(mFieldList);
|
||||
parentClass = NULL;
|
||||
}
|
||||
virtual ~AbstractClassRep() { }
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Representation Interface
|
||||
/// @{
|
||||
|
||||
S32 mClassGroupMask; ///< Mask indicating in which NetGroups this object belongs.
|
||||
S32 mClassType; ///< Stores the NetClass of this class.
|
||||
S32 mNetEventDir; ///< Stores the NetDirection of this class.
|
||||
S32 mClassId[NetClassGroupsCount]; ///< Stores the IDs assigned to this class for each group.
|
||||
|
||||
S32 getClassId (U32 netClassGroup) const;
|
||||
static U32 getClassCRC (U32 netClassGroup);
|
||||
const char* getClassName() const;
|
||||
static AbstractClassRep* getClassList();
|
||||
Namespace* getNameSpace();
|
||||
AbstractClassRep* getNextClass();
|
||||
AbstractClassRep* getParentClass();
|
||||
|
||||
/// Helper class to see if we are a given class, or a subclass thereof.
|
||||
bool isClass(AbstractClassRep *acr)
|
||||
{
|
||||
AbstractClassRep *walk = this;
|
||||
|
||||
// Walk up parents, checking for equivalence.
|
||||
while(walk)
|
||||
{
|
||||
if(walk == acr)
|
||||
return true;
|
||||
|
||||
walk = walk->parentClass;
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual ConsoleObject* create () const = 0;
|
||||
|
||||
protected:
|
||||
virtual void init() const = 0;
|
||||
|
||||
const char * mClassName;
|
||||
AbstractClassRep * nextClass;
|
||||
AbstractClassRep * parentClass;
|
||||
Namespace * mNamespace;
|
||||
|
||||
/// @}
|
||||
|
||||
|
||||
/// @name Fields
|
||||
/// @{
|
||||
public:
|
||||
|
||||
/// This is a function pointer typedef to support get/set callbacks for fields
|
||||
typedef bool (*SetDataNotify)( void *obj, const char *data );
|
||||
typedef const char *(*GetDataNotify)( void *obj, const char *data );
|
||||
|
||||
enum ACRFieldTypes
|
||||
{
|
||||
StartGroupFieldType = 0xFFFFFFFD,
|
||||
EndGroupFieldType = 0xFFFFFFFE,
|
||||
DepricatedFieldType = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
struct Field {
|
||||
const char* pFieldname; ///< Name of the field.
|
||||
const char* pGroupname; ///< Optionally filled field containing the group name.
|
||||
///
|
||||
/// This is filled when type is StartField or EndField
|
||||
|
||||
const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc.
|
||||
bool groupExpand; ///< Flag to track expanded/not state of this group in the editor.
|
||||
U32 type; ///< A type ID. @see ACRFieldTypes
|
||||
U32 offset; ///< Memory offset from beginning of class for this field.
|
||||
S32 elementCount; ///< Number of elements, if this is an array.
|
||||
EnumTable * table; ///< If this is an enum, this points to the table defining it.
|
||||
BitSet32 flag; ///< Stores various flags
|
||||
TypeValidator *validator; ///< Validator, if any.
|
||||
SetDataNotify setDataFn; ///< Set data notify Fn
|
||||
GetDataNotify getDataFn; ///< Get data notify Fn
|
||||
};
|
||||
typedef Vector<Field> FieldList;
|
||||
|
||||
FieldList mFieldList;
|
||||
|
||||
bool mDynamicGroupExpand;
|
||||
|
||||
const Field *findField(StringTableEntry fieldName) const;
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Abstract Class Database
|
||||
/// @{
|
||||
|
||||
protected:
|
||||
static AbstractClassRep ** classTable[NetClassGroupsCount][NetClassTypesCount];
|
||||
static AbstractClassRep * classLinkList;
|
||||
static U32 classCRC[NetClassGroupsCount];
|
||||
static bool initialized;
|
||||
|
||||
static ConsoleObject* create(const char* in_pClassName);
|
||||
static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId);
|
||||
|
||||
public:
|
||||
static U32 NetClassCount [NetClassGroupsCount][NetClassTypesCount];
|
||||
static U32 NetClassBitSize[NetClassGroupsCount][NetClassTypesCount];
|
||||
|
||||
static void registerClassRep(AbstractClassRep*);
|
||||
static void initialize(); // Called from Con::init once on startup
|
||||
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
inline AbstractClassRep *AbstractClassRep::getClassList()
|
||||
{
|
||||
return classLinkList;
|
||||
}
|
||||
|
||||
inline U32 AbstractClassRep::getClassCRC(U32 group)
|
||||
{
|
||||
return classCRC[group];
|
||||
}
|
||||
|
||||
inline AbstractClassRep *AbstractClassRep::getNextClass()
|
||||
{
|
||||
return nextClass;
|
||||
}
|
||||
|
||||
inline AbstractClassRep *AbstractClassRep::getParentClass()
|
||||
{
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
inline S32 AbstractClassRep::getClassId(U32 group) const
|
||||
{
|
||||
return mClassId[group];
|
||||
}
|
||||
|
||||
inline const char* AbstractClassRep::getClassName() const
|
||||
{
|
||||
return mClassName;
|
||||
}
|
||||
|
||||
inline Namespace *AbstractClassRep::getNameSpace()
|
||||
{
|
||||
return mNamespace;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//-------------------------------------- ConcreteClassRep
|
||||
//
|
||||
|
||||
|
||||
/// Helper class for AbstractClassRep.
|
||||
///
|
||||
/// @see AbtractClassRep
|
||||
/// @see ConsoleObject
|
||||
template <class T>
|
||||
class ConcreteClassRep : public AbstractClassRep
|
||||
{
|
||||
public:
|
||||
ConcreteClassRep(const char *name, S32 netClassGroupMask, S32 netClassType, S32 netEventDir, AbstractClassRep *parent)
|
||||
{
|
||||
// name is a static compiler string so no need to worry about copying or deleting
|
||||
mClassName = name;
|
||||
|
||||
// Clean up mClassId
|
||||
for(U32 i = 0; i < NetClassGroupsCount; i++)
|
||||
mClassId[i] = -1;
|
||||
|
||||
// Set properties for this ACR
|
||||
mClassType = netClassType;
|
||||
mClassGroupMask = netClassGroupMask;
|
||||
mNetEventDir = netEventDir;
|
||||
parentClass = parent;
|
||||
|
||||
// Finally, register ourselves.
|
||||
registerClassRep(this);
|
||||
};
|
||||
|
||||
/// Perform class specific initialization tasks.
|
||||
///
|
||||
/// Link namespaces, call initPersistFields() and consoleInit().
|
||||
void init() const
|
||||
{
|
||||
// Get handle to our parent class, if any, and ourselves (we are our parent's child).
|
||||
AbstractClassRep *parent = T::getParentStaticClassRep();
|
||||
AbstractClassRep *child = T::getStaticClassRep ();
|
||||
|
||||
// If we got reps, then link those namespaces! (To get proper inheritance.)
|
||||
if(parent && child)
|
||||
Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace());
|
||||
|
||||
// Finally, do any class specific initialization...
|
||||
T::initPersistFields();
|
||||
T::consoleInit();
|
||||
}
|
||||
|
||||
/// Wrap constructor.
|
||||
ConsoleObject* create() const { return new T; }
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Forward declaration of this function so it can be used in the class
|
||||
const char *defaultProtectedGetFn( void *obj, const char *data );
|
||||
|
||||
/// Interface class to the console.
|
||||
///
|
||||
/// @section ConsoleObject_basics The Basics
|
||||
///
|
||||
/// Any object which you want to work with the console system should derive from this,
|
||||
/// and access functionality through the static interface.
|
||||
///
|
||||
/// This class is always used with the DECLARE_CONOBJECT and IMPLEMENT_* macros.
|
||||
///
|
||||
/// @code
|
||||
/// // A very basic example object. It will do nothing!
|
||||
/// class TorqueObject : public ConsoleObject {
|
||||
/// // Must provide a Parent typedef so the console system knows what we inherit from.
|
||||
/// typedef ConsoleObject Parent;
|
||||
///
|
||||
/// // This does a lot of menial declaration for you.
|
||||
/// DECLARE_CONOBJECT(TorqueObject);
|
||||
///
|
||||
/// // This is for us to register our fields in.
|
||||
/// static void initPersistFields();
|
||||
///
|
||||
/// // A sample field.
|
||||
/// S8 mSample;
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// @code
|
||||
/// // And the accordant implementation...
|
||||
/// IMPLEMENT_CONOBJECT(TorqueObject);
|
||||
///
|
||||
/// void TorqueObject::initPersistFields()
|
||||
/// {
|
||||
/// // If you want to inherit any fields from the parent (you do), do this:
|
||||
/// Parent::initPersistFields();
|
||||
///
|
||||
/// // Pass the field, the type, the offset, and a usage string.
|
||||
/// addField("sample", TypeS8, Offset(mSample, TorqueObject), "A test field.");
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// That's all you need to do to get a class registered with the console system. At this point,
|
||||
/// you can instantiate it via script, tie methods to it using ConsoleMethod, register fields,
|
||||
/// and so forth. You can also register any global variables related to the class by creating
|
||||
/// a consoleInit() method.
|
||||
///
|
||||
/// You will need to use different IMPLEMENT_ macros in different cases; for instance, if you
|
||||
/// are making a NetObject (for ghosting), a DataBlock, or a NetEvent.
|
||||
///
|
||||
/// @see AbstractClassRep for gory implementation details.
|
||||
/// @nosubgrouping
|
||||
class ConsoleObject
|
||||
{
|
||||
protected:
|
||||
/// @deprecated This is disallowed.
|
||||
ConsoleObject() { /* disallowed */ }
|
||||
/// @deprecated This is disallowed.
|
||||
ConsoleObject(const ConsoleObject&);
|
||||
|
||||
protected:
|
||||
/// Get a reference to a field by name.
|
||||
const AbstractClassRep::Field *findField(StringTableEntry fieldName) const;
|
||||
|
||||
public:
|
||||
|
||||
/// Gets the ClassRep.
|
||||
virtual AbstractClassRep* getClassRep() const;
|
||||
|
||||
/// Set the value of a field.
|
||||
bool setField(const char *fieldName, const char *value);
|
||||
virtual ~ConsoleObject();
|
||||
|
||||
public:
|
||||
/// @name Object Creation
|
||||
/// @{
|
||||
static ConsoleObject* create(const char* in_pClassName);
|
||||
static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId);
|
||||
/// @}
|
||||
|
||||
public:
|
||||
/// Get the classname from a class tag.
|
||||
static const char* lookupClassName(const U32 in_classTag);
|
||||
|
||||
protected:
|
||||
/// @name Fields
|
||||
/// @{
|
||||
|
||||
/// Mark the beginning of a group of fields.
|
||||
///
|
||||
/// This is used in the consoleDoc system.
|
||||
/// @see console_autodoc
|
||||
static void addGroup(const char* in_pGroupname, const char* in_pGroupDocs = NULL);
|
||||
|
||||
/// Mark the end of a group of fields.
|
||||
///
|
||||
/// This is used in the consoleDoc system.
|
||||
/// @see console_autodoc
|
||||
static void endGroup(const char* in_pGroupname);
|
||||
|
||||
/// Register a complex field.
|
||||
///
|
||||
/// @param in_pFieldname Name of the field.
|
||||
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
|
||||
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
|
||||
/// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory.
|
||||
/// @param in_table An EnumTable, if this is an enumerated field.
|
||||
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
|
||||
static void addField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
const U32 in_elementCount = 1,
|
||||
EnumTable * in_table = NULL,
|
||||
const char* in_pFieldDocs = NULL);
|
||||
|
||||
/// Register a simple field.
|
||||
///
|
||||
/// @param in_pFieldname Name of the field.
|
||||
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
|
||||
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
|
||||
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
|
||||
static void addField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
const char* in_pFieldDocs);
|
||||
|
||||
/// Register a validated field.
|
||||
///
|
||||
/// A validated field is just like a normal field except that you can't
|
||||
/// have it be an array, and that you give it a pointer to a TypeValidator
|
||||
/// subclass, which is then used to validate any value placed in it. Invalid
|
||||
/// values are ignored and an error is printed to the console.
|
||||
///
|
||||
/// @see addField
|
||||
/// @see typeValidators.h
|
||||
static void addFieldV(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
TypeValidator *v,
|
||||
const char * in_pFieldDocs = NULL);
|
||||
|
||||
/// Register a complex protected field.
|
||||
///
|
||||
/// @param in_pFieldname Name of the field.
|
||||
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
|
||||
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
|
||||
/// @param in_setDataFn When this field gets set, it will call the callback provided. @see console_protected
|
||||
/// @param in_getDataFn When this field is accessed for it's data, it will return the value of this function
|
||||
/// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory.
|
||||
/// @param in_table An EnumTable, if this is an enumerated field.
|
||||
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
|
||||
static void addProtectedField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
AbstractClassRep::SetDataNotify in_setDataFn,
|
||||
AbstractClassRep::GetDataNotify in_getDataFn = &defaultProtectedGetFn,
|
||||
const U32 in_elementCount = 1,
|
||||
EnumTable * in_table = NULL,
|
||||
const char* in_pFieldDocs = NULL);
|
||||
|
||||
/// Register a simple protected field.
|
||||
///
|
||||
/// @param in_pFieldname Name of the field.
|
||||
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
|
||||
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
|
||||
/// @param in_setDataFn When this field gets set, it will call the callback provided. @see console_protected
|
||||
/// @param in_getDataFn When this field is accessed for it's data, it will return the value of this function
|
||||
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
|
||||
static void addProtectedField(const char* in_pFieldname,
|
||||
const U32 in_fieldType,
|
||||
const dsize_t in_fieldOffset,
|
||||
AbstractClassRep::SetDataNotify in_setDataFn,
|
||||
AbstractClassRep::GetDataNotify in_getDataFn = &defaultProtectedGetFn,
|
||||
const char* in_pFieldDocs = NULL);
|
||||
|
||||
/// Add a deprecated field.
|
||||
///
|
||||
/// A deprecated field will always be undefined, even if you assign a value to it. This
|
||||
/// is useful when you need to make sure that a field is not being used anymore.
|
||||
static void addDepricatedField(const char *fieldName);
|
||||
|
||||
/// Remove a field.
|
||||
///
|
||||
/// Sometimes, you just have to remove a field!
|
||||
/// @returns True on success.
|
||||
static bool removeField(const char* in_pFieldname);
|
||||
|
||||
/// @}
|
||||
public:
|
||||
/// Register dynamic fields in a subclass of ConsoleObject.
|
||||
///
|
||||
/// @see addField(), addFieldV(), addDepricatedField(), addGroup(), endGroup()
|
||||
static void initPersistFields();
|
||||
|
||||
/// Register global constant variables and do other one-time initialization tasks in
|
||||
/// a subclass of ConsoleObject.
|
||||
///
|
||||
/// @deprecated You should use ConsoleMethod and ConsoleFunction, not this, to
|
||||
/// register methods or commands.
|
||||
/// @see console
|
||||
static void consoleInit();
|
||||
|
||||
/// @name Field List
|
||||
/// @{
|
||||
|
||||
/// Get a list of all the fields. This information cannot be modified.
|
||||
const AbstractClassRep::FieldList& getFieldList() const;
|
||||
|
||||
/// Get a list of all the fields, set up so we can modify them.
|
||||
///
|
||||
/// @note This is a bad trick to pull if you aren't very careful,
|
||||
/// since you can blast field data!
|
||||
AbstractClassRep::FieldList& getModifiableFieldList();
|
||||
|
||||
/// Get a handle to a boolean telling us if we expanded the dynamic group.
|
||||
///
|
||||
/// @see GuiInspector::Inspect()
|
||||
bool& getDynamicGroupExpand();
|
||||
/// @}
|
||||
|
||||
/// @name ConsoleObject Implementation
|
||||
///
|
||||
/// These functions are implemented in every subclass of
|
||||
/// ConsoleObject by an IMPLEMENT_CONOBJECT or IMPLEMENT_CO_* macro.
|
||||
/// @{
|
||||
|
||||
/// Get the abstract class information for this class.
|
||||
static AbstractClassRep *getStaticClassRep() { return NULL; }
|
||||
|
||||
/// Get the abstract class information for this class's superclass.
|
||||
static AbstractClassRep *getParentStaticClassRep() { return NULL; }
|
||||
|
||||
/// Get our network-layer class id.
|
||||
///
|
||||
/// @param netClassGroup The net class for which we want our ID.
|
||||
/// @see
|
||||
S32 getClassId(U32 netClassGroup) const;
|
||||
|
||||
/// Get our compiler and platform independent class name.
|
||||
///
|
||||
/// @note This name can be used to instantiate another instance using create()
|
||||
const char *getClassName() const;
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
// Deprecated? -pw
|
||||
// Nope, not in TGE - THB
|
||||
#define addNamedField(fieldName,type,className) addField(#fieldName, type, Offset(fieldName,className))
|
||||
#define addNamedFieldV(fieldName,type,className, validator) addFieldV(#fieldName, type, Offset(fieldName,className), validator)
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//-------------------------------------- Inlines
|
||||
//
|
||||
inline S32 ConsoleObject::getClassId(U32 netClassGroup) const
|
||||
{
|
||||
AssertFatal(getClassRep() != NULL,"Cannot get tag from non-declared dynamic class!");
|
||||
return getClassRep()->getClassId(netClassGroup);
|
||||
}
|
||||
|
||||
inline const char * ConsoleObject::getClassName() const
|
||||
{
|
||||
AssertFatal(getClassRep() != NULL,
|
||||
"Cannot get tag from non-declared dynamic class");
|
||||
return getClassRep()->getClassName();
|
||||
}
|
||||
|
||||
inline const AbstractClassRep::Field * ConsoleObject::findField(StringTableEntry name) const
|
||||
{
|
||||
AssertFatal(getClassRep() != NULL,
|
||||
avar("Cannot get field '%s' from non-declared dynamic class.", name));
|
||||
return getClassRep()->findField(name);
|
||||
}
|
||||
|
||||
inline bool ConsoleObject::setField(const char *fieldName, const char *value)
|
||||
{
|
||||
//sanity check
|
||||
if ((! fieldName) || (! fieldName[0]) || (! value))
|
||||
return false;
|
||||
|
||||
if (! getClassRep())
|
||||
return false;
|
||||
|
||||
const AbstractClassRep::Field *myField = getClassRep()->findField(StringTable->insert(fieldName));
|
||||
|
||||
if (! myField)
|
||||
return false;
|
||||
|
||||
Con::setData(
|
||||
myField->type,
|
||||
(void *) (((const char *)(this)) + myField->offset),
|
||||
0,
|
||||
1,
|
||||
&value,
|
||||
myField->table,
|
||||
myField->flag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline ConsoleObject* ConsoleObject::create(const char* in_pClassName)
|
||||
{
|
||||
return AbstractClassRep::create(in_pClassName);
|
||||
}
|
||||
|
||||
inline ConsoleObject* ConsoleObject::create(const U32 groupId, const U32 typeId, const U32 in_classId)
|
||||
{
|
||||
return AbstractClassRep::create(groupId, typeId, in_classId);
|
||||
}
|
||||
|
||||
inline const AbstractClassRep::FieldList& ConsoleObject::getFieldList() const
|
||||
{
|
||||
return getClassRep()->mFieldList;
|
||||
}
|
||||
|
||||
inline AbstractClassRep::FieldList& ConsoleObject::getModifiableFieldList()
|
||||
{
|
||||
return getClassRep()->mFieldList;
|
||||
}
|
||||
|
||||
inline bool& ConsoleObject::getDynamicGroupExpand()
|
||||
{
|
||||
return getClassRep()->mDynamicGroupExpand;
|
||||
}
|
||||
|
||||
/// @name ConsoleObject Macros
|
||||
/// @{
|
||||
|
||||
#define DECLARE_CONOBJECT(className) \
|
||||
static ConcreteClassRep<className> dynClassRep; \
|
||||
static AbstractClassRep* getParentStaticClassRep(); \
|
||||
static AbstractClassRep* getStaticClassRep(); \
|
||||
virtual AbstractClassRep* getClassRep() const
|
||||
|
||||
#define IMPLEMENT_CONOBJECT(className) \
|
||||
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
||||
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
||||
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
||||
ConcreteClassRep<className> className::dynClassRep(#className, 0, -1, 0, className::getParentStaticClassRep())
|
||||
|
||||
#define IMPLEMENT_CO_NETOBJECT_V1(className) \
|
||||
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
||||
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
||||
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
||||
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeObject, 0, className::getParentStaticClassRep())
|
||||
|
||||
#define IMPLEMENT_CO_DATABLOCK_V1(className) \
|
||||
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
||||
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
||||
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
||||
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeDataBlock, 0, className::getParentStaticClassRep())
|
||||
|
||||
/// @}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Protected field default get/set functions
|
||||
//
|
||||
// The reason for these functions is that it will save one branch per console
|
||||
// data request and script functions will still execute at the same speed as
|
||||
// before the modifications to allow protected static fields. These will just
|
||||
// inline and the code should be roughly the same size, and just as fast as
|
||||
// before the modifications. -pw
|
||||
inline bool defaultProtectedSetFn( void *obj, const char *data )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
inline const char *defaultProtectedGetFn( void *obj, const char *data )
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
#endif //_CONSOLEOBJECT_H_
|
76
engine/console/consoleParser.cc
Executable file
76
engine/console/consoleParser.cc
Executable file
@ -0,0 +1,76 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleParser.h"
|
||||
|
||||
namespace Compiler
|
||||
{
|
||||
|
||||
static ConsoleParser *gParserList = NULL;
|
||||
static ConsoleParser *gDefaultParser = NULL;
|
||||
|
||||
void freeConsoleParserList(void)
|
||||
{
|
||||
ConsoleParser *pParser;
|
||||
|
||||
while(pParser = gParserList)
|
||||
{
|
||||
gParserList = pParser->next;
|
||||
delete pParser;
|
||||
}
|
||||
|
||||
gDefaultParser = NULL;
|
||||
}
|
||||
|
||||
bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb, bool def /* = false */)
|
||||
{
|
||||
AssertFatal(ext && gcf && gcl && p && r, "AddConsoleParser called with one or more NULL arguments");
|
||||
|
||||
ConsoleParser *pParser;
|
||||
if(pParser = new ConsoleParser)
|
||||
{
|
||||
pParser->ext = ext;
|
||||
pParser->getCurrentFile = gcf;
|
||||
pParser->getCurrentLine = gcl;
|
||||
pParser->parse = p;
|
||||
pParser->restart = r;
|
||||
pParser->setScanBuffer = ssb;
|
||||
|
||||
if(def)
|
||||
gDefaultParser = pParser;
|
||||
|
||||
pParser->next = gParserList;
|
||||
gParserList = pParser;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ConsoleParser * getParserForFile(const char *filename)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
if(filename == NULL)
|
||||
return gDefaultParser;
|
||||
|
||||
if(ptr = dStrrchr((char *)filename, '.'))
|
||||
{
|
||||
ptr++;
|
||||
|
||||
ConsoleParser *p;
|
||||
for(p = gParserList;p;p = p->next)
|
||||
{
|
||||
if(dStricmp(ptr, p->ext) == 0)
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return gDefaultParser;
|
||||
}
|
||||
|
||||
} // end namespace Con
|
105
engine/console/consoleParser.h
Executable file
105
engine/console/consoleParser.h
Executable file
@ -0,0 +1,105 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//
|
||||
// TorqueBASIC
|
||||
// (c) Copyright 2004 Burnt Wasp
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _CONSOLE_PARSER_H_
|
||||
#define _CONSOLE_PARSER_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
namespace Compiler
|
||||
{
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Function for GetCurrentFile from the lexer
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
typedef const char *(*fnGetCurrentFile)();
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Function for GetCurrentLine from the lexer
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
typedef S32 (*fnGetCurrentLine)();
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Function for Parse from the lexer
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
typedef S32 (*fnParse)();
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Function for Restart from the lexer
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
typedef void (*fnRestart)(FILE *input_file);
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Function for SetScanBuffer from the lexer
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
typedef void (*fnSetScanBuffer)(const char *sb, const char *fn);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief List of parsers for the compiler
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
struct ConsoleParser
|
||||
{
|
||||
struct ConsoleParser *next; //!< Next object in list or NULL
|
||||
|
||||
char *ext; //!< Filename extension handled by this parser
|
||||
|
||||
fnGetCurrentFile getCurrentFile; //!< GetCurrentFile lexer function
|
||||
fnGetCurrentLine getCurrentLine; //!< GetCurrentLine lexer function
|
||||
fnParse parse; //!< Parse lexer function
|
||||
fnRestart restart; //!< Restart lexer function
|
||||
fnSetScanBuffer setScanBuffer; //!< SetScanBuffer lexer function
|
||||
};
|
||||
|
||||
// Macros
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Declare a parser's function prototypes
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
#define CON_DECLARE_PARSER(prefix) \
|
||||
const char * prefix##GetCurrentFile(); \
|
||||
S32 prefix##GetCurrentLine(); \
|
||||
void prefix##SetScanBuffer(const char *sb, const char *fn); \
|
||||
S32 prefix##parse(); \
|
||||
void prefix##restart(FILE *input_file)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Helper macro to add console parsers
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
#define CON_ADD_PARSER(prefix, ext, def) \
|
||||
Compiler::addConsoleParser(ext, prefix##GetCurrentFile, prefix##GetCurrentLine, prefix##parse, \
|
||||
prefix##restart, prefix##SetScanBuffer, def)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Free the console parser list
|
||||
///
|
||||
/// \sa AddConsoleParser()
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void freeConsoleParserList(void);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Add a console parser to the list
|
||||
///
|
||||
/// \param ext Filename extension
|
||||
/// \param gcf GetCurrentFile function
|
||||
/// \param gcl GetCurrentLine function
|
||||
/// \param p Parse function
|
||||
/// \param r Restart function
|
||||
/// \param ssb SetScanBuffer function
|
||||
/// \param def true if this is the default parser (<b>Note:</b> set this only on the .cs parser!)
|
||||
/// \return true for success, false for failure (out of memory)
|
||||
/// \sa FreeConsoleParserList(), ConsoleParser
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb, bool def = false);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/// \brief Get the parser for a particular file based on its extension
|
||||
///
|
||||
/// \param filename Filename of file to obtain parser for
|
||||
/// \sa ConsoleParser
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleParser * getParserForFile(const char *filename);
|
||||
|
||||
} // end namespace Con
|
||||
|
||||
#endif // _CONSOLE_PARSER_H_
|
527
engine/console/consoleTypes.cc
Executable file
527
engine/console/consoleTypes.cc
Executable file
@ -0,0 +1,527 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "console/console.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/stringTable.h"
|
||||
#include "core/color.h"
|
||||
#include "console/simBase.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeString
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( string, TypeString, sizeof(const char*) )
|
||||
|
||||
ConsoleGetType( TypeString )
|
||||
{
|
||||
return *((const char **)(dptr));
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeString )
|
||||
{
|
||||
if(argc == 1)
|
||||
*((const char **) dptr) = StringTable->insert(argv[0]);
|
||||
else
|
||||
Con::printf("(TypeString) Cannot set multiple args to a single string.");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeCaseString
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( caseString, TypeCaseString, sizeof(const char*) )
|
||||
|
||||
ConsoleSetType( TypeCaseString )
|
||||
{
|
||||
if(argc == 1)
|
||||
*((const char **) dptr) = StringTable->insert(argv[0], true);
|
||||
else
|
||||
Con::printf("(TypeCaseString) Cannot set multiple args to a single string.");
|
||||
}
|
||||
|
||||
ConsoleGetType( TypeCaseString )
|
||||
{
|
||||
return *((const char **)(dptr));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeFileName
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsolePrepType( filename, TypeFilename, sizeof( const char* ) )
|
||||
|
||||
ConsoleSetType( TypeFilename )
|
||||
{
|
||||
if(argc == 1)
|
||||
{
|
||||
char buffer[1024];
|
||||
if (Con::expandScriptFilename(buffer, 1024, argv[0]))
|
||||
*((const char **) dptr) = StringTable->insert(buffer);
|
||||
else
|
||||
Con::warnf("(TypeFilename) illegal filename detected: %s", argv[0]);
|
||||
}
|
||||
else
|
||||
Con::printf("(TypeFilename) Cannot set multiple args to a single filename.");
|
||||
}
|
||||
|
||||
ConsoleGetType( TypeFilename )
|
||||
{
|
||||
return *((const char **)(dptr));
|
||||
}
|
||||
|
||||
ConsoleProcessData( TypeFilename )
|
||||
{
|
||||
if( Con::expandScriptFilename( buffer, bufferSz, data ) )
|
||||
return buffer;
|
||||
else
|
||||
{
|
||||
Con::warnf("(TypeFilename) illegal filename detected: %s", data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeS8
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( char, TypeS8, sizeof(U8) )
|
||||
|
||||
ConsoleGetType( TypeS8 )
|
||||
{
|
||||
char* returnBuffer = Con::getReturnBuffer(256);
|
||||
dSprintf(returnBuffer, 256, "%d", *((U8 *) dptr) );
|
||||
return returnBuffer;
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeS8 )
|
||||
{
|
||||
if(argc == 1)
|
||||
*((U8 *) dptr) = dAtoi(argv[0]);
|
||||
else
|
||||
Con::printf("(TypeU8) Cannot set multiple args to a single S32.");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeS32
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( int, TypeS32, sizeof(S32) )
|
||||
|
||||
ConsoleGetType( TypeS32 )
|
||||
{
|
||||
char* returnBuffer = Con::getReturnBuffer(256);
|
||||
dSprintf(returnBuffer, 256, "%d", *((S32 *) dptr) );
|
||||
return returnBuffer;
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeS32 )
|
||||
{
|
||||
if(argc == 1)
|
||||
*((S32 *) dptr) = dAtoi(argv[0]);
|
||||
else
|
||||
Con::printf("(TypeS32) Cannot set multiple args to a single S32.");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeS32Vector
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( intList, TypeS32Vector, sizeof(Vector<S32>) )
|
||||
|
||||
ConsoleGetType( TypeS32Vector )
|
||||
{
|
||||
Vector<S32> *vec = (Vector<S32> *)dptr;
|
||||
S32 buffSize = ( vec->size() * 15 ) + 16 ;
|
||||
char* returnBuffer = Con::getReturnBuffer( buffSize );
|
||||
S32 maxReturn = buffSize;
|
||||
returnBuffer[0] = '\0';
|
||||
S32 returnLeng = 0;
|
||||
for (Vector<S32>::iterator itr = vec->begin(); itr != vec->end(); itr++)
|
||||
{
|
||||
// concatenate the next value onto the return string
|
||||
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", *itr);
|
||||
// update the length of the return string (so far)
|
||||
returnLeng = dStrlen(returnBuffer);
|
||||
}
|
||||
// trim off that last extra space
|
||||
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
|
||||
returnBuffer[returnLeng - 1] = '\0';
|
||||
return returnBuffer;
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeS32Vector )
|
||||
{
|
||||
Vector<S32> *vec = (Vector<S32> *)dptr;
|
||||
// we assume the vector should be cleared first (not just appending)
|
||||
vec->clear();
|
||||
if(argc == 1)
|
||||
{
|
||||
const char *values = argv[0];
|
||||
const char *endValues = values + dStrlen(values);
|
||||
S32 value;
|
||||
// advance through the string, pulling off S32's and advancing the pointer
|
||||
while (values < endValues && dSscanf(values, "%d", &value) != 0)
|
||||
{
|
||||
vec->push_back(value);
|
||||
const char *nextValues = dStrchr(values, ' ');
|
||||
if (nextValues != 0 && nextValues < endValues)
|
||||
values = nextValues + 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (argc > 1)
|
||||
{
|
||||
for (S32 i = 0; i < argc; i++)
|
||||
vec->push_back(dAtoi(argv[i]));
|
||||
}
|
||||
else
|
||||
Con::printf("Vector<S32> must be set as { a, b, c, ... } or \"a b c ...\"");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeF32
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( float, TypeF32, sizeof(F32) )
|
||||
|
||||
ConsoleGetType( TypeF32 )
|
||||
{
|
||||
char* returnBuffer = Con::getReturnBuffer(256);
|
||||
dSprintf(returnBuffer, 256, "%g", *((F32 *) dptr) );
|
||||
return returnBuffer;
|
||||
}
|
||||
ConsoleSetType( TypeF32 )
|
||||
{
|
||||
if(argc == 1)
|
||||
*((F32 *) dptr) = dAtof(argv[0]);
|
||||
else
|
||||
Con::printf("(TypeF32) Cannot set multiple args to a single F32.");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeF32Vector
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( floatList, TypeF32Vector, sizeof(Vector<F32>) )
|
||||
|
||||
ConsoleGetType( TypeF32Vector )
|
||||
{
|
||||
Vector<F32> *vec = (Vector<F32> *)dptr;
|
||||
S32 buffSize = ( vec->size() * 15 ) + 16 ;
|
||||
char* returnBuffer = Con::getReturnBuffer( buffSize );
|
||||
S32 maxReturn = buffSize;
|
||||
returnBuffer[0] = '\0';
|
||||
S32 returnLeng = 0;
|
||||
for (Vector<F32>::iterator itr = vec->begin(); itr != vec->end(); itr++)
|
||||
{
|
||||
// concatenate the next value onto the return string
|
||||
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%g ", *itr);
|
||||
// update the length of the return string (so far)
|
||||
returnLeng = dStrlen(returnBuffer);
|
||||
}
|
||||
// trim off that last extra space
|
||||
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
|
||||
returnBuffer[returnLeng - 1] = '\0';
|
||||
return returnBuffer;
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeF32Vector )
|
||||
{
|
||||
Vector<F32> *vec = (Vector<F32> *)dptr;
|
||||
// we assume the vector should be cleared first (not just appending)
|
||||
vec->clear();
|
||||
if(argc == 1)
|
||||
{
|
||||
const char *values = argv[0];
|
||||
const char *endValues = values + dStrlen(values);
|
||||
F32 value;
|
||||
// advance through the string, pulling off F32's and advancing the pointer
|
||||
while (values < endValues && dSscanf(values, "%g", &value) != 0)
|
||||
{
|
||||
vec->push_back(value);
|
||||
const char *nextValues = dStrchr(values, ' ');
|
||||
if (nextValues != 0 && nextValues < endValues)
|
||||
values = nextValues + 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (argc > 1)
|
||||
{
|
||||
for (S32 i = 0; i < argc; i++)
|
||||
vec->push_back(dAtof(argv[i]));
|
||||
}
|
||||
else
|
||||
Con::printf("Vector<F32> must be set as { a, b, c, ... } or \"a b c ...\"");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeBool
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( bool, TypeBool, sizeof(bool) )
|
||||
|
||||
ConsoleGetType( TypeBool )
|
||||
{
|
||||
return *((bool *) dptr) ? "1" : "0";
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeBool )
|
||||
{
|
||||
if(argc == 1)
|
||||
*((bool *) dptr) = dAtob(argv[0]);
|
||||
else
|
||||
Con::printf("(TypeBool) Cannot set multiple args to a single bool.");
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeBoolVector
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( boolList, TypeBoolVector, sizeof(Vector<bool>) )
|
||||
|
||||
ConsoleGetType( TypeBoolVector )
|
||||
{
|
||||
Vector<bool> *vec = (Vector<bool>*)dptr;
|
||||
char* returnBuffer = Con::getReturnBuffer(1024);
|
||||
S32 maxReturn = 1024;
|
||||
returnBuffer[0] = '\0';
|
||||
S32 returnLeng = 0;
|
||||
for (Vector<bool>::iterator itr = vec->begin(); itr < vec->end(); itr++)
|
||||
{
|
||||
// concatenate the next value onto the return string
|
||||
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", (*itr == true ? 1 : 0));
|
||||
returnLeng = dStrlen(returnBuffer);
|
||||
}
|
||||
// trim off that last extra space
|
||||
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
|
||||
returnBuffer[returnLeng - 1] = '\0';
|
||||
return(returnBuffer);
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeBoolVector )
|
||||
{
|
||||
Vector<bool> *vec = (Vector<bool>*)dptr;
|
||||
// we assume the vector should be cleared first (not just appending)
|
||||
vec->clear();
|
||||
if (argc == 1)
|
||||
{
|
||||
const char *values = argv[0];
|
||||
const char *endValues = values + dStrlen(values);
|
||||
S32 value;
|
||||
// advance through the string, pulling off bool's and advancing the pointer
|
||||
while (values < endValues && dSscanf(values, "%d", &value) != 0)
|
||||
{
|
||||
vec->push_back(value == 0 ? false : true);
|
||||
const char *nextValues = dStrchr(values, ' ');
|
||||
if (nextValues != 0 && nextValues < endValues)
|
||||
values = nextValues + 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (argc > 1)
|
||||
{
|
||||
for (S32 i = 0; i < argc; i++)
|
||||
vec->push_back(dAtob(argv[i]));
|
||||
}
|
||||
else
|
||||
Con::printf("Vector<bool> must be set as { a, b, c, ... } or \"a b c ...\"");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeEnum
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( enumval, TypeEnum, sizeof(S32) )
|
||||
|
||||
ConsoleGetType( TypeEnum )
|
||||
{
|
||||
AssertFatal(tbl, "Null enum table passed to getDataTypeEnum()");
|
||||
S32 dptrVal = *(S32*)dptr;
|
||||
for (S32 i = 0; i < tbl->size; i++)
|
||||
{
|
||||
if (dptrVal == tbl->table[i].index)
|
||||
{
|
||||
return tbl->table[i].label;
|
||||
}
|
||||
}
|
||||
|
||||
//not found
|
||||
return "";
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeEnum )
|
||||
{
|
||||
AssertFatal(tbl, "Null enum table passed to setDataTypeEnum()");
|
||||
if (argc != 1) return;
|
||||
|
||||
S32 val = 0;
|
||||
for (S32 i = 0; i < tbl->size; i++)
|
||||
{
|
||||
if (! dStricmp(argv[0], tbl->table[i].label))
|
||||
{
|
||||
val = tbl->table[i].index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*((S32 *) dptr) = val;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeFlag
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( flag, TypeFlag, sizeof(S32) )
|
||||
|
||||
ConsoleGetType( TypeFlag )
|
||||
{
|
||||
BitSet32 tempFlags = *(BitSet32 *)dptr;
|
||||
if (tempFlags.test(flag)) return "true";
|
||||
else return "false";
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeFlag )
|
||||
{
|
||||
bool value = true;
|
||||
if (argc != 1)
|
||||
{
|
||||
Con::printf("flag must be true or false");
|
||||
}
|
||||
else
|
||||
{
|
||||
value = dAtob(argv[0]);
|
||||
}
|
||||
((BitSet32 *)dptr)->set(flag, value);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeColorF
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( ColorF, TypeColorF, sizeof(ColorF) )
|
||||
|
||||
ConsoleGetType( TypeColorF )
|
||||
{
|
||||
ColorF * color = (ColorF*)dptr;
|
||||
char* returnBuffer = Con::getReturnBuffer(256);
|
||||
dSprintf(returnBuffer, 256, "%g %g %g %g", color->red, color->green, color->blue, color->alpha);
|
||||
return(returnBuffer);
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeColorF )
|
||||
{
|
||||
ColorF *tmpColor = (ColorF *) dptr;
|
||||
if(argc == 1)
|
||||
{
|
||||
tmpColor->set(0, 0, 0, 1);
|
||||
F32 r,g,b,a;
|
||||
S32 args = dSscanf(argv[0], "%g %g %g %g", &r, &g, &b, &a);
|
||||
tmpColor->red = r;
|
||||
tmpColor->green = g;
|
||||
tmpColor->blue = b;
|
||||
if (args == 4)
|
||||
tmpColor->alpha = a;
|
||||
}
|
||||
else if(argc == 3)
|
||||
{
|
||||
tmpColor->red = dAtof(argv[0]);
|
||||
tmpColor->green = dAtof(argv[1]);
|
||||
tmpColor->blue = dAtof(argv[2]);
|
||||
tmpColor->alpha = 1.f;
|
||||
}
|
||||
else if(argc == 4)
|
||||
{
|
||||
tmpColor->red = dAtof(argv[0]);
|
||||
tmpColor->green = dAtof(argv[1]);
|
||||
tmpColor->blue = dAtof(argv[2]);
|
||||
tmpColor->alpha = dAtof(argv[3]);
|
||||
}
|
||||
else
|
||||
Con::printf("Color must be set as { r, g, b [,a] }");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeColorI
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( ColorI, TypeColorI, sizeof(ColorI) )
|
||||
|
||||
ConsoleGetType( TypeColorI )
|
||||
{
|
||||
ColorI *color = (ColorI *) dptr;
|
||||
char* returnBuffer = Con::getReturnBuffer(256);
|
||||
dSprintf(returnBuffer, 256, "%d %d %d %d", color->red, color->green, color->blue, color->alpha);
|
||||
return returnBuffer;
|
||||
}
|
||||
|
||||
ConsoleSetType( TypeColorI )
|
||||
{
|
||||
ColorI *tmpColor = (ColorI *) dptr;
|
||||
if(argc == 1)
|
||||
{
|
||||
tmpColor->set(0, 0, 0, 255);
|
||||
S32 r,g,b,a;
|
||||
S32 args = dSscanf(argv[0], "%d %d %d %d", &r, &g, &b, &a);
|
||||
tmpColor->red = r;
|
||||
tmpColor->green = g;
|
||||
tmpColor->blue = b;
|
||||
if (args == 4)
|
||||
tmpColor->alpha = a;
|
||||
}
|
||||
else if(argc == 3)
|
||||
{
|
||||
tmpColor->red = dAtoi(argv[0]);
|
||||
tmpColor->green = dAtoi(argv[1]);
|
||||
tmpColor->blue = dAtoi(argv[2]);
|
||||
tmpColor->alpha = 255;
|
||||
}
|
||||
else if(argc == 4)
|
||||
{
|
||||
tmpColor->red = dAtoi(argv[0]);
|
||||
tmpColor->green = dAtoi(argv[1]);
|
||||
tmpColor->blue = dAtoi(argv[2]);
|
||||
tmpColor->alpha = dAtoi(argv[3]);
|
||||
}
|
||||
else
|
||||
Con::printf("Color must be set as { r, g, b [,a] }");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeSimObjectPtr
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( SimObjectPtr, TypeSimObjectPtr, sizeof(SimObject*) )
|
||||
|
||||
ConsoleSetType( TypeSimObjectPtr )
|
||||
{
|
||||
if(argc == 1)
|
||||
{
|
||||
SimObject **obj = (SimObject **)dptr;
|
||||
*obj = Sim::findObject(argv[0]);
|
||||
}
|
||||
else
|
||||
Con::printf("(TypeSimObjectPtr) Cannot set multiple args to a single S32.");
|
||||
}
|
||||
|
||||
ConsoleGetType( TypeSimObjectPtr )
|
||||
{
|
||||
SimObject **obj = (SimObject**)dptr;
|
||||
char* returnBuffer = Con::getReturnBuffer(256);
|
||||
dSprintf(returnBuffer, 256, "%s", *obj ? (*obj)->getName() ? (*obj)->getName() : (*obj)->getIdString() : "");
|
||||
return returnBuffer;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TypeSimObjectName
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ConsoleType( SimObjectPtr, TypeSimObjectName, sizeof(SimObject*) )
|
||||
|
||||
ConsoleSetType( TypeSimObjectName )
|
||||
{
|
||||
if(argc == 1)
|
||||
{
|
||||
SimObject **obj = (SimObject **)dptr;
|
||||
*obj = Sim::findObject(argv[0]);
|
||||
}
|
||||
else
|
||||
Con::printf("(TypeSimObjectName) Cannot set multiple args to a single S32.");
|
||||
}
|
||||
|
||||
ConsoleGetType( TypeSimObjectName )
|
||||
{
|
||||
SimObject **obj = (SimObject**)dptr;
|
||||
char* returnBuffer = Con::getReturnBuffer(128);
|
||||
dSprintf(returnBuffer, 128, "%s", *obj && (*obj)->getName() ? (*obj)->getName() : "");
|
||||
return returnBuffer;
|
||||
}
|
39
engine/console/consoleTypes.h
Executable file
39
engine/console/consoleTypes.h
Executable file
@ -0,0 +1,39 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _CONSOLETYPES_H_
|
||||
#define _CONSOLETYPES_H_
|
||||
|
||||
#ifndef _DYNAMIC_CONSOLETYPES_H_
|
||||
#include "console/dynamicTypes.h"
|
||||
#endif
|
||||
|
||||
#ifndef Offset
|
||||
#if defined(TORQUE_COMPILER_GCC) && (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
|
||||
#define Offset(m,T) ((int)(&((T *)1)->m) - 1)
|
||||
#else
|
||||
#define Offset(x, cls) ((dsize_t)((const char *)&(((cls *)0)->x)-(const char *)0))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Define Core Console Types
|
||||
DefineConsoleType( TypeF32 )
|
||||
DefineConsoleType( TypeS8 )
|
||||
DefineConsoleType( TypeS32 )
|
||||
DefineConsoleType( TypeS32Vector )
|
||||
DefineConsoleType( TypeBool )
|
||||
DefineConsoleType( TypeBoolVector )
|
||||
DefineConsoleType( TypeF32Vector )
|
||||
DefineConsoleType( TypeString )
|
||||
DefineConsoleType( TypeCaseString )
|
||||
DefineConsoleType( TypeFilename )
|
||||
DefineConsoleType( TypeEnum )
|
||||
DefineConsoleType( TypeFlag )
|
||||
DefineConsoleType( TypeColorI )
|
||||
DefineConsoleType( TypeColorF )
|
||||
DefineConsoleType( TypeSimObjectPtr )
|
||||
DefineConsoleType( TypeSimObjectName )
|
||||
|
||||
#endif
|
72
engine/console/dynamicTypes.cc
Executable file
72
engine/console/dynamicTypes.cc
Executable file
@ -0,0 +1,72 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "console/dynamicTypes.h"
|
||||
|
||||
// Init the globals.
|
||||
ConsoleBaseType *ConsoleBaseType::smListHead = NULL;
|
||||
S32 ConsoleBaseType::smConsoleTypeCount = 0;
|
||||
|
||||
// And, we also privately store the types lookup table.
|
||||
VectorPtr<ConsoleBaseType*> gConsoleTypeTable;
|
||||
|
||||
ConsoleBaseType *ConsoleBaseType::getListHead()
|
||||
{
|
||||
return smListHead;
|
||||
}
|
||||
|
||||
void ConsoleBaseType::initialize()
|
||||
{
|
||||
// Prep and empty the vector.
|
||||
gConsoleTypeTable.setSize(smConsoleTypeCount+1);
|
||||
dMemset(gConsoleTypeTable.address(), 0, sizeof(ConsoleBaseType*) * gConsoleTypeTable.size());
|
||||
|
||||
// Walk the list and register each one with the console system.
|
||||
ConsoleBaseType *walk = getListHead();
|
||||
while(walk)
|
||||
{
|
||||
// Store a pointer to the type in the appropriate slot.
|
||||
const S32 id = walk->getTypeID();
|
||||
AssertFatal(gConsoleTypeTable[id]==NULL, "ConsoleBaseType::initialize - encountered a table slot that contained something!");
|
||||
gConsoleTypeTable[id] = walk;
|
||||
|
||||
// Advance down the list...
|
||||
walk = walk->getListNext();
|
||||
}
|
||||
|
||||
// Alright, we're all done here; we can now achieve fast lookups by ID.
|
||||
}
|
||||
|
||||
ConsoleBaseType *ConsoleBaseType::getType(const S32 typeID)
|
||||
{
|
||||
return gConsoleTypeTable[typeID];
|
||||
}
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
ConsoleBaseType::ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName)
|
||||
{
|
||||
// General initialization.
|
||||
mInspectorFieldType = NULL;
|
||||
|
||||
// Store general info.
|
||||
mTypeSize = size;
|
||||
mTypeName = aTypeName;
|
||||
|
||||
// Get our type ID and store it.
|
||||
mTypeID = smConsoleTypeCount++;
|
||||
*idPtr = mTypeID;
|
||||
|
||||
// Link ourselves into the list.
|
||||
mListNext = smListHead;
|
||||
smListHead = this;
|
||||
|
||||
// Alright, all done for now. Console initialization time code
|
||||
// takes us from having a list of general info and classes to
|
||||
// a fully initialized type table.
|
||||
}
|
||||
|
||||
ConsoleBaseType::~ConsoleBaseType()
|
||||
{
|
||||
// Nothing to do for now; we could unlink ourselves from the list, but why?
|
||||
}
|
123
engine/console/dynamicTypes.h
Executable file
123
engine/console/dynamicTypes.h
Executable file
@ -0,0 +1,123 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef _DYNAMIC_CONSOLETYPES_H_
|
||||
#define _DYNAMIC_CONSOLETYPES_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
|
||||
class ConsoleBaseType
|
||||
{
|
||||
protected:
|
||||
/// This is used to generate unique IDs for each type.
|
||||
static S32 smConsoleTypeCount;
|
||||
|
||||
/// We maintain a linked list of all console types; this is its head.
|
||||
static ConsoleBaseType *smListHead;
|
||||
|
||||
/// Next item in the list of all console types.
|
||||
ConsoleBaseType *mListNext;
|
||||
|
||||
/// Destructor is private to avoid people mucking up the list.
|
||||
~ConsoleBaseType();
|
||||
|
||||
S32 mTypeID;
|
||||
dsize_t mTypeSize;
|
||||
const char *mTypeName;
|
||||
const char *mInspectorFieldType;
|
||||
|
||||
public:
|
||||
|
||||
/// @name cbt_list List Interface
|
||||
///
|
||||
/// Interface for accessing/traversing the list of types.
|
||||
|
||||
/// Get the head of the list.
|
||||
static ConsoleBaseType *getListHead();
|
||||
|
||||
/// Get the item that follows this item in the list.
|
||||
ConsoleBaseType *getListNext() const
|
||||
{
|
||||
return mListNext;
|
||||
}
|
||||
|
||||
/// Called once to initialize the console type system.
|
||||
static void initialize();
|
||||
|
||||
/// Call me to get a pointer to a type's info.
|
||||
static ConsoleBaseType *getType(const S32 typeID);
|
||||
|
||||
/// @}
|
||||
|
||||
/// The constructor is responsible for linking an element into the
|
||||
/// master list, registering the type ID, etc.
|
||||
ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName);
|
||||
|
||||
const S32 getTypeID() const { return mTypeID; }
|
||||
const S32 getTypeSize() const { return mTypeSize; }
|
||||
const char *getTypeName() const { return mTypeName; }
|
||||
|
||||
void setInspectorFieldType(const char *type) { mInspectorFieldType = type; }
|
||||
const char *getInspectorFieldType() { return mInspectorFieldType; }
|
||||
|
||||
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag)=0;
|
||||
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag )=0;
|
||||
virtual const char *getTypeClassName()=0;
|
||||
virtual const bool isDatablock() { return false; };
|
||||
virtual const char *prepData(const char *data, char *buffer, U32 bufferLen) { return data; };
|
||||
};
|
||||
|
||||
#define DefineConsoleType( type ) extern S32 type;
|
||||
|
||||
#define ConsoleType( typeName, type, size ) \
|
||||
class ConsoleType##type : public ConsoleBaseType \
|
||||
{ \
|
||||
public: \
|
||||
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
|
||||
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
|
||||
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
|
||||
virtual const char *getTypeClassName() { return #typeName ; } \
|
||||
}; \
|
||||
S32 type = -1; \
|
||||
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
|
||||
|
||||
#define ConsolePrepType( typeName, type, size ) \
|
||||
class ConsoleType##type : public ConsoleBaseType \
|
||||
{ \
|
||||
public: \
|
||||
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
|
||||
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
|
||||
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
|
||||
virtual const char *getTypeClassName() { return #typeName; }; \
|
||||
virtual const char *prepData(const char *data, char *buffer, U32 bufferLen); \
|
||||
}; \
|
||||
S32 type = -1; \
|
||||
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
|
||||
|
||||
#define ConsoleSetType( type ) \
|
||||
void ConsoleType##type::setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag)
|
||||
|
||||
#define ConsoleGetType( type ) \
|
||||
const char *ConsoleType##type::getData(void *dptr, EnumTable *tbl, BitSet32 flag)
|
||||
|
||||
#define ConsoleProcessData( type ) \
|
||||
const char *ConsoleType##type::prepData(const char *data, char *buffer, U32 bufferSz)
|
||||
|
||||
#define DatablockConsoleType( typeName, type, size, className ) \
|
||||
class ConsoleType##type : public ConsoleBaseType \
|
||||
{ \
|
||||
public: \
|
||||
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
|
||||
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
|
||||
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
|
||||
virtual const char *getTypeClassName() { return #className; }; \
|
||||
virtual const bool isDatablock() { return true; }; \
|
||||
}; \
|
||||
S32 type = -1; \
|
||||
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
|
||||
|
||||
|
||||
#endif
|
299
engine/console/scriptObject.cc
Executable file
299
engine/console/scriptObject.cc
Executable file
@ -0,0 +1,299 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/simBase.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Script object placeholder
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class ScriptObject : public SimObject
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
StringTableEntry mClassName;
|
||||
StringTableEntry mSuperClassName;
|
||||
public:
|
||||
ScriptObject();
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
|
||||
DECLARE_CONOBJECT(ScriptObject);
|
||||
|
||||
static void initPersistFields();
|
||||
};
|
||||
|
||||
IMPLEMENT_CONOBJECT(ScriptObject);
|
||||
|
||||
void ScriptObject::initPersistFields()
|
||||
{
|
||||
addGroup("Classes", "Script objects have the ability to inherit and have class information.");
|
||||
addField("class", TypeString, Offset(mClassName, ScriptObject), "Class of object.");
|
||||
addField("superClass", TypeString, Offset(mSuperClassName, ScriptObject), "Superclass of object.");
|
||||
endGroup("Classes");
|
||||
}
|
||||
|
||||
ScriptObject::ScriptObject()
|
||||
{
|
||||
mClassName = "";
|
||||
mSuperClassName = "";
|
||||
}
|
||||
|
||||
bool ScriptObject::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
// it's possible that all the namespace links can fail, if
|
||||
// multiple objects are named the same thing with different script
|
||||
// hierarchies.
|
||||
// linkNamespaces will now return false and echo an error message
|
||||
// rather than asserting.
|
||||
|
||||
// superClassName -> ScriptObject
|
||||
StringTableEntry parent = StringTable->insert("ScriptObject");
|
||||
if(mSuperClassName[0])
|
||||
{
|
||||
if(Con::linkNamespaces(parent, mSuperClassName))
|
||||
parent = mSuperClassName;
|
||||
}
|
||||
|
||||
// className -> superClassName
|
||||
if (mClassName[0])
|
||||
{
|
||||
if(Con::linkNamespaces(parent, mClassName))
|
||||
parent = mClassName;
|
||||
}
|
||||
|
||||
// objectName -> className
|
||||
StringTableEntry objectName = getName();
|
||||
if (objectName && objectName[0])
|
||||
{
|
||||
if(Con::linkNamespaces(parent, objectName))
|
||||
parent = objectName;
|
||||
}
|
||||
|
||||
// Store our namespace
|
||||
mNameSpace = Con::lookupNamespace(parent);
|
||||
|
||||
// Call onAdd in script!
|
||||
Con::executef(this, 2, "onAdd", Con::getIntArg(getId()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScriptObject::onRemove()
|
||||
{
|
||||
// We call this on this objects namespace so we unlink them after. - jdd
|
||||
//
|
||||
// Call onRemove in script!
|
||||
Con::executef(this, 2, "onRemove", Con::getIntArg(getId()));
|
||||
|
||||
// Restore NameSpace's
|
||||
StringTableEntry child = getName();
|
||||
if( child && child[0] )
|
||||
{
|
||||
if(mClassName && mClassName[0])
|
||||
{
|
||||
if(Con::unlinkNamespaces(mClassName, child))
|
||||
child = mClassName;
|
||||
}
|
||||
|
||||
if(mSuperClassName && mSuperClassName[0])
|
||||
{
|
||||
if(Con::unlinkNamespaces(mSuperClassName, child))
|
||||
child = mSuperClassName;
|
||||
}
|
||||
|
||||
Con::unlinkNamespaces(getClassName(), child);
|
||||
}
|
||||
else
|
||||
{
|
||||
child = mClassName;
|
||||
if(child && child[0])
|
||||
{
|
||||
if(mSuperClassName && mSuperClassName[0])
|
||||
{
|
||||
if(Con::unlinkNamespaces(mSuperClassName, child))
|
||||
child = mSuperClassName;
|
||||
}
|
||||
|
||||
Con::unlinkNamespaces(getClassName(), child);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mSuperClassName && mSuperClassName[0])
|
||||
Con::unlinkNamespaces(getClassName(), mSuperClassName);
|
||||
}
|
||||
}
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Script group placeholder
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class ScriptGroup : public SimGroup
|
||||
{
|
||||
typedef SimGroup Parent;
|
||||
StringTableEntry mClassName;
|
||||
StringTableEntry mSuperClassName;
|
||||
public:
|
||||
ScriptGroup();
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
|
||||
DECLARE_CONOBJECT(ScriptGroup);
|
||||
|
||||
static void initPersistFields();
|
||||
};
|
||||
|
||||
IMPLEMENT_CONOBJECT(ScriptGroup);
|
||||
|
||||
void ScriptGroup::initPersistFields()
|
||||
{
|
||||
addGroup("Classes");
|
||||
addField("class", TypeString, Offset(mClassName, ScriptGroup));
|
||||
addField("superClass", TypeString, Offset(mSuperClassName, ScriptGroup));
|
||||
endGroup("Classes");
|
||||
}
|
||||
|
||||
ScriptGroup::ScriptGroup()
|
||||
{
|
||||
mClassName = "";
|
||||
mSuperClassName = "";
|
||||
}
|
||||
|
||||
bool ScriptGroup::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
// superClassName -> ScriptGroup
|
||||
StringTableEntry parent = StringTable->insert("ScriptGroup");
|
||||
if(mSuperClassName[0])
|
||||
{
|
||||
if(Con::linkNamespaces(parent, mSuperClassName))
|
||||
parent = mSuperClassName;
|
||||
}
|
||||
|
||||
// className -> superClassName
|
||||
if(mClassName[0])
|
||||
{
|
||||
if(Con::linkNamespaces(parent, mClassName))
|
||||
parent = mClassName;
|
||||
}
|
||||
|
||||
// objectName -> className
|
||||
StringTableEntry objectName = getName();
|
||||
if (objectName && objectName[0])
|
||||
{
|
||||
if(Con::linkNamespaces(parent, objectName))
|
||||
parent = objectName;
|
||||
}
|
||||
|
||||
// Store our namespace
|
||||
mNameSpace = Con::lookupNamespace(parent);
|
||||
|
||||
// Call onAdd in script!
|
||||
Con::executef(this, 2, "onAdd", Con::getIntArg(getId()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScriptGroup::onRemove()
|
||||
{
|
||||
// Call onRemove in script!
|
||||
Con::executef(this, 2, "onRemove", Con::getIntArg(getId()));
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Script Class placeholder
|
||||
//-----------------------------------------------------------------------------
|
||||
class ScriptClass : public SimObject
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
StringTableEntry mLibraryName;
|
||||
StringTableEntry mClassName;
|
||||
public:
|
||||
ScriptClass();
|
||||
|
||||
DECLARE_CONOBJECT(ScriptClass);
|
||||
|
||||
bool onAdd();
|
||||
|
||||
static void initPersistFields();
|
||||
};
|
||||
|
||||
IMPLEMENT_CONOBJECT(ScriptClass);
|
||||
|
||||
void ScriptClass::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
|
||||
addGroup("ClassLibrary", "Script objects have the ability to inherit and have class information.");
|
||||
addField("libraryName", TypeString, Offset(mLibraryName, ScriptClass), "Class Library This Belongs to.");
|
||||
addField("className", TypeString, Offset(mClassName, ScriptClass), "Script Class Namespace this object defines.");
|
||||
endGroup("ClassLibrary");
|
||||
|
||||
}
|
||||
|
||||
ScriptClass::ScriptClass()
|
||||
{
|
||||
mLibraryName = "";
|
||||
mClassName = "";
|
||||
}
|
||||
|
||||
|
||||
bool ScriptClass::onAdd()
|
||||
{
|
||||
SimGroup *scriptClassGroup = Sim::getScriptClassGroup();
|
||||
if( !scriptClassGroup )
|
||||
{
|
||||
Con::errorf("ScriptClass::onAdd - No ScriptClassGroup found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
// No library goes in root.
|
||||
if( !mLibraryName || mLibraryName == "" )
|
||||
{
|
||||
scriptClassGroup->addObject( this );
|
||||
return true;
|
||||
}
|
||||
|
||||
SimGroup *libraryGroup = dynamic_cast<SimGroup*>( scriptClassGroup->findObjectByInternalName( mLibraryName ) );
|
||||
if( libraryGroup != NULL )
|
||||
{
|
||||
libraryGroup->addObject( this );
|
||||
return true;
|
||||
}
|
||||
|
||||
libraryGroup = new SimGroup();
|
||||
if ( libraryGroup == NULL )
|
||||
{
|
||||
Con::errorf("ScriptClass::onAdd - Unable to create non-existent Script ClassLibrary %s!", mLibraryName );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register the Script ClassLibrary SimGroup
|
||||
libraryGroup->registerObject();
|
||||
|
||||
// Set Internal Name
|
||||
libraryGroup->setInternalName( StringTable->insert( mLibraryName ) );
|
||||
|
||||
// Add to ScriptClassGroup SimGroup
|
||||
scriptClassGroup->addObject( libraryGroup );
|
||||
|
||||
// Add ourselves to our new Script ClassLibrary
|
||||
libraryGroup->addObject( this );
|
||||
|
||||
// Success!
|
||||
return true;
|
||||
|
||||
}
|
2033
engine/console/simBase.cc
Executable file
2033
engine/console/simBase.cc
Executable file
File diff suppressed because it is too large
Load Diff
1406
engine/console/simBase.h
Executable file
1406
engine/console/simBase.h
Executable file
File diff suppressed because it is too large
Load Diff
301
engine/console/simDictionary.cc
Executable file
301
engine/console/simDictionary.cc
Executable file
@ -0,0 +1,301 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "console/simDictionary.h"
|
||||
#include "console/simBase.h"
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------
|
||||
extern S32 HashPointer(StringTableEntry e);
|
||||
|
||||
SimNameDictionary::SimNameDictionary()
|
||||
{
|
||||
hashTable = NULL;
|
||||
mutex = Mutex::createMutex();
|
||||
}
|
||||
|
||||
SimNameDictionary::~SimNameDictionary()
|
||||
{
|
||||
delete[] hashTable;
|
||||
Mutex::destroyMutex(mutex);
|
||||
}
|
||||
|
||||
void SimNameDictionary::insert(SimObject* obj)
|
||||
{
|
||||
if(!obj->objectName)
|
||||
return;
|
||||
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
if(!hashTable)
|
||||
{
|
||||
hashTable = new SimObject *[DefaultTableSize];
|
||||
hashTableSize = DefaultTableSize;
|
||||
hashEntryCount = 0;
|
||||
S32 i;
|
||||
for(i = 0; i < hashTableSize; i++)
|
||||
hashTable[i] = NULL;
|
||||
}
|
||||
S32 idx = HashPointer(obj->objectName) % hashTableSize;
|
||||
obj->nextNameObject = hashTable[idx];
|
||||
hashTable[idx] = obj;
|
||||
hashEntryCount++;
|
||||
if(hashEntryCount > hashTableSize)
|
||||
{
|
||||
// resize the hash table
|
||||
S32 i;
|
||||
SimObject *head = NULL, *walk, *temp;
|
||||
for(i = 0; i < hashTableSize; i++) {
|
||||
walk = hashTable[i];
|
||||
while(walk)
|
||||
{
|
||||
temp = walk->nextNameObject;
|
||||
walk->nextNameObject = head;
|
||||
head = walk;
|
||||
walk = temp;
|
||||
}
|
||||
}
|
||||
delete[] hashTable;
|
||||
hashTableSize = hashTableSize * 2 + 1;
|
||||
hashTable = new SimObject *[hashTableSize];
|
||||
|
||||
for(i = 0; i < hashTableSize;i++)
|
||||
hashTable[i] = NULL;
|
||||
while(head)
|
||||
{
|
||||
temp = head->nextNameObject;
|
||||
idx = HashPointer(head->objectName) % hashTableSize;
|
||||
head->nextNameObject = hashTable[idx];
|
||||
hashTable[idx] = head;
|
||||
head = temp;
|
||||
}
|
||||
}
|
||||
Mutex::unlockMutex(mutex);
|
||||
}
|
||||
|
||||
SimObject* SimNameDictionary::find(StringTableEntry name)
|
||||
{
|
||||
// NULL is a valid lookup - it will always return NULL
|
||||
if(!hashTable)
|
||||
return NULL;
|
||||
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
S32 idx = HashPointer(name) % hashTableSize;
|
||||
SimObject *walk = hashTable[idx];
|
||||
while(walk)
|
||||
{
|
||||
if(walk->objectName == name)
|
||||
{
|
||||
Mutex::unlockMutex(mutex);
|
||||
return walk;
|
||||
}
|
||||
walk = walk->nextNameObject;
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void SimNameDictionary::remove(SimObject* obj)
|
||||
{
|
||||
if(!obj->objectName)
|
||||
return;
|
||||
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize];
|
||||
while(*walk)
|
||||
{
|
||||
if(*walk == obj)
|
||||
{
|
||||
*walk = obj->nextNameObject;
|
||||
obj->nextNameObject = (SimObject*)-1;
|
||||
hashEntryCount--;
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
return;
|
||||
}
|
||||
walk = &((*walk)->nextNameObject);
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
SimManagerNameDictionary::SimManagerNameDictionary()
|
||||
{
|
||||
hashTable = new SimObject *[DefaultTableSize];
|
||||
hashTableSize = DefaultTableSize;
|
||||
hashEntryCount = 0;
|
||||
S32 i;
|
||||
for(i = 0; i < hashTableSize; i++)
|
||||
hashTable[i] = NULL;
|
||||
mutex = Mutex::createMutex();
|
||||
}
|
||||
|
||||
SimManagerNameDictionary::~SimManagerNameDictionary()
|
||||
{
|
||||
delete[] hashTable;
|
||||
Mutex::destroyMutex(mutex);
|
||||
}
|
||||
|
||||
void SimManagerNameDictionary::insert(SimObject* obj)
|
||||
{
|
||||
if(!obj->objectName)
|
||||
return;
|
||||
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
S32 idx = HashPointer(obj->objectName) % hashTableSize;
|
||||
obj->nextManagerNameObject = hashTable[idx];
|
||||
hashTable[idx] = obj;
|
||||
hashEntryCount++;
|
||||
if(hashEntryCount > hashTableSize)
|
||||
{
|
||||
// resize the hash table
|
||||
S32 i;
|
||||
SimObject *head = NULL, *walk, *temp;
|
||||
for(i = 0; i < hashTableSize; i++) {
|
||||
walk = hashTable[i];
|
||||
while(walk)
|
||||
{
|
||||
temp = walk->nextManagerNameObject;
|
||||
walk->nextManagerNameObject = head;
|
||||
head = walk;
|
||||
walk = temp;
|
||||
}
|
||||
}
|
||||
delete[] hashTable;
|
||||
hashTableSize = hashTableSize * 2 + 1;
|
||||
hashTable = new SimObject *[hashTableSize];
|
||||
|
||||
for(i = 0; i < hashTableSize;i++)
|
||||
hashTable[i] = NULL;
|
||||
while(head)
|
||||
{
|
||||
temp = head->nextManagerNameObject;
|
||||
idx = HashPointer(head->objectName) % hashTableSize;
|
||||
head->nextManagerNameObject = hashTable[idx];
|
||||
hashTable[idx] = head;
|
||||
head = temp;
|
||||
}
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
}
|
||||
|
||||
SimObject* SimManagerNameDictionary::find(StringTableEntry name)
|
||||
{
|
||||
// NULL is a valid lookup - it will always return NULL
|
||||
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
S32 idx = HashPointer(name) % hashTableSize;
|
||||
SimObject *walk = hashTable[idx];
|
||||
while(walk)
|
||||
{
|
||||
if(walk->objectName == name)
|
||||
{
|
||||
Mutex::unlockMutex(mutex);
|
||||
return walk;
|
||||
}
|
||||
walk = walk->nextManagerNameObject;
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void SimManagerNameDictionary::remove(SimObject* obj)
|
||||
{
|
||||
if(!obj->objectName)
|
||||
return;
|
||||
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize];
|
||||
while(*walk)
|
||||
{
|
||||
if(*walk == obj)
|
||||
{
|
||||
*walk = obj->nextManagerNameObject;
|
||||
obj->nextManagerNameObject = (SimObject*)-1;
|
||||
hashEntryCount--;
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
return;
|
||||
}
|
||||
walk = &((*walk)->nextManagerNameObject);
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
SimIdDictionary::SimIdDictionary()
|
||||
{
|
||||
for(S32 i = 0; i < DefaultTableSize; i++)
|
||||
table[i] = NULL;
|
||||
mutex = Mutex::createMutex();
|
||||
}
|
||||
|
||||
SimIdDictionary::~SimIdDictionary()
|
||||
{
|
||||
Mutex::destroyMutex(mutex);
|
||||
}
|
||||
|
||||
void SimIdDictionary::insert(SimObject* obj)
|
||||
{
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
S32 idx = obj->getId() & TableBitMask;
|
||||
obj->nextIdObject = table[idx];
|
||||
AssertFatal( obj->nextIdObject != obj, "SimIdDictionary::insert - Creating Infinite Loop linking to self!" );
|
||||
table[idx] = obj;
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
}
|
||||
|
||||
SimObject* SimIdDictionary::find(S32 id)
|
||||
{
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
S32 idx = id & TableBitMask;
|
||||
SimObject *walk = table[idx];
|
||||
while(walk)
|
||||
{
|
||||
if(walk->getId() == U32(id))
|
||||
{
|
||||
Mutex::unlockMutex(mutex);
|
||||
return walk;
|
||||
}
|
||||
walk = walk->nextIdObject;
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void SimIdDictionary::remove(SimObject* obj)
|
||||
{
|
||||
Mutex::lockMutex(mutex);
|
||||
|
||||
SimObject **walk = &table[obj->getId() & TableBitMask];
|
||||
while(*walk && *walk != obj)
|
||||
walk = &((*walk)->nextIdObject);
|
||||
if(*walk)
|
||||
*walk = obj->nextIdObject;
|
||||
|
||||
Mutex::unlockMutex(mutex);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------
|
||||
|
95
engine/console/simDictionary.h
Executable file
95
engine/console/simDictionary.h
Executable file
@ -0,0 +1,95 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _SIMDICTIONARY_H_
|
||||
#define _SIMDICTIONARY_H_
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _STRINGTABLE_H_
|
||||
#include "core/stringTable.h"
|
||||
#endif
|
||||
|
||||
#ifndef _PLATFORMMUTEX_H_
|
||||
#include "platform/platformMutex.h"
|
||||
#endif
|
||||
|
||||
class SimObject;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Map of names to SimObjects
|
||||
///
|
||||
/// Provides fast lookup for name->object and
|
||||
/// for fast removal of an object given object*
|
||||
class SimNameDictionary
|
||||
{
|
||||
enum
|
||||
{
|
||||
DefaultTableSize = 29
|
||||
};
|
||||
|
||||
SimObject **hashTable; // hash the pointers of the names...
|
||||
S32 hashTableSize;
|
||||
S32 hashEntryCount;
|
||||
|
||||
void *mutex;
|
||||
|
||||
public:
|
||||
void insert(SimObject* obj);
|
||||
void remove(SimObject* obj);
|
||||
SimObject* find(StringTableEntry name);
|
||||
|
||||
SimNameDictionary();
|
||||
~SimNameDictionary();
|
||||
};
|
||||
|
||||
class SimManagerNameDictionary
|
||||
{
|
||||
enum
|
||||
{
|
||||
DefaultTableSize = 29
|
||||
};
|
||||
|
||||
SimObject **hashTable; // hash the pointers of the names...
|
||||
S32 hashTableSize;
|
||||
S32 hashEntryCount;
|
||||
|
||||
void *mutex;
|
||||
|
||||
public:
|
||||
void insert(SimObject* obj);
|
||||
void remove(SimObject* obj);
|
||||
SimObject* find(StringTableEntry name);
|
||||
|
||||
SimManagerNameDictionary();
|
||||
~SimManagerNameDictionary();
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// Map of ID's to SimObjects.
|
||||
///
|
||||
/// Provides fast lookup for ID->object and
|
||||
/// for fast removal of an object given object*
|
||||
class SimIdDictionary
|
||||
{
|
||||
enum
|
||||
{
|
||||
DefaultTableSize = 4096,
|
||||
TableBitMask = 4095
|
||||
};
|
||||
SimObject *table[DefaultTableSize];
|
||||
|
||||
void *mutex;
|
||||
|
||||
public:
|
||||
void insert(SimObject* obj);
|
||||
void remove(SimObject* obj);
|
||||
SimObject* find(S32 id);
|
||||
|
||||
SimIdDictionary();
|
||||
~SimIdDictionary();
|
||||
};
|
||||
|
||||
#endif //_SIMDICTIONARY_H_
|
537
engine/console/simManager.cc
Executable file
537
engine/console/simManager.cc
Executable file
@ -0,0 +1,537 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/platformMutex.h"
|
||||
#include "console/simBase.h"
|
||||
#include "core/stringTable.h"
|
||||
#include "console/console.h"
|
||||
#include "core/fileStream.h"
|
||||
#include "core/resManager.h"
|
||||
#include "core/fileObject.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "core/idGenerator.h"
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// We comment out the implementation of the Con namespace when doxygenizing because
|
||||
// otherwise Doxygen decides to ignore our docs in console.h
|
||||
#ifndef DOXYGENIZING
|
||||
|
||||
namespace Sim
|
||||
{
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// event queue variables:
|
||||
|
||||
SimTime gCurrentTime;
|
||||
SimTime gTargetTime;
|
||||
|
||||
void *gEventQueueMutex;
|
||||
SimEvent *gEventQueue;
|
||||
U32 gEventSequence;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// event queue init/shutdown
|
||||
|
||||
static void initEventQueue()
|
||||
{
|
||||
gCurrentTime = 0;
|
||||
gTargetTime = 0;
|
||||
gEventSequence = 1;
|
||||
gEventQueue = NULL;
|
||||
gEventQueueMutex = Mutex::createMutex();
|
||||
}
|
||||
|
||||
static void shutdownEventQueue()
|
||||
{
|
||||
// Delete all pending events
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
SimEvent *walk = gEventQueue;
|
||||
while(walk)
|
||||
{
|
||||
SimEvent *temp = walk->nextEvent;
|
||||
delete walk;
|
||||
walk = temp;
|
||||
}
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
Mutex::destroyMutex(gEventQueueMutex);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// event post
|
||||
|
||||
U32 postEvent(SimObject *destObject, SimEvent* event,U32 time)
|
||||
{
|
||||
AssertFatal(time >= getCurrentTime(),
|
||||
"Sim::postEvent: Cannot go back in time. (flux capacitor unavailable -- BJG)");
|
||||
AssertFatal(destObject, "Destination object for event doesn't exist.");
|
||||
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
|
||||
event->time = time;
|
||||
event->destObject = destObject;
|
||||
|
||||
if(!destObject)
|
||||
{
|
||||
delete event;
|
||||
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
|
||||
return InvalidEventId;
|
||||
}
|
||||
event->sequenceCount = gEventSequence++;
|
||||
SimEvent **walk = &gEventQueue;
|
||||
SimEvent *current;
|
||||
|
||||
while((current = *walk) != NULL && (current->time < event->time))
|
||||
walk = &(current->nextEvent);
|
||||
|
||||
// [tom, 6/24/2005] This ensures that SimEvents are dispatched in the same order that they are posted.
|
||||
// This is needed to ensure Con::threadSafeExecute() executes script code in the correct order.
|
||||
while((current = *walk) != NULL && (current->time == event->time))
|
||||
walk = &(current->nextEvent);
|
||||
|
||||
event->nextEvent = current;
|
||||
*walk = event;
|
||||
|
||||
U32 seqCount = event->sequenceCount;
|
||||
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
|
||||
return seqCount;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// event cancellation
|
||||
|
||||
void cancelEvent(U32 eventSequence)
|
||||
{
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
|
||||
SimEvent **walk = &gEventQueue;
|
||||
SimEvent *current;
|
||||
|
||||
while((current = *walk) != NULL)
|
||||
{
|
||||
if(current->sequenceCount == eventSequence)
|
||||
{
|
||||
*walk = current->nextEvent;
|
||||
delete current;
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
return;
|
||||
}
|
||||
else
|
||||
walk = &(current->nextEvent);
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
}
|
||||
|
||||
static void cancelPendingEvents(SimObject *obj)
|
||||
{
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
|
||||
SimEvent **walk = &gEventQueue;
|
||||
SimEvent *current;
|
||||
|
||||
while((current = *walk) != NULL)
|
||||
{
|
||||
if(current->destObject == obj)
|
||||
{
|
||||
*walk = current->nextEvent;
|
||||
delete current;
|
||||
}
|
||||
else
|
||||
walk = &(current->nextEvent);
|
||||
}
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// event pending test
|
||||
|
||||
bool isEventPending(U32 eventSequence)
|
||||
{
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
|
||||
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
|
||||
if(walk->sequenceCount == eventSequence)
|
||||
{
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
return true;
|
||||
}
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
U32 getEventTimeLeft(U32 eventSequence)
|
||||
{
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
|
||||
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
|
||||
if(walk->sequenceCount == eventSequence)
|
||||
{
|
||||
SimTime t = walk->time - getCurrentTime();
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
return t;
|
||||
}
|
||||
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
U32 getScheduleDuration(U32 eventSequence)
|
||||
{
|
||||
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
|
||||
if(walk->sequenceCount == eventSequence)
|
||||
return (walk->time-walk->startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
U32 getTimeSinceStart(U32 eventSequence)
|
||||
{
|
||||
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
|
||||
if(walk->sequenceCount == eventSequence)
|
||||
return (getCurrentTime()-walk->startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// event timing
|
||||
|
||||
void advanceToTime(SimTime targetTime)
|
||||
{
|
||||
AssertFatal(targetTime >= getCurrentTime(), "EventQueue::process: cannot advance to time in the past.");
|
||||
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
gTargetTime = targetTime;
|
||||
while(gEventQueue && gEventQueue->time <= targetTime)
|
||||
{
|
||||
SimEvent *event = gEventQueue;
|
||||
gEventQueue = gEventQueue->nextEvent;
|
||||
AssertFatal(event->time >= gCurrentTime,
|
||||
"SimEventQueue::pop: Cannot go back in time (flux capacitor not installed - BJG).");
|
||||
gCurrentTime = event->time;
|
||||
SimObject *obj = event->destObject;
|
||||
|
||||
if(!obj->isDeleted())
|
||||
event->process(obj);
|
||||
delete event;
|
||||
}
|
||||
gCurrentTime = targetTime;
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
}
|
||||
|
||||
void advanceTime(SimTime delta)
|
||||
{
|
||||
advanceToTime(getCurrentTime() + delta);
|
||||
}
|
||||
|
||||
U32 getCurrentTime()
|
||||
{
|
||||
Mutex::lockMutex(gEventQueueMutex);
|
||||
SimTime t = gCurrentTime;
|
||||
Mutex::unlockMutex(gEventQueueMutex);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
U32 getTargetTime()
|
||||
{
|
||||
return gTargetTime;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
SimGroup *gRootGroup = NULL;
|
||||
SimManagerNameDictionary *gNameDictionary;
|
||||
SimIdDictionary *gIdDictionary;
|
||||
U32 gNextObjectId;
|
||||
|
||||
static void initRoot()
|
||||
{
|
||||
gIdDictionary = new SimIdDictionary;
|
||||
gNameDictionary = new SimManagerNameDictionary;
|
||||
|
||||
gRootGroup = new SimGroup();
|
||||
gRootGroup->setId(RootGroupId);
|
||||
gRootGroup->assignName("RootGroup");
|
||||
gRootGroup->registerObject();
|
||||
|
||||
gNextObjectId = DynamicObjectIdFirst;
|
||||
}
|
||||
|
||||
static void shutdownRoot()
|
||||
{
|
||||
gRootGroup->deleteObject();
|
||||
|
||||
delete gNameDictionary;
|
||||
delete gIdDictionary;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
SimObject* findObject(const char* name)
|
||||
{
|
||||
// Play nice with bad code - JDD
|
||||
if( !name )
|
||||
return NULL;
|
||||
|
||||
SimObject *obj;
|
||||
char c = *name;
|
||||
if(c == '/')
|
||||
return gRootGroup->findObject(name + 1 );
|
||||
if(c >= '0' && c <= '9')
|
||||
{
|
||||
// it's an id group
|
||||
const char* temp = name + 1;
|
||||
for(;;)
|
||||
{
|
||||
c = *temp++;
|
||||
if(!c)
|
||||
return findObject(dAtoi(name));
|
||||
else if(c == '/')
|
||||
{
|
||||
obj = findObject(dAtoi(name));
|
||||
if(!obj)
|
||||
return NULL;
|
||||
return obj->findObject(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
S32 len;
|
||||
|
||||
for(len = 0; name[len] != 0 && name[len] != '/'; len++)
|
||||
;
|
||||
StringTableEntry stName = StringTable->lookupn(name, len);
|
||||
if(!stName)
|
||||
return NULL;
|
||||
obj = gNameDictionary->find(stName);
|
||||
if(!name[len])
|
||||
return obj;
|
||||
if(!obj)
|
||||
return NULL;
|
||||
return obj->findObject(name + len + 1);
|
||||
}
|
||||
|
||||
SimObject* findObject(SimObjectId id)
|
||||
{
|
||||
return gIdDictionary->find(id);
|
||||
}
|
||||
|
||||
SimGroup *getRootGroup()
|
||||
{
|
||||
return gRootGroup;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#define InstantiateNamedSet(set) g##set = new SimSet; g##set->registerObject(#set); gRootGroup->addObject(g##set)
|
||||
#define InstantiateNamedGroup(set) g##set = new SimGroup; g##set->registerObject(#set); gRootGroup->addObject(g##set)
|
||||
|
||||
SimDataBlockGroup *gDataBlockGroup;
|
||||
SimDataBlockGroup *getDataBlockGroup()
|
||||
{
|
||||
return gDataBlockGroup;
|
||||
}
|
||||
|
||||
|
||||
void init()
|
||||
{
|
||||
initEventQueue();
|
||||
initRoot();
|
||||
|
||||
InstantiateNamedSet(ActiveActionMapSet);
|
||||
InstantiateNamedSet(GhostAlwaysSet);
|
||||
InstantiateNamedSet(LightSet);
|
||||
InstantiateNamedSet(WayPointSet);
|
||||
InstantiateNamedSet(fxReplicatorSet);
|
||||
InstantiateNamedSet(fxFoliageSet);
|
||||
InstantiateNamedGroup(ActionMapGroup);
|
||||
InstantiateNamedGroup(ClientGroup);
|
||||
InstantiateNamedGroup(GuiGroup);
|
||||
InstantiateNamedGroup(ScriptClassGroup);
|
||||
InstantiateNamedGroup(GuiDataGroup);
|
||||
InstantiateNamedGroup(TCPGroup);
|
||||
InstantiateNamedGroup(ClientConnectionGroup);
|
||||
InstantiateNamedGroup(ChunkFileGroup);
|
||||
|
||||
gDataBlockGroup = new SimDataBlockGroup();
|
||||
gDataBlockGroup->registerObject("DataBlockGroup");
|
||||
gRootGroup->addObject(gDataBlockGroup);
|
||||
|
||||
InstantiateNamedSet(sgMissionLightingFilterSet);
|
||||
}
|
||||
|
||||
void shutdown()
|
||||
{
|
||||
shutdownRoot();
|
||||
shutdownEventQueue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // DOXYGENIZING.
|
||||
|
||||
SimDataBlockGroup::SimDataBlockGroup()
|
||||
{
|
||||
mLastModifiedKey = 0;
|
||||
}
|
||||
|
||||
S32 QSORT_CALLBACK SimDataBlockGroup::compareModifiedKey(const void* a,const void* b)
|
||||
{
|
||||
return (reinterpret_cast<const SimDataBlock* >(a))->getModifiedKey() -
|
||||
(reinterpret_cast<const SimDataBlock*>(b))->getModifiedKey();
|
||||
}
|
||||
|
||||
|
||||
void SimDataBlockGroup::sort()
|
||||
{
|
||||
if(mLastModifiedKey != SimDataBlock::getNextModifiedKey())
|
||||
{
|
||||
mLastModifiedKey = SimDataBlock::getNextModifiedKey();
|
||||
dQsort(objectList.address(),objectList.size(),sizeof(SimObject *),compareModifiedKey);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bool SimObject::registerObject()
|
||||
{
|
||||
AssertFatal( !mFlags.test( Added ), "reigsterObject - Object already registered!");
|
||||
mFlags.clear(Deleted | Removed);
|
||||
|
||||
if(!mId)
|
||||
mId = Sim::gNextObjectId++;
|
||||
|
||||
AssertFatal(Sim::gIdDictionary && Sim::gNameDictionary,
|
||||
"SimObject::registerObject - tried to register an object before Sim::init()!");
|
||||
|
||||
Sim::gIdDictionary->insert(this);
|
||||
|
||||
Sim::gNameDictionary->insert(this);
|
||||
|
||||
// Notify object
|
||||
bool ret = onAdd();
|
||||
|
||||
if(!ret)
|
||||
unregisterObject();
|
||||
|
||||
AssertFatal(!ret || isProperlyAdded(), "Object did not call SimObject::onAdd()");
|
||||
return ret;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void SimObject::unregisterObject()
|
||||
{
|
||||
mFlags.set(Removed);
|
||||
|
||||
// Notify object first
|
||||
onRemove();
|
||||
|
||||
// Clear out any pending notifications before
|
||||
// we call our own, just in case they delete
|
||||
// something that we have referenced.
|
||||
clearAllNotifications();
|
||||
|
||||
// Notify all objects that are waiting for delete
|
||||
// messages
|
||||
if (getGroup())
|
||||
getGroup()->removeObject(this);
|
||||
|
||||
processDeleteNotifies();
|
||||
|
||||
// Do removals from the Sim.
|
||||
Sim::gNameDictionary->remove(this);
|
||||
Sim::gIdDictionary->remove(this);
|
||||
Sim::cancelPendingEvents(this);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void SimObject::deleteObject()
|
||||
{
|
||||
AssertFatal(mFlags.test(Added),
|
||||
"SimObject::deleteObject: Object not registered.");
|
||||
AssertFatal(!isDeleted(),"SimManager::deleteObject: "
|
||||
"Object has already been deleted");
|
||||
AssertFatal(!isRemoved(),"SimManager::deleteObject: "
|
||||
"Object in the process of being removed");
|
||||
mFlags.set(Deleted);
|
||||
|
||||
unregisterObject();
|
||||
delete this;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
void SimObject::setId(SimObjectId newId)
|
||||
{
|
||||
if(!mFlags.test(Added))
|
||||
{
|
||||
mId = newId;
|
||||
return;
|
||||
}
|
||||
|
||||
// get this object out of the id dictionary if it's in it
|
||||
Sim::gIdDictionary->remove(this);
|
||||
|
||||
// Free current Id.
|
||||
// Assign new one.
|
||||
mId = newId ? newId : Sim::gNextObjectId++;
|
||||
Sim::gIdDictionary->insert(this);
|
||||
}
|
||||
|
||||
void SimObject::assignName(const char *name)
|
||||
{
|
||||
StringTableEntry newName = NULL;
|
||||
if(name[0])
|
||||
newName = StringTable->insert(name);
|
||||
|
||||
if(mGroup)
|
||||
mGroup->nameDictionary.remove(this);
|
||||
if(mFlags.test(Added))
|
||||
Sim::gNameDictionary->remove(this);
|
||||
|
||||
objectName = newName;
|
||||
|
||||
if(mGroup)
|
||||
mGroup->nameDictionary.insert(this);
|
||||
if(mFlags.test(Added))
|
||||
Sim::gNameDictionary->insert(this);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bool SimObject::registerObject(U32 id)
|
||||
{
|
||||
setId(id);
|
||||
return registerObject();
|
||||
}
|
||||
|
||||
bool SimObject::registerObject(const char *name)
|
||||
{
|
||||
assignName(name);
|
||||
return registerObject();
|
||||
}
|
||||
|
||||
bool SimObject::registerObject(const char *name, U32 id)
|
||||
{
|
||||
setId(id);
|
||||
assignName(name);
|
||||
return registerObject();
|
||||
}
|
25
engine/console/stringStack.cc
Executable file
25
engine/console/stringStack.cc
Executable file
@ -0,0 +1,25 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "console/stringStack.h"
|
||||
|
||||
void StringStack::getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv)
|
||||
{
|
||||
U32 startStack = mFrameOffsets[--mNumFrames] + 1;
|
||||
U32 argCount = getMin(mStartStackSize - startStack, (U32)MaxArgs);
|
||||
|
||||
*in_argv = mArgV;
|
||||
mArgV[0] = name;
|
||||
|
||||
for(U32 i = 0; i < argCount; i++)
|
||||
mArgV[i+1] = mBuffer + mStartOffsets[startStack + i];
|
||||
argCount++;
|
||||
|
||||
mStartStackSize = startStack - 1;
|
||||
*argc = argCount;
|
||||
|
||||
mStart = mStartOffsets[mStartStackSize];
|
||||
mLen = 0;
|
||||
}
|
247
engine/console/stringStack.h
Executable file
247
engine/console/stringStack.h
Executable file
@ -0,0 +1,247 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _STRINGSTACK_H_
|
||||
#define _STRINGSTACK_H_
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/console.h"
|
||||
#include "console/compiler.h"
|
||||
#include "core/stringTable.h"
|
||||
|
||||
/// Core stack for interpreter operations.
|
||||
///
|
||||
/// This class provides some powerful semantics for working with strings, and is
|
||||
/// used heavily by the console interpreter.
|
||||
struct StringStack
|
||||
{
|
||||
enum {
|
||||
MaxStackDepth = 1024,
|
||||
MaxArgs = 20,
|
||||
ReturnBufferSpace = 512
|
||||
};
|
||||
char *mBuffer;
|
||||
U32 mBufferSize;
|
||||
const char *mArgV[MaxArgs];
|
||||
U32 mFrameOffsets[MaxStackDepth];
|
||||
U32 mStartOffsets[MaxStackDepth];
|
||||
|
||||
U32 mNumFrames;
|
||||
U32 mArgc;
|
||||
|
||||
U32 mStart;
|
||||
U32 mLen;
|
||||
U32 mStartStackSize;
|
||||
U32 mFunctionOffset;
|
||||
|
||||
U32 mArgBufferSize;
|
||||
char *mArgBuffer;
|
||||
|
||||
void validateBufferSize(U32 size)
|
||||
{
|
||||
if(size > mBufferSize)
|
||||
{
|
||||
mBufferSize = size + 2048;
|
||||
mBuffer = (char *) dRealloc(mBuffer, mBufferSize);
|
||||
}
|
||||
}
|
||||
void validateArgBufferSize(U32 size)
|
||||
{
|
||||
if(size > mArgBufferSize)
|
||||
{
|
||||
mArgBufferSize = size + 2048;
|
||||
mArgBuffer = (char *) dRealloc(mArgBuffer, mArgBufferSize);
|
||||
}
|
||||
}
|
||||
StringStack()
|
||||
{
|
||||
mBufferSize = 0;
|
||||
mBuffer = NULL;
|
||||
mNumFrames = 0;
|
||||
mStart = 0;
|
||||
mLen = 0;
|
||||
mStartStackSize = 0;
|
||||
mFunctionOffset = 0;
|
||||
validateBufferSize(8192);
|
||||
validateArgBufferSize(2048);
|
||||
}
|
||||
|
||||
/// Set the top of the stack to be an integer value.
|
||||
void setIntValue(U32 i)
|
||||
{
|
||||
validateBufferSize(mStart + 32);
|
||||
dSprintf(mBuffer + mStart, 32, "%d", i);
|
||||
mLen = dStrlen(mBuffer + mStart);
|
||||
}
|
||||
|
||||
/// Set the top of the stack to be a float value.
|
||||
void setFloatValue(F64 v)
|
||||
{
|
||||
validateBufferSize(mStart + 32);
|
||||
dSprintf(mBuffer + mStart, 32, "%g", v);
|
||||
mLen = dStrlen(mBuffer + mStart);
|
||||
}
|
||||
|
||||
/// Return a temporary buffer we can use to return data.
|
||||
///
|
||||
/// @note This clobbers anything in our buffers!
|
||||
char *getReturnBuffer(U32 size)
|
||||
{
|
||||
if(size > ReturnBufferSpace)
|
||||
{
|
||||
validateArgBufferSize(size);
|
||||
return mArgBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
validateBufferSize(mStart + size);
|
||||
return mBuffer + mStart;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a buffer we can use for arguments.
|
||||
///
|
||||
/// This updates the function offset.
|
||||
char *getArgBuffer(U32 size)
|
||||
{
|
||||
validateBufferSize(mStart + mFunctionOffset + size);
|
||||
char *ret = mBuffer + mStart + mFunctionOffset;
|
||||
mFunctionOffset += size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Clear the function offset.
|
||||
void clearFunctionOffset()
|
||||
{
|
||||
mFunctionOffset = 0;
|
||||
}
|
||||
|
||||
/// Set a string value on the top of the stack.
|
||||
void setStringValue(const char *s)
|
||||
{
|
||||
if(!s)
|
||||
{
|
||||
mLen = 0;
|
||||
mBuffer[mStart] = 0;
|
||||
return;
|
||||
}
|
||||
mLen = dStrlen(s);
|
||||
|
||||
validateBufferSize(mStart + mLen + 2);
|
||||
dStrcpy(mBuffer + mStart, s);
|
||||
}
|
||||
|
||||
/// Get the top of the stack, as a StringTableEntry.
|
||||
///
|
||||
/// @note Don't free this memory!
|
||||
inline StringTableEntry getSTValue()
|
||||
{
|
||||
return StringTable->insert(mBuffer + mStart);
|
||||
}
|
||||
|
||||
/// Get an integer representation of the top of the stack.
|
||||
inline U32 getIntValue()
|
||||
{
|
||||
return dAtoi(mBuffer + mStart);
|
||||
}
|
||||
|
||||
/// Get a float representation of the top of the stack.
|
||||
inline F64 getFloatValue()
|
||||
{
|
||||
return dAtof(mBuffer + mStart);
|
||||
}
|
||||
|
||||
/// Get a string representation of the top of the stack.
|
||||
///
|
||||
/// @note This returns a pointer to the actual top of the stack, be careful!
|
||||
inline const char *getStringValue()
|
||||
{
|
||||
return mBuffer + mStart;
|
||||
}
|
||||
|
||||
/// Advance the start stack, placing a zero length string on the top.
|
||||
///
|
||||
/// @note You should use StringStack::push, not this, if you want to
|
||||
/// properly push the stack.
|
||||
void advance()
|
||||
{
|
||||
mStartOffsets[mStartStackSize++] = mStart;
|
||||
mStart += mLen;
|
||||
mLen = 0;
|
||||
}
|
||||
|
||||
/// Advance the start stack, placing a single character, null-terminated strong
|
||||
/// on the top.
|
||||
///
|
||||
/// @note You should use StringStack::push, not this, if you want to
|
||||
/// properly push the stack.
|
||||
void advanceChar(char c)
|
||||
{
|
||||
mStartOffsets[mStartStackSize++] = mStart;
|
||||
mStart += mLen;
|
||||
mBuffer[mStart] = c;
|
||||
mBuffer[mStart+1] = 0;
|
||||
mStart += 1;
|
||||
mLen = 0;
|
||||
}
|
||||
|
||||
/// Push the stack, placing a zero-length string on the top.
|
||||
void push()
|
||||
{
|
||||
advanceChar(0);
|
||||
}
|
||||
|
||||
inline void setLen(U32 newlen)
|
||||
{
|
||||
mLen = newlen;
|
||||
}
|
||||
|
||||
/// Pop the start stack.
|
||||
void rewind()
|
||||
{
|
||||
mStart = mStartOffsets[--mStartStackSize];
|
||||
mLen = dStrlen(mBuffer + mStart);
|
||||
}
|
||||
|
||||
// Terminate the current string, and pop the start stack.
|
||||
void rewindTerminate()
|
||||
{
|
||||
mBuffer[mStart] = 0;
|
||||
mStart = mStartOffsets[--mStartStackSize];
|
||||
mLen = dStrlen(mBuffer + mStart);
|
||||
}
|
||||
|
||||
/// Compare 1st and 2nd items on stack, consuming them in the process,
|
||||
/// and returning true if they matched, false if they didn't.
|
||||
U32 compare()
|
||||
{
|
||||
// Figure out the 1st and 2nd item offsets.
|
||||
U32 oldStart = mStart;
|
||||
mStart = mStartOffsets[--mStartStackSize];
|
||||
|
||||
// Compare current and previous strings.
|
||||
U32 ret = !dStricmp(mBuffer + mStart, mBuffer + oldStart);
|
||||
|
||||
// Put an empty string on the top of the stack.
|
||||
mLen = 0;
|
||||
mBuffer[mStart] = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void pushFrame()
|
||||
{
|
||||
mFrameOffsets[mNumFrames++] = mStartStackSize;
|
||||
mStartOffsets[mStartStackSize++] = mStart;
|
||||
mStart += ReturnBufferSpace;
|
||||
validateBufferSize(0);
|
||||
}
|
||||
|
||||
/// Get the arguments for a function call from the stack.
|
||||
void getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv);
|
||||
};
|
||||
|
||||
#endif
|
274
engine/console/telnetConsole.cc
Executable file
274
engine/console/telnetConsole.cc
Executable file
@ -0,0 +1,274 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "platform/event.h"
|
||||
#include "console/telnetConsole.h"
|
||||
#include "platform/gameInterface.h"
|
||||
|
||||
TelnetConsole *TelConsole = NULL;
|
||||
|
||||
void TelnetConsole::create()
|
||||
{
|
||||
TelConsole = new TelnetConsole;
|
||||
}
|
||||
|
||||
void TelnetConsole::destroy()
|
||||
{
|
||||
delete TelConsole;
|
||||
TelConsole = NULL;
|
||||
}
|
||||
|
||||
ConsoleFunction( telnetSetParameters, void, 4, 5, "(int port, string consolePass, string listenPass)"
|
||||
"Initialize and open the telnet console.\n\n"
|
||||
"@param port Port to listen on for console connections (0 will shut down listening).\n"
|
||||
"@param consolePass Password for read/write access to console.\n"
|
||||
"@param listenPass Password for read access to console."
|
||||
"@param remoteEcho [optional] Enable echoing back to the client, off by default.")
|
||||
{
|
||||
if (TelConsole)
|
||||
{
|
||||
bool remoteEcho = false;
|
||||
if( argc == 5 )
|
||||
remoteEcho = dAtob( argv[4] );
|
||||
TelConsole->setTelnetParameters(dAtoi(argv[1]), argv[2], argv[3], remoteEcho);
|
||||
}
|
||||
}
|
||||
|
||||
static void telnetCallback(ConsoleLogEntry::Level level, const char *consoleLine)
|
||||
{
|
||||
level;
|
||||
if (TelConsole)
|
||||
TelConsole->processConsoleLine(consoleLine);
|
||||
}
|
||||
|
||||
TelnetConsole::TelnetConsole()
|
||||
{
|
||||
Con::addConsumer(telnetCallback);
|
||||
|
||||
mAcceptSocket = InvalidSocket;
|
||||
mAcceptPort = -1;
|
||||
mClientList = NULL;
|
||||
mRemoteEchoEnabled = false;
|
||||
}
|
||||
|
||||
TelnetConsole::~TelnetConsole()
|
||||
{
|
||||
Con::removeConsumer(telnetCallback);
|
||||
if(mAcceptSocket != InvalidSocket)
|
||||
Net::closeSocket(mAcceptSocket);
|
||||
TelnetClient *walk = mClientList, *temp;
|
||||
while(walk)
|
||||
{
|
||||
temp = walk->nextClient;
|
||||
if(walk->socket != InvalidSocket)
|
||||
Net::closeSocket(walk->socket);
|
||||
delete walk;
|
||||
walk = temp;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetConsole::setTelnetParameters(S32 port, const char *telnetPassword, const char *listenPassword, bool remoteEcho)
|
||||
{
|
||||
if(port == mAcceptPort)
|
||||
return;
|
||||
|
||||
mRemoteEchoEnabled = remoteEcho;
|
||||
|
||||
if(mAcceptSocket != InvalidSocket)
|
||||
{
|
||||
Net::closeSocket(mAcceptSocket);
|
||||
mAcceptSocket = InvalidSocket;
|
||||
}
|
||||
mAcceptPort = port;
|
||||
if(mAcceptPort != -1 && mAcceptPort != 0)
|
||||
{
|
||||
mAcceptSocket = Net::openSocket();
|
||||
Net::bind(mAcceptSocket, mAcceptPort);
|
||||
Net::listen(mAcceptSocket, 4);
|
||||
|
||||
Net::setBlocking(mAcceptSocket, false);
|
||||
}
|
||||
dStrncpy(mTelnetPassword, telnetPassword, PasswordMaxLength);
|
||||
dStrncpy(mListenPassword, listenPassword, PasswordMaxLength);
|
||||
}
|
||||
|
||||
void TelnetConsole::processConsoleLine(const char *consoleLine)
|
||||
{
|
||||
if (mClientList==NULL) return; // just escape early. don't even do another step...
|
||||
|
||||
// ok, spew this line out to all our subscribers...
|
||||
S32 len = dStrlen(consoleLine)+1;
|
||||
for(TelnetClient *walk = mClientList; walk; walk = walk->nextClient)
|
||||
{
|
||||
if(walk->state == FullAccessConnected || walk->state == ReadOnlyConnected)
|
||||
{
|
||||
Net::send(walk->socket, (const unsigned char*)consoleLine, len);
|
||||
Net::send(walk->socket, (const unsigned char*)"\r\n", 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetConsole::process()
|
||||
{
|
||||
NetAddress address;
|
||||
|
||||
if(mAcceptSocket != InvalidSocket)
|
||||
{
|
||||
// ok, see if we have any new connections:
|
||||
NetSocket newConnection;
|
||||
newConnection = Net::accept(mAcceptSocket, &address);
|
||||
|
||||
if(newConnection != InvalidSocket)
|
||||
{
|
||||
Con::printf ("Telnet connection from %i.%i.%i.%i",
|
||||
address.netNum[0], address.netNum[1], address.netNum[2], address.netNum[3]);
|
||||
|
||||
TelnetClient *cl = new TelnetClient;
|
||||
cl->socket = newConnection;
|
||||
cl->curPos = 0;
|
||||
cl->state = PasswordTryOne;
|
||||
|
||||
Net::setBlocking(newConnection, false);
|
||||
|
||||
char *connectMessage = "Torque Telnet Remote Console\r\n\r\nEnter Password:";
|
||||
|
||||
Net::send(cl->socket, (const unsigned char*)connectMessage, dStrlen(connectMessage)+1);
|
||||
cl->nextClient = mClientList;
|
||||
mClientList = cl;
|
||||
}
|
||||
}
|
||||
|
||||
char recvBuf[256];
|
||||
char reply[1024];
|
||||
|
||||
// see if we have any input to process...
|
||||
|
||||
for(TelnetClient *client = mClientList; client; client = client->nextClient)
|
||||
{
|
||||
S32 numBytes;
|
||||
Net::Error err = Net::recv(client->socket, (unsigned char*)recvBuf, sizeof(recvBuf), &numBytes);
|
||||
|
||||
if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
|
||||
{
|
||||
Net::closeSocket(client->socket);
|
||||
client->socket = InvalidSocket;
|
||||
continue;
|
||||
}
|
||||
|
||||
S32 replyPos = 0;
|
||||
for(S32 i = 0; i < numBytes;i++)
|
||||
{
|
||||
if(recvBuf[i] == '\r')
|
||||
continue;
|
||||
// execute the current command
|
||||
|
||||
if(recvBuf[i] == '\n')
|
||||
{
|
||||
reply[replyPos++] = '\r';
|
||||
reply[replyPos++] = '\n';
|
||||
|
||||
client->curLine[client->curPos] = 0;
|
||||
client->curPos = 0;
|
||||
|
||||
if(client->state == FullAccessConnected)
|
||||
{
|
||||
Net::send(client->socket, (const unsigned char*)reply, replyPos);
|
||||
replyPos = 0;
|
||||
|
||||
dStrcpy(mPostEvent.data, client->curLine);
|
||||
mPostEvent.size = ConsoleEventHeaderSize + dStrlen(client->curLine) + 1;
|
||||
Game->postEvent(mPostEvent);
|
||||
|
||||
// note - send prompt next
|
||||
const char *prompt = Con::getVariable("Con::Prompt");
|
||||
Net::send(client->socket, (const unsigned char*)prompt, dStrlen(prompt));
|
||||
}
|
||||
else if(client->state == ReadOnlyConnected)
|
||||
{
|
||||
Net::send(client->socket, (const unsigned char*)reply, replyPos);
|
||||
replyPos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
client->state++;
|
||||
if(!dStrncmp(client->curLine, mTelnetPassword, PasswordMaxLength))
|
||||
{
|
||||
Net::send(client->socket, (const unsigned char*)reply, replyPos);
|
||||
replyPos = 0;
|
||||
|
||||
// send prompt
|
||||
const char *prompt = Con::getVariable("Con::Prompt");
|
||||
Net::send(client->socket, (const unsigned char*)prompt, dStrlen(prompt));
|
||||
client->state = FullAccessConnected;
|
||||
}
|
||||
else if(!dStrncmp(client->curLine, mListenPassword, PasswordMaxLength))
|
||||
{
|
||||
Net::send(client->socket, (const unsigned char*)reply, replyPos);
|
||||
replyPos = 0;
|
||||
|
||||
// send prompt
|
||||
const char *listenConnected = "Connected.\r\n";
|
||||
Net::send(client->socket, (const unsigned char*)listenConnected, dStrlen(listenConnected));
|
||||
client->state = ReadOnlyConnected;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *sendStr;
|
||||
if(client->state == DisconnectThisDude)
|
||||
sendStr = "Too many tries... cya.";
|
||||
else
|
||||
sendStr = "Nope... try agian.\r\nEnter Password:";
|
||||
Net::send(client->socket, (const unsigned char*)sendStr, dStrlen(sendStr));
|
||||
if(client->state == DisconnectThisDude)
|
||||
{
|
||||
Net::closeSocket(client->socket);
|
||||
client->socket = InvalidSocket;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(recvBuf[i] == '\b')
|
||||
{
|
||||
// pull the old backspace manuever...
|
||||
if(client->curPos > 0)
|
||||
{
|
||||
client->curPos--;
|
||||
if(client->state == FullAccessConnected)
|
||||
{
|
||||
reply[replyPos++] = '\b';
|
||||
reply[replyPos++] = ' ';
|
||||
reply[replyPos++] = '\b';
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(client->curPos < Con::MaxLineLength-1)
|
||||
{
|
||||
client->curLine[client->curPos++] = recvBuf[i];
|
||||
// don't echo password chars...
|
||||
if(client->state == FullAccessConnected)
|
||||
reply[replyPos++] = recvBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Echo the character back to the user, unless the remote echo
|
||||
// is disabled (by default)
|
||||
if(replyPos && mRemoteEchoEnabled)
|
||||
Net::send(client->socket, (const unsigned char*)reply, replyPos);
|
||||
}
|
||||
|
||||
TelnetClient ** walk = &mClientList;
|
||||
TelnetClient *cl;
|
||||
while((cl = *walk) != NULL)
|
||||
{
|
||||
if(cl->socket == InvalidSocket)
|
||||
{
|
||||
*walk = cl->nextClient;
|
||||
delete cl;
|
||||
}
|
||||
else
|
||||
walk = &cl->nextClient;
|
||||
}
|
||||
}
|
104
engine/console/telnetConsole.h
Executable file
104
engine/console/telnetConsole.h
Executable file
@ -0,0 +1,104 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _TELNETCONSOLE_H_
|
||||
#define _TELNETCONSOLE_H_
|
||||
|
||||
#ifndef _CONSOLE_H_
|
||||
#include "console/console.h"
|
||||
#endif
|
||||
|
||||
/// Telnet admin console.
|
||||
///
|
||||
/// Torque supports remote access to its console. This is most useful when
|
||||
/// running a dedicated server, as you can remotely administer the game
|
||||
/// (for instance, kicking people). In the context of a MMORPG, this sort of
|
||||
/// functionality would be useful for managing a server.
|
||||
///
|
||||
/// There are a number of products for Tribes2 which allow remote administration
|
||||
/// via a nice GUI.
|
||||
///
|
||||
/// @section telnetconsole_use Using the Telnet Console
|
||||
///
|
||||
/// The TelnetConsole is designed to be used globally, so you don't instantiate
|
||||
/// it like a normal class. Instead, you allow it to manage itself:
|
||||
///
|
||||
/// @code
|
||||
/// // How to initialize the TelnetConsole.
|
||||
/// TelnetConsole::create();
|
||||
///
|
||||
/// // How to shut down the TelnetConsole.
|
||||
/// TelnetConsole::destroy();
|
||||
/// @endcode
|
||||
///
|
||||
///
|
||||
class TelnetConsole
|
||||
{
|
||||
NetSocket mAcceptSocket;
|
||||
S32 mAcceptPort;
|
||||
|
||||
enum {
|
||||
PasswordMaxLength = 32 ///< Maximum length of the telnet and listen passwords.
|
||||
};
|
||||
|
||||
bool mRemoteEchoEnabled;
|
||||
char mTelnetPassword[PasswordMaxLength+1];
|
||||
char mListenPassword[PasswordMaxLength+1];
|
||||
ConsoleEvent mPostEvent;
|
||||
|
||||
/// State of a TelnetClient.
|
||||
enum State
|
||||
{
|
||||
PasswordTryOne, ///< Allow three password attempts.
|
||||
PasswordTryTwo,
|
||||
PasswordTryThree,
|
||||
DisconnectThisDude, ///< If they've failed all three, disconnect them.
|
||||
FullAccessConnected, ///< They presented the telnetPassword, they get full access.
|
||||
ReadOnlyConnected ///< They presented the listenPassword, they get read only access.
|
||||
};
|
||||
|
||||
/// Represents a connection to the telnet console.
|
||||
///
|
||||
/// This is also a linked list.
|
||||
struct TelnetClient
|
||||
{
|
||||
NetSocket socket;
|
||||
char curLine[Con::MaxLineLength];
|
||||
S32 curPos;
|
||||
S32 state; ///< State of the client.
|
||||
/// @see TelnetConsole::State
|
||||
TelnetClient *nextClient;
|
||||
};
|
||||
TelnetClient *mClientList;
|
||||
TelnetConsole();
|
||||
~TelnetConsole();
|
||||
|
||||
public:
|
||||
static void create(); ///< Initialize the telnet console.
|
||||
static void destroy(); ///< Shut down the telnet console.
|
||||
void process(); ///< Called by the main loop to let the console process commands
|
||||
/// and connections.
|
||||
|
||||
/// Configure the parameter for the telnet console.
|
||||
///
|
||||
/// @param port Port on which to listen for connections.
|
||||
/// @param telnetPassword Password for full access to the console.
|
||||
/// @param listenPassword Password for read-only access to the console.
|
||||
/// @param remoteEcho Enable/disable echoing input back to the client
|
||||
void setTelnetParameters(S32 port, const char *telnetPassword, const char *listenPassword, bool remoteEcho = false);
|
||||
|
||||
/// Callback to handle a line from the console.
|
||||
///
|
||||
/// @note This is used internally by the class; you
|
||||
/// shouldn't need to call it.
|
||||
///
|
||||
/// @see Con::addConsumer()
|
||||
void processConsoleLine(const char *line);
|
||||
};
|
||||
|
||||
extern TelnetConsole *TelConsole;
|
||||
|
||||
#endif
|
||||
|
876
engine/console/telnetDebugger.cc
Executable file
876
engine/console/telnetDebugger.cc
Executable file
@ -0,0 +1,876 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/console.h"
|
||||
#include "console/telnetDebugger.h"
|
||||
#include "platform/event.h"
|
||||
#include "core/stringTable.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "console/ast.h"
|
||||
#include "console/compiler.h"
|
||||
#include "platform/gameInterface.h"
|
||||
|
||||
|
||||
//
|
||||
// Enhanced TelnetDebugger for Torsion
|
||||
// http://www.sickheadgames.com/torsion
|
||||
//
|
||||
//
|
||||
// Debugger commands:
|
||||
//
|
||||
// CEVAL console line - evaluate the console line
|
||||
// output: none
|
||||
//
|
||||
// BRKVARSET varName passct expr - NOT IMPLEMENTED!
|
||||
// output: none
|
||||
//
|
||||
// BRKVARCLR varName - NOT IMPLEMENTED!
|
||||
// output: none
|
||||
//
|
||||
// BRKSET file line clear passct expr - set a breakpoint on the file,line
|
||||
// it must pass passct times for it to break and if clear is true, it
|
||||
// clears when hit
|
||||
// output:
|
||||
//
|
||||
// BRKNEXT - stop execution at the next breakable line.
|
||||
// output: none
|
||||
//
|
||||
// BRKCLR file line - clear a breakpoint on the file,line
|
||||
// output: none
|
||||
//
|
||||
// BRKCLRALL - clear all breakpoints
|
||||
// output: none
|
||||
//
|
||||
// CONTINUE - continue execution
|
||||
// output: RUNNING
|
||||
//
|
||||
// STEPIN - run until next statement
|
||||
// output: RUNNING
|
||||
//
|
||||
// STEPOVER - run until next break <= current frame
|
||||
// output: RUNNING
|
||||
//
|
||||
// STEPOUT - run until next break <= current frame - 1
|
||||
// output: RUNNING
|
||||
//
|
||||
// EVAL tag frame expr - evaluate the expr in the console, on the frame'th stack frame
|
||||
// output: EVALOUT tag exprResult
|
||||
//
|
||||
// FILELIST - list script files loaded
|
||||
// output: FILELISTOUT file1 file2 file3 file4 ...
|
||||
//
|
||||
// BREAKLIST file - get a list of breakpoint-able lines in the file
|
||||
// output: BREAKLISTOUT file skipBreakPairs skiplinecount breaklinecount skiplinecount breaklinecount ...
|
||||
//
|
||||
//
|
||||
// Other output:
|
||||
//
|
||||
// BREAK file1 line1 function1 file2 line2 function2 ... - Sent when the debugger hits a
|
||||
// breakpoint. It lists out one file/line/function triplet for each stack level.
|
||||
// The first one is the top of the stack.
|
||||
//
|
||||
// COUT console-output - echo of console output from engine
|
||||
//
|
||||
// BRKMOV file line newline - sent when a breakpoint is moved to a breakable line.
|
||||
//
|
||||
// BRKCLR file line - sent when a breakpoint cannot be moved to a breakable line on the client.
|
||||
//
|
||||
|
||||
|
||||
ConsoleFunction( dbgSetParameters, void, 3, 4, "(int port, string password, bool waitForClient)"
|
||||
"Open a debug server port on the specified port, requiring the specified password, "
|
||||
"and optionally waiting for the debug client to connect.")
|
||||
{
|
||||
if (TelDebugger)
|
||||
TelDebugger->setDebugParameters(dAtoi(argv[1]), argv[2], argc > 3 ? dAtob(argv[3]) : false );
|
||||
}
|
||||
|
||||
ConsoleFunction( dbgIsConnected, bool, 1, 1, "()"
|
||||
"Returns true if a script debugging client is connected else return false.")
|
||||
{
|
||||
return TelDebugger && TelDebugger->isConnected();
|
||||
}
|
||||
|
||||
ConsoleFunction( dbgDisconnect, void, 1, 1, "()"
|
||||
"Forcibly disconnects any attached script debugging client.")
|
||||
{
|
||||
if (TelDebugger)
|
||||
TelDebugger->disconnect();
|
||||
}
|
||||
|
||||
static void debuggerConsumer(ConsoleLogEntry::Level level, const char *line)
|
||||
{
|
||||
level;
|
||||
if (TelDebugger)
|
||||
TelDebugger->processConsoleLine(line);
|
||||
}
|
||||
|
||||
TelnetDebugger::TelnetDebugger()
|
||||
{
|
||||
Con::addConsumer(debuggerConsumer);
|
||||
|
||||
mAcceptPort = -1;
|
||||
mAcceptSocket = InvalidSocket;
|
||||
mDebugSocket = InvalidSocket;
|
||||
|
||||
mState = NotConnected;
|
||||
mCurPos = 0;
|
||||
|
||||
mBreakpoints = NULL;
|
||||
mBreakOnNextStatement = false;
|
||||
mStackPopBreakIndex = -1;
|
||||
mProgramPaused = false;
|
||||
mWaitForClient = false;
|
||||
|
||||
// Add the version number in a global so that
|
||||
// scripts can detect the presence of the
|
||||
// "enhanced" debugger features.
|
||||
Con::evaluatef( "$dbgVersion = %d;", Version );
|
||||
}
|
||||
|
||||
TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber)
|
||||
{
|
||||
Breakpoint **walk = &mBreakpoints;
|
||||
Breakpoint *cur;
|
||||
while((cur = *walk) != NULL)
|
||||
{
|
||||
if(cur->fileName == fileName && cur->lineNumber == U32(lineNumber))
|
||||
return walk;
|
||||
walk = &cur->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
TelnetDebugger::~TelnetDebugger()
|
||||
{
|
||||
Con::removeConsumer(debuggerConsumer);
|
||||
|
||||
if(mAcceptSocket != InvalidSocket)
|
||||
Net::closeSocket(mAcceptSocket);
|
||||
if(mDebugSocket != InvalidSocket)
|
||||
Net::closeSocket(mDebugSocket);
|
||||
}
|
||||
|
||||
TelnetDebugger *TelDebugger = NULL;
|
||||
|
||||
void TelnetDebugger::create()
|
||||
{
|
||||
TelDebugger = new TelnetDebugger;
|
||||
}
|
||||
|
||||
void TelnetDebugger::destroy()
|
||||
{
|
||||
delete TelDebugger;
|
||||
TelDebugger = NULL;
|
||||
}
|
||||
|
||||
void TelnetDebugger::send(const char *str)
|
||||
{
|
||||
Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str));
|
||||
}
|
||||
|
||||
void TelnetDebugger::disconnect()
|
||||
{
|
||||
if ( mDebugSocket != InvalidSocket )
|
||||
{
|
||||
Net::closeSocket(mDebugSocket);
|
||||
mDebugSocket = InvalidSocket;
|
||||
}
|
||||
|
||||
removeAllBreakpoints();
|
||||
|
||||
mState = NotConnected;
|
||||
mProgramPaused = false;
|
||||
}
|
||||
|
||||
void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient)
|
||||
{
|
||||
// Don't bail if same port... we might just be wanting to change
|
||||
// the password.
|
||||
// if(port == mAcceptPort)
|
||||
// return;
|
||||
|
||||
if(mAcceptSocket != InvalidSocket)
|
||||
{
|
||||
Net::closeSocket(mAcceptSocket);
|
||||
mAcceptSocket = InvalidSocket;
|
||||
}
|
||||
mAcceptPort = port;
|
||||
if(mAcceptPort != -1 && mAcceptPort != 0)
|
||||
{
|
||||
mAcceptSocket = Net::openSocket();
|
||||
Net::bind(mAcceptSocket, mAcceptPort);
|
||||
Net::listen(mAcceptSocket, 4);
|
||||
|
||||
Net::setBlocking(mAcceptSocket, false);
|
||||
}
|
||||
dStrncpy(mDebuggerPassword, password, PasswordMaxLength);
|
||||
|
||||
mWaitForClient = waitForClient;
|
||||
if ( !mWaitForClient )
|
||||
return;
|
||||
|
||||
// Wait for the client to fully connect.
|
||||
while ( mState != Connected )
|
||||
{
|
||||
Platform::sleep(10);
|
||||
process();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TelnetDebugger::processConsoleLine(const char *consoleLine)
|
||||
{
|
||||
if(mState != NotConnected)
|
||||
{
|
||||
send("COUT ");
|
||||
send(consoleLine);
|
||||
send("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::process()
|
||||
{
|
||||
NetAddress address;
|
||||
|
||||
if(mAcceptSocket != InvalidSocket)
|
||||
{
|
||||
// ok, see if we have any new connections:
|
||||
NetSocket newConnection;
|
||||
newConnection = Net::accept(mAcceptSocket, &address);
|
||||
|
||||
if(newConnection != InvalidSocket && mDebugSocket == InvalidSocket)
|
||||
{
|
||||
Con::printf ("Debugger connection from %i.%i.%i.%i",
|
||||
address.netNum[0], address.netNum[1], address.netNum[2], address.netNum[3]);
|
||||
|
||||
mState = PasswordTry;
|
||||
mDebugSocket = newConnection;
|
||||
|
||||
Net::setBlocking(newConnection, false);
|
||||
}
|
||||
else if(newConnection != InvalidSocket)
|
||||
Net::closeSocket(newConnection);
|
||||
}
|
||||
// see if we have any input to process...
|
||||
|
||||
if(mDebugSocket == InvalidSocket)
|
||||
return;
|
||||
|
||||
checkDebugRecv();
|
||||
if(mDebugSocket == InvalidSocket)
|
||||
removeAllBreakpoints();
|
||||
}
|
||||
|
||||
void TelnetDebugger::checkDebugRecv()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
// Process all the complete commands in the buffer.
|
||||
while ( mCurPos > 0 )
|
||||
{
|
||||
// Remove leading whitespace.
|
||||
while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) )
|
||||
{
|
||||
mCurPos--;
|
||||
dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos);
|
||||
}
|
||||
|
||||
// Look for a complete command.
|
||||
bool gotCmd = false;
|
||||
for(S32 i = 0; i < mCurPos; i++)
|
||||
{
|
||||
if( mLineBuffer[i] == 0 )
|
||||
mLineBuffer[i] = '_';
|
||||
|
||||
else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' )
|
||||
{
|
||||
// Send this command to be processed.
|
||||
mLineBuffer[i] = '\n';
|
||||
processLineBuffer(i+1);
|
||||
|
||||
// Remove the command from the buffer.
|
||||
mCurPos -= i + 1;
|
||||
dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos);
|
||||
|
||||
gotCmd = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find a command in this pass
|
||||
// then we have an incomplete buffer.
|
||||
if ( !gotCmd )
|
||||
break;
|
||||
}
|
||||
|
||||
// found no <CR> or <LF>
|
||||
if(mCurPos == MaxCommandSize) // this shouldn't happen
|
||||
{
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
S32 numBytes;
|
||||
Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes);
|
||||
|
||||
if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
|
||||
{
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
if(err == Net::WouldBlock)
|
||||
return;
|
||||
|
||||
mCurPos += numBytes;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber)
|
||||
{
|
||||
if(mProgramPaused)
|
||||
return;
|
||||
|
||||
if(mBreakOnNextStatement)
|
||||
{
|
||||
setBreakOnNextStatement( false );
|
||||
breakProcess();
|
||||
return;
|
||||
}
|
||||
|
||||
Breakpoint **bp = findBreakpoint(code->name, lineNumber);
|
||||
if(!bp)
|
||||
return;
|
||||
|
||||
Breakpoint *brk = *bp;
|
||||
mProgramPaused = true;
|
||||
Con::evaluatef("$Debug::result = %s;", brk->testExpression);
|
||||
if(Con::getBoolVariable("$Debug::result"))
|
||||
{
|
||||
brk->curCount++;
|
||||
if(brk->curCount >= brk->passCount)
|
||||
{
|
||||
brk->curCount = 0;
|
||||
if(brk->clearOnHit)
|
||||
removeBreakpoint(code->name, lineNumber);
|
||||
breakProcess();
|
||||
}
|
||||
}
|
||||
mProgramPaused = false;
|
||||
}
|
||||
|
||||
void TelnetDebugger::pushStackFrame()
|
||||
{
|
||||
if(mState == NotConnected)
|
||||
return;
|
||||
|
||||
if(mBreakOnNextStatement && mStackPopBreakIndex > -1 &&
|
||||
gEvalState.stack.size() > mStackPopBreakIndex)
|
||||
setBreakOnNextStatement( false );
|
||||
}
|
||||
|
||||
void TelnetDebugger::popStackFrame()
|
||||
{
|
||||
if(mState == NotConnected)
|
||||
return;
|
||||
|
||||
if(mStackPopBreakIndex > -1 && gEvalState.stack.size()-1 <= mStackPopBreakIndex)
|
||||
setBreakOnNextStatement( true );
|
||||
}
|
||||
|
||||
void TelnetDebugger::breakProcess()
|
||||
{
|
||||
// Send out a break with the full stack.
|
||||
sendBreak();
|
||||
|
||||
mProgramPaused = true;
|
||||
while(mProgramPaused)
|
||||
{
|
||||
Platform::sleep(10);
|
||||
checkDebugRecv();
|
||||
if(mDebugSocket == InvalidSocket)
|
||||
{
|
||||
mProgramPaused = false;
|
||||
removeAllBreakpoints();
|
||||
debugContinue();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::sendBreak()
|
||||
{
|
||||
// echo out the break
|
||||
send("BREAK");
|
||||
char buffer[MaxCommandSize];
|
||||
char scope[MaxCommandSize];
|
||||
|
||||
S32 last = 0;
|
||||
|
||||
for(S32 i = (S32) gEvalState.stack.size() - 1; i >= last; i--)
|
||||
{
|
||||
CodeBlock *code = gEvalState.stack[i]->code;
|
||||
const char *file = "<none>";
|
||||
if (code && code->name && code->name[0])
|
||||
file = code->name;
|
||||
|
||||
Namespace *ns = gEvalState.stack[i]->scopeNamespace;
|
||||
scope[0] = 0;
|
||||
if ( ns ) {
|
||||
|
||||
if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) {
|
||||
dStrcat( scope, ns->mParent->mPackage );
|
||||
dStrcat( scope, "::" );
|
||||
}
|
||||
if ( ns->mName && ns->mName[0] ) {
|
||||
dStrcat( scope, ns->mName );
|
||||
dStrcat( scope, "::" );
|
||||
}
|
||||
}
|
||||
|
||||
const char *function = gEvalState.stack[i]->scopeName;
|
||||
if ((!function) || (!function[0]))
|
||||
function = "<none>";
|
||||
dStrcat( scope, function );
|
||||
|
||||
U32 line=0, inst;
|
||||
U32 ip = gEvalState.stack[i]->ip;
|
||||
if (code)
|
||||
code->findBreakLine(ip, line, inst);
|
||||
dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope);
|
||||
send(buffer);
|
||||
}
|
||||
|
||||
send("\r\n");
|
||||
}
|
||||
|
||||
void TelnetDebugger::processLineBuffer(S32 cmdLen)
|
||||
{
|
||||
if (mState == PasswordTry)
|
||||
{
|
||||
if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1))
|
||||
{
|
||||
// failed password:
|
||||
send("PASS WrongPassword.\r\n");
|
||||
disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
send("PASS Connected.\r\n");
|
||||
mState = mWaitForClient ? Initialize : Connected;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
char evalBuffer[MaxCommandSize];
|
||||
char varBuffer[MaxCommandSize];
|
||||
char fileBuffer[MaxCommandSize];
|
||||
char clear[MaxCommandSize];
|
||||
S32 passCount, line, frame;
|
||||
|
||||
if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1)
|
||||
{
|
||||
ConsoleEvent postEvent;
|
||||
dStrcpy(postEvent.data, evalBuffer);
|
||||
postEvent.size = ConsoleEventHeaderSize + dStrlen(evalBuffer) + 1;
|
||||
Game->postEvent(postEvent);
|
||||
}
|
||||
else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3)
|
||||
addVariableBreakpoint(varBuffer, passCount, evalBuffer);
|
||||
else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1)
|
||||
removeVariableBreakpoint(varBuffer);
|
||||
else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5)
|
||||
addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer);
|
||||
else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2)
|
||||
removeBreakpoint(fileBuffer, line);
|
||||
else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen))
|
||||
removeAllBreakpoints();
|
||||
else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen))
|
||||
debugBreakNext();
|
||||
else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen))
|
||||
debugContinue();
|
||||
else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen))
|
||||
debugStepIn();
|
||||
else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen))
|
||||
debugStepOver();
|
||||
else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen))
|
||||
debugStepOut();
|
||||
else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3)
|
||||
evaluateExpression(varBuffer, frame, evalBuffer);
|
||||
else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen))
|
||||
dumpFileList();
|
||||
else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1)
|
||||
dumpBreakableList(fileBuffer);
|
||||
else
|
||||
{
|
||||
// invalid stuff.
|
||||
send("DBGERR Invalid command!\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*)
|
||||
{
|
||||
send("addVariableBreakpoint\r\n");
|
||||
}
|
||||
|
||||
void TelnetDebugger::removeVariableBreakpoint(const char*)
|
||||
{
|
||||
send("removeVariableBreakpoint\r\n");
|
||||
}
|
||||
|
||||
void TelnetDebugger::addAllBreakpoints(CodeBlock *code)
|
||||
{
|
||||
if(mState == NotConnected)
|
||||
return;
|
||||
|
||||
char buffer[MaxCommandSize];
|
||||
dSprintf(buffer, MaxCommandSize, "LOAD %s\r\n", code->name);
|
||||
send(buffer);
|
||||
|
||||
// Find the breakpoints for this code block and attach them.
|
||||
Breakpoint **walk = &mBreakpoints;
|
||||
Breakpoint *cur;
|
||||
while((cur = *walk) != NULL)
|
||||
{
|
||||
if(cur->fileName == code->name)
|
||||
{
|
||||
cur->code = code;
|
||||
|
||||
// Find the fist breakline starting from and
|
||||
// including the requested breakline.
|
||||
S32 newLine = code->findFirstBreakLine(cur->lineNumber);
|
||||
if (newLine <= 0)
|
||||
{
|
||||
walk = &cur->next;
|
||||
|
||||
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
|
||||
send(buffer);
|
||||
|
||||
removeBreakpoint(cur->fileName, cur->lineNumber);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the requested breakline does not match
|
||||
// the actual break line we need to inform
|
||||
// the client.
|
||||
if (newLine != cur->lineNumber)
|
||||
{
|
||||
// If we already have a line at this breapoint then
|
||||
// tell the client to clear the breakpoint.
|
||||
if ( findBreakpoint(cur->fileName, newLine) ) {
|
||||
|
||||
walk = &cur->next;
|
||||
|
||||
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
|
||||
send(buffer);
|
||||
|
||||
removeBreakpoint(cur->fileName, cur->lineNumber);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We're moving the breakpoint to new line... inform the
|
||||
// client so it can update it's view.
|
||||
dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine);
|
||||
send(buffer);
|
||||
cur->lineNumber = newLine;
|
||||
}
|
||||
|
||||
code->setBreakpoint(cur->lineNumber);
|
||||
}
|
||||
|
||||
walk = &cur->next;
|
||||
}
|
||||
|
||||
// Enable all breaks if a break next was set.
|
||||
if (mBreakOnNextStatement)
|
||||
code->setAllBreaks();
|
||||
}
|
||||
|
||||
void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code)
|
||||
{
|
||||
Breakpoint **walk = &mBreakpoints;
|
||||
Breakpoint *cur;
|
||||
|
||||
while((cur = *walk) != NULL)
|
||||
{
|
||||
if(cur->code == code)
|
||||
cur->code = NULL;
|
||||
|
||||
walk = &cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString)
|
||||
{
|
||||
fileName = StringTable->insert(fileName);
|
||||
Breakpoint **bp = findBreakpoint(fileName, line);
|
||||
|
||||
if(bp)
|
||||
{
|
||||
// trying to add the same breakpoint...
|
||||
Breakpoint *brk = *bp;
|
||||
dFree(brk->testExpression);
|
||||
brk->testExpression = dStrdup(evalString);
|
||||
brk->passCount = passCount;
|
||||
brk->clearOnHit = clear;
|
||||
brk->curCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note that if the code block is not already
|
||||
// loaded it is handled by addAllBreakpoints.
|
||||
CodeBlock* code = CodeBlock::find(fileName);
|
||||
if (code)
|
||||
{
|
||||
// Find the fist breakline starting from and
|
||||
// including the requested breakline.
|
||||
S32 newLine = code->findFirstBreakLine(line);
|
||||
if (newLine <= 0)
|
||||
{
|
||||
char buffer[MaxCommandSize];
|
||||
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
|
||||
send(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the requested breakline does not match
|
||||
// the actual break line we need to inform
|
||||
// the client.
|
||||
if (newLine != line)
|
||||
{
|
||||
char buffer[MaxCommandSize];
|
||||
|
||||
// If we already have a line at this breapoint then
|
||||
// tell the client to clear the breakpoint.
|
||||
if ( findBreakpoint(fileName, newLine) ) {
|
||||
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
|
||||
send(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're moving the breakpoint to new line... inform the client.
|
||||
dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine);
|
||||
send(buffer);
|
||||
line = newLine;
|
||||
}
|
||||
|
||||
code->setBreakpoint(line);
|
||||
}
|
||||
|
||||
Breakpoint *brk = new Breakpoint;
|
||||
brk->code = code;
|
||||
brk->fileName = fileName;
|
||||
brk->lineNumber = line;
|
||||
brk->passCount = passCount;
|
||||
brk->clearOnHit = clear;
|
||||
brk->curCount = 0;
|
||||
brk->testExpression = dStrdup(evalString);
|
||||
brk->next = mBreakpoints;
|
||||
mBreakpoints = brk;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code)
|
||||
{
|
||||
Breakpoint **walk = &mBreakpoints;
|
||||
Breakpoint *cur;
|
||||
while((cur = *walk) != NULL)
|
||||
{
|
||||
if(cur->code == code)
|
||||
{
|
||||
dFree(cur->testExpression);
|
||||
*walk = cur->next;
|
||||
delete walk;
|
||||
}
|
||||
else
|
||||
walk = &cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line)
|
||||
{
|
||||
fileName = StringTable->insert(fileName);
|
||||
Breakpoint **bp = findBreakpoint(fileName, line);
|
||||
if(bp)
|
||||
{
|
||||
Breakpoint *brk = *bp;
|
||||
*bp = brk->next;
|
||||
if ( brk->code )
|
||||
brk->code->clearBreakpoint(brk->lineNumber);
|
||||
dFree(brk->testExpression);
|
||||
delete brk;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::removeAllBreakpoints()
|
||||
{
|
||||
Breakpoint *walk = mBreakpoints;
|
||||
while(walk)
|
||||
{
|
||||
Breakpoint *temp = walk->next;
|
||||
if ( walk->code )
|
||||
walk->code->clearBreakpoint(walk->lineNumber);
|
||||
dFree(walk->testExpression);
|
||||
delete walk;
|
||||
walk = temp;
|
||||
}
|
||||
mBreakpoints = NULL;
|
||||
}
|
||||
|
||||
void TelnetDebugger::debugContinue()
|
||||
{
|
||||
if (mState == Initialize) {
|
||||
mState = Connected;
|
||||
return;
|
||||
}
|
||||
|
||||
setBreakOnNextStatement( false );
|
||||
mStackPopBreakIndex = -1;
|
||||
mProgramPaused = false;
|
||||
send("RUNNING\r\n");
|
||||
}
|
||||
|
||||
void TelnetDebugger::setBreakOnNextStatement( bool enabled )
|
||||
{
|
||||
if ( enabled )
|
||||
{
|
||||
// Apply breaks on all the code blocks.
|
||||
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
|
||||
walk->setAllBreaks();
|
||||
mBreakOnNextStatement = true;
|
||||
}
|
||||
else if ( !enabled )
|
||||
{
|
||||
// Clear all the breaks on the codeblocks
|
||||
// then go reapply the breakpoints.
|
||||
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
|
||||
walk->clearAllBreaks();
|
||||
for(Breakpoint *w = mBreakpoints; w; w = w->next)
|
||||
{
|
||||
if ( w->code )
|
||||
w->code->setBreakpoint(w->lineNumber);
|
||||
}
|
||||
mBreakOnNextStatement = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetDebugger::debugBreakNext()
|
||||
{
|
||||
if (mState != Connected)
|
||||
return;
|
||||
|
||||
if ( !mProgramPaused )
|
||||
setBreakOnNextStatement( true );
|
||||
}
|
||||
|
||||
void TelnetDebugger::debugStepIn()
|
||||
{
|
||||
// Note that step in is allowed during
|
||||
// the initialize state, so that we can
|
||||
// break on the first script line executed.
|
||||
|
||||
setBreakOnNextStatement( true );
|
||||
mStackPopBreakIndex = -1;
|
||||
mProgramPaused = false;
|
||||
|
||||
// Don't bother sending this to the client
|
||||
// if it's in the initialize state. It will
|
||||
// just be ignored as the client knows it
|
||||
// is in a running state when it connects.
|
||||
if (mState != Initialize)
|
||||
send("RUNNING\r\n");
|
||||
else
|
||||
mState = Connected;
|
||||
}
|
||||
|
||||
void TelnetDebugger::debugStepOver()
|
||||
{
|
||||
if (mState != Connected)
|
||||
return;
|
||||
|
||||
setBreakOnNextStatement( true );
|
||||
mStackPopBreakIndex = gEvalState.stack.size();
|
||||
mProgramPaused = false;
|
||||
send("RUNNING\r\n");
|
||||
}
|
||||
|
||||
void TelnetDebugger::debugStepOut()
|
||||
{
|
||||
if (mState != Connected)
|
||||
return;
|
||||
|
||||
setBreakOnNextStatement( false );
|
||||
mStackPopBreakIndex = gEvalState.stack.size() - 1;
|
||||
if ( mStackPopBreakIndex == 0 )
|
||||
mStackPopBreakIndex = -1;
|
||||
mProgramPaused = false;
|
||||
send("RUNNING\r\n");
|
||||
}
|
||||
|
||||
void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer)
|
||||
{
|
||||
// Make sure we're passing a valid frame to the eval.
|
||||
if ( frame > gEvalState.stack.size() )
|
||||
frame = gEvalState.stack.size() - 1;
|
||||
if ( frame < 0 )
|
||||
frame = 0;
|
||||
|
||||
// Build a buffer just big enough for this eval.
|
||||
const char* format = "return %s;";
|
||||
dsize_t len = dStrlen( format ) + dStrlen( evalBuffer );
|
||||
char* buffer = new char[ len ];
|
||||
dSprintf( buffer, len, format, evalBuffer );
|
||||
|
||||
// Execute the eval.
|
||||
CodeBlock *newCodeBlock = new CodeBlock();
|
||||
const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame );
|
||||
delete [] buffer;
|
||||
|
||||
// Create a new buffer that fits the result.
|
||||
format = "EVALOUT %s %s\r\n";
|
||||
len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result );
|
||||
buffer = new char[ len ];
|
||||
dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" );
|
||||
|
||||
send( buffer );
|
||||
delete [] buffer;
|
||||
}
|
||||
|
||||
void TelnetDebugger::dumpFileList()
|
||||
{
|
||||
send("FILELISTOUT ");
|
||||
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
|
||||
{
|
||||
send(walk->name);
|
||||
if(walk->nextFile)
|
||||
send(" ");
|
||||
}
|
||||
send("\r\n");
|
||||
}
|
||||
|
||||
void TelnetDebugger::dumpBreakableList(const char *fileName)
|
||||
{
|
||||
fileName = StringTable->insert(fileName);
|
||||
CodeBlock *file = CodeBlock::find(fileName);
|
||||
char buffer[MaxCommandSize];
|
||||
if(file)
|
||||
{
|
||||
dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1);
|
||||
send(buffer);
|
||||
for(U32 i = 0; i < file->breakListSize; i += 2)
|
||||
{
|
||||
dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]);
|
||||
send(buffer);
|
||||
}
|
||||
send("\r\n");
|
||||
}
|
||||
else
|
||||
send("DBGERR No such file!\r\n");
|
||||
}
|
116
engine/console/telnetDebugger.h
Executable file
116
engine/console/telnetDebugger.h
Executable file
@ -0,0 +1,116 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _TELNETDEBUGGER_H_
|
||||
#define _TELNETDEBUGGER_H_
|
||||
|
||||
class CodeBlock;
|
||||
|
||||
/// Telnet debug service implementation.
|
||||
///
|
||||
/// This is the C++ side of the built-in Torque debugger.
|
||||
///
|
||||
/// To use the debugger, use dbgSetParameters(port, password); in the console
|
||||
/// of the server to enable debugger connections. Then on some other system,
|
||||
/// start up the app (you don't have to start a game or connect to the
|
||||
/// server) and exec("common/debugger/debugger.cs"); in the console. Then use
|
||||
/// the debugger GUI to connect to the server with the right port and password.
|
||||
///
|
||||
/// @see http://www.planettribes.com/tribes2/editing.shtml for more thorough discussion.
|
||||
class TelnetDebugger
|
||||
{
|
||||
S32 mAcceptPort;
|
||||
NetSocket mAcceptSocket;
|
||||
NetSocket mDebugSocket;
|
||||
|
||||
enum {
|
||||
|
||||
// We should only change this if we truely
|
||||
// break the protocol in a future version.
|
||||
Version = 2,
|
||||
|
||||
PasswordMaxLength = 32,
|
||||
MaxCommandSize = 2048
|
||||
};
|
||||
|
||||
char mDebuggerPassword[PasswordMaxLength+1];
|
||||
enum State
|
||||
{
|
||||
NotConnected,
|
||||
PasswordTry,
|
||||
Initialize,
|
||||
Connected
|
||||
};
|
||||
S32 mState;
|
||||
char mLineBuffer[MaxCommandSize];
|
||||
S32 mCurPos;
|
||||
bool mWaitForClient;
|
||||
|
||||
TelnetDebugger();
|
||||
~TelnetDebugger();
|
||||
|
||||
struct Breakpoint
|
||||
{
|
||||
StringTableEntry fileName;
|
||||
CodeBlock *code;
|
||||
U32 lineNumber;
|
||||
S32 passCount;
|
||||
S32 curCount;
|
||||
char *testExpression;
|
||||
bool clearOnHit;
|
||||
Breakpoint *next;
|
||||
};
|
||||
Breakpoint *mBreakpoints;
|
||||
|
||||
Breakpoint **findBreakpoint(StringTableEntry fileName, S32 lineNumber);
|
||||
|
||||
bool mProgramPaused;
|
||||
bool mBreakOnNextStatement;
|
||||
S32 mStackPopBreakIndex;
|
||||
|
||||
void addVariableBreakpoint(const char *varName, S32 passCount, const char *evalString);
|
||||
void removeVariableBreakpoint(const char *varName);
|
||||
void addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString);
|
||||
void removeBreakpoint(const char *fileName, S32 line);
|
||||
void removeAllBreakpoints();
|
||||
|
||||
void debugBreakNext();
|
||||
void debugContinue();
|
||||
void debugStepIn();
|
||||
void debugStepOver();
|
||||
void debugStepOut();
|
||||
void evaluateExpression(const char *tag, S32 frame, const char *evalBuffer);
|
||||
void dumpFileList();
|
||||
void dumpBreakableList(const char *fileName);
|
||||
void removeBreakpointsFromCode(CodeBlock *code);
|
||||
|
||||
void sendBreak();
|
||||
|
||||
void checkDebugRecv();
|
||||
void processLineBuffer(S32);
|
||||
void breakProcess();
|
||||
void setBreakOnNextStatement( bool enabled );
|
||||
public:
|
||||
static void create();
|
||||
static void destroy();
|
||||
|
||||
void disconnect();
|
||||
bool isConnected() const { return mState == Connected; }
|
||||
|
||||
void process();
|
||||
void popStackFrame();
|
||||
void pushStackFrame();
|
||||
void addAllBreakpoints(CodeBlock *code);
|
||||
void clearCodeBlockPointers(CodeBlock *code);
|
||||
|
||||
virtual void executionStopped(CodeBlock *code, U32 lineNumber);
|
||||
void send(const char *s);
|
||||
void setDebugParameters(S32 port, const char *password, bool waitForClient);
|
||||
void processConsoleLine(const char *consoleLine);
|
||||
};
|
||||
|
||||
extern TelnetDebugger *TelDebugger;
|
||||
|
||||
#endif
|
70
engine/console/typeValidators.cc
Executable file
70
engine/console/typeValidators.cc
Executable file
@ -0,0 +1,70 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleObject.h"
|
||||
#include "console/typeValidators.h"
|
||||
#include "console/simBase.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
void TypeValidator::consoleError(SimObject *object, const char *format, ...)
|
||||
{
|
||||
char buffer[1024];
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
dVsprintf(buffer, sizeof(buffer), format, argptr);
|
||||
va_end(argptr);
|
||||
|
||||
AbstractClassRep *rep = object->getClassRep();
|
||||
AbstractClassRep::Field &fld = rep->mFieldList[fieldIndex];
|
||||
const char *objectName = object->getName();
|
||||
if(!objectName)
|
||||
objectName = "unnamed";
|
||||
|
||||
|
||||
Con::warnf("%s - %s(%d) - invalid value for %s: %s",
|
||||
rep->getClassName(), objectName, object->getId(), fld.pFieldname, buffer);
|
||||
}
|
||||
|
||||
void FRangeValidator::validateType(SimObject *object, void *typePtr)
|
||||
{
|
||||
F32 *v = (F32 *) typePtr;
|
||||
if(*v < minV || *v > maxV)
|
||||
{
|
||||
consoleError(object, "Must be between %g and %g", minV, maxV);
|
||||
if(*v < minV)
|
||||
*v = minV;
|
||||
else if(*v > maxV)
|
||||
*v = maxV;
|
||||
}
|
||||
}
|
||||
|
||||
void IRangeValidator::validateType(SimObject *object, void *typePtr)
|
||||
{
|
||||
S32 *v = (S32 *) typePtr;
|
||||
if(*v < minV || *v > maxV)
|
||||
{
|
||||
consoleError(object, "Must be between %d and %d", minV, maxV);
|
||||
if(*v < minV)
|
||||
*v = minV;
|
||||
else if(*v > maxV)
|
||||
*v = maxV;
|
||||
}
|
||||
}
|
||||
|
||||
void IRangeValidatorScaled::validateType(SimObject *object, void *typePtr)
|
||||
{
|
||||
S32 *v = (S32 *) typePtr;
|
||||
*v /= factor;
|
||||
if(*v < minV || *v > maxV)
|
||||
{
|
||||
consoleError(object, "Scaled value must be between %d and %d", minV, maxV);
|
||||
if(*v < minV)
|
||||
*v = minV;
|
||||
else if(*v > maxV)
|
||||
*v = maxV;
|
||||
}
|
||||
}
|
72
engine/console/typeValidators.h
Executable file
72
engine/console/typeValidators.h
Executable file
@ -0,0 +1,72 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _TYPEVALIDATORS_H_
|
||||
#define _TYPEVALIDATORS_H_
|
||||
|
||||
class TypeValidator
|
||||
{
|
||||
public:
|
||||
S32 fieldIndex;
|
||||
|
||||
/// Prints a console error message for the validator.
|
||||
///
|
||||
/// The message is prefaced with with:
|
||||
/// @code
|
||||
/// className objectName (objectId) - invalid value for fieldName: msg
|
||||
/// @endcode
|
||||
void consoleError(SimObject *object, const char *format, ...);
|
||||
|
||||
/// validateType is called for each assigned value on the field this
|
||||
/// validator is attached to.
|
||||
virtual void validateType(SimObject *object, void *typePtr) = 0;
|
||||
};
|
||||
|
||||
|
||||
/// Floating point min/max range validator
|
||||
class FRangeValidator : public TypeValidator
|
||||
{
|
||||
F32 minV, maxV;
|
||||
public:
|
||||
FRangeValidator(F32 minValue, F32 maxValue)
|
||||
{
|
||||
minV = minValue;
|
||||
maxV = maxValue;
|
||||
}
|
||||
void validateType(SimObject *object, void *typePtr);
|
||||
};
|
||||
|
||||
/// Signed integer min/max range validator
|
||||
class IRangeValidator : public TypeValidator
|
||||
{
|
||||
S32 minV, maxV;
|
||||
public:
|
||||
IRangeValidator(S32 minValue, S32 maxValue)
|
||||
{
|
||||
minV = minValue;
|
||||
maxV = maxValue;
|
||||
}
|
||||
void validateType(SimObject *object, void *typePtr);
|
||||
};
|
||||
|
||||
/// Scaled integer field validator
|
||||
///
|
||||
/// @note This should NOT be used on a field that gets exported -
|
||||
/// the field is only validated once on initial assignment
|
||||
class IRangeValidatorScaled : public TypeValidator
|
||||
{
|
||||
S32 minV, maxV;
|
||||
S32 factor;
|
||||
public:
|
||||
IRangeValidatorScaled(S32 scaleFactor, S32 minValueScaled, S32 maxValueScaled)
|
||||
{
|
||||
minV = minValueScaled;
|
||||
maxV = maxValueScaled;
|
||||
factor = scaleFactor;
|
||||
}
|
||||
void validateType(SimObject *object, void *typePtr);
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user