quakeforge/libs/gib/gib_semantics.c
Brian Koropoff 687a0845b6 Introducing the GIB Object System (TM). This breaks scriptable HUD for now
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).
2003-09-11 06:03:13 +00:00

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;
}