Torque3D/Engine/source/console/torquescript/CMDgram.y
2026-04-12 11:17:59 +01:00

713 lines
23 KiB
Text

%define parse.error custom
%locations
%define api.header.include {"CMDgram.h"}
%{
// bison --defines=cmdgram.h --verbose -o cmdgram.cpp -p CMD CMDgram.y
// Make sure we don't get gram.h twice.
#define _CMDGRAM_H_
#include <stdlib.h>
#include <stdio.h>
#include "console/console.h"
#include "console/torquescript/compiler.h"
#include "console/consoleInternal.h"
#include "core/strings/stringFunctions.h"
#ifndef YYDEBUG
#define YYDEBUG 0
#endif
#define YYSSIZE 350
int outtext(char *fmt, ...);
extern int serrors;
extern Vector<String> lines;
#define nil 0
#undef YY_ARGS
#define YY_ARGS(x) x
int CMDlex();
void CMDerror(const char *, ...);
#ifdef alloca
#undef alloca
#endif
#define alloca dMalloc
template< typename T >
struct Token
{
T value;
U32 lineNumber;
};
%}
%{
/* Reserved word token definitions */
%}
%token <i> rwDEFINE rwENDDEF rwDECLARE rwDECLARESINGLETON
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
%token <i> rwIF rwNIL rwRETURN rwWHILE rwDO
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
%token <i> rwFOR rwFOREACH rwFOREACHSTR rwIN rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
%token <i> rwCASEOR rwPACKAGE
%token <i> rwASSERT
%token ILLEGAL_TOKEN
// NOTE: rwNAMESPACE and rwCLASS were declared here previously but had no
// lexer rules and appeared in no grammar productions. They have been
// removed. If namespace/class syntax is added in future, add both the
// token declaration AND the lexer rule at the same time.
%{
/* Constant and identifier token definitions */
%}
%token <c> CHRCONST
%token <i> INTCONST
%token <s> TTAG
%token <s> VAR
%token <s> IDENT
%token <i> TYPEIDENT
%token <str> DOCBLOCK
%token <str> STRATOM
%token <str> TAGATOM
%token <f> FLTCONST
%{
/* Operator token definitions */
%}
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
%token <i> opINTNAME opINTNAMER
%token <i> opMINUSMINUS opPLUSPLUS
// NOTE: STMT_SEP was declared here but never returned by the lexer and never
// used in any grammar production. Removed to prevent confusion.
%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
// FIX: opSTREQ and opSTRNE must be declared with their semantic type <i>.
// Previously opSTRNE was only mentioned in the %left precedence line, which
// does declare it as a token but gives it no type — causing a silent type
// mismatch when used in grammar rules (even if $2 isn't used in the action,
// the generated parser code is technically undefined behaviour).
%token <i> opSTREQ opSTRNE
%token <i> opCOLONCOLON
%union {
Token< char > c;
Token< int > i;
Token< const char* > s;
Token< char* > str;
Token< double > f;
StmtNode* stmt;
ExprNode* expr;
SlotAssignNode* slist;
VarNode* var;
SlotDecl slot;
InternalSlotDecl intslot;
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> assert_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> foreach_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_opt
%type <slist> slot_assign_list
%type <slist> slot_assign
%type <slot> slot_acc
%type <intslot> intslot_acc
%type <stmt> expression_stmt
%type <var> param
%type <var> var_list
%type <var> var_list_decl
%type <asn> assign_op_struct
// Operator precedence — lowest to highest.
// FIX: opMDASN, opNDASN, opNTASN were listed here but were never defined
// as tokens anywhere and were never returned by the lexer. They appear to
// be leftovers from an earlier revision. Removed to prevent compiler
// warnings about undeclared token names.
%left '['
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN 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 '.'
%left opINTNAME opINTNAMER
%%
start
: decl_list
{ }
;
decl_list
:
{ $$ = nil; }
| decl_list decl
{ if(!Script::gStatementList) { Script::gStatementList = $2; } else { Script::gStatementList->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.value); }
;
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
| foreach_stmt
| datablock_decl
| switch_stmt
| rwBREAK ';'
{ $$ = BreakStmtNode::alloc( $1.lineNumber ); }
| rwCONTINUE ';'
{ $$ = ContinueStmtNode::alloc( $1.lineNumber ); }
| rwRETURN ';'
{ $$ = ReturnStmtNode::alloc( $1.lineNumber, NULL ); }
| rwRETURN expr ';'
{ $$ = ReturnStmtNode::alloc( $1.lineNumber, $2 ); }
| expression_stmt ';'
{ $$ = $1; }
| TTAG '=' expr ';'
{ $$ = TTagSetStmtNode::alloc( $1.lineNumber, $1.value, $3, NULL ); }
| TTAG '=' expr ',' expr ';'
{ $$ = TTagSetStmtNode::alloc( $1.lineNumber, $1.value, $3, $5 ); }
| DOCBLOCK
{ $$ = StrConstNode::alloc( $1.lineNumber, $1.value, false, true ); }
;
fn_decl_stmt
// Global function
: rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc( $1.lineNumber, $2.value, NULL, $4, $7 ); }
// Namespaced method: function Namespace::name(...) { }
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc( $1.lineNumber, $4.value, $2.value, $6, $9 ); }
;
var_list_decl
:
{ $$ = NULL; }
| var_list
{ $$ = $1; }
;
var_list
: param
{ $$ = $1; }
| var_list ',' param
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)$3 ); }
;
// Parameter declaration forms:
//
// %var — required parameter
// %var ? — optional parameter, evaluates to "" / 0 when absent
// %var = expr — optional parameter with default value
// %var ? = expr — same as above; the '?' makes the optionality explicit
//
// NOTE: the default `expr` can be any valid expression, including function
// calls and variable references. At present these are evaluated once at
// declaration time (global scope). The planned codelet change (see
// FunctionDeclStmtNode::compileStmt in ast.cpp) will evaluate them at
// each call site instead — no grammar change is required for that fix.
param
: VAR
{ $$ = VarNode::allocParam($1.lineNumber, $1.value, NULL); }
| VAR '?'
{ $$ = VarNode::allocParam($1.lineNumber, $1.value, NULL); }
| VAR '=' expr
{ $$ = VarNode::allocParam($1.lineNumber, $1.value, $3); }
| VAR '?' '=' expr
{ $$ = VarNode::allocParam($1.lineNumber, $1.value, $4); }
;
datablock_decl
: rwDATABLOCK class_name_expr '(' expr parent_block ')' '{' slot_assign_list_opt '}' ';'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $4, NULL, $5.value, $8, NULL, true, false, false); }
;
object_decl
: rwDECLARE class_name_expr '(' object_name parent_block object_args ')' '{' object_declare_block '}'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $4, $6, $5.value, $9.slots, $9.decls, false, false, false); }
| rwDECLARE class_name_expr '(' object_name parent_block object_args ')'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $4, $6, $5.value, NULL, NULL, false, false, false); }
| rwDECLARE class_name_expr '(' '[' object_name ']' parent_block object_args ')' '{' object_declare_block '}'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $5, $8, $7.value, $11.slots, $11.decls, false, true, false); }
| rwDECLARE class_name_expr '(' '[' object_name ']' parent_block object_args ')'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $5, $8, $7.value, NULL, NULL, false, true, false); }
| rwDECLARESINGLETON class_name_expr '(' object_name parent_block object_args ')' '{' object_declare_block '}'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $4, $6, $5.value, $9.slots, $9.decls, false, false, true); }
| rwDECLARESINGLETON class_name_expr '(' object_name parent_block object_args ')'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $4, $6, $5.value, NULL, NULL, false, false, true); }
;
parent_block
:
{ $$.value = NULL; }
| ':' IDENT
{ $$ = $2; }
;
object_name
:
{ $$ = StrConstNode::alloc( CodeBlock::smCurrentParser->getCurrentLine(), "", 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); }
;
// NOTE: propagateSwitchExpr builds a recursive OR expression tree that is
// O(n) deep for n cases. Large switch statements (100+ cases) can overflow
// the compiler stack.
case_block
: rwCASE case_expr ':' statement_list
{ $$ = IfStmtNode::alloc( $1.lineNumber, $2, $4, NULL, false); }
| rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list
{ $$ = IfStmtNode::alloc( $1.lineNumber, $2, $4, $7, false); }
| rwCASE case_expr ':' statement_list case_block
{ $$ = IfStmtNode::alloc( $1.lineNumber, $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.lineNumber, $3, $5, NULL, false); }
| rwIF '(' expr ')' stmt_block rwELSE stmt_block
{ $$ = IfStmtNode::alloc($1.lineNumber, $3, $5, $7, false); }
;
while_stmt
: rwWHILE '(' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, nil, $3, nil, $5, false); }
| rwDO stmt_block rwWHILE '(' expr ')'
{ $$ = LoopStmtNode::alloc($3.lineNumber, nil, $5, nil, $2, true); }
;
for_stmt
: rwFOR '(' expr ';' expr ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, $3, $5, $7, $9, false); }
| rwFOR '(' expr ';' expr ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, $3, $5, NULL, $8, false); }
| rwFOR '(' expr ';' ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, $3, NULL, $6, $8, false); }
| rwFOR '(' expr ';' ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, $3, NULL, NULL, $7, false); }
| rwFOR '(' ';' expr ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, NULL, $4, $6, $8, false); }
| rwFOR '(' ';' expr ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, NULL, $4, NULL, $7, false); }
| rwFOR '(' ';' ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, NULL, NULL, $5, $7, false); }
| rwFOR '(' ';' ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1.lineNumber, NULL, NULL, NULL, $6, false); }
;
foreach_stmt
: rwFOREACH '(' VAR rwIN expr ')' stmt_block
{ $$ = IterStmtNode::alloc( $1.lineNumber, $3.value, $5, $7, false ); }
| rwFOREACHSTR '(' VAR rwIN expr ')' stmt_block
{ $$ = IterStmtNode::alloc( $1.lineNumber, $3.value, $5, $7, true ); }
;
expression_stmt
: stmt_expr
{ $$ = $1; }
;
expr
: stmt_expr
{ $$ = $1; }
| '(' expr ')'
{ $$ = $2; }
| expr '^' expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '%' expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '&' expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '|' expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '+' expr
{ $$ = FloatBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '-' expr
{ $$ = FloatBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '*' expr
{ $$ = FloatBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '/' expr
{ $$ = FloatBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| '-' expr %prec UNARY
{ $$ = FloatUnaryExprNode::alloc( $1.lineNumber, $1.value, $2); }
| '*' expr %prec UNARY
{ $$ = TTagDerefNode::alloc( $1.lineNumber, $2 ); }
| TTAG
{ $$ = TTagExprNode::alloc( $1.lineNumber, $1.value ); }
| expr '?' expr ':' expr
{ $$ = ConditionalExprNode::alloc( $1->dbgLineNumber, $1, $3, $5); }
| expr '<' expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr '>' expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opGE expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opLE expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opEQ expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opNE expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opOR expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opSHL expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opSHR expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opAND expr
{ $$ = IntBinaryExprNode::alloc( $1->dbgLineNumber, $2.value, $1, $3); }
| expr opSTREQ expr
{ $$ = StreqExprNode::alloc( $1->dbgLineNumber, $1, $3, true); }
| expr opSTRNE expr
{ $$ = StreqExprNode::alloc( $1->dbgLineNumber, $1, $3, false); }
// The '@' operator covers four cases via token value encoding in the lexer:
// '@' → value 0 (plain concatenation)
// NL → value '\n'
// TAB → value '\t'
// SPC → value ' '
// The appendChar is stored in $2.value and forwarded to StrcatExprNode.
| expr '@' expr
{ $$ = StrcatExprNode::alloc( $1->dbgLineNumber, $1, $3, $2.value); }
| '!' expr
{ $$ = IntUnaryExprNode::alloc($1.lineNumber, $1.value, $2); }
| '~' expr
{ $$ = IntUnaryExprNode::alloc($1.lineNumber, $1.value, $2); }
| TAGATOM
{ $$ = StrConstNode::alloc( $1.lineNumber, $1.value, true); }
| FLTCONST
{ $$ = FloatNode::alloc( $1.lineNumber, $1.value ); }
| INTCONST
{ $$ = IntNode::alloc( $1.lineNumber, $1.value ); }
| rwBREAK
{ $$ = ConstantNode::alloc( $1.lineNumber, StringTable->insert("break")); }
| slot_acc
{ $$ = SlotAccessNode::alloc( $1.lineNumber, $1.object, $1.array, $1.slotName ); }
| intslot_acc
{ $$ = InternalSlotAccessNode::alloc( $1.lineNumber, $1.object, $1.slotExpr, $1.recurse); }
| IDENT
{ $$ = ConstantNode::alloc( $1.lineNumber, $1.value ); }
| STRATOM
{ $$ = StrConstNode::alloc( $1.lineNumber, $1.value, false); }
| VAR
{ $$ = (ExprNode*)VarNode::alloc( $1.lineNumber, $1.value, NULL); }
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc( $1.lineNumber, $1.value, $3 ); }
;
slot_acc
: expr '.' IDENT
{ $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotName = $3.value; $$.array = NULL; }
| expr '.' IDENT '[' aidx_expr ']'
{ $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotName = $3.value; $$.array = $5; }
;
intslot_acc
: expr opINTNAME class_name_expr
{ $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotExpr = $3; $$.recurse = false; }
| expr opINTNAMER class_name_expr
{ $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotExpr = $3; $$.recurse = true; }
;
class_name_expr
: IDENT
{ $$ = ConstantNode::alloc( $1.lineNumber, $1.value ); }
| '(' expr ')'
{ $$ = $2; }
;
assign_op_struct
: opPLUSPLUS
{ $$.lineNumber = $1.lineNumber; $$.token = opPLUSPLUS; $$.expr = FloatNode::alloc( $1.lineNumber, 1 ); }
| opMINUSMINUS
{ $$.lineNumber = $1.lineNumber; $$.token = opMINUSMINUS; $$.expr = FloatNode::alloc( $1.lineNumber, 1 ); }
| opPLASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '+'; $$.expr = $2; }
| opMIASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '-'; $$.expr = $2; }
| opMLASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '*'; $$.expr = $2; }
| opDVASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '/'; $$.expr = $2; }
| opMODASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '%'; $$.expr = $2; }
| opANDASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '&'; $$.expr = $2; }
| opXORASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '^'; $$.expr = $2; }
| opORASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = '|'; $$.expr = $2; }
| opSLASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = opSHL; $$.expr = $2; }
| opSRASN expr
{ $$.lineNumber = $1.lineNumber; $$.token = opSHR; $$.expr = $2; }
;
stmt_expr
: funcall_expr
{ $$ = $1; }
| assert_expr
{ $$ = $1; }
| object_decl
{ $$ = $1; }
| VAR '=' expr
{ $$ = AssignExprNode::alloc( $1.lineNumber, $1.value, NULL, $3); }
| VAR '[' aidx_expr ']' '=' expr
{ $$ = AssignExprNode::alloc( $1.lineNumber, $1.value, $3, $6); }
| VAR assign_op_struct
{ $$ = AssignOpExprNode::alloc( $1.lineNumber, $1.value, NULL, $2.expr, $2.token); }
| VAR '[' aidx_expr ']' assign_op_struct
{ $$ = AssignOpExprNode::alloc( $1.lineNumber, $1.value, $3, $5.expr, $5.token); }
| slot_acc assign_op_struct
{ $$ = SlotAssignOpNode::alloc( $1.lineNumber, $1.object, $1.slotName, $1.array, $2.token, $2.expr); }
| slot_acc '=' expr
{ $$ = SlotAssignNode::alloc( $1.lineNumber, $1.object, $1.array, $1.slotName, $3); }
| slot_acc '=' '{' expr_list '}'
{ $$ = SlotAssignNode::alloc( $1.lineNumber, $1.object, $1.array, $1.slotName, $4); }
;
funcall_expr
// Global function call: name(args)
: IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc( $1.lineNumber, $1.value, NULL, $3, false); }
// Static/namespace call: Namespace::name(args)
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc( $1.lineNumber, $3.value, $1.value, $5, false); }
// Method call: object.method(args)
// The object expression is prepended to the arg list so that exec() can
// find it as callArgv[1] (the implicit 'this').
| expr '.' IDENT '(' expr_list_decl ')'
{ $1->append($5); $$ = FuncCallExprNode::alloc( $1->dbgLineNumber, $3.value, NULL, $1, true); }
;
assert_expr
: rwASSERT '(' expr ')'
{ $$ = AssertCallExprNode::alloc( $1.lineNumber, $3, NULL ); }
| rwASSERT '(' expr ',' STRATOM ')'
{ $$ = AssertCallExprNode::alloc( $1.lineNumber, $3, $5.value ); }
;
expr_list_decl
:
{ $$ = NULL; }
| expr_list
{ $$ = $1; }
;
expr_list
: expr
{ $$ = $1; }
| expr_list ',' expr
{ ($1)->append($3); $$ = $1; }
;
slot_assign_list_opt
:
{ $$ = NULL; }
| slot_assign_list
{ $$ = $1; }
;
slot_assign_list
: slot_assign
{ $$ = $1; }
| slot_assign_list slot_assign
{ $1->append($2); $$ = $1; }
;
slot_assign
: IDENT '=' expr ';'
{ $$ = SlotAssignNode::alloc( $1.lineNumber, NULL, NULL, $1.value, $3); }
| TYPEIDENT IDENT '=' expr ';'
{ $$ = SlotAssignNode::alloc( $1.lineNumber, NULL, NULL, $2.value, $4, $1.value); }
| rwDATABLOCK '=' expr ';'
{ $$ = SlotAssignNode::alloc( $1.lineNumber, NULL, NULL, StringTable->insert("datablock"), $3); }
| IDENT '[' aidx_expr ']' '=' expr ';'
{ $$ = SlotAssignNode::alloc( $1.lineNumber, NULL, $3, $1.value, $6); }
| TYPEIDENT IDENT '[' aidx_expr ']' '=' expr ';'
{ $$ = SlotAssignNode::alloc( $1.lineNumber, NULL, $4, $2.value, $7, $1.value); }
;
// Array index expressions. Multiple comma-separated indices get
// concatenated with '_' separators at runtime (e.g. arr[1,2] → "arr_1_2").
aidx_expr
: expr
{ $$ = $1; }
| aidx_expr ',' expr
{ $$ = CommaCatExprNode::alloc( $1->dbgLineNumber, $1, $3); }
;
%%
int
yyreport_syntax_error(const yypcontext_t *ctx)
{
int ret = 0;
String output;
const YYLTYPE *loc = yypcontext_location(ctx);
output += "syntax error: ";
yysymbol_kind_t nxt = yypcontext_token(ctx);
if (nxt != YYSYMBOL_YYEMPTY)
output += String::ToString("unexpected: %s at column: %d",
yysymbol_name(nxt), loc->first_column);
enum { TOKENMAX = 10 };
yysymbol_kind_t expected[TOKENMAX];
int exp = yypcontext_expected_tokens(ctx, expected, TOKENMAX);
if (exp < 0)
{
ret = exp;
}
else
{
for (int i = 0; i < exp; ++i)
output += String::ToString("%s %s",
i == 0 ? ": expected" : "or",
yysymbol_name(expected[i]));
}
if (lines.size() > 0)
{
output += "\n";
for (int i = 0; i < lines.size(); i++)
{
int line = lines.size() - i;
output += String::ToString("%5d | ", loc->first_line - (line - 1))
+ lines[i] + "\n";
}
output += String::ToString("%5s | %*s", "", loc->first_column, "^");
}
yyerror("%s", output.c_str());
return ret;
}