mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-13 00:24:12 +00:00
687a0845b6
and probably has enough bugs to leave the Orkin man scratching his head, but it works and allows you to do neat things like write classes in GIB (amazing!) and subclass builtin classes (which are Object and Thread at the moment, Hash should be coming soon as a replacement for stem and leaf variables).
544 lines
15 KiB
C
544 lines
15 KiB
C
/*
|
|
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
|
|
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#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 colon and parent class, and "
|
|
"program block.", tokens->start);
|
|
return -1;
|
|
}
|
|
|
|
if (tokens->next->next->delim == ' ' && !strcmp
|
|
(tokens->next->next->str, ":")) {
|
|
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 if (!cmd->next || !cmd->next->next ||
|
|
cmd->next->next->delim != '{'
|
|
|| !cmd->next->next->children) {
|
|
GIB_Parse_Error ("Malformed function "
|
|
"definition; name, "
|
|
"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;
|
|
}
|