foundation-module: Chapter 2: Nowebify.

This commit is contained in:
AwesomeAdam54321 2024-03-09 10:18:53 +08:00
parent b3ba94b5a6
commit 830118d644
10 changed files with 643 additions and 600 deletions

View file

@ -3,31 +3,32 @@
To write to the debugging log, a plain text file which traces what
we're doing, in order to assist those lost souls debugging it.
@h The DL stream.
@ \section{The DL stream.}
The debugging log file occupies the following stream:
=
<<*>>=
text_stream debug_log_file_struct; /* The actual debugging log file */
text_stream *debug_log_file = &debug_log_file_struct; /* The actual debugging log file */
@h Macros.
@ \section{Macros.}
"The most effective debugging tool is still careful thought, coupled with
judiciously placed print statements" (Brian Kernighan).
To write to the debugging log, we must in principle write to a stream called
|DL|. In practice we more often use a pair of pseudo-functions called |LOG|
and |LOGIF|, which are macros defined in the section on Streams. For
[[DL]]. In practice we more often use a pair of pseudo-functions called [[LOG]]
and [[LOGIF]], which are macros defined in the section on Streams. For
instance, the pseudo-function-call
= (text)
LOGIF(WHATEVER, "Heading %d skipped\n", n);
=
prints the line in question to the debugging log only if the aspect |WHATEVER|
is currently switched on. Plain |LOG| does the same, but unconditionally.
@d LOG_INDENT STREAM_INDENT(DL)
@d LOG_OUTDENT STREAM_OUTDENT(DL)
prints the line in question to the debugging log only if the aspect [[WHATEVER]]
is currently switched on. Plain [[LOG]] does the same, but unconditionally.
@h Debugging aspects.
<<*>>=
#define LOG_INDENT STREAM_INDENT(DL)
#define LOG_OUTDENT STREAM_OUTDENT(DL)
@ \section{Debugging aspects.}
There are many different things which can go into the debugging file, or
need not: for Inform even a simple half-page source can result in a
debugging log 5MB in size, so we generally don't want everything included
@ -37,7 +38,7 @@ A "debugging aspect" is a category of information that can be included, or
not, as we please. Each has a unique number and a name of up to three words in
length.
=
<<*>>=
typedef struct debugging_aspect {
struct text_stream *hyphenated_name; /* e.g., "memory-usage" */
struct text_stream *negated_name; /* e.g., "no-memory-usage" */
@ -48,31 +49,32 @@ typedef struct debugging_aspect {
} debugging_aspect;
@ And now we must define all those constants and names. Note that the
|TRUE| or |FALSE| settings below are the defaults, and apply unless the
source says otherwise. The |alternate| settings are those used in
[[TRUE]] or [[FALSE]] settings below are the defaults, and apply unless the
source says otherwise. The [[alternate]] settings are those used in
trace-sentences mode, that is, between asterisk sentences.
@e DEBUGGING_LOG_INCLUSIONS_DA from 0
@e SHELL_USAGE_DA
@e MEMORY_USAGE_DA
@e TEXT_FILES_DA
<<*>>=
enum DEBUGGING_LOG_INCLUSIONS_DA from 0
enum SHELL_USAGE_DA
enum MEMORY_USAGE_DA
enum TEXT_FILES_DA
=
<<*>>=
int das_created = FALSE;
debugging_aspect the_debugging_aspects[NO_DEFINED_DA_VALUES];
void Log::declare_aspect(int a, wchar_t *name, int def, int alt) {
if (das_created == FALSE) {
das_created = TRUE;
@<Empty the aspects table@>;
<<Empty the aspects table@>;
}
if ((a < 0) || (a >= NO_DEFINED_DA_VALUES)) internal_error("aspect out of range");
debugging_aspect *da = &(the_debugging_aspects[a]);
@<Set up the new aspect@>;
<<Set up the new aspect@>;
}
@<Empty the aspects table@> =
<<Empty the aspects table>>=
for (int a=0; a<NO_DEFINED_DA_VALUES; a++) {
debugging_aspect *da = &(the_debugging_aspects[a]);
da->hyphenated_name = Str::new();
@ -82,7 +84,7 @@ void Log::declare_aspect(int a, wchar_t *name, int def, int alt) {
da->alternate = FALSE;
}
@<Set up the new aspect@> =
<<Set up the new aspect>>=
WRITE_TO(da->negated_name, "no-");
for (int i=0; name[i]; i++) {
wchar_t c = name[i];
@ -99,13 +101,13 @@ behind the scenes; but such a log file is often buffered by the filing system,
so that a sudden crash of Inform may result in the loss of recent data written to
the log. Which is a pity, since this is exactly the most useful evidence as to
the cause of the crash in the first place. Accordingly, we fairly often
|fflush| the debug log file, forcing any buffered output to be written.
[[fflush]] the debug log file, forcing any buffered output to be written.
In this rest of this section, we always assume that |DL| is open. Note that it
is possible this has been switched to be |stdout|, or even that it is
In this rest of this section, we always assume that [[DL]] is open. Note that it
is possible this has been switched to be [[stdout]], or even that it is
temporarily the sentence tracing file: but we don't care.
=
<<*>>=
filename *debug_log_filename = NULL;
filename *Log::get_debug_log_filename(void) {
@ -144,12 +146,12 @@ void Log::close(void) {
}
}
@h Subheadings.
@ \section{Subheadings.}
To provide signposts in what is otherwise a huge amorphous pile of text,
the debugging log can be divided into "phases", subdivided into "stages".
This is how.
=
<<*>>=
char debug_log_phase[32];
int debug_log_subheading = 1;
void Log::new_phase(char *p, text_stream *q) {
@ -167,14 +169,14 @@ void Log::new_stage(text_stream *p) {
STREAM_FLUSH(DL);
}
@h Aspects.
@ \section{Aspects.}
As mentioned above, a wide range of activities can be logged to the debugging
log: these are called "aspects" and we can switch logging of them off or on
independently. The following routine tests whether a given aspect is currently
being logged, and is used by our main macros. Aspect 0 mandates writing to the
debug log, and is used when errors occur.
=
<<*>>=
int Log::aspect_switched_on(int aspect) {
int decision = the_debugging_aspects[aspect].on_or_off;
if (aspect == DEBUGGING_LOG_INCLUSIONS_DA) decision = TRUE;
@ -188,7 +190,7 @@ void Log::set_aspect(int aspect, int state) {
@ We sometimes want to switch everything on, or switch everything off:
=
<<*>>=
void Log::set_all_aspects(int new_state) {
if (DL) LOGIF(DEBUGGING_LOG_INCLUSIONS, "Set debugging aspect: everything -> %s\n",
new_state?"TRUE":"FALSE");
@ -200,13 +202,13 @@ void Log::set_all_aspects(int new_state) {
@ We also want the ability to change debugging log settings from the command
line; the command line form is derived from the textual form by replacing
every space with a hyphen: for instance, |property-provision|.
every space with a hyphen: for instance, [[property-provision]].
We also recognise |no-property-provision| to switch this off again,
|everything| and |nothing| with the obvious meanings, and |list| to
print out a list of debugging aspects to |STDOUT|.
We also recognise [[no-property-provision]] to switch this off again,
[[everything]] and [[nothing]] with the obvious meanings, and [[list]] to
print out a list of debugging aspects to [[STDOUT]].
=
<<*>>=
int Log::set_aspect_from_command_line(text_stream *name, int give_error) {
int list_mode = FALSE;
if (Str::eq_wide_string(name, L"everything")) { Log::set_all_aspects(TRUE); return TRUE; }
@ -233,11 +235,11 @@ int Log::set_aspect_from_command_line(text_stream *name, int give_error) {
return FALSE;
}
@h The starred trace.
@ \section{The starred trace.}
This is a useful way to switch into a more detailed view, and then switch
out again without having lost our earlier settings.
=
<<*>>=
void Log::tracing_on(int starred, text_stream *heading) {
if (starred) {
LOG("\n*** Entering sentence trace mode: %S ***\n", heading);
@ -252,12 +254,12 @@ void Log::tracing_on(int starred, text_stream *heading) {
}
}
@h Wrapping up.
@ \section{Wrapping up.}
At the end of the debugging log we list what was in it, mostly to provide
the reader with a list of other things which could have been put into it,
but weren't.
=
<<*>>=
void Log::show_debugging_settings_with_state(int state) {
int c = 0;
for (int i=0; i<NO_DEFINED_DA_VALUES; i++) {

View file

@ -3,13 +3,13 @@
A simple implementation for a flexible-sized dictionary of key-value
pairs.
@h Storage.
@ \section{Storage.}
There's nothing fancy here. A "dictionary" is a hash table allowing reasonably
efficient lookup: it's a correspondence between "keys", which are texts, and
pointers to some external structure. Often these will also be texts, but
not necessarily.
=
<<*>>=
typedef struct dictionary {
int textual; /* values are texts? */
int no_entries; /* total number of key-value pairs currently stored here */
@ -25,11 +25,11 @@ typedef struct dict_entry {
struct dict_entry *next_in_entry;
} dict_entry;
@h Creation.
@ \section{Creation.}
Dictionaries can have arbitrary size, in that they expand as needed, but for
efficiency's sake the caller can set them up with an initial size of her choice.
=
<<*>>=
dictionary *Dictionaries::new(int S, int textual) {
if (S < 2) internal_error("dictionary too small");
dictionary *D = CREATE(dictionary);
@ -45,9 +45,9 @@ dictionary *Dictionaries::new(int S, int textual) {
return D;
}
@h Logging.
@ \section{Logging.}
=
<<*>>=
void Dictionaries::log(OUTPUT_STREAM, dictionary *D) {
WRITE("Dictionary:\n"); INDENT;
for (int i=0; i<D->hash_table_size; i++) {
@ -61,13 +61,13 @@ void Dictionaries::log(OUTPUT_STREAM, dictionary *D) {
OUTDENT;
}
@h Hashing.
@ \section{Hashing.}
The whole point of a hash table is that it crudely sorts the contents by a rough
indication of the key values. This crude indication is the hash value, calculated
here. If there are |N| slots in the dictionary table, this tells us which slot
(from 0 to |N-1|) a given key value belongs in.
here. If there are [[N]] slots in the dictionary table, this tells us which slot
(from 0 to [[N-1]]) a given key value belongs in.
=
<<*>>=
int Dictionaries::hash(text_stream *K, int N) {
unsigned int hash = 0;
LOOP_THROUGH_TEXT(P, K)
@ -75,13 +75,13 @@ int Dictionaries::hash(text_stream *K, int N) {
return (int) (hash % ((unsigned int) N));
}
@h Create, find, destroy.
@ \section{Create, find, destroy.}
These three fundamental operations locate the dictionary entry structure for
a given key value, and then do something to/with it. Note that these pointers
remain valid only until somebody writes a new value into the dictionary;
so be careful if thread safety's an issue.
=
<<*>>=
dict_entry *Dictionaries::find(dictionary *D, text_stream *K) {
return Dictionaries::find_p(D, K, 0);
}
@ -93,10 +93,10 @@ void Dictionaries::destroy(dictionary *D, text_stream *K) {
}
@ A nuisance we have to live with is that we often want to express the key
as wide text (so that we can use literals like |L"my-key"|) instead of text
streams. So we also offer versions suffixed |_literal|:
as wide text (so that we can use literals like [[L"my-key"]]) instead of text
streams. So we also offer versions suffixed [[_literal]]:
=
<<*>>=
dict_entry *Dictionaries::find_literal(dictionary *D, wchar_t *lit) {
TEMPORARY_TEXT(K)
WRITE_TO(K, "%w", lit);
@ -118,19 +118,19 @@ void Dictionaries::destroy_literal(dictionary *D, wchar_t *lit) {
DISCARD_TEXT(K)
}
@ So, then, find an entry (if |change| is |0|), create it (if |+1|) or delete
it (if |-1|).
@ So, then, find an entry (if [[change| is |0|), create it (if |+1]]) or delete
it (if [[-1]]).
=
<<*>>=
dict_entry *Dictionaries::find_p(dictionary *D, text_stream *K, int change) {
if (D == NULL) @<Handle the null dictionary@>;
if (change == 1) @<Expand the dictionary if necessary@>;
@<Work within the existing dictionary@>;
if (D == NULL) <<Handle the null dictionary>>;
if (change == 1) <<Expand the dictionary if necessary>>;
<<Work within the existing dictionary>>;
}
@ It's legal to perform a find on the null dictionary: the answer's always "no".
@<Handle the null dictionary@> =
<<Handle the null dictionary>>=
if (change == 0) return NULL;
internal_error("tried to create or destroy in a null dictionary");
@ -139,7 +139,7 @@ same number of slots as there are entries in the dictionary; that way, each
slot will have an average of one or fewer entries. When we exceed this
ideal population, we double the dictionary's capacity.
@<Expand the dictionary if necessary@> =
<<Expand the dictionary if necessary>>=
if (D->no_entries > D->hash_table_size) {
dictionary *D2 = Dictionaries::new(2*D->hash_table_size, D->textual);
for (int i=0; i<D->hash_table_size; i++)
@ -154,23 +154,23 @@ ideal population, we double the dictionary's capacity.
D->hash_table = D2->hash_table;
}
@<Work within the existing dictionary@> =
<<Work within the existing dictionary>>=
dict_entry *last_E = NULL;
for (dict_entry *E = &(D->hash_table[Dictionaries::hash(K, D->hash_table_size)]);
E; E = E->next_in_entry) {
last_E = E;
if (E->vacant) {
if (change == 1) { @<Make E the new entry@>; return E; }
if (change == 1) { <<Make E the new entry>>; return E; }
} else {
if (Str::eq(K, E->key)) {
if (change == -1) @<Destroy the E entry@>;
if (change == -1) <<Destroy the E entry>>;
return E;
}
}
}
if (change == 1) {
dict_entry *E = CREATE(dict_entry);
@<Make E the new entry@>;
<<Make E the new entry>>;
last_E->next_in_entry = E;
return E;
}
@ -179,7 +179,7 @@ ideal population, we double the dictionary's capacity.
@ When creating text values, we want them to be empty strings rather than null
strings, so that printing to them will work.
@<Make E the new entry@> =
<<Make E the new entry>>=
E->vacant = FALSE;
if (D->textual) E->value = Str::new(); else E->value = NULL;
E->key = Str::duplicate(K);
@ -189,16 +189,16 @@ strings, so that printing to them will work.
Careful: it would not be thread-safe to allow different threads to use the
same dictionary if deletions are a possibility.
@<Destroy the E entry@> =
<<Destroy the E entry>>=
E->vacant = TRUE; D->no_entries--;
if ((D->textual) && (E->value)) Str::dispose_of(E->value);
E->value = NULL;
@h Accessing entries.
@ \section{Accessing entries.}
Eventually we're going to want the value. In principle we could be storing
values which are arbitrary pointers, so we have to use void pointers:
=
<<*>>=
void *Dictionaries::value_for_entry(dict_entry *de) {
if (de) return de->value;
return NULL;
@ -241,7 +241,7 @@ void Dictionaries::write_value_literal(dictionary *D, wchar_t *key, void *val) {
@ But the commonest use case is that the dictionary stores texts as values,
so we provide convenient wrappers with the correct C types.
=
<<*>>=
text_stream *Dictionaries::create_text(dictionary *D, text_stream *key) {
if (D == NULL) internal_error("wrote to null dictionary");
if (D->textual == FALSE) internal_error("pointy dictionary accessed as textual");
@ -256,9 +256,9 @@ text_stream *Dictionaries::create_text_literal(dictionary *D, wchar_t *lit) {
}
@ We only need a read operation, because the caller can write to the dictionary
entry by reading the text pointer and then using |WRITE_TO|.
entry by reading the text pointer and then using [[WRITE_TO]].
=
<<*>>=
text_stream *Dictionaries::get_text(dictionary *D, text_stream *key) {
if (D == NULL) return NULL;
if (D->textual == FALSE) internal_error("pointy dictionary accessed as textual");
@ -275,11 +275,11 @@ text_stream *Dictionaries::get_text_literal(dictionary *D, wchar_t *lit) {
return (text_stream *) E->value;
}
@h Disposal.
@ \section{Disposal.}
If a dictionary was only needed temporarily then we should dispose of it
and free the memory when done:
=
<<*>>=
void Dictionaries::dispose_of(dictionary *D) {
if (D->textual)
for (int i=0; i<D->hash_table_size; i++)

View file

@ -8,54 +8,55 @@ exception because it's the module which defines the memory manager: class
declarations have to come after that point in the tangled code. But now
here we are.
@e chapter_md_CLASS
@e command_line_switch_CLASS
@e debugging_aspect_CLASS
@e dict_entry_CLASS
@e dictionary_CLASS
@e ebook_chapter_CLASS
@e ebook_CLASS
@e ebook_datum_CLASS
@e ebook_image_CLASS
@e ebook_mark_CLASS
@e ebook_page_CLASS
@e ebook_volume_CLASS
@e filename_CLASS
@e heterogeneous_tree_CLASS
@e HTML_file_state_CLASS
@e HTML_tag_CLASS
@e JSON_pair_requirement_CLASS
@e JSON_requirement_CLASS
@e JSON_single_requirement_CLASS
@e JSON_type_CLASS
@e JSON_value_CLASS
@e linked_list_CLASS
@e linked_list_item_CLASS
@e match_avinue_CLASS
@e match_trie_CLASS
@e method_CLASS
@e method_set_CLASS
@e module_CLASS
@e module_search_CLASS
@e pathname_CLASS
@e preprocessor_macro_CLASS
@e preprocessor_macro_parameter_CLASS
@e preprocessor_variable_CLASS
@e preprocessor_variable_set_CLASS
@e scan_directory_CLASS
@e section_md_CLASS
@e semantic_version_number_holder_CLASS
@e semver_range_CLASS
@e stopwatch_timer_CLASS
@e string_storage_area_CLASS
@e text_stream_CLASS
@e tree_node_CLASS
@e tree_node_type_CLASS
@e tree_type_CLASS
@e web_bibliographic_datum_CLASS
@e web_md_CLASS
<<*>>=
enum chapter_md_CLASS
enum command_line_switch_CLASS
enum debugging_aspect_CLASS
enum dict_entry_CLASS
enum dictionary_CLASS
enum ebook_chapter_CLASS
enum ebook_CLASS
enum ebook_datum_CLASS
enum ebook_image_CLASS
enum ebook_mark_CLASS
enum ebook_page_CLASS
enum ebook_volume_CLASS
enum filename_CLASS
enum heterogeneous_tree_CLASS
enum HTML_file_state_CLASS
enum HTML_tag_CLASS
enum JSON_pair_requirement_CLASS
enum JSON_requirement_CLASS
enum JSON_single_requirement_CLASS
enum JSON_type_CLASS
enum JSON_value_CLASS
enum linked_list_CLASS
enum linked_list_item_CLASS
enum match_avinue_CLASS
enum match_trie_CLASS
enum method_CLASS
enum method_set_CLASS
enum module_CLASS
enum module_search_CLASS
enum pathname_CLASS
enum preprocessor_macro_CLASS
enum preprocessor_macro_parameter_CLASS
enum preprocessor_variable_CLASS
enum preprocessor_variable_set_CLASS
enum scan_directory_CLASS
enum section_md_CLASS
enum semantic_version_number_holder_CLASS
enum semver_range_CLASS
enum stopwatch_timer_CLASS
enum string_storage_area_CLASS
enum text_stream_CLASS
enum tree_node_CLASS
enum tree_node_type_CLASS
enum tree_type_CLASS
enum web_bibliographic_datum_CLASS
enum web_md_CLASS
=
<<*>>=
DECLARE_CLASS(chapter_md)
DECLARE_CLASS(command_line_switch)
DECLARE_CLASS(debugging_aspect)

View file

@ -3,15 +3,16 @@
A simple implementation for single-linked lists of objects allocated by Foundation's
memory manager, and for last-in-first-out stacks of same.
@h Implementation.
@ \section{Implementation.}
Basically, there's a head structure, which points to a chain of body structures,
each linking to the next. But to reduce memory manager overhead, we're going to store
the first few body structures inside the head structure: that way, for a list of just
a few items, only one call to the memory manager is needed.
@d NO_LL_EARLY_ITEMS 32
<<*>>=
#define NO_LL_EARLY_ITEMS 32
=
<<*>>=
typedef struct linked_list {
struct linked_list_item *first_list_item;
struct linked_list_item *last_list_item;
@ -26,7 +27,7 @@ typedef struct linked_list_item {
struct linked_list_item *next_list_item;
} linked_list_item;
@ =
<<*>>=
linked_list *LinkedLists::new(void) {
linked_list *ll = CREATE(linked_list);
LinkedLists::empty(ll);
@ -43,7 +44,7 @@ void LinkedLists::empty(linked_list *ll) {
@ The following runs in constant time, i.e., performs no loops. In general we
want speed rather than memory efficiency.
=
<<*>>=
void LinkedLists::add(linked_list *L, void *P, int to_end) {
if (L == NULL) internal_error("null list");
linked_list_item *item = NULL;
@ -71,7 +72,7 @@ void LinkedLists::add(linked_list *L, void *P, int to_end) {
@ Because of the direction of the links, only removing from the front is quick:
=
<<*>>=
void *LinkedLists::remove_from_front(linked_list *L) {
if (L == NULL) internal_error("null list");
linked_list_item *front = L->first_list_item;
@ -84,10 +85,10 @@ void *LinkedLists::remove_from_front(linked_list *L) {
@ It's rather slower to delete from a known position in the middle:
=
<<*>>=
void *LinkedLists::delete(int N, linked_list *L) {
if (L == NULL) internal_error("null list");
if ((N < 0) || (N >= L->linked_list_length)) internal_error("index not valid");
if ((N < 0) [[]] (N >= L->linked_list_length)) internal_error("index not valid");
if (N == 0) return LinkedLists::remove_from_front(L);
for (linked_list_item *item = L->first_list_item; item; item = item->next_list_item) {
@ -105,10 +106,10 @@ void *LinkedLists::delete(int N, linked_list *L) {
return NULL;
}
@ And indeed to insert at a known position, where |N| being 0 means the front
of the list, |N| being 1 means "after the first item", and so on.
@ And indeed to insert at a known position, where [[N]] being 0 means the front
of the list, [[N]] being 1 means "after the first item", and so on.
=
<<*>>=
void LinkedLists::insert(linked_list *L, int N, void *P) {
if (N <= 0) LinkedLists::add(L, P, FALSE);
else {
@ -134,9 +135,9 @@ void LinkedLists::insert(linked_list *L, int N, void *P) {
}
}
@h A function call API.
@ \section{A function call API.}
=
<<*>>=
int LinkedLists::len(linked_list *L) {
return L?(L->linked_list_length):0;
}
@ -144,14 +145,14 @@ linked_list_item *LinkedLists::first(linked_list *L) {
return L?(L->first_list_item):NULL;
}
void *LinkedLists::entry(int N, linked_list *L) {
if ((N < 0) || (L == NULL) || (N >= L->linked_list_length)) return NULL;
if ((N < 0) [[| (L == NULL) |]] (N >= L->linked_list_length)) return NULL;
for (linked_list_item *I = L->first_list_item; I; I = I->next_list_item)
if (N-- == 0)
return I->item_contents;
return NULL;
}
void LinkedLists::set_entry(int N, linked_list *L, void *P) {
if ((N < 0) || (L == NULL) || (N >= L->linked_list_length)) return;
if ((N < 0) [[| (L == NULL) |]] (N >= L->linked_list_length)) return;
for (linked_list_item *I = L->first_list_item; I; I = I->next_list_item)
if (N-- == 0) {
I->item_contents = P; return;
@ -167,90 +168,94 @@ void *LinkedLists::content(linked_list_item *I) {
return I?(I->item_contents):NULL;
}
@h A macro-ized API.
@ \section{A macro-ized API.}
These intentionally hide the implementation. The difference between
|FIRST_IN_LINKED_LIST| and |FIRST_ITEM_IN_LINKED_LIST| is that one returns
[[FIRST_IN_LINKED_LIST]] and [[FIRST_ITEM_IN_LINKED_LIST]] is that one returns
the first structure in the list, and the other returns the first
|linked_list_item| chunk in the list. From the latter you can make the
former using |CONTENT_IN_ITEM|, but not vice versa. The same object
[[linked_list_item]] chunk in the list. From the latter you can make the
former using [[CONTENT_IN_ITEM]], but not vice versa. The same object
may be listed in many different lists, so if all you have is the object,
you don't know its place in the list.
@d NEW_LINKED_LIST(T)
<<*>>=
#define NEW_LINKED_LIST(T)
(LinkedLists::new())
@d FIRST_ITEM_IN_LINKED_LIST(T, L)
#define FIRST_ITEM_IN_LINKED_LIST(T, L)
(LinkedLists::first(L))
@d ENTRY_IN_LINKED_LIST(N, T, L)
#define ENTRY_IN_LINKED_LIST(N, T, L)
((T *) (LinkedLists::entry(N, L)))
@d DELETE_FROM_LINKED_LIST(N, T, L)
#define DELETE_FROM_LINKED_LIST(N, T, L)
((T *) (LinkedLists::delete(N, L)))
@d LAST_ITEM_IN_LINKED_LIST(T, L)
#define LAST_ITEM_IN_LINKED_LIST(T, L)
(LinkedLists::last(L))
@d NEXT_ITEM_IN_LINKED_LIST(I, T)
#define NEXT_ITEM_IN_LINKED_LIST(I, T)
(LinkedLists::next(I))
@d CONTENT_IN_ITEM(I, T)
#define CONTENT_IN_ITEM(I, T)
((T *) (LinkedLists::content(I)))
@d ADD_TO_LINKED_LIST(I, T, L)
#define ADD_TO_LINKED_LIST(I, T, L)
LinkedLists::add(L, (void *) (I), TRUE)
@d FIRST_IN_LINKED_LIST(T, L)
#define FIRST_IN_LINKED_LIST(T, L)
((T *) (LinkedLists::content(LinkedLists::first(L))))
@d LAST_IN_LINKED_LIST(T, L)
#define LAST_IN_LINKED_LIST(T, L)
((T *) (LinkedLists::content(LinkedLists::last(L))))
@ The following macro requires slight care to use: the list |L| needs to be
calculable without side-effects. There's no such worry over |P| or |T|, since
@ The following macro requires slight care to use: the list [[L]] needs to be
calculable without side-effects. There's no such worry over [[P]] or [[T]], since
they're just identifier names: the loop variable and the type name respectively.
Note that the loop variable |P| must already be defined. Inside the loop body,
a new variable will also then exist, |P_item|, to refer to the item which
points to |P|. This allows us to iterate despite the comments above.
Note that the loop variable [[P]] must already be defined. Inside the loop body,
a new variable will also then exist, [[P_item]], to refer to the item which
points to [[P]]. This allows us to iterate despite the comments above.
@d LOOP_OVER_LINKED_LIST(P, T, L)
<<*>>=
#define LOOP_OVER_LINKED_LIST(P, T, L)
for (linked_list_item *P##_item = (P = FIRST_IN_LINKED_LIST(T, L), FIRST_ITEM_IN_LINKED_LIST(T, L));
P##_item;
P##_item = (P = CONTENT_IN_ITEM(NEXT_ITEM_IN_LINKED_LIST(P##_item, T), T), NEXT_ITEM_IN_LINKED_LIST(P##_item, T)))
@h LIFO stacks.
@ \section{LIFO stacks.}
The above gives us an almost free implementation of LIFO, last-in-first-out,
stacks, where we represent a stack as a linked list whose first entry is at
the front. To push an item, we add it at the front; to pull, we remove the
front item.
We provide an abstract type name for these stacks, even though they're the
exact same structure. For reasons to do with the way |typedef| works in C,
exact same structure. For reasons to do with the way [[typedef]] works in C,
it is awkward to typedef the two names together, so we'll simply use the
preprocessor:
@d lifo_stack linked_list
<<*>>=
#define lifo_stack linked_list
@ Otherwise, it's macros all the way:
@d NEW_LIFO_STACK(T)
<<*>>=
#define NEW_LIFO_STACK(T)
(LinkedLists::new())
@d PUSH_TO_LIFO_STACK(I, T, L)
#define PUSH_TO_LIFO_STACK(I, T, L)
LinkedLists::add((L), (void *) (I), FALSE)
@d PULL_FROM_LIFO_STACK(T, L)
#define PULL_FROM_LIFO_STACK(T, L)
((T *) LinkedLists::remove_from_front(L))
@d POP_LIFO_STACK(T, L)
#define POP_LIFO_STACK(T, L)
(LinkedLists::remove_from_front(L))
@d TOP_OF_LIFO_STACK(T, L)
#define TOP_OF_LIFO_STACK(T, L)
FIRST_IN_LINKED_LIST(T, L)
@d LIFO_STACK_EMPTY(T, L)
#define LIFO_STACK_EMPTY(T, L)
((LinkedLists::len(L) == 0)?TRUE:FALSE)
@d LOOP_DOWN_LIFO_STACK(P, T, L)
#define LOOP_DOWN_LIFO_STACK(P, T, L)
LOOP_OVER_LINKED_LIST(P, T, L)

View file

@ -5,10 +5,11 @@ interacting with them: in filenames, or when printing to the console.
@ We will support two different locales:
@e SHELL_LOCALE from 0
@e CONSOLE_LOCALE
<<*>>=
enum SHELL_LOCALE from 0
enum CONSOLE_LOCALE
=
<<*>>=
char *Locales::name(int L) {
switch (L) {
case SHELL_LOCALE: return "shell";
@ -25,22 +26,22 @@ int Locales::parse_locale(char *name) {
}
@ The encodings for each locale are stored in the following global array.
The value |-1| means "platform", that is, "the default value for the current
The value [[-1]] means "platform", that is, "the default value for the current
operating system".
=
<<*>>=
int locales_unset = TRUE;
int locale_settings[NO_DEFINED_LOCALE_VALUES];
int Locales::get(int L) {
if ((L < 0) || (L >= NO_DEFINED_LOCALE_VALUES)) Errors::fatal("locale out of range");
if ((L < 0) [[]] (L >= NO_DEFINED_LOCALE_VALUES)) Errors::fatal("locale out of range");
if (locales_unset) return Locales::platform_locale();
if (locale_settings[L] >= 0) return locale_settings[L];
return Locales::platform_locale();
}
void Locales::set(int L, int E) {
if ((L < 0) || (L >= NO_DEFINED_LOCALE_VALUES)) Errors::fatal("locale out of range");
if ((L < 0) [[]] (L >= NO_DEFINED_LOCALE_VALUES)) Errors::fatal("locale out of range");
if (locales_unset) {
for (int i=0; i<NO_DEFINED_LOCALE_VALUES; i++) locale_settings[i] = -1;
locales_unset = FALSE;
@ -48,7 +49,7 @@ void Locales::set(int L, int E) {
locale_settings[L] = E;
}
@ The possible encodings have names. We must do everything here with |char *|
@ The possible encodings have names. We must do everything here with [[char *]]
and without any higher-level //foundation// facilities, because locale-setting
has to be done extremely early in the run (since it affects how command line
arguments are read).
@ -56,7 +57,7 @@ arguments are read).
Note that new encodings could only be added here if matching changes were made
to //Streams//.
=
<<*>>=
int Locales::parse_encoding(char *name) {
if (strcmp(name, "platform") == 0) return -1;
if (strcmp(name, "iso-latin1") == 0) return FILE_ENCODING_ISO_STRF;
@ -65,10 +66,10 @@ int Locales::parse_encoding(char *name) {
}
@ This can only run after locales have safely been set, since it probably
writes to |STDOUT|, whose encoding depends on locale. For example, //inweb//
calls this in response to |-verbose|.
writes to [[STDOUT]], whose encoding depends on locale. For example, //inweb//
calls this in response to [[-verbose]].
=
<<*>>=
void Locales::write_locales(OUTPUT_STREAM) {
WRITE("Locales are: ");
for (int i=0; i<NO_DEFINED_LOCALE_VALUES; i++) {
@ -92,9 +93,9 @@ void Locales::write_locale(OUTPUT_STREAM, int L) {
}
@ And this is how we determine the default; see //POSIX Platforms// and
//Windows Platform// for these |LOCALE_IS_*| constants.
//Windows Platform// for these [[LOCALE_IS_*]] constants.
=
<<*>>=
int Locales::platform_locale(void) {
#ifdef LOCALE_IS_ISO
return FILE_ENCODING_ISO_STRF;
@ -111,10 +112,10 @@ int Locales::platform_locale(void) {
}
@ This unlovely function parses a comma-separated list of assignments in
the form |LOCALE=ENCODING|, returning |TRUE| if this was syntactically valid
and |FALSE| if not.
the form [[LOCALE=ENCODING]], returning [[TRUE]] if this was syntactically valid
and [[FALSE]] if not.
=
<<*>>=
int Locales::set_locales(char *text) {
if (text == NULL) return FALSE;
for (int at=0; ((at >= 0) && (text[at])); ) {
@ -131,7 +132,7 @@ int Locales::set_locales(char *text) {
L2[i] = (char) tolower((int) text[c+1+i]);
}
int L = Locales::parse_locale(L1), E = Locales::parse_encoding(L2);
if ((L < 0) || (L >= NO_DEFINED_LOCALE_VALUES)) return FALSE;
if ((L < 0) [[]] (L >= NO_DEFINED_LOCALE_VALUES)) return FALSE;
if (E == 0) return FALSE;
Locales::set(L, E);
at = next_at;

View file

@ -4,7 +4,7 @@ To allocate memory suitable for the dynamic creation of objects
of different sizes, placing some larger objects automatically into doubly
linked lists and assigning each a unique allocation ID number.
@h Memory manager.
@ \section{Memory manager.}
This allocates memory as needed to store the numerous "objects" of different
sizes, all C structures. There's no garbage collection because nothing is ever
destroyed. Each "class" has its own doubly-linked list, and in each class the
@ -12,49 +12,51 @@ objects created are given unique IDs (within that type) counting upwards
from 0. These IDs will be unique across all threads.
@ Before going much further, we will need to anticipate what the memory
manager wants. An "object" is a copy in memory of a C |struct|; thus,
a plain |int| is not an object. The memory manager can only deal with
a given type of |struct| if it contains three special elements, and we
manager wants. An "object" is a copy in memory of a C [[struct]]; thus,
a plain [[int]] is not an object. The memory manager can only deal with
a given type of [[struct]] if it contains three special elements, and we
define those using a macro. Thus, if the user wants to allocate larger
structures of type |thingummy|, then it needs to be defined like so:
= (text as code)
structures of type [[thingummy]], then it needs to be defined like so:
typedef struct thingummy {
int whatsit;
struct text_stream *doobrey;
...
CLASS_DEFINITION
} thingummy;
=
The caveat about "larger structures" is that smaller objects can instead be
stored in arrays, to reduce memory and speed overheads. Their structure
declarations do not include the following macro; they do not have unique
IDs; and they cannot be iterated over.
@d CLASS_DEFINITION
<<*>>=
#define CLASS_DEFINITION
int allocation_id; /* Numbered from 0 upwards in creation order */
void *next_structure; /* Next object in double-linked list */
void *prev_structure; /* Previous object in double-linked list */
@ It is also necessary to define a constant in the following enumeration
family: for |thingummy|, it would be |thingummy_CLASS|. Had it been a smaller
object, it would have been |thingummy_array_CLASS| instead.
family: for [[thingummy]], it would be [[thingummy_CLASS]]. Had it been a smaller
object, it would have been [[thingummy_array_CLASS]] instead.
There is no significance to the order in which classes are registered
with the memory system; the following sentinel value is not the class ID
of any actual class, and simply forces the others to have IDs which are
positive, since they count upwards from this.
@e unused_class_value_CLASS from 0
<<*>>=
enum unused_class_value_CLASS from 0
@ For each type of object to be allocated, a single structure of the
following design is maintained. Types which are allocated individually,
like world objects, have |no_allocated_together| set to 1, and the doubly
like world objects, have [[no_allocated_together]] set to 1, and the doubly
linked list is of the objects themselves. For types allocated in small
arrays (typically of 100 objects at a time), |no_allocated_together| is set
arrays (typically of 100 objects at a time), [[no_allocated_together]] is set
to the number of objects in each completed array (so, typically 100) and
the doubly linked list is of the arrays.
=
<<*>>=
typedef struct allocation_status_structure {
/* actually needed for allocation purposes: */
int objects_allocated; /* total number of objects (or arrays) ever allocated */
@ -62,7 +64,7 @@ typedef struct allocation_status_structure {
void *last_in_memory; /* tail of doubly linked list */
/* used only to provide statistics for the debugging log: */
char *name_of_type; /* e.g., |"index_lexicon_entry_CLASS"| */
char *name_of_type; /* e.g., [["index_lexicon_entry_CLASS"]] */
int bytes_allocated; /* total allocation for this type of object, not counting overhead */
int objects_count; /* total number currently in existence (i.e., undeleted) */
int no_allocated_together; /* number of objects in each array of this type of object */
@ -72,7 +74,7 @@ typedef struct allocation_status_structure {
fairly small array of the structures defined above. The allocator can safely
begin as soon as this is initialised.
=
<<*>>=
allocation_status_structure alloc_status[NO_DEFINED_CLASS_VALUES];
void Memory::start(void) {
@ -88,7 +90,7 @@ void Memory::start(void) {
Memory::name_fundamental_reasons();
}
@h Architecture.
@ \section{Architecture.}
The memory manager is built in three levels, with its interface to the user
being entirely at level 3 (except that when it shuts down it calls a level 1
routine to free everything). Each level uses the one below it.
@ -100,7 +102,7 @@ a way to create new small objects of any given type.
or an array of small objects.
(1) Allocating and freeing a few dozen large blocks of contiguous memory.
@h Level 1: memory blocks.
@ \section{Level 1: memory blocks.}
Memory is allocated in blocks within which objects are allocated as
needed. The "safety margin" is the number of spare bytes left blank at the
end of each object: this is done because we want to be paranoid about
@ -111,35 +113,37 @@ chance of a mistake causing a memory exception which crashes the compiler,
because if that happens it will be difficult to recover the circumstances from
the debugging log.
@d SAFETY_MARGIN 128
@d BLANK_END_SIZE 256
<<*>>=
#define SAFETY_MARGIN 128
#define BLANK_END_SIZE 256
@ At present |MEMORY_GRANULARITY| is 800K. This is the quantity of memory
allocated by each individual |malloc| call.
@ At present [[MEMORY_GRANULARITY]] is 800K. This is the quantity of memory
allocated by each individual [[malloc]] call.
As of the early 2020s, typical Inform projects need around 500 blocks to be
allocated, for around 400 MB of memory in all; the largest known take us into
the low 10000s of blocks, for more like 8 to 10 GB. But the latter are very rare.
@d MEMORY_GRANULARITY 100*1024*8 /* which must be divisible by 1024 */
<<*>>=
#define MEMORY_GRANULARITY 100*1024*8 /* which must be divisible by 1024 */
=
<<*>>=
int no_blocks_allocated = 0;
int total_objects_allocated = 0; /* a potentially larger number, used only for the debugging log */
@ Memory blocks are stored in a linked list, and we keep track of the
size of the current block: that is, the block at the tail of the list.
Each memory block consists of a header structure, followed by |SAFETY_MARGIN|
Each memory block consists of a header structure, followed by [[SAFETY_MARGIN]]
null bytes, followed by actual data.
=
<<*>>=
typedef struct memblock_header {
int block_number;
struct memblock_header *next;
char *the_memory;
} memblock_header;
@ =
<<*>>=
memblock_header *first_memblock_header = NULL; /* head of list of memory blocks */
memblock_header *current_memblock_header = NULL; /* tail of list of memory blocks */
@ -152,24 +156,24 @@ CREATE_MUTEX(memory_statistics_mutex)
@ The actual allocation and deallocation is performed by the following
pair of routines.
=
<<*>>=
void Memory::allocate_another_block(void) {
unsigned char *cp;
memblock_header *mh;
@<Allocate and zero out a block of memory, making cp point to it@>;
<<Allocate and zero out a block of memory, making cp point to it>>;
mh = (memblock_header *) cp;
used_in_current_memblock = sizeof(memblock_header) + SAFETY_MARGIN;
mh->the_memory = (void *) (cp + used_in_current_memblock);
@<Add new block to the tail of the list of memory blocks@>;
<<Add new block to the tail of the list of memory blocks>>;
}
@ Note that |cp| and |mh| are set to the same value: they merely have different
@ Note that [[cp]] and [[mh]] are set to the same value: they merely have different
pointer types as far as the C compiler is concerned.
@<Allocate and zero out a block of memory, making cp point to it@> =
<<Allocate and zero out a block of memory, making cp point to it>>=
Memory::check_memory_integrity();
cp = (unsigned char *) (Memory::paranoid_calloc(MEMORY_GRANULARITY, 1));
if (cp == NULL) Errors::fatal("Run out of memory: malloc failed");
@ -178,7 +182,7 @@ pointer types as far as the C compiler is concerned.
@ As can be seen, memory block numbers count upwards from 0 in order of
their allocation.
@<Add new block to the tail of the list of memory blocks@> =
<<Add new block to the tail of the list of memory blocks>>=
if (current_memblock_header == NULL) {
mh->block_number = 0;
first_memblock_header = mh;
@ -192,7 +196,7 @@ their allocation.
in turn, but of course being careful to avoid following links in a just-freed
block.
=
<<*>>=
void Memory::free(void) {
CStrings::free_ssas();
memblock_header *mh = first_memblock_header;
@ -204,18 +208,19 @@ void Memory::free(void) {
}
}
@h Level 2: memory frames and integrity checking.
@ \section{Level 2: memory frames and integrity checking.}
Within these extensive blocks of contiguous memory, we place the actual
objects in between "memory frames", which are only used at present to police
the integrity of memory: again, finding obscure and irritating memory-corruption
bugs is more important to us than saving bytes. Each memory frame wraps either
a single large object, or a single array of small objects.
@d INTEGRITY_NUMBER 0x12345678 /* a value unlikely to be in memory just by chance */
<<*>>=
#define INTEGRITY_NUMBER 0x12345678 /* a value unlikely to be in memory just by chance */
=
<<*>>=
typedef struct memory_frame {
int integrity_check; /* this should always contain the |INTEGRITY_NUMBER| */
int integrity_check; /* this should always contain the [[INTEGRITY_NUMBER]] */
struct memory_frame *next_frame; /* next frame in the list of memory frames */
int mem_type; /* type of object stored in this frame */
int allocation_id; /* allocation ID number of object stored in this frame */
@ -225,24 +230,24 @@ typedef struct memory_frame {
10000 entries in length, beginning here. (These frames live in different memory
blocks, but we don't need to worry about that.)
=
<<*>>=
memory_frame *first_memory_frame = NULL; /* earliest memory frame ever allocated */
memory_frame *last_memory_frame = NULL; /* most recent memory frame allocated */
@ If the integrity numbers of every frame are still intact, then it is pretty
unlikely that any bug has caused memory to overwrite one frame into another.
|Memory::check_memory_integrity| might on very large runs be run often, if we didn't
[[Memory::check_memory_integrity]] might on very large runs be run often, if we didn't
prevent this: since the number of calls would be roughly proportional to
memory usage, we would implicitly have an $O(n^2)$ running time in the
amount of storage $n$ allocated.
=
<<*>>=
int calls_to_cmi = 0;
void Memory::check_memory_integrity(void) {
int c;
memory_frame *mf;
c = calls_to_cmi++;
if (!((c<10) || (c == 100) || (c == 1000) || (c == 10000))) return;
if (!((c<10) [[| (c == 100) || (c == 1000) |]] (c == 10000))) return;
for (c = 0, mf = first_memory_frame; mf; c++, mf = mf->next_frame)
if (mf->integrity_check != INTEGRITY_NUMBER)
@ -265,16 +270,16 @@ list of memory frames will live inside those blocks; we have seen how the
list is checked for integrity; but we not seen how it is built. Every
memory frame is created by the following function:
=
<<*>>=
void *Memory::allocate(int mem_type, int extent) {
unsigned char *cp;
memory_frame *mf;
int bytes_free_in_current_memblock, extent_without_overheads = extent;
extent += sizeof(memory_frame); /* each allocation is preceded by a memory frame */
extent += SAFETY_MARGIN; /* each allocation is followed by |SAFETY_MARGIN| null bytes */
extent += SAFETY_MARGIN; /* each allocation is followed by [[SAFETY_MARGIN]] null bytes */
@<Ensure that the current memory block has room for this many bytes@>;
<<Ensure that the current memory block has room for this many bytes>>;
cp = ((unsigned char *) (current_memblock_header->the_memory)) + used_in_current_memblock;
used_in_current_memblock += extent;
@ -286,8 +291,8 @@ void *Memory::allocate(int mem_type, int extent) {
mf->allocation_id = alloc_status[mem_type].objects_allocated;
mf->mem_type = mem_type;
@<Add the new memory frame to the big linked list of all frames@>;
@<Update the allocation status for this type of object@>;
<<Add the new memory frame to the big linked list of all frames>>;
<<Update the allocation status for this type of object>>;
total_objects_allocated++;
return (void *) cp;
@ -297,7 +302,7 @@ void *Memory::allocate(int mem_type, int extent) {
object type is allocated. So this is not a potential time-bomb just waiting
for a user with a particularly long and involved source text to discover.
@<Ensure that the current memory block has room for this many bytes@> =
<<Ensure that the current memory block has room for this many bytes>>=
if (current_memblock_header == NULL) Memory::allocate_another_block();
bytes_free_in_current_memblock = MEMORY_GRANULARITY - (used_in_current_memblock + extent);
if (bytes_free_in_current_memblock < BLANK_END_SIZE) {
@ -308,22 +313,22 @@ for a user with a particularly long and involved source text to discover.
@ New memory frames are added to the tail of the list:
@<Add the new memory frame to the big linked list of all frames@> =
<<Add the new memory frame to the big linked list of all frames>>=
mf->next_frame = NULL;
if (first_memory_frame == NULL) first_memory_frame = mf;
else last_memory_frame->next_frame = mf;
last_memory_frame = mf;
@ See the definition of |alloc_status| above.
@ See the definition of [[alloc_status]] above.
@<Update the allocation status for this type of object@> =
<<Update the allocation status for this type of object>>=
if (alloc_status[mem_type].first_in_memory == NULL)
alloc_status[mem_type].first_in_memory = (void *) cp;
alloc_status[mem_type].last_in_memory = (void *) cp;
alloc_status[mem_type].objects_allocated++;
alloc_status[mem_type].bytes_allocated += extent_without_overheads;
@h Level 3: managing linked lists of allocated objects.
@ \section{Level 3: managing linked lists of allocated objects.}
We define macros which look as if they are functions, but for which one
argument is the name of a type: expanding these macros provides suitable C
functions to handle each possible type. These macros provide the interface
@ -331,43 +336,46 @@ through which all other sections allocate and leaf through memory.
Note that Inweb allows multi-line macro definitions without backslashes
to continue them, unlike ordinary C. Otherwise these are "standard"
macros, though this was my first brush with the |##| concatenation
operator: basically |CREATE(thing)| expands into |(allocate_thing())|
because of the |##|. (See Kernighan and Ritchie, section 4.11.2.)
macros, though this was my first brush with the [[##]] concatenation
operator: basically [[CREATE(thing)]] expands into [[(allocate_thing())]]
because of the [[##]]. (See Kernighan and Ritchie, section 4.11.2.)
@d CREATE(type_name) (allocate_##type_name())
@d COPY(to, from, type_name) (copy_##type_name(to, from))
@d CREATE_BEFORE(existing, type_name) (allocate_##type_name##_before(existing))
@d DESTROY(this, type_name) (deallocate_##type_name(this))
@d FIRST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_CLASS].first_in_memory)
@d LAST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_CLASS].last_in_memory)
@d NEXT_OBJECT(this, type_name) ((type_name *) (this->next_structure))
@d PREV_OBJECT(this, type_name) ((type_name *) (this->prev_structure))
@d NUMBER_CREATED(type_name) (alloc_status[type_name##_CLASS].objects_count)
<<*>>=
#define CREATE(type_name) (allocate_##type_name())
#define COPY(to, from, type_name) (copy_##type_name(to, from))
#define CREATE_BEFORE(existing, type_name) (allocate_##type_name##_before(existing))
#define DESTROY(this, type_name) (deallocate_##type_name(this))
#define FIRST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_CLASS].first_in_memory)
#define LAST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_CLASS].last_in_memory)
#define NEXT_OBJECT(this, type_name) ((type_name *) (this->next_structure))
#define PREV_OBJECT(this, type_name) ((type_name *) (this->prev_structure))
#define NUMBER_CREATED(type_name) (alloc_status[type_name##_CLASS].objects_count)
@ The following macros are widely used (well, the first one is, anyway)
for looking through the double linked list of existing objects of a
given type.
@d LOOP_OVER(var, type_name)
<<*>>=
#define LOOP_OVER(var, type_name)
for (var=FIRST_OBJECT(type_name); var != NULL; var = NEXT_OBJECT(var, type_name))
@d LOOP_BACKWARDS_OVER(var, type_name)
#define LOOP_BACKWARDS_OVER(var, type_name)
for (var=LAST_OBJECT(type_name); var != NULL; var = PREV_OBJECT(var, type_name))
@h Allocator functions created by macros.
@ \section{Allocator functions created by macros.}
The following macros generate a family of systematically named functions.
For instance, we shall shortly expand |DECLARE_CLASS(parse_node)|,
which will expand to three functions: |allocate_parse_node|,
|deallocate_parse_node| and |allocate_parse_node_before|.
For instance, we shall shortly expand [[DECLARE_CLASS(parse_node)]],
which will expand to three functions: [[allocate_parse_node]],
[[deallocate_parse_node]] and [[allocate_parse_node_before]].
Quaintly, |#type_name| expands into the value of |type_name| put within
Quaintly, [[#type_name]] expands into the value of [[type_name]] put within
double-quotes.
@d NEW_OBJECT(type_name) ((type_name *) Memory::allocate(type_name##_CLASS, sizeof(type_name)))
<<*>>=
#define NEW_OBJECT(type_name) ((type_name *) Memory::allocate(type_name##_CLASS, sizeof(type_name)))
@d DECLARE_CLASS(type_name) DECLARE_CLASS_WITH_ID(type_name, type_name##_CLASS)
#define DECLARE_CLASS(type_name) DECLARE_CLASS_WITH_ID(type_name, type_name##_CLASS)
@d DECLARE_CLASS_WITH_ID(type_name, id_name)
#define DECLARE_CLASS_WITH_ID(type_name, id_name)
MAKE_REFERENCE_ROUTINES(type_name, id_name)
type_name *allocate_##type_name(void) {
LOCK_MUTEX(memory_single_allocation_mutex);
@ -426,21 +434,22 @@ void copy_##type_name(type_name *to, type_name *from) {
UNLOCK_MUTEX(memory_single_allocation_mutex);
}
@ |DECLARE_CLASS_ALLOCATED_IN_ARRAYS| is still more obfuscated. When we
|DECLARE_CLASS_ALLOCATED_IN_ARRAYS(X, 100)|, the result will be definitions of
a new type |X_array| and constructors for both |X| and |X_array|, the former
@ [[DECLARE_CLASS_ALLOCATED_IN_ARRAYS]] is still more obfuscated. When we
[[DECLARE_CLASS_ALLOCATED_IN_ARRAYS(X, 100)]], the result will be definitions of
a new type [[X_array]] and constructors for both [[X]] and [[X_array]], the former
of which uses the latter. Note that we are not provided with the means to
deallocate individual objects this time: that's the trade-off for
allocating in blocks.
@d DECLARE_CLASS_ALLOCATED_IN_ARRAYS(type_name, NO_TO_ALLOCATE_TOGETHER)
<<*>>=
#define DECLARE_CLASS_ALLOCATED_IN_ARRAYS(type_name, NO_TO_ALLOCATE_TOGETHER)
MAKE_REFERENCE_ROUTINES(type_name, type_name##_CLASS)
typedef struct type_name##_array {
int used;
struct type_name array[NO_TO_ALLOCATE_TOGETHER];
CLASS_DEFINITION
} type_name##_array;
int type_name##_array_CLASS = type_name##_CLASS; /* C does permit |#define| to make |#define|s */
int type_name##_array_CLASS = type_name##_CLASS; /* C does permit [[#define]] to make [[#define]]s */
DECLARE_CLASS_WITH_ID(type_name##_array, type_name##_CLASS)
type_name##_array *next_##type_name##_array = NULL;
struct type_name *allocate_##type_name(void) {
@ -456,17 +465,18 @@ struct type_name *allocate_##type_name(void) {
return rv;
}
@h Simple memory allocations.
@ \section{Simple memory allocations.}
Not all of our memory will be claimed in the form of structures: now and then
we need to use the equivalent of traditional |malloc| and |calloc| routines.
we need to use the equivalent of traditional [[malloc]] and [[calloc]] routines.
@e STREAM_MREASON from 0
@e FILENAME_STORAGE_MREASON
@e STRING_STORAGE_MREASON
@e DICTIONARY_MREASON
@e ARRAY_SORTING_MREASON
<<*>>=
enum STREAM_MREASON from 0
enum FILENAME_STORAGE_MREASON
enum STRING_STORAGE_MREASON
enum DICTIONARY_MREASON
enum ARRAY_SORTING_MREASON
=
<<*>>=
void Memory::name_fundamental_reasons(void) {
Memory::reason_name(STREAM_MREASON, "text stream storage");
Memory::reason_name(FILENAME_STORAGE_MREASON, "filename/pathname storage");
@ -477,7 +487,7 @@ void Memory::name_fundamental_reasons(void) {
@ And here is the (very simple) implementation.
=
<<*>>=
char *memory_needs[NO_DEFINED_MREASON_VALUES];
void Memory::reason_name(int r, char *reason) {
@ -497,19 +507,19 @@ efficient use of the memory we free, we can't know, but it probably is, and
therefore the best estimate of how well we're doing is the "maximum memory
claimed" -- the highest recorded net usage count over the run.
=
<<*>>=
int max_memory_at_once_for_each_need[NO_DEFINED_MREASON_VALUES],
memory_claimed_for_each_need[NO_DEFINED_MREASON_VALUES],
number_of_claims_for_each_need[NO_DEFINED_MREASON_VALUES];
int total_claimed_simply = 0;
@ Our allocation routines behave just like the standard C library's |malloc|
and |calloc|, but where a third argument supplies a reason why the memory is
@ Our allocation routines behave just like the standard C library's [[malloc]]
and [[calloc]], but where a third argument supplies a reason why the memory is
needed, and where any failure to allocate memory is tidily dealt with. We will
exit on any such failure, so that the caller can be certain that the return
values of these functions are always non-|NULL| pointers.
values of these functions are always non-[[NULL]] pointers.
=
<<*>>=
void *Memory::calloc(int how_many, int size_in_bytes, int reason) {
return Memory::alloc_inner(how_many, size_in_bytes, reason);
}
@ -519,22 +529,22 @@ void *Memory::malloc(int size_in_bytes, int reason) {
@ And this, then, is the joint routine implementing both.
=
<<*>>=
void *Memory::alloc_inner(int N, int S, int R) {
void *pointer;
int bytes_needed;
if ((R < 0) || (R >= NO_DEFINED_MREASON_VALUES)) internal_error("no such memory reason");
if (total_claimed_simply == 0) @<Zero out the statistics on simple memory allocations@>;
@<Claim the memory using malloc or calloc as appropriate@>;
@<Update the statistics on simple memory allocations@>;
if (total_claimed_simply == 0) <<Zero out the statistics on simple memory allocations>>;
<<Claim the memory using malloc or calloc as appropriate>>;
<<Update the statistics on simple memory allocations>>;
return pointer;
}
@ I am nervous about assuming that |calloc(0, X)| returns a non-|NULL| pointer
in all implementations of the standard C library, so the case when |N| is zero
@ I am nervous about assuming that [[calloc(0, X)]] returns a non-[[NULL]] pointer
in all implementations of the standard C library, so the case when [[N]] is zero
allocates a tiny but positive amount of memory, just to be safe.
@<Claim the memory using malloc or calloc as appropriate@> =
<<Claim the memory using malloc or calloc as appropriate>>=
if (N > 0) {
pointer = Memory::paranoid_calloc((size_t) N, (size_t) S);
bytes_needed = N*S;
@ -547,10 +557,10 @@ allocates a tiny but positive amount of memory, just to be safe.
}
@ These statistics have no function except to improve the diagnostics in the
debugging log, but they are very cheap to keep, since |Memory::alloc_inner| is called only
debugging log, but they are very cheap to keep, since [[Memory::alloc_inner]] is called only
rarely and to allocate large blocks of memory.
@<Zero out the statistics on simple memory allocations@> =
<<Zero out the statistics on simple memory allocations>>=
LOCK_MUTEX(memory_statistics_mutex);
for (int i=0; i<NO_DEFINED_MREASON_VALUES; i++) {
max_memory_at_once_for_each_need[i] = 0;
@ -559,7 +569,7 @@ rarely and to allocate large blocks of memory.
}
UNLOCK_MUTEX(memory_statistics_mutex);
@<Update the statistics on simple memory allocations@> =
<<Update the statistics on simple memory allocations>>=
LOCK_MUTEX(memory_statistics_mutex);
memory_claimed_for_each_need[R] += bytes_needed;
total_claimed_simply += bytes_needed;
@ -568,9 +578,9 @@ rarely and to allocate large blocks of memory.
max_memory_at_once_for_each_need[R] = memory_claimed_for_each_need[R];
UNLOCK_MUTEX(memory_statistics_mutex);
@ We also provide our own wrapper for |free|:
@ We also provide our own wrapper for [[free]]:
=
<<*>>=
void Memory::I7_free(void *pointer, int R, int bytes_freed) {
if ((R < 0) || (R >= NO_DEFINED_MREASON_VALUES)) internal_error("no such memory reason");
if (pointer == NULL) internal_error("can't free NULL memory");
@ -584,26 +594,26 @@ void Memory::I7_array_free(void *pointer, int R, int num_cells, size_t cell_size
Memory::I7_free(pointer, R, num_cells*((int) cell_size));
}
@h Memory usage report.
@ \section{Memory usage report.}
A small utility routine to help keep track of our unquestioned profligacy.
=
<<*>>=
void Memory::log_statistics(void) {
int total_for_objects = MEMORY_GRANULARITY*no_blocks_allocated; /* usage in bytes */
int total_for_SMAs = Memory::log_usage(0); /* usage in bytes */
int sorted_usage[NO_DEFINED_CLASS_VALUES]; /* memory type numbers, in usage order */
int total = (total_for_objects + total_for_SMAs)/1024; /* total memory usage in KB */
@<Sort the table of memory type usages into decreasing size order@>;
<<Sort the table of memory type usages into decreasing size order>>;
int total_for_objects_used = 0; /* out of the |total_for_objects|, the bytes used */
int total_for_objects_used = 0; /* out of the [[total_for_objects]], the bytes used */
int total_objects = 0;
@<Calculate the memory usage for objects@>;
<<Calculate the memory usage for objects>>;
int overhead_for_objects = total_for_objects - total_for_objects_used; /* bytes wasted */
@<Print the report to the debugging log@>;
<<Print the report to the debugging log>>;
}
@<Calculate the memory usage for objects@> =
<<Calculate the memory usage for objects>>=
int i, j;
for (j=0; j<NO_DEFINED_CLASS_VALUES; j++) {
i = sorted_usage[j];
@ -620,13 +630,13 @@ void Memory::log_statistics(void) {
@ This is the criterion for sorting memory types in the report: descending
order of total number of bytes allocated.
@<Sort the table of memory type usages into decreasing size order@> =
<<Sort the table of memory type usages into decreasing size order>>=
for (int i=0; i<NO_DEFINED_CLASS_VALUES; i++) sorted_usage[i] = i;
qsort(sorted_usage, (size_t) NO_DEFINED_CLASS_VALUES, sizeof(int), Memory::compare_usage);
@ And here is the actual report:
@<Print the report to the debugging log@> =
<<Print the report to the debugging log>>=
LOG("Total memory consumption was %dK = %d MB\n\n",
total, (total+512)/1024);
@ -669,7 +679,7 @@ order of total number of bytes allocated.
LOG(" was overhead - %d bytes = %dK = %d MB\n\n", overhead_for_objects,
overhead_for_objects/1024, (overhead_for_objects+512)/1024/1024);
@ =
<<*>>=
int Memory::log_usage(int total) {
if (total_claimed_simply == 0) return 0;
int i, t = 0;
@ -689,7 +699,7 @@ int Memory::log_usage(int total) {
return t;
}
@ =
<<*>>=
int Memory::compare_usage(const void *ent1, const void *ent2) {
int ix1 = *((const int *) ent1);
int ix2 = *((const int *) ent2);
@ -697,9 +707,9 @@ int Memory::compare_usage(const void *ent1, const void *ent2) {
}
@ Finally, a little routine to compute the proportions of memory for each
usage. Recall that |bytes| is measured in bytes, but |total| in kilobytes.
usage. Recall that [[bytes]] is measured in bytes, but [[total]] in kilobytes.
=
<<*>>=
void Memory::log_percentage(int bytes, int total) {
float B = (float) bytes, T = (float) total;
float P = (1000*B)/(1024*T);
@ -709,37 +719,38 @@ void Memory::log_percentage(int bytes, int total) {
}
@ At one time, the following function was paranoid about thread-safety of
|calloc| as implemented in some C libraries, and was protected by a mutex.
[[calloc]] as implemented in some C libraries, and was protected by a mutex.
It has now learned to chill.
=
<<*>>=
void *Memory::paranoid_calloc(size_t N, size_t S) {
void *P = calloc(N, S);
return P;
}
@h Run-time pointer type checking.
In several places Inform needs to store pointers of type |void *|, that is,
@ \section{Run-time pointer type checking.}
In several places Inform needs to store pointers of type [[void *]], that is,
pointers which have no indication of what type of data they point to.
This is not type-safe and therefore offers plenty of opportunity for
blunders. The following provides run-time type checking to ensure that
each time we dereference a typeless pointer, it does indeed point to
a structure of the type we think it should.
The structure |general_pointer| holds a |void *| pointer to any one of the
The structure [[general_pointer]] holds a [[void *]] pointer to any one of the
following:
(a) |NULL|, to which we assign ID number $-1$;
(b) |char|, to which we assign ID number 1000;
(a) [[NULL]], to which we assign ID number $-1$;
(b) [[char]], to which we assign ID number 1000;
(c) any individually allocated structure of the types listed above, to
which we assign the ID numbers used above: for instance, |figures_data_CLASS|
is the ID number for a |general_pointer| which points to a |figures_data|
which we assign the ID numbers used above: for instance, [[figures_data_CLASS]]
is the ID number for a [[general_pointer]] which points to a [[figures_data]]
structure.
@d NULL_GENERAL_POINTER (Memory::store_gp_null())
@d GENERAL_POINTER_IS_NULL(gp) (Memory::test_gp_null(gp))
<<*>>=
#define NULL_GENERAL_POINTER (Memory::store_gp_null())
#define GENERAL_POINTER_IS_NULL(gp) (Memory::test_gp_null(gp))
=
<<*>>=
typedef struct general_pointer {
void *pointer_to_data;
int run_time_type_code;
@ -748,7 +759,7 @@ typedef struct general_pointer {
general_pointer Memory::store_gp_null(void) {
general_pointer gp;
gp.pointer_to_data = NULL;
gp.run_time_type_code = -1; /* guaranteed to differ from all |_CLASS| values */
gp.run_time_type_code = -1; /* guaranteed to differ from all [[_CLASS]] values */
return gp;
}
int Memory::test_gp_null(general_pointer gp) {
@ -761,19 +772,21 @@ their types, but only to test equality, so we abstract that thus. And the
debugging log also shows actual hexadecimal addresses to distinguish nameless
objects and to help with interpreting output from GDB, so we abstract that too.
@d COMPARE_GENERAL_POINTERS(gp1, gp2)
<<*>>=
#define COMPARE_GENERAL_POINTERS(gp1, gp2)
(gp1.pointer_to_data == gp2.pointer_to_data)
@d GENERAL_POINTER_AS_INT(gp)
#define GENERAL_POINTER_AS_INT(gp)
((pointer_sized_int) gp.pointer_to_data)
@ If we have a pointer to |circus| (say) then |g=STORE_POINTER_circus(p)|
returns a |general_pointer| with |p| as the actual pointer, but will not
compile unless |p| is indeed of type |circus *|. When we later
|RETRIEVE_POINTER_circus(g)|, an internal error is thrown if |g| contains a pointer
which is other than |void *|, or which has never been referenced.
@ If we have a pointer to [[circus]] (say) then [[g=STORE_POINTER_circus(p)]]
returns a [[general_pointer]] with [[p]] as the actual pointer, but will not
compile unless [[p]] is indeed of type [[circus *]]. When we later
[[RETRIEVE_POINTER_circus(g)]], an internal error is thrown if [[g]] contains a pointer
which is other than [[void *]], or which has never been referenced.
@d MAKE_REFERENCE_ROUTINES(type_name, id_code)
<<*>>=
#define MAKE_REFERENCE_ROUTINES(type_name, id_code)
general_pointer STORE_POINTER_##type_name(type_name *data) {
general_pointer gp;
gp.pointer_to_data = (void *) data;
@ -799,8 +812,8 @@ int VALID_POINTER_##type_name(general_pointer gp) {
return FALSE;
}
@ Suitable |MAKE_REFERENCE_ROUTINES| were expanded for all of the memory
allocated objects above; so that leaves only humble |char *| pointers:
@ Suitable [[MAKE_REFERENCE_ROUTINES]] were expanded for all of the memory
allocated objects above; so that leaves only humble [[char *]] pointers:
=
<<*>>=
MAKE_REFERENCE_ROUTINES(char, 1000)

View file

@ -2,7 +2,7 @@
General support for something approximating method calls.
@h Method sets.
@ \section{Method sets.}
This section provides a very rudimentary implementation of method calls,
ordinarily not available in C, but doesn't pretend to offer the full
functionality of an object-oriented language.
@ -23,7 +23,7 @@ providing "methods".
@ A "method set" is simply a linked list of methods:
=
<<*>>=
typedef struct method_set {
struct method *first_method;
CLASS_DEFINITION
@ -35,37 +35,40 @@ method_set *Methods::new_set(void) {
return S;
}
@h Declaring methods.
@ \section{Declaring methods.}
Each method is a function, though we don't know its type -- which is why we
resort to the desperate measure of storing it as a |void *| -- with an ID
number attached to it. IDs should be from the |*_MTID| enumeration set.
resort to the desperate measure of storing it as a [[void *]] -- with an ID
number attached to it. IDs should be from the [[*_MTID]] enumeration set.
@e UNUSED_METHOD_ID_MTID from 1
<<*>>=
enum UNUSED_METHOD_ID_MTID from 1
@ The type of a method must neverthess be specified, and we do it with one
of two macros: one for methods returning an integer, one for void methods,
i.e., those returning no value.
What these do is to use typedef to give the name |X_type| to the type of all
functions sharing the method ID |X|.
What these do is to use typedef to give the name [[X_type]] to the type of all
functions sharing the method ID [[X]].
@d INT_METHOD_TYPE(id, args...)
<<*>>=
#define INT_METHOD_TYPE(id, args...)
typedef int (*id##_type)(args);
@d VOID_METHOD_TYPE(id, args...)
#define VOID_METHOD_TYPE(id, args...)
typedef void (*id##_type)(args);
=
<<*>>=
INT_METHOD_TYPE(UNUSED_METHOD_ID_MTID, text_stream *example, int wont_be_used)
@h Adding methods.
@ \section{Adding methods.}
Provided a function has the right type for the ID we're using, we can now
attach it to an object with a method set, using the |METHOD_ADD| macro.
attach it to an object with a method set, using the [[METHOD_ADD]] macro.
(If the type is wrong, the C compiler will throw errors here.)
@d METHOD_ADD(upon, id, func)
<<*>>=
#define METHOD_ADD(upon, id, func)
Methods::add(upon->methods, id, (void *) &func);
=
<<*>>=
typedef struct method {
int method_id;
void *method_function;
@ -95,31 +98,32 @@ int Methods::provided(method_set *S, int ID) {
return FALSE;
}
@h Calling methods.
@ \section{Calling methods.}
Method calls are also done with a macro, but it has to come in four variants:
(a) |INT_METHOD_CALL| for a method taking arguments and returning an |int|,
(b) |INT_METHOD_CALL_WITHOUT_ARGUMENTS| for a method without arguments which returns an |int|,
(c) |VOID_METHOD_CALL| for a method taking arguments and returning nothing,
(d) |VOID_METHOD_CALL_WITHOUT_ARGUMENTS| for a method without arguments which returns nothing.
(a) [[INT_METHOD_CALL]] for a method taking arguments and returning an [[int]],
(b) [[INT_METHOD_CALL_WITHOUT_ARGUMENTS]] for a method without arguments which returns an [[int]],
(c) [[VOID_METHOD_CALL]] for a method taking arguments and returning nothing,
(d) [[VOID_METHOD_CALL_WITHOUT_ARGUMENTS]] for a method without arguments which returns nothing.
For example:
= (text as code)
INT_METHOD_CALL(some_object, UNUSED_METHOD_ID_MTID, I"Hello", 17)
=
Note that it's entirely possible for the |upon| object to have multiple methods
added for the same ID -- or none. In the |V| (void) cases, what we then do is
to call each of them in turn. In the |I| (int) cases, we call each in turn, but
stop the moment any of them returns something other than |FALSE|, and then
we put that value into the specified result variable |rval|.
If |some_object| has no methods for the given ID, then nothing happens, and
in the |I| case, the return value is |FALSE|.
Note that it's entirely possible for the [[upon]] object to have multiple methods
added for the same ID -- or none. In the [[V]] (void) cases, what we then do is
to call each of them in turn. In the [[I]] (int) cases, we call each in turn, but
stop the moment any of them returns something other than [[FALSE]], and then
we put that value into the specified result variable [[rval]].
It will, however, produce a compilation error if |some_object| is not a pointer
to a structure which has a |methods| element as part of its definition.
If [[some_object]] has no methods for the given ID, then nothing happens, and
in the [[I]] case, the return value is [[FALSE]].
@d INT_METHOD_CALL(rval, upon, id, args...) {
It will, however, produce a compilation error if [[some_object]] is not a pointer
to a structure which has a [[methods]] element as part of its definition.
<<*>>=
#define INT_METHOD_CALL(rval, upon, id, args...) {
rval = FALSE;
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
if (M->method_id == id) {
@ -130,7 +134,7 @@ to a structure which has a |methods| element as part of its definition.
}
}
}
@d INT_METHOD_CALL_WITHOUT_ARGUMENTS(rval, upon, id) {
#define INT_METHOD_CALL_WITHOUT_ARGUMENTS(rval, upon, id) {
rval = FALSE;
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
if (M->method_id == id) {
@ -141,11 +145,11 @@ to a structure which has a |methods| element as part of its definition.
}
}
}
@d VOID_METHOD_CALL(upon, id, args...)
#define VOID_METHOD_CALL(upon, id, args...)
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
if (M->method_id == id)
(*((id##_type) (M->method_function)))(upon, args);
@d VOID_METHOD_CALL_WITHOUT_ARGUMENTS(upon, id)
#define VOID_METHOD_CALL_WITHOUT_ARGUMENTS(upon, id)
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
if (M->method_id == id)
(*((id##_type) (M->method_function)))(upon);

View file

@ -3,7 +3,7 @@
Support for writing structured textual output, perhaps to the screen,
to a file, or to a flexible-sized wide string.
@h About streams.
@ \section{About streams.}
The Inform tools produce textual output in many formats (HTML, EPS, plain
text, Inform 6 code, XML, and so on), writing to a variety of files, and
often need to juggle and rearrange partially written segments. These
@ -23,17 +23,18 @@ is composed to a single code point as E-acute.
We give just one character value a non-Unicode meaning:
@d NEWLINE_IN_STRING ((char) 0x7f) /* Within quoted text, all newlines are converted to this */
<<*>>=
#define NEWLINE_IN_STRING ((char) 0x7f) /* Within quoted text, all newlines are converted to this */
@ The |text_stream| type began as a generalisation of the standard C library's
|FILE|, and it is used in mostly similar ways. The user -- the whole
program outside of this section -- deals only with |text_stream *| pointers to
@ The [[text_stream]] type began as a generalisation of the standard C library's
[[FILE]], and it is used in mostly similar ways. The user -- the whole
program outside of this section -- deals only with [[text_stream *]] pointers to
represent streams in use.
All stream handling is defined via macros. While many operations could be
handled by ordinary functions, others cannot. |text_stream| cannot have exactly
the semantics of |FILE| since we cannot rely on the host operating system
to allocate and deallocate the structures behind |text_stream *| pointers; and
handled by ordinary functions, others cannot. [[text_stream]] cannot have exactly
the semantics of [[FILE]] since we cannot rely on the host operating system
to allocate and deallocate the structures behind [[text_stream *]] pointers; and
we cannot use our own memory system, either, since we need stream handling
to work both before the memory allocator starts and after it has finished.
Our macros allow us to hide all this. Besides that, a macro approach makes it
@ -44,16 +45,16 @@ implementation is the second stab at it.)
with code ordering if we leave them until later. They are written in the
old-fashioned way, for compatibility with old copies of GCC, and avoid the
need for comma deletion around empty tokens, as that is a point of
incompatibility between implementations of the C preprocessor |cpp|. All the
incompatibility between implementations of the C preprocessor [[cpp]]. All the
same, if you're porting this code, you may need to rewrite the macro with
|...| in place of |args...| in the header, and then |__VA_ARGS__| in place
of |args| in the definition: that being the modern way, apparently.
[[...]] in place of [[args...]] in the header, and then [[__VA_ARGS__]] in place
of [[args]] in the definition: that being the modern way, apparently.
|WRITE| is essentially |sprintf| and |fprintf| combined, since it prints
[[WRITE]] is essentially [[sprintf]] and [[fprintf]] combined, since it prints
formatted text to the current stream, which could be either a string or a
file. |PRINT| does the same but to |STDOUT|, and is thus essentially |printf|.
file. [[PRINT]] does the same but to [[STDOUT]], and is thus essentially [[printf]].
= (early code)
<<*>>=
#define WRITE(args...) Writers::printf(OUT, args)
#define PRINT(args...) Writers::printf(STDOUT, args)
@ -68,173 +69,185 @@ file. |PRINT| does the same but to |STDOUT|, and is thus essentially |printf|.
@ The main purpose of many functions is to write textual material to some
file. Such functions almost always have a special argument in their
prototypes: |OUTPUT_STREAM|. This tells them where to pipe their output, which
is always to a "current stream" called |OUT|. What this leads to, and who will
prototypes: [[OUTPUT_STREAM]]. This tells them where to pipe their output, which
is always to a "current stream" called [[OUT]]. What this leads to, and who will
see that it's properly opened and closed, are not their concern.
@d OUTPUT_STREAM text_stream *OUT /* used only as a function prototype argument */
<<*>>=
#define OUTPUT_STREAM text_stream *OUT /* used only as a function prototype argument */
@ Three output streams are always open. One is |NULL|, that is, its value
as a |text_stream *| pointer is |NULL|, the generic C null pointer. This represents
an oubliette: it is entirely valid to use it, but output sent to |NULL| will
@ Three output streams are always open. One is [[NULL]], that is, its value
as a [[text_stream *]] pointer is [[NULL]], the generic C null pointer. This represents
an oubliette: it is entirely valid to use it, but output sent to [[NULL]] will
never be seen again.
The others are |STDOUT| and |STDERR|. As the names suggest these are wrappers
for |stdout| and |stderr|, the standard console output and error messages
The others are [[STDOUT]] and [[STDERR]]. As the names suggest these are wrappers
for [[stdout]] and [[stderr]], the standard console output and error messages
"files" provided by the C library.
We should always use |PRINT(...)| instead of |printf(...)| for console output,
so that there are no uses of |printf| anywhere in the program.
We should always use [[PRINT(...)]] instead of [[printf(...)]] for console output,
so that there are no uses of [[printf]] anywhere in the program.
@d STDOUT Streams::get_stdout()
@d STDERR Streams::get_stderr()
<<*>>=
#define STDOUT Streams::get_stdout()
#define STDERR Streams::get_stderr()
@ |PUT| and |PUT_TO| similarly print single characters, which are
specified as unsigned integer values. In practice, |WRITE_TO| and
|PUT_TO| are seldom needed because there is almost always only one
stream of interest at a time -- |OUT|, the current stream.
@ [[PUT]] and [[PUT_TO]] similarly print single characters, which are
specified as unsigned integer values. In practice, [[WRITE_TO]] and
[[PUT_TO]] are seldom needed because there is almost always only one
stream of interest at a time -- [[OUT]], the current stream.
@d PUT(c) Streams::putc(c, OUT)
<<*>>=
#define PUT(c) Streams::putc(c, OUT)
@d PUT_TO(stream, c) Streams::putc(c, stream)
#define PUT_TO(stream, c) Streams::putc(c, stream)
@ Each stream has a current indentation level, initially 0. Lines of text
will be indented by one tab stop for each level; it's an error for the level
to become negative.
@d INDENT Streams::indent(OUT);
@d STREAM_INDENT(x) Streams::indent(x);
@d OUTDENT Streams::outdent(OUT);
@d STREAM_OUTDENT(x) Streams::outdent(x);
@d SET_INDENT(N) Streams::set_indentation(OUT, N);
<<*>>=
#define INDENT Streams::indent(OUT);
#define STREAM_INDENT(x) Streams::indent(x);
#define OUTDENT Streams::outdent(OUT);
#define STREAM_OUTDENT(x) Streams::outdent(x);
#define SET_INDENT(N) Streams::set_indentation(OUT, N);
@ Other streams only exist when explicitly created, or "opened". A function
is only allowed to open a new stream if it can be proved that this stream will
always subsequently be "closed". (Except for the possibility of the tool
halting with an internal error, and therefore an |exit(1)|, while the stream
halting with an internal error, and therefore an [[exit(1)]], while the stream
is still open.) A stream can be opened and closed only once, and outside that
time its state is undefined: it must not be used at all.
The simplest way is to make a temporary stream, which can be used as a sort
of clipboard. For instance, suppose we have to compile X before Y, but have to
ensure Y comes before X in the eventual output. We create a temporary stream,
compile X into it, then compile Y to |OUT|, then copy the temporary stream
into |OUT| and dispose of it.
compile X into it, then compile Y to [[OUT]], then copy the temporary stream
into [[OUT]] and dispose of it.
Temporary streams are always created in memory, held in C's local stack frame
rather than allocated and freed via |malloc| and |free|. It must always be
possible to prove that execution passes from |TEMPORARY_TEXT| to
|DISCARD_TEXT|, unless the program makes a fatal exit in between. The stream,
let's call it |TEMP|, exists only between those macros. We can legitimately
rather than allocated and freed via [[malloc]] and [[free]]. It must always be
possible to prove that execution passes from [[TEMPORARY_TEXT]] to
[[DISCARD_TEXT]], unless the program makes a fatal exit in between. The stream,
let's call it [[TEMP]], exists only between those macros. We can legitimately
create a temporary stream many times in one function (for instance inside a
loop body) because each time |TEMP| is created as a new stream, overwriting the
old one. |TEMP| is a different stream each time it is created, so it does
loop body) because each time [[TEMP]] is created as a new stream, overwriting the
old one. [[TEMP]] is a different stream each time it is created, so it does
not violate the rule that every stream is opened and closed once only.
@d TEMPORARY_TEXT(T)
<<*>>=
#define TEMPORARY_TEXT(T)
wchar_t T##_dest[2048];
text_stream T##_stream_structure = Streams::new_buffer(2048, T##_dest);
text_stream *T = &T##_stream_structure;
@d DISCARD_TEXT(T)
#define DISCARD_TEXT(T)
STREAM_CLOSE(T);
@ Otherwise we can create new globally existing streams, provided we take on
the responsibility for seeing that they are properly closed. There are two
choices: a stream in memory, allocated via |malloc| and freed by |free| when
the stream is closed; or a file written to disc, opened via |fopen| and
later closed by |fclose|. Files are always written in text mode, that is,
|"w"| not |"wb"|, for those platforms where this makes a difference.
choices: a stream in memory, allocated via [[malloc]] and freed by [[free]] when
the stream is closed; or a file written to disc, opened via [[fopen]] and
later closed by [[fclose]]. Files are always written in text mode, that is,
[["w"]] not [["wb"]], for those platforms where this makes a difference.
We use streams to handle all of our text file output, so there should be no
calls to |fprintf| anywhere in the program except for binary files.
calls to [[fprintf]] anywhere in the program except for binary files.
@d STREAM_OPEN_TO_FILE(new, fn, enc) Streams::open_to_file(new, fn, enc)
<<*>>=
#define STREAM_OPEN_TO_FILE(new, fn, enc) Streams::open_to_file(new, fn, enc)
@d STREAM_OPEN_TO_FILE_APPEND(new, fn, enc) Streams::open_to_file_append(new, fn, enc)
#define STREAM_OPEN_TO_FILE_APPEND(new, fn, enc) Streams::open_to_file_append(new, fn, enc)
@d STREAM_OPEN_IN_MEMORY(new) Streams::open_to_memory(new, 20480)
#define STREAM_OPEN_IN_MEMORY(new) Streams::open_to_memory(new, 20480)
@d STREAM_CLOSE(stream) Streams::close(stream)
#define STREAM_CLOSE(stream) Streams::close(stream)
@ The following operation is equivalent to |fflush| and makes it more likely
@ The following operation is equivalent to [[fflush]] and makes it more likely
(I put it no higher) that the text written to a stream has all actually been
copied onto the disc, rather than sitting in some operating system buffer.
This helps ensure that any debugging log is up to the minute, in case of
a crash, but its absence wouldn't hurt our normal function. Flushing
a memory stream is legal but does nothing.
@d STREAM_FLUSH(stream) Streams::flush(stream)
<<*>>=
#define STREAM_FLUSH(stream) Streams::flush(stream)
@ A piece of information we can read for any stream is the number of characters
written to it: its "extent". In fact, UTF-8 multi-byte encoding schemes,
together with differing platform interpretations of C's |'\n'|, mean that this
together with differing platform interpretations of C's [['\n']], mean that this
extent is not necessarily either the final file size in bytes or the final
number of human-readable characters. We will only actually use it to detect
whether text has, or has not, been written to a stream between two points in
time, by seeing whether or not it has increased.
@d STREAM_EXTENT(x) Streams::get_position(x)
<<*>>=
#define STREAM_EXTENT(x) Streams::get_position(x)
@ The remaining operations are available only for streams in memory (well, and
for |NULL|, but of course they do nothing when applied to that). While they
for [[NULL]], but of course they do nothing when applied to that). While they
could be provided for file streams, this would be so inefficient that we will
pretend it is impossible. Any function which might need to use one
of these operations should open with the following sentinel macro:
@d STREAM_MUST_BE_IN_MEMORY(x)
<<*>>=
#define STREAM_MUST_BE_IN_MEMORY(x)
if ((x != NULL) && (x->write_to_memory == NULL))
internal_error("text_stream not in memory");
@ First, we can erase one or more recently written characters:
@d STREAM_BACKSPACE(x) Streams::set_position(x, Streams::get_position(x) - 1)
<<*>>=
#define STREAM_BACKSPACE(x) Streams::set_position(x, Streams::get_position(x) - 1)
@d STREAM_ERASE_BACK_TO(start_position) Streams::set_position(OUT, start_position)
#define STREAM_ERASE_BACK_TO(start_position) Streams::set_position(OUT, start_position)
@ Second, we can look at the text written. The minimal form is to look at
just the most recent character, but we can also copy one entire memory
stream into another stream (where the target can be either a memory or file
stream).
@d STREAM_MOST_RECENT_CHAR(x) Streams::latest(x)
<<*>>=
#define STREAM_MOST_RECENT_CHAR(x) Streams::latest(x)
@d STREAM_COPY(to, from) Streams::copy(to, from)
#define STREAM_COPY(to, from) Streams::copy(to, from)
@ So much for the definition; now the implementation.
Here is the |text_stream| structure. Open memory streams are represented by
structures where |write_to_memory| is valid, open file streams by those where
|write_to_file| is valid. That counts every open stream except |NULL|, which
of course doesn't point to a |text_stream| structure at all.
Here is the [[text_stream]] structure. Open memory streams are represented by
structures where [[write_to_memory]] is valid, open file streams by those where
[[write_to_file]] is valid. That counts every open stream except [[NULL]], which
of course doesn't point to a [[text_stream]] structure at all.
Any stream can have |USES_XML_ESCAPES_STRF| set or cleared. When this is set, the
XML (and HTML) escapes of |&amp;| for ampersand, and |&lt;| and |&gt;| for
Any stream can have [[USES_XML_ESCAPES_STRF]] set or cleared. When this is set, the
XML (and HTML) escapes of [[&amp;]] for ampersand, and [[&lt;]] and [[&gt;]] for
angle brackets, will be used automatically on writing. By default this flag
is clear, that is, no conversion is made.
@d MALLOCED_STRF 0x00000001 /* was the |write_to_memory| pointer claimed by |malloc|? */
@d USES_XML_ESCAPES_STRF 0x00000002 /* see above */
@d USES_LOG_ESCAPES_STRF 0x00000004 /* |WRITE| to this stream supports |$| escapes */
@d INDENT_PENDING_STRF 0x00000008 /* we have just ended a line, so further text should indent */
@d FILE_ENCODING_ISO_STRF 0x00000010 /* relevant only for file streams */
@d FILE_ENCODING_UTF8_STRF 0x00000020 /* relevant only for file streams */
@d ECHO_BYTES_STRF 0x00000080 /* for debugging only */
@d FOR_RE_STRF 0x00000100 /* for debugging only */
@d FOR_TT_STRF 0x00000200 /* for debugging only */
@d FOR_CO_STRF 0x00000400 /* for debugging only */
@d FOR_FI_STRF 0x00000800 /* for debugging only */
@d FOR_OM_STRF 0x00001000 /* for debugging only */
@d USES_I6_ESCAPES_STRF 0x00002000 /* as if an Inform 6 string */
@d READ_ONLY_STRF 0x00008000
<<*>>=
#define MALLOCED_STRF 0x00000001 /* was the |write_to_memory| pointer claimed by [[malloc]]? */
#define USES_XML_ESCAPES_STRF 0x00000002 /* see above */
#define USES_LOG_ESCAPES_STRF 0x00000004 /* |WRITE| to this stream supports |$| escapes */
#define INDENT_PENDING_STRF 0x00000008 /* we have just ended a line, so further text should indent */
#define FILE_ENCODING_ISO_STRF 0x00000010 /* relevant only for file streams */
#define FILE_ENCODING_UTF8_STRF 0x00000020 /* relevant only for file streams */
#define ECHO_BYTES_STRF 0x00000080 /* for debugging only */
#define FOR_RE_STRF 0x00000100 /* for debugging only */
#define FOR_TT_STRF 0x00000200 /* for debugging only */
#define FOR_CO_STRF 0x00000400 /* for debugging only */
#define FOR_FI_STRF 0x00000800 /* for debugging only */
#define FOR_OM_STRF 0x00001000 /* for debugging only */
#define USES_I6_ESCAPES_STRF 0x00002000 /* as if an Inform 6 string */
#define READ_ONLY_STRF 0x00008000
@d INDENTATION_BASE_STRF 0x00010000 /* number of tab stops in from the left margin */
@d INDENTATION_MASK_STRF 0x0FFF0000 /* (held in these bits) */
#define INDENTATION_BASE_STRF 0x00010000 /* number of tab stops in from the left margin */
#define INDENTATION_MASK_STRF 0x0FFF0000 /* (held in these bits) */
=
<<*>>=
typedef struct text_stream {
int stream_flags; /* bitmap of the |*_STRF| values above */
FILE *write_to_file; /* for an open stream, exactly one of these is |NULL| */
FILE *write_to_file; /* for an open stream, exactly one of these is [[NULL]] */
struct HTML_file_state *as_HTML; /* relevant only to the |HTML::| section */
wchar_t *write_to_memory;
struct filename *file_written; /* ditto */
@ -245,21 +258,23 @@ typedef struct text_stream {
@ A theological question: what is the text encoding for the null stream?
@d STREAM_USES_UTF8(x) ((x)?((x->stream_flags) & FILE_ENCODING_UTF8_STRF):FALSE)
<<*>>=
#define STREAM_USES_UTF8(x) ((x)?((x->stream_flags) & FILE_ENCODING_UTF8_STRF):FALSE)
@ When text is stored at |write_to_memory|, it is kept as a zero-terminated C
@ When text is stored at [[write_to_memory]], it is kept as a zero-terminated C
wide string, with one word per Unicode code point. It turns out to be
efficient to preserve a small margin of clear space at the end of the space,
so out of the |chars_capacity| space, the following amount will be kept clear:
so out of the [[chars_capacity]] space, the following amount will be kept clear:
@d SPACE_AT_END_OF_STREAM 6
<<*>>=
#define SPACE_AT_END_OF_STREAM 6
@ A statistic we keep, since it costs little:
=
<<*>>=
int total_file_writes = 0; /* number of text files opened for writing during the run */
@h Initialising the stream structure.
@ \section{Initialising the stream structure.}
Note that the following fills in sensible defaults for every field, but the
result is not a valid open stream; it's a blank form ready to be filled in.
@ -270,7 +285,7 @@ The only output file with a sorceror's-apprentice-like ability to grow and
grow is the debugging file, and if it should reach 2 GB then it deserves to be
truncated and we will shed no tears.
=
<<*>>=
void Streams::initialise(text_stream *stream, int from) {
if (stream == NULL) internal_error("tried to initialise NULL stream");
stream->stream_flags = from;
@ -284,11 +299,11 @@ void Streams::initialise(text_stream *stream, int from) {
}
@ Any stream can have the following flag set or cleared. When this is set, the
XML (and HTML) escapes of |&amp;| for ampersand, and |&lt;| and |&gt;| for
XML (and HTML) escapes of [[&amp;]] for ampersand, and [[&lt;]] and [[&gt;]] for
angle brackets, will be used automatically on writing. By default this flag
is clear, that is, no conversion is made.
=
<<*>>=
void Streams::enable_XML_escapes(text_stream *stream) {
if (stream) stream->stream_flags |= USES_XML_ESCAPES_STRF;
}
@ -335,9 +350,9 @@ HTML_file_state *Streams::get_HTML_file_state(text_stream *stream) {
return stream->as_HTML;
}
@h Logging.
@ \section{Logging.}
=
<<*>>=
void Streams::log(OUTPUT_STREAM, void *vS) {
text_stream *stream = (text_stream *) vS;
if (stream == NULL) {
@ -355,11 +370,11 @@ void Streams::log(OUTPUT_STREAM, void *vS) {
}
}
@h Standard I/O wrappers.
The first call to |Streams::get_stdout()| creates a suitable wrapper for |stdout|
and returns a |text_stream *| pointer to it; subsequent calls just return this wrapper.
@ \section{Standard I/O wrappers.}
The first call to [[Streams::get_stdout()]] creates a suitable wrapper for [[stdout]]
and returns a [[text_stream *]] pointer to it; subsequent calls just return this wrapper.
=
<<*>>=
text_stream STDOUT_struct; int stdout_wrapper_initialised = FALSE;
text_stream *Streams::get_stdout(void) {
if (stdout_wrapper_initialised == FALSE) {
@ -378,7 +393,7 @@ text_stream *Streams::get_stdout(void) {
@ And similarly for the standard error file.
=
<<*>>=
text_stream STDERR_struct; int stderr_wrapper_initialised = FALSE;
text_stream *Streams::get_stderr(void) {
if (stderr_wrapper_initialised == FALSE) {
@ -392,11 +407,11 @@ text_stream *Streams::get_stderr(void) {
return &STDERR_struct;
}
@h Creating file streams.
@ \section{Creating file streams.}
Note that this can fail, if the host filing system refuses to open the file,
so we return |TRUE| if and only if successful.
so we return [[TRUE]] if and only if successful.
=
<<*>>=
int Streams::open_to_file(text_stream *stream, filename *name, int encoding) {
if (stream == NULL) internal_error("tried to open NULL stream");
if (name == NULL) internal_error("stream_open_to_file on null filename");
@ -415,7 +430,7 @@ int Streams::open_to_file(text_stream *stream, filename *name, int encoding) {
@ Similarly for appending:
=
<<*>>=
int Streams::open_to_file_append(text_stream *stream, filename *name, int encoding) {
if (stream == NULL) internal_error("tried to open NULL stream");
if (name == NULL) internal_error("stream_open_to_file on null filename");
@ -431,12 +446,12 @@ int Streams::open_to_file_append(text_stream *stream, filename *name, int encodi
return TRUE;
}
@h Creating memory streams.
@ \section{Creating memory streams.}
Here we have a choice. One option is to use //Memory::calloc// to allocate
memory to hold the text of the stream; this too can fail for host platform
reasons, so again we return a success code.
=
<<*>>=
int Streams::open_to_memory(text_stream *stream, int capacity) {
if (stream == NULL) internal_error("tried to open NULL stream");
capacity += SPACE_AT_END_OF_STREAM;
@ -452,7 +467,7 @@ int Streams::open_to_memory(text_stream *stream, int capacity) {
@ The other option avoids fresh memory allocqtion by using specific storage
already available. If called validly, this cannot fail.
=
<<*>>=
text_stream Streams::new_buffer(int capacity, wchar_t *at) {
if (at == NULL) internal_error("tried to make stream wrapper for NULL string");
if (capacity < SPACE_AT_END_OF_STREAM)
@ -465,16 +480,16 @@ text_stream Streams::new_buffer(int capacity, wchar_t *at) {
return stream;
}
@h Converting from C strings.
@ \section{Converting from C strings.}
We then have three ways to open a stream whose initial contents are given
by a C string. First, a wide string (a sequence of 32-bit Unicode code
points, null terminated):
=
<<*>>=
int Streams::open_from_wide_string(text_stream *stream, const wchar_t *c_string) {
if (stream == NULL) internal_error("tried to open NULL stream");
int capacity = (c_string)?((int) wcslen(c_string)):0;
@<Ensure a capacity large enough to hold the initial string in one frame@>;
<<Ensure a capacity large enough to hold the initial string in one frame>>;
if (c_string) Streams::write_wide_string(stream, c_string);
return TRUE;
}
@ -486,11 +501,11 @@ void Streams::write_wide_string(text_stream *stream, const wchar_t *c_string) {
@ Similarly, an ISO string (a sequence of 8-bit code points in the first
page of the Unicode set, null terminated):
=
<<*>>=
int Streams::open_from_ISO_string(text_stream *stream, const char *c_string) {
if (stream == NULL) internal_error("tried to open NULL stream");
int capacity = (c_string)?((int) strlen(c_string)):0;
@<Ensure a capacity large enough to hold the initial string in one frame@>;
<<Ensure a capacity large enough to hold the initial string in one frame>>;
if (c_string) Streams::write_ISO_string(stream, c_string);
return TRUE;
}
@ -501,11 +516,11 @@ void Streams::write_ISO_string(text_stream *stream, const char *c_string) {
@ Finally, a UTF-8 encoded C string:
=
<<*>>=
int Streams::open_from_UTF8_string(text_stream *stream, const char *c_string) {
if (stream == NULL) internal_error("tried to open NULL stream");
int capacity = (c_string)?((int) strlen(c_string)):0;
@<Ensure a capacity large enough to hold the initial string in one frame@>;
<<Ensure a capacity large enough to hold the initial string in one frame>>;
if (c_string) Streams::write_UTF8_string(stream, c_string);
return TRUE;
}
@ -519,16 +534,16 @@ void Streams::write_UTF8_string(text_stream *stream, const char *c_string) {
@ ...all of which use:
@<Ensure a capacity large enough to hold the initial string in one frame@> =
<<Ensure a capacity large enough to hold the initial string in one frame>>=
if (capacity < 8) capacity = 8;
capacity += 1+SPACE_AT_END_OF_STREAM;
int rv = Streams::open_to_memory(stream, capacity);
if (rv == FALSE) return FALSE;
@h Converting to C strings.
@ \section{Converting to C strings.}
Now for the converse problem.
=
<<*>>=
void Streams::write_as_wide_string(wchar_t *C_string, text_stream *stream, int buffer_size) {
if (buffer_size == 0) return;
if (stream == NULL) { C_string[0] = 0; return; }
@ -547,7 +562,7 @@ void Streams::write_as_wide_string(wchar_t *C_string, text_stream *stream, int b
@ Unicode code points outside the first page are flattened to |'?'| in an
ISO string:
=
<<*>>=
void Streams::write_as_ISO_string(char *C_string, text_stream *stream, int buffer_size) {
if (buffer_size == 0) return;
if (stream == NULL) { C_string[0] = 0; return; }
@ -564,7 +579,7 @@ void Streams::write_as_ISO_string(char *C_string, text_stream *stream, int buffe
C_string[i] = 0;
}
@ =
<<*>>=
void Streams::write_as_UTF8_string(char *C_string, text_stream *stream, int buffer_size) {
if (buffer_size == 0) return;
if (stream == NULL) { C_string[0] = 0; return; }
@ -593,9 +608,9 @@ void Streams::write_as_UTF8_string(char *C_string, text_stream *stream, int buff
to[i] = 0;
}
@h Locale versions.
@ \section{Locale versions.}
=
<<*>>=
int Streams::open_from_locale_string(text_stream *stream, const char *C_string) {
if (Locales::get(SHELL_LOCALE) == FILE_ENCODING_UTF8_STRF)
return Streams::open_from_UTF8_string(stream, C_string);
@ -621,19 +636,19 @@ void Streams::write_locale_string(text_stream *stream, char *C_string) {
else Errors::fatal("unknown command line locale");
}
@h Flush and close.
@ \section{Flush and close.}
Note that flush is an operation which can be performed on any stream, including
|NULL|:
[[NULL]]:
=
<<*>>=
void Streams::flush(text_stream *stream) {
if (stream == NULL) return;
if (stream->write_to_file) fflush(stream->write_to_file);
}
@ But closing is not allowed for |NULL| or the standard I/O wrappers:
@ But closing is not allowed for [[NULL]] or the standard I/O wrappers:
=
<<*>>=
void Streams::close(text_stream *stream) {
if (stream == NULL) internal_error("tried to close NULL stream");
if (stream == &STDOUT_struct) internal_error("tried to close STDOUT stream");
@ -644,17 +659,17 @@ void Streams::close(text_stream *stream) {
stream->stream_continues = NULL;
}
stream->chars_capacity = -1; /* mark as closed */
if (stream->write_to_file) @<Take suitable action to close the file stream@>;
if (stream->write_to_memory) @<Take suitable action to close the memory stream@>;
if (stream->write_to_file) <<Take suitable action to close the file stream>>;
if (stream->write_to_memory) <<Take suitable action to close the memory stream>>;
}
@ Note that we need do nothing to close a memory stream when the storage
was supplied by our client; it only needs freeing if we were the ones who
allocated it.
Inscrutably, |fclose| returns |EOF| to report any failure.
Inscrutably, [[fclose]] returns [[EOF]] to report any failure.
@<Take suitable action to close the file stream@> =
<<Take suitable action to close the file stream>>=
if ((ferror(stream->write_to_file)) || (fclose(stream->write_to_file) == EOF))
Errors::fatal("The host computer reported an error trying to write a text file");
if (stream != DL)
@ -666,14 +681,14 @@ Inscrutably, |fclose| returns |EOF| to report any failure.
@ Note that we need do nothing to close a memory stream when the storage
was supplied by our client; it only needs freeing if we were the ones who
allocated it. |free| is a void function; in theory it cannot fail, if
allocated it. [[free]] is a void function; in theory it cannot fail, if
supplied a valid argument.
We have to be very careful once we have called |free|, because that memory
may well contain the |text_stream| structure to which |stream| points -- see
We have to be very careful once we have called [[free]], because that memory
may well contain the [[text_stream]] structure to which [[stream]] points -- see
how continuations are made, below.
@<Take suitable action to close the memory stream@> =
<<Take suitable action to close the memory stream>>=
if ((stream->stream_flags) & MALLOCED_STRF) {
wchar_t *mem = stream->write_to_memory;
stream->write_to_memory = NULL;
@ -681,16 +696,16 @@ how continuations are made, below.
stream = NULL;
}
@h Writing.
Our equivalent of |fputc| reads:
@ \section{Writing.}
Our equivalent of [[fputc]] reads:
=
<<*>>=
void Streams::putc(int c_int, text_stream *stream) {
unsigned int c;
if (c_int >= 0) c = (unsigned int) c_int; else c = (unsigned int) (c_int + 256);
if (stream == NULL) return;
text_stream *first_stream = stream;
if (c != '\n') @<Insert indentation if this is pending@>;
if (c != '\n') <<Insert indentation if this is pending>>;
if (stream->stream_flags & READ_ONLY_STRF) internal_error("modifying read-only stream");
if ((stream->stream_flags) & USES_XML_ESCAPES_STRF) {
switch(c) {
@ -701,10 +716,10 @@ void Streams::putc(int c_int, text_stream *stream) {
}
}
while (stream->stream_continues) stream = stream->stream_continues;
@<Ensure there is room to expand the escape sequence into@>;
<<Ensure there is room to expand the escape sequence into>>;
if (stream->write_to_file) {
if (stream->stream_flags & FILE_ENCODING_UTF8_STRF)
@<Put a UTF8-encoded character into the underlying file@>
<<Put a UTF8-encoded character into the underlying file>>
else if (stream->stream_flags & FILE_ENCODING_ISO_STRF) {
if (c >= 0x100) c = '?';
fputc((int) c, stream->write_to_file);
@ -734,7 +749,7 @@ void Streams::putc(int c_int, text_stream *stream) {
@ Where we pack large character values, up to 65535, as follows.
@<Put a UTF8-encoded character into the underlying file@> =
<<Put a UTF8-encoded character into the underlying file>>=
if (c >= 0x800) {
fputc(0xE0 + (c >> 12), stream->write_to_file);
fputc(0x80 + ((c >> 6) & 0x3f), stream->write_to_file);
@ -744,7 +759,7 @@ void Streams::putc(int c_int, text_stream *stream) {
fputc(0x80 + (c & 0x3f), stream->write_to_file);
} else fputc((int) c, stream->write_to_file);
@<Insert indentation if this is pending@> =
<<Insert indentation if this is pending>>=
if (first_stream->stream_flags & INDENT_PENDING_STRF) {
first_stream->stream_flags -= INDENT_PENDING_STRF;
int L = (first_stream->stream_flags & INDENTATION_MASK_STRF)/INDENTATION_BASE_STRF;
@ -754,24 +769,24 @@ void Streams::putc(int c_int, text_stream *stream) {
}
}
@ The following is checked before any numerical |printf|-style escape is expanded
@ The following is checked before any numerical [[printf]]-style escape is expanded
into the stream, or before any single character is written. Thus we cannot
overrun our buffers unless the expansion of a numerical escape exceeds
|SPACE_AT_END_OF_STREAM| plus 1 in size. Since no outside influence gets to
choose what formatting escapes we use (so that |%3000d|, say, can't occur),
[[SPACE_AT_END_OF_STREAM]] plus 1 in size. Since no outside influence gets to
choose what formatting escapes we use (so that [[%3000d]], say, can't occur),
we can be pretty confident.
The interesting case occurs when we run out of memory in a memory stream.
We make a continuation to a fresh |text_stream| structure, which points to twice
as much memory as the original, allocated via |malloc|. (We will actually need
We make a continuation to a fresh [[text_stream]] structure, which points to twice
as much memory as the original, allocated via [[malloc]]. (We will actually need
a little more memory than that because we also have to make room for the
|text_stream| structure itself.) We then set |stream| to the |continuation|. Given
that |malloc| was successful -- and it must have been or we would have stopped
[[text_stream]] structure itself.) We then set [[stream]] to the [[continuation]]. Given
that [[malloc]] was successful -- and it must have been or we would have stopped
with a fatal error -- the continuation is guaranteed to be large enough,
since it's twice the size of the original, which itself was large enough to
hold any escape sequence when opened.
@<Ensure there is room to expand the escape sequence into@> =
<<Ensure there is room to expand the escape sequence into>>=
if (stream->chars_written + SPACE_AT_END_OF_STREAM >= stream->chars_capacity) {
if (stream->write_to_file) return; /* write nothing further */
if (stream->write_to_memory) {
@ -791,7 +806,7 @@ hold any escape sequence when opened.
@ Literal printing is just printing with XML escapes switched off:
=
<<*>>=
void Streams::literal(text_stream *stream, char *p) {
if (stream == NULL) return;
int i, x = ((stream->stream_flags) & USES_XML_ESCAPES_STRF);
@ -805,7 +820,7 @@ outdent, but error conditions can cause some compilation routines to issue
problem messages and then leave what they're doing incomplete, so we will
be a little cautious about assuming that a mismatch means an error.
=
<<*>>=
void Streams::indent(text_stream *stream) {
if (stream == NULL) return;
stream->stream_flags += INDENTATION_BASE_STRF;
@ -827,13 +842,13 @@ void Streams::set_indentation(text_stream *stream, int N) {
stream->stream_flags += N*INDENTATION_BASE_STRF;
}
@ We can read the position for any stream, including |NULL|, but no matter
how much is written to |NULL| this position never budges.
@ We can read the position for any stream, including [[NULL]], but no matter
how much is written to [[NULL]] this position never budges.
Because of continuations, this is not as simple as returning the |chars_written|
field.
=
<<*>>=
int Streams::get_position(text_stream *stream) {
int t = 0;
while (stream) {
@ -843,11 +858,11 @@ int Streams::get_position(text_stream *stream) {
return t;
}
@h Memory-stream-only functions.
@ \section{Memory-stream-only functions.}
While it would be easy enough to implement this for file streams too, there's
no point, since it is used only in concert with backspacing.
=
<<*>>=
int Streams::latest(text_stream *stream) {
if (stream == NULL) return 0;
if (stream->write_to_file) internal_error("stream_latest on file stream");
@ -863,7 +878,7 @@ int Streams::latest(text_stream *stream) {
@ Accessing characters by index. Note that the stream terminates at the first
zero byte found, so that putting a zero truncates it.
=
<<*>>=
wchar_t Streams::get_char_at_index(text_stream *stream, int position) {
if (stream == NULL) internal_error("examining null stream");
if (stream->write_to_file) internal_error("examining file stream");
@ -901,7 +916,7 @@ won't worry about the inefficiency of freeing up the memory saved by closing
such continuation blocks (which would be inefficient if we immediately had
to open similar ones again).
=
<<*>>=
void Streams::set_position(text_stream *stream, int position) {
if (stream == NULL) return;
if (position < 0) position = 0; /* to simplify the implementation of backspacing */
@ -923,9 +938,9 @@ void Streams::set_position(text_stream *stream, int position) {
}
@ Lastly, our copying function, where |from| has to be a memory stream (or
|NULL|) but |to| can be anything (including |NULL|).
[[NULL]]) but [[to]] can be anything (including [[NULL]]).
=
<<*>>=
void Streams::copy(text_stream *to, text_stream *from) {
if ((from == NULL) || (to == NULL)) return;
if (from == to) internal_error("tried to copy a stream to itself");
@ -939,10 +954,10 @@ void Streams::copy(text_stream *to, text_stream *from) {
}
}
@h Writer.
This writes one stream into another one, which implements |%S|.
@ \section{Writer.}
This writes one stream into another one, which implements [[%S]].
=
<<*>>=
void Streams::writer(OUTPUT_STREAM, char *format_string, void *vS) {
text_stream *S = (text_stream *) vS;
Streams::copy(OUT, S);

View file

@ -3,18 +3,18 @@
To provide heterogeneous tree structures, where a node can be any structure
known to the Foundation memory manager.
@h Trees and nodes.
@ \section{Trees and nodes.}
The tree itself is really just a root node, which is initially null, so that
a tree can be empty.
=
<<*>>=
typedef struct heterogeneous_tree {
struct tree_type *type;
struct tree_node *root;
CLASS_DEFINITION
} heterogeneous_tree;
@ =
<<*>>=
heterogeneous_tree *Trees::new(tree_type *type) {
heterogeneous_tree *T = CREATE(heterogeneous_tree);
T->type = type;
@ -22,7 +22,7 @@ heterogeneous_tree *Trees::new(tree_type *type) {
return T;
}
@ =
<<*>>=
typedef struct tree_node {
struct heterogeneous_tree *owner;
struct tree_node_type *type;
@ -36,7 +36,7 @@ typedef struct tree_node {
@ A node is created in limbo, removed from its tree, but it is still somehow
owned by it.
=
<<*>>=
tree_node *Trees::new_node(heterogeneous_tree *T, tree_node_type *type, general_pointer wrapping) {
if (T == NULL) internal_error("no tree");
if (wrapping.run_time_type_code == -1)
@ -57,27 +57,27 @@ tree_node *Trees::new_node(heterogeneous_tree *T, tree_node_type *type, general_
@ A convenient abbreviation for a common manoeuvre:
=
<<*>>=
tree_node *Trees::new_child(tree_node *of, tree_node_type *type, general_pointer wrapping) {
tree_node *N = Trees::new_node(of->owner, type, wrapping);
Trees::make_child(N, of);
return N;
}
@h Types.
@ \section{Types.}
The above will provide for multiple different types of tree to be used for
different purposes. Heterogeneous trees allow the coder to make dangerously
type-unsafe structures, so we want to hedge them in with self-imposed
constraints:
=
<<*>>=
typedef struct tree_type {
struct text_stream *name;
int (*verify_root)(struct tree_node *); /* function to vet the root node */
CLASS_DEFINITION
} tree_type;
@ =
<<*>>=
tree_type *Trees::new_type(text_stream *name, int (*verifier)(tree_node *)) {
tree_type *T = CREATE(tree_type);
T->name = Str::duplicate(name);
@ -86,17 +86,17 @@ tree_type *Trees::new_type(text_stream *name, int (*verifier)(tree_node *)) {
}
@ Each node in a tree also has a type. Whenever the children of a node change,
they are re-verified by the |verify_children|.
they are re-verified by the [[verify_children]].
=
<<*>>=
typedef struct tree_node_type {
struct text_stream *node_type_name; /* text such as |I"INVOCATION"| */
struct text_stream *node_type_name; /* text such as [[I"INVOCATION"]] */
int required_CLASS; /* if any; or negative for no restriction */
int (*verify_children)(struct tree_node *); /* function to vet the children */
CLASS_DEFINITION
} tree_node_type;
@ =
<<*>>=
tree_node_type *Trees::new_node_type(text_stream *name, int req,
int (*verifier)(tree_node *)) {
tree_node_type *NT = CREATE(tree_node_type);
@ -106,11 +106,11 @@ tree_node_type *Trees::new_node_type(text_stream *name, int req,
return NT;
}
@h Hierarchy.
@ \section{Hierarchy.}
A special function is needed to choose the root node; and note that this is
verified.
=
<<*>>=
void Trees::make_root(heterogeneous_tree *T, tree_node *N) {
if (T == NULL) internal_error("no tree");
if (N == NULL) internal_error("no node");
@ -130,7 +130,7 @@ void Trees::remove_root(heterogeneous_tree *T) {
@ Otherwise, nodes are placed in a tree with respect to other nodes:
=
<<*>>=
void Trees::make_child(tree_node *N, tree_node *of) {
if (N == NULL) internal_error("no node");
if (of == NULL) internal_error("no node");
@ -175,7 +175,7 @@ void Trees::make_sibling(tree_node *N, tree_node *of) {
@ Removing a node from a tree does not change its ownership -- it still belongs
to that tree.
=
<<*>>=
void Trees::remove(tree_node *N) {
if (N == NULL) internal_error("no node");
if (N == N->owner->root) { Trees::remove_root(N->owner); return; }
@ -198,7 +198,7 @@ int Trees::verify_children(tree_node *N) {
return TRUE;
}
@h Traversals.
@ \section{Traversals.}
These two functions allow us to traverse the tree, visiting each node along
the way and carrying a state as we do. The distinction is that //Trees::traverse_from//
iterates from and then below the given node, but doesn't go through its siblings,
@ -206,7 +206,7 @@ whereas //Trees::traverse// does.
Note that it is legal to traverse the empty node, and does nothing.
=
<<*>>=
void Trees::traverse_tree(heterogeneous_tree *T,
int (*visitor)(tree_node *, void *, int L), void *state) {
if (T == NULL) internal_error("no tree");
@ -229,7 +229,7 @@ void Trees::traverse(tree_node *N,
@
=
<<*>>=
void Trees::prune_tree(heterogeneous_tree *T,
int (*visitor)(tree_node *, void *), void *state) {
if (T == NULL) internal_error("no tree");

View file

@ -2,40 +2,41 @@
Formatted text output to streams.
@h Registration.
The main function here is modelled on the "minimum |printf|" function
@ \section{Registration.}
The main function here is modelled on the "minimum [[printf]]" function
used as an example in Kernighan and Ritchie, Chapter 7, but because it
prints to streams, it combines the traditional functions |printf|, |sprintf|
and |fprintf| in one. It also contains a number of doohickeys to provide
prints to streams, it combines the traditional functions [[printf]], [[sprintf]]
and [[fprintf]] in one. It also contains a number of doohickeys to provide
for a wider and extensible range of string interpolations.
Traditionally, in the C library, everything in the formatting string is
literal except for |%| escapes: thus |%d| means "integer goes here", and
so on. We follow this but allow extra |%| escapes unknown to K&R, and we
also allow a further family of |$| escapes intended for the debugging log
literal except for [[%]] escapes: thus [[%d]] means "integer goes here", and
so on. We follow this but allow extra [[%]] escapes unknown to K&R, and we
also allow a further family of [[$]] escapes intended for the debugging log
only; these are restricted to streams flagged as for debugging and generally
produce guru meditation numbers rather than user-friendly information.
Each escape, say |%z|, must be "registered" before use, and will be
Each escape, say [[%z]], must be "registered" before use, and will be
given one of the following categories:
@d VACANT_ECAT 0 /* unregistered */
@d POINTER_ECAT 1 /* data to be printed is a pointer to a structure */
@d INTSIZED_ECAT 2 /* data to be printed is or fits into an integer */
@d WORDING_ECAT 3 /* data to be printed is a |wording| structure from inform7 */
@d DIRECT_ECAT 4 /* data must be printed directly by the code below */
<<*>>=
#define VACANT_ECAT 0 /* unregistered */
#define POINTER_ECAT 1 /* data to be printed is a pointer to a structure */
#define INTSIZED_ECAT 2 /* data to be printed is or fits into an integer */
#define WORDING_ECAT 3 /* data to be printed is a [[wording]] structure from inform7 */
#define DIRECT_ECAT 4 /* data must be printed directly by the code below */
@ We'll start with |%| escapes, which generalise the familiar |printf|
escapes such as |%d|. Cumbersomely, we need three sorts of escape: those where
@ We'll start with [[%]] escapes, which generalise the familiar [[printf]]
escapes such as [[%d]]. Cumbersomely, we need three sorts of escape: those where
the variable argument token is a pointer, those where it's essentially an
integer, and those where it's a structure used only in the Inform 7 compiler
called a |wording|. The standard C typechecker can't generalise across these,
called a [[wording]]. The standard C typechecker can't generalise across these,
so we have to do everything three times. (And then we have to do all that twice,
because the loggers don't use format strings.)
=
<<*>>=
int escapes_registered = FALSE;
int escapes_category[2][128]; /* one of the |*_ECAT| values above */
int escapes_category[2][128]; /* one of the [[*_ECAT]] values above */
void *the_escapes[2][128]; /* the function to call to implement this */
typedef void (*writer_function)(text_stream *, char *, void *);
@ -47,7 +48,7 @@ typedef void (*log_function_I)(text_stream *, int);
typedef void (*log_function_W)(text_stream *, wording);
#endif
@ =
<<*>>=
void Writers::log_escape_usage(void) {
for (int cat = 0; cat < 2; cat++) {
char *alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@ -63,7 +64,7 @@ void Writers::log_escape_usage(void) {
@ That gives us a number of front doors:
=
<<*>>=
void Writers::register_writer(int esc, void (*f)(text_stream *, char *, void *)) {
Writers::register_writer_p(0, esc, (void *) f, POINTER_ECAT);
}
@ -83,9 +84,9 @@ void Writers::register_logger_I(int esc, void (*f)(text_stream *, int)) {
@ All leading to:
=
<<*>>=
void Writers::register_writer_p(int set, int esc, void *f, int cat) {
if (escapes_registered == FALSE) @<Initialise the table of escapes@>;
if (escapes_registered == FALSE) <<Initialise the table of escapes>>;
if ((esc < 0) || (esc >= 128) ||
((Characters::isalpha((wchar_t) esc) == FALSE) &&
(Characters::isdigit((wchar_t) esc) == FALSE)))
@ -98,13 +99,13 @@ void Writers::register_writer_p(int set, int esc, void *f, int cat) {
the_escapes[set][esc] = f;
}
@ We're going to implement |%d| and a few others directly, so those are marked
@ We're going to implement [[%d]] and a few others directly, so those are marked
in the table as being unavailable for registration.
Note that we don't support |%f| for floats; but we do add our very own |%w|
Note that we don't support [[%f]] for floats; but we do add our very own [[%w]]
for wide strings.
@<Initialise the table of escapes@> =
<<Initialise the table of escapes>>=
escapes_registered = TRUE;
for (int e=0; e<2; e++)
for (int i=0; i<128; i++) {
@ -122,11 +123,11 @@ for wide strings.
escapes_category[1]['%'] = DIRECT_ECAT;
escapes_category[1]['$'] = DIRECT_ECAT;
@h Writing.
@ \section{Writing.}
We can finally get on with that formatted-print function we've all been
waiting for:
=
<<*>>=
void Writers::printf(text_stream *stream, char *fmt, ...) {
va_list ap; /* the variable argument list signified by the dots */
char *p;
@ -135,13 +136,13 @@ void Writers::printf(text_stream *stream, char *fmt, ...) {
for (p = fmt; *p; p++) {
switch (*p) {
case '%': {
int set = 0; @<Deal with escape sequences@>;
int set = 0; <<Deal with escape sequences>>;
break;
}
case '$': {
int set = 1;
if ((stream->stream_flags) & USES_LOG_ESCAPES_STRF)
@<Deal with escape sequences@>
<<Deal with escape sequences>>
else Streams::putc('$', stream);
break;
}
@ -159,7 +160,7 @@ void Writers::printf(text_stream *stream, char *fmt, ...) {
va_end(ap); /* macro to end variable argument processing */
}
@<Deal with escape sequences@> =
<<Deal with escape sequences>>=
char format_string[8];
int esc_number = ' ';
int i = 0;
@ -212,7 +213,7 @@ void Writers::printf(text_stream *stream, char *fmt, ...) {
#endif
break;
}
case DIRECT_ECAT: @<Implement this using the original printf@>; break;
case DIRECT_ECAT: <<Implement this using the original printf>>; break;
case VACANT_ECAT:
WRITE_TO(STDERR, "*** Bad WRITE escape: <%s> ***\n", format_string);
internal_error("Unknown string escape");
@ -220,24 +221,24 @@ void Writers::printf(text_stream *stream, char *fmt, ...) {
}
@ Here the traditional C library helps us out with the difficult ones to get
right. We don't trouble to check that correct |printf| escapes have been used:
right. We don't trouble to check that correct [[printf]] escapes have been used:
instead, we pass anything in the form of a percentage sign, followed by
up to four nonalphabetic modifying characters, followed by an alphabetic
category character for numerical printing, straight through to |sprintf|
or |fprintf|.
category character for numerical printing, straight through to [[sprintf]]
or [[fprintf]].
Thus an escape like |%04d| is handled by the standard C library, but not
|%s|, which we handle directly. That's for two reasons: first, we want to
Thus an escape like [[%04d]] is handled by the standard C library, but not
[[%s]], which we handle directly. That's for two reasons: first, we want to
be careful to prevent overruns of memory streams; second, we need to ensure
that the correct encoding is used when writing to disc. The numerical
escapes involve only characters whose representation is the same in all our
file encodings, but expanding |%s| does not.
file encodings, but expanding [[%s]] does not.
@<Implement this using the original printf@> =
<<Implement this using the original printf>>=
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-nonliteral"
switch (esc_number) {
case 'c': case 'd': case 'i': case 'x': { /* |char| is promoted to |int| in variable arguments */
case 'c': case 'd': case 'i': case 'x': { /* [[char| is promoted to |int]] in variable arguments */
int ival = va_arg(ap, int);
char temp[256];
if (snprintf(temp, 255, format_string, ival) >= 255) strcpy(temp, "?");
@ -264,13 +265,14 @@ file encodings, but expanding |%s| does not.
}
#pragma clang diagnostic pop
@h Abbreviation macros.
@ \section{Abbreviation macros.}
The following proved convenient for Inform, at any rate.
@d REGISTER_WRITER(c, f) Writers::register_logger(c, &f##_writer);
@d COMPILE_WRITER(t, f)
<<*>>=
#define REGISTER_WRITER(c, f) Writers::register_logger(c, &f##_writer);
#define COMPILE_WRITER(t, f)
void f##_writer(text_stream *format, void *obj) { text_stream *SDL = DL; DL = format; if (DL) f((t) obj); DL = SDL; }
@d REGISTER_WRITER_I(c, f) Writers::register_logger_I(c, &f##_writer);
@d COMPILE_WRITER_I(t, f)
#define REGISTER_WRITER_I(c, f) Writers::register_logger_I(c, &f##_writer);
#define COMPILE_WRITER_I(t, f)
void f##_writer(text_stream *format, int I) { text_stream *SDL = DL; DL = format; if (DL) f((t) I); DL = SDL; }