foundation-module: Chapter 3: Nowebify.

This commit is contained in:
AwesomeAdam54321 2024-03-09 11:11:42 +08:00
parent 830118d644
commit dce27ab9d8
8 changed files with 290 additions and 284 deletions

View file

@ -7,36 +7,36 @@ code in this section provides a routine to carry out file opening as if
filenames are case-insensitive, and is used only for extensions.
@ This section contains a single utility routine, contributed by Adam
Thornton: a specialised, case-insensitive form of |fopen()|. It is specialised
Thornton: a specialised, case-insensitive form of [[fopen()]]. It is specialised
in that it is designed for opening extensions, where the file path will be
case-correct up to the last two components of the path (the leafname and the
immediately containing directory), but where the casing may be wrong in those
last two components.
@ If the exact filename or extension directory (case-correct) exists,
|CIFilingSystem::fopen()| will choose it to open. If not, it will
use |strcasecmp()| to find a file or directory with the same name but
[[CIFilingSystem::fopen()]] will choose it to open. If not, it will
use [[strcasecmp()]] to find a file or directory with the same name but
differing in case and use it instead. If it finds exactly one candidate file,
it will then attempt to |fopen()| it and return the result.
it will then attempt to [[fopen()]] it and return the result.
If |CIFilingSystem::fopen()| succeeds, it returns a |FILE *|
(passed back to it from the underlying |fopen()|). If
|CIFilingSystem::fopen()| fails, it returns |NULL|, and
|errno| is set accordingly:
(a) If no suitable file was found, |errno| is set to |ENOENT|.
If [[CIFilingSystem::fopen()]] succeeds, it returns a [[FILE *]]
(passed back to it from the underlying [[fopen()]]). If
[[CIFilingSystem::fopen()]] fails, it returns [[NULL]], and
[[errno]] is set accordingly:
(a) If no suitable file was found, [[errno]] is set to [[ENOENT]].
(b) If more than one possibility was found, but none of them exactly match
the supplied case, |errno| is set to |EBADF|.
the supplied case, [[errno]] is set to [[EBADF]].
(c) Note that if multiple directories which match case-insensitively are
found, but none is an exact match, |EBADF| will be set regardless of the
found, but none is an exact match, [[EBADF]] will be set regardless of the
contents of the directories.
(d) If |CIFilingSystem::fopen()| fails during its allocation of
(d) If [[CIFilingSystem::fopen()]] fails during its allocation of
space to hold its intermediate strings for comparison, or for its various
data structures, |errno| is set to |ENOMEM|.
(e) If an unambiguous filename is found but the |fopen()| fails, |errno| is
left at whatever value the underlying |fopen()| set it to.
data structures, [[errno]] is set to [[ENOMEM]].
(e) If an unambiguous filename is found but the [[fopen()]] fails, [[errno]] is
left at whatever value the underlying [[fopen()]] set it to.
@h The routine. ^"ifdef-PLATFORM_POSIX"
The routine is available only on POSIX platforms where |PLATFORM_POSIX|
@ \section{The routine. ^"ifdef-PLATFORM_POSIX"}
The routine is available only on POSIX platforms where [[PLATFORM_POSIX]]
is defined (see "Platform-Specific Definitions"). In practice this means
everywhere except Windows, but all Windows file systems are case-preserving
and case-insensitive in any case.
@ -46,7 +46,7 @@ for the given casing, then if that fails, for a unique alternative with
different casing; and then repeat within that directory for the extension
file itself.
=
<<*>>=
FILE *CIFilingSystem::fopen(const char *path, const char *mode) {
char *topdirpath = NULL, *ciextdirpath = NULL, *cistring = NULL, *ciextname = NULL;
char *workstring = NULL, *workstring2 = NULL;
@ -54,96 +54,96 @@ FILE *CIFilingSystem::fopen(const char *path, const char *mode) {
size_t length;
/* for efficiency's sake, though it's logically equivalent, we try... */
handle = fopen(path, mode); if (handle) @<Happy ending to ci-fopen@>;
handle = fopen(path, mode); if (handle) <<Happy ending to ci-fopen>>;
@<Find the length of the path, giving an error if it is empty or NULL@>;
@<Allocate memory for strings large enough to hold any subpath of the path@>;
@<Parse the path to break it into topdir path, extension directory and leafname@>;
<<Find the length of the path, giving an error if it is empty or NULL>>;
<<Allocate memory for strings large enough to hold any subpath of the path>>;
<<Parse the path to break it into topdir path, extension directory and leafname>>;
topdir = opendir(topdirpath); /* whose pathname is assumed case-correct... */
if (topdir == NULL) @<Sad ending to ci-fopen@>; /* ...so that failure is fatal; |errno| is set by |opendir| */
if (topdir == NULL) <<Sad ending to ci-fopen>>; /* ...so that failure is fatal; [[errno| is set by |opendir]] */
sprintf(workstring, "%s%c%s", topdirpath, FOLDER_SEPARATOR, ciextdirpath);
extdir = opendir(workstring); /* try with supplied extension directory name */
if (extdir == NULL) @<Try to find a unique insensitively matching directory name in topdir@>
if (extdir == NULL) <<Try to find a unique insensitively matching directory name in topdir>>
else strcpy(cistring, workstring);
sprintf(workstring, "%s%c%s", cistring, FOLDER_SEPARATOR, ciextname);
handle = fopen(workstring, mode); /* try with supplied name */
if (handle) @<Happy ending to ci-fopen@>;
if (handle) <<Happy ending to ci-fopen>>;
@<Try to find a unique insensitively matching entry in extdir@>;
<<Try to find a unique insensitively matching entry in extdir>>;
}
@h Looking for case-insensitive matches instead.
@ \section{Looking for case-insensitive matches instead.}
We emerge from the following only in the happy case where a unique matching
directory name can be found.
@<Try to find a unique insensitively matching directory name in topdir@> =
<<Try to find a unique insensitively matching directory name in topdir>>=
int rc = CIFilingSystem::match_in_directory(topdir, ciextdirpath, workstring);
switch (rc) {
case 0:
errno = ENOENT; @<Sad ending to ci-fopen@>;
errno = ENOENT; <<Sad ending to ci-fopen>>;
case 1:
sprintf(cistring, "%s%c%s", topdirpath, FOLDER_SEPARATOR, workstring);
extdir = opendir(cistring);
if (extdir == NULL) {
errno = ENOENT; @<Sad ending to ci-fopen@>;
errno = ENOENT; <<Sad ending to ci-fopen>>;
}
break;
default:
errno = EBADF; @<Sad ending to ci-fopen@>;
errno = EBADF; <<Sad ending to ci-fopen>>;
}
@ More or less the same, but we never emerge at all: all cases of the switch
return from the function.
@<Try to find a unique insensitively matching entry in extdir@> =
<<Try to find a unique insensitively matching entry in extdir>>=
int rc = CIFilingSystem::match_in_directory(extdir, ciextname, workstring);
switch (rc) {
case 0:
errno = ENOENT; @<Sad ending to ci-fopen@>;
errno = ENOENT; <<Sad ending to ci-fopen>>;
case 1:
sprintf(workstring2, "%s%c%s", cistring, FOLDER_SEPARATOR, workstring);
workstring2[length] = 0;
handle = fopen(workstring2, mode);
if (handle) @<Happy ending to ci-fopen@>;
errno = ENOENT; @<Sad ending to ci-fopen@>;
if (handle) <<Happy ending to ci-fopen>>;
errno = ENOENT; <<Sad ending to ci-fopen>>;
default:
errno = EBADF; @<Sad ending to ci-fopen@>;
errno = EBADF; <<Sad ending to ci-fopen>>;
}
@h Allocation and deallocation.
@ \section{Allocation and deallocation.}
We use six strings to hold full or partial pathnames.
@<Allocate memory for strings large enough to hold any subpath of the path@> =
<<Allocate memory for strings large enough to hold any subpath of the path>>=
workstring = calloc(length+1, sizeof(char));
if (workstring == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
if (workstring == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
workstring2 = calloc(length+1, sizeof(char));
if (workstring2 == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
if (workstring2 == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
topdirpath = calloc(length+1, sizeof(char));
if (topdirpath == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
if (topdirpath == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
ciextdirpath = calloc(length+1, sizeof(char));
if (ciextdirpath == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
if (ciextdirpath == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
cistring = calloc(length+1, sizeof(char));
if (cistring == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
if (cistring == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
ciextname = calloc(length+1, sizeof(char));
if (ciextname == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
if (ciextname == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
@ If we are successful, we return a valid file handle...
@<Happy ending to ci-fopen@> =
@<Prepare to exit ci-fopen cleanly@>;
<<Happy ending to ci-fopen>>=
<<Prepare to exit ci-fopen cleanly>>;
return handle;
@ ...and otherwise |NULL|, having already set |errno| with the reason why.
@ ...and otherwise [[NULL]], having already set [[errno]] with the reason why.
@<Sad ending to ci-fopen@> =
@<Prepare to exit ci-fopen cleanly@>;
<<Sad ending to ci-fopen>>=
<<Prepare to exit ci-fopen cleanly>>;
return NULL;
@<Prepare to exit ci-fopen cleanly@> =
<<Prepare to exit ci-fopen cleanly>>=
if (workstring) free(workstring);
if (workstring2) free(workstring2);
if (topdirpath) free(topdirpath);
@ -153,25 +153,25 @@ We use six strings to hold full or partial pathnames.
if (topdir) closedir(topdir);
if (extdir) closedir(extdir);
@h Pathname hacking.
@ \section{Pathname hacking.}
@<Find the length of the path, giving an error if it is empty or NULL@> =
<<Find the length of the path, giving an error if it is empty or NULL>>=
length = 0;
if (path) length = (size_t) strlen(path);
if (length < 1) { errno = ENOENT; return NULL; }
@ And here we break up a pathname like
= (text)
/Users/bobama/Library/Inform/Extensions/Hillary Clinton/Health Care.i7x
=
into three components:
(a) |topdirpath| is |/Users/bobama/Library/Inform/Extensions|, and its casing is correct.
(b) |ciextdirpath| is |Hillary Clinton|, but its casing may not be correct.
(c) |ciextname| is |Health Care.i7x|, but its casing may not be correct.
(a) [[topdirpath]] is [[/Users/bobama/Library/Inform/Extensions]], and its casing is correct.
(b) [[ciextdirpath]] is [[Hillary Clinton]], but its casing may not be correct.
(c) [[ciextname]] is [[Health Care.i7x]], but its casing may not be correct.
The contents of |workstring| are not significant afterwards.
The contents of [[workstring]] are not significant afterwards.
@<Parse the path to break it into topdir path, extension directory and leafname@> =
<<Parse the path to break it into topdir path, extension directory and leafname>>=
char *p;
size_t extdirindex = 0, extindex = 0, namelen = 0, dirlen = 0;
@ -197,11 +197,11 @@ The contents of |workstring| are not significant afterwards.
strncpy(ciextdirpath, path + extdirindex + 1, dirlen);
ciextdirpath[dirlen] = 0;
@h strrchr.
@ \section{strrchr.}
This is an elderly C library function, really, but rewritten so that it
can recognise any folder separator character.
=
<<*>>=
char *CIFilingSystem::strrchr(const char *p) {
const char *q = NULL;
while (*p) {
@ -211,17 +211,17 @@ char *CIFilingSystem::strrchr(const char *p) {
return (char *) q;
}
@h Counting matches.
@ \section{Counting matches.}
We count the number of names within the directory which case-insensitively
match against |name|, and copy the last which matches into |last_match|.
This must be at least as long as |name|. (We ought to be just a little careful
match against [[name]], and copy the last which matches into [[last_match]].
This must be at least as long as [[name]]. (We ought to be just a little careful
in case of improbable cases where the matched name contains a different
number of characters from |name|, for instance because on a strict reading
number of characters from [[name]], for instance because on a strict reading
of Unicode "SS" is casing-equivalent to the eszet, but it's unlikely
that many contemporary implementations of |strcasecmp| are aware of this,
that many contemporary implementations of [[strcasecmp]] are aware of this,
and in any case the code above contains much larger buffers than needed.)
=
<<*>>=
int CIFilingSystem::match_in_directory(void *vd,
char *name, char *last_match) {
DIR *d = (DIR *) vd;
@ -238,10 +238,10 @@ int CIFilingSystem::match_in_directory(void *vd,
return rc;
}
@h Non-POSIX tail. ^"ifndef-PLATFORM_POSIX"
On platforms without POSIX directory handling, we revert to regular |fopen|.
@ \section{Non-POSIX tail. ^"ifndef-PLATFORM_POSIX"}
On platforms without POSIX directory handling, we revert to regular [[fopen]].
=
<<*>>=
FILE *CIFilingSystem::fopen(const char *path, const char *mode) {
return fopen(path, mode);
}

View file

@ -3,63 +3,65 @@
To parse the command line arguments with which inweb was called,
and to handle any errors it needs to issue.
@h Model.
@ \section{Model.}
Our scheme is that the command line syntax will contain an optional
series of dashed switches. Some switches appear alone, others must be
followed by an argument. Anything not part of the switches is termed
a "bareword". For example, in
= (text)
-log no-memory-usage -fixtime jam marmalade
=
there are two switches, |-log| taking an argument (it has valency 2
in the terminology below), |-fixtime| not (valency 1). There are
then two barewords, |jam| and |marmalade|.
there are two switches, [[-log]] taking an argument (it has valency 2
in the terminology below), [[-fixtime]] not (valency 1). There are
then two barewords, [[jam]] and [[marmalade]].
For an example of all this in action, see Inweb, or see the basic
command-line switches created by Foundation itself in "Foundation".
@h Switches.
@ \section{Switches.}
Each different switch available is stored in one of these structures.
Switches come in five sorts:
@e ACTION_CLSF from 1 /* does something */
@e BOOLEAN_ON_CLSF /* sets a flag true */
@e BOOLEAN_OFF_CLSF /* sets a flag false */
@e NUMERICAL_CLSF /* sets an integer to the given value */
@e TEXTUAL_CLSF /* sets text to the given value */
<<*>>=
enum ACTION_CLSF from 1 /* does something */
enum BOOLEAN_ON_CLSF /* sets a flag true */
enum BOOLEAN_OFF_CLSF /* sets a flag false */
enum NUMERICAL_CLSF /* sets an integer to the given value */
enum TEXTUAL_CLSF /* sets text to the given value */
@ Switches are also grouped, though this affects only the printout of them
in |-help|. Groups are enumerated thus:
in [[-help]]. Groups are enumerated thus:
@e NO_CLSG from 0
@e FOUNDATION_CLSG
<<*>>=
enum NO_CLSG from 0
enum FOUNDATION_CLSG
=
<<*>>=
typedef struct command_line_switch {
int switch_id;
struct text_stream *switch_name; /* e.g., |no-verbose| */
struct text_stream *switch_sort_name; /* e.g., |verbose| */
struct text_stream *switch_name; /* e.g., [[no-verbose]] */
struct text_stream *switch_sort_name; /* e.g., [[verbose]] */
struct text_stream *help_text;
int valency; /* 1 for bare, 2 for one argument follows */
int form; /* one of the |*_CLSF| values above */
int switch_group; /* one of the |*_CLSG| valyes above */
int form; /* one of the [[*_CLSF]] values above */
int switch_group; /* one of the [[*_CLSG]] valyes above */
int active_by_default; /* relevant only for booleans */
struct command_line_switch *negates; /* relevant only for booleans */
CLASS_DEFINITION
} command_line_switch;
@ In case of a prodigious number of switches (ever tried typing |clang -help|?),
@ In case of a prodigious number of switches (ever tried typing [[clang -help]]?),
we'll hash the switch names into the following:
=
<<*>>=
dictionary *cls_dictionary = NULL;
@ The client must declare all the switches her program will make use of, not
counting the standard set already declared by Foundation (such as |-help|).
A new |*_CLSW| value should be enumerated to be the ID referring to this
counting the standard set already declared by Foundation (such as [[-help]]).
A new [[*_CLSW]] value should be enumerated to be the ID referring to this
swtich, and then the client should call:
=
<<*>>=
int current_switch_group = -1;
text_stream *switch_group_names[NO_DEFINED_CLSG_VALUES+1];
void CommandLine::begin_group(int id, text_stream *name) {
@ -86,7 +88,7 @@ command_line_switch *CommandLine::declare_switch_p(int id,
if (cls_dictionary == NULL) cls_dictionary = Dictionaries::new(16, FALSE);
command_line_switch *cls = CREATE(command_line_switch);
cls->switch_name = name;
@<Make the sorting name@>;
<<Make the sorting name>>;
cls->switch_id = id;
cls->valency = val;
cls->help_text = help_literal;
@ -99,21 +101,21 @@ command_line_switch *CommandLine::declare_switch_p(int id,
return cls;
}
@ When we alphabetically sort switches for the |-help| output, we want to
file, say, |-no-verbose| immediately after |-verbose|, not back in the N
section. So the sorting version of |no-verbose| is |verbose_|.
@ When we alphabetically sort switches for the [[-help]] output, we want to
file, say, [[-no-verbose]] immediately after [[-verbose]], not back in the N
section. So the sorting version of [[no-verbose]] is [[verbose_]].
@<Make the sorting name@> =
<<Make the sorting name>>=
cls->switch_sort_name = Str::duplicate(name);
if (Str::begins_with_wide_string(name, L"no-")) {
Str::delete_n_characters(cls->switch_sort_name, 3);
WRITE_TO(cls->switch_sort_name, "_");
}
@ Booleans are automatically created in pairs, e.g., |-destroy-world| and
|-no-destroy-world|:
@ Booleans are automatically created in pairs, e.g., [[-destroy-world]] and
[[-no-destroy-world]]:
=
<<*>>=
command_line_switch *CommandLine::declare_boolean_switch(int id,
wchar_t *name_literal, int val, wchar_t *help_literal, int active) {
command_line_switch *cls =
@ -147,17 +149,17 @@ void CommandLine::declare_textual_switch(int id,
cls->form = TEXTUAL_CLSF;
}
@h Reading the command line.
@ \section{Reading the command line.}
Once all the switches are declared, the client calls the following routine
in order to parse the usual C |argc| and |argv| pair, and take action as
appropriate. The client passes a pointer to some structure in |state|:
in order to parse the usual C [[argc]] and [[argv]] pair, and take action as
appropriate. The client passes a pointer to some structure in [[state]]:
probably a structure holding its settings variables. When we parse a
switch, we call |f| to say so; when we parse a bareword, we call |g|. In
each case we pass back |state| so that these functions can record whatever
switch, we call [[f]] to say so; when we parse a bareword, we call [[g]]. In
each case we pass back [[state]] so that these functions can record whatever
they would like to in the state structure.
The return value is |TRUE| if the command line appeared to contain at least
one non-trivial request, but |FALSE| if it only asked for e.g. |-help|. In
The return value is [[TRUE]] if the command line appeared to contain at least
one non-trivial request, but [[FALSE]] if it only asked for e.g. [[-help]]. In
general, the client should then exit with exit code 0 if this happens.
This is all easier to demonstrate than explain. See Inweb for an example.
@ -165,9 +167,10 @@ This is all easier to demonstrate than explain. See Inweb for an example.
@ Here goes the reader. It works through the command line arguments, then
through the file if one has by that point been provided.
@d BOGUS_CLSN -12345678 /* bogus because guaranteed not to be a genuine switch ID */
<<*>>=
#define BOGUS_CLSN -12345678 /* bogus because guaranteed not to be a genuine switch ID */
=
<<*>>=
typedef struct clf_reader_state {
void *state;
void (*f)(int, int, text_stream *, void *);
@ -220,7 +223,7 @@ void CommandLine::read_array(clf_reader_state *crs, int argc, char **argv) {
@ We can also read the "command line" from a file. The following variable
holds the filename to read from.
=
<<*>>=
filename *command_line_file = NULL;
void CommandLine::also_read_file(filename *F) {
command_line_file = F;
@ -231,7 +234,7 @@ from the debugging log what switches were actually used. But since the log
might not exist as early as now, we have to record any log entries, and play
them back later (i.e., when the debugging log does exist).
=
<<*>>=
linked_list *command_line_logs = NULL;
void CommandLine::record_log(text_stream *line) {
if (command_line_logs == NULL)
@ -248,14 +251,14 @@ void CommandLine::play_back_log(void) {
}
@ White space at start and end of lines is ignored; blank lines and those
beginning with a |#| are ignored (but a # following other content does not
beginning with a [[#]] are ignored (but a # following other content does not
mean a comment, so don't use trailing comments on lines); each line must
either be a single switch like |-no-service| or a pair like |-connect tower11|.
Shell conventions on quoting are not used, but the line |-greet Fred Smith|
is equivalent to |-greet 'Fred Smith'| on the command line, so there's no
either be a single switch like [[-no-service]] or a pair like [[-connect tower11]].
Shell conventions on quoting are not used, but the line [[-greet Fred Smith]]
is equivalent to [[-greet 'Fred Smith']] on the command line, so there's no
problem with internal space characters in arguments.
=
<<*>>=
void CommandLine::read_file(clf_reader_state *crs) {
text_stream *logline = Str::new();
WRITE_TO(logline, "Reading further switches from file: %f", command_line_file);
@ -271,7 +274,7 @@ void CommandLine::read_file(clf_reader_state *crs) {
void CommandLine::read_file_helper(text_stream *text, text_file_position *tfp, void *state) {
clf_reader_state *crs = (clf_reader_state *) state;
match_results mr = Regexp::create_mr();
if ((Str::is_whitespace(text)) || (Regexp::match(&mr, text, L" *#%c*"))) {
if ((Str::is_whitespace(text)) [[]] (Regexp::match(&mr, text, L" *#%c*"))) {
;
} else {
text_stream *logline = Str::new();
@ -302,16 +305,16 @@ void CommandLine::read_one(clf_reader_state *crs, text_stream *opt) {
crs->subs = TRUE;
}
@ We also allow |-setting=X| as equivalent to |-setting X|.
@ We also allow [[-setting=X]] as equivalent to [[-setting X]].
=
<<*>>=
int CommandLine::read_pair(clf_reader_state *crs, text_stream *opt, text_stream *arg) {
TEMPORARY_TEXT(opt_p)
TEMPORARY_TEXT(opt_val)
Str::copy(opt_p, opt);
int N = BOGUS_CLSN;
match_results mr = Regexp::create_mr();
if ((Regexp::match(&mr, opt, L"(%c+)=(%d+)")) ||
if ((Regexp::match(&mr, opt, L"(%c+)=(%d+)")) [[]]
(Regexp::match(&mr, opt, L"(%c+)=(-%d+)"))) {
N = Str::atoi(mr.exp[1], 0);
Str::copy(opt_p, mr.exp[0]);
@ -328,7 +331,7 @@ int CommandLine::read_pair(clf_reader_state *crs, text_stream *opt, text_stream
@ So at this point we have definitely found what looks like a switch:
=
<<*>>=
int CommandLine::read_pair_p(text_stream *opt, text_stream *opt_val, int N,
text_stream *arg, void *state,
void (*f)(int, int, text_stream *, void *), int *substantive) {
@ -350,22 +353,22 @@ int CommandLine::read_pair_p(text_stream *opt, text_stream *opt_val, int N,
}
}
int innocuous = FALSE;
@<Take action on what is now definitely a switch@>;
<<Take action on what is now definitely a switch>>;
if ((innocuous == FALSE) && (substantive)) *substantive = TRUE;
return cls->valency;
}
@ The common set of switches declared by Foundation are all handled here;
all other switches are delegated to the client's callback function |f|.
all other switches are delegated to the client's callback function [[f]].
@<Take action on what is now definitely a switch@> =
<<Take action on what is now definitely a switch>>=
switch (cls->switch_id) {
case CRASH_CLSW:
if (cls->form == BOOLEAN_ON_CLSF) {
Errors::enter_debugger_mode(); innocuous = TRUE;
}
break;
case LOG_CLSW: @<Parse debugging log inclusion@>; innocuous = TRUE; break;
case LOG_CLSW: <<Parse debugging log inclusion>>; innocuous = TRUE; break;
case VERSION_CLSW: {
PRINT("[[Title]]");
char *svn = "[[Semantic Version Number]]";
@ -398,7 +401,7 @@ all other switches are delegated to the client's callback function |f|.
break;
}
@<Parse debugging log inclusion@> =
<<Parse debugging log inclusion>>=
if (Log::get_debug_log_filename() == NULL) {
TEMPORARY_TEXT(itn)
WRITE_TO(itn, "%s", PROGRAM_NAME);
@ -409,8 +412,8 @@ all other switches are delegated to the client's callback function |f|.
Log::open();
Log::set_aspect_from_command_line(arg, TRUE);
@h Help text.
That just leaves the following, which implements the |-help| switch. It
@ \section{Help text.}
That just leaves the following, which implements the [[-help]] switch. It
alphabetically sorts the switches, and prints out a list of them as grouped,
with ungrouped switches as the top paragraph and Foundation switches as the
bottom one. (Those are the dull ones.)
@ -418,7 +421,7 @@ bottom one. (Those are the dull ones.)
If a header text has been declared, that appears above the list. It's usually
a brief description of the tool's name and purpose.
=
<<*>>=
text_stream *cls_heading = NULL;
void CommandLine::declare_heading(wchar_t *heading_text_literal) {
@ -440,17 +443,17 @@ void CommandLine::write_help(OUTPUT_STREAM) {
if (Str::len(cls_heading) > 0) WRITE("%S\n", cls_heading);
int filter = NO_CLSG, new_para_needed = FALSE;
@<Show options in alphabetical order@>;
<<Show options in alphabetical order>>;
for (filter = NO_CLSG; filter<NO_DEFINED_CLSG_VALUES; filter++)
if ((filter != NO_CLSG) && (filter != FOUNDATION_CLSG))
@<Show options in alphabetical order@>;
<<Show options in alphabetical order>>;
filter = FOUNDATION_CLSG;
@<Show options in alphabetical order@>;
<<Show options in alphabetical order>>;
Memory::I7_free(sorted_table, ARRAY_SORTING_MREASON, N*((int) sizeof(command_line_switch *)));
}
@<Show options in alphabetical order@> =
<<Show options in alphabetical order>>=
if (new_para_needed) {
WRITE("\n");
new_para_needed = FALSE;
@ -458,7 +461,7 @@ void CommandLine::write_help(OUTPUT_STREAM) {
for (int i=0; i<N; i++) {
command_line_switch *cls = sorted_table[i];
if (cls->switch_group != filter) continue;
if ((cls->form == BOOLEAN_OFF_CLSF) || (cls->form == BOOLEAN_ON_CLSF)) {
if ((cls->form == BOOLEAN_OFF_CLSF) [[]] (cls->form == BOOLEAN_ON_CLSF)) {
if (cls->active_by_default) continue;
}
text_stream *label = switch_group_names[filter];
@ -482,7 +485,7 @@ void CommandLine::write_help(OUTPUT_STREAM) {
DISCARD_TEXT(line)
}
@ =
<<*>>=
int CommandLine::compare_names(const void *ent1, const void *ent2) {
text_stream *tx1 = (*((const command_line_switch **) ent1))->switch_sort_name;
text_stream *tx2 = (*((const command_line_switch **) ent2))->switch_sort_name;

View file

@ -4,7 +4,7 @@ Scanning directories on the host filing system.
@ All of this abstracts the code already found in the platform definitions.
=
<<*>>=
typedef struct scan_directory {
void *directory_handle;
char directory_name_written_out[4*MAX_FILENAME_LENGTH];
@ -15,7 +15,7 @@ typedef struct scan_directory {
whatever the locale encoding is; the filenames coming back have to be
transcoded the other way.
=
<<*>>=
scan_directory *Directories::open_from(text_stream *name) {
scan_directory *D = CREATE(scan_directory);
Str::copy_to_locale_string(D->directory_name_written_out, name, 4*MAX_FILENAME_LENGTH);
@ -51,7 +51,7 @@ void Directories::close(scan_directory *D) {
@ Incredibly, this seems to be the most portable method for testing whether a
directory exists in the file system:
=
<<*>>=
int Directories::exists(pathname *P) {
scan_directory *TRY = Directories::open(P);
if (TRY == NULL) return FALSE;
@ -60,7 +60,7 @@ int Directories::exists(pathname *P) {
}
@ It turns out to be useful to scan the contents of a directory in an order
which is predictable regardless of platform -- |Platform::readdir| works in a
which is predictable regardless of platform -- [[Platform::readdir]] works in a
different order on MacOS, Windows and Linux, even given the same directory
of files to work on. So the following returns a linked list of the contents,
sorted into alphabetical order, but case-insensitively. For the Inform project,
@ -70,7 +70,7 @@ in casing, so this ordering is effectively deterministic.
There's some time and memory overhead here, but unless we're dealing with
directories holding upwards of 10,000 files or so, it'll be trivial.
=
<<*>>=
linked_list *Directories::listing(pathname *P) {
int capacity = 4, used = 0;
text_stream **listing_array = (text_stream **)

View file

@ -2,11 +2,11 @@
A basic system for command-line tool error messages.
@h Errors handler.
@ \section{Errors handler.}
The user can provide a routine to deal with error messages before they're
issued. If this returns |FALSE|, nothing is printed to |stderr|.
issued. If this returns [[FALSE]], nothing is printed to [[stderr]].
=
<<*>>=
int (*errors_handler)(text_stream *, int) = NULL;
void (*internal_errors_handler)(void *, char *, char *, int) = NULL;
@ -32,14 +32,14 @@ void Errors::issue(text_stream *message, int fatality) {
if (fatality) Errors::die(); else problem_count++;
}
@h Error messages.
@ \section{Error messages.}
Ah, they kill you; or they don't. The fatal kind cause an exit code of 2, to
distinguish this from a proper completion in which non-fatal errors occur.
These two routines (alone) can be caused by failures of the memory allocation
or streams systems, and therefore must be written with a little care to use
the temporary stream, not some other string which might need fresh allocation.
=
<<*>>=
void Errors::fatal(char *message) {
TEMPORARY_TEXT(ERM)
WRITE_TO(ERM, "%s: fatal error: %s\n", PROGRAM_NAME, message);
@ -80,13 +80,13 @@ void Errors::fatal_with_path(char *message, pathname *P) {
}
@ Assertion failures lead to the following. Note the use of the C11 syntax
|_Noreturn|; this seems now to be well-supported on modern C compilers, though.
[[_Noreturn]]; this seems now to be well-supported on modern C compilers, though.
(It needs to be on its own line to avoid hassle with InC, which has no special
support for C annotations.)
@d internal_error(message) Errors::internal_error_handler(NULL, message, __FILE__, __LINE__)
#define internal_error(message) Errors::internal_error_handler(NULL, message, __FILE__, __LINE__)
=
<<*>>=
_Noreturn
void Errors::internal_error_handler(void *p, char *message, char *f, int lc) {
if (internal_errors_handler)
@ -96,12 +96,12 @@ void Errors::internal_error_handler(void *p, char *message, char *f, int lc) {
exit(1); /* redundant but needed to remove compiler warning in clang */
}
@h Deliberately crashing.
@ \section{Deliberately crashing.}
It's sometimes convenient to get a backtrace from the debugger when an error
occurs unexpectedly, and one way to do that is to force a division by zero.
(This is only enabled by |-crash| at the command line and is for debugging only.)
(This is only enabled by [[-crash]] at the command line and is for debugging only.)
=
<<*>>=
int debugger_mode = FALSE;
void Errors::enter_debugger_mode(void) {
debugger_mode = TRUE;
@ -119,11 +119,11 @@ void Errors::die(void) { /* as void as it gets */
exit(2);
}
@h Survivable errors.
@ \section{Survivable errors.}
The trick with error messages is to indicate where they occur, and we can
specify this at three levels of abstraction:
=
<<*>>=
void Errors::nowhere(char *message) {
Errors::in_text_file(message, NULL);
}
@ -144,7 +144,7 @@ void Errors::in_text_file_S(text_stream *message, text_file_position *here) {
@ Which funnel through:
=
<<*>>=
void Errors::at_position(char *message, filename *file, int line) {
TEMPORARY_TEXT(ERM)
WRITE_TO(ERM, "%s: ", PROGRAM_NAME);
@ -165,7 +165,7 @@ void Errors::at_position_S(text_stream *message, filename *file, int line) {
@ Lastly:
=
<<*>>=
void Errors::with_file(char *message, filename *F) {
TEMPORARY_TEXT(ERM)
WRITE_TO(ERM, "%s: %f: %s\n", PROGRAM_NAME, F, message);

View file

@ -2,23 +2,23 @@
Names of hypothetical or real files in the filing system.
@h Storage.
@ \section{Storage.}
Filename objects behave much like pathname ones, but they have their own
structure in order that the two are distinct C types. Individual filenames
are a single instance of the following. (Note that the text part is stored
as Unicode code points, regardless of what text encoding the locale has.)
=
<<*>>=
typedef struct filename {
struct pathname *pathname_of_location;
struct text_stream *leafname;
CLASS_DEFINITION
} filename;
@h Creation.
@ \section{Creation.}
A filename is made by supplying a pathname and a leafname.
=
<<*>>=
filename *Filenames::in(pathname *P, text_stream *file_name) {
return Filenames::primitive(file_name, 0, Str::len(file_name), P);
}
@ -35,10 +35,10 @@ filename *Filenames::primitive(text_stream *S, int from, int to, pathname *P) {
return F;
}
@h Strings to filenames.
@ \section{Strings to filenames.}
The following takes a textual name and returns a filename.
=
<<*>>=
filename *Filenames::from_text(text_stream *path) {
int i = 0, pos = -1;
LOOP_THROUGH_TEXT(at, path) {
@ -68,10 +68,10 @@ filename *Filenames::from_text_relative(pathname *from, text_stream *path) {
return F;
}
@h The writer.
@ \section{The writer.}
And conversely:
=
<<*>>=
void Filenames::writer(OUTPUT_STREAM, char *format_string, void *vF) {
filename *F = (filename *) vF;
if (F == NULL) WRITE("<no file>");
@ -87,7 +87,7 @@ void Filenames::writer(OUTPUT_STREAM, char *format_string, void *vF) {
@ And again relative to a given pathname:
=
<<*>>=
void Filenames::to_text_relative(OUTPUT_STREAM, filename *F, pathname *P) {
TEMPORARY_TEXT(ft)
TEMPORARY_TEXT(pt)
@ -109,17 +109,17 @@ void Filenames::to_text_relative(OUTPUT_STREAM, filename *F, pathname *P) {
DISCARD_TEXT(pt)
}
@h Reading off the directory.
@ \section{Reading off the directory.}
=
<<*>>=
pathname *Filenames::up(filename *F) {
if (F == NULL) return NULL;
return F->pathname_of_location;
}
@h Reading off the leafname.
@ \section{Reading off the leafname.}
=
<<*>>=
filename *Filenames::without_path(filename *F) {
return Filenames::in(NULL, F->leafname);
}
@ -137,14 +137,14 @@ void Filenames::write_unextended_leafname(OUTPUT_STREAM, filename *F) {
}
}
@h Filename extensions.
@ \section{Filename extensions.}
The following is cautiously written because of an oddity in Windows's handling
of filenames, which are allowed to have trailing dots or spaces, in a way
which isn't necessarily visible to the user, who may have added these by
an accidental brush of the keyboard. Thus |frog.jpg .| should be treated
as equivalent to |frog.jpg| when deciding the likely file format.
an accidental brush of the keyboard. Thus [[frog.jpg .]] should be treated
as equivalent to [[frog.jpg]] when deciding the likely file format.
=
<<*>>=
void Filenames::write_extension(OUTPUT_STREAM, filename *F) {
int on = FALSE;
LOOP_THROUGH_TEXT(pos, F->leafname) {
@ -170,23 +170,24 @@ filename *Filenames::set_extension(filename *F, text_stream *extension) {
return N;
}
@h Guessing file formats.
@ \section{Guessing file formats.}
The following guesses the file format from its file extension:
@d FORMAT_PERHAPS_HTML 1
@d FORMAT_PERHAPS_JPEG 2
@d FORMAT_PERHAPS_PNG 3
@d FORMAT_PERHAPS_OGG 4
@d FORMAT_PERHAPS_AIFF 5
@d FORMAT_PERHAPS_MIDI 6
@d FORMAT_PERHAPS_MOD 7
@d FORMAT_PERHAPS_GLULX 8
@d FORMAT_PERHAPS_ZCODE 9
@d FORMAT_PERHAPS_SVG 10
@d FORMAT_PERHAPS_GIF 11
@d FORMAT_UNRECOGNISED 0
<<*>>=
#define FORMAT_PERHAPS_HTML 1
#define FORMAT_PERHAPS_JPEG 2
#define FORMAT_PERHAPS_PNG 3
#define FORMAT_PERHAPS_OGG 4
#define FORMAT_PERHAPS_AIFF 5
#define FORMAT_PERHAPS_MIDI 6
#define FORMAT_PERHAPS_MOD 7
#define FORMAT_PERHAPS_GLULX 8
#define FORMAT_PERHAPS_ZCODE 9
#define FORMAT_PERHAPS_SVG 10
#define FORMAT_PERHAPS_GIF 11
#define FORMAT_UNRECOGNISED 0
=
<<*>>=
int Filenames::guess_format(filename *F) {
TEMPORARY_TEXT(EXT)
Filenames::write_extension(EXT, F);
@ -224,13 +225,13 @@ int Filenames::guess_format(filename *F) {
return verdict;
}
@h Opening.
These files are wrappers for |fopen|, the traditional C library call, but
@ \section{Opening.}
These files are wrappers for [[fopen]], the traditional C library call, but
referring to the file by filename structure rather than a textual name. Note
that we must transcode the filename to whatever the locale expects before
we call |fopen|, which is the main reason for the wrapper.
we call [[fopen]], which is the main reason for the wrapper.
=
<<*>>=
FILE *Filenames::fopen(filename *F, char *usage) {
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
TEMPORARY_TEXT(FN)
@ -249,12 +250,12 @@ FILE *Filenames::fopen_caseless(filename *F, char *usage) {
return CIFilingSystem::fopen(transcoded_pathname, usage);
}
@h Comparing.
@ \section{Comparing.}
Not as easy as it seems. The following is a slow but effective way to
compare two filenames by seeing if they have the same canonical form
when printed out.
=
<<*>>=
int Filenames::eq(filename *F1, filename *F2) {
if (F1 == F2) return TRUE;
TEMPORARY_TEXT(T1)
@ -267,9 +268,9 @@ int Filenames::eq(filename *F1, filename *F2) {
return rv;
}
@h Timestamps and sizes.
@ \section{Timestamps and sizes.}
=
<<*>>=
time_t Filenames::timestamp(filename *F) {
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
TEMPORARY_TEXT(FN)

View file

@ -2,39 +2,39 @@
Locations of hypothetical or real directories in the filing system.
@h About pathnames.
@ \section{About pathnames.}
We use the word "pathname" to mean a file-system location of a directory,
and "filename" to mean a location of a file. For example:
= (text)
/Users/rblackmore/Documents/Fireball
=
is a pathname, whereas
= (text)
/Users/rblackmore/Documents/Fireball/whoosh.aiff
=
is a filename. All references to directory locations in the filing system will be
held internally as |pathname| objects, and all references to file locations as
|filename| objects. Once created, these are never destroyed or modified,
held internally as [[pathname]] objects, and all references to file locations as
[[filename]] objects. Once created, these are never destroyed or modified,
so that it's safe to store a pointer to a pathname or filename anywhere.
Note that a pathname may well be hypothetical, that is, it may well
describe a directory which doesn't exist on disc.
A full path is a linked list, but reverse-ordered: thus,
= (text)
/Users/rblackmore/Documents/
=
would be represented as a pointer to the |pathname| for "Documents", which
would be represented as a pointer to the [[pathname]] for "Documents", which
in turn points to one for "rblackmore", which in turn points to "/Users".
Thus the root of the filing system is represented by the null pointer.
Each |pathname| can represent only a single level in the hierarchy, and
its textual name is not allowed to contain the |FOLDER_SEPARATOR| character,
with just one exception: the |pathname| at the end of the chain is allowed
to begin with |FOLDER_SEPARATOR| to denote that it's at the root of the
Each [[pathname]] can represent only a single level in the hierarchy, and
its textual name is not allowed to contain the [[FOLDER_SEPARATOR]] character,
with just one exception: the [[pathname]] at the end of the chain is allowed
to begin with [[FOLDER_SEPARATOR]] to denote that it's at the root of the
host file system.
=
<<*>>=
typedef struct pathname {
struct text_stream *intermediate;
struct pathname *pathname_of_parent;
@ -42,11 +42,11 @@ typedef struct pathname {
CLASS_DEFINITION
} pathname;
@h Home directory.
@ \section{Home directory.}
We get the path to the user's home directory from the environment variable
|HOME|, if it exists.
[[HOME]], if it exists.
=
<<*>>=
pathname *home_path = NULL;
void Pathnames::start(void) {
char *home = (char *) (Platform::getenv("HOME"));
@ -57,9 +57,9 @@ void Pathnames::start(void) {
}
}
@h Installation directory.
@ \section{Installation directory.}
=
<<*>>=
pathname *installation_path = NULL;
void Pathnames::set_installation_path(pathname *P) {
installation_path = P;
@ -88,11 +88,11 @@ pathname *Pathnames::installation_path(const char *V, text_stream *def) {
return NULL;
}
@h Creation.
A subdirectory is made by taking an existing pathname (or possible |NULL|) and
@ \section{Creation.}
A subdirectory is made by taking an existing pathname (or possible [[NULL]]) and
then going one level deeper, using the supplied name.
=
<<*>>=
pathname *Pathnames::down(pathname *P, text_stream *dir_name) {
return Pathnames::primitive(dir_name, 0, Str::len(dir_name), P);
}
@ -109,14 +109,14 @@ pathname *Pathnames::primitive(text_stream *str, int from, int to, pathname *par
return P;
}
@h Text to pathnames.
@ \section{Text to pathnames.}
The following takes a text of a name and returns a pathname,
possibly relative to the home directory. Empty directory names are ignored
except possibly for an initial slash, so for example |paris/roubaix|,
|paris//roubaix| and |paris/roubaix/| are indistinguishable here, but
|/paris/roubaix| is different.
except possibly for an initial slash, so for example [[paris/roubaix]],
[[paris//roubaix]] and [[paris/roubaix/]] are indistinguishable here, but
[[/paris/roubaix]] is different.
=
<<*>>=
pathname *Pathnames::from_text(text_stream *path) {
return Pathnames::from_text_relative(NULL, path);
}
@ -134,10 +134,10 @@ pathname *Pathnames::from_text_relative(pathname *P, text_stream *path) {
return at;
}
@h Writer.
@ \section{Writer.}
Conversely, by the miracle of depth-first recursion:
=
<<*>>=
void Pathnames::writer(OUTPUT_STREAM, char *format_string, void *vP) {
pathname *P = (pathname *) vP;
int divider = FOLDER_SEPARATOR;
@ -153,28 +153,28 @@ void Pathnames::writer_r(OUTPUT_STREAM, pathname *P, int divider) {
WRITE("%S", P->intermediate);
}
@h Relative pathnames.
@ \section{Relative pathnames.}
Occasionally we want to shorten a pathname relative to another one:
for example,
= (text)
/Users/rblackmore/Documents/Fireball/tablature
=
relative to
= (text)
/Users/rblackmore/Documents/
=
would be
= (text)
Fireball/tablature
=
If the two pathnames are the same, the relative pathname is the empty text,
and so nothing is output.
Note that this does not correctly handle symlinks, |.|, |..| and so on,
Note that this does not correctly handle symlinks, [[.]], [[..]] and so on,
so it's probably not wise to use it with filenames typed in at the command
line.
=
<<*>>=
void Pathnames::to_text_relative(OUTPUT_STREAM, pathname *P, pathname *R) {
TEMPORARY_TEXT(rt)
TEMPORARY_TEXT(pt)
@ -200,12 +200,12 @@ text_stream *Pathnames::directory_name(pathname *P) {
return P->intermediate;
}
@h Relative URLs.
Suppose a web page in the directory at |from| wants to link to a page in
the directory |to|. The following composes a minimal-length URL to do so:
@ \section{Relative URLs.}
Suppose a web page in the directory at [[from]] wants to link to a page in
the directory [[to]]. The following composes a minimal-length URL to do so:
possibly, if they are in fact the same directory, an empty one.
=
<<*>>=
void Pathnames::relative_URL(OUTPUT_STREAM, pathname *from, pathname *to) {
TEMPORARY_TEXT(url)
int found = FALSE;
@ -237,11 +237,11 @@ void Pathnames::relative_URL(OUTPUT_STREAM, pathname *from, pathname *to) {
DISCARD_TEXT(url)
}
@h Existence in the file system.
@ \section{Existence in the file system.}
Just because we have a pathname, it doesn't follow that any directory exists
on the file system with that path.
=
<<*>>=
int Pathnames::create_in_file_system(pathname *P) {
if (P == NULL) return TRUE; /* the root of the file system always exists */
if (P->known_to_exist) return TRUE;
@ -254,12 +254,12 @@ int Pathnames::create_in_file_system(pathname *P) {
return P->known_to_exist;
}
@h Directory synchronisation.
@ \section{Directory synchronisation.}
Both pathnames here represent directories which do exist. The function makes
the |dest| tree an exact copy of the |source| tree (and therefore deletes
anything different which was originally in |dest|).
the [[dest]] tree an exact copy of the [[source]] tree (and therefore deletes
anything different which was originally in [[dest]]).
=
<<*>>=
void Pathnames::rsync(pathname *source, pathname *dest) {
char transcoded_source[4*MAX_FILENAME_LENGTH];
TEMPORARY_TEXT(pn)

View file

@ -3,14 +3,14 @@
Sending commands to the shell, on Unix-like platforms, or simulating
this on Windows.
@h Operating system interface.
@ \section{Operating system interface.}
Some of our programs have to issue commands to the host operating system,
to copy files, pass them through TeX, and so on. All of that is done using
the C standard library |system| function; the commands invoked are all
the C standard library [[system]] function; the commands invoked are all
standard for POSIX, so will work on MacOS and Linux, but on a Windows system
they would need to be read in a POSIX-style environment like Cygwin.
=
<<*>>=
void Shell::quote_path(OUTPUT_STREAM, pathname *P) {
TEMPORARY_TEXT(FN)
WRITE_TO(FN, "%p", P);
@ -44,9 +44,9 @@ void Shell::quote_text(OUTPUT_STREAM, text_stream *raw) {
PUT(' ');
}
@ The generic shell code to apply |command| to a file |F|:
@ The generic shell code to apply [[command]] to a file [[F]]:
=
<<*>>=
void Shell::apply(char *command, filename *F) {
TEMPORARY_TEXT(COMMAND)
Shell::plain(COMMAND, command);
@ -64,9 +64,9 @@ void Shell::apply_S(text_stream *command, filename *F) {
DISCARD_TEXT(COMMAND)
}
@ Applications to using |rm| and |cp|:
@ Applications to using [[rm]] and [[cp]]:
=
<<*>>=
void Shell::rm(filename *F) {
Shell::apply("rm", F);
}
@ -83,25 +83,26 @@ void Shell::copy(filename *F, pathname *T, char *options) {
}
@ This writes the traditional Unix shell syntax for redirecting the output
from both |stdout| and |stderr| to the same named file.
from both [[stdout]] and [[stderr]] to the same named file.
=
<<*>>=
void Shell::redirect(OUTPUT_STREAM, filename *F) {
Shell::plain(OUT, ">");
Shell::quote_file(OUT, F);
Shell::plain(OUT, "2>&1");
}
@h Actual commands.
@ \section{Actual commands.}
The scheme is that commands are composed using the above functions, and
then sent to this one.
We make the buffer here long enough for 8 filenames of worst-case length,
all transcoded to UTF-8 in the most unlucky way imaginable.
@d SPOOL_LENGTH 4*8*MAX_FILENAME_LENGTH
<<*>>=
#define SPOOL_LENGTH 4*8*MAX_FILENAME_LENGTH
=
<<*>>=
int shell_verbosity = FALSE;
void Shell::verbose(void) {
shell_verbosity = TRUE;

View file

@ -2,10 +2,10 @@
Managing how we record and use the current time and date.
@h Clock.
@ \section{Clock.}
From the local environment, we'll extract the time at which we're running.
=
<<*>>=
time_t right_now;
struct tm *the_present = NULL;
int fix_time_mode = FALSE;
@ -16,13 +16,13 @@ void Time::begin(void) {
fix_time_mode = FALSE;
}
@ The command line option |-fixtime| causes any tool compiled with Foundation
@ The command line option [[-fixtime]] causes any tool compiled with Foundation
to fix the date as 11 a.m. on 28 March 2016, which is Inform's birthday. This
makes it easier to automate testing, since we can compare output generated
in one session with output generated another, even though that was on two
different dates.
=
<<*>>=
void Time::fix(void) {
struct tm start;
start.tm_sec = 0; start.tm_min = 0; start.tm_hour = 11;
@ -36,7 +36,7 @@ int Time::fixed(void) {
return fix_time_mode;
}
@h Calendrical.
@ \section{Calendrical.}
The date of Easter depends on who and where you are. Inform's notional home
is England. Following League of Nations advice in 1926, Easter is legally
celebrated in England on the Sunday after the second Saturday in April, unless
@ -75,7 +75,7 @@ practically a random-number generator. The one thing to be said in its
favour is that it can be computed accurately with integer arithmetic using
fairly low numbers, and this we now do.
=
<<*>>=
void Time::easter(int year, int *d, int *m) {
int c, y, k, i, n, j, l;
y = year;
@ -94,11 +94,12 @@ void Time::easter(int year, int *d, int *m) {
@ And we can use this to tell if the season's merry:
@d CHRISTMAS_FEAST 1
@d EASTER_FEAST 2
@d NON_FEAST 3
<<*>>=
#define CHRISTMAS_FEAST 1
#define EASTER_FEAST 2
#define NON_FEAST 3
=
<<*>>=
int Time::feast(void) {
int this_month = the_present->tm_mon + 1;
int this_day = the_present->tm_mday;
@ -118,30 +119,30 @@ int Time::feast(void) {
return NON_FEAST;
}
@h Stopwatch timings.
@ \section{Stopwatch timings.}
The following provides a sort of hierarchical stopwatch. In principle it
could time anything (though not very accurately), but it's mainly intended
for monitoring how long programs internally work, since it reads time from
the |clock()| (i.e., how much CPU time the current process has taken) rather
the [[clock()]] (i.e., how much CPU time the current process has taken) rather
than from the actual time of day.
=
<<*>>=
typedef struct stopwatch_timer {
int running; /* set if this has been started but not stopped */
struct text_stream *event;
clock_t start_time;
clock_t end_time;
int time_taken; /* measured in centiseconds of CPU time */
linked_list *stages_chronological; /* of |stopwatch_timer| */
linked_list *stages_sorted; /* of |stopwatch_timer| */
linked_list *stages_chronological; /* of [[stopwatch_timer]] */
linked_list *stages_sorted; /* of [[stopwatch_timer]] */
CLASS_DEFINITION
} stopwatch_timer;
@ If |within| is not null, it must be another stopwatch which is also running;
@ If [[within]] is not null, it must be another stopwatch which is also running;
the idea is that the new stopwatch is to time a sub-task of the main task which
|within| is timing.
[[within]] is timing.
=
<<*>>=
stopwatch_timer *Time::start_stopwatch(stopwatch_timer *within, text_stream *name) {
stopwatch_timer *st = CREATE(stopwatch_timer);
st->event = Str::duplicate(name);
@ -162,7 +163,7 @@ stopwatch_timer *Time::start_stopwatch(stopwatch_timer *within, text_stream *nam
@ Every started stopwatch must be stopped in order to register time having
been used. Once this is done, we sort:
=
<<*>>=
int Time::stop_stopwatch(stopwatch_timer *st) {
if (st->running == FALSE) internal_error("already stopped");
st->running = FALSE;
@ -170,11 +171,11 @@ int Time::stop_stopwatch(stopwatch_timer *st) {
st->time_taken +=
(((int) (st->end_time)) - ((int) (st->start_time))) / ((int) (CLOCKS_PER_SEC/100));
int N = LinkedLists::len(st->stages_chronological);
if (N > 0) @<Sort the subtasks in descreasing order of how much time they took@>;
if (N > 0) <<Sort the subtasks in descreasing order of how much time they took>>;
return st->time_taken;
}
@<Sort the subtasks in descreasing order of how much time they took@> =
<<Sort the subtasks in descreasing order of how much time they took>>=
st->stages_sorted = NEW_LINKED_LIST(stopwatch_timer);
stopwatch_timer **as_array = (stopwatch_timer **)
(Memory::calloc(N, sizeof(stopwatch_timer *), ARRAY_SORTING_MREASON));
@ -189,11 +190,11 @@ int Time::stop_stopwatch(stopwatch_timer *st) {
@ This sorts first by elapsed time, then by event name in alphabetical order:
=
<<*>>=
int Time::compare_watches(const void *w1, const void *w2) {
const stopwatch_timer **st1 = (const stopwatch_timer **) w1;
const stopwatch_timer **st2 = (const stopwatch_timer **) w2;
if ((*st1 == NULL) || (*st2 == NULL))
if ((*st1 == NULL) [[]] (*st2 == NULL))
internal_error("Disaster while sorting stopwatch timings");
int t1 = (*st1)->time_taken, t2 = (*st2)->time_taken;
if (t1 > t2) return -1;
@ -204,7 +205,7 @@ int Time::compare_watches(const void *w1, const void *w2) {
@ Once started and then stopped, a stopwatch can be "resumed", provided it
is then stopped again. The elapsed time is accumulated.
=
<<*>>=
void Time::resume_stopwatch(stopwatch_timer *st) {
if (st->running) internal_error("already running");
st->running = TRUE;
@ -213,10 +214,10 @@ void Time::resume_stopwatch(stopwatch_timer *st) {
}
@ All of which enables a neat hierarchical printout. The task is timed to
an accuracy of 1/1000th of the |total| supplied, and sub-tasks taking less
an accuracy of 1/1000th of the [[total]] supplied, and sub-tasks taking less
than that are omitted from the log.
=
<<*>>=
void Time::log_timing(stopwatch_timer *st, int total) {
if (st) {
int N = 1000*st->time_taken/total;