/* gib_semantics.c GIB tree handling functions Copyright (C) 2003 Brian Koropoff Author: Brian Koropoff Date: #DATE# This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "QF/qtypes.h" #include "gib_tree.h" #include "gib_parse.h" #include "gib_semantics.h" static int GIB_Semantic_Validate_Class (gib_tree_t * tokens) { gib_tree_t *a_class, *line, *cmd; if (!tokens->next || !tokens->next->next) { GIB_Parse_Error ("Malformed class definition; expected class " "name, optional 'extends' and parent class, and " "program block.", tokens->start); return -1; } if (tokens->next->next->delim == ' ' && !strcmp (tokens->next->next->str, "extends")) { if (!tokens->next->next->next) { GIB_Parse_Error ("Malformed class definition; " "expected parent class after " "\":\".", tokens->next->next->start); return -1; } a_class = tokens->next->next->next->next; } else a_class = tokens->next->next; if (!a_class || !a_class->children || a_class->delim != '{') { GIB_Parse_Error ("Malformed class definition; expected " "program block.", tokens->next->next->start); return -1; } for (line = a_class->children; line; line = line->next) { switch (line->type) { case TREE_T_LABEL: if (strcmp (line->str, "class") && strcmp (line->str, "instance")) { GIB_Parse_Error ("Malformed class " "definition; allowed " "labels are instance " "and class.", line->start); return -1; } break; case TREE_T_CMD: cmd = line->children; if (strcmp (cmd->str, "function")) { GIB_Parse_Error ("Malformed class " "definition; only " "allowed command is " "function.", cmd->start); return -1; } else { gib_tree_t *last; for (last = cmd; last->next; last = last->next); if (!cmd->next || !last || last->delim != '{' || !last->children) { GIB_Parse_Error ("Malformed function " "definition; name, " "optional arg list, " "program block " "expected.", cmd->start); return -1; } } break; default: GIB_Parse_Error ("Malformed class " "definition; only commands " "and labels allowed.", line->start); return -1; break; } } return 0; } static gib_tree_t * GIB_Semantic_Normal_To_Lines (gib_tree_t * tokens, const char *program, gib_tree_flags_t flags, unsigned int start, unsigned int end) { gib_tree_t *mainline, *lines = 0, *embedded, *token, *temp; /* Normal tokens */ // Create main line struct now as an anchor lines = mainline = GIB_Tree_New (TREE_T_CMD); for (token = tokens; token; token = token->next) { // Normal token? if (token->delim == ' ' || token->delim == '(') { // Check for embedded commands/variables embedded = GIB_Parse_Embedded (token); if (gib_parse_error) { GIB_Tree_Unref (&mainline); return 0; } if (embedded) { // Find end of embedded commands for (temp = embedded; temp->next; temp = temp->next); // Link it in temp->next = lines; lines = embedded; // Mark token for processing at runtime token->flags |= TREE_A_EMBED; } else if (token->children) // Mark token for processing at runtime token->flags |= TREE_A_EMBED; } } // Fill in info for primary line mainline->str = program; mainline->flags = flags; mainline->start = start; mainline->end = end; // Check for assignment if (tokens->next && tokens->next->delim == ' ' && !strcmp (tokens->next->str, "=")) mainline->type = TREE_T_ASSIGN; // Check for message sending else if (tokens->next && tokens->next->delim == ' ' && !strcmp (tokens->next->str, "->")) { if (!tokens->next->next) { GIB_Tree_Unref (&mainline); GIB_Parse_Error ("Cannot send empty message.", token->next->start); return NULL; } else mainline->type = TREE_T_SEND; // Check for class definition, validate } else if (!strcmp (tokens->str, "class") && GIB_Semantic_Validate_Class (tokens)) { GIB_Tree_Unref (&mainline); return NULL; } mainline->children = tokens; return lines; } static gib_tree_t * GIB_Semantic_Process_Conditional (gib_tree_t *tokens) { gib_tree_t *lines = 0, *temp, *cur, *embedded; // Check for embeddeds in first token lines = GIB_Parse_Embedded (tokens); if (gib_parse_error) return 0; if (lines || tokens->children) tokens->flags |= TREE_A_EMBED; // Iterate through all tokens concatenated to the first for (cur = tokens->next; cur && cur->flags & TREE_A_CONCAT; cur = cur->next) { embedded = GIB_Parse_Embedded (cur); if (gib_parse_error) return 0; if (embedded) { for (temp = embedded; temp->next; temp = temp->next); temp->next = lines; lines = embedded; cur->flags |= TREE_A_EMBED; } else if (cur->children) cur->flags |= TREE_A_EMBED; } return lines; } static gib_tree_t * GIB_Semantic_If_To_Lines (gib_tree_t **tokens, const char *program, gib_tree_flags_t flags, gib_tree_t *end) { gib_tree_t *token = *tokens, *lines, *temp, **next = &lines, *conditional; gib_tree_t *a_block; // Sanity checking if (flags & TREE_L_EMBED) { GIB_Parse_Error ("if statements may not be used in embedded commands.", token->start); return 0; } if (!token->next || !token->next->next) { GIB_Parse_Error ("malformed if statement; not enough arguments", token->start); return 0; } for (a_block = token->next->next; a_block->flags & TREE_A_CONCAT; a_block = a_block->next) if (!a_block->next) { GIB_Parse_Error ("malformed if statement; not enough arguments", token->start); return 0; } if (a_block->delim != '{') { GIB_Parse_Error ("malformed if statement; second argument is not a program block", token->next->next->start); return 0; } if (a_block->next) { // Else statement? if (a_block->next->delim != ' ' || strcmp (a_block->next->str, "else") || (a_block->next->next && a_block->next->next->flags & TREE_A_CONCAT)) { GIB_Parse_Error ("malformed if statement; expected \"else\" for third argument", token->next->next->next->start); return 0; } if (!a_block->next->next) { GIB_Parse_Error ("malformed if statement; expected arguments after \"else\"", token->next->next->next->start); return 0; } } // Process embedded stuff on conditional argument lines = GIB_Semantic_Process_Conditional (token->next); if (gib_parse_error) return 0; if (lines) { for (temp = lines; temp->next; temp = temp->next); next = &temp->next; } // Create conditional instruction conditional = GIB_Tree_New (TREE_T_COND); conditional->children = token; conditional->start = token->start; conditional->end = token->next->end; conditional->str = program; conditional->flags = token->str[2] ? flags | TREE_L_NOT : flags; // Link it in *next = conditional; next = &conditional->next; // Move program block inline *next = a_block->children; a_block->children = 0; // Find end of program block for (temp = *next; temp->next; temp = temp->next); next = &temp->next; // Add jump to end point *next = GIB_Tree_New (TREE_T_JUMP); (*next)->jump = end; conditional->jump = *next; next = &(*next)->next; // Is there an else statement? if (a_block->next) { temp = a_block->next; token = a_block->next->next; // Is this a program block? if (token->delim == '{') { // Move program block inline *next = token->children; token->children = 0; *tokens = 0; } else { // Cut off part we don't use temp->next = 0; // Indicate to caller that more tokens remain *tokens = token; // String will get used again, make a personal copy conditional->str = strdup (program); } } else // No tokens left *tokens = 0; return lines; } static gib_tree_t * GIB_Semantic_While_To_Lines (gib_tree_t *tokens, const char *program, gib_tree_flags_t flags) { gib_tree_t *lines, **next = &lines, *temp, *conditional, *endp; gib_tree_t *a_block; // Sanity checking if (flags & TREE_L_EMBED) { GIB_Parse_Error ("while statements may not be used in embedded commands", tokens->start); return 0; } if (!tokens->next || !tokens->next->next) { GIB_Parse_Error ("malformed while statement; incorrect number of arguments", tokens->start); return 0; } for (a_block = tokens->next->next; a_block->flags & TREE_A_CONCAT; a_block = a_block->next) if (!a_block->next) { GIB_Parse_Error ("malformed while statement; incorrect number of arguments", tokens->start); return 0; } if (a_block->delim != '{') { GIB_Parse_Error ("malformed while statement; expected program block for second argument", a_block->start); return 0; } // Process embedded stuff on conditional argument lines = GIB_Semantic_Process_Conditional (tokens->next); if (gib_parse_error) return 0; if (lines) { for (temp = lines; temp->next; temp = temp->next); next = &temp->next; } // Create conditional instruction conditional = GIB_Tree_New (TREE_T_COND); conditional->children = tokens; conditional->start = tokens->start; conditional->end = tokens->next->end; conditional->str = program; conditional->flags = flags; // Link it in *next = conditional; next = &conditional->next; // Create end point (for 'break' commands) endp = GIB_Tree_New (TREE_T_LABEL); // Move program block inline *next = a_block->children; a_block->children = 0; // Find end of program block for (temp = *next; temp->next; temp = temp->next) { // Check for break or continue if (!temp->jump && temp->children) { if (!strcmp (temp->children->str, "break")) { temp->type = TREE_T_JUMP; temp->jump = endp; } else if (!strcmp (temp->children->str, "continue")) { temp->type = TREE_T_JUMP; temp->jump = lines; } } } next = &temp->next; // Add jump back to top of loop temp = GIB_Tree_New (TREE_T_JUMP); temp->jump = lines; *next = temp; next = &temp->next; // Attach landing point for 'break' instructions *next = endp; // This is our last instruction, set it as escape for loop conditional->jump = endp; return lines; } static gib_tree_t * GIB_Semantic_For_To_Lines (gib_tree_t *tokens, const char *program, gib_tree_flags_t flags) { gib_tree_t *lines = 0, **next = &lines, *temp, *endp, *forinst, *embedded; gib_tree_t *a_in, *a_block; // Do sanity checks if (flags & TREE_L_EMBED) { GIB_Parse_Error ("for statements may not be used in embedded commands.", tokens->start); return 0; } if (!tokens->next) { GIB_Parse_Error ("malformed for statement; not enough arguments.", tokens->start); return 0; } for (a_in = tokens->next; a_in->delim != ' ' || strcmp (a_in->str, "in"); a_in = a_in->next) { if (!a_in->next) { GIB_Parse_Error ("malformed for statement; expected \"in\".", tokens->start); return 0; } if (a_in->flags & TREE_A_EMBED || a_in->flags & TREE_A_EXPAND) a_in->flags &= ~(TREE_A_EMBED | TREE_A_EXPAND); } if (!a_in->next) { GIB_Parse_Error ("malformed for statement; expected arguments after \"in\".", a_in->start); return 0; } for (a_block = a_in->next; a_block->next; a_block = a_block->next); if (a_block->delim != '{') { GIB_Parse_Error ("malformed for statement; expected program block as last argument.", a_block->start); return 0; } for (a_in = a_in->next; a_in != a_block; a_in = a_in->next) { embedded = GIB_Parse_Embedded (a_in); if (gib_parse_error) return 0; if (embedded) { for (temp = embedded; temp->next; temp = temp->next); temp->next = lines; lines = embedded; a_in->flags |= TREE_A_EMBED; } else if (a_in->children) a_in->flags |= TREE_A_EMBED; } if (lines) { for (temp = lines; temp->next; temp = temp->next); next = &temp->next; } forinst = GIB_Tree_New (TREE_T_CMD); forinst->children = tokens; forinst->start = tokens->start; forinst->end = a_block->start; forinst->str = program; forinst->flags = flags; *next = forinst; forinst->next = GIB_Tree_New (TREE_T_FORNEXT); forinst = forinst->next; next = &forinst->next; *next = a_block->children; a_block->children = 0; endp = GIB_Tree_New (TREE_T_LABEL); // Find end of program block for (temp = *next; temp->next; temp = temp->next) { // Check for break or continue if (!temp->jump && temp->children) { if (!strcmp (temp->children->str, "break")) { temp->type = TREE_T_JUMP; temp->jump = endp; } else if (!strcmp (temp->children->str, "continue")) { temp->type = TREE_T_JUMP; temp->jump = forinst; } } } next = &temp->next; *next = GIB_Tree_New (TREE_T_JUMP); (*next)->jump = forinst; (*next)->next = endp; forinst->jump = endp; return lines; } static gib_tree_t * GIB_Semantic_Label_To_Lines (gib_tree_t *tokens, const char *program, gib_tree_flags_t flags) { gib_tree_t *line; char *name; line = GIB_Tree_New (TREE_T_LABEL); name = strdup (tokens->str); name[strlen(name)-1] = '\0'; line->str = name; line->flags = flags; GIB_Tree_Unref (&tokens); return line; } gib_tree_t * GIB_Semantic_Tokens_To_Lines (gib_tree_t *tokens, const char *program, gib_tree_flags_t flags, unsigned int start, unsigned int end) { gib_tree_t *lines, **next = &lines, *temp, *endp = 0; // If we are being weird, don't even bother trying to match special statements if (tokens->next && tokens->next->flags & TREE_A_CONCAT) return GIB_Semantic_Normal_To_Lines (tokens, program, flags, start, end); // Handle if statements if (!strcmp (tokens->str, "if") || !strcmp (tokens->str, "ifnot")) { // Create landing pad where all program blocks in // a chained if/else structure will jump to after // completing so that only one block is executed. endp = GIB_Tree_New (TREE_T_LABEL); do { // Link in output from if statement handler *next = GIB_Semantic_If_To_Lines (&tokens, program, flags, endp); if (gib_parse_error) goto ERROR; // Find end of output for (temp = *next; temp->next; temp = temp->next); next = &temp->next; } while (tokens && (!strcmp (tokens->str, "if") || !strcmp (tokens->str, "ifnot"))); } if (!tokens) // Yes, goto is evil. See if I care. goto DONE; if (!strcmp (tokens->str, "while")) *next = GIB_Semantic_While_To_Lines (tokens, program, flags); else if (!strcmp (tokens->str, "for")) *next = GIB_Semantic_For_To_Lines (tokens, program, flags); else if (tokens->str[strlen(tokens->str)-1] == ':' && !tokens->next) *next = GIB_Semantic_Label_To_Lines (tokens, program, flags); else *next = GIB_Semantic_Normal_To_Lines (tokens, program, flags, start, end); next = &(*next)->next; if (gib_parse_error) goto ERROR; DONE: // Connect landing pad, if one exists if (endp) *next = endp; return lines; ERROR: if (endp) GIB_Tree_Unref (&endp); return 0; }