%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 #include #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 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 rwDEFINE rwENDDEF rwDECLARE rwDECLARESINGLETON %token rwBREAK rwELSE rwCONTINUE rwGLOBAL %token rwIF rwNIL rwRETURN rwWHILE rwDO %token rwENDIF rwENDWHILE rwENDFOR rwDEFAULT %token rwFOR rwFOREACH rwFOREACHSTR rwIN rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR %token rwCASEOR rwPACKAGE %token 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 CHRCONST %token INTCONST %token TTAG %token VAR %token IDENT %token TYPEIDENT %token DOCBLOCK %token STRATOM %token TAGATOM %token FLTCONST %{ /* Operator token definitions */ %} %token '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%' %token '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@' %token opINTNAME opINTNAMER %token 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 opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN %token opXORASN opORASN opSLASN opSRASN opCAT %token opEQ opNE opGE opLE opAND opOR // FIX: opSTREQ and opSTRNE must be declared with their semantic type . // 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 opSTREQ opSTRNE %token 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 parent_block %type case_block %type switch_stmt %type decl %type decl_list %type package_decl %type fn_decl_stmt %type fn_decl_list %type statement_list %type stmt %type expr_list %type expr_list_decl %type aidx_expr %type funcall_expr %type assert_expr %type object_name %type object_args %type stmt_expr %type case_expr %type class_name_expr %type if_stmt %type while_stmt %type for_stmt %type foreach_stmt %type stmt_block %type datablock_decl %type object_decl %type object_decl_list %type object_declare_block %type expr %type slot_assign_list_opt %type slot_assign_list %type slot_assign %type slot_acc %type intslot_acc %type expression_stmt %type param %type var_list %type var_list_decl %type 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; }