mirror of
https://github.com/ganelson/inform.git
synced 2024-07-16 22:14:23 +03:00
2134 lines
76 KiB
C
Executable file
2134 lines
76 KiB
C
Executable file
/* ------------------------------------------------------------------------- */
|
|
/* "expressp" : The expression parser */
|
|
/* */
|
|
/* Part of Inform 6.41 */
|
|
/* copyright (c) Graham Nelson 1993 - 2022 */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
#include "header.h"
|
|
|
|
/* --- Interface to lexer -------------------------------------------------- */
|
|
|
|
static char separators_to_operators[103];
|
|
static char conditionals_to_operators[7];
|
|
static char token_type_allowable[301];
|
|
|
|
#define NOT_AN_OPERATOR (char) 0x7e
|
|
|
|
static void make_lexical_interface_tables(void)
|
|
{ int i;
|
|
for (i=0;i<103;i++)
|
|
separators_to_operators[i] = NOT_AN_OPERATOR;
|
|
for (i=0;i<NUM_OPERATORS;i++)
|
|
if (operators[i].token_type == SEP_TT)
|
|
separators_to_operators[operators[i].token_value] = i;
|
|
|
|
for (i=0;i<7;i++) /* 7 being the size of keyword_group "conditions" */
|
|
conditionals_to_operators[i] = NOT_AN_OPERATOR;
|
|
for (i=0;i<NUM_OPERATORS;i++)
|
|
if (operators[i].token_type == CND_TT)
|
|
conditionals_to_operators[operators[i].token_value] = i;
|
|
|
|
for (i=0;i<301;i++) token_type_allowable[i] = 0;
|
|
|
|
token_type_allowable[VARIABLE_TT] = 1;
|
|
token_type_allowable[SYSFUN_TT] = 1;
|
|
token_type_allowable[DQ_TT] = 1;
|
|
token_type_allowable[DICTWORD_TT] = 1;
|
|
token_type_allowable[SUBOPEN_TT] = 1;
|
|
token_type_allowable[SUBCLOSE_TT] = 1;
|
|
token_type_allowable[SMALL_NUMBER_TT] = 1;
|
|
token_type_allowable[LARGE_NUMBER_TT] = 1;
|
|
token_type_allowable[ACTION_TT] = 1;
|
|
token_type_allowable[SYSTEM_CONSTANT_TT] = 1;
|
|
token_type_allowable[OP_TT] = 1;
|
|
}
|
|
|
|
static token_data current_token, previous_token, heldback_token;
|
|
|
|
static int comma_allowed, arrow_allowed, superclass_allowed,
|
|
bare_prop_allowed,
|
|
array_init_ambiguity, action_ambiguity,
|
|
etoken_count, inserting_token, bracket_level;
|
|
|
|
int system_function_usage[NUMBER_SYSTEM_FUNCTIONS];
|
|
|
|
static int get_next_etoken(void)
|
|
{ int v, symbol = 0, mark_symbol_as_used = FALSE,
|
|
initial_bracket_level = bracket_level;
|
|
|
|
etoken_count++;
|
|
|
|
if (inserting_token)
|
|
{ current_token = heldback_token;
|
|
inserting_token = FALSE;
|
|
}
|
|
else
|
|
{ get_next_token();
|
|
current_token.text = token_text;
|
|
current_token.value = token_value;
|
|
current_token.type = token_type;
|
|
current_token.marker = 0;
|
|
current_token.symindex = -1;
|
|
current_token.symtype = 0;
|
|
current_token.symflags = -1;
|
|
}
|
|
|
|
switch(current_token.type)
|
|
{ case LOCAL_VARIABLE_TT:
|
|
current_token.type = VARIABLE_TT;
|
|
variables[current_token.value].usage = TRUE;
|
|
break;
|
|
|
|
case DQ_TT:
|
|
current_token.marker = STRING_MV;
|
|
break;
|
|
|
|
case SQ_TT:
|
|
{ int32 unicode = text_to_unicode(token_text);
|
|
if (token_text[textual_form_length] == 0)
|
|
{
|
|
if (!glulx_mode) {
|
|
current_token.value = unicode_to_zscii(unicode);
|
|
if (current_token.value == 5)
|
|
{ unicode_char_error("Character can be printed \
|
|
but not used as a value:", unicode);
|
|
current_token.value = '?';
|
|
}
|
|
if (current_token.value >= 0x100)
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
else {
|
|
current_token.value = unicode;
|
|
if (current_token.value >= 0x8000
|
|
|| current_token.value < -0x8000)
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
}
|
|
else
|
|
{ current_token.type = DICTWORD_TT;
|
|
current_token.marker = DWORD_MV;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SYMBOL_TT:
|
|
ReceiveSymbol:
|
|
symbol = current_token.value;
|
|
|
|
mark_symbol_as_used = TRUE;
|
|
|
|
v = symbols[symbol].value;
|
|
|
|
current_token.symindex = symbol;
|
|
current_token.symtype = symbols[symbol].type;
|
|
current_token.symflags = symbols[symbol].flags;
|
|
switch(symbols[symbol].type)
|
|
{ case ROUTINE_T:
|
|
/* Replaced functions must always be backpatched
|
|
because there could be another definition coming. */
|
|
if (symbols[symbol].flags & REPLACE_SFLAG)
|
|
{ current_token.marker = SYMBOL_MV;
|
|
v = symbol;
|
|
break;
|
|
}
|
|
current_token.marker = IROUTINE_MV;
|
|
break;
|
|
case GLOBAL_VARIABLE_T:
|
|
current_token.marker = VARIABLE_MV;
|
|
break;
|
|
case OBJECT_T:
|
|
case CLASS_T:
|
|
/* All objects must be backpatched in Glulx. */
|
|
if (glulx_mode)
|
|
current_token.marker = OBJECT_MV;
|
|
break;
|
|
case ARRAY_T:
|
|
current_token.marker = ARRAY_MV;
|
|
break;
|
|
case STATIC_ARRAY_T:
|
|
current_token.marker = STATIC_ARRAY_MV;
|
|
break;
|
|
case INDIVIDUAL_PROPERTY_T:
|
|
break;
|
|
case CONSTANT_T:
|
|
if (symbols[symbol].flags & (UNKNOWN_SFLAG + CHANGE_SFLAG))
|
|
{ current_token.marker = SYMBOL_MV;
|
|
v = symbol;
|
|
}
|
|
else current_token.marker = 0;
|
|
break;
|
|
case LABEL_T:
|
|
error_named("Label name used as value:", token_text);
|
|
break;
|
|
default:
|
|
current_token.marker = 0;
|
|
break;
|
|
}
|
|
if (symbols[symbol].flags & SYSTEM_SFLAG)
|
|
current_token.marker = 0;
|
|
|
|
current_token.value = v;
|
|
|
|
if (!glulx_mode) {
|
|
if (((current_token.marker != 0)
|
|
&& (current_token.marker != VARIABLE_MV))
|
|
|| (v < 0) || (v > 255))
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
else {
|
|
if (((current_token.marker != 0)
|
|
&& (current_token.marker != VARIABLE_MV))
|
|
|| (v < -0x8000) || (v >= 0x8000))
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
|
|
if (symbols[symbol].type == GLOBAL_VARIABLE_T)
|
|
{ current_token.type = VARIABLE_TT;
|
|
variables[current_token.value].usage = TRUE;
|
|
}
|
|
break;
|
|
|
|
case NUMBER_TT:
|
|
if (!glulx_mode) {
|
|
if (current_token.value >= 256)
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else
|
|
current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
else {
|
|
if (current_token.value < -0x8000
|
|
|| current_token.value >= 0x8000)
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else
|
|
current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
break;
|
|
|
|
case SEP_TT:
|
|
switch(current_token.value)
|
|
{ case ARROW_SEP:
|
|
if (!arrow_allowed)
|
|
current_token.type = ENDEXP_TT;
|
|
break;
|
|
|
|
case COMMA_SEP:
|
|
if ((bracket_level==0) && (!comma_allowed))
|
|
current_token.type = ENDEXP_TT;
|
|
break;
|
|
|
|
case SUPERCLASS_SEP:
|
|
if ((bracket_level==0) && (!superclass_allowed))
|
|
current_token.type = ENDEXP_TT;
|
|
break;
|
|
|
|
case GREATER_SEP:
|
|
get_next_token();
|
|
if ((token_type == SEP_TT)
|
|
&&((token_value == SEMICOLON_SEP)
|
|
|| (token_value == GREATER_SEP)))
|
|
current_token.type = ENDEXP_TT;
|
|
put_token_back();
|
|
break;
|
|
|
|
case OPENB_SEP:
|
|
bracket_level++;
|
|
if (expr_trace_level>=3)
|
|
{ printf("Previous token type = %d\n",previous_token.type);
|
|
printf("Previous token val = %d\n",previous_token.value);
|
|
}
|
|
if ((previous_token.type == OP_TT)
|
|
|| (previous_token.type == SUBOPEN_TT)
|
|
|| (previous_token.type == ENDEXP_TT)
|
|
|| (array_init_ambiguity)
|
|
|| ((bracket_level == 1) && (action_ambiguity)))
|
|
current_token.type = SUBOPEN_TT;
|
|
else
|
|
{ inserting_token = TRUE;
|
|
heldback_token = current_token;
|
|
current_token.text = "<call>";
|
|
bracket_level--;
|
|
}
|
|
break;
|
|
|
|
case CLOSEB_SEP:
|
|
bracket_level--;
|
|
if (bracket_level < 0)
|
|
current_token.type = ENDEXP_TT;
|
|
else current_token.type = SUBCLOSE_TT;
|
|
break;
|
|
|
|
case SEMICOLON_SEP:
|
|
current_token.type = ENDEXP_TT; break;
|
|
|
|
case MINUS_SEP:
|
|
if ((previous_token.type == OP_TT)
|
|
|| (previous_token.type == SUBOPEN_TT)
|
|
|| (previous_token.type == ENDEXP_TT))
|
|
current_token.value = UNARY_MINUS_SEP; break;
|
|
|
|
case INC_SEP:
|
|
if ((previous_token.type == VARIABLE_TT)
|
|
|| (previous_token.type == SUBCLOSE_TT)
|
|
|| (previous_token.type == LARGE_NUMBER_TT)
|
|
|| (previous_token.type == SMALL_NUMBER_TT))
|
|
current_token.value = POST_INC_SEP; break;
|
|
|
|
case DEC_SEP:
|
|
if ((previous_token.type == VARIABLE_TT)
|
|
|| (previous_token.type == SUBCLOSE_TT)
|
|
|| (previous_token.type == LARGE_NUMBER_TT)
|
|
|| (previous_token.type == SMALL_NUMBER_TT))
|
|
current_token.value = POST_DEC_SEP; break;
|
|
|
|
case HASHHASH_SEP:
|
|
token_text = current_token.text + 2;
|
|
|
|
ActionUsedAsConstant:
|
|
|
|
current_token.type = ACTION_TT;
|
|
current_token.text = token_text;
|
|
current_token.value = 0;
|
|
current_token.marker = ACTION_MV;
|
|
|
|
break;
|
|
|
|
case HASHADOLLAR_SEP:
|
|
obsolete_warning("'#a$Act' is now superseded by '##Act'");
|
|
token_text = current_token.text + 3;
|
|
goto ActionUsedAsConstant;
|
|
|
|
case HASHGDOLLAR_SEP:
|
|
|
|
/* This form generates the position of a global variable
|
|
in the global variables array. So Glob is the same as
|
|
#globals_array --> #g$Glob */
|
|
|
|
current_token.text += 3;
|
|
current_token.type = SYMBOL_TT;
|
|
symbol = symbol_index(current_token.text, -1);
|
|
if (symbols[symbol].type != GLOBAL_VARIABLE_T) {
|
|
ebf_error(
|
|
"global variable name after '#g$'",
|
|
current_token.text);
|
|
current_token.value = 0;
|
|
current_token.type = SMALL_NUMBER_TT;
|
|
current_token.marker = 0;
|
|
break;
|
|
}
|
|
mark_symbol_as_used = TRUE;
|
|
current_token.value = symbols[symbol].value - MAX_LOCAL_VARIABLES;
|
|
current_token.marker = 0;
|
|
if (!glulx_mode) {
|
|
if (current_token.value >= 0x100)
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
else {
|
|
if (current_token.value >= 0x8000
|
|
|| current_token.value < -0x8000)
|
|
current_token.type = LARGE_NUMBER_TT;
|
|
else current_token.type = SMALL_NUMBER_TT;
|
|
}
|
|
break;
|
|
|
|
case HASHNDOLLAR_SEP:
|
|
|
|
/* This form is still needed for constants like #n$a (the
|
|
dictionary address of the word "a"), since 'a' means
|
|
the ASCII value of 'a' */
|
|
|
|
if (strlen(token_text) > 4)
|
|
obsolete_warning(
|
|
"'#n$word' is now superseded by ''word''");
|
|
current_token.type = DICTWORD_TT;
|
|
current_token.value = 0;
|
|
current_token.text = token_text + 3;
|
|
current_token.marker = DWORD_MV;
|
|
break;
|
|
|
|
case HASHRDOLLAR_SEP:
|
|
|
|
/* This form -- #r$Routinename, to return the routine's */
|
|
/* packed address -- is needed far less often in Inform 6, */
|
|
/* where just giving the name Routine returns the packed */
|
|
/* address. But it's used in a lot of Inform 5 code. */
|
|
|
|
obsolete_warning(
|
|
"'#r$Routine' can now be written just 'Routine'");
|
|
current_token.text += 3;
|
|
current_token.type = SYMBOL_TT;
|
|
current_token.value = symbol_index(current_token.text, -1);
|
|
goto ReceiveSymbol;
|
|
|
|
case HASHWDOLLAR_SEP:
|
|
error("The obsolete '#w$word' construct has been removed");
|
|
break;
|
|
|
|
case HASH_SEP:
|
|
system_constants.enabled = TRUE;
|
|
get_next_token();
|
|
system_constants.enabled = FALSE;
|
|
if (token_type != SYSTEM_CONSTANT_TT)
|
|
{ ebf_error(
|
|
"'r$', 'n$', 'g$' or internal Inform constant name after '#'",
|
|
token_text);
|
|
break;
|
|
}
|
|
else
|
|
{ current_token.type = token_type;
|
|
current_token.value = token_value;
|
|
current_token.text = token_text;
|
|
current_token.marker = INCON_MV;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CND_TT:
|
|
v = conditionals_to_operators[current_token.value];
|
|
if (v != NOT_AN_OPERATOR)
|
|
{ current_token.type = OP_TT; current_token.value = v;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (current_token.type == SEP_TT)
|
|
{ v = separators_to_operators[current_token.value];
|
|
if (v != NOT_AN_OPERATOR)
|
|
{ if ((veneer_mode)
|
|
|| ((v!=MESSAGE_OP) && (v!=MPROP_NUM_OP) && (v!=MPROP_NUM_OP)))
|
|
{ current_token.type = OP_TT; current_token.value = v;
|
|
if (array_init_ambiguity &&
|
|
((v==MINUS_OP) || (v==UNARY_MINUS_OP)) &&
|
|
(initial_bracket_level == 0) &&
|
|
(etoken_count != 1))
|
|
warning("Without bracketing, the minus sign '-' is ambiguous");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* A feature of Inform making it annoyingly hard to parse left-to-right
|
|
is that there is no clear delimiter for expressions; that is, the
|
|
legal syntax often includes sequences of expressions with no
|
|
intervening markers such as commas. We therefore need to use some
|
|
internal context to determine whether an end is in sight... */
|
|
|
|
if (token_type_allowable[current_token.type]==0)
|
|
{ if (expr_trace_level >= 3)
|
|
{ printf("Discarding as not allowable: '%s' ", current_token.text);
|
|
describe_token(¤t_token);
|
|
printf("\n");
|
|
}
|
|
current_token.type = ENDEXP_TT;
|
|
}
|
|
else
|
|
if ((!((initial_bracket_level > 0)
|
|
|| (previous_token.type == ENDEXP_TT)
|
|
|| ((previous_token.type == OP_TT)
|
|
&& (operators[previous_token.value].usage != POST_U))
|
|
|| (previous_token.type == SYSFUN_TT)))
|
|
&& ((current_token.type != OP_TT)
|
|
|| (operators[current_token.value].usage == PRE_U)))
|
|
{ if (expr_trace_level >= 3)
|
|
{ printf("Discarding as no longer part: '%s' ", current_token.text);
|
|
describe_token(¤t_token);
|
|
printf("\n");
|
|
}
|
|
current_token.type = ENDEXP_TT;
|
|
}
|
|
else
|
|
{ if (mark_symbol_as_used) symbols[symbol].flags |= USED_SFLAG;
|
|
if (expr_trace_level >= 3)
|
|
{ printf("Expr token = '%s' ", current_token.text);
|
|
describe_token(¤t_token);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
if ((previous_token.type == ENDEXP_TT)
|
|
&& (current_token.type == ENDEXP_TT)) return FALSE;
|
|
|
|
previous_token = current_token;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* --- Operator precedences ------------------------------------------------ */
|
|
|
|
#define LOWER_P 101
|
|
#define EQUAL_P 102
|
|
#define GREATER_P 103
|
|
|
|
#define e1 1 /* Missing operand error */
|
|
#define e2 2 /* Unexpected close bracket */
|
|
#define e3 3 /* Missing operator error */
|
|
#define e4 4 /* Expression ends with an open bracket */
|
|
#define e5 5 /* Associativity illegal error */
|
|
|
|
const int prec_table[] = {
|
|
|
|
/* a .......... ( ) end op term */
|
|
|
|
/* b ( */ LOWER_P, e3, LOWER_P, LOWER_P, e3,
|
|
/* . ) */ EQUAL_P, GREATER_P, e2, GREATER_P, GREATER_P,
|
|
/* . end */ e4, GREATER_P, e1, GREATER_P, GREATER_P,
|
|
/* . op */ LOWER_P, GREATER_P, LOWER_P, -1, GREATER_P,
|
|
/* . term */ LOWER_P, e3, LOWER_P, LOWER_P, e3
|
|
|
|
};
|
|
|
|
static int find_prec(const token_data *a, const token_data *b)
|
|
{
|
|
/* We are comparing the precedence of tokens a and b
|
|
(where a occurs to the left of b). If the expression is correct,
|
|
the only possible values are GREATER_P, LOWER_P or EQUAL_P;
|
|
if it is malformed then one of e1 to e5 results.
|
|
|
|
Note that this routine is not symmetrical and that the relation
|
|
is not trichotomous.
|
|
|
|
If a and b are equal (and aren't brackets), then
|
|
|
|
a LOWER_P a if a right-associative
|
|
a GREATER_P a if a left-associative
|
|
*/
|
|
|
|
int i, j, l1, l2;
|
|
|
|
switch(a->type)
|
|
{ case SUBOPEN_TT: i=0; break;
|
|
case SUBCLOSE_TT: i=1; break;
|
|
case ENDEXP_TT: i=2; break;
|
|
case OP_TT: i=3; break;
|
|
default: i=4; break;
|
|
}
|
|
switch(b->type)
|
|
{ case SUBOPEN_TT: i+=0; break;
|
|
case SUBCLOSE_TT: i+=5; break;
|
|
case ENDEXP_TT: i+=10; break;
|
|
case OP_TT: i+=15; break;
|
|
default: i+=20; break;
|
|
}
|
|
|
|
j = prec_table[i]; if (j != -1) return j;
|
|
|
|
l1 = operators[a->value].precedence;
|
|
l2 = operators[b->value].precedence;
|
|
if (operators[b->value].usage == PRE_U) return LOWER_P;
|
|
if (operators[a->value].usage == POST_U) return GREATER_P;
|
|
|
|
/* Anomalous rule to resolve the function call precedence, which is
|
|
different on the right from on the left, e.g., in:
|
|
|
|
alpha.beta(gamma)
|
|
beta(gamma).alpha
|
|
*/
|
|
|
|
if ((l1 == 11) && (l2 > 11)) return GREATER_P;
|
|
|
|
if (l1 < l2) return LOWER_P;
|
|
if (l1 > l2) return GREATER_P;
|
|
switch(operators[a->value].associativity)
|
|
{ case L_A: return GREATER_P;
|
|
case R_A: return LOWER_P;
|
|
case 0: return e5;
|
|
}
|
|
return GREATER_P;
|
|
}
|
|
|
|
/* --- Converting token to operand ----------------------------------------- */
|
|
|
|
/* List used to generate gameinfo.dbg.
|
|
Must match the switch statement below. */
|
|
int z_system_constant_list[] =
|
|
{ adjectives_table_SC,
|
|
actions_table_SC,
|
|
classes_table_SC,
|
|
identifiers_table_SC,
|
|
preactions_table_SC,
|
|
largest_object_SC,
|
|
strings_offset_SC,
|
|
code_offset_SC,
|
|
actual_largest_object_SC,
|
|
static_memory_offset_SC,
|
|
array_names_offset_SC,
|
|
readable_memory_offset_SC,
|
|
cpv__start_SC,
|
|
cpv__end_SC,
|
|
ipv__start_SC,
|
|
ipv__end_SC,
|
|
array__start_SC,
|
|
array__end_SC,
|
|
highest_attribute_number_SC,
|
|
attribute_names_array_SC,
|
|
highest_property_number_SC,
|
|
property_names_array_SC,
|
|
highest_action_number_SC,
|
|
action_names_array_SC,
|
|
highest_fake_action_number_SC,
|
|
fake_action_names_array_SC,
|
|
highest_routine_number_SC,
|
|
routine_names_array_SC,
|
|
routines_array_SC,
|
|
routine_flags_array_SC,
|
|
highest_global_number_SC,
|
|
global_names_array_SC,
|
|
globals_array_SC,
|
|
global_flags_array_SC,
|
|
highest_array_number_SC,
|
|
array_names_array_SC,
|
|
array_flags_array_SC,
|
|
highest_constant_number_SC,
|
|
constant_names_array_SC,
|
|
highest_class_number_SC,
|
|
class_objects_array_SC,
|
|
highest_object_number_SC,
|
|
dictionary_table_SC,
|
|
grammar_table_SC,
|
|
-1 };
|
|
|
|
static int32 value_of_system_constant_z(int t)
|
|
{ switch(t)
|
|
{ case adjectives_table_SC:
|
|
return adjectives_offset;
|
|
case actions_table_SC:
|
|
return actions_offset;
|
|
case classes_table_SC:
|
|
return class_numbers_offset;
|
|
case identifiers_table_SC:
|
|
return identifier_names_offset;
|
|
case preactions_table_SC:
|
|
return preactions_offset;
|
|
case largest_object_SC:
|
|
return 256 + no_objects - 1;
|
|
case strings_offset_SC:
|
|
return strings_offset/scale_factor;
|
|
case code_offset_SC:
|
|
return code_offset/scale_factor;
|
|
case actual_largest_object_SC:
|
|
return no_objects;
|
|
case static_memory_offset_SC:
|
|
return static_memory_offset;
|
|
case array_names_offset_SC:
|
|
return array_names_offset;
|
|
case readable_memory_offset_SC:
|
|
return Write_Code_At;
|
|
case cpv__start_SC:
|
|
return prop_values_offset;
|
|
case cpv__end_SC:
|
|
return class_numbers_offset;
|
|
case ipv__start_SC:
|
|
return individuals_offset;
|
|
case ipv__end_SC:
|
|
return variables_offset;
|
|
case array__start_SC:
|
|
return variables_offset + (MAX_ZCODE_GLOBAL_VARS*WORDSIZE);
|
|
case array__end_SC:
|
|
return static_memory_offset;
|
|
case dictionary_table_SC:
|
|
return dictionary_offset;
|
|
case grammar_table_SC:
|
|
return static_memory_offset;
|
|
|
|
case highest_attribute_number_SC:
|
|
return no_attributes-1;
|
|
case attribute_names_array_SC:
|
|
return attribute_names_offset;
|
|
|
|
case highest_property_number_SC:
|
|
return no_individual_properties-1;
|
|
case property_names_array_SC:
|
|
return identifier_names_offset + 2;
|
|
|
|
case highest_action_number_SC:
|
|
return no_actions-1;
|
|
case action_names_array_SC:
|
|
return action_names_offset;
|
|
|
|
case highest_fake_action_number_SC:
|
|
return ((grammar_version_number==1)?256:4096) + no_fake_actions-1;
|
|
case fake_action_names_array_SC:
|
|
return fake_action_names_offset;
|
|
|
|
case highest_routine_number_SC:
|
|
return no_named_routines-1;
|
|
case routine_names_array_SC:
|
|
return routine_names_offset;
|
|
case routines_array_SC:
|
|
return routines_array_offset;
|
|
case routine_flags_array_SC:
|
|
return routine_flags_array_offset;
|
|
case highest_global_number_SC:
|
|
return 16 + no_globals-1;
|
|
case global_names_array_SC:
|
|
return global_names_offset;
|
|
case globals_array_SC:
|
|
return variables_offset;
|
|
case global_flags_array_SC:
|
|
return global_flags_array_offset;
|
|
case highest_array_number_SC:
|
|
return no_arrays-1;
|
|
case array_names_array_SC:
|
|
return array_names_offset;
|
|
case array_flags_array_SC:
|
|
return array_flags_array_offset;
|
|
case highest_constant_number_SC:
|
|
return no_named_constants-1;
|
|
case constant_names_array_SC:
|
|
return constant_names_offset;
|
|
case highest_class_number_SC:
|
|
return no_classes-1;
|
|
case class_objects_array_SC:
|
|
return class_numbers_offset;
|
|
case highest_object_number_SC:
|
|
return no_objects-1;
|
|
}
|
|
|
|
error_named("System constant not implemented in Z-code",
|
|
system_constants.keywords[t]);
|
|
|
|
return(0);
|
|
}
|
|
|
|
/* List used to generate gameinfo.dbg.
|
|
Must match the switch statement below. */
|
|
int glulx_system_constant_list[] =
|
|
{ classes_table_SC,
|
|
identifiers_table_SC,
|
|
array_names_offset_SC,
|
|
cpv__start_SC,
|
|
cpv__end_SC,
|
|
dictionary_table_SC,
|
|
dynam_string_table_SC,
|
|
grammar_table_SC,
|
|
actions_table_SC,
|
|
globals_array_SC,
|
|
highest_class_number_SC,
|
|
highest_object_number_SC,
|
|
-1 };
|
|
|
|
static int32 value_of_system_constant_g(int t)
|
|
{
|
|
switch (t) {
|
|
case classes_table_SC:
|
|
return Write_RAM_At + class_numbers_offset;
|
|
case identifiers_table_SC:
|
|
return Write_RAM_At + identifier_names_offset;
|
|
case array_names_offset_SC:
|
|
return Write_RAM_At + array_names_offset;
|
|
case cpv__start_SC:
|
|
return prop_defaults_offset;
|
|
case cpv__end_SC:
|
|
return Write_RAM_At + class_numbers_offset;
|
|
case dictionary_table_SC:
|
|
return dictionary_offset;
|
|
case dynam_string_table_SC:
|
|
return abbreviations_offset;
|
|
case grammar_table_SC:
|
|
return grammar_table_offset;
|
|
case actions_table_SC:
|
|
return actions_offset;
|
|
case globals_array_SC:
|
|
return variables_offset;
|
|
case highest_class_number_SC:
|
|
return no_classes-1;
|
|
case highest_object_number_SC:
|
|
return no_objects-1;
|
|
}
|
|
|
|
error_named("System constant not implemented in Glulx",
|
|
system_constants.keywords[t]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern int32 value_of_system_constant(int t)
|
|
{
|
|
if (!glulx_mode)
|
|
return value_of_system_constant_z(t);
|
|
else
|
|
return value_of_system_constant_g(t);
|
|
}
|
|
|
|
extern char *name_of_system_constant(int t)
|
|
{
|
|
if (t < 0 || t >= NO_SYSTEM_CONSTANTS) {
|
|
return "???";
|
|
}
|
|
return system_constants.keywords[t];
|
|
}
|
|
|
|
static int evaluate_term(const token_data *t, assembly_operand *o)
|
|
{
|
|
/* If the given token is a constant, evaluate it into the operand.
|
|
For now, the identifiers are considered variables.
|
|
|
|
Returns FALSE if it fails to understand type. */
|
|
|
|
int32 v;
|
|
|
|
o->marker = t->marker;
|
|
o->symindex = t->symindex;
|
|
|
|
switch(t->type)
|
|
{ case LARGE_NUMBER_TT:
|
|
v = t->value;
|
|
if (!glulx_mode) {
|
|
if (v < 0) v = v + 0x10000;
|
|
o->type = LONG_CONSTANT_OT;
|
|
o->value = v;
|
|
}
|
|
else {
|
|
o->value = v;
|
|
o->type = CONSTANT_OT;
|
|
}
|
|
return(TRUE);
|
|
case SMALL_NUMBER_TT:
|
|
v = t->value;
|
|
if (!glulx_mode) {
|
|
if (v < 0) v = v + 0x10000;
|
|
o->type = SHORT_CONSTANT_OT;
|
|
o->value = v;
|
|
}
|
|
else {
|
|
o->value = v;
|
|
set_constant_ot(o);
|
|
}
|
|
return(TRUE);
|
|
case DICTWORD_TT:
|
|
/* Find the dictionary address, adding to dictionary if absent */
|
|
if (!glulx_mode)
|
|
o->type = LONG_CONSTANT_OT;
|
|
else
|
|
o->type = CONSTANT_OT;
|
|
o->value = dictionary_add(t->text, 0x80, 0, 0);
|
|
return(TRUE);
|
|
case DQ_TT:
|
|
/* Create as a static string */
|
|
if (!glulx_mode)
|
|
o->type = LONG_CONSTANT_OT;
|
|
else
|
|
o->type = CONSTANT_OT;
|
|
o->value = compile_string(t->text, STRCTX_GAME);
|
|
return(TRUE);
|
|
case VARIABLE_TT:
|
|
if (!glulx_mode) {
|
|
o->type = VARIABLE_OT;
|
|
}
|
|
else {
|
|
if (t->value >= MAX_LOCAL_VARIABLES) {
|
|
o->type = GLOBALVAR_OT;
|
|
}
|
|
else {
|
|
/* This includes "local variable zero", which is really
|
|
the stack-pointer magic variable. */
|
|
o->type = LOCALVAR_OT;
|
|
}
|
|
}
|
|
o->value = t->value;
|
|
return(TRUE);
|
|
case SYSFUN_TT:
|
|
if (!glulx_mode) {
|
|
o->type = VARIABLE_OT;
|
|
o->value = t->value + 256;
|
|
}
|
|
else {
|
|
o->type = SYSFUN_OT;
|
|
o->value = t->value;
|
|
}
|
|
system_function_usage[t->value] = 1;
|
|
return(TRUE);
|
|
case ACTION_TT:
|
|
*o = action_of_name(t->text);
|
|
return(TRUE);
|
|
case SYSTEM_CONSTANT_TT:
|
|
/* Certain system constants depend only on the
|
|
version number and need no backpatching, as they
|
|
are known in advance. We can therefore evaluate
|
|
them immediately. */
|
|
if (!glulx_mode) {
|
|
o->type = LONG_CONSTANT_OT;
|
|
switch(t->value)
|
|
{
|
|
case version_number_SC:
|
|
o->type = SHORT_CONSTANT_OT;
|
|
o->marker = 0;
|
|
v = version_number; break;
|
|
case dict_par1_SC:
|
|
o->type = SHORT_CONSTANT_OT;
|
|
o->marker = 0;
|
|
v = (version_number==3)?4:6; break;
|
|
case dict_par2_SC:
|
|
o->type = SHORT_CONSTANT_OT;
|
|
o->marker = 0;
|
|
v = (version_number==3)?5:7; break;
|
|
case dict_par3_SC:
|
|
o->type = SHORT_CONSTANT_OT;
|
|
o->marker = 0;
|
|
if (ZCODE_LESS_DICT_DATA)
|
|
error("#dict_par3 is unavailable when ZCODE_LESS_DICT_DATA is set");
|
|
v = (version_number==3)?6:8; break;
|
|
case lowest_attribute_number_SC:
|
|
case lowest_action_number_SC:
|
|
case lowest_routine_number_SC:
|
|
case lowest_array_number_SC:
|
|
case lowest_constant_number_SC:
|
|
case lowest_class_number_SC:
|
|
o->type = SHORT_CONSTANT_OT; o->marker = 0; v = 0; break;
|
|
case lowest_object_number_SC:
|
|
case lowest_property_number_SC:
|
|
o->type = SHORT_CONSTANT_OT; o->marker = 0; v = 1; break;
|
|
case lowest_global_number_SC:
|
|
o->type = SHORT_CONSTANT_OT; o->marker = 0; v = 16; break;
|
|
case lowest_fake_action_number_SC:
|
|
o->type = LONG_CONSTANT_OT; o->marker = 0;
|
|
v = ((grammar_version_number==1)?256:4096); break;
|
|
case oddeven_packing_SC:
|
|
o->type = SHORT_CONSTANT_OT; o->marker = 0;
|
|
v = oddeven_packing_switch; break;
|
|
default:
|
|
v = t->value;
|
|
o->marker = INCON_MV;
|
|
break;
|
|
}
|
|
o->value = v;
|
|
}
|
|
else {
|
|
o->type = CONSTANT_OT;
|
|
switch(t->value)
|
|
{
|
|
/* The three dict_par flags point at the lower byte
|
|
of the flag field, because the library is written
|
|
to expect one-byte fields, even though the compiler
|
|
generates a dictionary with room for two. */
|
|
case dict_par1_SC:
|
|
o->type = BYTECONSTANT_OT;
|
|
o->marker = 0;
|
|
v = DICT_ENTRY_FLAG_POS+1;
|
|
break;
|
|
case dict_par2_SC:
|
|
o->type = BYTECONSTANT_OT;
|
|
o->marker = 0;
|
|
v = DICT_ENTRY_FLAG_POS+3;
|
|
break;
|
|
case dict_par3_SC:
|
|
o->type = BYTECONSTANT_OT;
|
|
o->marker = 0;
|
|
v = DICT_ENTRY_FLAG_POS+5;
|
|
break;
|
|
|
|
case lowest_attribute_number_SC:
|
|
case lowest_action_number_SC:
|
|
case lowest_routine_number_SC:
|
|
case lowest_array_number_SC:
|
|
case lowest_constant_number_SC:
|
|
case lowest_class_number_SC:
|
|
o->type = BYTECONSTANT_OT;
|
|
o->marker = 0;
|
|
v = 0;
|
|
break;
|
|
case lowest_object_number_SC:
|
|
case lowest_property_number_SC:
|
|
o->type = BYTECONSTANT_OT;
|
|
o->marker = 0;
|
|
v = 1;
|
|
break;
|
|
|
|
/* ###fix: need to fill more of these in! */
|
|
|
|
default:
|
|
v = t->value;
|
|
o->marker = INCON_MV;
|
|
break;
|
|
}
|
|
o->value = v;
|
|
}
|
|
return(TRUE);
|
|
default:
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
/* --- Emitter ------------------------------------------------------------- */
|
|
|
|
expression_tree_node *ET; /* Allocated to ET_used */
|
|
static memory_list ET_memlist;
|
|
static int ET_used;
|
|
|
|
extern void clear_expression_space(void)
|
|
{ ET_used = 0;
|
|
}
|
|
|
|
typedef struct emitterstackinfo_s {
|
|
assembly_operand op;
|
|
int marker;
|
|
int bracket_count;
|
|
} emitterstackinfo;
|
|
|
|
#define FUNCTION_VALUE_MARKER 1
|
|
#define ARGUMENT_VALUE_MARKER 2
|
|
#define OR_VALUE_MARKER 3
|
|
|
|
static emitterstackinfo *emitter_stack; /* Allocated to emitter_sp */
|
|
static memory_list emitter_stack_memlist;
|
|
static int emitter_sp;
|
|
|
|
static int is_property_t(int symbol_type)
|
|
{ return ((symbol_type == PROPERTY_T) || (symbol_type == INDIVIDUAL_PROPERTY_T));
|
|
}
|
|
|
|
static void mark_top_of_emitter_stack(int marker, const token_data *t)
|
|
{ if (emitter_sp < 1)
|
|
{ compiler_error("SR error: Attempt to add a marker to the top of an empty emitter stack");
|
|
return;
|
|
}
|
|
if (expr_trace_level >= 2)
|
|
{ printf("Marking top of emitter stack (which is ");
|
|
print_operand(&emitter_stack[emitter_sp-1].op, FALSE);
|
|
printf(") as ");
|
|
switch(marker)
|
|
{
|
|
case FUNCTION_VALUE_MARKER:
|
|
printf("FUNCTION");
|
|
break;
|
|
case ARGUMENT_VALUE_MARKER:
|
|
printf("ARGUMENT");
|
|
break;
|
|
case OR_VALUE_MARKER:
|
|
printf("OR_VALUE");
|
|
break;
|
|
default:
|
|
printf("UNKNOWN");
|
|
break;
|
|
}
|
|
printf("\n");
|
|
}
|
|
if (emitter_stack[emitter_sp-1].marker)
|
|
{ if (marker == ARGUMENT_VALUE_MARKER)
|
|
{
|
|
warning("Ignoring spurious leading comma");
|
|
return;
|
|
}
|
|
error_named("Missing operand for", t->text);
|
|
ensure_memory_list_available(&emitter_stack_memlist, emitter_sp+1);
|
|
emitter_stack[emitter_sp].marker = 0;
|
|
emitter_stack[emitter_sp].bracket_count = 0;
|
|
emitter_stack[emitter_sp].op = zero_operand;
|
|
emitter_sp++;
|
|
}
|
|
emitter_stack[emitter_sp-1].marker = marker;
|
|
}
|
|
|
|
static void add_bracket_layer_to_emitter_stack(int depth)
|
|
{ /* There's no point in tracking bracket layers that don't fence off any values. */
|
|
if (emitter_sp < depth + 1) return;
|
|
if (expr_trace_level >= 2)
|
|
printf("Adding bracket layer\n");
|
|
++emitter_stack[emitter_sp-depth-1].bracket_count;
|
|
}
|
|
|
|
static void remove_bracket_layer_from_emitter_stack()
|
|
{ /* Bracket layers that don't fence off any values will not have been tracked. */
|
|
if (emitter_sp < 2) return;
|
|
if (expr_trace_level >= 2)
|
|
printf("Removing bracket layer\n");
|
|
if (emitter_stack[emitter_sp-2].bracket_count <= 0)
|
|
{ compiler_error("SR error: Attempt to remove a nonexistent bracket layer from the emitter stack");
|
|
return;
|
|
}
|
|
--emitter_stack[emitter_sp-2].bracket_count;
|
|
}
|
|
|
|
static void emit_token(const token_data *t)
|
|
{ assembly_operand o1, o2; int arity, stack_size, i;
|
|
int op_node_number, operand_node_number, previous_node_number;
|
|
int32 x = 0;
|
|
|
|
if (expr_trace_level >= 2)
|
|
{ printf("Output: %-19s%21s ", t->text, "");
|
|
for (i=0; i<emitter_sp; i++)
|
|
{ print_operand(&emitter_stack[i].op, FALSE); printf(" ");
|
|
if (emitter_stack[i].marker == FUNCTION_VALUE_MARKER) printf(":FUNCTION ");
|
|
if (emitter_stack[i].marker == ARGUMENT_VALUE_MARKER) printf(":ARGUMENT ");
|
|
if (emitter_stack[i].marker == OR_VALUE_MARKER) printf(":OR ");
|
|
if (emitter_stack[i].bracket_count) printf(":BRACKETS(%d) ", emitter_stack[i].bracket_count);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
if (t->type == SUBOPEN_TT) return;
|
|
|
|
stack_size = 0;
|
|
while ((stack_size < emitter_sp) &&
|
|
!emitter_stack[emitter_sp-stack_size-1].marker &&
|
|
!emitter_stack[emitter_sp-stack_size-1].bracket_count)
|
|
stack_size++;
|
|
|
|
if (t->type == SUBCLOSE_TT)
|
|
{ if (stack_size < emitter_sp && emitter_stack[emitter_sp-stack_size-1].bracket_count)
|
|
{ if (stack_size == 0)
|
|
{ error("No expression between brackets '(' and ')'");
|
|
ensure_memory_list_available(&emitter_stack_memlist, emitter_sp+1);
|
|
emitter_stack[emitter_sp].op = zero_operand;
|
|
emitter_stack[emitter_sp].marker = 0;
|
|
emitter_stack[emitter_sp].bracket_count = 0;
|
|
emitter_sp++;
|
|
}
|
|
else if (stack_size < 1)
|
|
compiler_error("SR error: emitter stack empty in subexpression");
|
|
else if (stack_size > 1)
|
|
compiler_error("SR error: emitter stack overfull in subexpression");
|
|
remove_bracket_layer_from_emitter_stack();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (t->type != OP_TT)
|
|
{
|
|
ensure_memory_list_available(&emitter_stack_memlist, emitter_sp+1);
|
|
emitter_stack[emitter_sp].marker = 0;
|
|
emitter_stack[emitter_sp].bracket_count = 0;
|
|
|
|
if (!evaluate_term(t, &(emitter_stack[emitter_sp++].op)))
|
|
compiler_error_named("Emit token error:", t->text);
|
|
return;
|
|
}
|
|
|
|
/* A comma is argument-separating if it follows an argument (or a function
|
|
call, since we ignore spurious leading commas in function argument lists)
|
|
with no intervening brackets. Function calls are variadic, so we don't
|
|
apply argument-separating commas. */
|
|
if (t->value == COMMA_OP &&
|
|
stack_size < emitter_sp &&
|
|
(emitter_stack[emitter_sp-stack_size-1].marker == ARGUMENT_VALUE_MARKER ||
|
|
emitter_stack[emitter_sp-stack_size-1].marker == FUNCTION_VALUE_MARKER) &&
|
|
!emitter_stack[emitter_sp-stack_size-1].bracket_count)
|
|
{ if (expr_trace_level >= 2)
|
|
printf("Treating comma as argument-separating\n");
|
|
return;
|
|
}
|
|
|
|
if (t->value == OR_OP)
|
|
return;
|
|
|
|
arity = 1;
|
|
if (t->value == FCALL_OP)
|
|
{ if (expr_trace_level >= 3)
|
|
{ printf("FCALL_OP finds marker stack: ");
|
|
for (x=0; x<emitter_sp; x++) printf("%d ", emitter_stack[x].marker);
|
|
printf("\n");
|
|
}
|
|
if (emitter_stack[emitter_sp-1].marker == ARGUMENT_VALUE_MARKER)
|
|
warning("Ignoring spurious trailing comma");
|
|
while (emitter_stack[emitter_sp-arity].marker != FUNCTION_VALUE_MARKER)
|
|
{
|
|
if ((glulx_mode &&
|
|
emitter_stack[emitter_sp-arity].op.type == SYSFUN_OT) ||
|
|
(!glulx_mode &&
|
|
emitter_stack[emitter_sp-arity].op.type == VARIABLE_OT &&
|
|
emitter_stack[emitter_sp-arity].op.value >= 256 &&
|
|
emitter_stack[emitter_sp-arity].op.value < 288))
|
|
{ int index = emitter_stack[emitter_sp-arity].op.value;
|
|
if(!glulx_mode)
|
|
index -= 256;
|
|
if(index >= 0 && index < NUMBER_SYSTEM_FUNCTIONS)
|
|
error_named("System function name used as a value:", system_functions.keywords[index]);
|
|
else
|
|
compiler_error("Found unnamed system function used as a value");
|
|
emitter_stack[emitter_sp-arity].op = zero_operand;
|
|
}
|
|
++arity;
|
|
}
|
|
}
|
|
else
|
|
{ arity = 1;
|
|
if (operators[t->value].usage == IN_U) arity = 2;
|
|
|
|
if (operators[t->value].precedence == 3)
|
|
{ arity = 2;
|
|
x = emitter_sp-1;
|
|
if(!emitter_stack[x].marker && !emitter_stack[x].bracket_count)
|
|
{ for (--x; emitter_stack[x].marker == OR_VALUE_MARKER && !emitter_stack[x].bracket_count; --x)
|
|
{ ++arity;
|
|
++stack_size;
|
|
}
|
|
for (;x >= 0 && !emitter_stack[x].marker && !emitter_stack[x].bracket_count; --x)
|
|
++stack_size;
|
|
}
|
|
}
|
|
|
|
if (arity > stack_size)
|
|
{ error_named("Missing operand for", t->text);
|
|
while (arity > stack_size)
|
|
{ ensure_memory_list_available(&emitter_stack_memlist, emitter_sp+1);
|
|
emitter_stack[emitter_sp].marker = 0;
|
|
emitter_stack[emitter_sp].bracket_count = 0;
|
|
emitter_stack[emitter_sp].op = zero_operand;
|
|
emitter_sp++;
|
|
stack_size++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* pseudo-typecheck in 6.30: catch an unqualified property name */
|
|
for (i = 1; i <= arity; i++)
|
|
{
|
|
o1 = emitter_stack[emitter_sp - i].op;
|
|
if ((o1.symindex >= 0)
|
|
&& is_property_t(symbols[o1.symindex].type)) {
|
|
switch(t->value)
|
|
{
|
|
case FCALL_OP:
|
|
case SETEQUALS_OP: case NOTEQUAL_OP:
|
|
case CONDEQUALS_OP:
|
|
case PROVIDES_OP: case NOTPROVIDES_OP:
|
|
case PROP_ADD_OP: case PROP_NUM_OP:
|
|
case SUPERCLASS_OP:
|
|
case MPROP_ADD_OP: case MESSAGE_OP:
|
|
case PROPERTY_OP:
|
|
if (i < arity) break;
|
|
case GE_OP: case LE_OP:
|
|
/* Direction properties "n_to", etc *are* compared
|
|
in some libraries. They have STAR_SFLAG to tell us
|
|
to skip the warning. */
|
|
if ((i < arity)
|
|
&& (symbols[o1.symindex].flags & STAR_SFLAG)) break;
|
|
default:
|
|
warning("Property name in expression is not qualified by object");
|
|
}
|
|
} /* if (is_property_t */
|
|
}
|
|
|
|
switch(arity)
|
|
{ case 1:
|
|
o1 = emitter_stack[emitter_sp - 1].op;
|
|
if ((o1.marker == 0) && is_constant_ot(o1.type))
|
|
{ switch(t->value)
|
|
{ case UNARY_MINUS_OP: x = -o1.value; goto FoldConstant;
|
|
case ARTNOT_OP:
|
|
if (!glulx_mode)
|
|
x = (~o1.value) & 0xffff;
|
|
else
|
|
x = (~o1.value) & 0xffffffff;
|
|
goto FoldConstant;
|
|
case LOGNOT_OP:
|
|
if (o1.value != 0) x=0; else x=1;
|
|
goto FoldConstant;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
o1 = emitter_stack[emitter_sp - 2].op;
|
|
o2 = emitter_stack[emitter_sp - 1].op;
|
|
|
|
if ((o1.marker == 0) && (o2.marker == 0)
|
|
&& is_constant_ot(o1.type) && is_constant_ot(o2.type))
|
|
{
|
|
int32 ov1, ov2;
|
|
if (glulx_mode)
|
|
{ ov1 = o1.value;
|
|
ov2 = o2.value;
|
|
}
|
|
else
|
|
{ ov1 = (o1.value >= 0x8000) ? (o1.value - 0x10000) : o1.value;
|
|
ov2 = (o2.value >= 0x8000) ? (o2.value - 0x10000) : o2.value;
|
|
}
|
|
|
|
switch(t->value)
|
|
{
|
|
case PLUS_OP: x = ov1 + ov2; goto FoldConstantC;
|
|
case MINUS_OP: x = ov1 - ov2; goto FoldConstantC;
|
|
case TIMES_OP: x = ov1 * ov2; goto FoldConstantC;
|
|
case DIVIDE_OP:
|
|
case REMAINDER_OP:
|
|
if (ov2 == 0)
|
|
error("Division of constant by zero");
|
|
else
|
|
if (t->value == DIVIDE_OP) {
|
|
if (ov2 < 0) {
|
|
ov1 = -ov1;
|
|
ov2 = -ov2;
|
|
}
|
|
if (ov1 >= 0)
|
|
x = ov1 / ov2;
|
|
else
|
|
x = -((-ov1) / ov2);
|
|
}
|
|
else {
|
|
if (ov2 < 0) {
|
|
ov2 = -ov2;
|
|
}
|
|
if (ov1 >= 0)
|
|
x = ov1 % ov2;
|
|
else
|
|
x = -((-ov1) % ov2);
|
|
}
|
|
goto FoldConstant;
|
|
case ARTAND_OP: x = o1.value & o2.value; goto FoldConstant;
|
|
case ARTOR_OP: x = o1.value | o2.value; goto FoldConstant;
|
|
case CONDEQUALS_OP:
|
|
if (o1.value == o2.value) x = 1; else x = 0;
|
|
goto FoldConstant;
|
|
case NOTEQUAL_OP:
|
|
if (o1.value != o2.value) x = 1; else x = 0;
|
|
goto FoldConstant;
|
|
case GE_OP:
|
|
if (o1.value >= o2.value) x = 1; else x = 0;
|
|
goto FoldConstant;
|
|
case GREATER_OP:
|
|
if (o1.value > o2.value) x = 1; else x = 0;
|
|
goto FoldConstant;
|
|
case LE_OP:
|
|
if (o1.value <= o2.value) x = 1; else x = 0;
|
|
goto FoldConstant;
|
|
case LESS_OP:
|
|
if (o1.value < o2.value) x = 1; else x = 0;
|
|
goto FoldConstant;
|
|
case LOGAND_OP:
|
|
if ((o1.value != 0) && (o2.value != 0)) x=1; else x=0;
|
|
goto FoldConstant;
|
|
case LOGOR_OP:
|
|
if ((o1.value != 0) || (o2.value != 0)) x=1; else x=0;
|
|
goto FoldConstant;
|
|
}
|
|
|
|
}
|
|
|
|
/* We can also fold logical operations if they are certain
|
|
to short-circuit. The right-hand argument is skipped even
|
|
if it's non-constant or has side effects. */
|
|
|
|
if ((o1.marker == 0)
|
|
&& is_constant_ot(o1.type)) {
|
|
|
|
if (t->value == LOGAND_OP && o1.value == 0)
|
|
{
|
|
x = 0;
|
|
goto FoldConstant;
|
|
}
|
|
|
|
if (t->value == LOGOR_OP && o1.value != 0)
|
|
{
|
|
x = 1;
|
|
goto FoldConstant;
|
|
}
|
|
}
|
|
}
|
|
|
|
ensure_memory_list_available(&ET_memlist, ET_used+1);
|
|
op_node_number = ET_used++;
|
|
|
|
ET[op_node_number].operator_number = t->value;
|
|
ET[op_node_number].up = -1;
|
|
ET[op_node_number].down = -1;
|
|
ET[op_node_number].right = -1;
|
|
|
|
/* This statement is redundant, but prevents compilers from wrongly
|
|
issuing a "used before it was assigned a value" error: */
|
|
previous_node_number = 0;
|
|
|
|
for (i = emitter_sp-arity; i != emitter_sp; i++)
|
|
{
|
|
if (expr_trace_level >= 3)
|
|
printf("i=%d, emitter_sp=%d, arity=%d, ETU=%d\n",
|
|
i, emitter_sp, arity, ET_used);
|
|
if (emitter_stack[i].op.type == EXPRESSION_OT)
|
|
operand_node_number = emitter_stack[i].op.value;
|
|
else
|
|
{
|
|
ensure_memory_list_available(&ET_memlist, ET_used+1);
|
|
operand_node_number = ET_used++;
|
|
ET[operand_node_number].down = -1;
|
|
ET[operand_node_number].value = emitter_stack[i].op;
|
|
}
|
|
ET[operand_node_number].up = op_node_number;
|
|
ET[operand_node_number].right = -1;
|
|
if (i == emitter_sp - arity)
|
|
{ ET[op_node_number].down = operand_node_number;
|
|
}
|
|
else
|
|
{ ET[previous_node_number].right = operand_node_number;
|
|
}
|
|
previous_node_number = operand_node_number;
|
|
}
|
|
|
|
emitter_sp = emitter_sp - arity + 1;
|
|
|
|
emitter_stack[emitter_sp - 1].op.type = EXPRESSION_OT;
|
|
emitter_stack[emitter_sp - 1].op.value = op_node_number;
|
|
emitter_stack[emitter_sp - 1].op.marker = 0;
|
|
emitter_stack[emitter_sp - 1].marker = 0;
|
|
emitter_stack[emitter_sp - 1].bracket_count = 0;
|
|
/* Remove the marker for the brackets implied by operator precedence */
|
|
remove_bracket_layer_from_emitter_stack();
|
|
|
|
return;
|
|
|
|
FoldConstantC:
|
|
|
|
/* In Glulx, skip this test; we can't check out-of-range errors
|
|
for 32-bit arithmetic. */
|
|
|
|
if (!glulx_mode && ((x<-32768) || (x > 32767)))
|
|
{ char folding_error[40];
|
|
int32 ov1 = (o1.value >= 0x8000) ? (o1.value - 0x10000) : o1.value;
|
|
int32 ov2 = (o2.value >= 0x8000) ? (o2.value - 0x10000) : o2.value;
|
|
switch(t->value)
|
|
{
|
|
case PLUS_OP:
|
|
sprintf(folding_error, "%d + %d = %d", ov1, ov2, x);
|
|
break;
|
|
case MINUS_OP:
|
|
sprintf(folding_error, "%d - %d = %d", ov1, ov2, x);
|
|
break;
|
|
case TIMES_OP:
|
|
sprintf(folding_error, "%d * %d = %d", ov1, ov2, x);
|
|
break;
|
|
}
|
|
error_named("Signed arithmetic on compile-time constants overflowed \
|
|
the range -32768 to +32767:", folding_error);
|
|
}
|
|
|
|
FoldConstant:
|
|
|
|
if (!glulx_mode) {
|
|
while (x < 0) x = x + 0x10000;
|
|
x = x & 0xffff;
|
|
}
|
|
else {
|
|
x = x & 0xffffffff;
|
|
}
|
|
|
|
emitter_sp = emitter_sp - arity + 1;
|
|
|
|
if (!glulx_mode) {
|
|
if (x<256)
|
|
emitter_stack[emitter_sp - 1].op.type = SHORT_CONSTANT_OT;
|
|
else emitter_stack[emitter_sp - 1].op.type = LONG_CONSTANT_OT;
|
|
}
|
|
else {
|
|
if (x == 0)
|
|
emitter_stack[emitter_sp - 1].op.type = ZEROCONSTANT_OT;
|
|
else if (x >= -128 && x <= 127)
|
|
emitter_stack[emitter_sp - 1].op.type = BYTECONSTANT_OT;
|
|
else if (x >= -32768 && x <= 32767)
|
|
emitter_stack[emitter_sp - 1].op.type = HALFCONSTANT_OT;
|
|
else
|
|
emitter_stack[emitter_sp - 1].op.type = CONSTANT_OT;
|
|
}
|
|
|
|
emitter_stack[emitter_sp - 1].op.value = x;
|
|
emitter_stack[emitter_sp - 1].op.marker = 0;
|
|
emitter_stack[emitter_sp - 1].marker = 0;
|
|
emitter_stack[emitter_sp - 1].bracket_count = 0;
|
|
|
|
if (expr_trace_level >= 2)
|
|
{ printf("Folding constant to: ");
|
|
print_operand(&emitter_stack[emitter_sp - 1].op, FALSE);
|
|
printf("\n");
|
|
}
|
|
|
|
/* Remove the marker for the brackets implied by operator precedence */
|
|
remove_bracket_layer_from_emitter_stack();
|
|
return;
|
|
}
|
|
|
|
/* --- Pretty printing ----------------------------------------------------- */
|
|
|
|
static void show_node(int n, int depth, int annotate)
|
|
{ int j;
|
|
for (j=0; j<2*depth+2; j++) printf(" ");
|
|
|
|
if (ET[n].down == -1)
|
|
{ print_operand(&ET[n].value, annotate);
|
|
printf("\n");
|
|
}
|
|
else
|
|
{ printf("%s ", operators[ET[n].operator_number].description);
|
|
j = operators[ET[n].operator_number].precedence;
|
|
if ((annotate) && ((j==2) || (j==3)))
|
|
{ printf(" %d|%d ", ET[n].true_label, ET[n].false_label);
|
|
if (ET[n].label_after != -1) printf(" def %d after ",
|
|
ET[n].label_after);
|
|
if (ET[n].to_expression) printf(" con to expr ");
|
|
}
|
|
printf("\n");
|
|
show_node(ET[n].down, depth+1, annotate);
|
|
}
|
|
|
|
if (ET[n].right != -1) show_node(ET[n].right, depth, annotate);
|
|
}
|
|
|
|
extern void show_tree(assembly_operand AO, int annotate)
|
|
{ if (AO.type == EXPRESSION_OT) show_node(AO.value, 0, annotate);
|
|
else
|
|
{ printf("Constant: "); print_operand(&AO, annotate);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/* --- Lvalue transformations ---------------------------------------------- */
|
|
|
|
/* This only gets called in Z-code, since Glulx doesn't distinguish
|
|
individual property operators from general ones. */
|
|
static void check_property_operator(int from_node)
|
|
{ int below = ET[from_node].down;
|
|
int opnum = ET[from_node].operator_number;
|
|
|
|
ASSERT_ZCODE();
|
|
|
|
if (veneer_mode) return;
|
|
|
|
if ((below != -1) && (ET[below].right != -1))
|
|
{ int n = ET[below].right, flag = FALSE;
|
|
|
|
/* Can we handle this dot operator as a native @get_prop (etc)
|
|
opcode? Only if we recognize the property value as a declared
|
|
common property constant. */
|
|
if ((ET[n].down == -1)
|
|
&& ((ET[n].value.type == LONG_CONSTANT_OT)
|
|
|| (ET[n].value.type == SHORT_CONSTANT_OT))
|
|
&& ((ET[n].value.value > 0) && (ET[n].value.value < 64))
|
|
&& (ET[n].value.marker == 0))
|
|
flag = TRUE;
|
|
|
|
if (!flag)
|
|
{ switch(opnum)
|
|
{ case PROPERTY_OP: opnum = MESSAGE_OP; break;
|
|
case PROP_ADD_OP: opnum = MPROP_ADD_OP; break;
|
|
case PROP_NUM_OP: opnum = MPROP_NUM_OP; break;
|
|
}
|
|
}
|
|
|
|
ET[from_node].operator_number = opnum;
|
|
}
|
|
|
|
if (below != -1)
|
|
check_property_operator(below);
|
|
if (ET[from_node].right != -1)
|
|
check_property_operator(ET[from_node].right);
|
|
}
|
|
|
|
static void check_lvalues(int from_node)
|
|
{ int below = ET[from_node].down;
|
|
int opnum = ET[from_node].operator_number, opnum_below;
|
|
int lvalue_form, i, j = 0;
|
|
|
|
if (below != -1)
|
|
{
|
|
if ((opnum == FCALL_OP) && (ET[below].down != -1))
|
|
{ opnum_below = ET[below].operator_number;
|
|
if ((opnum_below == PROPERTY_OP) || (opnum_below == MESSAGE_OP))
|
|
{ i = ET[ET[from_node].down].right;
|
|
ET[from_node].down = ET[below].down;
|
|
ET[ET[below].down].up = from_node;
|
|
ET[ET[ET[below].down].right].up = from_node;
|
|
ET[ET[ET[below].down].right].right = i;
|
|
opnum = PROP_CALL_OP;
|
|
ET[from_node].operator_number = opnum;
|
|
}
|
|
}
|
|
|
|
if (operators[opnum].requires_lvalue)
|
|
{ opnum_below = ET[below].operator_number;
|
|
|
|
if (ET[below].down == -1)
|
|
{ if (!is_variable_ot(ET[below].value.type))
|
|
{ error("'=' applied to undeclared variable");
|
|
goto LvalueError;
|
|
}
|
|
}
|
|
else
|
|
{ lvalue_form=0;
|
|
switch(opnum)
|
|
{ case SETEQUALS_OP:
|
|
switch(opnum_below)
|
|
{ case ARROW_OP: lvalue_form = ARROW_SETEQUALS_OP; break;
|
|
case DARROW_OP: lvalue_form = DARROW_SETEQUALS_OP; break;
|
|
case MESSAGE_OP: lvalue_form = MESSAGE_SETEQUALS_OP; break;
|
|
case PROPERTY_OP: lvalue_form = PROPERTY_SETEQUALS_OP; break;
|
|
}
|
|
break;
|
|
case INC_OP:
|
|
switch(opnum_below)
|
|
{ case ARROW_OP: lvalue_form = ARROW_INC_OP; break;
|
|
case DARROW_OP: lvalue_form = DARROW_INC_OP; break;
|
|
case MESSAGE_OP: lvalue_form = MESSAGE_INC_OP; break;
|
|
case PROPERTY_OP: lvalue_form = PROPERTY_INC_OP; break;
|
|
}
|
|
break;
|
|
case POST_INC_OP:
|
|
switch(opnum_below)
|
|
{ case ARROW_OP: lvalue_form = ARROW_POST_INC_OP; break;
|
|
case DARROW_OP: lvalue_form = DARROW_POST_INC_OP; break;
|
|
case MESSAGE_OP: lvalue_form = MESSAGE_POST_INC_OP; break;
|
|
case PROPERTY_OP: lvalue_form = PROPERTY_POST_INC_OP; break;
|
|
}
|
|
break;
|
|
case DEC_OP:
|
|
switch(opnum_below)
|
|
{ case ARROW_OP: lvalue_form = ARROW_DEC_OP; break;
|
|
case DARROW_OP: lvalue_form = DARROW_DEC_OP; break;
|
|
case MESSAGE_OP: lvalue_form = MESSAGE_DEC_OP; break;
|
|
case PROPERTY_OP: lvalue_form = PROPERTY_DEC_OP; break;
|
|
}
|
|
break;
|
|
case POST_DEC_OP:
|
|
switch(opnum_below)
|
|
{ case ARROW_OP: lvalue_form = ARROW_POST_DEC_OP; break;
|
|
case DARROW_OP: lvalue_form = DARROW_POST_DEC_OP; break;
|
|
case MESSAGE_OP: lvalue_form = MESSAGE_POST_DEC_OP; break;
|
|
case PROPERTY_OP: lvalue_form = PROPERTY_POST_DEC_OP; break;
|
|
}
|
|
break;
|
|
}
|
|
if (lvalue_form == 0)
|
|
{ error_named("'=' applied to",
|
|
(char *) operators[opnum_below].description);
|
|
goto LvalueError;
|
|
}
|
|
|
|
/* Transform from_node from_node
|
|
| \ | \\\ \
|
|
below value to value
|
|
| \\\
|
|
*/
|
|
|
|
ET[from_node].operator_number = lvalue_form;
|
|
i = ET[below].down;
|
|
ET[from_node].down = i;
|
|
while (i != -1)
|
|
{ ET[i].up = from_node;
|
|
j = i;
|
|
i = ET[i].right;
|
|
}
|
|
ET[j].right = ET[below].right;
|
|
}
|
|
}
|
|
check_lvalues(below);
|
|
}
|
|
if (ET[from_node].right != -1)
|
|
check_lvalues(ET[from_node].right);
|
|
return;
|
|
|
|
LvalueError:
|
|
ET[from_node].down = -1;
|
|
ET[from_node].value = zero_operand;
|
|
if (ET[from_node].right != -1)
|
|
check_lvalues(ET[from_node].right);
|
|
}
|
|
|
|
/* --- Tree surgery for conditionals --------------------------------------- */
|
|
|
|
static void negate_condition(int n)
|
|
{ int i;
|
|
|
|
if (ET[n].right != -1) negate_condition(ET[n].right);
|
|
if (ET[n].down == -1) return;
|
|
i = operators[ET[n].operator_number].negation;
|
|
if (i!=0) ET[n].operator_number = i;
|
|
if (operators[i].precedence==2) negate_condition(ET[n].down);
|
|
}
|
|
|
|
static void delete_negations(int n, int context)
|
|
{
|
|
/* Recursively apply
|
|
|
|
~~(x && y) = ~~x || ~~y
|
|
~~(x || y) = ~~x && ~~y
|
|
~~(x == y) = x ~= y
|
|
|
|
(etc) to delete the ~~ operator from the tree. Since this is
|
|
depth first, the ~~ being deleted has no ~~s beneath it, which
|
|
is important to make "negate_condition" work.
|
|
|
|
We also do the check for (x <= y or z) here. This must be done
|
|
before negate_condition.
|
|
*/
|
|
|
|
int i;
|
|
|
|
if (ET[n].operator_number == LE_OP || ET[n].operator_number == GE_OP) {
|
|
if (ET[n].down != -1
|
|
&& ET[ET[n].down].right != -1
|
|
&& ET[ET[ET[n].down].right].right != -1) {
|
|
if (ET[n].operator_number == LE_OP)
|
|
warning("The behavior of (<= or) may be unexpected.");
|
|
else
|
|
warning("The behavior of (>= or) may be unexpected.");
|
|
}
|
|
}
|
|
|
|
if (ET[n].right != -1) delete_negations(ET[n].right, context);
|
|
if (ET[n].down == -1) return;
|
|
delete_negations(ET[n].down, context);
|
|
|
|
if (ET[n].operator_number == LOGNOT_OP)
|
|
{ negate_condition(ET[n].down);
|
|
ET[n].operator_number
|
|
= ET[ET[n].down].operator_number;
|
|
ET[n].down = ET[ET[n].down].down;
|
|
i = ET[n].down;
|
|
while(i != -1) { ET[i].up = n; i = ET[i].right; }
|
|
}
|
|
}
|
|
|
|
static void insert_exp_to_cond(int n, int context)
|
|
{
|
|
/* Insert a ~= test when an expression is used as a condition.
|
|
|
|
Check for possible confusion over = and ==, e.g. "if (a = 1) ..." */
|
|
|
|
int new, i;
|
|
|
|
if (ET[n].right != -1) insert_exp_to_cond(ET[n].right, context);
|
|
|
|
if (ET[n].down == -1)
|
|
{ if (context==CONDITION_CONTEXT)
|
|
{
|
|
ensure_memory_list_available(&ET_memlist, ET_used+1);
|
|
new = ET_used++;
|
|
ET[new] = ET[n];
|
|
ET[n].down = new; ET[n].operator_number = NONZERO_OP;
|
|
ET[new].up = n; ET[new].right = -1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch(operators[ET[n].operator_number].precedence)
|
|
{ case 3: /* Conditionals have level 3 */
|
|
context = QUANTITY_CONTEXT;
|
|
break;
|
|
case 2: /* Logical operators level 2 */
|
|
context = CONDITION_CONTEXT;
|
|
break;
|
|
case 1: /* Forms of '=' have level 1 */
|
|
if (context == CONDITION_CONTEXT)
|
|
warning("'=' used as condition: '==' intended?");
|
|
default:
|
|
if (context != CONDITION_CONTEXT) break;
|
|
|
|
ensure_memory_list_available(&ET_memlist, ET_used+1);
|
|
new = ET_used++;
|
|
ET[new] = ET[n];
|
|
ET[n].down = new; ET[n].operator_number = NONZERO_OP;
|
|
ET[new].up = n; ET[new].right = -1;
|
|
|
|
i = ET[new].down;
|
|
while (i!= -1) { ET[i].up = new; i = ET[i].right; }
|
|
context = QUANTITY_CONTEXT; n = new;
|
|
}
|
|
|
|
insert_exp_to_cond(ET[n].down, context);
|
|
}
|
|
|
|
static unsigned int etoken_num_children(int n)
|
|
{
|
|
int count = 0;
|
|
int i;
|
|
i = ET[n].down;
|
|
if (i == -1) { return 0; }
|
|
do {
|
|
count++;
|
|
i = ET[i].right;
|
|
} while (i!=-1);
|
|
return count;
|
|
}
|
|
|
|
static void func_args_on_stack(int n, int context)
|
|
{
|
|
/* Make sure that the arguments of every function-call expression
|
|
are stored to the stack. If any aren't (ie, if any arguments are
|
|
constants or variables), cover them with push operators.
|
|
(The very first argument does not need to be so treated, because
|
|
it's the function address, not a function argument. We also
|
|
skip the treatment for most system functions.) */
|
|
|
|
int new, pn, fnaddr, opnum;
|
|
|
|
ASSERT_GLULX();
|
|
|
|
if (ET[n].right != -1)
|
|
func_args_on_stack(ET[n].right, context);
|
|
if (ET[n].down == -1) {
|
|
pn = ET[n].up;
|
|
if (pn != -1) {
|
|
opnum = ET[pn].operator_number;
|
|
if (opnum == FCALL_OP
|
|
|| opnum == MESSAGE_CALL_OP
|
|
|| opnum == PROP_CALL_OP) {
|
|
/* If it's an FCALL, get the operand which contains the function
|
|
address (or system-function number) */
|
|
if (opnum == MESSAGE_CALL_OP
|
|
|| opnum == PROP_CALL_OP
|
|
|| ((fnaddr=ET[pn].down) != n
|
|
&& (ET[fnaddr].value.type != SYSFUN_OT
|
|
|| ET[fnaddr].value.value == INDIRECT_SYSF
|
|
|| ET[fnaddr].value.value == GLK_SYSF))) {
|
|
if (etoken_num_children(pn) > (unsigned int)(opnum == FCALL_OP ? 4:3)) {
|
|
ensure_memory_list_available(&ET_memlist, ET_used+1);
|
|
new = ET_used++;
|
|
ET[new] = ET[n];
|
|
ET[n].down = new;
|
|
ET[n].operator_number = PUSH_OP;
|
|
ET[new].up = n;
|
|
ET[new].right = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
func_args_on_stack(ET[n].down, context);
|
|
}
|
|
|
|
static assembly_operand check_conditions(assembly_operand AO, int context)
|
|
{ int n;
|
|
|
|
if (AO.type != EXPRESSION_OT)
|
|
{ if (context != CONDITION_CONTEXT) return AO;
|
|
ensure_memory_list_available(&ET_memlist, ET_used+1);
|
|
n = ET_used++;
|
|
ET[n].down = -1;
|
|
ET[n].up = -1;
|
|
ET[n].right = -1;
|
|
ET[n].value = AO;
|
|
INITAOT(&AO, EXPRESSION_OT);
|
|
AO.value = n;
|
|
}
|
|
|
|
insert_exp_to_cond(AO.value, context);
|
|
delete_negations(AO.value, context);
|
|
|
|
if (glulx_mode)
|
|
func_args_on_stack(AO.value, context);
|
|
|
|
return AO;
|
|
}
|
|
|
|
/* --- Shift-reduce parser ------------------------------------------------- */
|
|
|
|
static int sr_sp;
|
|
static token_data *sr_stack; /* Allocated to sr_sp */
|
|
static memory_list sr_stack_memlist;
|
|
|
|
extern assembly_operand parse_expression(int context)
|
|
{
|
|
/* Parses an expression, evaluating it as a constant if possible.
|
|
|
|
Possible contexts are:
|
|
|
|
VOID_CONTEXT the expression is used as a statement, so that
|
|
its value will be thrown away and it only
|
|
needs to exist for any resulting side-effects
|
|
(function calls and assignments)
|
|
|
|
CONDITION_CONTEXT the result must be a condition
|
|
|
|
CONSTANT_CONTEXT there is required to be a constant result
|
|
(so that, for instance, comma becomes illegal)
|
|
|
|
QUANTITY_CONTEXT the default: a quantity is to be specified
|
|
|
|
ACTION_Q_CONTEXT like QUANTITY_CONTEXT, but postfixed brackets
|
|
at the top level do not indicate function call:
|
|
used for e.g.
|
|
<Insert button (random(pocket1, pocket2))>
|
|
|
|
RETURN_Q_CONTEXT like QUANTITY_CONTEXT, but a single property
|
|
name does not generate a warning
|
|
|
|
ASSEMBLY_CONTEXT a quantity which cannot use the '->' operator
|
|
(needed for assembly language to indicate
|
|
store destinations)
|
|
|
|
FORINIT_CONTEXT a quantity which cannot use an (unbracketed)
|
|
'::' operator
|
|
|
|
ARRAY_CONTEXT like CONSTANT_CONTEXT, but where an unbracketed
|
|
minus sign is ambiguous, and brackets always
|
|
indicate subexpressions, not function calls
|
|
|
|
Return value: an assembly operand.
|
|
|
|
If the type is OMITTED_OT, then the expression has no resulting value.
|
|
|
|
If the type is EXPRESSION_OT, then the value will need to be
|
|
calculated at run-time by code compiled from the expression tree
|
|
whose root node-number is the operand value.
|
|
|
|
Otherwise the assembly operand is the value of the expression, which
|
|
is constant and thus known at compile time.
|
|
|
|
If an error has occurred in the expression, which recovery from was
|
|
not possible, then the return is (short constant) 0. This should
|
|
minimise the chance of a cascade of further error messages.
|
|
*/
|
|
|
|
token_data a, b, pop; int i;
|
|
assembly_operand AO;
|
|
|
|
superclass_allowed = (context != FORINIT_CONTEXT);
|
|
if (context == FORINIT_CONTEXT) context = VOID_CONTEXT;
|
|
|
|
comma_allowed = (context == VOID_CONTEXT);
|
|
arrow_allowed = (context != ASSEMBLY_CONTEXT);
|
|
bare_prop_allowed = (context == RETURN_Q_CONTEXT);
|
|
array_init_ambiguity = ((context == ARRAY_CONTEXT) ||
|
|
(context == ASSEMBLY_CONTEXT));
|
|
|
|
action_ambiguity = (context == ACTION_Q_CONTEXT);
|
|
|
|
if (context == ASSEMBLY_CONTEXT) context = QUANTITY_CONTEXT;
|
|
if (context == ACTION_Q_CONTEXT) context = QUANTITY_CONTEXT;
|
|
if (context == RETURN_Q_CONTEXT) context = QUANTITY_CONTEXT;
|
|
if (context == ARRAY_CONTEXT) context = CONSTANT_CONTEXT;
|
|
|
|
etoken_count = 0;
|
|
inserting_token = FALSE;
|
|
|
|
emitter_sp = 0;
|
|
bracket_level = 0;
|
|
|
|
previous_token.text = "$";
|
|
previous_token.type = ENDEXP_TT;
|
|
previous_token.value = 0;
|
|
|
|
ensure_memory_list_available(&sr_stack_memlist, 1);
|
|
sr_sp = 1;
|
|
sr_stack[0] = previous_token;
|
|
|
|
AO = zero_operand;
|
|
|
|
statements.enabled = FALSE;
|
|
directives.enabled = FALSE;
|
|
|
|
if (get_next_etoken() == FALSE)
|
|
{ ebf_error("expression", token_text);
|
|
return AO;
|
|
}
|
|
|
|
do
|
|
{ if (expr_trace_level >= 2)
|
|
{ printf("Input: %-20s", current_token.text);
|
|
for (i=0; i<sr_sp; i++) printf("%s ", sr_stack[i].text);
|
|
printf("\n");
|
|
}
|
|
if (expr_trace_level >= 3) printf("ET_used = %d\n", ET_used);
|
|
|
|
if (sr_sp == 0)
|
|
{ compiler_error("SR error: stack empty");
|
|
return(AO);
|
|
}
|
|
|
|
a = sr_stack[sr_sp-1]; b = current_token;
|
|
|
|
if ((a.type == ENDEXP_TT) && (b.type == ENDEXP_TT))
|
|
{ if (emitter_sp == 0)
|
|
{ error("No expression between brackets '(' and ')'");
|
|
put_token_back();
|
|
return AO;
|
|
}
|
|
if (emitter_sp > 1)
|
|
{ compiler_error("SR error: emitter stack overfull");
|
|
return AO;
|
|
}
|
|
|
|
AO = emitter_stack[0].op;
|
|
if (AO.type == EXPRESSION_OT)
|
|
{ if (expr_trace_level >= 3)
|
|
{ printf("Tree before lvalue checking:\n");
|
|
show_tree(AO, FALSE);
|
|
}
|
|
if (!glulx_mode)
|
|
check_property_operator(AO.value);
|
|
check_lvalues(AO.value);
|
|
ET[AO.value].up = -1;
|
|
}
|
|
else {
|
|
if ((context != CONSTANT_CONTEXT)
|
|
&& (AO.symindex >= 0)
|
|
&& is_property_t(symbols[AO.symindex].type)
|
|
&& (arrow_allowed) && (!bare_prop_allowed))
|
|
warning("Bare property name found. \"self.prop\" intended?");
|
|
}
|
|
|
|
check_conditions(AO, context);
|
|
|
|
if (context == CONSTANT_CONTEXT)
|
|
if (!is_constant_ot(AO.type))
|
|
{ AO = zero_operand;
|
|
ebf_error("constant", "<expression>");
|
|
}
|
|
put_token_back();
|
|
|
|
return(AO);
|
|
}
|
|
|
|
switch(find_prec(&a,&b))
|
|
{
|
|
case e5: /* Associativity error */
|
|
error_named("Brackets mandatory to clarify order of:",
|
|
a.text);
|
|
|
|
case LOWER_P:
|
|
case EQUAL_P:
|
|
ensure_memory_list_available(&sr_stack_memlist, sr_sp+1);
|
|
sr_stack[sr_sp++] = b;
|
|
switch(b.type)
|
|
{
|
|
case SUBOPEN_TT:
|
|
if (sr_sp >= 2 && sr_stack[sr_sp-2].type == OP_TT && sr_stack[sr_sp-2].value == FCALL_OP)
|
|
mark_top_of_emitter_stack(FUNCTION_VALUE_MARKER, &b);
|
|
else
|
|
add_bracket_layer_to_emitter_stack(0);
|
|
break;
|
|
case OP_TT:
|
|
switch(b.value){
|
|
case OR_OP:
|
|
if (sr_stack[sr_sp-2].type == OP_TT &&
|
|
operators[sr_stack[sr_sp-2].value].precedence == 3)
|
|
mark_top_of_emitter_stack(OR_VALUE_MARKER, &b);
|
|
else
|
|
{ error("'or' not between values to the right of a condition");
|
|
/* Convert to + for error recovery purposes */
|
|
sr_stack[sr_sp-1].value = PLUS_OP;
|
|
}
|
|
break;
|
|
case COMMA_OP:
|
|
{
|
|
/* A comma separates arguments only if the shallowest open bracket belongs to a function call. */
|
|
int shallowest_open_bracket_index = sr_sp - 2;
|
|
while (shallowest_open_bracket_index > 0 && sr_stack[shallowest_open_bracket_index].type != SUBOPEN_TT)
|
|
--shallowest_open_bracket_index;
|
|
if (shallowest_open_bracket_index > 0 &&
|
|
sr_stack[shallowest_open_bracket_index-1].type == OP_TT &&
|
|
sr_stack[shallowest_open_bracket_index-1].value == FCALL_OP)
|
|
{ mark_top_of_emitter_stack(ARGUMENT_VALUE_MARKER, &b);
|
|
break;
|
|
}
|
|
/* Non-argument-separating commas get treated like any other operator; we fall through to the default case. */
|
|
}
|
|
default:
|
|
{
|
|
/* Add a marker for the brackets implied by operator precedence */
|
|
int operands_on_left = (operators[b.value].usage == PRE_U) ? 0 : 1;
|
|
add_bracket_layer_to_emitter_stack(operands_on_left);
|
|
}
|
|
}
|
|
}
|
|
get_next_etoken();
|
|
break;
|
|
case GREATER_P:
|
|
do
|
|
{ pop = sr_stack[sr_sp - 1];
|
|
emit_token(&pop);
|
|
sr_sp--;
|
|
} while (find_prec(&sr_stack[sr_sp-1], &pop) != LOWER_P);
|
|
break;
|
|
|
|
case e1: /* Missing operand error */
|
|
error_named("Missing operand after", a.text);
|
|
put_token_back();
|
|
current_token.type = NUMBER_TT;
|
|
current_token.value = 0;
|
|
current_token.marker = 0;
|
|
current_token.text = "0";
|
|
break;
|
|
|
|
case e2: /* Unexpected close bracket */
|
|
error("Found '(' without matching ')'");
|
|
get_next_etoken();
|
|
break;
|
|
|
|
case e3: /* Missing operator error */
|
|
error("Missing operator: inserting '+'");
|
|
put_token_back();
|
|
current_token.type = OP_TT;
|
|
current_token.value = PLUS_OP;
|
|
current_token.marker = 0;
|
|
current_token.text = "+";
|
|
break;
|
|
|
|
case e4: /* Expression ends with an open bracket */
|
|
error("Found '(' without matching ')'");
|
|
sr_sp--;
|
|
break;
|
|
|
|
}
|
|
}
|
|
while (TRUE);
|
|
}
|
|
|
|
/* --- Test for simple ++ or -- usage: used to optimise "for" loop code ---- */
|
|
|
|
extern int test_for_incdec(assembly_operand AO)
|
|
{ int s = 0;
|
|
if (AO.type != EXPRESSION_OT) return 0;
|
|
if (ET[AO.value].down == -1) return 0;
|
|
switch(ET[AO.value].operator_number)
|
|
{ case INC_OP: s = 1; break;
|
|
case POST_INC_OP: s = 1; break;
|
|
case DEC_OP: s = -1; break;
|
|
case POST_DEC_OP: s = -1; break;
|
|
}
|
|
if (s==0) return 0;
|
|
if (ET[ET[AO.value].down].down != -1) return 0;
|
|
if (!is_variable_ot(ET[ET[AO.value].down].value.type)) return 0;
|
|
return s*(ET[ET[AO.value].down].value.value);
|
|
}
|
|
|
|
/* ========================================================================= */
|
|
/* Data structure management routines */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
extern void init_expressp_vars(void)
|
|
{ int i;
|
|
/* make_operands(); */
|
|
make_lexical_interface_tables();
|
|
for (i=0; i<NUMBER_SYSTEM_FUNCTIONS; i++)
|
|
system_function_usage[i] = 0;
|
|
|
|
ET = NULL;
|
|
emitter_stack = NULL;
|
|
sr_stack = NULL;
|
|
}
|
|
|
|
extern void expressp_begin_pass(void)
|
|
{
|
|
}
|
|
|
|
extern void expressp_allocate_arrays(void)
|
|
{
|
|
initialise_memory_list(&ET_memlist,
|
|
sizeof(expression_tree_node), 100, (void**)&ET,
|
|
"expression parse trees");
|
|
|
|
initialise_memory_list(&emitter_stack_memlist,
|
|
sizeof(emitterstackinfo), 100, (void**)&emitter_stack,
|
|
"expression stack");
|
|
|
|
initialise_memory_list(&sr_stack_memlist,
|
|
sizeof(token_data), 100, (void**)&sr_stack,
|
|
"shift-reduce parser stack");
|
|
}
|
|
|
|
extern void expressp_free_arrays(void)
|
|
{
|
|
deallocate_memory_list(&ET_memlist);
|
|
|
|
deallocate_memory_list(&emitter_stack_memlist);
|
|
|
|
deallocate_memory_list(&sr_stack_memlist);
|
|
}
|
|
|
|
/* ========================================================================= */
|