mirror of
https://github.com/ganelson/inform.git
synced 2024-05-05 02:28:34 +03:00
809 lines
28 KiB
OpenEdge ABL
809 lines
28 KiB
OpenEdge ABL
[InterTypes::] Inter Data Types.
|
|
|
|
A primitive notion of data type, below the level of kinds.
|
|
|
|
@h Constructors.
|
|
Abstractly, an Inter type is a combination of a "constructor" and 0 or more
|
|
"operand types", the number depending on which constructor is used. If this
|
|
is 0, type is a "base" constructor.
|
|
|
|
Constructors are identified textually by keywords, such as |int32|, and also
|
|
by "constructor ID" numbers, such as |INT32_ITCONC|. This one is a base;
|
|
whereas |list|, for example, is not -- |list of int32| is a valid type, with
|
|
1 type operand, but |list| alone is not sufficient to specify a type.
|
|
|
|
@ These are the constructor IDs. Note that changing any of these values would
|
|
invalidate existing Inter binary files, necessitating a bump of //The Inter Version//.
|
|
|
|
@e UNCHECKED_ITCONC from 1
|
|
@e INT32_ITCONC
|
|
@e INT16_ITCONC
|
|
@e INT8_ITCONC
|
|
@e INT2_ITCONC
|
|
@e REAL_ITCONC
|
|
@e TEXT_ITCONC
|
|
@e ENUM_ITCONC
|
|
@e LIST_ITCONC
|
|
@e ACTIVITY_ITCONC
|
|
@e COLUMN_ITCONC
|
|
@e TABLE_ITCONC
|
|
@e FUNCTION_ITCONC
|
|
@e STRUCT_ITCONC
|
|
@e RELATION_ITCONC
|
|
@e DESCRIPTION_ITCONC
|
|
@e RULE_ITCONC
|
|
@e RULEBOOK_ITCONC
|
|
@e EQUATED_ITCONC
|
|
@e VOID_ITCONC
|
|
|
|
@d MIN_INTER_TYPE_CONSTRUCTOR UNCHECKED_ITCONC
|
|
@d MAX_INTER_TYPE_CONSTRUCTOR VOID_ITCONC
|
|
|
|
=
|
|
int InterTypes::is_valid_constructor_code(inter_ti constructor) {
|
|
if ((constructor < MIN_INTER_TYPE_CONSTRUCTOR) ||
|
|
(constructor > MAX_INTER_TYPE_CONSTRUCTOR)) return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
@ Clearly we need to store some metadata about what these constructor IDs
|
|
mean, and we do that with a simple lookup array large enough to hold all
|
|
valid constructor codes as indexes:
|
|
|
|
=
|
|
typedef struct inter_type_constructor {
|
|
inter_ti constructor_ID;
|
|
struct text_stream *constructor_keyword;
|
|
long long int min_value;
|
|
long long int max_value;
|
|
int is_enumerated;
|
|
int is_base;
|
|
int arity;
|
|
} inter_type_constructor;
|
|
|
|
inter_type_constructor inter_type_constructors[MAX_INTER_TYPE_CONSTRUCTOR + 1];
|
|
|
|
@ That array initially contains undetermined data, of course, so we need to
|
|
initialise it:
|
|
|
|
=
|
|
void InterTypes::initialise_constructors(void) {
|
|
InterTypes::init_con(UNCHECKED_ITCONC, I"unchecked", -2147483648, 2147483647, FALSE, TRUE, 0);
|
|
InterTypes::init_con(INT32_ITCONC, I"int32", -2147483648, 2147483647, FALSE, TRUE, 0);
|
|
InterTypes::init_con(INT16_ITCONC, I"int16", -32768, 32767, FALSE, TRUE, 0);
|
|
InterTypes::init_con(INT8_ITCONC, I"int8", -128, 127, FALSE, TRUE, 0);
|
|
InterTypes::init_con(INT2_ITCONC, I"int2", 0, 1, FALSE, TRUE, 0);
|
|
InterTypes::init_con(REAL_ITCONC, I"real", -2147483648, 2147483647, FALSE, TRUE, 0);
|
|
InterTypes::init_con(TEXT_ITCONC, I"text", -2147483648, 2147483647, FALSE, TRUE, 0);
|
|
InterTypes::init_con(ENUM_ITCONC, I"enum", 0, 2147483647, TRUE, TRUE, 0);
|
|
InterTypes::init_con(LIST_ITCONC, I"list", -2147483648, 2147483647, FALSE, FALSE, 1);
|
|
InterTypes::init_con(ACTIVITY_ITCONC, I"activity", -2147483648, 2147483647, FALSE, FALSE, 1);
|
|
InterTypes::init_con(COLUMN_ITCONC, I"column", -2147483648, 2147483647, FALSE, FALSE, 1);
|
|
InterTypes::init_con(TABLE_ITCONC, I"table", -2147483648, 2147483647, FALSE, FALSE, 1);
|
|
InterTypes::init_con(FUNCTION_ITCONC, I"function", -2147483648, 2147483647, FALSE, FALSE, 2);
|
|
InterTypes::init_con(STRUCT_ITCONC, I"struct", -2147483648, 2147483647, FALSE, FALSE, 0);
|
|
InterTypes::init_con(RELATION_ITCONC, I"relation", -2147483648, 2147483647, FALSE, FALSE, 2);
|
|
InterTypes::init_con(DESCRIPTION_ITCONC, I"description", -2147483648, 2147483647, FALSE, FALSE, 1);
|
|
InterTypes::init_con(RULE_ITCONC, I"rule", -2147483648, 2147483647, FALSE, FALSE, 2);
|
|
InterTypes::init_con(RULEBOOK_ITCONC, I"rulebook", -2147483648, 2147483647, FALSE, FALSE, 1);
|
|
InterTypes::init_con(EQUATED_ITCONC, I"", -2147483648, 2147483647, FALSE, FALSE, 1);
|
|
InterTypes::init_con(VOID_ITCONC, I"void", 1, 0, FALSE, TRUE, 0);
|
|
}
|
|
|
|
@ Where:
|
|
|
|
=
|
|
inter_type_constructor *InterTypes::init_con(inter_ti ID, text_stream *name,
|
|
int range_from, int range_to, int en, int base, int arity) {
|
|
if (InterTypes::is_valid_constructor_code(ID) == FALSE)
|
|
internal_error("constructor ID out of range");
|
|
|
|
inter_type_constructor *IDT = &(inter_type_constructors[ID]);
|
|
IDT->constructor_ID = ID;
|
|
IDT->constructor_keyword = Str::duplicate(name);
|
|
IDT->min_value = range_from;
|
|
IDT->max_value = range_to;
|
|
IDT->is_enumerated = en;
|
|
IDT->is_base = base;
|
|
IDT->arity = arity;
|
|
|
|
return IDT;
|
|
}
|
|
|
|
@ Assuming that has been done, it is safe to call these lookup functions. Note
|
|
that it's fine for textual lookups to be relatively slow.
|
|
|
|
=
|
|
inter_type_constructor *InterTypes::constructor_from_ID(inter_ti ID) {
|
|
if (InterTypes::is_valid_constructor_code(ID)) return &(inter_type_constructors[ID]);
|
|
return NULL;
|
|
}
|
|
|
|
inter_type_constructor *InterTypes::constructor_from_name(text_stream *name) {
|
|
for (inter_ti ID = MIN_INTER_TYPE_CONSTRUCTOR; ID <= MAX_INTER_TYPE_CONSTRUCTOR; ID++) {
|
|
inter_type_constructor *itc = &(inter_type_constructors[ID]);
|
|
if (Str::eq(itc->constructor_keyword, name))
|
|
return itc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
@h Simple types and type names.
|
|
We need to represent types very economically in terms of memory. In principle,
|
|
the set of abstract types is infinite (consider for example |int32|, |list of int32|,
|
|
|list of list of int32|, ...), so there is no limit to the memory which might
|
|
be required.
|
|
|
|
We use the following representations, starting with the most concise:
|
|
|
|
(1) A "TID", or type ID, is a single |inter_ti| value, often stored as a field
|
|
in the bytecode for some instruction. For example, a |VARIABLE_IST| instruction
|
|
includes a field holding the TID of its variable's type. This can only represent
|
|
simple descriptions (see below), and you need to know what package a TID
|
|
came from (i.e., what symbols table was in use there) to unravel it.
|
|
|
|
(2) An //inter_type// is a lightweight structure intended for passing around
|
|
the functions in this section. It can also only represent simple descriptions -- in
|
|
fact, TIDs and |inter_type|s can faithfully be converted back and forth -- but
|
|
has the advantage that you don't need any package context to understand it.
|
|
Arguably this should be called |inter_simple_type_description|, but this is the
|
|
one we use most often, so brevity is good.
|
|
|
|
(3) An //inter_semisimple_type_description// is a much larger structure used only
|
|
when parsing Inter code from text -- so in a regular Inform 7 compilation run, no
|
|
such structures will ever exist. This is still limited, but to the larger
|
|
set of semi-simple type descriptions.
|
|
|
|
The following definitions look circular, but are not:[1]
|
|
|
|
(*) A "simple type description" is either a constructor for which all type
|
|
operands are |unchecked|, such as |int32| or |list of unchecked|, or else a
|
|
"type name".
|
|
|
|
(*) A "semi-simple type description" is either a constructor for which all
|
|
type operands are simple, or else a "type name".
|
|
|
|
(*) A "type name" is a name defined with a specific semi-simple type description.
|
|
|
|
[1] Because the hierarchy of definitions of type names must be well-founded.
|
|
You cannot define |K_apple| to equal |K_pear| and vice versa. Each type name
|
|
must be defined in terms of a finite number of simple types, once all type
|
|
name substitution has been performed, and no type name can ever lead back to
|
|
itself.
|
|
|
|
@ By using type names we can (indirectly) represent any abstract type using
|
|
any of the representations above. For example, |list of list of int32| is neither
|
|
simple nor semi-simple, but we can get to it by:
|
|
|
|
(1) Defining |K_list_of_int32| as a type name for |list of int32|, which is
|
|
semi-simple.
|
|
|
|
(2) Defining |K_list_of_list_of_int32| as a type name for |list of K_list_of_int32|,
|
|
which is semi-simple.
|
|
|
|
And we now have |K_list_of_list_of_int32|, which is simple since it is a bare
|
|
type name, and so can be stored in an //inter_type// or a TID.
|
|
|
|
@ So, then, this holds any simple type description:
|
|
|
|
=
|
|
typedef struct inter_type {
|
|
inter_ti underlying_constructor;
|
|
inter_symbol *type_name;
|
|
} inter_type;
|
|
|
|
@ Since there are two possibilities, there are two functions to construct these:
|
|
|
|
=
|
|
inter_type InterTypes::from_constructor_code(inter_ti constructor_code) {
|
|
if (InterTypes::is_valid_constructor_code(constructor_code) == FALSE)
|
|
internal_error("invalid constructor code");
|
|
inter_type type;
|
|
type.underlying_constructor = constructor_code;
|
|
type.type_name = NULL;
|
|
return type;
|
|
}
|
|
|
|
inter_type InterTypes::from_type_name(inter_symbol *S) {
|
|
if (S) {
|
|
inter_type type;
|
|
type.underlying_constructor = TypenameInstruction::constructor(S);
|
|
type.type_name = S;
|
|
return type;
|
|
}
|
|
return InterTypes::unchecked();
|
|
}
|
|
|
|
@ Reading those back:
|
|
|
|
=
|
|
inter_symbol *InterTypes::type_name(inter_type type) {
|
|
return type.type_name;
|
|
}
|
|
|
|
inter_type_constructor *InterTypes::constructor(inter_type type) {
|
|
inter_type_constructor *itc = InterTypes::constructor_from_ID(type.underlying_constructor);
|
|
if (itc == NULL) itc = InterTypes::constructor_from_ID(UNCHECKED_ITCONC);
|
|
return itc;
|
|
}
|
|
|
|
inter_ti InterTypes::constructor_code(inter_type type) {
|
|
return InterTypes::constructor(type)->constructor_ID;
|
|
}
|
|
|
|
@ In some ways the most useful simple type is |unchecked|. This declares that
|
|
all type-checking rules are waived for the data being described. A program
|
|
in which all data is |unchecked| is a program with no type-checking at all.
|
|
|
|
=
|
|
inter_type InterTypes::unchecked(void) {
|
|
return InterTypes::from_constructor_code(UNCHECKED_ITCONC);
|
|
}
|
|
|
|
int InterTypes::is_unchecked(inter_type type) {
|
|
if (InterTypes::constructor_code(type) == UNCHECKED_ITCONC) return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
@ Access to the arity and operands depends on whether there's a typename or not:
|
|
only with a typename can we have an arity other than the default (e.g. a
|
|
|FUNCTION_ITCONC| with arity 5) or operands other than |unchecked|.
|
|
|
|
=
|
|
int InterTypes::arity_is_possible(inter_type type, int arity) {
|
|
inter_type_constructor *itc = InterTypes::constructor(type);
|
|
if (itc->arity == (int) arity) return TRUE;
|
|
if (itc->constructor_ID == TABLE_ITCONC)
|
|
if (arity == 0) return TRUE;
|
|
if ((itc->constructor_ID == FUNCTION_ITCONC) ||
|
|
(itc->constructor_ID == RULE_ITCONC) ||
|
|
(itc->constructor_ID == STRUCT_ITCONC)) {
|
|
if (itc->arity <= (int) arity) return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int InterTypes::type_arity(inter_type type) {
|
|
inter_symbol *type_name = InterTypes::type_name(type);
|
|
if (type_name) return TypenameInstruction::arity(type_name);
|
|
return InterTypes::constructor(type)->arity;
|
|
}
|
|
|
|
inter_type InterTypes::type_operand(inter_type type, int n) {
|
|
inter_symbol *type_name = InterTypes::type_name(type);
|
|
if (type_name) return TypenameInstruction::operand_type(InterTypes::type_name(type), n);
|
|
return InterTypes::unchecked();
|
|
}
|
|
|
|
@h Converting inter_type to TID.
|
|
|
|
=
|
|
inter_type InterTypes::from_TID(inter_symbols_table *T, inter_ti TID) {
|
|
if (TID >= SYMBOL_BASE_VAL)
|
|
return InterTypes::from_type_name(InterSymbolsTable::symbol_from_ID(T, TID));
|
|
if (InterTypes::is_valid_constructor_code(TID))
|
|
return InterTypes::from_constructor_code(TID);
|
|
return InterTypes::unchecked();
|
|
}
|
|
|
|
inter_type InterTypes::from_TID_in_field(inter_tree_node *P, int field) {
|
|
return InterTypes::from_TID(InterPackage::scope(Inode::get_package(P)),
|
|
P->W.instruction[field]);
|
|
}
|
|
|
|
@h Converting TID to inter_type.
|
|
|
|
=
|
|
inter_ti InterTypes::to_TID(inter_symbols_table *T, inter_type type) {
|
|
if (type.type_name)
|
|
return InterSymbolsTable::id_from_symbol_in_table(T, type.type_name);
|
|
return type.underlying_constructor;
|
|
}
|
|
|
|
inter_ti InterTypes::to_TID_at(inter_bookmark *IBM, inter_type type) {
|
|
if (type.type_name)
|
|
return InterSymbolsTable::id_at_bookmark(IBM, type.type_name);
|
|
return type.underlying_constructor;
|
|
}
|
|
|
|
@h Parsing from text.
|
|
The data structure //inter_semisimple_type_description// exists as a way of
|
|
holding the results of the function //InterTypes::parse_semisimple// -- see
|
|
below. It's made convoluted by the remote but theoretical need to handle an
|
|
arbitrarily large number of type operands. No human user of Inter would ever
|
|
write a type exceeding about 10 operands, but we want to avoid any maxima in
|
|
Inter because it's primarily designed as a language for programs to write.
|
|
|
|
@d DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY 32
|
|
|
|
=
|
|
typedef struct inter_semisimple_type_description {
|
|
inter_ti constructor_code;
|
|
int arity;
|
|
int capacity;
|
|
inter_ti default_operand_TIDs[DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY];
|
|
inter_ti *operand_TIDs;
|
|
} inter_semisimple_type_description;
|
|
|
|
void InterTypes::initialise_isstd(inter_semisimple_type_description *results) {
|
|
results->constructor_code = UNCHECKED_ITCONC;
|
|
results->arity = 0;
|
|
results->capacity = DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY;
|
|
results->operand_TIDs = results->default_operand_TIDs;
|
|
}
|
|
|
|
void InterTypes::add_operand_to_isstd(inter_semisimple_type_description *results,
|
|
inter_symbols_table *T, inter_type type) {
|
|
inter_ti TID = InterTypes::to_TID(T, type);
|
|
if (results->arity >= results->capacity) {
|
|
inter_ti *extended = (inter_ti *) Memory::calloc(2*results->capacity, sizeof(inter_ti),
|
|
INTER_BYTECODE_MREASON);
|
|
for (int i=0; i<2*results->capacity; i++)
|
|
if (i < results->capacity)
|
|
extended[i] = results->operand_TIDs[i];
|
|
else
|
|
extended[i] = 0;
|
|
@<Free operand memory@>;
|
|
results->capacity = 2*results->capacity;
|
|
results->operand_TIDs = extended;
|
|
}
|
|
results->operand_TIDs[(results->arity)++] = TID;
|
|
}
|
|
|
|
@ After a call to //InterTypes::parse_semisimple// the following should be used
|
|
to dispose of the structure. Of course, almost always it does nothing, but it
|
|
prevents a memory leak if really large results were returned.
|
|
|
|
=
|
|
void InterTypes::dispose_of_isstd(inter_semisimple_type_description *results) {
|
|
results->constructor_code = UNCHECKED_ITCONC;
|
|
results->arity = 0;
|
|
@<Free operand memory@>;
|
|
}
|
|
|
|
@<Free operand memory@> =
|
|
if (results->capacity > DEFAULT_SIZE_OF_ISSTD_OPERAND_ARRAY)
|
|
Memory::I7_array_free(results->operand_TIDs, INTER_BYTECODE_MREASON,
|
|
results->capacity, sizeof(inter_ti));
|
|
|
|
@ Here goes, then:
|
|
|
|
=
|
|
inter_error_message *InterTypes::parse_semisimple(text_stream *text, inter_symbols_table *T,
|
|
inter_error_location *eloc, inter_semisimple_type_description *results) {
|
|
results->constructor_code = UNCHECKED_ITCONC;
|
|
results->arity = 0;
|
|
inter_error_message *E = NULL;
|
|
match_results mr = Regexp::create_mr();
|
|
|
|
@<Parse rulebook syntax@>;
|
|
@<Parse list syntax@>;
|
|
@<Parse activity syntax@>;
|
|
@<Parse column syntax@>;
|
|
@<Parse table syntax@>;
|
|
@<Parse description syntax@>;
|
|
@<Parse relation syntax@>;
|
|
@<Parse rule or function syntax@>;
|
|
@<Parse struct syntax@>;
|
|
@<Parse bare constructor-name syntax@>;
|
|
@<Parse bare typename syntax@>;
|
|
|
|
Regexp::dispose_of(&mr);
|
|
return InterErrors::quoted(I"no such data type", text, eloc);
|
|
}
|
|
|
|
@<Parse rulebook syntax@> =
|
|
if (Regexp::match(&mr, text, L"rulebook of (%C+)")) {
|
|
results->constructor_code = RULEBOOK_ITCONC;
|
|
inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, conts_type);
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@<Parse list syntax@> =
|
|
if (Regexp::match(&mr, text, L"list of (%C+)")) {
|
|
results->constructor_code = LIST_ITCONC;
|
|
inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, conts_type);
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@<Parse activity syntax@> =
|
|
if (Regexp::match(&mr, text, L"activity on (%C+)")) {
|
|
results->constructor_code = ACTIVITY_ITCONC;
|
|
inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, conts_type);
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@<Parse column syntax@> =
|
|
if (Regexp::match(&mr, text, L"column of (%C+)")) {
|
|
results->constructor_code = COLUMN_ITCONC;
|
|
inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, conts_type);
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@<Parse table syntax@> =
|
|
if (Regexp::match(&mr, text, L"table of (%C+)")) {
|
|
results->constructor_code = TABLE_ITCONC;
|
|
inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, conts_type);
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@<Parse description syntax@> =
|
|
if (Regexp::match(&mr, text, L"description of (%C+)")) {
|
|
results->constructor_code = DESCRIPTION_ITCONC;
|
|
inter_type conts_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, conts_type);
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@<Parse relation syntax@> =
|
|
if (Regexp::match(&mr, text, L"relation of (%C+) to (%C+)")) {
|
|
results->constructor_code = RELATION_ITCONC;
|
|
inter_type X_type = InterTypes::parse_simple(T, eloc, mr.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, X_type);
|
|
if (E == NULL) {
|
|
inter_type Y_type = InterTypes::parse_simple(T, eloc, mr.exp[1], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, Y_type);
|
|
} else {
|
|
InterTypes::add_operand_to_isstd(results, T, InterTypes::unchecked());
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@<Parse rule or function syntax@> =
|
|
if ((Regexp::match(&mr, text, L"(function) (%c+?) -> (%c+)")) ||
|
|
(Regexp::match(&mr, text, L"(rule) (%c+?) -> (%c+)"))) {
|
|
if (Str::eq(mr.exp[0], I"function")) results->constructor_code = FUNCTION_ITCONC;
|
|
else results->constructor_code = RULE_ITCONC;
|
|
text_stream *from = mr.exp[1];
|
|
text_stream *to = mr.exp[2];
|
|
inter_error_message *returned_E = NULL;
|
|
if (Str::eq(from, I"void")) {
|
|
InterTypes::add_operand_to_isstd(results, T,
|
|
InterTypes::from_constructor_code(VOID_ITCONC));
|
|
} else {
|
|
match_results mr3 = Regexp::create_mr();
|
|
while (Regexp::match(&mr3, from, L" *(%C+) *(%c*)")) {
|
|
inter_type arg_type = InterTypes::parse_simple(T, eloc, mr3.exp[0], &E);
|
|
InterTypes::add_operand_to_isstd(results, T, arg_type);
|
|
if ((E) && (returned_E == NULL)) returned_E = E;
|
|
Str::copy(from, mr3.exp[1]);
|
|
}
|
|
}
|
|
if (Str::eq(to, I"void")) {
|
|
InterTypes::add_operand_to_isstd(results, T,
|
|
InterTypes::from_constructor_code(VOID_ITCONC));
|
|
} else {
|
|
inter_type res_type = InterTypes::parse_simple(T, eloc, to, &E);
|
|
if ((E) && (returned_E == NULL)) returned_E = E;
|
|
InterTypes::add_operand_to_isstd(results, T, res_type);
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
return returned_E;
|
|
}
|
|
|
|
@<Parse struct syntax@> =
|
|
if (Regexp::match(&mr, text, L"struct (%c+)")) {
|
|
results->constructor_code = STRUCT_ITCONC;
|
|
text_stream *elements = mr.exp[0];
|
|
inter_error_message *returned_E = NULL;
|
|
match_results mr3 = Regexp::create_mr();
|
|
while (Regexp::match(&mr3, elements, L" *(%C+) *(%c*)")) {
|
|
inter_type arg_type = InterTypes::parse_simple(T, eloc, mr3.exp[0], &E);
|
|
if ((E) && (returned_E == NULL)) returned_E = E;
|
|
Str::copy(elements, mr3.exp[1]);
|
|
InterTypes::add_operand_to_isstd(results, T, arg_type);
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
return returned_E;
|
|
}
|
|
|
|
@<Parse bare constructor-name syntax@> =
|
|
inter_type_constructor *itc = InterTypes::constructor_from_name(text);
|
|
if (itc) {
|
|
results->constructor_code = itc->constructor_ID;
|
|
if (itc->constructor_ID == VOID_ITCONC)
|
|
return InterErrors::quoted(I"'void' cannot be used as a type", text, eloc);
|
|
Regexp::dispose_of(&mr);
|
|
return NULL;
|
|
}
|
|
|
|
@<Parse bare typename syntax@> =
|
|
inter_symbol *typename_s = NULL;
|
|
if (Str::get_first_char(text) == '/') {
|
|
typename_s = InterSymbolsTable::wire_to_URL(
|
|
InterPackage::tree(InterSymbolsTable::package(T)), text, T);
|
|
if (typename_s == NULL)
|
|
return InterErrors::quoted(I"no typename at this URL", text, eloc);
|
|
} else {
|
|
typename_s = TextualInter::find_symbol_in_table(T, eloc, text, TYPENAME_IST, &E);
|
|
}
|
|
if (typename_s) {
|
|
results->constructor_code = EQUATED_ITCONC;
|
|
InterTypes::add_operand_to_isstd(results, T, InterTypes::from_type_name(typename_s));
|
|
Regexp::dispose_of(&mr);
|
|
return NULL;
|
|
}
|
|
if (E) {
|
|
Regexp::dispose_of(&mr);
|
|
return E;
|
|
}
|
|
|
|
@ Sometimes we want to allow only a simple type, and if so then we can return
|
|
an //inter_type//, which is less fuss. But we still write this as a wrapper
|
|
around the full //InterTypes::parse_semisimple// so that it can produce a
|
|
useful error message in response to a semisimple but not simple piece of syntax.
|
|
|
|
=
|
|
inter_type InterTypes::parse_simple(inter_symbols_table *T, inter_error_location *eloc,
|
|
text_stream *text, inter_error_message **E) {
|
|
if (Str::len(text) > 0) {
|
|
inter_semisimple_type_description parsed_description;
|
|
InterTypes::initialise_isstd(&parsed_description);
|
|
*E = InterTypes::parse_semisimple(text, T, eloc, &parsed_description);
|
|
if (*E) return InterTypes::unchecked();
|
|
if (parsed_description.constructor_code == EQUATED_ITCONC) {
|
|
inter_type type = InterTypes::from_TID(T, parsed_description.operand_TIDs[0]);
|
|
InterTypes::dispose_of_isstd(&parsed_description);
|
|
return type;
|
|
}
|
|
int over_complex = FALSE;
|
|
for (int i=0; i<parsed_description.arity; i++)
|
|
if (parsed_description.operand_TIDs[i] != UNCHECKED_ITCONC)
|
|
over_complex = TRUE;
|
|
if (over_complex) {
|
|
InterTypes::dispose_of_isstd(&parsed_description);
|
|
*E = InterErrors::quoted(I"type too complex", text, eloc);
|
|
return InterTypes::unchecked();
|
|
}
|
|
inter_type type = InterTypes::from_constructor_code(parsed_description.constructor_code);
|
|
InterTypes::dispose_of_isstd(&parsed_description);
|
|
return type;
|
|
}
|
|
return InterTypes::unchecked();
|
|
}
|
|
|
|
@h Writing to text.
|
|
We offer two functions here so that it's possible to force a typename to print
|
|
its full definition out: otherwise printing the typename |K_whatever| would
|
|
just print that name, |K_whatever|.
|
|
|
|
=
|
|
void InterTypes::write_type(OUTPUT_STREAM, inter_type type) {
|
|
if (type.type_name) {
|
|
InterSymbolsTable::write_symbol_URL(OUT, type.type_name);
|
|
} else {
|
|
InterTypes::write_type_longhand(OUT, type);
|
|
}
|
|
}
|
|
|
|
void InterTypes::write_typename_definition(OUTPUT_STREAM, inter_symbol *type_name) {
|
|
InterTypes::write_type_longhand(OUT, InterTypes::from_type_name(type_name));
|
|
}
|
|
|
|
@ Both of which use this:
|
|
|
|
=
|
|
void InterTypes::write_type_longhand(OUTPUT_STREAM, inter_type type) {
|
|
inter_type_constructor *itc = InterTypes::constructor(type);
|
|
WRITE("%S", itc->constructor_keyword);
|
|
switch (itc->constructor_ID) {
|
|
case EQUATED_ITCONC:
|
|
InterTypes::write_type(OUT, InterTypes::type_operand(type, 0));
|
|
break;
|
|
case DESCRIPTION_ITCONC:
|
|
case COLUMN_ITCONC:
|
|
case RULEBOOK_ITCONC:
|
|
case LIST_ITCONC:
|
|
WRITE(" of ");
|
|
InterTypes::write_type(OUT, InterTypes::type_operand(type, 0));
|
|
break;
|
|
case ACTIVITY_ITCONC:
|
|
WRITE(" on ");
|
|
InterTypes::write_type(OUT, InterTypes::type_operand(type, 0));
|
|
break;
|
|
case RELATION_ITCONC:
|
|
WRITE(" of ");
|
|
InterTypes::write_type(OUT, InterTypes::type_operand(type, 0));
|
|
WRITE(" to ");
|
|
InterTypes::write_type(OUT, InterTypes::type_operand(type, 1));
|
|
break;
|
|
case FUNCTION_ITCONC:
|
|
case RULE_ITCONC: {
|
|
int arity = InterTypes::type_arity(type);
|
|
for (int i=0; i<arity; i++) {
|
|
WRITE(" ");
|
|
if (i == arity - 1) WRITE("-> ");
|
|
InterTypes::write_type(OUT, InterTypes::type_operand(type, i));
|
|
}
|
|
break;
|
|
}
|
|
case STRUCT_ITCONC: {
|
|
int arity = InterTypes::type_arity(type);
|
|
for (int i=0; i<arity; i++) {
|
|
WRITE(" ");
|
|
InterTypes::write_type(OUT, InterTypes::type_operand(type, i));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@h Typechecking.
|
|
These are easy enough:
|
|
|
|
=
|
|
int InterTypes::is_enumerated(inter_type type) {
|
|
inter_type_constructor *itc = InterTypes::constructor(type);
|
|
if (itc->is_enumerated) return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
int InterTypes::literal_is_in_range(long long int N, inter_type type) {
|
|
inter_type_constructor *itc = InterTypes::constructor(type);
|
|
if ((N < itc->min_value) || (N > itc->max_value)) return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int InterTypes::unsigned_literal_is_in_range(long long int N, inter_type type) {
|
|
inter_type_constructor *itc = InterTypes::constructor(type);
|
|
if ((N < 0) || (N > itc->max_value - itc->min_value)) return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
@ This is what matters: whether we allow a value of type |A| to be used where
|
|
a value of type |B| is expected.
|
|
|
|
Anything can be used as |unchecked|, and |unchecked| can be used as anything.
|
|
|
|
Otherwise, they must both be base types, or both constructed: and then we
|
|
split into four cases as to whether they are the same or different.
|
|
|
|
=
|
|
inter_error_message *InterTypes::can_be_used_as(inter_type A, inter_type B,
|
|
text_stream *S, inter_error_location *eloc) {
|
|
inter_type_constructor *A_itc = InterTypes::constructor(A);
|
|
inter_type_constructor *B_itc = InterTypes::constructor(B);
|
|
|
|
if ((A_itc->constructor_ID == UNCHECKED_ITCONC) ||
|
|
(B_itc->constructor_ID == UNCHECKED_ITCONC))
|
|
return NULL;
|
|
|
|
if (A_itc->is_base != B_itc->is_base) @<Throw type mismatch error@>;
|
|
|
|
if (A_itc->is_base) {
|
|
if (A_itc->constructor_ID != B_itc->constructor_ID) @<Different base@>
|
|
else @<Same base@>;
|
|
} else {
|
|
if (A_itc->constructor_ID != B_itc->constructor_ID) @<Different proper constructor@>
|
|
else @<Same proper constructor@>;
|
|
}
|
|
}
|
|
|
|
@ This expresses that |int2 <= int8 <= int16 <= int32|.
|
|
|
|
@<Different base@> =
|
|
switch (A_itc->constructor_ID) {
|
|
case INT2_ITCONC:
|
|
if ((B_itc->constructor_ID == INT8_ITCONC) ||
|
|
(B_itc->constructor_ID == INT16_ITCONC) ||
|
|
(B_itc->constructor_ID == INT32_ITCONC)) return NULL;
|
|
break;
|
|
case INT8_ITCONC:
|
|
if ((B_itc->constructor_ID == INT16_ITCONC) ||
|
|
(B_itc->constructor_ID == INT32_ITCONC)) return NULL;
|
|
break;
|
|
case INT16_ITCONC:
|
|
if (B_itc->constructor_ID == INT32_ITCONC) return NULL;
|
|
break;
|
|
}
|
|
@<Throw type mismatch error@>;
|
|
|
|
@ Enumerated types, if named, can be declared as subtypes of each other. This
|
|
is used by Inform to make the enumerated type for "vehicle" a subtype of the
|
|
enumerated type for "thing", for example.
|
|
|
|
@<Same base@> =
|
|
if (A_itc->constructor_ID == ENUM_ITCONC) {
|
|
inter_symbol *typenameB_s = B.type_name;
|
|
inter_symbol *typenameA_s = A.type_name;
|
|
if ((typenameB_s) && (typenameA_s) &&
|
|
(TypenameInstruction::is_a(typenameA_s, typenameB_s) == FALSE))
|
|
@<Throw type mismatch error@>;
|
|
}
|
|
return NULL;
|
|
|
|
@ Different proper constructors never match.
|
|
|
|
@<Different proper constructor@> =
|
|
@<Throw type mismatch error@>;
|
|
|
|
@ If the same proper constructor is used, the question is then whether the
|
|
operands match. For example, |list of int2| can be used as |list of int32|
|
|
but not vice versa: that's a covariant operand. But |function int2 -> text|
|
|
cannot be used as |function int32 -> text|, it's the other way around: that
|
|
is an example of contravariance. In the simple type system of Inter, only
|
|
function arguments are contravariant.
|
|
|
|
@<Same proper constructor@> =
|
|
inter_error_message *operand_E = NULL;
|
|
switch (A_itc->constructor_ID) {
|
|
case STRUCT_ITCONC:
|
|
case FUNCTION_ITCONC: {
|
|
inter_symbol *typename_A = A.type_name;
|
|
inter_symbol *typename_B = B.type_name;
|
|
if ((typename_A) && (typename_B)) {
|
|
if (InterTypes::type_arity(A) != InterTypes::type_arity(B))
|
|
@<Throw type mismatch error@>;
|
|
int arity = InterTypes::type_arity(A);
|
|
for (int i=0; i<arity; i++) {
|
|
int covariant = TRUE;
|
|
if ((A_itc->constructor_ID == FUNCTION_ITCONC) && (i<arity-1))
|
|
covariant = FALSE;
|
|
if (covariant)
|
|
operand_E = InterTypes::can_be_used_as(InterTypes::type_operand(A, i),
|
|
InterTypes::type_operand(B, i), S, eloc);
|
|
else
|
|
operand_E = InterTypes::can_be_used_as(InterTypes::type_operand(B, i),
|
|
InterTypes::type_operand(A, i), S, eloc);
|
|
if (operand_E) @<Throw type mismatch error@>;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
for (int i=0; i<A_itc->arity; i++) {
|
|
operand_E = InterTypes::can_be_used_as(InterTypes::type_operand(A, i),
|
|
InterTypes::type_operand(B, i), S, eloc);
|
|
if (operand_E) @<Throw type mismatch error@>;
|
|
}
|
|
break;
|
|
}
|
|
return NULL;
|
|
|
|
@<Throw type mismatch error@> =
|
|
text_stream *err = Str::new();
|
|
WRITE_TO(err, "value '%S' has type ", S);
|
|
InterTypes::write_type(err, A);
|
|
WRITE_TO(err, " which is not a ");
|
|
InterTypes::write_type(err, B);
|
|
return InterErrors::plain(err, eloc);
|
|
|
|
@h The type of a defined symbol.
|
|
Note that a typename can be used as a value, and that if so, its type is |unchecked|.
|
|
|
|
=
|
|
inter_type InterTypes::of_symbol(inter_symbol *symb) {
|
|
if (symb == NULL) return InterTypes::unchecked();
|
|
inter_tree_node *D = InterSymbol::definition(symb);
|
|
if (D == NULL) return InterTypes::unchecked();
|
|
if (InterSymbol::defined_elsewhere(symb)) return InterTypes::unchecked();
|
|
inter_construct *IC = NULL;
|
|
if (InterInstruction::get_construct(D, &IC)) return InterTypes::unchecked();
|
|
if (IC->TID_field >= 0) return InterTypes::from_TID_in_field(D, IC->TID_field);
|
|
return InterTypes::unchecked();
|
|
}
|
|
|
|
int InterTypes::expresses_value(inter_symbol *symb) {
|
|
inter_tree_node *D = InterSymbol::definition(symb);
|
|
if (D) {
|
|
inter_construct *IC = NULL;
|
|
if (InterInstruction::get_construct(D, &IC)) return FALSE;
|
|
if (IC->construct_ID == TYPENAME_IST) return TRUE;
|
|
if (IC->TID_field >= 0) return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|