12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708 |
- //
- // gravity_parser.c
- // gravity
- //
- // Created by Marco Bambini on 01/09/14.
- // Copyright (c) 2014 CreoLabs. All rights reserved.
- //
- #include "gravity_symboltable.h"
- #include "gravity_optionals.h"
- #include "gravity_parser.h"
- #include "gravity_macros.h"
- #include "gravity_lexer.h"
- #include "gravity_token.h"
- #include "gravity_utils.h"
- #include "gravity_array.h"
- #include "gravity_hash.h"
- #include "gravity_core.h"
- #include "gravity_ast.h"
- typedef marray_t(gravity_lexer_t*) lexer_r;
- struct gravity_parser_t {
- lexer_r *lexer; // stack of lexers (stack used in #include statements)
- gnode_r *declarations; // used to keep track of nodes hierarchy
- gnode_r *statements; // used to build AST
- gravity_delegate_t *delegate; // compiler delegate
- uint16_r vdecl; // to keep track of func expression in variable declaration nondes
- double time;
- uint32_t nerrors;
- uint32_t unique_id;
- uint32_t last_error_lineno;
- uint32_t depth; // to keep track of maximum statements depth
- uint32_t expr_depth; // to keep track of maximum expression depth
- // state ivars used by Pratt parser
- gtoken_t current_token;
- gnode_t *current_node;
- };
- // MARK: - PRATT parser specs -
- // http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
- // http://javascript.crockford.com/tdop/tdop.html
- // Precedence table as defined in Swift
- // http://nshipster.com/swift-operators/
- typedef enum {
- PREC_LOWEST,
- PREC_ASSIGN = 90, // = *= /= %= += -= <<= >>= &= ^= |= (11 cases)
- PREC_TERNARY = 100, // ?: (1 case)
- PREC_LOGICAL_OR = 110, // || (1 case)
- PREC_LOGICAL_AND = 120, // && (1 case)
- PREC_COMPARISON = 130, // < <= > >= == != === !== ~= (9 cases)
- PREC_ISA = 132, // isa (1 case)
- PREC_RANGE = 135, // ..< ... (2 cases)
- PREC_TERM = 140, // + - | ^ (4 cases)
- PREC_FACTOR = 150, // * / % & (4 cases)
- PREC_SHIFT = 160, // << >> (2 cases)
- PREC_UNARY = 170, // + - ! ~ (4 cases)
- PREC_CALL = 200 // . ( [ (3 cases)
- } prec_level;
- typedef gnode_t* (*parse_func) (gravity_parser_t *parser);
- typedef struct {
- parse_func prefix;
- parse_func infix;
- prec_level precedence;
- const char *name;
- bool right;
- } grammar_rule;
- // This table defines all of the parsing rules for the prefix and infix expressions in the grammar.
- #define RULE(prec, fn1, fn2) (grammar_rule){ fn1, fn2, prec, NULL, false}
- #define PREFIX(prec, fn) (grammar_rule){ fn, NULL, prec, NULL, false}
- #define INFIX(prec, fn) (grammar_rule){ NULL, fn, prec, NULL, false}
- #define INFIX_OPERATOR(prec, name) (grammar_rule){ NULL, parse_infix, prec, name, false}
- #define INFIX_OPERATOR_RIGHT(prec,name) (grammar_rule){ NULL, parse_infix, prec, name, true}
- #define PREFIX_OPERATOR(name) (grammar_rule){ parse_unary, NULL, PREC_LOWEST, name, false}
- #define OPERATOR(prec, name) (grammar_rule){ parse_unary, parse_infix, prec, name, false}
- // Global singleton grammar rule table
- static grammar_rule rules[TOK_END];
- // MARK: - Internal macros -
- #define MAX_RECURSION_DEPTH 1000
- #define MAX_EXPRESSION_DEPTH 512
- #define MAX_NUMBER_LENGTH 512
- #define SEMICOLON_IS_OPTIONAL 1
- #define REPORT_ERROR(_tok,...) report_error(parser, GRAVITY_ERROR_SYNTAX, _tok, __VA_ARGS__)
- #define REPORT_WARNING(_tok,...) report_error(parser, GRAVITY_WARNING, _tok, __VA_ARGS__)
- #define PUSH_DECLARATION(_decl) marray_push(gnode_t*, *parser->declarations, (gnode_t*)_decl)
- #define POP_DECLARATION() marray_pop(*parser->declarations)
- #define LAST_DECLARATION() (marray_size(*parser->declarations) ? marray_last(*parser->declarations) : NULL)
- #define IS_FUNCTION_ENCLOSED() (get_enclosing(parser, NODE_FUNCTION_DECL))
- #define IS_CLASS_ENCLOSED() (get_enclosing(parser, NODE_CLASS_DECL))
- #define CHECK_NODE(_n) if (!_n) return NULL
- #define POP_LEXER (gravity_lexer_t *)marray_pop(*parser->lexer)
- #define CURRENT_LEXER (gravity_lexer_t *)marray_last(*parser->lexer)
- #define DECLARE_LEXER gravity_lexer_t *lexer = CURRENT_LEXER; DEBUG_LEXER(lexer)
- #define PARSER_CALL_CALLBACK(_t) if (parser->delegate && parser->delegate->parser_callback) parser->delegate->parser_callback(&_t, parser->delegate->xdata)
- #define STATIC_TOKEN_CSTRING(_s,_n,_l,_b,_t) char _s[_n] = {0}; uint32_t _l = 0; \
- const char *_b = token_string(_t, &_l); \
- if (_l) memcpy(_s, _b, MINNUM(_n, _l))
- // MARK: - Prototypes -
- static const char *parse_identifier (gravity_parser_t *parser);
- static gnode_t *parse_statement (gravity_parser_t *parser);
- static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser, bool is_implicit, bool *has_default_values);
- static gnode_t *parse_compound_statement (gravity_parser_t *parser);
- static gnode_t *parse_expression (gravity_parser_t *parser);
- static gnode_t *parse_declaration_statement (gravity_parser_t *parser);
- static gnode_t *parse_function (gravity_parser_t *parser, bool is_declaration, gtoken_t access_specifier, gtoken_t storage_specifier);
- static gnode_t *adjust_assignment_expression (gravity_parser_t *parser, gtoken_t tok, gnode_t *lnode, gnode_t *rnode);
- static gnode_t *parse_literal_expression (gravity_parser_t *parser);
- static gnode_t *parse_macro_statement (gravity_parser_t *parser);
- // MARK: - Utils functions -
- static gnode_t *get_enclosing (gravity_parser_t *parser, gnode_n tag) {
- int32_t n = (int32_t)gnode_array_size(parser->declarations);
- if (!n) return NULL;
- --n;
- while (n >= 0) {
- gnode_t *decl = gnode_array_get(parser->declarations, n);
- if (!decl) return NULL;
- if (decl->tag == tag) return decl;
- --n;
- }
- return NULL;
- }
- static void patch_token_node (gnode_t *node, gtoken_s token) {
- node->token = token;
-
- if (node->tag == NODE_POSTFIX_EXPR) {
- gnode_postfix_expr_t *expr = (gnode_postfix_expr_t *)node;
- if (expr->id) expr->id->token = token;
-
- size_t count = gnode_array_size(expr->list);
- for (size_t i=0; i<count; ++i) {
- gnode_t *subnode = (gnode_t *)gnode_array_get(expr->list, i);
- if (subnode) subnode->token = token;
- }
- }
- }
- static void report_error (gravity_parser_t *parser, error_type_t error_type, gtoken_s token, const char *format, ...) {
- // just one error for each line
- if (parser->last_error_lineno == token.lineno) return;
- // increment internal error counter (and save last reported line) only if it was a real error
- if (error_type != GRAVITY_WARNING) {
- parser->last_error_lineno = token.lineno;
- ++parser->nerrors;
- }
- // get error callback (if any)
- void *data = (parser->delegate) ? parser->delegate->xdata : NULL;
- gravity_error_callback error_fn = (parser->delegate) ? ((gravity_delegate_t *)parser->delegate)->error_callback : NULL;
- // build error message
- char buffer[1024];
- va_list arg;
- if (format) {
- va_start (arg, format);
- vsnprintf(buffer, sizeof(buffer), format, arg);
- va_end (arg);
- }
- // setup error struct
- error_desc_t error_desc = {
- .lineno = token.lineno,
- .colno = token.colno,
- .fileid = token.fileid,
- .offset = token.position
- };
- // finally call error callback
- if (error_fn) error_fn(NULL, error_type, buffer, error_desc, data);
- else printf("%s\n", buffer);
- }
- static gnode_t *parse_error (gravity_parser_t *parser) {
- DECLARE_LEXER;
- gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- REPORT_ERROR(token, "%s", token.value);
- return NULL;
- }
- // RETURN:
- // - true if next token is equal to token passed as parameter (token is also consumed)
- // - false if next token is not equal (and no error is reported)
- //
- static bool parse_optional (gravity_parser_t *parser, gtoken_t token) {
- DECLARE_LEXER;
- gtoken_t peek = gravity_lexer_peek(lexer);
- if (token_iserror(peek)) {
- parse_error(parser);
- peek = gravity_lexer_peek(lexer);
- }
- if (peek == token) {
- gravity_lexer_next(lexer); // consume expected token
- return true;
- }
- // do not report any error in this case
- return false;
- }
- static bool parse_skip_until (gravity_parser_t *parser, gtoken_t token) {
- DECLARE_LEXER;
- while (1) {
- gtoken_t tok = gravity_lexer_next(lexer);
- if (tok == token) return true;
- if (tok == TOK_EOF) return false;
- }
- return false;
- }
- static bool parse_required (gravity_parser_t *parser, gtoken_t token) {
- if (parse_optional(parser, token)) return true;
- // token not found (and not consumed) so an error strategy must be implemented here
- // simple (but not simpler) error recovery
- // parser should keep track of what I am parsing
- // so based on tok I could have a token list of terminal symbols
- // call next until first sync symbol (or EOF or start of another terminal symbol is found)
- // simple error recovery, just consume next and report error
- DECLARE_LEXER;
- gtoken_t next = gravity_lexer_next(lexer);
- gtoken_s unexpected_token = gravity_lexer_token(lexer);
- REPORT_ERROR(unexpected_token, "Expected %s but found %s.", token_name(token), token_name(next));
- return false;
- }
- static bool parse_semicolon (gravity_parser_t *parser) {
- #if SEMICOLON_IS_OPTIONAL
- DECLARE_LEXER;
- if (gravity_lexer_peek(lexer) == TOK_OP_SEMICOLON) {gravity_lexer_next(lexer); return true;}
- return false;
- #else
- return parse_required(parser, TOK_OP_SEMICOLON);
- #endif
- }
- gnode_t *parse_function (gravity_parser_t *parser, bool is_declaration, gtoken_t access_specifier, gtoken_t storage_specifier) {
- DECLARE_LEXER;
- // access_specifier? storage_specifier? already parsed
- // 'function' IDENTIFIER '(' parameter_declaration_clause? ')' compound_statement
- // consume FUNC keyword (or peek for OPEN_CURLYBRACE)
- bool is_implicit = (gravity_lexer_peek(lexer) == TOK_OP_OPEN_CURLYBRACE);
- gtoken_s token = gravity_lexer_token(lexer);
- if (!is_implicit) {
- gtoken_t type = gravity_lexer_next(lexer);
- token = gravity_lexer_token(lexer);
- if (type != TOK_KEY_FUNC) {
- REPORT_ERROR(token, "Invalid function expression.");
- return NULL;
- }
- }
- // parse IDENTIFIER
- const char *identifier = NULL;
- if (is_declaration) {
- gtoken_t peek = gravity_lexer_peek(lexer);
- identifier = (token_isoperator(peek)) ? string_dup(token_name(gravity_lexer_next(lexer))) : parse_identifier(parser);
- DEBUG_PARSER("parse_function_declaration %s", identifier);
- }
- // create func declaration node
- gnode_function_decl_t *func = (gnode_function_decl_t *) gnode_function_decl_create(token, identifier, access_specifier, storage_specifier, NULL, NULL, LAST_DECLARATION());
- // check and consume TOK_OP_OPEN_PARENTHESIS
- if (!is_implicit) parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
- // parse optional parameter declaration clause
- bool has_default_values = false;
- gnode_r *params = parse_optional_parameter_declaration(parser, is_implicit, &has_default_values);
- // check and consume TOK_OP_CLOSED_PARENTHESIS
- if (!is_implicit) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- // parse compound statement
- PUSH_DECLARATION(func);
- gnode_compound_stmt_t *compound = (gnode_compound_stmt_t*)parse_compound_statement(parser);
- POP_DECLARATION();
- // if func is declarared inside a variable declaration node then the semicolon check must be
- // performed once at variable declaration node level ad not inside the func node
- bool is_inside_var_declaration = ((marray_size(parser->vdecl) > 0) && (marray_last(parser->vdecl) == 1));
- func->is_closure = is_inside_var_declaration;
-
- // parse optional semicolon
- if (!is_inside_var_declaration) parse_semicolon(parser);
- // finish func setup
- func->has_defaults = has_default_values;
- func->params = params;
- func->block = compound;
- return (gnode_t *)func;
- }
- static char *cstring_from_token (gravity_parser_t *parser, gtoken_s token) {
- #pragma unused(parser)
- uint32_t len = 0;
- const char *buffer = token_string(token, &len);
- char *str = (char *)mem_alloc(NULL, len+1);
- memcpy(str, buffer, len);
- return str;
- }
- static gnode_t *local_store_declaration (gravity_parser_t *parser, const char *identifier, const char *annotation_type, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_t *declaration) {
- gnode_r *decls = gnode_array_create();
-
- gnode_variable_decl_t *vdecl = (gnode_variable_decl_t *)gnode_variable_decl_create(declaration->token, TOK_KEY_VAR, access_specifier, storage_specifier, decls, LAST_DECLARATION());
- gnode_t *decl = gnode_variable_create(declaration->token, identifier ? string_dup(identifier) : NULL, annotation_type, declaration, LAST_DECLARATION(), vdecl);
- gnode_array_push(decls, decl);
-
- return (gnode_t *)vdecl;
- }
- static gnode_t *decl_check_access_specifier (gnode_t *node) {
- // set proper access specifiers
- // default is PUBLIC but if IDENTIFIER begins with _ then set it to PRIVATE
- if (node->tag == NODE_VARIABLE_DECL) {
- // default access specifier for variables is TOK_KEY_PUBLIC
- gnode_variable_decl_t *vdec_node = (gnode_variable_decl_t *)node;
- if (vdec_node->access == 0) {
- bool is_private = false;
- if (gnode_array_size(vdec_node->decls) > 0) {
- gnode_var_t *var = (gnode_var_t *)gnode_array_get(vdec_node->decls, 0);
- is_private = (var->identifier && var->identifier[0] == '_');
- }
- vdec_node->access = (is_private) ? TOK_KEY_PRIVATE : TOK_KEY_PUBLIC;
- }
- } else if (node->tag == NODE_FUNCTION_DECL) {
- // default access specifier for functions is PUBLIC
- gnode_function_decl_t *fdec_node = (gnode_function_decl_t *)node;
- if (!fdec_node->identifier) return node;
- bool is_private = (fdec_node->identifier[0] == '_');
- if (fdec_node->access == 0) fdec_node->access = (is_private) ? TOK_KEY_PRIVATE : TOK_KEY_PUBLIC;
- } else if (node->tag == NODE_CLASS_DECL) {
- // default access specifier for inner class declarations is PUBLIC
- gnode_class_decl_t *cdec_node = (gnode_class_decl_t *)node;
- if (!cdec_node->identifier) return node;
- bool is_private = (cdec_node->identifier[0] == '_');
- if (cdec_node->access == 0) cdec_node->access = (is_private) ? TOK_KEY_PRIVATE : TOK_KEY_PUBLIC;
- }
- return node;
- }
- static gliteral_t decode_number_binary (gtoken_s token, int64_t *n) {
- // from 2 in order to skip 0b
- *n = number_from_bin(&token.value[2], token.bytes-2);
- return LITERAL_INT;
- }
- static gliteral_t decode_number_octal (gtoken_s token, int64_t *n) {
- STATIC_TOKEN_CSTRING(str, 512, len, buffer, token);
- if (len) *n = (int64_t) number_from_oct(&str[2], len-2);
- return LITERAL_INT;
- }
- static gliteral_t decode_number_hex (gtoken_s token, int64_t *n, double *d) {
- #pragma unused(d)
- STATIC_TOKEN_CSTRING(str, 512, len, buffer, token);
- if (len) *n = (int64_t) number_from_hex(str, token.bytes);
- return LITERAL_INT;
- }
- // MARK: - Expressions -
- static gnode_t *parse_ternary_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_ternary_expression");
- DECLARE_LEXER;
- // conditional expression already parsed
- gnode_t *cond = parser->current_node;
- if (!cond) return NULL;
- // '?' expression ':' expression
- // '?' already consumed
- gtoken_s token = gravity_lexer_token(lexer);
- // parse expression 1
- gnode_t *expr1 = parse_expression(parser);
- CHECK_NODE(expr1);
- parse_required(parser, TOK_OP_COLON);
- // parse expression 2
- gnode_t *expr2 = parse_expression(parser);
- CHECK_NODE(expr2);
- // read current token to extract node total length
- gtoken_s end_token = gravity_lexer_token(lexer);
- return gnode_flow_stat_create(token, cond, expr1, expr2, LAST_DECLARATION(), end_token.position + end_token.length - token.position);
- }
- static gnode_t *parse_file_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_file_expression");
- DECLARE_LEXER;
- // at least one identifier is mandatory
- // 'file' ('.' IDENTIFIER)+
- gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- if (gravity_lexer_peek(lexer) != TOK_OP_DOT) {
- REPORT_ERROR(token, "A .identifier list is expected here.");
- return NULL;
- }
- cstring_r *list = cstring_array_create();
- while (gravity_lexer_peek(lexer) == TOK_OP_DOT) {
- gravity_lexer_next(lexer); // consume TOK_OP_DOT
- const char *identifier = parse_identifier(parser);
- if (!identifier) {
- mem_free(list);
- return NULL;
- }
- cstring_array_push(list, identifier);
- }
- return gnode_file_expr_create(token, list, LAST_DECLARATION());
- }
- static const char *parse_identifier (gravity_parser_t *parser) {
- DECLARE_LEXER;
- // parse IDENTIFIER is always mandatory
- gtoken_t type = gravity_lexer_peek(lexer);
- if (type != TOK_IDENTIFIER) {
- if (type == TOK_ERROR) parse_error(parser);
- else REPORT_ERROR(gravity_lexer_token(lexer), "Expected identifier but found %s", token_name(type));
- return NULL;
- }
- gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- const char *identifier = cstring_from_token(parser, token);
- return identifier;
- }
- static const char *parse_optional_type_annotation (gravity_parser_t *parser) {
- DECLARE_LEXER;
- const char *type_annotation = NULL;
- gtoken_t peek = gravity_lexer_peek(lexer);
- // type annotation
- // function foo (a: string, b: number)
- // check for optional type_annotation
- if (peek == TOK_OP_COLON) {
- gravity_lexer_next(lexer); // consume TOK_OP_COLON
- // parse identifier
- type_annotation = parse_identifier(parser);
- }
- return type_annotation;
- }
- static gnode_t *parse_optional_default_value (gravity_parser_t *parser) {
- DECLARE_LEXER;
- gnode_t *default_value = NULL;
- gtoken_t peek = gravity_lexer_peek(lexer);
-
- // optional literal default value
- // function foo (a: string = "Hello", b: number = 3)
- // type annotation not enforced here
-
- // check for optional default value
- if (peek == TOK_OP_ASSIGN) {
- gravity_lexer_next(lexer); // consume TOK_OP_ASSIGN
-
- // parse literal value
- default_value = parse_literal_expression(parser);
- }
-
- return default_value;
- }
- static gnode_t *parse_parentheses_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_parentheses_expression");
- // check and consume TOK_OP_OPEN_PARENTHESIS
- parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
- // parse expression
- gnode_t *expr = parse_expression(parser);
- CHECK_NODE(expr);
- // check and consume TOK_OP_CLOSED_PARENTHESIS
- parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- return expr;
- }
- static gnode_t *parse_list_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_list_expression");
- DECLARE_LEXER;
- /*
- list_expression
- : '[' ((expression) (',' expression)*)? ']' // array or empty array
- | '[' ((map_entry (',' map_entry)*) | ':') ']' // map or empty map
- ;
- map_entry
- : STRING ':' expression
- ;
- */
- // consume first '['
- parse_required(parser, TOK_OP_OPEN_SQUAREBRACKET);
- // this saved token is necessary to save start of the list/map
- gtoken_s token = gravity_lexer_token(lexer);
- // check for special empty list
- if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_SQUAREBRACKET) {
- gravity_lexer_next(lexer); // consume TOK_OP_CLOSED_SQUAREBRACKET
- return gnode_list_expr_create(token, NULL, NULL, false, LAST_DECLARATION());
- }
- // check for special empty map
- if (gravity_lexer_peek(lexer) == TOK_OP_COLON) {
- gravity_lexer_next(lexer); // consume TOK_OP_COLON
- parse_required(parser, TOK_OP_CLOSED_SQUAREBRACKET);
- return gnode_list_expr_create(token, NULL, NULL, true, LAST_DECLARATION());
- }
- // parse first expression (if any) outside of the list/map loop
- // in order to check if it is a list or map expression
- gnode_t *expr1 = parse_expression(parser);
- // if next token is a colon then assume a map
- bool ismap = (gravity_lexer_peek(lexer) == TOK_OP_COLON);
- // a list expression can be an array [expr1, expr2] or a map [string1: expr1, string2: expr2]
- // cannot be mixed so be very restrictive here
- gnode_r *list1 = gnode_array_create();
- gnode_r *list2 = (ismap) ? gnode_array_create() : NULL;
- if (expr1) gnode_array_push(list1, expr1);
- if (ismap) {
- parse_required(parser, TOK_OP_COLON);
- gnode_t *expr2 = parse_expression(parser);
- if (expr2) gnode_array_push(list2, expr2);
- }
- while (gravity_lexer_peek(lexer) == TOK_OP_COMMA) {
- gravity_lexer_next(lexer); // consume TOK_OP_COMMA
- // parse first expression
- expr1 = parse_expression(parser);
- if (expr1) gnode_array_push(list1, expr1);
- if (ismap) {
- parse_required(parser, TOK_OP_COLON);
- gnode_t *expr2 = parse_expression(parser);
- if (expr2) gnode_array_push(list2, expr2);
- }
- }
- parse_required(parser, TOK_OP_CLOSED_SQUAREBRACKET);
- return gnode_list_expr_create(token, list1, list2, ismap, LAST_DECLARATION());
- }
- static gnode_t *parse_function_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_function_expression");
- // 'func' '(' parameter_declaration_clause? ')' compound_statement
- // or
- // compound_statement (implicit func and implicit parameters)
- /*
- example:
- func foo () {
- var bar = func(x) {return x*2;}
- return bar(3);
- }
- it is equivalent to:
- func foo () {
- func bar(x) {return x*2;}
- return bar(3);
- }
- */
- // check if func is a function expression or
- // if it is a func keyword used to refers to
- // the current executing function
- gnode_t *node = parse_function(parser, false, 0, 0);
- return node;
- }
- static gnode_t *parse_identifier_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_identifier_expression");
- DECLARE_LEXER;
- const char *identifier = parse_identifier(parser);
- if (!identifier) return NULL;
- DEBUG_PARSER("IDENTIFIER: %s", identifier);
- gtoken_s token = gravity_lexer_token(lexer);
- return gnode_identifier_expr_create(token, identifier, NULL, LAST_DECLARATION());
- }
- static gnode_t *parse_identifier_or_keyword_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_identifier_expression");
- DECLARE_LEXER;
- // check if token is a keyword
- uint32_t idx_start, idx_end;
- token_keywords_indexes(&idx_start, &idx_end);
- gtoken_t peek = gravity_lexer_peek(lexer);
- if (((uint32_t)peek >= idx_start) && ((uint32_t)peek <= idx_end)) {
- // consume token keyword
- gtoken_t keyword = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- // convert from keyword to identifier
- const char *identifier = string_dup(token_name(keyword));
- return gnode_identifier_expr_create(token, identifier, NULL, LAST_DECLARATION());
- }
- // default case
- return parse_identifier_expression(parser);
- }
- static gnode_t *parse_number_expression (gravity_parser_t *parser, gtoken_s token) {
- DEBUG_PARSER("parse_number_expression");
- // check for special built-in cases first
- if (token.builtin != BUILTIN_NONE) {
- if (token.builtin == BUILTIN_LINE) return gnode_literal_int_expr_create(token, token.lineno, LAST_DECLARATION());
- else if (token.builtin == BUILTIN_COLUMN) return gnode_literal_int_expr_create(token, token.colno, LAST_DECLARATION());
- }
- // what I know here is that token is a well formed NUMBER
- // so I just need to properly decode it
- const char *value = token.value;
- gliteral_t type;
- int64_t n = 0;
- double d = 0;
- if (value[0] == '0') {
- int c = toupper(value[1]);
- if (c == 'B') {type = decode_number_binary(token, &n); goto report_node;}
- else if (c == 'O') {type = decode_number_octal(token, &n); goto report_node;}
- else if (c == 'X') {type = decode_number_hex(token, &n, &d); goto report_node;}
- }
- // number is decimal (check if it is float)
- bool isfloat = false;
- for (uint32_t i=0; i<token.bytes; ++i) {
- if (value[i] == '.') {isfloat = true; break;}
- if (value[i] == 'e') {isfloat = true; break;}
- }
- STATIC_TOKEN_CSTRING(str, MAX_NUMBER_LENGTH, len, buffer, token);
- if (len >= MAX_NUMBER_LENGTH) {
- REPORT_ERROR(token, "Malformed numeric expression.");
- return NULL;
- }
- if (isfloat) {
- d = strtod(str, NULL);
- type = LITERAL_FLOAT;
- DEBUG_PARSER("FLOAT: %.2f", d);
- }
- else {
- n = (int64_t) strtoll(str, NULL, 0);
- type = LITERAL_INT;
- DEBUG_PARSER("INT: %lld", n);
- }
- report_node:
- if (type == LITERAL_FLOAT) return gnode_literal_float_expr_create(token, (double)d, LAST_DECLARATION());
- else if (type == LITERAL_INT) return gnode_literal_int_expr_create(token, n, LAST_DECLARATION());
- else assert(0);
- return NULL;
- }
- static gnode_t *parse_analyze_literal_string (gravity_parser_t *parser, gtoken_s token, const char *s, uint32_t len) {
- // check for special built-in cases first
- if (token.builtin != BUILTIN_NONE) {
- if (token.builtin == BUILTIN_FILE) {
- if (parser->delegate && parser->delegate->filename_callback) {
- const char *filename = parser->delegate->filename_callback(token.fileid, parser->delegate->xdata);
- if (!filename) filename = "";
- return gnode_literal_string_expr_create(token, (char *)filename, (uint32_t)strlen(filename), false, LAST_DECLARATION());
- }
- }
- else if (token.builtin == BUILTIN_FUNC) {
- gnode_function_decl_t *node = (gnode_function_decl_t *)get_enclosing(parser, NODE_FUNCTION_DECL);
- const char *identifier = (node && node->identifier) ? (node->identifier) : "";
- return gnode_literal_string_expr_create(token, (char *)identifier, (uint32_t)strlen(identifier), false, LAST_DECLARATION());
- }
- else if (token.builtin == BUILTIN_CLASS) {
- gnode_class_decl_t *node = (gnode_class_decl_t *)get_enclosing(parser, NODE_CLASS_DECL);
- const char *identifier = (node && node->identifier) ? (node->identifier) : "";
- return gnode_literal_string_expr_create(token, (char *)identifier, (uint32_t)strlen(identifier), false, LAST_DECLARATION());
- }
- }
- // used in string interpolation
- gnode_r *r = NULL;
- // analyze s (of length len) for escaped characters or for interpolations
- char *buffer = mem_alloc(NULL, len+1);
- uint32_t length = 0;
- for (uint32_t i=0; i<len;) {
- int c = s[i];
- if (c == '\\') {
- // handle escape sequence here
- if (i+1 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
- switch (s[i+1]) {
- case '\'': c = '\''; ++i; break;
- case '"': c = '"'; ++i; break;
- case '\\': c = '\\'; ++i; break;
- case 'a': c = '\a'; ++i; break;
- case 'b': c = '\b'; ++i; break;
- case 'f': c = '\f'; ++i; break;
- case 'n': c = '\n'; ++i; break;
- case 'r': c = '\r'; ++i; break;
- case 't': c = '\t'; ++i; break;
- case 'v': c = '\v'; ++i; break;
- case 'x': {
- // double hex digits sequence
- // \XFF
- if (i+1+2 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
- // setup a static buffer assuming the next two characters are hex
- char b[3] = {s[i+2], s[i+3], 0};
- // convert from base 16 to base 10 (FF is at maximum 255)
- c = (int)strtoul(b, NULL, 16);
- buffer[length] = c;
- // i+2 is until \x plus 2 hex characters
- i+=2+2; ++length;
- continue;
- }
- case 'u': {
- // 4 digits unicode sequence
- // \uXXXX
- if (i+1+4 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
- // setup a static buffer assuming the next four characters are hex
- char b[5] = {s[i+2], s[i+3], s[i+4], s[i+5], 0};
- // convert from base 16 to base 10 (FFFF is at maximum 65535)
- uint32_t n = (uint32_t)strtoul(b, NULL, 16);
- length += utf8_encode(&buffer[length], n);
- i+=2+4;
- continue;
- }
- case 'U': {
- // 8 digits unicode sequence
- // \uXXXXXXXX
- if (i+1+8 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
- // setup a static buffer assuming the next height characters are hex
- char b[9] = {s[i+2], s[i+3], s[i+4], s[i+5], s[i+6], s[i+7], s[i+8], s[i+9], 0};
- // convert from base 16 to base 10 (FFFF is at maximum 4294967295)
- uint32_t n = (uint32_t)strtoul(b, NULL, 16);
- length += utf8_encode(&buffer[length], n);
- i+=2+8;
- continue;
- }
- case '(': {
- // string interpolation case
- i+=2; // skip \ and (
- uint32_t j=i;
- uint32_t nesting_level = 0;
- bool subfound = false;
- while (i<len) {
- if (s[i] == ')') {
- if (nesting_level == 0) subfound = true;
- else --nesting_level;
- }
- else if (s[i] == '(') {
- ++nesting_level;
- }
- ++i;
- if (subfound) break;
- }
- if (!subfound || nesting_level != 0) {
- REPORT_ERROR(token, "Malformed interpolation string not closed by )");
- goto return_string;
- }
- uint32_t sublen = i - j;
- // create a new temp lexer
- gravity_lexer_t *sublexer = gravity_lexer_create(&s[j], sublen, 0, true);
- marray_push(gravity_lexer_t*, *parser->lexer, sublexer);
- // parse interpolated expression
- gnode_t *subnode = parse_expression(parser);
- // add expression to r
- if (subnode) {
- // subnode contains information from a temp lexer so let's fix it
- patch_token_node(subnode, token);
-
- if (!r) r = gnode_array_create();
- if (length) gnode_array_push(r, gnode_literal_string_expr_create(token, buffer, length, true, LAST_DECLARATION()));
- gnode_array_push(r, subnode);
- }
- // free temp lexer
- marray_pop(*parser->lexer);
- gravity_lexer_free(sublexer);
- if (!subnode) goto return_string;
- buffer = mem_alloc(NULL, len+1);
- length = 0;
- continue;
- }
- default:
- // ignore unknown sequence
- break;
- }
- }
- buffer[length] = c;
- ++i; ++length;
- }
- return_string:
- // append the last string if any and if interpolation mode is on
- if (r && length) gnode_array_push(r, gnode_literal_string_expr_create(token, buffer, length, true, LAST_DECLARATION()));
- // return a node (even in case of error) so its memory will be automatically freed
- return (r) ? gnode_string_interpolation_create(token, r, LAST_DECLARATION()) : gnode_literal_string_expr_create(token, buffer, length, true, LAST_DECLARATION());
- }
- gnode_t *parse_literal_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_literal_expression");
- DECLARE_LEXER;
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- if (type == TOK_STRING) {
- uint32_t len = 0;
- const char *value = token_string(token, &len);
- DEBUG_PARSER("STRING: %.*s", len, value);
- // run string analyzer because string is returned as is from the lexer
- // but string can contains escaping sequences and interpolations that
- // need to be processed
- return parse_analyze_literal_string(parser, token, value, len);
- }
- if (type == TOK_KEY_TRUE || type == TOK_KEY_FALSE) {
- return gnode_literal_bool_expr_create(token, (int32_t)(type == TOK_KEY_TRUE) ? 1 : 0, LAST_DECLARATION());
- }
- if (type != TOK_NUMBER) {
- REPORT_ERROR(token, "Expected literal expression but found %s.", token_name(type));
- return NULL;
- }
- return parse_number_expression(parser, token);
- }
- static gnode_t *parse_keyword_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_keyword_expression");
- DECLARE_LEXER;
- gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- return gnode_keyword_expr_create(token, LAST_DECLARATION());
- }
- static gnode_r *parse_arguments_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_arguments_expression");
- DECLARE_LEXER;
- // it's OK for a call_expression_list to be empty
- if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_PARENTHESIS) return NULL;
- // https://en.wikipedia.org/wiki/Named_parameter
- // with the introduction of named parameters there are a lot
- // of sub-cases to handle here, for example I cannot know in
- // advance if a call has named parameters or not from the
- // beginning because we also support mixed calls (both position
- // and named parameters)
- // so basically I collect two arrays here
- // one for names (or positions) and one for values
- // if the call is not a named call then the useless
- // array is discarded
-
- bool arg_expected = true;
- gnode_r *list = gnode_array_create();
- uint32_t index = 0;
- while (1) {
- gtoken_t peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_COMMA) {
- // added the ability to convert ,, to ,undefined,
- gnode_array_push(list, gnode_keyword_expr_create(UNDEF_TOKEN, LAST_DECLARATION()));
- arg_expected = true;
- // consume next TOK_OP_COMMA and check for special ,) case
- gravity_lexer_next(lexer);
- if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_PARENTHESIS)
- gnode_array_push(list, gnode_keyword_expr_create(UNDEF_TOKEN, LAST_DECLARATION()));
- } else {
- // check exit condition
- if ((peek == TOK_EOF) || (peek == TOK_OP_CLOSED_PARENTHESIS))
- break;
- // I am going to parse and expression but is it allowed?
- if (!arg_expected) {
- REPORT_ERROR(gravity_lexer_token_next(lexer), "Missing , in function call.");
- return list;
- }
- // parse expression
- gnode_t *expr = parse_expression(parser);
- if (expr) gnode_array_push(list, expr);
- // consume next TOK_OP_COMMA and check for special ,) case
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_COMMA) {
- gravity_lexer_next(lexer);
- if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_PARENTHESIS)
- gnode_array_push(list, gnode_keyword_expr_create(UNDEF_TOKEN, LAST_DECLARATION()));
- }
- // arg is expected only if a comma is consumed
- // this fixes syntax errors like System.print("Hello" " World")
- arg_expected = (peek == TOK_OP_COMMA);
- }
-
- ++index;
- }
- return list;
- }
- static gnode_t *parse_postfix_expression (gravity_parser_t *parser, gtoken_t tok) {
- DEBUG_PARSER("parse_postfix_expression");
- DECLARE_LEXER;
- // '[' assignment_expression ']' => Subscript operator
- // '(' expression_list? ')' => Function call operator
- // '.' IDENTIFIER => Member access operator
- // tok already consumed and used to identify postfix sub-expression
- gnode_t *lnode = parser->current_node;
- gtoken_s token = gravity_lexer_token(lexer);
- // a postfix expression is an expression followed by a list of other expressions (separated by specific tokens)
- gnode_r *list = gnode_array_create();
- while (1) {
- gnode_t *node = NULL;
- if (tok == TOK_OP_OPEN_SQUAREBRACKET) {
- gnode_t *expr = parse_expression(parser);
- gtoken_s subtoken = gravity_lexer_token(lexer);
- parse_required(parser, TOK_OP_CLOSED_SQUAREBRACKET);
- node = gnode_postfix_subexpr_create(subtoken, NODE_SUBSCRIPT_EXPR, expr, NULL, LAST_DECLARATION());
- } else if (tok == TOK_OP_OPEN_PARENTHESIS) {
- gnode_r *args = parse_arguments_expression(parser); // can be NULL and it's OK
- gtoken_s subtoken = gravity_lexer_token(lexer);
- parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- node = gnode_postfix_subexpr_create(subtoken, NODE_CALL_EXPR, NULL, args, LAST_DECLARATION());
- } else if (tok == TOK_OP_DOT) {
- // was parse_identifier_expression but we need to allow also keywords here in order
- // to be able to supports expressions like name.repeat (repeat is a keyword but in this
- // context it should be interpreted as an identifier)
- gnode_t *expr = parse_identifier_or_keyword_expression(parser);
- gtoken_s subtoken = gravity_lexer_token(lexer);
- node = gnode_postfix_subexpr_create(subtoken, NODE_ACCESS_EXPR, expr, NULL, LAST_DECLARATION());
- } else {
- // should never reach this point
- assert(0);
- }
- // add subnode to list
- gnode_array_push(list, node);
- // check if postifx expression has more sub-nodes
- gtoken_t peek = gravity_lexer_peek(lexer);
- if ((peek != TOK_OP_OPEN_SQUAREBRACKET) && (peek != TOK_OP_OPEN_PARENTHESIS) && (peek != TOK_OP_DOT)) break;
- tok = gravity_lexer_next(lexer);
- }
- return gnode_postfix_expr_create(token, lnode, list, LAST_DECLARATION());
- }
- static gnode_t *parse_postfix_subscript (gravity_parser_t *parser) {
- // NOTE:
- // Gravity does not support a syntax like m[1,2] for matrix access (not m[1,2,3])
- // but it supports a syntax like m[1][2] (or m[1][2][3])
- DEBUG_PARSER("parse_postfix_subscript");
- return parse_postfix_expression(parser, TOK_OP_OPEN_SQUAREBRACKET);
- }
- static gnode_t *parse_postfix_access (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_postfix_access");
- return parse_postfix_expression(parser, TOK_OP_DOT);
- }
- static gnode_t *parse_postfix_call (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_postfix_call");
- return parse_postfix_expression(parser, TOK_OP_OPEN_PARENTHESIS);
- }
- static gnode_t *parse_precedence(gravity_parser_t *parser, prec_level precedence) {
- DEBUG_PARSER("parse_precedence (level %d)", precedence);
- DECLARE_LEXER;
- // peek next and check for EOF
- gtoken_t type = gravity_lexer_peek(lexer);
- if (type == TOK_EOF) return NULL;
- // execute prefix callback (if any)
- parse_func prefix = rules[type].prefix;
- // to protect stack from excessive recursion
- if (prefix && (++parser->expr_depth > MAX_EXPRESSION_DEPTH)) {
- // consume next token to avoid infinite loops
- gravity_lexer_next(lexer);
- REPORT_ERROR(gravity_lexer_token(lexer), "Maximum expression depth reached.");
- return NULL;
- }
- gnode_t *node = (prefix) ? prefix(parser) : NULL;
- if (prefix) --parser->expr_depth;
- if (!prefix || !node) {
- // we need to consume next token because error was triggered in peek
- gravity_lexer_next(lexer);
- REPORT_ERROR(gravity_lexer_token(lexer), "Expected expression but found %s.", token_name(type));
- return NULL;
- }
- // peek next and check for EOF
- gtoken_t peek = gravity_lexer_peek(lexer);
- if (peek == TOK_EOF) return node;
- while (precedence < rules[peek].precedence) {
- gtoken_t tok = gravity_lexer_next(lexer);
- grammar_rule *rule = &rules[tok];
- // execute infix callback
- parser->current_token = tok;
- parser->current_node = node;
- node = rule->infix(parser);
- // peek next and check for EOF
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_EOF) break;
- }
- return node;
- }
- static gnode_t *parse_expression (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_expression");
- return parse_precedence(parser, PREC_LOWEST);
- }
- static gnode_t *parse_unary (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_unary");
- DECLARE_LEXER;
- gtoken_t tok = gravity_lexer_next(lexer);
- gnode_t *node = parse_precedence(parser, PREC_UNARY);
- return gnode_unary_expr_create(tok, node, LAST_DECLARATION());
- }
- static gnode_t *parse_infix (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_infix");
- gtoken_t tok = parser->current_token;
- gnode_t *lnode = parser->current_node;
- // we can make right associative operators by reducing the right binding power
- grammar_rule *rule = &rules[tok];
- prec_level precedence = (rule->right) ? rule->precedence-1 : rule->precedence;
- gnode_t *rnode = parse_precedence(parser, precedence);
- if ((tok != TOK_OP_ASSIGN) && token_isassignment(tok)) return adjust_assignment_expression(parser, tok, lnode, rnode);
- return gnode_binary_expr_create(tok, lnode, rnode, LAST_DECLARATION());
- }
- // MARK: -
- static gnode_t *adjust_assignment_expression (gravity_parser_t *parser, gtoken_t tok, gnode_t *lnode, gnode_t *rnode) {
- DEBUG_PARSER("adjust_assignment_expression");
- // called when tok is an assignment != TOK_OP_ASSIGN
- // convert expressions:
- // a += 1 => a = a + 1
- // a -= 1 => a = a - 1
- // a *= 1 => a = a * 1
- // a /= 1 => a = a / 1
- // a %= 1 => a = a % 1
- // a <<=1 => a = a << 1
- // a >>=1 => a = a >> 1
- // a &= 1 => a = a & 1
- // a |= 1 => a = a | 1
- // a ^= 1 => a = a ^ 1
- gtoken_t t;
- switch (tok) {
- case TOK_OP_MUL_ASSIGN: t = TOK_OP_MUL; break;
- case TOK_OP_DIV_ASSIGN: t = TOK_OP_DIV; break;
- case TOK_OP_REM_ASSIGN: t = TOK_OP_REM; break;
- case TOK_OP_ADD_ASSIGN: t = TOK_OP_ADD; break;
- case TOK_OP_SUB_ASSIGN: t = TOK_OP_SUB; break;
- case TOK_OP_SHIFT_LEFT_ASSIGN: t = TOK_OP_SHIFT_LEFT; break;
- case TOK_OP_SHIFT_RIGHT_ASSIGN: t = TOK_OP_SHIFT_RIGHT; break;
- case TOK_OP_BIT_AND_ASSIGN: t = TOK_OP_BIT_AND; break;
- case TOK_OP_BIT_OR_ASSIGN: t = TOK_OP_BIT_OR; break;
- case TOK_OP_BIT_XOR_ASSIGN: t = TOK_OP_BIT_XOR; break;
- // should never reach this point
- default: assert(0); break;
- }
- // duplicate node is mandatory here, otherwise the deallocator will try to free memory occopied by the same node twice
- gnode_t *duplicate = gnode_duplicate(lnode, true);
- if (!duplicate) {DECLARE_LEXER; REPORT_ERROR(gravity_lexer_token(lexer), "An unexpected error occurred in %s", token_name(tok)); return NULL;}
- rnode = gnode_binary_expr_create(t, duplicate, rnode, LAST_DECLARATION());
- tok = TOK_OP_ASSIGN;
- // its an assignment expression so switch the order
- return gnode_binary_expr_create(tok, lnode, rnode, LAST_DECLARATION());
- }
- static void init_grammer_rules (void) {
- static bool created = false;
- if (created) return;
- created = true;
- // rules is a static variable initialized to 0
- // so we automatically have all members initialized to UNUSED
- rules[TOK_OP_OPEN_PARENTHESIS] = RULE(PREC_CALL, parse_parentheses_expression, parse_postfix_call);
- rules[TOK_OP_OPEN_SQUAREBRACKET] = RULE(PREC_CALL, parse_list_expression, parse_postfix_subscript);
- rules[TOK_OP_DOT] = RULE(PREC_CALL, parse_literal_expression, parse_postfix_access);
- rules[TOK_OP_OPEN_CURLYBRACE] = PREFIX(PREC_LOWEST, parse_function_expression);
- rules[TOK_KEY_FUNC] = PREFIX(PREC_LOWEST, parse_function_expression);
- rules[TOK_IDENTIFIER] = PREFIX(PREC_LOWEST, parse_identifier_expression);
- rules[TOK_STRING] = PREFIX(PREC_LOWEST, parse_literal_expression);
- rules[TOK_NUMBER] = PREFIX(PREC_LOWEST, parse_literal_expression);
- rules[TOK_KEY_UNDEFINED] = PREFIX(PREC_LOWEST, parse_keyword_expression);
- rules[TOK_KEY_CURRARGS] = PREFIX(PREC_LOWEST, parse_keyword_expression);
- rules[TOK_KEY_CURRFUNC] = PREFIX(PREC_LOWEST, parse_keyword_expression);
- rules[TOK_KEY_SUPER] = PREFIX(PREC_LOWEST, parse_keyword_expression);
- rules[TOK_KEY_FILE] = PREFIX(PREC_LOWEST, parse_file_expression);
- rules[TOK_KEY_NULL] = PREFIX(PREC_LOWEST, parse_keyword_expression);
- rules[TOK_KEY_TRUE] = PREFIX(PREC_LOWEST, parse_keyword_expression);
- rules[TOK_KEY_FALSE] = PREFIX(PREC_LOWEST, parse_keyword_expression);
- rules[TOK_OP_SHIFT_LEFT] = INFIX_OPERATOR(PREC_SHIFT, "<<");
- rules[TOK_OP_SHIFT_RIGHT] = INFIX_OPERATOR(PREC_SHIFT, ">>");
- rules[TOK_OP_MUL] = INFIX_OPERATOR(PREC_FACTOR, "*");
- rules[TOK_OP_DIV] = INFIX_OPERATOR(PREC_FACTOR, "/");
- rules[TOK_OP_REM] = INFIX_OPERATOR(PREC_FACTOR, "%");
- rules[TOK_OP_BIT_AND] = INFIX_OPERATOR(PREC_FACTOR, "&");
- rules[TOK_OP_ADD] = OPERATOR(PREC_TERM, "+");
- rules[TOK_OP_SUB] = OPERATOR(PREC_TERM, "-");
- rules[TOK_OP_BIT_OR] = INFIX_OPERATOR(PREC_TERM, "|");
- rules[TOK_OP_BIT_XOR] = INFIX_OPERATOR(PREC_TERM, "^");
- rules[TOK_OP_BIT_NOT] = PREFIX_OPERATOR("~");
- rules[TOK_OP_RANGE_EXCLUDED] = INFIX_OPERATOR(PREC_RANGE, "..<");
- rules[TOK_OP_RANGE_INCLUDED] = INFIX_OPERATOR(PREC_RANGE, "...");
- rules[TOK_KEY_ISA] = INFIX_OPERATOR(PREC_ISA, "is");
- rules[TOK_OP_LESS] = INFIX_OPERATOR(PREC_COMPARISON, "<");
- rules[TOK_OP_LESS_EQUAL] = INFIX_OPERATOR(PREC_COMPARISON, "<=");
- rules[TOK_OP_GREATER] = INFIX_OPERATOR(PREC_COMPARISON, ">");
- rules[TOK_OP_GREATER_EQUAL] = INFIX_OPERATOR(PREC_COMPARISON, ">=");
- rules[TOK_OP_ISEQUAL] = INFIX_OPERATOR(PREC_COMPARISON, "==");
- rules[TOK_OP_ISNOTEQUAL] = INFIX_OPERATOR(PREC_COMPARISON, "!=");
- rules[TOK_OP_ISIDENTICAL] = INFIX_OPERATOR(PREC_COMPARISON, "===");
- rules[TOK_OP_ISNOTIDENTICAL] = INFIX_OPERATOR(PREC_COMPARISON, "!==");
- rules[TOK_OP_PATTERN_MATCH] = INFIX_OPERATOR(PREC_COMPARISON, "~=");
- rules[TOK_OP_AND] = INFIX_OPERATOR_RIGHT(PREC_LOGICAL_AND, "&&");
- rules[TOK_OP_OR] = INFIX_OPERATOR_RIGHT(PREC_LOGICAL_OR, "||");
- rules[TOK_OP_TERNARY] = INFIX(PREC_TERNARY, parse_ternary_expression);
- rules[TOK_OP_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "=");
- rules[TOK_OP_MUL_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "*=");
- rules[TOK_OP_DIV_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "/=");
- rules[TOK_OP_REM_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "%=");
- rules[TOK_OP_ADD_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "+=");
- rules[TOK_OP_SUB_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "-=");
- rules[TOK_OP_SHIFT_LEFT_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "<<=");
- rules[TOK_OP_SHIFT_RIGHT_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, ">>=");
- rules[TOK_OP_BIT_AND_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "=&");
- rules[TOK_OP_BIT_OR_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "|=");
- rules[TOK_OP_BIT_XOR_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "^=");
- rules[TOK_OP_NOT] = PREFIX_OPERATOR("!");
- }
- // MARK: - Declarations -
- static gnode_t *parse_getter_setter (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_getter_setter");
- DECLARE_LEXER;
- gnode_t *getter = NULL;
- gnode_t *setter = NULL;
- gtoken_s token_block = gravity_lexer_token(lexer);
- while (gravity_lexer_peek(lexer) != TOK_OP_CLOSED_CURLYBRACE) {
- const char *identifier = parse_identifier(parser);
- if (!identifier) goto parse_error;
- bool is_getter = false;
- gtoken_s token = gravity_lexer_token(lexer);
- gnode_r *params = NULL;
- // getter case: does not have explicit parameters (only implicit self)
- if (strcmp(identifier, GETTER_FUNCTION_NAME) == 0) {
- is_getter = true;
- params = gnode_array_create(); // add implicit SELF param
- gnode_array_push(params, gnode_variable_create(NO_TOKEN, string_dup(SELF_PARAMETER_NAME), NULL, NULL, LAST_DECLARATION(), NULL));
- }
- // setter case: could have explicit parameters (otherwise value is implicit)
- if (strcmp(identifier, SETTER_FUNCTION_NAME) == 0) {
- is_getter = false;
- // check if parameters are explicit
- if (gravity_lexer_peek(lexer) == TOK_OP_OPEN_PARENTHESIS) {
- parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
- params = parse_optional_parameter_declaration(parser, false, NULL); // add implicit SELF
- parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- } else {
- params = gnode_array_create(); // add implicit SELF and VALUE params
- gnode_array_push(params, gnode_variable_create(NO_TOKEN, string_dup(SELF_PARAMETER_NAME), NULL, NULL, LAST_DECLARATION(), NULL));
- gnode_array_push(params, gnode_variable_create(NO_TOKEN, string_dup(SETTER_PARAMETER_NAME), NULL, NULL, LAST_DECLARATION(), NULL));
- }
- }
- mem_free(identifier);
- // create getter/setter func declaration
- gnode_t *f = gnode_function_decl_create(token, NULL, 0, 0, params, NULL, LAST_DECLARATION());
- // set storage to var so I can identify f as a special getter/setter function
- ((gnode_function_decl_t *)f)->storage = TOK_KEY_VAR;
- // parse compound statement
- PUSH_DECLARATION(f);
- gnode_compound_stmt_t *compound = (gnode_compound_stmt_t*)parse_compound_statement(parser);
- POP_DECLARATION();
- // finish func setup
- ((gnode_function_decl_t *)f)->block = compound;
- // assign f to the right function
- if (is_getter) getter = f; else setter = f;
- }
- gnode_r *functions = gnode_array_create();
- gnode_array_push(functions, (getter) ? getter : NULL); // getter is at index 0
- gnode_array_push(functions, (setter) ? setter : NULL); // setter is at index 1
- // read current token to extract node total length
- gtoken_s end_token = gravity_lexer_token(lexer);
-
- // a compound node is used to capture getter and setter
- return gnode_block_stat_create(NODE_COMPOUND_STAT, token_block, functions, LAST_DECLARATION(), end_token.position + end_token.length - token_block.position);
- parse_error:
- return NULL;
- }
- static gnode_t *parse_variable_declaration (gravity_parser_t *parser, bool isstatement, gtoken_t access_specifier, gtoken_t storage_specifier) {
- DEBUG_PARSER("parse_variable_declaration");
- DECLARE_LEXER;
- gnode_r *decls = NULL;
- gnode_t *decl = NULL;
- gnode_t *expr = NULL;
- const char *identifier = NULL;
- const char *type_annotation = NULL;
- gtoken_t type;
- gtoken_t peek;
- gtoken_s token, token2;
- // access_specifier? storage_specifier? variable_declaration ';'
- // variable_declaration: variable_declarator decl_item
- // variable_declarator: 'const' | 'var'
- // decl_item: (IDENTIFIER assignment?) (',' IDENTIFIER assignment?)*
- // assignment
- // sanity check on variable type
- type = gravity_lexer_next(lexer);
- if (!token_isvariable_declaration(type)) {
- REPORT_ERROR(gravity_lexer_token(lexer), "VAR or CONST expected here but found %s.", token_name(type));
- return NULL;
- }
- token = gravity_lexer_token(lexer);
- // create node variable declaration
- gnode_variable_decl_t *node = (gnode_variable_decl_t *) gnode_variable_decl_create(token, type, access_specifier, storage_specifier, NULL, LAST_DECLARATION());
- // initialize node array
- decls = gnode_array_create();
- loop:
- identifier = parse_identifier(parser);
- if (!identifier) return NULL;
- token2 = gravity_lexer_token(lexer);
- // type annotation is optional so it can be NULL
- type_annotation = parse_optional_type_annotation(parser);
- DEBUG_PARSER("IDENTIFIER: %s %s", identifier, (type_annotation) ? type_annotation : "");
- if (type_annotation && parser->delegate && parser->delegate->type_callback) {
- parser->delegate->type_callback(&token2, type_annotation, parser->delegate->xdata);
- }
- // check for optional assignment or getter/setter declaration (ONLY = is ALLOWED here!)
- expr = NULL;
- bool is_computed = false;
- peek = gravity_lexer_peek(lexer);
- if (token_isvariable_assignment(peek)) {
- gravity_lexer_next(lexer); // consume ASSIGNMENT
- marray_push(uint16_t, parser->vdecl, 1);
- expr = parse_expression(parser);
- marray_pop(parser->vdecl);
- } else if (peek == TOK_OP_OPEN_CURLYBRACE) {
- gravity_lexer_next(lexer); // consume TOK_OP_OPEN_CURLYBRACE
- expr = parse_getter_setter(parser);
- parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
- is_computed = true;
- }
- // sanity checks
- // 1. CONST must be followed by an assignment expression ?
- // 2. check if identifier is unique inside variable declarations
- decl = gnode_variable_create(token2, identifier, type_annotation, expr, LAST_DECLARATION(), node);
- if (decl) {
- ((gnode_var_t *)decl)->iscomputed = is_computed;
- gnode_array_push(decls, decl);
- }
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_COMMA) {
- gravity_lexer_next(lexer); // consume TOK_OP_COMMA
- goto loop;
- }
- // check and consume TOK_OP_SEMICOLON (ALWAYS required for assignments)
- // Aaron: I would keep it consistent, even if it's not strictly required.
- // Otherwise we end up with all of JavaScript's terrible ideas. ;-)
- // So I would require the semicolon at the end of any assignment statement.
- if (isstatement) parse_semicolon(parser);
- // finish to setup declaration
- node->decls = decls;
- return (gnode_t *)node;
- }
- static gnode_t *parse_enum_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
- DEBUG_PARSER("parse_enum_declaration");
- DECLARE_LEXER;
- // enum is a bit different than the traditional C like enum statements
- // in Gravity enum can contains String, Integer, Boolean and Float BUT cannot be mixed
- // Integer case can also skip values and autoincrement will be applied
- // String and Float must have a default value
- // this code will take care of parsing and syntax check for the above restrictions
- // in order to simplify node struct all the sematic checks are performed here
- // even if it is not a best practice, I find a lot easier to perform all the checks
- // directly into the parser
- // parse_enum_declaration is the only reason why gravity_symboltable.h is included here
- // checks are:
- // 1: unique internal identifiers
- // 2: if not INT then a default value is mandatory
- // 3: all values must be literals
- // 'enum' IDENTIFIER '{' enum_list '}' ';'
- // enum_list: enum_list_item (',' enum_list_item)*
- // enum_list_item: IDENTIFIER ('=' LITERAL)?
- // NODE_ENUM_DECL
- // optional scope already consumed
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- assert(type == TOK_KEY_ENUM);
- // parse IDENTIFIER
- const char *identifier = parse_identifier(parser);
- DEBUG_PARSER("parse_enum_declaration %s", identifier);
- // check and consume TOK_OP_OPEN_CURLYBRACE
- if (parse_required(parser, TOK_OP_OPEN_CURLYBRACE) == false) return NULL;
- symboltable_t *symtable = symboltable_create(SYMTABLE_TAG_ENUM); // enum symbol table (symtable is OK because order is not important inside an enum)
- int64_t enum_autoint = 0; // autoincrement value (in case of INT enum)
- uint32_t enum_counter = 0; // enum internal counter (first value (if any) determines enum type)
- gliteral_t enum_type = LITERAL_INT; // enum type (default to int)
- // create enum node
- gnode_enum_decl_t *node = (gnode_enum_decl_t *)gnode_enum_decl_create(token, identifier, access_specifier, storage_specifier, symtable, LAST_DECLARATION());
- while (1) {
- // check for empty enum
- if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_CURLYBRACE) break;
- // identifier is mandatory here
- const char *enum_id = NULL;
- gtoken_t peek = gravity_lexer_peek(lexer);
- gtoken_s enumid_token = NO_TOKEN;
- if (peek == TOK_IDENTIFIER) {
- enum_id = parse_identifier(parser);
- enumid_token = gravity_lexer_token(lexer);
- }
- if (!enum_id) {
- REPORT_ERROR(enumid_token, "Identifier expected here (found %s).", token_name(peek));
- }
- // peek next that can be only = or , or }
- peek = gravity_lexer_peek(lexer);
- gtoken_s enum_token = gravity_lexer_token(lexer);
- if (!token_isvariable_assignment(peek) && (peek != TOK_OP_COMMA) && (peek != TOK_OP_CLOSED_CURLYBRACE)) {
- REPORT_ERROR(enum_token, "Token %s not allowed here.", token_name(peek));
- }
- // check for assignment (ONLY = is ALLOWED here!)
- // assignment is optional ONLY for LITERAL_TYPE_INT case
- if ((!token_isvariable_assignment(peek)) && (enum_type != LITERAL_INT)) {
- REPORT_ERROR(enum_token, "A default value is expected here (found %s).", token_name(peek));
- }
- // check for optional default value (optional only in LITERAL_INT case)
- gnode_base_t *enum_value = NULL;
- if (token_isvariable_assignment(peek)) {
- gravity_lexer_next(lexer); // consume ASSIGNMENT
- enum_value = (gnode_base_t *)parse_expression(parser);
- }
- if (enum_value) {
- // make sure that value is a literal (or a unary expression like +num or -num)
- gnode_literal_expr_t *enum_literal = NULL;
- if (enum_value->base.tag == NODE_LITERAL_EXPR) {
- enum_literal = (gnode_literal_expr_t *)enum_value;
- } else if (enum_value->base.tag == NODE_UNARY_EXPR) {
- gnode_unary_expr_t *unary = (gnode_unary_expr_t *)enum_value;
- gnode_base_t *expr = (gnode_base_t *)unary->expr;
- // sanity check on unary expression
- if (expr->base.tag != NODE_LITERAL_EXPR) {
- REPORT_ERROR(enum_token, "Literal value expected here.");
- continue;
- }
- if ((unary->op != TOK_OP_SUB) && (unary->op != TOK_OP_ADD)) {
- REPORT_ERROR(enum_token, "Only + or - allowed in enum value definition.");
- continue;
- }
- enum_literal = (gnode_literal_expr_t *)expr;
- if ((enum_literal->type != LITERAL_FLOAT) && (enum_literal->type != LITERAL_INT)) {
- REPORT_ERROR(enum_token, "A number is expected after a + or - unary expression in an enum definition.");
- continue;
- }
- if (unary->op == TOK_OP_SUB) {
- gnode_t *temp = NULL;
- if (enum_literal->type == LITERAL_FLOAT) {
- //enum_literal->value.d = -enum_literal->value.d;
- temp = gnode_literal_float_expr_create(enum_value->base.token, -enum_literal->value.d, LAST_DECLARATION());
- }
- else if (enum_literal->type == LITERAL_INT) {
- //enum_literal->value.n64 = -enum_literal->value.n64;
- temp = gnode_literal_int_expr_create(enum_value->base.token, -enum_literal->value.n64, LAST_DECLARATION());
- }
-
- if (temp) {
- gnode_free((gnode_t *)enum_value);
- enum_value = (gnode_base_t *)temp;
- enum_literal = (gnode_literal_expr_t *)temp;
- }
- }
- } else {
- REPORT_ERROR(enum_token, "Literal value expected here.");
- continue;
- }
- // first assignment (if any) determines enum type, otherwise default INT case is assumed
- if (enum_counter == 0) {
- if (enum_literal->type == LITERAL_STRING) enum_type = LITERAL_STRING;
- else if (enum_literal->type == LITERAL_FLOAT) enum_type = LITERAL_FLOAT;
- else if (enum_literal->type == LITERAL_BOOL) enum_type = LITERAL_BOOL;
- }
- // check if literal value conforms to enum type
- if (enum_literal->type != enum_type) {
- REPORT_ERROR(enum_token, "Literal value of type %s expected here.", token_literal_name(enum_type));
- }
- // update enum_autoint value to next value
- if (enum_literal->type == LITERAL_INT) {
- enum_autoint = enum_literal->value.n64 + 1;
- }
- } else {
- enum_value = (gnode_base_t *)gnode_literal_int_expr_create(NO_TOKEN, enum_autoint, LAST_DECLARATION());
- ++enum_autoint;
- }
- // update internal enum counter
- ++enum_counter;
- // enum identifier could be NULL due to an already reported error
- if (enum_id) {
- if (!symboltable_insert(symtable, enum_id, (void *)enum_value)) {
- REPORT_ERROR(enumid_token, "Identifier %s redeclared.", enum_id);
- gnode_free((gnode_t *)enum_value); // free value here because it has not beed saved into symbol table
- }
- mem_free(enum_id); // because key is duplicated inside internal hash table
- }
- peek = gravity_lexer_peek(lexer);
- if (peek != TOK_OP_COMMA) break;
- // consume TOK_OP_COMMA and continue loop
- gravity_lexer_next(lexer);
- }
- // check and consume TOK_OP_CLOSED_CURLYBRACE
- parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
- // consume semicolon
- parse_semicolon(parser);
- // check for empty enum (not allowed)
- if (enum_counter == 0) {
- REPORT_ERROR(token, "Empty enum %s not allowed.", identifier);
- }
- if (IS_FUNCTION_ENCLOSED()) return local_store_declaration(parser, identifier, NULL, access_specifier, storage_specifier, (gnode_t *)node);
- return (gnode_t *)node;
- }
- static gnode_t *parse_module_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
- DEBUG_PARSER("parse_module_declaration");
- // module parsed but not yet supported
- // 'module' IDENTIFIER '{' declaration_statement* '}' ';'
- // optional scope already consumed
- DECLARE_LEXER;
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- assert(type == TOK_KEY_MODULE);
- // parse IDENTIFIER
- const char *identifier = parse_identifier(parser);
- DEBUG_PARSER("parse_module_declaration %s", identifier);
- #pragma unused(identifier)
- // parse optional curly brace
- bool curly_brace = parse_optional(parser, TOK_OP_OPEN_CURLYBRACE);
- // create array of declarations nodes
- gnode_r *declarations = gnode_array_create();
- // create module node
- gnode_t *node = NULL;//gnode_module_decl_create(token, identifier, access_specifier, storage_specifier, declarations, meta, LAST_DECLARATION());
- #pragma unused(access_specifier,storage_specifier)
- while (token_isdeclaration_statement(gravity_lexer_peek(lexer))) {
- gnode_t *decl = parse_declaration_statement(parser);
- if (decl) gnode_array_push(declarations, decl);
- }
- // check and consume TOK_OP_CLOSED_CURLYBRACE
- if (curly_brace) parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
- parse_semicolon(parser);
- REPORT_ERROR(token, "Module declarations not yet supported.");
- return node;
- }
- static gnode_t *parse_event_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
- #pragma unused(parser, access_specifier, storage_specifier)
- // 'event' IDENTIFIER '(' parameter_declaration_clause? ')' ';'
- // NODE_EVENT_DECL
- DECLARE_LEXER;
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- assert(type == TOK_KEY_EVENT);
- REPORT_ERROR(token, "Event declarations not yet supported.");
- return NULL;
- }
- static gnode_t *parse_function_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
- // convert a function declaration within another function to a local variable assignment
- // for example:
- //
- // func foo() {
- // func bar() {...}
- // }
- //
- // is converter to:
- //
- // func foo() {
- // var bar = func() {...}
- // }
- //
- // conversion is performed inside the parser
- // so next semantic checks can perform
- // identifier uniqueness checks
- gnode_t *node = parse_function(parser, true, access_specifier, storage_specifier);
- if (IS_FUNCTION_ENCLOSED()) {
- gnode_function_decl_t *func = (gnode_function_decl_t *)node;
- func->is_closure = true;
- return local_store_declaration(parser, func->identifier, NULL, access_specifier, storage_specifier, node);
- }
- return node;
- }
- static gnode_t *parse_id (gravity_parser_t *parser) {
- DECLARE_LEXER;
- const char *identifier1 = NULL;
- const char *identifier2 = NULL;
- gtoken_t peek;
- gtoken_s token;
- // IDENTIFIER | (IDENTIFIER)('.' IDENTIFIER)
- DEBUG_PARSER("parse_id");
- identifier1 = parse_identifier(parser);
- token = gravity_lexer_token(lexer);
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_DOT) {
- gravity_lexer_next(lexer); // consume TOK_OP_DOT
- identifier2 = parse_identifier(parser);
- }
- DEBUG_PARSER("ID: %s %s", identifier1, (identifier2) ? identifier2 : "");
- return gnode_identifier_expr_create(token, identifier1, identifier2, LAST_DECLARATION());
- }
- static gnode_r *parse_protocols (gravity_parser_t *parser) {
- DECLARE_LEXER;
- gtoken_t peek;
- gnode_t *node = NULL;
- gnode_r *list = NULL;
- // (id) (',' id)*
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_GREATER) return NULL; // just an empty protocols implementation statement
- list = gnode_array_create();
- loop:
- if (!token_isidentifier(peek)) goto abort;
- node = parse_id(parser);
- if (node) gnode_array_push(list, node);
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_COMMA) {
- gravity_lexer_next(lexer); // consume TOK_OP_COMMA
- goto loop;
- }
- return list;
- abort:
- if (list) gnode_array_free(list);
- return NULL;
- }
- static gnode_t *parse_class_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
- DEBUG_PARSER("parse_class_declaration");
- DECLARE_LEXER;
- // access_specifier? storage_specifier? 'class' IDENTIFIER class_superclass? class_protocols? '{' declaration_statement* '}' ';'
- // class_superclass: (':') id
- // class_protocols: '<' (id) (',' id)* '>'
- // optional scope already consumed (when here I am sure type is TOK_KEY_CLASS or TOK_KEY_STRUCT)
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- bool is_struct = (type == TOK_KEY_STRUCT);
- // parse IDENTIFIER
- const char *identifier = parse_identifier(parser);
- // check for optional superclass
- gnode_t *super = NULL;
- gnode_r *protocols = NULL;
- gtoken_t peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_COLON) {
- gravity_lexer_next(lexer); // consume TOK_OP_COLON
- super = parse_id(parser);
- }
- // check for optional protocols (not supported in this version)
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_LESS) {
- gravity_lexer_next(lexer); // consume '<'
- protocols = parse_protocols(parser);
- parse_required(parser, TOK_OP_GREATER); // consume '>'
- }
- // check and consume TOK_OP_OPEN_CURLYBRACE
- if (storage_specifier != TOK_KEY_EXTERN) parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
- gnode_r *declarations = gnode_array_create();
- // if class is declared inside another class then a hidden implicit private "outer" instance var at index 0
- // is automatically added
- if (IS_CLASS_ENCLOSED()) {
- gnode_r *decls = gnode_array_create();
- gnode_t *outer_var = gnode_variable_create(NO_TOKEN, string_dup(OUTER_IVAR_NAME), NULL, NULL, LAST_DECLARATION(), NULL);
- gnode_array_push(decls, outer_var);
- gnode_t *outer_decl = gnode_variable_decl_create(NO_TOKEN, TOK_KEY_VAR, TOK_KEY_PRIVATE, 0, decls, LAST_DECLARATION());
- gnode_array_push(declarations, outer_decl);
- }
- // create class declaration node
- gnode_class_decl_t *node = (gnode_class_decl_t*) gnode_class_decl_create(token, identifier, access_specifier, storage_specifier, super, protocols, NULL, is_struct, LAST_DECLARATION());
- if (storage_specifier != TOK_KEY_EXTERN) {
- PUSH_DECLARATION(node);
- peek = gravity_lexer_peek(lexer);
- while (token_isdeclaration_statement(peek) || token_ismacro(peek)) {
- gnode_t *decl = parse_declaration_statement(parser);
- if (decl) gnode_array_push(declarations, decl_check_access_specifier(decl));
- peek = gravity_lexer_peek(lexer);
- }
- POP_DECLARATION();
- }
- // check and consume TOK_OP_CLOSED_CURLYBRACE
- if (storage_specifier != TOK_KEY_EXTERN) parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
- // to check
- parse_semicolon(parser);
- // finish setup node class
- node->decls = declarations;
- const char *class_manifest_type = gravity_class_class->identifier;
- if (IS_FUNCTION_ENCLOSED()) return local_store_declaration(parser, identifier, string_dup(class_manifest_type), access_specifier, storage_specifier, (gnode_t *)node);
- return (gnode_t *)node;
- }
- static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser, bool is_implicit, bool *has_default_values) {
- DEBUG_PARSER("parse_parameter_declaration");
- DECLARE_LEXER;
- gtoken_s token = NO_TOKEN;
- gnode_t *node = NULL;
- gnode_t *default_value = NULL;
- const char *identifier = NULL;
- const char *type_annotation = NULL;
- // (IDENTIFIER type_annotation?) (',' type_annotation)*
- // type_annotation: ':' identifier
- gnode_r *params = gnode_array_create();
- assert(params);
- // check if implicit self parameter must be added
- // was if (IS_CLASS_ENCLOSED()*/) { ... add SELF PARAMETER ...}
- // but we decided to ALWAYS pass SELF because it simplified cases
- // like c1().p1.p1.p1(1234);
- // ALWAYS add an implicit SELF parameter
- // string_dup mandatory here because when the node will be freed
- // memory for the identifier will be deallocated
- node = gnode_variable_create(token, string_dup(SELF_PARAMETER_NAME), type_annotation, NULL, LAST_DECLARATION(), NULL);
- DEBUG_PARSER("PARAMETER: %s %s", SELF_PARAMETER_NAME, (type_annotation) ? type_annotation : "");
- if (node) gnode_array_push(params, node);
- if (is_implicit) return params;
- // parameter declaration clause is ALWAYS optional
- gtoken_t peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_CLOSED_PARENTHESIS) return params;
- // so there is at leat one explicit parameter
- loop:
- // initialize variables
- type_annotation = NULL;
- // parse identifier
- identifier = parse_identifier(parser);
- token = gravity_lexer_token(lexer);
- // parse optional type annotation
- type_annotation = parse_optional_type_annotation(parser);
- if (type_annotation && parser->delegate && parser->delegate->type_callback) {
- parser->delegate->type_callback(&token, type_annotation, parser->delegate->xdata);
- }
- // parse optional default LITERAL value
- default_value = parse_optional_default_value(parser);
- if (default_value && has_default_values) *has_default_values = true;
-
- // fill parameters array with the new node
- node = gnode_variable_create(token, identifier, type_annotation, default_value, LAST_DECLARATION(), NULL);
- if (node) gnode_array_push(params, node);
- DEBUG_PARSER("PARAMETER: %s %s", identifier, (type_annotation) ? type_annotation : "");
- // check for optional comma in order to decide
- // if the loop should continue or not
- peek = gravity_lexer_peek(lexer);
- if (peek == TOK_OP_COMMA) {
- gravity_lexer_next(lexer); // consume TOK_OP_COMMA
- goto loop;
- }
- return params;
- }
- // MARK: - Macro -
- typedef enum {
- UNITTEST_NONE,
- UNITTEST_NAME,
- UNITTEST_ERROR,
- UNITTEST_RESULT,
- UNITTEST_ERROR_ROW,
- UNITTEST_ERROR_COL,
- UNITTEST_NOTE
- } unittest_t;
- static unittest_t parse_unittest_identifier (const char *identifier) {
- if (string_cmp(identifier, "name") == 0) return UNITTEST_NAME;
- if (string_cmp(identifier, "note") == 0) return UNITTEST_NOTE;
- if (string_cmp(identifier, "error") == 0) return UNITTEST_ERROR;
- if (string_cmp(identifier, "error_row") == 0) return UNITTEST_ERROR_ROW;
- if (string_cmp(identifier, "error_col") == 0) return UNITTEST_ERROR_COL;
- if (string_cmp(identifier, "result") == 0) return UNITTEST_RESULT;
- return UNITTEST_NONE;
- }
- static gnode_t *parse_unittest_macro (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_unittest_macro");
- DECLARE_LEXER;
- // #unittest {
- // name: "Unit test name";
- // note: "Some notes here";
- // error: NONE, SYNTAX, RUNTIME, WARNING;
- // error_row: number;
- // error_col: number;
- // result: LITERAL;
- // '}' ';'?
-
- // sanity check: unittest macro must be root of the document
- // 1. parse_statement
- // 2. parse_macro_statement
- if (marray_size(*parser->statements) != 2) {
- REPORT_ERROR(gravity_lexer_token(lexer), "#unittest macro cannot be embedded in a statement (it must be the root of the document).");
- return NULL;
- }
- gnode_literal_expr_t *name_node = NULL;
- gnode_literal_expr_t *note_node = NULL;
- gnode_identifier_expr_t *err_node = NULL;
- gnode_literal_expr_t *row_node = NULL;
- gnode_literal_expr_t *col_node = NULL;
- gnode_literal_expr_t *value_node = NULL;
- parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
- while (gravity_lexer_peek(lexer) != TOK_OP_CLOSED_CURLYBRACE) {
- const char *id = parse_identifier(parser);
- if (id == NULL) goto handle_error;
- parse_required(parser, TOK_OP_COLON);
- unittest_t type = parse_unittest_identifier(id);
- mem_free(id);
- if (type == UNITTEST_NAME) {
- name_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
- if (name_node == NULL) goto handle_error;
- }
- else if (type == UNITTEST_NOTE) {
- note_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
- if (note_node == NULL) goto handle_error;
- }
- else if (type == UNITTEST_ERROR) {
- err_node = (gnode_identifier_expr_t *)parse_identifier_expression(parser);
- if (err_node == NULL) goto handle_error;
- }
- else if (type == UNITTEST_ERROR_ROW) {
- row_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
- if (row_node == NULL) goto handle_error;
- }
- else if (type == UNITTEST_ERROR_COL) {
- col_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
- if (col_node == NULL) goto handle_error;
- }
- else if (type == UNITTEST_RESULT) {
- gtoken_t op = TOK_EOF;
- gtoken_t peek = gravity_lexer_peek(lexer);
- // check if peek is a + or - sign
- if ((peek == TOK_OP_SUB) || (peek == TOK_OP_ADD))
- op = gravity_lexer_next(lexer);
- else if (peek == TOK_KEY_NULL) {
- // an expected return value can now be keyword NULL
- gravity_lexer_next(lexer); // consume NULL keyword
- value_node = NULL;
- goto handle_continue;
- }
- value_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
- if (value_node == NULL) goto handle_error;
- // if a negative sign has been parsed then manually fix the literal expression (if it is a number)
- if (op == TOK_OP_SUB) {
- if (value_node->type == LITERAL_INT) value_node->value.n64 = -value_node->value.n64;
- else if (value_node->type == LITERAL_FLOAT) value_node->value.d = -value_node->value.d;
- }
- }
- else {
- REPORT_ERROR(gravity_lexer_token(lexer), "Unknown token found in #unittest declaration.");
- goto handle_error;
- }
- handle_continue:
- parse_semicolon(parser);
- }
- parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
- parse_semicolon(parser);
- // decode unit array and report error/unittest
- // unit test name max length is 1024
- const char *description = NULL;
- const char *note = NULL;
- char buffer[1024];
- char buffer2[1024];
- error_type_t expected_error = GRAVITY_ERROR_NONE;
- gravity_value_t expected_value = VALUE_FROM_NULL;
- int32_t expected_nrow = -1;
- int32_t expected_ncol = -1;
- // unittest name should be a literal string
- if ((name_node) && (name_node->type == LITERAL_STRING)) {
- // no more C strings in AST so we need a static buffer
- snprintf(buffer, sizeof(buffer), "%.*s", name_node->len, name_node->value.str);
- description = buffer;
- }
- // note (optional) should be a literal string
- if ((note_node) && (note_node->type == LITERAL_STRING)) {
- // no more C strings in AST so we need a static buffer
- snprintf(buffer2, sizeof(buffer2), "%.*s", note_node->len, note_node->value.str);
- note = buffer2;
- }
- // decode expected error: NONE, SYNTAX, SEMANTIC, RUNTIME, WARNING
- if (err_node) {
- if (string_cmp(err_node->value, "NONE") == 0)
- expected_error = GRAVITY_ERROR_NONE;
- else if (string_cmp(err_node->value, "SYNTAX") == 0)
- expected_error = GRAVITY_ERROR_SYNTAX;
- else if (string_cmp(err_node->value, "SEMANTIC") == 0)
- expected_error = GRAVITY_ERROR_SEMANTIC;
- else if (string_cmp(err_node->value, "RUNTIME") == 0)
- expected_error = GRAVITY_ERROR_RUNTIME;
- else if (string_cmp(err_node->value, "WARNING") == 0)
- expected_error = GRAVITY_WARNING;
- }
- // decode error line/col
- if ((row_node) && (row_node->type == LITERAL_INT)) {
- expected_nrow = (int32_t)row_node->value.n64;
- }
- if ((col_node) && (col_node->type == LITERAL_INT)) {
- expected_ncol = (int32_t)col_node->value.n64;
- }
- // decode unittest expected result
- if (value_node) {
- if (value_node->type == LITERAL_STRING)
- expected_value = VALUE_FROM_CSTRING(NULL, value_node->value.str);
- else if (value_node->type == LITERAL_INT)
- expected_value = VALUE_FROM_INT((gravity_int_t)value_node->value.n64);
- else if (value_node->type == LITERAL_FLOAT)
- expected_value = VALUE_FROM_FLOAT((gravity_float_t)value_node->value.d);
- else if (value_node->type == LITERAL_BOOL)
- expected_value = (value_node->value.n64) ? VALUE_FROM_TRUE : VALUE_FROM_FALSE;
- }
- // report unittest to delegate
- if ((parser->delegate) && (parser->delegate->unittest_callback)) {
- gravity_unittest_callback unittest_cb = parser->delegate->unittest_callback;
- unittest_cb(NULL, expected_error, description, note, expected_value, expected_nrow, expected_ncol, parser->delegate->xdata);
- } else {
- // it was unit test responsability to free expected_value but if no unit test delegate is set I should take care of it
- gravity_value_free(NULL, expected_value);
- }
- // free temp nodes
- if (name_node) gnode_free((gnode_t*)name_node);
- if (note_node) gnode_free((gnode_t*)note_node);
- if (err_node) gnode_free((gnode_t*)err_node);
- if (row_node) gnode_free((gnode_t*)row_node);
- if (col_node) gnode_free((gnode_t*)col_node);
- if (value_node) gnode_free((gnode_t*)value_node);
- // always return NULL
- return NULL;
- handle_error:
- parse_skip_until(parser, TOK_OP_CLOSED_CURLYBRACE);
- return NULL;
- }
- static gnode_t *parse_include_macro (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_include_macro");
- DECLARE_LEXER;
- // process filename (can be an identifier or a literal string)
- // only literals are supported in this version
- gtoken_t type;
- gtoken_s token;
- const char *module_name;
- gravity_lexer_t *newlexer;
- loop:
- newlexer = NULL;
- type = gravity_lexer_next(lexer);
- token = gravity_lexer_token(lexer);
- // check if it is a string token
- if (type != TOK_STRING) {
- REPORT_ERROR(token, "Expected file name but found %s.", token_name(type));
- return NULL;
- }
- // check pre-requisites
- if ((!parser->delegate) || (!parser->delegate->loadfile_callback)) {
- REPORT_ERROR(gravity_lexer_token(lexer), "Unable to load file because no loadfile callback registered in delegate.");
- return NULL;
- }
- // parse string
- module_name = cstring_from_token(parser, token);
- size_t size = 0;
- uint32_t fileid = 0;
- // module_name is a filename and it is used by lexer to store filename into tokens
- // tokens are then stored inside AST nodes in order to locate errors into source code
- // AST can live a lot longer than both lexer and parser so we need a way to persistent
- // store these chuncks of memory
- bool is_static = false;
- const char *source = parser->delegate->loadfile_callback(module_name, &size, &fileid, parser->delegate->xdata, &is_static);
- if (source) newlexer = gravity_lexer_create(source, size, fileid, is_static);
- if (newlexer) {
- // push new lexer into lexer stack
- marray_push(gravity_lexer_t*, *parser->lexer, newlexer);
- } else {
- REPORT_ERROR(token, "Unable to load file %s.", module_name);
- }
- // cleanup memory
- if (module_name) mem_free(module_name);
- // check for optional comma
- if (gravity_lexer_peek(lexer) == TOK_OP_COMMA) {
- gravity_lexer_next(lexer); // consume TOK_OP_COMMA
- goto loop;
- }
- // parse semicolon
- parse_semicolon(parser);
- return NULL;
- }
- // MARK: - Statements -
- static gnode_t *parse_label_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_label_statement");
- // 'case' expression ':' statement
- // 'default' ':' statement
- DECLARE_LEXER;
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- assert((type == TOK_KEY_CASE) || (type == TOK_KEY_DEFAULT));
- // case specific expression
- gnode_t *expr = NULL;
- if (type == TOK_KEY_CASE) {
- expr = parse_expression(parser);
- }
- // common part
- parse_required(parser, TOK_OP_COLON);
- gnode_t *stmt = parse_statement(parser);
- return gnode_label_stat_create(token, expr, stmt, LAST_DECLARATION());
- }
- static gnode_t *parse_flow_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_flow_statement");
- // 'if' '(' expression ')' statement ('else' statement)?
- // 'switch' '(' expression ')' statement
- DECLARE_LEXER;
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- assert((type == TOK_KEY_IF) || (type == TOK_KEY_SWITCH));
- // check required TOK_OP_OPEN_PARENTHESIS
- parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
- // parse common expression
- gnode_t *cond = parse_expression(parser);
- // check and consume TOK_OP_CLOSED_PARENTHESIS
- parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- // parse common statement
- gnode_t *stmt1 = parse_statement(parser);
- gnode_t *stmt2 = NULL;
- if ((type == TOK_KEY_IF) && (gravity_lexer_peek(lexer) == TOK_KEY_ELSE)) {
- gravity_lexer_next(lexer); // consume TOK_KEY_ELSE
- stmt2 = parse_statement(parser);
- }
- // read current token to extract node total length
- gtoken_s end_token = gravity_lexer_token(lexer);
-
- return gnode_flow_stat_create(token, cond, stmt1, stmt2, LAST_DECLARATION(), end_token.position + end_token.length - token.position);
- }
- static gnode_t *parse_loop_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_loop_statement");
- // 'while' '(' expression ')' statement
- // 'repeat' statement 'while' '(' expression ')' ';'
- // 'for' '(' condition 'in' expression ')' statement
- DECLARE_LEXER;
- gnode_t *cond = NULL;
- gnode_t *stmt = NULL;
- gnode_t *expr = NULL;
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- gtoken_s end_token;
- assert((type == TOK_KEY_WHILE) || (type == TOK_KEY_REPEAT) || (type == TOK_KEY_FOR));
- // 'while' '(' expression ')' statement
- if (type == TOK_KEY_WHILE) {
- // check optional TOK_OP_OPEN_PARENTHESIS
- bool is_parenthesize = parse_optional(parser, TOK_OP_OPEN_PARENTHESIS);
- // parse while condition
- cond = parse_expression(parser);
- // check and consume TOK_OP_CLOSED_PARENTHESIS
- if (is_parenthesize) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- // parse while statement
- stmt = parse_statement(parser);
- goto return_node;
- }
- // 'repeat' statement 'while' '(' expression ')' ';'
- if (type == TOK_KEY_REPEAT) {
- // parse repeat statement
- stmt = parse_statement(parser);
- // check and consume TOK_KEY_WHILE
- parse_required(parser, TOK_KEY_WHILE);
- // check optional TOK_OP_OPEN_PARENTHESIS
- bool is_parenthesize = parse_optional(parser, TOK_OP_OPEN_PARENTHESIS);
- // parse while expression
- expr = parse_expression(parser);
- // check and consume TOK_OP_CLOSED_PARENTHESIS
- if (is_parenthesize) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- // semicolon
- parse_semicolon(parser);
- goto return_node;
- }
- // 'for' '(' condition 'in' expression ')' statement
- if (type == TOK_KEY_FOR) {
- // check optional TOK_OP_OPEN_PARENTHESIS
- bool is_parenthesize = parse_optional(parser, TOK_OP_OPEN_PARENTHESIS);
- // parse condition (means parse variable declaration or expression)
- if (token_isvariable_declaration(gravity_lexer_peek(lexer))) {
- cond = parse_variable_declaration(parser, false, 0, 0);
- } else {
- cond = parse_expression(parser);
- }
- // check and consume TOK_KEY_IN
- parse_required(parser, TOK_KEY_IN);
- // parse expression
- expr = parse_expression(parser);
- // check and consume TOK_OP_CLOSED_PARENTHESIS
- if (is_parenthesize) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
- // parse for statement
- stmt = parse_statement(parser);
- }
-
- return_node:
- // read current token to extract node total length
- end_token = gravity_lexer_token(lexer);
-
- // return loop node
- return gnode_loop_stat_create(token, cond, stmt, expr, LAST_DECLARATION(), end_token.position + end_token.length - token.position);
- }
- static gnode_t *parse_jump_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_jump_statement");
- // 'break' ';'
- // 'continue' ';'
- // 'return' expression? ';'
- DECLARE_LEXER;
- gtoken_t type = gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- assert((type == TOK_KEY_BREAK) || (type == TOK_KEY_CONTINUE) || (type == TOK_KEY_RETURN));
- gnode_t *expr = NULL;
- if ((type == TOK_KEY_RETURN) && (gravity_lexer_peek(lexer) != TOK_OP_SEMICOLON) && (gravity_lexer_peek(lexer) != TOK_OP_CLOSED_CURLYBRACE)) {
- expr = parse_expression(parser);
- }
- parse_semicolon(parser);
- return gnode_jump_stat_create(token, expr, LAST_DECLARATION());
- }
- static gnode_t *parse_compound_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_compound_statement");
- // '{' (statement+)? '}'
- // check and consume TOK_OP_OPEN_CURLYBRACE
- parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
- DECLARE_LEXER;
- gtoken_s token = gravity_lexer_token(lexer);
- gnode_r *stmts = gnode_array_create();
- while (token_isstatement(gravity_lexer_peek(lexer))) {
- if (++parser->depth > MAX_RECURSION_DEPTH) {
- REPORT_ERROR(gravity_lexer_token(lexer), "Maximum statement recursion depth reached.");
- return NULL;
- }
- gnode_t *node = parse_statement(parser);
- if (node) gnode_array_push(stmts, node);
- --parser->depth;
- }
- // check and consume TOK_OP_CLOSED_CURLYBRACE
- parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
- // read current token to extract node total length
- gtoken_s end_token = gravity_lexer_token(lexer);
- return gnode_block_stat_create(NODE_COMPOUND_STAT, token, stmts, LAST_DECLARATION(), end_token.position + end_token.length - token.position);
- }
- static gnode_t *parse_empty_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_empty_statement");
- // ;
- DECLARE_LEXER;
- gravity_lexer_next(lexer);
- gtoken_s token = gravity_lexer_token(lexer);
- return gnode_empty_stat_create(token, LAST_DECLARATION());
- }
- static gnode_t *parse_declaration_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_declaration_statement");
- DECLARE_LEXER;
- gtoken_t peek = gravity_lexer_peek(lexer);
- gtoken_t access_specifier = 0; // 0 means no access specifier set
- gtoken_t storage_specifier = 0; // 0 means no storage specifier set
- // check if an access specifier is set
- if (token_isaccess_specifier(peek)) {
- access_specifier = gravity_lexer_next(lexer);
- peek = gravity_lexer_peek(lexer);
- }
- // check if a storage specifier is set
- if (token_isstorage_specifier(peek)) {
- storage_specifier = gravity_lexer_next(lexer);
- peek = gravity_lexer_peek(lexer);
- }
- // it is a syntax error to specify an access or storage specifier followed by an empty declaration
- if ((peek == TOK_OP_SEMICOLON) && ((access_specifier) || (storage_specifier))) {
- REPORT_ERROR(gravity_lexer_token(lexer), "Access or storage specifier cannot be used here.");
- }
- switch (peek) {
- case TOK_MACRO: return parse_macro_statement(parser);
- case TOK_KEY_FUNC: return parse_function_declaration(parser, access_specifier, storage_specifier);
- case TOK_KEY_ENUM: return parse_enum_declaration(parser, access_specifier, storage_specifier);
- case TOK_KEY_MODULE: return parse_module_declaration(parser, access_specifier, storage_specifier);
- case TOK_KEY_EVENT: return parse_event_declaration(parser, access_specifier, storage_specifier);
- case TOK_KEY_CLASS:
- case TOK_KEY_STRUCT: return parse_class_declaration(parser, access_specifier, storage_specifier);
- case TOK_OP_SEMICOLON: return parse_empty_statement(parser);
- case TOK_KEY_VAR:
- case TOK_KEY_CONST: return parse_variable_declaration(parser, true, access_specifier, storage_specifier);
- default: REPORT_ERROR(gravity_lexer_token(lexer), "Unrecognized token %s.", token_name(peek)); return NULL;
- }
- // should never reach this point
- assert(0);
- return NULL;
- }
- static gnode_t *parse_import_statement (gravity_parser_t *parser) {
- #pragma unused(parser)
- DEBUG_PARSER("parse_import_statement");
- DECLARE_LEXER;
- // import is a syntactic sugar for System.import
- gravity_lexer_next(lexer);
- return NULL;
- }
- static gnode_t *parse_macro_statement (gravity_parser_t *parser) {
- typedef enum {
- MACRO_UNKNOWN = 0,
- MACRO_UNITEST = 1,
- MACRO_INCLUDE = 2,
- MACRO_PUSH = 3,
- MACRO_POP = 4
- } builtin_macro;
- DEBUG_PARSER("parse_macro_statement");
- DECLARE_LEXER;
- // consume special # symbol
- gtoken_t type = gravity_lexer_next(lexer);
- assert(type == TOK_MACRO);
- // check for #! and interpret #! shebang bash as one line comment
- // only if found on first line
- if (gravity_lexer_peek(lexer) == TOK_OP_NOT && gravity_lexer_lineno(lexer) == 1) {
- // consume special ! symbol
- type = gravity_lexer_next(lexer);
- assert(type == TOK_OP_NOT);
- // skip until EOL
- gravity_lexer_skip_line(lexer);
- return NULL;
- }
- // macro has its own parser because I don't want to mess standard syntax
- const char *macroid = parse_identifier(parser);
- if (macroid == NULL) goto handle_error;
- // check macro
- builtin_macro macro_type = MACRO_UNKNOWN;
- if (string_cmp(macroid, "unittest") == 0) macro_type = MACRO_UNITEST;
- else if (string_cmp(macroid, "include") == 0) macro_type = MACRO_INCLUDE;
- else if (string_cmp(macroid, "push") == 0) macro_type = MACRO_PUSH;
- else if (string_cmp(macroid, "pop") == 0) macro_type = MACRO_POP;
- mem_free(macroid);
- gtoken_s token = gravity_lexer_token(lexer);
- token.type = TOK_MACRO;
- PARSER_CALL_CALLBACK(token);
- switch (macro_type) {
- case MACRO_UNITEST:
- return parse_unittest_macro(parser);
- break;
- case MACRO_INCLUDE:
- return parse_include_macro(parser);
- break;
- case MACRO_PUSH:
- break;
- case MACRO_POP:
- break;
- case MACRO_UNKNOWN:
- break;
- }
- handle_error:
- REPORT_WARNING(gravity_lexer_token(lexer), "%s", "Unknown macro token. Declaration will be ignored.");
- return NULL;
- }
- static gnode_t *parse_special_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_special_statement");
- DECLARE_LEXER;
- // consume special @ symbol
- gtoken_t type = gravity_lexer_next(lexer);
- assert(type == TOK_SPECIAL);
- // special is really special so it has its own parser
- // because I don't want to mess standard syntax
- const char *specialid = parse_identifier(parser);
- if (specialid == NULL) goto handle_error;
- handle_error:
- REPORT_WARNING(gravity_lexer_token(lexer), "%s", "Unknown special token. Declaration will be ignored.");
- return NULL;
- }
- static gnode_t *parse_expression_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_expression_statement");
- gnode_t *expr = parse_expression(parser);
- parse_semicolon(parser);
- return expr;
- }
- static gnode_t *parse_statement (gravity_parser_t *parser) {
- DEBUG_PARSER("parse_statement");
- // label_statement
- // flow_statement
- // loop_statement
- // jump_statement
- // compound_statement
- // declaration_statement
- // empty_statement
- // import_statement
- // expression_statement (default)
- DECLARE_LEXER;
- gtoken_t token = gravity_lexer_peek(lexer);
- if (token_iserror(token)) return parse_error(parser);
- if (token_islabel_statement(token)) return parse_label_statement(parser);
- else if (token_isflow_statement(token)) return parse_flow_statement(parser);
- else if (token_isloop_statement(token)) return parse_loop_statement(parser);
- else if (token_isjump_statement(token)) return parse_jump_statement(parser);
- else if (token_iscompound_statement(token)) return parse_compound_statement(parser);
- else if (token_isdeclaration_statement(token)) return parse_declaration_statement(parser);
- else if (token_isempty_statement(token)) return parse_empty_statement(parser);
- else if (token_isimport_statement(token)) return parse_import_statement(parser);
- else if (token_isspecial_statement(token)) return parse_special_statement(parser);
- else if (token_ismacro(token)) return parse_macro_statement(parser);
- return parse_expression_statement(parser); // DEFAULT
- }
- // MARK: - Internal functions -
- static void parser_register_core_classes (gravity_parser_t *parser) {
- const char **list = gravity_core_identifiers();
- // for each core identifier create a dummy extern variable node
- gnode_r *decls = gnode_array_create();
- uint32_t i = 0;
- while (list[i]) {
- const char *identifier = list[i];
- gnode_t *node = gnode_variable_create(NO_TOKEN, string_dup(identifier), NULL, NULL, LAST_DECLARATION(), NULL);
- gnode_array_push(decls, node);
- ++i;
- }
- // register a variable declaration node in global statements
- gnode_t *node = gnode_variable_decl_create(NO_TOKEN, TOK_KEY_VAR, 0, TOK_KEY_EXTERN, decls, LAST_DECLARATION());;
- gnode_array_push(parser->statements, (gnode_t *)node);
- }
- static void parser_register_optional_classes (gravity_parser_t *parser) {
- // for each optional identifier create a dummy extern variable node
- gnode_r *decls = gnode_array_create();
- // compile time optional classes
- const char **list = gravity_optional_identifiers();
- uint32_t i = 0;
- while (list[i]) {
- const char *identifier = list[i];
- gnode_t *decl = gnode_variable_create(NO_TOKEN, string_dup(identifier), NULL, NULL, LAST_DECLARATION(), NULL);
- gnode_array_push(decls, decl);
- ++i;
- }
- // check if optional classes callback is registered (runtime optional classes)
- if (parser->delegate && parser->delegate->optional_classes) {
- list = parser->delegate->optional_classes(parser->delegate->xdata);
- i = 0;
- while (list[i]) {
- const char *identifier = list[i];
- gnode_t *decl_node = gnode_variable_create(NO_TOKEN, string_dup(identifier), NULL, NULL, LAST_DECLARATION(), NULL);
- gnode_array_push(decls, decl_node);
- ++i;
- }
- }
-
- // register a variable declaration node in global statements
- gnode_t *node = gnode_variable_decl_create(NO_TOKEN, TOK_KEY_VAR, 0, TOK_KEY_EXTERN, decls, LAST_DECLARATION());;
- gnode_array_push(parser->statements, (gnode_t *)node);
- }
- static uint32_t parser_run (gravity_parser_t *parser) {
- DEBUG_PARSER("=== BEGIN PARSING ===");
- // register core and optional classes as extern globals
- parser_register_core_classes(parser);
- parser_register_optional_classes(parser);
- nanotime_t t1 = nanotime();
- do {
- while (gravity_lexer_peek(CURRENT_LEXER)) {
- gnode_t *node = parse_statement(parser);
- if (node) gnode_array_push(parser->statements, node);
- }
- // since it is a stack of lexers then check if it is a real EOF
- gravity_lexer_t *lexer = CURRENT_LEXER;
- gravity_lexer_free(lexer);
- marray_pop(*parser->lexer);
- } while (marray_size(*parser->lexer));
- nanotime_t t2 = nanotime();
- parser->time = millitime(t1, t2);
- DEBUG_PARSER("=== END PARSING ===\n");
- return parser->nerrors;
- }
- static void parser_cleanup (gravity_parser_t *parser) {
- // in case of error (so AST is not returned)
- // then cleanup internal nodes
- gnode_t *node= gnode_block_stat_create(NODE_LIST_STAT, NO_TOKEN, parser->statements, LAST_DECLARATION(), 0);
- gnode_free(node);
- }
- static void parser_appendcode (const char *source, gravity_parser_t *parser) {
- if (source == NULL) return;
- size_t len = strlen(source);
- if (len <= 0) return;
- // build a new lexer based on source code to prepend
- gravity_lexer_t *lexer1 = gravity_lexer_create(source, len, 0, true);
- if (!lexer1) return;
- // pop current lexer
- gravity_lexer_t *lexer2 = POP_LEXER;
- // swap lexer2 with lexer1
- marray_push(gravity_lexer_t*, *parser->lexer, lexer1);
- marray_push(gravity_lexer_t*, *parser->lexer, lexer2);
- }
- // MARK: - Public functions -
- gravity_parser_t *gravity_parser_create (const char *source, size_t len, uint32_t fileid, bool is_static) {
- init_grammer_rules();
- gravity_parser_t *parser = mem_alloc(NULL, sizeof(gravity_parser_t));
- if (!parser) return NULL;
- gravity_lexer_t *lexer = gravity_lexer_create(source, len, fileid, is_static);
- if (!lexer) goto abort_init;
- parser->lexer = mem_alloc(NULL, sizeof(lexer_r));
- marray_init(*parser->lexer);
- marray_push(gravity_lexer_t*, *parser->lexer, lexer);
- parser->statements = gnode_array_create();
- if (!parser->statements) goto abort_init;
- parser->declarations = gnode_array_create();
- if (!parser->declarations) goto abort_init;
- marray_init(parser->vdecl);
-
- parser->last_error_lineno = UINT32_MAX;
- return parser;
- abort_init:
- gravity_parser_free(parser);
- return NULL;
- }
- gnode_t *gravity_parser_run (gravity_parser_t *parser, gravity_delegate_t *delegate) {
- parser->delegate = delegate;
- gravity_lexer_setdelegate(CURRENT_LEXER, delegate);
- // check if some user code needs to be prepended
- if ((delegate) && (delegate->precode_callback))
- parser_appendcode(delegate->precode_callback(delegate->xdata), parser);
- // if there are syntax errors then just returns
- if (parser_run(parser) > 0) {
- parser_cleanup (parser);
- return NULL;
- }
- // if there are some open declarations then there should be an error somewhere
- if (marray_size(*parser->declarations) > 0) return NULL;
- // return ast
- return gnode_block_stat_create(NODE_LIST_STAT, NO_TOKEN, parser->statements, NULL, 0);
- }
- void gravity_parser_free (gravity_parser_t *parser) {
- // free memory for stack of lexers
- if (parser->lexer) {
- size_t _len = marray_size(*parser->lexer);
- for (size_t i=0; i<_len; ++i) {
- gravity_lexer_t *lexer = (gravity_lexer_t *)marray_get(*parser->lexer, i);
- gravity_lexer_free(lexer);
- }
- marray_destroy(*parser->lexer);
- mem_free(parser->lexer);
- }
- // parser->declarations is used to keep track of nodes hierarchy and it contains pointers to
- // parser->statements nodes so there is no need to free it using node_array_free because nodes
- // are freed when AST (parser->statements) is freed
- if (parser->declarations) {
- marray_destroy(*parser->declarations);
- mem_free(parser->declarations);
- }
- // parser->statements is returned from gravity_parser_run
- // and must be deallocated using gnode_free
- marray_destroy(parser->vdecl);
- mem_free(parser);
- }
|