mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-27 14:42:07 +00:00
888192a9ea
It turns out to be useful still as using symbol expressions isn't always appropriate and the workarounds were getting nasty.
361 lines
8.6 KiB
C
361 lines
8.6 KiB
C
/*
|
|
expr_assign.c
|
|
|
|
assignment expression construction and manipulations
|
|
|
|
Copyright (C) 2001 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2001/06/15
|
|
|
|
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
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
|
|
#include "QF/alloc.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/mathlib.h"
|
|
#include "QF/sys.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "qfcc.h"
|
|
#include "class.h"
|
|
#include "def.h"
|
|
#include "defspace.h"
|
|
#include "diagnostic.h"
|
|
#include "emit.h"
|
|
#include "expr.h"
|
|
#include "function.h"
|
|
#include "idstuff.h"
|
|
#include "method.h"
|
|
#include "options.h"
|
|
#include "reloc.h"
|
|
#include "shared.h"
|
|
#include "strpool.h"
|
|
#include "struct.h"
|
|
#include "symtab.h"
|
|
#include "type.h"
|
|
#include "value.h"
|
|
#include "qc-parse.h"
|
|
|
|
static expr_t *
|
|
check_assign_logic_precedence (expr_t *dst, expr_t *src)
|
|
{
|
|
if (src->type == ex_expr && !src->paren && is_logic (src->e.expr.op)) {
|
|
// traditional QuakeC gives = higher precedence than && and ||
|
|
expr_t *assignment;
|
|
notice (src, "precedence of `=' and `%s' inverted for "
|
|
"traditional code", get_op_string (src->e.expr.op));
|
|
// change {a = (b logic c)} to {(a = b) logic c}
|
|
assignment = assign_expr (dst, src->e.expr.e1);
|
|
assignment->paren = 1; // protect assignment from binary_expr
|
|
return binary_expr (src->e.expr.op, assignment, src->e.expr.e2);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
is_lvalue (const expr_t *expr)
|
|
{
|
|
switch (expr->type) {
|
|
case ex_def:
|
|
return !expr->e.def->constant;
|
|
case ex_symbol:
|
|
switch (expr->e.symbol->sy_type) {
|
|
case sy_name:
|
|
break;
|
|
case sy_var:
|
|
return 1;
|
|
case sy_const:
|
|
break;
|
|
case sy_type:
|
|
break;
|
|
case sy_expr:
|
|
break;
|
|
case sy_func:
|
|
break;
|
|
case sy_class:
|
|
break;
|
|
case sy_convert:
|
|
break;
|
|
}
|
|
break;
|
|
case ex_temp:
|
|
return 1;
|
|
case ex_expr:
|
|
if (expr->e.expr.op == '.') {
|
|
return 1;
|
|
}
|
|
if (expr->e.expr.op == 'A') {
|
|
return is_lvalue (expr->e.expr.e1);
|
|
}
|
|
break;
|
|
case ex_uexpr:
|
|
if (expr->e.expr.op == '.') {
|
|
return 1;
|
|
}
|
|
if (expr->e.expr.op == 'A') {
|
|
return is_lvalue (expr->e.expr.e1);
|
|
}
|
|
break;
|
|
case ex_memset:
|
|
case ex_compound:
|
|
case ex_state:
|
|
case ex_bool:
|
|
case ex_label:
|
|
case ex_labelref:
|
|
case ex_block:
|
|
case ex_vector:
|
|
case ex_nil:
|
|
case ex_value:
|
|
case ex_error:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static expr_t *
|
|
check_valid_lvalue (expr_t *expr)
|
|
{
|
|
if (!is_lvalue (expr)) {
|
|
if (options.traditional) {
|
|
warning (expr, "invalid lvalue in assignment");
|
|
return 0;
|
|
}
|
|
return error (expr, "invalid lvalue in assignment");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static expr_t *
|
|
check_types_compatible (expr_t *dst, expr_t *src)
|
|
{
|
|
type_t *dst_type = get_type (dst);
|
|
type_t *src_type = get_type (src);
|
|
|
|
if (dst_type == src_type) {
|
|
return 0;
|
|
}
|
|
|
|
if (type_assignable (dst_type, src_type)) {
|
|
if (is_scalar (dst_type) && is_scalar (src_type)) {
|
|
if (!src->implicit) {
|
|
if (is_double (src_type)) {
|
|
warning (dst, "assignment of double to %s (use a cast)\n",
|
|
dst_type->name);
|
|
}
|
|
}
|
|
// the types are different but cast-compatible
|
|
expr_t *new = cast_expr (dst_type, src);
|
|
// the cast was a no-op, so the types are compatible at the
|
|
// low level (very true for default type <-> enum)
|
|
if (new != src) {
|
|
return assign_expr (dst, new);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
// traditional qcc is a little sloppy
|
|
if (!options.traditional) {
|
|
return type_mismatch (dst, src, '=');
|
|
}
|
|
if (is_func (dst_type) && is_func (src_type)) {
|
|
warning (dst, "assignment between disparate function types");
|
|
return 0;
|
|
}
|
|
if (is_float (dst_type) && is_vector (src_type)) {
|
|
warning (dst, "assignment of vector to float");
|
|
src = field_expr (src, new_name_expr ("x"));
|
|
return assign_expr (dst, src);
|
|
}
|
|
if (is_vector (dst_type) && is_float (src_type)) {
|
|
warning (dst, "assignment of float to vector");
|
|
dst = field_expr (dst, new_name_expr ("x"));
|
|
return assign_expr (dst, src);
|
|
}
|
|
return type_mismatch (dst, src, '=');
|
|
}
|
|
|
|
static expr_t *
|
|
assign_vector_expr (expr_t *dst, expr_t *src)
|
|
{
|
|
expr_t *dx, *sx;
|
|
expr_t *dy, *sy;
|
|
expr_t *dz, *sz;
|
|
expr_t *dw, *sw;
|
|
expr_t *ds, *ss;
|
|
expr_t *dv, *sv;
|
|
expr_t *block;
|
|
|
|
if (src->type == ex_vector) {
|
|
src = convert_vector (src);
|
|
if (src->type != ex_vector) {
|
|
// src was constant and thus converted
|
|
return assign_expr (dst, src);
|
|
}
|
|
}
|
|
if (src->type == ex_vector && dst->type != ex_vector) {
|
|
if (src->e.vector.type == &type_vector) {
|
|
// guaranteed to have three elements
|
|
sx = src->e.vector.list;
|
|
sy = sx->next;
|
|
sz = sy->next;
|
|
dx = field_expr (dst, new_name_expr ("x"));
|
|
dy = field_expr (dst, new_name_expr ("y"));
|
|
dz = field_expr (dst, new_name_expr ("z"));
|
|
block = new_block_expr ();
|
|
append_expr (block, assign_expr (dx, sx));
|
|
append_expr (block, assign_expr (dy, sy));
|
|
append_expr (block, assign_expr (dz, sz));
|
|
block->e.block.result = dst;
|
|
return block;
|
|
}
|
|
if (src->e.vector.type == &type_quaternion) {
|
|
// guaranteed to have two or four elements
|
|
if (src->e.vector.list->next->next) {
|
|
// four vals: x, y, z, w
|
|
sx = src->e.vector.list;
|
|
sy = sx->next;
|
|
sz = sy->next;
|
|
sw = sz->next;
|
|
dx = field_expr (dst, new_name_expr ("x"));
|
|
dy = field_expr (dst, new_name_expr ("y"));
|
|
dz = field_expr (dst, new_name_expr ("z"));
|
|
dw = field_expr (dst, new_name_expr ("w"));
|
|
block = new_block_expr ();
|
|
append_expr (block, assign_expr (dx, sx));
|
|
append_expr (block, assign_expr (dy, sy));
|
|
append_expr (block, assign_expr (dz, sz));
|
|
append_expr (block, assign_expr (dw, sw));
|
|
block->e.block.result = dst;
|
|
return block;
|
|
} else {
|
|
// v, s
|
|
sv = src->e.vector.list;
|
|
ss = sv->next;
|
|
dv = field_expr (dst, new_name_expr ("v"));
|
|
ds = field_expr (dst, new_name_expr ("s"));
|
|
block = new_block_expr ();
|
|
append_expr (block, assign_expr (dv, sv));
|
|
append_expr (block, assign_expr (ds, ss));
|
|
block->e.block.result = dst;
|
|
return block;
|
|
}
|
|
}
|
|
internal_error (src, "bogus vector expression");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static __attribute__((pure)) int
|
|
is_memset (expr_t *e)
|
|
{
|
|
return e->type == ex_memset;
|
|
}
|
|
|
|
expr_t *
|
|
assign_expr (expr_t *dst, expr_t *src)
|
|
{
|
|
int op = '=';
|
|
expr_t *expr;
|
|
type_t *dst_type, *src_type;
|
|
|
|
convert_name (dst);
|
|
if (dst->type == ex_error) {
|
|
return dst;
|
|
}
|
|
if ((expr = check_valid_lvalue (dst))) {
|
|
return expr;
|
|
}
|
|
dst_type = get_type (dst);
|
|
if (!dst_type) {
|
|
internal_error (dst, "dst_type broke in assign_expr");
|
|
}
|
|
|
|
if (src && !is_memset (src)) {
|
|
convert_name (src);
|
|
if (src->type == ex_error) {
|
|
return src;
|
|
}
|
|
|
|
if (options.traditional
|
|
&& (expr = check_assign_logic_precedence (dst, src))) {
|
|
return expr;
|
|
}
|
|
} else {
|
|
if (!src && is_scalar (dst_type)) {
|
|
return error (dst, "empty scalar initializer");
|
|
}
|
|
src = new_nil_expr ();
|
|
}
|
|
if (src->type == ex_compound) {
|
|
src = initialized_temp_expr (dst_type, src);
|
|
if (src->type == ex_error) {
|
|
return src;
|
|
}
|
|
}
|
|
src_type = get_type (src);
|
|
if (!src_type) {
|
|
internal_error (src, "src_type broke in assign_expr");
|
|
}
|
|
|
|
if (is_pointer (dst_type) && is_array (src_type)) {
|
|
// assigning an array to a pointer is the same as taking the address of
|
|
// the array but using the type of the array elements
|
|
src = address_expr (src, 0, src_type->t.fldptr.type);
|
|
src_type = get_type (src);
|
|
}
|
|
if (src->type == ex_bool) {
|
|
// boolean expressions are chains of tests, so extract the result
|
|
// of the tests
|
|
src = convert_from_bool (src, dst_type);
|
|
if (src->type == ex_error) {
|
|
return src;
|
|
}
|
|
src_type = get_type (src);
|
|
}
|
|
|
|
if (!is_nil (src)) {
|
|
if ((expr = check_types_compatible (dst, src))) {
|
|
// expr might be a valid expression, but if so,
|
|
// check_types_compatible will take care of everything
|
|
return expr;
|
|
}
|
|
if ((expr = assign_vector_expr (dst, src))) {
|
|
return expr;
|
|
}
|
|
} else {
|
|
convert_nil (src, dst_type);
|
|
}
|
|
|
|
expr = new_binary_expr (op, dst, src);
|
|
expr->e.expr.type = dst_type;
|
|
return expr;
|
|
}
|