polserver/pol-core/bscript/parser.cpp
Gabriele Tozzi 5bf416ca24 Merge branch 'cmake'
Conflicts:
- pol-core/clib/xmain.cpp
- pol-core/ecompile/ecompile.cpp
2015-12-18 23:41:43 +01:00

2389 lines
70 KiB
C++

/*
History
=======
2005/01/24 Shinigami: added ObjMember character.spyonclient2 to get data from packet 0xd9 (Spy on Client 2)
2005/03/09 Shinigami: added Prop Character::Delay_Mod [ms] for WeaponDelay
2005/04/04 Shinigami: added Prop Character::CreatedAt [PolClock]
2005/05/24 Shinigami: added ObjMethod account.delete() to delete this account
2005/05/25 Shinigami: added ObjMethod account.split( newacctname : string, index : 1..5 )
to create a new account and move character to it
2005/05/25 Shinigami: added ObjMethod account.move_char( destacctname : string, index : 1..5 )
to move character from this account to destaccount
2005/08/29 Shinigami: character.spyonclient2 renamed to character.clientinfo
2005/09/12 Shinigami: added ObjMethods packet.GetIntxxFlipped and packet.SetIntxxFlipped (Byte Order)
2005/10/02 Shinigami: added Prop Script.attached_to and Script.controller
2005/11/26 Shinigami: changed "strcmp" into "stricmp" to suppress Script Errors
2005/12/06 MuadDib: Added uclang member for storing UC language from client.
2006/05/16 Shinigami: added Prop Character.Race [RACE_* Constants] to support Elfs
2006/09/16 Shinigami: added ObjMethods packet.GetUnicodeStringFlipped and packet.SetUnicodeStringFlipped (Byte Order)
2007/07/09 Shinigami: added Prop Character.isUOKR [bool] - UO:KR client used?
2008/07/08 Turley: Added character.movemode - returns the MoveMode like given in NPCDesc
Added item.stackable - Is item stackable?
2008/12/17 MuadDub: Added item.tile_layer - returns layer entry from tiledata/tiles.cfg
2009/08/06 MuadDib: Removed PasswordOnlyHash support
GetGottenBy Method
2009/08/19 Turley: Added character.uo_expansion_client
2009/08/25 Shinigami: STLport-5.2.1 fix: ParseErrorStr and ReservedWord changed little bit
2009/09/06 Turley: Removed chr.isUOKR added chr.ClientType
2009/10/09 Turley: Added spellbook.spells() & .hasspell() methods
2009/10/10 Turley: Added spellbook.addspell() & .removespell() methods
2009/10/14 Turley: Added char.deaf() methods & char.deafened member
2011/12/13 Tomi: Boat members MBR_COMPONENT, MBR_HULL, MBR_ROPE, MBR_SAILS, MBR_WHEEL, MBR_TILLER, MBR_RUDDER, MBR_STORAGE, MBR_WEAPONSLOT
2012/02/02 Tomi: Multi member MBR_MULTIID
2012/03/26 Tomi: Added MBR_LASTCOORD
2012/04/14 Tomi: Added MBR_FACETID for new map message packet
2012/04/15 Tomi: Added MBR_EDITABLE for maps
2012/06/02 Tomi: Added MBR_ACTIVE_SKILL and MBR_CASTING_SPELL for characters
Notes
=======
*/
#include "parser.h"
#include "fmodule.h"
#include "modules.h"
#include "operator.h"
#include "symcont.h"
#include "token.h"
#include "tokens.h"
#include "verbtbl.h"
#include "userfunc.h"
#include "compilercfg.h"
#include "objmembers.h"
#include "objmethods.h"
#include "../clib/clib.h"
#include "../clib/maputil.h"
#include "../clib/strutil.h"
#include "../clib/unittest.h"
#include "../clib/logfacility.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <cstddef>
#include <cstdio>
#include <map>
#include <stack>
#include <string>
#include <stdexcept>
#ifdef _MSC_VER
#pragma warning(disable:4996) // stricmp, strtok POSIX deprecation warning
#endif
namespace Pol {
namespace Bscript {
static void init_tables();
Parser::Parser() : quiet( 0 ), err( PERR_NONE), contains_tabs( false )
{
memset( &ext_err, 0, sizeof( ext_err ) );
memset( &buffer, 0, sizeof( buffer ) );
init_tables();
}
const char *ParseErrorStr[PERR_NUM_ERRORS] =
{
"(No Error, or not specified)",
"Unexpected ')'",
"Missing '('",
"Missing ')'",
"Bad Token",
"Unknown Operator",
"Waaah!",
"Unterminated String Literal",
"Too Few Arguments",
"Too Many Arguments",
"Unexpected Comma",
"Illegal Construction",
"Missing ';'",
"Token not legal here",
"Procedure calls not allowed here",
"Unexpected Semicolon",
"Expected 'while'",
"Unexpected ']'",
"Missing ']'"
};
char operator_brk[] = "+-/*(),<=>,:;%";
char ident_allowed[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"_"; // $%@& removed 9/17/1998 Syz
int allowed_table[8][8] =
{
/* this token is a(n)... */
/* binary unary
* TERMINATOR OPERAND OPERATOR OPERATOR LPAREN RPAREN LBRACK RBRACK*/
/* Last token was a(n)... */
{ 1, 1, 0, 1, 1, 0, 0, 0 }, /* TERMINATOR */
{ 1, 0, 1, 0, 0, 1, 1, 1 }, /* OPERAND */
{ 0, 1, 0, 1, 1, 0, 0, 0 }, /* BINARY OPERATOR */
{ 0, 1, 0, 0, 1, 0, 0, 0 }, /* UNARY OPERATOR */
{ 0, 1, 0, 1, 1, 0, 0, 0 }, /* LEFT PARENTHESIS */
{ 1, 0, 1, 0, 0, 1, 0, 1 }, /* RIGHT PARENTHESIS */
{ 0, 1, 0, 0, 1, 0, 1, 0 }, /* LEFT BRACKET */
{ 1, 0, 1, 0, 0, 1, 1, 1 }, /* RIGHT BRACKET */
/* Separators are always allowed. Terminators are mostly always allowed. */
};
/* examples matrix: -- connected denotes unary operator
legal:
{ TT T AB T -AB T ( },
{ AB T AB + AB ) A[ A] },
{ * AB *- * ( },
{ -AB -( },
{ ( AB (- ( ( },
{ ) T ) - ) ) )] }
{ [A . [- [( [[ . }
{ ] T . ] * . . ]) ][ ][ }
illegal:
{ T b-A T ) T [ T ] },
{ AB AB AB~ AB ( },
{ * T * / - ) *[ *] },
{ b- T -* -- -) -[ -] },
{ ( T ( * ( ) ([ (] },
{ ) AB )- ) ( )[ }
{ [ T [+ [) [] }
{ ]A ]- ] ( . }
*/
/* operator characters // (and precedence table)
( ) [ ]
+ - (unary-arithmetic) ! (unary-logical) ~ (unary-boolean)
* / %
+ -
< <= > >=
== <>
& (band ?)
^ (bxor ?)
| (bor ?)
and
or
:=
,
Problem: How to recognize a unary operator?
Proposed solution: If a binary operator would be legal, use it
otherwise, it must be a unary operator.
Examples: (-5+7)
1) grabs '('. now only lparens and operands are legal.
2) grabs '-'. Since this is a binary operator (TYP_OPERATOR)
by default, this is illegal. Since it is illegal,
parseToken() tries the unary operator table, and finds it.
( now, only left parens and operands are legal. )
3) gets 5, an operand.
Alternative: Pass getToken()a parameter denoting what is legal and
let it ignore possibilities that are illegal.
See end of file for handling unary operators
*/
/*
The precedence table should be split from the operator
recognition table, because some operators are words and
have to be picked up in the reserved word table.
*/
Operator binary_operators[] =
{
{ "(", TOK_LPAREN, PREC_PAREN, TYP_LEFTPAREN, false, false },
{ ")", TOK_RPAREN, PREC_PAREN, TYP_RIGHTPAREN, false, false },
{ "[", TOK_LBRACKET, PREC_PAREN, TYP_LEFTBRACKET, false, false },
{ "]", TOK_RBRACKET, PREC_PAREN, TYP_RIGHTBRACKET, false, false },
{ "{", TOK_LBRACE, PREC_PAREN, TYP_LEFTBRACE, false, false },
{ "}", TOK_RBRACE, PREC_PAREN, TYP_RIGHTBRACE, false, false },
{ ".", TOK_MEMBER, PREC_PAREN, TYP_OPERATOR, true, false },
{ "->", TOK_DICTKEY, PREC_ASSIGN, TYP_RESERVED, false, false },
{ "*", TOK_MULT, PREC_MULT, TYP_OPERATOR, true, false },
{ "/", TOK_DIV, PREC_MULT, TYP_OPERATOR, true, false },
{ "%", TOK_MODULUS, PREC_MULT, TYP_OPERATOR, true, false },
{ "+", TOK_ADD, PREC_PLUS, TYP_OPERATOR, true, false },
{ "-", TOK_SUBTRACT, PREC_PLUS, TYP_OPERATOR, true, false },
{ "+=", TOK_PLUSEQUAL, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ "-=", TOK_MINUSEQUAL, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ "*=", TOK_TIMESEQUAL, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ "/=", TOK_DIVIDEEQUAL, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ "%=", TOK_MODULUSEQUAL, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ "<=", TOK_LESSEQ, PREC_LESSTHAN, TYP_OPERATOR, false, false },
{ "<", TOK_LESSTHAN, PREC_LESSTHAN, TYP_OPERATOR, true, false },
{ ">=", TOK_GREQ, PREC_LESSTHAN, TYP_OPERATOR, false, false },
{ ">", TOK_GRTHAN, PREC_LESSTHAN, TYP_OPERATOR, true, false },
{ ">>", TOK_BSRIGHT, PREC_BSRIGHT, TYP_OPERATOR, false, false },
{ "<<", TOK_BSLEFT, PREC_BSLEFT, TYP_OPERATOR, false, false },
{ "&", TOK_BITAND, PREC_BITAND, TYP_OPERATOR, true, false },
{ "^", TOK_BITXOR, PREC_BITXOR, TYP_OPERATOR, false, false },
{ "|", TOK_BITOR, PREC_BITOR, TYP_OPERATOR, true, false },
{ "<>", TOK_NEQ, PREC_EQUALTO, TYP_OPERATOR, false, false },
{ "!=", TOK_NEQ, PREC_EQUALTO, TYP_OPERATOR, false, false },
{ "=", TOK_EQUAL1, PREC_EQUALTO, TYP_OPERATOR, true, false }, // deprecated: :=/==
{ "==", TOK_EQUAL, PREC_EQUALTO, TYP_OPERATOR, false, false },
// { "and", TOK_AND, PREC_LOGAND, TYP_OPERATOR, false, false },
{ "&&", TOK_AND, PREC_LOGAND, TYP_OPERATOR, false, false },
// { "or", TOK_OR, PREC_LOGOR, TYP_OPERATOR, false, false },
{ "||", TOK_OR, PREC_LOGOR, TYP_OPERATOR, false, false },
{ ":=", TOK_ASSIGN, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ ".+", TOK_ADDMEMBER, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ ".-", TOK_DELMEMBER, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ ".?", TOK_CHKMEMBER, PREC_ASSIGN, TYP_OPERATOR, false, false },
{ ",", TOK_COMMA, PREC_COMMA, TYP_SEPARATOR, false, false },
{ "", TOK_TERM, PREC_TERMINATOR, TYP_TERMINATOR, false, false }
};
int n_operators = sizeof binary_operators / sizeof binary_operators[0];
Operator unary_operators[] =
{
{ "+", TOK_UNPLUS, PREC_UNARY_OPS, TYP_UNARY_OPERATOR, false, false },
{ "-", TOK_UNMINUS, PREC_UNARY_OPS, TYP_UNARY_OPERATOR, false, false },
{ "!", TOK_LOG_NOT, PREC_UNARY_OPS, TYP_UNARY_OPERATOR, false, false },
{ "~", TOK_BITWISE_NOT, PREC_UNARY_OPS, TYP_UNARY_OPERATOR, false, false }
// { "not", TOK_LOG_NOT, PREC_UNARY_OPS, TYP_UNARY_OPERATOR, false, false }
// "refto", TOK_REFTO, 12, TYP_UNARY_OPERATOR, false, false
};
int n_unary = sizeof unary_operators / sizeof unary_operators[0];
ObjMember object_members[] = {
// MBR_*, "name", fits into another member name
// Example of true/false -> ('ar' vs 'warmode', 'ar' must be true)
{ MBR_X, "x", true }, //0
{ MBR_Y, "y", true }, //1
{ MBR_Z, "z", true },
{ MBR_NAME, "name", false },
{ MBR_OBJTYPE, "objtype", true },
{ MBR_GRAPHIC, "graphic", false }, //5
{ MBR_SERIAL, "serial", true },
{ MBR_COLOR, "color", false },
{ MBR_HEIGHT, "height", true },
{ MBR_FACING, "facing", false },
{ MBR_DIRTY, "dirty", true }, //10
{ MBR_WEIGHT, "weight", true },
{ MBR_MULTI, "multi", true },
{ MBR_AMOUNT, "amount", true },//item
{ MBR_LAYER, "layer", true },
{ MBR_CONTAINER, "container", true }, //15
{ MBR_USESCRIPT, "usescript", false },
{ MBR_EQUIPSCRIPT, "equipscript", false },
{ MBR_UNEQUIPSCRIPT, "unequipscript", false },
{ MBR_DESC, "desc", false },
{ MBR_MOVABLE, "movable", false }, //20
{ MBR_INVISIBLE, "invisible", false },
{ MBR_DECAYAT, "decayat", false },
{ MBR_SELLPRICE, "sellprice", false },
{ MBR_BUYPRICE, "buyprice", false },
{ MBR_NEWBIE, "newbie", false }, //25
{ MBR_ITEM_COUNT, "item_count", true },
{ MBR_WARMODE, "warmode", true },//character
{ MBR_GENDER, "gender", false },
{ MBR_TRUEOBJTYPE, "trueobjtype", false },
{ MBR_TRUECOLOR, "truecolor", false }, //30
{ MBR_AR_MOD, "ar_mod", false },
{ MBR_HIDDEN, "hidden", false },
{ MBR_CONCEALED, "concealed", false },
{ MBR_FROZEN, "frozen", false },
{ MBR_PARALYZED, "paralyzed", false }, //35
{ MBR_POISONED, "poisoned", false },
{ MBR_STEALTHSTEPS, "stealthsteps", false },
{ MBR_SQUELCHED, "squelched", true },
{ MBR_DEAD, "dead", true },
{ MBR_AR, "ar", true }, //40
{ MBR_BACKPACK, "backpack", true },
{ MBR_WEAPON, "weapon", true },
{ MBR_SHIELD, "shield", true },
{ MBR_ACCTNAME, "acctname", true },
{ MBR_ACCT, "acct", true }, //45
{ MBR_CMDLEVEL, "cmdlevel", false },
{ MBR_CMDLEVELSTR, "cmdlevelstr", true },
{ MBR_CRIMINAL, "criminal", true },
{ MBR_IP, "ip", true },
{ MBR_GOLD, "gold", true }, //50
{ MBR_TITLE_PREFIX, "title_prefix", false },
{ MBR_TITLE_SUFFIX, "title_suffix", false },
{ MBR_TITLE_GUILD, "title_guild", false },
{ MBR_TITLE_RACE, "title_race", false },
{ MBR_GUILDID, "guildid", true }, //55
{ MBR_GUILD, "guild", true },
{ MBR_MURDERER, "murderer", false },
{ MBR_ATTACHED, "attached", true },
{ MBR_CLIENTVERSION, "clientversion", true },
{ MBR_REPORTABLES, "reportables", true }, //60
{ MBR_SCRIPT, "script", false }, //npc
{ MBR_NPCTEMPLATE, "npctemplate", true },
{ MBR_MASTER, "master", true },
{ MBR_PROCESS, "process", true },
{ MBR_EVENTMASK, "eventmask", true }, //65
{ MBR_SPEECH_COLOR, "speech_color", false },
{ MBR_SPEECH_FONT, "speech_font", false },
{ MBR_USE_ADJUSTMENTS, "use_adjustments", false },
{ MBR_RUN_SPEED, "run_speed", false },
{ MBR_LOCKED, "locked", false }, //lockable //70
{ MBR_CORPSETYPE, "corpsetype", true }, //corpse
{ MBR_TILLERMAN, "tillerman", true }, //boat
{ MBR_PORTPLANK, "portplank", true },
{ MBR_STARBOARDPLANK, "starboardplank", true },
{ MBR_HOLD, "hold", true }, //75
{ MBR_HAS_OFFLINE_MOBILES, "has_offline_mobiles", true },
{ MBR_COMPONENTS, "components", true }, //house
{ MBR_ITEMS, "items", true }, //multi
{ MBR_MOBILES, "mobiles", true },
{ MBR_XEAST, "xeast", false }, //map //80
{ MBR_XWEST, "xwest", false },
{ MBR_YNORTH, "ynorth", false },
{ MBR_YSOUTH, "ysouth", false },
{ MBR_GUMPWIDTH, "gumpwidth", false },
{ MBR_GUMPHEIGHT, "gumpheight", false }, //85
{ MBR_ISOPEN, "isopen", true }, //door
{ MBR_QUALITY, "quality", false }, //equipment
{ MBR_HP, "hp", false },
{ MBR_MAXHP_MOD, "maxhp_mod", false },
{ MBR_MAXHP, "maxhp", true }, //90
{ MBR_DMG_MOD, "dmg_mod", false }, //weapon
{ MBR_ATTRIBUTE, "attribute", true },
{ MBR_INTRINSIC, "intrinsic", true },
{ MBR_HITSCRIPT, "hitscript", false },
{ MBR_AR_BASE, "ar_base", true }, //95
{ MBR_ONHIT_SCRIPT, "onhitscript", false },
{ MBR_ENABLED, "enabled", true }, //account
{ MBR_BANNED, "banned", true },
{ MBR_USERNAMEPASSWORDHASH, "usernamepasswordhash", true },
{ MBR_MEMBERS, "members", true }, //guild //100
{ MBR_ALLYGUILDS, "allyguilds", true },
{ MBR_ENEMYGUILDS, "enemyguilds", true },
{ MBR_PID, "pid", true },//script
{ MBR_STATE, "state", true },
{ MBR_INSTR_CYCLES, "instr_cycles", true },//105
{ MBR_SLEEP_CYCLES, "sleep_cycles", true },
{ MBR_CONSEC_CYCLES, "consec_cycles", true },
{ MBR_PC, "pc", true },
{ MBR_CALL_DEPTH, "call_depth", true },
{ MBR_NUM_GLOBALS, "num_globals", true },//110
{ MBR_VAR_SIZE, "var_size", true },
{ MBR_REALM, "realm", true },
{ MBR_UO_EXPANSION, "uo_expansion", true },
{ MBR_CUSTOM, "custom", true }, //house
{ MBR_GLOBALS, "globals", true }, //115
{ MBR_FOOTPRINT, "footprint", true },
{ MBR_CLIENTINFO, "clientinfo", true },
{ MBR_DELAY_MOD, "delay_mod", false },
{ MBR_CREATEDAT, "createdat", true },
{ MBR_OPPONENT, "opponent", true },//120
{ MBR_CONNECTED, "connected", true },
{ MBR_ATTACHED_TO, "attached_to", true },
{ MBR_CONTROLLER, "controller", true },
{ MBR_OWNERSERIAL, "ownerserial", true },
{ MBR_DEFAULTCMDLEVEL, "defaultcmdlevel", true },//125
{ MBR_UCLANG, "uclang", true },
{ MBR_RACE, "race", false },
{ MBR_TRADING_WITH, "trading_with", false },
{ MBR_TRADE_CONTAINER, "trade_container", false },
{ MBR_ALIGNMENT, "alignment", false },//130
{ MBR_CURSOR, "cursor", false },
{ MBR_GUMP, "gump", false },
{ MBR_PROMPT, "prompt", false },
{ MBR_STACKABLE, "stackable", false },
{ MBR_MOVEMODE, "movemode", false }, //135
{ MBR_HITCHANCE_MOD, "hitchance_mod", false },
{ MBR_EVASIONCHANCE_MOD, "evasionchance_mod", false },
{ MBR_TILE_LAYER, "tile_layer", true },
{ MBR_CLIENTVERSIONDETAIL, "clientver_detail", true },
{ MBR_SAVEONEXIT, "saveonexit", true }, //140
{ MBR_FIRE_RESIST, "resist_fire", true },
{ MBR_COLD_RESIST, "resist_cold", true },
{ MBR_ENERGY_RESIST, "resist_energy", true },
{ MBR_POISON_RESIST, "resist_poison", true },
{ MBR_PHYSICAL_RESIST, "resist_physical", true },//145
{ MBR_FIRE_RESIST_MOD, "resist_fire_mod", true },
{ MBR_COLD_RESIST_MOD, "resist_cold_mod", true },
{ MBR_ENERGY_RESIST_MOD, "resist_energy_mod", true },
{ MBR_POISON_RESIST_MOD, "resist_poison_mod", true },
{ MBR_PHYSICAL_RESIST_MOD, "resist_physical_mod", true }, //150
{ MBR_STATCAP, "statcap", false },
{ MBR_SKILLCAP, "skillcap", false },
{ MBR_LUCK, "luck", false },
{ MBR_FOLLOWERSMAX, "followers_max", false },
{ MBR_TITHING, "tithing", false }, //155
{ MBR_FOLLOWERS, "followers", false },
{ MBR_FIRE_DAMAGE, "damage_fire", true },
{ MBR_COLD_DAMAGE, "damage_cold", true },
{ MBR_ENERGY_DAMAGE, "damage_energy", true },
{ MBR_POISON_DAMAGE, "damage_poison", true }, //160
{ MBR_PHYSICAL_DAMAGE, "damage_physical", true },
{ MBR_FIRE_DAMAGE_MOD, "damage_fire_mod", true },
{ MBR_COLD_DAMAGE_MOD, "damage_cold_mod", true },
{ MBR_ENERGY_DAMAGE_MOD, "damage_energy_mod", true },
{ MBR_POISON_DAMAGE_MOD, "damage_poison_mod", true }, //165
{ MBR_PHYSICAL_DAMAGE_MOD, "damage_physical_mod", true },
{ MBR_PARTY, "party", true },
{ MBR_LEADER, "leader", true },
{ MBR_PARTYLOOT, "partycanloot", true },
{ MBR_CANDIDATE_OF_PARTY, "candidate_of_party", true }, //170
{ MBR_CANDIDATES, "candidates", true },
{ MBR_MOVECOST_WALK, "movecost_walk_mod", true },
{ MBR_MOVECOST_RUN, "movecost_run_mod", true },
{ MBR_MOVECOST_WALK_MOUNTED, "movecost_walk_mounted_mod", true },
{ MBR_MOVECOST_RUN_MOUNTED, "movecost_run_mounted_mod", true }, //175
{ MBR_AGGRESSORTO, "aggressorto", true },
{ MBR_LAWFULLYDAMAGED, "lawfullydamaged", true },
{ MBR_GETGOTTENBY, "getgottenby", true },
{ MBR_UO_EXPANSION_CLIENT, "uo_expansion_client", true },
{ MBR_CLIENTTYPE, "clienttype", true }, //180
{ MBR_DEAFENED, "deafed", true },
{ MBR_CLIENT, "client", true },
{ MBR_TYPE, "type", true },
{ MBR_ATTRIBUTES, "attributes", true },
{ MBR_EDITING, "house_editing", true }, //185
{ MBR_HOUSEPARTS, "house_parts", true },
{ MBR_DOUBLECLICKRANGE, "doubleclickrange", false },
{ MBR_MOUNTEDSTEPS, "mountedsteps", false },
// New boat stuff start
{ MBR_ROPE, "rope", true },
{ MBR_WHEEL, "wheel", true }, //190
{ MBR_HULL, "hull", true },
{ MBR_TILLER, "tiller", true },
{ MBR_RUDDER, "rudder", true },
{ MBR_SAILS, "sails", true },
{ MBR_STORAGE, "storage", true }, //195
{ MBR_WEAPONSLOT, "weaponslot", true },
// New boat stuff end
{ MBR_MULTIID, "multiid", true },
{ MBR_TRADEWINDOW, "tradewindow", true },
{ MBR_LASTCOORD, "lastcoord", true },
{ MBR_FACETID, "facetid", true }, //200
{ MBR_EDITABLE, "editable", true },
{ MBR_ACTIVE_SKILL, "active_skill", true },
{ MBR_CASTING_SPELL, "casting_spell", true },
{ MBR_CARRYINGCAPACITY_MOD, "carrying_capacity_mod", false },
{ MBR_MAX_ITEMS_MOD, "max_items_mod", false }, //205
{ MBR_MAX_WEIGHT_MOD, "max_weight_mod", false },
{ MBR_MAX_SLOTS_MOD, "max_slots_mod", false },
{ MBR_SPEED_MOD, "speed_mod", false },
{ MBR_NAME_SUFFIX, "name_suffix", false },
{ MBR_TEMPORALLY_CRIMINAL, "temporally_criminal", true }, //210
{ MBR_LAST_TEXTCOLOR, "last_textcolor", true },
{ MBR_INSURED, "insured", false },
{ MBR_LAST_ACTIVITY_AT, "last_activity_at", false },
{ MBR_LAST_PACKET_AT, "last_packet_at", false },
};
int n_objmembers = sizeof object_members / sizeof object_members[0];
ObjMember* getKnownObjMember( const char* token )
{
for ( int i = 0; i < n_objmembers; i++ )
{
if ( stricmp( object_members[i].code, token ) == 0 )
{
return &( object_members[i] );
}
}
return NULL;
}
ObjMember* getObjMember( int id )
{
if ( id >= n_objmembers )
return NULL;
else
return &( object_members[id] );
}
ObjMethod object_methods[] = {
{ MTH_ISA, "isa", false }, //0
{ MTH_SET_MEMBER, "set_member", false }, //1
{ MTH_GET_MEMBER, "get_member", false },
{ MTH_SETPOISONED, "setpoisoned", false },
{ MTH_SETPARALYZED, "setparalyzed", false },
{ MTH_SETCRIMINAL, "setcriminal", false }, //5
{ MTH_SETLIGHTLEVEL, "setlightlevel", false },
{ MTH_SQUELCH, "squelch", false },
{ MTH_ENABLE, "enable", false },
{ MTH_DISABLE, "disable", false },
{ MTH_ENABLED, "enabled", false }, //10
{ MTH_SETCMDLEVEL, "setcmdlevel", false },
{ MTH_SPENDGOLD, "spendgold", false },
{ MTH_SETMURDERER, "setmurderer", false },
{ MTH_REMOVEREPORTABLE, "removereportable", false },
{ MTH_GETGOTTENITEM, "getgottenitem", false }, //15
{ MTH_CLEARGOTTENITEM, "cleargottenitem", false },
{ MTH_SETWARMODE, "setwarmode", false },
{ MTH_SETMASTER, "setmaster", false }, //npc
{ MTH_MOVE_OFFLINE_MOBILES, "move_offline_mobiles", false },//boat
{ MTH_SETCUSTOM, "setcustom", false }, //house //20
{ MTH_GETPINS, "getpins", false },//map
{ MTH_INSERTPIN, "insertpin", false },
{ MTH_APPENDPIN, "appendpin", false },
{ MTH_ERASEPIN, "erasepin", false },
{ MTH_OPEN, "open", false },//door //25
{ MTH_CLOSE, "close", false },
{ MTH_TOGGLE, "toggle", false },
{ MTH_BAN, "ban", false }, //account
{ MTH_UNBAN, "unban", false },
{ MTH_SETPASSWORD, "setpassword", false }, //30
{ MTH_CHECKPASSWORD, "checkpassword", false },
{ MTH_SETNAME, "setname", false },
{ MTH_GETCHARACTER, "getcharacter", false },
{ MTH_DELETECHARACTER, "deletecharacter", false },
{ MTH_GETPROP, "getprop", false }, //35
{ MTH_SETPROP, "setprop", false },
{ MTH_ERASEPROP, "eraseprop", false },
{ MTH_PROPNAMES, "propnames", false },
{ MTH_ISMEMBER, "ismember", false },//guild
{ MTH_ISALLYGUILD, "isallyguild", false }, //40
{ MTH_ISENEMYGUILD, "isenemyguild", false },
{ MTH_ADDMEMBER, "addmember", false },
{ MTH_ADDALLYGUILD, "addallyguild", false },
{ MTH_ADDENEMYGUILD, "addenemyguild", false },
{ MTH_REMOVEMEMBER, "removemember", false }, //45
{ MTH_REMOVEALLYGUILD, "removeallyguild", false },
{ MTH_REMOVEENEMYGUILD, "removeenemyguild", false },
{ MTH_SIZE, "size", false }, //ARRAY
{ MTH_ERASE, "erase", false },
{ MTH_INSERT, "insert", false }, //50
{ MTH_SHRINK, "shrink", false },
{ MTH_APPEND, "append", false },
{ MTH_REVERSE, "reverse", false },
{ MTH_SORT, "sort", false }, //dict
{ MTH_EXISTS, "exists", false }, //55
{ MTH_KEYS, "keys", false },
{ MTH_SENDPACKET, "sendpacket", false }, //packet
{ MTH_SENDAREAPACKET, "sendareapacket", false },
{ MTH_GETINT8, "getint8", false },
{ MTH_GETINT16, "getint16", false }, //60
{ MTH_GETINT32, "getint32", false },
{ MTH_SETINT8, "setint8", false },
{ MTH_SETINT16, "setint16", false },
{ MTH_SETINT32, "setint32", false },
{ MTH_GETSTRING, "getstring", false }, //65
{ MTH_GETUNICODESTRING, "getunicodestring", false },
{ MTH_SETSTRING, "setstring", false },
{ MTH_SETUNICODESTRING, "setunicodestring", false },
{ MTH_GETSIZE, "getsize", false },
{ MTH_SETSIZE, "setsize", false }, //70
{ MTH_CREATEELEMENT, "createelement", false },//datastore
{ MTH_FINDELEMENT, "findelement", false },
{ MTH_DELETEELEMENT, "deleteelement", false },
{ MTH_SENDEVENT, "sendevent", false },//script
{ MTH_KILL, "kill", false }, //75
{ MTH_LOADSYMBOLS, "loadsymbols", false },
{ MTH_SET_UO_EXPANSION, "set_uo_expansion", false },
{ MTH_CLEAR_EVENT_QUEUE, "clear_event_queue", false },
{ MTH_ADD_COMPONENT, "add_component", false },
{ MTH_ERASE_COMPONENT, "erase_component", false }, //80
{ MTH_DELETE, "delete", false },
{ MTH_SPLIT, "split", false },
{ MTH_MOVE_CHAR, "move_char", false },
{ MTH_GETINT16FLIPPED, "getint16flipped", false },
{ MTH_GETINT32FLIPPED, "getint32flipped", false }, //85
{ MTH_SETINT16FLIPPED, "setint16flipped", false },
{ MTH_SETINT32FLIPPED, "setint32flipped", false },
{ MTH_GETCORPSE, "getcorpse", false },
{ MTH_SETDEFAULTCMDLEVEL, "setdefaultcmdlevel", false },
{ MTH_PRIVILEGES, "privileges", false }, //90
{ MTH_GETUNICODESTRINGFLIPPED, "getunicodestringflipped", false },
{ MTH_SETUNICODESTRINGFLIPPED, "setunicodestringflipped", false },
{ MTH_ADD_CHARACTER, "AddCharacter", false },
{ MTH_SET_SWINGTIMER, "setswingtimer", false },
{ MTH_ATTACK_ONCE, "attack_once", false }, //95
{ MTH_SETFACING, "setfacing", false },
{ MTH_COMPAREVERSION, "compareversion", false },
{ MTH_SETLEADER, "setleader", false },
{ MTH_ADDCANDIDATE, "addcandidate", false },
{ MTH_REMOVECANDIDATE, "removecandidate", false }, //100
{ MTH_RANDOMENTRY, "randomentry", false },
{ MTH_SEEK, "seek", false },
{ MTH_PEEK, "peek", false },
{ MTH_TELL, "tell", false },
{ MTH_FLUSH, "flush", false }, //105
{ MTH_GETSINT8, "getsint8", false },
{ MTH_GETSINT16, "getsint16", false },
{ MTH_GETSINT32, "getsint32", false },
{ MTH_SETSINT8, "setsint8", false },
{ MTH_SETSINT16, "setsint16", false }, //110
{ MTH_SETSINT32, "setsint32", false },
{ MTH_SETAGGRESSORTO, "setaggressorto", false },
{ MTH_SETLAWFULLYDAMAGEDTO, "setlawfullydamagedto", false },
{ MTH_CLEARAGGRESSORTO, "clearaggressorto", false },
{ MTH_CLEARLAWFULLYDAMAGEDTO, "clearlawfullydamagedto", false }, //115
{ MTH_HASSPELL, "hasspell", false },
{ MTH_SPELLS, "spells", false },
{ MTH_REMOVESPELL, "removespell", false },
{ MTH_ADDSPELL, "addspell", false },
{ MTH_DEAF, "deaf", false }, //120
{ MTH_SETSEASON, "setseason", false },
{ MTH_NEXTSIBLING, "nextxmlsibling", false },
{ MTH_FIRSTCHILD, "firstxmlchild", false },
{ MTH_SAVEXML, "savexml", false },
{ MTH_APPENDNODE, "appendxmlnode", false }, //125
{ MTH_SETDECLARATION, "setxmldeclaration", false },
{ MTH_SETATTRIBUTE, "setxmlattribute", false },
{ MTH_REMOVEATTRIBUTE, "removexmlattribute", false },
{ MTH_REMOVENODE, "removexmlnode", false },
{ MTH_APPENDTEXT, "appendxmltext", false }, //130
{ MTH_XMLTOSTRING, "xmltostring", false },
{ MTH_APPENDXMLCOMMENT, "appendxmlcomment", false },
{ MTH_ADD_HOUSE_PART, "addhousepart", false },
{ MTH_ERASE_HOUSE_PART, "erasehousepart", false },
{ MTH_ACCEPT_COMMIT, "acceptcommit", false }, //135
{ MTH_SPLITSTACK_AT, "splitstackat", false },
{ MTH_SPLITSTACK_INTO, "splitstackinto", false },
{ MTH_CANCEL_EDITING, "cancelediting", false },
{ MTH_CLONENODE, "clonenode", false },
{ MTH_HAS_EXISTING_STACK, "hasexistingstack", false }, //140
{ MTH_LENGTH, "length", false },
{ MTH_JOIN, "join", false },
{ MTH_FIND, "find", false },
{ MTH_UPPER, "upper", false },
{ MTH_LOWER, "lower", false }, //145
{ MTH_FORMAT, "format", false },
{ MTH_DISABLE_SKILLS_FOR, "disableskillsfor", false },
{ MTH_CYCLE, "cycle", false },
{ MTH_ADD_BUFF, "addbuff", false },
{ MTH_DEL_BUFF, "delbuff", false }, //150
{ MTH_CLEAR_BUFFS, "clearbuffs", false },
};
int n_objmethods = sizeof object_methods / sizeof object_methods[0];
ObjMethod* getKnownObjMethod( const char* token )
{
for ( int i = 0; i < n_objmethods; i++ )
{
if ( stricmp( object_methods[i].code, token ) == 0 )
{
return &( object_methods[i] );
}
}
return NULL;
}
ObjMethod* getObjMethod( int id )
{
if ( id >= n_objmethods )
return NULL;
else
return &( object_methods[id] );
}
void testparserdefinitions()
{
for ( int i = 0; i < n_objmethods; i++ )
{
if ( object_methods[i].id != i )
{
INFO_PRINT << "ERROR: Object Method definition of " << object_methods[i].code << " has an invalid index!\n";
}
}
for ( int i = 0; i < n_objmembers; i++ )
{
if ( object_members[i].id != i )
{
INFO_PRINT << "ERROR: Object Member definition of " << object_members[i].code << " has an invalid index!\n";
}
}
}
Clib::UnitTest testparserdefinitions_obj( testparserdefinitions );
void matchOperators( Operator *oplist, int n_ops,
char *buf,
int *nPartial,
Operator** pTotalMatchOperator )
{
// all operators are 1 or 2 characters.
// they also don't have case.
int lenbuf = buf[1] ? 2 : 1;
*nPartial = 0;
*pTotalMatchOperator = NULL;
if ( lenbuf == 1 )
{
Operator* op = &oplist[0];
for ( int i = 0; i < n_ops; ++i, ++op )
{
if ( op->code[0] == buf[0] )
{
if ( op->code[1] == '\0' )
{
( *pTotalMatchOperator ) = op;
if ( !op->ambig )
return;
}
else
{
( *nPartial )++;
}
}
}
}
else // lenbuf == 2
{
Operator* op = &oplist[0];
for ( int i = 0; i < n_ops; ++i, ++op )
{
if ( op->code[0] == buf[0] && op->code[1] == buf[1] )
{
( *pTotalMatchOperator ) = op;
return;
}
}
}
}
/*
void matchOperators(char *buf, int *nPartial, Operator** ppMatch)
{
matchOperators(binary_operators, n_operators,
buf, nPartial, ppMatch);
}
void matchUnaryOperators(char *buf, int *nPartial, Operator** ppMatch)
{
matchOperators(unary_operators, n_unary,
buf, nPartial, ppMatch);
}
*/
// FIXME this really should be a word, type, and id
// (or better yet, eliminate type altogether.)
// reserved words first get read out as literals, then
// are recognized to be reserved words.
// the "and", "or", and "not" operators won't be recognized
// right, for the moment.
typedef struct
{
const char *word;
BTokenId id;
BTokenType type;
Precedence precedence;
bool deprecated;
} ReservedWord;
ReservedWord reserved_words[] = {
{ "if", RSV_ST_IF, TYP_RESERVED, PREC_TERMINATOR, false },
{ "then", RSV_THEN, TYP_RESERVED, PREC_TERMINATOR, false },
{ "elseif", RSV_ELSEIF, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endif", RSV_ENDIF, TYP_RESERVED, PREC_TERMINATOR, false },
{ "else", RSV_ELSE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "_OptionBracketed", RSV_OPTION_BRACKETED, TYP_RESERVED, PREC_TERMINATOR, false },
{ "goto", RSV_GOTO, TYP_RESERVED, PREC_TERMINATOR, false },
{ "gosub", RSV_GOSUB, TYP_RESERVED, PREC_TERMINATOR, false },
{ "return", RSV_RETURN, TYP_RESERVED, PREC_TERMINATOR, false },
// { "global", RSV_GLOBAL, TYP_RESERVED, PREC_DEPRECATED, true }, // internal only
// { "local", RSV_LOCAL, TYP_RESERVED, PREC_DEPRECATED, true }, // internal only
{ "const", RSV_CONST, TYP_RESERVED, PREC_TERMINATOR, false },
{ "var", RSV_VAR, TYP_RESERVED, PREC_TERMINATOR, false },
// { "begin", RSV_BEGIN, TYP_RESERVED, PREC_DEPRECATED, true }, // deprecated
// { "end", RSV_ENDB, TYP_RESERVED, PREC_DEPRECATED, true }, // deprecated
{ "do", RSV_DO, TYP_RESERVED, PREC_TERMINATOR, false },
{ "dowhile", RSV_DOWHILE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "while", RSV_WHILE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endwhile", RSV_ENDWHILE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "exit", RSV_EXIT, TYP_RESERVED, PREC_TERMINATOR, false },
{ "declare", RSV_DECLARE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "function", RSV_FUNCTION, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endfunction", RSV_ENDFUNCTION, TYP_RESERVED, PREC_TERMINATOR, false },
{ "exported", RSV_EXPORTED, TYP_RESERVED, PREC_TERMINATOR, false },
{ "use", RSV_USE_MODULE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "include", RSV_INCLUDE_FILE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "break", RSV_BREAK, TYP_RESERVED, PREC_TERMINATOR, false },
{ "continue", RSV_CONTINUE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "for", RSV_FOR, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endfor", RSV_ENDFOR, TYP_RESERVED, PREC_TERMINATOR, false },
{ "to", RSV_TO, TYP_RESERVED, PREC_TERMINATOR, false },
{ "next", RSV_NEXT, TYP_RESERVED, PREC_TERMINATOR, false },
{ "foreach", RSV_FOREACH, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endforeach", RSV_ENDFOREACH, TYP_RESERVED, PREC_TERMINATOR, false },
{ "repeat", RSV_REPEAT, TYP_RESERVED, PREC_TERMINATOR, false },
{ "until", RSV_UNTIL, TYP_RESERVED, PREC_TERMINATOR, false },
{ "program", RSV_PROGRAM, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endprogram", RSV_ENDPROGRAM, TYP_RESERVED, PREC_TERMINATOR, false },
{ "case", RSV_SWITCH, TYP_RESERVED, PREC_TERMINATOR, false },
// { "case", RSV_CASE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "default", RSV_DEFAULT, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endcase", RSV_ENDSWITCH, TYP_RESERVED, PREC_TERMINATOR, false },
{ "enum", RSV_ENUM, TYP_RESERVED, PREC_TERMINATOR, false },
{ "endenum", RSV_ENDENUM, TYP_RESERVED, PREC_TERMINATOR, false },
{ "downto", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "step", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "reference", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "out", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "inout", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
// { "ByRef", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "ByVal", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "string", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "long", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "integer", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "unsigned", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "signed", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "real", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "float", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "double", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "as", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "is", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "and", TOK_AND, TYP_OPERATOR, PREC_LOGAND, false },
{ "or", TOK_OR, TYP_OPERATOR, PREC_LOGOR, false },
{ "not", TOK_LOG_NOT, TYP_UNARY_OPERATOR, PREC_UNARY_OPS, false },
{ "byref", TOK_REFTO, TYP_RESERVED, PREC_TERMINATOR, false }, //UNARY_OPERATOR, 12 },
{ "unused", TOK_UNUSED, TYP_RESERVED, PREC_TERMINATOR, false },
{ "error", TOK_ERROR, TYP_OPERAND, PREC_TERMINATOR, false },
{ "hash", RSV_FUTURE, TYP_RESERVED, PREC_TERMINATOR, false },
{ "dictionary", TOK_DICTIONARY, TYP_OPERAND, PREC_TERMINATOR, false },
{ "struct", TOK_STRUCT, TYP_OPERAND, PREC_TERMINATOR, false },
{ "array", TOK_ARRAY, TYP_OPERAND, PREC_TERMINATOR, false },
{ "stack", TOK_STACK, TYP_OPERAND, PREC_TERMINATOR, false },
{ "in", TOK_IN, TYP_OPERATOR, PREC_EQUALTO, false }
// { "bitand", TOK_BITAND, TYP_OPERATOR, PREC_BITAND },
// { "bitxor", TOK_BITXOR, TYP_OPERATOR, PREC_BITXOR },
// { "bitor", TOK_BITOR, TYP_OPERATOR, PREC_BITOR }
/* "/""*", RSV_COMMENT_START,
"*""/", RSV_COMMENT_END,
"/""/", RSV_COMMENT_TO_EOL,
"--", RSV_COMMENT_TO_EOL
*/
};
unsigned n_reserved = sizeof reserved_words / sizeof reserved_words[0];
typedef std::map<std::string, ReservedWord*, Clib::ci_cmp_pred> ReservedWords;
ReservedWords reservedWordsByName;
static void init_tables()
{
static std::once_flag flag;
std::call_once( flag, []( )
{
for ( unsigned i = 0; i < n_reserved; ++i )
{
reservedWordsByName[reserved_words[i].word] = &reserved_words[i];
}
} );
}
void Parser::write_words( std::ostream& os )
{
os << "Reserved:" << std::endl;
for ( unsigned i = 0; i < n_reserved; ++i )
{
os << reserved_words[i].word
<< (reserved_words[i].deprecated ? " (deprecated)" : "") << std::endl;
}
os << std::endl;
os << "Binary:" << std::endl;
for ( int i = 0; i < n_operators; ++i )
{
os << binary_operators[i].code
<< (binary_operators[i].deprecated ? " (deprecated)" : "") << std::endl;
}
os << std::endl;
os << "Unary:" << std::endl;
for ( int i = 0; i < n_unary; ++i )
{
os << unary_operators[i].code
<< (unary_operators[i].deprecated ? " (deprecated)" : "") << std::endl;
}
os << std::endl;
os << "Methodlist:" << std::endl;
for ( int i = 0; i < n_objmethods; i++ )
{
os << object_methods[i].id << " " << object_methods[i].code << std::endl;
}
os << std::endl;
os << "Memberlist:" << std::endl;
for ( int i = 0; i < n_objmembers; i++ )
{
os << object_members[i].id << " " << object_members[i].code << std::endl;
}
}
#if 0
void matchReservedWords(char *buf,
int *nPartial,
int *nTotal)
{
int lenbuf = strlen(buf);
assert(nPartial && nTotal);
*nPartial = 0;
*nTotal = 0;
for(int i = 0; i < n_reserved; i++)
{
if (strnicmp(reserved_words[i].word, buf, lenbuf)==0)
(*nPartial)++;
if (stricmp(reserved_words[i].word, buf)==0)
(*nTotal)++;
}
}
#endif
/*
WTF is going on in this function? It seems like it waits for a match, followed
by a nonmatch? eh?
What does this mean for variables like "IfDone" etc?
*/
#if 0
int Parser::tryReservedWord(Token& tok, char *t, char **s)
{
char opbuf[10];
int bufp = 0;
int thisMatchPartial, thisMatchTotal;
int lastMatchTotal = 0;
while (t && *t) {
/* let's try to match it as we go. */
if (bufp==10) { err = PERR_BADTOKEN; return -1; }
opbuf[bufp++] = *t++;
opbuf[bufp] = '\0';
matchReservedWords(opbuf, &thisMatchPartial, &thisMatchTotal);
if (!thisMatchPartial) { /* can't match a bloody thing! */
switch(lastMatchTotal) {
case 0:
return 0; // this just wasn't a reserved word..
case 1: // this is the only way it will work..
// here, we don't match now but if we don't count
// this character, it was a unique match.
opbuf[bufp-1] = '\0';
tok.nulStr();
recognize_reserved_word(tok, opbuf);
*s = t-1;
return 1;
case 2: // here, with this character there is no match
// but before there were multiple matches.
// really shouldn't happen.
err = PERR_BADOPER;
return -1;
}
} else { /* this partially matches.. */
// "Remember....."
lastMatchTotal = thisMatchTotal;
}
}
if (thisMatchTotal == 1) {
tok.nulStr();
recognize_reserved_word( tok, opbuf );
*s = t;
return 1;
}
return 0; // didn't find one!
}
#endif
/**
* Tries to read a an operator from context
*
* @param tok Token&: The token to store the found literal into
* @param ctx CompilerContext&: The context to look into
* @param opList: The list of possible operators to look for, as Operator[]
* @param n_ops: Number of operators in the list
* @return 0 when no matching text is found, 1 on success, -1 on error (also sets err)
*/
int Parser::tryOperator( Token& tok,
const char *t,
const char **s,
Operator *opList, int n_ops,
char *opbuf )
{
int bufp = 0;
int thisMatchPartial;
Operator* pLastMatch = NULL;
Operator* pMatch = NULL;
while ( t && *t )
{
// if (strchr(operator_brk, *t)) mustBeOperator = 1;
/* let's try to match it as we go. */
if ( bufp == 4 ) { err = PERR_BADTOKEN; return -1; }
opbuf[bufp++] = *t++;
opbuf[bufp] = '\0';
matchOperators( opList, n_ops,
opbuf,
&thisMatchPartial, &pMatch );
if ( !thisMatchPartial )
{
if ( pMatch )
{ // no partial matches, but a total match.
break;
}
else if ( !pLastMatch )
{
// no total matches, no partial matches.
return 0;
}
else // (pLastMatch)
{
// had a match before, but no partials this time.
break;
}
}
else
{ /* this partially matches.. */
// "Remember....."
if ( pMatch )
{
pLastMatch = pMatch;
}
}
}
if ( pMatch == NULL )
pMatch = pLastMatch;
if ( pMatch )
{
*s += strlen( pMatch->code );
tok.module = Mod_Basic;
tok.id = pMatch->id;
tok.type = pMatch->type;
tok.precedence = pMatch->precedence;
tok.deprecated = pMatch->deprecated;
tok.setStr( pMatch->code );
return 1;
}
return 0; // didn't find one!
}
/**
* Tries to read a binary operator from context
*
* @param tok Token&: The token to store the found literal into
* @param ctx CompilerContext&: The context to look into
* @return 0 when no matching text is found, 1 on success, -1 on error (also sets err)
*/
int Parser::tryBinaryOperator( Token& tok, CompilerContext& ctx )
{
int res;
char opbuf[10];
res = tryOperator( tok, ctx.s, &ctx.s, binary_operators, n_operators, opbuf );
return res;
}
/**
* Tries to read an unary operator from context
*
* @param tok Token&: The token to store the found literal into
* @param ctx CompilerContext&: The context to look into
* @return 0 when no matching text is found, 1 on success, -1 on error (also sets err)
*/
int Parser::tryUnaryOperator( Token& tok, CompilerContext& ctx )
{
int res;
char opbuf[10];
res = tryOperator( tok, ctx.s, &ctx.s, unary_operators, n_unary, opbuf );
return res;
}
/**
* Tries to read a numeric value from context
*
* @param tok Token&: The token to store the found literal into
* @param ctx CompilerContext&: The context to look into
* @return 0 when no matching text is found, 1 on success, -1 on error (also sets err)
*/
int Parser::tryNumeric( Token& tok, CompilerContext& ctx )
{
if ( isdigit( ctx.s[0] ) || ctx.s[0] == '.' )
{
char *endptr, *endptr2;
int l = strtol( ctx.s, &endptr, 0 );
double d = strtod( ctx.s, &endptr2 );
tok.type = TYP_OPERAND;
if ( endptr >= endptr2 )
{ // long got more out of it, we'll go with that
tok.id = TOK_LONG;
tok.lval = l;
ctx.s = endptr;
return 1;
}
else
{
tok.id = TOK_DOUBLE;
tok.dval = d;
ctx.s = endptr2;
return 1;
}
}
return 0; // not numeric
}
/**
* Tries to read a literal (string/variable name) from context
*
* @param tok Token&: The token to store the found literal into
* @param ctx CompilerContext&: The context to look into
* @return 0 when no matching text is found, 1 on success, -1 on error (also sets err)
*/
int Parser::tryLiteral( Token& tok, CompilerContext& ctx )
{
if ( ctx.s[0] == '\"' )
{
const char* end = &ctx.s[1];
std::string lit;
bool escnext = false;
for ( ;; )
{
if ( !*end )
{
err = PERR_UNTERMSTRING;
return -1;
}
if ( escnext )
{
escnext = false;
if ( *end == 'n' )
lit += '\n';
else if ( *end == 't' )
lit += '\t';
else
lit += *end;
}
else
{
if ( *end == '\\' )
escnext = true;
else if ( *end == '\"' )
break;
else
lit += *end;
}
++end;
}
/*
char *end = strchr(&ctx.s[1], '\"');
if (!end)
{
err = PERR_UNTERMSTRING;
return -1;
}
*/
//int len = end - ctx.s; // "abd" len = 5-1 = 4
tok.id = TOK_STRING; // this is a misnomer I think!
tok.type = TYP_OPERAND;
tok.copyStr( lit.c_str() );
ctx.s = end + 1; // skip past the ending delimiter
return 1;
}
else if ( isalpha( ctx.s[0] ) || ctx.s[0] == '_' )
{ // we have a variable/label/verb.
const char *end = ctx.s;
while ( *end &&
!isspace( *end ) &&
strchr( ident_allowed, *end ) )
{
++end;
}
// Catch identifiers of the form module::name
if ( end[0] == ':' && end[1] == ':' )
{
end += 2;
while ( *end &&
!isspace( *end ) &&
strchr( ident_allowed, *end ) )
{
++end;
}
}
int len = static_cast<int>( end - ctx.s + 1 ); // "abcd"
tok.copyStr( ctx.s, len - 1 );
tok.id = TOK_IDENT;
tok.type = TYP_OPERAND;
ctx.s = end;
return 1;
}
return 0;
}
int Parser::recognize_binary( Token& tok, const char *buf, const char** /*s*/ )
{
for ( int i = 0; i < n_operators; i++ )
{
if ( stricmp( buf, binary_operators[i].code ) == 0 )
{
tok.module = Mod_Basic;
tok.id = binary_operators[i].id;
tok.type = binary_operators[i].type;
tok.precedence = binary_operators[i].precedence;
/*
if (tok.type == TYP_OPERATOR &&
(tok.id == TOK_ADDMEMBER ||
tok.id == TOK_DELMEMBER ||
tok.id == TOK_CHKMEMBER ||
tok.id == TOK_MEMBER) )
{
int res;
Token tk2;
res = getToken( s, tk2 );
if (res < 0) return res;
if (tk2.type != TYP_OPERAND || tk2.id != TOK_IDENT)
return -1;
tok.copyStr( tk2.tokval() );
}
else
{
*/
tok.setStr( binary_operators[i].code );
//tok.nulStr();
/*
}
*/
return 1;
}
}
return 0;
}
int Parser::recognize_unary( Token& tok, const char *buf )
{
for ( int i = 0; i < n_unary; i++ )
{
if ( stricmp( buf, unary_operators[i].code ) == 0 )
{
tok.module = Mod_Basic;
tok.id = unary_operators[i].id;
tok.type = unary_operators[i].type;
tok.precedence = unary_operators[i].precedence;
tok.setStr( unary_operators[i].code );
return 1;
}
}
return 0;
}
int Parser::recognize( Token& tok, const char *buf, const char **s )
{
if ( recognize_binary( tok, buf, s ) ) return 1;
return recognize_unary( tok, buf );
}
bool Parser::recognize_reserved_word( Token& tok, const char *buf )
{
if ( tok.id != TOK_IDENT )
return false;
auto itr = reservedWordsByName.find( buf );
if ( itr != reservedWordsByName.end() )
{
ReservedWord* rv = ( *itr ).second;
tok.module = Mod_Basic;
tok.id = rv->id;
tok.type = rv->type;
tok.precedence = rv->precedence;
tok.deprecated = rv->deprecated;
tok.setStr( rv->word );
return true;
}
return false;
}
/*
int isOperator(Token& token)
{
for(int i=0;i < n_operators; i++) {
if (stricmp(binary_operators[i].code, token )==0) {
token.setStr(binary_operators[i].code);
token.id = binary_operators[i].id;
token.type = binary_operators[i].type;
return 1;
}
}
return 0;
}
*/
Precedence find_precedence( Token& token )
{
return static_cast<Precedence>( token.precedence );
}
void Parser::reinit( Expression& ex )
{
// qCA.clear();
while ( !ex.CA.empty() )
{
delete ex.CA.front();
ex.CA.pop();
}
// TX.clear()
while ( !ex.TX.empty() )
{
delete ex.TX.top();
ex.TX.pop();
}
err = PERR_NONE;
ext_err[0] = '\0';
}
/**
* Reads next token from given context
*
* what is a token? a set of homogeneous characters
* a Label:
* begins with [A-Za-z_], followed by [A-Za-z0-9]
*
* A character literal:
* begins with '"', ends with '"'. anything goes in between.
*
* a Number:
* begins with [0-9] (note: not plus or minus, these get eaten as unary ops)
* can be either a float (stored as double), or a long.
* 0xABC is hex, which is read okay.
* So is 0.5e+17. I let strtod do most of the work.
* basically whichever of strtod or strtol can do more with it
* (endptr arg is greater on exit), that's what i decide that it is.
*
* an operator:
* any collection of the operator characters
* [ ( ) * / + - < = > ,] not separated by whitespace, digits, or alphas
* note a collection of more than one is considered a SINGLE operator.
* So if you put 6*-7, *- is the operator. nasty I know, but
* what am I supposed to do? (Maximal munch, is what is actually done!)
*
* @param tok Token&: The token to store the found literal into
* @param ctx CompilerContext&: The context to look into
* @param pexpr unused
* @return 0 when no matching text is found, 1 on success, -1 on error (also sets err)
*/
int Parser::getToken( CompilerContext& ctx, Token& tok, Expression* /* expr not used */ )
{
int hit = 0;
int res = ctx.skipcomments();
if ( res ) return res;
tok.dbg_filenum = ctx.dbg_filenum;
tok.dbg_linenum = ctx.line;
if ( ctx.s[0] == ';' )
{
tok.module = Mod_Basic;
tok.id = TOK_SEMICOLON;
tok.type = TYP_DELIMITER;
tok.setStr( ";" );
ctx.s++;
return 0;
}
hit = tryLiteral( tok, ctx );
if ( hit == -1 )
{
return -1;
}
else if ( hit )
{
recognize_reserved_word( tok, tok.tokval() );
return 0;
}
/*
hit = tryReservedWord(tok, t, s);
if (hit==-1) return -1;
else if (hit) return 0;
*/
hit = tryBinaryOperator( tok, ctx );
if ( hit == -1 ) return -1;
else if ( hit ) return 0;
hit = tryUnaryOperator( tok, ctx );
if ( hit == -1 ) return -1;
else if ( hit ) return 0;
//label:
//A:=4;
hit = tryNumeric( tok, ctx );
if ( hit == -1 ) return -1;
else if ( hit ) return 0;
if ( ctx.s[0] == ':' )
{
++ctx.s;
tok.id = RSV_COLON;
tok.type = TYP_RESERVED;
tok.setStr( ":" );
return 0;
}
INFO_PRINT << "Your syntax frightens and confuses me.\n";
err = PERR_WAAH;
return -1;
}
/**
* Reads next token from given context, but without modifying the context
*
* @see Parser::getToken( CompilerContext& ctx, Token& tok )
*/
int Parser::peekToken( const CompilerContext& ctx, Token& token, Expression* expr )
{
CompilerContext tctx( ctx );
return getToken( tctx, token, expr );
}
/* Parser::parseToken deleted. */
/* not used? ens 12/10/1998
int Parser::IP(Expression& expr, char *s)
{
reinit(expr);
Token *ptoken;
if (!quiet) cout << "Parsing \"" << s << '\"' << endl;
expr.TX.push(new Token); // push terminator token
ptoken = new Token;
while (getToken(&s, *ptoken)==0)
{
parseToken(expr, ptoken);
ptoken = new Token;
}
parseToken(expr, ptoken);
return 0;
}
*/
int SmartParser::isOkay( const Token& token, BTokenType last_type )
{
BTokenType this_type = token.type;
if ( !quiet )
INFO_PRINT << "isOkay(" << this_type << "," << last_type << ")\n";
if ( last_type == TYP_FUNC || last_type == TYP_USERFUNC || last_type == TYP_METHOD )
last_type = TYP_OPERAND;
if ( this_type == TYP_FUNC || this_type == TYP_USERFUNC || this_type == TYP_METHOD )
this_type = TYP_OPERAND;
if ( token.id == TOK_LBRACE ) // an array declared somewhere out there
this_type = TYP_OPERAND;
if ( last_type > TYP_TESTMAX ) return 1; // assumed okay
if ( this_type > TYP_TESTMAX ) return 1; // assumed okay
return allowed_table[last_type][this_type]; // maybe okay
}
/*
int SmartParser::isFunc(Token& token, Verb **v)
{
if (token.id != TOK_IDENT) return 0; // ain't no verb..
// note that this catches string literals.
assert(token.tokval());
if (isInTable(parser_verbs, n_parser_verbs, token.tokval(), v))
{
token.id = (*v)->id;
token.lval = (*v)->narg;
token.type = TYP_FUNC;
token.module = Mod_Basic;
return 1;
}
return 0;
}
*/
/**
* Like Parser::tryLiteral, with extra elements supported
*
* Labels.
* A label is an ident operand, followed by a colon, followed by
* either whitespace or end-of-file.
* Note, this definition just happens to exclude ':=' and '::',
* which is important.
*
* @see Parser::tryLiteral( Token& tok, CompilerContext& ctx )
*/
int SmartParser::tryLiteral( Token& tok, CompilerContext& ctx )
{
int res;
res = Parser::tryLiteral( tok, ctx );
if ( res == 1 && tok.id == TOK_IDENT )
{ // check for "label:"
// whitespace used to be skipped here.
// while (*t && isspace(*t)) t++;
if ( !ctx.s[0] ) // ident EOF can't be a label
{
return 1;
}
// this might be a nice place to look for module::function, too.
#if 0
if (t[0] == ':' && t[1] == ':')
{
t += 2;
*s = t;
Token tok2;
int res2 = Parser::tryLiteral( tok2, t, s );
if (res2 < 0) return res2;
if (res2 == 0)
return -1;
// append '::{tok2 tokval}' to tok.tokval
// (easier when/if token uses string)
}
#endif
if ( ctx.s[0] == ':' && ( ctx.s[1] == '\0' || isspace( ctx.s[1] ) ) )
{
tok.id = CTRL_LABEL;
tok.type = TYP_LABEL;
++ctx.s;
}
return 1;
}
return res;
}
int SmartParser::parseToken( CompilerContext& ctx, Expression& expr, Token *token )
{
// return Parser::parseToken(token);
if ( !quiet )
{
fmt::Writer _tmp;
_tmp << "parseToken( " << *token << ")\n";
_tmp << " CA: ";
std::queue<Token*> ca(expr.CA);
while ( !ca.empty() )
{
Token* tk = ca.front();
_tmp << *tk << " ";
ca.pop();
}
_tmp << "\n";
_tmp << " TX: ";
std::stack<Token*> tx(expr.TX);
while ( !tx.empty() )
{
Token* tk = tx.top();
_tmp << *tk << " ";
tx.pop();
}
INFO_PRINT << _tmp.str() << "\n";
}
for ( ;; )
{
Token *last = expr.TX.top();
switch ( token->type )
{
case TYP_FUNC:
case TYP_USERFUNC:
case TYP_OPERAND:
expr.CA.push( token );
return 0;
case TYP_TERMINATOR:
switch ( last->type )
{
case TYP_TERMINATOR:
delete token;
delete expr.TX.top();
expr.TX.pop();
return 1; // all done!
case TYP_UNARY_OPERATOR:
expr.CA.push( expr.TX.top() );
expr.TX.pop();
break;
case TYP_OPERATOR:
expr.CA.push( expr.TX.top() );
expr.TX.pop();
break;
case TYP_LEFTPAREN:
err = PERR_MISSRPAREN;
return -1;
case TYP_LEFTBRACKET:
err = PERR_MISSRBRACKET;
return -1;
default:
err = PERR_WAAH;
return -1;
}
break;
case TYP_UNARY_OPERATOR:
case TYP_OPERATOR:
case TYP_LEFTBRACKET:
// these start out at 0, but need to start at one.
if ( token->type == TYP_LEFTBRACKET )
token->lval = 1;
switch ( last->type )
{
case TYP_TERMINATOR:
case TYP_LEFTPAREN:
case TYP_LEFTBRACKET:
// possible check CA for array dereference operator here.
// if one found, remove it, and change this assignment to a "deref and assign"
expr.TX.push( token );
return 0;
case TYP_UNARY_OPERATOR:
case TYP_OPERATOR:
int this_prec;
int last_prec;
this_prec = find_precedence( *token );
last_prec = find_precedence( *last );
if ( this_prec > last_prec )
{
expr.TX.push( token );
return 0;
}
else
{
expr.CA.push( expr.TX.top() );
expr.TX.pop();
}
break;
default:
err = PERR_WAAH;
return -1;
break;
}
break;
case TYP_LEFTPAREN:
expr.TX.push( token );
return 0;
case TYP_RIGHTPAREN:
switch ( last->type )
{
case TYP_TERMINATOR:
err = PERR_UNEXRPAREN;
return -1;
case TYP_UNARY_OPERATOR:
case TYP_OPERATOR:
expr.CA.push( expr.TX.top() );
expr.TX.pop();
break;
case TYP_LEFTPAREN:
delete token;
delete expr.TX.top();
expr.TX.pop();
return 0;
default:
INFO_PRINT << "Unmatched ')' in expression. (Trying to match against a '" << *last << "')\n"
<< ctx;
ERROR_PRINT << "parseToken(): Not sure what to do.\n"
<< "Token: " << *token << "\n"
<< "Last: " << *last << "\n";
throw std::runtime_error( "Error in parseToken() (1)" );
}
break;
//case TYP_LEFTBRACKET:
// expr.TX.push(token);
// return 0;
case TYP_RIGHTBRACKET:
switch ( last->type )
{
case TYP_TERMINATOR:
err = PERR_UNEXRBRACKET;
return -1;
case TYP_UNARY_OPERATOR:
case TYP_OPERATOR:
expr.CA.push( expr.TX.top() );
expr.TX.pop();
break;
case TYP_LEFTBRACKET:
Token *ptkn;
ptkn = new Token( TOK_ARRAY_SUBSCRIPT, TYP_OPERATOR );
ptkn->lval = last->lval;
ptkn->dbg_filenum = token->dbg_filenum;
ptkn->dbg_linenum = token->dbg_linenum;
if ( ptkn->lval != 1 )
ptkn->id = INS_MULTISUBSCRIPT;
expr.CA.push( ptkn );
delete token;
delete expr.TX.top();
expr.TX.pop();
return 0;
default:
INFO_PRINT << "Unmatched ']' in expression. (Trying to match against a '" << *last << "')\n"
<< ctx;
ERROR_PRINT << "parseToken(): Not sure what to do.\n"
<< "Token: " << *token << "\n"
<< "Last: " << *last << "\n";
throw std::runtime_error("Error in parseToken() (2)");
}
break;
/* Special Case: two syntaxes are supported for array indexing:
A[3][4] and A[3,4].
If this is a comma, and we are working in a left bracket area,
increase the number of indices (stored in lval of the leftbracket)
*/
case TYP_SEPARATOR:
if ( last->type == TYP_LEFTBRACKET )
{
++last->lval;
//Token *ptkn = new Token( Mod_Basic, TOK_ARRAY_SUBSCRIPT, TYP_OPERATOR );
//ptkn->lval = last->lval++;
//expr.CA.push( ptkn );
delete token;
return 0;
}
else if ( last->type == TYP_UNARY_OPERATOR ||
last->type == TYP_OPERATOR )
{
expr.CA.push( expr.TX.top() );
expr.TX.pop();
break; // try again
}
else
{
err = PERR_UNEXPCOMMA;
return -1;
}
default:
INFO_PRINT << "Don't know what to do with '" << *token << "' in SmartParser::parseToken\n"
<< ctx;
err = PERR_WAAH;
return -1;
}
}
return 0;
}
/**
* Some identifiers are functions (user-defined or module-defined)
* these are recognized here. HOWEVER, in some cases these should
* be ignored - particularly, after the "." operator and its ilk.
* For example, if the LEN function is defined,
* "print a.len;" should still be valid (assuming A is a variable
* with a 'len' member). In these cases, the operator in question
* will be at the top of the TX stack. So, if we find this operator
* there, we won't check for functions.
* This is also the perfect opportunity to morph would-be identifiers
* into strings, or "member names" if that turns out the way to go.
* (Normally, we would emit TOK_IDENT(left) TOK_IDENT(right) TOK_MEMBER.
* The problem here is that TOK_IDENT(left) is seen as a variable
* (quite rightly), but so is TOK_IDENT(right), which is wrong.
* We used to transform this in addToken, but this is better
* I think.)
*
* @note let's suppose..
* an overridden getToken is really smart, and figures out if IDENTS
* are variable names, verbs, functions, or labels. To this end it
* pulls out the ':' if necessary.
* TYP_OPERAND, TOK_VARNAME
* TYP_FUNC, TOK_MID, <-- TYP_OPERAND for purposes of legality
* TYP_PROC, TOK_PRINT,
* TYP_LABEL, (don't care)
*
* IP still does the same thing only it no longer looks for isVerb.
* have the new getToken put, say, the verb number in lval, so we now
* have an array element number.
*
* @see Parser::getToken( CompilerContext& ctx, Token& token )
*/
int SmartParser::getToken( CompilerContext& ctx, Token& token, Expression* pexpr )
{
int res = Parser::getToken( ctx, token );
if ( res == -1 ) return -1;
if ( token.id == TOK_IDENT )
{
if ( pexpr )
{
if ( !pexpr->TX.empty() )
{
Token* tkn = pexpr->TX.top();
if ( tkn->id == TOK_MEMBER ||
tkn->id == TOK_ADDMEMBER ||
tkn->id == TOK_DELMEMBER ||
tkn->id == TOK_CHKMEMBER )
{
token.id = TOK_STRING;
return res;
}
}
}
if ( isFunc( token, &modfunc_ ) ) return 0;
if ( isUserFunc( token, &userfunc_ ) ) return 0;
}
return res;
}
int SmartParser::getArgs( Expression& expr, CompilerContext& ctx )
{
unsigned nargs = 0;
int res = 0;
ModuleFunction* mfUse = modfunc_;
Token token;
// int nullArgOk = 0;
// int nArgsUse = v->narg;
/*
if (vUse->narg == -1) {
nullArgOk = 1;
nArgsUse = 1;
}
*/
switch ( mfUse->nargs )
{
case 0: // verb[()];, so INPUT(); or INPUT;
peekToken( ctx, token );
if ( token.id == TOK_LPAREN )
{
getToken( ctx, token );
peekToken( ctx, token );
if ( token.id != TOK_RPAREN )
{
err = PERR_TOOMANYARGS;
return -1;
}
else
{
getToken( ctx, token );
}
}
break;
case 1: // for one arg, make parens optional.
peekToken( ctx, token );
if ( token.id != TOK_LPAREN )
{
if ( token.id == TOK_SEMICOLON )
{
// if (nullArgOk) return 0;
err = PERR_TOOFEWARGS;
return -1;
}
res = IIP( expr,
ctx,
EXPR_FLAG_SEMICOLON_TERM_ALLOWED |
EXPR_FLAG_COMMA_TERM_ALLOWED );
break;
}
// FALLTHROUGH if left paren specified
default:
// for more than one arg, or voluntary left paren on single
// arg, so parens are required
// grab the opening parenthesis
getToken( ctx, token );
if ( token.id != TOK_LPAREN )
{
res = -1;
err = PERR_MISSLPAREN;
return -1;
}
for ( nargs = 0; nargs < mfUse->nargs; nargs++ )
{
if ( nargs ) // grab the comma.
{
getToken( ctx, token );
if ( token.id != TOK_COMMA )
{
// if (nullArgOk) break;
res = -1;
err = PERR_TOOFEWARGS;
break;
}
}
res = IIP( expr,
ctx,
EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED |
EXPR_FLAG_COMMA_TERM_ALLOWED );
if ( res < 0 ) break;
err = PERR_NONE;
}
if ( nargs != mfUse->nargs )
{
err = PERR_TOOFEWARGS;
return -1;;
}
// get the right paren.
getToken( ctx, token );
if ( token.id != TOK_RPAREN )
{
err = PERR_TOOMANYARGS;
res = -1;
}
break;
}
// now have parsed the arguments and parenthesis.
// leave the delimiter ';', it will be picked up later, or
// may not be there (i.e. MID(A,5) + A; )
return res;
}
bool SmartParser::callingMethod( CompilerContext& ctx )
{
// if we have something like x.foo(), change to call-method
CompilerContext tctx( ctx );
Token tk_name, tk_paren;
if ( getToken( tctx, tk_name ) == 0 &&
getToken( tctx, tk_paren ) == 0 &&
( tk_name.id == TOK_IDENT || tk_name.id == TOK_FUNC || tk_name.id == TOK_USERFUNC ) &&
tk_paren.id == TOK_LPAREN )
{
return true;
}
return false;
}
/**
* if comma terminator is allowed, (reading args, or declaring variables)
* leaves the terminator/comma.
* if comma term not allowed, eats the semicolon.
* if right paren allowed, leaves the right paren.
*
* @note (obviously, this function's behavior needs some work!)
**/
int SmartParser::IIP( Expression& expr, CompilerContext& ctx, unsigned flags )
{
BTokenType last_type = TYP_TERMINATOR;
Token last_token;
//Token* debug_last_tx_token = NULL;
int done = 0;
int res = 0; // 1=done, -1=error, 0=not done
int semicolon_term_allowed = flags & EXPR_FLAG_SEMICOLON_TERM_ALLOWED;
int comma_term_allowed = flags & EXPR_FLAG_COMMA_TERM_ALLOWED;
int rightparen_term_allowed = flags & EXPR_FLAG_RIGHTPAREN_TERM_ALLOWED;
int rightbrace_term_allowed = flags & EXPR_FLAG_RIGHTBRACE_TERM_ALLOWED;
int dictkey_term_allowed = flags & EXPR_FLAG_DICTKEY_TERM_ALLOWED;
int endenum_term_allowed = flags & EXPR_FLAG_ENDENUM_TERM_ALLOWED;
int to_term_allowed = flags & EXPR_FLAG_TO_TERM_ALLOWED;
int auto_term_allowed = flags & EXPR_FLAG_AUTO_TERM_ALLOWED;
if ( !quiet )
{
char buf[80];
Clib::stracpy( buf, ctx.s, 80 );
strtok( buf, "\r\n" );
INFO_PRINT << "Parsing " << buf << "\n";
}
expr.TX.push( new Token ); /* push a terminator token */
int leftbracket_count = 0;
while ( !done && ( res == 0 ) )
{
const char *t = ctx.s;
Token token;
res = peekToken( ctx, token );
if ( res ) break;
if ( !isLegal( token ) )
{
err = PERR_NOTLEGALHERE;
res = -1;
break;
}
// if (token.type == TYP_DELIMITER) break;
//debug_last_tx_token = expr.TX.top();
/*
if (comma_term_allowed)
{
Token* tkn = expr.TX.top();
if (leftbracket_count > 0) // tkn != NULL && tkn->id == TOK_LBRACKET )
{
// ignore commas if we're in a left bracket situation.
;
}
else if (token.id == TOK_COMMA || token.id == TOK_SEMICOLON)
{
break;
}
}
*/
if ( leftbracket_count == 0 )
{
if ( comma_term_allowed &&
( token.id == TOK_COMMA || token.id == TOK_SEMICOLON ) )
{
break;
}
if ( endenum_term_allowed &&
( token.id == RSV_ENDENUM ) )
{
break;
}
if ( to_term_allowed &&
( token.id == RSV_TO ) )
{
break;
}
}
if ( rightbrace_term_allowed && token.id == TOK_RBRACE )
{
break;
}
if ( dictkey_term_allowed && token.id == TOK_DICTKEY )
{
break;
}
/*
else
{
if (token.type == TYP_SEPARATOR)
{
err = PERR_UNEXPCOMMA;
res = -1;
break;
}
}
*/
// auto-terminated expressions need to not eat what broke them out...
CompilerContext save_ctx( ctx );
res = getToken( ctx, token, &expr );
if ( semicolon_term_allowed && token.id == TOK_SEMICOLON )
break;
if ( token.id == TOK_LBRACKET )
++leftbracket_count;
else if ( token.id == TOK_RBRACKET )
--leftbracket_count;
if ( !isOkay( token, last_type ) )
{
if ( token.type == TYP_OPERATOR ) // check for a unary operator that looks the same.
recognize_unary( token, token.tokval() );
if ( !isOkay( token, last_type ) )
{
if ( auto_term_allowed )
{
ctx = save_ctx;
break;
}
res = -1;
err = PERR_ILLEGALCONS;
ctx.s = t; // FIXME operator=
INFO_PRINT << "Token '" << token << "' cannot follow token '" << last_token << "'\n";
if ( last_token.type == TYP_OPERAND &&
token.id == TOK_LPAREN )
{
INFO_PRINT << "Function " << last_token << "() is not defined.\n";
}
break;
}
}
else if ( last_type == TYP_TERMINATOR && token.type == TYP_LEFTBRACE && ( ( compilercfg.DisplayWarnings || compilercfg.ErrorOnWarning ) && compilercfg.ParanoiaWarnings ) )
{
INFO_PRINT << "Warning: Using { } is inappropriate; please define array, struct or dictionary.\n";
if ( compilercfg.ErrorOnWarning )
throw std::runtime_error("Warnings treated as errors.");
else
INFO_PRINT << ctx;
}
last_type = token.type;
last_token = token;
//
// only certain types of things can be in an auto-terminated expr
// stuff like 'var' and statements can't.
//
if ( auto_term_allowed )
{
if ( token.type != TYP_FUNC &&
token.type != TYP_USERFUNC &&
token.type != TYP_OPERAND &&
token.type != TYP_UNARY_OPERATOR &&
token.type != TYP_OPERATOR &&
token.type != TYP_LEFTPAREN &&
token.type != TYP_RIGHTPAREN &&
token.type != TYP_LEFTBRACKET &&
token.type != TYP_RIGHTBRACKET &&
token.type != TYP_SEPARATOR &&
token.id != TOK_LBRACE &&
token.id != TOK_RBRACE )
{
ctx = save_ctx;
break;
}
}
Token *ptok2;
if ( token.type == TYP_USERFUNC )
{
res = getUserArgs( expr, ctx );
if ( res < 0 ) return res;
ptok2 = new Token( token );
res = parseToken( ctx, expr, ptok2 );
if ( res < 0 )
{
delete ptok2;
ctx.s = t;
}
}
else if ( token.type == TYP_FUNC )
{
userfunc_ = modfunc_->uf;
res = getUserArgs( expr, ctx, false );
if ( res < 0 )
{
INFO_PRINT << "Error getting arguments for function " << token.tokval() << "\n"
<< ctx << "\n";
return res;
}
ptok2 = new Token( token );
res = parseToken( ctx, expr, ptok2 );
if ( res < 0 )
{
delete ptok2;
ctx.s = t; // FIXME operator=
if ( ( err == PERR_UNEXRPAREN ) && rightparen_term_allowed )
{
err = PERR_NONE;
res = 0;
done = 1;
}
}
}
else if ( token.id == TOK_ARRAY )
{
auto array_tkn = new Token( TOK_ARRAY, TYP_OPERAND );
array_tkn->dbg_filenum = token.dbg_filenum;
array_tkn->dbg_linenum = token.dbg_linenum;
res = parseToken( ctx, expr, array_tkn );
if ( res < 0 )
return res;
//expr.CA.push( array_tkn );
// 'array' can be of the following forms:
// var x := array; // preferred
// var x := array { 2, 4, 6, 1 }; // preferred
// var x := array ( 2, 4, 6, 1 ); // not preferred, looks too much like a multi-dim
Token peek_token;
res = peekToken( ctx, peek_token );
if ( res == 0 )
{
if ( peek_token.id == TOK_LPAREN )
{
res = getArrayElements( expr, ctx );
}
else if ( peek_token.id == TOK_LBRACE )
{
// this form expects that the LBRACE has been eaten, so do so.
getToken( ctx, peek_token );
res = getNewArrayElements( expr, ctx );
}
if ( res < 0 )
{
INFO_PRINT << "Error getting elements for array\n";
}
}
}
else if ( token.id == TOK_LBRACE ) // a bare array declaration, like var x := { 2, 4 };
{
expr.CA.push( new Token( TOK_ARRAY, TYP_OPERAND ) );
res = getNewArrayElements( expr, ctx );
if ( res < 0 )
{
INFO_PRINT << "Error getting elements for array\n";
}
}
else if ( token.id == TOK_ERROR )
{
auto error_tkn = new Token( TOK_ERROR, TYP_OPERAND );
error_tkn->dbg_filenum = token.dbg_filenum;
error_tkn->dbg_linenum = token.dbg_linenum;
expr.CA.push( error_tkn );
res = getStructMembers( expr, ctx );
if ( res < 0 )
{
INFO_PRINT << "Error reading members for error\n";
}
}
else if ( token.id == TOK_STRUCT )
{
auto struct_tkn = new Token( TOK_STRUCT, TYP_OPERAND );
struct_tkn->dbg_filenum = token.dbg_filenum;
struct_tkn->dbg_linenum = token.dbg_linenum;
expr.CA.push( struct_tkn );
res = getStructMembers( expr, ctx );
if ( res < 0 )
{
INFO_PRINT << "Error reading members for struct\n";
}
}
else if ( token.id == TOK_DICTIONARY )
{
auto dict_tkn = new Token( token ); //Mod_Basic, TOK_DICTIONARY, TYP_OPERAND );
//struct_tkn->dbg_filenum = token.dbg_filenum;
//struct_tkn->dbg_linenum = token.dbg_linenum;
expr.CA.push( dict_tkn );
res = getDictionaryMembers( expr, ctx );
if ( res < 0 )
{
INFO_PRINT << "Error reading members for dictionary\n";
}
}
else if ( token.id == TOK_MEMBER && callingMethod( ctx ) )
{
// look for something like x.foo(b);
// we'll catch this on the 'foo'
// so check for preceding TOK_MEMBER, and succeeding TOK_LPAREN
ptok2 = new Token( token ); // this we will turn into an INS_CALL_METHOD.
int _res = parseToken( ctx, expr, ptok2 );
if ( _res < 0 )
{
delete ptok2;
return _res;
}
// grab the method name
getToken( ctx, token, &expr );
std::string methodName(token.tokval());
Clib::mklower( methodName );
int nargs;
res = getMethodArguments( expr, ctx, nargs );
// our ptok2 is now sitting in TX. Move it to CA.
Token* _t = expr.TX.top();
expr.TX.pop();
expr.CA.push( _t );
ObjMethod* objmeth = getKnownObjMethod( methodName.c_str() );
if ( objmeth != NULL && compilercfg.OptimizeObjectMembers )
{
ptok2->id = INS_CALL_METHOD_ID;
ptok2->type = TYP_METHOD;
ptok2->lval = nargs;
ptok2->dval = objmeth->id;
}
else
{
ptok2->id = INS_CALL_METHOD;
ptok2->type = TYP_METHOD;
ptok2->lval = nargs;
ptok2->copyStr( methodName.c_str() );
}
last_type = TYP_OPERAND;
}
else
{
if ( res >= 0 )
{
ptok2 = new Token( token );
res = parseToken( ctx, expr, ptok2 );
if ( res < 0 )
{
delete ptok2;
ctx.s = t; // FIXME operator=
if ( ( err == PERR_UNEXRPAREN ) && rightparen_term_allowed )
{
err = PERR_NONE;
res = 0;
done = 1;
}
}
}
}
if ( flags & EXPR_FLAG_SINGLE_ELEMENT )
done = 1;
}
int nres = parseToken( ctx, expr, new Token ); /* end of expression */
if ( res >= 0 ) res = nres;
if ( !comma_term_allowed && !quiet )
{
INFO_PRINT << "Result: " << res << "\n";
}
return res;
}
/* not used? 12/10/1998 ens
int SmartParser::IP(Expression& expr, char *s)
{
// return Parser::IP(s);
reinit(expr);
int res = IIP(expr, &s, EXPR_FLAG_SEMICOLON_TERM_ALLOWED);
if (res < 0 && !quiet)
{
cout << "Parse Error: " << ParseErrorStr[err];
if (ext_err[0]) cout << " " << ext_err;
cout << endl;
}
return res;
}
*/
int SmartParser::IP( Expression& expr, CompilerContext& ctx )
{
reinit( expr );
return IIP( expr, ctx, EXPR_FLAG_SEMICOLON_TERM_ALLOWED );
}
}
}