foundation-module: Chapter 2: Nowebify.
This commit is contained in:
parent
b3ba94b5a6
commit
830118d644
10 changed files with 643 additions and 600 deletions
|
@ -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++) {
|
|
@ -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++)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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;
|
|
@ -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)
|
|
@ -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);
|
|
@ -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 |&| for ampersand, and |<| and |>| for
|
||||
Any stream can have [[USES_XML_ESCAPES_STRF]] set or cleared. When this is set, the
|
||||
XML (and HTML) escapes of [[&]] for ampersand, and [[<]] and [[>]] 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 |&| for ampersand, and |<| and |>| for
|
||||
XML (and HTML) escapes of [[&]] for ampersand, and [[<]] and [[>]] 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);
|
|
@ -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");
|
|
@ -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; }
|
Loading…
Reference in a new issue