From 830118d6446cea606b443ffce0654cc194a0ae47 Mon Sep 17 00:00:00 2001 From: AwesomeAdam54321 Date: Sat, 9 Mar 2024 10:18:53 +0800 Subject: [PATCH] foundation-module: Chapter 2: Nowebify. --- .../{Debugging Log.w => Debugging_Log.nw} | 86 ++--- .../{Dictionaries.w => Dictionaries.nw} | 72 ++-- ...dation Classes.w => Foundation_Classes.nw} | 95 ++--- ...nd Stacks.w => Linked_Lists_and_Stacks.nw} | 95 ++--- .../Chapter_2/{Locales.w => Locales.nw} | 37 +- .../Chapter_2/{Memory.w => Memory.nw} | 297 ++++++++------- .../Chapter_2/{Methods.w => Methods.nw} | 74 ++-- .../Chapter_2/{Streams.w => Streams.nw} | 357 +++++++++--------- .../Chapter_2/{Trees.w => Trees.nw} | 40 +- ...s and Loggers.w => Writers_and_Loggers.nw} | 90 ++--- 10 files changed, 643 insertions(+), 600 deletions(-) rename foundation-module/Chapter_2/{Debugging Log.w => Debugging_Log.nw} (85%) rename foundation-module/Chapter_2/{Dictionaries.w => Dictionaries.nw} (89%) rename foundation-module/Chapter_2/{Foundation Classes.w => Foundation_Classes.nw} (61%) rename foundation-module/Chapter_2/{Linked Lists and Stacks.w => Linked_Lists_and_Stacks.nw} (76%) rename foundation-module/Chapter_2/{Locales.w => Locales.nw} (83%) rename foundation-module/Chapter_2/{Memory.w => Memory.nw} (77%) rename foundation-module/Chapter_2/{Methods.w => Methods.nw} (65%) rename foundation-module/Chapter_2/{Streams.w => Streams.nw} (75%) rename foundation-module/Chapter_2/{Trees.w => Trees.nw} (95%) rename foundation-module/Chapter_2/{Writers and Loggers.w => Writers_and_Loggers.nw} (75%) diff --git a/foundation-module/Chapter_2/Debugging Log.w b/foundation-module/Chapter_2/Debugging_Log.nw similarity index 85% rename from foundation-module/Chapter_2/Debugging Log.w rename to foundation-module/Chapter_2/Debugging_Log.nw index cf81a87..962ae4a 100644 --- a/foundation-module/Chapter_2/Debugging Log.w +++ b/foundation-module/Chapter_2/Debugging_Log.nw @@ -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; - @; + <; } if ((a < 0) || (a >= NO_DEFINED_DA_VALUES)) internal_error("aspect out of range"); debugging_aspect *da = &(the_debugging_aspects[a]); - @; + <; } -@ = +<>= for (int a=0; ahyphenated_name = Str::new(); @@ -82,7 +84,7 @@ void Log::declare_aspect(int a, wchar_t *name, int def, int alt) { da->alternate = FALSE; } -@ = +<>= 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>= 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; ihash_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) @; - if (change == 1) @; - @; + if (D == NULL) <>; + if (change == 1) <>; + <>; } @ It's legal to perform a find on the null dictionary: the answer's always "no". -@ = +<>= 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. -@ = +<>= if (D->no_entries > D->hash_table_size) { dictionary *D2 = Dictionaries::new(2*D->hash_table_size, D->textual); for (int i=0; ihash_table_size; i++) @@ -154,23 +154,23 @@ ideal population, we double the dictionary's capacity. D->hash_table = D2->hash_table; } -@ = +<>= 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) { @; return E; } + if (change == 1) { <>; return E; } } else { if (Str::eq(K, E->key)) { - if (change == -1) @; + if (change == -1) <>; return E; } } } if (change == 1) { dict_entry *E = CREATE(dict_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. -@ = +<>= 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. -@ = +<>= 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; ihash_table_size; i++) diff --git a/foundation-module/Chapter_2/Foundation Classes.w b/foundation-module/Chapter_2/Foundation_Classes.nw similarity index 61% rename from foundation-module/Chapter_2/Foundation Classes.w rename to foundation-module/Chapter_2/Foundation_Classes.nw index 1086757..5742c0e 100755 --- a/foundation-module/Chapter_2/Foundation Classes.w +++ b/foundation-module/Chapter_2/Foundation_Classes.nw @@ -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) diff --git a/foundation-module/Chapter_2/Linked Lists and Stacks.w b/foundation-module/Chapter_2/Linked_Lists_and_Stacks.nw similarity index 76% rename from foundation-module/Chapter_2/Linked Lists and Stacks.w rename to foundation-module/Chapter_2/Linked_Lists_and_Stacks.nw index dec6da0..d8fe444 100644 --- a/foundation-module/Chapter_2/Linked Lists and Stacks.w +++ b/foundation-module/Chapter_2/Linked_Lists_and_Stacks.nw @@ -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) diff --git a/foundation-module/Chapter_2/Locales.w b/foundation-module/Chapter_2/Locales.nw similarity index 83% rename from foundation-module/Chapter_2/Locales.w rename to foundation-module/Chapter_2/Locales.nw index 4e721af..dca5170 100644 --- a/foundation-module/Chapter_2/Locales.w +++ b/foundation-module/Chapter_2/Locales.nw @@ -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>= 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>= 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; diff --git a/foundation-module/Chapter_2/Memory.w b/foundation-module/Chapter_2/Memory.nw similarity index 77% rename from foundation-module/Chapter_2/Memory.w rename to foundation-module/Chapter_2/Memory.nw index e13082e..cb7be31 100755 --- a/foundation-module/Chapter_2/Memory.w +++ b/foundation-module/Chapter_2/Memory.nw @@ -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; - @; + <>; mh = (memblock_header *) cp; used_in_current_memblock = sizeof(memblock_header) + SAFETY_MARGIN; mh->the_memory = (void *) (cp + used_in_current_memblock); - @; + <>; } -@ 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. -@ = +<>= 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. -@ = +<>= 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 */ - @; + <>; 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; - @; - @; + <>; + <>; 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. -@ = +<>= 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: -@ = +<>= 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. -@ = +<>= 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) @; - @; - @; + if (total_claimed_simply == 0) <>; + <>; + <>; 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. -@ = +<>= 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. -@ = +<>= LOCK_MUTEX(memory_statistics_mutex); for (int i=0; i = +<>= 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 */ - @; + <>; - 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; - @; + <>; int overhead_for_objects = total_for_objects - total_for_objects_used; /* bytes wasted */ - @; + <>; } -@ = +<>= int i, j; for (j=0; j = +<>= for (int i=0; i = +<>= 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) diff --git a/foundation-module/Chapter_2/Methods.w b/foundation-module/Chapter_2/Methods.nw similarity index 65% rename from foundation-module/Chapter_2/Methods.w rename to foundation-module/Chapter_2/Methods.nw index 4ce92d5..d300a78 100644 --- a/foundation-module/Chapter_2/Methods.w +++ b/foundation-module/Chapter_2/Methods.nw @@ -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); diff --git a/foundation-module/Chapter_2/Streams.w b/foundation-module/Chapter_2/Streams.nw similarity index 75% rename from foundation-module/Chapter_2/Streams.w rename to foundation-module/Chapter_2/Streams.nw index b3bd1ab..c26824a 100644 --- a/foundation-module/Chapter_2/Streams.w +++ b/foundation-module/Chapter_2/Streams.nw @@ -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; - @; + <>; 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; - @; + <>; 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; - @; + <>; 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: -@ = +<>= 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) @; - if (stream->write_to_memory) @; + if (stream->write_to_file) <>; + if (stream->write_to_memory) <>; } @ 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. -@ = +<>= 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. -@ = +<>= 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') @; + if (c != '\n') <>; 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; - @; + <>; if (stream->write_to_file) { if (stream->stream_flags & FILE_ENCODING_UTF8_STRF) - @ + <> 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. -@ = +<>= 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); -@ = +<>= 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. -@ = +<>= 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); diff --git a/foundation-module/Chapter_2/Trees.w b/foundation-module/Chapter_2/Trees.nw similarity index 95% rename from foundation-module/Chapter_2/Trees.w rename to foundation-module/Chapter_2/Trees.nw index 662197f..b62ffde 100644 --- a/foundation-module/Chapter_2/Trees.w +++ b/foundation-module/Chapter_2/Trees.nw @@ -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"); diff --git a/foundation-module/Chapter_2/Writers and Loggers.w b/foundation-module/Chapter_2/Writers_and_Loggers.nw similarity index 75% rename from foundation-module/Chapter_2/Writers and Loggers.w rename to foundation-module/Chapter_2/Writers_and_Loggers.nw index a35058c..d96192e 100644 --- a/foundation-module/Chapter_2/Writers and Loggers.w +++ b/foundation-module/Chapter_2/Writers_and_Loggers.nw @@ -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) @; + if (escapes_registered == FALSE) <>; 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. -@ = +<>= 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; @; + int set = 0; <>; break; } case '$': { int set = 1; if ((stream->stream_flags) & USES_LOG_ESCAPES_STRF) - @ + <> 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 */ } -@ = +<>= 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: @; break; + case DIRECT_ECAT: <>; 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. -@ = +<>= #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; }