1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105 |
- /* GCSx
- ** COMPILE.CPP
- **
- ** Bytecode compilation (to pre-link state)
- */
- /*****************************************************************************
- ** Copyright (C) 2003-2006 Janson
- **
- ** This program is free software; you can redistribute it and/or modify
- ** it under the terms of the GNU General Public License as published by
- ** the Free Software Foundation; either version 2 of the License, or
- ** (at your option) any later version.
- **
- ** This program is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ** GNU General Public License for more details.
- **
- ** You should have received a copy of the GNU General Public License
- ** along with this program; if not, write to the Free Software
- ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
- *****************************************************************************/
- #include "all.h"
- // Code has been ear-marked for places that must be revisted if certain
- // features are later added; search for the following:
- // OPERATOR++ (refers to prefix or postfix -- or ++)
- // OPERATOR+= (refers to the entire family of similar assignment operators)
- // @TODO: all compiler and tokenizer asserts need throw_Compile and need
- // catch blocks if memory resources need to be freed (such as Operands)
- // @TODO: sub-expression memoization
- // @TODO:
- // warning if a function definitively never returns and isn't main ('end' doesn't count?)
- // some optimizations:
- // value left over after an expression (e.g. a bare function) doesn't HAVE to be DISCARD'd yet (can accumulate, but watch out for labels/etc)
- // DISCARD followed by PUSH could sometimes be a STORE
- // POP followed by equivalent PUSH could be a STORE
- // RETurning local[0] doesn't need it to be PUSHed first (just return it and reduce # of entries discarded)
- // In fact, returning local[x] could be done as DISCARD, RET instead of PUSH, RET (would be faster)
- // JUMP, IFTRUE, IFFALSE, SUBR to a JUMP, RET, or STOP is redundant (SUBR to RET shouldn't happen)
- // IFTRUE, IFFALSE followed by a JUMP should be condensed (flipping the condition)
- // IFTRUE, IFFALSE, JUMP to the next opcode should be removed
- // (remove NOOPs if ever used)
- // PUSH of a literal followed by later converting that literal (ex: float a = 5 / b does this)
- // DISCARD followed by STOP is unneeded
- // PUSH followed by DISCARD should be removed
- // defining members for built-in object types
- struct ObjectMember {
- int objtype;
- const char* name;
- int index;
- DataType membertype;
- int readonly;
- };
- static ObjectMember memberTable[] = {
- { SUB_SPRITE, "x", Sprite::PROP_X, { DATA_INT, 0, 0 }, 0 },
- { SUB_SPRITE, "y", Sprite::PROP_Y, { DATA_INT, 0, 0 }, 0 },
- { SUB_SPRITE, "originx", Sprite::PROP_ORIGIN_X, { DATA_INT, 0, 0 }, 0 },
- { SUB_SPRITE, "originy", Sprite::PROP_ORIGIN_Y, { DATA_INT, 0, 0 }, 0 },
- { SUB_SPRITE, "priority", Sprite::PROP_PRIORITY, { DATA_INT, 0, 0 }, 1 },
- { SUB_ENTITY, "name", Entity::PROP_NAME, { DATA_STR, 0, 0 }, 1 },
- { SUB_ENTITY, "sprite", Entity::PROP_SPRITE, { DATA_OBJECT, SUB_SPRITE, 0 }, 0 },
- { SUB_ENTITY, "priority", Entity::PROP_PRIORITY, { DATA_INT, 0, 0 }, 1 },
- { 0, NULL, 0, {0, 0, 0}, 0 }
- };
- Compiler::Compiler(const list<string>* src, const World* srcWorld) : stringMap(), main(), localInit(), functions(), strings() { start_func
- source = src;
- world = srcWorld;
- t = NULL;
- func = NULL;
- vars = NULL;
- labels = NULL;
- errorCount = 0;
- warningCount = 0;
-
- inFunctionScope = 0;
-
- main.stackDepth = 0;
- localInit.stackDepth = 0;
- functions.stackDepth = 0;
- strings.stackDepth = 0;
- }
- Compiler::~Compiler() { start_func
- delete t;
- destroyVariableMap(vars);
- delete vars;
- destroyLabelMap(labels);
- delete labels;
- }
- int Compiler::parseSymbols(FunctionMap* funcMap) { start_func
- assert(funcMap);
-
- t = new Tokenizer(source);
- func = funcMap;
-
- errorCount = 0;
- #ifdef COMPILEASSERT
- try {
- #endif
- doParseSymbols();
- #ifdef COMPILEASSERT
- }
- catch (CompileException& e) {
- destroyFunctionMap(funcMap);
- errorCount = 1;
- }
- #endif
-
- func = NULL;
- delete t;
- t = NULL;
-
- return errorCount;
- }
-
- int Compiler::compile(Uint32** bytecode, FunctionMap* funcMap, list<LinkEntry>* links, int scriptId) { start_func
- assert(bytecode);
- assert(funcMap);
- assert(links);
- assert(scriptId >= SUB_ENTITY_FIRST);
-
- t = new Tokenizer(source);
- func = funcMap;
- vars = new VariableMap;
- labels = new LabelMap;
- scriptType = scriptId;
-
- #ifdef COMPILEASSERT
- try {
- #endif
- doCompile(bytecode, links);
- #ifdef COMPILEASSERT
- }
- catch (CompileException& e) {
- t->outputError(e.details);
- }
- #endif
- errorCount = t->numErrors();
- warningCount = t->numWarnings();
- destroyVariableMap(vars);
- delete vars;
- destroyLabelMap(labels);
- delete labels;
- labels = NULL;
- vars = NULL;
- func = NULL;
- delete t;
- t = NULL;
-
- return errorCount;
- }
- void Compiler::doParseSymbols() { start_func
- assert(func);
- assert(t);
- int type;
- string token;
-
- // Ensure error output is off
- t->silentErrors();
-
- // Scan for functions, format- (parens much match if present)
- // [datatype] identifier [(] [datatype identifier [, datatype identifier ...]] [)] {
-
- // Also scan for scopes, format-
- // state identifier {
-
- // Will also handle certain # configuration commands
-
- // Any other commands or entire blocks are ignored
-
- DataType datatype;
- datatype.baseType = 0;
- int keywState = 0;
- string state;
- string ident;
- while (t->peekToken(type, token)) {
- int doSkip = 0;
-
- if (isDataType(type)) {
- // Note datatype and loop for next token
- datatype = parseDataType();
- if (keywState) doSkip = 1;
- }
- else if (type == TOKEN_IDENTIFIER) {
- ident = token;
- t->skipToken();
- if (keywState) {
- // Next token MUST be brace
- t->peekToken(type, token);
- if (type == TOKEN_OPEN_BRACE) {
- state = ident + "::";
- t->skipToken();
- }
- // Anything that doesn't follow the above format is just ignored
- // (main compile will remark on errors)
- keywState = 0;
- }
- else if (datatype.baseType) {
- vector<Parameter> plist;
-
- if (parseParameterList(plist)) {
- // Next token MUST be brace
- t->peekToken(type, token);
- if (type == TOKEN_OPEN_BRACE) {
- // Build function struct
- Function newFunc;
- newFunc.returntype = datatype;
- newFunc.returntype.flags &= ~(DATA_CONST | DATA_LOCAL | DATA_GLOBAL | DATA_PRIVATE | DATA_PUBLIC);
- newFunc.offset = -1;
- newFunc.numparam = plist.size();
- if (newFunc.numparam) {
- // Compile list of parameter types
- DataType* where = newFunc.parameters = new DataType[newFunc.numparam];
- for (vector<Parameter>::iterator pos = plist.begin(); pos != plist.end(); ++pos) {
- *where++ = (*pos).type;
- }
- }
- else newFunc.parameters = NULL;
- // Build function name
- ident = state + ident;
- // Store in map (overwrites previous if found)
- addFunctionMap(func, ident) = newFunc;
- // Don't skip token- we need to see { to skip function block
- }
- }
- // Anything that doesn't follow the above format is just ignored
- // (main compile will remark on errors)
- doSkip = 1;
- }
- else {
- doSkip = 1;
- }
- }
- else if (type == KEYW_STATE) {
- // Loop for next token
- keywState = 1;
- t->skipToken();
- if (datatype.baseType) doSkip = 1;
- }
- else if (type == TOKEN_CLOSE_BRACE) {
- state = blankString;
- t->skipToken();
- datatype.baseType = 0;
- keywState = 0;
- }
- else {
- doSkip = 1;
- }
-
- if (doSkip) {
- // Loop until end of line, skipping over blocks
- int blockLevel = 0;
- while (t->nextToken(type, token)) {
- if (type == TOKEN_OPEN_BRACE) ++blockLevel;
- if (type == TOKEN_CLOSE_BRACE) --blockLevel;
- if (((type == TOKEN_ENDLINE) || (type == TOKEN_CLOSE_BRACE)) &&
- (blockLevel == 0)) break;
- }
- datatype.baseType = 0;
- keywState = 0;
- }
- }
- }
- int Compiler::isDataType(int tokenType) { start_func
- if ((tokenType == KEYW_ARRAY) || (tokenType == KEYW_HASH) ||
- (tokenType == KEYW_INT) || (tokenType == KEYW_STR) ||
- (tokenType == KEYW_FLOAT) || (tokenType == KEYW_VAR) ||
- (tokenType == KEYW_GLOBAL) || (tokenType == KEYW_LOCAL) ||
- (tokenType == KEYW_CONST) || (tokenType == KEYW_VOID) ||
- (tokenType == KEYW_PRIVATE) || (tokenType == KEYW_PUBLIC) ||
- (tokenType == KEYW_OBJ_ENTITY) || (tokenType == KEYW_OBJ_SPRITE) ||
- (tokenType == KEYW_OBJ_SCENE) || (tokenType == TOKEN_STRINGTYPE))
- return 1;
- return 0;
- }
- DataType Compiler::parseDataType() { start_func
- DataType datatype;
- datatype.baseType = 0;
- datatype.flags = 0;
- datatype.subType = 0;
- int type;
- string token;
- while ((t->peekToken(type, token)) && (isDataType(type))) {
- t->skipToken();
- int base = 0, sub = 0, varConflict = 0, objConflict = 0;
- int flag = 0, flagConflict = 0;
- switch (type) {
- case KEYW_ARRAY:
- flag = DATA_ARRAY;
- flagConflict = DATA_HASH | DATA_CONST;
- break;
- case KEYW_HASH:
- flag = DATA_HASH;
- flagConflict = DATA_ARRAY | DATA_CONST;
- break;
- case KEYW_CONST:
- flag = DATA_CONST;
- flagConflict = DATA_HASH | DATA_CONST;
- varConflict = 1;
- objConflict = 1;
- break;
- case KEYW_VOID:
- base = DATA_VOID;
- flagConflict = DATA_HASH | DATA_ARRAY | DATA_CONST | DATA_LOCAL | DATA_GLOBAL;
- break;
- case KEYW_VAR:
- base = DATA_VAR;
- flagConflict = DATA_CONST;
- break;
- case KEYW_STR:
- base = DATA_STR;
- break;
- case KEYW_FLOAT:
- base = DATA_FLOAT;
- break;
- case KEYW_INT:
- base = DATA_INT;
- break;
- case KEYW_OBJ_ENTITY:
- // obj_entity doesn't conflict with script type
- if ((datatype.baseType == DATA_ENTITY) && (datatype.subType))
- datatype.baseType = 0;
- base = DATA_ENTITY;
- sub = 0;
- flagConflict = DATA_CONST;
- break;
- case KEYW_OBJ_SPRITE:
- base = DATA_OBJECT;
- sub = SUB_SPRITE;
- flagConflict = DATA_CONST;
- break;
- case KEYW_OBJ_SCENE:
- base = DATA_OBJECT;
- sub = SUB_SCENE;
- flagConflict = DATA_CONST;
- break;
- case KEYW_LOCAL:
- flag = DATA_LOCAL;
- flagConflict = DATA_GLOBAL;
- break;
- case KEYW_GLOBAL:
- flag = DATA_GLOBAL;
- flagConflict = DATA_LOCAL | DATA_PUBLIC | DATA_PRIVATE;
- break;
- case KEYW_PRIVATE:
- flag = DATA_PRIVATE;
- flagConflict = DATA_PUBLIC | DATA_GLOBAL;
- break;
- case KEYW_PUBLIC:
- flag = DATA_PUBLIC;
- flagConflict = DATA_PRIVATE | DATA_GLOBAL;
- break;
-
- case TOKEN_STRINGTYPE: {
- // script type doesn't conflict with obj_entity
- if ((datatype.baseType == DATA_ENTITY) && (datatype.subType == 0))
- datatype.baseType = 0;
-
- Script* found = world->findScriptCode(token);
- if (found) {
- sub = found->getId();
- }
- else {
- t->outputError("Unknown script type '%s' (no script by that name)", token.c_str());
- // RESOLUTION: fake sub type
- sub = SUB_ENTITY_FIRST;
- }
- base = DATA_ENTITY;
- break;
- }
- }
-
- if ((varConflict && datatype.baseType == DATA_VAR) ||
- (objConflict && (datatype.baseType == DATA_OBJECT || datatype.baseType == DATA_ENTITY))) {
- t->outputError("Conflicting datatype '%s' (two or more given types cannot be used together)", token.c_str());
- // RESOLUTION: ignore conflicting datatype
- }
- else {
- if (base) {
- if (datatype.baseType == base) {
- t->outputWarning("Duplicated datatype '%s' (datatype is listed twice)", token.c_str());
- // RESOLUTION: ignore duplicated datatype
- }
- else if (datatype.baseType) {
- t->outputError("Conflicting datatype '%s' (two or more given types cannot be used together)", token.c_str());
- // RESOLUTION: ignore conflicting datatype
- }
- else {
- datatype.baseType = base;
- datatype.subType = sub;
- }
- }
- if (datatype.flags & flag) {
- t->outputWarning("Duplicated datatype '%s' (datatype is listed twice)", token.c_str());
- // RESOLUTION: ignore duplicated datatype
- }
- else if (datatype.flags & flagConflict) {
- t->outputError("Conflicting datatype '%s' (two or more given types cannot be used together)", token.c_str());
- // RESOLUTION: ignore conflicting datatype
- }
- else {
- datatype.flags |= flag;
- }
- }
- }
-
- if (!datatype.baseType) {
- t->outputError("No base datatype given (must specify 'int', 'str', 'float', etc.)");
- // RESOLUTION: assume int
- datatype.baseType = DATA_INT;
- }
-
- return datatype;
- }
- int Compiler::parseParameterList(vector<Parameter>& list) { start_func
- int type;
- string token;
- assert(list.empty());
- // Optional paren
- int paren = 0;
- t->peekToken(type, token);
- if (type == TOKEN_OPEN_PAREN) {
- paren = 1;
- t->skipToken();
- t->peekToken(type, token);
- }
-
- // parameters?
- if (isDataType(type)) {
- for (;;) {
- // (already peeked, above, or at end of loop below)
- DataType vartype = parseDataType();
- checkDataType(vartype, 1, 1, 1, 1);
- t->peekToken(type, token);
- if (type != TOKEN_IDENTIFIER) {
- t->outputError("Expected parameter identifier here");
- // RESOLUTION: parameter list unusable
- return 0;
- }
- // Matches a function?
- FunctionMap::iterator flist = func->find(token.c_str());
- if (flist != func->end()) {
- t->outputError("Function '%s' already exists in this script (parameter variables and functions cannot share names)", token.c_str());
- // RESOLUTION: define parameter anyways
- }
-
- // Matches a built-in?
- for (int pos = 0; memberTable[pos].objtype; ++pos) {
- if ((memberTable[pos].objtype == SUB_ENTITY) &&
- (token == memberTable[pos].name)) {
- t->outputError("'%s' is a built-in entity member (parameter variables cannot override built-in members)", token.c_str());
- // RESOLUTION: define parameter anyways
- }
- }
- // All parameters are marked as local
- vartype.flags |= DATA_LOCAL;
- Parameter param = { vartype, token };
- list.push_back(param);
- t->skipToken();
-
- t->peekToken(type, token);
- if (type == TOKEN_COMMA) {
- t->skipToken();
- t->peekToken(type, token);
- if (!isDataType(type)) {
- t->outputError("Expected parameter here (datatype, then identifier)");
- // RESOLUTION: assume no more parameters
- break;
- }
- }
- else break;
- }
- }
-
- // End paren? (already peeked previously)
- if (type == TOKEN_CLOSE_PAREN) {
- t->skipToken();
- if (!paren) {
- t->outputError("Missing open parenthesis to match closing parenthesis");
- // RESOLUTION: pretend paren was there
- }
- }
- else if (paren) {
- t->outputError("Missing closing parenthesis to match open parenthesis");
- // RESOLUTION: pretend paren was there
- }
-
- return (list.empty() && (!paren)) ? -1 : 1;
- }
- void Compiler::doCompile(Uint32** bytecode, list<LinkEntry>* links) { start_func
- assert(bytecode);
- assert(func);
- assert(t);
- assert(vars);
- assert(labels);
- assert(links);
-
- // First, dealloc any existing bytecode
- if (*bytecode) {
- delete[] *bytecode;
- *bytecode = NULL;
- }
-
- // All areas should already be clear
- compileAssert(main.code.empty());
- compileAssert(main.links.empty());
- compileAssert(main.stackDepth == 0);
- compileAssert(localInit.code.empty());
- compileAssert(localInit.links.empty());
- compileAssert(localInit.stackDepth == 0);
- compileAssert(functions.code.empty());
- compileAssert(functions.links.empty());
- compileAssert(functions.stackDepth == 0);
- compileAssert(strings.code.empty());
- compileAssert(strings.links.empty());
- compileAssert(strings.stackDepth == 0);
- compileAssert(workspaces.empty());
- compileAssert(stringMap.empty());
-
- // Initially adding code to main
- pushWorkspace(&main);
- scope = -1; // lowest scope will be 0
- inFunctionScope = 0;
- Ended junk;
- compileBlock(junk);
- compileAssert(scope == -1);
- compileAssert(inFunctionScope == 0);
-
- // Get sizes
- int sizeI = localInit.code.size();
- int sizeIM = sizeI + main.code.size();
- int sizeIMF = sizeIM + functions.code.size();
- int sizeIMFS = sizeIMF + strings.code.size();
-
- // Final links adjustment-
- // link local strings/functions
- // move others to final link table
- // (localInit probably won't have links, but it could happen with future improvements)
- links->clear();
- completeLinkage(links, localInit, 0, sizeIMF, sizeIM);
- completeLinkage(links, main, sizeI, sizeIMF - sizeI, sizeIM - sizeI);
- completeLinkage(links, functions, sizeIM, sizeIMF - sizeIM, 0);
- compileAssert(strings.links.empty());
-
- // Move code back to bytecode
- Uint32* work = new Uint32[2 + sizeIMFS];
- *bytecode = work;
- *work++ = sizeIMFS;
- *work++ = sizeIMF;
-
- vector<Uint32>::iterator pos;
- vector<Uint32>::iterator end = localInit.code.end();
- for (pos = localInit.code.begin(); pos != end; ++pos) *work++ = *pos;
- end = main.code.end();
- for (pos = main.code.begin(); pos != end; ++pos) *work++ = *pos;
- end = functions.code.end();
- for (pos = functions.code.begin(); pos != end; ++pos) *work++ = *pos;
- end = strings.code.end();
- for (pos = strings.code.begin(); pos != end; ++pos) *work++ = *pos;
- // Clean up
- main.code.clear();
- compileAssert(main.links.empty());
- compileAssert(main.stackDepth == 0);
- localInit.code.clear();
- compileAssert(localInit.links.empty());
- localInit.stackDepth = 0;
- functions.code.clear();
- compileAssert(functions.links.empty());
- compileAssert(functions.stackDepth == 0);
- strings.code.clear();
- compileAssert(strings.links.empty());
- compileAssert(strings.stackDepth == 0);
- workspaces.pop_back();
- compileAssert(workspaces.empty());
- // We clear one at a time to ensure we can delete hash keys at the right times
- StringMap::iterator smpos = stringMap.begin();
- while (smpos != stringMap.end()) {
- const char* toDel = (*smpos).first;
- stringMap.erase(smpos);
- delete[] toDel;
- smpos = stringMap.begin();
- }
- }
- void Compiler::completeLinkage(list<LinkEntry>* links, Codespace& code, int linkOffset, int strOffset, int funcOffset) { start_func
- list<LinkEntry>::iterator pos = code.links.begin();
- list<LinkEntry>::iterator end = code.links.end();
- for (; pos != end; ++pos) {
- if ((*pos).type == LinkEntry::LINK_STRING) {
- code.code[(*pos).offset] += strOffset - (*pos).offset;
- }
- else if ((*pos).type == LinkEntry::LINK_FUNCTION) {
- FunctionMap::iterator flist = func->find((*pos).name.c_str());
- compileAssert(flist != func->end());
- compileAssert((*flist).second.offset >= 0);
- code.code[(*pos).offset] += (*flist).second.offset + funcOffset - (*pos).offset;
- }
- else {
- compileAssert((*pos).type != LinkEntry::LINK_LOOP_CONTINUE);
- compileAssert((*pos).type != LinkEntry::LINK_LOOP_END);
- (*pos).offset += linkOffset;
- links->push_back(*pos);
- }
- }
- code.links.clear();
- }
- void Compiler::compileBlock(Ended& ended, const vector<Parameter>* parameters, DataType* returnType) { start_func
- int type;
- string token;
- int braceBlock = 0;
- // Skip remainder of command (until end line or open brace)
- int skipCmd = 0;
- int exitBlock = 0;
- int detectEndOfCommand;
- // stackDepth upon entering this code block, so we can discard all NEW vars
- int entryStackDepth = ws->stackDepth;
- int paramStackDepth = entryStackDepth;
- Label* loopLabel = NULL;
- string loopLabelName;
- int justDidLoopLabel = 0;
-
- ++scope;
-
- enum {
- // least-to-most restrictive-
- // combining two values (if/else) results in higher scope, lower 'how' if scope tied
- ENDED_NONE = 0,
- ENDED_CONTINUE = 1, // continue- full stop until it's loop boundary
- ENDED_BREAK = 2, // break- full stop until it's loop boundary, also skips do{} ending condition
- ENDED_RESTART = 3, // restart- full stop until it's loop boundary, becomes full stop if a do{} loop
- ENDED_RETURN = 4, // return/end/infinite loop- full stop (scope set to 0 to always 'lose')
- };
- // If set, we've returned/stopped, any remaining code is redundant and might get discarded
- ended.how = ENDED_NONE;
- // For loops, scope of inner loop block that it applies to
- ended.scope = scope + 2; // Default to 'highest' scope, so this always 'wins'
- // Scope of farthest-out potentially-infinite-loop to break
- ended.breakInfinite = scope + 2; // Default to breaking nothing
- // Define parameters and function start?
- if (parameters) {
- for (vector<Parameter>::const_iterator pos = parameters->begin(); pos != parameters->end(); ++pos) {
- defineVariable((*pos).type, (*pos).name, 1);
- }
- // reserve spot for code return pointer
- ++ws->stackDepth;
- paramStackDepth = ws->stackDepth;
-
- inFunctionScope = scope;
- funcEntryStackDepth = entryStackDepth;
- funcParamStackDepth = paramStackDepth;
- }
-
- if (scope == 0) {
- // (local variable initialization will go right before our workspace)
- postCmd(OP_INIT);
- }
- else {
- // Beyond top scope, a block can be a single cmd or { block }
- // Discard any stray endline
- t->peekToken(type, token);
- if (type == TOKEN_ENDLINE) {
- t->skipToken();
- t->peekToken(type, token);
- }
- // Does our block start with a brace?
- if (type == TOKEN_OPEN_BRACE) {
- t->skipToken();
- braceBlock = 1;
- }
- }
-
- while (t->peekToken(type, token)) {
- detectEndOfCommand = 0;
-
- // Loop label only persists for one token
- if (justDidLoopLabel)
- justDidLoopLabel = 0;
- else {
- loopLabel = NULL;
- loopLabelName = blankString;
- }
-
- // Skipping stuff?
- if ((skipCmd) && (type != TOKEN_OPEN_BRACE)) {
- if (type == TOKEN_ENDLINE) skipCmd = 0;
- t->skipToken();
- // that was our one command, if not a brace block
- if ((!braceBlock) && (scope)) break;
- // next command
- continue;
- }
- skipCmd = 0;
-
- // Ended code? Something other than a closing brace?
- if ((ended.how) && (type != TOKEN_CLOSE_BRACE)) {
- t->outputWarning("Unreachable code (previous code will prevent this code from ever being executed)");
- // RESOLUTION: skip everything until our closing brace, but leave that
- int blockLevel = 0;
- while (t->peekToken(type, token)) {
- if (type == TOKEN_OPEN_BRACE) ++blockLevel;
- if (type == TOKEN_CLOSE_BRACE) --blockLevel;
- if (blockLevel < 0) break;
- t->skipToken();
- }
- continue;
- }
-
- // As a general rule, nothing is left on the stack between full commands
- // except for new local variables. Temporaries that must stay on the stack
- // must either be right before starting a new block, or be assigned
- // an internal temporary local variable; this may change if we determine
- // leaving temporaries on the stack can be safe in some situations
- // Detect datatypes first, outside of switch
- if (isDataType(type)) {
- // Datatype for definition
- DataType datatype = parseDataType();
-
- // Next MUST be identifier
- t->peekToken(type, token);
- if (type != TOKEN_IDENTIFIER) {
- t->outputError("Expected identifier after datatype (appears to be a function or variable definition)");
- // RESOLUTION: ignore rest of command
- skipCmd = 1;
- }
- else {
- // Identifier
- string ident = token;
- t->skipToken();
-
- // Parameters?
- vector<Parameter> plist;
- int paramResult = parseParameterList(plist);
-
- // Function definition, variable definition, or error?
- t->peekToken(type, token);
- if ((paramResult) && (type == TOKEN_OPEN_BRACE)) {
- // Error-check datatype
- checkDataType(datatype, 1, 1, 0, 0);
- if (scope) {
- t->outputError("Functions must be defined outside all other code blocks");
- // RESOLUTION: open brace will just be treated as a scope block now
- }
- else {
- // PARSE: FUNCTION DEFINITION
- // Starts at open-brace (denotes block)
- // Note position in list
- FunctionMap::iterator flist = func->find(ident.c_str());
- // (if not found, we shouldn't probably be here, but
- // might happen in some situations with symbol preparser)
- if (flist != func->end()) {
- if ((*flist).second.offset >= 0)
- t->outputError("Function '%s' defined twice (you can only create one function with a given name)", ident.c_str());
- // RESOLUTION: code gets added, but never referenced
- else
- // Store offset in function list
- (*flist).second.offset = functions.code.size();
- }
- VariableMap::iterator vlist = vars->find(ident.c_str());
- if (vlist != vars->end()) {
- t->outputError("Variable '%s' already exists in this script (variables and functions cannot share names)", ident.c_str());
- // RESOLUTION: define function anyways
- }
-
- for (int pos = 0; memberTable[pos].objtype; ++pos) {
- if ((memberTable[pos].objtype == SUB_ENTITY) &&
- (ident == memberTable[pos].name)) {
- t->outputError("'%s' is a built-in entity member (functions cannot override built-in members)", ident.c_str());
- // RESOLUTION: define function anyways
- }
- }
- // Parse function block
- compileAssert(ws == &main);
- pushWorkspace(&functions);
- Ended junk;
- compileBlock(junk, &plist, &datatype);
- popWorkspace();
- compileAssert(functions.stackDepth == 0);
- }
- }
- else if ((paramResult < 0) && ((type == TOKEN_ENDLINE) || (type == OPER_ASSIGN))) {
- // Error-check datatype
- checkDataType(datatype, 0, 0, 0, 1);
- // PARSE: VARIABLE DEFINITION
- const Variable& v = defineVariable(datatype, ident);
- if (type == OPER_ASSIGN) {
- // PARSE: VARIABLE ASSIGNMENT
- // (discard =)
- t->skipToken();
- Operand var = variableToOperand(v);
- parseAssignment(var, v.datatype);
-
- // Should be end of command now
- t->peekToken(type, token);
- }
-
- detectEndOfCommand = 1;
- }
- else {
- // Errors
- if (paramResult > 0)
- t->outputError("Expected open brace (appears to be a function definition)");
- else if (paramResult < 0)
- t->outputError("Expected open brace, assignment, or end-of-command (appears to be a function or variable definition)");
- // (if 0, parseParameterList already output an error)
- // RESOLUTION: ignore rest of command
- skipCmd = 1;
- }
- }
- }
- else {
- switch (type) {
- // These types can all start an expression, although currently
- // expressions starting with these are gauranteed to be 'wrong'
- // in that at least a portion will have no side-effects and should
- // produce a warning. Future operators may make these more useful
- // so they are here now.
- case TOKEN_INTEGER:
- case TOKEN_HEX:
- case TOKEN_DECIMAL:
- case KEYW_TRUE:
- case KEYW_FALSE:
- case KEYW_QUERY:
- case OPER_PLUS:
- case OPER_MINUS:
- case OPER_B_NOT:
- case OPER_L_NOT:
- case KEYW_NOTHING:
- // These tokens can actually start a useful 'standalone' expression.
- case KEYW_ALL:
- case KEYW_ALL_OTHER:
- case KEYW_THIS:
- case KEYW_SOURCE:
- case KEYW_DEFAULT:
- case TOKEN_STRING:
- case TOKEN_OPEN_PAREN:
- case TOKEN_IDENTIFIER: {
- // Label?
- if (type == TOKEN_IDENTIFIER) {
- t->bookmarkStore(1);
- t->skipToken();
- string nextToken;
- if ((t->nextToken(type, nextToken)) && (type == TOKEN_LABEL)) {
- // Process label token
- t->bookmarkCancel(1);
- Label newLabel;
- newLabel.scope = scope;
- newLabel.offset = ws->code.size();
- newLabel.isLoop = 0;
- newLabel.stackDepth = ws->stackDepth;
- // Store label
- list<Label>& found = addLabelMap(labels, token);
- found.push_back(newLabel);
- // (in case a loop is next)
- loopLabel = &found.back();
- loopLabelName = token;
- justDidLoopLabel = 1;
- // Use up end-of-command IF present (doesn't count)
- t->peekToken(type, token);
- if (type == TOKEN_ENDLINE) t->skipToken();
- break;
- }
- else {
- t->bookmarkReturn(1);
- t->bookmarkCancel(1);
- }
- }
-
- // Variable assignment, function call, scope, message send, etc.
- // All of these are handled via expressions
- Operand o = parseExpression(0, 0);
- if (!O_IS_SIDEFX(o) && !O_IS_VOID(o))
- t->outputWarning("Part or all of expression has no effect (no side effects and result is not used)");
- destroyOperand(o);
-
- // Should be end of command now
- t->peekToken(type, token);
- detectEndOfCommand = 1;
- break;
- }
- case TOKEN_OPEN_BRACE: {
- // Build a sub-block of code starting at brace
- compileBlock(ended, NULL, returnType);
- break;
- }
- case TOKEN_CLOSE_BRACE: {
- t->skipToken();
-
- if (scope == 0) {
- t->outputError("Unexpected close brace (does not match an open brace)");
- // RESOLUTION: just skip the brace
- }
- else {
- // Done with block
- exitBlock = 1;
- }
- break;
- }
-
- case KEYW_RETURN: {
- t->skipToken();
-
- // Returning anything?
- Operand o;
- createEmpty(o);
- t->peekToken(type, token);
- if ((type != TOKEN_ENDLINE) && (type != TOKEN_OPEN_BRACE)) {
- o = parseExpression();
- t->peekToken(type, token);
- }
-
- processReturn(o, returnType);
- ended.how = ENDED_RETURN;
- ended.scope = 0;
-
- detectEndOfCommand = 1;
- break;
- }
-
- case KEYW_END: {
- t->skipToken();
-
- // @TODO: don't show warning if main was used
- if (inFunctionScope)
- t->outputWarning("'end' inside of a function halts any suspended functions as well- did you mean 'return'?");
-
- postCmd(OP_STOP);
- ended.how = ENDED_RETURN;
- ended.scope = 0;
- t->peekToken(type, token);
- detectEndOfCommand = 1;
- break;
- }
- case KEYW_IF: {
- t->skipToken();
-
- // If-condition
- Operand o = parseExpression(2);
- if (!o.mode) {
- skipCmd = 1;
- break;
- }
-
- // If cmd
- int alwaysJumps, neverJumps;
- int offset = postCmdIf(0, 0, o, alwaysJumps, neverJumps);
- // Build a sub-block of code starting here
- int codeStart = ws->code.size();
- Ended endedIf;
- compileBlock(endedIf, NULL, returnType);
- // Adjust offset to skip new block
- if (offset >= 0) ws->code[offset] += ws->code.size() - codeStart;
-
- // else clause? (will automatically chain additional ifs)
- t->peekToken(type, token);
- if (type == KEYW_ELSE) {
- t->skipToken();
-
- // Add to 'if' block a jump to skip past 'else' block IF didn't end
- if (endedIf.how)
- offset = -1;
- else {
- // original 'if' jump needs to go 2 spaces further
- if (offset >= 0) ws->code[offset] += 2;
- // Offset of new 'if' jump (at end of 'if')
- offset = ws->code.size() + 1; // (remember where operand is)
- // By default, skips to next opcode
- postCmdI(OP_JUMP, 0);
- }
- // 'else' sub-block of code starting here
- int codeStart = ws->code.size();
- Ended endedElse;
- compileBlock(endedElse, NULL, returnType);
- // Adjust jump at end of 'if' to skip this block
- if (offset >= 0) ws->code[offset] += ws->code.size() - codeStart;
-
- // If both if and else clauses ended, we ended
- // Take highest scope
- if (endedElse.scope > endedIf.scope) {
- ended.how = endedElse.how;
- ended.scope = endedElse.scope;
- }
- else if (endedIf.scope > endedElse.scope) {
- ended.how = endedIf.how;
- ended.scope = endedIf.scope;
- }
- else {
- // Scope equal, take smallest 'how'
- ended.how = min(endedIf.how, endedElse.how);
- ended.scope = endedIf.scope; // (same as Else)
- }
- ended.breakInfinite = min(endedIf.breakInfinite, endedElse.breakInfinite);
- }
-
- // @TODO: we could, if we know if was always-true or always-false,
- // fill endedHow/Scope more aggressively
- // @TODO: discard code entirely if always-true or always-false?
-
- break;
- }
-
- case KEYW_ELSE: {
- t->outputError("'else' can only appear after an 'if' command or block (did you forget braces around your 'if' blocks?)");
- // RESOLUTION: ignore rest of command
- skipCmd = 1;
- break;
- }
-
- case KEYW_DO:
- case KEYW_UNTIL:
- case KEYW_WHILE: {
- t->skipToken();
-
- int loopOffset = ws->code.size();
- int continuePos = loopOffset;
- int condOffset = -1;
- int infiniteLoop = 0; // <= 0 means no, here (-1 means a break gaurantees no infinite)
- //int neverLoops = 0;
- int skipJumpBack = 0;
- // Label marking loop- set scope to sub-block's scope so it
- // conveniently deletes it for us; use existing label if any
- Label newLabel;
- Label* myLabel = loopLabel ? loopLabel : &newLabel;
- myLabel->scope = scope + 1;
- myLabel->offset = loopOffset; // For 'restart'; 'continue'/'break' linked later
- myLabel->isLoop = 1;
- myLabel->stackDepth = ws->stackDepth;
- // Pre-condition?
- if (type != KEYW_DO) {
- // Loop condition
- Operand o = parseExpression(2);
- if (!o.mode) {
- skipCmd = 1;
- break;
- }
-
- // Loop 'if'
- int alwaysJumps, neverJumps;
- condOffset = postCmdIf(type == KEYW_WHILE ? 0 : 1, 0, o, alwaysJumps, neverJumps);
- // (must have same stackdepth now)
- compileAssert(myLabel->stackDepth == ws->stackDepth);
- // ('restart' position is now here)
- myLabel->offset = ws->code.size();
-
- // Special cases
- if (alwaysJumps) {
- //neverLoops = 1;
- t->outputWarning("Code within loop will never execute (loop condition is false and is checked before loop)");
- // @TODO: discard code entirely?
- }
- else if (neverJumps)
- infiniteLoop = 1;
- }
-
- // Store label if new
- if (!loopLabel) {
- list<Label>& found = addLabelMap(labels, loopLabelName);
- found.push_back(newLabel);
- }
-
- // Build a sub-block of code starting here
- int codeStart = ws->code.size();
- compileBlock(ended, NULL, returnType);
-
- // Handle ended.how now
- if (ended.how == ENDED_RETURN) {
- // Return- a 'do' loop or infinite loop- prevails
- // otherwise- discarded
- if ((type != KEYW_DO) && (!infiniteLoop)) {
- ended.how = ENDED_NONE;
- ended.scope = scope + 2;
- }
- }
- else if (ended.scope > scope) {
- // Relates to our scope
- // Restart- a 'do' loop- becomes _RETURN (set later) as it's infinite now
- if ((ended.how == ENDED_RESTART) && (type == KEYW_DO))
- infiniteLoop = 1;
- else if (ended.how > ENDED_NONE) {
- // User handled all loop control for us (doesn't apply to DO loops)
- skipJumpBack = 1;
-
- // Break- a 'do' loop- final condition is useless,
- // but code afterwards is executed as normal
- if ((ended.how == ENDED_BREAK) && (type == KEYW_DO)) {
- t->outputWarning("Loop condition will never get checked (loop always exits before reaching condition at end)");
- // @TODO: discard code for loop condition?
- }
- ended.how = ENDED_NONE;
- ended.scope = scope + 2;
- }
- }
- if (ended.breakInfinite <= scope) {
- // Breaks thru infinite loops
- infiniteLoop = -1;
- // All types continue downward to proper scope
- }
- // (must have same stackdepth now)
- compileAssert(myLabel->stackDepth == ws->stackDepth);
-
- // Loop expression still needed? (do loop)
- if (type == KEYW_DO) {
- if (ended.how) {
- t->outputWarning("Loop condition will never get checked (loop always repeats or exits before reaching condition at end)");
- // @TODO: discard code for loop condition?
- }
- t->peekToken(type, token);
- if ((type != KEYW_WHILE) && (type != KEYW_UNTIL)) {
- t->outputError("Unexpected token '%s'- expected 'while' or 'until'", token.c_str());
- // RESOLUTION: treat as new command; block is now just a scope-block that may have used loop controls
- ended.how = ENDED_NONE;
- ended.scope = scope + 2;
- break;
- }
- // Loop condition
- continuePos = ws->code.size();
- t->skipToken();
- Operand o = parseExpression(2);
- if (!o.mode) {
- skipCmd = 1;
- break;
- }
- // Loop 'if'
- int alwaysJumps, neverJumps;
- postCmdIf(type == KEYW_WHILE ? 1 : 0, loopOffset - ws->code.size(), o, alwaysJumps, neverJumps);
- if ((alwaysJumps) && (infiniteLoop == 0))
- infiniteLoop = 1;
-
- // (must have same stackdepth now)
- compileAssert(myLabel->stackDepth == ws->stackDepth);
- t->peekToken(type, token);
- detectEndOfCommand = 1;
- }
- else {
- // Unconditional loop, back to expression, if loop wasn't aborted/looped already
- // (-2 to account for opcode we're about to use)
- if (!skipJumpBack)
- postCmdI(OP_JUMP, loopOffset - ws->code.size() - 2);
-
- // Adjust loop jump to skip this block and jump cmd
- if (condOffset >= 0) ws->code[condOffset] += ws->code.size() - codeStart;
- }
- int endPos = ws->code.size();
- if (infiniteLoop > 0) {
- // No warnings, they will be given elsewhere if needed (always-true condition)
- // as an infinite-loop containing an actual return cmd is reasonable
- ended.how = ENDED_RETURN;
- ended.scope = 0;
- }
-
- // Finally, handle any links from within loop
- list<LinkEntry>::iterator end = ws->links.end();
- list<LinkEntry>::iterator edit;
- for (list<LinkEntry>::iterator pos = ws->links.begin(); pos != end; ) {
- edit = pos;
- ++pos;
- if (((*edit).type == LinkEntry::LINK_LOOP_CONTINUE) ||
- ((*edit).type == LinkEntry::LINK_LOOP_END)) {
- if (((*edit).name == blankString) || ((*edit).name == loopLabelName)) {
- ws->code[(*edit).offset] +=
- (((*edit).type == LinkEntry::LINK_LOOP_CONTINUE) ? continuePos : endPos)
- - (*edit).offset;
- ws->links.erase(edit);
- }
- }
- }
- break;
- }
- case KEYW_BREAK:
- case KEYW_CONTINUE:
- case KEYW_RESTART: {
- t->skipToken();
-
- // Identifier or anonymous loop?
- int nextType;
- string loopName;
- t->peekToken(nextType, loopName);
- if (nextType == TOKEN_IDENTIFIER) {
- t->skipToken();
- }
- else {
- loopName = blankString;
- }
-
- // Find label
- LabelMap::iterator llist = labels->find(loopName.c_str());
- if (llist == labels->end()) {
- if (loopName == blankString)
- t->outputError("Must use '%s' from within a loop", token.c_str());
- else
- t->outputError("No label matching '%s' found ('%s' can only be used with labels that immediately precede a loop command such as 'do' or 'for')", loopName.c_str(), token.c_str());
- // RESOLUTION: skip any remaining command even though there shouldn't be any
- skipCmd = 1;
- break;
- }
- // Get label
- Label& label = (*llist).second.back();
- if (!label.isLoop) {
- t->outputError("Label '%s' does not mark a loop ('%s' can only be used with labels that immediately precede a loop command such as 'do' or 'for')", loopName.c_str(), token.c_str());
- // RESOLUTION: skip any remaining command even though there shouldn't be any
- skipCmd = 1;
- break;
- }
-
- // Ensure stack depth matches (shouldn't be lower- just higher)
- compileAssert(label.stackDepth <= ws->stackDepth);
- if (label.stackDepth < ws->stackDepth) {
- // @TODO: Use OP_DISCARDL when we know all entries are int/float
- postCmdI(OP_DISCARD, ws->stackDepth - label.stackDepth);
- }
-
- // Jump to label
- if (type == KEYW_RESTART) {
- // (-2 for opcode we're adding)
- postCmdI(OP_JUMP, label.offset - ws->code.size() - 2);
- ended.how = ENDED_RESTART;
- }
- else {
- compileAssert((type == KEYW_BREAK) || (type == KEYW_CONTINUE));
- prepLinker(type == KEYW_CONTINUE, loopName);
- // -1 = offset of operand from next instruction, added into final label offset
- postCmdI(OP_JUMP, -1);
- ended.how = (type == KEYW_CONTINUE ? ENDED_CONTINUE : ENDED_BREAK);
- }
- ended.scope = label.scope;
- // Breaks any infinites up to but not including this loop
- ended.breakInfinite = label.scope;
- // Breaks our loop too if a break
- if (type == KEYW_BREAK) --ended.breakInfinite;
-
- t->peekToken(type, token);
- detectEndOfCommand = 1;
- break;
- }
-
- case KEYW_DEBUG: {
- t->skipToken();
- // Debug output
- Operand o = parseExpression(1);
- if (!o.mode) {
- skipCmd = 1;
- break;
- }
- // Source already has conversion flag set if necessary, but it needs to
- // be a 'copy' flag
- if (!OM_IS_LITERAL(o.mode)) {
- if (OM_IS_CONVERT(o.mode))
- o.mode = (OperMode)(o.mode | OM_COPY);
- }
- // Source also needs to be const; define string literals
- if (o.mode == OM_STR)
- defineString(o);
- if (OM_IS_STR(o.mode))
- o.mode = (OperMode)(OM_STR_CONST | (o.mode & ~OM_BASETYPE));
- // Finally, source could be a pop; temporaries should never be variants
- if (O_IS_TEMP(o)) {
- compileAssert(!OM_IS_ENTRY(o.mode));
- compileAssert(o.data.i == 0);
- o.mode = (OperMode)((o.mode & ~OM_LOCATION) | OM_POP);
- }
- postCmdRaw(OP_DEBUG, &o);
- if (OM_IS_POP(o.mode))
- --ws->stackDepth;
- t->peekToken(type, token);
- detectEndOfCommand = 1;
- break;
- }
-
- case KEYW_IDLE: {
- t->skipToken();
- postCmd(OP_IDLE);
- t->peekToken(type, token);
- detectEndOfCommand = 1;
- break;
- }
- case TOKEN_CONFIG:
- case KEYW_EXTEND:
- case KEYW_IMPORT:
- case KEYW_WITH:
- // @TODO:
- case KEYW_FOR:
- case KEYW_FOREACH:
- case KEYW_REPEAT:
- // @TODO: Just specialized while() loops
-
- case KEYW_STATE:
- case KEYW_REPLY: { // @TODO: may have to peek past current block
- // @TODO:
- t->outputError("Not currently supported: '%s'", token.c_str());
- skipCmd = 1;
- break;
- }
-
- case TOKEN_ENDLINE: {
- // Compilation should never see one of these
- compileAssert(0);
- break;
- }
-
- default: {
- t->outputError("Unexpected token '%s'", token.c_str());
- // RESOLUTION: ignore rest of command
- skipCmd = 1;
- break;
- }
- }
- }
- if (detectEndOfCommand) {
- if (type == TOKEN_ENDLINE) {
- t->skipToken();
- }
- else if (type != TOKEN_OPEN_BRACE) {
- t->outputError("Expected end-of-command");
- skipCmd = 1;
- }
- }
-
- if (exitBlock) break;
- // Did one command, done with non-brace block
- // Unless skipping command, then let that happen first
- if ((!braceBlock) && (scope) && (!skipCmd)) break;
- }
-
- // Missing closing brace?
- if ((braceBlock) && (!exitBlock))
- t->outputError("Missing closing brace");
- // RESOLUTION: assume brace is there
- // Remove variables created during this scope
- VariableMap::iterator edit;
- for (VariableMap::iterator pos = vars->begin(); pos != vars->end(); ) {
- edit = pos;
- ++pos;
- compileAssert(!(*edit).second.empty());
- Variable& check = (*edit).second.back();
- if (check.scope == scope) {
- // This var was created by us- remove it
- if ((check.datatype.baseType == DATA_STR) &&
- (check.datatype.flags == DATA_CONST))
- delete[] check.val.constS;
- (*edit).second.pop_back();
- if ((*edit).second.empty()) {
- // No more vars matching this name- delete
- const char* toKill = (*edit).first;
- vars->erase(edit);
- delete[] toKill;
- }
- else {
- // Should never be two vars same name same scope
- compileAssert((*edit).second.back().scope != scope);
- }
- }
- }
- // Remove labels created during this scope
- LabelMap::iterator editL;
- for (LabelMap::iterator posL = labels->begin(); posL != labels->end(); ) {
- editL = posL;
- ++posL;
- compileAssert(!(*editL).second.empty());
- Label& check = (*editL).second.back();
- if (check.scope == scope) {
- // This label was created by us- remove it
- (*editL).second.pop_back();
- if ((*editL).second.empty()) {
- // No more labels matching this name- delete
- const char* toKill = (*editL).first;
- labels->erase(editL);
- delete[] toKill;
- }
- else {
- // Should never be two labels same name same scope
- compileAssert((*editL).second.back().scope != scope);
- }
- }
- }
-
- // Main scope has a "stop"
- if (scope == 0) {
- if (!ended.how) postCmd(OP_STOP);
- ended.how = ENDED_RETURN;
- ended.scope = 0;
- }
-
- compileAssert(ws->stackDepth >= paramStackDepth);
- if (!ended.how) {
- // If end of function, return
- if (parameters) {
- Operand o;
- createEmpty(o);
- processReturn(o, returnType);
- }
- else {
- // Pop local variables
- if (ws->stackDepth > entryStackDepth) {
- // @TODO: Use OP_DISCARDL when we know all entries are int/float
- postCmdI(OP_DISCARD, ws->stackDepth - entryStackDepth);
- }
- }
- }
-
- // local variables have now been discarded
- ws->stackDepth = entryStackDepth;
- if (inFunctionScope == scope) inFunctionScope = 0;
- --scope;
- }
- void Compiler::processReturn(Operand& o, DataType* returnType) { start_func
- // Not in function?
- if (!inFunctionScope) {
- // (no need to destroy operand, OP_STOP clears stack)
- if (o.mode)
- t->outputError("Attempting to return value outside of function ('return' outside of a function is equivalent to 'end' and cannot return a value)");
- else
- t->outputWarning("'return' outside of a function is equivalent to 'end'");
- postCmd(OP_STOP);
- }
- else {
- // warn if return value when void
- if ((returnType == NULL) || (returnType->baseType == DATA_VOID)) {
- if (o.mode != OM_NONE) {
- t->outputError("Attempting to return value within 'void' function ('void' functions cannot return a value)");
- // RESOLUTION: destroy operand
- destroyOperand(o);
- }
- // No return value to pop
- postCmdII(OP_RETVOID, ws->stackDepth - funcParamStackDepth,
- // (-1 for assumed code return pointer)
- funcParamStackDepth - funcEntryStackDepth - 1);
- }
- // if nothing to return, return blank, and warn if NOT void
- else {
- if (o.mode == OM_NONE) {
- t->outputWarning("Return without value (function was not declared as 'void' and therefore should return a value)");
- generateBlank(*returnType);
- ++ws->stackDepth;
- }
- else {
- convertToMatch(o, *returnType, "return type");
- // return values can be references
- convertToTemp(o, 0, 1);
- compileAssert(o.data.i == 0);
- // Finish any pending conversions on o-
- // Anything that was a var was converted by convertToTemp, so
- // if it's still pending conversion, we can safely do it in place
- if (OM_IS_CONVERT(o.mode))
- postCmdRaw(OP_CONVERT, &o);
- }
- // Returned value is assumed to be popped
- // (subtract now for popped return value)
- postCmdII(OP_RET, --ws->stackDepth - funcParamStackDepth,
- // (-1 for assumed code return pointer)
- funcParamStackDepth - funcEntryStackDepth - 1);
- }
- }
- }
- void Compiler::createEmpty(Operand& o) { start_func
- o.mode = OM_NONE;
- o.data.i = 0;
- o.subType = 0;
- o.flags = 0;
- o.ref.global = NULL;
- o.arrayType = 0;
- }
- void Compiler::createInt(Operand& o, Sint32 value) { start_func
- o.mode = OM_INT;
- o.data.i = value;
- o.subType = 0;
- o.flags = 0;
- o.ref.global = NULL;
- o.arrayType = 0;
- }
- void Compiler::createFloat(Operand& o, BCfloat value) { start_func
- o.mode = OM_FLOAT;
- o.data.f = value;
- o.subType = 0;
- o.flags = 0;
- o.ref.global = NULL;
- o.arrayType = 0;
- }
- void Compiler::appendWorkspaces(Codespace* dest, const Codespace* src) { start_func
- int size = dest->code.size();
- list<LinkEntry>::const_iterator end = src->links.end();
- for (list<LinkEntry>::const_iterator pos = src->links.begin(); pos != end; ++pos) {
- dest->links.push_back(*pos);
- dest->links.back().offset += size;
- }
- dest->code.insert(dest->code.end(), src->code.begin(), src->code.end());
- }
- void Compiler::checkDataType(DataType& type, int noConst, int noScope, int noVisibility, int noVoid) { start_func
- if ((noConst) && (type.flags & DATA_CONST)) {
- t->outputError("Cannot use const datatype here");
- // RESOLUTION: drop invalid datatype
- type.flags &= ~DATA_CONST;
- }
- if ((noScope) && (type.flags & DATA_LOCAL)) {
- t->outputError("Cannot use datatype scopes ('local') here");
- // RESOLUTION: drop invalid datatype
- type.flags &= ~DATA_LOCAL;
- }
- if ((noScope) && (type.flags & DATA_GLOBAL)) {
- t->outputError("Cannot use datatype scopes ('global') here");
- // RESOLUTION: drop invalid datatype
- type.flags &= ~DATA_GLOBAL;
- }
- if ((noVisibility) && (type.flags & DATA_PRIVATE)) {
- t->outputError("Cannot use visibility modifiers ('private') here");
- // RESOLUTION: drop invalid datatype
- type.flags &= ~DATA_PRIVATE;
- }
- if ((noVisibility) && (type.flags & DATA_PUBLIC)) {
- t->outputError("Cannot use visibility modifiers ('public') here");
- // RESOLUTION: drop invalid datatype
- type.flags &= ~DATA_PUBLIC;
- }
- if ((noVoid) && (type.baseType == DATA_VOID)) {
- t->outputError("Cannot use 'void' datatype here");
- // RESOLUTION: change to variant
- type.baseType = DATA_VAR;
- }
- }
- Compiler::Operand Compiler::parseFunction(const Function& function, const string& funcName) { start_func
- // @TODO: only supports local functions so far (no global/built-in/sends/queries)
- int paramNum = 0;
- int type;
- string token;
- // Optional paren
- int paren = 0;
- t->peekToken(type, token);
- if (type == TOKEN_OPEN_PAREN) {
- paren = 1;
- t->skipToken();
- t->peekToken(type, token);
- }
- // Parameters? these tokens MOSTLY match those in parseoperand-
- // Exceptions- don't recognize +, - as starting an operand if no parens
- if ((type == TOKEN_INTEGER) || (type == TOKEN_HEX) || (type == TOKEN_DECIMAL) ||
- (type == TOKEN_STRING) || (type == KEYW_TRUE) || (type == KEYW_FALSE) ||
- (type == KEYW_QUERY) || (type == TOKEN_IDENTIFIER) || (type == KEYW_ALL) ||
- (type == KEYW_ALL_OTHER) || (type == KEYW_THIS) || (type == KEYW_SOURCE) ||
- (type == KEYW_DEFAULT) || (type == OPER_B_NOT) || (type == OPER_L_NOT) ||
- (type == KEYW_NOTHING) ||
- ((paren) && ((type == OPER_PLUS) || (type == OPER_MINUS)))) {
- // Parse operands from here on
- do {
- Operand o = parseExpression();
- if (!o.mode) break;
-
- // Convert to proper operand type
- // (can't convert if no more param types known)
- if (paramNum < function.numparam)
- convertToMatch(o, function.parameters[paramNum], "parameter");
- ++paramNum;
- // (parameters can be references)
- convertToTemp(o, 0, 1);
- // (above ensures any string has been deleted)
-
- // Finish any pending conversions on o-
- // Anything that was a var was converted by convertToTemp, so
- // if it's still pending conversion, we can safely do it in place
- if (OM_IS_CONVERT(o.mode))
- postCmdRaw(OP_CONVERT, &o);
- // Another parameter?
- t->peekToken(type, token);
- if (type == TOKEN_COMMA) t->skipToken();
- } while (type == TOKEN_COMMA);
- }
-
- // Incorrect number of parameters?
- if (paramNum != function.numparam) {
- t->outputError("Incorrect number of parameters to function (%d were given; %d were expected)", paramNum, function.numparam);
- // RESOLUTION: continue on; code is of course unusable
- }
-
- // End paren? (already peeked previously)
- if (paren) {
- if (type == TOKEN_CLOSE_PAREN)
- t->skipToken();
- else
- t->outputError("Missing closing parenthesis to match open parenthesis");
- // RESOLUTION: pretend paren was there
- }
-
- // Parameters are on stack now
- prepLinker(funcName);
- // -1 = offset of operand compared to next opcode; added into final function offset
- postCmdI(OP_SUBR, -1);
-
- // "Pop" parameters, push return value if any
- if (function.returntype.baseType == DATA_VOID) {
- ws->stackDepth -= paramNum;
- Operand o;
- createInt(o, 0);
- o.flags = Operand::OF_IS_VOID;
- return o;
- }
- else {
- ws->stackDepth -= paramNum - 1;
- // Return value is on stack now, type is known
- Operand o = dataTypeToOperand(function.returntype);
- o.mode = (OperMode)(o.mode | OM_STACK);
- return o;
- }
- }
- Compiler::Operand Compiler::dataTypeToOperand(DataType dt) { start_func
- Operand o;
- o.flags = 0;
- o.data.i = 0;
- o.ref.global = NULL;
- o.mode = (OperMode)dt.baseType;
- if (dt.baseType == DATA_VAR)
- o.mode = OM_ENTRY;
- if ((o.mode == OM_ENTITY) || (o.mode == OM_OBJECT))
- o.subType = dt.subType;
- if (dt.flags & DATA_ARRAY) {
- o.arrayType = o.mode;
- o.mode = OM_ARRAY;
- }
- else if (dt.flags & DATA_HASH) {
- o.arrayType = o.mode;
- o.mode = OM_HASH;
- }
- o.mode = (OperMode)(o.mode | OM_NO_CONVERT);
- return o;
- }
- int Compiler::findFunction(const string& name, Function& function) const { start_func
- // find matching local function
- FunctionMap::iterator flist = func->find(name.c_str());
- if (flist == func->end()) {
- // @TODO: also need to check global functions and built-ins
- return 0;
- }
-
- // @TODO: optimize self/tail recursion?
- function = (*flist).second;
- return 1;
- }
- void Compiler::convertToMatch(Operand& o, DataType datatype, const char* errorTarget) { start_func
- compileAssert(!OM_IS_NONE(o.mode));
-
- // Ensure data types match- convert or error if needed
- // Check array/hash first because target could be var hash/array
- if ((datatype.flags & DATA_ARRAY) || (datatype.flags & DATA_HASH)) {
- string defined = "array";
- int isType = OM_IS_ARRAY(o.mode);
- if (datatype.flags & DATA_HASH) {
- defined = "hash";
- isType = OM_IS_HASH(o.mode);
- }
- if (isType) {
- if ((o.arrayType != datatype.baseType) && (o.arrayType != DATA_VAR) && (datatype.baseType != DATA_VAR)) {
- t->outputError("Incorrect %s type- does not match %s", defined.c_str(), errorTarget);
- }
- else if ((o.arrayType == DATA_ENTITY) && (datatype.baseType == DATA_ENTITY)) {
- // Ensure same subtype or converting to no type
- if ((o.subType != datatype.subType) && (datatype.subType))
- t->outputError("Incorrect %s type- script type does not match %s", defined.c_str(), errorTarget);
- }
- else if ((o.arrayType == DATA_OBJECT) && (datatype.baseType == DATA_OBJECT)) {
- // Ensure same subtype
- if (o.subType != datatype.subType)
- t->outputError("Incorrect %s type- object type does not match %s", defined.c_str(), errorTarget);
- }
- }
- else if (!OM_IS_ENTRY(o.mode)) {
- t->outputError("Incorrect data type- %s must be assigned %s", errorTarget, defined.c_str());
- }
- // RESOLUTION: converts anyways
- if (datatype.flags & DATA_ARRAY) convertToArray(o, datatype);
- else convertToHash(o, datatype);
- }
- // Anything else, no need to convert to var
- else if (datatype.baseType != DATA_VAR) {
- if (datatype.baseType == DATA_INT) {
- if (!OM_IS_INT(o.mode) && !OM_IS_FLOAT(o.mode) && !OM_IS_ENTRY(o.mode))
- t->outputError("Cannot automatically convert to integer here");
- // RESOLUTION: converts anyways
- convertToInt(o);
- }
- else if (datatype.baseType == DATA_FLOAT) {
- if (!OM_IS_INT(o.mode) && !OM_IS_FLOAT(o.mode) && !OM_IS_ENTRY(o.mode))
- t->outputError("Cannot automatically convert to floating point here");
- // RESOLUTION: converts anyways
- convertToFloat(o);
- }
- else if (datatype.baseType == DATA_STR) {
- if (!OM_IS_INT(o.mode) && !OM_IS_FLOAT(o.mode) && !OM_IS_STR(o.mode) && !OM_IS_ENTRY(o.mode))
- t->outputError("Cannot automatically convert to string here");
- // RESOLUTION: converts anyways
- convertToStr(o);
- }
- else if (datatype.baseType == DATA_ENTITY) {
- if (OM_IS_LITERAL(o.mode)) {
- if ((o.mode != OM_ALL) && (o.mode != OM_ALL_OTHER) &&
- (o.mode != OM_NOTHING) && (o.mode != OM_THIS))
- t->outputError("Cannot automatically convert to entity object here");
- }
- else {
- if ((o.mode & OM_BASETYPE) != DATA_ENTITY)
- t->outputError("Cannot automatically convert to entity object here");
- // Ensure same subtype or converting to no type
- else if ((o.subType != datatype.subType) && (datatype.subType))
- t->outputError("Incorrect script type- does not match %s", errorTarget);
- }
- // RESOLUTION: converts anyways
- convertToObject(o, datatype);
- }
- else {
- // All other object types
- assert(datatype.baseType == DATA_OBJECT);
- if (OM_IS_LITERAL(o.mode)) {
- if (o.mode != OM_NOTHING)
- t->outputError("Cannot automatically convert to object type here");
- }
- else {
- if ((o.mode & OM_BASETYPE) != DATA_OBJECT)
- t->outputError("Cannot automatically convert to object type here");
- // Ensure same subtype
- else if (o.subType != datatype.subType)
- t->outputError("Incorrect object type- does not match %s", errorTarget);
- }
- // RESOLUTION: converts anyways
- convertToObject(o, datatype);
- }
- }
- }
- void Compiler::parseAssignment(Operand& var, DataType varType, const Variable* varToCount, int memberPos) { start_func
- // @TODO: calculate directly into variable instead of temporary?
- // (only if no possibility of variable self-referenced in expression)
- // use same algorithm as for a = a + 1 below
- // @TODO: optimize a = a + 1 (etc.) by preparsing and counting uses of a; then
- // during expression parsing, the last time you encounter it, you can now modify
- // it directly; for non-scoped vars, you must count function calls as a possible 'use'.
- // Mark this in operand somehow, and treat it as an equal to a temporary in many places (be careful of type matching)
- // OPERATOR++ and inline-assignment potentially affect this algorithm.
- // This optimization must be ignored if OPERATOR+= is being used.
- // @TODO: prevent use of variable in it's own initialization
- // (just don't define it before calling this?)
- // @TODO: a = b + 1 should be (store b into a, += 1) not (push b, += 1, pop into a)
- // if preparse above finds NO uses of variable, then first time something would be pushed,
- // store into variable instead and then use that as a temporary (only if type matches)
- // @TODO: doesn't work with . (as . isn't coded yet) or probably with 'as'
-
- // Preparse the expression for uses of variable, or functions that MIGHT affect variable
- // This count allows us to optimize the expression once all other uses of the variable
- // are out of the way by doing further operations directly within the variable
-
- /*
- int bknum = t->getBookmarkName();
- t->bookmarkStore(bknum);
- t->silentErrors(1);
- Operand counted = parseExpression(0, 1, NULL, 100, &var);
- t->silentErrors(0);
- if (varToCount) {
- t->bookmarkCancel(bknum);
- return;
- }
- t->bookmarkReturn(bknum);
- debugWrite("Found %d instances of %s", counted.data.i, var.name);
- */
- // The easy part- parse the expression
- Operand o = parseExpression();
- if (!o.mode) return;
- compileAssert(!OM_IS_POP(o.mode));
- compileAssert(!OM_IS_COPY(o.mode));
-
- // @TODO: should error if storing to a const
- convertToMatch(o, varType, "variable");
- // Permutations:
- // known into known, same type
- // known (conversion needed) into known, same type
- // known into variant (convert variant)
- // known (conversion needed) into variant (convert variant)
- // variant into variant
- // Default to object, as that covers multiple types and literals
- Opcode opc = OP_STOREo;
- if (memberPos >= 0) {
- assert(!OM_IS_ENTRY(o.mode));
- if (OM_IS_ENTITY(var.mode)) opc = OP_SETe;
- else {
- compileAssert(OM_IS_OBJECT(var.mode));
- opc = OP_SETo;
- }
- }
- else if (OM_IS_STR(o.mode)) opc = OP_STOREs;
- else if (OM_IS_FLOAT(o.mode)) opc = OP_STOREf;
- else if (OM_IS_INT(o.mode)) opc = OP_STORE;
- else if (OM_IS_ARRAY(o.mode)) opc = OP_STOREa;
- else if (OM_IS_HASH(o.mode)) opc = OP_STOREh;
- else if (!OM_IS_LITERAL(o.mode)) {
- if (OM_IS_ENTRY(o.mode)) {
- compileAssert(varType.baseType == DATA_VAR);
- // @TODO: ensure storev does ALL ref/deref needed, conversions, and copying new array/hash
- opc = OP_STOREv;
- }
- }
-
- // Dest is set to 'no convert' which is correct unless storing to a variant
- // from a non-variant
- if ((varType.baseType == DATA_VAR) && (opc != OP_STOREv))
- var.mode = (OperMode)(var.mode & ~OM_NO_CONVERT);
-
- // Source already has conversion flag set if necessary, but it needs to
- // be a 'copy' flag
- if (!OM_IS_LITERAL(o.mode)) {
- if (OM_IS_CONVERT(o.mode))
- o.mode = (OperMode)(o.mode | OM_COPY);
- // Force a copy if non-unique array/hash
- else if (OM_IS_KNOWN(o.mode) && (OM_IS_ARRAY(o.mode) || OM_IS_HASH(o.mode)) && !O_IS_COPY(o) && (opc != OP_STOREv))
- o.mode = (OperMode)((o.mode & ~OM_FLAGS) | OM_COPY);
- }
-
- // Source also needs to be const; define string literals
- if (o.mode == OM_STR)
- defineString(o);
- if (OM_IS_STR(o.mode))
- o.mode = (OperMode)(OM_STR_CONST | (o.mode & ~OM_BASETYPE));
- // Finally, source could be a pop; temporaries should never be variants
- if (O_IS_TEMP(o)) {
- compileAssert(!OM_IS_ENTRY(o.mode));
- compileAssert(o.data.i == 0);
- o.mode = (OperMode)((o.mode & ~OM_LOCATION) | OM_POP);
- }
-
- // 'var' goes after 'o' in case both are POPs- popping happens in right order
- if ((opc == OP_SETo) || (opc == OP_SETe)) {
- // Specialized versions
- if (OM_IS_FLOAT(o.mode)) opc = (opc == OP_SETo) ? OP_SETof : OP_SETef;
- else if (OM_IS_INT(o.mode)) opc = (opc == OP_SETo) ? OP_SEToi : OP_SETei;
- Operand b;
- createInt(b, memberPos);
- postCmdRaw(opc, &o, &var, &b);
- }
- else
- postCmdRaw(opc, &o, &var);
- var.mode = (OperMode)(var.mode | OM_NO_CONVERT);
-
- if (OM_IS_POP(o.mode))
- --ws->stackDepth;
- }
- Compiler::Operand Compiler::variableToOperand(const Variable& var) { start_func
- Operand result;
-
- if (var.datatype.flags & DATA_CONST) {
- // Constants
- result.arrayType = 0;
- result.flags = 0;
- result.subType = 0;
- result.mode = (OperMode)var.datatype.baseType;
- result.ref.global = NULL;
- if (var.datatype.baseType == DATA_INT) {
- result.data.i = var.val.constI;
- }
- else if (var.datatype.baseType == DATA_FLOAT) {
- result.data.f = var.val.constF;
- }
- else {
- compileAssert(var.datatype.baseType == DATA_STR);
- result.data.i = 0;
- result.ref.strLiteral = new string(var.val.constS);
- }
- }
- else {
- result = dataTypeToOperand(var.datatype);
- result.flags |= Operand::OF_IS_VAR;
- if (var.scope == 0) {
- result.mode = (OperMode)(result.mode | OM_LOCAL);
- result.data.i = var.val.offs;
- }
- else if (var.scope > 0) {
- result.mode = (OperMode)(result.mode | OM_STACK);
- result.data.i = var.val.offs + ws->stackDepth;
- }
- else {
- result.mode = (OperMode)(result.mode | OM_GLOBAL);
- result.ref.global = &var;
- result.data.i = var.val.offs;
- }
- }
- return result;
- }
- int Compiler::convertToTemp(Operand& o, int partialConvert, int refOK) { start_func
- int redoString = 0;
- if (partialConvert) compileAssert(OM_IS_LITERAL(o.mode));
-
- if (!O_IS_TEMP(o)) {
- // Create temporary via PUSH
- if (o.mode == OM_STR) {
- if (partialConvert) {
- redoString = 1;
- // We NEED to be sure this is zero because we finish conversion
- // later via an addition to the operand created
- compileAssert(o.data.i == 0);
- }
- else defineString(o);
- }
- Operand copyOfO = o;
- postCmdPush(copyOfO, refOK);
- ++ws->stackDepth;
- // Modify operand to match new position
- o.flags = 0;
- if (o.mode != OM_STR) o.ref.global = NULL;
- if (!refOK) o.flags = Operand::OF_IS_COPY;
- o.mode = (OperMode)((o.mode & ~OM_LOCATION) | OM_STACK);
- if (OM_IS_CONVERT(o.mode)) o.mode = (OperMode)((o.mode & ~OM_FLAGS) | OM_NO_CONVERT);
- if (!partialConvert) o.data.i = 0;
- }
-
- return redoString;
- }
- void Compiler::convertToInt(Operand& o) { start_func
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
-
- // Already int
- if (OM_IS_INT(o.mode)) return;
- // Main possibilities-
- // Literal- convert to another literal
- // Otherwise- mark as 'needed conversion'
- if (OM_IS_LITERAL(o.mode)) {
- if (o.mode == OM_FLOAT) {
- o.data.i = (Sint32)o.data.f;
- }
- else if (o.mode == OM_STR) {
- o.data.i = strToInt(*o.ref.strLiteral);
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
- else {
- o.data.i = 0;
- o.subType = 0;
- o.arrayType = 0;
- }
- o.mode = OM_INT;
- return;
- }
-
- // (clears no_convert flag also)
- o.mode = (OperMode)((o.mode & OM_LOCATION) | OM_INT);
- }
- void Compiler::convertToFloat(Operand& o) { start_func
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
-
- // Already float
- if (OM_IS_FLOAT(o.mode)) return;
- // Main possibilities-
- // Literal- convert to another literal
- // Otherwise- mark as 'needed conversion'
- if (OM_IS_LITERAL(o.mode)) {
- if (o.mode == OM_INT) {
- o.data.f = (BCfloat)o.data.i;
- }
- else if (o.mode == OM_STR) {
- o.data.f = strToFloat(*o.ref.strLiteral);
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
- else {
- o.data.f = 0.0;
- o.subType = 0;
- o.arrayType = 0;
- }
- o.mode = OM_FLOAT;
- return;
- }
-
- // (clears no_convert flag also)
- o.mode = (OperMode)((o.mode & OM_LOCATION) | OM_FLOAT);
- }
- void Compiler::convertToStr(Operand& o) { start_func
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
-
- // Already string
- if (OM_IS_STR(o.mode)) return;
- // Main possibilities-
- // Literal- convert to another literal
- // Otherwise- mark as 'needed conversion'
- if (OM_IS_LITERAL(o.mode)) {
- o.data.i = 0;
- if (o.mode == OM_INT) {
- o.ref.strLiteral = new string(intToStr(o.data.i));
- }
- else if (o.mode == OM_FLOAT) {
- o.ref.strLiteral = new string(floatToStr(o.data.f));
- }
- else {
- o.ref.strLiteral = new string(blankString);
- o.subType = 0;
- o.arrayType = 0;
- }
- o.mode = OM_STR;
- return;
- }
-
- // (clears no_convert flag also)
- o.mode = (OperMode)((o.mode & OM_LOCATION) | OM_STR);
- }
- void Compiler::convertToEntity(Operand& o, DataType type) { start_func
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
- compileAssert(type.baseType == DATA_ENTITY);
- // Already entity- check for proper type
- if (OM_IS_ENTITY(o.mode) && !OM_IS_LITERAL(o.mode)) {
- // any type matches no-subtype
- if (!type.subType) return;
- // Otherwise type must match exactly
- if (type.subType == o.subType) return;
- // Type failed to match- will mark for conversion later
- }
- // Main possibilities-
- // Literal- convert to another literal
- // Otherwise- mark as 'needed conversion'
- if (OM_IS_LITERAL(o.mode)) {
- // Certain literals are ok
- if ((o.mode == OM_ALL) || (o.mode == OM_ALL_OTHER) || (o.mode == OM_NOTHING)) {
- o.subType = type.subType;
- return;
- }
-
- if ((o.mode == OM_THIS) && ((type.subType == scriptType) || (!type.subType)))
- return;
- if (o.mode == OM_STR) {
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
-
- // Create a 'nothing' entity
- o.mode = OM_NOTHING;
- o.arrayType = 0;
- o.subType = type.subType;
- o.data.i = 0;
- o.flags = 0;
- return;
- }
-
- // (clears no_convert flag also)
- o.mode = (OperMode)((o.mode & OM_LOCATION) | OM_ENTITY);
- o.subType = type.subType;
- }
- void Compiler::convertToObject(Operand& o, DataType type) { start_func
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
- compileAssert(type.baseType == DATA_OBJECT);
- compileAssert(type.subType);
- // Already object- check for proper type
- if (OM_IS_OBJECT(o.mode) && !OM_IS_LITERAL(o.mode)) {
- // Type must match exactly
- if (type.subType == o.subType) return;
- // Type failed to match- will mark for conversion later
- }
- // Main possibilities-
- // Literal- convert to another literal
- // Otherwise- mark as 'needed conversion'
- if (OM_IS_LITERAL(o.mode)) {
- // Certain literals are ok
- if (o.mode == OM_NOTHING) {
- o.subType = type.subType;
- return;
- }
- if (o.mode == OM_STR) {
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
-
- // Create a 'nothing' object
- o.mode = OM_NOTHING;
- o.arrayType = 0;
- o.subType = type.subType;
- o.data.i = 0;
- o.flags = 0;
- return;
- }
-
- // (clears no_convert flag also)
- o.mode = (OperMode)((o.mode & OM_LOCATION) | OM_OBJECT);
- o.subType = type.subType;
- }
- void Compiler::convertToArray(Operand& o, DataType type) { start_func
- // @TODO: remember that when this is used, a simple copy bit won't
- // complete a specific array type conversion- just promise it's an
- // array of SOME type; you must specifically use a conversion opcode
- // or assume you are pulling a variant out. WE CAN CHANGE THIS by
- // pushing array+subtype whenever conversion/copy operands are used
- assert(0);
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
- compileAssert(type.flags & DATA_ARRAY);
- // Already array- check for proper type
- if (OM_IS_ARRAY(o.mode)) {
- // any type matches variant type
- if (type.baseType == DATA_VAR) return;
- // Otherwise type must match exactly
- if (o.arrayType == type.baseType) {
- if ((type.baseType != DATA_ENTITY) && (type.baseType != DATA_OBJECT)) return;
- // Match subtype exactly for entities/objects
- if (type.subType == o.subType) return;
- }
- // Type failed to match- will mark for conversion later
- }
- // Main possibilities-
- // Literal- convert to another literal
- // Otherwise- mark as 'needed conversion'
- if (OM_IS_LITERAL(o.mode)) {
- if (o.mode == OM_STR) {
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
-
- // Create a new array- if variant type, create an INT array
- o.mode = (OperMode)(OM_ARRAY | OM_STACK | OM_NO_CONVERT);
- o.arrayType = type.baseType;
- o.subType = 0;
- o.data.i = 0;
- o.flags = Operand::OF_IS_COPY;
-
- if (type.baseType == DATA_VAR) {
- postCmdII(OP_CREATEa, DATA_INT, 0);
- o.arrayType = DATA_INT;
- }
- else {
- postCmdII(OP_CREATEa, type.baseType,
- (type.baseType == DATA_ENTITY ||
- type.baseType == DATA_OBJECT) ? type.subType : 0);
- }
- return;
- }
-
- // (clears no_convert flag also)
- // If was a copy before, would still be; if not, is not, so don't change flags
- o.mode = (OperMode)((o.mode & OM_LOCATION) | OM_ARRAY);
- o.subType = type.subType;
- o.arrayType = type.baseType;
- if (type.baseType == DATA_VAR) o.arrayType = OM_INT;
- }
- void Compiler::convertToHash(Operand& o, DataType type) { start_func
- // @TODO: remember that when this is used, a simple copy bit won't
- // complete a specific hash type conversion- just promise it's an
- // array of SOME time; you must specifically use a conversion opcode
- // or assume you are pulling a variant out. WE CAN CHANGE THIS by
- // pushing hash+subtype whenever conversion/copy operands are used
- assert(0);
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
- compileAssert(type.flags & DATA_HASH);
- // Already hash- check for proper type
- if (OM_IS_HASH(o.mode)) {
- // any type matches variant type
- if (type.baseType == DATA_VAR) return;
- // Otherwise type must match exactly
- if (o.arrayType == type.baseType) {
- if ((type.baseType != DATA_ENTITY) && (type.baseType != DATA_OBJECT)) return;
- // Match subtype exactly for entities/objects
- if (type.subType == o.subType) return;
- }
- // Type failed to match- will mark for conversion later
- }
- // Main possibilities-
- // Literal- convert to another literal
- // Otherwise- mark as 'needed conversion'
- if (OM_IS_LITERAL(o.mode)) {
- if (o.mode == OM_STR) {
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
-
- // Create a new hash- if variant type, create an INT hash
- o.mode = (OperMode)(OM_HASH | OM_STACK | OM_NO_CONVERT);
- o.arrayType = type.baseType;
- o.subType = 0;
- o.data.i = 0;
- o.flags = Operand::OF_IS_COPY;
-
- if (type.baseType == DATA_VAR) {
- postCmdII(OP_CREATEh, DATA_INT, 0);
- o.arrayType = DATA_INT;
- }
- else {
- postCmdII(OP_CREATEh, type.baseType,
- (type.baseType == DATA_ENTITY ||
- type.baseType == DATA_OBJECT) ? type.subType : 0);
- }
- return;
- }
-
- // (clears no_convert flag also)
- // If was a copy before, would still be; if not, is not, so don't change flags
- o.mode = (OperMode)((o.mode & OM_LOCATION) | OM_HASH);
- o.subType = type.subType;
- o.arrayType = type.baseType;
- if (type.baseType == DATA_VAR) o.arrayType = OM_INT;
- }
- void Compiler::destroyOperand(Operand& o) { start_func
- compileAssert(!OM_IS_POP(o.mode));
- if (!o.mode)
- return;
- if (o.mode == OM_STR) {
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
- if (O_IS_TEMP(o)) {
- compileAssert(o.data.i == 0);
- if (OM_IS_INT(o.mode) || OM_IS_FLOAT(o.mode)) postCmdI(OP_DISCARDL, 1);
- else postCmdI(OP_DISCARD, 1);
- --ws->stackDepth;
- }
- }
- int Compiler::precedence(int oper) { start_func
- // (for error states)
- if (oper == 0) return 0;
- compileAssert(oper & TOKEN_OPERATOR);
- switch (oper) {
- case OPER_MEMBER: // (presumed to have lowest precedence)
- case OPER_AS:
- return 2;
-
- case OPER_B_NOT:
- case OPER_L_NOT:
- return 3;
-
- case OPER_MULT:
- case OPER_DIV:
- case OPER_MOD:
- return 4;
-
- case OPER_PLUS:
- case OPER_MINUS:
- return 5;
-
- case OPER_LSHIFT:
- case OPER_RSHIFT:
- return 6;
- case OPER_B_AND:
- case OPER_B_OR:
- case OPER_B_XOR:
- return 7;
- case OPER_CONCAT:
- return 8;
-
- case OPER_LT:
- case OPER_LE:
- case OPER_GT:
- case OPER_GE:
- return 9;
- case OPER_EQ:
- case OPER_NE:
- case OPER_IS:
- return 10;
- case OPER_L_AND:
- case OPER_L_OR:
- case OPER_L_XOR:
- return 11;
-
- case OPER_ASSIGN:
- return 12;
- }
-
- compileAssert(0);
- return 1;
- }
- Compiler::Operand Compiler::parseExpression(int warnAssign, int usingValue, Operand* firstOperand, int stopPrecedence, const Variable* varToCount) { start_func
- int type;
- string token;
- Operand a;
- if (firstOperand) a = *firstOperand;
- else a = parseOperand(warnAssign, usingValue, varToCount);
- if (a.mode == 0) {
- return a;
- }
-
- t->peekToken(type, token);
-
- while (1) {
- size_t aFinishLiteralAt = 0;
- size_t aFinishLiteralSize = 0;
- int aFinishString = 0;
-
- if (type & TOKEN_OPERATOR) {
- if (O_IS_VOID(a)) {
- t->outputError("Function does not return a value (function returns 'void' and cannot be used within an expression)");
- // RESOLUTION: proceed with int constant
- a.flags &= ~Operand::OF_IS_VOID;
- }
- if (type == OPER_IS) {
- // Second operand can be a datatype or scriptname
- // @TODO:
- t->outputError("Not currently supported: '%s'", token.c_str());
- }
- else if (type == OPER_AS) {
- // Second operand must be a datatype or scriptname
- // @TODO:
- t->outputError("Not currently supported: '%s'", token.c_str());
- }
- else if ((type == OPER_B_NOT) || (type == OPER_L_NOT)) {
- t->outputError("Unexpected operator '%s' (this operator only takes one operand and should not appear between two values)", token.c_str());
- // RESOLUTION: Start expression anew at this unary operator for (hopefully) proper expression parsing
- return parseExpression(warnAssign, usingValue, NULL, stopPrecedence, varToCount);
- }
- else if (type == OPER_ASSIGN) {
- // Should've been handled in operand parsing
- t->outputError("Cannot assign here- can only assign to a variable or subscripted variable");
- // RESOLUTION: treat like == in order to proceed
- type = OPER_EQ;
- }
-
- // Anything else- proceed normally
- // Non-commutative non-reversible operators (- / % << >> #) require the
- // first operand to be a temporary. We hold off on defining strings as
- // we may cancel this if a and b end up being constants
- // (= . 'as' 'is' are non-commutative but not applicable here)
- if (((type == OPER_MINUS) || (type == OPER_DIV) || (type == OPER_MOD) ||
- (type == OPER_LSHIFT) || (type == OPER_RSHIFT) || (type == OPER_CONCAT)) &&
- !varToCount) {
- if (OM_IS_LITERAL(a.mode)) aFinishLiteralAt = ws->code.size();
- aFinishString = convertToTemp(a, aFinishLiteralAt);
- aFinishLiteralSize = ws->code.size();
- }
-
- // Do type-check for . operator now, to prevent repeated checks later
- if (type == OPER_MEMBER) {
- if (!OM_IS_ENTRY(a.mode) && !OM_IS_STR(a.mode) && a.mode != OM_ALL &&
- a.mode != OM_ALL_OTHER && a.mode != OM_THIS &&
- (OM_IS_LITERAL(a.mode) || (!OM_IS_ENTITY(a.mode) && !OM_IS_OBJECT(a.mode)))) {
- t->outputError("Left operand to . must be an entity or object or something that can be used as one");
- // RESOLUTION: continue at next operand
- t->skipToken();
- return parseOperand(warnAssign, usingValue, varToCount);
- }
- }
- }
- else {
- // Not an error- no recognized operator means a single operand- just return it
- return a;
- }
- t->skipToken();
- // The first non-. operator means we're now "using" return values.
- if (type != OPER_MEMBER) usingValue = 1;
- Operand b = parseOperand(warnAssign, usingValue, varToCount, type == OPER_MEMBER ? &a : NULL);
- if (b.mode == 0)
- return a;
-
- // If parseOperand did all the work of the OPER_MEMBER, then a.mode will be 0 now
- // Now peek ahead for another operator
- int nextType;
- string nextToken;
- t->peekToken(nextType, nextToken);
- // @TODO: future optimization, if operator is same and is
- // commutative/associative (+, *) and is more likely to be const+const,
- // yield precedence to help precalculate constants
-
- // Compare precedence
- if ((nextType & TOKEN_OPERATOR) && (type != OPER_MEMBER) &&
- (precedence(type) > precedence(nextType))) {
- // Next expression has precedence for now
- b = parseExpression(warnAssign, 1, &b, precedence(type), varToCount);
- if (b.mode == 0) {
- // Even though an error condition, we need to finish temporarization
- if (aFinishLiteralAt) {
- compileAssert(!varToCount);
-
- if (aFinishString) {
- a.mode = OM_STR;
- defineString(a);
- a.mode = (OperMode)(OM_STR | OM_STACK | OM_NO_CONVERT);
- // We know that a string push would've been 2 ints total
- ws->code[aFinishLiteralAt + 1] += a.data.i;
- }
- a.data.i = 0;
- }
- return a;
- }
-
- // (peek again)
- t->peekToken(nextType, nextToken);
- }
-
- // Now we have precedence.
-
- // If a was a temporarized literal...
- if (aFinishLiteralAt) {
- compileAssert(!varToCount);
-
- // We have to finish OR cancel temporarization of a
- if (OM_IS_LITERAL(b.mode)) {
- // Cancel- nothing else should have been placed on stack!
- compileAssert(ws->code.size() == aFinishLiteralSize);
- // We can't be positive that exactly 2 ints were added to bytecode,
- // so calculate difference and remove that much
- while (aFinishLiteralSize != aFinishLiteralAt) {
- ws->code.pop_back();
- --aFinishLiteralSize;
- }
- --ws->stackDepth;
- a.flags = 0;
- // Clears location and OM_NO_CONVERT
- a.mode = (OperMode)(a.mode & OM_BASETYPE);
- }
- else {
- // Finish
- if (aFinishString) {
- a.mode = OM_STR;
- defineString(a);
- a.mode = (OperMode)(OM_STR | OM_STACK | OM_NO_CONVERT);
- // We know that a string push would've been 2 ints total
- ws->code[aFinishLiteralAt + 1] += a.data.i;
- }
- a.data.i = 0;
- }
- }
-
- // Operation
- if (a.mode == 0)
- a = b;
- else if (varToCount)
- a.data.i += b.data.i;
- else
- a = parseOperation(type, token, a, b);
-
- // Loop back for more? Don't loop back if next operator
- // has worse precedence than our caller
- if ((nextType & TOKEN_OPERATOR) &&
- (stopPrecedence > precedence(nextType))) {
- type = nextType;
- token = nextToken;
- continue;
- }
-
- // Done
- return a;
- }
- }
- Compiler::Operand Compiler::parseOperand(int warnAssign, int usingValue, const Variable* varToCount, Operand* memberOf) { start_func
- int type;
- string token;
- Operand result;
- createEmpty(result);
- int skippedToken = 0;
- if (t->peekToken(type, token)) {
- // REMEMBER WHEN ADDING NEW OPERAND TOKENS THAT THESE NEED
- // TO BE ADDED TO PARSEFUNCTION() AS INITIATING TOKENS ALSO
- // AND ALSO TO MAIN COMPILEBLOCK() LOOP AS EXPRESSION TOKENS
- switch (type) {
- case TOKEN_INTEGER: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = strToInt(token);
- result.mode = OM_INT;
- break;
- }
-
- case TOKEN_HEX: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = strtol(token.c_str(), NULL, 0);
- result.mode = OM_INT;
- break;
- }
-
- case TOKEN_DECIMAL: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.f = strToFloat(token);
- result.mode = OM_FLOAT;
- break;
- }
-
- case TOKEN_STRING: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = 0;
- result.ref.strLiteral = new string(token);
- result.mode = OM_STR;
- break;
- }
-
- // @TTODO: Consolidate these constants?
- case KEYW_TRUE: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = 1;
- result.mode = OM_INT;
- break;
- }
-
- case KEYW_FALSE: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = 0;
- result.mode = OM_INT;
- break;
- }
-
- case KEYW_QUERY: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = -1;
- result.mode = OM_INT;
- break;
- }
-
- case KEYW_ALL: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = 0;
- result.mode = OM_ALL;
- result.subType = 0;
- break;
- }
-
- case KEYW_ALL_OTHER: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = 0;
- result.mode = OM_ALL_OTHER;
- result.subType = 0;
- break;
- }
-
- case KEYW_NOTHING: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = 0;
- result.mode = OM_NOTHING;
- result.subType = 0;
- break;
- }
- case KEYW_THIS: {
- if (varToCount) { createInt(result, 0); break; }
- result.data.i = 0;
- result.mode = OM_THIS;
- result.subType = scriptType;
- break;
- }
-
- case TOKEN_IDENTIFIER: {
- // function call/message send or variable
- t->skipToken();
- skippedToken = 1;
- // @TODO: no scope or subscript for now
- // @TODO: varToCount not supported for this portion entirely
- // member variable or function
- int searchFor = 0;
- Operand assumeThis;
-
- if (memberOf) {
- if (OM_IS_LITERAL(memberOf->mode) || OM_IS_STR(memberOf->mode)) {
- searchFor = SUB_ENTITY;
- }
- else if (OM_IS_OBJECT(memberOf->mode)) {
- searchFor = memberOf->subType;
- }
- else if (OM_IS_ENTITY(memberOf->mode)) {
- searchFor = SUB_ENTITY;
- }
- else compileAssert(0);
- }
- else {
- // Basically "this." is presumed
- searchFor = SUB_ENTITY;
- assumeThis.data.i = 0;
- assumeThis.mode = OM_THIS;
- assumeThis.subType = scriptType;
- memberOf = &assumeThis;
- }
-
- // Search for matching built-in member
- for (int pos = 0; memberTable[pos].objtype; ++pos) {
- if ((memberTable[pos].objtype == searchFor) &&
- (token == memberTable[pos].name)) {
- // Ensure copy, not convert, for object pointer
- if (!OM_IS_LITERAL(memberOf->mode) && OM_IS_CONVERT(memberOf->mode))
- memberOf->mode = (OperMode)(memberOf->mode | OM_COPY);
- // Use pop mode if appropriate
- if (O_IS_TEMP(*memberOf)) {
- compileAssert(memberOf->data.i == 0);
- memberOf->mode = (OperMode)((memberOf->mode & ~OM_LOCATION) | OM_POP);
- }
- // Member found- assign or read?
- if ((t->peekToken(type, token)) && (type == OPER_ASSIGN)) {
- if (memberTable[pos].readonly)
- t->outputError("Member '%s' is read-only (cannot assign a value to it)", token.c_str());
- // RESOLUTION: assign anyways
- if (warnAssign)
- t->outputWarning("Assignment as a condition- did you mean '==' to compare? (surround assignment in extra parenthesis to suppress warning)");
- t->skipToken();
- // @TODO: Ensure handles all/all_other/string as memberOf
- parseAssignment(*memberOf, memberTable[pos].membertype, NULL, memberTable[pos].index);
- // @TODO: if we're usingValue, should be a OP_REFe instead of OP_SETe
- // and skip OM_POP earlier; this irrelevant value is only valid if we aren't
- compileAssert(!usingValue);
- createInt(result, 0);
- result.flags |= Operand::OF_IS_SIDEFX;
- // If popped, adjust stack
- if (OM_IS_POP(memberOf->mode))
- --ws->stackDepth;
- }
- else {
- Operand b, c;
- createInt(b, memberTable[pos].index);
- // Prep result now, as we use it's datatype
- result = dataTypeToOperand(memberTable[pos].membertype);
- result.mode = (OperMode)(result.mode | OM_STACK);
- result.data.i = 0;
- // Need a StackEntry datatype
- createInt(c, result.mode & OM_BASETYPE);
- // @TODO: Ensure handles all/all_other/string as memberOf
- postCmdRaw(searchFor == SUB_ENTITY ? OP_GETe : OP_GETo, memberOf, &b, &c);
- // If popped, it was replaced with newly created value;
- // if not popped, we have a new value on the stack
- if (!OM_IS_POP(memberOf->mode))
- ++ws->stackDepth;
- }
- // (this tells caller that we handled the OPER_MEMBER
- memberOf->mode = OM_NONE;
- return result;
- }
- }
-
- // Objects only have these specific members, so we're done
- if (searchFor != SUB_ENTITY) {
- t->outputError("Unrecognized identifier '%s'- no object member by that name", token.c_str());
- // RESOLUTION: discarded token
- break;
- }
-
- // Special handing for this. (may be presumed)
- if (memberOf->mode == OM_THIS) {
- // find matching function
- Function f;
- if (findFunction(token, f)) {
- // (only vartocount if var isn't scoped)
- if (varToCount) { createInt(result, (varToCount->scope <= 0) ? 1 : 0); break; }
- result = parseFunction(f, token);
- result.flags |= Operand::OF_IS_SIDEFX;
- if ((usingValue) && (O_IS_VOID(result))) {
- t->outputError("Function does not return a value (function returns 'void' and cannot be used within an expression)");
- // RESOLUTION: proceed with int constant
- result.flags &= ~Operand::OF_IS_VOID;
- }
- break;
- }
- // find matching variable
- VariableMap::iterator vlist = vars->find(token.c_str());
- if (vlist == vars->end()) {
- // One last possibility- message send, only if we "presumed" this.
- if (memberOf == &result) {
- // @TODO: make this auto-conversion an option
- string nextToken;
- if ((t->peekToken(type, nextToken)) && (type == OPER_MEMBER)) {
- if (varToCount) { createInt(result, 0); break; }
- // Convert identifier to string and continue
- result.data.i = 0;
- result.ref.strLiteral = new string(token);
- result.mode = OM_STR;
- break;
- }
- }
- t->outputError("Unrecognized identifier '%s'- no variable or function by that name", token.c_str());
- // RESOLUTION: discarded token
- break;
- }
- Variable& v = (*vlist).second.back();
- // If assignment coming up, perform that now
- if ((t->peekToken(type, token)) && (type == OPER_ASSIGN)) {
- if (warnAssign)
- t->outputWarning("Assignment as a condition- did you mean '==' to compare? (surround assignment in extra parenthesis to suppress warning)");
- t->skipToken();
- result = variableToOperand(v);
- parseAssignment(result, v.datatype, varToCount);
- // @TODO: optimize by getting operand from parseAssignment instead of
- // turning variable into operand, when appropriate? (careful with arrays
- // and hashes, they might need to become refs; careful with variants or
- // type changes; careful with temporaries that would have been popped now;
- // might also be affected by the optimization algorithm for a=a+1?)
- result.flags |= Operand::OF_IS_SIDEFX;
- }
- else if (!varToCount) {
- result = variableToOperand(v);
- }
- if (varToCount) { createInt(result, (&v == varToCount) ? 1 : 0); break; }
- }
- // Now it's either another entity, or an entity literal/string/etc.
- else {
- // @TODO: Ensure handles all/all_other/string
- // @TODO: Search for user-defined variable or function
- compileAssert(0);
- }
-
- break;
- }
- case KEYW_SOURCE:
- case KEYW_DEFAULT: // (needs to basically follow identifier section, expecting a :: next)
- case OPER_PLUS:
- case OPER_MINUS:
- case OPER_B_NOT:
- case OPER_L_NOT: {
- // @TODO: L_NOT can be optimized if last operation on an operand before
- // an if-clause or sometimes before an expression consisting of other
- // logical/comparison operators
- // @TODO: (how to handle precedence for opers? just recurse to parseoperand; watch for void)
- if (varToCount) { createInt(result, 0); break; }
- t->outputError("Not currently supported: '%s'", token.c_str());
- break;
- }
-
- case TOKEN_OPEN_PAREN: {
- // Another expression within parenthesis
- t->skipToken();
- result = parseExpression(warnAssign > 0 ? warnAssign - 1 : warnAssign, usingValue, NULL, 100, varToCount);
- t->peekToken(type, token);
- if (type == TOKEN_CLOSE_PAREN) {
- t->skipToken();
- }
- else {
- t->outputError("Missing closing parenthesis");
- // RESOLUTION: pretend paren was present
- }
- return result;
- }
-
- case TOKEN_ENDLINE: {
- t->outputError("Unexpected end-of-line (expected value or expression)", token.c_str());
- // RESOLUTION: token will get processed by higher routines
- break;
- }
-
- default: {
- t->outputError("Unexpected token '%s' (expected value or expression)", token.c_str());
- // RESOLUTION: token will get processed by higher routines
- break;
- }
- }
- }
-
- if (result.mode == 0) {
- // Error state
- result.data.i = 0;
- }
- else if (!skippedToken) {
- t->skipToken();
- }
-
- return result;
- }
- Compiler::Operand Compiler::parseOperation(int oper, const string& operToken, Operand& a, Operand& b) { start_func
- compileAssert(oper & TOKEN_OPERATOR);
- compileAssert(a.mode);
- compileAssert(!OM_IS_COPY(a.mode));
- compileAssert(!OM_IS_CONST(a.mode));
- compileAssert(!OM_IS_POP(a.mode));
- compileAssert(!OM_IS_POINTER(a.mode));
- compileAssert(b.mode);
- compileAssert(!OM_IS_COPY(b.mode));
- compileAssert(!OM_IS_CONST(b.mode));
- compileAssert(!OM_IS_POP(b.mode));
- compileAssert(!OM_IS_POINTER(b.mode));
- // Categorize operator- 4) string 3) math, 2) bit/mod, 1) logic, 0) equality
- int otype = 0;
- if ((oper == OPER_L_AND) || (oper == OPER_L_OR) || (oper == OPER_L_XOR))
- otype = 1;
- else if ((oper == OPER_LSHIFT) || (oper == OPER_RSHIFT) ||
- (oper == OPER_B_AND) || (oper == OPER_B_OR) ||
- (oper == OPER_B_XOR) || (oper == OPER_MOD))
- otype = 2;
- else if ((oper == OPER_PLUS) || (oper == OPER_MINUS) ||
- (oper == OPER_MULT) || (oper == OPER_DIV))
- otype = 3;
- else if (oper == OPER_CONCAT)
- otype = 4;
-
- // @TODO: convert var operands if unambiguous e.g. bit/mod, convert to int
- // General error: no hash or array or variant operands except for logical operators
- if (otype != 1) {
- if (OM_IS_ENTRY(a.mode)) {
- t->outputError("Left operand to '%s' cannot be a variant (convert to a non-variant first)", operToken.c_str());
- // RESOLUTION: continue with (hopefully) non-variant operand
- return b;
- }
- if (OM_IS_ENTRY(b.mode)) {
- t->outputError("Right operand to '%s' cannot be a variant (convert to a non-variant first)", operToken.c_str());
- // RESOLUTION: continue with non-variant operand
- return a;
- }
- if (OM_IS_ARRAY(a.mode)) {
- t->outputError("Left operand to '%s' cannot be an array (use [ and ] to select a value from the array)", operToken.c_str());
- // RESOLUTION: continue with (hopefully) non-array operand
- return b;
- }
- if (OM_IS_ARRAY(b.mode)) {
- t->outputError("Right operand to '%s' cannot be a array (use [ and ] to select a value from the array)", operToken.c_str());
- // RESOLUTION: continue with non-array operand
- return a;
- }
- if (OM_IS_HASH(a.mode)) {
- t->outputError("Left operand to '%s' cannot be a hash (use [ and ] to select a value from the hash)", operToken.c_str());
- // RESOLUTION: continue with (hopefully) non-hash operand
- return b;
- }
- if (OM_IS_HASH(b.mode)) {
- t->outputError("Right operand to '%s' cannot be a hash (use [ and ] to select a value from the hash)", operToken.c_str());
- // RESOLUTION: continue with non-hash operand
- return a;
- }
- }
-
- // General error: no string operands except for logical/comparison/string operators
- if ((otype > 1) && (otype < 4)) {
- if (OM_IS_STR(a.mode)) {
- t->outputError("Left operand to '%s' cannot be a string (convert to a number first)", operToken.c_str());
- // RESOLUTION: continue with (hopefully) non-string operand
- if (a.mode == OM_STR) delete a.ref.strLiteral;
- return b;
- }
- if (OM_IS_STR(b.mode)) {
- t->outputError("Right operand to '%s' cannot be a string (convert to a number first)", operToken.c_str());
- // RESOLUTION: continue with non-string operand
- if (b.mode == OM_STR) delete b.ref.strLiteral;
- return a;
- }
- }
- // General error: no object/"odd literal" except for logical/comparison operators
- if (otype > 1) {
- if (a.mode == OM_ALL || a.mode == OM_ALL_OTHER || a.mode == OM_NOTHING || a.mode == OM_THIS ||
- OM_IS_OBJECT(a.mode) || OM_IS_ENTITY(a.mode)) {
- t->outputError("Left operand to '%s' cannot be an entity or object", operToken.c_str());
- // RESOLUTION: continue with (hopefully) non-object operand
- return b;
- }
- if (b.mode == OM_ALL || b.mode == OM_ALL_OTHER || b.mode == OM_NOTHING || b.mode == OM_THIS ||
- OM_IS_OBJECT(b.mode) || OM_IS_ENTITY(b.mode)) {
- t->outputError("Right operand to '%s' cannot be an entity or object", operToken.c_str());
- // RESOLUTION: continue with non-object operand
- return a;
- }
- }
- else if (otype == 0) {
- // object-type operands are allowed here but must be compatible types
- if (a.mode == OM_ALL || a.mode == OM_ALL_OTHER || a.mode == OM_NOTHING || a.mode == OM_THIS ||
- b.mode == OM_ALL || b.mode == OM_ALL_OTHER || b.mode == OM_NOTHING || b.mode == OM_THIS ||
- OM_IS_OBJECT(a.mode) || OM_IS_ENTITY(a.mode) ||
- OM_IS_OBJECT(b.mode) || OM_IS_ENTITY(b.mode)) {
- // Literals are basically of type OM_ENTITY except for 'nothing'
- int aType = a.mode & OM_BASETYPE;
- int aSub = a.subType;
- int bType = b.mode & OM_BASETYPE;
- int bSub = b.subType;
- if (OM_IS_LITERAL(a.mode)) {
- if (a.mode == OM_ALL || a.mode == OM_ALL_OTHER) {
- t->outputError("Cannot use multi-object literals ('all', 'all_other') in comparison", operToken.c_str());
- // RESOLUTION: just continue with other operand for simplicity
- return b;
- }
- if ((a.mode == OM_NOTHING) && (!OM_IS_LITERAL(b.mode))) {
- aType = bType;
- aSub = bSub;
- }
- else aType = OM_ENTITY;
- }
- if (OM_IS_LITERAL(b.mode)) {
- if (b.mode == OM_ALL || b.mode == OM_ALL_OTHER) {
- t->outputError("Cannot use multi-object literals ('all', 'all_other') in comparison", operToken.c_str());
- // RESOLUTION: just continue with other operand for simplicity
- return a;
- }
- if ((b.mode == OM_NOTHING) && (!OM_IS_LITERAL(b.mode))) {
- bType = aType;
- bSub = aSub;
- }
- else bType = OM_ENTITY;
- }
- // Ensure both are same type
- if (aType != bType) {
- t->outputError("Cannot compare object to non-object or entity to non-entity using '%s' operator", operToken.c_str());
- // RESOLUTION: just continue with first operand for simplicity
- return a;
- }
- // Now ensure subtype matches exactly or is non-type vs type
- if ((aSub != bSub) && (aSub) && (bSub)) {
- t->outputError("Object or script types are not compatible for '%s' operator", operToken.c_str());
- // RESOLUTION: just continue with first operand for simplicity
- return a;
- }
- }
- }
-
- // Now we know the operands are valid modes- i/s/f, a/h/o for logicals only, l/s/g/ind/convert unverified
- // Perform standard conversions
- // One problem could occur- if b is a temporary and a converts to a temporary now,
- // a will be on stack top, so a/b and operation needs reversing
- // this won't ever occur with non-commutative operations because those
- // will already have converted a to a temporary
- int watchForSwap = O_IS_TEMP(b) && !O_IS_TEMP(a);
- if (otype == 4) {
- // String- upgrade to string always
- if (!OM_IS_STR(a.mode))
- convertToStr(a);
- if (!OM_IS_STR(b.mode))
- convertToStr(b);
- }
- if (otype == 0) {
- // Equality- upgrade to string
- if (OM_IS_STR(a.mode) && !OM_IS_STR(b.mode))
- convertToStr(b);
- if (OM_IS_STR(b.mode) && !OM_IS_STR(a.mode))
- convertToStr(a);
- }
- if ((otype == 3) || (otype == 0)) {
- // Math or equality- upgrade to float
- if (OM_IS_FLOAT(a.mode) && !OM_IS_FLOAT(b.mode))
- convertToFloat(b);
- if (OM_IS_FLOAT(b.mode) && !OM_IS_FLOAT(a.mode))
- convertToFloat(a);
- }
- if (otype == 2) {
- // Bit/Mod- downgrade to integer
- if (!OM_IS_INT(a.mode))
- convertToInt(a);
- if (!OM_IS_INT(b.mode))
- convertToInt(b);
- }
- // This will be true if we need inverse operation now
- watchForSwap = watchForSwap && O_IS_TEMP(a);
- if (watchForSwap) swap(a, b);
-
- // Triggers common routines for most basic operators
- // Common routines do not support hash/array operands (logical operators)
- int doCommon = 0;
- int isCommutative = 1;
- int hasInverse = 0; // Can we swap operands using a different opcode? (equalities mostly)
- int pushesResult = !otype; // Equality operators push a new result
- Opcode forInt, forFloat, forStr, forObj;
- Opcode swapInt, swapFloat, swapStr, swapObj;
-
- switch (oper) {
- case OPER_PLUS: {
- // Const+const
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- // (both are the same due to earlier upgrades)
- if (OM_IS_FLOAT(a.mode))
- a.data.f += b.data.f;
- else
- a.data.i += b.data.i;
- }
- else {
- doCommon = 1;
- forInt = OP_ADD;
- forFloat = OP_ADDf;
- }
- break;
- }
- case OPER_MULT: {
- // Const*const
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_FLOAT(a.mode))
- a.data.f *= b.data.f;
- else
- a.data.i *= b.data.i;
- }
- else {
- doCommon = 1;
- forInt = OP_MULT;
- forFloat = OP_MULTf;
- }
- break;
- }
- case OPER_MINUS: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_FLOAT(a.mode))
- a.data.f -= b.data.f;
- else
- a.data.i -= b.data.i;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- forInt = OP_SUB;
- forFloat = OP_SUBf;
- }
- break;
- }
- case OPER_DIV: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_FLOAT(a.mode)) {
- if (b.data.f == 0.0) {
- t->outputError("Divide by zero");
- // RESOLUTION: result is zero
- a.data.f = 0.0;
- }
- else {
- a.data.f /= b.data.f;
- }
- }
- else {
- if (b.data.i == 0) {
- t->outputError("Divide by zero");
- // RESOLUTION: result is zero
- a.data.i = 0;
- }
- else {
- a.data.i /= b.data.i;
- }
- }
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- forInt = OP_DIV;
- forFloat = OP_DIVf;
- }
- break;
- }
- case OPER_MOD: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (b.data.i == 0) {
- t->outputError("Divide by zero");
- // RESOLUTION: result is zero
- a.data.i = 0;
- }
- else {
- a.data.i %= b.data.i;
- }
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- forInt = OP_MOD;
- }
- break;
- }
- case OPER_LSHIFT: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- a.data.i <<= b.data.i;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- forInt = OP_SHIFTL;
- }
- break;
- }
- case OPER_RSHIFT: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- a.data.i >>= b.data.i;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- forInt = OP_SHIFTR;
- }
- break;
- }
- case OPER_B_AND: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- a.data.i &= b.data.i;
- }
- else {
- doCommon = 1;
- forInt = OP_AND;
- }
- break;
- }
- case OPER_B_OR: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- a.data.i |= b.data.i;
- }
- else {
- doCommon = 1;
- forInt = OP_OR;
- }
- break;
- }
- case OPER_B_XOR: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- a.data.i ^= b.data.i;
- }
- else {
- doCommon = 1;
- forInt = OP_XOR;
- }
- break;
- }
- case OPER_CONCAT: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- compileAssert(a.ref.strLiteral);
- compileAssert(b.ref.strLiteral);
- *a.ref.strLiteral += *b.ref.strLiteral;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- forStr = OP_CONCAT;
- }
- break;
- }
- case OPER_EQ: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_STR(a.mode)) {
- compileAssert(a.ref.strLiteral);
- compileAssert(b.ref.strLiteral);
- a.data.i = (myStricmp(a.ref.strLiteral->c_str(), b.ref.strLiteral->c_str()) == 0);
- delete a.ref.strLiteral;
- a.ref.strLiteral = NULL;
- }
- else if (OM_IS_FLOAT(a.mode))
- a.data.i = (a.data.f == b.data.f);
- // Only going to be a combination of this/empty
- else if ((a.mode == OM_THIS) || (a.mode == OM_NOTHING))
- a.data.i = (a.mode == b.mode);
- else
- a.data.i = (a.data.i == b.data.i);
- compileAssert(a.flags == 0);
- a.mode = OM_INT;
- }
- else {
- doCommon = 1;
- forInt = OP_EQ;
- forFloat = OP_EQf;
- forStr = OP_EQs;
- forObj = OP_EQo;
- }
- break;
- }
- case OPER_LE: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_STR(a.mode)) {
- compileAssert(a.ref.strLiteral);
- compileAssert(b.ref.strLiteral);
- a.data.i = (myStricmp(a.ref.strLiteral->c_str(), b.ref.strLiteral->c_str()) <= 0);
- delete a.ref.strLiteral;
- a.ref.strLiteral = NULL;
- }
- else if (OM_IS_FLOAT(a.mode))
- a.data.i = (a.data.f <= b.data.f);
- // Only going to be a combination of this/empty
- else if ((a.mode == OM_THIS) || (a.mode == OM_NOTHING))
- a.data.i = (a.mode == b.mode);
- else
- a.data.i = (a.data.i <= b.data.i);
- compileAssert(a.flags == 0);
- a.mode = OM_INT;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- hasInverse = 1;
- forInt = OP_LE;
- forFloat = OP_LEf;
- forStr = OP_LEs;
- forObj = OP_EQo;
- swapInt = OP_GE;
- swapFloat = OP_GEf;
- swapStr = OP_GEs;
- swapObj = OP_EQo;
- }
- break;
- }
- case OPER_GT: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_STR(a.mode)) {
- compileAssert(a.ref.strLiteral);
- compileAssert(b.ref.strLiteral);
- a.data.i = (myStricmp(a.ref.strLiteral->c_str(), b.ref.strLiteral->c_str()) > 0);
- delete a.ref.strLiteral;
- a.ref.strLiteral = NULL;
- }
- else if (OM_IS_FLOAT(a.mode))
- a.data.i = (a.data.f > b.data.f);
- // Only going to be a combination of this/empty
- else if ((a.mode == OM_THIS) || (a.mode == OM_NOTHING))
- a.data.i = (a.mode != b.mode);
- else
- a.data.i = (a.data.i > b.data.i);
- compileAssert(a.flags == 0);
- a.mode = OM_INT;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- hasInverse = 1;
- forInt = OP_GT;
- forFloat = OP_GTf;
- forStr = OP_GTs;
- forObj = OP_NEo;
- swapInt = OP_LT;
- swapFloat = OP_LTf;
- swapStr = OP_LTs;
- swapObj = OP_NEo;
- }
- break;
- }
- case OPER_GE: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_STR(a.mode)) {
- compileAssert(a.ref.strLiteral);
- compileAssert(b.ref.strLiteral);
- a.data.i = (myStricmp(a.ref.strLiteral->c_str(), b.ref.strLiteral->c_str()) >= 0);
- delete a.ref.strLiteral;
- a.ref.strLiteral = NULL;
- }
- else if (OM_IS_FLOAT(a.mode))
- a.data.i = (a.data.f >= b.data.f);
- // Only going to be a combination of this/empty
- else if ((a.mode == OM_THIS) || (a.mode == OM_NOTHING))
- a.data.i = (a.mode == b.mode);
- else
- a.data.i = (a.data.i >= b.data.i);
- compileAssert(a.flags == 0);
- a.mode = OM_INT;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- hasInverse = 1;
- forInt = OP_GE;
- forFloat = OP_GEf;
- forStr = OP_GEs;
- forObj = OP_EQo;
- swapInt = OP_LE;
- swapFloat = OP_LEf;
- swapStr = OP_LEs;
- swapObj = OP_EQo;
- }
- break;
- }
- case OPER_LT: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_STR(a.mode)) {
- compileAssert(a.ref.strLiteral);
- compileAssert(b.ref.strLiteral);
- a.data.i = (myStricmp(a.ref.strLiteral->c_str(), b.ref.strLiteral->c_str()) < 0);
- delete a.ref.strLiteral;
- a.ref.strLiteral = NULL;
- }
- else if (OM_IS_FLOAT(a.mode))
- a.data.i = (a.data.f < b.data.f);
- // Only going to be a combination of this/empty
- else if ((a.mode == OM_THIS) || (a.mode == OM_NOTHING))
- a.data.i = (a.mode != b.mode);
- else
- a.data.i = (a.data.i < b.data.i);
- compileAssert(a.flags == 0);
- a.mode = OM_INT;
- }
- else {
- doCommon = 1;
- isCommutative = 0;
- hasInverse = 1;
- forInt = OP_LT;
- forFloat = OP_LTf;
- forStr = OP_LTs;
- forObj = OP_NEo;
- swapInt = OP_GT;
- swapFloat = OP_GTf;
- swapStr = OP_GTs;
- swapObj = OP_NEo;
- }
- break;
- }
- case OPER_NE: {
- if (OM_IS_LITERAL(a.mode) && OM_IS_LITERAL(b.mode)) {
- if (OM_IS_STR(a.mode)) {
- compileAssert(a.ref.strLiteral);
- compileAssert(b.ref.strLiteral);
- a.data.i = (myStricmp(a.ref.strLiteral->c_str(), b.ref.strLiteral->c_str()) != 0);
- delete a.ref.strLiteral;
- a.ref.strLiteral = NULL;
- }
- else if (OM_IS_FLOAT(a.mode))
- a.data.i = (a.data.f != b.data.f);
- // Only going to be a combination of this/empty
- else if ((a.mode == OM_THIS) || (a.mode == OM_NOTHING))
- a.data.i = (a.mode != b.mode);
- else
- a.data.i = (a.data.i != b.data.i);
- compileAssert(a.flags == 0);
- a.mode = OM_INT;
- }
- else {
- doCommon = 1;
- forInt = OP_NE;
- forFloat = OP_NEf;
- forStr = OP_NEs;
- forObj = OP_NEo;
- }
- break;
- }
-
- case OPER_L_AND:
- case OPER_L_XOR:
- case OPER_L_OR: {
- // @TODO: (for now will just return first operand)
- // commutative, so no need to check watchForSwap
- // Also note that operands may have disparate types, be 'this'/'nothing'/'all'/'all_other',
- // or even be variants!
- // @TODO: short-circuiting if enabled
- t->outputError("Not currently supported: '%s'", operToken.c_str());
- break;
- }
-
- default: {
- // (should never get past other operations)
- compileAssert(0);
- break;
- }
- }
-
- // Common operator routine
- if (doCommon) {
- // Push-result commands must be swappable
- if (pushesResult) compileAssert(isCommutative || hasInverse);
-
- // Presumes temporaries are on top of stack
- compileAssert(!O_IS_TEMP(a) || (a.data.i == 0));
- compileAssert(!O_IS_TEMP(b) || (b.data.i == 0));
-
- // If both items are temporaries, "move" first item down to it's true position
- if (O_IS_TEMP(a) && O_IS_TEMP(b)) ++a.data.i;
-
- // Different handling based on whether we overwrite first operand
- if (pushesResult) {
- // Just confirm first item isn't the literal; any other combination is valid
- if (OM_IS_LITERAL(a.mode)) {
- // Literal goes in second position
- swap(a, b);
- // must use the inverse operation if exists
- if (hasInverse) watchForSwap = !watchForSwap;
- }
- }
- else {
- // (*for non-commutative operations, first should already be temporary*)
- if (!isCommutative) compileAssert(O_IS_TEMP(a));
- // One item MUST be a temporary (ideally, first item)
- if (!O_IS_TEMP(a) && !O_IS_TEMP(b)) {
- // Create temporary via PUSH- no reference allowed
- convertToTemp(a);
- // This moves other stack entry down if it's a stack var or temporary
- if (OM_IS_STACK(b.mode)) {
- ++b.data.i;
- }
- }
-
- // Possibilities now: var + temp, temp + temp, temp + literal
- // First item must be a temporary
- // (*never occurs if this is a commutative operation or both are temps*)
- if (!O_IS_TEMP(a)) swap(a, b);
-
- // First item should also be a copy (we know it's a temporary)
- // HOWEVER as we can never have arrays, hashes, variants here it's moot
- compileAssert(!(OM_IS_ARRAY(a.mode) || OM_IS_HASH(a.mode) || OM_IS_ENTRY(a.mode)));
- }
- // Handle COPYness now, before doing POP
- if (pushesResult && !OM_IS_LITERAL(a.mode) && OM_IS_CONVERT(a.mode))
- a.mode = (OperMode)(a.mode | OM_COPY);
- if (!OM_IS_LITERAL(b.mode) && OM_IS_CONVERT(b.mode))
- b.mode = (OperMode)(b.mode | OM_COPY);
- // If second item is a temporary, convert to a POP
- // (*ok since this isn't hash/array*)
- if (O_IS_TEMP(b)) {
- compileAssert(b.data.i == 0);
- b.mode = (OperMode)((b.mode & ~OM_LOCATION) | OM_POP);
- }
-
- // If first item is a temporary and we push result, convert that to a POP too
- if (pushesResult && O_IS_TEMP(a)) {
- a.mode = (OperMode)((a.mode & ~OM_LOCATION) | OM_POP);
- // If was further down on the stack...
- if (a.data.i == 1) {
- // ...b is also a temporary that's been converted
- compileAssert(OM_IS_POP(b.mode));
- compileAssert(b.data.i == 0);
- // a and b will get popped in reverse order, so we
- // must use the inverse operation if exists
- if (hasInverse) watchForSwap = !watchForSwap;
- }
- }
-
- // Need to swap to inverse operation?
- if (watchForSwap) {
- if (hasInverse) {
- forStr = swapStr;
- forFloat = swapFloat;
- forInt = swapInt;
- forObj = swapObj;
- }
- else compileAssert(isCommutative);
- }
-
- // Post command (we know a/b aren't variants)
- // Define strings
- if (a.mode == OM_STR) defineString(a);
- if (b.mode == OM_STR) defineString(b);
- if (OM_IS_STR(a.mode)) {
- compileAssert(OM_IS_STR(b.mode));
- // Handle CONSTness
- if (pushesResult) a.mode = (OperMode)(OM_STR_CONST | (a.mode & ~OM_BASETYPE));
- b.mode = (OperMode)(OM_STR_CONST | (b.mode & ~OM_BASETYPE));
- postCmdRaw(forStr, &a, &b);
- }
- else if (OM_IS_INT(a.mode)) {
- compileAssert(OM_IS_INT(b.mode));
- postCmdRaw(forInt, &a, &b);
- }
- else if (OM_IS_FLOAT(a.mode)) {
- compileAssert(OM_IS_FLOAT(b.mode));
- postCmdRaw(forFloat, &a, &b);
- }
- else {
- compileAssert(OM_IS_ENTITY(a.mode) || OM_IS_OBJECT(a.mode) || a.mode == OM_THIS || a.mode == OM_NOTHING);
- compileAssert(OM_IS_ENTITY(b.mode) || OM_IS_OBJECT(b.mode) || b.mode == OM_THIS || b.mode == OM_NOTHING);
- postCmdRaw(forObj, &a, &b);
- }
-
- // (move item back up if popped; also adjust stack depth)
- if (OM_IS_POP(b.mode)) {
- --ws->stackDepth;
- if (O_IS_TEMP(a)) --a.data.i;
- }
- if (OM_IS_POP(a.mode))
- --ws->stackDepth;
-
- // New result? (always an int- logical ops)
- if (pushesResult) {
- // a.ref.strLiteral is already NULL if there was anything (defineString() above)
- ++ws->stackDepth;
- createInt(a, 0);
- a.mode = (OperMode)(OM_INT | OM_STACK | OM_NO_CONVERT);
- }
-
- else {
- // Otherwise, mark result with new status- we know it's the right type now
- a.mode = (OperMode)((a.mode & ~OM_FLAGS) | OM_NO_CONVERT);
- // (would set OF_IS_COPY except we know it's not an array or hash in this code)
- }
- }
- else {
- destroyOperand(b);
- }
- // None of these operations have useful side effects. (@TODO: this won't be true for OPER_MEMBER)
- a.flags &= ~Operand::OF_IS_SIDEFX;
- return a;
- }
- const Variable& Compiler::defineVariable(DataType type, const string& name, int isParam) { start_func
- // Possibilities-
- // global var
- // local var in outer scope (local to entire script)
- // local var inner scope (local to block)
- // local var function parameter
- // global const
- // local const outer scope
- // local const inner scope
-
- Variable var;
-
- // Global / local / scoped
- var.datatype = type;
- var.datatype.flags &= ~(DATA_LOCAL | DATA_GLOBAL | DATA_CONST);
- if (type.flags & DATA_GLOBAL) {
- var.scope = -1;
- }
- else {
- var.scope = scope;
- }
-
- // Const
- if (type.flags & DATA_CONST) {
- compileAssert(!isParam);
-
- // @TODO: assign actual value once expression parsing is complete
- if (type.baseType == DATA_INT)
- var.val.constI = 0;
- else if (type.baseType == DATA_FLOAT)
- var.val.constF = 0.0;
- else {
- char* newCC = new char[1];
- newCC[0] = 0;
- var.val.constS = newCC;
- }
- // @TODO: Global consts need go into links to verify against each other during linking
- }
- // Non-globals
- else if (var.scope >= 0) {
- // inner scope- next stack item
- if (var.scope > 0) {
- // adjust for preexisting stack depth
- // and adjust current stack depth
- var.val.offs = -(++ws->stackDepth);
- }
- else {
- compileAssert(!isParam);
-
- // Take up next init spot
- var.val.offs = localInit.stackDepth++;
- // Code only goes to init for outer scope
- pushWorkspace(&localInit);
- }
-
- if (!isParam) {
- // @TODO: original assignment, if not outer scope, should be done instead
- // even outer scope assignments can be done if at top of script and simple
- generateBlank(type);
- }
- if (var.scope == 0) popWorkspace();
- }
- // Global will get added to link table whenever used
- else {
- compileAssert(!isParam);
- var.val.offs = 0;
- }
-
- // Matches a function?
- FunctionMap::iterator flist = func->find(name.c_str());
- if (flist != func->end()) {
- t->outputError("Function '%s' already exists in this script (variables and functions cannot share names)", name.c_str());
- // RESOLUTION: define variable anyways
- }
- // Matches a built-in?
- for (int pos = 0; memberTable[pos].objtype; ++pos) {
- if ((memberTable[pos].objtype == SUB_ENTITY) &&
- (name == memberTable[pos].name)) {
- t->outputError("'%s' is a built-in entity member (variables cannot override built-in members)", name.c_str());
- // RESOLUTION: define variable anyways
- }
- }
- // @TODO: error if already defined at this scope
- list<Variable>& found = addVariableMap(vars, name, var.name);
- // Globals go to the "back" of the line (overridden by any existing locals)
- if (var.scope < 0) {
- found.push_front(var);
- return found.front();
- }
- // Everything else overrides any prior variable of the same name
- found.push_back(var);
- return found.back();
- }
- void Compiler::generateBlank(DataType& type) { start_func
- if (type.flags & (DATA_ARRAY | DATA_HASH))
- postCmdII((type.flags & DATA_ARRAY) ? OP_CREATEa : OP_CREATEh,
- type.baseType == DATA_VAR ? DATA_INT : type.baseType,
- (type.baseType == DATA_ENTITY || type.baseType == DATA_OBJECT) ? type.subType : 0);
- else if (type.baseType == DATA_FLOAT)
- postCmdF(OP_PUSHf, 0.0);
- else if (type.baseType == DATA_STR)
- postCmdS(OP_PUSHs, defineString(blankString));
- else if (type.baseType == DATA_ENTITY)
- postCmd(OP_CREATEe);
- else if (type.baseType == DATA_OBJECT)
- postCmd(OP_CREATEo);
- else {
- compileAssert((type.baseType == DATA_VAR) || (type.baseType == DATA_INT));
- // (default for variants is integer 0)
- postCmdI(OP_PUSH, 0);
- }
- }
- void Compiler::defineString(Operand& o) { start_func
- compileAssert(o.mode == OM_STR);
-
- if (o.ref.strLiteral) {
- o.data.i = defineString(*o.ref.strLiteral);
- delete o.ref.strLiteral;
- o.ref.strLiteral = NULL;
- }
- }
- int Compiler::defineString(const string& str) { start_func
- // Existing element?
- StringMap::iterator found = stringMap.find(str.c_str());
- if (found != stringMap.end()) {
- // Use existing string
- return (*found).second;
- }
-
- // New string will go at end
- int offset = strings.code.size();
-
- // Translating a string to 32-bit ints in this manner (and untranslating
- // by using a char* ptr later) should be platform-neutral.
-
- // Turn into an array of 32-bit ints; includes a byte for terminating NUL
- int length = (str.size() + sizeof(Uint32)) / sizeof(Uint32);
- Uint32* uiCopy = new Uint32[length];
- memcpy(uiCopy, str.c_str(), str.size() + 1);
-
- // Push onto strings
- strings.code.reserve(offset + length);
- for (int pos = 0; pos < length; ++pos)
- strings.code.push_back(uiCopy[pos]);
-
- // Remember offset for reuse
- stringMap[newCpCopy(str.c_str())] = offset;
-
- delete[] uiCopy;
- return offset;
- }
- void Compiler::prepLinker(const Variable* var, int toData) { start_func
- compileAssert(var);
- compileAssert(var->scope < 0);
-
- LinkEntry prepLink;
- prepLink.type = toData ? LinkEntry::LINK_GLOBAL : LinkEntry::LINK_GLOBALVAR;
- prepLink.offset = ws->code.size();
- prepLink.scope = blankString;
- prepLink.name = var->name;
- prepLink.wart = dataTypeToWart(var->datatype);
- ws->links.push_back(prepLink);
- }
- void Compiler::prepLinker(const string& funcName) { start_func
- LinkEntry prepLink;
- prepLink.type = LinkEntry::LINK_FUNCTION;
- prepLink.offset = ws->code.size() + 1;
- prepLink.scope = blankString;
- prepLink.name = funcName;
- prepLink.wart = blankString;
- ws->links.push_back(prepLink);
- }
- void Compiler::prepLinker(int isContinue, const string& loopName) { start_func
- LinkEntry prepLink;
- prepLink.type = isContinue ? LinkEntry::LINK_LOOP_CONTINUE : LinkEntry::LINK_LOOP_END;
- prepLink.offset = ws->code.size() + 1;
- prepLink.scope = blankString;
- prepLink.name = loopName;
- prepLink.wart = blankString;
- ws->links.push_back(prepLink);
- }
- void Compiler::prepLinker() { start_func
- LinkEntry prepLink;
- prepLink.type = LinkEntry::LINK_STRING;
- prepLink.offset = ws->code.size();
- prepLink.scope = blankString;
- prepLink.name = blankString;
- prepLink.wart = blankString;
- ws->links.push_back(prepLink);
- }
- int Compiler::postCmdRaw(Opcode opc, Operand* o1, Operand* o2, Operand* o3) { start_func
- Operand* opers[3] = { o1, o2, o3 };
- Uint8 oms[3] = { o1 ? o1->mode : OM_NONE,
- o2 ? o2->mode : OM_NONE,
- o3 ? o3->mode : OM_NONE };
- // Must be OM_STR_CONST by now
- compileAssert(oms[0] != OM_STR);
- compileAssert(oms[1] != OM_STR);
- compileAssert(oms[2] != OM_STR);
- #ifdef COMPILEASSERT
- // Checks for valid opcode/mode combinations
- checkOpcode(opc, oms[0], oms[1], oms[2]);
- #endif
- // Create opcode/mode combination
- Uint32 opcFinal = (((((oms[2] << 8) | oms[1]) << 8) | oms[0]) << 8) | opc;
- // Always store opcode
- ws->code.push_back(opcFinal);
- int used = 1;
-
- // Operands
- for (int pos = 0; pos < 3; ++pos) {
- // (skip empty operands)
- if (oms[pos]) {
- compileAssert(opers[pos]);
-
- // @TODO: array/hash conversion/copy should add type+subtype! (see convertToArray)
-
- // Adding subtype- any conversion or copy to entity or object (verify not literal)
- // OR all/all_other literals
- if ((((oms[pos] & OM_BASETYPE) == OM_ENTITY || (oms[pos] & OM_BASETYPE) == OM_OBJECT) &&
- (oms[pos] & OM_FLAGS) != OM_NO_CONVERT && (oms[pos] & OM_FLAGS) != OM_INDIRECT &&
- (oms[pos] & OM_BASETYPE) != oms[pos])
- || oms[pos] == OM_ALL || oms[pos] == OM_ALL_OTHER) {
- ws->code.push_back(opers[pos]->subType);
- ++used;
- }
-
- // Special literals, pop- nothing more
- if (((oms[pos] >= OM_ALL) && (oms[pos] < OM_POINTER)) || OM_IS_POP(oms[pos]))
- continue;
-
- // Linking globals
- if (OM_IS_GLOBAL(oms[pos])) {
- prepLinker(opers[pos]->ref.global);
- assert(opers[pos]->data.p == NULL);
- }
-
- // Linking static strings
- // After linking, this will be-
- // String offset within strings
- // +Offset of string section
- // -Offset of operand itself
- if (oms[pos] == OM_STR_CONST) {
- prepLinker();
- ws->code.push_back(opers[pos]->data.i);
- ++used;
- }
-
- // Pushing float
- else if (oms[pos] == OM_FLOAT) {
- Uint32 buffer[bcFloatSize];
- memset(buffer, 0, sizeof(buffer[bcFloatSize]));
- *(BCfloat*)(&buffer) = opers[pos]->data.f;
-
- for (int pos = 0; pos < bcFloatSize; ++pos)
- ws->code.push_back(buffer[pos]);
- used += bcFloatSize;
- }
-
- // Pushing pointer
- else if (OM_IS_POINTER(oms[pos]) || OM_IS_GLOBAL(oms[pos])) {
- Uint32 buffer[vPtrSize];
- memset(buffer, 0, sizeof(buffer[vPtrSize]));
- *(void**)(&buffer) = opers[pos]->data.p;
-
- for (int pos = 0; pos < bcFloatSize; ++pos)
- ws->code.push_back(buffer[pos]);
- used += vPtrSize;
- }
- /* @TODO: may optimize things to have stack refs be + 1
- // Pushing stack
- else if (OM_IS_STACK(oms[pos])) {
- ws->code.push_back(opers[pos]->data.i + 1);
- ++used;
- }
- */
-
- // Pushing int
- else {
- ws->code.push_back(opers[pos]->data.i);
- ++used;
- }
- }
- }
- // Display?
- if (debugLevel() & DEBUG_BYTECODE) {
- int start = ws->code.size() - used;
- Uint32* temp = new Uint32[used];
- for (int copy = 0; copy < used; ++copy) {
- temp[copy] = ws->code[start + copy];
- }
-
- string line;
- debugBytecode(line, temp);
- debugWrite("%s", line.c_str());
-
- delete[] temp;
- }
-
- return used;
- }
- void Compiler::postCmd(Opcode opc) { start_func
- postCmdRaw(opc);
- }
- void Compiler::postCmdI(Opcode opc, Sint32 int1) { start_func
- Operand o;
- createInt(o, int1);
- postCmdRaw(opc, &o);
- }
- void Compiler::postCmdII(Opcode opc, Sint32 int1, Sint32 int2) { start_func
- Operand o1;
- Operand o2;
- createInt(o1, int1);
- createInt(o2, int2);
- postCmdRaw(opc, &o1, &o2);
- }
- void Compiler::postCmdF(Opcode opc, BCfloat float1) { start_func
- Operand o;
- createFloat(o, float1);
- postCmdRaw(opc, &o);
- }
- void Compiler::postCmdS(Opcode opc, Sint32 str1) { start_func
- Operand o;
- createInt(o, str1);
- o.mode = OM_STR_CONST;
- postCmdRaw(opc, &o);
- }
- void Compiler::postCmdPush(Operand& oper, int refOK) { start_func
- compileAssert(oper.mode);
- compileAssert(!OM_IS_COPY(oper.mode));
- compileAssert(!OM_IS_CONST(oper.mode));
- compileAssert(!OM_IS_POP(oper.mode));
- compileAssert(!OM_IS_POINTER(oper.mode));
- Opcode opc = OP_PUSHo;
-
- // TODO: ensure pushv does ALL ref/deref needed, conversions, and copying new array/hash
- if (OM_IS_ENTRY(oper.mode)) opc = OP_PUSHv;
- else if (OM_IS_ARRAY(oper.mode)) opc = OP_PUSHa;
- else if (OM_IS_HASH(oper.mode)) opc = OP_PUSHh;
- else if (OM_IS_INT(oper.mode)) opc = OP_PUSH;
- else if (OM_IS_FLOAT(oper.mode)) opc = OP_PUSHf;
- else if (OM_IS_STR(oper.mode)) opc = OP_PUSHs;
- if (!OM_IS_LITERAL(oper.mode)) {
- // Source already has conversion flag set if necessary, but it needs to
- // be a 'copy' flag
- if (OM_IS_CONVERT(oper.mode))
- oper.mode = (OperMode)(oper.mode | OM_COPY);
-
- // Force a copy if non-unique array/hash and no refs allowed
- // (known flag precludes this from being an indirect, too)
- else if (!refOK && OM_IS_KNOWN(oper.mode) && (OM_IS_ARRAY(oper.mode) || OM_IS_HASH(oper.mode)) && !O_IS_COPY(oper) && (opc != OP_PUSHv))
- oper.mode = (OperMode)((oper.mode & ~OM_FLAGS) | OM_COPY);
- }
-
- // Source also needs to be const
- if (OM_IS_STR(oper.mode))
- oper.mode = (OperMode)(OM_STR_CONST | (oper.mode & ~OM_BASETYPE));
- postCmdRaw(opc, &oper);
- }
- int Compiler::postCmdIf(int ifTrue, Sint32 offset, Operand& o, int& alwaysJumps, int& neverJumps) { start_func
- compileAssert(o.mode);
- compileAssert(!OM_IS_COPY(o.mode));
- compileAssert(!OM_IS_CONST(o.mode));
- compileAssert(!OM_IS_POP(o.mode));
- compileAssert(!OM_IS_POINTER(o.mode));
-
- int curPos = ws->code.size();
- int result = -1;
- int alwaysTrue = 0;
-
- // If item is literal, always true or false
- if (OM_IS_LITERAL(o.mode)) {
- if (OM_IS_INT(o.mode)) alwaysTrue = o.data.i;
- else if (OM_IS_FLOAT(o.mode)) alwaysTrue = (o.data.f != 0.0);
- else if (OM_IS_STR(o.mode)) {
- compileAssert(o.ref.strLiteral);
- alwaysTrue = (*o.ref.strLiteral != blankString);
- }
- // 'nothing' is always false
- else if (o.mode == OM_NOTHING) alwaysTrue = 0;
- // All other 'literals' are true
- else alwaysTrue = 1;
- if (alwaysTrue) {
- t->outputWarning("Condition is always true");
- }
- // If always false...
- else {
- t->outputWarning("Condition is always false");
- // Reverse condition
- alwaysTrue = 1;
- ifTrue = !ifTrue;
- }
- }
-
- alwaysJumps = 0;
- neverJumps = 0;
-
- if (alwaysTrue) {
- destroyOperand(o);
- if (ifTrue) {
- alwaysJumps = 1;
- // 2 = adjusted by size of opcode we're about to add
- if (offset < 0) offset -= ws->code.size() - curPos + 2;
- result = ws->code.size() + 1; // 1 = position of offset operand
- postCmdI(OP_JUMP, offset);
- }
- else {
- neverJumps = 1;
- }
- return result;
- }
- // Default to object, as that covers a lot of types
- Opcode opc = ifTrue ? OP_IFTRUEo : OP_IFFALSEo;
- if (OM_IS_STR(o.mode)) opc = ifTrue ? OP_IFTRUEs : OP_IFFALSEs;
- else if (OM_IS_FLOAT(o.mode)) opc = ifTrue ? OP_IFTRUEf : OP_IFFALSEf;
- else if (OM_IS_INT(o.mode)) opc = ifTrue ? OP_IFTRUE : OP_IFFALSE;
- else if (OM_IS_ARRAY(o.mode)) opc = ifTrue ? OP_IFTRUEa : OP_IFFALSEa;
- else if (OM_IS_HASH(o.mode)) opc = ifTrue ? OP_IFTRUEh : OP_IFFALSEh;
- else if (OM_IS_ENTRY(o.mode)) opc = ifTrue ? OP_IFTRUEv : OP_IFFALSEv;
-
- // Source already has conversion flag set if necessary, but it needs to
- // be a 'copy' flag; we already know it's not a literal.
- if (OM_IS_CONVERT(o.mode))
- o.mode = (OperMode)(o.mode | OM_COPY);
-
- // Source also needs to be const; define string literals
- if (OM_IS_STR(o.mode))
- o.mode = (OperMode)(OM_STR_CONST | (o.mode & ~OM_BASETYPE));
- // Finally, source could be a pop; temporaries should never be variants
- if (O_IS_TEMP(o)) {
- compileAssert(!OM_IS_ENTRY(o.mode));
- compileAssert(o.data.i == 0);
- o.mode = (OperMode)((o.mode & ~OM_LOCATION) | OM_POP);
- }
-
- // Post command
- if (offset < 0) offset -= ws->code.size() - curPos;
- Operand jump;
- createInt(jump, offset);
- int dist = postCmdRaw(opc, &o, &jump);
-
- // We KNOW that the last operand, the jump, is 1 byte, so we can
- // deduce the position of the jump
- result = ws->code.size() - 1;
-
- // We need to adjust the jump, if it was negative, to cover the
- // size of the op.
- if (offset < 0) ws->code[result] -= dist;
- // (adjust stack depth if popped)
- if (OM_IS_POP(o.mode))
- --ws->stackDepth;
-
- return result;
- }
|